|
@@ -6,8 +6,12 @@ version (by means of substitution).
|
|
|
|
|
|
|
|
from copy import deepcopy
|
|
from copy import deepcopy
|
|
|
|
|
|
|
|
|
|
+_LTX_ADD = []
|
|
|
|
|
+"""Additional statements to add in the LaTeX generation."""
|
|
|
|
|
+
|
|
|
# TODO: better paths; i.e. remove common prefix and escape non-latex characters
|
|
# TODO: better paths; i.e. remove common prefix and escape non-latex characters
|
|
|
-# TODO: better implementation of 'delay', based on the time
|
|
|
|
|
|
|
+# TODO: change the delta?
|
|
|
|
|
+# TODO: variable step sizes?
|
|
|
class CBD2Latex:
|
|
class CBD2Latex:
|
|
|
"""
|
|
"""
|
|
|
Creates a corresponding set of LaTeX-equations for a CBD model.
|
|
Creates a corresponding set of LaTeX-equations for a CBD model.
|
|
@@ -41,8 +45,6 @@ class CBD2Latex:
|
|
|
elif isinstance(res, list):
|
|
elif isinstance(res, list):
|
|
|
for r in res:
|
|
for r in res:
|
|
|
self.equations[r[0]] = r[1]
|
|
self.equations[r[0]] = r[1]
|
|
|
- if block.getBlockType() == "DelayBlock":
|
|
|
|
|
- self.outputs.append(block.getPath() + ".OUT1")
|
|
|
|
|
|
|
|
|
|
# Add all connections
|
|
# Add all connections
|
|
|
for block in self.model.getBlocks():
|
|
for block in self.model.getBlocks():
|
|
@@ -58,12 +60,29 @@ class CBD2Latex:
|
|
|
"""
|
|
"""
|
|
|
Creates the LaTeX string for the model, based on the current level of simplifications.
|
|
Creates the LaTeX string for the model, based on the current level of simplifications.
|
|
|
"""
|
|
"""
|
|
|
|
|
+ # TODO: less code duplication
|
|
|
latex = ""
|
|
latex = ""
|
|
|
for variable, value in self.equations.items():
|
|
for variable, value in self.equations.items():
|
|
|
var = variable
|
|
var = variable
|
|
|
val = value
|
|
val = value
|
|
|
- if isinstance(value, Fnc):
|
|
|
|
|
- val = value.latex()
|
|
|
|
|
|
|
+ if isinstance(val, Fnc):
|
|
|
|
|
+ val = deepcopy(value)
|
|
|
|
|
+ val.apply_time()
|
|
|
|
|
+ val = val.latex()
|
|
|
|
|
+ while val[0] == "(" and val[-1] == ")":
|
|
|
|
|
+ val = val[1:-1]
|
|
|
|
|
+ latex += r"{v}(t) &=& {val}\\".format(v=var, val=val)
|
|
|
|
|
+
|
|
|
|
|
+ ic = self.create_ic()
|
|
|
|
|
+ for variable, value in ic.items():
|
|
|
|
|
+ var = variable
|
|
|
|
|
+ val = value
|
|
|
|
|
+ if isinstance(val, Fnc):
|
|
|
|
|
+ val = deepcopy(value)
|
|
|
|
|
+ val.apply_time()
|
|
|
|
|
+ val = val.latex()
|
|
|
|
|
+ while val[0] == "(" and val[-1] == ")":
|
|
|
|
|
+ val = val[1:-1]
|
|
|
latex += r"{v} &=& {val}\\".format(v=var, val=val)
|
|
latex += r"{v} &=& {val}\\".format(v=var, val=val)
|
|
|
|
|
|
|
|
return latex
|
|
return latex
|
|
@@ -171,18 +190,41 @@ class CBD2Latex:
|
|
|
- :func:`substitute`
|
|
- :func:`substitute`
|
|
|
"""
|
|
"""
|
|
|
if show_steps:
|
|
if show_steps:
|
|
|
- self.render()
|
|
|
|
|
|
|
+ print(self.render())
|
|
|
self.simplify_links()
|
|
self.simplify_links()
|
|
|
peq = None
|
|
peq = None
|
|
|
i = 0
|
|
i = 0
|
|
|
while peq != self.equations:
|
|
while peq != self.equations:
|
|
|
if 0 <= steps <= i: break
|
|
if 0 <= steps <= i: break
|
|
|
if show_steps:
|
|
if show_steps:
|
|
|
- self.render()
|
|
|
|
|
|
|
+ print(self.render())
|
|
|
peq = self.equations.copy()
|
|
peq = self.equations.copy()
|
|
|
self.substitute()
|
|
self.substitute()
|
|
|
i += 1
|
|
i += 1
|
|
|
|
|
|
|
|
|
|
+ def create_ic(self):
|
|
|
|
|
+ stop = [0]
|
|
|
|
|
+ for e in self.equations.values():
|
|
|
|
|
+ if isinstance(e, Fnc):
|
|
|
|
|
+ stop.append(e.get_delay_depth())
|
|
|
|
|
+ stop = max(stop)
|
|
|
|
|
+ created = {}
|
|
|
|
|
+ for i in range(stop):
|
|
|
|
|
+ eqs = deepcopy(self.equations)
|
|
|
|
|
+ for k in eqs.keys():
|
|
|
|
|
+ eqs[k] = Fnc('+', [eqs[k]])
|
|
|
|
|
+ eqs[k].apply_time(t=i)
|
|
|
|
|
+ eqs[k].apply_delay(i)
|
|
|
|
|
+ for c, v in created.items():
|
|
|
|
|
+ eqs[k].apply(c, v)
|
|
|
|
|
+
|
|
|
|
|
+ old = None
|
|
|
|
|
+ while old != eqs[k]:
|
|
|
|
|
+ old = eqs[k]
|
|
|
|
|
+ if isinstance(eqs[k], Fnc):
|
|
|
|
|
+ eqs[k] = eqs[k].simplify()
|
|
|
|
|
+ created["%s(%d)" % (k, i)] = eqs[k]
|
|
|
|
|
+ return created
|
|
|
|
|
|
|
|
class Fnc:
|
|
class Fnc:
|
|
|
"""
|
|
"""
|
|
@@ -222,6 +264,30 @@ class Fnc:
|
|
|
elif isinstance(elem, Fnc):
|
|
elif isinstance(elem, Fnc):
|
|
|
elem.apply(name, value)
|
|
elem.apply(name, value)
|
|
|
|
|
|
|
|
|
|
+ def apply_delay(self, time=-1):
|
|
|
|
|
+ """
|
|
|
|
|
+ Applies a delay to the function and all its children.
|
|
|
|
|
+ Can be used to remove delays from the system.
|
|
|
|
|
+
|
|
|
|
|
+ Calling :func:`apply_time` followed by :func:`apply_delay`
|
|
|
|
|
+ with the same time solves the system for that time.
|
|
|
|
|
+
|
|
|
|
|
+ Args:
|
|
|
|
|
+ time (int): The time whence to apply the delay.
|
|
|
|
|
+ """
|
|
|
|
|
+ if self.name == 'D':
|
|
|
|
|
+ if time == 0:
|
|
|
|
|
+ return self.args[1]
|
|
|
|
|
+ elif time > 0:
|
|
|
|
|
+ if isinstance(self.args[0], Fnc):
|
|
|
|
|
+ return self.args[0].apply_delay(time - 1)
|
|
|
|
|
+ return self.args[0]
|
|
|
|
|
+ else:
|
|
|
|
|
+ for i, a in enumerate(self.args):
|
|
|
|
|
+ if isinstance(a, Fnc):
|
|
|
|
|
+ self.args[i] = a.apply_delay(time)
|
|
|
|
|
+ return self
|
|
|
|
|
+
|
|
|
def simplify(self):
|
|
def simplify(self):
|
|
|
"""
|
|
"""
|
|
|
Simplifies the function w.r.t. its meaning.
|
|
Simplifies the function w.r.t. its meaning.
|
|
@@ -384,10 +450,11 @@ class Fnc:
|
|
|
largs = deepcopy(self.args)
|
|
largs = deepcopy(self.args)
|
|
|
for i, a in enumerate(self.args):
|
|
for i, a in enumerate(self.args):
|
|
|
if isinstance(a, Fnc):
|
|
if isinstance(a, Fnc):
|
|
|
|
|
+ txt = a.latex()
|
|
|
if a.brackets():
|
|
if a.brackets():
|
|
|
- largs[i] = "(%s)" % a.latex()
|
|
|
|
|
|
|
+ largs[i] = "(%s)" % txt
|
|
|
else:
|
|
else:
|
|
|
- largs[i] = a.latex()
|
|
|
|
|
|
|
+ largs[i] = txt
|
|
|
elif isinstance(a, str):
|
|
elif isinstance(a, str):
|
|
|
largs[i] = "%s" % a
|
|
largs[i] = "%s" % a
|
|
|
else:
|
|
else:
|
|
@@ -420,9 +487,66 @@ class Fnc:
|
|
|
elif self.name == 'D':
|
|
elif self.name == 'D':
|
|
|
# return r"\left\{{\begin{{array}}{{lcr}}{ic}&\textrm{{if }}t + 1 = 0\\" \
|
|
# return r"\left\{{\begin{{array}}{{lcr}}{ic}&\textrm{{if }}t + 1 = 0\\" \
|
|
|
# r"{n}&\textrm{{otherwise}}\end{{array}}\right.".format(ic=largs[1], n=largs[0])
|
|
# r"{n}&\textrm{{otherwise}}\end{{array}}\right.".format(ic=largs[1], n=largs[0])
|
|
|
- return "delay(%s, %s)" % (largs[0], largs[1])
|
|
|
|
|
|
|
+ # return "delay(%s, %s)" % (largs[0], largs[1])
|
|
|
|
|
+ return largs[0]
|
|
|
return "{}({})".format(self.name, ", ".join(largs))
|
|
return "{}({})".format(self.name, ", ".join(largs))
|
|
|
|
|
|
|
|
|
|
+ def apply_time(self, time=0, t="t"):
|
|
|
|
|
+ """
|
|
|
|
|
+ Converts all equations to functions that take a time-argument.
|
|
|
|
|
+ Delay blocks decrease the "time" annotation. This function is used
|
|
|
|
|
+ to find the initial conditions of a system.
|
|
|
|
|
+
|
|
|
|
|
+ Calling :func:`apply_time` followed by :func:`apply_delay`
|
|
|
|
|
+ with the same time solves the system for that time.
|
|
|
|
|
+
|
|
|
|
|
+ Args:
|
|
|
|
|
+ time (int): How much time in the past this must be applied.
|
|
|
|
|
+ A positive value of :code:`n` means this is applied
|
|
|
|
|
+ at :code:`time-n`.
|
|
|
|
|
+ t (str/int): The time variable name, or an integer indicative of
|
|
|
|
|
+ a specific time that must be applied. E.g., setting
|
|
|
|
|
+ this value to 2 will apply the the formulas at time 2.
|
|
|
|
|
+ """
|
|
|
|
|
+ if self.name == 'D':
|
|
|
|
|
+ time += 1
|
|
|
|
|
+ to = t
|
|
|
|
|
+ if isinstance(t, str):
|
|
|
|
|
+ if time > 0:
|
|
|
|
|
+ t += "-%d" % time
|
|
|
|
|
+ if time is None:
|
|
|
|
|
+ t = "0"
|
|
|
|
|
+ else:
|
|
|
|
|
+ if time is None:
|
|
|
|
|
+ t = 0
|
|
|
|
|
+ else:
|
|
|
|
|
+ t -= time
|
|
|
|
|
+ for i, a in enumerate(self.args):
|
|
|
|
|
+ if isinstance(a, str):
|
|
|
|
|
+ self.args[i] = "%s(%s)" % (a, str(t))
|
|
|
|
|
+ elif isinstance(a, Fnc):
|
|
|
|
|
+ if self.name == 'D' and i == 1:
|
|
|
|
|
+ a.apply_time(None, to)
|
|
|
|
|
+ else:
|
|
|
|
|
+ a.apply_time(time, to)
|
|
|
|
|
+
|
|
|
|
|
+ def get_delay_depth(self, start=0):
|
|
|
|
|
+ """
|
|
|
|
|
+ Counts the amount of "nested" delay blocks.
|
|
|
|
|
+
|
|
|
|
|
+ Args:
|
|
|
|
|
+ start (int): Initial count value.
|
|
|
|
|
+ """
|
|
|
|
|
+ c = start
|
|
|
|
|
+ if self.name == 'D':
|
|
|
|
|
+ c += 1
|
|
|
|
|
+ v = [c]
|
|
|
|
|
+ for a in self.args:
|
|
|
|
|
+ if isinstance(a, Fnc):
|
|
|
|
|
+ v.append(a.get_delay_depth(c))
|
|
|
|
|
+ return max(v)
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
def _clamp_block(block, p):
|
|
def _clamp_block(block, p):
|
|
|
if block._use_const:
|
|
if block._use_const:
|
|
|
return p + ".OUT1", Fnc('clamp', [p + ".IN1", block.min, block.max])
|
|
return p + ".OUT1", Fnc('clamp', [p + ".IN1", block.min, block.max])
|
|
@@ -486,18 +610,22 @@ if __name__ == '__main__':
|
|
|
# Create the Blocks
|
|
# Create the Blocks
|
|
|
self.addBlock(DelayBlock("delay1"))
|
|
self.addBlock(DelayBlock("delay1"))
|
|
|
self.addBlock(DelayBlock("delay2"))
|
|
self.addBlock(DelayBlock("delay2"))
|
|
|
|
|
+ self.addBlock(DelayBlock("delay3"))
|
|
|
self.addBlock(AdderBlock("sum"))
|
|
self.addBlock(AdderBlock("sum"))
|
|
|
self.addBlock(ConstantBlock("zero", value=(0)))
|
|
self.addBlock(ConstantBlock("zero", value=(0)))
|
|
|
self.addBlock(ConstantBlock("one", value=(1)))
|
|
self.addBlock(ConstantBlock("one", value=(1)))
|
|
|
|
|
|
|
|
# Create the Connections
|
|
# Create the Connections
|
|
|
self.addConnection("delay1", "delay2", output_port_name='OUT1', input_port_name='IN1')
|
|
self.addConnection("delay1", "delay2", output_port_name='OUT1', input_port_name='IN1')
|
|
|
- self.addConnection("delay1", "sum", output_port_name='OUT1', input_port_name='IN2')
|
|
|
|
|
- self.addConnection("delay2", "sum", output_port_name='OUT1', input_port_name='IN1')
|
|
|
|
|
- self.addConnection("sum", "delay1", output_port_name='OUT1', input_port_name='IN1')
|
|
|
|
|
- self.addConnection("sum", "OUT1", output_port_name='OUT1')
|
|
|
|
|
|
|
+ self.addConnection("delay2", "delay3", output_port_name='OUT1', input_port_name='IN1')
|
|
|
|
|
+ self.addConnection("delay1", "sum", output_port_name='OUT1', input_port_name='IN1')
|
|
|
|
|
+ self.addConnection("delay2", "sum", output_port_name='OUT1', input_port_name='IN2')
|
|
|
|
|
+ self.addConnection("delay3", "delay1", output_port_name='OUT1', input_port_name='IN1')
|
|
|
|
|
+ self.addConnection("sum", "delay3", output_port_name='OUT1', input_port_name='IN1')
|
|
|
self.addConnection("zero", "delay1", output_port_name='OUT1', input_port_name='IC')
|
|
self.addConnection("zero", "delay1", output_port_name='OUT1', input_port_name='IC')
|
|
|
self.addConnection("one", "delay2", output_port_name='OUT1', input_port_name='IC')
|
|
self.addConnection("one", "delay2", output_port_name='OUT1', input_port_name='IC')
|
|
|
|
|
+ self.addConnection("one", "delay3", output_port_name='OUT1', input_port_name='IC')
|
|
|
|
|
+ self.addConnection("delay3", "OUT1", output_port_name='OUT1')
|
|
|
|
|
|
|
|
|
|
|
|
|
ltx = CBD2Latex(FibonacciGen("fib"))
|
|
ltx = CBD2Latex(FibonacciGen("fib"))
|