Visar inlägg med etikett CLIPS. Visa alla inlägg
Visar inlägg med etikett CLIPS. Visa alla inlägg

2008-10-16

Handling defrules in MPS, executing the RHS

I've been rewriting the deftemplate code for MPS more or less from scratch... but I'll have to talk about that some other time. It's high time to focus on the defrule macro(s). I'll break this up into several posts and I'll start with the function for executing the RHS.

The MPS syntax (a subset of CLIPS') for defrule looks like this:

  defrule-construct
::= (defrule rule-name
conditional-element*
=>
expression*)

conditional-element
::= template-pattern-CE ¦ assigned-pattern-CE ¦
not-CE ¦ and-CE ¦ or-CE ¦ test-CE ¦
exists-CE ¦ forall-CE

assigned-pattern-CE
::= single-field-variable <- template-pattern-CE

not-CE ::= (not conditional-element)
and-CE ::= (and conditional-element+)
or-CE ::= (or conditional-element+)
test-CE ::= (test function-call)
exists-CE ::= (exists conditional-element+)
forall-CE ::= (forall conditional-element
conditional-element+)

template-pattern-CE
::= (deftemplate-name single-field-LHS-slot*)

single-field-LHS-slot
::= (slot-name constraint)

constraint ::= ? connected-constraint


connected-constraint
::= single-constraint ¦
single-constraint & connected-constraint
single-constraint ¦ connected-constraint

single-constraint
::= term ¦ ~term

term ::= constant ¦ single-field-variable ¦
:function-call ¦ =function-call

single-field-variable
::= ?variable-symbol
I'm quite far from having all of the above supported, but I've got enough to show how the function that executes the RHS of a rule works.

The defrule macro works by expanding into a series of macro calls which handle more and more specific cases in the processing. The macro itself is quite simple
(defmacro defrule (name &body body)
(let ((rhs (cdr (member '=> body)))
(lhs (ldiff body (member '=> body))))
`(progn
(compile-lhs ,name ,@lhs)
(compile-rhs ,name ,@rhs))))
As you can see, it expands into two other macro-calls: compile-lhs and compile-rhs, where the first parses the conditional-elements and, in the process, creates two symbol-tables (fact-bindings and variable-bindings) with all of the variables it can find.

The second macro handles the RHS and looks like this:
(defmacro compile-rhs (name &body rhs)
(when (null rhs)
(setf rhs '(t)))
`(defun ,(make-sym "RHS/" name) (activation)
(let* ((token (activation-token activation))
,@(mapcar #'make-fact-binding (reverse fact-bindings))
,@(mapcar #'make-variable-binding (reverse variable-bindings)))
,@rhs)))
So, if we evaluate this:
MPS> (defrule foobar
?foo <- (foo (bar ?bar) (baz 1))
=>
(format t "~%~A ~A" ?foo ?bar))
it expands into this (among other things):
(DEFUN RHS/FOOBAR (ACTIVATION)
(LET* ((TOKEN (ACTIVATION-TOKEN ACTIVATION))
(?FOO (NTH 0 TOKEN))
(?BAR (DEFTEMPLATE/FOO-BAR ?FOO)))
(FORMAT T "~%~A ~A" ?FOO ?BAR)))
Most of the work is with figuring out how to assign values to each of the variables. I'm using a simple list as the structure for the tokens. WMEs (fact objects) are added in the order they appear in the list of conditional elements. Once all of the WMEs are bound, all of the variable-bindings are bound by using the automatically constructed accessor methods for those structs.

The actions in the RHS are simply spliced into the let* form at macro-expansion time which completes the function definition. It is then evaluated and stored with the rule's production node. Later, when that production node is left-activated, it creates an activation (with the WMEs and some additional meta-data like timestamps and such) and places it in the conflict-set. If that activation ever triggers a rule, it is passed as an argument to the RHS function.

Since I haven't got enough of "the rest" of MPS in place. We're going to have to mock an activation and call the function directly to see whether or not it works.
MPS> (rhs/foobar (make-activation :token (list (foo (bar 1) (baz 1)))))
#S(DEFTEMPLATE/FOO :BAR 1 :BAZ 1) 1
NIL
MPS>
There's really not much more to say about the RHS so I'll stop here. The LHS is a bit more complicated and I hope that I can show some of that code soon.

2008-09-28

CLIPS is not a Lisp

In a recent thread on JBoss Drools' Rules Users mailing list, Mark Proctor writes:

... The Drools DRL language itself was designed as a more intuitive and less verbose language, this becomes increasinly important as you start to add more complex syntax which becomes harder to read with a lisp approach. I think most people in here would agree that the Drools DRL approach is an improvement over the lisp approach of clips/jess - apart from the die hard lisp fans.
I might be a die hard Lisp fan, but if you ask me, the problem is not that CLIPS (and Jess) are Lisp-like it's that they are Lisp-like.

CLIPS doesn't use a Lisp approach. It might look that way, but the similarities are only skin-deep. CLIPS lack one of the most fundamental requirements for being Lispy, namely, code as data. It lacks other things as well, but given that capacity (or in other words to be able to provide a higher level syntactical abstraction on top of CLIPS) there would be nothing wrong with CLIPS syntax that couldn't be fixed with CLIPS syntax. Yes. I know about build and eval but they'll only take you so far. It's not that I'm unhappy with CLIPS, but I honestly believe a Lisp-based CLIPS would be so much better.

My apologies to Mark. This rant has nothing to do with JBoss Drools. It's just that his post had the magic words in it ;-)

2008-08-23

MPS inference engine, the rest of the implementation

In the last post I mentioned a few of the design choices I've made for the Rete Network implementation. The rest of the engine is quite straight forward and simple. There's really not much to talk about but I'll show some code anyway ;-).

Another thing though, and a bit more interesting really, is that I have been thinking about whether or not I should try to stay true to CLIPS' behaviour and functionality in the misc engine functions as well.

For example, the agenda function currently returns a list of all activations on the agenda and CLIPS prints them but returns no value. Similarly there's facts which behaves like get-fact-list instead. Used at the REPL there's little difference in what the user sees but the reason I've made them different is because then I can write other functions on top of them.

Ok. As promised, some code. Here is the run function (which uses agenda):

|(defun run (&optional (limit -1))
| (do* ((curr-agenda (agenda) (agenda))
| (execution-count 0 (+ execution-count 1))
| (limit limit (- limit 1)))
| ((or (eq limit 0)
| (= (length curr-agenda) 0)) execution-count)
| (let* ((activation (first curr-agenda))
| (rhs-func (make-sym "RHS-" (string (activation-rule activation))))
| (prod-mem (make-sym "PRODUCTION-" (string (activation-rule activation)) "-MEMORY")))
| (funcall rhs-func activation)
| (store '- activation prod-mem))))
and here is the assert function (I'm shadowing the built-in assert function, still accessible as cl:assert though):
|(defun assert (&rest facts)
| (incf timestamp)
| (dolist (fact facts)
| (store '+ fact 'working-memory)
| (mapcar #'(lambda (node) (funcall node '+ fact timestamp))
| (gethash (type-of fact) (gethash 'root rete-network)))))
I hope that they are at least somewhat readable to others.

I've now finished the functions that make up the inference engine implementation. And it works... as long as you manually divide your program into alpha, beta and production nodes and connect them properly in the Rete Network ;-)

2008-08-15

MPS inference engine, the Rete Network implementation

It's difficult to talk about the MPS defrule macro and the code it expands to without first explaining the environment in which it is meant to run. So I'll try to explain and describe my current thoughts and ideas that make up the design of the MPS inference engine in this and following posts.

The engine is made up of some I/O functions (assert and retract), the Rete Network, a Conflict Resolver and an Execution Engine. The Rete Network is the most central part of the whole thing so I'll start there.

It is basically implemented within a hash-table. This is the simplest possible representation of a Rete Network that I can think of. There are, of course, a few helper functions to abstract away some of the book keeping details of this design choice. During rule compilation there are functions for adding a node/memory and connecting nodes/memories with each other and at run time there are functions to propagate facts/tokens, access contents of and store facts/tokens in memory.

A processing node (for example an alpha node) is implemented as a function and is stored in the hash-table as well. A very simple LHS construct like:

|(defrule foo
| (bar (baz ?baz&:(> ?baz 10)))
| =>)
would be represented in the Rete Network by the following function:
|(defun bar-baz->-10 (key fact timestamp)
| (when (> (deftemplate/bar-baz fact) 10)
| (store key fact 'bar-baz->-10-memory)
| (propagate key fact timestamp 'bar-baz->-10)))
A join node is represented by two functions (one for left and one for right activation) and is slightly more complex (but not much). If we change the LHS to:
|(defrule foo
| (bar-1 (baz ?baz))
| (bar-2 (baz ?baz))
| =>)
we would have to create a join node for joining facts on the ?baz variable. It would look something like this:
|(let ((left-memory  'bind-bar-1-memory)
| (right-memory 'bind-bar-2-memory))
| (defun join-bar-1-baz-and-bar-2-baz-left (key token timestamp)
| (dolist (fact (contents-of right-memory))
| (when (eq (bar-1-baz (nth 0 token))
| (bar-2-baz fact))
| (store key (append token (list fact)) 'join-bar-1-baz-and-bar-2-baz-memory)
| (propagate key (append token (list fact)) timestamp 'join-bar-1-baz-and-bar-2-baz))))
|
| (defun join-bar-1-baz-and-bar-2-baz-right (key fact timestamp)
| (dolist (token (contents-of left-memory))
| (when (eq (bar-1-baz (nth 0 token))
| (bar-2-baz fact))
| (store key (append token (list fact)) 'join-bar-1-baz-and-bar-2-baz-memory)
| (propagate key (append token (list fact)) timestamp 'join-bar-1-baz-and-bar-2-baz)))))
As you can see variables are expanded into "positions" everywhere in the functions. Variables are, however, available and bound in the execution context of the production node's RHS function.

There are some more bits and pieces that are good to know. The root node, for example, is a hash-table. Each deftemplate-type is a key and the value contains a list of alpha nodes.

Compilation consists of (apart from expanding the rule's LHS into a number of functions) a series of calls to add-to-root and connect-nodes. Since each node is responsible for propagating as well as storing facts/tokens in memory we can connect the processing nodes to each other directly. The example above would be compiled with:
|(add-to-root 'bar-1 #'bind-bar-1) ; This might not be necessary! We should
|(add-to-root 'bar-2 #'bind-bar-2) ; be able to connect directly to the join.
|(connect-nodes 'bind-bar-1 #'join-bar-1-baz-and-bar-2-baz-left)
|(connect-nodes 'bind-bar-2 #'join-bar-1-baz-and-bar-2-baz-right)
|(connect-nodes 'join-bar-1-baz-and-bar-2-baz #'production-foo)
This is all theory, though. I don't actually have all of the defrule macro in place so the final version might very well expand into something different. But I hope this conveys the general idea of the Rete Network implementation.

2008-08-11

Handling deftemplates in MPS

There are three parts to my MPS (Minimal Production System) experiment, the "engine" itself and the deftemplate and defrule macros. So far, I've spent most of my time on the deftemplate macro. It turned out to be a bit trickier than I thought to implement. Mostly because I was stuck thinking about too complex macro expansions (which I never got to work).

Here is the syntax as BNF (well, sort of[1]):

|  deftemplate-construct
| ::= (deftemplate deftemplate-name
| [comment]
| single-slot-definition*)
|
| single-slot-definition
| ::= (slot slot-name [default-attribute])
|
| default-attribute
| ::= (default ?NONE | expression) |
| (default-dynamic expression)
It is a subset of CLIPS' deftemplate syntax. There are no multislots and no constraint-attributes for slots (types, ranges and such). But, apart from that, it is more or less the same[2]. And here's how it works:
|; SLIME 2007-03-14
|;;;; Compile file / [...] /mps. ...
|CL-USER> (in-package :mps)
|#[Package "MPS"]
|MPS> (deftemplate foo
| (slot a-slot)
| (slot a-default-slot (default 1))
| (slot required-slot (default ?NONE)))
|FOO
|MPS> (foo)
|
|The slot: REQUIRED-SLOT in deftemplate: FOO requires an explicit value.
| [Condition of type SIMPLE-ERROR]
|
|Restarts:
| 0: [ABORT] Return to SLIME's top level.
| 1: [ABORT-BREAK] Reset this process
| 2: [ABORT] Kill this process
|
|Invoking restart: Return to SLIME's top level.
|; Evaluation aborted
|MPS> (foo (required-slot 1))
|#S(DEFTEMPLATE/FOO :A-SLOT NIL :A-DEFAULT-SLOT 1 :REQUIRED-SLOT 1)
|MPS> (deftemplate bar
| (slot default-gensym (default (gensym)))
| (slot dynamic-gensym (default-dynamic (gensym))))
|BAR
|MPS> (bar)
|#S(DEFTEMPLATE/BAR :DEFAULT-GENSYM #:G31 :DYNAMIC-GENSYM #:G40)
|MPS> (bar)
|#S(DEFTEMPLATE/BAR :DEFAULT-GENSYM #:G31 :DYNAMIC-GENSYM #:G41)
|MPS>
The macro expands into a defstruct (deftemplate/foo) and another defmacro (foo) that is used as a constructor for the template. The reason it is a macro and not a regular function is because a function's arguments are evaluated whilst a macro's is not. And since there's no function named a-slot or a-default-slot etc. So we'd be thrown into the debugger if we tried to evaluate something like (foo (a-slot 1)).

Here's the macroexpansion of a simple template:
|MPS> (pprint (macroexpand-1 '(deftemplate foo
| (slot a)
| (slot b (default 1)))))
|
|(PROGN (DEFSTRUCT DEFTEMPLATE/FOO "" (A NIL) (B 1))
| (DEFMACRO FOO (&REST SLOTS)
| ""
| (CALL-DEFSTRUCT-CONSTRUCTOR 'DEFTEMPLATE/FOO SLOTS)))
|; No value
|MPS>
Most of the code is spent checking that the template follows the syntax described in the BNF. The expansion itself is rather simple, almost trivial (it is the last progn below).
|(defmacro deftemplate (deftemplate-name &body body)
| "
| The deftemplate construct is used to create a template which can then
| be used by non-ordered facts to access fields of the fact by name.
|
| Examples:
| (deftemplate object
| (slot id (default-dynamic (gensym)))
| (slot name (default ?NONE)) ; Makes name a required field
| (slot age))
| "
|
| (macrolet ((signal-deftemplate-error (msg &rest args)
| `(error (concatenate 'string "~&The deftemplate ~A contains at least one invalid slot-definition: ~S." ,msg)
| ,@args)))
| (let ((comment "")
| (defstruct-name (intern (concatenate 'string "DEFTEMPLATE/" (string deftemplate-name))))
| (defstruct-slots '()))
|
| ;; Extract the documentation string
| (when (stringp (car body))
| (setf comment (car body))
| (setf body (cdr body)))
|
| ;; Check syntax and extract slot-specifiers
| (dolist (slot body)
| (let* ((slot-name (cadr slot))
| (curr-defstruct-slot `(,slot-name nil)))
| (unless (eq (car slot) 'slot)
| (signal-deftemplate-error "~&Expected SLOT instead of ~A."
| deftemplate-name slot (car slot)))
|
| (when (> (length slot) 2)
| (dolist (default-attribute (cddr slot))
| (unless (consp default-attribute)
| (signal-deftemplate-error "~&Expected (default ?NONE|expression) or (default-dynamic expression) instead of ~A."
| deftemplate-name slot default-attribute))
| (unless (or (eq (car default-attribute) 'default)
| (eq (car default-attribute) 'default-dynamic))
| (signal-deftemplate-error "~&Expected DEFAULT or DEFAULT-DYNAMIC instead of: ~A."
| deftemplate-name slot (car default-attribute)))
| (if (eq (car default-attribute) 'default)
| (if (eq (cadr default-attribute) '?NONE)
| (setf curr-defstruct-slot `(,slot-name (required ',slot-name ',deftemplate-name)))
| (setf curr-defstruct-slot `(,slot-name ',(eval (cadr default-attribute)))))
| (setf curr-defstruct-slot `(,slot-name ,(cadr default-attribute))))))
|
| (setf defstruct-slots (append defstruct-slots (list curr-defstruct-slot)))))
|
| `(progn
| (defstruct ,defstruct-name
| ,comment
| ,@defstruct-slots)
|
| (defmacro ,deftemplate-name (&rest slots)
| ,comment
| (call-defstruct-constructor ',defstruct-name slots))))))
and here are the functions used as helpers:
|(defun required (slot-name deftemplate-name)
| (error "~&The slot: ~A in deftemplate: ~A requires an explicit value." slot-name deftemplate-name))
|
|(defun as-keyword (sym)
| (intern (string-upcase sym) :keyword))
|
|(defun call-defstruct-constructor (defstruct-name &rest slots)
| (let ((constructor (intern (concatenate 'string "MAKE-" (string defstruct-name)))))
| (apply constructor (mapcan #'(lambda (slot)
| `(,(as-keyword (car slot)) ,(cadr slot)))
| (car slots)))))
I should probably try to write a macro to abstract away all those (intern (concatenate 'string ...)) calls but otherwise, that's about it for deftemplate. Next up is getting all of the defrule construct working (which feels like a much tougher task).

[1] I hate that I still haven't found a good way of sharing code using Blogger. Tips and pointers are very welcome!

[2] The default and default-dynamic attributes in CLIPS take a variable number of expressions (at least according to the BNF found in the CLIPS Basic Programming Guide, Appendix H). I assume it assigns the value of the last as the default but I haven't tried. Anyway, if you want to evaluate several expressions in that place you're going to have to wrap it explicitly in a progn (effectively making it one expression).

2008-08-02

A Minimal Production System

I've been experimenting a bit with bits and pieces of a production system in Common Lisp for a few days now. It all started as a fun exercise in Common Lisp macrology (the idea was to convert CLIPS syntax into executable Common Lisp code) but since things have fallen into place so neatly. I thought maybe it would be worth implementing parts of CLIPS syntax and functionality. So now, the only question is: which parts to implement?

I'm not going to do anything about data types and built-in functions. I'll add support for ?variables but that's about it (no multifields and no globals) and as far as constructs go I'll manage with defrule and deftemplate (no implied/ordered facts). If it doesn't turn out to be too difficult I'll try to include connected constraints in defrule but I won't bother with slot constraints in deftemplates (types and such). I'm hoping to include all CEs but it depends on how hairy it gets.

So. What have I missed? Is there something unnecessary on the list?

[Update 2008-08-03]: Hm. I guess I forgot about a bunch of engine commands. So here goes. Assert and retract seem stupid to leave out, as does run. I'll probably be annoyed not to have facts so I guess that's in as well. Load, clear, reset and agenda feel necessary but I think I'll only work with one conflict resolution strategy (depth) though.

2008-07-14

Using printout and readline in PyCLIPS applications

I am trying to help a CLIPS user compile his application into a Windows executable (.exe) by following the description in one of my previous blog posts. But his application relies on the built in functions read and printout. And my blog post says nothing about that type of situation since all of the times I've done this before have been with applications that have GUIs and I've never had to bother with command line interaction.

The problem is, more specifically, that PyCLIPS consumes all calls to printout and read without printing to screen or requiring any user input. This may seem like a stupid design decision at first, but if you consider the many situations and environments that PyCLIPS can run in. It starts to feel like a rather sensible approach since it's obviously impossible to know what the right thing to do is. If you need user interaction via command line, you can provide Python functions that perform I/O for you.

So. Just to be clear. PyCLIPS does not constrain the possibilities for interaction with CLIPS in any way. It just doesn't make any assumptions about how it should best be done.

Ok. Fair enough. But, what to do?

First off. I don't want to have to maintain different versions of the application just to perform I/O. I need a way to dynamically dispatch I/O to either the built in functions (if it's run in CLIPS Dialog) or to my Python functions (if it's run in PyCLIPS).

Here is a simple application that uses printout and read:

|(deffacts start
| (sum 0))
|
|(defrule calc-sum
| ?sum <- (sum ?s)
| =>
| (retract ?sum)
| (printout t "Enter a number or Q to quit: ")
| (bind ?input (read))
| (if (numberp ?input)
| then (bind ?s (+ ?s ?input))
| (assert (sum ?s))
| else (if (or (eq ?input q)
| (eq ?input Q))
| then (printout t "Sum = " ?s crlf)
| else (printout t "Invalid input: " ?input crlf)
| (assert (sum ?s)))))
It works like this:
CLIPS> (load "sum.clp")
!!!$*
TRUE
CLIPS> (reset)
CLIPS> (run)
Enter a number or Q to quit: one
Invalid input: one
Enter a number or Q to quit: 9
Enter a number or Q to quit: 8
Enter a number or Q to quit: 5
Enter a number or Q to quit: Q
Sum = 22
When I first thought about how to make this program run (with as little effort as possible) in PyCLIPS, I didn't think that it would be very difficult. I was actually quite certain I'd be able to monkey-patch PyCLIPS with my I/O functions. CLIPS, however, was far from impressed with my attempt. So, a slap on the wrist later, I settled on (manually) replacing all function calls to printout, readline and read to deffunction wrappers (printout1, readline1 and read1) instead.

Here are the CLIPS deffunctions:
|(deffunction printout1 (?logical-name $?args)
| (if (member$ python-call (get-function-list))
| then (funcall python-call pyprintout ?logical-name $?args)
| else (progn$ (?arg $?args)
| (printout ?logical-name ?arg))))
|
|(deffunction readline1 ($?logical-name)
| (if (> (length$ $?logical-name) 0)
| then (bind ?logical-name (first$ $?logical-name))
| else (bind ?logical-name t))
|
| (if (member$ python-call (get-function-list))
| then (funcall python-call pyreadline ?logical-name)
| else (readline ?logical-name)))
|
|(deffunction read1 ($?logical-name)
| (if (> (length$ $?logical-name) 0)
| then (bind ?logical-name (first$ $?logical-name))
| else (bind ?logical-name t))
|
| (if (member$ python-call (get-function-list))
| then (eval (funcall python-call pyread ?logical-name))
| else (read ?logical-name)))
They look quite hairy. I know. But they're generic so it's not something you'd have to write and modify for each application that you want to be able to run in both CLIPS and PyCLIPS. There's also some Python boiler plate. It looks like this:
|import clips
|
|def pyprintout(*args):
| for arg in args[1]:
| if arg.cltypename().upper() == "SYMBOL":
| if arg.upper() == "CRLF":
| print
| elif arg.upper() == "TAB":
| print "\t",
| else:
| print arg,
|
| else:
| print arg,
|
|def pyreadline(*args):
| return raw_input()
|
|def pyread(*args):
| return raw_input()
|
|clips.RegisterPythonFunction(pyprintout)
|clips.RegisterPythonFunction(pyreadline)
|clips.RegisterPythonFunction(pyread)
Once we've got all of the above in place. We can load, reset and run from within a PyCLIPS script and have the application work directly in the Windows command line.
C:\...>python sum.py
Enter a number or Q to quit: one
Invalid input: one
Enter a number or Q to quit: 9
Enter a number or Q to quit: 8
Enter a number or Q to quit: 5
Enter a number or Q to quit: Q
Sum = 22

C:\...>
The files sum.py and sum.clp contain the whole application if you're interested. The point of all this is of course to be able to compile the PyCLIPS application into an executable. Step for step instructions on how to do that can be found here.

[Update 2008-07-14] I just fixed the pyprintout function (in sum.py), so that it also prints symbols. The code I added is marked with red.

2008-06-18

Testing CLIPS applications with TextTest

I was recently re-introduced to TextTest - a tool for text-based functional testing. TextTest uses the same approach as is used to test CLIPS functionality and that has been suggested several times on the CLIPSESG mailing list for unit-testing CLIPS applications.

The benefits of using TextTest for your CLIPS testing is that you can automate the execution of the tests and that you get a nice summary of the differences between expected output and actual output. So if you're currently testing your application "manually" using the CLIPS Dialog window and a diff tool you should give TextTest a try. It may well save you some time.

2008-06-06

Creating an .exe for a CLIPS application

Every now and then someone (on CLIPSESG) asks about how to create a standalone binary (usually a Windows .exe) for a CLIPS application they wrote. The process for doing so is described in Section 5 of the CLIPS Advanced Programming Guide. But even though that may be the correct answer to such questions it isn't always what the poster wants to hear. At least that's the impression I get.

The problem is (probably, I'm guessing here), that the description is kind of brief, contains little or no examples and is written for an experienced (or at least decent) C programmer. Which, unfortunately, kind of misses the mark since it seems that most people that ask about how to create .exe files for their applications are (relatively) new programmers and probably feel a bit lost reading that section. I know I do.

Though I can see several reasons why distributing your CLIPS application as a binary file (in any format) is a bad idea. I also know that sometimes it's the only way, so I'm not going to question your motives or try to talk you out of it. Instead. Here is a step-for-step demonstration on how to do it using PyCLIPS.

Step 0. Know the difference.
I'm not sure whether or not the steps described in CLIPS APG Section 5 results in a binary that includes the CLIPS IDE (the Dialog window and other useful things). But what I am describing here does not! So if you rely on user input at runtime you're also going to have to create a GUI for your application. I've written about that before and all of those examples are also available as PyCLIPS downloads.

Step 1: Install the tools.
The first thing you need to to do is to get all of the tools needed. You're going to have to download and install Python, PyCLIPS and py2exe. If you also need a GUI or want to try either of the wxPython CLIPS demos you should get wxPython. Make sure to get the latest version of Python (2.5.2 as I'm writing this) and releases matching it (2.5) for the other projects. Installation should be very simple since all projects provide binaries and the installers pretty much take care of themselves.

When you're done with all of the installations you can make sure that everything went alright by opening up IDLE (it should be in your start menu) and try the following:

>>> import clips
>>> import py2exe
>>> import wx
If either of those raises an exception you're going to have to re-install. Installation instructions and trouble-shooting guidance can be found on the projects' websites and in their docs.

Step 2: Wrap your application in PyCLIPS.
Once you've got all the tools in place it's time to write a wrapper for your CLIPS application. I'll use the sudoku demo (sudoku.clp and solve.clp) as base for my example application.

I'll wrap it in a Python program that reads a file as input (containing an unsolved sudoku puzzle), triggers the CLIPS program to solve the puzzle, queries CLIPS for the answer and writes the solution to stdout.

The program is in essence a copy of the on_openfile and on_solve functions from the wxPython demo so I've copy-pasted most of it. You can download a .zip with the code here.

Step 3: Write a setup.py script.
The py2exe website has tutorials and examples on how to best use it so I'll be quite brief here.

In order to compile a Python program/script into a standalone Windows executable you're going to have write a small configuration file (setup.py) which specifies the options py2exe should work with. Py2exe is an extension to distutils so you should read those docs as well.

The setup.py which I'll use looks like this:
from distutils.core import setup
import py2exe

setup(console=['sudoku.py'], data_files=[('', ['sudoku.clp', 'solve.clp'])])
there are a lot more options available but since this is more of a teaser (or a shameless plug for Python and PyCLIPS depending on how you look at it) I'll let you look into that yourself.

Step 4: Compile the application.
All we need to do to generate the .exe is to run the setup.py script in a command prompt:
C:\Program\...\py2exe-demo>setup.py py2exe
running py2exe
...
*** copy data files ***
copying C:\Python25\lib\site-packages\py2exe\run.exe -> C:\Program\...\py2exe-demo\dist\sudoku.exe

*** binary dependencies ***
Your executable(s) also depend on these dlls which are not included,
you may or may not need to distribute them.

Make sure you have the license if you distribute any of them, and
make sure you don't distribute files belonging to the operating system.

ADVAPI32.dll - C:\WINDOWS\system32\ADVAPI32.dll
USER32.dll - C:\WINDOWS\system32\USER32.dll
SHELL32.dll - C:\WINDOWS\system32\SHELL32.dll
KERNEL32.dll - C:\WINDOWS\system32\KERNEL32.dll
msvcrt.dll - C:\WINDOWS\system32\msvcrt.dll

C:\Program\...\py2exe-demo>cd dist
C:\Program\...\py2exe-demo\dist>sudoku.exe ..\Game03.txt
5 9 7 8 3 4 1 2 6
2 6 8 9 1 5 7 3 4
3 1 4 7 2 6 5 8 9
8 7 3 2 4 1 9 6 5
9 2 5 6 7 3 4 1 8
1 4 6 5 8 9 2 7 3
4 5 2 1 6 8 3 9 7
7 8 9 3 5 2 6 4 1
6 3 1 4 9 7 8 5 2
Seems to work. But listing the contents of the dist directory might make you a bit disappointed:
C:\Program\...\py2exe-demo\dist>dir
...
2006-09-19 09:52 77 824 bz2.pyd
2008-06-04 19:42 1 034 699 library.zip
2006-03-16 16:19 348 160 MSVCR71.dll
2006-09-19 09:52 2 109 440 python25.dll
2008-01-18 21:08 39 997 solve.clp
2008-01-18 21:08 5 036 sudoku.clp
2008-06-04 19:42 18 944 sudoku.exe
2006-09-19 09:52 475 136 unicodedata.pyd
2006-09-19 09:52 4 608 w9xpopen.exe
2008-03-09 04:29 1 009 152 _clips.pyd
...
Neither of the .clp files have been compiled!

If the purpose of generating the .exe is to hide implementation details, such as the CLIPS program, then you're going to have to convert the .clp files into PyCLIPS programs. It's easy enough to do, but it's boring.

You have to go through each .clp file and
1) Either remove all comments or convert them into Python comments (use # instead of ;).
2) Indent all lines in the file with 4 spaces.
3) Insert import clips as the first line.
4) Insert def load(): as the second line.
5) Wrap all definitions with a clips.Build(""" ... """).
6) Rename the file to [filename]_clp.py.

After doing that you can remove the data_files option in setup.py and modify a few lines in sudoku.py. If we change:
clips.Load("sudoku.clp")
clips.Load("solve.clp")
to
import sudoku_clp, solve_clp
sudoku_clp.load()
solve_clp.load()
and run the compilation again, the problem is "fixed". Since all of the CLIPS constructs are now embedded in Python files they will be sucked into the .exe. The "fixed" code is available here.

The steps outlined above might at least be an alternative for those of us that aren't that well-versed in C/C++. I'm sure that the same, or at least something similar, can be done with other tools and languages - but that's for someone else to write about.

The code I've used as the example ought to be pretty simple to understand, but if you have any questions or if you think I've been too brief at some point, leave a comment or send me an e-mail and we'll sort it out.

2008-05-07

Implied deftemplates considered harmful

CLIPS is an extremely flexible and adaptive programming environment. Unfortunately, that's not always for the best.

Implied deftemplates (see chapter 3.4 in CLIPS Basic Programming Guide) can be a nuisance when debugging. It makes a difficult task even more difficult because certain, *trivial*, errors (more specifically, spelling errors) can go undetected. It's a bit similar to weak typing, though usually not as bad.

I've learned (the hard way) to stay clear from ordered facts and implied deftemplates but if you still do use them and your application isn't really behaving as you expect. Try a (list-deftemplates) as your first debugging effort. You may well be surprised.

2008-05-04

12.3.6 Evaluating a Construct within a String

The CLIPS Basic Programming Guide is (more or less) the only reference you'll ever need for your CLIPS programming needs. It is however sometimes a bit brief. Like the section describing how to add constructs at run-time:

12.3.6 Evaluating a Construct within a String

The build function evaluates the string as though it were entered at the command prompt.

Syntax
(build [string-or-symbol-expression])

where the only argument is the construct to be added. The return value is TRUE if the construct was added (or FALSE if an error occurs).

The build function is not available for binary-load only or run-time CLIPS configurations (see the Advanced Programming Guide).

Example
CLIPS> (clear)
CLIPS> (build "(defrule foo (a) => (assert (b)))")
TRUE
CLIPS> (rules)
foo
For a total of 1 rule.
CLIPS>

It could use a few more examples. Or at least one that is slightly more complex. Personally, I've never used build without wrapping it in a deffunction. But that also introduces the complexity of handling several str-cat calls to build up the definition of the construct in a variable.

Almost all such builder-deffunctions that I've made follow the same basic template
|CLIPS> (deffunction build-foo-deftemplate (?n)
| (bind ?templ (str-cat "(deftemplate foo (slot id)"))
| (loop-for-count (?i ?n)
| (bind ?templ (str-cat ?templ
| " (slot slot" ?i ")")))
| (bind ?templ (str-cat ?templ ")"))
| (build ?templ))
|CLIPS> (build-foo-deftemplate 3)
|TRUE
|CLIPS> (ppdeftemplate foo)
|(deftemplate MAIN::foo
| (slot id)
| (slot slot1)
| (slot slot2)
| (slot slot3))
|CLIPS>
Granted, this example might not be very useful but at least it shows the steps required to incrementally build up a definition in a variable. It's easy to get lost in all of those extra parens but if you've got a decent editor it can match them up for you even though they're embedded in strings.

2008-04-21

Debugging in CLIPS

I spend part of my time at CLIPSESG and I try to help whenever I can. Usually that involves figuring out why a rule is, or isn't, firing and mostly it's a simple-enough error or an "obvious" mistake that causes the problem. The thing is that it's mostly real easy to find them as well. At least if you know where to look.

One (very useful) way of looking at your CLIPS program is by using the built-in facts and matches functions (see chapter 13.4.1 Displaying the Fact-List and chapter 13.6.4 Displaying Matches for a Rule in the CLIPS Basic Programming Guide). There are some things to think about though.

Evaluating (facts) is tricky in the same way select * from Foo is tricky in SQL. You've got to make sure that when you've got enough facts in Working Memory you don't print them all in the shell. Mostly because it takes a while, but also because you can't scroll more than a few hundred lines. So if what you're interested in is printed early on, chances are you won't be able to see it anyway.

The matches function is equally verbose. Unfortunately there's no way to turn that verbosity off. If you've got a lot of facts matching some pattern in a rule they will all be printed to the shell and if you've got a lot of patterns with a lot of matching facts you're not going to be able to inspect all of the output in the shell. Luckily you can use the dribble-on/off functions (see chapter 13.2.1 Generating Trace Files in the BPG) to copy output to a file. That way, you can use your favorite text editor to inspect the output instead.

I should also mention breakpoints and set-break (see chapter 13.6.5 Setting a Breakpoint for a Rule in the BPG). They're great if you need to inspect the application after it has done some processing but before it has finished. You can also use the poor-man's version: (run n) if you're really certain on how many rule firings it takes to build up the problematic state. But I've found that I usually only use that when I'm in an exploratory mood and step through the application using (run 1).

It might take a while to become familiar with debugging in CLIPS but it's well worth the effort.

2008-04-08

The Absolute Minimum Every CLIPS Developer Absolutely, Positively Must Know About the Not-CE and Template-Pattern-Constraints (No Excuses!)

Ok. So I stole the title from Joel's very popular article about Unicode, but it's not just a way to get attention. The point is that there are some things about how CLIPS (and other Rule Engines) handle not that might not be all that obvious to programmers used to languages such as Python or Java.

More specifically, I'm talking about the difference between

|CLIPS> (defrule rule1
| (spam (eggs ?eggs) (bacon ?bacon))
| (test (not (and (eq ?eggs 1) (eq ?bacon 2))))
| =>)
and
|CLIPS> (defrule rule2
| (not (spam (eggs 1) (bacon 2))
| =>)
You're not alone if you've ever made the mistake of thinking that wrapping a template-pattern-CE with a not would negate the constraints inside it. That's, however, not what the Not-CE does.

A logical not is the simplest possible operator. It simply returns True if the expression it applies to returns False and vice versa. In CLIPS, it's easy enough to figure out what expression a not applies to, but you might be surprised at how that expression is evaluated?

The Not-CE evaluates to True if there are no facts in Working Memory matching the pattern it applies to. You should think of not as short hand for Not-in-Working-Memory and not as a Not-Equals variant. I know it's not at all that obvious since there are no hints (compare Java's !=) but it should make it's behaviour less of a surprise.

2008-04-02

LISA, Multislots and thoughts about Pattern matching

LISA has no multislots. That's just the way it is. You can add a list to a slot, but you have to match it as-is unless you provide a custom test-function.

About a week ago, Pedro Kröger announced on the Lisa Users mailing list that he was converting the Zebra puzzle from CLIPS to LISA. The rule syntax is so similar between the engines that it ought to have been a quite straightforward and simple exercise. But, since the CLIPS solution uses implied deftemplates (which are implemented as multislots) the LISA program wouldn't work.

This, and many other situations like it, has made me wonder about why nested fact structures aren't supported. Matching, on any level in a nested list, is actually described in Forgy's thesis (from 1979) but hasn't, for some reason, been implemented in most rule engines. Probably because it's faster to only allow flat structures.

The Rete implementation described in the thesis constructs an awful lot of nodes but I'm not sure you need all of them. A rule compiler ought to be able to figure out which part of a structure to test without doing too many feature-tests ("is the second subelement a list of 3 subelements" etc).

2008-03-29

Utility for working with CSV-files in CLIPS

A couple of months ago I wrote a CLIPS function that could load and assert data from a CSV-file. It was simple but it worked well for my needs. When I started working on The CLIPS Cookbook a couple of weeks ago, a CSV-utility was one of the first things I thought about. Unfortunately, it turned out to be more difficult than I thought to produce an RFC4180 compliant parser so the recipe has remained a placeholder. But, it won't much longer.

Yesterday, I managed to get an implementation to work such that it can handle both import and export from and to CSV-files. I've still got some polishing to do but here's a short introduction to using it.

Assuming we've got a set of link-facts in Working Memory:

CLIPS> (facts)
f-0 (initial-fact)
f-1 (link (title "CLIPSESG") (url "http://groups.google.com/group/CLIPSESG"))
f-2 (link (title "CLIPS") (url "http://clipsrules.sourceforge.net/"))
For a total of 3 facts.
We can export them to a CSV-file using the function CSV-export:
CLIPS> (load "csv.clp")
:!!!!
TRUE
CLIPS> (CSV-export "links.csv" link)
2
The file links.csv looks like this afterwards:
title,url
"CLIPSESG","http://groups.google.com/group/CLIPSESG"
"CLIPS","http://clipsrules.sourceforge.net/"
And if we instead want to load the link-facts from file we can use CSV-import:
CLIPS> (load "csv.clp")
:!!!!
TRUE
CLIPS> (CSV-import "links.csv" link)
2
CLIPS> (facts)
f-0 (initial-fact)
f-1 (link (title "CLIPSESG") (url "http://groups.google.com/group/CLIPSESG"))
f-2 (link (title "CLIPS") (url "http://clipsrules.sourceforge.net/"))
For a total of 3 facts.
Both functions take the filename and the deftemplate as parameters. You can also specify which slots should be exported/imported but that's really not neccessary. However, the first row of the CSV-file must contain slot names (column headers) if you import facts without specifying slots.

2008-03-14

ANN: CLIPS Cookbook

The CLIPS Cookbook is now available at http://clipsrules.wiki.sourceforge.net/.

At this point it only includes 5 recipes but I hope that, with a little help and given time, it will grow into a valuable resource as a complement to the CLIPS documentation.

The Cookbook uses a public wiki which means that anyone can, and is encouraged to, add new or improve existing recipes. I will try to, periodically, add recipes based on what people ask about in CLIPSESG but you don't have to wait for me or anyone else if you think something is missing. Just add it. If you don't feel comfortable writing a recipe yourself, please add a page for it anyway and indicate that you want someone to help you write it.

All standard expectations of good wiki-behaviour applies so please behave.

2008-02-12

Another backward chaining example from Jess In Action

This is the multi-level backward chaining example from Jess In Action. My implementation of backward chaining had some problems with the original example so here is a new version of the shell that does the job.

CLIPS[001/01]> (deffunction fetch-price-from-database (?number)
CLIPS[001/02]: (if (eq ?number 1)
CLIPS[001/03]: then (return $1.99))
CLIPS[001/04]: (return nil))
CLIPS[002/01]> (deffunction fetch-number-from-database (?name)
CLIPS[002/02]: (if (eq ?name waffles)
CLIPS[002/03]: then (return 1))
CLIPS[002/04]: (return 0))
CLIPS[003/01]> (defrule price-check
CLIPS[003/02]: (do-price-check ?name)
CLIPS[003/03]: (price ?name ?price)
CLIPS[003/04]: =>
CLIPS[003/05]: (printout t "Price of " ?name " is " ?price crlf))
CLIPS[004/01]> (defrule find-price
CLIPS[004/02]: (need-price ?name ?)
CLIPS[004/03]: (item-number ?name ?number)
CLIPS[004/04]: =>
CLIPS[004/05]: (bind ?price (fetch-price-from-database ?number))
CLIPS[004/06]: (assert (price ?name ?price)))
CLIPS[005/01]> (defrule find-item-number
CLIPS[005/02]: (need-item-number ?name ?)
CLIPS[005/03]: =>
CLIPS[005/04]: (bind ?number (fetch-number-from-database ?name))
CLIPS[005/05]: (assert (item-number ?name ?number)))
CLIPS[006/01]> (do-backward-chaining item-number)
; + MAIN::find-price/0
nil
CLIPS[007/01]> (do-backward-chaining price)
; + MAIN::price-check/0
nil
CLIPS[008/01]> (ppdefrule price-check/0)
(defrule MAIN::price-check/0
(do-price-check ?name)
=>
(assert (need-price ?name nil)))
CLIPS[009/01]> (ppdefrule find-price/0)
(defrule MAIN::find-price/0
(need-price ?name ?)
=>
(assert (need-item-number ?name nil)))
CLIPS[010/01]> (reset)
CLIPS[011/01]> (assert (do-price-check waffles))
CLIPS[012/01]> (watch rules)
CLIPS[013/01]> (run)
FIRE 1 price-check/0: f-1
FIRE 2 find-price/0: f-2
FIRE 3 find-item-number: f-3
FIRE 4 find-price: f-2,f-4
FIRE 5 price-check: f-1,f-5
Price of waffles is $1.99
CLIPS[014/01]> (facts)
f-0 (initial-fact)
f-1 (do-price-check waffles)
f-2 (need-price waffles nil)
f-3 (need-item-number waffles nil)
f-4 (item-number waffles 1)
f-5 (price waffles $1.99)
For a total of 6 facts.
CLIPS[015/01]>

2008-02-05

Implementing backward chaining in PyCLIPS II.

In my previous post I described a proof-of-concept implementation for backward chaining in PyCLIPS. One of the drawbacks was that it doesn't work on deftemplates.

Consider the factorial example again:

CLIPS[001/01]> (deftemplate factorial
CLIPS[001/02]: (slot v)
CLIPS[001/03]: (slot r))
CLIPS[002/01]> (defrule print-factorial-10
CLIPS[002/02]: (factorial (v 10) (r ?r1))
CLIPS[002/03]: =>
CLIPS[002/04]: (printout t "The factorial of 10 is " ?r1 crlf))
CLIPS[003/01]> (defrule do-factorial
CLIPS[003/02]: (need-factorial (v ?x) (r nil))
CLIPS[003/03]: =>
CLIPS[003/04]: (bind ?r 1)
CLIPS[003/05]: (bind ?n ?x)
CLIPS[003/06]: (while (> ?n 1)
CLIPS[003/07]: (bind ?r (* ?r ?n))
CLIPS[003/08]: (bind ?n (- ?n 1)))
CLIPS[003/09]: (assert (factorial (v ?x) (r ?r))))
[PYCLIPS] C09: unable to understand argument
[PRNTUTIL2] Syntax Error: Check appropriate syntax for defrule.
ERROR:
(defrule MAIN::do-factorial
(need-factorial (
CLIPS[004/01]>
The problem is that the CE that matches need-factorial requires a deftemplate and we haven't provided one. The "solution" is to have the do-backward-chaining function define the neccessary deftemplate dynamically. So all you need to think of is that you must either call do-backward-chaining or define the appropriate need-template before defining any rules that try to match it.
CLIPS[001/01]> (deftemplate factorial
CLIPS[001/02]: (slot v)
CLIPS[001/03]: (slot r))
CLIPS[002/01]> (defrule print-factorial-10
CLIPS[002/02]: (factorial (v 10) (r ?r1))
CLIPS[002/03]: =>
CLIPS[002/04]: (printout t "The factorial of 10 is " ?r1 crlf))
CLIPS[003/01]> (do-backward-chaining factorial)
; + MAIN::need-factorial
; + MAIN::print-factorial-10/0
nil
CLIPS[004/01]> (defrule do-factorial
CLIPS[004/02]: (need-factorial (v ?x) (r nil))
CLIPS[004/03]: =>
CLIPS[004/04]: (bind ?r 1)
CLIPS[004/05]: (bind ?n ?x)
CLIPS[004/06]: (while (> ?n 1)
CLIPS[004/07]: (bind ?r (* ?r ?n))
CLIPS[004/08]: (bind ?n (- ?n 1)))
CLIPS[004/09]: (assert (factorial (v ?x) (r ?r))))
CLIPS[005/01]> (reset)
CLIPS[006/01]> (run)
The factorial of 10 is 3628800
CLIPS[007/01]>
The code is here, if you're interested. I'm not saying it's done or anything and I should probably have cleaned it up a bit more but I've been battling a cold for two weeks now and there are tons of other interesting things to try out. So this will have to do, at least for now.

2008-01-27

Implementing backward chaining in PyCLIPS.

I have been having an interesting e-mail conversation with Francesco about PyCLIPS this last week. PyCLIPS is mostly geared towards adding CLIPS to Python programs but it can also be used to add Python functionality to CLIPS. I've been urging him to add his Shell utility to the main package. The reason is because I think it would make a brilliant starting point for extensions. Regular expressions, backward chaining, database access and GUI capabilities are all good examples of what could be done.

I've done a little proof-of-concept implementation of backward chaining to show the idea and to provide a basis for discussion of these kinds of things. There are still a lot of things to do if this experiment is ever going to be useful and it might not even be possible to reach decent functionality (but it might be worth exploring further?). However, there are ways around most things when you control the dialog window ;-)

The general idea of the experiment is to implement something similar to how Jess handles backward chaining. Since there's no way to extend the syntax of CLIPS I can't add an explicit-CE and instead we'll have to make do with an explicit function.

> (deffunction MAIN::explicit ($?rules)
> (progn$ (?rule $?rules)
> (python-call explicit ?rule)))
I'll explain how it works after we've looked at do-backward-chaining and talked about some of the Python code.
> (deffunction MAIN::do-backward-chaining ($?templates)
> (progn$ (?template $?templates)
> (python-call do-backward-chaining ?template)))
I'm using Francesco's shell.py script as a basis for this experiment. I've modified it slightly though. When loaded it defines the above methods and attaches them to their Python counterpart using:
_cm.RegisterPythonFunction(do_backward_chaining, "do-backward-chaining")
_cm.RegisterPythonFunction(explicit, "explicit")
I've also made it such that whenever a user enters "(clear)" in the console the above functions are re-defined. I haven't found a good way of hooking into the clear command yet so if it's executed from a batch, this little hack won't do any good.

OK, let's see how it "works". Here's the factorial example from Jess In Action:
CLIPS[1/1]> (defrule print-factorial-10
CLIPS[1/2]: (factorial 10 ?r1)
CLIPS[1/3]: =>
CLIPS[1/4]: (printout t "The factorial of 10 is " ?r1 crlf))
CLIPS[2/1]> (defrule do-factorial
CLIPS[2/2]: (need-factorial ?x ?)
CLIPS[2/3]: =>
CLIPS[2/4]: (bind ?r 1)
CLIPS[2/5]: (bind ?n ?x)
CLIPS[2/6]: (while (> ?n 1)
CLIPS[2/7]: (bind ?r (* ?r ?n))
CLIPS[2/8]: (bind ?n (- ?n 1)))
CLIPS[2/9]: (assert (factorial ?x ?r)))
CLIPS[3/1]> (do-backward-chaining factorial)
; + rule MAIN::print-factorial-10/0
nil
CLIPS[4/1]> (reset)
CLIPS[5/1]> (run)
The factorial of 10 is 3628800
CLIPS[6/1]>
The rules themselves are really not that interesting. What's interesting is that none of them ought to have been activated. And the reason they are is because of the function call to do-backward-chaining. We get a hint at what it has done on the line that says
; + rule MAIN::print-factorial-10/0
What happens is that the do-backward-chaining function in CLIPS calls the do_backward_chaining function in Python which searches through all rules (in all modules) for CEs that match
r"^\s*?\(%s\b(.*?)\)$" % (template)
(Yes. It's a Regular Expression. I know it's a bit hackish to do introspection work based on ppdefrule calls but it's not like there's any other option. At least not any that I can think of. If you know of a better way please let me know!)

If it finds a CE that matches, in our case (factorial 10 ?r1) in print-factorial-10, it generates a new rule based on the one containing the match. It adds a "/0" to the name (making it print-factorial-10/0), copies the LHS but removes the matching CE and constructs an RHS by taking the matching CE with a few modifications and placing it in an assert statement. The modifications it does is adding "need-" to the template name and replacing all variables with nil. We can see the result with
CLIPS[6/1]> (ppdefrule print-factorial-10/0)
(defrule MAIN::print-factorial-10/0
=>
(assert (need-factorial 10 nil)))
CLIPS[7/1]>
So, now it's easy to see why we got a result after (reset) and (run). Here's the dialog again, with facts and rules watched.
CLIPS[7/1]> (watch facts)
CLIPS[8/1]> (watch rules)
CLIPS[9/1]> (reset)
<== f-0 (initial-fact)
<== f-1 (need-factorial 10 nil)
<== f-2 (factorial 10 3628800)
==> f-0 (initial-fact)
CLIPS[10/1]> (run)
FIRE 1 print-factorial-10/0: f-0
==> f-1 (need-factorial 10 nil)
FIRE 2 do-factorial: f-1
==> f-2 (factorial 10 3628800)
FIRE 3 print-factorial-10: f-2
The factorial of 10 is 3628800
CLIPS[11/1]>
This little hack does unfortunately not work with deftemplates unless you also define a need-deftemplate as an exact copy. And, that just doesn't feel right. Also, if you've got a connected constraint in your CE, it will be ignored. I haven't been able to figure out a good way to communicate that type of constraint to the rule matching the need-fact so if you've got one please let me know.

When it comes to explicit, I've tried to keep it as simple as possible. I keep a list of all generated rules and when you execute
CLIPS[11/1]> (explicit print-factorial-10)
; - rule MAIN::print-factorial-10/0
any generated rule for print-factorial-10 is taken away (undefruled).
CLIPS[12/1]> (reset)
<== f-0 (initial-fact)
<== f-1 (need-factorial 10 nil)
<== f-2 (factorial 10 3628800)
==> f-0 (initial-fact)
CLIPS[13/1]> (run)
CLIPS[14/1]>
and that's it really.

That's all it does (for now at least). It's simple and it's not very elegant, but it works. I won't publish the code (yet) because I'm still trying to clean it up. Trying to make it "easier" to build on. I'll share with anyone interested though (send me an e-mail). But I would like to hear if this is a stupid idea in general or if I've done something wrong somewhere. Is there something I havent thought of that might pop up later and turn out to be near impossible to fix?

2008-01-21

SudokuDemo.wx.py

About a week later than I had hoped I've finally managed to finish the Sudoku demo from ClipsJNI. I've tried staying as close to the Java version as possible but there are a few differences. For example, I've added an Open File-button that allows you to load a puzzle from a text file. I've included a few sample puzzles in the zip. The format of the puzzle must be exactly as in the examples: 9 lines of 9 characters (and a newline) each. Characters "1", "2", "3" to "9" are added as-is, all other characters are "added" as blanks (I use "0" in my examples).



I've still got some work to do before I upload this as a pyClips example on SourceForge. I really want to clean up the code and try to make it as easy to understand as possible. I also intend to write a short introduction to using wxPython and pyClips together.

The code is here if you want to have a premature look. If you find any bugs or peculiar behaviour please let me know. Enjoy!