Procházet zdrojové kódy

delays now work better in LaTeX generator

rparedis před 4 roky
rodič
revize
f604088cd9
1 změnil soubory, kde provedl 142 přidání a 14 odebrání
  1. 142 14
      src/CBD/converters/latexify.py

+ 142 - 14
src/CBD/converters/latexify.py

@@ -6,8 +6,12 @@ version (by means of substitution).
 
 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 implementation of 'delay', based on the time
+# TODO: change the delta?
+# TODO: variable step sizes?
 class CBD2Latex:
 	"""
 	Creates a corresponding set of LaTeX-equations for a CBD model.
@@ -41,8 +45,6 @@ class CBD2Latex:
 			elif isinstance(res, list):
 				for r in res:
 					self.equations[r[0]] = r[1]
-			if block.getBlockType() == "DelayBlock":
-				self.outputs.append(block.getPath() + ".OUT1")
 
 		# Add all connections
 		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.
 		"""
+		# TODO: less code duplication
 		latex = ""
 		for variable, value in self.equations.items():
 			var = variable
 			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)
 
 		return latex
@@ -171,18 +190,41 @@ class CBD2Latex:
 			- :func:`substitute`
 		"""
 		if show_steps:
-			self.render()
+			print(self.render())
 		self.simplify_links()
 		peq = None
 		i = 0
 		while peq != self.equations:
 			if 0 <= steps <= i: break
 			if show_steps:
-				self.render()
+				print(self.render())
 			peq = self.equations.copy()
 			self.substitute()
 			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:
 	"""
@@ -222,6 +264,30 @@ class Fnc:
 			elif isinstance(elem, Fnc):
 				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):
 		"""
 		Simplifies the function w.r.t. its meaning.
@@ -384,10 +450,11 @@ class Fnc:
 		largs = deepcopy(self.args)
 		for i, a in enumerate(self.args):
 			if isinstance(a, Fnc):
+				txt = a.latex()
 				if a.brackets():
-					largs[i] = "(%s)" % a.latex()
+					largs[i] = "(%s)" % txt
 				else:
-					largs[i] = a.latex()
+					largs[i] = txt
 			elif isinstance(a, str):
 				largs[i] = "%s" % a
 			else:
@@ -420,9 +487,66 @@ class Fnc:
 		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 "delay(%s, %s)" % (largs[0], largs[1])
+			return largs[0]
 		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):
 	if block._use_const:
 		return p + ".OUT1", Fnc('clamp', [p + ".IN1", block.min, block.max])
@@ -486,18 +610,22 @@ if __name__ == '__main__':
 			# Create the Blocks
 			self.addBlock(DelayBlock("delay1"))
 			self.addBlock(DelayBlock("delay2"))
+			self.addBlock(DelayBlock("delay3"))
 			self.addBlock(AdderBlock("sum"))
 			self.addBlock(ConstantBlock("zero", value=(0)))
 			self.addBlock(ConstantBlock("one", value=(1)))
 
 			# Create the Connections
 			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("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"))