diff --git a/docs/examples/blocks.ipynb b/docs/examples/blocks.ipynb new file mode 100644 index 0000000..891a7f0 --- /dev/null +++ b/docs/examples/blocks.ipynb @@ -0,0 +1,141 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": 1, + "metadata": {}, + "outputs": [], + "source": [ + "# ======================================================\n", + "# Blocks World Program\n", + "#\n", + "# This program was introduced in Chapter 8.\n", + "#\n", + "# CLIPS Version 6.0 Example\n", + "#\n", + "# To execute, merely load, reset and run.\n", + "# ======================================================" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": {}, + "outputs": [], + "source": [ + "from pyknow import *" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": {}, + "outputs": [], + "source": [ + "class Goal(Fact):\n", + " pass\n", + "\n", + "class Stack(Fact):\n", + " pass" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": {}, + "outputs": [], + "source": [ + "class Blocks(KnowledgeEngine):\n", + " @DefFacts()\n", + " def initial_state(self):\n", + " yield Stack(\"A\", \"B\", \"C\")\n", + " yield Stack(\"D\", \"E\", \"F\")\n", + " yield Goal(move=\"C\", on_top_of=\"E\")\n", + " yield Stack()\n", + " \n", + " @Rule(\n", + " AS.goal << Goal(move=MATCH.block1, on_top_of=MATCH.block2),\n", + " AS.stack1 << Stack(MATCH.block1, +MATCH.rest1),\n", + " AS.stack2 << Stack(MATCH.block2, +MATCH.rest2)\n", + " )\n", + " def move_directly(self, goal, block1, block2, stack1, stack2, rest1, rest2):\n", + " self.retract(goal)\n", + " self.retract(stack1)\n", + " self.retract(stack2)\n", + " self.declare(Stack(*rest1))\n", + " self.declare(Stack(block1, block2, *rest2))\n", + " print(block1, \"moved on top of\", block2)\n", + " \n", + " @Rule(\n", + " AS.goal << Goal(move=MATCH.block1, on_top_of=\"floor\"),\n", + " AS.stack1 << Stack(MATCH.block1, +MATCH.rest)\n", + " )\n", + " def move_to_floor(self, goal, stack1, block1, rest):\n", + " self.retract(goal)\n", + " self.retract(stack1)\n", + " self.declare(Stack(block1))\n", + " self.declare(Stack(*rest))\n", + " print(block1, \"moved on top of floor\")\n", + "\n", + " @Rule(\n", + " Goal(move=MATCH.block1),\n", + " Stack(MATCH.top, +MATCH.others),\n", + " TEST(lambda block1, others: block1 in others)\n", + " )\n", + " def clear_upper_block(self, top):\n", + " self.declare(Goal(move=top, on_top_of=\"floor\"))\n", + " \n", + " @Rule(\n", + " Goal(on_top_of=MATCH.block1),\n", + " Stack(MATCH.top, +MATCH.others),\n", + " TEST(lambda block1, others: block1 in others)\n", + " )\n", + " def clear_lower_block(self, top):\n", + " self.declare(Goal(move=top, on_top_of=\"floor\"))" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "D moved on top of floor\n", + "A moved on top of floor\n", + "B moved on top of floor\n", + "C moved on top of E\n" + ] + } + ], + "source": [ + "ke=Blocks()\n", + "ke.reset()\n", + "ke.run()" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.5.3" + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} diff --git a/pyknow/fieldconstraint.py b/pyknow/fieldconstraint.py index caa830f..a28bde2 100644 --- a/pyknow/fieldconstraint.py +++ b/pyknow/fieldconstraint.py @@ -63,13 +63,19 @@ def __repr__(self): # pragma: no cover class W(Bindable, FieldConstraint): """Wildcard Field Constraint""" - def __new__(cls, __bind__=None): + def __new__(cls, __bind__=None, __multi__=False): obj = super(W, cls).__new__(cls) obj.__bind__ = __bind__ + obj.__multi__ = __multi__ return obj def __repr__(self): # pragma: no cover - return "W()" if self.__bind__ is None else "W(%r)" % self.__bind__ + sign = "+" if self.__multi__ else "" + body = ("W(%r)" % self.__bind__ if self.__bind__ else "W()") + return sign + body + + def __pos__(self): + return W(__bind__=self.__bind__, __multi__=True) class P(Bindable, FieldConstraint): diff --git a/pyknow/matchers/rete/check.py b/pyknow/matchers/rete/check.py index e3b87e6..6b2ecf4 100644 --- a/pyknow/matchers/rete/check.py +++ b/pyknow/matchers/rete/check.py @@ -12,7 +12,11 @@ CheckFunction = namedtuple('CheckFunction', - ['key_a', 'key_b', 'expected', 'check']) + ['key_a', + 'key_b', + 'multi', + 'expected', + 'check']) class TypeCheck(Check, namedtuple('_TypeCheck', ['fact_type'])): @@ -60,7 +64,11 @@ def __str__(self): # pragma: no cover class FeatureCheck(Check, namedtuple('_FeatureCheck', - ['what', 'how', 'check', 'expected'])): + ['what', + 'how', + 'multi', + 'check', + 'expected'])): _instances = dict() @@ -77,6 +85,7 @@ def __new__(cls, what, how): cls, what, how, + check_function.multi, check_function.check, check_function.expected) @@ -97,7 +106,13 @@ def __call__(self, data, is_fact=True): return False else: try: - record = data[self.what] + if self.multi: + numidx = sorted( + x for x in data.keys() + if isinstance(x, int) and x >= self.what) + record = tuple(data[k] for k in numidx) + else: + record = data[self.what] except (IndexError, KeyError, TypeError): return False else: @@ -131,6 +146,7 @@ def equal_literal(actual, expected): return CheckFunction(key_a=L, key_b=(pce.value, pce.__bind__), + multi=False, expected=pce, check=equal_literal) @@ -151,6 +167,7 @@ def match_predicate(actual, expected): return CheckFunction(key_a=P, key_b=key_b, + multi=False, expected=pce, check=match_predicate) @@ -163,7 +180,8 @@ def wildcard_match(actual, expected): return {expected.__bind__: actual} return CheckFunction(key_a=W, - key_b=pce.__bind__, + key_b=(pce.__bind__, pce.__multi__), + multi=pce.__multi__, expected=pce, check=wildcard_match) @@ -182,6 +200,7 @@ def not_equal(actual, expected): return CheckFunction(key_a=NOTFC, key_b=key_b, + multi=False, expected=key_b, check=not_equal) @@ -210,6 +229,7 @@ def and_match(actual, expected): return CheckFunction(key_a=ANDFC, key_b=key_b, + multi=False, expected=key_b, check=and_match) @@ -227,6 +247,7 @@ def or_match(actual, expected): return CheckFunction(key_a=ORFC, key_b=key_b, + multi=False, expected=key_b, check=or_match) diff --git a/pyknow/shortcuts.py b/pyknow/shortcuts.py index e094fa2..67ca11c 100644 --- a/pyknow/shortcuts.py +++ b/pyknow/shortcuts.py @@ -11,7 +11,7 @@ class _MATCH: MATCH.something """ def __getattr__(self, name): - return (name << W()) + return name << W() MATCH = _MATCH()