CBD_Controller.py 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262
  1. """
  2. This is CBD simulator controller exposing the most relevant methods of
  3. a CBD simulator (See CBDMultipleOutput.Source.CBD.run) to allow for control from a SCCD.
  4. date: Oct 2015
  5. author (responsible): Claudio Gomes
  6. """
  7. import math
  8. from CBDMultipleOutput.Source.CBD import Clock, DepGraph
  9. from CBDMultipleOutput.Source.CBD import CBD
  10. class CBDController:
  11. """The CBD contoller acts as a facade to the simulator methods that are in the CBD objects.
  12. The original design decision to keep the methods in the CBD Domain classes
  13. (as opposed to, for instance, keep them in a visitor/interpreter was not the decision of the current author.)"""
  14. def __init__(self, cbdModel, delta_t):
  15. self.model = cbdModel
  16. self.model.setDeltaT(delta_t)
  17. self.delta = delta_t
  18. def initSimulation(self):
  19. self.model.resetSignals();
  20. self.model.setClock(Clock(self.delta))
  21. def createDepGraph(self, iteratonStep):
  22. return self.__createDepGraph(iteratonStep)
  23. def createStrongComponents(self, depGraph, iteratonStep):
  24. return depGraph.getStrongComponents(iteratonStep)
  25. def componentIsCycle(self, component, depGraph):
  26. return self.__hasCycle(component, depGraph)
  27. def computeNextBlock(self, comp, iteratonStep):
  28. block = comp[0] # the strongly connected component has a single element
  29. block.compute(iteratonStep)
  30. def computeNextAlgebraicLoop(self, comp, iteratonStep):
  31. currentComponent = comp
  32. # Detected a strongly connected component
  33. if not self.__isLinear(currentComponent):
  34. raise NotImplementedError("Cannot solve non-linear algebraic loop")
  35. solverInput = self.__constructLinearInput(currentComponent, iteratonStep)
  36. self.__gaussjLinearSolver(solverInput)
  37. solutionVector = solverInput[1]
  38. for block in currentComponent:
  39. blockIndex = currentComponent.index(block)
  40. block.appendToSignal(solutionVector[blockIndex])
  41. def advanceTimeStep(self):
  42. self.model.getClock().step()
  43. def getCurrentSimulationTime(self):
  44. return self.model.getClock().getTime()
  45. def __isLinear(self, strongComponent):
  46. """
  47. Determines if an algebraic loop describes a linear equation or not
  48. As input you get the detected loop in a list.
  49. If the loop is linear return True
  50. Else: call exit(1) to exit the simulation with exit code 1
  51. The strong component parameter is a list of blocks that comprise the strong component.
  52. For a block to comprise the strong component, at least one of its dependencies must be in the strong component as well.
  53. A non-linear equation is generated when the following conditions occur:
  54. (1) there is a multiplication operation being performed between two unknowns.
  55. (2) there is an invertion operatioon being performed in an unknown.
  56. (3) some non linear block belongs to the strong component
  57. The condition (1) can be operationalized by finding a product block that has two dependencies belonging to the strong component.
  58. This will immediatly tell us that it is a product between two unknowns.
  59. The condition (2) can be operationalized simply by finding an inverter block in the strong component.
  60. Because the inverter block only has one input, if it is in the strong component, it means that its only dependency is in the strong component.
  61. """
  62. for block in strongComponent:
  63. if not block.getBlockType() == "AdderBlock" and not block.getBlockType() == "NegatorBlock":
  64. # condition (1)
  65. if block.getBlockType() == "ProductBlock":
  66. dependenciesUnknown = [x for x in block.getDependencies(0) if x in strongComponent ]
  67. if (len(dependenciesUnknown) == 2):
  68. return False
  69. # conditions (2) and (3)
  70. if block.getBlockType() in ["InverterBlock", "LessThanBlock", "ModuloBlock", "RootBlock", "EqualsBlock", "NotBlock", "OrBlock", "AndBlock", "SequenceBlock"]:
  71. return False
  72. return True
  73. def __hasCycle(self, component, depGraph):
  74. """
  75. Determine whether a component is cyclic
  76. """
  77. assert len(component)>=1, "A component should have at least one element"
  78. if len(component)>1:
  79. return True
  80. else: # a strong component of size one may still have a cycle: a self-loop
  81. if depGraph.hasDependency(component[0], component[0]):
  82. return True
  83. else:
  84. return False
  85. def __constructLinearInput(self, strongComponent, curIteration):
  86. """
  87. Constructs input for a solver of systems of linear equations
  88. Input consists of two matrices:
  89. M1: coefficient matrix, where each row represents an equation of the system
  90. M2: result matrix, where each element is the result for the corresponding equation in M1
  91. """
  92. size = len(strongComponent)
  93. row = []
  94. M1 = []
  95. M2 = []
  96. # Initialize matrices with zeros
  97. i = 0
  98. while (i < size):
  99. j = 0
  100. row = []
  101. while (j < size):
  102. row.append(0)
  103. j += 1
  104. M1.append(row)
  105. M2.append(0)
  106. i += 1
  107. # block -> index of block
  108. indexdict = dict()
  109. for (i,block) in enumerate(strongComponent):
  110. indexdict[block] = i
  111. resolveBlock = lambda possibleDep, output_port: possibleDep if not isinstance(possibleDep, CBD) else possibleDep.getBlockByName(output_port)
  112. def getBlockDependencies2(block):
  113. return [ resolveBlock(b,output_port) for (b, output_port) in [block.getBlockConnectedToInput("IN1"), block.getBlockConnectedToInput("IN2")]]
  114. for (i, block) in enumerate(strongComponent):
  115. if block.getBlockType() == "AdderBlock":
  116. for external in [ x for x in getBlockDependencies2(block) if x not in strongComponent ]:
  117. M2[i] -= external.getSignal()[curIteration].value
  118. M1[i][i] = -1
  119. for compInStrong in [ x for x in getBlockDependencies2(block) if x in strongComponent ]:
  120. M1[i][indexdict[compInStrong]] = 1
  121. elif block.getBlockType() == "ProductBlock":
  122. #M2 can stay 0
  123. M1[i][i] = -1
  124. M1[i][indexdict[[ x for x in getBlockDependencies2(block) if x in strongComponent ][0]]] = reduce(lambda x,y: x*y, [ x.getSignal()[curIteration].value for x in getBlockDependencies2(block) if x not in strongComponent ])
  125. elif block.getBlockType() == "NegatorBlock":
  126. #M2 can stay 0
  127. M1[i][i] = -1
  128. possibleDep, output_port = block.getBlockConnectedToInput("IN1")
  129. M1[i][indexdict[resolveBlock(possibleDep, output_port)]] = - 1
  130. elif block.getBlockType() == "InputPortBlock":
  131. #M2 can stay 0
  132. M1[i][i] = 1
  133. possibleDep, output_port = block.parent.getBlockConnectedToInput(block.getBlockName())
  134. M1[i][indexdict[resolveBlock(possibleDep, output_port)]] = - 1
  135. elif block.getBlockType() == "OutputPortBlock" or block.getBlockType() == "WireBlock":
  136. #M2 can stay 0
  137. M1[i][i] = 1
  138. M1[i][indexdict[block.getDependencies(0)[0]]] = - 1
  139. elif block.getBlockType() == "DelayBlock":
  140. # If a delay is in a strong component, this is the first iteration
  141. assert curIteration == 0
  142. # And so the dependency is the IC
  143. # M2 can stay 0 because we have an equation of the type -x = -ic <=> -x + ic = 0
  144. M1[i][i] = -1
  145. possibleDep, output_port = block.getBlockConnectedToInput("IC")
  146. dependency = resolveBlock(possibleDep, output_port)
  147. assert dependency in strongComponent
  148. M1[i][indexdict[dependency]] = 1
  149. else:
  150. self.__logger.fatal("Unknown element, please implement")
  151. return [M1, M2]
  152. def __gaussjLinearSolver(self, solverInput):
  153. M1 = solverInput[0]
  154. M2 = solverInput[1]
  155. n = len(M1)
  156. indxc = self.__ivector(n)
  157. indxr = self.__ivector(n)
  158. ipiv = self.__ivector(n)
  159. icol = 0
  160. irow = 0
  161. for i in range(n):
  162. big = 0.0
  163. for j in range(n):
  164. if (ipiv[j] != 1):
  165. for k in range(n):
  166. if (ipiv[k] == 0):
  167. if (math.fabs(M1[j][k]) >= big):
  168. big = math.fabs(M1[j][k])
  169. irow = j
  170. icol = k
  171. elif (ipiv[k] > 1):
  172. raise ValueError("GAUSSJ: Singular Matrix-1")
  173. ipiv[icol] += 1
  174. if (irow != icol):
  175. for l in range(n):
  176. (M1[irow][l], M1[icol][l]) = self.__swap(M1[irow][l], M1[icol][l])
  177. (M2[irow], M2[icol]) = self.__swap(M2[irow], M2[icol])
  178. indxr[i] = irow
  179. indxc[i] = icol
  180. if (M1[icol][icol] == 0.0):
  181. raise ValueError("GAUSSJ: Singular Matrix-2")
  182. pivinv = 1.0/M1[icol][icol]
  183. M1[icol][icol] = 1.0
  184. for l in range(n):
  185. M1[icol][l] *= pivinv
  186. M2[icol] *= pivinv
  187. for ll in range(n):
  188. if (ll != icol):
  189. dum = M1[ll][icol]
  190. M1[ll][icol] = 0.0
  191. for l in range(n):
  192. M1[ll][l] -= M1[icol][l] * dum
  193. M2[ll] -= M2[icol] * dum
  194. l = n
  195. while (l > 0):
  196. l -= 1
  197. if (indxr[l] != indxc[l]):
  198. for k in range(n):
  199. (M1[k][indxr[l]], M1[k][indxc[l]]) = self.__swap(M1[k][indxr[l]], M1[k][indxc[l]])
  200. def __createDepGraph(self, curIteration):
  201. """
  202. Create a dependency graph of the CBD model.
  203. Use the curIteration to differentiate between the first and other iterations
  204. Watch out for dependencies with sub-models.
  205. """
  206. blocks = self.model.getBlocks()
  207. depGraph = DepGraph()
  208. for block in blocks:
  209. depGraph.addMember(block);
  210. def recSetInternalDependencies(blocks, curIteration):
  211. for block in blocks:
  212. for dep in block.getDependencies(curIteration):
  213. depGraph.setDependency(block, dep, curIteration)
  214. if isinstance(block, CBD):
  215. recSetInternalDependencies(block.getBlocks(), curIteration)
  216. recSetInternalDependencies(blocks, curIteration);
  217. return depGraph
  218. def __ivector(self, n):
  219. v = []
  220. for i in range(n):
  221. v.append(0)
  222. return v
  223. def __swap(self, a, b):
  224. return (b, a)