def test_or_operator(): """ Test OR operator """ from experta.engine import KnowledgeEngine from experta import Rule, OR from experta import Fact, L class Test(KnowledgeEngine): """ Test KE """ @Rule(OR(Fact(something=L(1)), Fact(something=L(2)))) def rule1(self): """ First rule, something=1 and something=2""" pass ke_ = Test() ke_.reset() ke_.declare(Fact(something=1)) assert len(ke_.agenda.activations) == 1 ke_ = Test() ke_.reset() ke_.declare(Fact(something=2)) assert len(ke_.agenda.activations) == 1 ke_ = Test() ke_.reset() ke_.declare(Fact(something=3)) assert len(ke_.agenda.activations) == 0
def test_TEST_2(): from collections import Counter from experta import KnowledgeEngine, Rule, Fact, TEST, W executed = Counter() class Test(KnowledgeEngine): @Rule(Fact("a" << W()), Fact("b" << W()), TEST(lambda a, b: a > b), Fact("c" << W()), TEST(lambda b, c: b > c)) def is_greater(self, a, b, c): nonlocal executed executed[(a, b, c)] += 1 t = Test() f1 = Fact(1) f2 = Fact(2) f3 = Fact(3) f4 = Fact(4) t.reset() t.declare(f1, f2, f3, f4) t.run() assert len(executed) == 4 assert (4, 3, 2) in executed and executed[(4, 3, 2)] == 1 assert (4, 3, 1) in executed and executed[(4, 3, 1)] == 1 assert (4, 2, 1) in executed and executed[(4, 2, 1)] == 1 assert (3, 2, 1) in executed and executed[(3, 2, 1)] == 1
class Test(KnowledgeEngine): @Rule( FORALL(Fact(key_a="k" << W()), Fact(key_b="k" << W()), Fact(key_c="k" << W()))) def any_fact_once(self): nonlocal executed executed += 1
def test_ANDNOT_reactivation(): from experta import KnowledgeEngine, Fact, Rule, NOT, W class KE(KnowledgeEngine): @Rule(Fact(x='x' << W()), NOT(Fact(y='x' << W()))) def r1(self): pass ke = KE() ke.reset() assert not ke.agenda.activations f1 = ke.declare(Fact(x=1)) assert ke.agenda.activations ke.retract(f1) assert not ke.agenda.activations f2 = ke.declare(Fact(y=1)) assert not ke.agenda.activations f3 = ke.declare(Fact(x=1)) assert not ke.agenda.activations f4 = ke.declare(Fact(x=2)) assert ke.agenda.activations ke.retract(f4) assert not ke.agenda.activations
def test_nested_values_dict_and_lists(): from experta import KnowledgeEngine, Fact, Rule class KE(KnowledgeEngine): @Rule(Fact(key__0__with__nested__1__dicts=1)) def r1(self): pass ke = KE() ke.reset() assert len(ke.agenda.activations) == 0 p0 = ke.declare( Fact(key={"with": { "nested": [{ "other": 1 }, { "dicts": 1 }] }})) assert len(ke.agenda.activations) == 0 p1 = ke.declare( Fact(key=[{ "with": { "nested": [{ "other": 1 }, { "dicts": 1 }] } }])) assert len(ke.agenda.activations) == 1
def test_KnowledgeEngine_reset(): """ Given a set of fixed facts, they're still there after a reset. Also, we have in account that InitialFact() is always there. And that if we add a normal fact after that, it's not persistent """ from experta.engine import KnowledgeEngine from experta.deffacts import DefFacts from experta import Fact class KE1(KnowledgeEngine): @DefFacts() def some_facts(self): yield Fact(foo=1) ke = KE1() ke.declare(Fact(bar=9)) ke.declare(Fact(bar=7)) ke.declare(Fact(bar=8)) ke.reset() assert len(ke.facts) == 2 # Initialfact + Fact(foo=1) ke = KE1() ke.reset() ke.declare(Fact(foo=2)) ke.declare(Fact(foo=3, bar=4)) assert len(ke.facts) == 4
def test_prepare_rule__or_starting_with_not(): from experta import Rule, InitialFact, NOT, Fact, OR, AND rule = Rule(OR(NOT(Fact(1)), NOT(Fact(2))))(lambda: None) assert list(utils.prepare_rule(rule)) == [ OR(AND(InitialFact(), NOT(Fact(1))), AND(InitialFact(), NOT(Fact(2)))) ]
def test_or_inside_or_inside_fact(): from experta import Fact, OR, Rule, L from experta.matchers.rete.dnf import dnf input_ = Rule(Fact(a=L(1) | (L(2) | L(3)))) output = Rule(OR(Fact(a=L(1)), Fact(a=L(2)), Fact(a=L(3)))) result = dnf(input_) assert result == output
def test_not_and_inside_fact(): from experta import Fact, OR, Rule, L from experta.matchers.rete.dnf import dnf input_ = Rule(Fact(a=~(L(1) & L(2)))) output = Rule(OR(Fact(a=~L(1)), Fact(a=~L(2)))) result = dnf(input_) assert result == output
def test_Rule_is_iterable(): from experta import Rule from experta import Fact rule_ = iter(Rule(Fact(a=1), Fact(a=2))) assert next(rule_) == Fact(a=1) assert next(rule_) == Fact(a=2) with pytest.raises(StopIteration): assert next(rule_)
def test_or_inside_fact(): from experta import Fact, OR, Rule, L from experta.matchers.rete.dnf import dnf input_ = Rule(Fact(a=L(1) | L(2), b=3)) output = Rule(OR(Fact(a=L(1), b=3), Fact(a=L(2), b=3))) result = dnf(input_) assert set(result[0]) == set(output[0])
def test_double_not_inside_fact(): from experta import Fact, OR, Rule, L from experta.matchers.rete.dnf import dnf input_ = Rule(Fact(a=~~L(1))) output = Rule(Fact(a=L(1))) result = dnf(input_) assert result == output
class KE(KnowledgeEngine): @Rule(Fact(s=True), Fact('A')) def second(self): executed.append(second) @Rule(AS.f << Fact(s=False), Fact('A')) def first(self, f): executed.append(first) self.modify(f, s=True)
class Test(KnowledgeEngine): @DefFacts() def test_deffacts(self): yield Fact(a=1) @Rule(~Fact(b=2) & Fact(a=1)) def test_fact_after_not(self): nonlocal executions executions += 1
def test_not_or_inside_fact(): from experta import Fact, OR, Rule, L from experta.fieldconstraint import ANDFC, NOTFC from experta.matchers.rete.dnf import dnf input_ = Rule(Fact(a=~(L(1) | L(2)))) output = Rule(Fact(a=ANDFC(NOTFC(L(1)), NOTFC(L(2))))) result = dnf(input_) assert result == output
def test_or_inside_or_inside_and(): from experta import Fact, OR, AND, Rule from experta.matchers.rete.dnf import dnf input_ = Rule(AND(Fact(b=1), OR(Fact(a=1), OR(Fact(a=3), Fact(a=4))))) output = Rule( OR(AND(Fact(b=1), Fact(a=1)), AND(Fact(b=1), Fact(a=3)), AND(Fact(b=1), Fact(a=4)))) result = dnf(input_) assert result == output
def test_and_inside_or_inside_fact(): from experta import Fact, OR, Rule, L from experta.fieldconstraint import ANDFC from experta.matchers.rete.dnf import dnf input_ = Rule(Fact(a=L(1) | (L(2) & L(3)))) output = Rule(OR(Fact(a=L(1)), Fact(a=ANDFC(L(2), L(3))))) result = dnf(input_) assert result == output
def test_featurecheck_call_not_predicate(): from experta.matchers.rete.check import FeatureCheck from experta import P, Fact # Positional matching (negated) check = FeatureCheck(0, ~P(lambda _: True)) assert not check(Fact('somedata')) # Positional not matching (negated) check = FeatureCheck(0, ~P(lambda _: False)) assert check(Fact('somedata'))
def test_featurecheck_call_not_wildcard(): from experta.matchers.rete.check import FeatureCheck from experta import W, Fact # Positional match (negated) check = FeatureCheck(0, ~W()) assert not check(Fact('somedata')) # Positional match (negated) with binding check = FeatureCheck(0, ~W('X')) assert check(Fact('somedata')) == {(False, 'X'): 'somedata'}
class Test(KnowledgeEngine): """ Test KE """ @DefFacts() def some_facts(self): yield Fact(other=1) yield Fact(other=2) @Rule(OR(Fact(something=1), Fact(something=2))) def rule1(self): """ First rule, something=1 and something=2""" pass
def test_featurecheck_call_not_literal(): from experta.matchers.rete.check import FeatureCheck from experta import L, Fact # Positional matching (negated) check = FeatureCheck(0, ~L('somedata')) assert not check(Fact('somedata')) # Positional not matching (negated) check = FeatureCheck(0, ~L('somedata')) assert check(Fact('otherdata'))
class RandomFracEngine(KnowledgeEngine): @Rule(Fact(id=MATCH.id, contentEditable=True, value=W())) def input_random(self, id): return Sai(selection=id, action='UpdateTextArea', inputs={'value': str(randint(0, 100))}) @Rule(Fact(id='done')) def click_done(self): return Sai(selection='done', action='ButtonPressed', inputs={'value': -1})
def test_retraction(): engine = RetractionEngine() engine.reset() a = Fact('A_parent') b = Fact('B_parent') engine.declare(a) engine.declare(b) engine.run(5) assert len(engine.facts) == 8 engine.retract(b) assert len(engine.facts) == 3
def test_and_inside_or(): from experta import Fact, Rule, AND, OR from experta.matchers.rete.dnf import dnf input_ = Rule(OR(AND(Fact(a=1), Fact(a=2)), AND(Fact(a=3), Fact(a=4)))) output = Rule(OR(AND(Fact(a=1), Fact(a=2)), AND(Fact(a=3), Fact(a=4)))) assert dnf(input_[0]) == output[0]
def test_featurecheck_call_literal(): from experta.matchers.rete.check import FeatureCheck from experta import L, Fact # Positional field not present check = FeatureCheck(0, L('mydata')) assert not check(Fact()) # Positional field present, matching and not matching check = FeatureCheck(0, L('mydata')) assert check(Fact('mydata')) check = FeatureCheck(0, L('otherdata')) assert not check(Fact('mydata')) # Named field present, matching and not matching check = FeatureCheck('mykey', L('mydata')) assert check(Fact(mykey='mydata')) check = FeatureCheck('mykey', L('mydata')) assert not check(Fact(mykey='myotherdata')) # Named field not present check = FeatureCheck('mykey', L('mydata')) assert not check(Fact(otherkey='mydata')) # Literal with binding, matching and not matching check = FeatureCheck('mykey', L('mydata', __bind__='D')) assert check(Fact(mykey='mydata')) == {'D': 'mydata'} check = FeatureCheck('mykey', L('mydata', __bind__='D')) assert check(Fact(mykey='otherdata')) is False
class ttt_engine(KnowledgeEngine): # @DefFacts() # def init_board(self, x=3, players=['X', 'O']): # return # yield CurrentPlayer(name=players[0]) # for row in range(3): # for col in range(3): # yield Square(row=row, col=col, player='') # @Rule( # AS.square1 << Square(row=MATCH.row, col=MATCH.square1col), # AS.square2 << Square(row=MATCH.row, col=MATCH.square2col), # TEST(lambda square1col, square2col: square2col == square1col + 1), # ) # def horizontally_adj(self, row, square1col, square2col): # relation = Fact( # relation="horizontally_adjacent", # row=row, # square1col=square1col, # square2col=square2col, # ) # self.declare(relation) # ... other relations # @Rule( # Fact(type="CurrentPlayer", player=MATCH.player), # AS.square << Fact(type="Square", row=MATCH.row, col=MATCH.col, # player=""), # NOT(Fact(type="PossibleMove", row=MATCH.row, col=MATCH.col, # player=MATCH.player)) # ) # def suggest_move(self, row, col, player): # self.declare(Fact(type="PossibleMove", row=row, col=col, # player=player)) # @Rule( # Fact(type="CurrentPlayer", player=MATCH.player), # AS.square # << Fact(type="PossibleMove", row=MATCH.row, col=MATCH.col, # player=MATCH.player), # ) # def make_move(self, row, col, player): # return Sai(None, "move", {"row": row, "col": col, "player": player}) @Rule(Fact(type='CurrentPlayer', player=MATCH.player), Fact(type='Square', row=MATCH.row, col=MATCH.col, player="")) def make_move(self, row, col, player): print("moving", row, col, player) return Sai(None, 'move', {'row': row, 'col': col, 'player': player})
class Test(KnowledgeEngine): @DefFacts() def tested_deffacts(self): for i in to_declare: yield Fact(something=i) @Rule(Fact(something=L(1)), Fact(something=L(2))) def rule1(self): nonlocal executions executions.append('rule1') @Rule(Fact(something=L(3))) def rule2(self): nonlocal executions executions.append('rule2')
def test_initial_not_vs_and_not(): from experta import KnowledgeEngine, Rule, Fact, DefFacts executions = 0 class Test(KnowledgeEngine): @DefFacts() def test_deffacts(self): yield Fact(a=1) @Rule(Fact(a=1) & ~Fact(b=2)) def test_fact_before_not(self): nonlocal executions executions += 1 t = Test() t.reset() t.run() assert executions == 1 t.retract(1) t.declare(Fact(a=1)) t.run() assert executions == 2 executions = 0 class Test(KnowledgeEngine): @DefFacts() def test_deffacts(self): yield Fact(a=1) @Rule(~Fact(b=2) & Fact(a=1)) def test_fact_after_not(self): nonlocal executions executions += 1 t = Test() t.reset() t.run() assert executions == 1 t.retract(1) t.declare(Fact(a=1)) t.run() assert executions == 2
def test_factlist_changes(): """ Test factlist changes """ from experta.factlist import FactList from experta import Fact flist = FactList() f0 = flist.declare(Fact(a=1)) assert flist.changes[0] == [f0] f1 = flist.declare(Fact(b=1)) assert flist.changes[0] == [f1] flist.retract(f1) assert flist.changes[1] == [f1]
class TestRule(KnowledgeEngine): @Rule(Fact(MATCH.value1, MATCH.value2)) def myrule(self, value1, **kws): assert value1 == "SOMETHING1" assert kws == {"value2": "SOMETHING2"} nonlocal called called = True