Closed. This question needs details or clarity. It is not currently accepting answers.
Want to improve this question? Add details and clarify the problem by editing this post.
Closed 8 years ago.
Improve this question
I use the Jess Rule Engine in Protégé.
I need to create a test rule using classes, I defined in Jess code.
Here are classes & instances definition code:
(clear)
(defclass Student
(is-a :THING)
(slot studId (type string))
(slot studName (type string))
(slot satGrade (type integer))
)
(defclass Course
(is-a :THING)
(slot courseId (type string))
(slot courseName (type string))
(slot passGrade (type integer))
)
(defclass StudentInCourse
(is-a :THING)
(slot studId (type string))
(slot courseId (type string))
(slot finalGrade (type integer))
)
(make-instance stud_01 of Student (studId "s123") (studName "Rob") (satGrade 650))
(make-instance stud_02 of Student (studId "s456") (studName "Pete") (satGrade 700))
(make-instance stud_03 of Student (studId "s789") (studName "Alex") (satGrade 770))
(mapclass Student)
(deffacts Student (Student (studId)(studName)(satGrade)))
(make-instance course_01 of Course (courseId "c123") (courseName "Calculus") (passGrade 60))
(make-instance course_02 of Course (courseId "c456") (courseName "Linear Algebra") (passGrade 70))
(mapclass Course)
(deffacts Course (Course (courseId)(courseName)(passGrade)))
(make-instance studInCourse_01 of StudentInCourse (studId "s123") (courseId "c123") (finalGrade 20))
(make-instance studInCourse_02 of StudentInCourse (studId "s123") (courseId "c456") (finalGrade 90))
(make-instance studInCourse_03 of StudentInCourse (studId "s456") (courseId "c123") (finalGrade 80))
(make-instance studInCourse_04 of StudentInCourse (studId "s789") (courseId "c456") (finalGrade 75))
(mapclass StudentInCourse)
(deffacts StudentInCourse (StudentInCourse (studId)(courseId)(finalGrade)))
Now I want to implement a check who among the students passes the course «Linear Algebra», I know how to implement it in SQL/Java/C#, but I can't understand how exactly to write it in Jess, each string I push to Jess returns a parsing/compilation error.
How exactly to implement kind of join in Jess or pass over the collection, get courseID, to compare the values according to ID and passGrade/finalGrade, for the correct values to retrieve the data from the student class and as the result to return something like: «Pete passed course Linear Algebra with grade 80»?
It's been ages since I touched Protege, but based on what you have above, I'm assuming you have facts like these (among others). The "deffacts" you have above must be notional at this point -- they won't do anything good (and may not even parse).
(Student (studId "s123")(studName "Rob")(satGrade 650))
(StudentInCourse (studId "s123") (courseId "c456") (finalGrade 90))
(Course (courseId "c456") (courseName "Linear Algebra") (passGrade 70))
Given that I'm right about the above, then a rule that reported each student who passed any course might look like
(defrule passed-algebra
(Course (courseName ?cn) (courseId ?cid) (passGrade ?pg))
(StudentInCourse (courseId ?cid) (studId ?sid) (finalGrade ?fg&:(>= ?fg ?pg))
(Student (studName ?name) (studId ?sid))
=>
(printout t ?name " passed course " ?cn " with a grade of " ?fg crlf))
Related
The following code in the Jess Tab continuously inserts the same instance into a multivalued slot.
(defrule satisfactibleEstudio
(object (is-a Estudio)
(OBJECT ?user)
(nombre ?name)
(preferencias_minimas ?pref))
(object (is-a Chalet)
(OBJECT ?viv)
(precio ?p&: (and
(>= ?p (slot-get ?pref precio_minimo))
(<= ?p (slot-get ?pref precio_maximo))))
(tamanno ?t&: (and
(>= ?t (slot-get ?pref tamanno_minimo))
(<= ?t (slot-get ?pref tamanno_maximo))))
(componentes $?comp&: (>= (get-dorms $?comp) (slot-get ?pref dormitorios))))
=>
(slot-insert$ ?user satisfactibles 1 ?viv))
However, if I replace the slot-insert$ function by a printout it works as intended. What am I doing wrong?
Update:
So apparently it's continuously inserting the same instance into the slot, however with a printout it only prints once per match.
See the Jess manual about modifying a fact on the right hand side:
If a rule includes the declaration (declare (no-loop TRUE)), then nothing that a rule does while firing can cause the immediate reactivation of the same rule; i.e., if a no-loop rule matches a fact, and the rule modifies that same fact such that the fact still matches, the rule will not be put back on the agenda, avoiding an infinite loop.
Question
Considering these classes:
class BookCase { ArrayList<Book> books }
class Book { ArrayList<Page> pages }
class Page { String color }
And considering this natural language rule:
When all pages in a bookcase are black, do A
The trivial approach would be to nest forall clauses, but in Drools can't do that, because forall clauses only allow Patterns (not Conditional Elements, what a forall clause is) inside!
How do I express this in Drools then?
This is close, but not quite the right thing:
rule "all black pages"
when
BookCase( $books: books )
$book: Book( $pages: pages ) from $books
not Page( color != "black" ) from $pages
then
System.out.println( "doing A" );
end
The problem is that this will fire once for each book where all pages are black. To assess all pages in all books one could assemble a list of all pages and make sure they are all black:
rule "all black pages, take 2"
when
BookCase( $books: books )
$pages: ArrayList() from accumulate( Book( $ps: pages ) from $books,
init( ArrayList list = new ArrayList(); ),
action( list.addAll( $ps ); ),
result( list ) )
not Page( color != "black" ) from $pages
then
System.out.println( "doing A" );
end
You can actually nest multiple foralls if you don't use forall(p1 p2 p3...), but the equivalent not(p1 and not(and p2 p3...)). Then, to keep individual books and pages from firing the rule, chuck an exists in between there.
rule 'all pages in bookcase are black'
when
(and
$bookCase: BookCase()
(not (exists (and
$book: Book() from $bookCase.books
not( (and
not( (exists (and
$page: Page() from $book.pages
not( (and
// slightly different constraint than I used in question
eval($page.color == $book.color)
) )
) ) )
) )
) ) )
)
then
...
end
Unlike using accumulate to create a flat list of all pages, this will maintain the context of $page, that is, when a page is put in a constraint with its parent book as in the example above, in this solution Drools still 'knows' what the parent book is.
While I was writing the question, I think I found the answer myself:
BookCase($caseContents : books)
$bookWithOnlyBlackPages : ArrayList<Page>() from $caseContents
forall ( $page : Page(this memberOf $bookWithOnlyBlackPages)
Page(this == $page,
color == "black") )
forall ( $bookInCase : ArrayList<Page>(this memberOf $caseContents)
ArrayList<Page>(this == $bookInCase,
this == $bookWithOnlyBlackPages) )
When i try to compile the render-object method shown here in the documentation http://restas.lisper.ru/en/manual/special-pages.html,
(defmethod restas:render-object ((designer mydrawer)
(code (eql hunchentoot:+http-internal-server-error+)))
(setf (hunchentoot:content-type*) "text/plain")
"Oh, all very bad")
it gives
There is no class named RESTAURANT::MYDRAWER SIMPLE-ERROR
How do these render-object thingies work ?
render-object is a generic function that takes a rendering object, which is either the object passed to :render-method for define-route or the value of *default-render-method*, and the object to render. It then renders that object (usually as text, although you could probably render it to an octet array as well).
The example assumes that you have a class called mydrawer. To get this working you would need to do something like the following:
(defclass mydrawer () ())
(defmethod restas:render-object ((designer mydrawer)
(code (eql hunchentoot:+http-internal-server-error+)))
(setf (hunchentoot:content-type*) "text/plain")
"Oh, all very bad")
(defmethod restas:render-object ((designer mydrawer) obj)
;; Default rendering of objects goes here,
;; this will just call the default render method
(restas:render-object nil obj))
And then use an instance of mydrawer as the render method either for individual routes, or for a restas module.
How can I find the relation between two or more family members in a family tree using CLIPS. I tried this rule but it's not working. I have a syntax Error.
Is there any hints to avoid the Error.
(defrule Family
(FamilyTree ?L-name ?F-name)
=>
(assert(FamilyTree ?L-name ?F-name(read))
(printout t ?L-name "is parent of" ?F-name crlf)))
There appears to be some critical information missing from your question. The code snippet you posted loads correctly. It's only when I add a FamilyTree deftemplate that I get the error you described. If you use deftemplate facts in your rules, you have to use the syntax for deftemplate facts which requires specifying the slot name.
CLIPS> (clear)
CLIPS>
(defrule Family
(FamilyTree ?L-name ?F-name)
=>
(assert(FamilyTree ?L-name ?F-name(read))
(printout t ?L-name "is parent of" ?F-name crlf)))
CLIPS> (clear)
CLIPS> (deftemplate FamilyTree (slot last-name) (slot first-name))
CLIPS>
(defrule Family
(FamilyTree ?L-name ?F-name)
=>
(assert(FamilyTree ?L-name ?F-name(read))
(printout t ?L-name "is parent of" ?F-name crlf)))
[PRNTUTIL2] Syntax Error: Check appropriate syntax for deftemplate patterns.
ERROR:
(defrule MAIN::Family
(FamilyTree ?L-name
CLIPS>
I'm working on porting a site from PHP to Snap w/ Heist. I've ported some of the simpler forms to using Digestive Functors successfully, but now I have to do the tricky ones that require the use of subforms.
This application manages producing flyers for retail stores, so one of the tasks that need to be done is adding an ad size and defining its physical dimensions on the printed flyer. Sizes will vary depending on the type of page (configurable by the flyer owner) and its orientation (which can only be controlled by the administrators).
This form is guaranteed to have a minimum of 3 cells, most likely going to have 9 cells (as pictured above from the PHP version), but could theoretically have an unlimited number.
Here's what I've got so far for the dimensions subform:
data AdDimensions = AdDimensions
{ sizeId :: Int64
, layoutId :: Int64
, dimensions :: Maybe String
}
adDimensionsForm :: Monad m => AdDimensions -> Form Text m AdDimensions
adDimensionsForm d = AdDimensions
<$> "size_id" .: stringRead "Must be a number" (Just $ sizeId d)
<*> "layout_id" .: stringRead "Must be a number" (Just $ layoutId d)
<*> "dimensions" .: opionalString (dimensions d)
The form definition doesn't feel quite right (maybe I have completely the wrong idea here?). AdDimensions.dimensions should be a Maybe String, since it will be null when coming back from the database when running the query to get a list of all of the possible combinations of size_id/layout_id for a new ad size, but it will be not null from a similar query that will be run when creating the edit form. The field itself is required (ad_dimensions.dimensions is set to not null in the database).
From here, I have no idea where to go to tell the parent form that it has a list of subforms or how I might render them using Heist.
I wrote a special combinator for this quite some time ago for digestive-functors-0.2. It was a very full featured solution that included javascript code allowing fields to be dynamically added and removed. That code was based on a much earlier implementation Chris and I did for the formlets package which digestive-functors eventually superceded. This function was never ported to work with the new API that digestive-functors got in 0.3.
The problem is tricky and has some subtle corner cases, so I would recommend that you spend some time looking at the code. I think Jasper would probably accept a good port of the code into the current version of digestive-functors. It's just that nobody has done the work yet.
Edit: This has been done now for the latest digestive-functors. See the listOf function.
Using the listOf functionality (which wasn't around when the question was originally asked/answered), this is how one would go about about it. This requires 2 forms where the form representing your list's type is a formlet:
data Thing = Thing { name: Text, properties: [(Text, Text)] }
thingForm :: Monad m => Maybe Thing -> Form Text m Thing
thingForm p = Thing
<$> "name" .: text (name <$> p)
<*> "properties" .: listOf propertyForm (properties <$> p)
propertyForm :: Monad m => Maybe (Text, Text) -> Form Text m (Text, Text)
propertyForm p = ( , )
<$> "name" .: text (fst <$> p)
<*> "value" .: text (snd <$> p)
Simple forms
If you have a simple list of items, digestive-functors-heist defines some splices for this, but you might find that you'll end up with invalid markup, especially if your form is in a table.
<label>Name <dfInputText ref="formname" /></label>
<fieldset>
<legend>Properties</legend>
<dfInputList ref="codes"><ul>
<dfListItem><li itemAttrs><dfLabel ref="name">Name <dfInputText ref="name" /></dfLabel>
<dfLabel ref="code">Value <dfInputText ref="value" required /></dfLabel>
<input type="button" name="remove" value="Remove" /></li></dfListItem>
</ul>
<input type="button" name="add" value="Add another property" /></dfInputList>
</fieldset>
There is JavaScript provided by digestiveFunctors to control adding and removing elements from the form that has a jQuery dependency. I ended up writing my own to avoid the jQuery dependency, which is why I'm not using the provided addControl or removeControl splices (attributes for button type elements).
Complex Forms
The form in the OP can't make use of the splices provided by digestive-functors-heist because the labels are dynamic (eg. they come from the database) and because we want it in a complex table layout. This means we have to perform 2 additional tasks:
Generate the markup manually
If you haven't looked at the markup that are generated by the digestive-functors-heist splices, you might want to do that first so that you get an idea of exactly what you have to generate so that your Form can be processed correctly.
For dynamic forms (eg. forms where the users are allowed to add or remove new items on the fly), you will need a hidden indices field:
<input type='hidden' name='formname.fieldname.indices' value='0,1,2,3' />
formname = whatever you named your form when you ran it via runForm
fieldname = the name of the list field in your main form (adjust as necessary if you're using subforms), in this example it would be named "properties"
value = a comma delimited list of numbers representing the indices of the subforms that should be processed when the form is submitted
When one of the items from your list is removed or a new one is added, this list will need to be adjusted otherwise new items will be completely ignored and removed items will still exist in your list. This step is unnecessary for static forms like the one in the OP.
Generating the rest of the form should be pretty straight forward if you already know how to write splices. Chunk up the data as appropriate (groupBy, chunksOf, etc.) and send it through your splices.
In case you can't already tell by looking at markup generated by digestive-splices-heist, you'll need to insert the index value of your subform as part of the fields for each subform. Tthis is what the output HTML should look like for the first field of our list of subforms:
<input type='text' name='formname.properties.0.name' value='Foo' />
<input type='text' name='formname.properties.0.value' value='Bar' />
(Hint: zip your list together with an infinite list starting from 0)
Pull the data back out of your form when handling errors
(I apologize in advance if none of this code is actually able to compile as written, but hopefully it illustrates the process)
This part is less straight forward than the other part, you'll have to dig through the innards of digestive-functors for this. Basically, we're going to use the same functions digestive-functors-heist does to get the data back out and populate our Thing with it. The function we're needing is listSubViews:
-- where `v` is the view returned by `runForm`
-- the return type will be `[View v]`, in our example `v` will be `Text`
viewList = listSubViews "properties" v
For a static form, this can be as simple as zipping this list together with your list of data.
let x = zipWith (curry updatePropertyData) xs viewList
And then your updatePropertyData function will need to update your records by pulling the information out of the view using the fileInputRead function:
updatePropertyData :: (Text, Text) -> View Text -> (Text, Text)
updatePropertyData x v =
let
-- pull the field information we want out of the subview
-- this is a `Maybe Text
val = fieldInputRead "value" v
in
-- update the tuple
maybe x ((fst x, )) val