Tim Bass posted an article entitled 'Bending CEP for Rules' which was, in turn, a response to previous posts from James Taylor (of Fair Isaac) and Paul Vincent (of Tibco). It is well worth browsing the various articles and discussions. Although people come from different perspectives, I detect something of a consensus beginning to emerge in regard to the relationship between rules processing, CEP, business process automation & management, etc.
Tim's post is at:
There's a useful summary from Paul Vincent of the issues that both unite and divide rules engines and CEP products at:
Note there is a 'part 2' to this article.
Tim's post dates back to the end of July, but was 're-ignited' last week with further comments from Peter Lin. Having nothing better to do on a warm summer afternoon, I decided I would contribute, albeit in a typically geeky fashion. Future discussions will be better served if based on accurate understandings of the technologies involved. I'm a beginner in the world of CEP, but a little more advanced in regard to rule processing, and some of the statements and references on the blog site were clearly not accurate. This response is meant to be read for interest only. It is not my intention here to address the more central issues that surround the worlds of CEP and rule processing. Wiser heads than mine make better reading as far as that is concerned. I only want to show that there are a few myths about rules processing systems which do not really stand up to scrutiny.
Tim provides a number of references to back the claim that "Rule-based systems are interesting and useful, congruent with expert-systems, but also have well documented limitations ... in the classes of complex problems they can efficiently address." One of the references is to a 1987 paper entitled "On the Expressiveness of Rule-based Systems for Reasoning with Uncertainty" by David E Hecherman and Eric J Horvitz. The abstract opens with the following statement:
"We demonstrate that classes of dependencies among beliefs held with uncertainty cannot be represented in rule-based systems in a natural or efficient manner."
This is a sweeping statement, and is demonstratively untrue. However, it turns out that the abstract isn't an accurate reflection of the authors' findings. The first hint comes a little further on when we read that:
"...simple augmentations to the rule-based approach are inadequate for reasoning with uncertainty...We exhibit this inadequacy in the context of the MYCIN certainty factor model." (emphasis mine)
MYCIN was an early expert system developed during the 1970s to diagnose blood disorders. It was a goal-based (backward chaining) rules-based system that used a simple approach to handle uncertainty in truth propositions. The paper demonstrates that the approach taken by MYCIN in handling uncertainty could not be used to model a range of dependencies between uncertain assertions. The paper's conclusion provides a far more accurate statement of the authors' findings than the abstract.
"...we demonstrated that particular classes of dependencies among uncertain beliefs cannot be represented in the certainty factor model in an efficient or natural manner." (emphasis mine)
The phrase "certainty factor model" refers to the specific approach taken by MYCIN. The paper argues that this approach cannot adequately represent certain types of dependency. The inference to be drawn from this is that certainty factors have only limited applicability.
Certainty factors are just one of several approaches which expert systems and rules engines may take in handling uncertainty. A reasonably comprehensive summary of these approaches is provided by Patrick Harrison and Joseph Kovalchik in chapter 8 of 'The Handbook of Applied Expert Systems' edited by Jay Liebowitz. The chapter is entitled 'Expert Systems and Uncertainty' and describes the role of certainty factors, Bayes Theorem and Dempster-Shafer. For a more thorough treatment of the subject, you may wish to obtain a copy of the thoroughly readable 'Managing Uncertainty in Expert Systems' by Jerzy Grzymala-Busse. There are many additional references I could provide. Most academic books that address the general topic of expert and rule systems contain material explaining the various approaches commonly employed for handling uncertainty. In addition, there are countless academic papers that address the subject.
In the comments to his blog, Tim contrasts rules processing systems with various types of analytics, including Bayesian analytics. He responds to a comment from Peter Lin by stating that "Bayesian analysis, one of the most popular analytics for a wide range of problems today cannot be implemented efficiently with a rules engine." There is no academic basis for this statement that I know of. Tim will probably be aware of the common use of polytree-based algorithms when computing Bayesian probabilities. These algorithms have the great advantage over alternatives that they are not NP-Hard, and can therefore be implemented efficiently. For the purpose of this discussion, it is important to note that they lend themselves very naturally to implementation within systems that support backward chaining inference - a feature of several rules engines!
Update: September 2008 - I was researching the whole topic of handling uncertainty in rules engines a little further, and discovered that two direct 'successors' to MYCIN both employed Bayes Theorem to handle uncertainty. These were early expert systems, dating back to the 1970s (Duda's 'Prospector' engine) and the 1980s (Michie/Reiter's AL/X - Advice Language /X). See http://www3.interscience.wiley.com/cgi-bin/fulltext/120033126/PDFSTART for a description of how AL/X handled uncertainty. Interestingly, on page 200, there is very nice diagram of an AL/X inference network which is a classic polytree, and which AL/X processed using backward chaining.
Grzymala-Busse's book, mentioned above, explains the use of Bayes Theorem in Prospector in some detail, including some limitations in the way it was implemented. He also describes the use of Dempster-Shafer in Quinlan's 'Inferno' expert system. If I come across other rules engines that have implememented Bayes Theorem or Dempster-Shafer, I will add them to the list.
Bayes theorem is a natural choice for handling the kind of uncertainty problems discussed in the Hecherman/Horvitz paper. The theorem is really quite straightforward, and does not pose much of a challenge for rules developers. I will content myself with providing a very simple example that does not use backward chaining or polytrees, but which does at least demonstrate that uncertainty handling can be done straightforwardly and naturally in a Rules Engine. I make no claims for efficiency and scalability in regard to the approach I take here.
I have taken the example problem described in section IV (A) of the Hecherman/Horvitz paper, and created a rule set to solve it. The rule set uses Bayes theorem. There is a long history of using jelly beans and jars to demonstrate Bayesian analysis! It proved very simple to create the logic. I didn't need to use any 'unnatural' techniques, or to rely on any proprietary features of a particular engine.
I decided to write the rule set using Jess. There were a couple of reasons for this. First, Jess is well-known and uses the same syntax as CLIPS. The syntax is familiar to a wide range of rules developers. The syntax is very similar to that used in OPS5, which was a very well-know rule processing system back in 1987. Second, Jess, CLIPS, OPS5 and a number of other engines use rule languages in which variable substitution is defined explicitly as part of pattern matching. Reading David Luckham's book on CEP, and especially chapter 8, it is easy to see the essential similarity between Rapide-EPL and the kind of languages supported by rules systems. Rapide-EPL is strongly typed and has a number of 'event-orientated' features, but it employs the same essential approach to pattern matching as is used in rules engines. The Jess language demonstrates this.
I purposely avoided using any non-mainstream features with the possible exception of using ‘if' function in the action lists of certain rules; however, these are just conveniences that slightly reduced the number of rules I had to define. The rule set can be re-written easily to target any of the common Rete engines available commercially or through open source. The logic will generally be (near) identical. Ironically, the rule engine I most commonly use (Microsoft BRE) would present a small challenge because it does not support the Negation-As-Failure functionality that is a feature of virtually all other Rete engines. However, it would be quite straightforward to ‘work around’ this unfortunate lack of expressivity.
As far as efficiency is concerned, I can see no good reason for assuming that the rule set is inherently inefficient. Of course, it is is focused on a single, simple task. I have noted above that there are efficient general algorithms for calculating Bayesian probability, and also that these algorithms fit well with backward-chaining rule processing approaches. The choice of appropriate algorithm is all important, and there is no reason to think that rules engines exhibit some inherant efficiency when used to implement good algorithms. Indeed, this would be a really odd claim. Many rule languages (like Jess/CLIPS) are highly declarative and broadly functional (admittedly with a great deal of functional 'impurity'). On what possible basis could one claim that the functional programming model is an inefficient basis for implementing Bayes Theorem? Another consideration is the ability of Rete engines to store partial matches during forward chaining. In this case, it means that the engine is able to minimise the work required in calculating the changes in probability each time an event is processed. I haven't undertaken any performance testing, but I would expect the simple approach I have taken to be really rather efficient, at least within reasonable limits.
In another reference provided in Tim's post, we read "Rule-based systems are fairly simplistic, consisting of little more than a set of if-then statements..." I understand why the author might think this, but this statement betrays a fundamental misunderstanding and ignorance of the 'state of the art'. In reality, many rules engines (certainly those languages used in conjunction with Rete engines) employ expressive languages with deep roots in functional, logical and set-based programming. The 'if-then' statements are far removed from their namesakes in imperative, procedural languages, having more in common with features of languages like SQL, or functional languages like Scheme. Indeed, I personally don't approve of using the keywords 'if-then' to define the central rule construct, although the engine I use most often has this syntax!
Anyway, for what it is worth, here is a rule set written for a well-known and mainstream Rete engine that uses only natural approaches to handle dependencies between uncertain beliefs using Bayes theorem. Please note a couple of points. First, I am not a Jess expert. I'm sure it is possible to improve on my code! Secondly, I have represented 'bean drawing' events using a 'drawEvent' deftemplate (a 'fact' type). It is entirely possible to assert events to most engines as and when these events occur (event stream processing). However, to simplify execution of the example, I have resorted to 'batching' a sequence of events in the input data and writing the rule set to process these in a sequential fashion. I have not written any logic to detect breaks in the sequence (which is defined using the 'id' slot of drawEvents), although the code will detect scenarios where an event is illegal (e.g., an event that says we have drawn a black bean from a jar that contains only white beans). In this case, the test will halt.
Here is the rule set:;;
;; --------------------------------------------------------
;; Author: Charles Young, Solidsoft - August 2007
;;
;; Jess/CLIPS rule set to demonstrate the use of Bayes'
;; theorem in handling dependencies between uncertain
;; beliefs. This rule set uses a very simple scenario in
;; which a number of jars hold combinations of black and
;; white jelly beans. As the test progresses, jars are
;; randomly selected, and beans are removed. The rule
;; set processes a sequence of these bean draw events.
;; Before each draw, the system uses Bayes' theorem to
;; determine the probability of a black or a white bean
;; being drawn from specific jars. The program answers
;; questions in the form of "if the next bean drawn is
;; black, what is the probability that it will be drawn
;; from jar 3"?
;;
;; This is example code only. Please feel free to use
;; as you wish.
;;
;; THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF
;; ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
;; TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
;; PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
;; SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
;; CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
;; OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
;; IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
;; DEALINGS IN THE SOFTWARE.
;; --------------------------------------------------------
;; --------------------------------------------------------
;; Define the fact types (templates)
;; --------------------------------------------------------
(deftemplate jar (slot id)
(slot marginal)
(slot blackBeanCount)
(slot whiteBeanCount)
(slot blackBeanMarginal)
(slot whiteBeanMarginal)
(slot blackBeanPosterior)
(slot whiteBeanPosterior)
(slot counted)
(slot marginalSet)
(slot posteriorsSet)
(slot onDrawBlack)
(slot onDrawWhite)
(slot blackBeanProbabilitySet)
(slot whiteBeanProbabilitySet)
)
(deftemplate bean (slot id)
(slot colour)
(slot jarId)
(slot counted)
(slot jarCounted)
)
(deftemplate drawEvent (slot id)
(slot jarId)
(slot colour)
)
(deftemplate blackBeans (slot count)
(slot marginal)
)
(deftemplate whiteBeans (slot count)
(slot marginal)
)
(deftemplate context (slot jarCount)
(slot state)
(slot lastEventId)
(slot allBeansDrawn)
)
;; --------------------------------------------------------
;; RULE SET
;; --------------------------------------------------------
;; --------------------------------------------------------
;; Assert and initialise the main context.
;; --------------------------------------------------------
(defrule assert_context
(not (context))
=>
(assert (context (jarCount 0)
(state created)
(lastEventId 0)))
(assert (blackBeans (count 0) (marginal 0.0)))
(assert (whiteBeans (count 0) (marginal 0.0)))
)
(defrule set_jar_count
?ctxt <- (context (jarCount ?jc) (state created))
?jar <- (jar (counted ~yes))
=>
(modify ?jar (counted yes))
(modify ?ctxt (jarCount (+ ?jc 1)))
)
(defrule set_state_start (declare (salience -10))
?ctxt <- (context (state created))
=>
(modify ?ctxt (state start))
)
;; --------------------------------------------------------
;; Assert and initialise blackBean and whiteBean contexts.
;; --------------------------------------------------------
(defrule set_blackbeans_count
(context (state start))
?bean <- (bean (colour black) (counted ~yes))
?blackBeans <- (blackBeans (count ?bbc))
=>
(modify ?bean (counted yes))
(modify ?blackBeans (count (+ ?bbc 1)))
)
(defrule set_whitebeans_count
(context (state start))
?bean <- (bean (colour white) (counted ~yes))
?whiteBeans <- (whiteBeans (count ?wbc))
=>
(modify ?bean (counted yes))
(modify ?whiteBeans (count (+ ?wbc 1)))
)
;; --------------------------------------------------------
;; Initialise each jar and set the beans counts in each
;; jar to 0. This rule may not be required in other
;; engines, if they use some for of auto-initialisation
;; and/or strong typing.
;; --------------------------------------------------------
(defrule initialise_jar
(context (state start))
?jar <- (jar (id ?id)
(blackBeanCount ~0.0))
=>
(modify ?jar (marginal 0.0)
(blackBeanCount 0.0)
(whiteBeanCount 0.0)
(blackBeanMarginal 0.0)
(whiteBeanMarginal 0.0)
(blackBeanPosterior 0.0)
(whiteBeanPosterior 0.0)
(onDrawBlack 0.0)
(onDrawWhite 0.0)
(counted no)
(marginalSet no)
(posteriorsSet no)
(blackBeanProbabilitySet no)
(whiteBeanProbabilitySet no))
)
;; --------------------------------------------------------
;; Calculate the marginal probability of selecting each
;; jar.
;; --------------------------------------------------------
(defrule initialise_hyp_marginals
(context (jarCount ?jc&:(> ?jc 0)) (state start))
?jar <- (jar (marginalSet ~yes))
=>
(modify ?jar (marginal (/ 1 ?jc)) (marginalSet yes))
)
(defrule set_state_initialised (declare (salience -10))
?ctxt <- (context (state start))
=>
(modify ?ctxt (state initialised))
)
;; --------------------------------------------------------
;; Initialise the white and black bean count for each jar.
;; --------------------------------------------------------
(defrule initialise_jar_blackbean_count
(context (state initialised))
?jar <- (jar (id ?id) (blackBeanCount ?bbc))
?bean <- (bean (jarId ?id) (colour black) (jarCounted ~yes))
=>
(modify ?jar (blackBeanCount (+ ?bbc 1)))
(modify ?bean (jarCounted yes))
)
(defrule initialise_jar_whitebean_count
(context (state initialised) )
?jar <- (jar (id ?id) (whiteBeanCount ?wbc))
?bean <- (bean (jarId ?id) (colour white) (jarCounted ~yes))
=>
(modify ?jar (whiteBeanCount (+ ?wbc 1)))
(modify ?bean (jarCounted yes))
)
(defrule set_state_jar_bean_counts (declare (salience -10))
?ctxt <- (context (state initialised))
=>
(modify ?ctxt (state jar_bean_counts_set))
)
;; --------------------------------------------------------
;; Calculate the marginal probabilities for the evidence
;; (probability of selecting a bean of a given colour) and
;; set the context state to 'marginals initialised' This
;; is calculated by summing, for each jar, the product of
;; the probability of selecting a particular coloured
;; bean from that jar and the probability of selecting the
;; given jar. The summation is done in the next section.
;; --------------------------------------------------------
(defrule initialise_ev_jar_bean_marginals
(context (state jar_bean_counts_set))
?jar <- (jar (marginal ?m)
(blackBeanCount ?bbc)
(whiteBeanCount ?wbc)
(blackBeanMarginal 0.0)
(whiteBeanMarginal 0.0))
=>
(if (> (+ ?bbc ?wbc) 0)
then
(modify ?jar (blackBeanMarginal (* ?m (/ ?bbc (+ ?bbc ?wbc))))
(whiteBeanMarginal (* ?m (/ ?wbc (+ ?bbc ?wbc)))))
)
)
(defrule reset_bean_context_black
(context (state jar_bean_counts_set))
?blackBeans <- (blackBeans (marginal ?bbm&:(> ?bbm 0.0)))
=>
(modify ?blackBeans (marginal 0.0))
)
(defrule reset_bean_context_white
(context (state jar_bean_counts_set))
?whiteBeans <- (whiteBeans (marginal ?wbm&:(> ?wbm 0.0)))
=>
(modify ?whiteBeans (marginal 0.0))
)
(defrule set_state_jar_bean_marginals (declare (salience -10))
?ctxt <- (context (state jar_bean_counts_set))
=>
(modify ?ctxt (state jar_bean_marginals_set))
)
;; --------------------------------------------------------
;; Sum the partial marginals calculated in the section
;; above.
;; --------------------------------------------------------
(defrule sum_ev_blackbean_marginals
(context (state jar_bean_marginals_set))
?jar <- (jar (blackBeanMarginal ?m&:(neq ?m nil)&:(> ?m 0.0)))
?blackBeans <- (blackBeans (marginal ?bbm))
=>
(modify ?blackBeans (marginal (+ ?bbm ?m)))
(modify ?jar (blackBeanMarginal 0.0))
)
(defrule sum_ev_whitebean_marginals
(context (state jar_bean_marginals_set))
?jar <- (jar (whiteBeanMarginal ?m&:(neq ?m nil)&:(> ?m 0.0)))
?whiteBeans <- (whiteBeans (marginal ?wbm))
=>
(modify ?whiteBeans (marginal (+ ?wbm ?m)))
(modify ?jar (whiteBeanMarginal 0.0))
)
(defrule set_state_marginals_initialised (declare (salience -10))
?ctxt <- (context (state jar_bean_marginals_set))
=>
(modify ?ctxt (state evidence_marginals_calculated))
)
;; --------------------------------------------------------
;; Set the posterior probability (likelihood) on each jar.
;; --------------------------------------------------------
(defrule initialise_posterior
(context (state evidence_marginals_calculated))
?jar <- (jar (blackBeanCount ?blackBeanCount&~nil)
(whiteBeanCount ?whiteBeanCount&~nil)
(posteriorsSet ~yes))
=>
(if (> (+ ?blackBeanCount ?whiteBeanCount) 0.0)
then (modify ?jar (blackBeanPosterior (/ ?blackBeanCount (+ ?blackBeanCount ?whiteBeanCount)))
(whiteBeanPosterior (/ ?whiteBeanCount (+ ?blackBeanCount ?whiteBeanCount)))
(posteriorsSet yes))
)
)
(defrule initialise_posterior_for_zeros
(context (state evidence_marginals_calculated))
?jar <- (jar (blackBeanCount ?blackBeanCount&~nil&0.0)
(whiteBeanCount ?whiteBeanCount&~nil&0.0)
(posteriorsSet ~yes))
=>
(modify ?jar (blackBeanPosterior 0.0)
(whiteBeanPosterior 0.0)
(posteriorsSet yes))
)
(defrule reset_jar_black_bean_probabilities
(context (state evidence_marginals_calculated))
?jar <- (jar (blackBeanProbabilitySet yes))
=>
(modify ?jar (blackBeanProbabilitySet no))
)
(defrule reset_jar_white_bean_probabilities
(context (state evidence_marginals_calculated))
?jar <- (jar (whiteBeanProbabilitySet yes))
=>
(modify ?jar (whiteBeanProbabilitySet no))
)
(defrule set_state_ready_to_calculate_pobabilities (declare (salience -10))
?ctxt <- (context (state evidence_marginals_calculated))
=>
(modify ?ctxt (state ready_to_calculate_probabilities))
)
;; --------------------------------------------------------
;; Calculate the probability of the hypotheses using Bayes'
;; theorem.
;; --------------------------------------------------------
(defrule calc_baysian_probability_black_bean
(context (state ready_to_calculate_probabilities))
?jar <- (jar (marginal ?jarMarginal)
(blackBeanPosterior ?blackBeanPosterior)
(blackBeanProbabilitySet ~yes))
(blackBeans (marginal ?bbMarginal&:(> ?bbMarginal 0.0)))
=>
(modify ?jar (onDrawBlack (/ (* ?blackBeanPosterior ?jarMarginal) ?bbMarginal))
(blackBeanProbabilitySet yes))
)
(defrule calc_baysian_probability_black_bean_marginal_zero
(context (state ready_to_calculate_probabilities))
?jar <- (jar (marginal ?jarMarginal)
(blackBeanPosterior ?blackBeanPosterior)
(blackBeanProbabilitySet ~yes))
(blackBeans (marginal ?bbMarginal&0.0))
=>
(modify ?jar (onDrawBlack 0.0) (blackBeanProbabilitySet yes))
)
(defrule calc_baysian_probability_white_bean
(context (state ready_to_calculate_probabilities))
?jar <- (jar (marginal ?jarMarginal)
(whiteBeanPosterior ?whiteBeanPosterior)
(whiteBeanProbabilitySet ~yes))
(whiteBeans (marginal ?wbMarginal&:(> ?wbMarginal 0.0)))
=>
(modify ?jar (onDrawWhite (/ (* ?whiteBeanPosterior ?jarMarginal) ?wbMarginal))
(whiteBeanProbabilitySet yes))
)
(defrule calc_baysian_probability_white_bean_marginal_zero
(context (state ready_to_calculate_probabilities))
?jar <- (jar (marginal ?jarMarginal)
(whiteBeanPosterior ?whiteBeanPosterior)
(whiteBeanProbabilitySet ~yes))
(whiteBeans (marginal ?wbMarginal&0.0))
=>
(modify ?jar (onDrawWhite 0.0) (whiteBeanProbabilitySet yes))
)
(defrule set_state_probabilities_calculated (declare (salience -10))
?ctxt <- (context (state ready_to_calculate_probabilities))
=>
(modify ?ctxt (state probabilities_calculated))
)
;; --------------------------------------------------------
;; Use StdIO to output the probabilities of drawing black
;; and white beans from each jar for next draw event.
;; --------------------------------------------------------
(defrule output_probabilities
(context (state probabilities_calculated))
(jar (id ?id)
(onDrawBlack ?onDrawBlack)
(onDrawWhite ?onDrawWhite))
=>
(printout t "The probabilities of the next bean being drawn from jar " ?id " are:" crlf)
(printout t " if next bean is black, " ?onDrawBlack crlf)
(printout t " if next bean is white, " ?onDrawWhite crlf)
)
(defrule halt_Processing (declare (salience -1))
(context (state probabilities_calculated)
(allBeansDrawn yes))
=>
(halt)
)
(defrule set_state_ready_to_draw_beans (declare (salience -10))
?ctxt <- (context (state probabilities_calculated))
=>
(modify ?ctxt (state ready_to_draw_bean))
)
;; --------------------------------------------------------
;; Draw the next bean from the jars and amend the bean
;; counts. If an invalid draw event is detected, show
;; a message and halt the engine.
;; --------------------------------------------------------
(defrule draw_next_black_bean
(context (state ready_to_draw_bean)
(lastEventId ?leid)
(allBeansDrawn ~yes))
?blackBeans <- (blackBeans (count ?bbc))
?drawEvent <- (drawEvent (id ?deid&:(= ?deid (+ ?leid 1)))
(jarId ?id)
(colour black))
?bean <- (bean (jarId ?id)
(colour black))
?jar <- (jar (id ?id)
(blackBeanCount ?jbbc))
=>
(modify ?blackBeans (count (- ?bbc 1)) (marginal 0.0))
(modify ?jar (blackBeanCount (- ?jbbc 1)) (posteriorsSet no))
(retract ?bean)
(retract ?drawEvent)
(printout t crlf "Drawing a black bean from jar " ?id crlf crlf)
)
(defrule draw_next_white_bean
(context (state ready_to_draw_bean)
(lastEventId ?leid)
(allBeansDrawn ~yes))
?whiteBeans <- (whiteBeans (count ?wbc))
?drawEvent <- (drawEvent (id ?deid&:(= ?deid (+ ?leid 1)))
(jarId ?id)
(colour white))
?bean <- (bean (jarId ?id)
(colour white))
?jar <- (jar (id ?id)
(whiteBeanCount ?jwbc))
=>
(modify ?whiteBeans (count (- ?wbc 1)) (marginal 0.0))
(modify ?jar (whiteBeanCount (- ?jwbc 1)) (posteriorsSet no))
(retract ?bean)
(retract ?drawEvent)
(printout t crlf "Drawing a white bean from jar " ?id crlf crlf)
)
(defrule is_last_bean
?ctx <- (context (state ready_to_draw_bean)
(allBeansDrawn ~yes))
(whiteBeans (count 0))
(blackBeans (count 0))
=>
(modify ?ctx (allBeansDrawn yes))
)
(defrule report_bad_event
(context (state ready_to_draw_bean)
(lastEventId ?leid))
(drawEvent (id ?deid&:(= ?deid (+ ?leid 1)))
(jarId ?id)
(colour ?colour))
(not (bean (jarId ?id)
(colour ?colour)) )
=>
(printout t crlf "EVENT ERROR: An invalid draw event has been detected." crlf)
(printout t " Event id " ?deid " cannot draw a " ?colour " bean" crlf)
(printout t " from jar " ?id "." crlf)
(printout t crlf " Test has been aborted." crlf)
(halt)
)
(defrule set_state_jar_bean_counts_on_draw (declare (salience -10))
?ctxt <- (context (state ready_to_draw_bean) (lastEventId ?leid))
=>
(modify ?ctxt (state jar_bean_counts_set) (lastEventId (+ ?leid 1)))
)
;; --------------------------------------------------------
;; END OF RULE SET
;; --------------------------------------------------------
Here is a very simply data set showing how facts can be represented and asserted:
;;--------------------------------------------------------
;; The following is a sample set of facts which can be
;; asserted to the engine for this rule set. For Jess,
;; please ensure that each jar, bean and event has a
;; unique numeric id value. Events will be processed by
;; the rule set in the order of their id values. It is
;; preferable to ensure that there are no breaks in the
;; sequence of event id values, although the test will
;; still run (with redundant output) if there are.
;; The order in which facts (including events) are
;; asserted to the engine is not important in this test.
;;--------------------------------------------------------
(jar (id 1))
(jar (id 2))
(jar (id 3))
(bean (id 1) (colour black) (jarId 1))
(bean (id 2) (colour white) (jarId 1))
(bean (id 3) (colour white) (jarId 2))
(bean (id 4) (colour white) (jarId 2))
(bean (id 5) (colour black) (jarId 3))
(bean (id 6) (colour black) (jarId 3))
;;--------------------------------------------------------
;; The events
;;--------------------------------------------------------
(drawEvent (id 1) (jarId 3) (colour black))
(drawEvent (id 2) (jarId 1) (colour black))
(drawEvent (id 3) (jarId 2) (colour white))
(drawEvent (id 4) (jarId 1) (colour white))
(drawEvent (id 5) (jarId 3) (colour black))
(drawEvent (id 6) (jarId 2) (colour white))
If you don't have Jess, or don't want to run the rule set, here is the output for the sample rule set:
The probabilities of the next bean being drawn from jar 3 are:
if next bean is black, 0.6666666666666666
if next bean is white, 0.0
The probabilities of the next bean being drawn from jar 1 are:
if next bean is black, 0.3333333333333333
if next bean is white, 0.3333333333333333
The probabilities of the next bean being drawn from jar 2 are:
if next bean is black, 0.0
if next bean is white, 0.6666666666666666
Drawing a black bean from jar 3
The probabilities of the next bean being drawn from jar 3 are:
if next bean is black, 0.6666666666666666
if next bean is white, 0.0
The probabilities of the next bean being drawn from jar 1 are:
if next bean is black, 0.3333333333333333
if next bean is white, 0.3333333333333333
The probabilities of the next bean being drawn from jar 2 are:
if next bean is black, 0.0
if next bean is white, 0.6666666666666666
Drawing a black bean from jar 1
The probabilities of the next bean being drawn from jar 3 are:
if next bean is black, 1.0
if next bean is white, 0.0
The probabilities of the next bean being drawn from jar 1 are:
if next bean is black, 0.0
if next bean is white, 0.5
The probabilities of the next bean being drawn from jar 2 are:
if next bean is black, 0.0
if next bean is white, 0.5
Drawing a white bean from jar 2
The probabilities of the next bean being drawn from jar 2 are:
if next bean is black, 0.0
if next bean is white, 0.5
The probabilities of the next bean being drawn from jar 1 are:
if next bean is black, 0.0
if next bean is white, 0.5
The probabilities of the next bean being drawn from jar 3 are:
if next bean is black, 1.0
if next bean is white, 0.0
Drawing a white bean from jar 1
The probabilities of the next bean being drawn from jar 2 are:
if next bean is black, 0.0
if next bean is white, 1.0
The probabilities of the next bean being drawn from jar 3 are:
if next bean is black, 1.0
if next bean is white, 0.0
The probabilities of the next bean being drawn from jar 1 are:
if next bean is black, 0.0
if next bean is white, 0.0
Drawing a black bean from jar 3
The probabilities of the next bean being drawn from jar 2 are:
if next bean is black, 0.0
if next bean is white, 1.0
The probabilities of the next bean being drawn from jar 3 are:
if next bean is black, 0.0
if next bean is white, 0.0
The probabilities of the next bean being drawn from jar 1 are:
if next bean is black, 0.0
if next bean is white, 0.0
Drawing a white bean from jar 2
The probabilities of the next bean being drawn from jar 2 are:
if next bean is black, 0.0
if next bean is white, 0.0
The probabilities of the next bean being drawn from jar 3 are:
if next bean is black, 0.0
if next bean is white, 0.0
The probabilities of the next bean being drawn from jar 1 are:
if next bean is black, 0.0
if next bean is white, 0.0
done: 229 rules fired
Elapsed time: 0.0
Before anyone asks, the fact that the probabilities calculated before the first and second draws are identical is correct. This is because the first draw is a black bean from jar 3, which contains two black beans. After the first draw, the marginal probability of drawing a black bean remains exactly the same, as do the other terms in Bayes theorem. Only jar 3 has been changed, and the probability of drawing a black bean if jar 3 is chosen remains 100%. Hence the normalising constant (P(B)) does not change. A similar situation occurs on the third and fourth draws.
Note that, because the rules engine is a set-based system, the output is not naturally ordered by jar id.