Please Make a Donation:
Support This Project

Hosted by:
Get Python Knowledge Engine (PyKE) at Fast, secure and Free Open Source software downloads

Plans and Automatic Program Generation

Once you understand how backward-chaining works, it is relatively easy to do automatic program generation.

Adding Plans to Backward-Chaining Rules

The way this is done is by attaching Python functions to backward-chaining rules. These functions are written in the with clause at the end of each rule in the .krb file. They don't affect how the rules run to prove a goal, but are gathered up to form a call graph that is returned along with the pattern variable bindings that prove the top-level goal.


Consider a small rule base to construct programs to transfer money between bank accounts. Each from_acct and to_acct takes one of two forms:

  1. (name, account_type)
    • This is a local account with this bank.
    • Example: ('bruce', 'checking').
  2. (bank, name, account_type)
    • This is a foreign account with another bank.
    • Example: ('my_other_bank', 'bruce', 'checking').

At least one of the bank accounts must be a local account.

Here's the example rule base:

 1  transfer1
 2      use transfer($from_acct, $to_acct) taking (amount)
 3      when
 4          withdraw($from_acct)
 5              $$(amount)
 6          deposit($to_acct)
 7              $$(amount)

 8  transfer2
 9      use transfer($from_acct, $to_acct) taking (amount)
10      when
11          transfer_ach($from_acct, $to_acct)
12              $$(amount)

13  withdraw
14      use withdraw(($who, $acct_type)) taking (amount)
15      with
16          print "withdraw", amount, "from", $who, $acct_type

17  deposit
18      use deposit(($who, $acct_type)) taking (amount)
19      with
20          print "deposit", amount, "to", $who, $acct_type

21  transfer_ach1
22      use transfer_ach($from_acct, ($bank, $who, $acct_type)) taking (amount)
23      when
24          withdraw($from_acct)
25              $$(amount)
26          deposit((central_accts, ach_send_acct))
27              $$(amount)
28      with
29          print "send", amount, "to bank", $bank, "acct", $who, $acct_type

30  transfer_ach2
31      use transfer_ach($from_acct, $to_acct) taking (amount)
32      when
33          get_ach($from_acct)
34              $$(amount)
35          withdraw((central_accts, ach_recv_acct))
36              $$(amount)
37          deposit($to_acct)
38              $$(amount)

39  get_ach
40      use get_ach(($bank, $who, $acct_type)) taking (amount)
41      with
42          print "get", amount, "from bank", $bank, "acct", $who, $acct_type

How the Plan Functions are Generated for This Example

Each of these rules will have a plan function generated for it. These plan functions are generated with the same name as the rule name. Thus, the name of the generated Python plan function for the first rule would be "transfer1".

The plan function generated for the first rule consists of two lines taken from lines 5 and 7 of this example. The $$ in each of these lines will be expanded to the subordinate plan function returned from the proof of "withdraw($from_acct)" and "deposit($to_acct)" respectfully. The generated plan function will be defined to take an "amount" parameter because of the taking clause on line 2. This parameter is passed on to each of the subordinate plan functions in lines 5 and 7.

The plan function generated for the "withdraw" rule on line 13 will have the single line taken from line 16 in the with clause. The "$who" and "$acct_type" pattern variables will be expanded to constant values taken from the values bound to these pattern variables after the top-level (transfer) goal has been proven.

Finally, the plan function generated for the "transfer_ach1" rule on line 21 will have three lines: two from the when clause (lines 25 and 27) followed by one from the with clause (line 29). These lines will be generated at the same indent level in the plan function even though they are at different indent levels in the .krb file.

For more detailed information about the options available for plans in the .krb file, see Bc_rule Syntax.

Running the Example

The plan is created as a byproduct of proving the goal:

>>> from pyke import knowledge_engine
>>> engine = knowledge_engine.engine(__file__)
>>> engine.activate('plan_example')
>>> no_vars, plan1 = \
...   engine.prove_1_goal(
...     'plan_example.transfer((bruce, checking), (bruce, savings))')

plan1 is now a program to transfer X amount from 'bruce', 'checking' to 'bruce', 'savings'. Using the above rule names as function names, plan1 looks like this:



And can be called like a standard function, passing the parameters specified in the taking clause of the rules for the top-level goal (transfer):

>>> plan1(100)
withdraw 100 from bruce checking
deposit 100 to bruce savings

The program may be used multiple times:

>>> plan1(50)
withdraw 50 from bruce checking
deposit 50 to bruce savings

Notice the strings: bruce, checking and savings in the output. These were specified as pattern variables in the code and are cooked into the plan along with the function call graph.

Let's create a second program:

>>> no_vars, plan2 = \
...   engine.prove_1_goal(
...     'plan_example.transfer((bruce, checking), '
...                           '(my_other_bank, bruce, savings))')

plan2 is now a program to transfer X amount from 'my_other_bank', 'bruce', 'checking' to 'bruce', 'savings'. Plan2 looks like this:



And is run just like plan1, but produces different results:

>>> plan2(200)
withdraw 200 from bruce checking
deposit 200 to central_accts ach_send_acct
send 200 to bank my_other_bank acct bruce savings

And the final use case:

>>> no_vars, plan3 = \
...   engine.prove_1_goal(
...     'plan_example.transfer((my_other_bank, bruce, checking), '
...                           '(bruce, savings))')
>>> plan3(150)
get 150 from bank my_other_bank acct bruce checking
withdraw 150 from central_accts ach_recv_acct
deposit 150 to bruce savings

Plan3 looks like this:



Note that the same transfer2 function is calling two different functions (transfer_ach1 and transfer_ach2) in plan2 and plan3. This shows how different functions may be chosen based on the rule inferencing. Also note that after the generation of plan3, plan2 is still valid; both may still be called successfully, resulting in different calls from the initial transfer2 function.


So you can see that it quite easy to use Pyke to automatically combine Python functions into programs!

It also allows data within each Python function to be specified using a pattern variable so that Pyke can customize these values to match the specific situation.

If you would like to know more about how Pyke cooks (or customizes) your Python functions, see Cooking Functions.



What is a statement in Pyke?

Pattern Matching

Explanation of pattern matching and pattern variables.


Explanation of rules, forward-chaining and backward-chaining.

Plans and Automatic Program Generation

Explanation of plans and automatic program generation.

Page last modified Fri, Mar 05 2010.