Giter Club home page Giter Club logo

experta's Introduction

Experta

pypi version tests Documentation Status codecov.io

Experta is a Python library for building expert systems strongly inspired by CLIPS.

from random import choice
from experta import *


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


class RobotCrossStreet(KnowledgeEngine):
    @Rule(Light(color='green'))
    def green_light(self):
        print("Walk")

    @Rule(Light(color='red'))
    def red_light(self):
        print("Don't walk")

    @Rule(AS.light << Light(color=L('yellow') | L('blinking-yellow')))
    def cautious(self, light):
        print("Be cautious because light is", light["color"])
>>> engine = RobotCrossStreet()
>>> engine.reset()
>>> engine.declare(Light(color=choice(['green', 'yellow', 'blinking-yellow', 'red'])))
>>> engine.run()
Be cautious because light is blinking-yellow

Migrating from Pyknow

Experta is a Pyknow fork. Just replace any pyknow references in your code/examples to experta and everything should work the same.

Examples

You can find some more examples on GitHub.

experta's People

Contributors

johnnywalls avatar jorgearanda avatar jparedes-buguroo avatar nilp0inter avatar xayon avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

experta's Issues

Firing a rule only once

Hi,

I've been testing out Experta a little bit, and so far it really looks like what I need. This might be more a question than an actual issue, but I figured it might be something to look at if not already existing.

I noticed that some rules (for example when no TESTs are used) only fired once by default. Others, where I am using a TEST, do keep firing as long as the test is fulfilled. I was wondering if there is a way to limit the amount of times a rule is fired.

Thanks in advance for the help!

Last activation not executed in some cases

In this case the last activation is not executed

from experta import *

class KE(KnowledgeEngine):
    @Rule(
        Fact(s=True),
        Fact('a')
    )
    def foo(self):
        print("foo")

    @Rule(
        AS.f << Fact(s=False),
        Fact('a')
    )
    def bar(self, f):
        print("bar")
        self.modify(f, s=True)

ke=KE()
ke.reset()
ke.declare(Fact("a"))
ke.declare(Fact(s=False))
ke.run()

It should print bar and then foo, but it only prints bar.

Not the expected result

I am trying to run this code:

class RuleEng(KnowledgeEngine):
    @Rule(salience=1)
    def r1(self):
        print("Execution r1 second")
        pass
    @Rule(salience=2)
    def r2(self):
        print("Execution r2 first")
        pass
    @Rule(Fact(foo="bar"))
    def Foo(self):
        print("Execute Foo")
        pass

engine = RuleEng()
engine.reset()
engine.declare(Fact(foo="bar"))
print(engine.facts)
engine.modify(engine.facts[1], foo="bar2")
engine.modify(engine.facts[1], foo="bar")

and got this error:

<f-0>: InitialFact()
<f-1>: Fact(foo='bar')

Traceback (most recent call last):
  File ***, in <module>   
    engine.modify(engine.facts[1], foo="bar")
                  ~~~~~~~~~~~~^^^
KeyError: 1

Is there an easy way to modify facts?

I don't know if my question is caused by a misunderstanding of the topic. I have a list of facts that I need to be able to easily modify.

The documentation shows the modify method as follows:

>>> engine.facts
<f-0> InitialFact()
<f-1> Fact(color='red')
>>> engine.modify(engine.facts[1], color='yellow', blink=True)
<f-2>
>>> engine.facts
<f-0> InitialFact()
<f-2> Fact(color='yellow', blink=True)

But to use it you need to know the index of the fact you want to modify in the list of facts, and I don't necessarily know the order in which the facts are declared. What's more, if I make successive modifications, the disorder of the list increases.

It would be great to have a way to modify facts like:

>>> engine.modify(Fact(color='red'), color='yellow', blink=True)

Or maybe a way to search for facts like:

>>> index = engine.facts.search(Fact(color='red'))
>>> engine.modify(engine.facts[index], color='yellow', blink=True)

I really appreciate the help. If it's something that wasn't covered in the code and you think it would be great to add, I'd be happy to consider working on it. If the solution to the question is a different approach, please share it with me.

Activations are not well-ordered

While fixing #10 I discovered that Activation objects don't seem to have coherent ordering which causes issues with bisect ordering in the Agenda resulting in more complex code than needed.

EXISTS doesn't want to match

I have a rule with this structure:

@Rule(
        # NOT(Excluded(patient_id=MATCH.patient_id)),
        EXISTS(
            FeverGreaterThanFourDays(
                patient_id=MATCH.patient_id,
                appointment_id=MATCH.appointment_id,
                weight=MATCH.fever_weight,
                present=W(),
            ),
            ConjunctivalInjection(
                patient_id=MATCH.patient_id,
                appointment_id=MATCH.appointment_id,
                weight=MATCH.conjunctival_weight,
                present=W(),
            ),
      # etc...
    )
   def _ (patient_id, appointment_id, **kwargs):
     # my function.

These are some sample facts:

FactList([(0, InitialFact()),
          (1, Patient(age=5, patient_id=5223)),
          (2, Patient(age=45, patient_id=1223)),
          (3,
           Objective(patient_id=5223, appointment_id=2, code='42631002', code_system='SNOMEDCT')),
          (4,
           Objective(patient_id=5223, appointment_id=2, code='82014009', code_system='SNOMEDCT')),
          (5,
           Objective(patient_id=5223, appointment_id=2, code='99999984', code_system='SNOMEDCT'))])

If I replace "EXISTS" with "OR", I can get matches almost as documented--basically I see the rule fire for each fact that matches. Unfortunately this is not what the doc says--it says it should fire for each combination of facts that match, so I'd expect to see it fire on individual facts, then pairs, then triples, etc. I suspect this is just an error in the docs.

This is the result using "OR":

Code system match: SNOMEDCT
Calculating initial score for 5223 (appointment 2) on available data: dict_items([('rash_weight', 0)])
Code system match: SNOMEDCT
Calculating initial score for 5223 (appointment 2) on available data: dict_items([('peripheral_weight', 1)])
Code system match: SNOMEDCT
Calculating initial score for 5223 (appointment 2) on available data: dict_items([('strawberry_tongue_weight', 1)])
Patient 1223 does not meet age criteria 45 > 10

Resulting facts are:
FactList([(0, InitialFact()),
          (1, Patient(age=5, patient_id=5223)),
          (2, Patient(age=45, patient_id=1223)),
          (3,
           Objective(patient_id=5223, appointment_id=2, code='42631002', code_system='SNOMEDCT')),
          (4,
           Objective(patient_id=5223, appointment_id=2, code='82014009', code_system='SNOMEDCT')),
          (5,
           Objective(patient_id=5223, appointment_id=2, code='99999984', code_system='SNOMEDCT')),
          (6,
           PolymorphousRash(patient_id=5223, appointment_id=2, weight=0, present=False)),
          (7, InitialRiskScore(patient_id=5223, appointment_id=2, score=0)),
          (8,
           PeripheralEdema(patient_id=5223, appointment_id=2, weight=1, present=True)),
          (9, InitialRiskScore(patient_id=5223, appointment_id=2, score=1)),
          (10,
           StrawberryTongue(patient_id=5223, appointment_id=2, weight=1, present=True)),
          (11, Excluded(patient_id=1223))])

However, with the same set of facts that match with OR, if I change from OR to EXISTS it ceases matching. My expectation is that the rule will fire once, and that the associated function will receive all the weight parameters for matching facts. Instead none of the facts match...

Code system match: SNOMEDCT
Code system match: SNOMEDCT
Code system match: SNOMEDCT
Patient 1223 does not meet age criteria 45 > 10

Resulting facts are:
FactList([(0, InitialFact()),
          (1, Patient(age=5, patient_id=5223)),
          (2, Patient(age=45, patient_id=1223)),
          (3,
           Objective(patient_id=5223, appointment_id=2, code='42631002', code_system='SNOMEDCT')),
          (4,
           Objective(patient_id=5223, appointment_id=2, code='82014009', code_system='SNOMEDCT')),
          (5,
           Objective(patient_id=5223, appointment_id=2, code='99999984', code_system='SNOMEDCT')),
          (6,
           PolymorphousRash(patient_id=5223, appointment_id=2, weight=0, present=False)),
          (7,
           PeripheralEdema(patient_id=5223, appointment_id=2, weight=1, present=True)),
          (8,
           StrawberryTongue(patient_id=5223, appointment_id=2, weight=1, present=True)),
          (9, Excluded(patient_id=1223))])

It seems like EXISTS is critical for "aggregating" information from multiple facts. If this is not the intended behavior of EXISTS, is there something else I should use? I need to be able to reason on the largest collection of facts in a set that match.

Let me know if there is a different approach I should use.

multifieldwildcard

Implementation of multifield wildcard for patern match - CLIPS equivalent for $?. Started at branch feature-multifieldwildcard a long time ago. Is there any milestone for this feature?

AttributeError: module 'collections' has no attribute 'Mapping'

Python Python 3.10.9
Jupyter Notebook 6.5.2

!pip install experta
Requirement already satisfied: experta in c:\programdata\anaconda3\lib\site-packages (1.9.4)
Requirement already satisfied: frozendict==1.2 in c:\programdata\anaconda3\lib\site-packages (from experta) (1.2)
Requirement already satisfied: schema==0.6.7 in c:\programdata\anaconda3\lib\site-packages (from experta) (0.6.7)

from experta import *

AttributeError Traceback (most recent call last)
Cell In[9], line 1
----> 1 from experta import *

File C:\ProgramData\anaconda3\lib\site-packages\experta_init_.py:5
3 try:
4 from .conditionalelement import AND, OR, NOT, TEST, EXISTS, FORALL
----> 5 from .engine import KnowledgeEngine
6 from .fact import Fact, InitialFact, Field
7 from .fieldconstraint import L, W, P

File C:\ProgramData\anaconda3\lib\site-packages\experta\engine.py:13
10 from experta import abstract
12 from experta.agenda import Agenda
---> 13 from experta.fact import InitialFact
14 from experta.factlist import FactList
15 from experta.rule import Rule

File C:\ProgramData\anaconda3\lib\site-packages\experta\fact.py:9
6 from schema import Schema
8 from experta.pattern import Bindable
----> 9 from experta.utils import freeze, unfreeze
10 from experta.conditionalelement import OperableCE
11 from experta.conditionalelement import ConditionalElement

File C:\ProgramData\anaconda3\lib\site-packages\experta\utils.py:4
1 from functools import singledispatch
2 import collections.abc
----> 4 from frozendict import frozendict
6 from .fieldconstraint import P
9 class frozenlist(tuple):

File C:\ProgramData\anaconda3\lib\site-packages\frozendict_init_.py:16
10 OrderedDict = NotImplemented
13 iteritems = getattr(dict, 'iteritems', dict.items) # py2-3 compatibility
---> 16 class frozendict(collections.Mapping):
17 """
18 An immutable wrapper around dictionaries that implements the complete :py:class:collections.Mapping
19 interface. It can be used as a drop-in replacement for dictionaries where immutability is desired.
20 """
22 dict_cls = dict

AttributeError: module 'collections' has no attribute 'Mapping'


After deleting the site-packages
!pip install experta
Collecting experta
Using cached experta-1.9.4-py3-none-any.whl (35 kB)
Requirement already satisfied: frozendict==1.2 in c:\programdata\anaconda3\lib\site-packages (from experta) (1.2)
Requirement already satisfied: schema==0.6.7 in c:\programdata\anaconda3\lib\site-packages (from experta) (0.6.7)
Installing collected packages: experta
Successfully installed experta-1.9.4

from experta import *
...
AttributeError: module 'collections' has no attribute 'Mapping'

โ€™EXISTS' cannot return accuracy results, influenced by the pattern order insider.

from experta import *


class Goal(Fact):
    pass


class Hero(Fact):
    name = Field(str)
    status = Field(str, default="unoccupied")


class KE(KnowledgeEngine):
    @DefFacts()
    def goal_and_heroes(self):
        yield Goal('save-the-day')
        yield Hero(name="Death Defying Man")
        yield Hero(name="Stupendous Man")
        yield Hero(name="Incredible Man")

    @Rule(
        Goal('save-the-day'),
        EXISTS(            # bugs: return results influenced by the patterns order
            Hero(name="Death Defying Man"),
            Hero(status='occupied'),
            # Hero(status='occupied'),
            # Hero(name="Death Defying Man")
        )
    )
    def save_the_day(self):
        print("The day is saved")


if __name__ == '__main__':
    ke = KE()
    ke.reset()
    watch('RULES')
    ke.run()

Result:

The day is saved
INFO:experta.watchers.RULES:FIRE 1 save_the_day: <f-1>, <f-0>

However, if I reverse the patterns order inEXISTS, the result would be empty.

Check that the fact does not exist, but with the data of another fact

I have a fact:
AS.run << Run(size=MATCH.size,
startX=MATCH.x,
startY=MATCH.y,
configuration=MATCH.conf,
lastX=MATCH.xe,
lastY=MATCH.ye,
length=MATCH.length,
plan=MATCH.plan)
.
I want add condition (NOT(EXISTS (Run (length = MATCH.length + 1))). \ I want verify, that there no eny facts Run, which have length ofcurrent fact "Run" - 1
I know, that "MATCH.length + 1" is elligal, but how i can it another way?

Implementation of the water jugs problem

Hi, thanks for this interesting library!

I tried implementing a version of the water jugs problem (of which a version was seen on the Die Hard movie), but it gets stuck in loop. The problem is usually formulated as this:

Suppose we have two jugs. One jug is capable of holding 3 gallons of water. A second jug can hold up to 4 gallons of water. There are no measurement lines on either jug. Therefore we can never determine the exact amount of water in either jug. However, by looking in either jug, we can determine if the jug is empty, full, or contains some water. We can empty a jug, fill a jug, or pour water from one jug into the other.

I thought the implementation would be straightforward, but after many tries I was unable to make work. I'm sure I'm missing something.

from experta import KnowledgeEngine, Fact, Field, Rule, DefFacts, AS, MATCH, TEST, P, W, L


class Jug(Fact):
    content = Field(int, default=0, mandatory=True)


class Jug3(Jug): pass
class Jug4(Jug): pass


class Jugs(KnowledgeEngine):

    @DefFacts()
    def init(self):
        yield Jug3(content=0)
        yield Jug4(content=0)

    @Rule(Jug4(content=L(2)))
    def goal(self):
        print("Done")
        self.halt()

    @Rule(
        AS.jug3 << Jug3(content=MATCH.content3),
        AS.jug4 << Jug4(content=MATCH.content4),
        TEST(lambda content3, content4: content3 < 3 and content4 > 0),
    )
    def pour_jug4_into_jug3(self, jug3, jug4, content3, content4):
        content_to_pour = min(3 - content3, content4, 3)
        jug3_content = content3 + content_to_pour
        jug4_content = content4 - content_to_pour
        self.modify(jug3, content=jug3_content)
        self.modify(jug4, content=jug4_content)
        print("Pour jug4 into jug3")

    @Rule(
        AS.jug3 << Jug3(content=MATCH.content3),
        AS.jug4 << Jug4(content=MATCH.content4),
        TEST(lambda content3, content4: content3 > 0 and content4 < 4),
    )
    def pour_jug3_into_jug4(self, jug3, jug4, content3, content4):
        content_to_pour = min(4 - content4, content3, 4)
        jug3_content = content3 - content_to_pour
        jug4_content = content4 + content_to_pour
        self.modify(jug3, content=jug3_content)
        self.modify(jug4, content=jug4_content)
        print("Pour jug3 into jug4")

    @Rule(AS.jug3 << Jug3(content=P(lambda x: x > 0)))
    def empty_jug3(self, jug3):
        self.modify(jug3, content=0)
        print("Empty jug3")

    @Rule(AS.jug4 << Jug4(content=P(lambda x: x > 0)))
    def empty_jug4(self, jug4):
        self.modify(jug4, content=0)
        print("Empty jug4")

    @Rule(AS.jug3 << Jug3(content=P(lambda x: x < 3)))
    def fill_jug3(self, jug3):
        self.modify(jug3, content=3)
        print("Fill jug3")

    @Rule(AS.jug4 << Jug4(content=P(lambda x: x < 4)))
    def fill_jug4(self, jug4):
        self.modify(jug4, content=4)
        print("Fill jug4")


engine = Jugs()
engine.reset()
engine.run()

Need some help about the GC of KnowledgeEngine

I use the KnowledgeEngine as my Rule Engine. but the instance Of Light() ect would not actually release in memory.

I use it with a loop and at least 10000 user which has a large number Fact. The amount of memory will raise very quickly and be killed because of the memory is not enough.

Try to use gc.collect(). But it has the same result. And I need some help about that. Thanks.

This is my test sample of Light() has not be release in memory after the loop. And I use objgraph package for checking it .

# here's the demo code use by a loop
from random import choice
from experta import *

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

class RobotCrossStreet(KnowledgeEngine):
    @Rule(Light(color='green'))
    def green_light(self):
        print("Walk")

    @Rule(Light(color='red'))
    def red_light(self):
        print("Don't walk")

    @Rule(AS.light << Light(color=L('yellow') | L('blinking-yellow')))
    def cautious(self, light):
        print("Be cautious because light is", light["color"])


def test_func():
    engine = RobotCrossStreet()
    engine.reset()
    engine.declare(Light(color=choice(['green', 'yellow', 'blinking-yellow', 'red'])))
    engine.run()
    # will do some record..


if __name__ == '__main__':
    import objgraph

    objgraph.show_growth()
    for i in range(10):
        test_func()
    print('======after======')
    objgraph.show_growth()

# the first print of show_growth()
'''
function                       2389     +2389
dict                           1211     +1211
wrapper_descriptor             1043     +1043
tuple                           831      +831
builtin_function_or_method      789      +789
method_descriptor               743      +743
weakref                         731      +731
getset_descriptor               455      +455
member_descriptor               299      +299
type                            231      +231
'''

# the second print of show_growth
'''
dict                           1311      +100
weakref                         786       +55
set                             155       +46
builtin_function_or_method      821       +32
FactCapture                      30       +30
list                            209       +29
Light                            32       +29    <---- here
tuple                           858       +27
method                           43       +15
ChildNode                        15       +15
'''

Rules called on already retracted facts

This is a copy of this issue.

Rules are being called on already retracted facts.

P = [[None, 2, None, 6, None, 8, None, None, None],
     [5, 8, None, None, None, 9, 7, None, None],
     [None, None, None, None, 4, None, None, None, None],
     [3, 7, None, None, None, None, 5, None, None],
     [6, None, None, None, None, None, None, None, 4],
     [None, None, 8, None, None, None, None, 1, 3],
     [None, None, None, None, 2, None, None, None, None],
     [None, None, 9, 8, None, None, None, 3, 6],
     [None, None, None, 3, None, 6, None, 9, None]]


class Possible(Fact):
    pass


class Solver(KnowledgeEngine):
    @DefFacts()
    def init_puzzle(self):
        for x, row in enumerate(P):
            for y, cell in enumerate(row):
                block = ((y // 3) * 3) + (x // 3)
                if cell is None:
                    yield Fact(value=None, y=y, x=x, block=block)
                    for i in range(1, 10):
                        yield Possible(value=i, y=y, x=x, block=block)
                else:
                    yield Fact(value=cell, y=y, x=x, block=block)


    @Rule(Fact(value=~L(None) & MATCH.v, y=MATCH.y),
          AS.p << Possible(value=MATCH.v, y=MATCH.y))
    def discarded_by_column(self, p):
        self.retract(p)

    @Rule(Fact(value=~L(None) & MATCH.v, x=MATCH.x),
          AS.p << Possible(value=MATCH.v, x=MATCH.x))
    def discarded_by_row(self, p):
        self.retract(p)

    @Rule(Fact(value=~L(None) & MATCH.v, block=MATCH.b),
          AS.p << Possible(value=MATCH.v, block=MATCH.b))
    def discarded_by_block(self, p):
        self.retract(p)

    @Rule(AS.cell << Fact(value=None, x=MATCH.x, y=MATCH.y, block=MATCH.b),
          Possible(value=MATCH.v, x=MATCH.x, y=MATCH.y, block=MATCH.b),
          NOT(Possible(value=~MATCH.v, x=MATCH.x, y=MATCH.y, block=MATCH.b)))
    def only_one_possible(self, cell, v):
        self.retract(cell)
        self.declare(Fact(value=v, x=cell['x'], y=cell['y'], block=cell['block']))

    @Rule(AS.cell << Fact(value=None, x=MATCH.x, y=MATCH.y, block=MATCH.b),
          Possible(value=MATCH.v, x=MATCH.x, y=MATCH.y, block=MATCH.b),
          NOT(Possible(value=MATCH.v, x=~MATCH.x, y=~MATCH.y, block=MATCH.b)))
    def unique_candidate_block(self, cell, v):
        self.retract(cell)
        self.declare(Fact(value=v, x=cell['x'], y=cell['y'], block=cell['block']))

    @Rule(AS.cell << Fact(value=None, x=MATCH.x, y=MATCH.y, block=MATCH.b),
          Possible(value=MATCH.v, x=MATCH.x, y=MATCH.y, block=MATCH.b),
          NOT(Possible(value=MATCH.v, x=~MATCH.x, y=MATCH.y, block=~MATCH.b)))
    def unique_candidate_col(self, cell, v):
        self.retract(cell)
        self.declare(Fact(value=v, x=cell['x'], y=cell['y'], block=cell['block']))

    @Rule(AS.cell << Fact(value=None, x=MATCH.x, y=MATCH.y, block=MATCH.b),
          Possible(value=MATCH.v, x=MATCH.x, y=MATCH.y, block=MATCH.b),
          NOT(Possible(value=MATCH.v, x=MATCH.x, y=~MATCH.y, block=~MATCH.b)))
    def unique_candidate_row(self, cell, v):
        self.retract(cell)
        self.declare(Fact(value=v, x=cell['x'], y=cell['y'], block=cell['block']))

    @Rule(Fact(value=~L(None) & MATCH.v, x=MATCH.x, y=MATCH.y, block=MATCH.b),
          AS.p << Possible(value=~MATCH.v, x=MATCH.x, y=MATCH.y, block=MATCH.b))
    def remove_other_candidates(self, p):
        self.retract(p)


watch('RULES', 'FACTS')
s = Solver()
s.reset()
s.run()

This code raises the following exception:

INFO:pyknow.watchers.FACTS: <== <f-130>: Possible(value=2, y=1, x=2, block=0)
INFO:pyknow.watchers.RULES:FIRE 531 unique_candidate_col: <f-130>, <f-128>
Traceback (most recent call last):
  File "sudoku.py", line 95, in <module>
    s.run()
  File "/home/nil/.local/share/virtualenvs/sudoku-VtHvu9jk/lib/python3.7/site-packages/pyknow/engine.py", line 168, in run
    for k, v in activation.context.items()
  File "/home/nil/.local/share/virtualenvs/sudoku-VtHvu9jk/lib/python3.7/site-packages/pyknow/rule.py", line 87, in __call__
    return self._wrapped(*args, **kwargs)
  File "sudoku.py", line 76, in unique_candidate_col
    self.retract(cell)
  File "/home/nil/.local/share/virtualenvs/sudoku-VtHvu9jk/lib/python3.7/site-packages/pyknow/engine.py", line 124, in retract
    self.facts.retract(idx_or_declared_fact)
  File "/home/nil/.local/share/virtualenvs/sudoku-VtHvu9jk/lib/python3.7/site-packages/pyknow/factlist.py", line 111, in retract
    raise IndexError('Fact not found.')
IndexError: Fact not found.

A similar exception is raised if the method modify is used instead of retract+declare.

Python expert system facts generated from an Excel table

I have this code, for animals:

from experta import rule
from experta.engine import KnowledgeEngine
from experta.fieldconstraint import L
from experta.rule import Rule
from experta.fact import Fact, Field
from experta.deffacts import DefFacts
from experta.shortcuts import MATCH
from experta.conditionalelement import TEST

class animal(Fact):
name = Field(str, mandatory=True)
number = Field(int, mandatory=True)

class AnimalAnalysis(KnowledgeEngine):
@DefFacts()
def init_animal(self):
yield animal(name = 'dog',
number = 23)
yield animal(name = 'cat',
number = 5)
yield animal(name = 'snake',
number = 14)

@Rule(animal(name=MATCH.name, number=MATCH.number), TEST(lambda number: number > 10))
def animal_analysis(self, name, number):
        return print(name, 'has more than 10 ', number)

engine = AnimalAnalysis()
engine.reset()
engine.run()

Which result:
snake has more than 10 14
dog has more than 10 23

How can I import, at once, a complete excel or txt list of facts?
(Below: )

animal number
dog 23
cat 5
snake 14
dog 32
bird 78
cat 48
dog 52
dog 36
cat 28
bird 14
snake 24
snake 17

Thanks folks! :)

Looping Experta for use with a chatbot

Hi I have a question relating to looping Experta infinitely. I am currently creating a rule based retrieval chat bot and I would like to use Experta as my Knowledge engine.

To give a quick background I need to get train ticket information from the user and use this to scrape a website. I need to assign facts based on a user input i.e assign destination, current location, date etc.

As it is a chatbot I need experta to run infinitely until it is given an instruction to break or it completes it's task. For example if the chatbot asks, "what station are you at? " and the response is not a station, then it should repeat the question.

At the moment my rules will fire once and then the process will exit. How can i implement this looping system?

Fact can only be modified once based on the fact name

Hi,

I would like for my rule engine to run, save some values and then use those values, after the run, to modify some facts. For background: I want to run the engine 'dynamically': for each time step, I am running the engine, it provides a result, and based on that my facts change for the next time instance (the next run).

A minimal working example (without any relevant facts and rules):

import experta as exp
   
class Fact_1(exp.Fact):
    value_1 = exp.Field(int)

class Fact_2(exp.Fact):
    value_2 = exp.Field(int)
    
class Rules(exp.KnowledgeEngine):
    @exp.Rule(Fact_1(value_1 = exp.MATCH.v1),
              Fact_2(value_2 = exp.MATCH.v2))
    def subtract(self, v1, v2):
        return v1-v2
    
    
engine = Rules()
fact_1 = Fact_1(value_1 = 1)
fact_2 = Fact_2(value_2 = 3)
engine.reset()
engine.declare(fact_1)
engine.declare(fact_2)

engine.run()
engine.modify(fact_1)

engine.run()
engine.modify(fact_1)

This yields a Fact not founderror, I think because any time a fact is modified, it moves to the end of the fact list (engine.facts), while it is the number in this list that is used for the next modification.

I am new to working with rule-engines, so this might be a very specific problem that only I am facing, but I still have the feeling this might be a useful thing to have (i.e. repeated modification of facts by use of their name).

How to show fired rule.

I want to let the user know which rule are fired based on their respond. How do I show the list of rules that are fired?

Is there still room for optimization in the judgment?

If in the case of AND, as long as one of the multiple facts is false, then there is no need to judge again, but the current situation is that every fact under AND will be judged.
In the same way, in the case of OR, as long as one of the multiple facts is true, there is no need to judge later, but the current situation is that every fact under the OR will be judged.

Last activation is not executed

The last activation on the Greetings example from the docs is not executed. So, when ask_name is executed, ask_location is not and vice-versa. Therefore, greet is never executed. I saw that you had a similar issue (#7 ) that is supposedly fixed, but I think this might be a similar thing.

I noticed that if salience is set, then this just works fine. When not set, the Activations' keys are the same and that's why the second activation is removed from the list when _update_agenda is called after the first activation is fired.

I'm currently using the latest code of the develop branch. This is the Greeting code I'm using:

from experta import *

class Greetings(KnowledgeEngine):
    @DefFacts()
    def _initial_action(self):
        yield Fact(action="greet")

    @Rule(Fact(action='greet'),
          NOT(Fact(name=W())))
    def ask_name(self):
        self.declare(Fact(name=input("What's your name? ")))

    @Rule(Fact(action='greet'),
          NOT(Fact(location=W())))
    def ask_location(self):
        self.declare(Fact(location=input("Where are you? ")))

    @Rule(Fact(action='greet'),
          Fact(name=MATCH.name),
          Fact(location=MATCH.location))
    def greet(self, name, location):
        print("Hi %s! How is the weather in %s?" % (name, location))

engine = Greetings()
engine.reset()  # Prepare the engine for the execution.
engine.run()  # Run it!

Result will be arbitrary when only NOT is in the rule

You can just keep running the code below, the result will be arbitrary, which is wierd.

from experta import *


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


class RobotCrossStreet(KnowledgeEngine):
    @Rule(NOT(Light(color=L('red'))))
    def red_light(self):
        print("dont")

    @Rule(NOT(Light(color=L('yellow'))))
    def yellow_light(self):
        print("wait")

engine = RobotCrossStreet()
engine.reset()
engine.declare(Light(color='red'))
engine.run()

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.