How to add multiple variable in CLIPS? - rules

I wanted to add and print the total quantity of three Products from facts. Also i need to perform a check too, on product 1 if the quantity is greater than 1.
(defrule sum_of_quantity
(or
(Product (productNumber 1)(quantity ?quantity0))
(Product (productNumber 2)(quantity ?quantity1))
(Product (productNumber 3)(quantity ?quantity2)))
=>
(bind ?totalQuantity (integer(+ ?quantity0 ?quantity1 ?quantity2)))
(printout t "TotalQuantity is " ?totalQuantity crlf))
I am actually new to clips and is finding it difficult to write rules

The or conditional element works by creating multiple rules, one for each permutation of all possible combinations of the conditional elements found within the or conditional elements in the conditions of the rule. Each of the generated rules use the same actions as the original rule. Thus your sum_of_quantity rule is translated to the following three rules (which share the same name):
(defrule sum_of_quantity
(Product (productNumber 1)(quantity ?quantity0))
=>
(bind ?totalQuantity (integer(+ ?quantity0 ?quantity1 ?quantity2)))
(printout t "TotalQuantity is " ?totalQuantity crlf))
(defrule sum_of_quantity
(Product (productNumber 2)(quantity ?quantity1))
=>
(bind ?totalQuantity (integer(+ ?quantity0 ?quantity1 ?quantity2)))
(printout t "TotalQuantity is " ?totalQuantity crlf))
(defrule sum_of_quantity
(Product (productNumber 3)(quantity ?quantity2)))
=>
(bind ?totalQuantity (integer(+ ?quantity0 ?quantity1 ?quantity2)))
(printout t "TotalQuantity is " ?totalQuantity crlf))
Looking at the generated rules you can see that there are variables in the actions of the rules that are not bound in the conditions of the rules. So when using the or conditional element, any variables referenced in the actions of the rule must be bound in each permutation.
Some rule-based language provide a "collect" conditional element that allows to easily compute summations in the conditions of a rule, but unfortunately CLIPS does not provide this functionality. You can however use the do-for-all-facts query function to iterate over a group of facts in the actions of a rule:
CLIPS (6.31 6/12/19)
CLIPS>
(deftemplate Product
(slot productNumber)
(slot quantity))
CLIPS>
(defrule sum_of_quantity
(exists (Product (productNumber 1 | 2 | 3)))
=>
(bind ?totalQuantity 0)
(do-for-all-facts ((?p Product))
(or (eq ?p:productNumber 1)
(eq ?p:productNumber 2)
(eq ?p:productNumber 3))
(bind ?totalQuantity (+ ?totalQuantity ?p:quantity)))
(printout t "TotalQuantity is " ?totalQuantity crlf))
CLIPS>
(assert (Product (productNumber 1) (quantity 10))
(Product (productNumber 2) (quantity 20))
(Product (productNumber 3) (quantity 30))
(Product (productNumber 4) (quantity 40)))
<Fact-4>
CLIPS> (run)
TotalQuantity is 60
CLIPS>
In this example, the exists conditional element is used in the conditions of the rule so that there is only one activation regardless of how many Product facts exists. This rule will activate if there is any Product fact with a productNumber of 1, 2, or 3. If you wanted all productNumbers to be required, you'd write the patterns like this:
(exists (Product (productNumber 1))
(Product (productNumber 2))
(Product (productNumber 3)))
The actions of the rule use the do-for-all-facts function to iterate over each Product fact and add the quantity slot to a running total.

The sum_of_quantity rule is expressing the need of at least one product between products 1, 2, 3. Nevertheless, it's trying to sum the quantity of all three of them. This is logically incorrect.
This is what the engine is trying to tell you. The Right Hand Side (RHS) is referring to a variable (quantity1) which might not be defined once you run the rule. This is because your rule will activate even if just productNumber 1 is asserted in.
Simply remove the or condition in your rule and it will work. To see the rule activating, just assert all the missing products with quantity 0.

Related

Call one rule from another in clips

I wanted to know if there is any possibility of firing a rule from the RHS of another rule.
for example,
rule 1:
(defrule printHello
=>
(printout t " Hello World" crlf ))
rule 2:
(defrule printName
=>
/* tigger rule 1 */)
Expert systems are knowledge representation engines. They use knowledge (static and dynamic) to represent their internal state. In CLIPS, dynamic knowledge is represented with facts.
You can use a custom dedicated fact for that.
(defrule printHello
(print hello)
=>
(printout t " Hello World" crlf))
(defrule printName
=>
(assert (print hello)))

CLIPS Error:Template xxx does not exist for assert

CLIPS version: 6.31
language: c++ clips C API
Why am I getting this error? How should I fix this error?
[FACTRHS1] Template be-contact-model.riskLevel does not exist for assert.
Function load-facts encountered an error
The process is as follows: Firstly, I create a CLIPS environment from the full clips rule code using the ClipsEnvLoadFromString function, I will get a normal result in this CLIPS environment using the EnvLoadFactsFromString function.Next I want to copy more than one CLIPS environment,
so I save the rules in a binary image file using the EnvBsave function and then I load a new environment from a binary file using EnvBload function, and then I use the EnvLoadFactsFromString function to load the user facts.But the EnvLoadFactsFromString function return false, and the cli stdout get the error string:
[FACTRHS1] Template be-contact-model.riskLevel does not exist for assert.
Function load-facts encountered an error
The facts parameter of the EnvLoadFactsFromString function as following:
(appId "TEST")
(be-contact-model.riskLevel "PASS")
(be-contact-model.score 0)
(channel "POST_TEXT.RlokQwRlVjUrTUlkIqOg.COMMENT")
(constantKey "constantKey")
(contact.model "contact_detector(GO)")
(contact.nicknameResult.has_contact FALSE)
(contact.nicknameResult.has_qq FALSE)
(contact.nicknameResult.has_tel FALSE)
(contact.nicknameResult.has_url FALSE)
(contact.nicknameResult.has_wechat FALSE)
(contact.riskLevel "PASS")
(contact.score 0)
(contact.textResult.baidusearch.REJECT_LEVEL 0)
(contact.textResult.has_contact FALSE)
(contact.textResult.has_qq FALSE)
(contact.textResult.has_tel FALSE)
(contact.textResult.has_url FALSE)
(contact.textResult.has_wechat FALSE)
Once you load a binary image, you can't create any new constructs. Ordered facts and patterns (those that don't have a corresponding deftemplate construct) automatically create a deftemplate. If your rules haven't already created this automatic deftemplate, it can't be created after you've already loaded a binary image:
CLIPS (6.31 6/12/19)
CLIPS> (bsave example.bin)
TRUE
CLIPS> (bload example.bin)
TRUE
CLIPS> (assert (be-contact-model.riskLevel "PASS"))
[FACTRHS1] Template be-contact-model.riskLevel does not exist for assert.
CLIPS>
If you have a rule that matches the ordered fact, you can assert this type of fact after you've loaded the binary image.
CLIPS> (clear)
CLIPS>
(defrule r1
(be-contact-model.riskLevel ?)
=>)
CLIPS> (bsave example.bin)
TRUE
CLIPS> (clear)
CLIPS> (bload example.bin)
TRUE
CLIPS> (assert (be-contact-model.riskLevel "PASS"))
<Fact-0>
CLIPS>
So the fact that you're getting an error message would indicate that you're attempting to assert a fact that none of your rules can match.
It looks like your facts are attribute/value pairs, so one thing you can do if you're asserting facts that no rule can match is to create a generic deftemplate for representing all of them:
CLIPS> (clear)
CLIPS> (deftemplate av (slot a) (slot v))
CLIPS> (assert (av (a be-contact-model.riskLevel) (v "PASS")))
<Fact-1>
CLIPS>

Replace-member is not functioning and simply says that defrule syntax is invalid

Hi i have written a display method to display some facts but i wish to replace all of the "}" with ")". But my program will not accept the replace-member method
I have used he replace-member earlier in my code but it does not seem to work in this instance. I have tried with $? and ? with no difference
(deftemplate sentence (multislot sent) (slot or-to-implies-done) (slot implies-to-or-done) (slot comm-or) )
some more code that creates the sentence
(Actual method that isn't working)
(defrule display
(sentence (sent $?check) (or-to-implies-done ~true) (implies-to-or-done ~true) (comm-or ~true))
(bind $?check (replace-member$ $?check "}" (sym-cat ")")))
=>
(printout t (implode$ (apply sym-cat $?check)) crlf))
I simply wish to replace all the "}" with "(" by using the replace-member method. Any suggestions are much appreciated error simply says syntax is wrong in defrule with no further info given.
Move the bind function call into the actions of the rule:
(defrule display
(sentence (sent $?check)
(or-to-implies-done ~true)
(implies-to-or-done ~true) (comm-or ~true))
=>
(bind $?check (replace-member$ $?check "}" (sym-cat ")")))
(printout t (implode$ (apply sym-cat $?check)) crlf))

Simple Calculator (Addition function input) in Clojure

I'm trying to write a simple calculator with addition, subtraction, etc.
My problem is with getting the user input. How do I turn the string of numerical values into a vector? And also what is a better way to write the program?
(ns scalc.core)
(defn add
[numbers]
(println (apply + numbers)))
(defn numchoose
[]
(println "What numbers?: ")
(let [numbers (read-line)] numbers))
(defn opchoose
[]
(println "What operation would you like to do?: ")
(let [operation (read-line)]
(if (= operation "add")
(do
(println "You chose to add.")
(let [numvect (numchoose)]
(add [numvect]))))))
(defn -main
[& args]
(opchoose)
(numchoose))
And this is the error:
~/clj/scalc 1/7 % lein trampoline run -m scalc.core
What operation would you like to do?:
add
You chose to add.
What numbers?:
5 7
Exception in thread "main" java.lang.ClassCastException: Cannot cast java.lang.String to java.lang.Number
at java.lang.Class.cast(Class.java:3005)
at clojure.core$cast.invoke(core.clj:318)
at clojure.core$_PLUS_.invoke(core.clj:927)
at clojure.lang.AFn.applyToHelper(AFn.java:161)
at clojure.lang.RestFn.applyTo(RestFn.java:132)
at clojure.core$apply.invoke(core.clj:601)
at scalc.core$add.invoke(core.clj:5)
at scalc.core$opchoose.invoke(core.clj:21)
at scalc.core$_main.doInvoke(core.clj:27)
at clojure.lang.RestFn.invoke(RestFn.java:397)
at clojure.lang.Var.invoke(Var.java:411)
at user$eval15.invoke(NO_SOURCE_FILE:1)
at clojure.lang.Compiler.eval(Compiler.java:6511)
at clojure.lang.Compiler.eval(Compiler.java:6501)
at clojure.lang.Compiler.eval(Compiler.java:6477)
at clojure.core$eval.invoke(core.clj:2797)
at clojure.main$eval_opt.invoke(main.clj:297)
at clojure.main$initialize.invoke(main.clj:316)
at clojure.main$null_opt.invoke(main.clj:349)
at clojure.main$main.doInvoke(main.clj:427)
at clojure.lang.RestFn.invoke(RestFn.java:421)
at clojure.lang.Var.invoke(Var.java:419)
at clojure.lang.AFn.applyToHelper(AFn.java:163)
at clojure.lang.Var.applyTo(Var.java:532)
at clojure.main.main(main.java:37)
EDIT: the solved program now looks like this:
(ns scalc.core)
(defn add [numbers]
(reduce + numbers))
(defn numchoose []
(let [nums (re-seq #"\d+" (read-line))]
(map #(Integer/parseInt %) nums)))
(defn delegate []
(println "What operation would you like to do?: ")
(let [operation (read-line)]
(when (= operation "add")
(println "You chose to add.")
(println "What numbers? ")
(add (numchoose)))))
(defn -main
[& args]
(delegate))
For getting the numbers, you can use re-seq:
(re-seq #"\d+" "123 456 789") => ("123" "456" 789")
You still only have strings rather than numbers though. You can use read-string to get the numbers (read-string is convenient, but not safe in all cases. Here we make sure there are really only numbers in these strings so it's fine).
(read-string "5") => 5
Instead of (apply + numbers) you could use reduce: (reduce + numbers), also your add function really shouldn't print anything (you should try to separate functional functions from side-effecty functions whenever possible).
This (let [numbers (read-line)] numbers) is equal to (read-line). Don't overcomplicate things!
Instead of
(if (= operation "add")
(do ... ))
you can write
(when (= operation "add")
...)
when is just a macro that's useful when you don't need the else case in your ifs (it wraps everything after the condition in a do, and evaluates to nil when the condition evaluates to false).

Why does the map key not retrieve the value that can be plainly seen in the map?

The following key :GIC-ID won't return its value "999-99-9999". I am trying to figure out why.
Here are the keys -- the first row containing column names of a .csv report. The output is formatted to prevent scrolling:
["AGY/DIV " "STS" "GIC-ID " "LAST-NAME " "FIRST-NAME "
"COVERAGE DESCRIPTION "
"PREMIUM " "RUN-DATE" "BIL MO "]
Here is the data doctored for privacy (output formatted to prevent scrolling):
["666/0010" "ACT" "999-99-9999" "MARGE " "SIMPSON "
"YE OLD PILGRIM FAMILY - INSURED "
"0000.00" "123456789" "99999enter code here"]
I get the first column containing the keys/column headers and the data created with the following including the zipping together of the column names with what will be each row of data.
(def gic-csv-inp (fetch-csv-data "billing_roster.csv"))
(def gic-csv-cols (first gic-csv-inp))
(def gic-csv-data (rest gic-csv-inp))
(def zm2 (zipmap (map #(keyword %1) gic-csv-cols) (first gic-csv-data)))
Now the following keys and data, pulled from a similar but different report, work just fine:
:Employee_SSN "999-99-9999"
That is I can extract the value of the key.
There must be something wrong with the keys, and I can certainly correct those, like removing spaces and so on, but I am not sure what is wrong.
Thanks.
Edit:
The answer to my question is to trim spaces like this:
(:require [clojure.string :as cstr])
.
.
.
(def zm2 (zipmap (map #(keyword (cstr/trim %1)) gic-csv-cols)
(first gic-csv-data)))
Trimming spaces does work
(def zm2 (zipmap (map #(keyword (re-find #"[^\s]*" %1)) gic-csv-cols) (first gic-csv-data)))
=> (zm2 :GIC-ID)
"999-99-9999"
[^\s]* being a regexp to match all non-whitespace characters

Resources