Plans and Automatic Program Generation

# 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. ## Example 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: (name, account_type) This is a local account with this bank. Example: ('bruce', 'checking'). (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: Plan1 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: Plan2 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: Plan3 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. ## Conclusion 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.
Copyright © 2007-2009 Bruce Frederiksen

Page last modified Wed, Mar 10 2010.