diff --git a/doc/sphinx/Makefile b/doc/sphinx/Makefile new file mode 100644 index 0000000..d0c3cbf --- /dev/null +++ b/doc/sphinx/Makefile @@ -0,0 +1,20 @@ +# Minimal makefile for Sphinx documentation +# + +# You can set these variables from the command line, and also +# from the environment for the first two. +SPHINXOPTS ?= +SPHINXBUILD ?= sphinx-build +SOURCEDIR = source +BUILDDIR = build + +# Put it first so that "make" without argument is like "make help". +help: + @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) + +.PHONY: help Makefile + +# Catch-all target: route all unknown targets to Sphinx using the new +# "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). +%: Makefile + @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) diff --git a/doc/sphinx/make.bat b/doc/sphinx/make.bat new file mode 100644 index 0000000..9534b01 --- /dev/null +++ b/doc/sphinx/make.bat @@ -0,0 +1,35 @@ +@ECHO OFF + +pushd %~dp0 + +REM Command file for Sphinx documentation + +if "%SPHINXBUILD%" == "" ( + set SPHINXBUILD=sphinx-build +) +set SOURCEDIR=source +set BUILDDIR=build + +if "%1" == "" goto help + +%SPHINXBUILD% >NUL 2>NUL +if errorlevel 9009 ( + echo. + echo.The 'sphinx-build' command was not found. Make sure you have Sphinx + echo.installed, then set the SPHINXBUILD environment variable to point + echo.to the full path of the 'sphinx-build' executable. Alternatively you + echo.may add the Sphinx directory to PATH. + echo. + echo.If you don't have Sphinx installed, grab it from + echo.http://sphinx-doc.org/ + exit /b 1 +) + +%SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% +goto end + +:help +%SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% + +:end +popd diff --git a/doc/sphinx/source/PyCon2008-paper.rst b/doc/sphinx/source/PyCon2008-paper.rst new file mode 100644 index 0000000..1037e18 --- /dev/null +++ b/doc/sphinx/source/PyCon2008-paper.rst @@ -0,0 +1,598 @@ +.. $Id: PyCon2008-paper.txt 057d79259b20 2009-05-14 mtnyogi $ +.. +.. Copyright © 2008 Bruce Frederiksen +.. +.. Permission is hereby granted, free of charge, to any person obtaining a copy +.. of this software and associated documentation files (the "Software"), to deal +.. in the Software without restriction, including without limitation the rights +.. to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +.. copies of the Software, and to permit persons to whom the Software is +.. furnished to do so, subject to the following conditions: +.. +.. The above copyright notice and this permission notice shall be included in +.. all copies or substantial portions of the Software. +.. +.. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +.. IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +.. FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +.. AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +.. LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +.. OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +.. THE SOFTWARE. + +========================================================================= +Applying Expert System Technology to Code Reuse with Pyke +========================================================================= +----------------------------- +PyCon 2008, Chicago +----------------------------- + +.. |bullet| unicode:: U+02022 +.. |copy| unicode:: U+000A9 + +:Author: Bruce Frederiksen +:Date: Fri, 14 Mar 2008 +:Web: pyke.sourceforge.net +:Copyright: |copy| 2008, Bruce Frederiksen + +Abstract +========= + +This paper explores a new approach to code reuse using a backward-chaining +rule-based system, similar to prolog, to generate a function call graph *before* +the functions are called. This is compared with current solutions which build +the call graph *as* the functions are called. + +This approach is introduced through an open source project called Pyke (Python +Knowledge Engine). + +Finally, the initial results show that the utility of this approach far +exceeds expectations; leading to something more akin to automatic +programming rather than adaptable libraries. A call for help is given to +explore the capabilities of this approach across different domains. + +.. contents:: + +The Thinking that Led to Pyke +============================= + +The Need for Code Reuse +~~~~~~~~~~~~~~~~~~~~~~~ + +At one of my contracting jobs, they had many clients running essentially the +same program, but each client needed minor code modifications. Their objective +was to maximize code reuse. + +What is Code Reuse? +~~~~~~~~~~~~~~~~~~~~~ + +The first question is what does "code reuse" mean? And the answer that seems +most logical is *function* reuse. Where code modifications are required, a +new function can be created incorporating those modifications. + +Then the remaining task is to bring the proper collection of functions +together for each client. + +This gets more complicated as several versions of many functions will be +produced for various clients that are all available for reuse by the next +client. So it's not simply the case that there will be one standard default +version of each function, and then several one-off customized versions that +each only apply to a single client. + +The result of this function combination exercise is a function call graph. + +Example 1 +--------- + +Let us imagine that we start out with two functions for client1: + + .. image:: images/PyCon2008/client1.png + :scale: 60 + +And then client2 comes along. + +Let us first suppose that we need a new version of function A, but can reuse +function B\ :sub:`1`: + + .. image:: images/PyCon2008/client2b.png + :scale: 60 + +This is easy in any programming language and leads naturally to the idea that +the functions to reuse are the lower-level ones, which can be placed into +libraries. + +But now let us suppose the opposite; that we need a new version of function B, +but can reuse function A\ :sub:`1`: + + .. image:: images/PyCon2008/client2d.png + :scale: 60 + +This is where we need help. + +Current Solutions +--------------------- + +The current solutions are all run-time solutions that trap the call from +function A\ :sub:`1` to some function B and figure out which function B to use +when the call is made. For example: + +* O-O Dynamic Binding +* Zope Adapters +* Generic Functions + +Current Solution Limitations +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +These solutions are all limited for the same reason. Let's look at another +example to see why. + +Example 2 +---------- + +Real world programs have many more than two functions, but we can start to see +the limitations of the current solutions by looking at a three function +example. + +We start with one client and three functions. + +When client2 was added, it could only share function A\ :sub:`1` and had to +have a new B (B\ :sub:`2`) that needs a new function with a different call +interface than C, so we'll call it D\ :sub:`1`. + +Then along comes client3. This time things are looking up, because all we +need is a new version of function D: + + .. image:: images/PyCon2008/client3d.png + :scale: 60 + +Now let's see what happens when we want to call the program for client3. We +know we need to start with function A\ :sub:`1`, since there is only version of +function A: + + .. image:: images/PyCon2008/client3e.png + :scale: 60 + +But at this point we have two choices for function B. All we know for client3 +is that we're supposed to use function D\ :sub:`2`, so we're left to guess about +function B. So we try the first one, function B\ :sub:`1`: + + .. image:: images/PyCon2008/client3f2.png + :scale: 60 + +It's not until function B\ :sub:`1` tries to call some function C that we +discover a problem. + +This is where the current solutions break down. + +Certainly for this example, it is easy to imagine a developer telling the +binding system: oh yea and client3 is going to have to use function B\ :sub:`2` +as well. But more realistic call graphs are much more complicated than this; +so the developer would have to specify which functions to use going back many +levels. + +And then when there is a change in these upper level shared functions later on, +it will affect the call graphs for many clients. + +So the current solutions don't scale well. + +Continuing on with our example; what we need to do at this point is back up +and try the other B function: + + .. image:: images/PyCon2008/client3g.png + :scale: 60 + +After doing this, we discover the solution for the final call graph: + + .. image:: images/PyCon2008/client3h.png + :scale: 60 + +What's Needed +------------------- + +By looking at this example, we discover two things about how to solve +this problem: + +#. Do function selection **prior** to calling any of the functions. + + We can't wait until one function calls another to figure out what to do, + because we may change our minds! + +#. Use a standard backward-chaining rule-based algorithm. + + The process of first trying function B\ :sub:`1`, then backing up and trying + function B\ :sub:`2` is exactly the process used in backward-chaining + rule-based systems like prolog. They call it *backtracking*. + +Applying Backward-Chaining to Code Reuse +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +The next question is how do we use a backward-chaining system to produce +function call graphs? + +Let's examine, conceptually, what a set of backward-chaining rules would look +like to find a solution to this problem. Then we can determine how to turn +this into a function call graph. + +The following diagram shows *goals* as dotted line boxes around the *rules* +that prove that goal. In this example, some goals only have one rule and some +have two. + +We also see how *rules* link to other *goals*. For example, rule ``Use B1`` and +rule ``Use B2`` both prove the same goal: ``Find B``. But ``Use B1`` links to +the ``Find C`` goal, while ``Use B2`` links to ``Find D``. + + .. image:: images/PyCon2008/bc_rules2.png + :scale: 60 + +Now we can follow how these rules would be run by the knowledge engine: + +* The whole process is kicked off by asking the knowledge engine for a + solution to ``Find A``. +* There is only one rule for ``Find A``: ``Use A1``, so the knowledge engine + tries this rule. +* ``Use A1`` needs a solution to ``Find B``. +* The knowledge engine tries the first rule for ``Find B``: ``Use B1``. +* ``Use B1`` needs a solution to ``Find C``. +* The knowledge engine tries the only rule for ``Find C``: + ``Use C1``, which fails for client3! + +The situation now looks like: + + .. image:: images/PyCon2008/bc_rules5.png + :scale: 60 + +Continuing on: + +* Since there are no other rules for ``Find C``, the ``Find C`` goal fails. +* Which means that the ``Use B1`` rule fails. +* So the knowledge engine tries the next rule for ``Find B``: ``Use B2``. +* ``Use B2`` needs a solution for ``Find D``. +* The knowledge engine tries the first rule for ``Find D``: ``Use D1``, + which fails for client3. +* The knowledge engine tries the next rule for ``Find D``: ``Use D2``, + which succeeds for client3! +* The ``Find D`` goal succeeds. +* The ``Find B`` goal succeeds. +* And the ``Find A`` goal succeeds. + +When we achieve final success, we have the following situation: + + .. image:: images/PyCon2008/bc_rules8.png + :scale: 60 + +What remains is to translate this into a function call graph. + +It becomes obvious that we want to attach our python functions directly to the +backward-chaining rules: + + .. image:: images/PyCon2008/bc_rules9.png + :scale: 60 + +Pyke +===== + +Pyke KRB Syntax +~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +How does all of this look in Pyke? + +Pyke has its own language for rules, which it compiles into python source +modules and then imports. This gives a performance boost by circumventing +nearly all of the inference engine interpretation logic. It also makes it +very easy to embed short python code snippets directly within the rules to +help out with the inferencing. This keeps the inference mechanism simpler as +it does not have to deal with things that are already easy in a procedural +language (like arithmetic and simple list manipulation). + +The Pyke rule source files are called *knowledge rule bases* and have a +``.krb`` suffix. + +We'll continue with the previous example here. + +First, let's look at the rules before we attach the python functions. +Here's three of the rules:: + + use_B2 + use find_B($client) + when + check_function($client, B, 2) + find_D($client) + + use_D1 + use find_D($client) + when + check_function($client, D, 1) + + use_D2 + use find_D($client) + when + check_function($client, D, 2) + +Note that Pyke uses a ``$`` to indicate pattern variables (anonymous pattern +variables start with ``$_``). + +The ``check_function`` goal checks to see what version of the indicated +function should be used for this client. If this is the incorrect version, +it fails. If there is no indication for this function, it succeeds to +allow guessing. + +Attaching Python Functions to Backward-Chaining Rules +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Here are the last two rules with the python code added. The rules have the +python function attached to them so that the function can be returned from +the goal as an additional parameter. Because this parameter does not affect +the inferencing process, it is a hidden parameter. + +These examples just show one line of python code, but you may have as many +lines as you want:: + + use_D1 + use find_D($client) + when + check_function($client, D, 1) + with + print "D1" + + use_D2 + use find_D($client) + when + check_function($client, D, 2) + with + print "D2" + +Pyke calls the function call graphs *plans*. This terms applies to both the +final top-level call graph, as well as intermediate call graphs. + +Calling Subordinate Plans +--------------------------- + +Now we do the same thing to add python code to the ``use_B2`` rule:: + + use_B2 + use find_B($client) + when + check_function($client, B, 2) + find_D($client) + with + print "B2" + +We have code for the B\ :sub:`2` function, but how does it call the plan +returned from the ``find_D`` goal? + +The most common way is:: + + use_B2 + use find_B($client) + when + check_function($client, B, 2) + find_D($client) + $$() + with + print "B2" + +In general, there may be many goals in the ``when`` clause that produce plans. +Each would have an indented line of python code under it with ``$$`` +indicating the subordinate function. These indented lines are combined with +the lines in the ``with`` clause to form the complete python function for this +rule (with the differences in indenting levels corrected). + +But in this case, this would mean that ``print "Dx"`` would be executed before +``print "B2"``, which seems backwards. + +To call the subordinate plan within the ``with`` clause, there is an alternate +mechanism:: + + use_B2 + use find_B($client) + when + check_function($client, B, 2) + find_D($client) as $d + with + print "B2" + $d() + +The ``as $d`` clause stores the plan function in pattern variable ``$d`` rather +than adding a call to it to the ``with`` clause. Then you can decide in the +``with`` clause whether to call it, when to call it, how many times to call it, +etc. + +Note that pattern variables in general can be used within the python code. +These are replaced by their final bound values (as constants) after the +top-level goal has been proven. Thus, the rules can also be used to determine +and set constant values within the plan functions to further customize the +code. This is the reason that the code for the attached python functions is +placed directly in the .krb file rather than in a separate python module. + +Some Final Points about Plans +------------------------------ + +* Function parameters are specified at the end of the ``use`` clause with an + optional ``taking`` clause:: + + use_B2 + use find_B($client) taking (a, b = None) + ... + +* A completed plan appears as a normal python function. +* Plans may be pickled and reused. + + * If you add functools.partial to copy_reg. + +* You don't need to import all of Pyke to unpickle and run a plan. + + * Only one small Pyke module is needed. + +Other Capabilities +~~~~~~~~~~~~~~~~~~~~~~~~ + +* Pyke also supports forward-chaining rules:: + + fc_rule_name + foreach + fact_base_name.fact_name(pattern...) + ... + assert + fact_base_name.fact_name(pattern...) + ... + + * Pyke runs all of the forward-chaining rules whose ``foreach`` clause + succeeds prior to running any backward-chaining rules. Thus, + forward-chaining rules can not call backward-chaining rules and vice versa. + But backward-chaining rules *can* examine facts asserted by + forward-chaining rules. + +* There are different kinds of knowledge bases: + + * Fact Bases: + + * simply store facts. + + * Rule Bases: + + * store both forward-chaining and backward-chaining rules. + * can use rule base inheritance to inherit, and build upon, the rules from + another rule base. + + * But only single inheritance. + * Thus each rule base has a unique root rule base. + * All rule bases that share the same root form a *rule base category*. + + * allow selection of which rule base(s) to use through rule base + *activation*. + + * But only one rule base per rule base category may be active at one time. + + * Extensibility. You can write your own knowledge bases. These might: + + * look up facts in a database + * ask users questions + * probe hardware/software settings + +Initial Results +================================ + +After writing Pyke's younger brother, it occurred to me that backward-chaining +could be used to automatically figure out how to join database tables together +and generate SQL statements. + +And if the backward-chaining rules could see which substitution variables are +needed by an HTML templating system, it could automatically generate the SQL +to get these data and build the code to update the template. + +It seemed that it would no longer be necessary to include anything that +looks like code in the HTML templates. The graphic designers could just add +simple attributes to their tags and the backward-chaining system would figure +out the rest. This would mean that the programmers don't need to modify the +HTML templates, and the graphic designers could maintain full ownership of +the HTML. + +I had a WSGI front-end that would simply assert the data passed to it as facts. + +The forward-chaining rules took these starting facts, parsed the cookie +information, form information, browser information, and url, determined whether +the user was logged in, figured out which client the request was for, +established all of this as additional facts and activated the appropriate rule +base for this client. + +Then the WSGI front-end simply asked for a proof of the ``process()`` goal and +executed the resulting plan function which returned the final HTTP status codes +and HTML document. + +For a page retrieval (vs. form action), the ``process`` goal used two sub-goals: + +#. A ``format_retrieval`` goal that read the HTML template, and built a plan + to render the template, given the needed data. This goal also returned a + simple descriptor of this needed data as part of its inferencing. +#. A ``retrieve`` goal then took that descriptor of the needed data, built + the necessary SQL statements, and cooked them into a plan to execute those + statements and return the needed data as a simple dictionary. + +Then the two sub plans were combined in the reverse order, to first retrieve +the data and then populate the template, for the final plan that went back to +the WSGI front-end. + +The Pyke examples/sqlgen and examples/web_framework are simplified examples +that you can look at. + +Now, as it turned out, the company had been running without a president for +quite awhile, and had finally hired a new president. + +So just as I finished the SQL generation logic to handle unique data (vs. +multi-row data) and was preparing to show some demonstrations; our new +president, coming from a java background and apparently never having heard of +python, decided to cancel the project. + +End of contract! + +Code Reuse through Automatic Programming +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +The fundamental lesson learned was that this technique ends up being far more +capable than what I had first imagined. + +More than producing adaptable libraries capable of using B\ :sub:`1` or +B\ :sub:`2` at some point in their call graphs, this approach leads to +something more akin to the back-end of a compiler -- except that the compiler +front-end does not target a textual language that needs to be written and +parsed; but is rather a simple observer of already known facts: + + Show me your schema, and I'll build your SQL statements. + + Show me your HTML templates, and I'll build the code to populate them for you. + +This seems to change the whole concept of *code reuse*; elevating it from the +realm of static *libraries*, to the realm of dynamic *automatic programming*. + +Going Forward +=============== + +Thinking that others might find this useful, I've re-implemented the underlying +knowledge engine from scratch, with numerous improvements gained from the +experience of the first attempt, and made it open source. + +With the backward-chaining rule base system, many applications are possible: + +* Complicated decision making applications. +* Compiler back-ends. + + * The .krb compiler uses Pyke. + +* Automatic SQL statement generation. +* Automatic HTML generation/template processing. +* The control module for a web framework tool. +* Incorporate new custom functions into a large set + of standard functions, which may change the + selection or configuration of standard functions + in other parts of the program. +* Automatically re-distribute the modules of a system + over different programs and computers to meet a + wide range of performance and capacity goals. +* Diagnosis systems. + + * E.g., Automated customer service systems. + +* Program or library customization for specific uses. +* Instantiate, configure, and interconnect networks of objects to meet a + specific need or situation. + +Up to this point, I've been flying solo. For this project to move forward +to fully explore its capabilities, I'm going to need help! + +I'd like to see several early adopters run with this and try it out in different +domains. Pyke is in alpha status now and is ready to start to lean on. + +.. raw:: html + +
+ + Creative Commons License + + +This paper is licensed under a `Creative Commons Attribution 3.0 Unported +License`__. + +.. __: http://creativecommons.org/licenses/by/3.0/ + + diff --git a/doc/sphinx/source/about_pyke/cooking_functions.rst b/doc/sphinx/source/about_pyke/cooking_functions.rst new file mode 100644 index 0000000..fe46c78 --- /dev/null +++ b/doc/sphinx/source/about_pyke/cooking_functions.rst @@ -0,0 +1,160 @@ +.. $Id: cooking_functions.txt abb78effaba9 2009-03-25 mtnyogi $ +.. +.. Copyright © 2008 Bruce Frederiksen +.. +.. Permission is hereby granted, free of charge, to any person obtaining a copy +.. of this software and associated documentation files (the "Software"), to deal +.. in the Software without restriction, including without limitation the rights +.. to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +.. copies of the Software, and to permit persons to whom the Software is +.. furnished to do so, subject to the following conditions: +.. +.. The above copyright notice and this permission notice shall be included in +.. all copies or substantial portions of the Software. +.. +.. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +.. IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +.. FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +.. AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +.. LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +.. OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +.. THE SOFTWARE. + +=========================== +Cooking Python Functions +=========================== + +"Cooking" a Python function means customizing it. And you customize it by +cooking certain parameter values into it as constant values. + +Cooking a single parameter value +================================ + +First you define the function that you want to cook with an extra parameter +at the start: + + >>> def foo(cooked, standard): + ... print("foo called with cooked: %s, standard: %s" % + ... (cooked, standard)) + +Now you can call this function with two parameters: + + >>> foo('a', 'b') + foo called with cooked: a, standard: b + +But your real intention is that it appear to be a function taking one +parameter, with the first parameter cooked in. + +This is done with the ``partial`` class of the functools_ module in the +standard Python library. + + >>> from functools import partial + +And then using ``partial`` to cook the first parameter: + + >>> cooked1 = partial(foo, 'cooked_value1') + +Now ``cooked_foo`` is a function that takes one parameter: + + >>> cooked1('value1') + foo called with cooked: cooked_value1, standard: value1 + >>> cooked1('value2') + foo called with cooked: cooked_value1, standard: value2 + +And you can make other cooked functions from foo with other cooked values: + + >>> cooked2 = partial(foo, 'cooked_value2') + >>> cooked2('value1') + foo called with cooked: cooked_value2, standard: value1 + >>> cooked2('value2') + foo called with cooked: cooked_value2, standard: value2 + +And you can still use the first cooked function, so now you have two functions +for the price of one! + + >>> cooked1('value3') + foo called with cooked: cooked_value1, standard: value3 + >>> cooked1('value4') + foo called with cooked: cooked_value1, standard: value4 + >>> cooked2('value5') + foo called with cooked: cooked_value2, standard: value5 + >>> cooked2('value6') + foo called with cooked: cooked_value2, standard: value6 + +And you can keep going with this to make as many functions as you care to +from your single starting function. + +Cooking a Function Call Graph +============================= + +This same technique can be used to cook a function call graph, by making the +subordinate function a cooked parameter: + + >>> def bar(child_fun, a): + ... print("bar called with:", a) + ... return child_fun(a) + +And now you can cook which function ``bar`` calls the same way you cook any +other parameter: + + >>> bar_float = partial(bar, float) + >>> bar_float('123') + bar called with: 123 + 123.0 + >>> bar_min = partial(bar, min) + >>> bar_min((3,2,5)) + bar called with: (3, 2, 5) + 2 + +And, of course, you can use cooked functions as these subordinate functions +too: + + >>> bar_cooked1 = partial(bar, cooked1) + >>> bar_cooked1('abc') + bar called with: abc + foo called with cooked: cooked_value1, standard: abc + +Which means that you can create function call graphs to any depth: + + >>> bar_bar_min = partial(bar, bar_min) + >>> bar_bar_min((3,2,5)) + bar called with: (3, 2, 5) + bar called with: (3, 2, 5) + 2 + +Cooking Several Parameters +========================== + +In general, you may want to cook several values for each function. Some of +these values may specify which subordinate functions to call, others may just +fix certain constant values for the function. + +Pyke does this using a single extra parameter called ``context``, which is a +read-only dictionary. It can then prepare this dictionary with as many values +as it needs and then cook the whole dictionary into the function using +``partial``. + +Pyke translates each individual access to a cooked parameter into a dictionary +lookup on ``context`` that looks up that parameter name:: + + context['parameter_name'] + +The Need for Pyke +================= + +Now that you understand how Pyke cooks Python functions, you should be able +to understand how this technique can achieve the "order of magnitude" +improvements to Adaptability/Customization, Performance and Code Reuse +discussed on the `About Pyke`_ page. + +You should also now see the need for a tool like Pyke to assemble all of +these functions to fit specific situations and use cases. + +.. note:: + Pyke calls a customized function call graph a *plan*. Plans_ are explained + later, after you've been introduced to `Logic Programming in Pyke`_. + +And, finally, you should start to get a sense for how "programming in the +large" with Pyke dovetails with "programming in the small" with Python. + + diff --git a/doc/sphinx/source/about_pyke/index.rst b/doc/sphinx/source/about_pyke/index.rst new file mode 100644 index 0000000..f7c7137 --- /dev/null +++ b/doc/sphinx/source/about_pyke/index.rst @@ -0,0 +1,144 @@ +.. $Id: index.txt f00035e4dab4 2009-11-02 mtnyogi $ +.. +.. Copyright © 2008 Bruce Frederiksen +.. +.. Permission is hereby granted, free of charge, to any person obtaining a copy +.. of this software and associated documentation files (the "Software"), to deal +.. in the Software without restriction, including without limitation the rights +.. to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +.. copies of the Software, and to permit persons to whom the Software is +.. furnished to do so, subject to the following conditions: +.. +.. The above copyright notice and this permission notice shall be included in +.. all copies or substantial portions of the Software. +.. +.. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +.. IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +.. FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +.. AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +.. LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +.. OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +.. THE SOFTWARE. + +.. toctree:: + :maxdepth: 1 + :hidden: + + cooking_functions + what_is_pyke + steps_to_using_pyke + installing_pyke + modifying_pyke + +============ +About Pyke +============ +------------------------- +What Does Pyke Do for Me? +------------------------- + +Pyke was primarily designed to allow you to "cook" your Python_ code. You +write Python code, and then you write Pyke code to cook that Python +code -- i.e. to assemble the Python functions that you've written and +customize them for a specific situation or use-case. + +Pyke can also be used for difficult decision making applications where each +part of the problem has multiple possible solutions and the selection of a +solution to one part of the problem affects whether another part of the problem +can be solved or not. + + +Cooking Your Python Code +======================== + +Cooking your Python code is a form of meta-programming, or writing programs +that manipulate other programs. I.e., it's a means of `programming in the +large`_. + +Thus, Pyke provides a way to directly "program in the large", which dovetails +with using Python to "program in the small". Pyke supplements but does not +replace Python! + +Pyke helps programmers to achieve order of magnitude improvements in: + +- Adaptability/Customization + + - Using Pyke allows your Python code to be combined into thousands of + different configurations. + + - Thus, your application or library takes on the characteristics of a + Domain Specific Language to achieve an order of magnitude increase in + adaptability without a corresponding increase in your program's "surface + area" to your users. + +- Performance + + - Thinking of your application or library as a Domain Specific Language + (DSL), you're using Pyke to "compile" rather than "interpret" your DSL to + achieve an order of magnitude improvement in performance. + +- Code Reuse + + - Making your code an order of magnitude more adaptable and an order of + magnitude faster allows it to be (re)used in a correspondingly broader + range of situations. + + +Examples of Cooking Python Code +=============================== + +Database Access Library +----------------------- + +You're writing a library package to make it easier for Python programmers to +access relational databases. You write Python code that deals with the +mechanics of accessing relational databases, and then you write Pyke code to +make a cooked version of this code for each database access with your user's +application. + +You might also use Pyke to provide help installing and configuring the +database and help creating the schema. + +By taking this approach, your library will be an order of magnitude faster +than competing database access libraries because you've used Pyke to +essentially compile custom code for each database access. + +The sqlgen_ example demonstrates this approach. + + +HTML Templating Library +----------------------- + +Or you're writing an HTML templating package to make it easier for Python +programmers to generate HTML. You write Python code that deals with the +mechanics of HTML, and then you write Pyke code to make a cooked version of +this code for each HTML template. + +By taking this approach, your library will be an order of magnitude faster +than competing HTML templating libraries because you've used Pyke to +essentially compile custom code for each HTML template. + +The web_framework_ example demonstrates this approach. It uses the sqlgen_ +example to make a little web framework. The 2 HTML templates in this example +were also done in `TurboGears 2`_ and then a siege_ benchmark test done on +both: + +- TurboGears 2 ran 75.83 transactions/sec +- The Pyke example ran 791.01 transactions/sec + + +Linux Configuration Program +--------------------------- + +Or you're writing a new Linux configuration program. You write the Python +code to query and set the various system configuration options, and then you +write Pyke code to ask the user what he wants and build a cooked version of +your code to make the necessary changes. + +In this case, you're not looking for performance. You use Pyke to handle the +complicated decision making and use its plan_ facility to postpone making any +configuration changes until your program is sure that it's "dotted all of the +i's and crossed all the t's". + + + diff --git a/doc/sphinx/source/about_pyke/installing_pyke.rst b/doc/sphinx/source/about_pyke/installing_pyke.rst new file mode 100644 index 0000000..aefcc12 --- /dev/null +++ b/doc/sphinx/source/about_pyke/installing_pyke.rst @@ -0,0 +1,186 @@ +.. $Id: installing_pyke.txt 70f7f9ee163a 2010-03-11 mtnyogi $ +.. +.. Copyright © 2007-2009 Bruce Frederiksen +.. +.. Permission is hereby granted, free of charge, to any person obtaining a copy +.. of this software and associated documentation files (the "Software"), to deal +.. in the Software without restriction, including without limitation the rights +.. to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +.. copies of the Software, and to permit persons to whom the Software is +.. furnished to do so, subject to the following conditions: +.. +.. The above copyright notice and this permission notice shall be included in +.. all copies or substantial portions of the Software. +.. +.. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +.. IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +.. FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +.. AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +.. LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +.. OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +.. THE SOFTWARE. + +=================================== +Installing Pyke +=================================== + +Index to This Page +======================= + +* Licensing_ +* `System Requirements`_ + + * `Other Required Packages`_ + +* Installation_ +* `Run the Examples`_ +* `Viewing the HTML Documentation`_ +* `Repository Directory Structure`_ + + +Licensing +================ + +This software is licensed under the MIT license:: + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. + + +System Requirements +==================== + +Pyke is 100% Python, so it should run on any platform supported by Python. +So all you'll need is `Python`_ 2.5, 2.6 or 3.1. + +Other Required Packages +----------------------- + +No other packages are required to develop, run and distribute an application +using Pyke. But there are package requirements to do the following additional +things: + +.. table:: + :class: table-offset + + +--------------------------------+------------------+------------------+ + | If you want to | you also need | minimum version | + +================================+==================+==================+ + | run the web_framework example | HTMLTemplate_ | 1.5 | + +--------------------------------+------------------+------------------+ + | run the unit tests | `doctest-tools`_ | 1.0a3 | + +--------------------------------+------------------+------------------+ + | rebuild the html documentation | rest2web_ | 0.5 | + + +------------------+------------------+ + | | docutils_ | 0.4.1 | + +--------------------------------+------------------+------------------+ + +If the docutils package is not part of your standard Python installation, +there is probably a package for it in the package index for your Linux +distribution. + +All of the other packages can be installed as the administrator using +pip_ or easy_install_. For example:: + + # pip install HTMLTemplate + + +Installation +============ + +The source code for the latest release can be found on the `Pyke project +download page`_ as ``pyke-.zip`` (for Python2) and +``pyke3-.zip`` (for Python3). After unzipping these, go into the +directory and run:: + + $ python setup.py build + +And then as administrator, run:: + + # python setup.py install + +The sources include a complete copy of the project directory, including the +documentation, unit tests, and examples. + +If you want to clone the source code repository to contribute to the project +development, or to use the latest developer version, read `Modifying Pyke`_. + +Run the Examples +================ + +There are several examples that are contained in the source directory. Each +example is in it's own subdirectory under the ``examples`` subdirectory, and +each has it's own README.txt file that explains how to run it. + +The web_framework example requires the HTMLTemplate_ package, version 1.5 or +later. This can be installed as administrator with pip or easy_install:: + + # pip install HTMLTemplate + +See also Examples_. + +Viewing the HTML Documentation +============================== + +This HTML documentation may be viewed directly from your hard drive. The HTML +files are in the ``doc/html`` directory. Start with ``doc/html/index.html``. + + +Repository Directory Structure +============================== + +You'll see the following directories. + +* ``doc`` + + - the ``html`` directory has all of the HTML documentation ready to browse + off of your hard drive. Start with doc/html/index.html. + - the ``source`` directory has all of the sources that were used to + generated the HTML documentation. See `Rebuilding the HTML Documentation`_. + - the ``examples`` directory just has a copy of the examples used by the + .txt files in the ``source`` directory so that the doctests will work on + the ``source`` directory. You should be able to skip this unless you + change an example in one of the ``source`` files. + - ``cheatsheets`` are a collection of text files with notes on various tools + used by Pyke, and processes used to maintain Pyke. + +* ``examples`` + + - There are several examples. Start with *family_relations*. Look at the + ``README.txt`` file for each example to see how to run it. See also, + Examples_. + +* ``experimental`` + + - This is a catch-all directory for various ideas that have been tried, but + that have not been incorporated into Pyke. You can safely skip over this + directory... + +* ``pyke`` + + - This is the top-level Python package directory for the Python sources. + This needs to be installed into a directory on your ``PYTHONPATH``. + The sources for the compilers are in the ``krb_compiler`` subdirectory, + which is expected to be a subpackage of ``pyke``. + +* ``Test`` + + - This is where the unit test scripts are stored. These use Python's + doctest_ package. Each test file has a .tst suffix. + - See `Running Unit Tests`_. + diff --git a/doc/sphinx/source/about_pyke/modifying_pyke.rst b/doc/sphinx/source/about_pyke/modifying_pyke.rst new file mode 100644 index 0000000..f995d9c --- /dev/null +++ b/doc/sphinx/source/about_pyke/modifying_pyke.rst @@ -0,0 +1,283 @@ +.. $Id: modifying_pyke.txt 70f7f9ee163a 2010-03-11 mtnyogi $ +.. +.. Copyright © 2009 Bruce Frederiksen +.. +.. Permission is hereby granted, free of charge, to any person obtaining a copy +.. of this software and associated documentation files (the "Software"), to deal +.. in the Software without restriction, including without limitation the rights +.. to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +.. copies of the Software, and to permit persons to whom the Software is +.. furnished to do so, subject to the following conditions: +.. +.. The above copyright notice and this permission notice shall be included in +.. all copies or substantial portions of the Software. +.. +.. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +.. IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +.. FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +.. AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +.. LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +.. OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +.. THE SOFTWARE. + +=================================== +Modifying Pyke +=================================== + +Index to This Page +======================= + +* `Mercurial Repositories`_ + + * `Mercurial Keyword Extension`_ + * `Which Repository Do I Use?`_ + +* `Compiling PLY Tables Files`_ +* `Compiling the Compiler.krb File`_ +* `Running Unit Tests`_ +* `Rebuilding the HTML Documentation`_ + + +Mercurial Repositories +====================== + +With Mercurial_, you clone the entire repository locally on your computer. +Then you can make changes and commit those changes to your local repository. +If you think those changes might be interesting to everybody, make your local +repository (or a clone of it) publicly available (either on your own server, +or on one of the `Mercurial Hosting Sites`_) and send me an email. I will +pull your changes, examine them, and push them to the master repository on +sourceforge. + +Mercurial Keyword Extension +--------------------------- + +The Pyke sources use the Mercurial `Keyword Extension`_ as a holdover from +when the repository used Subversion rather than Mercurial. + +The ``hgrc_keywords`` file has been provided to enable and configure this +extension for Pyke use. You can append this file to either your personal +.hgrc configuration file (which would then apply to all of your Mercurial +projects) or the project .hg/hgrc file (see `hgrc`_ in the Mercurial wiki). + +If you use a ``post-clone`` `Mercurial hook`_, or append ``hgrc_keywords`` +manually after cloning, the keywords won't be expanded properly when the +project is first cloned. But they will be expanded properly if the clone is +done with the -U option and then an ``hg update`` done in the newly cloned +repository (after the changes to .hg/hgrc have been made). + +The keyword expansions are only used by the tools that generate the html +documentation (see `Rebuilding the HTML Documentation`_, below). + +Which Repository Do I Use? +-------------------------- + +Normally, you will clone one of the following four repositories locally to +make a master copy of what's on sourceforge. Then you would clone your master +copy (which is very fast) to make separate clones for each development task +that you are working on for Pyke. + +So it is best to keep all of these clones together in a common directory. + +There are four repositories on sourceforge that you can start with: + +release_1 + Use this for bug fixes, code and documentation cleanup, and anything else + that would go into a point release for release 1. I merge the changes made + here into all of the other repositories. So this code goes into both the + Python2.x and Python3.x versions of Pyke. + +pyke + Use this for major new features that would result in a major new release + (e.g., release 1.2). I merge the changes made in release_1 into the pyke + repository (but maybe not the other way around). And I merge the changes + made in the pyke repository into the pre_2to3 repository. So the code here + goes into both the Python2.x and Python3.x future versions of Pyke. + +pre_2to3_r1 + Use this for bug fixes, code and documentation cleanup, and anything else + that would go into a point release for release 1, but only apply to the + Python3.x version of Pyke. I merge the changes made in release_1 into the + pre_2to3_r1 repository (but not the other way around). And I merge the + changes made in the pre_2to3_r1 repository into the pre_2to3 repository. + So changes here only go into the next point release of the Python3.x version + of Pyke. + + .. warning:: + This code is maintained in a state just prior to running Python's + 2to3_ tool on it. So you can't just run the code here directly. + + The ``run_2to3`` script runs 2to3 on the current copy of the sources. Do + **not** run this in a repository clone that you still want to use to do + commits! Instead, commit all of your changes, then clone the repository + and do ``run_2to3`` in the clone. If anything doesn't work, go back to + the first repository to fix it, delete the clone, and repeat the whole + process. This was done to minimize merge conflicts caused by the 2to3 + changes. + + The ``run_pre_test`` script will: + + * clone the current repository + * then in the clone do: + + * ``run_2to3`` + * ``testpyke`` -3.1 + * python setup.py -q sdist --formats zip + * insert '3' after 'pyke' in the name of the source distribution zip + file. + + ``Run_pre_test`` assumes that you either have the keywording options set + in your personal .hgrc file, or have clone hooks in place to copy these + into the .hg/hgrc file of all clones within your pyke work area. See + `Mercurial Keyword Extension`_, above. + +pre_2to3 + Normally I merge changes from the pyke repository and the pre_2to3_r1 + repository into pre_2to3 so that nothing needs to be done in this repository. + Most major new features would be developed in the ``pyke`` repository and + merged into pre_2to3. Making changes to pre_2to3 directly would only be + done when those changes are for major new features that only apply to the + Python3.x version of Pyke. + +So, for example, if you wanted to work on the ``release_1`` repository, you +would:: + + $ mkdir pyke_repos + $ cd pyke_repos + $ hg clone -U http://pyke.hg.sourceforge.net:8000/hgroot/pyke/release_1 master + $ hg clone master task_1 + $ cd task_1 + +.. note:: + This assumes that you've added the `hgrc_keywords`_ file to your ~/.hgrc + file. See `Mercurial Keyword Extension`_, above. + + +Compiling PLY Tables Files +========================== + +Pyke uses PLY_ (Python Lex and Yacc) as it's parser generator. PLY compiles +the Pyke grammars into a set of three tables files: + +- kfbparser_tables.py (from kfbparser.py) +- krbparser_tables.py (from krbparser.py) +- scanner_tables.py (from scanner.py) + +A copy of PLY is included in the source directory (pyke/krb_compiler/ply) so +that there there can be no version mismatch between the version of PLY used to +compile these tables files and the version of PLY installed on your machine. + +To regenerate these tables files, at the top-level source directory:: + + $ python + >>> from pyke.krb_compiler import kfbparser, krbparser, scanner + >>> scanner.init(scanner, 0, True) + >>> krbparser.init(krbparser, True) + >>> kfbparser.init(kfbparser, True) + +or just run the "testall.py" program from the doctest-tools package:: + + $ cd pyke/krb_compiler + $ testall.py + + +Compiling the Compiler.krb File +=============================== + +Pyke uses itself to compile your `rule base`_ sources (`.krb`_ files) into +Python source (``.py``) files. + +The knowledge base file that Pyke uses for this is +pyke/krb_compiler/compiler.krb. This gets compiled into compiler_bc.py, which +is stored in the source code repository. + +.. this code is hidden and will create the pyke/krb_compiler/compiled_krb + directory, if needed, for the code section following: + >>> import os, os.path + >>> os.chdir('../../..') + >>> root='pyke/krb_compiler' + >>> dir=root + '/compiled_krb' + >>> os.path.isdir(root) + True + >>> if not os.path.isdir(dir): os.mkdir(dir) + +To recompile the compiler_bc.py file, from the top-level source directory:: + + $ mkdir pyke/krb_compiler/compiled_krb + $ python + >>> from pyke import krb_compiler + >>> krb_compiler.compile_krb('compiler', 'pyke.krb_compiler.compiled_krb', + ... 'pyke/krb_compiler/compiled_krb', + ... 'pyke/krb_compiler/compiler.krb') + ['compiler_bc.py'] + + $ mv pyke/krb_compiler/compiled_krb/compiler_bc.py pyke/krb_compiler + +.. this code is also hidden and deletes the + pyke/krb_compiler/compiled_krb/compiler_bc.py file and + pyke/krb_compiler/compiled_krb directory created above. + >>> os.path.isdir(root) + True + >>> os.remove(dir + '/compiler_bc.py') + >>> os.rmdir(dir) + +Running Unit Tests +================== + +The `doctest-tools`_ package is required to run the unit tests (see +`Other Required Packages`_ for more details). + +The ``testall.py`` and ``testdoc.py`` scripts from ``doctest-tools`` can be run +anywhere. + +In addition, the top-level directory contains a ``testpyke`` script that will +delete all of the compiled_krb directories, then run ``testall.py`` twice. The +first run must recompile all of the `knowledge base`_ sources (`.krb`_, +`.kfb`_ and `.kqb`_ files) into the compiled_krb directories in order to run +the tests. The second run reuses the files compiled in the first run. This +makes sure that all of the tests run properly whether they have to compile the +knowledge base sources or not. + + +Rebuilding the HTML Documentation +================================= + +The ``doc/html`` directory contains all of the documents that you are reading +now. These are ready to browse directly from your hard drive if you'd like. + +The documentation is generated using the rest2web_ package, which uses +docutils_ (see `Other Required Packages`_ for more details). + +The sources for the documentation are in ``doc/source``. Each .txt file there +is converted into an .html file in the doc/html directory by running:: + + $ cd doc/source + $ bin/gen_html + +This takes about 9 seconds. It: + +#. Temporarily appends hyperlink references onto all of the \*.txt files. +#. Runs ``r2w`` to regenerate the files in ``doc/html`` + + - except for those in ``doc/html/stylesheets`` and ``doc/html/images``. + +#. Strips all of the hyperlink references from the \*.txt files. +#. Creates a new sitemap.xml file with all of the dates that the files were + last modified. + +.. note:: + This process uses the date information expanded by the Mercurial `Keyword + Extension`_. See `Mercurial Keyword Extension`_, above. + +I've gone ahead and placed the generated html files in the source repository +so that you can browse the documentation locally without having to run +``bin/gen_html``. So you only need these procedures if you change the +documentation (i.e., change the .txt files in doc/source). + +To test all of the code examples in the documents, use the ``testall.py`` +command from the `doctest-tools`_ package:: + + $ cd doc/source + $ testall.py + + diff --git a/doc/sphinx/source/about_pyke/steps_to_using_pyke.rst b/doc/sphinx/source/about_pyke/steps_to_using_pyke.rst new file mode 100644 index 0000000..0b725c3 --- /dev/null +++ b/doc/sphinx/source/about_pyke/steps_to_using_pyke.rst @@ -0,0 +1,87 @@ +.. $Id: steps_to_using_pyke.txt a2119c07028f 2008-10-27 mtnyogi $ +.. +.. Copyright © 2007-2008 Bruce Frederiksen +.. +.. Permission is hereby granted, free of charge, to any person obtaining a copy +.. of this software and associated documentation files (the "Software"), to deal +.. in the Software without restriction, including without limitation the rights +.. to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +.. copies of the Software, and to permit persons to whom the Software is +.. furnished to do so, subject to the following conditions: +.. +.. The above copyright notice and this permission notice shall be included in +.. all copies or substantial portions of the Software. +.. +.. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +.. IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +.. FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +.. AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +.. LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +.. OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +.. THE SOFTWARE. + +==================== +Steps to Using Pyke +==================== + +#. You provide the following to Pyke's `knowledge engine`_: + + - A set of universally true statements_. + + - These statements are true for all time. + + - The text for all questions_ that you might want Pyke to ask your end user. + - Multiple sets of rules_. + + - Your rules may include both `forward-chaining`_ and + `backward-chaining`_ rules. + +#. Repeat for each specific use case: + + #. You provide a set of statements_ describing this specific use case to + Pyke. + + #. You select which set of rules apply to this use case. + + #. Pyke automatically runs all of the selected forward-chaining rules that + apply to the statements that you've given to it to deduce new statements. + + - Your forward-chaining rules may interactively ask your end user + `questions`_, or get information by executing `commands`_ (programs) + on the computer that it's running on to help in its decision + making. + + #. You ask Pyke a question by having it prove_ a goal_ (which is just + another statement). This goal may include `pattern variables`_ that + allow you to ask "for what values is this statement true?". + + - Pyke runs the selected backward-chaining rules against the statements + that it has in order to figure out the answer to your question. + + - Your backward-chaining rules may also ask your end user questions_ and + run commands_. + + - You may have written Python code at the end of some of your + backward-chaining rules. For each such rule, Pyke has compiled this + Python code into a Python function called a plan_ which it has attached + to the rule. + + - Once Pyke finds an answer to your question, it gathers all of the plan + functions of the rules that it used to find your answer into a + complete function call graph. The plan functions are linked together + mirroring the way that the rules were linked together to find your + answer. In this way, you can write high-level compilers that assemble + together and configure a set of Python functions to solve specific + problems. + + - Pyke returns the top Python function of this function call graph as a + standard Python function along with the answer to your question. You + may call this function as may times as you like. You may also pickle_ + the function so that you can send it to another program or save it to + disk. You only need one small Pyke module to load and run these + pickles. + + #. You reset_ Pyke to clear out all of these case specific statements and + prepare it for the next use case. + + diff --git a/doc/sphinx/source/about_pyke/what_is_pyke.rst b/doc/sphinx/source/about_pyke/what_is_pyke.rst new file mode 100644 index 0000000..b24d210 --- /dev/null +++ b/doc/sphinx/source/about_pyke/what_is_pyke.rst @@ -0,0 +1,99 @@ +.. $Id: what_is_pyke.txt a2119c07028f 2008-10-27 mtnyogi $ +.. +.. Copyright © 2007-2008 Bruce Frederiksen +.. +.. Permission is hereby granted, free of charge, to any person obtaining a copy +.. of this software and associated documentation files (the "Software"), to deal +.. in the Software without restriction, including without limitation the rights +.. to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +.. copies of the Software, and to permit persons to whom the Software is +.. furnished to do so, subject to the following conditions: +.. +.. The above copyright notice and this permission notice shall be included in +.. all copies or substantial portions of the Software. +.. +.. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +.. IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +.. FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +.. AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +.. LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +.. OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +.. THE SOFTWARE. + +============================= +What is Pyke? +============================= + +Pyke integrates a form of `Logic Programming`_ into Python by providing a +knowledge engine that can: + +- Do both forward-chaining (data driven) and + backward-chaining (goal directed) inferencing. + + - Pyke may be embedded into any Python program. + +- Automatically generate Python programs by assembling individual Python + functions into complete call graphs. + + - This is done through a unique design where the individual Python + functions are attached to backward-chaining rules. + - Unlike other approaches to code reuse (e.g. Object-oriented programming, + Zope adapters, generic functions), this allows the inference engine to + ensure that all of the function's requirements are completely satisfied, + by examining the entire call graph down to the leaves, before **any** of + the functions are executed. + - This is an optional feature. You don't need to use it if you just + want the inferencing capability by itself. + +The Knowledge Engine Supports: +======================================== + +- Multiple *fact bases*, each with its own list of facts. +- Both *forward-chaining* rules and *backward-chaining* rules. +- Multiple *rule bases*, each with its own list of forward-chaining + and/or backward-chaining rules. +- Rule base inheritance -- *activating* the derived rule base + includes the rules from the parent rule base. +- The inference rules are compiled into Python functions, allowing + Python code snippets to be used within the rules. + This greatly enhances the expressiveness of the rules. + +Automatic Program Generation: +======================================== + +- Calls the generated Python programs *plans*. +- Plans may be run multiple times without needing to rerun the inference + rules. +- Plans may be pickled and cached to disk to be used by other programs or + in later runs of the same program. +- Only one small Pyke module is required to run the plans. + +Potential Pyke Applications: +======================================== + +- Complicated decision making applications. +- The back-end (code generation and optimization) of compilers. + Pyke is used as the back-end of its own + compiler that translates rules into Python code. +- Automatic SQL statement generation. +- Automatic HTML generation and automatic HTML template processing. +- Automatic program builder to reuse a common set of functions for many + different specific situations. This could also easily + incorporate a new custom function into a much larger program, where the use + of the custom function might influence the choice of other standard + functions in other parts of the program. +- The control module for a web framework tool. +- A high-level planner to automatically distribute the + modules of a large system over several computers in a distributed system + to meet specific performance and capacity goals. This could be used to + automatically scale the same system code from a small one program, + one computer system + to much larger distributed systems to meet a wide range of performance + goals. +- Diagnosis systems, including automated customer service systems. +- Program or library customization for specific uses. +- In addition to being able to build programs, Pyke can instantiate, + configure and interconnect a network of objects to meet a specific need + or situation. + + diff --git a/doc/sphinx/source/conf.py b/doc/sphinx/source/conf.py new file mode 100644 index 0000000..462b1e9 --- /dev/null +++ b/doc/sphinx/source/conf.py @@ -0,0 +1,75 @@ +# Configuration file for the Sphinx documentation builder. +# +# This file only contains a selection of the most common options. For a full +# list see the documentation: +# https://www.sphinx-doc.org/en/master/usage/configuration.html + +# -- Path setup -------------------------------------------------------------- + +# If extensions (or modules to document with autodoc) are in another directory, +# add these directories to sys.path here. If the directory is relative to the +# documentation root, use os.path.abspath to make it absolute, like shown here. +# +import os +# import sys +# sys.path.insert(0, os.path.abspath('.')) + + +# -- Project information ----------------------------------------------------- + +project = 'PyKE' +copyright = '2007-2021, Bruce Frederiksen' +author = 'Bruce Frederiksen' + + +# -- General configuration --------------------------------------------------- + +# Add any Sphinx extension module names here, as strings. They can be +# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom +# ones. +extensions = [ +] + +# Add any paths that contain templates here, relative to this directory. +templates_path = ['_templates'] + +# List of patterns, relative to source directory, that match files and +# directories to ignore when looking for source files. +# This pattern also affects html_static_path and html_extra_path. +exclude_patterns = ['_build', 'links.rst'] + + +# -- Options for HTML output ------------------------------------------------- + +# The theme to use for HTML and HTML Help pages. See the documentation for +# a list of builtin themes. +# +html_theme = 'sphinxdoc' + +# Add any paths that contain custom static files (such as style sheets) here, +# relative to this directory. They are copied after the builtin static files, +# so a file named "default.css" will overwrite the builtin "default.css". +html_static_path = ['_static'] + +# -- Custom behaviour + + +def fix_ref(ref, html_dir): + """Fixes a reference prepending it with a relative path (if necessary) + """ + # Is it either another link or an external page? + if ref.rstrip()[-1] == '_' or ref.startswith('http://'): + return ref + else: + return os.path.join(html_dir, ref) + + +source_dir = os.path.dirname(os.path.relpath(__file__, 'pyke/doc/sphinx/')) +html_dir = os.path.join(source_dir, '../build/html') + +# Example from https://stackoverflow.com/a/61694897 +rst_epilog = "" +with open('links.rst') as f: + for line in f: + link, ref = line.split(':', 1) + rst_epilog += link + ": " + fix_ref(ref, html_dir) diff --git a/doc/sphinx/source/copyright_license b/doc/sphinx/source/copyright_license new file mode 100644 index 0000000..d1785c0 --- /dev/null +++ b/doc/sphinx/source/copyright_license @@ -0,0 +1,22 @@ +.. $Id: copyright_license 05a128b38e5d 2007-11-08 mtnyogi $ +.. +.. Copyright © 2007 Bruce Frederiksen +.. +.. Permission is hereby granted, free of charge, to any person obtaining a copy +.. of this software and associated documentation files (the "Software"), to deal +.. in the Software without restriction, including without limitation the rights +.. to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +.. copies of the Software, and to permit persons to whom the Software is +.. furnished to do so, subject to the following conditions: +.. +.. The above copyright notice and this permission notice shall be included in +.. all copies or substantial portions of the Software. +.. +.. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +.. IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +.. FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +.. AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +.. LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +.. OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +.. THE SOFTWARE. + diff --git a/doc/sphinx/source/examples.rst b/doc/sphinx/source/examples.rst new file mode 100644 index 0000000..40ddf06 --- /dev/null +++ b/doc/sphinx/source/examples.rst @@ -0,0 +1,164 @@ +.. $Id: examples.txt 6de8ee4e7d2d 2010-03-29 mtnyogi $ +.. +.. Copyright © 2008 Bruce Frederiksen +.. +.. Permission is hereby granted, free of charge, to any person obtaining a copy +.. of this software and associated documentation files (the "Software"), to deal +.. in the Software without restriction, including without limitation the rights +.. to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +.. copies of the Software, and to permit persons to whom the Software is +.. furnished to do so, subject to the following conditions: +.. +.. The above copyright notice and this permission notice shall be included in +.. all copies or substantial portions of the Software. +.. +.. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +.. IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +.. FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +.. AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +.. LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +.. OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +.. THE SOFTWARE. + + +======== +Examples +======== + +.. this code is hidden and will change to the root directory and add '' to + sys.path for the code section following: + >>> import sys + >>> if '' not in sys.path: sys.path.insert(0, '') + >>> import os + >>> os.chdir("../..") # get out of documents directory back to root dir + >>> os.chdir("examples/towers_of_hanoi") + +Several examples are included to help you become familiar with Pyke. These +are all in an ``examples`` directory:: + + $ cd examples/towers_of_hanoi + $ python + >>> import driver + >>> driver.test(2) + got 1: ((0, 1), (0, 2), (1, 2)) + got 2: ((0, 2), (0, 1), (2, 0), (1, 2), (0, 2)) + +Each example is in its own sub-directory and has a README.txt file to get you +started. They all have `.krb files`_ and a Python module to run the example +that also demonstrates `how to call Pyke`_ from your Python program. + + +Family_relations +================ + +This is a very good basic example to start with. + +The family_relations example takes an initial set of facts_ about people +(stated in a `.kfb file`_):: + + son_of(david_r2, david_r, sarah_r) + daughter_of(shirley, david_r, sarah_r) + +And figures out how any two people are related:: + + david_r2, shirley are ('brother', 'sister') + +This same problem is solved in four different ways so that you can compare +them: + +- Forward-chaining_ only +- Backward-chaining_ only +- Backward-chaining only with a few rule optimizations that make the rules + run 100 times faster! +- A mix of forward-chaining and backward-chaining with some use of plans_ added + too. + +The driver.py program also demonstrates how to use krb_traceback_ and the +print_stats_ function. + + +Knapsack +======== + +At the `PyCon 2008`_ conference, somebody asked about the `knapsack problem`_. +We found a solution in Prolog here__ (starting on page 19), and rewrote it in +Pyke. This is a quick simple little example. + +.. __: http://www.ise.gmu.edu/~duminda/classes/fall03/set3.ppt + + +Sqlgen +====== + +Pyke was originally developed as the control component for a web framework. +This example shows how Pyke can automatically generate SQL SELECT statements, +given a set of tables that the calling program has keys to and a tuple of the +desired column names. Column names specified at the top-level in this tuple +are expected to have a single value each. Nested tuples are used when +multiple rows are expected. The column names in nested tuples make up the +columns in the result rows. + +The top-level goal returns a plan_ that takes the key values for the initial +set of tables given to the goal and returns an immutable dictionary mapping +the column names to the values retrieved from the database. The plan may be +used repeatedly without re-running the rules each time to figure out the +SELECT statements. Thus, this acts like a SELECT statement compiler resulting +in queries with virtually no extra overhead. It is *not*, however, an Object +Relational Mapper (ORM). + +The data model used for the requested columns is that tables inherit the +columns from tables they link to. So if there is a 1-many relationship +between tables A and B (1 A row has many B rows), the B table inherits the +columns from the A table through it's link to table A. The Pyke rules will +automatically figure out the table joins for this. + +The program automatically introspects the schema information. For this +example, it assumes that ``id`` is the primary key for each table, and that +when one table links to another, it uses the target table name suffixed with +``_id`` as the column name. + +This example was originally done using MySQL_ and includes the .sql files to +create the database, tables, and example data. The example has since been +converted to use the Sqlite3 database to make it easier to run, as Sqlite3 +does not require any setup (the Sqlite3 database file is included in the +example). + +Sqlgen lacks more general capabilities that would be required for real use, +but may serve as a starting point for another project that's more complete. + +This example also has much more elaborate rules than the prior two examples +and is a very real example of generating plans_. + + +Web_framework +============= + +This example completes the Python web framework demo by adding rules to +automatically generate code to render HTML templates from the HTMLTemplate_ +package (you can run ``pip install HTMLTemplate`` or ``easy_install +HTMLTemplate`` to install the HTMLTemplate package). This example uses the +sqlgen_ example, above, to generate the SQL statements. + +An HTMLTemplate does not include anything resembling program code in it, so +that your graphics designers can completely own the html files without the +developers having to modify them in any way. + +Note that the code generated here is fully cooked_ code, custom built for +that specific schema and HTML template. This runs extremely fast because +there is nothing left at run-time concerning parsing and figuring out the +HTML template, or constructing the SQL statements. + +A test was done comparing this web framework example to the same example +done in `TurboGears 2`_ running against the same MySQL database. The results +of the siege_ benchmark tests show that Pyke is just over 10 times faster than +TurboGears 2:: + +- Pyke: 791 trans/sec +- TurboGears 2: 76 trans/sec + +The demo is packaged as a WSGI_ application. It also demonstrates the use of +multiple `rule bases`_ by using the sqlgen example above, as well as the +caching and reuse of plans_ to achieve the order of magnitude improvement in +performance over current practice. + + diff --git a/doc/sphinx/source/images/PyCon2008/bc_rules2.png b/doc/sphinx/source/images/PyCon2008/bc_rules2.png new file mode 100644 index 0000000..787494c Binary files /dev/null and b/doc/sphinx/source/images/PyCon2008/bc_rules2.png differ diff --git a/doc/sphinx/source/images/PyCon2008/bc_rules5.png b/doc/sphinx/source/images/PyCon2008/bc_rules5.png new file mode 100644 index 0000000..88ee096 Binary files /dev/null and b/doc/sphinx/source/images/PyCon2008/bc_rules5.png differ diff --git a/doc/sphinx/source/images/PyCon2008/bc_rules8.png b/doc/sphinx/source/images/PyCon2008/bc_rules8.png new file mode 100644 index 0000000..2706ff0 Binary files /dev/null and b/doc/sphinx/source/images/PyCon2008/bc_rules8.png differ diff --git a/doc/sphinx/source/images/PyCon2008/bc_rules9.png b/doc/sphinx/source/images/PyCon2008/bc_rules9.png new file mode 100644 index 0000000..32e30c0 Binary files /dev/null and b/doc/sphinx/source/images/PyCon2008/bc_rules9.png differ diff --git a/doc/sphinx/source/images/PyCon2008/client1.png b/doc/sphinx/source/images/PyCon2008/client1.png new file mode 100644 index 0000000..7cbd169 Binary files /dev/null and b/doc/sphinx/source/images/PyCon2008/client1.png differ diff --git a/doc/sphinx/source/images/PyCon2008/client2b.png b/doc/sphinx/source/images/PyCon2008/client2b.png new file mode 100644 index 0000000..405d9f8 Binary files /dev/null and b/doc/sphinx/source/images/PyCon2008/client2b.png differ diff --git a/doc/sphinx/source/images/PyCon2008/client2d.png b/doc/sphinx/source/images/PyCon2008/client2d.png new file mode 100644 index 0000000..27c4f52 Binary files /dev/null and b/doc/sphinx/source/images/PyCon2008/client2d.png differ diff --git a/doc/sphinx/source/images/PyCon2008/client3d.png b/doc/sphinx/source/images/PyCon2008/client3d.png new file mode 100644 index 0000000..4d0d0f5 Binary files /dev/null and b/doc/sphinx/source/images/PyCon2008/client3d.png differ diff --git a/doc/sphinx/source/images/PyCon2008/client3e.png b/doc/sphinx/source/images/PyCon2008/client3e.png new file mode 100644 index 0000000..95228e0 Binary files /dev/null and b/doc/sphinx/source/images/PyCon2008/client3e.png differ diff --git a/doc/sphinx/source/images/PyCon2008/client3f2.png b/doc/sphinx/source/images/PyCon2008/client3f2.png new file mode 100644 index 0000000..a846cdf Binary files /dev/null and b/doc/sphinx/source/images/PyCon2008/client3f2.png differ diff --git a/doc/sphinx/source/images/PyCon2008/client3g.png b/doc/sphinx/source/images/PyCon2008/client3g.png new file mode 100644 index 0000000..b690ac5 Binary files /dev/null and b/doc/sphinx/source/images/PyCon2008/client3g.png differ diff --git a/doc/sphinx/source/images/PyCon2008/client3h.png b/doc/sphinx/source/images/PyCon2008/client3h.png new file mode 100644 index 0000000..a37e65e Binary files /dev/null and b/doc/sphinx/source/images/PyCon2008/client3h.png differ diff --git a/doc/sphinx/source/images/backtracking.dia b/doc/sphinx/source/images/backtracking.dia new file mode 100644 index 0000000..dcb439c Binary files /dev/null and b/doc/sphinx/source/images/backtracking.dia differ diff --git a/doc/sphinx/source/images/backtracking.png b/doc/sphinx/source/images/backtracking.png new file mode 100644 index 0000000..410b3df Binary files /dev/null and b/doc/sphinx/source/images/backtracking.png differ diff --git a/doc/sphinx/source/images/bc_backtracking.dia b/doc/sphinx/source/images/bc_backtracking.dia new file mode 100644 index 0000000..1c78bbe Binary files /dev/null and b/doc/sphinx/source/images/bc_backtracking.dia differ diff --git a/doc/sphinx/source/images/bc_backtracking.png b/doc/sphinx/source/images/bc_backtracking.png new file mode 100644 index 0000000..de55603 Binary files /dev/null and b/doc/sphinx/source/images/bc_backtracking.png differ diff --git a/doc/sphinx/source/images/bc_rules.dia b/doc/sphinx/source/images/bc_rules.dia new file mode 100644 index 0000000..c3928ce Binary files /dev/null and b/doc/sphinx/source/images/bc_rules.dia differ diff --git a/doc/sphinx/source/images/bc_rules.png b/doc/sphinx/source/images/bc_rules.png new file mode 100644 index 0000000..ded9412 Binary files /dev/null and b/doc/sphinx/source/images/bc_rules.png differ diff --git a/doc/sphinx/source/images/header.gif b/doc/sphinx/source/images/header.gif new file mode 100644 index 0000000..38eebbf Binary files /dev/null and b/doc/sphinx/source/images/header.gif differ diff --git a/doc/sphinx/source/images/plan1.dia b/doc/sphinx/source/images/plan1.dia new file mode 100644 index 0000000..c3038f2 Binary files /dev/null and b/doc/sphinx/source/images/plan1.dia differ diff --git a/doc/sphinx/source/images/plan1.png b/doc/sphinx/source/images/plan1.png new file mode 100644 index 0000000..1ee9ea6 Binary files /dev/null and b/doc/sphinx/source/images/plan1.png differ diff --git a/doc/sphinx/source/images/plan2.dia b/doc/sphinx/source/images/plan2.dia new file mode 100644 index 0000000..4179f3b Binary files /dev/null and b/doc/sphinx/source/images/plan2.dia differ diff --git a/doc/sphinx/source/images/plan2.png b/doc/sphinx/source/images/plan2.png new file mode 100644 index 0000000..23001ae Binary files /dev/null and b/doc/sphinx/source/images/plan2.png differ diff --git a/doc/sphinx/source/images/plan3.dia b/doc/sphinx/source/images/plan3.dia new file mode 100644 index 0000000..6077389 Binary files /dev/null and b/doc/sphinx/source/images/plan3.dia differ diff --git a/doc/sphinx/source/images/plan3.png b/doc/sphinx/source/images/plan3.png new file mode 100644 index 0000000..d668243 Binary files /dev/null and b/doc/sphinx/source/images/plan3.png differ diff --git a/doc/sphinx/source/images/rule_base_categories.dia b/doc/sphinx/source/images/rule_base_categories.dia new file mode 100644 index 0000000..bfae3bc Binary files /dev/null and b/doc/sphinx/source/images/rule_base_categories.dia differ diff --git a/doc/sphinx/source/images/rule_base_categories.png b/doc/sphinx/source/images/rule_base_categories.png new file mode 100644 index 0000000..8a2704f Binary files /dev/null and b/doc/sphinx/source/images/rule_base_categories.png differ diff --git a/doc/sphinx/source/index.rst b/doc/sphinx/source/index.rst new file mode 100644 index 0000000..9be6569 --- /dev/null +++ b/doc/sphinx/source/index.rst @@ -0,0 +1,100 @@ +.. $Id: index.txt 7e7a566ccb5b 2010-03-04 mtnyogi $ +.. +.. Copyright © 2007-2009 Bruce Frederiksen +.. +.. Permission is hereby granted, free of charge, to any person obtaining a copy +.. of this software and associated documentation files (the "Software"), to deal +.. in the Software without restriction, including without limitation the rights +.. to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +.. copies of the Software, and to permit persons to whom the Software is +.. furnished to do so, subject to the following conditions: +.. +.. The above copyright notice and this permission notice shall be included in +.. all copies or substantial portions of the Software. +.. +.. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +.. IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +.. FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +.. AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +.. LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +.. OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +.. THE SOFTWARE. + +.. toctree:: + :maxdepth: 1 + :hidden: + :titlesonly: + + about_pyke/index + logic_programming/index + knowledge_bases/index + pyke_syntax/index + using_pyke/index + examples + PyCon2008-paper + +============================= +Welcome to Pyke +============================= +----------------------------- +Release 1.1 +----------------------------- + +Pyke introduces a form of `Logic Programming`_ (inspired by Prolog_) to the +Python community by providing a knowledge-based inference engine (expert +system) written in 100% Python. + +Unlike Prolog, Pyke integrates with Python allowing you to invoke Pyke from +Python and intermingle Python statements and expressions within your expert +system rules. + +Pyke was developed to significantly raise the bar on code reuse. Here's how +it works: + +#. You write a set of Python functions, and a set of Pyke rules__ to direct the + configuration and combination of these functions. +#. These functions refer to Pyke `pattern variables`_ within the function body. +#. Pyke may instantiate each of your functions multiple times, providing a + different set of constant values for each of the pattern variables used + within the function body. Each of these instances appears as a different + function. +#. Pyke then automatically assembles these customized functions into a + complete program (function call graph) to meet a specific need or use case. + Pyke calls this function call graph a plan_. + +.. __: `backward-chaining rule lp`_ + +In this way, Pyke provides a way to radically customize and adapt your Python +code for a specific purpose or use case. + +Doing this essentially makes Pyke a very high-level compiler. And taking +this approach also produces dramatic increases in performance. + +And Pyke is very successful at this, providing order of magnitude improvements +in: + +- Code adaptability (or customization), +- Code reuse and +- Performance + +Pyke does not replace Python, nor is meant to compete with Python. Python is +an excellent general purpose programming language, that allows you to "program +in the small". + +Pyke builds upon Python by also giving you tools to directly `program in the +large`_. + +Oh, and Pyke uses Logic Programming to do all of this. So if you're interested +in Logic Programming or Expert Systems, well Pyke has that too... + +Pyke on Google Groups +===================== + +Please join Pyke_ on Google Groups for questions and discussion! + +FAQ +=== + +There is also an FAQ_ list on the sourceforge wiki_, to make it easy to +contribute. + diff --git a/doc/sphinx/source/knowledge_bases/fact_bases.rst b/doc/sphinx/source/knowledge_bases/fact_bases.rst new file mode 100644 index 0000000..180f85f --- /dev/null +++ b/doc/sphinx/source/knowledge_bases/fact_bases.rst @@ -0,0 +1,78 @@ +.. $Id: fact_bases.txt a2119c07028f 2008-10-27 mtnyogi $ +.. +.. Copyright © 2007-2008 Bruce Frederiksen +.. +.. Permission is hereby granted, free of charge, to any person obtaining a copy +.. of this software and associated documentation files (the "Software"), to deal +.. in the Software without restriction, including without limitation the rights +.. to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +.. copies of the Software, and to permit persons to whom the Software is +.. furnished to do so, subject to the following conditions: +.. +.. The above copyright notice and this permission notice shall be included in +.. all copies or substantial portions of the Software. +.. +.. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +.. IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +.. FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +.. AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +.. LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +.. OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +.. THE SOFTWARE. + +========== +Fact Bases +========== + +When statements of fact are stored in Pyke, they are stored in *fact bases*. +Pyke allows you to have as many fact bases as you like to help you organize +your facts. The fact bases are created automatically, as needed, as new facts +are asserted. + +Facts +===== + +Think of a fact as a simple statement_. It has a name and a set of +arguments. The arguments may be: + +- strings + + - proper identifiers don't need quotes: ``Fred`` is the same as ``'Fred'`` + +- numbers +- None, True or False +- tuples of any of these (including nested tuples) + + - singleton tuples don't require a comma: ``(1)`` is the same as ``(1,)`` + +Duplicate facts are not allowed. An attempt to assert a fact that already +exists is silently ignored. But note that to be a duplicate, *all* of the +arguments must be the same! + +Currently facts are thought to be immutable, meaning that they may not be +changed or retracted. That's why dictionaries, lists and user-defined +objects are not recommended as arguments. + +Case Specific Facts +--------------------- + +Most facts are *case specific* facts. This means that they will be deleted +when an engine_ reset_ is done to prepare for another run of the inference +engine. Case specific facts are asserted through either:: + + some_engine.assert_(kb_name, fact_name, arguments) + some_engine.add_case_specific_fact(kb_name, fact_name, arguments) + +They may also be asserted by forward-chaining_ rules. + +Universal Facts +--------------------- + +Universal facts are never deleted (specifically, when a reset_ is done). You +can specify universal facts in a `.kfb file`_, or add universal facts by +calling:: + + some_engine.add_universal_fact(kb_name, fact_name, arguments) + +Typically, all universal facts are added once at `program startup`_. + diff --git a/doc/sphinx/source/knowledge_bases/index.rst b/doc/sphinx/source/knowledge_bases/index.rst new file mode 100644 index 0000000..451c193 --- /dev/null +++ b/doc/sphinx/source/knowledge_bases/index.rst @@ -0,0 +1,105 @@ +.. $Id: index.txt a2119c07028f 2008-10-27 mtnyogi $ +.. +.. Copyright © 2007-2008 Bruce Frederiksen +.. +.. Permission is hereby granted, free of charge, to any person obtaining a copy +.. of this software and associated documentation files (the "Software"), to deal +.. in the Software without restriction, including without limitation the rights +.. to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +.. copies of the Software, and to permit persons to whom the Software is +.. furnished to do so, subject to the following conditions: +.. +.. The above copyright notice and this permission notice shall be included in +.. all copies or substantial portions of the Software. +.. +.. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +.. IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +.. FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +.. AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +.. LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +.. OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +.. THE SOFTWARE. + +.. toctree:: + :maxdepth: 1 + :hidden: + + fact_bases + rule_bases + question_bases + special + +==================== +Knowledge Bases +==================== + +Knowledge is organized into named repositories called *knowledge bases*. +A knowledge base is like a directory for files on disk, except that knowledge +bases may not be nested. Therefore, the *entities* within a knowledge base +always have a two-level name: *knowledge_base_name.knowledge_entity_name*. + +Here are some examples of facts you might see in a web server application:: + + header.cookie('session=123456789;remember=the_alamo') + cookie.session(123456789) + cookie.remember(the_alamo) + header.accept_encoding(gzip) + header.accept_encoding(deflate) + request.path('/my/site.html') + request.path_segment(0, my) + request.path_segment(1, 'site.html') + request.path_segment(-2, my) + request.path_segment(-1, 'site.html') + +Note that three different knowledge bases (all `fact bases`_) are shown here +named ``header``, ``cookie``, and ``request``; each with multiple facts. + +The second part of the two-part name is the name of the *knowledge entity*. +You can think of knowledge entities as statement_ *types* or *topics*. So: + + :request: + is the name of the *knowledge base*. + :request.path_segment: + is the name of the *knowledge entity*, or statement topic. + :request.path_segment(-1, 'site.html'): + is a specific statement about the topic of request.path_segments. + +What do Knowledge Bases Do? +=========================== + +Ultimately Pyke is interested in proving statements, or answering the question +"Is statement X true"? There are several different types of knowledge bases. +Each type of knowledge base represents a different way of doing this: + +- Those that contain simple lists of statements of fact (as you see in the + example above) are called `fact bases`_. + + - These determine whether a statement is true by simply checking to see if + that statement is in their list of known facts. + +- Those that contain *if-then* rules_ are called `rule bases`__. + + - These determine whether a statement is true by running their if-then rules + to try to construct a proof for that statement. + - Rule bases may include both forward-chaining_ and backward-chaining_ rules. + +- It is also possible to create other kinds of knowledge bases that determine + the truth of statements in other ways. Pyke provides two of these: + + - The `question base`_ which just poses the statement to an end user as a + question. + - The special_ knowledge base which has several special one-off knowledge + entities to do different things like run a command_ on the underlying + system and examine its output and/or exit status. + + - There is only has one instance of this knowledge base -- called + ``special``. + +.. __: rule_bases.html + +.. note:: + + All knowledge bases share the same name space. So no two of them, + regardless of their type, may have the same name. + + diff --git a/doc/sphinx/source/knowledge_bases/question_bases.rst b/doc/sphinx/source/knowledge_bases/question_bases.rst new file mode 100644 index 0000000..32bd588 --- /dev/null +++ b/doc/sphinx/source/knowledge_bases/question_bases.rst @@ -0,0 +1,130 @@ +.. $Id: question_bases.txt 4670da845e46 2010-03-05 mtnyogi $ +.. +.. Copyright © 2008 Bruce Frederiksen +.. +.. Permission is hereby granted, free of charge, to any person obtaining a copy +.. of this software and associated documentation files (the "Software"), to deal +.. in the Software without restriction, including without limitation the rights +.. to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +.. copies of the Software, and to permit persons to whom the Software is +.. furnished to do so, subject to the following conditions: +.. +.. The above copyright notice and this permission notice shall be included in +.. all copies or substantial portions of the Software. +.. +.. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +.. IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +.. FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +.. AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +.. LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +.. OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +.. THE SOFTWARE. + +============================================= +Question Bases +============================================= + +Question bases contain questions for end users. These questions can be of +various types (e.g., yes/no, multiple_choice). The questions may be +parametrized, with the parameter values substituted into the question text +when the question is asked. In this case, different parameter values are +treated as different question statements_. + +The answers to all questions are automatically remembered so that if multiple +rules ask the same question, the end user only sees it once. These answers +are erased when an `engine.reset`_ is done. + +Finally, questions may have *reviews* attached to them to automatically +display different canned messages (with parameter substitution) depending on +the end user's answer. + + +KQB files +========== + +Each question base is defined by a `.kqb file`_. The name of each question +base is the filename of the .kqb file (with the ``.kqb`` suffix removed). +This must be a legal Python identifier. + +These .kqb files are compiled and loaded automatically when you create your +`knowledge_engine.engine`_ object. + +The .kqb file contains all of the information about the question needed to +ask the question, validate the answer, and output the appropriate *review* +text. + +The .kqb file also specifies which parameter contains the answer. All +parameters, except this one, must be bound to values before testing the +question. + + +Example +======= + +In writing a program to diagnose car problems, you might have a +``user_question`` question base with questions like:: + + engine_starts($ans) + Can you start the engine? + --- + $ans = yn + + mileage($ans) + How many miles are on the car? + --- + $ans = integer(1-999999) + 200000- ! Wow, that's a lot of miles! + + noise_from($location, $ans) + Do you hear a noise from $location? + --- + $ans = yn + +These would look like the following statements_ within rules_:: + + user_question.engine_starts(True) + user_question.mileage($mileage) + user_question.noise_from('under the hood', False) + +Presenting Questions to Your End Users +====================================== + +Pyke provides two modules to actually present a question to your end user: + +- ask_tty + + This presents the question on stdout and reads stdin for the answer. + +- ask_wx + + This presents the question in a dialog box for use with wxpython_. + +You may write your own module to present questions to end users. Look at the +modules provided by Pyke for guidance. + +To ask a question, Pyke looks for an ``ask_module`` attribute on: + +#. the question_base object, then +#. the `knowledge_engine.engine`_ object + +If the `knowledge_engine.engine`_ object does not have an ``ask_module`` +attribute, ask_tty is imported (by default) and stored there. + +.. This code is hidden. It will add '' to sys.path, change to the doc.examples + directory and store the directory path in __file__ for the code section + following: + >>> import sys + >>> if '' not in sys.path: sys.path.insert(0, '') + >>> import os + >>> os.chdir("../../examples") + >>> __file__ = os.getcwd() + +Here's an example of setting this attribute:: + + >>> from pyke import knowledge_engine + >>> from pyke import ask_wx + + >>> engine = knowledge_engine.engine(__file__) + >>> engine.ask_module = ask_wx + + diff --git a/doc/sphinx/source/knowledge_bases/rule_bases.rst b/doc/sphinx/source/knowledge_bases/rule_bases.rst new file mode 100644 index 0000000..7ba0b68 --- /dev/null +++ b/doc/sphinx/source/knowledge_bases/rule_bases.rst @@ -0,0 +1,243 @@ +.. $Id: rule_bases.txt a2119c07028f 2008-10-27 mtnyogi $ +.. +.. Copyright © 2007-2008 Bruce Frederiksen +.. +.. Permission is hereby granted, free of charge, to any person obtaining a copy +.. of this software and associated documentation files (the "Software"), to deal +.. in the Software without restriction, including without limitation the rights +.. to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +.. copies of the Software, and to permit persons to whom the Software is +.. furnished to do so, subject to the following conditions: +.. +.. The above copyright notice and this permission notice shall be included in +.. all copies or substantial portions of the Software. +.. +.. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +.. IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +.. FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +.. AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +.. LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +.. OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +.. THE SOFTWARE. + +========== +Rule Bases +========== + +Rule bases are collections of rules_. Rule bases are created by writing a +*knowledge rule base* (`.krb`_) file with your favorite text editor. + +A single rule base may contain both forward-chaining_ and backward-chaining_ +rules. + +The forward-chaining rules are run automatically when the rule base is +activated_ to assert new statements_ of fact_. Thus, forward-chaining rules +are not directly used to determine whether any particular statement_ is true. + +But backward-chaining rules *are* directly used to determine whether a +particular statement is true. Thus, when a rule base name (or, more properly, +a `rule base category`_ name, explained below) is used as the +*knowledge base name* in a statement, it refers to the backward-chaining +rules within that rule base. + +.. note:: + + Pyke runs all forward-chaining rules before running any backward-chaining + rules. + + Thus, using rule base names as the *knowledge base name* in statements + within a forward-chaining rule is prohibited, as this would cause + backward-chaining rules to run in order to process the forward-chaining + rule. + +Why Multiple Rule Bases? +======================== + +There are two reasons to have more than one rule base (i.e., more than one +`.krb file`_): + +#. To divide a large set of rules into human manageable units. + + In this case, you want Pyke to use all of the rule bases combined. + + For example, you may have rules governing the automatic generation of SQL + statements, and other rules governing the generation of HTML documents. + You want to keep these rules in different rule bases to make them more + manageable. + +#. To enable your Python program to choose between different rule bases + that are tailored to different specific situations. + + For example, some of the rules governing the automatic generation of SQL + statements may vary depending upon the target database (e.g., ``mysql``, + ``postgresql``, ``oracle``). + + In this case, you want to be able to tell Pyke which of several rule bases + to use on each invocation, depending upon which target database you will + be accessing. You would never use more than one of these rule bases at + the same time, so these rule bases are mutually exclusive. + +These two goals are met by three capabilities: + +- `Rule Base Categories`_ +- `Rule Base Inheritance`_ +- `Rule Base Activation`_ + +Rule Base Categories +==================== + +All rule bases are grouped into categories. Each rule base category only gets +to have **one** active_ rule base. + +Thus, you place rule bases that you want to have active simultaneously into +*different* rule base categories; and place rule bases that are mutually +exclusive with each other (e.g., the ``mysql``, ``postgresql`` and ``oracle`` +rule bases) into the *same* rule base category. + +Each rule base category has a unique name. In the example above you would +want two rule base categories: ``database`` and ``html_generation``. + +The rule base category name is used as the *knowledge base name* for +statements within rules of one rule base category that want to refer to +rules within another rule base category. For example, rule bases in the +``html_generation`` category would need to use ``database.insert(...)`` to +refer to the ``insert`` rules in the ``database`` category. + +Rule Base Inheritance +===================== + +The rule bases within the same category_ share rules amongst themselves +through rule base inheritance. + +Rule bases use single inheritance to inherit the rules_ from one other rule +base. This can go on to any depth. Both forward-chaining_ and +backward-chaining_ rules_ are inherited. + +This allows mutually exclusive rule bases to share common rules in a parent +rule base without having to duplicate these rules amongst themselves. + +Here is the definition, then, of a `rule base category`_: + + Each *root* rule base (through rule base inheritance) defines a unique + rule base category. All rule bases derived (directly or indirectly) + from that root rule base are in the same rule base category. + + The name of the rule base category is simply the name of its root + rule base. + +So, our ``database`` and ``html_generation`` example would look like this: + +.. figure:: ../images/rule_base_categories.png + :width: 450 + :height: 424 + :scale: 100 + :align: center + + Rule Base Categories + +We have one root rule base called ``database`` and it has derived rule bases +called ``mysql``, ``postgresql`` and ``oracle``. And a second root rule base +called ``html_generation`` with ``firefox`` and ``internet_explorer``. + +The two root rule bases define two rule base categories: + +- database, which includes: + + - database + - mysql + - postgresql + - oracle + +- html_generation, which includes: + + - html_generation + - firefox + - internet_explorer + +.. note:: + + The `.krb files`_ for these rule bases may be placed anywhere you want + them within your Pyke source directory structure -- in other words, your + directory structure is not required to match your rule base inheritance + structure. + +Only one rule base from each rule base category may be active_ at any point in +time. + +Within each of these rule bases, if the knowledge base name is omitted from a +statement within a `backward-chaining rule`_, it defaults to the +`rule base category`_ of that rule base, *not* the rule base itself. +Thus, ``insert(...)`` within ``postgresql`` would mean +``database.insert(...)``, and ``make_tag(...)`` within ``firefox`` would mean +``html_generation.make_tag(...)``. + +.. important:: + + Note that referring to a rule base category (either explicitly or + implicitly) *always* refers to the active_ rule base within that category. + This may not be the rule base with that name (the root rule base), or even + the rule base making implicit use of the rule base category. For example, + ``insert(...)`` within ``postgresql`` will end up referring to ``insert`` + rules within the ``oracle`` rule base when ``oracle`` is the active rule + base. + +All rules referring to foreign `rule base categories`_ must explicitly use the +rule base category name. For example, to refer to the ``insert`` rule for +databases, within the ``html_generation`` category, you would have to say +``database.insert(...)``. + +In this way, all of the rules will work no matter which rule base is active +within each rule base category. + +Rule Inheritance +---------------- + +There is an important difference between how backward-chaining_ rule +inheritance works within Pyke rule bases and how method inheritance works +within Python classes: + +* When a derived class in Python defines a method by the same name as a + method in its parent class, the derived method *overrides* the parent + method. I.e., only the derived method is used when a call is made + to it. + +* In contrast, when a derived rule base in Pyke defines + `backward-chaining rules`_ for a goal that also has backward-chaining rules + defined for it in the parent rule base, the derived rule *extends* the + set of rules that may be used to try to prove this goal_kb_. All of the + derived rules will be tried first. If all of these fail, then the parent + rules are tried. + + If you don't want the parent rules to be used for a particular goal_kb_, + you must list that goal name in the ``without`` clause of the extending_ + statement at the beginning of the derived rule base. + +.. note:: + + All `forward-chaining rules`_ in the parent rule base are always included + in the derived rule base. The ``without`` clause only applies to + backward-chaining rules. + +Rule Base Activation +===================== + +Loading_ rule bases only makes them available for use. It does not +automatically activate_ any of them. This must be done explicitly by your +Python program. Your program may decide to activate different rule bases +in different situations. + +Additionally, `forward-chaining rules`_ may be used to activate more specific +rule bases, based upon their inferencing. But once a rule base has been +activated for a `rule base category`_, only children of the currently active +rule base may be activated from that point on. Because these children +inherit_ the rules of the currently active rule base; activating child +rule bases only adds new rules, and doesn't take any away. Thus, any +forward-chaining rule run during the activation of the first rule base +are not invalidated by activating the second rule base. + +In our database example, your program could activate the root ``database`` +rule base and let the forward-chaining rules within the ``database`` rule +base figure out which derived rule base to activate depending on the +particular database in use at the time the program is run. + + diff --git a/doc/sphinx/source/knowledge_bases/special.rst b/doc/sphinx/source/knowledge_bases/special.rst new file mode 100644 index 0000000..38ce193 --- /dev/null +++ b/doc/sphinx/source/knowledge_bases/special.rst @@ -0,0 +1,250 @@ +.. $Id: special.txt 4dca5ad0f397 2010-03-10 mtnyogi $ +.. +.. Copyright © 2007-2008 Bruce Frederiksen +.. +.. Permission is hereby granted, free of charge, to any person obtaining a copy +.. of this software and associated documentation files (the "Software"), to deal +.. in the Software without restriction, including without limitation the rights +.. to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +.. copies of the Software, and to permit persons to whom the Software is +.. furnished to do so, subject to the following conditions: +.. +.. The above copyright notice and this permission notice shall be included in +.. all copies or substantial portions of the Software. +.. +.. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +.. IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +.. FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +.. AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +.. LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +.. OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +.. THE SOFTWARE. + +======= +Special +======= + +There is only one instance of this 'special' knowledge base, called +``special``. + +The ``special`` knowledge base is a collection of miscellaneous helper +*knowledge entities* that determine whether a statement is true or not in +various interesting ways. + +Thus, each entity in this `knowledge base`_ is a Python function that does +something "special" when run. + +The ``special`` functions are: + + - claim_goal_ + - check_command_ + - command_ + - general_command_ + +Claim_goal +========== + +The ``claim_goal`` function has no arguments:: + + special.claim_goal() + +This acts like the Prolog_ `cut operator`_. + +In general there are multiple rules_ that might be used to try to prove_ any +goal_kb_. They are each tried in the order that they appear in the `.krb file`_. +If one rule fails, the next rule is tried. The goal itself doesn't fail +until all of the rules for it have failed. + +Example +------- + +Suppose I want to translate a number, N, into the phrase "N dogs". I could +use the following rules:: + + one_dog + use n_dogs(1, '1 dog') + + n_dogs + use n_dogs($n, $phrase) + when + $phrase = "%d dogs" % $n + +The problem here is that both rules might be used when ``n`` is 1, but the +second rule isn't appropriate in this case. ``Special.claim_goal()`` may +be used to fix this, as follows:: + + one_dog + use n_dogs(1, '1 dog') + when + special.claim_goal() + + n_dogs + use n_dogs($n, $phrase) + when + $phrase = "%d dogs" % $n + +The ``special.claim_goal()`` prevents the second rule from being used when +``n`` is 1. + +Explanation +----------- + +When a rule_ executes ``special.claim_goal()`` in its ``when`` +clause, none of the rest of the rules will be tried for that goal_kb_. +Thus, when ``special.claim_goal()`` is backtracked_ over, the goal fails +immediately without trying any more rules for it. + +This ends up acting like an "else". You place it in the ``when`` clause +after the premises_ that show that this rule **must** be the correct one +to use. Then the subsequent rules will only be tried if these premises +fail, such that ``special.claim_goal()`` is never executed. + +This means that you don't need to add extra premises in each subsequent rule +to make sure that these premises have **not** occurred. + +Without the ``special.claim_goal()`` in the prior example, you would have to +write:: + + one_dog + use n_dogs(1, '1 dog') + + n_dogs + use n_dogs($n, $phrase) + when + check $n != 1 + $phrase = "%d dogs" % $n + +This is a simple example where it is easy to add the check in the second +rule. But in general, it can be difficult to check for prior conditions, +especially when many rules are involved that each has its own condition. + +Running Commands +================ + +The remaining three functions deal with running programs (commands) on the +host computer that is running your Pyke program. Their difference is in what +kind of output they provide from the command. + +These functions all use the `subprocess.Popen`_ function from the standard +Python library. + +Thus, each of these functions accept these three parameters that are passed +on to subprocess.Popen: + + - The ``$command`` parameter (required). + + - This is a tuple indicating the program to run along with its command + line arguments, such as ``(ls, '-l')``. + + - The ``$cwd`` parameter (optional). + + - This specifies the *current working directory* to run the command in. + - If omitted or ``None`` the current working directory is not changed. + + - The ``$stdin`` parameter (optional). + + - This is a string that is fed to the command as its stdin. + + - If the command expects multiple lines of input, this string must + include embedded newlines (e.g., ``'line 1\nline 2\n'``). + + - If omitted or ``None``, no stdin is provided to the command. + +All of these functions fail on backtracking_. + +Check_command +------------- + +:: + + special.check_command($command [, $cwd [, $stdin]]) + +Succeeds if the command returns a zero exit status. Fails otherwise. Any +output from the command to stdout or stderr is unavailable. + + >>> from pyke import knowledge_engine + >>> engine = knowledge_engine.engine() + >>> engine.prove_1_goal('special.check_command((true))') + ({}, None) + >>> engine.prove_1_goal('special.check_command((false))') + Traceback (most recent call last): + ... + pyke.knowledge_engine.CanNotProve: Can not prove special.check_command((false)) + +Command +------- + +:: + + special.command($stdout, $command [, $cwd [, $stdin]]) + +This just looks at the stdout of the command. Any output from the command +to stderr is unavailable. + +The ``$stdout`` is a tuple of lines with the trailing newlines removed. + +This raises `subprocess.CalledProcessError`_ if the command returns a non-zero +exit status. + + >>> from __future__ import with_statement + >>> from pyke import pattern, contexts + >>> def run_command(entity, command, cwd=None, stdin=None): + ... with engine.prove_goal( + ... 'special.%s($output, $command, $cwd, $stdin)' % entity, + ... command=command, + ... cwd=cwd, + ... stdin=stdin) \ + ... as gen: + ... for vars, no_plan in gen: + ... print(vars['output']) + >>> run_command('command', ('echo', 'hi', 'mom')) + ('hi mom',) + >>> run_command('command', ('ls',)) # doctest: +NORMALIZE_WHITESPACE + ('fact_bases.txt', 'index.txt', 'links', 'question_bases.txt', + 'rule_bases.txt', 'special.txt') + >>> run_command('command', ('ls', '-l', 'links.rst')) # doctest: +ELLIPSIS + ('-rw-r--r-- 1 ... links',) + >>> run_command('command', ('tail', '-n', '5', 'template.txt', '-'), + ... '..', # cwd (doc/source) + ... 'stdin: line 1\nstdin: line 2\nstdin: line 3\n') + ... # doctest: +NORMALIZE_WHITESPACE + ('==> template.txt <==', + ' } catch(err) {}', + ' ', + '', + '', + '', + '', + '==> standard input <==', + 'stdin: line 1', + 'stdin: line 2', + 'stdin: line 3') + >>> run_command('command', ('false',)) + Traceback (most recent call last): + ... + subprocess.CalledProcessError: Command 'false' returned non-zero exit status 1 + +General_command +--------------- + +:: + + special.general_command($output, $command [, $cwd [, $stdin]]) + +This is the fully general form that gives you all output from the command. + +The ``$output`` is a three tuple: (exit_status, stdout, stderr). Both stdout +and stderr are single strings (with embedded newlines). + + >>> run_command('general_command', ('echo', 'hi', 'mom')) + (0, 'hi mom\n', '') + >>> run_command('general_command', ('cat', 'foobar')) + (1, '', 'cat: foobar: No such file or directory\n') + >>> run_command('general_command', ('tail', '-n', '5', '../../r2w.ini', 'foobar')) + ... # doctest: +NORMALIZE_WHITESPACE + (1, + "==> ../../r2w.ini <==\ntarget_directory = 'html'\nmacros = + ''\n\n[uservalues]\nversion = '0.2'\n", + "tail: cannot open `foobar' for reading: No such file or directory\n") + + diff --git a/doc/sphinx/source/links.rst b/doc/sphinx/source/links.rst new file mode 100644 index 0000000..b6f71bd --- /dev/null +++ b/doc/sphinx/source/links.rst @@ -0,0 +1,188 @@ +.. _2to3: http://docs.python.org/library/2to3.html +.. _about pyke: about_pyke/index.html +.. _activated: activate_ +.. _activate: using_pyke/index.html#getting-started +.. _activating: activate_ +.. _activation: activate_ +.. _active: knowledge_bases/rule_bases.html#rule-base-activation +.. _anonymous variable: `anonymous variable lp`_ +.. _anonymous variable lp: logic_programming/pattern_matching/pattern_variables.html#anonymous-pattern-variables +.. _anonymous variable syntax: pyke_syntax/krb_syntax/pattern.html#anonymous-variable +.. _asserting new facts: using_pyke/adding_facts.html +.. _ask_tty: knowledge_bases/question_bases.html#presenting-questions-to-your-end-users +.. _assert: using_pyke/adding_facts.html +.. _asserted: assert_ +.. _asserts: assert_ +.. _backtracked: backtracking_ +.. _backtracking: logic_programming/rules/index.html#backtracking +.. _backward chaining: `backward-chaining rule lp`_ +.. _backward-chaining: `backward chaining`_ +.. _backward-chaining rule: `backward-chaining rule lp`_ +.. _backward-chaining rule lp: logic_programming/rules/backward_chaining.html +.. _backward-chaining rule syntax: pyke_syntax/krb_syntax/bc_rule.html +.. _backward-chaining rules: `backward-chaining rule`_ +.. _bc_premise: pyke_syntax/krb_syntax/bc_rule.html#when-clause +.. _bc_rule: `backward-chaining rule`_ +.. _Bc_rule Syntax: bc_rule_ +.. _case specific facts: knowledge_bases/fact_bases.html#case-specific-facts +.. _category: `rule base category`_ +.. _command: knowledge_bases/special.html#running-commands +.. _commands: command_ +.. _compound_premise: pyke_syntax/krb_syntax/compound_premise.html +.. _cooked: `cooking functions`_ +.. _cooking functions: about_pyke/cooking_functions.html +.. _copy_reg: http://docs.python.org/library/copy_reg.html +.. _create an engine: using_pyke/creating_engine.html +.. _creating an inference engine: `create an engine`_ +.. _cut operator: http://en.wikipedia.org/wiki/Cut_%28logic_programming%29 +.. _doctest: http://docs.python.org/library/doctest.html +.. _doctest-tools: http://code.google.com/p/doctest-tools/ +.. _Docutils: http://sourceforge.net/projects/docutils +.. _easy_install: http://peak.telecommunity.com/DevCenter/EasyInstall +.. _engine: `knowledge engine`_ +.. _engine.reset: reset_ +.. _Examples: examples.html +.. _extending: pyke_syntax/krb_syntax/index.html#extending-clause +.. _extending clause: extending_ +.. _fact base: knowledge_bases/fact_bases.html +.. _fact bases: `fact base`_ +.. _fact: knowledge_bases/fact_bases.html#facts +.. _facts: fact_ +.. _family_relations: examples.html#family-relations +.. _FAQ: http://sourceforge.net/apps/trac/pyke/wiki/FAQ +.. _fc_premise: pyke_syntax/krb_syntax/fc_rule.html#foreach-clause +.. _fc_rule: pyke_syntax/krb_syntax/fc_rule.html +.. _forall: pyke_syntax/krb_syntax/compound_premise.html#forall-premise +.. _forall premise: forall_ +.. _forward-chaining: `forward-chaining rule lp`_ +.. _forward-chaining rule: `forward-chaining rule lp`_ +.. _forward-chaining rule lp: logic_programming/rules/forward_chaining.html +.. _forward-chaining rule syntax: pyke_syntax/krb_syntax/fc_rule.html +.. _forward-chaining rules: `forward-chaining rule`_ +.. _fully bound: logic_programming/pattern_matching/matching_patterns.html#binding-to-a-tuple-pattern +.. _functools: http://docs.python.org/library/functools.html +.. _functools.partial: functools_ +.. _goal: `goal lp`_ +.. _goal_kb: `goal syntax`_ +.. _goal lp: logic_programming/rules/backward_chaining.html +.. _goal syntax: pyke_syntax/krb_syntax/bc_rule.html#use-clause +.. _goals: goal_ +.. _hg: Mercurial_ +.. _hgrc: http://www.selenic.com/mercurial/hgrc.5.html +.. _hgrc_keywords: http://pyke.hg.sourceforge.net/hgweb/pyke/release_1/raw-file/tip/hgrc_keywords +.. _home page: index.html +.. _how to call Pyke: using_pyke/index.html +.. _HTMLTemplate: http://py-templates.sourceforge.net/htmltemplate/index.html +.. _inherit: knowledge_bases/rule_bases.html#rule-base-inheritance +.. _inherits: inherit_ +.. _Keyword Extension: http://mercurial.selenic.com/wiki/KeywordExtension +.. _.kfb file: pyke_syntax/kfb_syntax.html +.. _.kfb files: `.kfb file`_ +.. _.kfb: `.kfb file`_ +.. _knapsack problem: http://en.wikipedia.org/wiki/Knapsack_problem +.. _knowledge base: knowledge_bases/index.html +.. _knowledge bases: `knowledge base`_ +.. _knowledge_engine.engine: using_pyke/creating_engine.html +.. _knowledge engine: `knowledge_engine.engine`_ +.. _.kqb file: pyke_syntax/kqb_syntax.html +.. _.kqb files: `.kqb file`_ +.. _.kqb: `.kqb file`_ +.. _.krb file: pyke_syntax/krb_syntax/index.html +.. _.krb files: `.krb file`_ +.. _.krb: `.krb file`_ +.. _KRB files: index.html#lexical-structure +.. _krb_traceback: using_pyke/other_functions.html#krb-traceback +.. _Literal patterns: logic_programming/pattern_matching/literal_patterns.html +.. _loading: `knowledge_engine.engine`_ +.. _Logic Programming: http://en.wikipedia.org/wiki/Logic_programming +.. _logic programming in pyke: logic_programming/index.html +.. _matched: `pattern matches`_ +.. _matching two patterns together: logic_programming/pattern_matching/matching_patterns.html +.. _Mercurial: http://mercurial.selenic.com/wiki/ +.. _Mercurial hook: http://www.selenic.com/mercurial/hgrc.5.html#hooks +.. _Mercurial Hosting Sites: http://mercurial.selenic.com/wiki/MercurialHosting +.. _Modifying Pyke: about_pyke/modifying_pyke.html +.. _MySQL: http://www.mysql.com/ +.. _notany: pyke_syntax/krb_syntax/compound_premise.html#notany-premise +.. _notany premise: notany_ +.. _Other Required Packages: about_pyke/installing_pyke.html#other-required-packages +.. _package: http://www.python.org/doc/essays/packages.html +.. _pathological question: logic_programming/pattern_matching/matching_patterns.html#pathological-question +.. _pattern: `pattern lp`_ +.. _pattern lp: logic_programming/pattern_matching/index.html +.. _pattern syntax: pyke_syntax/krb_syntax/pattern.html +.. _pattern matches: logic_programming/pattern_matching/index.html +.. _pattern matching: `pattern matches`_ +.. _pattern variable: `pattern variable lp`_ +.. _pattern variable lp: logic_programming/pattern_matching/pattern_variables.html +.. _pattern variable syntax: pyke_syntax/krb_syntax/pattern.html#pattern-variable +.. _pattern variables: `pattern variable`_ +.. _patterns: pattern_ +.. _pickled: using_pyke/other_functions.html#running-and-pickling-plans +.. _pickle: http://docs.python.org/library/pickle.html +.. _pickles: pickle_ +.. _pip: http://pypi.python.org/pypi/pip +.. _plan: logic_programming/plans.html +.. _plan_spec: pyke_syntax/krb_syntax/bc_rule.html#plan-spec +.. _plans: plan_ +.. _PLY: http://www.dabeaz.com/ply/ +.. _premise: `premise lp`_ +.. _premise lp: logic_programming/rules/index.html#premises-and-conclusions +.. _premise syntax: pyke_syntax/krb_syntax/bc_rule.html#when-clause +.. _premises: premise_ +.. _print_stats: using_pyke/other_functions.html#miscellaneous +.. _programming in the large: http://portal.acm.org/citation.cfm?id=808431 +.. _program in the large: `programming in the large`_ +.. _program startup: using_pyke/creating_engine.html +.. _Prolog: http://en.wikipedia.org/wiki/Prolog +.. _prove_1: prove_ +.. _prove_1_goal: prove_ +.. _prove_goal: `prove`_ +.. _prove_n: `prove`_ +.. _prove: using_pyke/proving_goals.html +.. _proving: prove_ +.. _proving goals: prove_ +.. _PyCon 2008: http://us.pycon.org/2008/about/ +.. _Pyke: http://groups.google.com/group/pyke +.. _Pyke project download page: http://sourceforge.net/projects/pyke/files/ +.. _pypi: http://pypi.python.org/pypi +.. _Python: http://www.python.org +.. _Python egg file: http://peak.telecommunity.com/DevCenter/PythonEggs +.. _python_premise: pyke_syntax/krb_syntax/python_premise.html +.. _python_statements: pyke_syntax/krb_syntax/python_premise.html#python-statements +.. _question base: knowledge_bases/question_bases.html +.. _question bases: `question base`_ +.. _question: knowledge_bases/question_bases.html +.. _questions: question_ +.. _Rebuilding the HTML Documentation: about_pyke/modifying_pyke.html#rebuilding-the-html-documentation +.. _regular expression syntax: http://docs.python.org/library/re.html#regular-expression-syntax +.. _relative import: http://www.python.org/dev/peps/pep-0328/ +.. _reset: using_pyke/index.html#using-different-facts-for-different-cases +.. _rest2web: http://sourceforge.net/projects/rest2web +.. _rule base category: knowledge_bases/rule_bases.html#rule-base-categories +.. _rule base: knowledge_bases/rule_bases.html +.. _rule base's: `rule base`_ +.. _rule bases: `rule base`_ +.. _rule: logic_programming/rules/index.html +.. _rule's: rule_ +.. _rules: rule_ +.. _Running Unit Tests: about_pyke/modifying_pyke.html#running-unit-tests +.. _siege: http://www.joedog.org/index/siege-home +.. _special: knowledge_bases/special.html +.. _sqlgen: examples.html#sqlgen +.. _statement: logic_programming/statements.html +.. _statements: statement_ +.. _string.Template: http://docs.python.org/library/string.html#template-strings +.. _subprocess.CalledProcessError: http://docs.python.org/library/subprocess.html#exceptions +.. _subprocess.Popen: http://docs.python.org/library/subprocess.html#subprocess.Popen +.. _taking clause: pyke_syntax/krb_syntax/bc_rule.html#taking-clause +.. _traceback: http://docs.python.org/library/traceback.html +.. _Tuple patterns: logic_programming/pattern_matching/tuple_patterns.html +.. _TurboGears 2: http://turbogears.org/2.0/ +.. _variables: `pattern variable`_ +.. _web_framework: examples.html#web-framework +.. _when clause: pyke_syntax/krb_syntax/bc_rule.html#when-clause +.. _wiki: http://sourceforge.net/apps/trac/pyke/wiki +.. _with clause: pyke_syntax/krb_syntax/bc_rule.html#with-clause +.. _WSGI: http://www.python.org/dev/peps/pep-0333/ +.. _wxpython: http://www.wxpython.org/ diff --git a/doc/sphinx/source/logic_programming/index.rst b/doc/sphinx/source/logic_programming/index.rst new file mode 100644 index 0000000..30ca21d --- /dev/null +++ b/doc/sphinx/source/logic_programming/index.rst @@ -0,0 +1,54 @@ +.. $Id: index.txt 057d79259b20 2009-05-14 mtnyogi $ +.. +.. Copyright © 2007-2008 Bruce Frederiksen +.. +.. Permission is hereby granted, free of charge, to any person obtaining a copy +.. of this software and associated documentation files (the "Software"), to deal +.. in the Software without restriction, including without limitation the rights +.. to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +.. copies of the Software, and to permit persons to whom the Software is +.. furnished to do so, subject to the following conditions: +.. +.. The above copyright notice and this permission notice shall be included in +.. all copies or substantial portions of the Software. +.. +.. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +.. IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +.. FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +.. AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +.. LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +.. OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +.. THE SOFTWARE. + +.. toctree:: + :maxdepth: 1 + :hidden: + + statements + pattern_matching/index + rules/index + plans + +========================== +Logic Programming Tutorial +========================== + +Pyke is an inference engine that applies rules_ to facts_ to establish +additional facts (through forward-chaining_ rules), and/or to prove *goals* +and optionally assemble Python functions +into customized call graphs, called plans_ (through backward-chaining_ rules). + +Pyke may then be reset_, deleting the last set of facts, so that the cycle +may be repeated. For each cycle a different rule base may be activated_. + +The plan_ capability allows the postponement of code execution until the +top-level goal has been completely proven. This shields the code from +blind alleys and the backtracking_ that occurs within the rules. + +Once a plan_ has been created, it may be executed multiple times with different +arguments. It may also be pickled_, and later run again only requiring one +small Pyke module. + +Pyke also provides an end user question_ capability, as well as the capability +to run commands_ on the local system to guide the inferencing. + diff --git a/doc/sphinx/source/logic_programming/pattern_matching/index.rst b/doc/sphinx/source/logic_programming/pattern_matching/index.rst new file mode 100644 index 0000000..75ef0e1 --- /dev/null +++ b/doc/sphinx/source/logic_programming/pattern_matching/index.rst @@ -0,0 +1,78 @@ +.. $Id: index.txt a2119c07028f 2008-10-27 mtnyogi $ +.. +.. Copyright © 2008 Bruce Frederiksen +.. +.. Permission is hereby granted, free of charge, to any person obtaining a copy +.. of this software and associated documentation files (the "Software"), to deal +.. in the Software without restriction, including without limitation the rights +.. to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +.. copies of the Software, and to permit persons to whom the Software is +.. furnished to do so, subject to the following conditions: +.. +.. The above copyright notice and this permission notice shall be included in +.. all copies or substantial portions of the Software. +.. +.. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +.. IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +.. FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +.. AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +.. LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +.. OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +.. THE SOFTWARE. + +.. toctree:: + :maxdepth: 1 + :hidden: + + literal_patterns + pattern_variables + tuple_patterns + matching_patterns + pathological_answer + +================ +Pattern Matching +================ + +Pattern matching is a little strange at first, so we're going to ease into +it slowly... + +There are two aspects to pattern matching: *patterns*, which you write, and +*matching*, which is what Pyke does with your patterns. + +In a nutshell, patterns are made up of two fundamental building blocks: + +#. `Literal patterns`_. + + - Theses are just data values that only match themselves. + +#. `Pattern variables`_. + + - These will match anything. (Including other pattern variables, as we'll + see later)! + +And one compound pattern: + +#. `Tuple patterns`_. + + - These match -- you guessed it! -- tuples! + +We'll examine all of the above looking at how Pyke matches a pattern to data. +Then we'll expand this to cover `matching two patterns together`_. + +And finally, a `pathological question`_ to see if you've been paying +attention. + +Sound like fun? Good! Let's get started! + +OK, so why do we need patterns? The simple answer is that we need patterns +to generalize statements_. One example is to turn statements into questions. + +.. important:: + - When you want a *direct* statement, such as to state a fact, you just use + *data* for its arguments. + - But when you want a *generalized* statement, such as to ask a question, + you use *patterns* for its arguments. + + + diff --git a/doc/sphinx/source/logic_programming/pattern_matching/literal_patterns.rst b/doc/sphinx/source/logic_programming/pattern_matching/literal_patterns.rst new file mode 100644 index 0000000..a006e07 --- /dev/null +++ b/doc/sphinx/source/logic_programming/pattern_matching/literal_patterns.rst @@ -0,0 +1,47 @@ +.. $Id: literal_patterns.txt a2119c07028f 2008-10-27 mtnyogi $ +.. +.. Copyright © 2008 Bruce Frederiksen +.. +.. Permission is hereby granted, free of charge, to any person obtaining a copy +.. of this software and associated documentation files (the "Software"), to deal +.. in the Software without restriction, including without limitation the rights +.. to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +.. copies of the Software, and to permit persons to whom the Software is +.. furnished to do so, subject to the following conditions: +.. +.. The above copyright notice and this permission notice shall be included in +.. all copies or substantial portions of the Software. +.. +.. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +.. IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +.. FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +.. AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +.. LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +.. OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +.. THE SOFTWARE. + +================ +Literal Patterns +================ + +You want to ask Pyke a question. The simplest questions are just asking "Is +statement X true?". Going back to Pyke's family_relations_ example, your +Python program might want to know whether it's really true that Bruce's +parents are Thomas and Norma? So it would ask whether the following statement +is true:: + + family.son_of(Bruce, Thomas, Norma) + +Pyke would search the facts that's it's been told about and answer "thumbs up" +because you've told it before that this statement is true and it has +remembered that. + +In this case, all of the statement's arguments are *literal patterns*. You +might consider literal patterns to be *input* to Pyke. You're passing Bruce, +Thomas and Norma *into* Pyke. And Pyke just answers "thumbs up" or "thumbs +down". + +Literal patterns look exactly like data. Thus, your question would look +exactly like you see above. + + diff --git a/doc/sphinx/source/logic_programming/pattern_matching/matching_patterns.rst b/doc/sphinx/source/logic_programming/pattern_matching/matching_patterns.rst new file mode 100644 index 0000000..773e79b --- /dev/null +++ b/doc/sphinx/source/logic_programming/pattern_matching/matching_patterns.rst @@ -0,0 +1,108 @@ +.. $Id: matching_patterns.txt a2119c07028f 2008-10-27 mtnyogi $ +.. +.. Copyright © 2008 Bruce Frederiksen +.. +.. Permission is hereby granted, free of charge, to any person obtaining a copy +.. of this software and associated documentation files (the "Software"), to deal +.. in the Software without restriction, including without limitation the rights +.. to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +.. copies of the Software, and to permit persons to whom the Software is +.. furnished to do so, subject to the following conditions: +.. +.. The above copyright notice and this permission notice shall be included in +.. all copies or substantial portions of the Software. +.. +.. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +.. IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +.. FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +.. AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +.. LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +.. OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +.. THE SOFTWARE. + +===================== +Matching Two Patterns +===================== + +You've now seen all of the different kinds of patterns and how they are +matched to data. Not too bad so far... + +Patterns are used to generalize statements. One situation where you need to +generalize a statement is when you want to ask a question. That's been +covered above. + +The other situation where you need to generalize a statement is when you write +rules_, which are explained later. + +But rules also need to be able to match one pattern to another *pattern*, +and not just match patterns to *data* as we have discussed so far. + +So before we can move on to rules, we need to examine how one pattern is +matched to another pattern. + +The short answer is that it all comes down to pattern variables and that +pattern variables may not only be bound to *data*, but may also be bound to +other *patterns*. + +Binding to a Literal Pattern +============================ + +Binding a pattern variable to a literal pattern is just like binding it to the +data within that literal pattern. Nothing fancy here! + +Binding to Another Pattern Variable +===================================== + +When pattern variable A is bound to pattern variable B, they essentially +become the *same pattern variable*. Basically, pattern variable A *becomes* +pattern variable B (or, you might say, *defers* to pattern variable B). + +Let's say that pattern variable A has been bound to pattern variable B and +that pattern variable B is still unbound. + +#. Prior to binding pattern variable B to a value, it doesn't matter whether + you ask if pattern variable A is bound or pattern variable B is bound, the + answer is False for both. + +#. It doesn't matter whether you match pattern variable A to a value or + pattern variable B to a value. In both cases, it is pattern variable B + that gets bound to this value. + +#. And once pattern variable B is bound to a value, it doesn't matter whether + you ask for the bound value of pattern variable A or pattern variable B, + you will get the same value. + +So for all intents and purposes pattern variable A and pattern variable B +become the same pattern variable. + +Binding to a Tuple Pattern +=========================== + +Because pattern variables may be bound to tuple patterns, the term *fully +bound* is introduced. Asking whether the pattern variable is *fully bound* +means that not only is it bound to a value (the tuple pattern), but that all +of the subordinate patterns (recursively) within the tuple pattern are also +bound to values. + +Being *fully bound* means that the bound value of the pattern variable can be +converted into standard Python data without any pattern variables in it. This +is important when Pyke wants to talk to Python because Python has no concept +of storing *variables* within its data structures. + + +Pathological Question +===================== + +What is the bound value of pattern variable ``$y`` after matching the +following two tuple patterns: + + Tuple pattern A: + ``((ho, $_, ($a, $a)), ($a, $a, $b), ($a, *$b))`` + Tuple pattern B: + ``($x, $x, $y)`` + +The answer is here_ (but no peeking!). + +.. _here: pathological_answer.html + + diff --git a/doc/sphinx/source/logic_programming/pattern_matching/pathological_answer.rst b/doc/sphinx/source/logic_programming/pattern_matching/pathological_answer.rst new file mode 100644 index 0000000..3ea1128 --- /dev/null +++ b/doc/sphinx/source/logic_programming/pattern_matching/pathological_answer.rst @@ -0,0 +1,97 @@ +.. $Id: pathological_answer.txt a2119c07028f 2008-10-27 mtnyogi $ +.. +.. Copyright © 2008 Bruce Frederiksen +.. +.. Permission is hereby granted, free of charge, to any person obtaining a copy +.. of this software and associated documentation files (the "Software"), to deal +.. in the Software without restriction, including without limitation the rights +.. to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +.. copies of the Software, and to permit persons to whom the Software is +.. furnished to do so, subject to the following conditions: +.. +.. The above copyright notice and this permission notice shall be included in +.. all copies or substantial portions of the Software. +.. +.. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +.. IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +.. FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +.. AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +.. LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +.. OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +.. THE SOFTWARE. + +==================== +Pathological Answer +==================== + +This is the answer to the following question: + +Pathological Question +===================== + +What is the bound value of pattern variable ``$y`` after matching the +following two tuple patterns: + + Tuple pattern A: + ``((ho, $_, ($a, $a)), ($a, $a, $b), ($a, *$b))`` + Tuple pattern B: + ``($x, $x, $y)`` + +Answer +====== + +Let's take this step by step, matching each element of the two tuple patterns +in turn. + +#. Match ``(ho, $_, ($a, $a))`` to ``$x``. + + This succeeds with the following binding: + + ``$x``: + ``(ho, $_, ($a, $a))`` + +#. Match ``($a, $a, $b)`` to ``$x``. + + Because ``$x`` is bound to a value, this becomes the same as matching: + + - ``($a, $a, $b)`` to + - ``(ho, $_, ($a, $a))`` + + Which succeeds, binding: + + ``$a``: + ``ho`` + ``$b``: + ``($a, $a)`` + + ``$_`` is an anonymous variable, so it is not bound (or bound to). + +#. Match ``($a, *$b)`` to ``$y``. + + Because both ``$a`` and ``$b`` have bound values, this becomes the same as + matching: + + - ``(ho, ho, ho)`` to + - ``$y`` + + Which succeeds, binding: + + ``$y``: + ``(ho, ho, ho)`` + +So the overall match succeeds with the following bindings: + + ``$x``: + ``(ho, $_, ($a, $a))`` + ``$a``: + ``ho`` + ``$b``: + ``($a, $a)`` + ``$y``: + ``(ho, ho, ho)`` + +And so ``$y`` is ``(ho, ho, ho)``! + +.. note:: + If you got this right, you should really be using Pyke! + diff --git a/doc/sphinx/source/logic_programming/pattern_matching/pattern_variables.rst b/doc/sphinx/source/logic_programming/pattern_matching/pattern_variables.rst new file mode 100644 index 0000000..956f36e --- /dev/null +++ b/doc/sphinx/source/logic_programming/pattern_matching/pattern_variables.rst @@ -0,0 +1,125 @@ +.. $Id: pattern_variables.txt a2119c07028f 2008-10-27 mtnyogi $ +.. +.. Copyright © 2008 Bruce Frederiksen +.. +.. Permission is hereby granted, free of charge, to any person obtaining a copy +.. of this software and associated documentation files (the "Software"), to deal +.. in the Software without restriction, including without limitation the rights +.. to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +.. copies of the Software, and to permit persons to whom the Software is +.. furnished to do so, subject to the following conditions: +.. +.. The above copyright notice and this permission notice shall be included in +.. all copies or substantial portions of the Software. +.. +.. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +.. IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +.. FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +.. AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +.. LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +.. OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +.. THE SOFTWARE. + +================= +Pattern Variables +================= + +To take this to the next level, you might want to ask "Who are the sons of +Thomas and Norma?". In this case, you are passing ``Thomas`` and ``Norma`` +*into* Pyke, and you'd like Pyke to pass something back *out* to you as part of +the answer (in addition to the thumbs up). + +Pattern variables serve as output parameters. They start with a ``$``:: + + family.son_of($son, Thomas, Norma) + +You can use whatever name you like after the ``$``. Pyke will answer with a +thumbs up *binding* ``$son`` to ``Bruce``. If you don't like that answer, you +can reject that answer and ask Pyke for another answer ("nope, try again!"). +Each time Pyke finds another son for Thomas and Norma, it answers with another +thumbs up and ``$son`` *bound* to a different value. + +If you reject the last son of ``Thomas`` and ``Norma`` (or if ``Thomas`` and +``Norma`` have no sons in the first place), Pyke will answer with a thumbs +down. + +We say that Pyke *binds* ``Bruce`` to the pattern variable ``$son`` when it +comes back with its first thumbs up. When we tell Pyke "nope, try again!", +Pyke must first *unbind* ``$son`` before it can go on and *bind* it to the +next value. The "nope" part does the *unbinding*, and the "try again" part +does the *binding* again to a new value. + +So at any point in time, a pattern variable is either *bound* to a value or +*unbound*. If we follow a particular pattern variable through time, we might +see that it is alternately bound and unbound many times as Pyke tries to find +a suitable answer to your question. Specifically, when Pyke comes back with +the final thumbs down, ``$son`` is unbound. + +Anonymous Pattern Variables +=========================== + +Suppose we want to know who Norma's sons are? In this case we don't care +about the father. We use *anonymous variables* as "don't care" placeholders. + +An anonymous variable is any pattern variable who's name starts with an +underscore (``_``). The rest of the name doesn't matter and just serves as +documentation (and so ``$_`` is all that's strictly needed). + +So "Who are Norma's sons?" looks like:: + + family.son_of($son, $_father, Norma) + +We're giving Norma as input to Pyke, and asking for the ``$son`` as output and +telling Pyke that we don't care about the ``$_father``. + +Anonymous variables are never bound to values. (So you could say that they +are always unbound). + +Pattern Variable Identity +=========================== + +Now this is very important, so pay attention! The same pattern variable +*name* means the same *pattern variable*. Thus, if you say ``$son`` twice, +it's the *same* pattern variable. And, a pattern variable can only be bound +to one value (at a time), so you mean the *same* data value must appear in +both places. + +.. note:: + This does *not* apply to `anonymous pattern variables`_. Since they are + never bound to a value, each use of the same anonymous variable can match + different data values. + +So if you wanted to see all of the sons with the same name as their fathers, +you would ask:: + + family.son_of($father, $father, $_mother) + +.. note:: + + The Pyke family_relations_ example never uses the same name for both + father and son because that would cause confusion about which of them + was the father to both of their sons and daughters. + + In these cases, it modifies the name of the son to make it unique. + + Thus, this question would always fail within the family_relations + example... + +And so here is the complete explanation of how pattern variables are matched +to a data value. + +First, the pattern variable is checked to see if it is already bound to a +value. + +If it is bound to a value, then this bound value has to match the data for +the match to succeed. + +If it is unbound, then the pattern variable is bound to the data value as a +by-product of doing the match, and the match always succeeds. + +And so pattern variables only "match any value" when they are unbound. And in +matching that value, they become bound to it as a by-product of doing the +match. Once bound to a value, a pattern variable will only match that value +(much like a literal pattern). + + diff --git a/doc/sphinx/source/logic_programming/pattern_matching/tuple_patterns.rst b/doc/sphinx/source/logic_programming/pattern_matching/tuple_patterns.rst new file mode 100644 index 0000000..015d5c1 --- /dev/null +++ b/doc/sphinx/source/logic_programming/pattern_matching/tuple_patterns.rst @@ -0,0 +1,60 @@ +.. $Id: tuple_patterns.txt a2119c07028f 2008-10-27 mtnyogi $ +.. +.. Copyright © 2008 Bruce Frederiksen +.. +.. Permission is hereby granted, free of charge, to any person obtaining a copy +.. of this software and associated documentation files (the "Software"), to deal +.. in the Software without restriction, including without limitation the rights +.. to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +.. copies of the Software, and to permit persons to whom the Software is +.. furnished to do so, subject to the following conditions: +.. +.. The above copyright notice and this permission notice shall be included in +.. all copies or substantial portions of the Software. +.. +.. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +.. IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +.. FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +.. AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +.. LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +.. OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +.. THE SOFTWARE. + +================ +Tuple Patterns +================ + +Tuple patterns only match tuples. They are written as simply a comma +separated list of patterns within parenthesis:: + + (1, $x, "buckle my shoe") + +You can also write a *rest* pattern variable at the end using an asterisk +(``*``):: + + ($a, fie, $b, *$c) + +This will match the rest of the items in the data value that the tuple pattern +is matched to. Note that the *rest* pattern variable is *always* bound to a +tuple. + +Examples: + + - matching ``(1, $x, "buckle my shoe")`` to ``(1, 2, "buckle my shoe")`` + matches, binding ``$x`` to 2. + - matching ``(1, $x, "buckle my shoe")`` to ``(1, 2, "buckle my belt")`` + does not match because the third pattern within the tuple pattern fails + to match the third value in the matched tuple. + - matching ``($a, fie, $b, *$c)`` to ``(fee, fie, foe, fum)`` + matches, binding ``$a`` to ``fee``, ``$b`` to ``foe`` and ``$c`` to + ``(fum)``. + - matching ``($a, fie, $b, *$c)`` to ``(fee, fie, foe)`` + matches, binding ``$a`` to ``fee``, ``$b`` to ``foe`` and ``$c`` to + ``()``. + - matching ``($a, fie, $b, *$c)`` to ``(fee, fie)`` does not match because + the data value has to have a length of at least three. + +.. hint:: + You can use ``(*$foo)`` to only match a tuple. It will bind ``$foo`` to + the entire tuple, but will fail to match any other data type. + diff --git a/doc/sphinx/source/logic_programming/plans.rst b/doc/sphinx/source/logic_programming/plans.rst new file mode 100644 index 0000000..51ad02d --- /dev/null +++ b/doc/sphinx/source/logic_programming/plans.rst @@ -0,0 +1,259 @@ +.. $Id: plans.txt 4dca5ad0f397 2010-03-10 mtnyogi $ +.. +.. Copyright © 2007-2008 Bruce Frederiksen +.. +.. Permission is hereby granted, free of charge, to any person obtaining a copy +.. of this software and associated documentation files (the "Software"), to deal +.. in the Software without restriction, including without limitation the rights +.. to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +.. copies of the Software, and to permit persons to whom the Software is +.. furnished to do so, subject to the following conditions: +.. +.. The above copyright notice and this permission notice shall be included in +.. all copies or substantial portions of the Software. +.. +.. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +.. IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +.. FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +.. AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +.. LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +.. OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +.. THE SOFTWARE. + +============================================= +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 +======================== + +.. This code is hidden. It will add '' to sys.path, change to the doc.examples + directory and store the directory path in __file__ for the code section + following: + >>> import sys + >>> if '' not in sys.path: sys.path.insert(0, '') + >>> import os + >>> os.chdir("../../examples") + >>> __file__ = os.getcwd() + +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: + +.. figure:: ../images/plan1.png + :width: 187 + :height: 118 + :scale: 100 + :align: center + + 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: + +.. figure:: ../images/plan2.png + :width: 187 + :height: 195 + :scale: 100 + :align: center + + 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: + +.. figure:: ../images/plan3.png + :width: 264 + :height: 198 + :scale: 100 + :align: center + + 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`_. + + diff --git a/doc/sphinx/source/logic_programming/rules/backward_chaining.rst b/doc/sphinx/source/logic_programming/rules/backward_chaining.rst new file mode 100644 index 0000000..40bbb71 --- /dev/null +++ b/doc/sphinx/source/logic_programming/rules/backward_chaining.rst @@ -0,0 +1,268 @@ +.. $Id: backward_chaining.txt 4dca5ad0f397 2010-03-10 mtnyogi $ +.. +.. Copyright © 2007-2008 Bruce Frederiksen +.. +.. Permission is hereby granted, free of charge, to any person obtaining a copy +.. of this software and associated documentation files (the "Software"), to deal +.. in the Software without restriction, including without limitation the rights +.. to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +.. copies of the Software, and to permit persons to whom the Software is +.. furnished to do so, subject to the following conditions: +.. +.. The above copyright notice and this permission notice shall be included in +.. all copies or substantial portions of the Software. +.. +.. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +.. IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +.. FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +.. AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +.. LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +.. OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +.. THE SOFTWARE. + +============================================= +Backward Chaining +============================================= + +Backward chaining rules_ are processed when your program asks Pyke a question_ +(i.e., asks Pyke to prove_ a specific *goal*). Pyke will only use activated_ +`rule bases`_ to do the proof. + +Overview of "Backward-Chaining" +=============================== + +To do backward-chaining, Pyke finds rules whose *then* part matches the *goal* +(i.e., the question). Once it finds such a rule, it tries to (recursively) +prove all of the subgoals in the *if* part of that rule. Some of these +subgoals are matched against facts, and others are subgoals for other +backward-chaining rules. If all of the subgoals can be proven, the rule +succeeds and the original goal is proven. Otherwise, the rule fails, and Pyke +tries to find another rule whose *then* part matches the goal, and so on. + +So Pyke ends up linking (or *chaining*) the *if* part of the first rule to the +*then* part of the next rule. + +Reviewing: + +#. Pyke starts by finding a rule whose *then* part matches the goal. +#. Pyke then proceeds to process the *if* part of that rule. +#. Which may link (or chain) to the *then* part of another rule. + +Since Pyke processes these rules from *then* to *if* to *then* to *if* in the +reverse manner that we normally think of using rules, it's called *backward* +chaining. + +To make this more clear, Pyke has you write your backward-chaining rules +upside down by writing the *then* part first (since that's how it is +processed). + +"Use", "When" Rather than "Then", "If" +====================================== + +But *then-if* rules sound confusing, so Pyke uses the words **use** and +**when** rather than **then** and **if**. You can then read the rule as "use" +this statement "when" these other statements can be proven. + +.. note:: + + Unlike the *assert* clause in forward-chaining_ rules, Pyke only allows + one statement in the *use* clause. + +Example +================= + +Consider this example:: + + 1 direct_father_son + 2 use father_son($father, $son, ()) + 3 when + 4 family2.son_of($son, $father) + + 5 grand_father_son + 6 use father_son($grand_father, $grand_son, (grand)) + 7 when + 8 father_son($father, $grand_son, ()) + 9 father_son($grand_father, $father, ()) + + 10 great_grand_father_son + 11 use father_son($gg_father, $gg_son, (great, $prefix1, *$rest_prefixes)) + 12 when + 13 father_son($father, $gg_son, ()) + 14 father_son($gg_father, $father, ($prefix1, *$rest_prefixes)) + + 15 brothers + 16 use brothers($brother1, $brother2) + 17 when + 18 father_son($father, $brother1, ()) + 19 father_son($father, $brother2, ()) + 20 check $brother1 != $brother2 + + 21 uncle_nephew + 22 use uncle_nephew($uncle, $nephew, $prefix) + 23 when + 24 brothers($uncle, $father) + 25 father_son($father, $nephew, $prefix1) + 26 $prefix = ('great',) * len($prefix1) + + 27 cousins + 28 use cousins($cousin1, $cousin2, $distance, $removed) + 29 when + 30 uncle_nephew($uncle, $cousin1, $prefix1) + 31 father_son($uncle, $cousin2, $prefix2) + 32 $distance = min(len($prefixes1), len($prefixes2)) + 1 + 33 $removed = abs(len($prefixes1) - len($prefixes2)) + +.. note:: + These rules_ draw the same conclusions as the forward-chaining_ example_, + with the addition of the *brothers*, *uncle_nephew* and *cousins* rules. + +We can draw something similar to a function call graph with these rules: + +.. figure:: ../../images/bc_rules.png + :width: 509 + :height: 583 + :scale: 100 + :align: center + + Example Rules + +These rules_ are not used until you ask Pyke to prove_ a goal. + +The easiest way to do this is with *some_engine.prove_1_goal* or +*some_engine.prove_goal*. Prove_1_goal_ only returns the first proof found +and then stops (or raises ``pyke.knowledge_engine.CanNotProve``). Prove_goal_ +returns a context manager for a generator that generates all possible proofs +(which, in some cases, might be infinite). + +Both functions return the `pattern variable`_ variable bindings, along with +the plan_. + +Backtracking with Backward-Chaining Rules +========================================= + +For this example, these are the starting set of ``family2`` facts:: + + 1 son_of(tim, thomas) + 2 son_of(fred, thomas) + 3 son_of(bruce, thomas) + 4 son_of(david, bruce) + +And we want to know who fred's nephews are. So we'd ask ``uncle_nephew(fred, +$nephew, $prefix)``. + +Here are the steps (in parenthesis) in the inferencing up until the first +failure is encountered (with the line number from the example preceding each +line):: + + (1) 22 use uncle_nephew(fred, $nephew, $prefix) + 24 brothers(fred, $father) + (2) 16 use brothers(fred, $brother2) + 18 father_son($father, fred, ()) + (3) 2 use father_son($father, fred, ()) + 4 family2.son_of(fred, $father) + matches fact 2: son_of(fred, thomas) + 19 father_son(thomas, $brother2, ()) + (4) 2 use father_son(thomas, $son, ()) + 4 family2.son_of($son, thomas) + matches fact 1: son_of(tim, thomas) + 20 check fred != tim + 25 father_son(tim, $nephew, $prefix1) + (5.1) 2 use father_son(tim, $son, ()) + 4 family2.son_of($son, tim) => FAILS + (5.2) 6 use father_son(tim, $grand_son, (grand)) + 8 father_son(tim, $grand_son, ()) + 2 use father_son(tim, $son, ()) + 4 family2.son_of($son, tim) => FAILS + (5.3) 11 use father_son(tim, $gg_son, (great, $prefix1, *$rest_prefixes)) + 13 father_son(tim, $gg_son, ()) + 2 use father_son(tim, $son, ()) + 4 family2.son_of($son, tim) => FAILS + +Each rule invocation is numbered (in parenthesis) as a step number. Step 5 +has tried 3 different rules and they have all failed (5.1, 5.2 and 5.3). + +If you think of the rules as functions, the situation now looks like this +(the step numbers that succeeded circled in black, and steps that failed +circled in red): + +.. figure:: ../../images/bc_backtracking.png + :width: 590 + :height: 465 + :scale: 100 + :align: center + + We Need to Backtrack! + +At this point, Pyke has hit a dead end and must backtrack. The way that +backtracking proceeds is to go back up the list of steps executed, combining +the steps from all rules into one list. Thus, when step 5 fails, Pyke backs +up to step 4 and tries to find another solution to that step. + +If another solution is found, Pyke proceeds forward again from that point. If +no other solutions are found, Pyke backs up another step. + +When Pyke goes back to step 4, the next solution binds ``$son`` to ``fred``. +This fails the subsequent check in the ``brothers`` rule:: + + 20 check $brother1 != $brother2 + +And so Pyke goes back to step 4 once again. The next solution binds ``$son`` +to ``bruce``. This succeeds for ``brother`` and is passed down to +``father_son`` which returns ``david`` as ``fred's`` nephew. + +Further backtracking reveals no other solutions. + +Backtracking Summary +-------------------- + +Thus we see: + +#. The backtracking_ algorithm: "**fail** goes *up* (or *back*) while + **success** goes *down* (or *forward*)" is not limited to the steps within a + *single* rule's ``when`` clause; but includes the *entire* chain of + inferencing from the very start of trying to prove the top level goal. +#. This execution model is not available within traditional programming + languages like Python. +#. The ability to go back to *any* point in the computation to try an + alternate solution is where backward-chaining systems get their power! + +.. This code is hidden. It will add '' to sys.path, change to the doc.examples + directory and store the directory path in __file__ for the code section + following: + >>> import sys + >>> if '' not in sys.path: sys.path.insert(0, '') + >>> import os + >>> os.chdir("../../../examples") + >>> __file__ = os.getcwd() + +Running the Example +======================== + + >>> from pyke import knowledge_engine + >>> engine = knowledge_engine.engine(__file__) + >>> engine.activate('bc_related') + +Nothing happens this time when we activate the rule base, because there are no +forward-chaining rules here. + +We want to ask the question: "Who are Fred's nephews?". This translates +into the Pyke statement: ``bc_related.uncle_nephew(fred, $v1, $v2)``. + +.. note:: + Note that we're using the name of the rule base, ``bc_related`` rather than + the fact base, ``family2`` here; because we expect this answer to come from + the ``bc_related`` rule base. + +This is 'bc_related', 'uncle_nephew', with ('fred',) followed by 2 pattern +variables as arguments: + + >>> from __future__ import with_statement + >>> with engine.prove_goal('bc_related.uncle_nephew(fred, $nephew, $distance)') as gen: + ... for vars, no_plan in gen: + ... print(vars['nephew'], vars['distance']) + david () + + +.. _example: forward_chaining.html#example + + diff --git a/doc/sphinx/source/logic_programming/rules/forward_chaining.rst b/doc/sphinx/source/logic_programming/rules/forward_chaining.rst new file mode 100644 index 0000000..b2c3610 --- /dev/null +++ b/doc/sphinx/source/logic_programming/rules/forward_chaining.rst @@ -0,0 +1,403 @@ +.. $Id: forward_chaining.txt 9f7068449a4b 2010-03-08 mtnyogi $ +.. +.. Copyright © 2007-2008 Bruce Frederiksen +.. +.. Permission is hereby granted, free of charge, to any person obtaining a copy +.. of this software and associated documentation files (the "Software"), to deal +.. in the Software without restriction, including without limitation the rights +.. to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +.. copies of the Software, and to permit persons to whom the Software is +.. furnished to do so, subject to the following conditions: +.. +.. The above copyright notice and this permission notice shall be included in +.. all copies or substantial portions of the Software. +.. +.. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +.. IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +.. FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +.. AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +.. LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +.. OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +.. THE SOFTWARE. + +================== +Forward Chaining +================== + +Forward chaining rules_ are processed automatically as each `rule base`_ is +activated_. + +When a rule base is activated_, all of its forward-chaining rules_ are run +in the order that they appear in the `.krb file`_ for that rule base. + +Overview of Forward-Chaining +============================= + +To do forward-chaining, Pyke finds rules whose *if* clause matches Pyke's list +of already known facts (the *if* clause may match, or *succeed*, multiple time; +see backtracking_). Each time a rule succeeds, it *fires* this rule, which +adds the facts in the *then* clause of that rule to the list of already known +facts. + +These new facts may fire other forward-chaining rules by matching their +*if* clause. This can go on to any depth. So Pyke ends up linking (or +*chaining*) the *then* clause of the first rule to the *if* clause of the next +rule. + +.. note:: + + Forward-chaining continues until no more rules_ can be fired. + +Reviewing +---------- + +#. Pyke starts with the *if* clause of the first rule and checks to see if it + matches the known facts. +#. If so, it proceeds to the *then* clause of that rule (*firing* the rule). +#. Which may link (or *chain*) to the *if* clause of another rule. + +Since Pyke processes these rules from *if* to *then* to *if* to *then* in the +manner that we normally think of using rules, it's called *forward* chaining. + +"Foreach", "Assert" Rather than "If", "Then" +============================================ + +Finally, since the statements within the *if* clause of the rule contain +patterns_; they may each match several facts. In this case, the rule will +succeed and be fired multiple times. + +The statements in the *then* clause of the rule also contain patterns. +Each time the rule is fired, the pattern variables within the *then* +statements are bound to different values so that different facts are asserted. + +To avoid confusion, Pyke uses the words **foreach** and **assert** rather +than **if** and **then** for forward-chaining rules. This is to suggest that +"for each" combination of facts matching the first list of statements, +the rule is fired to "assert" the facts in the second list of statements. + +.. note:: + + The use of **foreach** and **assert** identifies the rule as a + forward-chaining rule. + +Example +======= + +This example will figure out the paternal ancestry of individuals given a list +of starting statements about who the sons of each father are. (Daughters and +mothers are omitted to keep the example brief). These facts are stored in a +`fact base`_ called ``family1`` as ``son_of(son, father)``:: + + 1 son_of(david, bruce) + 2 son_of(bruce, thomas) + 3 son_of(thomas, frederik) + 4 son_of(frederik, hiram) + +We want to derive ``father_son`` relationships of the following form:: + + father_son($father, $son, $prefix) + +where + + :$son: + is the name of the son (e.g., david) + :$father: + is the name of the father (e.g., bruce) + :$prefix: + is a tuple of prefixes before the 'father' and 'son' titles to + indicate the number of generations (e.g., ``()`` for direct + father_son relationships, ``(grand)``, ``(great, grand)``, etc). + +This is done using three forward-chaining rules. Each rule is presented as a +separate step: + +- `Step 1: Direct_father_son`_ + + - Step 1 demonstrates the use of `pattern matching`_ to transfer values + from one statement within the rule to another statement. + +- `Step 2: Grand_father_son`_ + + - Step 2 demonstrates backtracking_ within the premises_ of a + forward-chaining rule. Understanding this will help you to understand + `backward-chaining rules`_. + +- `Step 3: Great_grand_father_son`_ + + - Step 3 demonstrates a recursive forward-chaining rule. + +Finally, you will be shown how to `Run the Example`_ yourself. + +Step 1: Direct_father_son +========================= + +First we need to establish the direct father_son relationships (those whose +``$prefix`` is ``()``):: + + 1 direct_father_son + 2 foreach + 3 family1.son_of($son, $father) + 4 assert + 5 family1.father_son($father, $son, ()) + +The Use of Pattern Variables +---------------------------- + +This demonstrates how `pattern variables`_ are used to transfer values from +one statement within a rule into another statement within the rule. + +This rule has a single statement in its ``foreach`` clause (on line 3). This +statement matches all four ``son_of`` facts, so the rule succeeds four times; +but with different bindings for the ``$son`` and ``$father`` pattern variables. +This causes different facts to be asserted when the same ``assert`` clause (on +line 5) is run four times; because each time line 5 is run, the values for +``$son`` and ``$father`` are transferred from the statement on line 3 to the +statement on line 5. + +When the rule fires matching line 3 to:: + + 1 son_of(david, bruce) + +It runs line 5 to assert:: + + 5 father_son(bruce, david, ()) + +And when the rule fires a second time matching line 3 to:: + + 2 son_of(bruce, thomas) + +It runs line 5 a second time to assert:: + + 6 father_son(thomas, bruce, ()) + +The rule fires twice more for the remaining ``son_of`` facts, asserting +two more ``father_son`` relationships. So this rule adds a total of four +new facts:: + + 5 father_son(bruce, david, ()) + 6 father_son(thomas, bruce, ()) + 7 father_son(frederik, thomas, ()) + 8 father_son(hiram, frederik, ()) + +Step 2: Grand_father_son +======================== + +Now we want to add grand son-father relationships. We have a new rule for +this:: + + 6 grand_father_son + 7 foreach + 8 family1.father_son($father, $grand_son, ()) + 9 family1.father_son($grand_father, $father, ()) + 10 assert + 11 family1.father_son($grand_father, $grand_son, (grand)) + +The Use of Backtracking +----------------------- + +The ``grand_father_son`` rule_ is run for all combinations of ``father_son`` +facts_ that satisfy the two ``foreach`` statements_ (on lines 8 and 9) and +asserts_ a ``(grand)`` ``father_son`` statement (on line 11) for each +combination. + +This rule is a good example for backtracking_ and will help later in your +understanding of backtracking with backward-chaining_. So let's follow the +backtracking in the execution of this rule. + +The ``foreach`` clause has two statements (on lines 8 and 9) in it that are +both looking for ``father_son`` facts with a prefix of ``()``:: + + 8 family1.father_son($father, $grand_son, ()) + 9 family1.father_son($grand_father, $father, ()) + +These will be matched to the following ``family1`` facts (facts 5 through 8):: + + 5 father_son(bruce, david, ()) + 6 father_son(thomas, bruce, ()) + 7 father_son(frederik, thomas, ()) + 8 father_son(hiram, frederik, ()) + +Pyke starts at the top of the list of premises and looks for a match for the +first premise (on line 8). This matches fact 5, so the first premise +succeeds, binding ``$father`` to ``bruce``:: + + 8 family1.father_son($father, $grand_son, ()) => fact 5, SUCCESS + 9 family1.father_son($grand_father, $father, ()) + +*Success* means go *down*, so Pyke goes to the next premise on line 9. This +succeeds with fact 6 (because ``$father`` is bound to ``bruce``):: + + 8 family1.father_son($father, $grand_son, ()) => fact 5 + 9 family1.father_son($grand_father, $father, ()) => fact 6, SUCCESS + +*Success* means go *down*, but Pyke is at the end of the list of premises, +so the *rule* succeeds and Pyke fires the rule to assert:: + + 9 father_son(thomas, david, (grand)) + +Since this is a forward-chaining rule, Pyke wants to get *all* of the answers +from it that it can, so it continues as if it had a failure (i.e., as if it's +not happy with this answer). + +.. note:: + + You'll see later that Pyke doesn't do this automatically with + backward-chaining_ rules. + +So Pyke *fails* back *up* to the second premise and looks for another +``father_son`` after fact 6 with ``bruce`` as the first argument. This +fails:: + + 8 family1.father_son($father, $grand_son, ()) => fact 5 + 9 family1.father_son($grand_father, $father, ()) => FAILS + +*Fail* means go *up*, so Pyke goes up to the first premise and looks for +another ``father_son`` after fact 5, which succeeds for fact 6, binding +``$father`` to ``thomas``:: + + 8 family1.father_son($father, $grand_son, ()) => fact 6, SUCCESS + 9 family1.father_son($grand_father, $father, ()) + +*Success* means go *down*, so Pyke goes down to the second premise which +succeeds for fact 7:: + + 8 family1.father_son($father, $grand_son, ()) => fact 6 + 9 family1.father_son($grand_father, $father, ()) => fact 7, SUCCESS + +*Success* means go *down*, but Pyke is at the end of the list of premises, +so the *rule* succeeds and Pyke fires the rule to assert:: + + 10 father_son(frederik, bruce, (grand)) + +Then Pyke *fails* back *up* to the second premise, and continues looking for +another match after fact 7. This fails:: + + 8 family1.father_son($father, $grand_son, ()) => fact 6 + 9 family1.father_son($grand_father, $father, ()) => FAILS + +*Fail* means go *up*, so Pyke goes back to the first premise and continues +looking for another match after fact 6. (Since fact 7 is just like the last +case, we'll skip matching fact 7 and go straight to the last fact, fact 8). +The match to fact 8 succeeds, binding ``$father`` to ``hiram``:: + + 8 family1.father_son($father, $grand_son, ()) => fact 8, SUCCESS + 9 family1.father_son($grand_father, $father, ()) + +*Success* means go *down*, so Pyke goes to the second premise and looks for a +``father_son`` for ``hiram``. This fails:: + + 8 family1.father_son($father, $grand_son, ()) => fact 8 + 9 family1.father_son($grand_father, $father, ()) => FAILS + +*Fail* means go *up*, so Pyke goes back up to the first premise and looks for +another match after fact 8. There are no more facts, so this fails:: + + 8 family1.father_son($father, $grand_son, ()) => FAILS + 9 family1.father_son($grand_father, $father, ()) + +*Fail* means go *up*, but Pyke is at the top of the list of premises, +so the *rule* fails and Pyke is done processing it. + +.. important:: + + Note that the *last* statement in the ``foreach`` clause may *succeed* + multiple times (which fires the ``assert`` clause multiple times). + + But the *first* statement in the ``foreach`` clause may only *fail* once. + When that happens, the whole rule fails and the show's over for this rule! + +So running the ``grand_father_son`` rule results in addition of these three +facts:: + + 9 father_son(thomas, david, (grand)) + 10 father_son(frederik, bruce, (grand)) + 11 father_son(hiram, thomas, (grand)) (this is the one we skipped) + +Step 3: Great_grand_father_son +============================== + +Finally, we want to add great(...) grand son-father relationships. We have +a final rule for this:: + + 12 great_grand_father_son + 13 foreach + 14 family1.father_son($father, $gg_son, ()) + 15 family1.father_son($gg_father, $father, ($prefix1, *$rest_prefixes)) + 16 assert + 17 family1.father_son($gg_father, $gg_son, + (great, $prefix1, *$rest_prefixes)) + +.. note:: + + Note how the $prefixes for the statement on line 15 are specified as + ``($prefix1, *$rest_prefixes)``, rather than just ``$prefix``. + This is done so that it does *not* match ``()``. (But it will still match + ``(grand)`` by binding ``$rest_prefixes`` to ``()``). + +This is the only rule that can be recursive. As this rule asserts_ new facts_, +those facts may be used by the same rule (by matching the statement on line +15) to produce even more great, great, ... ``father_son`` relationships. + +Recursive Rules +--------------- + +Running this rule normally will assert the following two facts:: + + 12 father_son(frederik, david, (great, grand)) + 13 father_son(hiram, bruce, (great, grand)) + +But, since these facts may also be used by the same rule (on line 15), Pyke +checks each one by trying to run the rule again just for that new fact. + +Trying this for the first new fact: ``father_son(frederik, david, +(great, grand))`` fails to find anything because ``david`` is not a father. + +Trying this for the second new fact: ``father_son(hiram, bruce, (great, +grand))`` results in one more new fact:: + + 14 father_son(hiram, david, (great, great, grand)) + +Now this last new fact is tried again with this rule, which fails again +because ``david`` is not a father. + +So at this point Pyke is finished with this rule. The rule ended up firing +three times, asserting:: + + 12 father_son(frederik, david, (great, grand)) + 13 father_son(hiram, bruce, (great, grand)) + 14 father_son(hiram, david, (great, great, grand)) + + +Running the Example +=========================== + +.. This code is hidden. It will add '' to sys.path, change to the doc.examples + directory and store the directory path in __file__ for the code section + following: + >>> import sys + >>> if '' not in sys.path: sys.path.insert(0, '') + >>> import os + >>> os.chdir("../../../examples") + >>> __file__ = os.getcwd() + +These rules could be run as follows: + + >>> from pyke import knowledge_engine + >>> engine = knowledge_engine.engine(__file__) + >>> engine.activate('fc_related') # This is where the rules are run! + >>> engine.get_kb('family1').dump_specific_facts() + father_son('bruce', 'david', ()) + father_son('thomas', 'bruce', ()) + father_son('frederik', 'thomas', ()) + father_son('hiram', 'frederik', ()) + father_son('thomas', 'david', ('grand',)) + father_son('frederik', 'bruce', ('grand',)) + father_son('hiram', 'thomas', ('grand',)) + father_son('frederik', 'david', ('great', 'grand')) + father_son('hiram', 'bruce', ('great', 'grand')) + father_son('hiram', 'david', ('great', 'great', 'grand')) + + +.. _Run the Example: `Running the Example`_ + + diff --git a/doc/sphinx/source/logic_programming/rules/index.rst b/doc/sphinx/source/logic_programming/rules/index.rst new file mode 100644 index 0000000..ab1dac8 --- /dev/null +++ b/doc/sphinx/source/logic_programming/rules/index.rst @@ -0,0 +1,140 @@ +.. $Id: index.txt a2119c07028f 2008-10-27 mtnyogi $ +.. +.. Copyright © 2007-2008 Bruce Frederiksen +.. +.. Permission is hereby granted, free of charge, to any person obtaining a copy +.. of this software and associated documentation files (the "Software"), to deal +.. in the Software without restriction, including without limitation the rights +.. to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +.. copies of the Software, and to permit persons to whom the Software is +.. furnished to do so, subject to the following conditions: +.. +.. The above copyright notice and this permission notice shall be included in +.. all copies or substantial portions of the Software. +.. +.. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +.. IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +.. FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +.. AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +.. LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +.. OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +.. THE SOFTWARE. + +.. toctree:: + :maxdepth: 1 + :hidden: + + forward_chaining + backward_chaining + +======= +Rules +======= + +Conceptually, a rule is very simple:: + + if + A + B + C + then + D + E + +Meaning, "if A, B *and* C are true, then D and E are also true". These are +often called *if-then* rules. + +.. admonition:: And what are A, B, C, D and E? + + They are simply statements_. Specifically, they are generalized statements + containing patterns_ as arguments. You'll see more about this later. + +Premises and Conclusions +================================= + +Rules have two parts to them: an **if** part (containing a list of statements +called the *premises*), and a **then** part (containing a list of statements +called the *conclusions*). + +.. note:: + + Pyke doesn't use the words **if** and **then**, as you'll see shortly. + +Each of these **if** and **then** parts contain one or more facts_ or goals_ +which are just generalized statements_ (meaning statements with patterns_ as +arguments). + + +Processing the Premises Within the *If* Clause +============================================== + +Because each premise with the *if* clause is a generalized statement, the +premise is pattern matched to known facts. This means that it may match more +than one fact. + +Pyke tries all combinations of matching facts through a process called +*backtracking*. This will cause the same rule to potentially succeed multiple +times, once for each unique combination of matching facts. + +Backtracking +------------ + +Note that while processing the list of premises within a rule's ``if`` +clause: + +* If Pyke succeeds at proving a premise: + + * Pyke will proceed *down* to the next premise in the list. + * Trying to proceed *down* from the *last* premise in the list (when it + succeeds) causes the rule to *succeed*. + +* If Pyke fails at proving a premise: + + * Pyke will back *up* to the prior premise in the list and try to find + another solution for it. The process starts over at the prior premise, + going back down or further up the list depending on whether another + solution can be found to the prior premise or not. + * Trying to back *up* from the *first* premise in the list (when it fails) + causes the rule to *fail*. + +.. figure:: ../../images/backtracking.png + :alt: Backtracking Flow Diagram + :align: center + + Backtracking Flow Diagram + +Thus, execution within each rule's ``if`` clause proceeds both backwards and +forwards, up and down the list of premises, depending on whether each +premise succeeds or fails. The process of backing up within the ``if`` +clause to try alternate solutions is called *backtracking*. + +Inferencing +=========== + +Rules are specified individually within a `rule base`_. They are not nested +or explicitly linked to each other. So Pyke must automatically figure out +how to combine these rules to accomplish some larger task. This is called +*inferencing* and there are two different approaches that Pyke uses: + +- All forward-chaining_ rules are processed when a `rule base`_ is activated_. + + - Forward-chaining rules may assert_ new facts, and activate_ more specific + `rule bases`_. + +- Backward-chaining_ rules are processed when your program asks Pyke to + prove_ a specific *goal* (i.e., asks Pyke a question_). + + - These rules are designed to answer a question rather than assert new + facts or activate more specific rule bases. + - They also have the ability to assemble Python functions into a customized + call graph or program, called a plan_, to meet a specific need. + +.. note:: + + Forward-chaining and backward-chaining rules may both be included within + the same `rule base`_. Pyke knows the difference between forward-chaining + and backward-chaining rules because they have different syntax__. + +.. __: ../../pyke_syntax/krb_syntax/index.html + + diff --git a/doc/sphinx/source/logic_programming/statements.rst b/doc/sphinx/source/logic_programming/statements.rst new file mode 100644 index 0000000..99ecbcb --- /dev/null +++ b/doc/sphinx/source/logic_programming/statements.rst @@ -0,0 +1,166 @@ +.. $Id: statements.txt a2119c07028f 2008-10-27 mtnyogi $ +.. +.. Copyright © 2008 Bruce Frederiksen +.. +.. Permission is hereby granted, free of charge, to any person obtaining a copy +.. of this software and associated documentation files (the "Software"), to deal +.. in the Software without restriction, including without limitation the rights +.. to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +.. copies of the Software, and to permit persons to whom the Software is +.. furnished to do so, subject to the following conditions: +.. +.. The above copyright notice and this permission notice shall be included in +.. all copies or substantial portions of the Software. +.. +.. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +.. IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +.. FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +.. AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +.. LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +.. OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +.. THE SOFTWARE. + +=========== +Statements +=========== + +A *statement* is a statement of fact, also just called a *fact*. They are +the bread and butter of Pyke. Statements are the data values that Pyke acts +upon. + +You might also think of a statement as a spoken sentence. For example, the +Pyke family_relations_ example deals with sentences like:: + + "Bruce is the son of Thomas (his father) and Norma (his mother)." + +But we condense the sentence down to it's essence. In this case, the sentence +revolves around three things: Bruce, Thomas and Norma. All of the rest of the +words can be condensed into a single identifier that identifies the +relationship between these three things:: + + son_of(Bruce, Thomas, Norma) + +We can give these condensed sentence structures any names that we want. In +this case, I chose ``son_of``. I might have also chosen "parents_of", which +might conjure the following English sentence:: + + "The parents of Bruce are Thomas (his father) and Norma (his mother)." + +Or:: + + "Bruce's parents are Thomas (his father) and Norma (his mother)." + +But the ``son_of`` form carries the additional information that Bruce is a son +rather than a daughter. So this is the form used in the family_relations +example. + +.. caution:: Statements are not functions! + When we wear our Python hats, ``son_of(Bruce, Thomas, Norma)`` looks like a + function call! We might expect that it can be executed to *do* something + and possibly return a value. But when we wear our Pyke hats, this is just + a statement, or a piece of data. It doesn't *do* anything and it **never** + returns a value! + +Note that it makes perfect sense to have several statements defining the same +relationship between their arguments:: + + son_of(Bruce, Thomas, Norma) + son_of(Michael, Bruce, Marilyn) + son_of(David, Bruce, Marilyn) + +But this only makes sense if they have different arguments. There is never a +need to state the same fact twice. Thus we can never establish two facts (two +statements) that are identical. If we try to do this, the second one is +silently ignored. + +So:: + + son_of(Bruce, Thomas, Norma) + son_of(Bruce, Thomas, Norma) + son_of(Bruce, Thomas, Norma) + +is exactly the same as:: + + son_of(Bruce, Thomas, Norma) + +Finally, we see that the position of each argument is important. In our +``son_of`` example, the meaning of each argument is:: + + son_of(son, father, mother) + +Thus, changing the order of the arguments changes the meaning of the +statement. So:: + + son_of(Bruce, Thomas, Norma) + +and:: + + son_of(Bruce, Norma, Thomas) + +mean different things! The first statement says that Thomas is the father of +Bruce, but the second statement says that Norma is the father! + +Syntactic Structure of Statements +================================= + +So we see that statements in Pyke are very structured. + +Pyke categorizes statements into `knowledge bases`_. You create knowledge +bases to help you organize your statements. A *knowledge base* in Pyke +roughly corresponds to a *module* in Python. + +.. note:: + Pyke does not allow knowledge bases to contain other knowledge bases, only + information about statements. Thus, there is only one level of knowledge + bases; and beneath each knowledge base, one level of statements. + +So statements have three components: + +#. The name of a knowledge base. For example, ``family``. +#. The name of a *knowledge entity*. For example, ``son_of``. +#. The statement arguments. These are just Python data. Currently in Pyke, + there is a push for these arguments to be immutable. + +The syntax for a statement looks like this:: + + statement ::= IDENTIFIER '.' IDENTIFIER '(' {argument,} ')' + +Knowledge Base +-------------- + +The first IDENTIFIER is the name of the knowledge base. In our +family_relations example, this is ``family``. + +.. note:: + You'll see that within `backward-chaining rules`_, the name of the + knowledge base may be omitted. It defaults to the currently selected + `rule base`_ for this `rule base category`_. You'll learn more about this + later. + +Knowledge Entity +---------------- + +The second IDENTIFIER is the name of the *knowledge entity*. This is the +relationship between the arguments. You could also think of this as the +statement *type* or *topic*. For example, ``son_of`` is a *type* of statement +with three arguments: (son, father, mother). Or the (son, father, mother) +arguments are about the *topic* ``son_of``. + +Arguments +--------- + +The arguments can be any simple Python data value (numbers, strings, None, +True or False) or tuples of these values (including nested tuples). +Currently, statements are supposed to be immutable, so all of the arguments +are immutable. The arguments relate to the topic, above, to make a complete +statement. + +.. note:: + Prolog_ allows arguments to be other statements (functors). But Pyke needs + to integrate into Python and Python has no concept of a "statement". So we + just use tuples in Pyke because Python is very happy with tuples! + +So the complete statement for our family_relations example is:: + + family.son_of(Bruce, Thomas, Norma) + diff --git a/doc/sphinx/source/pyke_syntax/index.rst b/doc/sphinx/source/pyke_syntax/index.rst new file mode 100644 index 0000000..d209d33 --- /dev/null +++ b/doc/sphinx/source/pyke_syntax/index.rst @@ -0,0 +1,138 @@ +.. $Id: index.txt a2119c07028f 2008-10-27 mtnyogi $ +.. +.. Copyright © 2008 Bruce Frederiksen +.. +.. Permission is hereby granted, free of charge, to any person obtaining a copy +.. of this software and associated documentation files (the "Software"), to deal +.. in the Software without restriction, including without limitation the rights +.. to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +.. copies of the Software, and to permit persons to whom the Software is +.. furnished to do so, subject to the following conditions: +.. +.. The above copyright notice and this permission notice shall be included in +.. all copies or substantial portions of the Software. +.. +.. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +.. IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +.. FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +.. AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +.. LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +.. OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +.. THE SOFTWARE. + +.. toctree:: + :maxdepth: 1 + :hidden: + + kfb_syntax + krb_syntax/index + kqb_syntax + +=========== +Pyke Syntax +=========== + +Source Files +============ + +Pyke has three different kinds of source files for the three main types of +`knowledge bases`_: + +#. *Knowledge Fact Base* (KFB) files for `fact bases`_. +#. *Knowledge Rule Base* (KRB) files for `rule bases`_. +#. *Knowledge Question Base* (KQB) files for `question bases`_. + +Each type of source file ends in a different file suffix: ``.kfb``, +``.krb`` or ``.kqb``. + +Place all of these source files into a directory structure. Then include this +directory as an argument to the `knowledge_engine.engine`_ constructor. +This will recursively search your directory for these three types of source +files, compile them, and load them into the engine. How you organize these +files into subdirectories is up to you -- the directory structure does not +matter to Pyke. + +The ``.kfb`` and ``.kqb`` files are compiled into Python pickles_ with +``.fbc`` and ``.qbc`` suffixes. + +The ``.krb`` files are compiled into up to three ``.py`` source files. +The names of these ``.py`` files are the same as the ``.krb`` file, but with +different endings: + +- ``_fc`` (if there are any forward-chaining_ rules) +- ``_bc`` (if there are any backward-chaining_ rules) and +- ``_plans`` (if any of the backward-chaining rules have a plan_) + +These ``.py`` files are then automatically imported to define the rule base. +This causes Python to compile them into ``.pyc`` or ``.pyo`` files. + +Subsequent runs of the `knowledge_engine.engine`_ constructor only recompile +the Pyke source files that have changed since the last time they were compiled. + +The name of each knowledge base is the filename of the Pyke source file with +the suffix removed. This must be a legal Python identifier. + +Syntax Legend +============== + +To describe this syntax, the following punctuation is used: + +'*any_chars*' + Required punctuation or keyword: *any_chars*. +*a* | *b* + Alternation: *a* or *b*. +[*a*] + Optional *a*. +{*a*} + One or more *a*'s. **But** it is understood that if *a* ends in a comma, + the last comma is optional. +IDENTIFIER + Any legal Python identifier. Example: *foobar* +NUMBER + Any legal Python integer or floating point literal. + Examples: *123*, *3.14*. +STRING + Any legal Python string literal. + Examples: *'Hi Mom!'*, *u"Hi Dad!\\n"*, *r'''don't gobble my \\'s!'''*, + *ur"""leave \\'s alone!"""*. +TEXT + Only used in KQB files. This signifies any text (any characters) other + than the delimiter characters containing the ``TEXT``. +PARAMETRIZED_TEXT + Only used in KQB files. This signifies any text (any characters) through + the end of the line and all text on subsequent lines that are indented at + least as much as the first ``PARAMETRIZED_TEXT`` character on the first + line. All ``PARAMETRIZED_TEXT`` is treated as a `string.Template`_ and + may include ``$IDENTIFIER`` or ``${IDENTIFIER}`` parameters. All other + ``$`` characters must be doubled (``$$``). +REGEXP_TEXT + Only used in KQB files. This signifies any text (any characters) excluding + an unescaped backslash (``\``) at the end. These are given to the Python's + ``re`` module as regular expressions and must follow Python's + `regular expression syntax`_. +NL + One or more newlines. +INDENT + The following text must be indented to a higher level (more) than the + previous text. +DEINDENT + The following text must be indented one less level than the previous text. + +Lexical Structure +======================= + +The lexical structure is much like Python. Like Python, indenting is +significant. It uses the same commenting, line continuation and literal +formats for strings and numbers (but doesn't use complex numbers). It also +uses the same rules for forming identifiers. + +The two notable exceptions to Python conventions are: + +#. Identifiers may be used as strings, without requiring quotes. + + - So ``foobar`` is the same as ``'foobar'``. + +#. Singleton tuples do not require a trailing comma. + + - So ``(1)`` is the same as ``(1,)``. + diff --git a/doc/sphinx/source/pyke_syntax/kfb_syntax.rst b/doc/sphinx/source/pyke_syntax/kfb_syntax.rst new file mode 100644 index 0000000..34126d3 --- /dev/null +++ b/doc/sphinx/source/pyke_syntax/kfb_syntax.rst @@ -0,0 +1,75 @@ +.. $Id: kfb_syntax.txt a2119c07028f 2008-10-27 mtnyogi $ +.. +.. Copyright © 2008 Bruce Frederiksen +.. +.. Permission is hereby granted, free of charge, to any person obtaining a copy +.. of this software and associated documentation files (the "Software"), to deal +.. in the Software without restriction, including without limitation the rights +.. to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +.. copies of the Software, and to permit persons to whom the Software is +.. furnished to do so, subject to the following conditions: +.. +.. The above copyright notice and this permission notice shall be included in +.. all copies or substantial portions of the Software. +.. +.. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +.. IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +.. FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +.. AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +.. LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +.. OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +.. THE SOFTWARE. + +=================================== +KFB Syntax +=================================== + +This uses the same lexical structure as `KRB files`_, except that the only +keywords are: + +- None +- True +- False + +The name of the `fact base`_ is simply the filename with the ``.kfb`` suffix +stripped. This must be a valid Python identifier. + +Syntax for KFB File +=================== + +:: + + file ::= [NL] {fact NL} + + fact ::= IDENTIFIER '(' [{data,}] ')' + + data ::= 'None' | 'True' | 'False' + | NUMBER | IDENTIFIER | STRING + | '(' [{data,}] ')' + + +Example +======= + +This is taken from the family_relations_ example:: + + # family.kfb + + son_of(bruce, thomas, norma) + son_of(fred_a, thomas, norma) + son_of(tim, thomas, norma) + daughter_of(vicki, thomas, norma) + daughter_of(jill, thomas, norma) + + daughter_of(nanette, arthur2, kathleen) + son_of(arthur3, arthur2, kathleen) + daughter_of(sue, arthur2, kathleen) + son_of(ed, arthur2, kathleen) + daughter_of(marilyn, arthur2, kathleen) + son_of(david_b, arthur2, kathleen) + daughter_of(m_helen, arthur2, kathleen) + + son_of(m_thomas, bruce, marilyn) + son_of(david_a, bruce, marilyn) + + diff --git a/doc/sphinx/source/pyke_syntax/kqb_syntax.rst b/doc/sphinx/source/pyke_syntax/kqb_syntax.rst new file mode 100644 index 0000000..5111429 --- /dev/null +++ b/doc/sphinx/source/pyke_syntax/kqb_syntax.rst @@ -0,0 +1,512 @@ +.. $Id: kqb_syntax.txt 4dca5ad0f397 2010-03-10 mtnyogi $ +.. +.. Copyright © 2008 Bruce Frederiksen +.. +.. Permission is hereby granted, free of charge, to any person obtaining a copy +.. of this software and associated documentation files (the "Software"), to deal +.. in the Software without restriction, including without limitation the rights +.. to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +.. copies of the Software, and to permit persons to whom the Software is +.. furnished to do so, subject to the following conditions: +.. +.. The above copyright notice and this permission notice shall be included in +.. all copies or substantial portions of the Software. +.. +.. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +.. IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +.. FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +.. AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +.. LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +.. OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +.. THE SOFTWARE. + +========== +KQB Syntax +========== + +This uses a different lexical structure than `KRB files`_. Textual parameter +substitution is done with the standard Python `string.Template`_ class, which +specifies parameters with a ``$``, much like `pattern variables`_. In the +following descriptions, substitution parameters are acted upon in +``PARAMETRIZED_TEXT``, but not in ``TEXT``. + +The name of the `question base`_ is simple the filename with the ``.kqb`` +suffix stripped. This must be a valid Python identifier. + + +PARAMETRIZED_TEXT +================== + +``PARAMETRIZED_TEXT`` may span multiple lines. Each subsequent line must +be indented at least as much as the first character of ``PARAMETRIZED_TEXT`` +on the first line. Line breaks and indentation are preserved. So syntax +like:: + + match '!' PARAMETRIZED_TEXT + +Could be:: + + 2-10 ! This is the start of the + parametrized text. + ^ + | + +------ all lines must be indented at least this far! + + - what do you think? + +The ``PARAMETRIZED_TEXT`` here would be:: + + This is the start of the + parametrized text. + ^ + | + +------ all lines must be indented at least this far! + + - what do you think? + +But this is not legal:: + + 2-10 ! An example of PARAMETRIZED_TEXT + with a second line that's not indented enough! + + +Syntax for KQB File +=================== + +:: + + file ::= [NL] {question} + + question ::= IDENTIFIER '(' [{parameter,}] ')' NL INDENT + {PARAMETRIZED_TEXT NL} + '---' NL + parameter '=' question_type + DEINDENT + + parameter ::= '$' IDENTIFIER + +Each question has a name and a fixed number of parameters. This is followed +by one or more lines of ``PARAMETRIZED_TEXT`` that will be presented as the +question to answer. These are terminated by a line containing only ``---``. + +One of the parameters is designated as the *answer* parameter on the line +immediately following the terminating ``---``. This is the only parameter +that may be unbound when a rule uses this question. + +For example, the file ``user_questions.kqb`` might contain:: + + ate($meal, $ans) + Did you eat $meal? + --- + $ans = yn + +This question could be referenced in the `premise syntax`_ of a rule_ as:: + + user_questions.ate(lunch, $ans) + +or:: + + user_questions.ate(lunch, False) + +But not:: + + user_questions.ate($meal, False) + +There are several different kinds of ``question_types``, each corresponding to +a different way that the user might answer the question:: + + question_type ::= yn_type + | integer_type + | number_type + | float_type + | string_type + | select_1_type + | select_n_type + + +The ``integer_type``, ``number_type``, ``float_type`` and ``string_type`` may +include a match_ to force the user to enter a sensible answer. + +All of these may also include a review_, which is just ``PARAMETRIZED_TEXT`` +that will be displayed when the user's answer matches a certain match_ value. + +Question_type links: + +- yn_type_ +- integer_type_ +- number_type_ +- float_type_ +- string_type_ +- select_1_type_ +- select_n_type_ + +YN_type +======= + +:: + + yn_type ::= 'yn' NL [review] + +The user answers "yes" or "no". The answer returned is True or False. If the +`ask_tty`_ module is used, the user may type "yes", "y", "true" or "t" for +True and "no", "n", "false", or "f" for False. These are case insensitive. + +Example:: + + ate($meal, $ans) + Did you eat $meal? + --- + $ans = yn + +See review_, below. + + +Integer_type +============ + +:: + + integer_type ::= 'integer' ['(' match ')'] NL [review] + +The user enters an integer. If the match_ is specified, the integer must +match it or the user is asked to try again. + +Example:: + + hours_since_last_meal($ans) + How many hours has it been since you last ate? + --- + $ans = integer(0-48) + +See review_, below. + + +Number_type +============ + +:: + + number_type ::= 'number' ['(' match ')'] NL [review] + +The user enters either an integer or a floating point number. If the user +enters an integer, a Python ``int`` is returned. Otherwise a Python ``float`` +is returned. + +If the match_ is specified, the number must match it or the user is asked +to try again. + +Example:: + + miles_to($dest, $ans) + How many miles did you travel to get to $dest? + --- + $ans = number(0.1-3000) + +See review_, below. + + +Float_type +============ + +:: + + float_type ::= 'float' ['(' match ')'] NL [review] + + +The user enters an integer or a floating point number. But the answer +returned is always a Python ``float``. + +If the match_ is specified, the number must match it or the user is asked +to try again. + +Example:: + + price($object, $price) + What did you pay for $object? + --- + $price = float + +See review_, below. + + +String_type +============ + +:: + + string_type ::= 'string' ['(' match ')'] NL [review] + +The user enters a string (text). If the match_ is specified, the string must +match it or the user is asked to try again. + +Example:: + + users_name($name) + What's your name? + - Please don't enter a fictitious (screen) name. + --- + $name = string(2-40) + +See review_, below. + + +Match +===== + +There are several kinds of simple_matches that may be or-ed together with +``|``:: + + match ::= simple_match {'|' simple_match} + +The match succeeds if any of the ``simple_matches`` succeed. + +:: + + simple_match ::= '(' match ')' + | [ STRING ] [ '[' TEXT ']' ] '/' REGEXP_TEXT '/' + | [NUMBER] '-' NUMBER + | NUMBER '-' + | value '=' simple_match + | value + +Regexp Match +------------ + +:: + + simple_match ::= [ STRING ] [ '[' TEXT ']' ] '/' REGEXP_TEXT '/' + +A regexp match can only be used with string_type_ questions. It matches if +the regexp matches. + +If the regexp contains a single group, that group is returned as the +question's answer rather than the entire string. + +If the regexp contains multiple groups, a tuple of the groups is returned as +the question's answer rather than entire string. + +If STRING is specified on a regexp, it is used in the error message if the +regexp fails. The error message is "Answer should be $error_msg, got $string". + +If '[' TEXT ']' is specified on a regexp, it is used in the prompt for the +end user to inform him of what is expected. Generally, this prompt message +is enclosed in '[' and ']' when it is displayed to the user. + +Example:: + + state_code($state) + Enter your two digit state code. + --- + $state = string('uppercase'[uppercase]/[A-Z][A-Z]/) + +Range Match +----------- + +:: + + simple_match ::= [NUMBER] '-' NUMBER + | NUMBER '-' + +A range match has a '-' in it. It matches if the answer is between the two +values. If either value is omitted, that limit is not tested. If matched to +a string, it matches the length of the string. + +Example:: + + age($years) + How old are you? + --- + $years = integer(1-130) + +Value '=' Match +--------------- + +:: + + simple_match ::= value '=' simple_match + +The '=' means "substituted for". The match_ fails if the match after the '=' +fails. Otherwise it returns the value before the '=' rather than what the +user entered. Note that you can or (``|``) several of these together to +translate several different matched values. + +Example:: + + age_category($period_of_life) + How old are you? + --- + $period_of_life = integer(child=1-12 | + teenager=13-19 | + young_adult=20-35 | + middle_age=35-64 | + elder=65-130) + + +Value Match +----------- + +:: + + simple_match ::= value + + value ::= STRING | IDENTIFIER | NUMBER | 'None' | 'True' | 'False' + +A value match, only matches that one value. An IDENTIFIER is treated as a +STRING. These are mostly used in reviews. + + +Review +====== + +:: + + review ::= {match '!' PARAMETRIZED_TEXT NL} + +All of the ``reviews`` must be at the same indent level. + +The review is applied after the answer has been validated (validation possibly +changes the value). + +Each match_ is checked and all of the matching review's ``PARAMETRIZED_TEXT`` +messages are displayed to the user. + +Examples:: + + stupid_question($ans) + Can you answer a question + that is several lines long? + --- + $ans = yn + True ! Correct! This is true because the + sky is blue! + False ! Nope! Remember that the sky is blue! + + wood($ans) + How much wood would a woodchuck chuck if a woodchuck could chuck wood? + --- + $ans = integer(0-100) + -10 ! more than that! + 10-20 ! bingo! + 21- ! I guess they're not as strong as you think ... + +.. This code is hidden. It will add '' to sys.path, change to the doc.examples + directory and store the directory path in __file__ for the code section + following: + >>> import sys + >>> if '' not in sys.path: sys.path.insert(0, '') + >>> import os + >>> os.chdir("../../examples") + >>> __file__ = os.getcwd() + +Asking ``stupid_question`` and answering "y" to it:: + + >>> from pyke import knowledge_engine + + >>> engine = knowledge_engine.engine(__file__) + + >>> from io import StringIO + >>> import sys + >>> class echo(object): + ... def __init__(self, f): self.f = f + ... def readline(self): + ... ans = self.f.readline() + ... sys.stdout.write(ans) + ... return ans + >>> sys.stdin = echo(StringIO('y\n')) + +displays:: + + >>> engine.prove_1_goal('user_questions.stupid_question($ans)') + ______________________________________________________________________________ + Can you answer a question + that is several lines long? (y/n) y + Correct! This is true because the + sky is blue! + ({'ans': True}, None) + + +Select_1_type +============= + +:: + + select_1_type ::= 'select_1' NL alternatives + +This is a multiple choice question. The alternatives_ are displayed to the +user, and he picks one (and only one). + +Example:: + + another_question($arg1, $arg2, $ans) + question text with $arg1 stuff in it. + on multiple lines + - possibly indented + - for who knows what reason... + - maybe for $arg2? + --- + $ans = select_1 + 1: prompt for this selection with $arg2 in it too + which can span multiple lines + - and be indented ... + ! Nope! Remember that the sky is blue! + 2: next prompt + ! =1 # same review as 1: + 3: pick me! pick me!!! + ! Correct! You certainly know about $arg1! + yep, multiple review lines too... + - and indented... + + +Select_n_type +============= + +:: + + select_n_type ::= 'select_n' NL alternatives + +This is a multiple choice question. The alternatives_ are displayed to the +user, and he picks as many as he likes. + +Example:: + + problems($list) + Which of these problems are you experiencing? + - select all that apply + --- + $list = select_n + boot: The system won't boot. + os: I hate Windows! + internet: I can't connect to the internet. + slow: The system is running too slow. + ouch: Help! I've fallen and I can't get up! + freeze: The system freezes or does not respond to input. + printer: The printer doesn't work. + senile: What's email? + +Alternatives +============ + +:: + + alternatives ::= {value ':' PARAMETRIZED_TEXT NL [alt_review]} + +All of the ``alternatives`` must be at the same indent level. + +The user only sees the ``PARAMETRIZED_TEXT`` values. The ``value`` +associated with the selected ``PARAMETRIZED_TEXT`` is returned (but the user +never sees it). The ``value`` *tags* the alternative. + +:: + + alt_review ::= '!' '=' value NL + | '!' PARAMETRIZED_TEXT NL + +Each alternative may have it's own review associated with it. + +The ``'!' '=' value`` form uses the same review text as the previous +alternative with that *tag*. Note that this can not refer forward to a +following alternative. + +The second form specifies the review text for this alternative directly. + + diff --git a/doc/sphinx/source/pyke_syntax/krb_syntax/bc_rule.rst b/doc/sphinx/source/pyke_syntax/krb_syntax/bc_rule.rst new file mode 100644 index 0000000..0ef2336 --- /dev/null +++ b/doc/sphinx/source/pyke_syntax/krb_syntax/bc_rule.rst @@ -0,0 +1,216 @@ +.. $Id: bc_rule.txt a2119c07028f 2008-10-27 mtnyogi $ +.. +.. Copyright © 2007-2008 Bruce Frederiksen +.. +.. Permission is hereby granted, free of charge, to any person obtaining a copy +.. of this software and associated documentation files (the "Software"), to deal +.. in the Software without restriction, including without limitation the rights +.. to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +.. copies of the Software, and to permit persons to whom the Software is +.. furnished to do so, subject to the following conditions: +.. +.. The above copyright notice and this permission notice shall be included in +.. all copies or substantial portions of the Software. +.. +.. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +.. IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +.. FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +.. AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +.. LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +.. OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +.. THE SOFTWARE. + +============================================= +Bc_rule Syntax +============================================= + +Bc_rule +========== + +Backward-chaining_ rules_ have four parts: + +#. A unique name. +#. A `use clause`_. +#. An optional `when clause`_. +#. An optional `with clause`_. + +:: + + bc_rule ::= IDENTIFIER NL INDENT + use + [when] + [with] + DEINDENT + +The ``IDENTIFIER`` is the unique name for this rule_ and is used as the +corresponding Python function name in the generated _bc.py file +and also for the Python function name of the plan_ function (if any) +associated with the rule. This name will show up in stack traces +associated with exceptions raised during inferencing or plan execution. + +Use Clause +============ + +The ``use`` clause is the **then** part of the rule. It identifies the +*goal* that this rule is prepared to prove. + +:: + + use ::= 'use' IDENTIFIER '(' {pattern,} ')' ['taking' '(' ')'] NL + | 'use' IDENTIFIER '(' {pattern,} ')' NL + INDENT 'taking' '(' ')' NL + DEINDENT + +Notice that it uses a single ``IDENTIFIER``. The `rule base`_ name is implied +as the `rule base category`_ name (the name of the root rule base, see +`extending clause`_) for the rule base containing this rule. + +Taking Clause +---------------- + +The ``use`` clause also defines parameters to the plan_ function (if one is +used for this rule_) with the optional ``taking`` sub-clause. + +The *python_arg_spec* is not parsed by Pyke, but simply copied to the +output plan function. Do **not** use ``$`` with these parameter names (or +their default values). + +When Clause +============== + +The ``when`` clause is the **if** part of the rule_. It defines the +premises that must be true for this rule to succeed. + +If the ``when`` clause is omitted, the only +requirement for the rule to succeed is that the ``use`` clause +`pattern matches`_ the goal. + +If the ``when`` clause is specified, the rule succeeds for each combination +of true premises (see backtracking_). + +:: + + when ::= 'when' NL INDENT + {bc_premise NL} + DEINDENT + + bc_premise ::= ['!'] [ name '.' ] name '(' {pattern,} ')' [ plan_spec ] + | compound_premise + | python_premise + + name ::= IDENTIFIER + | '$'IDENTIFIER + +Here are the links to the definitions for `pattern syntax`_, compound_premise_ and +python_premise_. + +If the *bc_premise* includes the ``!``, an AssertionError will be raised if the +premise fails on the first try. This can help in debugging. + +.. Note:: + This does not apply when the premise fails on backtracking_ (in which case + it has already succeeded at least once). + +If a single *name* is used in the *bc_premise*, +the `rule base category`_ for the current `rule base`_ (the root rule base +name, see `extending clause`_) is assumed. + +If two *names* are used in the *bc_premise*, the first may name a rule +base category or some other `knowledge base`_. + +If a rule base category name is used (or assumed), the currently active_ +`rule base`_ for that category is used to prove the premise. + +.. note:: + + If the rule base category name is omitted, and therefore assumed + to be the current rule base's rule base category, the current rule base + does *not* have to be the active rule base for that category. It could be + the case that a derived rule base is the active rule base. In that case, + the derived rule base is used to prove the premise. + + In this way, different rules may be used to prove the same premise, + depending upon which rule base has been activated. + +Plan_spec +------------ + +A *plan_spec* is required for each premise that returns a subordinate plan_. +This shows what should be done with that subordinate plan_ function. + +Thus, a rule's plan function is composed first of the collected +python_statements taken from its plan_specs (as described below), followed by +the python_statements within its `with clause`_ (if any). The inclusion of +any plan_spec containing a python_statement will cause a plan_ function to be +generated for this rule, even if the rule lacks a ``with`` clause. + +:: + + plan_spec ::= [ 'step' NUMBER ] NL INDENT + { NL} + DEINDENT + | 'as' '$'IDENTIFIER NL + +Within each python_statement, the subordinate plan function is indicated by +``$$``. The result of this function may be assigned to a Python variable, +but not a `pattern variable syntax`_ (``$variable``). +Parameters from the rule's ``taking`` clause may be passed on to the +subordinate plan_ functions. + +When multiple premises have python_statements in their *plan_specs*, the +python_statements in plan_specs *without* a ``step`` clause are executed first +in the order that they appear in the ``when`` clause. + +Then the python_statements in plan_specs *with* a ``step`` clause are +executed in ascending NUMBER sequence. It is permissible for the NUMBER +to be negative or a float. + +If the ``as`` clause is used, the plan function is bound to the +pattern variable as a Python function, but not automatically executed. +This allows you to call the function (or not) when and as many times as you +please. The parameters required are defined in the ``taking`` clause of the +rule used to prove the premise. + +.. note:: + + Within a forall_ or notany_ premise, the only ``plan_spec`` that may be + used is the ``as`` clause. + +With Clause +============== + +The ``with`` clause contains Python statements to include in the plan_ +produced by this rule_. These Python statements may include +`pattern variables`_ whose values will be cooked_ into these statements +when the plan is created. + +:: + + with ::= 'with' NL INDENT + { NL} + DEINDENT + +The *python_statements* are included in the rule's plan function after +all of the calls to the subordinate plan functions made from the +*plan_specs* in the `when clause`_. + +If the ``with`` clause is omitted, but the ``when`` clause has *plan_specs* +(excluding the *as* clause), a plan function is still generated for this +rule so that the subordinate plan functions are still called. + +The *python_statements* are not parsed. They are simply scanned for ``$`` +pattern variables that don't occur within string literals or comments. +The values bound to these variables are cooked_ into the code to produce the +plan. + +Thus, all pattern variables used within *python_statements* (both in the +``plan_specs`` and the ``when`` clause) must be bound to a value. This +value is a constant value that never changes for this plan_. + +.. note:: + + This occurs after the entire top-level goal is proven so that it is + permissible to bind these pattern variables to values *following* the + execution of the rule containing them. + + diff --git a/doc/sphinx/source/pyke_syntax/krb_syntax/compound_premise.rst b/doc/sphinx/source/pyke_syntax/krb_syntax/compound_premise.rst new file mode 100644 index 0000000..7fa8d03 --- /dev/null +++ b/doc/sphinx/source/pyke_syntax/krb_syntax/compound_premise.rst @@ -0,0 +1,288 @@ +.. $Id: compound_premise.txt a2119c07028f 2008-10-27 mtnyogi $ +.. +.. Copyright © 2008 Bruce Frederiksen +.. +.. Permission is hereby granted, free of charge, to any person obtaining a copy +.. of this software and associated documentation files (the "Software"), to deal +.. in the Software without restriction, including without limitation the rights +.. to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +.. copies of the Software, and to permit persons to whom the Software is +.. furnished to do so, subject to the following conditions: +.. +.. The above copyright notice and this permission notice shall be included in +.. all copies or substantial portions of the Software. +.. +.. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +.. IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +.. FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +.. AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +.. LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +.. OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +.. THE SOFTWARE. + +============================================= +Compound Premise Syntax +============================================= + +There are three kinds of compound premises. These can be used in both +`forward-chaining rules`_ and `backward-chaining rules`_, but the nested +premises within each of these are restricted to the kind of premises legal +for that kind of rule: fc_premise_ for forward-chaining rules, and +bc_premise_ for backward-chaining rules. + +:: + + compound_premise ::= first_premise + | forall_premise + | notany_premise + +First Premise +===================== + +The ``first`` premise is used to prevent backtracking_ from finding subsequent +solutions to a set of premises. The ``first`` premise always fails on +backtracking (but does do backtracking *within* the nested premises). + +:: + + first_premise ::= ['!'] 'first' premise + | ['!'] 'first' NL + INDENT + {premise NL} + DEINDENT + +The ``!`` option can only be used in backward-chaining rules. + +When used within backward-chaining rules, the nested premises may include any +type of plan_spec_. + +Forall Premise +===================== + +The ``forall`` premise forces backtracking_ within the nested premises to +process all of the possible solutions found before the ``forall`` succeeds. +After the first success, the ``forall`` fails on backtracking. + +:: + + forall_premise ::= 'forall' NL + INDENT + {premise NL} + DEINDENT + [ 'require' NL + INDENT + {premise NL} + DEINDENT ] + +The premises within the ``require`` clause are tried for each solution found +to the ``forall`` clause. If these fail for any solution, then the entire +``forall`` premise fails. Thus, the ``forall`` only succeeds if the +``require`` premises are true for *all* solutions generated within the +``forall`` clause. Thus, the ``forall`` clause may be read: "Forall X, +require Y". + +The ``forall`` always succeeds if the ``require`` clause is omitted (even if +no solutions are found to the nested premises). This can be used in +conjunction with python_statements_ to gather a list of results. + +See `Notes on Forall and Notany Premises`_ and Examples_, below. + +Notany Premise +===================== + +The ``notany`` premise only succeeds if no solution can be found to the nested +premises. ``Notany`` always fails on backtracking_. + +:: + + notany_premise ::= 'notany' NL + INDENT + {premise NL} + DEINDENT + +See `Notes on Forall and Notany Premises`_ and Examples_, below. + +Notes on Forall and Notany Premises +====================================== + +#. All `pattern variable syntax`_ bindings made during the execution of a ``forall`` + or ``notany`` premise are undone before the premises following the + ``forall`` or ``notany`` are run. + Thus, ``forall`` and ``notany`` can be used to test + values produced by prior premises; but to generate values for subsequent + premises the values must be captured in Python variables within the + ``forall`` or ``notany`` clause before the `pattern variables` are unbound + (see `Computing a Value for Each Generated Value`_, below). + +#. When used within `backward-chaining rules`_, the only plan_spec_ allowed in + nested premises is the ``as`` clause. + +Examples +============= + +- `Finding the First Solution From a Set of Values`_ +- `Testing Every Generated Value`_ +- `Computing a Value for Each Generated Value`_ +- `Iterating on Tuples`_ +- `Computing Values for All Generated Values that Pass a Test`_ + +These examples use the following subgoals: + +* ``generate_x($x)`` generates multiple solutions (as ``$x``) that will be + looped over +* ``test_x($x)`` does some test on ``$x`` +* ``compute_y($x, $y)`` takes ``$x`` as input and computes a ``$y`` value + +Finding the First Solution From a Set of Values +------------------------------------------------- + +If you want the first ``$x`` that passes the ``test_x($x)`` test, you have two +options:: + + generate_x($x) + test_x($x) + ... + +And:: + + first + generate_x($x) + test_x($x) + ... + +The difference is that the first example will find other ``$x`` values that +pass ``test_x($x)`` on backtracking_, while the second example will stop after +the first value is found and fail on backtracking. + +Testing Every Generated Value +----------------------------- + +There are two general cases. You might want to verify that ``test_x($x)`` +*succeeds* for all generated ``$x`` values:: + + forall + generate_x($x) + require + test_x($x) + +.. Note:: + + While ``$x`` is set and used within the ``forall`` premise to transfer + values from the ``generate_x($x)`` goal to the ``test_x($x)`` goal, it is + no longer set afterwards and can not be referenced in the premises + following the ``forall`` premise. + +The second case that you might want to verify is that ``test_x($x)`` *fails* +for every generated ``$x`` value:: + + forall + generate_x($x) + require + notany + test_x($x) + +Or, more simply:: + + notany + generate_x($x) + test_x($x) + +Computing a Value for Each Generated Value +------------------------------------------ + +If you want a tuple of computed ``$y`` values for all of the ``$x`` values:: + + python y_list = [] + forall + generate_x($x) + require + compute_x($x, $y) + python y_list.append($y) + $y_list = tuple(y_list) + +This will only succeed if ``compute_y`` succeeds for every ``$x`` value. + +If you want to skip over ``$x`` values that ``compute_y`` fails on, you +*might* try:: + + python y_list = [] + forall + generate_x($x) + compute_x($x, $y) + python y_list.append($y) + $y_list = tuple(y_list) + +But note that if ``compute_y`` computes multiple solutions for a single +``$x`` value on backtracking_, you would end up including all of these +solutions in your ``$y_list``. To only get the first computed value for each +``$x`` value:: + + python y_list = [] + forall + generate_x($x) + first + compute_x($x, $y) + python y_list.append($y) + $y_list = tuple(y_list) + +Iterating on Tuples +------------------- + +A simple common case of ``generate_x`` is when you are computing values for +each element of a tuple:: + + python y_list = [] + forall + $x in $x_list + require + compute_x($x, $y) + python y_list.append($y) + $y_list = tuple(y_list) + +This can also be done by creating a new subgoal that recurses on ``$x_list``. +If you call the new subgoal ``compute_list``, you would use it like this:: + + compute_list($x_list, $y_list) + +And define it like this:: + + compute_list_done + use compute_list((), ()) + + compute_list_step + use compute_list(($x, *$x_rest), ($y, *$y_rest)) + when + compute_y($x, $y) + compute_list($x_rest, $y_rest) + +.. important:: + + Note that there is an important difference between these two examples if + ``compute_y`` may find alternate ``$y`` values for any given ``$x`` value + on backtracking_. + + The first example will only generate one ``$y_list``. If that ``$y_list`` + doesn't work for subsequent premises, the ``forall`` fails on backtracking, + so no overall solution will be found. + + The second example will not fail in this situation, but will produce all + possible combinations of solutions to ``compute_y`` for each ``$x`` on + backtracking until a resulting ``$y_list`` satisfies the subsequent + premises so that an overall solution *is* found. + +Computing Values for All Generated Values that Pass a Test +---------------------------------------------------------- + +Finally, if you want to gather only the computed ``$y`` values for ``$x`` +values that pass ``test_x($x)``:: + + python y_list = [] + forall + generate_x($x) + test_x($x) + require + compute_x($x, $y) + python y_list.append($y) + $y_list = tuple(y_list) + + diff --git a/doc/sphinx/source/pyke_syntax/krb_syntax/fc_rule.rst b/doc/sphinx/source/pyke_syntax/krb_syntax/fc_rule.rst new file mode 100644 index 0000000..fdd25f6 --- /dev/null +++ b/doc/sphinx/source/pyke_syntax/krb_syntax/fc_rule.rst @@ -0,0 +1,88 @@ +.. $Id: fc_rule.txt a2119c07028f 2008-10-27 mtnyogi $ +.. +.. Copyright © 2007-2008 Bruce Frederiksen +.. +.. Permission is hereby granted, free of charge, to any person obtaining a copy +.. of this software and associated documentation files (the "Software"), to deal +.. in the Software without restriction, including without limitation the rights +.. to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +.. copies of the Software, and to permit persons to whom the Software is +.. furnished to do so, subject to the following conditions: +.. +.. The above copyright notice and this permission notice shall be included in +.. all copies or substantial portions of the Software. +.. +.. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +.. IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +.. FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +.. AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +.. LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +.. OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +.. THE SOFTWARE. + +============================================= +Fc_rule Syntax +============================================= + +Fc_rule +============= + +Forward-chaining_ rules have three parts: + +#. A unique name. +#. An optional `foreach clause`_. +#. An `assert clause`_. + +:: + + fc_rule ::= IDENTIFIER NL INDENT + [fc_foreach] + fc_assert + DEINDENT + +The ``IDENTIFIER`` uniquely names this rule_ and is used as the corresponding +Python function name in the generated _fc.py file. + +Foreach clause +================= + +:: + + fc_foreach ::= 'foreach' NL INDENT + {fc_premise NL} + DEINDENT + + fc_premise ::= fact_pattern + | compound_premise + | python_premise + + fact_pattern ::= IDENTIFIER '.' IDENTIFIER '(' [{pattern,}] ')' + +Here are links to the definitions for `pattern syntax`_, compound_premise_ and +python_premise_. + +If the ``foreach`` clause is omitted, the rule_ is always fired once. + +If the ``foreach`` clause is present, the rule is fired for each combination +of true premises. + +Assert clause +================= + +:: + + fc_assert ::= 'assert' NL INDENT + {assertion NL} + DEINDENT + + assertion ::= fact_pattern + | python_statements + +Here is the link to the definitions of python_statements_. + +The ``assert`` clause lists new facts_ to assert, and/or Python +statements to execute each time the rule_ is fired. Each of these may +include `pattern variables`_ which should also appear in the ``foreach`` +clause where they are bound to a value. These values will then be substituted +into the facts and Python statements. + diff --git a/doc/sphinx/source/pyke_syntax/krb_syntax/index.rst b/doc/sphinx/source/pyke_syntax/krb_syntax/index.rst new file mode 100644 index 0000000..825524e --- /dev/null +++ b/doc/sphinx/source/pyke_syntax/krb_syntax/index.rst @@ -0,0 +1,129 @@ +.. $Id: index.txt a2119c07028f 2008-10-27 mtnyogi $ +.. +.. Copyright © 2007-2008 Bruce Frederiksen +.. +.. Permission is hereby granted, free of charge, to any person obtaining a copy +.. of this software and associated documentation files (the "Software"), to deal +.. in the Software without restriction, including without limitation the rights +.. to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +.. copies of the Software, and to permit persons to whom the Software is +.. furnished to do so, subject to the following conditions: +.. +.. The above copyright notice and this permission notice shall be included in +.. all copies or substantial portions of the Software. +.. +.. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +.. IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +.. FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +.. AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +.. LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +.. OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +.. THE SOFTWARE. + +.. toctree:: + :maxdepth: 1 + :hidden: + + fc_rule + bc_rule + pattern + compound_premise + python_premise + +=================== +KRB Syntax +=================== + +This section describes the syntax for defining rules_ in KRB files. + + +Keywords +================== + +========= =========== ========= +as foreach taking +assert in True +bc_extras None use +check plan_extras when +extending python with +False step without +fc_extras +========= =========== ========= + + + +Syntax of the Entire KRB File +=================================== + +:: + + file ::= [NL] + + ['extending' IDENTIFIER ['without' {IDENTIFIER,}] NL] + + [{fc_rule} + ['fc_extras' NL INDENT + { NL} + DEINDENT]] + + [{bc_rule} + ['bc_extras' NL INDENT + { NL} + DEINDENT] + ['plan_extras' NL INDENT + { NL} + DEINDENT]] + +The KRB file has three optional parts. It must contain at least one rule_ +(either forward-chaining_ or backward-chaining_). + +The filename (minus the .krb extension) is the name of the `rule base`_. +This must be a legal Python identifier. + +Extending clause +----------------- + +The optional ``extending`` clause, if used, is the first line of the file. +This defines the parent `rule base`_ that this `rule base`_ inherits_ from. +It may also specify a list of backward-chaining_ goal names to be excluded +from this inheritance. + +Forward-Chaining Section +-------------------------- + +If the krb file contains any forward-chaining_ rules, a Python source file +will be created named _fc.py, where is the `rule base`_ +name. + +The syntax of a forward-chaining rule (fc_rule_) is defined here__. + +The ``fc_extras`` may only be used if there are forward-chaining rules. +This allows you to add other Python code (for example, ``import`` statements) +to the generated Python source file. + +.. __: fc_rule_ + +Backward-Chaining Section +-------------------------- + +If the krb file contains any backward-chaining_ rules, a Python source file +will be created named _bc.py, where is the `rule base`_ +name. + +The syntax of a backward-chaining rule (bc_rule_) is defined here__. + +The ``bc_extras`` can only be used if there are backward-chaining rules. +This allows you to add other Python code (for example, ``import`` statements) +to the generated Python source file. + +In addition, if any of the backward-chaining rules have plan_ code (a +`with clause`_ or any subgoals in the `when clause`_ with a plan_spec_), +a Python source file will be created named _plans.py, +where is the `rule base` name. + +You use the ``plan_extras`` to include arbitrary Python code in this plans +file. + +.. __: bc_rule_ + + diff --git a/doc/sphinx/source/pyke_syntax/krb_syntax/pattern.rst b/doc/sphinx/source/pyke_syntax/krb_syntax/pattern.rst new file mode 100644 index 0000000..062c32b --- /dev/null +++ b/doc/sphinx/source/pyke_syntax/krb_syntax/pattern.rst @@ -0,0 +1,75 @@ +.. $Id: pattern.txt 057d79259b20 2009-05-14 mtnyogi $ +.. +.. Copyright © 2007-2008 Bruce Frederiksen +.. +.. Permission is hereby granted, free of charge, to any person obtaining a copy +.. of this software and associated documentation files (the "Software"), to deal +.. in the Software without restriction, including without limitation the rights +.. to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +.. copies of the Software, and to permit persons to whom the Software is +.. furnished to do so, subject to the following conditions: +.. +.. The above copyright notice and this permission notice shall be included in +.. all copies or substantial portions of the Software. +.. +.. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +.. IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +.. FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +.. AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +.. LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +.. OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +.. THE SOFTWARE. + +============================================= +Pattern Syntax +============================================= + +Pattern +============ + +:: + + pattern ::= 'None' | 'True' | 'False' + | NUMBER | IDENTIFIER | STRING | variable + | '(' [{pattern,}] ['*' variable] ')' + +IDENTIFIER acts like a STRING here, meaning that it is taken as a literal +value. All variables in patterns must be preceded by a ``$``. + +Pyke does not currently support complex NUMBERS (for no good reason -- email +me if you need them). + +Pattern Variable +=================== + +`Pattern variables`__ are simply called *variable* in the syntax:: + + variable ::= '$'IDENTIFIER + +The variable must not have a space between the ``$`` and the ``IDENTIFIER``. + +.. __: `pattern variable lp`_ + +Anonymous Variable +==================== + +If the pattern variable IDENTIFIER begins with an underscore (_), the variable +is an `anonymous variable`__. It acts like a "don't care". Technically, this +means that multiple uses of the same IDENTIFIER may stand for different +values. The name of the IDENTIFIER after the underscore is ignored and may be +used to document the use of the anonymous variable. + +.. __: `anonymous variable lp`_ + +Rest Variable +================ + +The ``*variable`` at the end of a tuple pattern will match the rest of the +tuple. Thus, ``variable`` is *always* bound to a (possibly empty) tuple. + +The syntax is taken from rest parameter syntax in Python function definitions. +The difference here is that the variable needs a ``$`` on it. + +You may use either a named variable or an anonymous variable here. + + diff --git a/doc/sphinx/source/pyke_syntax/krb_syntax/python_premise.rst b/doc/sphinx/source/pyke_syntax/krb_syntax/python_premise.rst new file mode 100644 index 0000000..0b5d0de --- /dev/null +++ b/doc/sphinx/source/pyke_syntax/krb_syntax/python_premise.rst @@ -0,0 +1,122 @@ +.. $Id: python_premise.txt 9c1b571b39ac 2009-02-15 mtnyogi $ +.. +.. Copyright © 2007-2008 Bruce Frederiksen +.. +.. Permission is hereby granted, free of charge, to any person obtaining a copy +.. of this software and associated documentation files (the "Software"), to deal +.. in the Software without restriction, including without limitation the rights +.. to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +.. copies of the Software, and to permit persons to whom the Software is +.. furnished to do so, subject to the following conditions: +.. +.. The above copyright notice and this permission notice shall be included in +.. all copies or substantial portions of the Software. +.. +.. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +.. IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +.. FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +.. AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +.. LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +.. OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +.. THE SOFTWARE. + +===================== +Python Premise Syntax +===================== + +Python_premise +============== + +:: + + python_premise ::= pattern '=' python_exp + | pattern 'in' python_exp + | 'check' python_exp + | python_statements + +Each of these clauses results in a Python expression being executed. Their +meaning is as follows: + +`pattern syntax`_ '=' python_exp + *python_exp* is evaluated and the result matched_ with `pattern syntax`_. + If the result does not match, the clause fails. + + The clause always fails on backtracking_, meaning that it only produces + a single result (contrasted with ``in``). +`pattern syntax`_ 'in' python_exp + *python_exp* is evaluated to produce a Python *iterable* and the first + element from the resulting iterable is matched_ with `pattern syntax`_. On + backtracking_, successive elements from the iterable are matched with + `pattern syntax`_. When the result is exhausted, the clause fails. + + This has the effect of offering each element of the result, one at a + time, to the subsequent premise clauses. Each element is thus acted upon + individually. +'check' python_exp + *python_exp* is evaluated. If the result is Python "true" the clause + succeeds, otherwise it fails. The clause always fails on backtracking_. + + +Python_statements +=================== + +:: + + python_statements ::= 'python' python_statement + | 'python' NL INDENT + {python_statement NL} + DEINDENT + +This clause allows the inclusion of arbitrary Python statements in your +rules_. This premise always succeeds; and then fails on backtracking_. + +The current ``knowledge_engine`` object is available within python_statements +as the variable called ``engine``. + +.. caution:: + + Always keep in mind the difference between `pattern variables`_ and + *Python variables*. Pattern variables are always indicated with a ``$`` + and are only bound to a value during inferencing. + + #. Thus, a ``python_statement`` may not set a pattern variable. Storing a + value computed by Python into a pattern variable can only be done using + the python_premise:: + + = + + #. When a pattern variable is used within a Python expression or statement, + it must be `fully bound`_. + + #. Python variables are not visible to the inference engine. They are local + variables that are also not visible to Python code in other rules_ or + other invocations of the same rule. + + #. Finally, Python variables in the `when clause`_ of a `backward-chaining + rule`_ are not visible to the Python code in the `with clause`_ of the + same rule. (These end up in two different Python functions after the + `.krb file`_ is compiled). So this won't work:: + + some_bc_rule + use some_goal(...) + when + ... + python x_list = + ... + with + for x in x_list: process(x) + + In this case, assign the value of the Python variable to a pattern + variable in the when clause and then use that pattern variable in the + with clause:: + + some_bc_rule + use some_goal(...) + when + ... + python x_list = + ... + $x_list = tuple(x_list) + with + for x in $x_list: process(x) + diff --git a/doc/sphinx/source/using_pyke/adding_facts.rst b/doc/sphinx/source/using_pyke/adding_facts.rst new file mode 100644 index 0000000..c6b68cb --- /dev/null +++ b/doc/sphinx/source/using_pyke/adding_facts.rst @@ -0,0 +1,88 @@ +.. $Id: adding_facts.txt 56035209fc8e 2010-03-08 mtnyogi $ +.. +.. Copyright © 2010 Bruce Frederiksen +.. +.. Permission is hereby granted, free of charge, to any person obtaining a copy +.. of this software and associated documentation files (the "Software"), to deal +.. in the Software without restriction, including without limitation the rights +.. to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +.. copies of the Software, and to permit persons to whom the Software is +.. furnished to do so, subject to the following conditions: +.. +.. The above copyright notice and this permission notice shall be included in +.. all copies or substantial portions of the Software. +.. +.. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +.. IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +.. FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +.. AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +.. LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +.. OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +.. THE SOFTWARE. + +=================================== +Asserting New Facts +=================================== + +.. this code is hidden and will set __file__ to the doc/examples directory. + >>> import os + >>> __file__ = \ + ... os.path.join(os.path.dirname(os.path.dirname(os.getcwd())), + ... 'examples') + >>> from pyke import knowledge_engine + >>> my_engine = knowledge_engine.engine(__file__) + +*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`_. + + +*some_engine*.assert_(kb_name, fact_name, arguments) + Call ``assert_`` 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') + diff --git a/doc/sphinx/source/using_pyke/creating_engine.rst b/doc/sphinx/source/using_pyke/creating_engine.rst new file mode 100644 index 0000000..f0024f7 --- /dev/null +++ b/doc/sphinx/source/using_pyke/creating_engine.rst @@ -0,0 +1,150 @@ +.. $Id: creating_engine.txt efc7674a8d3a 2010-03-29 mtnyogi $ +.. +.. Copyright © 2010 Bruce Frederiksen +.. +.. Permission is hereby granted, free of charge, to any person obtaining a copy +.. of this software and associated documentation files (the "Software"), to deal +.. in the Software without restriction, including without limitation the rights +.. to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +.. copies of the Software, and to permit persons to whom the Software is +.. furnished to do so, subject to the following conditions: +.. +.. The above copyright notice and this permission notice shall be included in +.. all copies or substantial portions of the Software. +.. +.. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +.. IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +.. FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +.. AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +.. LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +.. OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +.. THE SOFTWARE. + +=================================== +Creating an Inference Engine Object +=================================== + +The ``engine`` object is your gateway into Pyke. Each engine object manages +multiple `knowledge bases`_ related to accomplishing some task. + +You may create multiple Pyke engines, each with it's own knowledge bases to +accomplish different disconnected tasks. + +When you create a Pyke engine object, Pyke scans for Pyke `.kfb`_, `.krb`_ +and `.kqb`_ source files and compiles these into .fbc pickle files, +Python .py source files and .qbc pickle files, respectively. + +Each time a Pyke engine object is created it checks the file modification +times of the Pyke source files to see whether they need to be recompiled. +If you change a Pyke source file, you may create a new Pyke engine to compile +the changes and run with the new knowledge bases without having to restart +your application. + +Pyke also lets you zip these compiled files into Python eggs and can load the +files from the egg. By including the compiled files in your application's +distribution, you don't need to include your Pyke source files if you don't +want to. + +Once you have an ``engine`` object; generally, all of the Pyke functions that +you need are provided directly by this object: + +.. this code is hidden and will set __file__ to the doc/examples directory. + >>> import os + >>> __file__ = \ + ... os.path.join(os.path.dirname(os.path.dirname(os.getcwd())), + ... 'examples') + +knowledge_engine.engine(\*paths, \*\*kws) + + Pyke recursively searches for Pyke source files (`.kfb files`_, + `.krb files`_, and `.kqb files`_) starting at each source directory + indicated in *paths* and places all of the compiled files in the + associated *target packages*. This causes all of the `knowledge bases`_ + to be loaded and made ready to activate_. + + Pyke source files may be spread out over multiple directories and may be + compiled into one or more target packages. Multiple target packages + would be used when more than one application wants to share a set of + `knowledge bases`_, perhaps adding some of its own knowledge that it + compiles into its own target package. + + Each ``path`` argument specifies a Pyke source directory and an optional + target package. The source directories do not have to be Python package + directories, but the target packages do. + + The target package defaults to ``.compiled_krb``. The leading dot (.) + indicates that the compiled_krb directory will be subordinate to the + lowest Python package directory on the path to the Pyke source directory. + This uses Python's `relative import`_ notation, so multiple dots go up to + higher directories (one per dot). If the target package does not start + with a dot, it is taken to be an absolute package path and will be located + using Python's sys.path like a normal Python ``import``. + + The Pyke source directory may be specified as a path (a string) or by + passing a Python module. If a module is passed, its __file__ attribute + is used. If the path points to a file, rather than a directory, the final + filename is discarded. In the simple case, when the Pyke source files are + in the same directory as the module creating the ``engine`` object, you + can just pass ``__file__`` as the sole argument. + + >>> from pyke import knowledge_engine + >>> my_engine = knowledge_engine.engine(__file__) + + Passing a package instead, this example could also be written: + + >>> import doc.examples + >>> my_engine = knowledge_engine.engine(doc.examples) + + or, you can pass a module within the desired package: + + >>> from doc.examples import some_module + >>> my_engine = knowledge_engine.engine(some_module) + + In the all three cases, the final filename is stripped from the value of + the module's __file__ attribute to get the directory that the package + is in. This directory will then be recursively searched for Pyke source + files. + + 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 (a reason to use `.kfb files`_ + instead). + + All of the compiled Python .py source files and .fbc/.qbc pickle files + generated from each source directory are placed, by default, in a + ``compiled_krb`` target package. You may specify a different target + package for any source directory by passing that source directory + along with the target package name as a 2-tuple. Thus, specifying the + default target package explicitly would look like: + + >>> my_engine = knowledge_engine.engine((__file__, '.compiled_krb')) + + You may specify the same target package for multiple source directories. + + The last component of the target package will be created automatically + if it does not already exist. + + .. note:: + You will 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. + + If you want to distribute your application *without* the knowledge bases, + you can use the 2-tuple notation with ``None`` as the source directory. + In this case, all of the Pyke source files must already be compiled, + and Pyke will simply load these files. Also, the target package must be + specified in absolute form (with no leading dots). + + Finally, there are four optional keyword arguments that you may also pass + to the ``engine`` constructor. These are all booleans that default to + ``True``: + + - ``load_fb`` -- load fact bases + - ``load_fc`` -- load forward-chaining rules + - ``load_bc`` -- load backward-chaining rules and + - ``load_qb`` -- load question bases + + These parameters must be passed as keyword parameters and let you + selectively load the various kinds of compiled files. + diff --git a/doc/sphinx/source/using_pyke/index.rst b/doc/sphinx/source/using_pyke/index.rst new file mode 100644 index 0000000..ddca8f1 --- /dev/null +++ b/doc/sphinx/source/using_pyke/index.rst @@ -0,0 +1,164 @@ +.. $Id: index.txt 4dca5ad0f397 2010-03-10 mtnyogi $ +.. +.. Copyright © 2007-2010 Bruce Frederiksen +.. +.. Permission is hereby granted, free of charge, to any person obtaining a copy +.. of this software and associated documentation files (the "Software"), to deal +.. in the Software without restriction, including without limitation the rights +.. to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +.. copies of the Software, and to permit persons to whom the Software is +.. furnished to do so, subject to the following conditions: +.. +.. The above copyright notice and this permission notice shall be included in +.. all copies or substantial portions of the Software. +.. +.. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +.. IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +.. FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +.. AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +.. LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +.. OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +.. THE SOFTWARE. + +.. toctree:: + :maxdepth: 1 + :hidden: + + creating_engine + adding_facts + proving_goals + other_functions + +========== +Using Pyke +========== + +This describes how to call Pyke from your Python program. + +Getting Started +=============== + +.. this code is hidden and will set __file__ to the doc/examples directory. + >>> import os + >>> __file__ = \ + ... os.path.join(os.path.dirname(os.path.dirname(os.getcwd())), + ... 'examples') + +The simplest use of Pyke involves three steps: + +`Create an engine`_ object. + + >>> from pyke import knowledge_engine + + >>> my_engine = knowledge_engine.engine(__file__) + + This step compiles the Pyke source files, if out of date, and loads the + `knowledge bases`_. + + There are three kinds of Pyke source files: + + #. `.kfb files`_ define `fact bases`_, which are compiled into .fbc pickle + files. + #. `.krb files`_ define `rule bases`_, which are compiled into 1 to 3 .py + Python source files. + #. `.kqb files`_ define `question bases`_, which are compiled into .qbc + pickle files. + + See `Creating an Inference Engine`_ to control where the compiled files + are written, load knowledge bases from multiple directories, distribute + your application without your knowledge base files, or distribute using + egg files. + +Activate `rule bases`_. + + >>> my_engine.activate('bc_related') + + You may activate one rule base for each `rule base category`_. Simply + pass multiple arguments to ``activate``. + + .. note:: + + Even if you only have one rule base, you must still activate it. + + This is when the `forward-chaining rules`_ are run. + +Prove_ goal_. + + >>> my_engine.prove_1_goal('bc_related.father_son(bruce, $son, ())') + ({'son': 'david'}, None) + + The goal might be met by simply matching an already known fact_, or + through the use of `backward-chaining rules`_. + + Then if you want to prove another goal, you can just repeat the last step. + In this case, the `forward-chaining rules`_ are only run once and all goals + operate against the same set of known facts. + + >>> my_engine.prove_1_goal('bc_related.father_son(thomas, $grandson, (grand))') + ({'grandson': 'david'}, None) + + See `Proving Goals`_ to pass different arguments into goals, compile the + goal statements once in advance, and to retrieve multiple answers for a + goal. + +Dynamically Asserting Facts +=========================== + +To dynamically assert_ facts_ within your Python program, a new step is +added: + + Create the engine object: + + >>> my_engine = knowledge_engine.engine(__file__) + +Assert_ facts_. + +>>> my_engine.assert_('family2', 'son_of', ('spike_the_dog', 'david')) + +These facts must be asserted prior to activating the rule bases so that they +are available to the `forward-chaining rules`_. This example shows asserting +case specific facts that are deleted before running the next case (as shown +in the next section, below). But you can also assert universal facts that +apply to all cases. See `Asserting New Facts`_ for more information. + + After asserting your facts, activate your rule bases and prove your goal + as before: + + >>> my_engine.activate('bc_related') + >>> my_engine.prove_1_goal('bc_related.father_son(bruce, $grandson, (grand))') + ({'grandson': 'spike_the_dog'}, None) + +Using Different Facts for Different Cases +========================================= + +But if you want to prove goals against different sets of facts or using +different rule bases, you need to reset_ the Pyke engine: + + Only need this once: + + >>> my_engine = knowledge_engine.engine(__file__) + + First case, as before: + + >>> my_engine.assert_('family2', 'son_of', ('spike_the_dog', 'david')) + >>> my_engine.activate('bc_related') + >>> my_engine.prove_1_goal('bc_related.father_son(bruce, $grandson, (grand))') + ({'grandson': 'spike_the_dog'}, None) + +Reset the Pyke engine. + +>>> my_engine.reset() + +This erases all of the case specific facts that you asserted in step 2, as +well as all of the facts asserted by the `forward-chaining rules`_. + +It also deactivates all of the `rule bases`_, so you'll need to call +activate again after asserting your facts. + + Second case: + + >>> my_engine.assert_('family2', 'son_of', ('felix_the_cat', 'david')) + >>> my_engine.activate('bc_related') + >>> my_engine.prove_1_goal('bc_related.father_son(bruce, $grandson, (grand))') + ({'grandson': 'felix_the_cat'}, None) + diff --git a/doc/sphinx/source/using_pyke/other_functions.rst b/doc/sphinx/source/using_pyke/other_functions.rst new file mode 100644 index 0000000..5ee3233 --- /dev/null +++ b/doc/sphinx/source/using_pyke/other_functions.rst @@ -0,0 +1,147 @@ +.. $Id: other_functions.txt 988349d92208 2010-03-12 mtnyogi $ +.. +.. Copyright © 2007-2010 Bruce Frederiksen +.. +.. Permission is hereby granted, free of charge, to any person obtaining a copy +.. of this software and associated documentation files (the "Software"), to deal +.. in the Software without restriction, including without limitation the rights +.. to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +.. copies of the Software, and to permit persons to whom the Software is +.. furnished to do so, subject to the following conditions: +.. +.. The above copyright notice and this permission notice shall be included in +.. all copies or substantial portions of the Software. +.. +.. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +.. IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +.. FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +.. AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +.. LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +.. OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +.. THE SOFTWARE. + +=============== +Other Functions +=============== + +Running and Pickling Plans +========================== + +.. this code is hidden and will set __file__ to the doc/examples directory. + >>> import os + >>> __file__ = \ + ... os.path.join(os.path.dirname(os.path.dirname(os.getcwd())), + ... 'examples') + >>> from pyke import knowledge_engine + >>> my_engine = knowledge_engine.engine(__file__) + >>> my_engine.add_universal_fact('family', 'son_of', ('bruce', 'thomas')) + >>> my_engine.add_universal_fact('family', 'son_of', ('david', 'bruce')) + >>> my_engine.activate('bc_related0') + +Once you've obtained a plan_ from `prove_goal`_ or `prove_1_goal`_, 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. The plans are constructed out of `functools.partial`_ +functions, which needed to be registered with copy_reg_ in Python 2.x; but +this is no longer needed in Python 3.x. + +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). + +.. __: ../pyke_syntax/krb_syntax/bc_rule.html + + +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_related0', 'grand_father_son') + >>> my_engine.prove_1_goal('bc_related0.father_son(thomas, david, $depth)') + bc_related0.grand_father_son('thomas', 'david', '$depth') + bc_related0.grand_father_son succeeded with ('thomas', 'david', ('grand',)) + ({'depth': ('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_related0', 'grand_father_son') + >>> my_engine.prove_1_goal('bc_related0.father_son(thomas, david, $depth)') + ({'depth': ('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(__file__) + >>> my_engine.activate('error_test') + >>> try: # doctest: +ELLIPSIS + ... my_engine.prove_1_goal('error_test.goal()') + ... except: + ... krb_traceback.print_exc(None, sys.stdout) # sys.stdout needed for doctest + Traceback (most recent call last): + File "", line 2, in + my_engine.prove_1_goal('error_test.goal()') + File "...knowledge_engine.py", line 367, in prove_1_goal + return goal.compile(goal_str).prove_1(self, **args) + File "...goal.py", line 47, in prove_1 + return next(iter(it)) + File "...rule_base.py", line 50, in __next__ + return next(self.iterator) + 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 231, in lookup_data + raise KeyError("$%s not bound" % var_name) + KeyError: '$bar not bound' + + +Miscellaneous +============= + +There are a few more functions that may be useful in special situations. + +*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. + diff --git a/doc/sphinx/source/using_pyke/proving_goals.rst b/doc/sphinx/source/using_pyke/proving_goals.rst new file mode 100644 index 0000000..b7d7d0d --- /dev/null +++ b/doc/sphinx/source/using_pyke/proving_goals.rst @@ -0,0 +1,119 @@ +.. $Id: proving_goals.txt 1c77af709502 2010-03-10 mtnyogi $ +.. +.. Copyright © 2010 Bruce Frederiksen +.. +.. Permission is hereby granted, free of charge, to any person obtaining a copy +.. of this software and associated documentation files (the "Software"), to deal +.. in the Software without restriction, including without limitation the rights +.. to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +.. copies of the Software, and to permit persons to whom the Software is +.. furnished to do so, subject to the following conditions: +.. +.. The above copyright notice and this permission notice shall be included in +.. all copies or substantial portions of the Software. +.. +.. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +.. IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +.. FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +.. AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +.. LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +.. OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +.. THE SOFTWARE. + +=================================== +Proving Goals +=================================== + +.. this code is hidden and will set __file__ to the doc/examples directory. + >>> import os + >>> __file__ = \ + ... os.path.join(os.path.dirname(os.path.dirname(os.getcwd())), + ... 'examples') + >>> from pyke import knowledge_engine + >>> my_engine = knowledge_engine.engine(__file__) + >>> my_engine.add_universal_fact('family', 'son_of', ('bruce', 'thomas')) + >>> my_engine.add_universal_fact('family', 'son_of', ('david', 'bruce')) + >>> my_engine.activate('bc_related0') + +Though Pyke has the capability to return multiple answers to a single goal, +often you just want the first answer: + +*some_engine*.prove_1_goal(goal, \*\*args) + ``goal`` is a Pyke goal (as a string). This may include `pattern + variables`_ (which start with a '$'). + + >>> my_engine.prove_1_goal('bc_related0.father_son(thomas, david, $depth)') + ({'depth': ('grand',)}, None) + + Returns the first proof found as a 2-tuple: a dict of bindings for the + pattern variables, and a plan_. The plan is ``None`` if no plan was + generated; otherwise, it is a Python function as described here__. + +.. __: other_functions.html#running-and-pickling-plans + + Args must be specified as keyword arguments and are set as the value of + the corresponding pattern variable. + + >>> vars, plan = \ + ... my_engine.prove_1_goal('bc_related0.father_son($father, $son, $depth)', + ... father='thomas', + ... son='david') + >>> sorted(vars.items(), key=lambda item: item[0]) + [('depth', ('grand',)), ('father', 'thomas'), ('son', 'david')] + + Prove_1_goal raises ``pyke.knowledge_engine.CanNotProve`` if no proof is + found: + + >>> my_engine.prove_1_goal('bc_related0.father_son(thomas, bogus, $depth)') + Traceback (most recent call last): + ... + pyke.knowledge_engine.CanNotProve: Can not prove bc_related0.father_son(thomas, bogus, $depth) + +*some_engine*.prove_goal(goal, \*\*args) + This returns a context manager for a generator yielding 2-tuples, as + above. Unlike ``prove_1_goal`` it does not raise an exception if no + proof is found: + + >>> from __future__ import with_statement + + >>> with my_engine.prove_goal( + ... 'bc_related0.father_son(thomas, $son, $depth)') as gen: + ... for vars, plan in gen: + ... print(vars['son'], vars['depth']) + bruce () + david ('grand',) + + Like ``prove_1_goal``, above, `pattern variables`_ in the goal_ may be + specified with keyword arguments: + + >>> with my_engine.prove_goal( + ... 'bc_related0.father_son($father, $son, $depth)', + ... father='thomas') as gen: + ... for vars, plan in gen: + ... print(vars['son'], vars['depth']) + bruce () + david ('grand',) + +Compiling Goals at Program Startup +================================== + +Similar to Python's regular expression library, ``re``, you may compile your +goal statements once at program startup: + + >>> from pyke import goal + + >>> my_goal = goal.compile('bc_related0.father_son($father, $son, $depth)') + +Then use ``my_goal.prove_1`` and ``my_goal.prove`` as many times as you'd +like: + + >>> vars, plan = my_goal.prove_1(my_engine, father='thomas', son='david') + >>> sorted(vars.items(), key=lambda item: item[0]) + [('depth', ('grand',)), ('father', 'thomas'), ('son', 'david')] + + >>> with my_goal.prove(my_engine, father='thomas') as gen: + ... for vars, plan in gen: + ... print(vars['son'], vars['depth']) + bruce () + david ('grand',) +