Procházet zdrojové kódy

Fixed bugs, errors and issues in the stepwise simplification

Also made docs for CBD encapsulation
rparedis před 4 roky
rodič
revize
86efc16a3b

+ 0 - 7
doc/CBD.converters.CBD2LaTeX.rst

@@ -1,7 +0,0 @@
-CBD.converters.CBD2LaTeX module
-===============================
-
-.. automodule:: CBD.converters.CBD2LaTeX
-    :members:
-    :undoc-members:
-    :show-inheritance:

+ 2 - 2
doc/CBD.converters.CBDDraw.rst

@@ -1,5 +1,5 @@
-CBD.converters.CBDDraw module
-=============================
+Generate GraphViz from CBD Models
+=================================
 
 
 .. automodule:: CBD.converters.CBDDraw
 .. automodule:: CBD.converters.CBDDraw
     :members:
     :members:

+ 2 - 2
doc/CBD.converters.eq2CBD.rst

@@ -1,5 +1,5 @@
-CBD.converters.eq2CBD module
-============================
+Generate CBD Models from Equations
+==================================
 
 
 .. automodule:: CBD.converters.eq2CBD
 .. automodule:: CBD.converters.eq2CBD
     :members:
     :members:

+ 7 - 0
doc/CBD.converters.hybrid.rst

@@ -0,0 +1,7 @@
+Encapsulating CBD Models in PythonPDEVS
+=======================================
+
+.. automodule:: CBD.converters.hybrid
+    :members:
+    :undoc-members:
+    :show-inheritance:

+ 7 - 0
doc/CBD.converters.latexify.rst

@@ -0,0 +1,7 @@
+Generate LaTeX from CBD Models
+==============================
+
+.. automodule:: CBD.converters.latexify
+    :members:
+    :undoc-members:
+    :show-inheritance:

+ 3 - 2
doc/CBD.converters.rst

@@ -11,7 +11,8 @@ Submodules
 
 
 .. toctree::
 .. toctree::
 
 
-   CBD.converters.CBD2LaTeX
-   CBD.converters.CBDDraw
    CBD.converters.eq2CBD
    CBD.converters.eq2CBD
+   CBD.converters.latexify
+   CBD.converters.CBDDraw
+   CBD.converters.hybrid
 
 

+ 1 - 1
doc/install.rst

@@ -28,7 +28,7 @@ Next, there are some additional **optional** requirements:
 * **Conversion:**
 * **Conversion:**
   * `Lark <https://lark-parser.readthedocs.io/en/latest/>`_ for the :mod:`CBD.converters.eq2CBD` converter, to
   * `Lark <https://lark-parser.readthedocs.io/en/latest/>`_ for the :mod:`CBD.converters.eq2CBD` converter, to
     allow the creation of CBDs directly from a textual language.
     allow the creation of CBDs directly from a textual language.
-  * `PythonPDEVS <http://msdl.cs.mcgill.ca/projects/DEVS/PythonPDEVS>`_ for the :mod:`CBD.converters.pypdevs`
+  * `PythonPDEVS <http://msdl.cs.mcgill.ca/projects/DEVS/PythonPDEVS>`_ for the :mod:`CBD.converters.hybrid`
     module. This allows CBDs to be run inside of DEVS simulations.
     module. This allows CBDs to be run inside of DEVS simulations.
 
 
 * **Documentation:**
 * **Documentation:**

Rozdílová data souboru nebyla zobrazena, protože soubor je příliš velký
+ 67 - 27
examples/notebook/.ipynb_checkpoints/HybridTrain-checkpoint.ipynb


Rozdílová data souboru nebyla zobrazena, protože soubor je příliš velký
+ 67 - 27
examples/notebook/HybridTrain.ipynb


binární
examples/notebook/Train.png


Rozdílová data souboru nebyla zobrazena, protože soubor je příliš velký
+ 2193 - 0
examples/notebook/data.txt


src/CBD/converters/CBD2LaTeX.py → src/CBD/converters/_old_CBD2LaTeX.py


+ 68 - 28
src/CBD/converters/hybrid.py

@@ -13,7 +13,7 @@ from CBD.simulator import Simulator
 from CBD.realtime.threadingBackend import Platform
 from CBD.realtime.threadingBackend import Platform
 from copy import deepcopy
 from copy import deepcopy
 
 
-__all__ = ['CBDRunner', 'CrossingDetection']
+__all__ = ['CBDRunner', 'prepare_cbd']
 
 
 class CBDRunner(AtomicDEVS):
 class CBDRunner(AtomicDEVS):
 	"""
 	"""
@@ -61,6 +61,69 @@ class CBDRunner(AtomicDEVS):
 		for out in self.crossings:
 		for out in self.crossings:
 			self.outputs["crossing-" + out] = self.addOutPort("crossing-" + out)
 			self.outputs["crossing-" + out] = self.addOutPort("crossing-" + out)
 
 
+	def reinit(self):
+		"""
+		Re-initializes the CBD model.
+
+		Warning:
+			Use this function with care. It is expected a simulation will commence
+			after this function was called.
+		"""
+		self.state["CBD"] = self.original_model.clone()
+		self.apply_initials()
+		self.simulator = Simulator(self.state["CBD"])
+		self.simulator.getClock().setStartTime(self.state["time"])
+		self.set_delta(float('inf'))
+
+	def apply_initials(self):
+		"""
+		Applies the initial values.
+		"""
+		for k, v in self.state["initials"].items():
+			self.state["CBD"].getBlockByName(k).setValue(v)
+
+	def get_signal(self, pname):
+		"""
+		Gets the output for a given name.
+
+		Args:
+			pname (str):    The name of the port.
+		"""
+		return self.state["CBD"].getBlockByName(pname).getSignal("OUT1")[-1].value
+
+	def crossing_detection(self, signal, y, y1):
+		"""
+		Simple linear crossing root finder for a signal.
+
+		Args:
+			signal (str):   The port to detect.
+			y (float):      The value to cross.
+			y1 (float):     The first simulation value that was obtained.
+
+		Note:
+			This function will change in the future, presumably becoming the IVP
+			root finding algorithm.
+		"""
+		t1 = self.state["time"]
+		t2 = t1 + self.state["delta_t"]
+		y2 = self.get_signal(signal)
+		return (t2 - t1) / (y2 - y1) * (y - y1) + t1
+
+	def set_delta(self, val):
+		"""
+		Sets the maximal allowed delta of the clock.
+
+		Args:
+			val (float):    The maximal allowed delta.
+		"""
+		self.state["CBD"].getBlockByName(self.Hname).setValue(val)
+
+	def get_delta(self):
+		"""
+		Obtains the last used delta.
+		"""
+		return self.simulator.getClock().getInputSignal(-1, 'h').value
+
 	def timeAdvance(self):
 	def timeAdvance(self):
 		if self.state["stopped"]:
 		if self.state["stopped"]:
 			return INFINITY
 			return INFINITY
@@ -164,32 +227,6 @@ class CBDRunner(AtomicDEVS):
 	def __del__(self):
 	def __del__(self):
 		self.simulator.stop()
 		self.simulator.stop()
 
 
-	def reinit(self):
-		self.state["CBD"] = self.original_model.clone()
-		self.apply_initials()
-		self.simulator = Simulator(self.state["CBD"])
-		self.simulator.getClock().setStartTime(self.state["time"])
-		self.set_delta(float('inf'))
-
-	def apply_initials(self):
-		for k, v in self.state["initials"].items():
-			self.state["CBD"].getBlockByName(k).setValue(v)
-
-	def get_signal(self, pname):
-		return self.state["CBD"].getBlockByName(pname).getSignal("OUT1")[-1].value
-
-	def crossing_detection(self, signal, y, y1):
-		t1 = self.state["time"]
-		t2 = t1 + self.state["delta_t"]
-		y2 = self.get_signal(signal)
-		return (t2 - t1) / (y2 - y1) * (y - y1) + t1
-
-	def set_delta(self, val):
-		self.state["CBD"].getBlockByName(self.Hname).setValue(val)
-
-	def get_delta(self):
-		return self.simulator.getClock().getInputSignal(-1, 'h').value
-
 
 
 def prepare_cbd(model, initials):
 def prepare_cbd(model, initials):
 	"""
 	"""
@@ -207,6 +244,9 @@ def prepare_cbd(model, initials):
 	Returns:
 	Returns:
 		A tuple of :code:`(prepared CBD, name)` where :code:`name` identifies the
 		A tuple of :code:`(prepared CBD, name)` where :code:`name` identifies the
 		name for the
 		name for the
+
+	Note:
+		Usually, this function shouldn't be called by a user.
 	"""
 	"""
 	cbd = model.clone()
 	cbd = model.clone()
 	for inp in (b for b in model.getBlocks() if b.getBlockType() == "InputPortBlock"):
 	for inp in (b for b in model.getBlocks() if b.getBlockType() == "InputPortBlock"):
@@ -231,7 +271,7 @@ def prepare_cbd(model, initials):
 	return cbd, Hname
 	return cbd, Hname
 
 
 
 
-class CrossingDetection(AtomicDEVS):
+class _CrossingDetection(AtomicDEVS):
 	"""
 	"""
 	Atomic DEVS model that allows for zero-crossing detection.
 	Atomic DEVS model that allows for zero-crossing detection.
 	This block is the generalized detector that allows detection of any
 	This block is the generalized detector that allows detection of any

+ 183 - 97
src/CBD/converters/latexify.py

@@ -6,24 +6,60 @@ 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: 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.
 
 
 	Args:
 	Args:
 		model (CBD.CBD.CBD):    The model to create the equations for.
 		model (CBD.CBD.CBD):    The model to create the equations for.
+
+	Keyword Arguments:
+		show_steps (bool):      When :code:`True`, all intermediary steps will
+								be shown. Defaults to :code:`False`.
+		ignore_path (bool):     When :code:`True`, the name of the original model
+								will be removed from all path names. This name is
+								a common prefix over the system.
+								Defaults to :code:`True`.
+		escape_nonlatex (bool): When :code:`True`, non-latex characters are escaped
+								from the rendered result. Defaults to :code:`True`.
+		time_variable (str):    The name for the variable that represents the time
+								(i.e., the current iteration). Defaults to :code:`'i'`.
+		render_latex (bool):    When :code:`True`, the :func:`render` method will
+								output a latex-formatted string. Otherwise, simple
+								text formatting is done. Defaults to :code:`True`.
 	"""
 	"""
-	def __init__(self, model):
+	def __init__(self, model, **kwargs):
 		self.model = model
 		self.model = model
+		self.config = {
+			"show_steps": False,
+			"ignore_path": True,
+			"escape_nonlatex": True,
+			"time_variable": 'i',
+			"render_latex": True
+		}
+
+		for k in kwargs:
+			if k in self.config:
+				self.config[k] = kwargs[k]
+
 		self.equations = {}
 		self.equations = {}
-		self.outputs = [self.model.getPath() + "." + x for x in self.model.getSignals().keys()]
+		self.outputs = [self._rename(self.model.getPath() + "." + x) for x in self.model.getSignals().keys()]
 		self._collect_equations()
 		self._collect_equations()
+		self._step = 0
+
+	def _rename(self, name):
+		"""Makes the name of a path accurate.
+
+		Args:
+			name (str):     The name to convert.
+		"""
+		if self.config["ignore_path"]:
+			mname = self.model.getPath() + "."
+			if name.startswith(mname):
+				name = name[len(mname):]
+		if self.config["escape_nonlatex"]:
+			name = name.replace("_", r"\_")
+		return name
 
 
 	def _collect_equations(self):
 	def _collect_equations(self):
 		"""
 		"""
@@ -38,8 +74,8 @@ class CBD2Latex:
 			func = _BLOCK_MAP.get(block.getBlockType(), None)
 			func = _BLOCK_MAP.get(block.getBlockType(), None)
 			if func is None: continue
 			if func is None: continue
 			if isinstance(func, str):
 			if isinstance(func, str):
-				func = lambda b, p, f=func: (p + ".OUT1", Fnc(f, [p + ".%s" % x for x in block.getInputPortNames()]))
-			res = func(block, block.getPath())
+				func = lambda b, p, f=func: (p("OUT1"), Fnc(f, [p("%s") % x for x in block.getInputPortNames()]))
+			res = func(block, lambda x: self._rename(block.getPath() + "." + x))
 			if isinstance(res, tuple):
 			if isinstance(res, tuple):
 				self.equations[res[0]] = res[1]
 				self.equations[res[0]] = res[1]
 			elif isinstance(res, list):
 			elif isinstance(res, list):
@@ -52,41 +88,91 @@ class CBD2Latex:
 			path = block.getPath()
 			path = block.getPath()
 			for k, v in block.getLinksIn().items():
 			for k, v in block.getLinksIn().items():
 				if tp == "OutputPortBlock":
 				if tp == "OutputPortBlock":
-					self.equations[path] = [v.block.getPath() + "." + v.output_port]
+					self.equations[self._rename(path)] = [self._rename(v.block.getPath() + "." + v.output_port)]
 				else:
 				else:
-					self.equations[path + "." + k] = v.block.getPath() + "." + v.output_port
+					self.equations[self._rename(path + "." + k)] = self._rename(v.block.getPath() + "." + v.output_port)
 
 
-	def render(self):
+	def render(self, rl=True):
 		"""
 		"""
 		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.
+
+		Args:
+			rl (bool):      Identifies if the rendering must result in a LaTeX-renderable string.
+							This argument basically overwrites the :attr:`render_latex` config
+							attribute. When :code:`None`, the value from the config is used.
+							Defaults to :code:`True`.
 		"""
 		"""
-		# TODO: less code duplication
 		latex = ""
 		latex = ""
-		for variable, value in self.equations.items():
-			var = variable
-			val = value
+		if rl is None:
+			rl = self.config["render_latex"]
+
+		def apply_eq(var, val, ltx):
+			"""
+			Applies a dictionary of equations.
+
+			Args:
+				var:    Lefthand-side of the equation.
+				val:    Righthand-side of the equation, i.e. the function.
+				ltx:    Latex-string to format var and val in.
+			"""
 			if isinstance(val, Fnc):
 			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)
+				val = deepcopy(val)
+				val.apply_time(t=self.config["time_variable"])
+				val = val.latex(rl)
+				if len(val) > 2:
+					while val[0] == "(" and val[-1] == ")":
+						val = val[1:-1]
+			return ltx.format(v=var, val=val, time=self.config["time_variable"])
+
+		for variable, value in self.equations.items():
+			x = "\t{v}({time}) = {val}\n"
+			if rl:
+				x = "\t" + r"{v}({time}) &=& {val}\\" + "\n"
+			if not isinstance(value, list):
+				value = [value]
+			latex += apply_eq(variable, Fnc('+', value), x)
 
 
 		ic = self.create_ic()
 		ic = self.create_ic()
 		for variable, value in ic.items():
 		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)
+			x = "\t{v} = {val}\n"
+			if rl:
+				x = "\t" + r"{v} &=& {val}\\" + "\n"
+			latex += apply_eq(variable, value, x)
 
 
+		if rl:
+			return "\\left\\{\\begin{array}{lcl}\n%s\\end{array}\\right." % latex
 		return latex
 		return latex
 
 
+	def create_ic(self):
+		"""
+		Creates the equations for the initial conditions of a system.
+		"""
+		# Maximal depth is the amount of nested delay blocks
+		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, e in eqs.items():
+				if isinstance(e, Fnc) and e.name == 'D':
+					eqs[k] = Fnc('+', [e])
+					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
+
 	def simplify_links(self):
 	def simplify_links(self):
 		"""
 		"""
 		First step to execute is a link simplification. Generally, there are more links
 		First step to execute is a link simplification. Generally, there are more links
@@ -172,14 +258,11 @@ class CBD2Latex:
 			i += 1
 			i += 1
 		return deps
 		return deps
 
 
-	def simplify(self, show_steps=False, steps=-1):
+	def simplify(self, steps=-1):
 		"""
 		"""
 		Simplifies the system of equations to become a more optimal solution.
 		Simplifies the system of equations to become a more optimal solution.
 
 
 		Args:
 		Args:
-			show_steps (bool):  When :code:`True`, all intermediary results will
-								be rendered with the :func:`render` method.
-								Defaults to :code:`False`.
 			steps (int):        When positive, this indicates the amount of steps
 			steps (int):        When positive, this indicates the amount of steps
 								that must be taken. When negative, the equations
 								that must be taken. When negative, the equations
 								will be simplified until a convergence (i.e. no
 								will be simplified until a convergence (i.e. no
@@ -189,42 +272,33 @@ class CBD2Latex:
 			- :func:`simplify_links`
 			- :func:`simplify_links`
 			- :func:`substitute`
 			- :func:`substitute`
 		"""
 		"""
-		if show_steps:
-			print(self.render())
+		if self.config["show_steps"]:
+			self._trace("INITIAL SYSTEM")
 		self.simplify_links()
 		self.simplify_links()
+		txt = " substituted all connections and constant values"
 		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:
-				print(self.render())
 			peq = self.equations.copy()
 			peq = self.equations.copy()
+			if self.config["show_steps"]:
+				self._trace(txt)
 			self.substitute()
 			self.substitute()
 			i += 1
 			i += 1
+			txt = ""
 
 
-	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
+	def _trace(self, text=""):
+		"""Traces a step in the solution.
+
+		Args:
+			text (str): Additional text to print.
+		"""
+		if self._step == 0:
+			print("" + text + ":")
+		else:
+			print("STEP %d:" % self._step, text)
+		print(self.render(None))
+		self._step += 1
 
 
 class Fnc:
 class Fnc:
 	"""
 	"""
@@ -314,6 +388,8 @@ class Fnc:
 					nargs.append(Fnc("*", [a, c]))
 					nargs.append(Fnc("*", [a, c]))
 			if len(nargs) == 1:
 			if len(nargs) == 1:
 				return nargs[0]
 				return nargs[0]
+			if len(nargs) == 0:
+				return 0
 		elif name == '*':
 		elif name == '*':
 			val = 1
 			val = 1
 			occ = {}
 			occ = {}
@@ -443,9 +519,12 @@ class Fnc:
 		"""
 		"""
 		return self.name in ["+", "-", "*", "~", "^", "root", "%", "or", "and", "==", "<=", "<"]
 		return self.name in ["+", "-", "*", "~", "^", "root", "%", "or", "and", "==", "<=", "<"]
 
 
-	def latex(self):
+	def latex(self, latex=True):
 		"""
 		"""
 		Returns a LaTeX-formatted string of this function.
 		Returns a LaTeX-formatted string of this function.
+
+		Args:
+			latex (bool):   Whether or not to use LaTeX-based strings.
 		"""
 		"""
 		largs = deepcopy(self.args)
 		largs = deepcopy(self.args)
 		for i, a in enumerate(self.args):
 		for i, a in enumerate(self.args):
@@ -460,34 +539,38 @@ class Fnc:
 			else:
 			else:
 				largs[i] = str(a)
 				largs[i] = str(a)
 
 
-		if self.name in ['+', '*', 'or', 'and']:
+		opers = {}
+		if latex:
 			op = {
 			op = {
 				'*': r"\cdot ",
 				'*': r"\cdot ",
 				'or': r"\wedge ",
 				'or': r"\wedge ",
 				'and': r"\vee ",
 				'and': r"\vee ",
-			}.get(self.name, self.name)
-			return (" %s " % op).join(largs)
-		elif self.name in '-!~':
-			op = {
 				'!': r"\neg ",
 				'!': r"\neg ",
 				'~': "1/",
 				'~': "1/",
-			}.get(self.name, self.name)
+				"%": r"\mod ",
+				"<=": r"\leq ",
+				"==": r"\leftrightarrow ",
+			}
+		if self.name in ['+', '*', 'or', 'and']:
+			op = opers.get(self.name, self.name)
+			return (" %s " % op).join(largs)
+		elif self.name in '-!~':
+			op = opers.get(self.name, self.name)
 			return "{}{}".format(op, largs[0])
 			return "{}{}".format(op, largs[0])
 		elif self.name == '^':
 		elif self.name == '^':
-			return "%s^{%s}" % (largs[0], largs[1])
+			if latex:
+				return "%s^{%s}" % (largs[0], largs[1])
+			return "%s^(%s)" % (largs[0], largs[1])
 		elif self.name == 'root':
 		elif self.name == 'root':
-			return "%s^{1/%s}" % (largs[1], largs[0])
+			if latex:
+				return "%s^{1/%s}" % (largs[0], largs[1])
+			if largs[1] == 2:
+				return "sqrt(%s)" % largs[0]
+			return "root(%s, %s)" % (largs[0], largs[1])
 		elif self.name in ['%', '<', '<=', '==']:
 		elif self.name in ['%', '<', '<=', '==']:
-			op = {
-				"%": r"\mod ",
-				"<=": r"\leq ",
-				"==": r"\leftrightarrow ",
-			}.get(self.name, self.name)
+			op = opers.get(self.name, self.name)
 			return "%s %s %s" % (largs[0], op, largs[1])
 			return "%s %s %s" % (largs[0], op, largs[1])
 		elif self.name == 'D':
 		elif self.name == 'D':
-			# 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])
-			# return "delay(%s, %s)" % (largs[0], largs[1])
 			return largs[0]
 			return largs[0]
 		return "{}({})".format(self.name, ", ".join(largs))
 		return "{}({})".format(self.name, ", ".join(largs))
 
 
@@ -523,7 +606,10 @@ class Fnc:
 				t -= time
 				t -= time
 		for i, a in enumerate(self.args):
 		for i, a in enumerate(self.args):
 			if isinstance(a, str):
 			if isinstance(a, str):
-				self.args[i] = "%s(%s)" % (a, str(t))
+				if not isinstance(t, str) and t < 0:
+					self.args[i] = "%s(0)" % a
+				else:
+					self.args[i] = "%s(%s)" % (a, str(t))
 			elif isinstance(a, Fnc):
 			elif isinstance(a, Fnc):
 				if self.name == 'D' and i == 1:
 				if self.name == 'D' and i == 1:
 					a.apply_time(None, to)
 					a.apply_time(None, to)
@@ -560,30 +646,30 @@ def _clamp_block(block, p):
 #       lambda b, p, f=func: (p + ".OUT1", Fnc(f, [p + ".%s" % x for x in block.getInputPortNames()]))
 #       lambda b, p, f=func: (p + ".OUT1", Fnc(f, [p + ".%s" % x for x in block.getInputPortNames()]))
 # Note: the LHS is required to be a single value!
 # Note: the LHS is required to be a single value!
 _BLOCK_MAP = {
 _BLOCK_MAP = {
-	"ConstantBlock": lambda block, p: (p + ".OUT1", block.getValue()),
-	"NegatorBlock": lambda block, p: (p + ".OUT1", Fnc('-', [p + ".IN1"])),
-	"InverterBlock": lambda block, p: (p + ".OUT1", Fnc('~', [p + ".IN1"])),
+	"ConstantBlock": lambda block, p: (p("OUT1"), block.getValue()),
+	"NegatorBlock": lambda block, p: (p("OUT1"), Fnc('-', [p("IN1")])),
+	"InverterBlock": lambda block, p: (p("OUT1"), Fnc('~', [p("IN1")])),
 	"AdderBlock": '+',
 	"AdderBlock": '+',
 	"ProductBlock": '*',
 	"ProductBlock": '*',
-	"ModuloBlock": lambda block, p: (p + ".OUT1", Fnc('%', [p + ".IN1", p + ".IN2"])),
-	"RootBlock": lambda block, p: (p + ".OUT1", Fnc('root', [p + ".IN1", p + ".IN2"])),
-	"PowerBlock": lambda block, p: (p + ".OUT1", Fnc('^', [p + ".IN1", p + ".IN2"])),
-	"AbsBlock": lambda block, p: (p + ".OUT1", Fnc('abs', [p + ".IN1"])),
-	"IntBlock": lambda block, p: (p + ".OUT1", Fnc('int', [p + ".IN1"])),
+	"ModuloBlock": lambda block, p: (p("OUT1"), Fnc('%', [p("IN1"), p("IN2")])),
+	"RootBlock": lambda block, p: (p("OUT1"), Fnc('root', [p("IN1"), p("IN2")])),
+	"PowerBlock": lambda block, p: (p("OUT1"), Fnc('^', [p("IN1"), p("IN2")])),
+	"AbsBlock": lambda block, p: (p("OUT1"), Fnc('abs', [p("IN1")])),
+	"IntBlock": lambda block, p: (p("OUT1"), Fnc('int', [p("IN1")])),
 	"ClampBlock": _clamp_block,
 	"ClampBlock": _clamp_block,
-	"GenericBlock": lambda block, p: (p + ".OUT1", Fnc(block.getBlockOperator(), [p + ".IN1"])),
+	"GenericBlock": lambda block, p: (p("OUT1"), Fnc(block.getBlockOperator(), [p("IN1")])),
 	"MultiplexerBlock": 'MUX',
 	"MultiplexerBlock": 'MUX',
 	"MaxBlock": 'max',
 	"MaxBlock": 'max',
 	"MinBlock": 'min',
 	"MinBlock": 'min',
-	"LessThanBlock": lambda block, p: (p + ".OUT1", Fnc('<', [p + ".IN1", p + ".IN2"])),
-	"LessThanOrEqualsBlock": lambda block, p: (p + ".OUT1", Fnc('<=', [p + ".IN1", p + ".IN2"])),
-	"EqualsBlock": lambda block, p: (p + ".OUT1", Fnc('==', [p + ".IN1", p + ".IN2"])),
-	"NotBlock": lambda block, p: (p + ".OUT1", Fnc('!', [p + ".IN1"])),
+	"LessThanBlock": lambda block, p: (p("OUT1"), Fnc('<', [p("IN1"), p("IN2")])),
+	"LessThanOrEqualsBlock": lambda block, p: (p("OUT1"), Fnc('<=', [p("IN1"), p("IN2")])),
+	"EqualsBlock": lambda block, p: (p("OUT1"), Fnc('==', [p("IN1"), p("IN2")])),
+	"NotBlock": lambda block, p: (p("OUT1"), Fnc('!', [p("IN1")])),
 	"OrBlock": 'or',
 	"OrBlock": 'or',
 	"AndBlock": 'and',
 	"AndBlock": 'and',
 	"DelayBlock": 'D',
 	"DelayBlock": 'D',
-	# "DelayBlock": lambda block, p: [(p + ".OUT1", Fnc('D', [p + ".IN1"])), (p + ".OUT1(0)", Fnc('S', [p + ".IC"]))],
-	"TimeBlock": lambda block, p: (p + ".OUT1", 't')
+	# "DelayBlock": lambda block, p: [(p(.UT1"), Fnc('D', [p(.N1")])), (p(.UT1()0)", Fnc('S', [p(.C")]))],
+	"TimeBlock": lambda block, p: (p("OUT1"), 't')
 }
 }
 
 
 
 
@@ -628,7 +714,7 @@ if __name__ == '__main__':
 			self.addConnection("delay3", "OUT1", output_port_name='OUT1')
 			self.addConnection("delay3", "OUT1", output_port_name='OUT1')
 
 
 
 
-	ltx = CBD2Latex(FibonacciGen("fib"))
+	ltx = CBD2Latex(FibonacciGen("fib"), render_latex=False, show_steps=True)
 	# ltx.render()
 	# ltx.render()
 	ltx.simplify()
 	ltx.simplify()
 	print(ltx.render())
 	print(ltx.render())