Browse Source

Fixed #34 and #35 + first work on debugging LaTeX gen

rparedis 2 years ago
parent
commit
cc17596b84

BIN
CBD.zip


+ 1 - 1
doc/index.rst

@@ -19,7 +19,7 @@ to model complex systems of equations.
   * Claudio Gomes
   * Randy Paredis
 
-:Python Version: :code:`>= 3.4`
+:Python Version: :code:`>= 3.6`
 
 .. versionchanged:: 1.5
     Python 2 is no longer supported.

+ 4 - 1
doc/install.rst

@@ -7,9 +7,12 @@ Dependencies
 ------------
 The following dependencies are required:
 
-* Python :code:`>= 2.7` or :code:`>= 3.2`, as identified by `vermin <https://pypi.org/project/vermin/>`_.
+* Python :code:`>= 3.6`, as identified by `vermin <https://pypi.org/project/vermin/>`_.
 * All additional libraries should be bundled with Python, so no extra installations should be needed.
 
+.. versionchanged:: 1.5
+    Python 2 is no longer supported.
+
 Next, there are some additional **optional** requirements:
 
 * **Simulation:**

File diff suppressed because it is too large
+ 73 - 50
examples/notebook/.ipynb_checkpoints/SinGen-checkpoint.ipynb


File diff suppressed because it is too large
+ 73 - 50
examples/notebook/SinGen.ipynb


+ 7 - 2
experiments/HarmonicOscilator/Oscillators.py

@@ -132,14 +132,19 @@ class ErrorB(CBD):
 
 
 if __name__ == '__main__':
+	from CBD.converters.latexify import CBD2Latex
+
 	errors = []
 	signals = []
-	for dt in [0.1, 0.01, 0.001]:
+	for dt in [0.1]:
 		print("DT:", dt)
 		signals.append(str(dt))
 
 		outputs = []
 		for cbd in [ErrorA("ErrorA"), ErrorB("ErrorB")]:
+			cbd2latex = CBD2Latex(cbd, show_steps=True, render_latex=False)
+			cbd2latex.simplify()
+
 			# Run the simulation
 			sim = Simulator(cbd)
 			sim.setDeltaT(dt)
@@ -163,7 +168,7 @@ if __name__ == '__main__':
 	plt.title("Errors")
 	plt.xlabel('time')
 	plt.ylabel('N')
-	for i in range(3):
+	for i in range(2):
 		for j in range(2):
 			time = [x for x, _ in errors[(i*2)+j]]
 			value = [x for _, x in errors[(i*2)+j]]

+ 50 - 24
src/CBD/Core.py

@@ -451,6 +451,29 @@ class BaseBlock:
         """
         raise NotImplementedError("BaseBlock has nothing to compute")
 
+    def defaultInputPortNameIdentifier(self):
+        """
+        Algorithm that identifies which input port name to select if no input port name
+        is provided for a link/connection.
+
+        Be default, all ports with name :code:`INx` are analyzed, where :code:`x` identifies
+        an integer of the auto-increment port.
+        E.g. if the last connected port was :code:`IN1`, :code:`IN2` will be returned (if
+        there is no incoming connection yet).
+
+        Returns:
+            The new input port.
+        """
+        i = 1
+        while True:
+            nextIn = "IN" + str(i)
+            if self.hasInputPortWithName(nextIn):
+                if self.getInputPortByName(nextIn).getIncoming() is None:
+                    return nextIn
+            else:
+                raise ValueError("There are no open IN inputs left in block %s" % self.getPath())
+            i += 1
+
     def linkToInput(self, in_block, *, name_input=None, name_output="OUT1"):
         """
         Links the output of the :code:`in_block` to the input of this block.
@@ -460,21 +483,12 @@ class BaseBlock:
 
         Keyword Args:
             name_input (str):       The name of the input port. When :code:`None` or omitted,
-                                    the next input port is used. E.g. if the last port was
-                                    :code:`IN1`, the input is assumed to be :code:`IN2`.
+                                    :func:`defaultInputPortNameIdentifier` is used to find the
+                                    next port name.
             name_output (str):      The name of the output port. Defaults to :code:`OUT1`.
         """
         if name_input is None:
-            i = 1
-            while True:
-                nextIn = "IN" + str(i)
-                if self.hasInputPortWithName(nextIn):
-                    if self.getInputPortByName(nextIn).getIncoming() is None:
-                        name_input = nextIn
-                        break
-                else:
-                    raise ValueError("There are no open IN inputs left in block %s" % self.getPath())
-                i += 1
+            name_input = self.defaultInputPortNameIdentifier()
         if in_block.hasOutputPortWithName(name_output):
             source = in_block.getOutputPortByName(name_output)
         elif (self._parent == in_block or in_block == self) and in_block.hasInputPortWithName(name_output):
@@ -593,8 +607,6 @@ class CBD(BaseBlock):
         Flatten the CBD inline and call recursively for all sub-CBDs.
 
         Args:
-            parent (CBD):   Reference to the parent. Used for the recursive call.
-                            Defaults to :code:`None`. Users should ignore this parameter.
             ignore (iter):  Block class names to ignore in the flattening. When :code:`None`,
                             no blocks are ignored. Defaults to :code:`None`.
             psep (str):     The path separator to use. Defaults to :code:`"."`.
@@ -614,16 +626,30 @@ class CBD(BaseBlock):
                     child.setBlockName(block.getBlockName() + psep + child.getBlockName())
                     self.addBlock(child)
                 for port in block.getInputPorts() + block.getOutputPorts():
-                    source = port.getIncoming().source
-                    Port.disconnect(source, port)
-                    outgoing = port.getOutgoing()[:]
-                    for conn in outgoing:
-                        target = conn.target
-                        Port.disconnect(port, target)
-                        self.addConnection(source.block, target.block, input_port_name=target.name,
-                                           output_port_name=source.name)
+                    if port.getIncoming() is not None:
+                        source = port.getIncoming().source
+                        Port.disconnect(source, port)
+                        outgoing = port.getOutgoing()[:]
+                        for conn in outgoing:
+                            target = conn.target
+                            Port.disconnect(port, target)
+                            self.addConnection(source.block, target.block, input_port_name=target.name,
+                                               output_port_name=source.name)
                 self.removeBlock(block)
 
+    def flattened(self, ignore=None, psep="."):
+        """
+        Return a flattened version of the provided CBD.
+
+        Args:
+            ignore (iter):  Block class names to ignore in the flattening. When :code:`None`,
+                            no blocks are ignored. Defaults to :code:`None`.
+            psep (str):     The path separator to use. Defaults to :code:`"."`.
+        """
+        clone = self.clone()
+        clone.flatten(ignore, psep)
+        return clone
+
     def getBlocks(self):
         """
         Gets the list of blocks.
@@ -753,8 +779,8 @@ class CBD(BaseBlock):
 
         Keyword Args:
             input_port_name (str):  The name of the input port. When :code:`None` or unset,
-                                    the next port is used. E.g. when called after :code:`IN1`
-                                    is already set, :code:`IN2` will be used.
+                                    :func:`defaultInputPortNameIdentifier` will be used to
+                                    identify the port name.
             output_port_name (str): The name of the output port. Defaults to :code:`OUT1`.
 
         Note:

+ 1 - 1
src/CBD/converters/latexify/CBD2Latex.py

@@ -64,7 +64,7 @@ class CBD2Latex:
 	"""Default configuration setup."""
 
 	def __init__(self, model, **kwargs):
-		self.model = model
+		self.model = model.flattened()
 		self.config = self.DEFAULT_CONFIG
 
 		for k in kwargs:

+ 13 - 3
src/CBD/converters/latexify/functions.py

@@ -1,7 +1,7 @@
 """
 New and improved LaTeX-generation module.
 """
-
+import copy
 from copy import deepcopy
 
 
@@ -193,7 +193,9 @@ class Fnc:
 		fncsets = []
 		for arg in self.args:
 			fncsets.append([x for x in arg.at(time)])
-		if len(fncsets) == 1:
+		if len(fncsets) == 0:
+			args = []
+		elif len(fncsets) == 1:
 			args = [a for a in fncsets]
 		else:
 			args = self._cross_product_fncs(*fncsets)
@@ -201,14 +203,18 @@ class Fnc:
 			k = self.create(a[0].time, a[0].eq_time)
 			k.args = a
 			yield k
+		if len(args) == 0:
+			yield self.create(time, time)
 
 	def create(self, time, eq_time):
 		return self.__class__(self.name, time, eq_time)
 
 	def apply(self, eq):
+		nargs = self.args[:]
 		for i, a in enumerate(self.args):
 			res = a.apply(eq)
-			self.args[i] = res
+			nargs[i] = res
+		self.args = nargs
 		return self
 
 	@staticmethod
@@ -328,6 +334,10 @@ class UnaryFnc(Fnc):
 
 	def simplify(self):
 		if isinstance(self.args[0], ConstantFnc):
+			if isinstance(self.args[0].val, str):
+				ret = self.create(self.time, self.eq_time)
+				ret.args = self.args[:]
+				return [ret]
 			if self.name == '-':
 				self.args[0].val *= -1
 				return [self.args[0]]

+ 23 - 0
src/CBD/lib/std.py

@@ -191,6 +191,9 @@ class ModuloBlock(BaseBlock):
 		# Use 'math.fmod' for validity with C w.r.t. negative values AND floats
 		self.appendToSignal(math.fmod(self.getInputSignal(curIteration, "IN1").value, self.getInputSignal(curIteration, "IN2").value))
 
+	def defaultInputPortNameIdentifier(self):
+		raise ValueError("The order of the operands is important for this block. Please provide a port name.")
+
 
 class RootBlock(BaseBlock):
 	"""
@@ -226,6 +229,9 @@ class RootBlock(BaseBlock):
 			raise ZeroDivisionError("RootBlock '{}' received input less than {}.".format(self.getPath(), self._tolerance))
 		self.appendToSignal(self.getInputSignal(curIteration, "IN1").value ** (1 / input))
 
+	def defaultInputPortNameIdentifier(self):
+		raise ValueError("The order of the operands is important for this block. Please provide a port name.")
+
 
 class PowerBlock(BaseBlock):
 	"""
@@ -251,6 +257,9 @@ class PowerBlock(BaseBlock):
 	def compute(self, curIteration):
 		self.appendToSignal(self.getInputSignal(curIteration, "IN1").value ** self.getInputSignal(curIteration, "IN2").value)
 
+	def defaultInputPortNameIdentifier(self):
+		raise ValueError("The order of the operands is important for this block. Please provide a port name.")
+
 
 class AbsBlock(BaseBlock):
 	"""
@@ -333,6 +342,11 @@ class ClampBlock(BaseBlock):
 		x = self.getInputSignal(curIteration, "IN1").value
 		self.appendToSignal(min(max(x, min_), max_))
 
+	def defaultInputPortNameIdentifier(self):
+		if not self._use_const:
+			raise ValueError("The order of the operands is important for this block. Please provide a port name.")
+		return super(ClampBlock, self).defaultInputPortNameIdentifier()
+
 
 class GenericBlock(BaseBlock):
 	"""
@@ -436,6 +450,9 @@ class MultiplexerBlock(BaseBlock):
 		"""
 		return self.__numberOfInputs
 
+	def defaultInputPortNameIdentifier(self):
+		raise ValueError("The order of the operands is important for this block. Please provide a port name.")
+
 
 class MinBlock(BaseBlock):
 	"""
@@ -568,6 +585,9 @@ class LessThanBlock(BaseBlock):
 		gisv = lambda s: self.getInputSignal(curIteration, s).value
 		self.appendToSignal(1 if gisv("IN1") < gisv("IN2") else 0)
 
+	def defaultInputPortNameIdentifier(self):
+		raise ValueError("The order of the operands is important for this block. Please provide a port name.")
+
 
 class EqualsBlock(BaseBlock):
 	"""
@@ -628,6 +648,9 @@ class LessThanOrEqualsBlock(BaseBlock):
 		gisv = lambda s: self.getInputSignal(curIteration, s).value
 		self.appendToSignal(1 if gisv("IN1") <= gisv("IN2") else 0)
 
+	def defaultInputPortNameIdentifier(self):
+		raise ValueError("The order of the operands is important for this block. Please provide a port name.")
+
 
 class NotBlock(BaseBlock):
 	"""

+ 7 - 3
src/CBD/simulator.py

@@ -125,8 +125,9 @@ class Simulator:
 		Simulates the model.
 
 		Args:
-			term_time (float):  When not :code:`None`, overwrites the
-								termination time with the new value.
+			term_time (float):  When not :code:`None`, overwrites the termination time
+								with the new value. This value will be accurate upto 8
+								decimal points.
 		"""
 		self.__finished = False
 		self.__stop_requested = False
@@ -194,7 +195,7 @@ class Simulator:
 		ret = self.__stop_requested
 		if self.__termination_condition is not None:
 			ret = ret or self.__termination_condition(self.model, self.__sim_data[2])
-		return ret or self.__termination_time <= self.getTime()
+		return ret or round(abs(self.__termination_time - self.getTime()), 8) < self.getDeltaT()
 
 	def stop(self):
 		"""
@@ -398,6 +399,9 @@ class Simulator:
 		"""
 		Sets the termination time of the system.
 
+		Note:
+			This is accurate upto 8 decimal points.
+
 		Args:
 			term_time (float):  Termination time for the simulation.
 		"""

+ 29 - 29
src/test/stdCBDTest.py

@@ -140,8 +140,8 @@ class StdCBDTestCase(unittest.TestCase):
 		self.CBD.addBlock(ConstantBlock(block_name="c2", value=3.0))
 		self.CBD.addBlock(RootBlock(block_name="g"))
 
-		self.CBD.addConnection("c1", "g")
-		self.CBD.addConnection("c2", "g")
+		self.CBD.addConnection("c1", "g", input_port_name="IN1")
+		self.CBD.addConnection("c2", "g", input_port_name="IN2")
 		self._run(1)
 		self.assertEqual(self._getSignal("g"), [2.0])
 
@@ -150,8 +150,8 @@ class StdCBDTestCase(unittest.TestCase):
 		self.CBD.addBlock(ConstantBlock(block_name="c2", value=2.0))
 		self.CBD.addBlock(RootBlock(block_name="g"))
 
-		self.CBD.addConnection("c1", "g")
-		self.CBD.addConnection("c2", "g")
+		self.CBD.addConnection("c1", "g", input_port_name="IN1")
+		self.CBD.addConnection("c2", "g", input_port_name="IN2")
 		self._run(1)
 		self.assertEqual(self._getSignal("g"), [3.0])
 
@@ -160,8 +160,8 @@ class StdCBDTestCase(unittest.TestCase):
 		self.CBD.addBlock(ConstantBlock(block_name="c2", value=2.0))
 		self.CBD.addBlock(RootBlock(block_name="root"))
 
-		self.CBD.addConnection("c2", "root")
-		self.CBD.addConnection("c1", "root")
+		self.CBD.addConnection("c2", "root", input_port_name="IN1")
+		self.CBD.addConnection("c1", "root", input_port_name="IN2")
 		self.assertRaises(ZeroDivisionError, self._run, NUM_DISCR_TIME_STEPS)
 
 	def testPowerBlock(self):
@@ -169,8 +169,8 @@ class StdCBDTestCase(unittest.TestCase):
 		self.CBD.addBlock(ConstantBlock(block_name="c2", value=3.0))
 		self.CBD.addBlock(PowerBlock(block_name="g"))
 
-		self.CBD.addConnection("c1", "g")
-		self.CBD.addConnection("c2", "g")
+		self.CBD.addConnection("c1", "g", input_port_name="IN1")
+		self.CBD.addConnection("c2", "g", input_port_name="IN2")
 		self._run(1)
 		self.assertEqual(self._getSignal("g"), [512.0])
 
@@ -243,9 +243,9 @@ class StdCBDTestCase(unittest.TestCase):
 
 		self.CBD.addConnection("time", "sum")
 		self.CBD.addConnection("c1", "sum")
-		self.CBD.addConnection("sum", "clamp")
-		self.CBD.addConnection("c2", "clamp")
-		self.CBD.addConnection("c3", "clamp")
+		self.CBD.addConnection("sum", "clamp", input_port_name="IN1")
+		self.CBD.addConnection("c2", "clamp", input_port_name="IN2")
+		self.CBD.addConnection("c3", "clamp", input_port_name="IN3")
 		self._run(5)
 		self.assertEqual(self._getSignal("clamp"), [max(min(float(x), 2.0), -1.0) for x in range(-2, 3)])
 
@@ -258,8 +258,8 @@ class StdCBDTestCase(unittest.TestCase):
 		self.CBD.addBlock(SequenceBlock(block_name="c3", sequence=s))
 		self.CBD.addBlock(MultiplexerBlock(block_name="mux"))
 
-		self.CBD.addConnection("c1", "mux")
-		self.CBD.addConnection("c2", "mux")
+		self.CBD.addConnection("c1", "mux", input_port_name="IN1")
+		self.CBD.addConnection("c2", "mux", input_port_name="IN2")
 		self.CBD.addConnection("c3", "mux", input_port_name="select")
 		self._run(10)
 		self.assertEqual(self._getSignal("mux"), [0, 99, 98, 3, 4, 5, 94, 93, 92, 9])
@@ -278,8 +278,8 @@ class StdCBDTestCase(unittest.TestCase):
 		self.CBD.addBlock(ConstantBlock(block_name="c2", value=6))
 		self.CBD.addBlock(LessThanBlock(block_name="lt"))
 
-		self.CBD.addConnection("c1", "lt")
-		self.CBD.addConnection("c2", "lt")
+		self.CBD.addConnection("c1", "lt", input_port_name="IN1")
+		self.CBD.addConnection("c2", "lt", input_port_name="IN2")
 		self._run(1)
 		self.assertEqual(self._getSignal("lt"), [1])
 
@@ -288,8 +288,8 @@ class StdCBDTestCase(unittest.TestCase):
 		self.CBD.addBlock(ConstantBlock(block_name="c2", value=7))
 		self.CBD.addBlock(LessThanBlock(block_name="lt"))
 
-		self.CBD.addConnection("c1", "lt")
-		self.CBD.addConnection("c2", "lt")
+		self.CBD.addConnection("c1", "lt", input_port_name="IN1")
+		self.CBD.addConnection("c2", "lt", input_port_name="IN2")
 		self._run(1)
 		self.assertEqual(self._getSignal("lt"), [0])
 
@@ -298,8 +298,8 @@ class StdCBDTestCase(unittest.TestCase):
 		self.CBD.addBlock(ConstantBlock(block_name="c2", value=5))
 		self.CBD.addBlock(LessThanBlock(block_name="lt"))
 
-		self.CBD.addConnection("c1", "lt")
-		self.CBD.addConnection("c2", "lt")
+		self.CBD.addConnection("c1", "lt", input_port_name="IN1")
+		self.CBD.addConnection("c2", "lt", input_port_name="IN2")
 		self._run(1)
 		self.assertEqual(self._getSignal("lt"), [0])
 
@@ -308,8 +308,8 @@ class StdCBDTestCase(unittest.TestCase):
 		self.CBD.addBlock(ConstantBlock(block_name="c2", value=6))
 		self.CBD.addBlock(LessThanOrEqualsBlock(block_name="leq"))
 
-		self.CBD.addConnection("c1", "leq")
-		self.CBD.addConnection("c2", "leq")
+		self.CBD.addConnection("c1", "leq", input_port_name="IN1")
+		self.CBD.addConnection("c2", "leq", input_port_name="IN2")
 		self._run(1)
 		self.assertEqual(self._getSignal("leq"), [1])
 
@@ -318,8 +318,8 @@ class StdCBDTestCase(unittest.TestCase):
 		self.CBD.addBlock(ConstantBlock(block_name="c2", value=7))
 		self.CBD.addBlock(LessThanOrEqualsBlock(block_name="leq"))
 
-		self.CBD.addConnection("c1", "leq")
-		self.CBD.addConnection("c2", "leq")
+		self.CBD.addConnection("c1", "leq", input_port_name="IN1")
+		self.CBD.addConnection("c2", "leq", input_port_name="IN2")
 		self._run(1)
 		self.assertEqual(self._getSignal("leq"), [0])
 
@@ -328,8 +328,8 @@ class StdCBDTestCase(unittest.TestCase):
 		self.CBD.addBlock(ConstantBlock(block_name="c2", value=5))
 		self.CBD.addBlock(LessThanOrEqualsBlock(block_name="leq"))
 
-		self.CBD.addConnection("c1", "leq")
-		self.CBD.addConnection("c2", "leq")
+		self.CBD.addConnection("c1", "leq", input_port_name="IN1")
+		self.CBD.addConnection("c2", "leq", input_port_name="IN2")
 		self._run(1)
 		self.assertEqual(self._getSignal("leq"), [1])
 
@@ -402,8 +402,8 @@ class StdCBDTestCase(unittest.TestCase):
 		self.CBD.addBlock(ConstantBlock(block_name="c2", value=3.0))
 		self.CBD.addBlock(ModuloBlock(block_name="g"))
 
-		self.CBD.addConnection("c1", "g")
-		self.CBD.addConnection("c2", "g")
+		self.CBD.addConnection("c1", "g", input_port_name="IN1")
+		self.CBD.addConnection("c2", "g", input_port_name="IN2")
 		self._run(1)
 		self.assertEqual(self._getSignal("g"), [2.0])
 
@@ -412,8 +412,8 @@ class StdCBDTestCase(unittest.TestCase):
 		self.CBD.addBlock(ConstantBlock(block_name="c2", value=8.0))
 		self.CBD.addBlock(ModuloBlock(block_name="g"))
 
-		self.CBD.addConnection("c1", "g")
-		self.CBD.addConnection("c2", "g")
+		self.CBD.addConnection("c1", "g", input_port_name="IN1")
+		self.CBD.addConnection("c2", "g", input_port_name="IN2")
 		self._run(1)
 		self.assertEqual(self._getSignal("g"), [0.0])
 

+ 0 - 27
streaming.py

@@ -1,27 +0,0 @@
-# streaming/main.py
-from bokeh import models, plotting, io
-import pandas as pd
-from time import sleep
-from itertools import cycle
-data = pd.read_csv(
-    "https://raw.githubusercontent.com/nytimes/covid-19-data/master/us-states.csv")
-data["date"] = pd.to_datetime(data["date"])
-data["new_cases"] = data.groupby("state")["cases"].diff()
-state = "California"
-california_covid_data = data[data["state"] == state].copy()
-source = models.ColumnDataSource(california_covid_data)
-p = plotting.figure(
-    x_axis_label="Date", y_axis_label="New Cases",
-    plot_width=800, plot_height=250, x_axis_type="datetime", tools=["hover", "wheel_zoom"]
-)
-p.line(x="date", y="new_cases",
-       source=source,
-       legend_label=state,
-       width=4,
-       )
-io.curdoc().add_root(p)
-index_generator = cycle(range(len(california_covid_data.index)))
-def stream():
-    index = next(index_generator)
-    source.data = california_covid_data.iloc[:index]
-io.curdoc().add_periodic_callback(stream, 10)