Fork me on GitHub
#clara
<
2023-11-09
>
Joel18:11:38

How do I write the rule below? Kit has a list of pieces, I want this to trigger when all the pieces of the Kit are available.

(defrecord Kit [pieces])
(defrecord Piece [kind])

(r/defrule complete-kit-rule
  [Kit (= ?pieces pieces)]
  [?assembly <- (acc/all) :from [Piece (= ?kind kind) (some #(= ?kind %) ?pieces)]]
  =>
  (prn "complete kit"))

(-> (r/mk-session)
    (r/insert (->Kit [:a :c]))
    (r/insert (->Piece :a))
    (r/insert (->Piece :c))
    (r/fire-rules))
This fires for each piece (when the kit isn’t actually complete). Adding the following test makes the rule not fire at all:
[:test (= (count ?pieces) (count ?assembly))]

ethanc18:11:42

adding a new binding in an accumulator performs a group-by behavior

mikerod18:11:44

How is Kit obtaining more pieces over time? That part confused me in the problem statement

mikerod18:11:19

However it seems like one approach would be for the RHS to insert an intermediate fact that combined a Kit with its Pieces as done in your rule here. But it also would have a new field/flag indicating if it was “complete”. You’d have then another rule that operated on complete flagged facts to do whatever it is.

Joel18:11:00

@U0LK1552A The Pieces are getting inserted from other rules. I want the Kit to be complete when it has all it’s pieces. The kit length won’t change, but could be various size.

Joel18:11:00

What I find odd is that the rule does not include both pieces in the assembly I get this

"complete kit" [#Piece{:kind :a}]
"complete kit" [#Piece{:kind :c}]
So @U0LK1552A not sure how I could signal a complete kit.

Joel18:11:12

@U3KC48GHW I’m unclear on your point about “group-by”. Why aren’t both pieces part of the accumulator?

ethanc18:11:08

?kind is a new binding in the accumulator, the reason its logged twice in the comment above is because “kind” differs on the two pieces. https://www.clara-rules.org/docs/accumulators/ > Note that we accumulate once per distinct set of bindings. So, for example, if we have temperatures from 3 different locations, the rule get-current-temperature above will fire three times, once per distinct location.

Joel18:11:27

But doesn’t it return all the WindSpeeds for a location? Not just 1?

Joel18:11:50

I’m guessing I need to change acc/all somehow.

Joel18:11:52

Maybe get rid of the some part from the accumulator and move it to the acc/all logic.

ethanc18:11:53

> But doesn’t it return all the WindSpeeds for a location? Not just 1? yes, it will return all wind speeds for a location and execute the RHS for each location. Similarly, your rule will collect all pieces by kind and execute the RHS per kind

ethanc19:11:32

kind in this case, is not something that you appear to wish to group by though

ethanc19:11:26

unless im missing something (i usually do, please correct me)

Joel19:11:46

I want the same behavior as Windspeed, but it gives a max, I want all (the ones in the kit list).

Joel19:11:26

I think that some condition is saying, yeah, i got one for you, but i need to filter in the reducer fn.

ethanc19:11:50

[?assembly <- (acc/all) :from [Piece (some #(= kind %) ?pieces)]]
perhaps then

thanks3 1
Joel19:11:15

ill try that.

Joel19:11:14

Yes! That did it, I suspected the (= ?kind kind) was superfluous but it changed the logic, didn’t realize that.

ethanc19:11:37

that grouping behavior is a common “gotcha”

ethanc19:11:47

even for experienced users

1
ethanc19:11:06

been bit a couple times myself… burned into my memory

Joel19:11:48

OK that and adding the “count :test” above makes it work just as I intended.

❤️ 1
mikerod19:11:30

If you are using normal insert! (truth maintenance system maintained), your RHS facts would reflect the eventual final state of the working memory

mikerod19:11:34

So that’s how you know it is “complete”

mikerod19:11:16

And then a flag could be added to the result type to distinguish it from incompletes in other rules. However, for your specific case, now that I see what you’re doing more, I think the non-grouping acc/all + a :test seems to capture what you want.

Joel21:11:16

In a similar vein, is it possible to iterate over the list of pieces? Say, instead I want to create each piece (insert) if it doesn’t exist that is in the Kit. Something like:

[?each <- (acc/all) :from [Kit (= pieces ?pieces)]]
  [:not (Piece (= kind ?each))]
=>
(insert (->Piece ?each))

mikerod21:11:34

In above ?each is a collection of Kit’s

mikerod21:11:59

Actually missed the whole point

mikerod21:11:42

whhops didn’t do RHS right

mikerod21:11:29

You don’t have these Piece’s yet 😆

mikerod21:11:55

So you have to be careful to not get into a logical loop

mikerod21:11:08

I think the obvious thing, falls prey to this:

(r/defrule pair-kit-pieces
  [Kit (= pieces ?kit-pieces)]
  [?pieces <- (acc/all) :from [Piece (some #(= kind %) ?kit-pieces)]]
  =>
  (let [matched-pieces (set ?pieces)]
    (insert (map->KitPieces {:pieces ?pieces
                             :missing-pieces (filterv matched-pieces ?kit-pieces)}))))

(r/defrule add-missing-kit-pieces
  [KitPieces (= ?missing-pieces missing-pieces)]
  =>
  (r/insert-all! (for [missing ?missing-pieces]
                   (->Piece missing))))

mikerod21:11:48

One way out of that, is to use a different fact for a “generated piece” (aka automatically added) vs a regular piece:

(r/defrule pair-kit-pieces
  [Kit (= pieces ?kit-pieces)]
  [?pieces <- (acc/all) :from [Piece (some #(= kind %) ?kit-pieces)]]
  =>
  (let [matched-pieces (set ?pieces)]
    (insert (map->KitPieces {:pieces ?pieces
                             :missing-pieces (filterv matched-pieces ?kit-pieces)}))))

(r/defrule add-missing-kit-pieces
  [KitPieces (= ?missing-pieces missing-pieces)]
  =>
  (r/insert-all! (for [missing ?missing-pieces]
                   (->GeneratedPiece missing))))

(r/defrule assemble-kit
  [KitPieces (= ?pieces pieces)]
  [?matched-pieces <- (acc/all) :from [Piece (some #(= kind %) ?pieces)]]
  [?gen-pieces <- (acc/all) :from [GeneratedPiece (some #(= kind %) ?pieces)]]
  =>
  (r/insert! (->Assembled (into (vec ?matched-pieces)
                                ?gen-pieces))))

mikerod21:11:13

I think more could be done here that I’d prefer to clean up. Such as not having a Kit fact have all the pieces collected forcing us to keep using accumulators.

mikerod21:11:46

Instead it’d be nice if Kit had an :id of some sort and we could decompose it into individual KitRequiredPiece <id> <kind> facts.

✔️ 1
mikerod21:11:10

Not 100% necessary though. I’d probably at least abstract (some #(= kind %) ?pieces) fn though (although the overhead is still there)

mikerod21:11:37

Even just a Kit :id alone would be nicer

mikerod21:11:56

eg.

(r/defrule pair-kit-pieces
  [Kit (= pieces ?kit-pieces) (= ?id id)]
  [?pieces <- (acc/all) :from [Piece (some #(= kind %) ?kit-pieces)]]
  =>
  (let [matched-pieces (set ?pieces)]
    (insert (map->KitPieces {:id ?id
                             :pieces ?pieces
                             :missing-pieces (filterv matched-pieces ?kit-pieces)}))))

(r/defrule add-missing-kit-pieces
  [KitPieces (= ?missing-pieces missing-pieces)]
  =>
  (r/insert-all! (for [missing ?missing-pieces]
                   (map->GeneratedPiece {:kit-id ?id
                                         :kind missing}))))

(r/defrule assemble-kit
  [KitPieces (= ?pieces pieces) (= ?kit-id id)]
  [?matched-pieces <- (acc/all) :from [Piece (some #(= kind %) ?pieces)]]
  [?gen-pieces <- (acc/all) :from [GeneratedPiece (= ?kit-id kit-id)]]
  =>
  (r/insert! (map->Assembled {:kit-id ?kit-id
                              :pieces (into (vec ?matched-pieces)
                                            ?gen-pieces)})))
Basically, having something like an “id” helps be more “database”-like, which I think rules typically are.

Joel21:11:38

That’s the “conflict” I was struggling a bit with. If I want a rule to fire more than once it’s like I need a separate fact for it versus trying to use accumulators somehow. I could also insert these separate before fire-rules as well in my case. But I think in any case, you’ve definitely delineated the solution space. Thanks.

👍 1
mikerod22:11:19

https://gist.github.com/mrrodriguez/09df52e5f9729992c61f21fc638a20e6https://gist.github.com/mrrodriguez/6a6f8373b25d69826b3efe154c928fac Not the exact same topic but a related topics conceptually. Maybe helpful to see a similar idea template.