Please Make a Donation:
Support This Project

Hosted by:
SourceForge.net Logo

Using Pyke

This describes how to use pyke from within your python program.

Initializing Pyke

There are two steps to initializing a pyke knowledge engine:

knowledge_engine.engine(paths = ('.',), gen_dir = '.', gen_root_dir = 'compiled_krb', load_fc = True, load_bc = True)

The pyke inference engine is offered as a class so that you can instantiate multiple copies of it with different rule bases to accomplish different tasks. Once you have a knowledge_engine.engine object; generally, all of the functions that you need are provided directly by this object:

>>> from pyke import knowledge_engine
>>> my_engine = knowledge_engine.engine('examples')

This expects either a single directory or a sequence of directories as the paths argument. It recursively walks each directory looking for .krb files. Each .krb file that it finds is compiled, if out of date, and then the resulting python modules imported (depending on load_fc and load_bc). This causes all of the rule bases to be loaded and made ready to activate (see below).

All generated python files are placed in a mirror directory structure under the gen_root_dir directory in gen_dir. Thus, by default, this mirrored directory structure would be rooted under the "./compiled_krb" directory. You probably want to add compiled_krb to your subversion global-ignores option. Gen_dir, gen_root_dir and the mirrored directory structure will be created automatically if any of them do not already exist.

If you change some of the .krb files, you can create a new engine object to compile and reload the generated python modules without restarting your program. But note that you'll need to rerun your add_universal_fact calls.

some_engine.add_universal_fact(kb_name, fact_name, arguments)

The add_universal_fact function is called once per fact. These facts are never deleted and apply to all cases.

>>> my_engine.add_universal_fact('family', 'son_of', ('bruce', 'thomas', 'norma'))

Multiple facts with the same name are allowed.

>>> my_engine.add_universal_fact('family', 'son_of', ('david', 'bruce', 'marilyn'))

But duplicate facts (with the same arguments) are silently ignored.

>>> my_engine.add_universal_fact('family', 'son_of', ('david', 'bruce', 'marilyn'))
>>> my_engine.get_kb('family').dump_universal_facts()
son_of('bruce', 'thomas', 'norma')
son_of('david', 'bruce', 'marilyn')

These facts are accessed as kb_name.fact_name(arguments) within the .krb files.

Setting up Each Case

Pyke is designed to be run multiple times for multiple cases. In general each case has its own set of starting facts and may use different rule bases, depending on the situation.

Three functions initialize each case:

some_engine.reset()
The reset function is called once to delete all of the case specific facts from the last run. It also deactivates all rule bases.
some_engine.assert_(kb_name, fact_name, arguments)

Call assert_ (or the equivalent, add_case_specific_fact, see Other Functions, below) for each starting fact for this case. Like universal facts, you may have multiple facts with the same name so long as they have different arguments.

>>> my_engine.assert_('family', 'daughter_of', ('marilyn', 'arthur', 'kathleen'))
>>> my_engine.assert_('family', 'daughter_of', ('sue', 'arthur', 'kathleen'))
>>> my_engine.assert_('family', 'daughter_of', ('sue', 'arthur', 'kathleen'))

Duplicates with universal facts are also ignored.

>>> my_engine.assert_('family', 'son_of', ('bruce', 'thomas', 'norma'))
>>> my_engine.get_kb('family').dump_specific_facts()
daughter_of('marilyn', 'arthur', 'kathleen')
daughter_of('sue', 'arthur', 'kathleen')
>>> my_engine.get_kb('family').dump_universal_facts()
son_of('bruce', 'thomas', 'norma')
son_of('david', 'bruce', 'marilyn')

There is no difference within the .krb files of how universal facts verses specific facts are used. The only difference between the two types of facts is that the specific facts are deleted when a reset is done.

>>> my_engine.reset()
>>> my_engine.get_kb('family').dump_specific_facts()
>>> my_engine.get_kb('family').dump_universal_facts()
son_of('bruce', 'thomas', 'norma')
son_of('david', 'bruce', 'marilyn')
some_engine.activate(*rb_names)

Then call activate to activate the appropriate rule bases. This may be called more than once, if desired, or it can simply take multiple arguments.

>>> my_engine.activate('bc_example')

Your pyke engine is now ready to prove goals for this case!

Proving Goals

Two functions are provided that cover the easy cases. More general functions are provided in Other Functions, below.

some_engine.prove_1(kb_name, entity_name, fixed_args, num_returns)

Kb_name may name either a fact_base or an activated rule base category. The entity_name is the fact_name for fact_bases, or the name of the backward chaining goal for rule bases. The fixed_args are a tuple of python values. These form the first group of arguments to the proof. Num_returns specifies the number of additional pattern variables to be appended to the arguments for the proof. The bindings of these pattern variables will be returned as the answer for the proof. For example:

some_engine.prove_1(some_rule_base_category, some_goal, (1, 2, 3), 2)

Proves the goal:

some_rule_base_category.some_goal (1, 2, 3, $ans_0, $ans_1)

And will return the bindings for $ans_0 and $ans_1 produced by the proof.

Returns the first proof found as a 2-tuple: a tuple of the bindings for the num_returns pattern variables, and a plan. The plan is None if no plan was generated; otherwise, it is a python function as described below.

>>> my_engine.prove_1('bc_example', 'child_parent', ('david', 'norma'), 3)
((('grand',), 'son', 'mother'), None)

Raises knowledge_engine.CanNotProve if no proof is found.

>>> my_engine.prove_1('bc_example', 'bogus', ('david', 'norma'), 3)
Traceback (most recent call last):
    ...
CanNotProve: Can not prove bc_example.bogus(david, norma, $ans_0, $ans_1, $ans_2)
some_engine.prove_n(kb_name, entity_name, fixed_args, num_returns)

This is a generator yielding 2-tuples, a tuple whose length == num_returns and a plan, for each possible proof. Like prove_1, the plan is None if no plan was generated.

>>> for ans in my_engine.prove_n('bc_example', 'child_parent', ('david',), 4):
...     print ans
(('bruce', (), 'son', 'father'), None)
(('marilyn', (), 'son', 'mother'), None)
(('thomas', ('grand',), 'son', 'father'), None)
(('norma', ('grand',), 'son', 'mother'), None)

Running and Pickling Plans

Once you've obtained a plan from prove_1 or prove_n, you just call it like a normal python function. The arguments required are simply those specified, if any, in the taking clause of the rule proving the top-level goal.

You may call the plan function any number of times. You may even pickle the plan for later use. But the plans are constructed out of functools.partial functions, so you need to register this with copy_reg before pickling the plan:

>>> import copy_reg
>>> import functools
>>> copy_reg.pickle(functools.partial,
...                 lambda p: (functools.partial, (p.func,) + p.args))

No special code is required to unpickle a plan. Also, the program that unpickles the plan does not have to import any pyke modules to be able to run the plan. Just unpickle and call it.

Tracing Rules

Individual rules may be traced to aid in debugging. The trace function takes two arguments: the rule base name, and the name of the rule to trace:

>>> my_engine.trace('bc_example', 'grand_parent_and_child')
>>> my_engine.prove_1('bc_example', 'child_parent', ('david', 'norma'), 3)
bc_example.grand_parent_and_child('david', 'norma', '$ans_0', '$ans_1', '$ans_2')
bc_example.grand_parent_and_child succeeded with ('david', 'norma', ('grand',), 'son', 'mother')
((('grand',), 'son', 'mother'), None)

This can be done either before or after rule base activation and will remain in effect until you call untrace:

>>> my_engine.untrace('bc_example', 'grand_parent_and_child')
>>> my_engine.prove_1('bc_example', 'child_parent', ('david', 'norma'), 3)
((('grand',), 'son', 'mother'), None)

Krb_traceback

A handy traceback module is provided to convert python functions, lines and line numbers to the .krb file rule names, lines and line numbers in python traceback. This makes it much easier to read the tracebacks that occur during proofs.

The krb_traceback module has exactly the same functions as the standard python traceback module, but they convert the generated python function information into .krb file information. They also delete the intervening python functions between subgoal proofs.

>>> import sys
>>> from pyke import knowledge_engine
>>> from pyke import krb_traceback
>>>
>>> my_engine = knowledge_engine.engine('examples')
>>> my_engine.activate('error_test')
>>> try:
...     my_engine.prove_1('error_test', 'goal', (), 0)
... except:
...     krb_traceback.print_exc(None, sys.stdout)    # sys.stdout needed for doctest
Traceback (most recent call last):
  File "<doctest using_pyke.txt[31]>", line 2, in <module>
    my_engine.prove_1('error_test', 'goal', (), 0)
  File "/home/bruce/python/workareas/sf.trunk/pyke/knowledge_engine.py", line 142, in prove_1
    return self.prove_n(kb_name, entity_name, fixed_args, num_returns) \
  File "/home/bruce/python/workareas/sf.trunk/pyke/knowledge_engine.py", line 129, in prove_n
    for arg in fixed_args) + vars):
  File "/home/bruce/python/workareas/sf.trunk/pyke/rule_base.py", line 37, in next
    return self.iterator.next()
  File "/home/bruce/python/workareas/sf.trunk/pyke/tmp_itertools.py", line 32, in chain
    for x in iterable: yield x
  File "/home/bruce/python/workareas/sf.trunk/pyke/tmp_itertools.py", line 32, in chain
    for x in iterable: yield x
  File "/home/bruce/python/workareas/sf.trunk/doc/examples/error_test.krb", line 26, in rule1
    goal2()
  File "/home/bruce/python/workareas/sf.trunk/doc/examples/error_test.krb", line 31, in rule2
    goal3()
  File "/home/bruce/python/workareas/sf.trunk/doc/examples/error_test.krb", line 36, in rule3
    goal4()
  File "/home/bruce/python/workareas/sf.trunk/doc/examples/error_test.krb", line 41, in rule4
    check $bar > 0
  File "/home/bruce/python/workareas/sf.trunk/pyke/contexts.py", line 224, in lookup_data
    raise KeyError("$%s not bound" % var_name)
KeyError: '$bar not bound'

Other Functions

There are a few more functions that may be useful in special situations.

The first two of these provide more general access to the fact lookup and goal proof mechanisms. The catch is that you must first convert all arguments into patterns and create a context for these patterns. This is discussed below.

some_engine.lookup(kb_name, entity_name, pattern_context, patterns)
This is a generator that binds patterns to successive facts. Yields None for each successful match.
some_engine.prove(kb_name, entity_name, pattern_context, patterns)
A generator that binds patterns to successive proofs. Yields a prototype_plan or None for each successful match. To turn the prototype_plan into a python function, use prototype_plan.create_plan(). This returns the plan function.

The remaining functions are:

some_engine.add_case_specific_fact(kb_name, fact_name, args)
This is an alternate to the assert_ function.
some_engine.get_kb(kb_name)
Finds and returns the knowledge base by the name kb_name. Raises KeyError if not found. Note that for rule bases, this returns the active rule base where kb_name is the rule base category name. Thus, not all rule bases are accessible through this call.
some_engine.get_rb(rb_name)
Finds and returns the rule base by the name rb_name. Raises KeyError if not found. This works for any rule base, whether it is active or not.
some_engine.print_stats([f = sys.stdout])
Prints a brief set of statistics for each knowledge base to file f. These are reset by the reset function. This will show how many facts were asserted, and counts of how many forward-chaining rules were triggered and rerun, as well as counts of how many backward-chaining goals were tried, and how many backward-chaining rules matched, succeeded and failed. Note that one backward-chaining rule may succeed many times through backtracking.

Creating Your Own Patterns

You'll need two more pyke modules to create your own patterns and contexts:

>>> from pyke import pattern, contexts

There are four kinds of patterns:

pattern.pattern_literal(data)
This matches the data provided.
pattern.pattern_tuple((elements), rest_var = None)
This matches a tuple. Elements must each be a pattern and must match the first n elements of the tuple. Rest_var must be a variable (or anonymous). It will match the rest of the tuple and is always bound to a (possibly empty) tuple.
contexts.variable(name)
This will match anything the first time it is encountered. But then must match the first value each additional time it is encountered. Calling the constructor twice with the same name produces the same variable and must match the same value in all of the places that it is used.
contexts.anonymous()
This will match anything each time it is encountered. Calling the constructor many times is not a problem.

Finally, to create a pattern context, you need:

contexts.simple_context()

You'll need to save this context to lookup your variable values after each proof is yielded. This is done by either:

some_context.lookup_data(variable_name)
some_variable.as_data(some_context)

More:

Overview

The big picture of what all this knowledge-base, inferencing and automatic program generation stuff means and how it works.

Installing Pyke

System Requirements and installing pyke.

Using Pyke

How your python program uses pyke.

KRB Syntax

Syntax of the knowledge rule base (KRB) files, which is where you write your rules.

Logic Tricks

How to accomplish different logic operations with rules.

Page last modified Wed, Mar 05 2008.