# AUTOGENERATED FILE! PLEASE DON'T EDIT
"""This module is for creating dynamic graphs using plain old
equations. Don't use this, still experimental"""
from typing import Callable as _Callable
import k1lib as _k1lib
[docs]class Expression:
    def __init__(self, a:"Variable", b:"Variable", operation:_Callable[[float, float], float]):
        self.a = a; self.b = b; self.operation = operation
    @property
    def resolved(self): return self.a.resolved and self.b.resolved
    @property
    def value(self): return self.operation(self.a.value, self.b.value)
[docs]    def applyF(self, f:_Callable[["Variable"], None]): self.a.applyF(f); self.b.applyF(f)  
def _op2(a, b, operation):
    a = a if isinstance(a, Variable) else Constant(a)
    b = b if isinstance(b, Variable) else Constant(b)
    answer = Variable(); answer.expr = Expression(a, b, operation)
    if answer.expr.resolved: answer.value = answer.expr.value
    return answer
[docs]class Variable:
    idx = 0
    def __init__(self):
        self.__class__.idx += 1; self.variableName = f"V{self.__class__.idx}"
        self.expr:Expression = None
        self.value:float = None # not None, then already resolved
        self.isConstant = False # to know if the value above is resolved, or is truely a literal number
        self.trial:int = 0 # current resolve trial number
[docs]    def unresolve(self): # restores variable if it's not a constant
        self.value = self.value if self.isConstant else None 
    @property
    def resolved(self): return self.value != None
[docs]    def applyF(self, f:_Callable[["Variable"], None]): # apply an operation to variable and its dependencies
        f(self); self.expr.applyF(f) if self.expr != None else 0 
[docs]    def resolve(self, trial:int) -> bool: # attempt to resolve variable. Return true if tree changes
        if self.trial >= trial or self.resolved or self.expr == None: return False
        changed = self.expr.a.resolve(trial) or self.expr.b.resolve(trial) # try to resolve dependencies first
        self.trial = trial
        if self.expr.resolved: self.value = self.expr.value; changed = True
        return changed 
[docs]    def simplify(self, printStuff:bool=False): # simplify system before solving
        self.applyF(lambda v: setattr(v, "trial", 0)); trial = 2
        while self.resolve(trial): trial += 1
        if printStuff and not self.resolved: print("Can't find a solution") 
[docs]    def solve(self, x): # try to solve this tree, given independent variable with specific value
        self.applyF(lambda v: v.unresolve()); self.simplify(); leaves = self.leaves
        if len(leaves) > 1: raise Exception(f"System of equation has {len(leaves)} indenpendent variables. Please constrain system more!")
        elif len(leaves) == 1: next(iter(leaves)).value = x
        self.simplify(True) 
    @property
    def _leaves(self): # get dependent variables's that does not have an expression linked to it
        if self.resolved: return []
        if self.expr == None: return [self]
        else: return self.expr.a._leaves + self.expr.b._leaves
    @property
    def leaves(self): return list(set(self._leaves))
    def __call__(self, x): self.solve(x); return self.value
    def __add__(self, variable): return _op2(self, variable, lambda a, b: a + b)
    def __sub__(self, variable): return _op2(self, variable, lambda a, b: a - b)
    def __mul__(self, variable): return _op2(self, variable, lambda a, b: a * b)
    def __truediv__(self, variable): return _op2(self, variable, lambda a, b: a / b)
    def __radd__(self, variable): return _op2(variable, self, lambda a, b: a + b)
    def __rsub__(self, variable): return _op2(variable, self, lambda a, b: a - b)
    def __rmul__(self, variable): return _op2(variable, self, lambda a, b: a * b)
    def __rtruediv__(self, variable): return _op2(variable, self, lambda a, b: a / b)
    def __repr__(self): return f"{self.value}" if self.resolved else f"Not resolved: {self.variableName}"
    def __int__(self): return self.value
    def __float__(self): return self.value 
[docs]class Constant(Variable):
    def __init__(self, value:float):
        super().__init__()
        self.value = value
        self.isConstant = True