I was wondering if you can point me in the right direction. I would like to write a rules-based system to audit a set of machines -- for example
Check configuration parameters to see if they were set correctly
Check state of machines to see if they are at acceptable ranges
The thing that I am struggling with is coming up with a grammar for the auditing rules so that I don't have to code defrules. I created some simple auditing rules but I am struggling with coming up with a generic form.
Here is my current attempt.
First, I am currently representing configuration parameters and running state with a single template (for a lack of imagination, I called it audit-fact) so that my auditing language can work for both configuration and state. I use the domain slot to specify configuration vs. state.
(deftemplate audit-fact
(slot domain (allowed-values config state)) ;; config vs. state
(slot machine) ;; machine to audit
(slot name) ;; parameter or state to check/audit
(slot value) ;; parameter value
(slot already-checked (default FALSE))
)
Next, I wrote a program to read in and assert the machines configuration and state as audit-facts. The following are sample configuration and state audit-facts
(assert (audit-fact
(domain config)
(machine drl-a-15_0)
(name os-version) (value 5.5))
)
(assert (audit-fact
(domain state)
(machine drl-a-15_0)
(name rpm) (value 5023))
)
Here is my current attempt at an auditing grammar/language ---
(deftemplate rule
(slot domain)
(slot name)
(slot constraint (default ?NONE) (allowed-values should-remain-at-default should-equal should-be-between ignore-if-ends-with))
(multislot values)
(multislot reasons)
(multislot references)
(slot criticality (allowed-values critical info suggestion warning))
(slot already-checked (default FALSE))
)
The following rule is used to check if a state or parameter is within a certain range
(assert (rule
(domain state)
(name rpm)
(constraint should-be-between)
(values 5000 5500)
(criticality critical)
(reasons "Low values could cause engine to stall. Prolonged high value could cause engine to heat up") )
)
Next, I used the following rule to check that a state or parameter is equal to a specific value
(assert (rule
(domain config)
(name os-version)
(constraint should-equal)
(values 5.5)
(criticality critical)
(reasons "OS version must be at 5.5 – no other levels are currently certified.") )
)
The following rules implements the equal and between checks
(defrule rule-should-eq
(rule (domain ?d) (name ?n) (constraint should-equal) (values ?cv) (reasons ?r))
?p <- (audit-fact (domain ?d) (name ?n) (value ?v) (already-checked FALSE))
=>
(if (eq ?v ?cv)
then
(printout t "checking " ?d ": " ?n ". Passed " crlf)
else
(printout t "checking " ?d “: “ ?n " should be set to " ?cv ". " ?r ". Warning" crlf)
)
(modify ?p (already-checked TRUE))
)
(defrule rule-should-be-between
(rule (domain ?d) (name ?n) (constraint should-be-between) (values ?cv-low ?cv-high) (reasons ?r))
?p <- (audit-fact (domain ?d) (name ?n) (value ?v) (already-checked FALSE))
=>
(if (and (>= ?v ?cv-low) (<= ?v ?cv-high))
then
(printout t "checking " ?n ". Passed " crlf)
else
(printout t "checking " ?n " should be between " ?cv-low " and " ?cv-high ". " ?r ". Warning" crlf)
)
(modify ?p (already-checked TRUE))
)
Using the above, I can implement simple checks -- e.g., must-be-set-to, must-be-between, must-not-equal, must-remain-at-default-value, must-be-higher-than, must-be-lower-than, etc.
But I can't think of an easy way to make the grammar handle multiple conditions with ands or ors, etc.
Question ---
is there a paper that describes a design pattern for auditing rules like above. I am currently studying the wine.clp in examples (CLIPS).
should I give up and just use the simple grammar for simple auditing rules and just use defrules for anything complicated -- e.g., if config-A is set to X, then check five other configs to make sure they are also set correctly, and then assert a check of the state. Once asserted, then check the state to be within a certain range.
Thanks in advance.
Bernie
Here's a method to evaluate arbitrary expressions from a generic rule:
CLIPS>
(deffunction str-replace (?str ?rpl ?fnd)
(if (eq ?fnd "") then (return ?str))
(bind ?rv "")
(bind ?i (str-index ?fnd ?str))
(while ?i
(bind ?rv (str-cat ?rv (sub-string 1 (- ?i 1) ?str) ?rpl))
(bind ?str (sub-string (+ ?i (str-length ?fnd)) (str-length ?str) ?str))
(bind ?i (str-index ?fnd ?str)))
(bind ?rv (str-cat ?rv ?str)))
CLIPS>
(deftemplate audit-fact
(slot domain)
(slot machine)
(slot name)
(slot value))
CLIPS>
(deftemplate rule
(slot domain)
(slot name)
(slot constraint)
(multislot reasons)
(multislot references)
(slot criticality))
CLIPS>
(deffacts initial
(audit-fact
(domain config)
(machine drl-a-15_0)
(name os-version)
(value 5.4))
(audit-fact
(domain state)
(machine drl-a-15_0)
(name rpm)
(value 5023))
(rule
(domain state)
(name rpm)
(constraint "(and (>= ?v 5000) (<= ?v 5500))")
(criticality critical)
(reasons "Low values could cause engine to stall. Prolonged high value could cause engine to heat up."))
(rule
(domain config)
(name os-version)
(constraint "(eq ?v 5.5)")
(criticality critical)
(reasons "OS version must be at 5.5 – no other levels are currently certified.")))
CLIPS>
(defrule rule-check
(rule (domain ?d) (name ?n) (constraint ?constraint) (reasons ?r))
?p <- (audit-fact (domain ?d) (name ?n) (value ?v))
=>
(bind ?constraint (str-replace ?constraint ?v "?v"))
(if (eval ?constraint)
then
(printout t "checking " ?d ": " ?n " Passed" crlf)
else
(printout t "checking " ?d ": " ?n " Warning : " ?r crlf)))
CLIPS> (reset)
CLIPS> (run)
checking config: os-version Warning : OS version must be at 5.5 – no other levels are currently certified.
checking state: rpm Passed
CLIPS>
1) If statements on the RHS are a code smell: don't!
2) You don't need the already-checked flag since you aren't modifying these facts.
Rewriting the check against one machine attribute:
?p <- (Configuration (machine ?m)(param ?p)(value ?v))
(ConfigCheckRange (param ?p){?v < loBound || $v > hiBound})
(You may need another variants, e.g., ConfigCheckEnum is permitted values aren't in one interval.)
Checking parameter value combinations is more difficult since you have chosen to represent every parameter value by a separate fact. You'll have to
?p1 <- (Configuration (machine ?m)(param ?p1)(value ?v1))
?p2 <- (Configuration (machine ?m)(param ?p2)(value ?v2)
{?p2 != ?p1))
(ConfigCheckCombi (param1 ?p1)(param2 ?p2)
{lowBound1 <= ?v1 && ?v2 <= lowBound2}
{?v2 < loBound2 || $v2 > hiBound2})
The right hand sides should merely register a violation in a (new) fact, let's call it Findings. One Findings contains the id to the machine and a list of (violated) ConfigCheck facts.
At the end (with a low-salience rule) you locate Findings facts and call the report method on it. (It may be more convenient to use some proper Java beans as facts.)
Related
I need to create a rule that will check if the list of facts I have entered matches the facts already given. Then the fact / facts corresponding to at least one of the entered ones are displayed.
this is what I have:
(deftemplate rule (multislot problem) (slot cause))
(deffacts info
(rule (problem one) (cause one1))
(rule (problem two) (cause two2))
(rule (problem three) (cause three3))
(defrule reading-input
=>
(printout t "Enter your problems: " )
(assert (problem (read))))
(defrule checking-input
(problem $?problem)
(rule (problem $?problem1) (cause ?cause1))
(test (eq ?problem ?problem1))
=>
(printout t "cause: " ?cause1 crlf))
how this should work:
CLIPS> Enter your problems: one two
CLIPS> cause: one1
cause: two2
Using the read function will retrieve just one value from your input. You need to use the readline function in conjunction with the explode$ function:
CLIPS (6.4 2/9/21)
CLIPS> (assert (problem (read)))
one two
<Fact-1>
CLIPS> (assert (problem (readline)))
one two
<Fact-2>
CLIPS> (assert (problem (explode$ (readline))))
one two
<Fact-3>
CLIPS> (facts)
f-1 (problem one)
f-2 (problem "one two")
f-3 (problem one two)
For a total of 3 facts.
CLIPS>
You can then use multifield wildcards to isolate individual problems within your rule:
CLIPS> (clear)
CLIPS>
(deftemplate rule
(multislot problem)
(slot cause))
CLIPS>
(deffacts info
(rule (problem one) (cause one1))
(rule (problem two four) (cause two2))
(rule (problem one three five) (cause three3)))
CLIPS>
(defrule reading-input
=>
(printout t "Enter your problems: " )
(assert (problem (explode$ (readline)))))
CLIPS>
(defrule checking-input
(problem $? ?problem $?)
(rule (problem $? ?problem $?) (cause ?cause))
=>
(printout t "Problem: " ?problem " cause: " ?cause crlf))
CLIPS> (reset)
CLIPS> (run)
Enter your problems: one two
Problem: one cause: three3
Problem: one cause: one1
Problem: two cause: two2
CLIPS>
(deftemplate client
(slot idClient (type INTEGER))
(multislot nome)
(multislot birthdate)
(multislot registryCard)
(multislot endOfRegistryCard))
(deffunction reservation(?idfunc)
(build (str-cat"(defrule existsClient
(exists(client(idClient ?idfunc)))
=>
(printout t "exists" ?idfunc crlf))"
))
(run)
)
I made this deffunction and I want to see if exists the client with that idfunc that is received as parameter. what happens is that the defrule inside doesn't process this variable any thoughts how can i resolve?
I think you should set condition (assert your idfunc) and then fire the execution of your "existsClient" rule (defined as rule and not inside your function). That would be a clearer design in my opinion.
Typically you'll directly define your rules rather than using a deffunction, but here's how you can do it both ways:
CLIPS> (clear) ; Create rule and run with deffunction
CLIPS>
(deftemplate client
(slot idClient (type INTEGER))
(multislot nome)
(multislot birthdate)
(multislot registryCard)
(multislot endOfRegistryCard))
CLIPS>
(deffunction reservation (?idfunc)
(build (str-cat
"(defrule existsClient
(exists (client (idClient " ?idfunc ")))
=>
(printout t \"exists " ?idfunc "\" crlf))"))
(assert (client (idClient ?idfunc)))
(run))
CLIPS> (reservation 2)
exists 2
CLIPS> (ppdefrule existsClient)
(defrule MAIN::existsClient
(exists
(client (idClient 2)))
=>
(printout t "exists 2" crlf))
CLIPS> (clear) ; Create rule directly
CLIPS>
(deftemplate client
(slot idClient (type INTEGER))
(multislot nome)
(multislot birthdate)
(multislot registryCard)
(multislot endOfRegistryCard))
CLIPS>
(defrule existsClient
(exists (client (idClient 2)))
=>
(printout t "exists 2" crlf))
CLIPS> (ppdefrule existsClient)
(defrule MAIN::existsClient
(exists
(client (idClient 2)))
=>
(printout t "exists 2" crlf))
CLIPS> (assert (client (idClient 2)))
<Fact-1>
CLIPS> (run)
exists 2
CLIPS>
Is it possible to cause CLIPS to re-evaluate the value of a global variable in a defrule? I have this:
(defrule encourage "Do we have a GPA higher than 3.7?"
(test (> (gpa) 3.7))
=>
(printout t "Keep up the excellent work!" crlf))
gpa is function that calculates and returns a number based on two global variables (grade points and number of credits). I read somewhere that changes to global variables do not invoke pattern matching. How do I go about forcing this? I want to print that string every time I do (run) as long as the GPA is higher than 3.7.
Don't attempt to use global variables or function calls in this manner. First, global variables are specifically designed to not trigger pattern matching. Second, it would take a bit of magic for CLIPS to know when a function call needs to be reevaluated as there are any number of changes which could cause a function to return a different value, not just changes to globals. If you want a particular piece of information to trigger pattern matching, then stick it in a fact or instance. It will make your code easier to understand if you parameterize the function calls and bind the values to be used as arguments in the conditions of the rule.
CLIPS> (clear)
CLIPS>
(deffunction gpa (?grade-points ?number-of-credits)
(/ ?grade-points ?number-of-credits))
CLIPS>
(defrule encourage "Do we have a GPA higher than 3.7?"
(grade-points ?gp)
(number-of-credits ?noc)
(test (> (gpa ?gp ?noc) 3.7))
=>
(printout t "Keep up the excellent work!" crlf))
CLIPS> (assert (grade-points 35) (number-of-credits 10))
<Fact-2>
CLIPS> (agenda)
CLIPS> (facts)
f-0 (initial-fact)
f-1 (grade-points 35)
f-2 (number-of-credits 10)
For a total of 3 facts.
CLIPS> (retract 1)
CLIPS> (assert (grade-points 38))
<Fact-3>
CLIPS> (agenda)
0 encourage: f-3,f-2
For a total of 1 activation.
CLIPS>
Alternately, you can use the fact query functions to iterate over a group of facts to dynamically compute the gpa based on facts rather than globals. Each time you modify one of these facts (add or remove), you can also assert a fact indicating the gpa needs to be rechecked to trigger the encourage rule.
CLIPS> (clear)
CLIPS>
(deftemplate grade
(slot class)
(slot grade-points)
(slot credits))
CLIPS>
(deffunction gpa ()
(bind ?grade-points 0)
(bind ?credits 0)
(do-for-all-facts ((?g grade)) TRUE
(bind ?grade-points (+ ?grade-points ?g:grade-points))
(bind ?credits (+ ?credits ?g:credits)))
(if (= ?credits 0)
then 0
else (/ ?grade-points ?credits)))
CLIPS>
(defrule encourage
?f <- (check-gpa)
=>
(retract ?f)
(if (> (gpa) 3.7)
then
(printout t "Keep up the excellent work!" crlf)))
CLIPS> (gpa)
0
CLIPS> (assert (check-gpa))
<Fact-1>
CLIPS> (run)
CLIPS> (assert (grade (class Algebra) (grade-points 12) (credits 3)))
<Fact-2>
CLIPS> (gpa)
4.0
CLIPS> (assert (check-gpa))
<Fact-3>
CLIPS> (run)
Keep up the excellent work!
CLIPS> (assert (grade (class History) (grade-points 6) (credits 2)))
<Fact-4>
CLIPS> (gpa)
3.6
CLIPS> (assert (check-gpa))
<Fact-5>
CLIPS> (run)
CLIPS> (assert (grade (class Science) (grade-points 12) (credits 3)))
<Fact-6>
CLIPS> (gpa)
3.75
CLIPS> (assert (check-gpa))
<Fact-7>
CLIPS> (run)
Keep up the excellent work!
CLIPS>
Here is my situation:
I want to run the CLIPS periodically and the system can record how many times it runs.
for example: I type in the terminal "run" many times to call the system periodically. then the system can record how many the system runs and show it on the screen. Here is my .clp file
(defglobal ?*lock* = 0)
(deftemplate counter
(slot number))
(deffacts initial_data
(counter (number 0))
)
(defrule set_counter
?f<-(counter (number ?x))
(test (= ?*lock* 0))
=>
(bind ?*lock* 1)
(printout t "plus 1" crlf)
(modify ?f (number (+ ?x 1)))
)
(defrule show_result
?f<-(counter (number ?x))
(test (= ?*lock* 1))
=>
(printout t "the counter value has been changed:" crlf)
(ppfact (fact-index ?f) t)
(bind ?*lock* 0)
)
I use a global value as a lock to control the rules and store the running times in the fact named counter. Now is my problem: Once the system finishes running for the first time. There are no rules in the agenda any more. I want the system can run again with out resetting the facts and the system can process the facts saved form the first running process. How can I refresh the agenda or rematch the rule without reset the facts?
I have find some commands like (refresh rule-name) and (refresh-agenda) but they can not solve my problem. I just type in "(refresh set_counter)" and "(refresh-agenda)" after "(run)". However, no rules are added into agenda.
I don't know if there are solution to my problem or clips can not work like this?
Another question is I try (refresh rule-name) with
(defglobal ?*lock* = 0)
(deftemplate counter
(slot number))
(deftemplate set
(slot number))
(deffacts initial_data
(counter (number 0))
)
(defrule set_counter
(initial-fact)
=>
(bind ?*lock* (+ ?*lock* 1))
(printout t ?*lock* crlf)
)
It works fine. I don't know why it doesn't work in the first example?
really thanks for any advice.
Global variables don't trigger pattern matching so you shouldn't use them in the condition of a rule unless the value is never changed. If you use a fact to represent the lock, the rules will execute for as many cycles as you specify:
(deftemplate lock
(slot value))
(deftemplate counter
(slot number))
(deffacts initial_data
(counter (number 0))
(lock (value 0))
)
(defrule set_counter
?f <- (counter (number ?x))
?l <- (lock (value 0))
=>
(modify ?l (value 1))
(printout t "plus 1" crlf)
(modify ?f (number (+ ?x 1)))
)
(defrule show_result
?f <- (counter (number ?x))
?l <- (lock (value 1))
=>
(printout t "the counter value has been changed:" crlf)
(ppfact (fact-index ?f) t)
(modify ?l (value 0))
)
CLIPS is quite new to me - I have been trying to dig deeply into this language for 2 days.
A question came to my mind, namely: how (if possible) can I create/add new rules dynamically?
I would like to do for example sth like this:
(deftemplate action
(slot prev)
(slot curr)
)
(defrule test
(action (prev ?p))
=>
(defrule test_inner
(action (curr ?p))
=>
(printout t "Result of a newly created rule.")
)
)
Please do not pay special attention to the logic of these rules - it is just an example.
After invoking above presented commands I receive:
[EXPRNPSR3] Missing function declaration for defrule.
ERROR:
(defrule MAIN::test
(action (prev ?p))
=>
(defrule
Is this error, a matter of command syntax or I can not define new rules "dynamically"?
First create a string containing the rule (or any other construct) and then use the build function:
CLIPS>
(deftemplate action
(slot prev)
(slot curr)
)
CLIPS>
(defrule test
(action (prev ?p))
=>
(build (str-cat
"(defrule test_inner
(action (curr " ?p "))
=>
(printout t \"Result of a newly created rule.\")
)"
)
)
)
CLIPS> (reset)
CLIPS> (assert (action (prev move)))
<Fact-1>
CLIPS> (agenda)
0 test: f-1
For a total of 1 activation.
CLIPS> (run)
CLIPS> (rules)
test
test_inner
For a total of 2 defrules.
CLIPS> (ppdefrule test_inner)
(defrule MAIN::test_inner
(action (curr move))
=>
(printout t "Result of a newly created rule."))
CLIPS>