Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Rules called on already retracted facts #41

Closed
nilp0inter opened this issue Nov 13, 2018 · 5 comments
Closed

Rules called on already retracted facts #41

nilp0inter opened this issue Nov 13, 2018 · 5 comments

Comments

@nilp0inter
Copy link
Contributor

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.

@ricardobur
Copy link

ricardobur commented Nov 20, 2018

Hi @nilp0inter,

I am using pyknow library to build some logic based on logic and facts. I have faced the same behavior as you explain in this issue. In fact, I had the same error as it seems in one point (not exactly when) the activations in the agenda have a factid that has being retracted (so it does not exists any more).

I have created on a fork the behavior that I would need, but I suppose the problem should be solved by ensuring the activation facts has the proper factid that stays in the facts list of the engine, no?

Thanks!

@nilp0inter
Copy link
Contributor Author

nilp0inter commented Nov 20, 2018

This is maybe a regression after the last optimizations made this year. A couple of regression tests and a git bisect could help here.

@lucasmpaim
Copy link

+1 any updates on this?

@lucasmpaim
Copy link

for my case this decorator solves my problem:

def safe_access_fact(attr, fact_type):
    def func_receiver(func):
        def inner(*args, **kwargs):
            database = args[0]
            for fact in database.facts.items():
                if isinstance(fact[1], fact_type):
                    return func(*args,
                                **kwargs,
                                **{attr: fact[1]})
            return func(*args, **kwargs)
        return inner
    return func_receiver

And the use:

    @Rule(
        EXISTS(SystemPreCheck()),
        Common(current_state=QuestionsBlock.SYSTEM_PRE_CHECK),
        ~SystemPreCheck(have_error_within_time_limit=W()))
    @safe_access_fact('system', SystemPreCheck)
    def have_error_within_time_limit(self, system):
        read = auto_read([True, False])
        print(f'Existe defeito atribuído dentro do prazo?: {"y" if read else "n"}')
        self.modify(system, have_error_within_time_limit=read)

if I use the AS.* << operator, I receive a retracted fact, causing the error

@nilp0inter
Copy link
Contributor Author

Already fixed in Experta

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants