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 = ('.',), generated_root_pkg = 'compiled_krb', load_fc = True, load_bc = True, load_fb = True, load_qb = 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 Pyke source files (.kfb files, .krb files, and .kqb files). Each source file that it finds is compiled, if out of date, and then imported (depending on load_fc, load_bc, load_fb and load_qb). This causes all of the rule bases to be loaded and made ready to activate.

All generated Python source files and pickle files are placed in the generated_root_pkg. By default, this is the package "compiled_krb" in the program's current working directory. The generated_root_pkg may be a dotted module path. In this case, the module path must be on Python's search path for modules.

The last component of the generated_root_pkg will be created automatically if it does not already exist.

You probably want to add compiled_krb to your subversion global-ignores option.

If you change some of your Pyke source 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 the add_universal_fact calls that you made outside of your .kfb files.

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.

Alternatively, you can place universal facts in a .kfb file so that they are loaded automatically.

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

Multiple facts with the same name are allowed.

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

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

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

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 upon 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', 'son_of', ('michael', 'bruce'))
>>> my_engine.assert_('family', 'son_of', ('fred', 'thomas'))
>>> my_engine.assert_('family', 'son_of', ('fred', 'thomas'))

Duplicates with universal facts are also ignored.

>>> my_engine.assert_('family', 'son_of', ('bruce', 'thomas'))
>>> my_engine.get_kb('family').dump_specific_facts()
son_of('michael', 'bruce')
son_of('fred', 'thomas')
>>> my_engine.get_kb('family').dump_universal_facts()
son_of('bruce', 'thomas')
son_of('david', 'bruce')

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')
son_of('david', 'bruce')
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, question base or rule base category.

The entity_name is the fact name for fact bases, question name for question 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 set of arguments to the proof. Num_returns specifies the number of additional pattern variables to be appended to these arguments for the proof. The bindings of these pattern variables will be returned as a tuple in 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 produced by the proof as ($ans_0, $ans_1).

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', 'father_son', ('thomas', 'david'), 1)
((('grand',),), None)

Raises pyke.knowledge_engine.CanNotProve if no proof is found.

>>> my_engine.prove_1('bc_example', 'father_son', ('thomas', 'bogus'), 1)
Traceback (most recent call last):
    ...
CanNotProve: Can not prove bc_example.father_son(thomas, bogus, $ans_0)
some_engine.prove_n(kb_name, entity_name, fixed_args, num_returns)

This returns a context manager for 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. Unlike prove_1 it does not raise an exception if no proof is found.

>>> from __future__ import with_statement
>>> with my_engine.prove_n('bc_example', 'father_son', ('thomas',), 2) as gen:
...     for ans in gen:
...         print ans
(('bruce', ()), None)
(('david', ('grand',)), 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. Just unpickle and call it. (Unpickling the plan only imports one small Pyke module to be able to run the plan).

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_father_son')
>>> my_engine.prove_1('bc_example', 'father_son', ('thomas', 'david'), 1)
bc_example.grand_father_son('thomas', 'david', '$ans_0')
bc_example.grand_father_son succeeded with ('thomas', 'david', ('grand',))
((('grand',),), 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_father_son')
>>> my_engine.prove_1('bc_example', 'father_son', ('thomas', 'david'), 1)
((('grand',),), 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 a 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:                                            # doctest: +ELLIPSIS
...     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[32]>", line 2, in <module>
    my_engine.prove_1('error_test', 'goal', (), 0)
  File "...knowledge_engine.py", line 234, in prove_1
    return iter(it).next()
  File "...knowledge_engine.py", line 218, in gen
    for plan in it:
  File "...rule_base.py", line 46, in next
    return self.iterator.next()
  File "...knowledge_engine.py", line 40, in from_iterable
    for x in iterable: yield x
  File "...knowledge_engine.py", line 40, in from_iterable
    for x in iterable: yield x
  File "...error_test.krb", line 26, in rule1
    goal2()
  File "...error_test.krb", line 31, in rule2
    goal3()
  File "...error_test.krb", line 36, in rule3
    goal4()
  File "...error_test.krb", line 41, in rule4
    check $bar > 0
  File "...contexts.py", line 227, 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 returns a context manager for a generator that binds patterns to successive facts. Yields None for each successful match.
some_engine.prove(kb_name, entity_name, pattern_context, patterns)
Returns a context manager for 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 fired 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 and becomes bound to that value. After that, it only matches this bound 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(name)
This will match anything each time it is encountered. Calling the constructor many times with the same name is not a problem. The name must start with an underscore.

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:

About Pyke

What pyke does for you, its features, steps to using pyke and installation.

Logic Programming Tutorial

An tutorial on logic programming in Pyke, including statements, pattern matching and rules.

Knowledge Bases

Knowledge is made up of both facts and rules. These are gathered into named repositories called knowledge bases.

Pyke Syntax

The syntax of Pyke's three different kinds of source files.

Using Pyke

How your Python program uses Pyke. I.e., Pyke's API to Python.

Examples

An overview of the examples provided with Pyke.

Applying Expert System Technology to Code Reuse with Pyke

Paper presented at the PyCon 2008 conference in Chicago.

Page last modified Tue, Dec 30 2008.