o
    gZL                     @   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atom r   G/var/www/visachat/venv/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bargsbimplr   r   	<genexpr>   s   & z,apply_beta_to_alpha_route.<locals>.<genexpr>)keysr   argsr
   r   	TypeErrorr    issubsetr   get	enumerateanyappend)alpha_implications
beta_rulesx_implxbcondbkseen_static_extensionximplsbbx_all
bimpl_implbidxr   r+   r   apply_beta_to_alpha_route   sP   








rC   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   r0   r   )rulesprereqr$   _r&   r   r   r   r   rules_2prereq   s   



rG   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   rH      s    rH   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)rN   r
   r   r6   )rQ   rules_alpha
rules_betar$   r%   r   r   r   split_alpha_beta"  s   
zProver.split_alpha_betac                 C      |   d S )Nr   rU   rP   r   r   r   rS   -     zProver.rules_alphac                 C   rV   )Nr   rW   rP   r   r   r   rT   1  rX   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
   boolrO   r   _process_rulerH   )rQ   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   sortedr0   strr[   r   r   rH   r   rangelenrN   r6   )	rQ   r$   r%   sorted_bargsr]   rB   brestsorted_aargsaargr   r   r   rZ   F  s<   


 
	
zProver._process_ruleN)rI   rJ   rK   rL   rR   rU   propertyrS   rT   r[   rZ   r   r   r   r   rM     s    

rM   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>  r^   z%FactRules.__init__.<locals>.<setcomp>c                 S   rj   r   )r   )r   r   r   r   r   rl     r^   c                 S   rj   r   rk   r   r   r   r   r   rl     r^   )r
   r`   
splitlinesrM   splitr   
fromstringr[   r"   r8   rT   r6   r0   r   r(   rS   rC   r/   defined_factsr   r   r    r   beta_triggersrG   rE   )rQ   rD   Pruler$   opr%   r;   r-   impl_aimpl_abr   rr   r   r&   betaidxsrE   
rel_prereqpitemsr   r   r   rR     sB   




zFactRules.__init__returnc                 C   s   d |  S )zD Generate a string with plain python representation of the instance 
)joinprint_rulesrP   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   rr   rE   r8   rq   )r   r   updatesetattrr8   rq   )clsr   rQ   r\   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)r_   rq   )rQ   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( (        r   z       ) ),z     ),z ] ) # full_implications)r_   rq   r   )rQ   r   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 = {r   z.    # facts that could determine the value of r   z: {r   r   z    },z
} # prereq)r_   rE   )rQ   r   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 = [r   r   z    # Rules implying r   r   r   z    ({z},r   z),z] # beta_ruleszbeta_triggers = {c                    s   g | ]} | qS r   r   )r   nindicesr   r   r     r^   z/FactRules._beta_rules_lines.<locals>.<listcomp>r   z: r   z} # beta_triggers)
r   listr4   r8   r6   r_   r}   r   r`   rr   )rQ   reverse_implicationsr   prer   mr   r   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 Nr   z`generated_assumptions = {'defined_facts': defined_facts, 'full_implications': full_implications,zZ               'prereq': prereq, 'beta_rules': beta_rules, 'beta_triggers': beta_triggers})r   r   r   r   rP   r   r   r   r~   #  s   
zFactRules.print_rulesN)rI   rJ   rK   rL   rR   r`   r   classmethoddictr   r   r   r   r   r   r~   r   r   r   r   rh   |  s    ;rh   c                   @   s   e Zd Zdd ZdS )InconsistentAssumptionsc                 C   s   | j \}}}d|||f S )Nz	%s, %s=%s)r0   )rQ   kbr   r   r   r   r   __str__6  s   zInconsistentAssumptions.__str__N)rI   rJ   rK   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   rm   r   r   r   r   A  r^   z"FactKB.__str__.<locals>.<listcomp>)r}   r_   r    rP   r   r   r   r   ?  s   zFactKB.__str__c                 C   s
   || _ d S r)   )rD   )rQ   rD   r   r   r   rR   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   )rQ   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)   )r3   )r   r   r   rP   r   r   r.   y  s     z*FactKB.deduce_all_facts.<locals>.<genexpr>)rD   r   rr   r8   r
   r   r    r   r   r   allr6   )rQ   factsr   rr   r8   beta_maytriggerr   r   r\   r   rB   r;   r-   r   rP   r   deduce_all_factsW  s(   	

zFactKB.deduce_all_factsN)rI   rJ   rK   rL   r   rR   r   r   r   r   r   r   r   ;  s    r   N)rL   collectionsr   typingr   logicr   r   r   r   r   r   r   r(   rC   rG   	ExceptionrH   rM   rh   r"   r   r   r   r   r   r   r   <module>   s     0	(O&{ :