Przeglądaj źródła

First work on creating connectors - NOT WORKING!

rparedis 3 lat temu
rodzic
commit
907b446146
40 zmienionych plików z 329 dodań i 345 usunięć
  1. 1 1
      doc/examples/Fibonacci.rst
  2. 2 2
      doc/examples/LCG.rst
  3. 1 1
      doc/examples/SinGen.rst
  4. 1 1
      examples/notebook/.ipynb_checkpoints/BouncingBallExample-checkpoint.ipynb
  5. 1 1
      examples/notebook/BouncingBallExample.ipynb
  6. 1 1
      examples/scripts/BouncingBall/BouncingBall.py
  7. 1 1
      examples/scripts/BouncingBall/BouncingBall_experiment.py
  8. 1 1
      examples/scripts/EvenNumberGen/EvenNumberGen_experiment.py
  9. 1 1
      examples/scripts/Fibonacci/Fibonacci_experiment.py
  10. 3 3
      examples/scripts/LCG/LCG_experiment.py
  11. 1 1
      examples/scripts/SinGen/SinGen_experiment.py
  12. 1 1
      examples/scripts/SinGen/SinGen_gameloop_experiment.py
  13. 1 1
      examples/scripts/SinGen/SinGen_threading_experiment.py
  14. 1 1
      examples/scripts/SinGen/SinGen_tkinter_experiment.py
  15. 2 2
      examples/scripts/algebraic-loop.py
  16. 1 1
      experiments/AGV/AGV_tuning.py
  17. 2 2
      experiments/HarmonicOscilator/CBDA.py
  18. 3 3
      experiments/HarmonicOscilator/CBDB.py
  19. 1 1
      experiments/HarmonicOscilator/HA.py
  20. 1 1
      experiments/HarmonicOscilator/code.py
  21. 1 1
      experiments/RungeKutta/Test_experiment.py
  22. 1 1
      experiments/SinGen/RKF45.py
  23. 187 204
      src/CBD/Core.py
  24. 6 19
      src/CBD/converters/CBDDraw.py
  25. 6 5
      src/CBD/converters/hybrid.py
  26. 17 24
      src/CBD/converters/latexify/CBD2Latex.py
  27. 27 15
      src/CBD/depGraph.py
  28. 4 4
      src/CBD/lib/std.py
  29. 22 22
      src/CBD/loopsolvers/linearsolver.py
  30. 1 1
      src/CBD/loopsolvers/sympysolver.py
  31. 4 4
      src/CBD/preprocessing/rungekutta.py
  32. 8 2
      src/CBD/simulator.py
  33. 3 3
      src/CBD/state_events/locators.py
  34. 9 7
      src/CBD/tracers/tracerVerbose.py
  35. 1 1
      src/test/basicCBDTest.py
  36. 1 1
      src/test/flattenCBDTest.py
  37. 1 1
      src/test/hierarchyCBDTest.py
  38. 1 1
      src/test/ioCBDTest.py
  39. 1 1
      src/test/otherCBDTest.py
  40. 1 1
      src/test/stdCBDTest.py

+ 1 - 1
doc/examples/Fibonacci.rst

@@ -101,7 +101,7 @@ When running the simulation for 10 time-units, we obtain the first 10 values:
     cbd = FibonacciGen("FibonacciGen")
     sim = Simulator(cbd)
     sim.run(10)
-    data = cbd.getSignal('OUT1')
+    data = cbd.getSignalHistory('OUT1')
     t, v = [t for t, _ in data], [v for _, v in data]
 
     print(v)  # prints [1, 1, 2, 3, 5, 8, 13, 21, 34, 55]

+ 2 - 2
doc/examples/LCG.rst

@@ -50,7 +50,7 @@ gives the following termination function:
 .. code-block:: python
 
     def term(model, curIt):
-        signals = [y for _, y in model.getSignal("IN1")]
+        signals = [y for _, y in model.getSignalHistory("IN1")]
         unique_signals = set(signals)
         return len(signals) > len(unique_signals)
 
@@ -70,4 +70,4 @@ Now, we can set up and run the simulation:
     sim.run()
 
     # Print a full cycle: [0, 4, 8, 3, 7, 2, 6, 1, 5]
-    print([v for _, v in lcg.getSignal("IN1")])
+    print([v for _, v in lcg.getSignalHistory("IN1")])

+ 1 - 1
doc/examples/SinGen.rst

@@ -80,7 +80,7 @@ output port of the :code:`sinGen` block, which can be plotted against their iter
 
 .. code-block:: python
 
-    data = sinGen.getSignal('OUT1')
+    data = sinGen.getSignalHistory('OUT1')
     x, y = [x for x, _ in data], [y for _, y in data]
 
 .. figure:: ../_figures/sin-disc.png

+ 1 - 1
examples/notebook/.ipynb_checkpoints/BouncingBallExample-checkpoint.ipynb

@@ -1235,7 +1235,7 @@
    "outputs": [],
    "source": [
     "manager.terminate()\n",
-    "# ball.freefall.state[\"CBD\"].getSignal('y')[-1]\n",
+    "# ball.freefall.state[\"CBD\"].getSignalHistory('y')[-1]\n",
     "# ball.collector.state[\"data\"]"
    ]
   },

+ 1 - 1
examples/notebook/BouncingBallExample.ipynb

@@ -1235,7 +1235,7 @@
    "outputs": [],
    "source": [
     "manager.terminate()\n",
-    "# ball.freefall.state[\"CBD\"].getSignal('y')[-1]\n",
+    "# ball.freefall.state[\"CBD\"].getSignalHistory('y')[-1]\n",
     "# ball.collector.state[\"data\"]"
    ]
   },

+ 1 - 1
examples/scripts/BouncingBall/BouncingBall.py

@@ -25,7 +25,7 @@ class BouncingBall(CBD):
 		self.addConnection("y0", "y", input_port_name="IC")
 
 	def bounce(self):
-		v_pre = self.getSignal("v")[-1].value
+		v_pre = self.getSignalHistory("v")[-1].value
 		v_new = -v_pre * self.k
 		self.getBlockByName("v0").setValue(v_new)
 		self.getBlockByName("y0").setValue(0.0)

+ 1 - 1
examples/scripts/BouncingBall/BouncingBall_experiment.py

@@ -34,7 +34,7 @@ def bounce(e, t, model):
 		model.bounce()
 		print("BOUNCE AT:", t)
 	else:
-		y = model.getSignal("height")[-1].value
+		y = model.getSignalHistory("height")[-1].value
 		model.getBlockByName("y0").setValue(y)
 		model.getBlockByName("v0").setValue(-41)
 

+ 1 - 1
examples/scripts/EvenNumberGen/EvenNumberGen_experiment.py

@@ -13,7 +13,7 @@ cbd = EvenNumberGen("gen")
 sim = Simulator(cbd)
 sim.run(30.0)
 
-data = cbd.getSignal('OUT1')
+data = cbd.getSignalHistory('OUT1')
 x, y = [x for x, _ in data], [y for _, y in data]
 
 plt.scatter(x, y)

+ 1 - 1
examples/scripts/Fibonacci/Fibonacci_experiment.py

@@ -13,7 +13,7 @@ cbd = FibonacciGen("FibonacciGen")
 sim = Simulator(cbd)
 sim.run(10)
 
-data = cbd.getSignal('OUT1')
+data = cbd.getSignalHistory('OUT1')
 t, v = [t for t, _ in data], [v for _, v in data]
 
 print(v)

+ 3 - 3
examples/scripts/LCG/LCG_experiment.py

@@ -7,7 +7,7 @@ from LCG import *
 import matplotlib.pyplot as plt
 
 def term(model, _):
-	signals = [y for __, y in model.getSignal("OUT1")]
+	signals = [y for __, y in model.getSignalHistory("OUT1")]
 	unique_signals = set(signals)
 	return len(signals) > len(unique_signals)
 
@@ -19,9 +19,9 @@ sim.setTerminationCondition(term)
 sim.run()
 
 # Print a full cycle: [0, 4, 8, 3, 7, 2, 6, 1, 5]
-print([v for _, v in lcg.getSignal("OUT1")])
+print([v for _, v in lcg.getSignalHistory("OUT1")])
 
-data = lcg.getSignal('OUT1')
+data = lcg.getSignalHistory('OUT1')
 x, y = [x for x, _ in data], [y for _, y in data]
 
 plt.scatter(x, y, s=5)

+ 1 - 1
examples/scripts/SinGen/SinGen_experiment.py

@@ -16,7 +16,7 @@ sim.setDeltaT(0.5)
 # The termination time can be set as argument to the run call
 sim.run(20.0)
 
-data = sinGen.getSignal('OUT1')
+data = sinGen.getSignalHistory('OUT1')
 x, y = [x for x, _ in data], [y for _, y in data]
 
 plt.plot(x, y)

+ 1 - 1
examples/scripts/SinGen/SinGen_gameloop_experiment.py

@@ -21,7 +21,7 @@ while sim.is_running():
 
 print("FINISHED!")
 
-data = sinGen.getSignal('OUT1')
+data = sinGen.getSignalHistory('OUT1')
 x, y = [x for x, _ in data], [y for _, y in data]
 
 plt.plot(x, y)

+ 1 - 1
examples/scripts/SinGen/SinGen_threading_experiment.py

@@ -20,7 +20,7 @@ while sim.is_running(): pass
 
 print("FINISHED!")
 
-data = sinGen.getSignal('OUT1')
+data = sinGen.getSignalHistory('OUT1')
 x, y = [x for x, _ in data], [y for _, y in data]
 
 plt.plot(x, y)

+ 1 - 1
examples/scripts/SinGen/SinGen_tkinter_experiment.py

@@ -20,7 +20,7 @@ root.mainloop()
 
 print("FINISHED!")
 
-data = sinGen.getSignal('OUT1')
+data = sinGen.getSignalHistory('OUT1')
 x, y = [x for x, _ in data], [y for _, y in data]
 
 plt.plot(x, y)

+ 2 - 2
examples/scripts/algebraic-loop.py

@@ -61,8 +61,8 @@ if __name__ == '__main__':
 	plt.show()
 
 	print()
-	print("X", test.getSignal("x"))
-	print("Y", test.getSignal("y"))
+	print("X", test.getSignalHistory("x"))
+	print("Y", test.getSignalHistory("y"))
 
 	# C code gen testing:
 	# from CBD.converters.CBD2C import CBD2C

+ 1 - 1
experiments/AGV/AGV_tuning.py

@@ -81,7 +81,7 @@ for Ki in np.arange(0.0, -0.06, -0.01):
 
 			file.write("{}\n".format(txt))
 			file.flush()
-			# print(cbd.findBlock("Controller")[0].getSignal("velocity"))
+			# print(cbd.findBlock("Controller")[0].getSignalHistory("velocity"))
 			# print(cbd.findBlock("plot")[0].data_xy)
 
 

+ 2 - 2
experiments/HarmonicOscilator/CBDA.py

@@ -17,7 +17,7 @@ def plot_signals(block, signals, title):
 	outputs = []
 
 	for signal in signals:
-		tvpl = block.getSignal(signal)
+		tvpl = block.getSignalHistory(signal)
 		times = [t for t, _ in tvpl]
 		outputs.append([v for _, v in tvpl])
 
@@ -157,7 +157,7 @@ if __name__ == '__main__':
 		sim = Simulator(cbd)
 		sim.setDeltaT(dt)
 		sim.run(int(50/dt))
-		# tvpl = cbd.getSignal("e")
+		# tvpl = cbd.getSignalHistory("e")
 		# outputs.append(tvpl)
 		# signals.append(str(dt))
 		# plot_signals(cbd, ['real', 'A'], f'Value A ({dt})')

+ 3 - 3
experiments/HarmonicOscilator/CBDB.py

@@ -38,7 +38,7 @@ def plot_signals(block, signals, title):
 	outputs = []
 
 	for signal in signals:
-		tvpl = block.getSignal(signal)
+		tvpl = block.getSignalHistory(signal)
 		times = [t for t, _ in tvpl]
 		outputs.append([v for _, v in tvpl])
 
@@ -175,7 +175,7 @@ if __name__ == '__main__':
 		# cbdB = ErrorB("ErrorB", dt=dt)
 		# # Run the simulation
 		# cbdB.run(int(10/dt), delta_t=dt)
-		# tvpl = cbdB.getSignal("e")
+		# tvpl = cbdB.getSignalHistory("e")
 		# outputs.append(tvpl)
 		# signals.append("B " + str(dt))
 		# plot_signals(cbdB, ['real', 'B'], f'Value B ({dt})')
@@ -185,7 +185,7 @@ if __name__ == '__main__':
 		sim = Simulator(cbdA)
 		sim.setDeltaT(dt)
 		sim.run(int(10/dt))
-		tvpl = cbdA.getSignal("e")
+		tvpl = cbdA.getSignalHistory("e")
 		outputs.append(tvpl)
 		signals.append("A " + str(dt))
 		plot_signals(cbdA, ['real', 'A'], f'Value A ({dt})')

+ 1 - 1
experiments/HarmonicOscilator/HA.py

@@ -17,7 +17,7 @@ def plot_signals(block, signals, title):
 	outputs = []
 
 	for signal in signals:
-		tvpl = block.getSignal(signal)
+		tvpl = block.getSignalHistory(signal)
 		times = [t for t, _ in tvpl]
 		outputs.append([v for _, v in tvpl])
 

+ 1 - 1
experiments/HarmonicOscilator/code.py

@@ -7,7 +7,7 @@ if __name__ == '__main__':
 		sim = Simulator(cbd)
 		sim.setDeltaT(dt)
 		sim.run(int(50/dt))
-		tvpl = cbd.getSignal("e")
+		tvpl = cbd.getSignalHistory("e")
 		outputs.append(tvpl)
 		signals.append(str(dt))
 		plot_signals(cbd, ['real', 'B'], f'Value B ({dt})')

+ 1 - 1
experiments/RungeKutta/Test_experiment.py

@@ -15,7 +15,7 @@ sim = Simulator(cbd)
 sim.setDeltaT(DELTA_T)
 sim.run(1.4)
 
-s = cbd.getSignal("y")
+s = cbd.getSignalHistory("y")
 L = len(s)
 
 print("+------------+------------+------------+------------+")

+ 1 - 1
experiments/SinGen/RKF45.py

@@ -17,7 +17,7 @@ sim.run(sim_time)
 import matplotlib.pyplot as plt
 import numpy as np
 
-data = newModel.getSignal('OUT1')
+data = newModel.getSignalHistory('OUT1')
 t, v = [t for t, _ in data], [v for _, v in data]
 
 fig = plt.figure()

+ 187 - 204
src/CBD/Core.py

@@ -12,6 +12,66 @@ Signal = namedtuple("Signal", ["time", "value"])
 level = enum(WARNING=1, ERROR=2, FATAL=3)
 epsilon = 0.001
 
+class Port:
+    IN = 0
+    OUT = 1
+
+    def __init__(self, name, direction, block):
+        self.name = name
+        self.direction = direction
+        self.block = block
+        self.__outgoing = []
+        self.__incoming = None
+        self.__history = []
+
+    def set(self, value):
+        self.__history.append(value)
+        for conn in self.__outgoing:
+            conn.transfer()
+
+    def get(self):
+        return self.__history[-1]
+
+    def clear(self):
+        self.__history.clear()
+
+    def getOutgoing(self):
+        return self.__outgoing
+
+    def getIncoming(self):
+        return self.__incoming
+
+    def getHistory(self):
+        return self.__history
+
+    def count(self):
+        return len(self.__history)
+
+    def _rewind(self):
+        self.__history.pop()
+
+    @staticmethod
+    def connect(source, target):
+        conn = Connector(source, target)
+        source.__outgoing.append(conn)
+        assert target.__incoming is None, "Fan-IN is not allowed!"
+        target.__incoming = conn
+
+    @staticmethod
+    def disconnect(source, target):
+        conn = target.__incoming
+        source.__outgoing.remove(conn)
+        target.__incoming = None
+
+
+class Connector:
+    def __init__(self, source, target):
+        self.source = source
+        self.target = target
+
+    def transfer(self):
+        self.target.set(self.source.get())
+
 
 class BaseBlock:
     """
@@ -35,18 +95,24 @@ class BaseBlock:
         # The output signals produced by this block are encoded in a dictionary.
         # The key of this dictionary is the name of the output port.
         # Each element of the dictionary contains an ordered list of values.
-        self.__signals = dict()
+        # self.__signals = dict()
+        self.__outputs = {}
         for output_port in output_ports:
-            self.__signals[output_port] = []
+            self.addOutputPort(output_port)
+            # self.__signals[output_port] = []
+
 
         # The input links produced by this block are encoded in a dictionary.
         # The key of this dictionary is the name of the input port.
         # Each element of the dictionary contains
         # a tuple of the block and the output name of the other block.
-        self._linksIn = dict()
+        # self._linksIn = dict()
+        self.__inputs = {}
+        for input_port in input_ports:
+            self.addInputPort(input_port)
 
         # The list of possible input ports
-        self.__nameLinks = input_ports
+        # self.__nameLinks = input_ports
         # In which CBD the BaseBlock is situated
         self._parent = None
 
@@ -57,8 +123,10 @@ class BaseBlock:
         Args:
             name (str): The name of the port.
         """
-        if name not in self.__nameLinks:
-            self.__nameLinks.append(name)
+        # if name not in self.__nameLinks:
+        #     self.__nameLinks.append(name)
+        if name not in self.__inputs:
+            self.__inputs[name] = Port(name, Port.IN, self)
 
     def addOutputPort(self, name):
         """
@@ -67,8 +135,41 @@ class BaseBlock:
         Args:
             name (str): The name of the port.
         """
-        if name not in self.__signals:
-            self.__signals[name] = []
+        # if name not in self.__signals:
+        #     self.__signals[name] = []
+        if name not in self.__outputs:
+            self.__outputs[name] = Port(name, Port.OUT, self)
+
+    def getInputPorts(self):
+        return list(self.__inputs.values())
+
+    def getInputPortNames(self):
+        # return self.__nameLinks
+        return list(self.__inputs.keys())
+
+    def getInputPortByName(self, name):
+        assert self.hasInputPortWithName(name), "No such input '%s' in %s" % (name, self.getPath())
+        return self.__inputs[name]
+
+    def hasInputPortWithName(self, name):
+        return name in self.__inputs
+
+    def getOutputPorts(self):
+        return list(self.__outputs.values())
+
+    def getOutputPortByName(self, name):
+        assert self.hasOutputPortWithName(name), "No such output '%s' in %s" % (name, self.getPath())
+        return self.__outputs[name]
+
+    def getOutputPortNames(self):
+        return list(self.__outputs.keys())
+
+    def hasOutputPortWithName(self, name):
+        return name in self.__outputs
+
+    def reparentPorts(self):
+        for port in self.getInputPorts() + self.getOutputPorts():
+            port.block = self
 
     def clone(self):
         """
@@ -76,7 +177,7 @@ class BaseBlock:
         that were set on this block.
         """
         other = deepcopy(self)
-        other._linksIn.clear()
+        other.reparentPorts()
         other._parent = None
         return other
 
@@ -119,33 +220,6 @@ class BaseBlock:
         """
         return self.__class__.__name__
 
-    def getLinksIn(self):
-        """
-        Gets the inputs of this block.
-        """
-        return self._linksIn
-
-    def getInputPortNames(self):
-        return self.__nameLinks
-
-    def getOutputNameOfInput(self, inputBlock):
-        """
-        Gets the name of the output port in the :code:`inputBlock` that is linked to this block.
-
-        Args:
-            inputBlock (BaseBlock): The block of which the output port must be obtained.
-        """
-        return [y for (x, y) in self._linksIn.items() if y.block == inputBlock][0].output_port
-
-    def getInputName(self, inputBlock):
-        """
-        Gets the names of the inputs that are linked to the :code:`inputBlock`.
-
-        Args:
-            inputBlock (BaseBlock): The block that is linked.
-        """
-        return [x for (x, y) in self._linksIn.items() if y.block == inputBlock]
-
     def getClock(self):
         """
         Gets the simulation clock. Only works if the block is part of a :class:`CBD` model.
@@ -163,11 +237,11 @@ class BaseBlock:
                                 the value of :code:`OUT1` will be used.
         """
         name_output = "OUT1" if name_output is None else name_output
-        assert name_output in self.__signals.keys()
-        curIt = len(self.__signals[name_output])
-        self.__signals[name_output].append(Signal(self.getClock().getTime(curIt), value))
+        port = self.getOutputPortByName(name_output)
+        curIt = port.count()
+        port.set(Signal(self.getClock().getTime(curIt), value))
 
-    def getSignal(self, name_output=None):
+    def getSignalHistory(self, name_output=None):
         """
         Obtains the set of signals this block has sent over an output port.
 
@@ -176,21 +250,11 @@ class BaseBlock:
                                 the value of :code:`OUT1` will be used.
         """
         name_output = "OUT1" if name_output is None else name_output
-        assert name_output in self.__signals.keys(), "No such output '%s' in %s" % (name_output, self.getPath())
-        return self.__signals[name_output]
+        return self.getOutputPortByName(name_output).getHistory()
 
-    def getSignals(self):
-        """
-        Obtains all the signals of the block.
-
-        Returns:
-            Dictionary of :code:`output port name -> list of signals`.
-        """
-        return self.__signals
-
-    def clearSignals(self):
-        for i in self.__signals.keys():
-            self.__signals[i].clear()
+    def clearPorts(self):
+        for port in self.getInputPorts() + self.getOutputPorts():
+            port.clear()
 
     def getDependencies(self, curIteration):
         """
@@ -199,9 +263,13 @@ class BaseBlock:
         Args:
             curIteration (int): The current simulation's iteration, for which
                                 the dependency graph must be constructed.
+
+        Returns:
+            A list of blocks that must be computed in order to compute this block,
+            at the time of the iteration.
         """
-        # TO IMPLEMENT: this is a helper function you can use to create the dependency graph...
-        return [x.block for x in self._linksIn.values()]
+        # TODO: what with multiple sequential connectors
+        return [p.getIncoming().source.block for p in self.getInputPorts() if p.getIncoming() is not None]
 
     def getBlockConnectedToInput(self, input_port):
         """
@@ -210,7 +278,9 @@ class BaseBlock:
         Args:
             input_port (str):   The name of the input port.
         """
-        return self._linksIn[input_port]
+        incoming = self.getInputPortByName(input_port).getIncoming()
+        assert incoming is not None, "No block found that links to '%s' in '%s'." % (input_port, self.getPath())
+        return incoming.source
 
     def getInputSignal(self, curIteration=-1, input_port="IN1"):
         """
@@ -223,8 +293,7 @@ class BaseBlock:
             input_port (str):       The name of the input port. If omitted, or when
                                     :code:`None`, the value of :code:`IN1` will be used.
         """
-        incoming_block, out_port_name = self._linksIn[input_port]
-        return incoming_block.getSignal(out_port_name)[curIteration]
+        return self.getInputPortByName(input_port).getHistory()[curIteration]
 
     def getPath(self, sep='.'):
         """Gets the path of the current block.
@@ -256,7 +325,7 @@ class BaseBlock:
         """
         raise NotImplementedError("BaseBlock has nothing to compute")
 
-    def linkInput(self, in_block, name_input=None, name_output=None):
+    def linkToInput(self, in_block, name_input=None, name_output=None):
         """
         Links the output of the :code:`in_block` to the input of this block.
 
@@ -269,20 +338,20 @@ class BaseBlock:
         """
         if name_output is None:
             name_output = "OUT1"
-        if name_input is not None:
-            assert name_input in self.__nameLinks, "Cannot link blocks, no such input '%s' in %s" % (name_input, self.getPath())
-            self._linksIn[name_input] = InputLink(in_block, name_output)
-        else:
+        if name_input is None:
             i = 1
             while True:
                 nextIn = "IN" + str(i)
-                if nextIn in self.__nameLinks:
-                    if not nextIn in self._linksIn:
-                        self._linksIn[nextIn] = InputLink(in_block, name_output)
-                        return
+                if self.hasInputPortWithName(nextIn):
+                    if self.getInputPortByName(nextIn).getIncoming() is None:
+                        name_input = nextIn
+                        break
                 else:
-                    exit("There are no open IN inputs left in block %s" % self.getBlockName())
+                    exit("There are no open IN inputs left in block %s" % self.getPath())
                 i += 1
+        source = in_block.getOutputPortByName(name_output)
+        target = self.getInputPortByName(name_input)
+        Port.connect(source, target)
 
     def unlinkInput(self, name_input):
         """
@@ -291,8 +360,9 @@ class BaseBlock:
         Args:
             name_input (str):   The name of the input.
         """
-        assert name_input in self.__nameLinks, "Cannot link blocks, no such input '%s' in %s" % (name_input, self.getPath())
-        del self._linksIn[name_input]
+        target = self.getOutputPortByName(name_input)
+        source = target.getIncoming()
+        Port.disconnect(source, target)
 
     def __repr__(self):
         return self.getPath() + ":" + self.getBlockType()
@@ -307,11 +377,12 @@ class BaseBlock:
         """
         idt = "\t" * indent
         repr = idt + self.getPath() + ":" + self.getBlockType() + "\n"
-        if len(self._linksIn) == 0:
+        if len(self.__inputs) == 0:
             repr += idt + "  No incoming connections to IN ports\n"
         else:
-            for (key, (in_block, out_port)) in self._linksIn.items():
-                repr += idt + "  On input " + key + ": " + in_block.getPath() + ":" + in_block.getBlockType() + "\n"
+            for key, inport in self.__inputs:
+                in_block = inport.getIncoming().block
+                repr += "%s  Input %s connected from %s (%s)\n" % (idt, key, in_block.getPath(), in_block.getBlockType())
         return repr
 
     def _rewind(self):
@@ -323,51 +394,8 @@ class BaseBlock:
             of the CBD simulator, **not** by a user. Using this function without a
             full understanding of the simulator may result in undefined behaviour.
         """
-        for signal in self.getSignals().keys():
-            self.__signals[signal].pop()
-
-
-class InputPortBlock(BaseBlock):
-    """
-    The input port of a CBD
-    """
-    def __init__(self, block_name, parent):
-        BaseBlock.__init__(self, block_name, [], ["OUT1"])
-        self._parent = parent
-
-    def compute(self, curIteration):
-        self.appendToSignal(self._parent.getInputSignal(curIteration, self.getBlockName()).value)
-
-    @property
-    def parent(self):
-        return self._parent
-
-
-class OutputPortBlock(BaseBlock):
-    """
-    The output port of a CBD
-    """
-    def __init__(self, block_name, parent):
-        BaseBlock.__init__(self, block_name, ["IN1"], ["OUT1"])
-        self._parent = parent
-
-    def compute(self, curIteration):
-        self.appendToSignal(self.getInputSignal(curIteration, "IN1").value)
-
-    @property
-    def parent(self):
-        return self._parent
-
-
-class WireBlock(BaseBlock):
-    """
-    When a CBD gets flattened, the port blocks will be replaced by a wire block
-    """
-    def __init__(self, block_name):
-        BaseBlock.__init__(self, block_name, ["IN1"], ["OUT1"])
-
-    def compute(self, curIteration):
-        self.appendToSignal(self.getInputSignal(curIteration, "IN1").value)
+        for port in self.getInputPorts() + self.getOutputPorts():
+            port._rewind()
 
 
 class CBD(BaseBlock):
@@ -387,22 +415,6 @@ class CBD(BaseBlock):
         self.__blocksDict = {}
         self.__clock = None
 
-        for input_port in input_ports:
-            self.addBlock(InputPortBlock(input_port, self))
-
-        for output_port in output_ports:
-            self.addBlock(OutputPortBlock(output_port, self))
-
-        # TODO: automatically add Clock?
-
-    def addInputPort(self, name):
-        BaseBlock.addInputPort(self, name)
-        self.addBlock(InputPortBlock(name, self))
-
-    def addOutputPort(self, name):
-        BaseBlock.addOutputPort(self, name)
-        self.addBlock(OutputPortBlock(name, self))
-
     def clone(self):
         other = BaseBlock.clone(self)
         # other.setClock(deepcopy(self.getClock()))
@@ -410,8 +422,8 @@ class CBD(BaseBlock):
         for block in self.getBlocks():
             other.addBlock(block.clone())
         for block in self.getBlocks():
-            for name_input, link in block.getLinksIn().items():
-                other.addConnection(link.block.getBlockName(), block.getBlockName(), name_input, link.output_port)
+            for inp in block.getInputPorts():
+                other.addConnection(inp.block, block, inp.name, inp.getIncoming().source.name)
         return other
 
     def getTopCBD(self):
@@ -420,7 +432,7 @@ class CBD(BaseBlock):
         """
         return self if self._parent is None else self._parent.getTopCBD()
 
-    def flatten(self, parent=None, ignore=None, psep="."):
+    def flatten(self, ignore=None, psep="."):
         """
         Flatten the CBD inline and call recursively for all sub-CBDs.
 
@@ -431,59 +443,22 @@ class CBD(BaseBlock):
                             no blocks are ignored. Defaults to :code:`None`.
             psep (str):     The path separator to use. Defaults to :code:`"."`.
         """
-        if ignore is None:
-            ignore = []
-        blocksToRemove = []
-        blocksToAdd = []
-
-        # First flatten all top-layer blocks to ensure valid port connections
-        for childBlock in self.__blocks:
-            if isinstance(childBlock, CBD) and not childBlock.getBlockType() in ignore:
-                childBlock.flatten(self, ignore, psep)
-                blocksToRemove.append(childBlock)
-
-        for childBlock in self.__blocks:
-            if isinstance(childBlock, InputPortBlock) and parent is not None:
-                # Replace InputPortBlock with WireBlock
-                wb = WireBlock(childBlock.getBlockName())
-
-                # Replace links going out of inputportblock
-                blocksToRemove.append(childBlock)
-                blocksToAdd.append(wb)
-
-                for b in self.getBlocks():
-                    for input_name, output_port in [(x, y.output_port) for (x, y) in b._linksIn.items() if y.block == childBlock]:
-                        b._linksIn[input_name] = InputLink(wb, "OUT1")
-
-                input = self._linksIn[wb.getBlockName()]
-                parent.addConnection(input.block, wb, output_port_name=input.output_port)
-            elif isinstance(childBlock, OutputPortBlock) and parent is not None:
-                # Replace OutputPortBlock with WireBlock
-                wb = WireBlock(childBlock.getBlockName())
-                blocksToRemove.append(childBlock)
-                blocksToAdd.append(wb)
-
-                for (x, y) in childBlock._linksIn.items():
-                    wb._linksIn[x] = y
-
-                # blocks connected to this output
-                for b in parent.__blocks:
-                    for (portname, input) in b._linksIn.items():
-                        if input.block == self and input.output_port == wb.getBlockName():
-                            b._linksIn[portname] = InputLink(wb, "OUT1")
-
-        # Delete blocksToRemove
-        for block in blocksToRemove:
-            self.removeBlock(block)
-
-        for b in blocksToAdd:
-            self.addBlock(b)
-
-        if parent is not None:
-            # Add all components to parent, do not copy blocksToRemove
-            for block in [b for b in self.__blocks if not b in blocksToRemove]:
-                block.setBlockName(self.getBlockName() + psep + block.getBlockName())
-                parent.addBlock(block)
+        if ignore is None: ignore = []
+
+        for block in self.__blocks:
+            if isinstance(block, CBD) and not block.getBlockType() in ignore:
+                block.flatten(ignore, psep)
+                for child in block.getBlocks():
+                    child.setBlockName(child.getPath(psep))
+                    self.addBlock(child)
+                for port in block.getInputPorts() + block.getOutputPorts():
+                    source = port.getIncoming().source
+                    for conn in port.getOutgoing():
+                        target = conn.target
+                        self.addConnection(source.block, target.block, target.name, source.name)
+                        Port.disconnect(port, target)
+                    Port.disconnect(source, port)
+                self.removeBlock(block)
 
     def getBlocks(self):
         """
@@ -570,14 +545,15 @@ class CBD(BaseBlock):
         assert isinstance(block, BaseBlock), "Can only add BaseBlock (subclass) instances to a CBD"
         block.setParent(self)
 
-        if block.getBlockName() not in self.__blocksDict:
+        if block.getBlockName() not in self.__blocksDict and \
+                block.getBlockName() not in self.getOutputPortNames() + self.getInputPortNames():
             self.__blocks.append(block)
             self.__blocksDict[block.getBlockName()] = block
             if isinstance(block, (Clock, DummyClock)):
                 self.__clock = block
         else:
             logger = naivelog.getLogger("CBD")
-            logger.warning("Warning: did not add this block as it has the same name %s as an already existing block" % block.getBlockName())
+            logger.warning("Warning: did not add this block as it has the same name %s as an already existing block/port" % block.getBlockName())
 
     def removeBlock(self, block):
         """
@@ -609,13 +585,21 @@ class CBD(BaseBlock):
                                     :code:`OUT1` will be used.
 
         See Also:
-            :func:`BaseBlock.linkInput`
+            :func:`BaseBlock.linkToInput`
         """
         if isinstance(from_block, str):
-            from_block = self.getBlockByName(from_block)
+            if self.hasInputPortWithName(from_block):
+                from_block = self
+                output_port_name = from_block
+            else:
+                from_block = self.getBlockByName(from_block)
         if isinstance(to_block, str):
-            to_block = self.getBlockByName(to_block)
-        to_block.linkInput(from_block, input_port_name, output_port_name)
+            if self.hasOutputPortWithName(to_block):
+                to_block = self
+                input_port_name = to_block
+            else:
+                to_block = self.getBlockByName(to_block)
+        to_block.linkToInput(from_block, input_port_name, output_port_name)
 
     def removeConnection(self, block, input_port_name):
         """
@@ -691,19 +675,18 @@ class CBD(BaseBlock):
         print("=========== Start of Signals Dump ===========")
         for block in self.getBlocks():
             print("%s:%s" % (block.getBlockName(), block.getBlockType()))
-            print(str(block.getSignal()) + "\n")
+            print(str(block.getSignalHistory()) + "\n")
         print("=========== End of Signals Dump =============\n")
 
-    def getSignal(self, name_output=None):
+    def getSignalHistory(self, name_output=None):
         name_output = "OUT1" if name_output is None else name_output
-        portBlock = self.getBlockByName(name_output)
-        assert portBlock is not None
-        return portBlock.getSignal("OUT1")
+        port = self.getOutputPortByName(name_output)
+        return port.getHistory()
 
     def getSignals(self):
         res = {}
-        for port in super(CBD, self).getSignals().keys():
-            res[port] = self.getSignal(port)
+        for port in self.getOutputPortNames():
+            res[port] = self.getSignalHistory(port)
         return res
 
     def clearSignals(self):

+ 6 - 19
src/CBD/converters/CBDDraw.py

@@ -1,11 +1,8 @@
 """
 Useful drawing function to easily gvDraw a CBD model in Graphviz.
 """
-import re
-from CBD.Core import CBD, InputPortBlock, OutputPortBlock
+from CBD.Core import CBD
 from CBD.lib.std import *
-from CBD.util import hash64
-import sys
 
 
 def gvDraw(cbd, filename, colors=None):
@@ -54,8 +51,6 @@ digraph model {{
 			label = " {}\\n({})\\n{}".format(block.getBlockType(), block.getBlockName(), block.getBlockOperator())
 		elif isinstance(block, ClampBlock) and block._use_const:
 			label = " {}\\n({})\\n[{}, {}]".format(block.getBlockType(), block.getBlockName(), block.min, block.max)
-		elif isinstance(block, (InputPortBlock, OutputPortBlock)):
-			label = block.getBlockName()
 		else:
 			label = block.getBlockType() + "\\n(" + block.getBlockName() + ")"
 
@@ -64,8 +59,6 @@ digraph model {{
 			shape = "Msquare"
 		elif isinstance(block, ConstantBlock):
 			shape = "ellipse"
-		elif isinstance(block, (InputPortBlock, OutputPortBlock)):
-			shape = "none"
 
 		col = ""
 		if block.getBlockName() in colors:
@@ -81,21 +74,15 @@ digraph model {{
 
 	for block in cbd.getBlocks():
 		writeBlock(block)
-		# conn = set()
-		for (name, other) in block.getLinksIn().items():
-			op = other.output_port
+		for in_port in block.getInputPorts():
+			other = in_port.getIncoming().source
+			op = other.name
 			i = "inter_%d_%s" % (id(other.block), op)
-			# conn.add(i)
-			if isinstance(block, OutputPortBlock):
-				name = ""
-			write(" {i} -> {b} [headlabel=\"{inp}\", arrowhead=\"normal\", arrowtail=\"none\", dir=both];\n".format(i=i, b=nodeName(block), inp=name))
-		for op in block.getSignals().keys():
-			if block.getBlockType() == "OutputPortBlock": continue
+			write(" {i} -> {b} [headlabel=\"{inp}\", arrowhead=\"normal\", arrowtail=\"none\", dir=both];\n".format(i=i, b=nodeName(block), inp=in_port.name))
+		for op in block.getOutputPortNames():
 			i = "inter_%d_%s" % (id(block), op)
 			# if i not in conn: continue
 			write(" {i} [shape=point, width=0.01, height=0.01];\n".format(i=i))
-			if isinstance(block, InputPortBlock):
-				op = ""
 			write(" {a} -> {i} [taillabel=\"{out}\", arrowtail=\"invempty\", arrowhead=\"none\", dir=both];\n"\
 			      .format(i=i, a=nodeName(block), out=op))
 

+ 6 - 5
src/CBD/converters/hybrid.py

@@ -250,7 +250,7 @@ class CBDRunner(AtomicDEVS):
 		Args:
 			pname (str):    The name of the port.
 		"""
-		return self.state["CBD"].getBlockByName(pname).getSignal("OUT1")[-1].value
+		return self.state["CBD"].getBlockByName(pname).getSignalHistory("OUT1")[-1].value
 
 	def crossing_detection(self, signal, y, y0):
 		"""
@@ -381,7 +381,7 @@ class CBDRunner(AtomicDEVS):
 
 		old = {}
 		for c in self.crossings:
-			# print("PRE", c, self.state["CBD"].getBlockByName(c).getSignal("OUT1")[-1])
+			# print("PRE", c, self.state["CBD"].getBlockByName(c).getSignalHistory("OUT1")[-1])
 			old[c] = self.get_signal(c)
 
 		self.state["delta_t"] = self.get_delta()
@@ -396,7 +396,7 @@ class CBDRunner(AtomicDEVS):
 			if isinstance(yq, str):
 				y = float(yq[1:])
 				q = yq[0]
-			# print("POST", z, self.state["CBD"].getBlockByName(z).getSignal("OUT1")[-1])
+			# print("POST", z, self.state["CBD"].getBlockByName(z).getSignalHistory("OUT1")[-1])
 			if (old[z] - y) * (value - y) < 0:
 				# A crossing will happen!
 				if q == '=' or (q == '<' and old[z] < value) or (q == '>' and old[z] > value):
@@ -448,6 +448,7 @@ class CBDRunner(AtomicDEVS):
 		self.simulator.stop()
 
 
+# TODO: fix
 def prepare_cbd(model, initials):
 	"""
 	Obtains a CBD model that can be executed with the :class:`CBDRunner`.
@@ -475,8 +476,8 @@ def prepare_cbd(model, initials):
 		cbd.addBlock(ConstantBlock(name, initials.get(name, 0)))
 		for block in model.getBlocks():
 			if block.getBlockType() == "InputPortBlock": continue
-			for k, (B, out) in block.getLinksIn().items():
-				if B.getBlockName() == name:
+			for P in block.getInputPorts():
+				if P.block.getBlockName() == name:
 					cbd.addConnection(name, block.getBlockName(), input_port_name=k)
 
 	# Set clock minimal H

+ 17 - 24
src/CBD/converters/latexify/CBD2Latex.py

@@ -124,34 +124,27 @@ class CBD2Latex:
 				eq = self.get_block_equation(block)
 				for e in eq:
 					self.equations.append(e)
-			elif block.getBlockType() in "WireBlock":
-				path = block.getPath(self.config['path_sep'])
-				lhs = self._rename(path + self.config['path_sep'] + "OUT1")
-				rhs = VarFnc(self._rename(path + self.config['path_sep'] + "IN1"))
-				self.equations.append(Eq(lhs, rhs))
-				self.equations.append(Eq(self._rename(path), VarFnc(lhs)))
-			elif block.getBlockType() in "InputPortBlock":
-				path = block.getPath(self.config['path_sep'])
-				lhs = self._rename(path)
-				rhs = VarFnc(self._rename(path))
-				self.equations.append(Eq(lhs, rhs))
+			# elif block.getBlockType() in "WireBlock":
+			# 	path = block.getPath(self.config['path_sep'])
+			# 	lhs = self._rename(path + self.config['path_sep'] + "OUT1")
+			# 	rhs = VarFnc(self._rename(path + self.config['path_sep'] + "IN1"))
+			# 	self.equations.append(Eq(lhs, rhs))
+			# 	self.equations.append(Eq(self._rename(path), VarFnc(lhs)))
+			# elif block.getBlockType() in "InputPortBlock":
+			# 	path = block.getPath(self.config['path_sep'])
+			# 	lhs = self._rename(path)
+			# 	rhs = VarFnc(self._rename(path))
+			# 	self.equations.append(Eq(lhs, rhs))
 
 		# Add all connections
 		for block in self.model.getBlocks():
-			tp = block.getBlockType()
 			path = block.getPath(self.config['path_sep'])
-			for k, v in block.getLinksIn().items():
-				v_path = self._rename(v.block.getPath(self.config['path_sep']) + self.config['path_sep'] + v.output_port)
-				if v.block.getBlockType() == "InputPortBlock":
-					v_path = self._rename(v.block.getPath(self.config['path_sep']))
-				if tp in ["OutputPortBlock"]:
-					lhs = self._rename(path)
-					rhs = VarFnc(v_path)
-					self.equations.append(Eq(lhs, rhs))
-				else:
-					lhs = self._rename(path + self.config['path_sep'] + k)
-					rhs = VarFnc(v_path)
-					self.equations.append(Eq(lhs, rhs))
+			for in_port in block.getInputPorts():
+				out_port = in_port.getIncoming().source
+				v_path = self._rename(out_port.block.getPath(self.config['path_sep']) + self.config['path_sep'] + out_port.name)
+				lhs = self._rename(path + self.config['path_sep'] + in_port.name)
+				rhs = VarFnc(v_path)
+				self.equations.append(Eq(lhs, rhs))
 
 	def render(self, rl=None):
 		"""

+ 27 - 15
src/CBD/depGraph.py

@@ -5,7 +5,7 @@
 :License:       GNU General Public License
 :Contact:       marc.provost@mail.mcgill.ca
 """
-from .Core import CBD, OutputPortBlock
+from .Core import CBD
 import copy
 
 
@@ -127,8 +127,8 @@ class DepGraph:
 		"""Creates a dependency between two objects.
 
 		Args:
-		   dependent:   The object which depends on the other
-		   influencer:  The object which influences the other
+		   dependent:   The object which depends on the :attr:`influencer`.
+		   influencer:  The object which influences the :attr:`dependent`.
 
 		Raises:
 			ValueError: if depedent or influencer is not member of this graph
@@ -139,22 +139,34 @@ class DepGraph:
 		if isinstance(influencer, CBD) and not self.ignore_hierarchy:
 			# When there is more than one connection from a CBD to one and the same block,
 			# more than one dependency should be set, as there is more than one underlying
-			# output block
-			for output_port in [y.output_port for (x, y) in dependent.getLinksIn().items() if y.block == influencer]:
-				self.setDependency(dependent, influencer.getBlockByName(output_port), curIt)
+			# connection
+			for inp in dependent.getInputPorts():
+				inf_out = inp.getIncoming().source
+				if inf_out.block == influencer:
+					inf = inf_out.getIncoming().source
+					self.setDependency(dependent, inf, curIt)
+			# for output_port in [y.output_port for (x, y) in dependent.getLinksIn().items() if y.block == influencer]:
+			# 	self.setDependency(dependent, influencer.getBlockByName(output_port), curIt)
 			return
 
 		# Link CBD inputs
 		if isinstance(dependent, CBD) and not self.ignore_hierarchy:
-			cbd = dependent
-			directlyConnected = influencer.parent if isinstance(influencer, OutputPortBlock) else influencer
-			inputnames = dependent.getInputName(directlyConnected)
-
-			# When one influencer has multiple connections to this CBD, call this function once fo
-			for inputname in inputnames:
-				inputtingBlock = dependent.getBlockByName(inputname)
-				thisdep = inputtingBlock
-				self.setDependency(thisdep, influencer, curIt)
+			# When there is more than one connection from a CBD to one and the same block,
+			# more than one dependency should be set, as there is more than one underlying
+			# connection
+			for inp in dependent.getInputPorts():
+				inf_out = inp.getIncoming().source
+				if inf_out.block == influencer:
+					for conn in inp.getOutgoing():
+						self.setDependency(conn.target, influencer, curIt)
+			# directlyConnected = influencer
+			# inputnames = dependent.getInputName(directlyConnected)
+			#
+			# # When one influencer has multiple connections to this CBD, call this function once
+			# for inputname in inputnames:
+			# 	inputtingBlock = dependent.getBlockByName(inputname)
+			# 	thisdep = inputtingBlock
+			# 	self.setDependency(thisdep, influencer, curIt)
 			return
 
 		if self.hasMember(dependent) and self.hasMember(influencer):

+ 4 - 4
src/CBD/lib/std.py

@@ -780,7 +780,7 @@ class DelayBlock(BaseBlock):
 		# TO IMPLEMENT: This is a helper function you can use to create the dependency graph
 		# Treat dependencies differently. For instance, at the first iteration (curIteration == 0), the block only depends on the IC;
 		if curIteration == 0:
-			return [self._linksIn["IC"].block]
+			return [self.getInputPortByName("IC").getIncoming().source.block]
 		return []
 
 	def compute(self, curIteration):
@@ -1073,7 +1073,7 @@ class Clock(CBD):
 		"""
 		# FIXME: this will produce wrong signals for all clock inputs and
 		#        all blocks in the clock itself
-		sig = self.getBlockByName("TSum").getSignal("OUT1")
+		sig = self.getBlockByName("TSum").getSignalHistory("OUT1")
 		if curIt == 0 or len(sig) == 0:
 			return self.__start_time
 		return sig[curIt - 1].value
@@ -1089,7 +1089,7 @@ class Clock(CBD):
 		self.getBlockByName("IC").setValue(start_time)
 
 	def getDeltaT(self):
-		dSig = self.getSignal("delta")
+		dSig = self.getSignalHistory("delta")
 		if len(dSig) == 0:
 			return 0.0
 		return dSig[-1].value
@@ -1147,7 +1147,7 @@ class DummyClock(BaseBlock):
 		"""
 		Obtains the current time of the clock.
 		"""
-		sig = self.getSignal("time")
+		sig = self.getSignalHistory("time")
 		if curIt == 0 or len(sig) == 0:
 			return self.__start_time
 		return sig[curIt - 1].value + self.__delta_t

+ 22 - 22
src/CBD/loopsolvers/linearsolver.py

@@ -94,11 +94,11 @@ class LinearSolver(Solver):
 		sep = '.'
 		known = {}
 		for block in strongComponent:
-			for inp, b in block.getLinksIn().items():
-				if b.block not in strongComponent:
-					val = b.block.getSignal(b.output_port)[curIteration].value
+			for inp in block.getInputPorts():
+				if inp.block not in strongComponent:
+					val = inp.getHistory()[curIteration].value
 					known[block.getPath(sep) + sep + inp] = val
-					known[b.block.getPath(sep)] = val
+					known[inp.block.getPath(sep)] = val
 		try:
 			return self.get_matrix(strongComponent, sep, known)
 		except ValueError as e:
@@ -125,7 +125,7 @@ class LinearSolver(Solver):
 
 		# Get list of low-level dependencies from n inputs
 		def getBlockDependencies2(block):
-			return (resolveBlock(b, output_port) for (b, output_port) in [block.getBlockConnectedToInput(x) for x in block.getInputPortNames()])
+			return (resolveBlock(port.block, port.name) for port in [block.getBlockConnectedToInput(x) for x in block.getInputPortNames()])
 
 		for i, block in enumerate(strongComponent):
 			if block.getBlockType() == "AdderBlock":
@@ -160,28 +160,28 @@ class LinearSolver(Solver):
 			elif block.getBlockType() == "NegatorBlock":
 				# M2 can stay 0
 				M1[i, i] = -1
-				possibleDep, output_port = block.getBlockConnectedToInput("IN1")
-				M1[i, indexdict[resolveBlock(possibleDep, output_port)]] = - 1
-			elif block.getBlockType() == "InputPortBlock":
-				# M2 can stay 0
-				M1[i, i] = 1
-				possibleDep, output_port = block.parent.getBlockConnectedToInput(block.getBlockName())
-				M1[i, indexdict[resolveBlock(possibleDep, output_port)]] = - 1
-			elif block.getBlockType() == "OutputPortBlock" or block.getBlockType() == "WireBlock":
-				# M2 can stay 0
-				M1[i, i] = 1
-				dblock = block.getDependencies(0)[0]
-				if isinstance(dblock, CBD):
-					oport = block.getLinksIn()['IN1'].output_port
-					dblock = dblock.getBlockByName(oport).getLinksIn()['IN1'].block
-				M1[i, indexdict[dblock]] = - 1
+				possibleDep = block.getBlockConnectedToInput("IN1")
+				M1[i, indexdict[resolveBlock(possibleDep.block, possibleDep.name)]] = - 1
+			# elif block.getBlockType() == "InputPortBlock":
+			# 	# M2 can stay 0
+			# 	M1[i, i] = 1
+			# 	possibleDep, output_port = block.parent.getBlockConnectedToInput(block.getBlockName())
+			# 	M1[i, indexdict[resolveBlock(possibleDep, output_port)]] = - 1
+			# elif block.getBlockType() == "OutputPortBlock" or block.getBlockType() == "WireBlock":
+			# 	# M2 can stay 0
+			# 	M1[i, i] = 1
+			# 	dblock = block.getDependencies(0)[0]
+			# 	if isinstance(dblock, CBD):
+			# 		oport = block.getLinksIn()['IN1'].output_port
+			# 		dblock = dblock.getBlockByName(oport).getLinksIn()['IN1'].block
+			# 	M1[i, indexdict[dblock]] = - 1
 			elif block.getBlockType() == "DelayBlock":
 				# If a delay is in a strong component, this is the first iteration
 				# And so the dependency is the IC
 				# M2 can stay 0 because we have an equation of the type -x = -ic <=> -x + ic = 0
 				M1[i, i] = -1
-				possibleDep, output_port = block.getBlockConnectedToInput("IC")
-				dependency = resolveBlock(possibleDep, output_port)
+				possibleDep = block.getBlockConnectedToInput("IC")
+				dependency = resolveBlock(possibleDep.block, possibleDep.name)
 				assert dependency in strongComponent
 				M1[i, indexdict[dependency]] = 1
 			else:

+ 1 - 1
src/CBD/loopsolvers/sympysolver.py

@@ -62,7 +62,7 @@ class SympySolver(Solver):
 		for block in component:
 			for x, port in self.__dependencies(block):
 				if x not in component:
-					vrs[x.getPath('_') + '_' + port] = x.getSignal()[curIt].value
+					vrs[x.getPath('_') + '_' + port] = x.getSignalHistory()[curIt].value
 		res = self.__cache[tuple(component)]
 		return res, vrs
 

+ 4 - 4
src/CBD/preprocessing/rungekutta.py

@@ -503,13 +503,13 @@ if __name__ == '__main__':
 	sim.setDeltaT(0.2)
 	sim.run(1.4)
 
-	s = model.getSignal("v")
+	s = model.getSignalHistory("v")
 	L = len(s)
-	errs = model.findBlock("RK.error")[0].getSignal("error")
-	hs = model.findBlock("RK.error")[0].getSignal("h_new")
+	errs = model.findBlock("RK.error")[0].getSignalHistory("error")
+	hs = model.findBlock("RK.error")[0].getSignalHistory("h_new")
 	# errs = hs = s
 
-	# print([x for _, x in model.findBlock("RK.myDelay")[0].getSignal("OUT1")])
+	# print([x for _, x in model.findBlock("RK.myDelay")[0].getSignalHistory("OUT1")])
 
 	import numpy as np
 	print("+------------+------------+------------+------------+------------+------------+")

+ 8 - 2
src/CBD/simulator.py

@@ -524,8 +524,7 @@ class Simulator:
 		if curIt < 2 or self.__sim_data[0] is None:
 			self.__sim_data[0] = createDepGraph(self.model, curIt)
 		self.__sim_data[1] = self.__scheduler.obtain(self.__sim_data[0], curIt, simT)
-		self.__computeBlocks(self.__sim_data[1], self.__sim_data[0], self.__sim_data[2])
-		self.__sim_data[2] += 1
+		self._lcc_compute()
 
 		# State Event Location
 		lcc = float('inf')
@@ -556,10 +555,17 @@ class Simulator:
 		self.signal("poststep", pre, post, self.getTime())
 
 	def _lcc_compute(self):
+		"""
+		Computes the blocks at the current time and increases the iteration counter.
+		Mainly used inside of Level Crossing Detection, hence the name.
+		"""
 		self.__computeBlocks(self.__sim_data[1], self.__sim_data[0], self.__sim_data[2])
 		self.__sim_data[2] += 1
 
 	def _rewind(self):
+		"""
+		Rewinds the simulator to the previous iteration.
+		"""
 		self.__sim_data[2] -= 1
 		self.model._rewind()
 

+ 3 - 3
src/CBD/state_events/locators.py

@@ -67,7 +67,7 @@ class StateEventLocator:
 		Returns:
 			:code:`True` when the crossing happened, otherwise :code:`False`.
 		"""
-		sig = self.sim.model.getSignal(output_name)
+		sig = self.sim.model.getSignalHistory(output_name)
 		if len(sig) < 2:
 			# No crossing possible (yet)
 			return False
@@ -105,7 +105,7 @@ class StateEventLocator:
 		self.sim._rewind()
 		self.setDeltaT(time - self.t_lower)
 		self.sim._lcc_compute()
-		s = self.sim.model.getSignal(output_name)[-1].value - level
+		s = self.sim.model.getSignalHistory(output_name)[-1].value - level
 
 		if noop:
 			self.sim._rewind()
@@ -140,7 +140,7 @@ class StateEventLocator:
 			The detected time at which the crossing is suspected to occur.
 		"""
 
-		sig = self.sim.model.getSignal(output_name)
+		sig = self.sim.model.getSignalHistory(output_name)
 		p1 = sig[-2].time, sig[-2].value - level
 		p2 = sig[-1].time, sig[-1].value - level
 		self.t_lower = p1[0]

+ 9 - 7
src/CBD/tracers/tracerVerbose.py

@@ -21,20 +21,22 @@ class VerboseTracer(BaseTracer):
 
 	def traceCompute(self, curIt, block):
 		text = "\n " + COLOR.colorize(block.getPath(), COLOR.ITALIC, COLOR.CYAN) + ":"
-		inps = block.getLinksIn().items()
+		inps = block.getInputPorts()
 		deps = [x.getBlockName() for x in block.getDependencies(curIt)]
 		if len(inps) > 0:
 			text += "\n\tINPUT VALUES:"
-			for inp, (other, out) in inps:
+			for inp in inps:
+				out = inp.getIncoming().source
+				other = out.block
 				if other in deps:
 					text += "\n\t\t{:>10} -> {:<10} : {}"\
-						.format(other.getBlockName() + "." + out, inp,
-					            COLOR.colorize(str(block.getInputSignal(curIt, inp).value), COLOR.YELLOW))
-		outs = block.getSignals().items()
+						.format(other.getPath() + ":" + out.name, inp.name,
+					            COLOR.colorize(str(inp.getHistory()[curIt]), COLOR.YELLOW))
+		outs = block.getOutputPorts()
 		if len(outs) > 0:
 			if len(inps) > 0:
 				text += "\n"
 			text += "\n\tOUTPUT VALUES:"
-			for out, vals in outs:
-				text += "\n\t\t{:<24} : {}".format(out, COLOR.colorize(str(vals[-1].value), COLOR.YELLOW))
+			for out in outs:
+				text += "\n\t\t{:<24} : {}".format(out.name, COLOR.colorize(str(out.getHistory()[curIt]), COLOR.YELLOW))
 		self.traceln(text)

+ 1 - 1
src/test/basicCBDTest.py

@@ -24,7 +24,7 @@ class BasicCBDTestCase(unittest.TestCase):
 
 	def _getSignal(self, blockname, output_port = None):
 		block = self.CBD.getBlockByName(blockname)
-		signal =  block.getSignal(name_output = output_port)
+		signal =  block.getSignalHistory(name_output = output_port)
 		return [x.value for x in signal]
 
 	def testLinearStrongComponent(self):

+ 1 - 1
src/test/flattenCBDTest.py

@@ -19,7 +19,7 @@ class FlattenCBDTest(unittest.TestCase):
 
 	def _getSignal(self, blockname, output_port = None):
 		block = self.CBD.getBlockByName(blockname)
-		signal =  block.getSignal(name_output = output_port)
+		signal =  block.getSignalHistory(name_output = output_port)
 		return [x.value for x in signal]
 							
 	def testInterCBD(self):

+ 1 - 1
src/test/hierarchyCBDTest.py

@@ -19,7 +19,7 @@ class HierarchyCBDTest(unittest.TestCase):
 
 	def _getSignal(self, blockname, output_port = None):
 		block = self.CBD.getBlockByName(blockname)
-		signal =  block.getSignal(name_output = output_port)
+		signal =  block.getSignalHistory(name_output = output_port)
 		return [x.value for x in signal]
 		
 	def testInterCBD(self):		

+ 1 - 1
src/test/ioCBDTest.py

@@ -22,7 +22,7 @@ class IOCBDTestCase(unittest.TestCase):
 
 	def _getSignal(self, blockname, output_port = None):
 		block = self.CBD.getBlockByName(blockname)
-		signal =  block.getSignal(name_output = output_port)
+		signal =  block.getSignalHistory(name_output = output_port)
 		return [x.value for x in signal]
 
 	def testCSVReaderNormal(self):

+ 1 - 1
src/test/otherCBDTest.py

@@ -20,7 +20,7 @@ class OtherCBDTestCase(unittest.TestCase):
 
 	def _getSignal(self, blockname, output_port = None):
 		block = self.CBD.getBlockByName(blockname)
-		signal =  block.getSignal(name_output = output_port)
+		signal =  block.getSignalHistory(name_output = output_port)
 		return [x.value for x in signal]
 
 	def testMultiRate(self):

+ 1 - 1
src/test/stdCBDTest.py

@@ -24,7 +24,7 @@ class StdCBDTestCase(unittest.TestCase):
 
 	def _getSignal(self, blockname, output_port = None):
 		block = self.CBD.getBlockByName(blockname)
-		signal =  block.getSignal(name_output = output_port)
+		signal =  block.getSignalHistory(name_output = output_port)
 		return [x.value for x in signal]
 
 	def testConstantBlock(self):