瀏覽代碼

Update presentation with CBD example.

bentleyjoakes 6 年之前
父節點
當前提交
2130882bce
共有 8 個文件被更改,包括 1970 次插入27 次删除
  1. 251 0
      .gitignore
  2. 75 27
      ComputationNotebookParadigm.ipynb
  3. 7 0
      README.md
  4. 98 0
      cbd/BouncingBallCBD.py
  5. 546 0
      cbd/CBDBlocks.py
  6. 216 0
      cbd/CBDDraw.py
  7. 776 0
      cbd/CBDsimulator.py
  8. 1 0
      environment.yml

+ 251 - 0
.gitignore

@@ -0,0 +1,251 @@
+.idea/
+
+# Byte-compiled / optimized / DLL files
+__pycache__/
+*.py[cod]
+*$py.class
+
+# C extensions
+*.so
+
+# Distribution / packaging
+.Python
+build/
+develop-eggs/
+dist/
+downloads/
+eggs/
+.eggs/
+lib/
+lib64/
+parts/
+sdist/
+var/
+wheels/
+pip-wheel-metadata/
+share/python-wheels/
+*.egg-info/
+.installed.cfg
+*.egg
+MANIFEST
+
+# PyInstaller
+#  Usually these files are written by a python script from a template
+#  before PyInstaller builds the exe, so as to inject date/other infos into it.
+*.manifest
+*.spec
+
+# Installer logs
+pip-log.txt
+pip-delete-this-directory.txt
+
+# Unit test / coverage reports
+htmlcov/
+.tox/
+.nox/# Byte-compiled / optimized / DLL files
+__pycache__/
+*.py[cod]
+*$py.class
+
+# C extensions
+*.so
+
+# Distribution / packaging
+.Python
+build/
+develop-eggs/
+dist/
+downloads/
+eggs/
+.eggs/
+lib/
+lib64/
+parts/
+sdist/
+var/
+wheels/
+pip-wheel-metadata/
+share/python-wheels/
+*.egg-info/
+.installed.cfg
+*.egg
+MANIFEST
+
+# PyInstaller
+#  Usually these files are written by a python script from a template
+#  before PyInstaller builds the exe, so as to inject date/other infos into it.
+*.manifest
+*.spec
+
+# Installer logs
+pip-log.txt
+pip-delete-this-directory.txt
+
+# Unit test / coverage reports
+htmlcov/
+.tox/
+.nox/
+.coverage
+.coverage.*
+.cache
+nosetests.xml
+coverage.xml
+*.cover
+.hypothesis/
+.pytest_cache/
+
+# Translations
+*.mo
+*.pot
+
+# Django stuff:
+*.log
+local_settings.py
+db.sqlite3
+db.sqlite3-journal
+
+# Flask stuff:
+instance/
+.webassets-cache
+
+# Scrapy stuff:
+.scrapy
+
+# Sphinx documentation
+docs/_build/
+
+# PyBuilder
+target/
+
+# Jupyter Notebook
+.ipynb_checkpoints
+
+# IPython
+profile_default/
+ipython_config.py
+
+# pyenv
+.python-version
+
+# pipenv
+#   According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
+#   However, in case of collaboration, if having platform-specific dependencies or dependencies
+#   having no cross-platform support, pipenv may install dependencies that don't work, or not
+#   install all needed dependencies.
+#Pipfile.lock
+
+# celery beat schedule file
+celerybeat-schedule
+
+# SageMath parsed files
+*.sage.py
+
+# Environments
+.env
+.venv
+env/
+venv/
+ENV/
+env.bak/
+venv.bak/
+
+# Spyder project settings
+.spyderproject
+.spyproject
+
+# Rope project settings
+.ropeproject
+
+# mkdocs documentation
+/site
+
+# mypy
+.mypy_cache/
+.dmypy.json
+dmypy.json
+
+# Pyre type checker
+.pyre/
+
+.coverage
+.coverage.*
+.cache
+nosetests.xml
+coverage.xml
+*.cover
+.hypothesis/
+.pytest_cache/
+
+# Translations
+*.mo
+*.pot
+
+# Django stuff:
+*.log
+local_settings.py
+db.sqlite3
+db.sqlite3-journal
+
+# Flask stuff:
+instance/
+.webassets-cache
+
+# Scrapy stuff:
+.scrapy
+
+# Sphinx documentation
+docs/_build/
+
+# PyBuilder
+target/
+
+# Jupyter Notebook
+.ipynb_checkpoints
+
+# IPython
+profile_default/
+ipython_config.py
+
+# pyenv
+.python-version
+
+# pipenv
+#   According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
+#   However, in case of collaboration, if having platform-specific dependencies or dependencies
+#   having no cross-platform support, pipenv may install dependencies that don't work, or not
+#   install all needed dependencies.
+#Pipfile.lock
+
+# celery beat schedule file
+celerybeat-schedule
+
+# SageMath parsed files
+*.sage.py
+
+# Environments
+.env
+.venv
+env/
+venv/
+ENV/
+env.bak/
+venv.bak/
+
+# Spyder project settings
+.spyderproject
+.spyproject
+
+# Rope project settings
+.ropeproject
+
+# mkdocs documentation
+/site
+
+# mypy
+.mypy_cache/
+.dmypy.json
+dmypy.json
+
+# Pyre type checker
+.pyre/
+

File diff suppressed because it is too large
+ 75 - 27
ComputationNotebookParadigm.ipynb


+ 7 - 0
README.md

@@ -0,0 +1,7 @@
+## The Computational Notebook Paradigm for Multi-Paradigm Modeling
+
+Contains the presentation shown at MPM4CPS 2019 and the paper.
+
+### Binder link
+
+[![Binder](https://mybinder.org/badge_logo.svg)](https://mybinder.org/v2/git/http%3A%2F%2Fmsdl.uantwerpen.be%2Fgit%2Fbentley%2FNotebookParadigmPaper2019-presentation.git/master)

+ 98 - 0
cbd/BouncingBallCBD.py

@@ -0,0 +1,98 @@
+from math import *
+import pylab as P
+from cbd.CBDBlocks import *
+from cbd.CBDsimulator import *
+from cbd.CBDDraw import draw_to_file
+
+class BouncingBallCBD(CBD):
+
+    def __init__(self, block_name):
+        # Don't forget to call the superclass' constructor!
+        CBD.__init__(self, block_name)
+
+        # Only for hard-coded Integrator and Derivative
+        self.delta_t = 0.001
+
+        # Blocks can be instantiated and added immediately to the CBD
+
+
+        #VELOCITY BLOCKS
+        self.addBlock(ConstantBlock(block_name="grav", value=-9.8))
+        self.addBlock(ConstantBlock(block_name="init_velo", value=0))
+        self.addBlock(GainBlock(block_name="rest_constant", gain=-0.8))
+        self.addBlock(IntegratorBlock(block_name="velo_integ"))
+
+        #POSITION BLOCKS
+        self.addBlock(ConstantBlock(block_name="init_height", value=100))
+        self.addBlock(IntegratorBlock(block_name="position_integ"))
+
+        self.addBlock(TestBlock(block_name="bounce_test", expr="<=", switch_value=0))
+        self.addBlock(ConstantBlock(block_name="test_true", value=1))
+        self.addBlock(ConstantBlock(block_name="test_false", value=0))
+
+        self.addBlock(ConstantBlock(block_name="pos_reset", value=0.01))
+
+        #SCOPE BLOCK
+        self.addBlock(ScopeBlock(block_name="scope"))
+
+        #VELOCITY CONNECTIONS
+        self.addConnection("grav", "velo_integ")
+        self.addConnection("init_velo", "velo_integ", inport_name="IC")
+
+        self.addConnection("velo_integ", "rest_constant")
+        self.addConnection("rest_constant", "velo_integ", inport_name="init_val")
+        self.addConnection("bounce_test", "velo_integ", inport_name="reset")
+
+        # POSITION CONNECTIONS
+        self.addConnection("velo_integ", "position_integ")
+        self.addConnection("init_height", "position_integ", inport_name="IC")
+
+        self.addConnection("position_integ", "bounce_test")
+        self.addConnection("test_true", "bounce_test", inport_name="TRUE")
+        self.addConnection("test_false", "bounce_test", inport_name="FALSE")
+
+        self.addConnection("bounce_test", "position_integ", inport_name="reset")
+        self.addConnection("pos_reset", "position_integ", inport_name="init_val")
+
+        self.addConnection("position_integ", "scope")
+
+    def simulate(self, steps=20000):
+        sim = CBDsimulator(self)
+        for i in range(steps):
+            sim.step()
+
+    def draw(self):
+        draw_to_file("bbCBD", self, show=True)
+
+    def draw_scopes(self):
+        for block in self.getBlocks():
+            if isinstance(block, ScopeBlock):
+                self.plotSignal(block.getBlockName(), block.getSignal(), write_to_file=True)
+
+    def plotSignal(self, block_name, signal, write_to_file=False):
+        fig = P.figure(figsize=(3, 2), )
+        P.clf()
+        t = [x*self.delta_t for x in range(len(signal))]
+        P.plot(t, signal)
+        P.ylabel('Height (m)')
+        P.xlabel('Step')
+        P.suptitle('CBD Bouncing Ball')
+
+        if write_to_file:
+            plt.savefig(block_name + '.png')
+        else:
+            P.show()
+
+    def plotAll(self):
+        for block in bbCBD.getBlocks():
+            self.plotSignal(block.getBlockName(), block.getSignal(), write_to_file=False)
+
+if __name__ == "__main__":
+    bbCBD = BouncingBallCBD("myCBD")
+    bbCBD.draw()
+    bbCBD.simulate()
+    bbCBD.draw_scopes()
+
+
+
+

+ 546 - 0
cbd/CBDBlocks.py

@@ -0,0 +1,546 @@
+import matplotlib.pyplot as plt
+
+
+#Blocks for the old CBD formalism
+#Some parts are obsolete, while others need to be moved over to the SimulinkCBD formalism (especially flattening, strong components, and topological sorting)
+
+
+
+class BaseBlock:
+
+  # A base class for all types of CBD blocks
+
+  def __init__(self, name):
+    self.setBlockName(name)
+
+    # The output signal produced by this block is encoded as an ordered list of values
+    self.__signal = []
+
+    # List of blocks that are linked to this block via indistinguishable inputs.
+    # The time-delay (Delay, Integrator and Derivative) and Test blocks are different
+    # as they have input ports that need to be distinguished
+    # (IC for Delay/Integrator/Derivative blocks and TRUE_in/FALSE_in for Test blocks).
+    # Each element of the list is a tuple containing
+    # (1) the block whose output this input is connected to, and
+    # (2) the name (a String) of the signal connecting the input block and self
+    self.linksIN = []
+
+
+    # List of blocks that are linked to this block via indistinguishable inputs.
+    # Note that multiple connections may be made from the output of
+    # any block. They will all have the same signal[] and the same name (that of the block)
+    self.linksOUT = []
+
+
+    self.Trigger = None
+
+  def getBlockName(self):
+    return self.__block_name
+
+  def setBlockName(self, block_name):
+    self.__block_name = block_name
+
+
+  def getBlockType(self):
+    return self.__class__.__name__
+
+  def appendToSignal(self, value):
+    self.__signal.append(value)
+
+  def getSignal(self):
+    return self.__signal
+
+  def linkInput(self, in_block):
+    self.linksIN.insert(0,in_block)
+
+  def linkOutput(self, out_block):
+    self.linksOUT.insert(0,out_block)
+
+
+  def __repr__(self):
+    repr = self.getBlockName() + ":" + self.getBlockType() + "\n"
+
+    if len(self.linksIN) == 0:
+      repr+= "  No incoming connections to IN port\n"
+    else:
+      for in_block in self.linksIN:
+        repr += "  IN <- " + in_block.getBlockName() + ":" + in_block.getBlockType() + "\n"
+
+    if len(self.linksOUT) == 0:
+      repr+= "  No outgoing connections from OUT port\n"
+    else:
+      for in_block in self.linksOUT:
+        repr += "  OUT <- " + in_block.getBlockName() + ":" + in_block.getBlockType() + "\n"
+
+    if self.Trigger == None:
+        repr += "  No incoming connections to Trigger port\n"
+    else:
+        repr += "  Trig <- " + self.Trigger.getBlockName() + ":" + self.Trigger.getBlockType() + "\n"
+    return repr
+
+class InputBlock(BaseBlock):
+    def __init__(self, block_name):
+        BaseBlock.__init__(self, block_name)
+
+class OutputBlock(BaseBlock):
+    def __init__(self, block_name):
+        BaseBlock.__init__(self,block_name)
+
+class SequenceRepeater(BaseBlock):
+    def __init__(self, block_name, seq = 1):
+        BaseBlock.__init__(self,block_name)
+        if seq == 1:
+            self.seq = [1,1,2,3]
+        else:
+            self.seq = [1,2,3,4]
+        self.iteration = 0
+class FloorBlock(BaseBlock):
+    def __init__(self,block_name):
+        BaseBlock.__init__(self,block_name)
+
+class ScopeBlock(BaseBlock):
+    def __init__(self, block_name):
+        BaseBlock.__init__(self,block_name)
+
+class ConstantBlock(BaseBlock):
+  def __init__(self, block_name, value=0.0):
+
+    BaseBlock.__init__(self, block_name)
+    self.__value = value
+
+  def getValue(self):
+    return self.__value
+
+  def setValue(self, value):
+    self.__value = value
+
+  def __repr__(self):
+    return BaseBlock.__repr__(self) + "  Value = " + str(self.getValue()) + "\n"
+
+
+class NegatorBlock(BaseBlock):
+  def __init__(self, block_name):
+    BaseBlock.__init__(self, block_name)
+
+  def linkInput(self, in_block):
+    if len(self.linksIN) >= 1:
+      print("Warning: Negator (block %s) takes exactly one input, ignoring extra connection (from block %s)"\
+             % (self.getBlockName(), in_block.getBlockName()))
+    else:
+      self.linksIN.append(in_block)
+
+class InverterBlock(BaseBlock):
+  def __init__(self, block_name):
+    BaseBlock.__init__(self, block_name)
+
+  def linkInput(self, in_block):
+    if len(self.linksIN) >= 1:
+      print("Warning: Inverter (block %s) takes exactly one input, ignoring extra connection (from block %s)"\
+             % (self.getBlockName(), in_block.getBlockName()))
+    else:
+      self.linksIN.append(in_block)
+
+
+class AdderBlock(BaseBlock):
+  def __init__(self, block_name):
+    BaseBlock.__init__(self, block_name)
+
+
+class ProductBlock(BaseBlock):
+  def __init__(self, block_name):
+    BaseBlock.__init__(self, block_name)
+
+class GainBlock(BaseBlock):
+  def __init__(self, block_name, gain = 1.0):
+
+    BaseBlock.__init__(self, block_name)
+    self.gain = gain
+
+class PowerBlock(BaseBlock):
+  def __init__(self, block_name, power=2.0):
+
+    BaseBlock.__init__(self, block_name)
+    self.power = power
+
+
+class ModuloBlock(BaseBlock):
+  def __init__(self, block_name, div = 2.0):
+    BaseBlock.__init__(self, block_name)
+    self.div = div
+
+
+class GenericBlock(BaseBlock):
+  def __init__(self, block_name, block_operator=None):
+    # 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
+    BaseBlock.__init__(self, block_name)
+    self.__block_operator = block_operator
+
+  def getBlockOperator(self):
+    return self.__block_operator
+
+  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 DelayBlock(BaseBlock):
+  def __init__(self, block_name):
+    BaseBlock.__init__(self, block_name)
+
+    # The block whose output is connected to the IC port, and
+    # None if no block connected to the IC port
+    self.IC = None
+
+  def linkInput(self, in_block):
+    if len(self.linksIN) >= 1:
+      print("Warning: Delay (block %s) takes exactly one input, ignoring extra connection (from block %s)"\
+             % (self.getBlockName(), in_block.getBlockName()))
+    else:
+      self.linksIN.append(in_block)
+
+  def __repr__(self):
+    repr = BaseBlock.__repr__(self)
+    if self.IC == None:
+      repr += "  No incoming connection to IC port\n"
+    else:
+      repr += "  IC <- " + self.IC.getBlockName() + ":" + self.IC.getBlockType() + "\n"
+    return repr
+
+class TimeDelayBlock(DelayBlock):
+  # serves as a base class for Integrator and Derivative
+  def __init__(self, block_name, time_increment=0.001):
+    DelayBlock.__init__(self, block_name)
+    self.__timeIncrement = time_increment
+
+    self.reset = None
+    self.init_val = None
+
+  def getTimeIncrement(self):
+    return self.__timeIncrement
+
+  def __repr__(self):
+    return DelayBlock.__repr__(self) #+ "  Time increment :: " + str(self.__timeIncrement)
+
+
+class IntegratorBlock(TimeDelayBlock):
+  def __init__(self, block_name, time_increment=0.001):
+    TimeDelayBlock.__init__(self, block_name, time_increment=time_increment)
+
+  def __repr__(self):
+     print("Reset: " + self.reset.__repr__())
+     print("Init Val: " + self.init_val.__repr__())
+     return TimeDelayBlock.__repr__(self)
+
+class DerivativeBlock(TimeDelayBlock):
+  def __init__(self, block_name, time_increment=0.001):
+    TimeDelayBlock.__init__(self, block_name, time_increment=time_increment)
+
+  def __repr__(self):
+     return TimeDelayBlock.__repr__(self)
+
+class TestBlock(BaseBlock):
+  def __init__(self, block_name,expr = "=", switch_value = 0.0):
+    BaseBlock.__init__(self, block_name)
+    # The block whose output is connected to the TRUE_in port, and
+    # None if no block connected to the TRUE_in port
+    self.TRUE_in = None
+    # The block whose output is connected to the FALSE_in port, and
+    # None if no block connected to the FALSE_in port
+    self.FALSE_in = None
+
+    self.expr = expr # possible expressions = > < >= <=
+
+    self.switch_value = switch_value
+
+  def __repr__(self):
+    repr = BaseBlock.__repr__(self)
+    if self.TRUE_in == None:
+      repr += "  No incoming connection to TRUE_in port\n"
+    else:
+      repr += "  TRUE_in <- " + self.TRUE_in.getBlockName() + ":" + self.TRUE_in.getBlockType() + "\n"
+    if self.FALSE_in == None:
+      repr += "  No incoming connection to FALSE_in port\n"
+    else:
+      repr += "  FALSE_in <- " + self.FALSE_in.getBlockName() + ":" + self.FALSE_in.getBlockType() + "\n"
+    return repr
+
+class CBD(BaseBlock):
+  # The CBD class, contains an entire Causal Block Diagram
+  def __init__(self, block_name):
+    BaseBlock.__init__(self, block_name)
+    # 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 = {}
+
+    #TODO: move to flattening optimization
+    self.__subsystems = []
+    self.__inportsParentsDict = {} # dictionary of inportsParent with inportnames (== inputblock names) as keys
+
+    self.__outportsDependentsDict = {} # dictionary of the parents of outports and the dependents of the outports. Outport names are keys
+
+
+  def setBlocks(self, blocks):
+    # blocks must be a list of BaseBlock (subclass) instances
+    if type(blocks) == list:
+      for block in blocks:
+        if not isinstance(block, BaseBlock):
+           exit("CBD.setBlocks() takes a list of BaseBlock (subclass) instances")
+    else:
+      exit("CBD.setBlocks() takes a list as argument, not a %s" % type(blocks))
+
+  def getBlocks(self):
+    return self.__blocks
+
+  def getBlockByName(self, name):
+    try:
+        return self.__blocksDict[name]
+    except Exception:
+        return None
+
+  def addBlock(self, block):
+    if not isinstance(block, BaseBlock):
+      exit("Can only add BaseBlock (subclass) instances to a CBD")
+
+    if not block.getBlockName() in self.__blocksDict:
+      self.__blocks.append(block)
+      self.__blocksDict[block.getBlockName()] = block
+      if isinstance(block,CBD):
+          self.__subsystems.append(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, debug=False):
+    if debug:
+        print("Removing: " + block.getBlockName())
+
+    for parent in block.linksIN:
+        if debug:
+            print("Removing from parent: " + parent.getBlockName())
+        parent.linksOUT.remove(block)
+
+    #TODO: handle other ports
+    for child in block.linksOUT:
+        if debug:
+            print("Removing from child: " + child.getBlockName())
+        child.linksIN.remove(block)
+
+    del self.__blocksDict[block.getBlockName()]
+    self.__blocks.remove(block)
+
+  def addConnection(self, from_block, to_block, inport_name=None, outport_name=None):
+    if type(from_block) == str:
+      from_block = self.getBlockByName(from_block)
+    if type(to_block) == str:
+      to_block = self.getBlockByName(to_block)
+
+    #add the block to the output list
+    from_block.linkOutput(to_block)
+
+
+    if inport_name == None and outport_name == None:
+      to_block.linkInput(from_block)
+
+    elif isinstance(to_block, CBD) and isinstance(from_block,CBD):
+        to_block.__inportsParentsDict[inport_name] = (from_block,outport_name)
+        if outport_name in from_block.__outportsDependentsDict:
+            from_block.__outportsDependentsDict[outport_name].append((to_block,inport_name))
+        else:
+            from_block.__outportsDependentsDict[outport_name] = [(to_block,inport_name)]
+        to_block.linkInput(from_block)
+
+    elif isinstance(to_block, CBD):# CBD block has multiple "unknown" inputblocks, they will be connected by name of inputport
+        to_block.__inportsParentsDict[inport_name] = (from_block,outport_name)
+        to_block.linkInput(from_block)
+
+    elif isinstance(from_block,CBD):
+        if inport_name == "Trig":
+            to_block.Trigger = from_block
+        else:
+            if isinstance(to_block,DelayBlock):
+                if inport_name == "IC":
+                    to_block.IC = from_block
+                else:
+                    to_block.linkInput(from_block)
+            elif isinstance(to_block,TestBlock):
+                if inport_name == "TRUE":
+                    to_block.TRUE_in = from_block
+                elif inport_name == "FALSE":
+                    to_block.FALSE_in = from_block
+                else:
+                    to_block.linkInput(from_block)
+            else:
+                to_block.linkInput(from_block)
+
+        if outport_name in from_block.__outportsDependentsDict:
+            from_block.__outportsDependentsDict[outport_name].append((to_block,inport_name))
+        else:
+            from_block.__outportsDependentsDict[outport_name] = ([(to_block,inport_name)])
+
+    elif inport_name == "Trig":
+        print("TrigConnect" +  from_block.getBlockName() + "Triggers" + to_block.getBlockName())
+        to_block.Trigger = from_block
+
+    elif isinstance(to_block, DelayBlock): # DelayBlock, DerivativeBlock, IntegratorBlock have one named port
+      if inport_name == "IC":
+        to_block.IC = from_block
+      elif inport_name == "reset":
+        to_block.reset = from_block
+      elif inport_name == "init_val":
+        to_block.init_val = from_block
+      else: exit("Invalid inport_name '%s' for DelayBlock\n should be IC" % inport_name)
+
+    elif isinstance(to_block, TestBlock): # TestBlock has two named ports
+      if inport_name == "TRUE":
+        to_block.TRUE_in = from_block
+      elif inport_name == "FALSE":
+        to_block.FALSE_in = from_block
+      else: exit("Invalid inport_name '%s' for TestBlock\n should be TRUE or FALSE" % inport_name)
+
+    else:
+      exit("addConnection to non-existing named port '%s'" % inport_name)
+
+
+  def __repr__(self):
+    if len(self.__inportsParentsDict) > 0:
+        repr = self.getBlockName() + ":" + self.getBlockType() + "\n"
+        for key in self.__inportsParentsDict:
+            repr += "  " + key + "<-" + self.__inportsParentsDict[key][0].getBlockName() + ":" + self.__inportsParentsDict[key][0].getBlockType() + "\n"
+    else:
+        repr = BaseBlock.__repr__(self)
+        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 showScopes(self,time):
+      for block in self.getBlocks():
+          #print "TIME len", len(time)
+          #print "Signal len", len(block.getSignal())
+          if isinstance(block,ScopeBlock):
+              plt.plot(time,block.getSignal())
+              plt.show()
+
+  def getSignalDict(self):
+    dict = {}
+    for block in self.getBlocks():
+       dict[block.getBlockName()] = block.getSignal()
+    return dict
+
+  def flatten(self):
+    for subsystem in self.__subsystems:
+       #print "SUB", subsystem.getBlockName()
+       if len(subsystem.__subsystems) > 0:
+           subsystem.flatten()
+
+       for block in subsystem.getBlocks():
+           #print "BLOCK", block.getBlockName()
+           if isinstance(block, InputBlock)or isinstance(block,OutputBlock):
+               pass
+           else:
+               block.setBlockName(subsystem.getBlockName()+"_"+block.getBlockName())
+               self.addBlock(block)
+
+       for block in subsystem.getBlocks():
+                for influencer in block.linksIN:
+                    if isinstance(influencer,InputBlock):
+                        block.linksIN.remove(influencer)
+                        if isinstance(subsystem.__inportsParentsDict[influencer.getBlockName()][0],CBD):
+                            self.addConnection(subsystem.__inportsParentsDict[influencer.getBlockName()][0].getBlockName(),block.getBlockName(), outport_name= subsystem.__inportsParentsDict[influencer.getBlockName()][1])
+                        else:
+                            self.addConnection(subsystem.__inportsParentsDict[influencer.getBlockName()][0].getBlockName(),block.getBlockName())
+
+                if isinstance(block,DelayBlock):
+                    if isinstance(block.IC,InputBlock):
+                        if isinstance(subsystem.__inportsParentsDict[block.IC.getBlockName()][0],CBD):
+                            self.addConnection(subsystem.__inportsParentsDict[block.IC.getBlockName()][0].getBlockName(),block.getBlockName(),inport_name= "IC", outport_name= subsystem.__inportsParentsDict[influencer.getBlockName()][1])
+                        else:
+                            self.addConnection(subsystem.__inportsParentsDict[block.IC.getBlockName()][0].getBlockName(),block.getBlockName(), inport_name= "IC")
+
+                elif isinstance(block, TestBlock):
+                    if isinstance(block.TRUE_in, InputBlock):
+                        if isinstance(subsystem.__inportsParentsDict[block.TRUE_in.getBlockName()][0],CBD):
+                            self.addConnection(subsystem.__inportsParentsDict[block.TRUE_in.getBlockName()][0].getBlockName(),block.getBlockName(),inport_name= "TRUE", outport_name= subsystem.__inportsParentsDict[influencer.getBlockName()][1])
+                        else:
+                            self.addConnection(subsystem.__inportsParentsDict[block.TRUE_in.getBlockName()][0].getBlockName(),block.getBlockName(), inport_name= "TRUE")
+                    elif isinstance(block.FALSE_in, InputBlock):
+                        if isinstance(subsystem.__inportsParentsDict[block.FALSE_in.getBlockName()][0],CBD):
+                            self.addConnection(subsystem.__inportsParentsDict[block.FALSE_in.getBlockName()][0].getBlockName(),block.getBlockName(),inport_name= "FALSE", outport_name= subsystem.__inportsParentsDict[influencer.getBlockName()][1])
+                        else:
+                            self.addConnection(subsystem.__inportsParentsDict[block.FALSE_in.getBlockName()][0].getBlockName(),block.getBlockName(), inport_name= "FALSE")
+
+                elif isinstance(block, OutputBlock):
+                    blockInfluencer = block.linksIN[0]
+                    if block.getBlockName() in subsystem.__outportsDependentsDict.keys():
+                        for (dependent, inport_name) in subsystem.__outportsDependentsDict[block.getBlockName()]:
+
+                            if isinstance(dependent, CBD):
+                                self.addConnection(blockInfluencer.getBlockName(),dependent.getBlockName(), inport_name= inport_name)
+                            else:
+                                if inport_name == None:
+                                    dependent.linksIN.remove(subsystem)
+                                    self.addConnection(blockInfluencer.getBlockName(),dependent.getBlockName())
+                                else:
+                                    if isinstance(dependent, DelayBlock) or isinstance(dependent,TestBlock):
+                                        self.addConnection(blockInfluencer.getBlockName(),dependent.getBlockName(), inport_name = inport_name)
+
+    for subsystem in self.__subsystems:
+        self.__blocks.remove(subsystem)
+        del self.__blocksDict[subsystem.getBlockName()]
+
+
+
+
+
+
+
+
+# To add: extensive conformance check of a model with the CBD meta-model.
+# This, to avoid building nonsense models and above all to avoid
+# passing such a model to a simulator.
+# Best to add a checkConformance() method to CBD to be called by the simulator
+# before starting.
+
+
+# A note on hierarchically nested blocks (HV 31/10/2012).
+#
+# This used to be implemented in the ChildBlock class.
+#
+# As an entire CBD is now also a class, which specializes
+# Baseblock, there is no more need for ChildBlock.
+#
+# For hierarchical modelling, the following need to be added:
+#
+# 1. input- and output-ports for entire CBDs. These will
+#    be modelled as InputBlock and OutputBlock classes.
+#
+# 2. a flatten() method which takes as input a hierarchical
+#    CBD and produces as output a flattended CBD.
+#    A flattened CBD will have unique names for all blocks and signals
+#    obtained by "_"-separated concatenation of all block
+#    names, starting from the hierarchical model's root.
+#    A flattened CBD will no longer have CBD instances in it.
+#    All InputBlock and OutputBlock instances will have been
+#    replaced by direct connections between blocks in the flattened model.
+

+ 216 - 0
cbd/CBDDraw.py

@@ -0,0 +1,216 @@
+from IPython.display import Image, display
+import os
+import subprocess
+
+try:
+	import pydot
+
+	has_pydot = True
+except ImportError:
+	print("pydot not installed")
+	has_pydot = False
+
+from cbd.CBDBlocks import *
+
+def draw_to_file(name, cbd, show=False):
+	if not has_pydot:
+		print("No pydot")
+		return
+
+	if cbd is None:
+		print("graph_to_dot Error: Empty CBD")
+		return
+
+	vattr = ''
+	nodes = {}
+	graph = pydot.Dot(name, graph_type='digraph')
+
+
+	for i, block in enumerate(cbd.getBlocks()):
+
+		block_type = block.getBlockType()
+
+		vattr = block.getBlockName()#block_type + str(i)
+
+		fillcolor = get_fill_color(block_type)
+
+		if "Constant" in block_type:
+			try:
+				vattr += "\\n Value= " + str(str(block.getValue()))
+			except KeyError:
+				pass
+		elif "Gain" in block_type:
+			try:
+				vattr += "\\n Value= " + str(str(block.gain))
+			except KeyError:
+				pass
+		elif "Test" in block_type:
+			try:
+				vattr += "\\n Test= " + str(block.expr) + " "+ str(block.switch_value)
+			except KeyError:
+				pass
+
+
+		nodes[block.getBlockName()] = pydot.Node(vattr, style="filled", fillcolor=fillcolor)
+		graph.add_node(nodes[block.getBlockName()])
+
+
+
+	link_colours = {"paired_with": "#505050", "link_in": "#6E2229", "init_val": "#938344",
+					"IC": "darkgoldenrod", "reset": "#B9512B"}
+
+
+	for block in cbd.getBlocks():
+
+		for other in block.linksIN:
+			colour = link_colours["link_in"]
+			graph.add_edge(pydot.Edge(nodes[other.getBlockName()], nodes[block.getBlockName()], color=colour))
+
+		try:
+			other = block.IC
+			colour = link_colours["IC"]
+			graph.add_edge(pydot.Edge(nodes[other.getBlockName()], nodes[block.getBlockName()], color=colour, label="IC"))
+		except AttributeError:
+			pass
+
+		try:
+			other = block.reset
+			colour = link_colours["reset"]
+			graph.add_edge(pydot.Edge(nodes[other.getBlockName()], nodes[block.getBlockName()], color=colour, label="reset"))
+		except AttributeError:
+			pass
+
+		try:
+			other = block.init_val
+			colour = link_colours["init_val"]
+			graph.add_edge(pydot.Edge(nodes[other.getBlockName()], nodes[block.getBlockName()], color=colour, label="init_val"))
+		except AttributeError:
+			pass
+
+		try:
+			other = block.TRUE_in
+			colour = "#000000"
+			graph.add_edge(pydot.Edge(nodes[other.getBlockName()], nodes[block.getBlockName()], color=colour, label="TRUE"))
+		except AttributeError:
+			pass
+
+		try:
+			other = block.FALSE_in
+			colour = "#000000"
+			graph.add_edge(pydot.Edge(nodes[other.getBlockName()], nodes[block.getBlockName()], color=colour, label="FALSE"))
+		except AttributeError:
+			pass
+
+
+	#
+	# 	for other in in_blocks:
+	# 		name = other.getBlockName()
+	# 		label = ""
+	#
+	# 		if not name.startswith("IN"):
+	# 			label=name
+	#
+	# 		# if not name.startswith("OUT"):
+	# 		# 	label = label + " / " + other.getBlockName()
+	#
+	# 		write("{a} -> {b} [label=\"{lbl}\"];\n".format(a=other.getBlockName(),
+	# 													   b=block.getBlockName(),
+	#
+
+
+	# for e in g.es:
+	#
+	# 	src, trgt = e.tuple
+	# 	src = int(src)
+	# 	trgt = int(trgt)
+	#
+	# 	if src in internal_links.keys():
+	# 		internal_links[src]["target"] = trgt
+	# 		continue
+	# 	elif trgt in internal_links.keys():
+	# 		internal_links[trgt]["source"] = src
+	# 		continue
+	#
+	# 	else:
+	#
+	# 		color = "black"
+	# 		if "MatchModel" in mms[src]:
+	# 			color = link_colours["match_contains"]
+	# 		elif "ApplyModel" in mms[src]:
+	# 			color = link_colours["apply_contains"]
+	#
+	#
+
+	png_filename = name + '.png'
+
+	dot_filename = png_filename.replace(".png", ".dot")
+	graph.write(dot_filename, prog='dot')
+
+	command = "dot -Tpng " + dot_filename + " -o " + png_filename
+	subprocess.call(command, shell=True)
+
+	command = "rm " + dot_filename
+	subprocess.call(command, shell=True)
+
+	if show:
+		im = Image(png_filename)
+		display(im)
+
+
+def get_fill_color(node_type):
+	nt = node_type
+
+	fillcolor = "#9999FF"
+
+	if nt == 'MatchModel':
+		fillcolor = "#E15C34"
+
+	elif "Gain" in nt:
+		fillcolor = "#22DDFF"
+
+	elif "Integrator" in nt:
+		fillcolor = "lightgoldenrod"
+
+	elif "Test" in nt:
+		fillcolor = "coral"
+
+	elif nt == 'ApplyModel':
+		fillcolor = "#FED017"
+
+	elif nt == 'Equation':
+		fillcolor = "#66FF33"
+
+	elif nt in ['leftExpr', "rightExpr"]:
+		fillcolor = "#22DDFF"
+
+	elif nt in ['hasAttr_S', 'MT_pre__hasAttr_S', 'MT_post__hasAttr_S', "hasAttribute_S"]:
+		fillcolor = "#E34A0E"
+
+	elif node_type == 'hasAttr_T' or "hasAttribute" in nt:
+		fillcolor = "#C75C0D"
+
+	elif nt == 'Attribute':
+		fillcolor = "#FFCC00"
+
+	elif "hasArgs" in nt:
+		fillcolor = "#FFFF99"
+
+	elif "Concat" in nt:
+		fillcolor = "#9999FF"
+
+	elif "Constant" in nt:
+		fillcolor = "lightblue"
+
+	elif nt in ['directLink_S', 'directLink_T']:
+		fillcolor = "lightyellow"
+
+	elif nt in ['indirectLink_S', 'indirectLink_T']:
+		fillcolor = "lightgreen"
+
+	elif node_type == 'backward_link':
+		fillcolor = "coral"
+
+	elif node_type == 'trace_link':
+		fillcolor = "lightgoldenrod"
+
+	return fillcolor

+ 776 - 0
cbd/CBDsimulator.py

@@ -0,0 +1,776 @@
+import math
+
+from cbd.CBDBlocks import *
+
+#the old CBD formalism simulator
+#topological sorting needs to be extracted from this
+
+
+class CBDsimulator:
+
+  def __init__(self, model, curIteration=0):
+
+      flattened = False
+      self.__model = model
+
+      self.__depGraph = None
+      self.sortedGraph = None
+
+      for block in model.getBlocks():
+          if isinstance(block,CBD) and flattened == False:
+              model.flatten()
+              flattened = True
+
+      self.__curIteration = curIteration
+
+      #get de sortedlist of components to use for constant folding optimization
+      """self.__depGraph = self.__createDepGraph()
+      self.sortedGraph = self.__depGraph.getStrongComponents()
+      model.foldConstants(self.sortedGraph)
+      model.dump()"""
+      self.__depGraph = None
+      self.sortedGraph = None
+
+  #return the dep graph
+  #TODO: make sure this is safe to do
+  def getDepGraph(self):
+    if self.__depGraph == None:
+        self.__depGraph = self.__createDepGraph()
+  
+    return self.__depGraph
+  
+  
+    # to add: flattening of hierarchical CBDs before simulation starts 
+    
+  def getModel(self):
+    return self.__model
+                              
+  def step(self):
+    #print("Iteration" + str(self.__curIteration))
+      
+    if (self.__curIteration < 2):
+      self.__depGraph = self.__createDepGraph()
+      """print "----------------- Dependency Graph ---------------------"
+      print self.__depGraph
+      print "--------------------------------------------------------"""""
+      self.sortedGraph = self.__depGraph.getStrongComponents()
+      #print "-- Sorted collection of Strongly Connected Components --"
+      #print self.sortedGraph # a list of lists
+      #print "--------------------------------------------------------"
+ 
+    self.__computeBlocks()
+    self.__curIteration += 1
+
+  # Constructs the dependency graph of the current state of the CBD
+  def __createDepGraph(self):
+    #self.__model.dump()
+    blocks = self.__model.getBlocks()
+    depGraph = DepGraph()
+  
+    for block in blocks:
+      #print "Block before Depgraph", block
+      depGraph.addMember(block)
+  
+    for block in blocks:
+        
+        if not block.Trigger == None:
+            depGraph.setDependency(block,block.Trigger)
+        if isinstance(block, DelayBlock):
+            if (self.__curIteration == 0):
+                if block.IC in blocks:
+                    depGraph.setDependency(block,block.IC)
+                else:
+                    raise ValueError("No block linked to IC port of: ", block.getBlockName())
+            else:
+                pass
+
+        else:
+            for influencer in block.linksIN:
+                if influencer in blocks:
+                    depGraph.setDependency(block, influencer)
+                else:
+                    raise ValueError("No block linked to port of: ", block.getBlockName())
+
+        if isinstance(block,TestBlock):
+            if block.TRUE_in in blocks:
+                depGraph.setDependency(block,block.TRUE_in)
+            else:
+                raise ValueError("No block linked to TRUE port of: ", block.getBlockName())
+
+            if block.FALSE_in in blocks:
+                depGraph.setDependency(block,block.FALSE_in)
+            else:
+                raise ValueError("No block linked to FALSE port of: ", block.getBlockName())
+
+      # TO IMPLEMENT
+      # Treat dependencies on memory blocks (Delay, Integrator, Derivative) differently: 
+      # During the first iteration (time == 0.0), the block only depends on the IC; 
+      # During later iterations, it depends on the block type. 
+      #
+      # use depGraph.setDependency(block, block_it_depends_on)
+      #
+    
+
+    return depGraph
+  
+  def __computeGeneric(self, block):
+    operator = getattr(math, block.getBlockOperator())
+    output = operator(block.linksIN[0].getSignal()[self.__curIteration])
+    block.appendToSignal(output)
+
+  def __computeConstant(self, block):
+    # TO IMPLEMENT
+    block.appendToSignal(block.getValue())
+
+  def __computeScope(self,block):
+    influentBlock = block.linksIN[0]
+    block.appendToSignal(influentBlock.getSignal()[self.__curIteration])
+
+
+  def __computeNegator(self, block):
+    if 1:
+    #if block.Trigger.getSignal()[self.__curIteration]:
+        influentBlock = block.linksIN[0]
+        block.appendToSignal(-influentBlock.getSignal()[self.__curIteration])
+    else:
+        block.appendToSignal(block.getSignal()[self.__curIteration-1])
+
+  def __computeGain(self, block):
+    if 1:
+    #if block.Trigger.getSignal()[self.__curIteration]:
+        influentBlock = block.linksIN[0]
+        block.appendToSignal(influentBlock.getSignal()[self.__curIteration]*block.gain)
+    else:
+        block.appendToSignal(block.getSignal()[self.__curIteration-1])
+
+  def __computeInverter(self, block):
+    if 1:
+    #if block.Trigger.getSignal()[self.__curIteration]:
+        influentBlock = block.linksIN[0]
+        if influentBlock.getSignal()[self.__curIteration] == 0:
+            raise ValueError("Can't divide by 0: ", block.getBlockName())
+        else:
+            block.appendToSignal(1/influentBlock.getSignal()[self.__curIteration])
+    else:
+        block.appendToSignal(block.getSignal()[self.__curIteration-1])
+
+    
+  def __computeAdder(self, block):
+    if 1:
+    #if block.Trigger.getSignal()[self.__curIteration]:
+        sum = 0
+        for influentBlock in block.linksIN:
+            sum += influentBlock.getSignal()[self.__curIteration]
+        block.appendToSignal(sum)
+    else:
+        block.appendToSignal(block.getSignal()[self.__curIteration-1])
+
+
+  def __computeProduct(self, block):
+    if 1:
+    #if block.Trigger.getSignal()[self.__curIteration]:
+        prod = 1
+        for influentBlock in block.linksIN:
+            prod *= influentBlock.getSignal()[self.__curIteration]
+        block.appendToSignal(prod)
+    else:
+        block.appendToSignal(block.getSignal()[self.__curIteration-1])
+
+  def __computePower(self, block):
+    if 1:
+    #if block.Trigger.getSignal()[self.__curIteration]:
+
+        influentBlock = block.linksIN[0]
+        block.appendToSignal(influentBlock.getSignal()[self.__curIteration] ** block.power)
+    else:
+        block.appendToSignal(block.getSignal()[self.__curIteration-1])
+
+
+  def __computeModulo(self, block):
+    if 1:
+    #if block.Trigger.getSignal()[self.__curIteration]:
+        influentBlock = block.linksIN[0]
+        if block.div == 0:
+            raise ValueError("Can't divide by 0: ", block.getBlockName())
+        else:
+            block.appendToSignal(influentBlock.getSignal()[self.__curIteration] % block.div)
+    else:
+        block.appendToSignal(block.getSignal()[self.__curIteration-1])
+
+
+  def __computeDelay(self, block):
+    if 1:
+    #if block.Trigger.getSignal()[self.__curIteration]:
+        
+        if self.__curIteration == 0:
+            block.appendToSignal(block.IC.getSignal()[self.__curIteration])
+        else:
+            block.appendToSignal(block.linksIN[0].getSignal()[self.__curIteration - 1])
+    else:
+        block.appendToSignal(block.getSignal()[self.__curIteration-1])
+
+
+  def __computeDerivative(self, block):
+    # Should never be used. Derivative blocks should be replaced 
+    # by block diagrams using only Delay blocks
+    if (self.__curIteration == 0):
+      output = block.IC.getSignal()[self.__curIteration]
+    else:
+      influent_block = block.linksIN[0] # only one input
+      influent_signal = influent_block.getSignal()
+      output = (influent_signal[self.__curIteration] - \
+                influent_signal[self.__curIteration-1]) / \
+                block.getTimeIncrement()
+    block.appendToSignal(output)
+
+  def __computeIntegrator(self, block):
+    # Should never be used. Integrator blocks should be replaced 
+    # by block diagrams using only Delay blocks
+    if (self.__curIteration == 0):
+      output = block.IC.getSignal()[self.__curIteration]
+    else:
+
+      if block.reset and block.reset.getSignal()[self.__curIteration-1]:
+          #print("Resetting")
+          #print(block.getBlockName())
+          influent_block = block.init_val
+          output = influent_block.getSignal()[self.__curIteration - 1]
+      else:
+          #print("Not resetting")
+          influent_block = block.linksIN[0] # only one input
+          influent_signal = influent_block.getSignal()
+
+          output = block.getSignal()[self.__curIteration-1] + \
+                    influent_signal[self.__curIteration-1] * \
+                    block.getTimeIncrement()
+    block.appendToSignal(output)
+
+  def __computeTest(self, block):
+    if 1:
+    #if block.Trigger.getSignal()[self.__curIteration]:
+        influentBlock = block.linksIN[0]
+        if block.expr == "=":
+            if influentBlock.getSignal()[self.__curIteration] == block.switch_value:
+                block.appendToSignal(block.TRUE_in.getSignal()[self.__curIteration])
+            else:
+                block.appendToSignal(block.FALSE_in.getSignal()[self.__curIteration])
+        elif block.expr == ">":
+            if influentBlock.getSignal()[self.__curIteration] > block.switch_value:
+                block.appendToSignal(block.TRUE_in.getSignal()[self.__curIteration])
+            else:
+                block.appendToSignal(block.FALSE_in.getSignal()[self.__curIteration])
+        elif block.expr == "<":
+            if influentBlock.getSignal()[self.__curIteration] < block.switch_value:
+                block.appendToSignal(block.TRUE_in.getSignal()[self.__curIteration])
+            else:
+                block.appendToSignal(block.FALSE_in.getSignal()[self.__curIteration])
+        elif block.expr == "<=":
+            if influentBlock.getSignal()[self.__curIteration] < block.switch_value or influentBlock.getSignal()[self.__curIteration] == block.switch_value:
+                block.appendToSignal(block.TRUE_in.getSignal()[self.__curIteration])
+            else:
+                block.appendToSignal(block.FALSE_in.getSignal()[self.__curIteration])
+        elif block.expr == ">=":
+            if influentBlock.getSignal()[self.__curIteration] > block.switch_value or influentBlock.getSignal()[self.__curIteration] == block.switch_value:
+                block.appendToSignal(block.TRUE_in.getSignal()[self.__curIteration])
+            else:
+                block.appendToSignal(block.FALSE_in.getSignal()[self.__curIteration])
+        else:
+            raise ValueError("Expression "+block.expr+" isn't a legal expression. Allowed expression are: =  >  <   >=   <=")
+    else:
+        block.appendToSignal(block.getSignal()[self.__curIteration-1])
+
+
+  def __computeSequence(self, block):
+    if 1:
+    #if block.Trigger.getSignal()[self.__curIteration]:
+        block.appendToSignal(block.seq[block.iteration])
+        if self.__curIteration != 0:
+            block.iteration += 1
+        if block.iteration == len(block.seq):
+            block.iteration = 0
+    else:
+        block.appendToSignal(block.getSignal()[self.__curIteration-1])
+
+  def __computeFloor(self, block):
+    if 1:
+    #if block.Trigger.getSignal()[self.__curIteration]:
+        influentBlock = block.linksIN[0]
+        block.appendToSignal(math.floor(influentBlock.getSignal()[self.__curIteration]))
+    else:
+        block.appendToSignal(block.getSignal()[self.__curIteration-1])
+
+  def __computeBlocks(self):
+
+    for component in self.sortedGraph:
+
+      if (not self.__hasCycle(component)):
+        block = component[0]   # the strongly connected component has a single element
+        #print "Block type + name:", block.getBlockType(),":",block.getBlockName()
+
+        if isinstance(block, GenericBlock):
+          self.__computeGeneric(block)
+        elif isinstance(block, ConstantBlock):
+          self.__computeConstant(block)
+        elif isinstance(block, ScopeBlock):
+          self.__computeScope(block)
+        elif isinstance(block, NegatorBlock):
+          self.__computeNegator(block)
+        elif isinstance(block, InverterBlock):
+          self.__computeInverter(block)
+        elif isinstance(block, AdderBlock):
+          self.__computeAdder(block)
+        elif isinstance(block, ProductBlock):
+          self.__computeProduct(block)
+        elif isinstance(block, GainBlock):
+          self.__computeGain(block)
+        elif isinstance(block, FloorBlock):
+          self.__computeFloor(block)
+        elif isinstance(block, PowerBlock):
+          self.__computePower(block)
+        elif isinstance(block, ModuloBlock):
+          self.__computeModulo(block)
+        elif isinstance(block, DerivativeBlock):
+          self.__computeDerivative(block)
+        elif isinstance(block, IntegratorBlock):
+          self.__computeIntegrator(block)
+        elif isinstance(block, DelayBlock): # note that this needs to come AFTER Derivative and Integrator !
+                                            # as they are sub-classes of DelayBlock
+          self.__computeDelay(block)
+        elif isinstance(block, TestBlock):
+          self.__computeTest(block)
+        elif isinstance(block,SequenceRepeater):
+            self.__computeSequence(block)
+        elif isinstance(block, CBD):
+          raise ValueError("Hierachical blocks should have been flattened by now")
+        else:
+          raise ValueError("Unknown block type")
+      else: #TODO what happens when blocks in loop have different rates?
+        print("Component" + str(component))
+        # Detected a strongly connected component
+        assert self.__isLinear(component), "Cannot solve non-linear algebraic loop"
+        solverInput = self.__constructLinearInput(component)
+        print("########### Input matrix for linear solver -> ###########")
+        self.__printLinearInputMatrices(component, solverInput)
+        self.__gaussjLinearSolver(solverInput)
+        solutionVector = solverInput[1]
+        print("########### Solution from linear solver -> ###########")
+        for block in component:
+          blockIndex = component.index(block) 
+          block.appendToSignal(solutionVector[blockIndex])
+          print(block.getBlockName() + "=", solutionVector[blockIndex] + "\n")
+    
+  # Determine whether a component is cyclic 
+  def __hasCycle(self, component):
+    assert len(component)>=1, "A component should have at least one element"
+    if len(component)>1:
+      return True
+    else: # a strong component of size one may still have a cycle: a self-loop
+      if self.__depGraph.hasDependency(component[0], component[0]):
+        return True
+      else:
+        return False
+
+  # Determines if an algebraic loop describes a linear equation or not
+  def __isLinear(self, strongComponent):
+    # TO IMPLEMENT
+    for block in strongComponent:
+        if (isinstance(block, NegatorBlock) or isinstance(block, AdderBlock) or isinstance(block,GainBlock) ):
+            pass
+        elif isinstance(block, ProductBlock):
+            strongInputs = 0
+            for influentBlock in block.linksIN:
+                if (influentBlock in strongComponent):
+                    strongInputs += 1
+            if (strongInputs >= 2):
+                return False
+        else:
+            return False
+
+    print("Linear algebraic loop detected")
+    return True
+
+  # Constructs input for a solver of systems of linear equations
+  # Input consists of two matrices:
+  # M1: coefficient matrix, where each row represents an equation of the system
+  # M2: result matrix, where each element is the result for the corresponding equation in M1
+  def __constructLinearInput(self, strongComponent):
+    size = len(strongComponent)
+    row = []
+    M1 = [] 
+    M2 = []
+    
+    # Initialize matrices with zeros
+    i = 0
+    while (i < size):
+      j = 0
+      row = []
+      while (j < size):
+        row.append(0)
+        j += 1
+      M1.append(row)
+      M2.append(0)
+      i += 1
+    
+    # TO IMPLEMENT
+
+    for index in range(0, len(strongComponent)):
+        print("index = ", index)
+        block = strongComponent[index]
+        print("strongComponent.index = " + str(strongComponent.index(block)))
+        blockType = block.getBlockType()
+        if isinstance(block, AdderBlock):
+            for influentblock in block.linksIN:
+                if  (not(influentblock in strongComponent)):
+                    M2[index] = -influentblock.getSignal()[self.__curIteration]
+                    print("I'm here " + str(influentblock.getSignal()[self.__curIteration]) +
+                          str(M2))
+                else:
+                    print("influentblock index = " + str(strongComponent.index(influentblock)))
+                    print("M1 = " + str(M1))
+                    M1[index][index] = -1
+                    M1[index][strongComponent.index(influentblock)] = 1
+        elif isinstance(block, NegatorBlock):
+            influentblock = block.linksIN[0]
+            M2[index] = 0
+            M1[index][index] = -1
+            M1[index][strongComponent.index(influentblock)] = -1
+
+        elif isinstance(block, ProductBlock):
+            factor = 1
+            infuentindex = 0
+            for influentblock in block.linksIN:
+                if  (not(influentblock in strongComponent)):
+                    factor = influentblock.getSignal()[self.__curIteration]
+                else:
+                    infuentindex = strongComponent.index(influentblock)
+            M2[index] = 0
+            M1[index][index] = -1
+            M1[index][infuentindex] = factor
+        else:
+            print("Fault in check linear")
+    return [M1, M2]
+   
+  def __swap(self, a, b):
+    t = a
+    a = b
+    b = t
+    return (a, b)
+  
+  def __ivector(self, n):
+    v = []
+    for i in range(n):
+      v.append(0)
+    return v
+    
+  # Linear equation solution by Gauss-Jordan elimination
+  def __gaussjLinearSolver(self, solverInput):
+    M1 = solverInput[0]
+    M2 = solverInput[1]
+    n = len(M1)
+    indxc = self.__ivector(n)
+    indxr = self.__ivector(n)
+    ipiv = self.__ivector(n)
+    icol = 0
+    irow = 0
+    for i in range(n):
+      big = 0.0
+      for j in range(n):
+        if (ipiv[j] != 1):
+          for k in range(n):
+            if (ipiv[k] == 0):
+              if (math.fabs(M1[j][k]) >= big):
+                big = math.fabs(M1[j][k])
+                irow = j
+                icol = k
+            elif (ipiv[k] > 1): 
+              raise ValueError("GAUSSJ: Singular Matrix-1")
+      ipiv[icol] += 1
+      if (irow != icol):
+        for l in range(n):
+          (M1[irow][l], M1[icol][l]) = self.__swap(M1[irow][l], M1[icol][l])
+        (M2[irow], M2[icol]) = self.__swap(M2[irow], M2[icol])
+      indxr[i] = irow
+      indxc[i] = icol
+      if (M1[icol][icol] == 0.0):
+        raise ValueError("GAUSSJ: Singular Matrix-2")
+      pivinv = 1.0/M1[icol][icol]
+      M1[icol][icol] = 1.0
+      for l in range(n):
+        M1[icol][l] *= pivinv
+      M2[icol] *= pivinv
+      for ll in range(n):
+        if (ll != icol):
+          dum = M1[ll][icol]
+          M1[ll][icol] = 0.0
+          for l in range(n):
+            M1[ll][l] -= M1[icol][l] * dum
+          M2[ll] -= M2[icol] * dum
+    l = n
+    while (l > 0):
+      l -= 1
+      if (indxr[l] != indxc[l]):
+        for k in range(n):
+          (M1[k][indxr[l]], M1[k][indxc[l]]) = self.__swap(M1[k][indxr[l]], M1[k][indxc[l]])     
+  
+  # Print out the input matrices 
+  def __printLinearInputMatrices(self, strongComponent, solverInput):
+    print("Indices:")
+    i = 0
+    for block in strongComponent:
+      print(str(i) + ":" + block.getBlockName() + block.getBlockType())
+      i += 1
+
+    print("Matrices:")
+    M1 = solverInput[0]
+    M2 = solverInput[1]
+    for row in M1:
+      print(row + "=" + M2[M1.index(row)])
+
+""" 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): 
+      node = DepNode(object) 
+      self.__dependents[object]                     = []
+      self.__influencers[object]                    = []
+      self.__semanticMapping[object] = node    
+    else:
+      raise ValueError("Specified object is already member of this graph")
+      
+  def hasMember(self, object):
+    return object in self.__semanticMapping
+
+  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):
+    """ Creates a dependency between two objects.
+        @param dependent: The object which depends on the other
+        @param influencer: The object which influences the other
+        @type dependent: Object
+        @type dependent: Object
+        @raise ValueError: if dependent or influencer is not member of this graph
+        @raise ValueError: if the dependency already exists
+    """
+    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:
+        print("DEPENDENT" + str(dependent))
+        print("influencer" + str(influencer))
+
+        raise ValueError("Specified dependency already 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 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 influencer: The object which influences the other
+        @type dependent: Object
+        @type dependent: Object
+        @raise ValueError: if dependent 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):
+    return self.__strongComponents()
+      
+  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):
+    """ 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)
+        strongComponents.append(component)
+    
+    strongComponents.reverse()         
+    return strongComponents
+
+  def __dfsCollect(self, object, component):
+    """ 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)
+        
+      component.append(object)

+ 1 - 0
environment.yml

@@ -3,5 +3,6 @@ channels:
   - conda-forge
 dependencies:
   - pyfmi
+  - pydot
   - rise
   - jupyter_nbextensions_configurator