|
|
@@ -0,0 +1,900 @@
|
|
|
+import math
|
|
|
+import naivelog
|
|
|
+from collections import namedtuple
|
|
|
+
|
|
|
+InputLink = namedtuple("InputLink", ["block", "output_port"])
|
|
|
+Signal = namedtuple("Signal", ["time", "value"])
|
|
|
+def enum(**enums):
|
|
|
+ return type('Enum', (), enums)
|
|
|
+level = enum(WARNING=1, ERROR=2, FATAL=3)
|
|
|
+epsilon = 0.001
|
|
|
+
|
|
|
+class BaseBlock:
|
|
|
+ """
|
|
|
+ A base class for all types of basic blocks
|
|
|
+ """
|
|
|
+ def __init__(self, name, input_ports, output_ports):
|
|
|
+ self.setBlockName(name)
|
|
|
+
|
|
|
+ #The output signals produced by this block is encoded as 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()
|
|
|
+ for output_port in output_ports:
|
|
|
+ self.__signals[output_port] = []
|
|
|
+
|
|
|
+ self._output_ports = output_ports
|
|
|
+
|
|
|
+ #The input links produced by this block is encoded as a dictionary.
|
|
|
+ #The key of this dictionary is the name of the input port.
|
|
|
+ #Each element of the dictionary contains
|
|
|
+ #an tuple of the block and the output name of the other block.
|
|
|
+ self._linksIn = dict()
|
|
|
+
|
|
|
+ #The list of possible input ports
|
|
|
+ self.__nameLinks = input_ports
|
|
|
+ #In wich CBD the baseblock is situated
|
|
|
+ self._parent = None
|
|
|
+
|
|
|
+ def getOutputPorts(self):
|
|
|
+ return self._output_ports
|
|
|
+
|
|
|
+ def resetSignals(self):
|
|
|
+ for output_port in self.__signals:
|
|
|
+ self.__signals[output_port] = []
|
|
|
+
|
|
|
+ def getBlockName(self):
|
|
|
+ return self.__block_name
|
|
|
+
|
|
|
+ def setBlockName(self, block_name):
|
|
|
+ self.__block_name = block_name
|
|
|
+
|
|
|
+ def setParent(self, parent):
|
|
|
+ self._parent = parent
|
|
|
+
|
|
|
+ def getBlockType(self):
|
|
|
+ return self.__class__.__name__
|
|
|
+
|
|
|
+ def getLinksIn(self):
|
|
|
+ return self._linksIn
|
|
|
+
|
|
|
+ def getOutputNameOfInput(self, inputBlock):
|
|
|
+ return [ y for (x,y) in self._linksIn.iteritems() if y.block == inputBlock ][0].output_port
|
|
|
+
|
|
|
+ def getInputName(self, inputBlock):
|
|
|
+ return [ x for (x,y) in self._linksIn.iteritems() if y.block == inputBlock ]
|
|
|
+
|
|
|
+ def getClock(self):
|
|
|
+ return self._parent.getClock()
|
|
|
+
|
|
|
+ def appendToSignal(self, value, name_output = None):
|
|
|
+ name_output = "OUT1" if name_output == None else name_output
|
|
|
+ assert name_output in self.__signals.keys()
|
|
|
+ self.__signals[name_output].append(Signal(self.getClock().getTime(), value))
|
|
|
+
|
|
|
+ def getSignal(self, name_output = None):
|
|
|
+ name_output = "OUT1" if name_output == None else name_output
|
|
|
+ assert name_output in self.__signals.keys()
|
|
|
+ return self.__signals[name_output] if name_output != None else self.__signals["OUT1"]
|
|
|
+
|
|
|
+ # TODO: probably remove this feature
|
|
|
+ def setSignal(self, value, name_output = None):
|
|
|
+ name_output = "OUT1" if name_output == None else name_output
|
|
|
+ assert name_output in self.__signals.keys()
|
|
|
+ if self.__signals[name_output]:
|
|
|
+ self.__signals[name_output][-1] = Signal(self.__signals[name_output][-1].time, value)
|
|
|
+
|
|
|
+ def getDependencies(self, curIteration):
|
|
|
+ return list(set([ tup.block for tup in self._linksIn.values() ]))
|
|
|
+
|
|
|
+ def getBlockConnectedToInput(self, input_port):
|
|
|
+ return self._linksIn[input_port]
|
|
|
+
|
|
|
+ def getInputSignal(self, curIteration, input_port = None):
|
|
|
+ """
|
|
|
+ Returns the signal sent out by the input block (IN1 if none given,
|
|
|
+ at the last time if no curIteration is given).
|
|
|
+ """
|
|
|
+ input_port = "IN1" if input_port == None else input_port
|
|
|
+ curIteration = -1 if curIteration == None else curIteration
|
|
|
+
|
|
|
+ (incoming_block, out_port_name) = self._linksIn[input_port]
|
|
|
+ return incoming_block.getSignal(out_port_name)[curIteration]
|
|
|
+
|
|
|
+ def compute(self, curIteration):
|
|
|
+ exit("BaseBlock has nothing to compute")
|
|
|
+
|
|
|
+ def linkInput(self, in_block, name_input, name_output):
|
|
|
+ """
|
|
|
+ linkInput will link the output of the from_block to the input of the to_block
|
|
|
+ -if no name_input was given for the to_block, we will derive the right input,
|
|
|
+ by checking which input IN has nothing connected to it yet
|
|
|
+ -if no name_output was given we use the first OUT output
|
|
|
+ """
|
|
|
+ name_output = "OUT1" if name_output == None else name_output
|
|
|
+ if name_input != None:
|
|
|
+ assert name_input in self.__nameLinks
|
|
|
+ self._linksIn[name_input] = InputLink(in_block, name_output)
|
|
|
+ else:
|
|
|
+ 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
|
|
|
+ else:
|
|
|
+ exit("There are no open IN inputs left in block %s" % self.getBlockName())
|
|
|
+ i += 1
|
|
|
+
|
|
|
+ def __repr__(self):
|
|
|
+ repr = self.getBlockName() + ":" + self.getBlockType() + "\n"
|
|
|
+ if len(self._linksIn) == 0:
|
|
|
+ repr+= " No incoming connections to IN ports\n"
|
|
|
+ else:
|
|
|
+ for (key, (in_block, out_port)) in self._linksIn.iteritems():
|
|
|
+ repr += "In input " + key + ": IN <- " + in_block.getBlockName() + ":" + in_block.getBlockType() + "\n"
|
|
|
+ return repr
|
|
|
+
|
|
|
+class ConstantBlock(BaseBlock):
|
|
|
+ """
|
|
|
+ The constant block will always output its constant value
|
|
|
+ """
|
|
|
+ def __init__(self, block_name, value=0.0):
|
|
|
+ BaseBlock.__init__(self, block_name, [], ["OUT1"])
|
|
|
+ self.__value = value
|
|
|
+
|
|
|
+ def getValue(self):
|
|
|
+ return self.__value
|
|
|
+
|
|
|
+ def setValue(self, value):
|
|
|
+ self.__value = value
|
|
|
+
|
|
|
+ def compute(self, curIteration):
|
|
|
+ self.appendToSignal(self.getValue())
|
|
|
+
|
|
|
+ def __repr__(self):
|
|
|
+ return BaseBlock.__repr__(self) + " Value = " + str(self.getValue()) + "\n"
|
|
|
+
|
|
|
+class NegatorBlock(BaseBlock):
|
|
|
+ """
|
|
|
+ The negator block will output the value of the input multiplied with -1
|
|
|
+ """
|
|
|
+ def __init__(self, block_name):
|
|
|
+ BaseBlock.__init__(self, block_name, ["IN1"], ["OUT1"])
|
|
|
+
|
|
|
+ def compute(self, curIteration):
|
|
|
+ self.appendToSignal(-self.getInputSignal(curIteration).value)
|
|
|
+
|
|
|
+class InverterBlock(BaseBlock):
|
|
|
+ """
|
|
|
+ The invertblock will output 1/IN
|
|
|
+ """
|
|
|
+ def __init__(self, block_name):
|
|
|
+ BaseBlock.__init__(self, block_name, ["IN1"], ["OUT1"])
|
|
|
+
|
|
|
+ def compute(self, curIteration):
|
|
|
+ self.appendToSignal(1.0/self.getInputSignal(curIteration).value)
|
|
|
+
|
|
|
+class AdderBlock(BaseBlock):
|
|
|
+ """
|
|
|
+ The adderblock will add the 2 inputs
|
|
|
+ """
|
|
|
+ def __init__(self, block_name):
|
|
|
+ BaseBlock.__init__(self, block_name, ["IN1", "IN2"], ["OUT1"])
|
|
|
+
|
|
|
+ def compute(self, curIteration):
|
|
|
+ self.appendToSignal(self.getInputSignal(curIteration, "IN1").value + self.getInputSignal(curIteration, "IN2").value)
|
|
|
+
|
|
|
+class ProductBlock(BaseBlock):
|
|
|
+ """
|
|
|
+ The product block will multiply the two inputs
|
|
|
+ """
|
|
|
+ def __init__(self, block_name):
|
|
|
+ BaseBlock.__init__(self, block_name, ["IN1", "IN2"], ["OUT1"])
|
|
|
+
|
|
|
+ def compute(self, curIteration):
|
|
|
+ self.appendToSignal(self.getInputSignal(curIteration, "IN1").value * self.getInputSignal(curIteration, "IN2").value)
|
|
|
+
|
|
|
+class GenericBlock(BaseBlock):
|
|
|
+ """
|
|
|
+ The generic block will evaluate the operator on the input
|
|
|
+ operator is the name (a string) of a Python function from the math library
|
|
|
+ which will be called when the block is evaluated
|
|
|
+ by default, initialized to None
|
|
|
+ """
|
|
|
+ def __init__(self, block_name, block_operator=None):
|
|
|
+ # operator is the name (a string) of a Python function from the math library
|
|
|
+ BaseBlock.__init__(self, block_name, ["IN1"], ["OUT1"])
|
|
|
+ self.__block_operator = block_operator
|
|
|
+
|
|
|
+ def getBlockOperator(self):
|
|
|
+ return self.__block_operator
|
|
|
+
|
|
|
+ def compute(self, curIteration):
|
|
|
+ operator = getattr(math, self.getBlockOperator())
|
|
|
+ self.appendToSignal(operator(self.getInputSignal(curIteration).value))
|
|
|
+
|
|
|
+ def __repr__(self):
|
|
|
+ repr = BaseBlock.__repr__(self)
|
|
|
+ if self.__block_operator == None:
|
|
|
+ repr += " No operator given\n"
|
|
|
+ else:
|
|
|
+ repr += " Operator :: " + self.__block_operator + "\n"
|
|
|
+ return repr
|
|
|
+
|
|
|
+class RootBlock(BaseBlock):
|
|
|
+ """
|
|
|
+ A basic block that computes the IN2-th root from IN1
|
|
|
+ """
|
|
|
+ def __init__(self, block_name):
|
|
|
+ BaseBlock.__init__(self, block_name, ["IN1", "IN2"], ["OUT1"])
|
|
|
+
|
|
|
+ def compute(self, curIteration):
|
|
|
+ self.appendToSignal(pow(self.getInputSignal(curIteration, "IN1").value, 1.0/self.getInputSignal(curIteration, "IN2").value))
|
|
|
+
|
|
|
+class ModuloBlock(BaseBlock):
|
|
|
+ """
|
|
|
+ A basic block that computes the IN1 modulo IN3
|
|
|
+ """
|
|
|
+ def __init__(self, block_name):
|
|
|
+ BaseBlock.__init__(self, block_name, ["IN1", "IN2"], ["OUT1"])
|
|
|
+
|
|
|
+ def compute(self, curIteration):
|
|
|
+ self.appendToSignal(math.fmod(self.getInputSignal(curIteration, "IN1").value, self.getInputSignal(curIteration, "IN2").value))
|
|
|
+
|
|
|
+class DelayBlock(BaseBlock):
|
|
|
+ """
|
|
|
+ A delay block that takes the last value from the list
|
|
|
+ IC: Initial Condition
|
|
|
+ """
|
|
|
+ def __init__(self, block_name):
|
|
|
+ BaseBlock.__init__(self, block_name, ["IN1", "IC"], ["OUT1"])
|
|
|
+ self.__values = []
|
|
|
+
|
|
|
+ def getDependencies(self, curIteration):
|
|
|
+ if curIteration == 0:
|
|
|
+ return [self._linksIn["IC"].block]
|
|
|
+ return []
|
|
|
+
|
|
|
+ def resetSignals(self):
|
|
|
+ BaseBlock.resetSignals(self)
|
|
|
+ self.__values = []
|
|
|
+
|
|
|
+ def compute(self, curIteration):
|
|
|
+ if curIteration == 0:
|
|
|
+ outputValue = self.getInputSignal(curIteration, "IC")
|
|
|
+ self.appendToSignal(outputValue.value)
|
|
|
+ else:
|
|
|
+ assert curIteration == len(self.__values)+1
|
|
|
+ (incoming_block, out_port_name) = self._linksIn["IN1"]
|
|
|
+ print incoming_block.getSignal(out_port_name)
|
|
|
+ self.__values.append(incoming_block.getSignal(out_port_name)[curIteration-1])
|
|
|
+ self.appendToSignal(self.__values[curIteration-1].value)
|
|
|
+
|
|
|
+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)
|
|
|
+
|
|
|
+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)
|
|
|
+
|
|
|
+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)
|
|
|
+
|
|
|
+class TimeBlock(BaseBlock):
|
|
|
+ """
|
|
|
+ Outputs the current time of the simulation
|
|
|
+ """
|
|
|
+ def __init__(self, block_name):
|
|
|
+ BaseBlock.__init__(self, block_name, [], ["OUT1"])
|
|
|
+
|
|
|
+ def compute(self, curIteration):
|
|
|
+ self.appendToSignal(self.getClock().getTime())
|
|
|
+
|
|
|
+class LessThanBlock(BaseBlock):
|
|
|
+ """
|
|
|
+ A simple block that will test if the IN1 is smaller than IC (output == 1 if true else 0)
|
|
|
+ """
|
|
|
+ def __init__(self, block_name):
|
|
|
+ BaseBlock.__init__(self, block_name, ["IC", "IN1"], ["OUT1"])
|
|
|
+
|
|
|
+ def compute(self, curIteration):
|
|
|
+ gisv = lambda s: self.getInputSignal(curIteration, s).value
|
|
|
+ self.appendToSignal(1 if gisv("IN1") < gisv("IC") else 0)
|
|
|
+
|
|
|
+class EqualsBlock(BaseBlock):
|
|
|
+ """
|
|
|
+ A simple block that will test if the IN1 is equal to IC (output == 1 if true else 0)
|
|
|
+ """
|
|
|
+ def __init__(self, block_name):
|
|
|
+ BaseBlock.__init__(self, block_name, ["IC", "IN1"], ["OUT1"])
|
|
|
+
|
|
|
+ def compute(self, curIteration):
|
|
|
+ gisv = lambda s: self.getInputSignal(curIteration, s).value
|
|
|
+ self.appendToSignal(1 if gisv("IN1") == gisv("IC") else 0)
|
|
|
+
|
|
|
+class NotBlock(BaseBlock):
|
|
|
+ """
|
|
|
+ A simple Not block that will set a 0 to 1 and vice versa
|
|
|
+ """
|
|
|
+ def __init__(self, block_name):
|
|
|
+ BaseBlock.__init__(self, block_name, ["IN1"], ["OUT1"])
|
|
|
+
|
|
|
+ def compute(self, curIteration):
|
|
|
+ result = 0 if self.getInputSignal(curIteration, "IN1").value else 1
|
|
|
+ self.appendToSignal(result)
|
|
|
+
|
|
|
+class OrBlock(BaseBlock):
|
|
|
+ """
|
|
|
+ A simple Or block with possibly multiple inputlines
|
|
|
+ """
|
|
|
+ def __init__(self, block_name, numberOfInputs=2):
|
|
|
+ BaseBlock.__init__(self, block_name, ["IN{0}".format(i) for i in xrange(1,numberOfInputs+1)], ["OUT1"])
|
|
|
+ self.__numberOfInputs = numberOfInputs
|
|
|
+
|
|
|
+ def compute(self, curIteration):
|
|
|
+ result = 0
|
|
|
+ for i in xrange(1, self.__numberOfInputs+1):
|
|
|
+ result = result or self.getInputSignal(curIteration, "IN"+str(i)).value
|
|
|
+ self.appendToSignal(result)
|
|
|
+
|
|
|
+class AndBlock(BaseBlock):
|
|
|
+ """
|
|
|
+ A simple And block with possibly multiple inputlines
|
|
|
+ """
|
|
|
+ def __init__(self, block_name, numberOfInputs=2):
|
|
|
+ BaseBlock.__init__(self, block_name, ["IN{0}".format(i) for i in xrange(1,numberOfInputs+1)], ["OUT1"])
|
|
|
+ self.__numberOfInputs = numberOfInputs
|
|
|
+
|
|
|
+ def compute(self, curIteration):
|
|
|
+ result = 1
|
|
|
+ for i in xrange(1, self.__numberOfInputs+1):
|
|
|
+ result = result and self.getInputSignal(curIteration, "IN"+str(i)).value
|
|
|
+ self.appendToSignal(result)
|
|
|
+
|
|
|
+class SequenceBlock(BaseBlock):
|
|
|
+ """
|
|
|
+ A simple Sequence block: block initializes signal input with given sequence
|
|
|
+ Use only for the tests please
|
|
|
+ """
|
|
|
+ def __init__(self, block_name, sequence):
|
|
|
+ BaseBlock.__init__(self, block_name, [], ["OUT1"])
|
|
|
+ self.__sequence = sequence
|
|
|
+
|
|
|
+ def compute(self, curIteration):
|
|
|
+ if len(self.__sequence) < curIteration:
|
|
|
+ self.__logger.fatal("Sequence is not long enough")
|
|
|
+ self.appendToSignal(self.__sequence[curIteration])
|
|
|
+
|
|
|
+class LoggingBlock(BaseBlock):
|
|
|
+ """
|
|
|
+ A simple Logging block
|
|
|
+ """
|
|
|
+ def __init__(self, block_name, string, lev = level.WARNING):
|
|
|
+ BaseBlock.__init__(self, block_name, ["IN1"], [])
|
|
|
+ self.__string = string
|
|
|
+ self.__logger = naivelog.getLogger("WarningLog")
|
|
|
+ self.__lev = lev
|
|
|
+
|
|
|
+ def compute(self, curIteration):
|
|
|
+ if self.getInputSignal(curIteration, "IN1").value == 1:
|
|
|
+ if self.__lev == level.WARNING:
|
|
|
+ self.__logger.warning("Time " + str(self.getClock().getTime()) + ": " + self.__string)
|
|
|
+ elif self.__lev == level.ERROR:
|
|
|
+ self.__logger.error("Time " + str(self.getClock().getTime()) + ": " + self.__string)
|
|
|
+ elif self.__lev == level.FATAL:
|
|
|
+ self.__logger.fatal("Time " + str(self.getClock().getTime()) + ": " + self.__string)
|
|
|
+
|
|
|
+class Clock:
|
|
|
+ """
|
|
|
+ The clock of the simulation
|
|
|
+ delta_t is the timestep of the simulation
|
|
|
+ """
|
|
|
+ def __init__(self, delta_t):
|
|
|
+ self.__delta_t = delta_t
|
|
|
+ self.__time = 0.0
|
|
|
+
|
|
|
+ def getTime(self):
|
|
|
+ return self.__time
|
|
|
+
|
|
|
+ def step(self):
|
|
|
+ self.__time = self.__time + self.__delta_t
|
|
|
+
|
|
|
+ def setDeltaT(self, new_delta_t):
|
|
|
+ self.__delta_t = new_delta_t
|
|
|
+
|
|
|
+ def getDeltaT(self):
|
|
|
+ return self.__delta_t
|
|
|
+
|
|
|
+class CBD(BaseBlock):
|
|
|
+ """
|
|
|
+ The CBD class, contains an entire Causal Block Diagram
|
|
|
+ Call the run function to simulate the model.
|
|
|
+ """
|
|
|
+ def __init__(self, block_name, input_ports = None, output_ports = None):
|
|
|
+ input_ports = input_ports if input_ports != None else []
|
|
|
+ output_ports = output_ports if output_ports != None else []
|
|
|
+ BaseBlock.__init__(self, block_name, input_ports, output_ports)
|
|
|
+ #The blocks in the CBD will be stored both
|
|
|
+ #-as an ordered list __blocks and
|
|
|
+ #-as a dictionary __blocksDict with the blocknames as keys
|
|
|
+ #for fast name-based retrieval and to ensure block names are unique within a single CBD
|
|
|
+ self.__blocks = []
|
|
|
+ self.__blocksDict = {}
|
|
|
+ self.__clock = None
|
|
|
+ self.__deltaT = None
|
|
|
+ self.__logger = naivelog.getLogger("CBD")
|
|
|
+
|
|
|
+ for input_port in input_ports:
|
|
|
+ self.addBlock(InputPortBlock(input_port, self))
|
|
|
+
|
|
|
+ for output_port in output_ports:
|
|
|
+ self.addBlock(OutputPortBlock(output_port, self))
|
|
|
+
|
|
|
+ def getTopCBD(self):
|
|
|
+ return self if self._parent == None else self._parent.getTopCBD()
|
|
|
+
|
|
|
+ def setBlocks(self, blocks):
|
|
|
+ # blocks must be a list of BaseBlock (subclass) instances
|
|
|
+ assert type(blocks) == list, ("CBD.setBlocks() takes a list as argument, not a %s" % type(blocks))
|
|
|
+ for block in blocks:
|
|
|
+ assert isinstance(block, BaseBlock), "CBD.setBlocks() takes a list of BaseBlock (subclass) instances"
|
|
|
+
|
|
|
+ def getBlocks(self):
|
|
|
+ return self.__blocks
|
|
|
+
|
|
|
+ def getBlockByName(self, name):
|
|
|
+ return self.__blocksDict[name]
|
|
|
+
|
|
|
+ def setClock(self, clock):
|
|
|
+ self.__clock = clock;
|
|
|
+
|
|
|
+ def getClock(self):
|
|
|
+ return self.__clock if self._parent == None else self._parent.getClock()
|
|
|
+
|
|
|
+ def setDeltaT(self, deltaT):
|
|
|
+ self.__deltaT = deltaT
|
|
|
+
|
|
|
+ def addBlock(self, block):
|
|
|
+ """
|
|
|
+ Add a block to the CBD model
|
|
|
+ """
|
|
|
+ assert isinstance(block, BaseBlock), "Can only add BaseBlock (subclass) instances to a CBD"
|
|
|
+ block.setParent(self)
|
|
|
+
|
|
|
+ if not self.__blocksDict.has_key(block.getBlockName()):
|
|
|
+ self.__blocks.append(block)
|
|
|
+ self.__blocksDict[block.getBlockName()] = block
|
|
|
+ else:
|
|
|
+ print("Warning: did not add this block as it has the same name %s as an existing block" % block.getBlockName())
|
|
|
+
|
|
|
+ def removeBlock(self, block):
|
|
|
+ assert isinstance(block, BaseBlock), "Can only delete BaseBlock (subclass) instances to a CBD"
|
|
|
+
|
|
|
+ if self.__blocksDict.has_key(block.getBlockName()):
|
|
|
+ self.__blocks.remove(block)
|
|
|
+ del self.__blocksDict[block.getBlockName()]
|
|
|
+ else:
|
|
|
+ exit("Warning: did not remove this block %s as it was not found" % block.getBlockName())
|
|
|
+
|
|
|
+ def addConnection(self, from_block, to_block, input_port_name = None, output_port_name = None):
|
|
|
+ """
|
|
|
+ Add a connection between from_block with input_port_name to to_block with outport_port_name
|
|
|
+ """
|
|
|
+ if type(from_block) == str:
|
|
|
+ from_block = self.getBlockByName(from_block)
|
|
|
+ if type(to_block) == str:
|
|
|
+ to_block = self.getBlockByName(to_block)
|
|
|
+ to_block.linkInput(from_block, input_port_name, output_port_name)
|
|
|
+
|
|
|
+ def __repr__(self):
|
|
|
+ repr = BaseBlock.__repr__(self)
|
|
|
+ repr += "\n"
|
|
|
+ for block in self.getBlocks():
|
|
|
+ repr+= block.__repr__()
|
|
|
+ return repr
|
|
|
+
|
|
|
+ def dump(self):
|
|
|
+ print("=========== Start of Model Dump ===========")
|
|
|
+ print(self)
|
|
|
+ print("=========== End of Model Dump =============\n")
|
|
|
+
|
|
|
+ def dumpSignals(self):
|
|
|
+ print("=========== Start of Signals Dump ===========")
|
|
|
+ for block in self.getBlocks():
|
|
|
+ print("%s:%s" % (block.getBlockName(), block.getBlockType()))
|
|
|
+ print(str(block.getSignal()) + "\n")
|
|
|
+ print("=========== End of Signals Dump =============\n")
|
|
|
+
|
|
|
+ def resetSignals(self):
|
|
|
+ for block in self.getBlocks():
|
|
|
+ block.resetSignals()
|
|
|
+
|
|
|
+ def getSignal(self, name_output = None):
|
|
|
+ name_output = "OUT1" if name_output == None else name_output
|
|
|
+ portBlock = self.getBlockByName(name_output)
|
|
|
+ assert portBlock != None
|
|
|
+ return portBlock.getSignal("OUT1")
|
|
|
+
|
|
|
+ # TODO: probably remove this feature
|
|
|
+ def setSignal(self, value, name_output = None):
|
|
|
+ name_output = "OUT1" if name_output == None else name_output
|
|
|
+ portBlock = self.getBlockByName(name_output)
|
|
|
+ assert portBlock != None
|
|
|
+ portBlock.setSignal(value, "OUT1")
|
|
|
+
|
|
|
+
|
|
|
+class AddOneBlock(CBD):
|
|
|
+ """
|
|
|
+ Block adds a one to the input (used a lot for mux)
|
|
|
+ """
|
|
|
+ def __init__(self, block_name, faultOrder=3):
|
|
|
+ CBD.__init__(self, block_name, ["IN1"], ["OUT1"])
|
|
|
+ self.addBlock(ConstantBlock(block_name="OneConstant", value=1))
|
|
|
+ self.addBlock(AdderBlock("PlusOne"))
|
|
|
+ self.addConnection("IN1", "PlusOne")
|
|
|
+ self.addConnection("OneConstant", "PlusOne")
|
|
|
+ self.addConnection("PlusOne", "OUT1")
|
|
|
+
|
|
|
+class DerivatorBlock(CBD):
|
|
|
+ """
|
|
|
+ The derivator block is a CBD that calculates the derivative
|
|
|
+ """
|
|
|
+ def __init__(self, block_name):
|
|
|
+ CBD.__init__(self, block_name, ["IN1", "delta_t", "IC"], ["OUT1"])
|
|
|
+ self.addBlock(ProductBlock(block_name="multIc"))
|
|
|
+ self.addBlock(NegatorBlock(block_name="neg1"))
|
|
|
+ self.addBlock(AdderBlock(block_name="sum1"))
|
|
|
+ self.addBlock(DelayBlock(block_name="delay"))
|
|
|
+ self.addBlock(NegatorBlock(block_name="neg2"))
|
|
|
+ self.addBlock(AdderBlock(block_name="sum2"))
|
|
|
+ self.addBlock(ProductBlock(block_name="mult"))
|
|
|
+ self.addBlock(InverterBlock(block_name="inv"))
|
|
|
+
|
|
|
+ self.addConnection("IC", "multIc")
|
|
|
+ self.addConnection("delta_t", "multIc")
|
|
|
+ self.addConnection("multIc", "neg1")
|
|
|
+ self.addConnection("neg1", "sum1")
|
|
|
+ self.addConnection("IN1", "sum1")
|
|
|
+ self.addConnection("sum1", "delay", input_port_name="IC")
|
|
|
+ self.addConnection("IN1", "delay", input_port_name="IN1")
|
|
|
+ self.addConnection("delay", "neg2")
|
|
|
+ self.addConnection("neg2", "sum2")
|
|
|
+ self.addConnection("IN1", "sum2")
|
|
|
+ self.addConnection("sum2", "mult")
|
|
|
+ self.addConnection("delta_t", "inv")
|
|
|
+ self.addConnection("inv", "mult")
|
|
|
+ self.addConnection("mult", "OUT1")
|
|
|
+
|
|
|
+
|
|
|
+class IntegratorBlock(CBD):
|
|
|
+ """
|
|
|
+ The integrator block is a CBD that calculates the integration
|
|
|
+ """
|
|
|
+ def __init__(self, block_name):
|
|
|
+ CBD.__init__(self, block_name, ["IN1", "delta_t", "IC"], ["OUT1"])
|
|
|
+ self.addBlock(ConstantBlock(block_name="zero", value=0))
|
|
|
+ self.addBlock(DelayBlock(block_name="delayIn"))
|
|
|
+ self.addBlock(ProductBlock(block_name="multDelta"))
|
|
|
+ self.addBlock(DelayBlock(block_name="delayState"))
|
|
|
+ self.addBlock(AdderBlock(block_name="sumState"))
|
|
|
+
|
|
|
+ self.addConnection("zero", "delayIn", input_port_name="IC")
|
|
|
+ self.addConnection("IN1", "delayIn", input_port_name="IN1")
|
|
|
+ self.addConnection("delayIn", "multDelta")
|
|
|
+ self.addConnection("delta_t", "multDelta")
|
|
|
+ self.addConnection("multDelta", "sumState")
|
|
|
+ self.addConnection("IC", "delayState", input_port_name="IC")
|
|
|
+ self.addConnection("delayState", "sumState")
|
|
|
+ self.addConnection("sumState", "delayState", input_port_name="IN1")
|
|
|
+ self.addConnection("sumState", "OUT1")
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+""" This module implements a dependency graph
|
|
|
+ @author: Marc Provost
|
|
|
+ @organization: McGill University
|
|
|
+ @license: GNU General Public License
|
|
|
+ @contact: marc.provost@mail.mcgill.ca
|
|
|
+"""
|
|
|
+
|
|
|
+import copy
|
|
|
+class DepNode:
|
|
|
+ """ Class implementing a node in the dependency graph.
|
|
|
+ """
|
|
|
+
|
|
|
+ def __init__(self, object):
|
|
|
+ """ DepNode's constructor.
|
|
|
+ @param object: Reference to a semantic object identifying the node
|
|
|
+ @type object: Object
|
|
|
+ """
|
|
|
+ self.__object = object
|
|
|
+ self.__isMarked = False
|
|
|
+
|
|
|
+ def mark(self):
|
|
|
+ self.__isMarked = True
|
|
|
+
|
|
|
+ def unMark(self):
|
|
|
+ self.__isMarked = False
|
|
|
+
|
|
|
+ def isMarked(self):
|
|
|
+ return self.__isMarked
|
|
|
+
|
|
|
+ def getMappedObj(self):
|
|
|
+ return self.__object
|
|
|
+
|
|
|
+ def __repr__(self):
|
|
|
+ return "DepNode :: "+str(self.__object)
|
|
|
+
|
|
|
+class DepGraph:
|
|
|
+ """ Class implementing dependency graph.
|
|
|
+ """
|
|
|
+
|
|
|
+ def __init__(self):
|
|
|
+ """ DepGraph's constructor.
|
|
|
+ """
|
|
|
+ #Dict holding a mapping "Object -> DepNode"
|
|
|
+ self.__semanticMapping = {}
|
|
|
+
|
|
|
+ #map object->list of objects depending on object
|
|
|
+ self.__dependents = {}
|
|
|
+ #map object->list of objects that influences object
|
|
|
+ self.__influencers = {}
|
|
|
+
|
|
|
+ def __repr__(self):
|
|
|
+ repr = "Dependents: \n"
|
|
|
+ for dep in self.__dependents:
|
|
|
+ repr += dep.getBlockName() + ":" + str(self.__dependents[dep]) + "\n"
|
|
|
+ repr += "Influencers: \n"
|
|
|
+ for infl in self.__influencers:
|
|
|
+ repr += infl.getBlockName() + ":" + str(self.__influencers[infl]) + "\n"
|
|
|
+ return repr
|
|
|
+
|
|
|
+ def addMember(self, object):
|
|
|
+ """ Add an object mapped to this graph.
|
|
|
+ @param object: the object to be added
|
|
|
+ @type object: Object
|
|
|
+ @raise ValueError: If object is already in the graph
|
|
|
+ """
|
|
|
+ if not self.hasMember(object):
|
|
|
+ if not isinstance(object, CBD):
|
|
|
+ node = DepNode(object)
|
|
|
+ self.__dependents[object] = []
|
|
|
+ self.__influencers[object] = []
|
|
|
+ self.__semanticMapping[object] = node
|
|
|
+ else:
|
|
|
+ for block in object.getBlocks():
|
|
|
+ self.addMember(block)
|
|
|
+ else:
|
|
|
+ raise ValueError("Specified object is already member of this graph")
|
|
|
+
|
|
|
+ def hasMember(self, object):
|
|
|
+ return self.__semanticMapping.has_key(object)
|
|
|
+
|
|
|
+ def removeMember(self, object):
|
|
|
+ """ Remove a object from this graph.
|
|
|
+ @param object: the object to be removed
|
|
|
+ @type object: Object
|
|
|
+ @raise ValueError: If object is not in the graph
|
|
|
+ """
|
|
|
+ if self.hasMember(object):
|
|
|
+ for dependent in self.getDependents(object):
|
|
|
+ self.__influencers[dependent].remove(object)
|
|
|
+ for influencer in self.getInfluencers(object):
|
|
|
+ self.__dependents[influencer].remove(object)
|
|
|
+
|
|
|
+ del self.__dependents[object]
|
|
|
+ del self.__influencers[object]
|
|
|
+ del self.__semanticMapping[object]
|
|
|
+ else:
|
|
|
+ raise ValueError("Specified object is not member of this graph")
|
|
|
+
|
|
|
+ def setDependency(self, dependent, influencer, curIt):
|
|
|
+ """
|
|
|
+ Creates a dependency between two objects.
|
|
|
+ @param dependent: The object which depends on the other
|
|
|
+ @param influcencer: The object which influences the other
|
|
|
+ @type dependent: Object
|
|
|
+ @type dependent: Object
|
|
|
+ @raise ValueError: if depedent or influencer is not member of this graph
|
|
|
+ @raise ValueError: if the dependency already exists
|
|
|
+ """
|
|
|
+
|
|
|
+ # Link CBD outputs
|
|
|
+ if isinstance(influencer, CBD):
|
|
|
+ # 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().iteritems() if y.block == influencer ]:
|
|
|
+ self.setDependency(dependent, influencer.getBlockByName(output_port), curIt)
|
|
|
+ return
|
|
|
+
|
|
|
+ # Link CBD inputs
|
|
|
+ if isinstance(dependent, CBD):
|
|
|
+ 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)
|
|
|
+ return
|
|
|
+
|
|
|
+ if self.hasMember(dependent) and self.hasMember(influencer):
|
|
|
+ if not influencer in self.__influencers[dependent] and\
|
|
|
+ not dependent in self.__dependents[influencer]:
|
|
|
+ self.__influencers[dependent].append(influencer)
|
|
|
+ self.__dependents[influencer].append(dependent)
|
|
|
+ else:
|
|
|
+ if not self.hasMember(dependent):
|
|
|
+ raise ValueError("Specified dependent object is not member of this graph")
|
|
|
+ if not self.hasMember(influencer):
|
|
|
+ print(influencer)
|
|
|
+ raise ValueError("Specified influencer object is not member of this graph")
|
|
|
+
|
|
|
+ def hasDependency(self, dependent, influencer):
|
|
|
+ if self.hasMember(dependent) and self.hasMember(influencer):
|
|
|
+ return influencer in self.__influencers[dependent] and\
|
|
|
+ dependent in self.__dependents[influencer]
|
|
|
+ else:
|
|
|
+ if not self.hasMember(dependent):
|
|
|
+ raise ValueError("Specified dependent object is not member of this graph")
|
|
|
+ if not self.hasMember(influencer):
|
|
|
+ raise ValueError("Specified influencer object is not member of this graph")
|
|
|
+
|
|
|
+ def unsetDependency(self, dependent, influencer):
|
|
|
+ """ Removes a dependency between two objects.
|
|
|
+ @param dependent: The object which depends on the other
|
|
|
+ @param influcencer: The object which influences the other
|
|
|
+ @type dependent: Object
|
|
|
+ @type dependent: Object
|
|
|
+ @raise ValueError: if depedent or influencer is not member of this graph
|
|
|
+ @raise ValueError: if the dependency does not exists
|
|
|
+ """
|
|
|
+ if self.hasMember(dependent) and self.hasMember(influencer):
|
|
|
+ if influencer in self.__influencers[dependent] and\
|
|
|
+ dependent in self.__dependents[influencer]:
|
|
|
+ self.__influencers[dependent].remove(influencer)
|
|
|
+ self.__dependents[influencer].remove(dependent)
|
|
|
+ else:
|
|
|
+ raise ValueError("Specified dependency does not exists")
|
|
|
+ else:
|
|
|
+ if not self.hasMember(dependent):
|
|
|
+ raise ValueError("Specified dependent object is not member of this graph")
|
|
|
+ if not self.hasMember(influencer):
|
|
|
+ raise ValueError("Specified influencer object is not member of this graph")
|
|
|
+
|
|
|
+ def getDependents(self, object):
|
|
|
+ if self.hasMember(object):
|
|
|
+ return copy.copy(self.__dependents[object])
|
|
|
+ else:
|
|
|
+ raise ValueError("Specified object is not member of this graph")
|
|
|
+
|
|
|
+ def getInfluencers(self, object):
|
|
|
+ if self.hasMember(object):
|
|
|
+ return copy.copy(self.__influencers[object])
|
|
|
+ else:
|
|
|
+ raise ValueError("Specified object is not member of this graph")
|
|
|
+
|
|
|
+ def getStrongComponents(self, curIt = 1):
|
|
|
+ return self.__strongComponents(curIt)
|
|
|
+
|
|
|
+ def __getDepNode(self, object):
|
|
|
+ if self.hasMember(object):
|
|
|
+ return self.__semanticMapping[object]
|
|
|
+ else:
|
|
|
+ raise ValueError("Specified object is not a member of this graph")
|
|
|
+
|
|
|
+ def __mark(self, object):
|
|
|
+ self.__getDepNode(object).mark()
|
|
|
+
|
|
|
+ def __unMark(self, object):
|
|
|
+ self.__getDepNode(object).unMark()
|
|
|
+
|
|
|
+ def __isMarked(self, object):
|
|
|
+ return self.__getDepNode(object).isMarked()
|
|
|
+
|
|
|
+ def __topoSort(self):
|
|
|
+ """ Performs a topological sort on the graph.
|
|
|
+ """
|
|
|
+ for object in self.__semanticMapping.keys():
|
|
|
+ self.__unMark(object)
|
|
|
+
|
|
|
+ sortedList = []
|
|
|
+
|
|
|
+ for object in self.__semanticMapping.keys():
|
|
|
+ if not self.__isMarked(object):
|
|
|
+ self.__dfsSort(object, sortedList)
|
|
|
+
|
|
|
+ return sortedList
|
|
|
+
|
|
|
+ def __dfsSort(self, object, sortedList):
|
|
|
+ """ Performs a depth first search collecting
|
|
|
+ the objects in topological order.
|
|
|
+ @param object: the currently visited object.
|
|
|
+ @param sortedList: partial sorted list of objects
|
|
|
+ @type object: Object
|
|
|
+ @type sortedList: list Of Object
|
|
|
+ """
|
|
|
+
|
|
|
+ if not self.__isMarked(object):
|
|
|
+ self.__mark(object)
|
|
|
+
|
|
|
+ for influencer in self.getInfluencers(object):
|
|
|
+ self.__dfsSort(influencer, sortedList)
|
|
|
+
|
|
|
+ sortedList.append(object)
|
|
|
+
|
|
|
+ def __strongComponents(self, curIt):
|
|
|
+ """ Determine the strong components of the graph
|
|
|
+ @rtype: list of list of Object
|
|
|
+ """
|
|
|
+ strongComponents = []
|
|
|
+ sortedList = self.__topoSort()
|
|
|
+
|
|
|
+ for object in self.__semanticMapping.keys():
|
|
|
+ self.__unMark(object)
|
|
|
+
|
|
|
+ sortedList.reverse()
|
|
|
+
|
|
|
+ for object in sortedList:
|
|
|
+ if not self.__isMarked(object):
|
|
|
+ component = []
|
|
|
+ self.__dfsCollect(object, component, curIt)
|
|
|
+ strongComponents.append(component)
|
|
|
+
|
|
|
+ strongComponents.reverse()
|
|
|
+ return strongComponents
|
|
|
+
|
|
|
+ def __dfsCollect(self, object, component, curIt):
|
|
|
+ """ Collects objects member of a strong component.
|
|
|
+ @param object: Node currently visited
|
|
|
+ @param component: current component
|
|
|
+ @type object: Object
|
|
|
+ @type component: List of Object
|
|
|
+ """
|
|
|
+ if not self.__isMarked(object):
|
|
|
+ self.__mark(object)
|
|
|
+
|
|
|
+ for dependent in self.getDependents(object):
|
|
|
+ self.__dfsCollect(dependent, component, curIt)
|
|
|
+
|
|
|
+ component.append(object)
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|