U
    9%eZL                     @   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/Darija-Ai-API/env/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}|rd}|D ]\}t|tstdt|j | D ]T\}\}}||hB }	|	kr |	r| |	}
|
dk	r||
d O }d}qqhq\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rt|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|  qnt |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 qt|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  qd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*| }|rt }|D ]R\}} ||r8|dkrVq8|||f D ]\}}	 ||	 qb|	|||f  q8g }|D ]0}
||
 \}}t
 fdd|D r|| qq*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&{ :