#----------------------------------------------------------
#                MicroTimingFormula150319b.py
#
# Eclipse Luna 4.4.0: downloaded and installed according to:
# https://www.ics.uci.edu/~pattis/common/handouts
#                    /pythoneclipsejava/eclipsepython.html
# 
# Python symbolic algebra: sympy-0.7.6.win32.exe
# downloaded, installed from 
#                 http://www.sympy.org/en/index.html

#----------------------------------------------------------
# This Python code implements the Micro-Timing Formula
# introduced on pp.109-114 of "Microlects of Mental Models."
# The basic idea is that the Micro-Timing Formula is a product
# of "multivariable probability polynomials". A probability
# polynomial is a polynomial with positive coefficients that
# sum to 1.
#
# The "key idea" that timeout of a node may trigger an
# arbitrary Python function is implemented by the dictionary
# V and the line eval(V[x]) in the function Productor.
# Such functions might spawn threads for concurrent actions;
# such functions might change the structure of the diagram
# itself, and in particular, change timeout values in the
# E dictionary, or carefully alter probability polynomials
# in the P dictionary.
#
# Note: The code here is a proof of concept; it is not
# optimized whatsoever. It would be very unlikely to scale
# up. Probability polynomials with thousands of symbols
# would not be multiplied by a symbolic algebra system.
# Monomials with very low probability would be discarded.
#

#----------------------------------------------------------
from sympy import *
import random
#----------------------------------------------------------
a,b,c,d,e =symbols('a,b,c,d,e')
def diagram():
    E=  {
         a:3.0,
         b:5.0,
         c:5.0,
         d:12.0,
         e:6.0
        }
    P=  {
        (e,e):0.55*a + 0.45*e,
        (a,b):0.6*c + 0.4*d,
        (c,e):1.0*e,
        (c,b):1.0*d, 
        (d,d):1.0*a         # Added to Fig.3.5
        }
    R=  {
        a:0.5,
        b:3.5,
        c:1.5,
        d:11.0,
        e:0.5
        }
    V=  {
        a:'print("a timeout, spawn a thread.")',
        b:'print("b timeout, change a variable.")',
        c:'print("c timeout, send an email.")',
        d:'print("d timeout, spawn a thread..")',
        e:'print("e timeout, move an image.")'
        }
    return (E,P,R,V)
#----------------------------------------------------------
def Productor(e,p,r):
    PRDCT=a**0                      # Funny way to write 1.
    DESETS=[]
    for x in r.keys():
        for y in r.keys():
            if(((x,y) in p)&(r[x]==0)&(r[y]>=0)):
                if not x in DESETS:
                    DESETS.append(x)
                    eval(V[x])      # Key idea.
                if not y in DESETS:    
                    DESETS.append(y)
                PRDCT=Mul(PRDCT,p[(x,y)]).expand()
    return (PRDCT,DESETS)
#----------------------------------------------------------
def Listor(prdct):
    d=prdct.as_coefficients_dict()
    m=[]
    l=[]
    for x in d:
        m.append(x)
        l.append(d[x])
    return (m,l)
#----------------------------------------------------------
def Probitor(l):
    r=random.random()
    I=0
    SUM=0
    b=0
    while(b==0):
        SUM=SUM+l[I]
        if(r>SUM):
            I=I+1
        else:
            b=1
    return I
#----------------------------------------------------------
def Resetor(e,r,m,i):
    NODES=m[i].as_terms()[1]
    for n in NODES:
        r[n]=E[n]
    return (r,NODES)
#----------------------------------------------------------
def Desetor(r,desets):
    for n in desets:
        r[n]=-E[n]
    return r
#----------------------------------------------------------
def TimeStepper(r):
    mu=1000
    for t in list(r.values()):
        if((0<t)&(t<mu)):
            mu=t
    for x in r:
        r[x]=max(r[x]-mu,-E[x])
    return r
#----------------------------------------------------------
# Un-optimized Algorithm:
#----------------------------------------------------------
E, P, R, V = diagram()
NMB=int(input('Enter iteration count: '))
for k in range(NMB):
    print('================================================  ',k)
    print( 'R:\n',str(R))
    R=TimeStepper(R)
    FNL, DST = Productor(E,P,R)
    print('DST = ', DST)
    R=Desetor(R,DST)
    MNL, PRB = Listor(FNL)
    print('MNL = ',MNL)
    IND=Probitor(PRB)
    R, RST = Resetor(E,R,MNL,IND)
    print('RST = ',RST)
    
'''
================================================   0
R:
 {a: 0.5, e: 0.5, d: 11.0, b: 3.5, c: 1.5}
a timeout, spawn a thread.
e timeout, move an image.
DST =  [a, b, e]
MNL =  [d*e, c*e, a*d, a*c]
RST =  [c, e]
================================================   1
R:
 {a: -3.0, e: 6.0, d: 10.5, b: -5.0, c: 5.0}
c timeout, send an email.
DST =  [c, e]
MNL =  [1.0*e]
RST =  [e]
================================================   2
R:
 {a: -3.0, e: 6.0, d: 5.5, b: -5.0, c: -5.0}
d timeout, spawn a thread..
DST =  [d]
MNL =  [1.0*a]
RST =  [a]
================================================   3
R:
 {a: 3.0, e: 0.5, d: -12.0, b: -5.0, c: -5.0}
e timeout, move an image.
DST =  [e]
MNL =  [a, e]
RST =  [a]
================================================   4
R:
 {a: 3.0, e: -6.0, d: -12.0, b: -5.0, c: -5.0}
DST =  []
MNL =  [1]
RST =  []
================================================   5
'''