buguroo / pyknow

PyKnow: Expert Systems for Python
GNU Lesser General Public License v3.0
470 stars 141 forks source link

Load rule set dynamically in KnowledgeEngine #35

Closed FTholin closed 6 years ago

FTholin commented 6 years ago

Hi, I am new to PyKnow and I am attempting to make a functionnality that will allow me to load the set of Rules from an external file. I tried to use set_attr but without success the interpreter doesn't like the RULE decorator. Do you know a manner to do it properly ? Thank you in advance for your time and consideration

## Extern file 
  @Rule(Light(color='green'))
     def green_light(self):
        print("Walk")

     @Rule(Light(color='red'))
     def red_light(self):
         print("Don't walk")
###########################
from pyknow import *

class Light(Fact):
    """Info about the traffic light."""
    pass

class RobotCrossStreet(KnowledgeEngine):
  # no rules hardcoded 

if __name__ == '__main__':
    engine = RobotCrossStreet()
    engine.reset()
    engine.declare(Light(color="green"))
    engine.run()
nilp0inter commented 6 years ago

There are several ways of doing this, but for recommending one we need to know more about your use case. What are you trying to accomplish by separating the rules from the engine declaration?

FTholin commented 6 years ago

I would like to add more flexibility in my program in adding all rules in a text or json file for example. That would allow me to change rules or load what those I want easier. I hope my answer was enough to explain my problem. Thanks again.

nilp0inter commented 6 years ago

PyKnow doesn't support rule externalization out of the box yet. But we are aware of this being a desirable feature and we have it in roadmap #25 . In the meanwhile you can check this already answered question about rule externalization #2 , the same principles may be applied to JSON instead of XML.

nilp0inter commented 6 years ago

You may find also interesting the multiple-engine joining method using mixings applied here: https://github.com/buguroo/pyknow/blob/develop/docs/talks/Sistemas%20Expertos%20en%20Python%20con%20PyKnow%20-%20PyConES%202017/Descuentos.ipynb

FTholin commented 6 years ago

Thank you very much for these resources ! I'll study them with great attention.

nilp0inter commented 6 years ago

Did you delete the comment after reopening the issue?

jclarte commented 6 years ago

Hello, i try to build up a KE with a specific rule as a goal setup, depending on external parameters. But the dynamic assertion of rule drive me crazy ! I tryed to setup the rule inside the init function, the Rule is returned by get_rules. A closer inspection show me some differences between a rule instancied "normally" and the dynamically added (in _wrapped_self). I assume there is some stuff on the KE side to be setup. My test code looks like following :

def set_goal(args):
    def goal(self):
        self.declare(Fact("GOAL"))
        print("GOAL REACHED")
    return Rule(args)(goal)

class OtherTestEngine(KnowledgeEngine):

    rule1 = set_goal(Fact("A"))

    def __init__(self, goal):
        super().__init__()  
        self.rule2 = set_goal(goal)
test = OtherTestEngine(Fact("B"))
test.reset()
print(test.get_rules())
test.declare(Fact("A"))
print(test.facts)
test.run()
print(test.facts)

output :

[Rule(Fact('A'),) => <function set_goal.<locals>.goal at 0x7f7af48cc7b8>, Rule(Fact('B'),) => <function set_goal.<locals>.goal at 0x7f7af48c2840>]
<f-0>: InitialFact()
<f-1>: Fact('A')
GOAL REACHED
<f-0>: InitialFact()
<f-1>: Fact('A')
<f-2>: Fact('GOAL')

And,

test.reset()
test.declare(Fact("B"))
print(test.facts)
test.run()
print(test.facts)

output :

<f-0>: InitialFact()
<f-1>: Fact('B')
<f-0>: InitialFact()
<f-1>: Fact('B')

Do you have any suggestion about the way to fix that ?

EDIT : i got a dirty way to fix that

def solve_stuff(goal, facts):

    class Engine(KnowledgeEngine):
        rule1 = set_goal(goal)

        @Rule(Fact("A"), Fact("B"))
        def rule2(self):
            self.declare(Fact("C"))

    e = Engine()
    e.reset()
    for f in facts:
        e.declare(f)
    e.run()

    return e

solution = solve_stuff(Fact("C"), [Fact("A"), Fact("B")])

output :

GOAL REACHED
nilp0inter commented 6 years ago

Hi @bonzeye ,

thank you for reporting this, we lack some documentation about the behavior of the matcher and rule generation.

The key point here is WHEN the matcher generates the RETE network, it is done ONLY on KnowledgeEngine.__init__(). So any rule not visible before your super().__init__() will not be taken into account.

So, your code:

def set_goal(args):
    def goal(self):
        self.declare(Fact("GOAL"))
        print("GOAL REACHED")
    return Rule(args)(goal)

class OtherTestEngine(KnowledgeEngine):

    rule1 = set_goal(Fact("A"))

    def __init__(self, goal):
        super().__init__()  
        self.rule2 = set_goal(goal)

will not work, but if you call __init__() AFTER the rule creation it will:

class OtherTestEngine(KnowledgeEngine):

    rule1 = set_goal(Fact("A"))

    def __init__(self, goal):
        self.rule2 = set_goal(goal)
        super().__init__()  # <-- AFTER the rule creation

Therefore, any code creating dynamic rules before __init__ will work and any code creating them after will not.

There are several ways of doing this and we have to document about it.

Additionally when we finish #25, the dynamic generation of rules will be less of a necessity.

jclarte commented 6 years ago

tyvm for the fast answer, it helped me a lot.