o
    FZhZL                     @   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 )zdReturn the literal fact of an atom.

    Effectively, this merely strips the Not around a fact.
    
isinstancer   argZatom r   ?/var/www/auris/lib/python3.10/site-packages/sympy/core/facts.py
_base_fact7   s   
r   c                 C   s   t | tr
| jdfS | dfS )NFTr	   r   r   r   r   _as_pairB   s   

r   c                 C   sb   t | }t  jtt | }|D ]}|D ]}||f|v r-|D ]}||f|v 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rq|| | q| D ]\}}|| t|}||v rBtd|||f q(|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                    sj  i }|   D ]}t| | g f||< q|D ]\}|jD ]}||v r#qt g f||< qqd}|rxd}|D ]A\}t|tsAtdt|j | D ]*\}\}}||hB }	|	vrt |	rt| |	}
|
durr||
d O }d}qJq4|s0t
|D ]6\}\}t|j | D ]&\}\}}||hB }	|	v rqt fdd|	D rq |	@ r|| qq||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 | v 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   sP   








r9   c                 C   sf   t t}|  D ](\\}}}t|tr|jd }|D ]\}}t|tr(|jd }|| | qq|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 ]\}}t|tr|||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      |   d S )Nr   rK   rF   r   r   r   rI   -     zProver.rules_alphac                 C   rL   )Nr   rM   rF   r   r   r   rJ   1  rN   zProver.rules_betac                 C   sj   |rt |tr	dS t |trdS ||f| jv rdS | j||f z	| || W dS  ty4   Y dS w )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rt|jtd}|D ]}| || qd S t |trnt|jtd}t |ts4||v r4t||d| tdd |jD  t	| t
t|D ]!}|| }|d | ||d d   }| t|t	|t|  qJd S t |trt|jtd}||v rt||d| j||f d S t |trt|jtd}||v rt||d|D ]}| || qd S | 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   [      z(Prover._process_rule.<locals>.<listcomp>r   z
a & b -> az
a | b -> a)r
   r   sortedr.   strrQ   r   r   r>   r   rangelenrD   r4   )	rG   r#   r$   Zsorted_bargsrS   r8   ZbrestZsorted_aargsZaargr   r   r   rP   F  s<   


 
	
zProver._process_ruleN)r?   r@   rA   rB   rH   rK   propertyrI   rJ   rQ   rP   r   r   r   r   rC     s    

rC   c                   @   sj   e Zd ZdZdd ZdefddZedefdd	Z	d
d Z
dd Zdd Zdd Zdee f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   sz  t |tr	| }t }|D ]6}|dd\}}}t|}t|}|dkr.||| q|dkr?||| ||| qtd| g | _	|j
D ]\}}| j	dd |jD t|f qKt|j}	t|	|j
}
dd |
 D | _tt}tt}|
 D ]\}\}}d	d |D |t|< ||t|< q|| _|| _tt}t|}| D ]\}}||  |O  < q|| _dS )
z)Compile rules into internal lookup tablesN   z->z==zunknown op %rc                 S      h | ]}t |qS r   r   )r   r#   r   r   r   	<setcomp>  rT   z%FactRules.__init__.<locals>.<setcomp>c                 S   r\   r   )r   )r   r   r   r   r   r^     rT   c                 S   r\   r   r]   r   r   r   r   r   r^     rT   )r
   rV   
splitlinesrC   splitr   Z
fromstringrQ   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   rc   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   rc   r;   r5   rb   )r   r   updatesetattrr5   rb   )clsrl   rG   rR   dr   r   r   _from_python  s   
zFactRules._from_pythonc                 c   s0    dV  t | jD ]	}d|dV  q	dV  d S )Nzdefined_facts = [    ,z] # defined_facts)rU   rb   )rG   factr   r   r   _defined_facts_lines  s
   
zFactRules._defined_facts_linesc                 c   s    dV  t | jD ]6}dD ]1}d| d| dV  d|d|dV  | j||f }t |D ]	}d	|d
V  q.dV  dV  qq	dV  d S )Nzfull_implications = dict( [)TFz    # Implications of  = :z    ((, z	), set( (        rt   z       ) ),z     ),z ] ) # full_implications)rU   rb   r   )rG   ru   valuer   impliedr   r   r   _full_implications_lines  s   
z"FactRules._full_implications_linesc                 c   sp    dV  dV  t | jD ]&}d| V  d|dV  t | j| D ]	}d|dV  q"dV  dV  qd	V  d S )
Nz
prereq = {rm   z.    # facts that could determine the value of rs   z: {rz   rt   z    },z
} # prereq)rU   r;   )rG   ru   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 ];}|\}}d| d| V  || D ]$\}}| |< |d7 }dttt|}d	| d
V  d|dV  q>dV  q+dV  dV  t| j	D ]}	|	\}} fdd| j	|	 D }
d|	d|
dV  qrdV  d S )Nz@# Note: the order of the beta rules is used in the beta_triggerszbeta_rules = [rm   r   z    # Rules implying rw   r   ry   z    ({z},rz   z),z] # beta_ruleszbeta_triggers = {c                    s   g | ]} | qS r   r   )r   nindicesr   r   r     rT   z/FactRules._beta_rules_lines.<locals>.<listcomp>rs   z: rt   z} # beta_triggers)
r   listr2   r5   r4   rU   ri   r   rV   rc   )rG   Zreverse_implicationsr   prer|   mru   r{   Zsetstrquerytriggersr   r   r   _beta_rules_lines  s4   
zFactRules._beta_rules_linesc                 c   sz    |   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 Nrm   z`generated_assumptions = {'defined_facts': defined_facts, 'full_implications': full_implications,zZ               'prereq': prereq, 'beta_rules': beta_rules, 'beta_triggers': beta_triggers})rv   r}   r~   r   rF   r   r   r   rj   #  s   
zFactRules.print_rulesN)r?   r@   rA   rB   rH   rV   rk   classmethoddictrr   rv   r}   r~   r   r   rj   r   r   r   r   rZ   |  s    ;rZ   c                   @   s   e Zd Zdd ZdS )InconsistentAssumptionsc                 C   s   | j \}}}d|||f S )Nz	%s, %s=%s)r.   )rG   kbru   r{   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  rT   z"FactKB.__str__.<locals>.<listcomp>)ri   rU   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   s<   || v r| | dur| | |krdS t | |||| |< 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| }|rgt }|D ])\}} ||r*|du r+q|||f D ]
\}}	 ||	 q1|	|||f  qg }|D ]}
||
 \}}t
 fdd|D rb|| qJ|sdS 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"    | ]\}}  ||u V  qd S r(   )r1   )r   r   r   rF   r   r   r,   y  s     z*FactKB.deduce_all_facts.<locals>.<genexpr>)r:   r   rc   r5   r
   r   r   r   r   rn   allr4   )rG   Zfactsr   rc   r5   Zbeta_maytriggerr   r   rR   r{   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   rZ   r!   r   r   r   r   r   r   r   <module>   s     0	(O&{ :