U
    ZÊ¹jZL  ã                   @   s´   d Z ddlmZ ddlmZ ddlmZmZmZm	Z	 dd„ Z
dd	„ Zd
d„ Zdd„ Zdd„ Zdd„ ZG dd„ deƒZG dd„ dƒZG dd„ dƒZG dd„ deƒZG dd„ deƒZdS )a>  This is rule-based deduction system for SymPy

The whole thing is split into two parts

 - rules compilation and preparation of tables
 - runtime inference

For rule-based inference engines, the classical work is RETE algorithm [1],
[2] Although we are not implementing it in full (or even significantly)
it's still worth a read to understand the underlying ideas.

In short, every rule in a system of rules is one of two forms:

 - atom                     -> ...      (alpha rule)
 - And(atom1, atom2, ...)   -> ...      (beta rule)


The major complexity is in efficient beta-rules processing and usually for an
expert system a lot of effort goes into code that operates on beta-rules.


Here we take minimalistic approach to get something usable first.

 - (preparation)    of alpha- and beta- networks, everything except
 - (runtime)        FactRules.deduce_all_facts

             _____________________________________
            ( Kirr: I've never thought that doing )
            ( logic stuff is that difficult...    )
             -------------------------------------
                    o   ^__^
                     o  (oo)\_______
                        (__)\       )\/\
                            ||----w |
                            ||     ||


Some references on the topic
----------------------------

[1] https://en.wikipedia.org/wiki/Rete_algorithm
[2] http://reports-archive.adm.cs.cmu.edu/anon/1995/CMU-CS-95-113.pdf

https://en.wikipedia.org/wiki/Propositional_formula
https://en.wikipedia.org/wiki/Inference_rule
https://en.wikipedia.org/wiki/List_of_rules_of_inference
é    )Údefaultdict)ÚIteratoré   )ÚLogicÚAndÚOrÚNotc                 C   s   t | tƒr| jS | S dS )zdReturn the literal fact of an atom.

    Effectively, this merely strips the Not around a fact.
    N©Ú
isinstancer   Úarg©Zatom© r   úO/var/www/html/bwave/python/venv/lib/python3.8/site-packages/sympy/core/facts.pyÚ
_base_fact7   s    
r   c                 C   s    t | tƒr| jdfS | dfS d S )NFTr	   r   r   r   r   Ú_as_pairB   s    

r   c                 C   sb   t | ƒ}t ƒ jtt |ƒŽ }|D ]>}|D ]4}||f|kr&|D ]}||f|kr:| ||f¡ q:q&q|S )z½
    Computes the transitive closure of a list of implications

    Uses Warshall's algorithm, as described at
    http://www.cs.hope.edu/~cusack/Notes/Notes/DiscreteMath/Warshall.pdf.
    )ÚsetÚunionÚmapÚadd)ÚimplicationsÚfull_implicationsÚliteralsÚkÚiÚjr   r   r   Útransitive_closureK   s    r   c                 C   sŠ   | dd„ | D ƒ } t tƒ}t| ƒ}|D ] \}}||kr8q&||  |¡ q&| ¡ D ]4\}}| |¡ t|ƒ}||krPtd|||f ƒ‚qP|S )a:  deduce all implications

       Description by example
       ----------------------

       given set of logic rules:

         a -> b
         b -> c

       we deduce all possible rules:

         a -> b, c
         b -> c


       implications: [] of (a,b)
       return:       {} of a -> set([b, c, ...])
    c                 S   s    g | ]\}}t |ƒt |ƒf‘qS r   ©r   )Ú.0r   r   r   r   r   Ú
<listcomp>s   s     z-deduce_alpha_implications.<locals>.<listcomp>z*implications are inconsistent: %s -> %s %s)r   r   r   r   ÚitemsÚdiscardr   Ú
ValueError)r   Úresr   ÚaÚbÚimplÚnar   r   r   Údeduce_alpha_implications_   s    
ÿr'   c                    st  i }|   ¡ D ]}t| | ƒg f||< q|D ],\}‰|jD ]}||krFq8tƒ g f||< q8q*d}|rîd}|D ]‚\}‰t|tƒs‚tdƒ‚t|jƒ‰ | ¡ D ]T\}\}}||hB }	ˆ|	kr”ˆ  |	¡r”| ˆ¡ | 	ˆ¡}
|
dk	rä||
d O }d}q”qhq\t
|ƒD ]x\}\}‰t|jƒ‰ | ¡ D ]X\}\}}||hB }	ˆ|	k�r8�qt‡ ‡fdd„|	D ƒƒ�rV�qˆ |	@ �r| |¡ �qqö|S )a¶  apply additional beta-rules (And conditions) to already-built
    alpha implication tables

       TODO: write about

       - static extension of alpha-chains
       - attaching refs to beta-nodes to alpha chains


       e.g.

       alpha_implications:

       a  ->  [b, !c, d]
       b  ->  [d]
       ...


       beta_rules:

       &(b,d) -> e


       then we'll extend a's rule to the following

       a  ->  [b, !c, d, e]
    TFzCond is not AndNr   c                 3   s&   | ]}t |ƒˆ kpt |ƒˆkV  qd S ©Nr   )r   Úxi©ZbargsÚbimplr   r   Ú	<genexpr>Í   s     z,apply_beta_to_alpha_route.<locals>.<genexpr>)Úkeysr   Úargsr
   r   Ú	TypeErrorr   Úissubsetr   ÚgetÚ	enumerateÚanyÚappend)Zalpha_implicationsÚ
beta_rulesZx_implÚxÚbcondZbkZseen_static_extensionZximplsZbbZx_allZ
bimpl_implÚbidxr   r*   r   Úapply_beta_to_alpha_route‡   sD    










r9   c                 C   sf   t tƒ}|  ¡ D ]P\\}}}t|tƒr0|jd }|D ]*\}}t|tƒrP|jd }||  |¡ q4q|S )aM  build prerequisites table from rules

       Description by example
       ----------------------

       given set of logic rules:

         a -> b, c
         b -> c

       we build prerequisites (from what points something can be deduced):

         b <- a
         c <- a, b

       rules:   {} of a -> [b, c, ...]
       return:  {} of c <- [a, b, ...]

       Note however, that this prerequisites may be *not* enough to prove a
       fact. An example is 'a -> b' rule, where prereq(a) is b, and prereq(b)
       is a. That's because a=T -> b=T, and b=F -> a=F, but a=F -> b=?
    r   )r   r   r   r
   r   r.   r   )ÚrulesÚprereqr#   Ú_r%   r   r   r   r   Úrules_2prereqÖ   s    



r=   c                   @   s   e Zd ZdZdS )ÚTautologyDetectedz:(internal) Prover uses it for reporting detected tautologyN)Ú__name__Ú
__module__Ú__qualname__Ú__doc__r   r   r   r   r>   ü   s   r>   c                   @   sH   e Zd ZdZdd„ Zdd„ Zedd„ ƒZedd	„ ƒZd
d„ Z	dd„ Z
dS )ÚProveraS  ai - prover of logic rules

       given a set of initial rules, Prover tries to prove all possible rules
       which follow from given premises.

       As a result proved_rules are always either in one of two forms: alpha or
       beta:

       Alpha rules
       -----------

       This are rules of the form::

         a -> b & c & d & ...


       Beta rules
       ----------

       This are rules of the form::

         &(a,b,...) -> c & d & ...


       i.e. beta rules are join conditions that say that something follows when
       *several* facts are true at the same time.
    c                 C   s   g | _ tƒ | _d S r(   )Úproved_rulesr   Ú_rules_seen©Úselfr   r   r   Ú__init__  s    zProver.__init__c                 C   sH   g }g }| j D ]0\}}t|tƒr0| ||f¡ q| ||f¡ q||fS )z-split proved rules into alpha and beta chains)rD   r
   r   r4   )rG   Úrules_alphaÚ
rules_betar#   r$   r   r   r   Úsplit_alpha_beta"  s    
zProver.split_alpha_betac                 C   s   |   ¡ d S )Nr   ©rK   rF   r   r   r   rI   -  s    zProver.rules_alphac                 C   s   |   ¡ d S )Nr   rL   rF   r   r   r   rJ   1  s    zProver.rules_betac                 C   sl   |rt |tƒrdS t |tƒr dS ||f| jkr2dS | j ||f¡ z|  ||¡ W n tk
rf   Y nX dS )zprocess a -> b ruleN)r
   ÚboolrE   r   Ú_process_ruler>   )rG   r#   r$   r   r   r   Úprocess_rule5  s    
zProver.process_rulec           	      C   s’  t |tƒr2t|jtd�}|D ]}|  ||¡ q�n\t |tƒrÚt|jtd�}t |tƒsh||krht||dƒ‚|  tdd„ |jD ƒŽ t	|ƒ¡ t
t|ƒƒD ]B}|| }|d |… ||d d …  }|  t|t	|ƒƒt|Ž ¡ q”n´t |tƒ�rt|jtd�}||k�r
t||dƒ‚| j ||f¡ nrt |tƒ�rft|jtd�}||k�rLt||dƒ‚|D ]}|  ||¡ �qPn(| j ||f¡ | j t	|ƒt	|ƒf¡ d S )N)Úkeyza -> a|c|...c                 S   s   g | ]}t |ƒ‘qS r   r   )r   Úbargr   r   r   r   [  s     z(Prover._process_rule.<locals>.<listcomp>r   z
a & b -> az
a | b -> a)r
   r   Úsortedr.   ÚstrrO   r   r   r>   r   ÚrangeÚlenrD   r4   )	rG   r#   r$   Zsorted_bargsrQ   r8   ZbrestZsorted_aargsZaargr   r   r   rN   F  s6    


 

zProver._process_ruleN)r?   r@   rA   rB   rH   rK   ÚpropertyrI   rJ   rO   rN   r   r   r   r   rC     s   

rC   c                   @   sj   e Zd ZdZdd„ Zedœdd„Zeedœdd	„ƒZ	d
d„ Z
dd„ Zdd„ Zdd„ Zee dœdd„ZdS )Ú	FactRulesa•  Rules that describe how to deduce facts in logic space

       When defined, these rules allow implications to quickly be determined
       for a set of facts. For this precomputed deduction tables are used.
       see `deduce_all_facts`   (forward-chaining)

       Also it is possible to gather prerequisites for a fact, which is tried
       to be proven.    (backward-chaining)


       Definition Syntax
       -----------------

       a -> b       -- a=T -> b=T  (and automatically b=F -> a=F)
       a -> !b      -- a=T -> b=F
       a == b       -- a -> b & b -> a
       a -> b & c   -- a=T -> b=T & c=T
       # TODO b | c


       Internals
       ---------

       .full_implications[k, v]: all the implications of fact k=v
       .beta_triggers[k, v]: beta rules that might be triggered when k=v
       .prereq  -- {} k <- [] of k's prerequisites

       .defined_facts -- set of defined fact names
    c                 C   s€  t |tƒr| ¡ }tƒ }|D ]n}| dd¡\}}}t |¡}t |¡}|dkr\| ||¡ q|dkr~| ||¡ | ||¡ qtd| ƒ‚qg | _	|j
D ](\}}| j	 dd„ |jD ƒt|ƒf¡ q˜t|jƒ}	t|	|j
ƒ}
dd„ |
 ¡ D ƒ| _ttƒ}ttƒ}|
 ¡ D ]0\}\}}d	d„ |D ƒ|t|ƒ< ||t|ƒ< �q|| _|| _ttƒ}t|ƒ}| ¡ D ]\}}||  |O  < �qZ|| _dS )
z)Compile rules into internal lookup tablesNé   z->z==zunknown op %rc                 S   s   h | ]}t |ƒ’qS r   ©r   )r   r#   r   r   r   Ú	<setcomp>·  s     z%FactRules.__init__.<locals>.<setcomp>c                 S   s   h | ]}t |ƒ’qS r   )r   )r   r   r   r   r   rZ   Ã  s     c                 S   s   h | ]}t |ƒ’qS r   rY   ©r   r   r   r   r   rZ   É  s     )r
   rS   Ú
splitlinesrC   Úsplitr   Ú
fromstringrO   r!   r5   rJ   r4   r.   r   r'   rI   r9   r-   Údefined_factsr   r   r   r   Úbeta_triggersr=   r;   )rG   r:   ÚPÚruler#   Úopr$   r7   r+   Zimpl_aZimpl_abr   r`   r   r%   Zbetaidxsr;   Z
rel_prereqZpitemsr   r   r   rH   ›  sB    


ÿ
zFactRules.__init__)Úreturnc                 C   s   d  |  ¡ ¡S )zD Generate a string with plain python representation of the instance Ú
)ÚjoinÚprint_rulesrF   r   r   r   Ú
_to_pythonÖ  s    zFactRules._to_python)Údatac                 C   sP   | dƒ}dD ]&}t tƒ}| || ¡ t|||ƒ q|d |_t|d ƒ|_|S )z; Generate an instance from the plain python representation Ú )r   r`   r;   r5   r_   )r   r   ÚupdateÚsetattrr5   r_   )Úclsri   rG   rP   Údr   r   r   Ú_from_pythonÚ  s    
zFactRules._from_pythonc                 c   s.   dV  t | jƒD ]}d|›d�V  qdV  d S )Nzdefined_facts = [ú    ú,z] # defined_facts)rR   r_   )rG   Úfactr   r   r   Ú_defined_facts_linesç  s    zFactRules._defined_facts_linesc                 c   sˆ   dV  t | jƒD ]l}dD ]b}d|› d|› d�V  d|›d|›d�V  | j||f }t |ƒD ]}d	|›d
�V  qZdV  dV  qqdV  d S )Nzfull_implications = dict( [)TFz    # Implications of ú = ú:z    ((ú, z	), set( (ú        rq   z       ) ),z     ),z ] ) # full_implications)rR   r_   r   )rG   rr   Úvaluer   Úimpliedr   r   r   Ú_full_implications_linesí  s    
z"FactRules._full_implications_linesc                 c   sn   dV  dV  t | jƒD ]L}d|› �V  d|›d�V  t | j| ƒD ]}d|›d�V  qBdV  dV  qd	V  d S )
Nz
prereq = {rj   z.    # facts that could determine the value of rp   z: {rw   rq   z    },z
} # prereq)rR   r;   )rG   rr   Zpfactr   r   r   Ú_prereq_linesú  s    zFactRules._prereq_linesc                 #   s&  t tƒ}t| jƒD ]\}\}}||  ||f¡ qdV  dV  dV  d}i ‰ t|ƒD ]v}|\}}d|› d|› �V  || D ]H\}}|ˆ |< |d7 }d ttt|ƒƒ¡}d	|› d
�V  d|›d�V  qzdV  qTdV  dV  t| j	ƒD ]8}	|	\}}‡ fdd„| j	|	 D ƒ}
d|	›d|
›d�V  qâdV  d S )Nz@# Note: the order of the beta rules is used in the beta_triggerszbeta_rules = [rj   r   z    # Rules implying rt   r   rv   z    ({z},rw   z),z] # beta_ruleszbeta_triggers = {c                    s   g | ]}ˆ | ‘qS r   r   )r   Ún©Úindicesr   r   r     s     z/FactRules._beta_rules_lines.<locals>.<listcomp>rp   z: rq   z} # beta_triggers)
r   Úlistr2   r5   r4   rR   rf   r   rS   r`   )rG   Zreverse_implicationsr|   Úprery   Úmrr   rx   ZsetstrÚqueryÚtriggersr   r}   r   Ú_beta_rules_lines  s2    zFactRules._beta_rules_linesc                 c   sx   |   ¡ E dH  dV  dV  |  ¡ E dH  dV  dV  |  ¡ E dH  dV  dV  |  ¡ E dH  dV  dV  dV  dV  dS )zA Returns a generator with lines to represent the facts and rules Nrj   z`generated_assumptions = {'defined_facts': defined_facts, 'full_implications': full_implications,zZ               'prereq': prereq, 'beta_rules': beta_rules, 'beta_triggers': beta_triggers})rs   rz   r{   r„   rF   r   r   r   rg   #  s    zFactRules.print_rulesN)r?   r@   rA   rB   rH   rS   rh   ÚclassmethodÚdictro   rs   rz   r{   r„   r   rg   r   r   r   r   rW   |  s   ;rW   c                   @   s   e Zd Zdd„ ZdS )ÚInconsistentAssumptionsc                 C   s   | j \}}}d|||f S )Nz	%s, %s=%s)r.   )rG   Úkbrr   rx   r   r   r   Ú__str__6  s    zInconsistentAssumptions.__str__N)r?   r@   rA   r‰   r   r   r   r   r‡   5  s   r‡   c                   @   s0   e Zd ZdZdd„ Zdd„ Zdd„ Zdd	„ Zd
S )ÚFactKBzT
    A simple propositional knowledge base relying on compiled inference rules.
    c                 C   s    dd  dd„ t|  ¡ ƒD ƒ¡ S )Nz{
%s}z,
c                 S   s   g | ]}d | ‘qS )z	%s: %sr   r[   r   r   r   r   A  s     z"FactKB.__str__.<locals>.<listcomp>)rf   rR   r   rF   r   r   r   r‰   ?  s    ÿzFactKB.__str__c                 C   s
   || _ d S r(   )r:   )rG   r:   r   r   r   rH   C  s    zFactKB.__init__c                 C   sB   || kr2| | dk	r2| | |kr$dS t | ||ƒ‚n|| |< dS dS )zxAdd fact k=v to the knowledge base.

        Returns True if the KB has actually been updated, False otherwise.
        NFT)r‡   )rG   r   Úvr   r   r   Ú_tellF  s    zFactKB._tellc                    sÌ   ˆ j j}ˆ j j}ˆ j j}t|tƒr*| ¡ }|rÈtƒ }|D ]R\}}ˆ  ||¡r8|dkrVq8|||f D ]\}}	ˆ  ||	¡ qb| 	|||f ¡ q8g }|D ]0}
||
 \}}t
‡ fdd„|D ƒƒr”| |¡ q”q*dS )z©
        Update the KB with all the implications of a list of facts.

        Facts can be specified as a dictionary or as a list of (key, value)
        pairs.
        Nc                 3   s    | ]\}}ˆ   |¡|kV  qd S r(   )r1   )r   r   r‹   rF   r   r   r,   y  s     z*FactKB.deduce_all_facts.<locals>.<genexpr>)r:   r   r`   r5   r
   r†   r   r   rŒ   rk   Úallr4   )rG   Zfactsr   r`   r5   Zbeta_maytriggerr   r‹   rP   rx   r8   r7   r+   r   rF   r   Údeduce_all_factsW  s$    	
zFactKB.deduce_all_factsN)r?   r@   rA   rB   r‰   rH   rŒ   rŽ   r   r   r   r   rŠ   ;  s
   rŠ   N)rB   Úcollectionsr   Útypingr   Zlogicr   r   r   r   r   r   r   r'   r9   r=   Ú	Exceptionr>   rC   rW   r!   r‡   r†   rŠ   r   r   r   r   Ú<module>   s   0	(O&{ :