  # Using Pyke This describes how to call Pyke from within your Python program. ## Initializing Pyke There are two steps to initializing a Pyke knowledge engine: knowledge_engine.engine(*paths, **kws) 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('doc.examples')
  Pyke requires that all Pyke source files be placed (either directly or indirectly) under a Python package directory. These packages must be on Python's search path for modules, and the directory for each dotted name must include an `__init__.py` file. Each `paths` argument represents a different Python package directory to search for Pyke source files. These source packages may be passed either as package names (as a string in Python's standard dotted notation), as the packages themselves, or as any module within the package. Thus, the example above could also be written:
      >>> from doc import examples
      >>> my_engine = knowledge_engine.engine(examples)
  or
      >>> from doc.examples import some_module
      >>> my_engine = knowledge_engine.engine(some_module)
  Pyke recursively walks each source package 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 the keyword arguments, see below). This causes all of the rule bases to be loaded and made ready to activate. 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. Note It is OK for your source package directory to be part of a zipped Python egg file. But, in this case, Pyke can't rebuild and recompile the source files. It will just import the compiled file that is there without complaining if the source file has a later modification time than the compiled file. All of the Python source files and pickle files generated from each source package are placed, by default, in a `compiled_krb` package directly subordinate to that source package. You may specify a different destination package for any source package by passing that source package along with the destination package name as a 2-tuple. The destination package name may be specified as an absolute Python module path; or as a relative path (with leading dots), relative to the source package. Thus, specifying the default destination explicitly would look like:
      >>> my_engine = knowledge_engine.engine(('doc.examples', '.compiled_krb'))
  You may specify the same destination package for multiple source packages. The last component of the destination package will be created automatically if it does not already exist. You probably want to add `compiled_krb` (or whatever you've chosen to call it) to your source code repository's list of files to ignore. There are four optional keyword arguments that you may also pass to the `engine` constructor: `load_fc` -- load forward-chaining rules `load_bc` -- load backward-chaining rules `load_fb` -- load fact bases and `load_qb` -- load question bases These parameters must be passed as keyword parameters. They all default to `True`. 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 case specific facts are used. The only difference between the two types of facts is that the case 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('doc.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[37]>", line 2, in <module>
>         my_engine.prove_1('error_test', 'goal', (), 0)
>       File "...knowledge_engine.py", line 288, in prove_1
>         return iter(it).next()
>       File "...knowledge_engine.py", line 272, 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)
Copyright © 2007-2009 Bruce Frederiksen

Page last modified Mon, Oct 19 2009.