devstone.py 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473
  1. from abc import ABC, abstractmethod
  2. from collections import defaultdict
  3. from pystone import pystones
  4. from pypdevs.DEVS import AtomicDEVS, CoupledDEVS
  5. from pypdevs.infinity import INFINITY
  6. from pypdevs.simulator import Simulator
  7. class DelayedAtomic(AtomicDEVS):
  8. def __init__(self, name: str, int_delay: float, ext_delay: float, add_out_port: bool = False, prep_time=0, mode:str = ""):
  9. super().__init__(name)
  10. self.int_delay = int_delay
  11. self.ext_delay = ext_delay
  12. self.prep_time = prep_time
  13. self.i_in = self.addInPort("i_in")
  14. if add_out_port: # for HI and HO models
  15. self.o_out = self.addOutPort("o_out")
  16. # Dynamic Structure extras
  17. self.mode = mode
  18. self.out_ports = [] # used for dynamic structure model LI2HI
  19. def intTransition(self):
  20. if self.int_delay:
  21. pystones(self.int_delay)
  22. return "passive"
  23. def timeAdvance(self):
  24. if self.state == "active":
  25. return self.prep_time
  26. else:
  27. return INFINITY
  28. def outputFnc(self):
  29. if hasattr(self, "o_out") and self.o_out is not None:
  30. return {self.o_out: [0]}
  31. # for DSDEVS LI2HI
  32. if len(self.out_ports) == 1 and self.out_ports[0].outline != []:
  33. # print(f"{self.name}: should be last call!!")
  34. # print("Send to ", self.out_ports[0].outline)
  35. return {self.out_ports[0]: [0]}
  36. return {}
  37. def extTransition(self, inputs):
  38. if self.ext_delay:
  39. pystones(self.ext_delay)
  40. return "active"
  41. def modelTransition(self, state):
  42. if self.mode == "LI2HI":
  43. return self.LI2HI(state)
  44. elif self.mode == "HI2LI":
  45. return self.HI2LI(state)
  46. return False
  47. def LI2HI(self, state):
  48. if len(self.out_ports) == 0 and not hasattr(self, "o_out"):
  49. # every automic dev gets the extra out port
  50. # even though the last atomic could miss it see fig
  51. # except the atomic model in the coupled model at max depth
  52. # which has a output port by default
  53. state["LI2HI"] = True
  54. state["name"] = self.name
  55. self.out_ports.append(self.addOutPort("o_HI")) # standard LI model doesn't have by default
  56. # print(f"{self.name}: should be first call!!")
  57. return True # send to the parent to connect ports
  58. return False
  59. def HI2LI(self, state):
  60. if hasattr(self, "o_out") and self.o_out is not None:
  61. state["HI2LI"] = True
  62. state["name"] = self.name
  63. self.removePort(self.o_out)
  64. self.o_out = None
  65. return False
  66. class DelayedAtomicStats(DelayedAtomic):
  67. def __init__(self, name: str, int_delay: float, ext_delay: float, add_out_port: bool = False, prep_time=0, mode:str = ""):
  68. super().__init__(name, int_delay, ext_delay, add_out_port, prep_time, mode)
  69. self.int_count = 0
  70. self.ext_count = 0
  71. def intTransition(self):
  72. self.int_count += 1
  73. return super().intTransition()
  74. def extTransition(self, inputs):
  75. self.ext_count += 1
  76. return super().extTransition(inputs)
  77. class DEVStoneWrapper(CoupledDEVS, ABC):
  78. def __init__(self, name: str, depth: int, width: int, int_delay: float, ext_delay: float,
  79. add_atomic_out_ports: bool = False, prep_time=0, stats=False, mode: str = ""):
  80. super().__init__(name)
  81. self.depth = depth
  82. self.width = width
  83. self.int_delay = int_delay
  84. self.ext_delay = ext_delay
  85. self.prep_time = prep_time
  86. self.stats = stats
  87. self.add_atomic_out_ports = add_atomic_out_ports
  88. self.i_in = self.addInPort("i_in")
  89. self.o_out = self.addOutPort("o_out")
  90. if depth < 1:
  91. raise ValueError("Invalid depth")
  92. if width < 1:
  93. raise ValueError("Invalid width")
  94. if int_delay < 0:
  95. raise ValueError("Invalid int_delay")
  96. if ext_delay < 0:
  97. raise ValueError("Invalid ext_delay")
  98. if depth == 1:
  99. if self.stats:
  100. atomic = DelayedAtomicStats("Atomic_0_0", int_delay, ext_delay, add_out_port=True, prep_time=prep_time, mode=mode)
  101. else:
  102. atomic = DelayedAtomic("Atomic_0_0", int_delay, ext_delay, add_out_port=True, prep_time=prep_time, mode=mode)
  103. self.addSubModel(atomic)
  104. self.connectPorts(self.i_in, atomic.i_in)
  105. self.connectPorts(atomic.o_out, self.o_out)
  106. else:
  107. coupled = self.gen_coupled()
  108. self.addSubModel(coupled)
  109. self.connectPorts(self.i_in, coupled.i_in)
  110. self.connectPorts(coupled.o_out, self.o_out)
  111. for idx in range(width - 1):
  112. if self.stats:
  113. atomic = DelayedAtomicStats("Atomic_%d_%d" % (depth - 1, idx), int_delay, ext_delay,
  114. add_out_port=add_atomic_out_ports, prep_time=prep_time, mode=mode)
  115. else:
  116. atomic = DelayedAtomic("Atomic_%d_%d" % (depth - 1, idx), int_delay, ext_delay,
  117. add_out_port=add_atomic_out_ports, prep_time=prep_time, mode=mode)
  118. self.addSubModel(atomic)
  119. @abstractmethod
  120. def gen_coupled(self):
  121. """ :return a coupled method with i_in and o_out ports"""
  122. pass
  123. class LI(DEVStoneWrapper):
  124. def __init__(self, name: str, depth: int, width: int, int_delay: float, ext_delay: float, prep_time=0, stats=False):
  125. super().__init__(name, depth, width, int_delay, ext_delay, add_atomic_out_ports=False, prep_time=prep_time,
  126. stats=stats)
  127. for idx in range(1, len(self.component_set)):
  128. assert isinstance(self.component_set[idx], AtomicDEVS)
  129. self.connectPorts(self.i_in, self.component_set[idx].i_in)
  130. def gen_coupled(self):
  131. return LI("Coupled_%d" % (self.depth - 1), self.depth - 1, self.width, self.int_delay, self.ext_delay,
  132. prep_time=self.prep_time, stats=self.stats)
  133. class HI(DEVStoneWrapper):
  134. def __init__(self, name: str, depth: int, width: int, int_delay: float, ext_delay: float, prep_time=0, stats=False):
  135. super().__init__(name, depth, width, int_delay, ext_delay, add_atomic_out_ports=True, prep_time=prep_time,
  136. stats=stats)
  137. if len(self.component_set) > 1:
  138. assert isinstance(self.component_set[-1], AtomicDEVS)
  139. self.connectPorts(self.i_in, self.component_set[-1].i_in)
  140. for idx in range(1, len(self.component_set) - 1):
  141. assert isinstance(self.component_set[idx], AtomicDEVS)
  142. self.connectPorts(self.component_set[idx].o_out, self.component_set[idx + 1].i_in)
  143. self.connectPorts(self.i_in, self.component_set[idx].i_in)
  144. def gen_coupled(self):
  145. return HI("Coupled_%d" % (self.depth - 1), self.depth - 1, self.width, self.int_delay, self.ext_delay,
  146. prep_time=self.prep_time, stats=self.stats)
  147. class HO(DEVStoneWrapper):
  148. def __init__(self, name: str, depth: int, width: int, int_delay: float, ext_delay: float, prep_time=0, stats=False):
  149. super().__init__(name, depth, width, int_delay, ext_delay, add_atomic_out_ports=True, prep_time=prep_time,
  150. stats=stats)
  151. self.i_in2 = self.addInPort("i_in2")
  152. self.o_out2 = self.addOutPort("o_out2")
  153. assert len(self.component_set) > 0
  154. if isinstance(self.component_set[0], CoupledDEVS):
  155. self.connectPorts(self.i_in, self.component_set[0].i_in2)
  156. if len(self.component_set) > 1:
  157. assert isinstance(self.component_set[-1], AtomicDEVS)
  158. self.connectPorts(self.i_in2, self.component_set[-1].i_in)
  159. self.connectPorts(self.component_set[-1].o_out, self.o_out2)
  160. for idx in range(1, len(self.component_set) - 1):
  161. assert isinstance(self.component_set[idx], AtomicDEVS)
  162. self.connectPorts(self.component_set[idx].o_out, self.component_set[idx + 1].i_in)
  163. self.connectPorts(self.i_in2, self.component_set[idx].i_in)
  164. self.connectPorts(self.component_set[idx].o_out, self.o_out2)
  165. def gen_coupled(self):
  166. return HO("Coupled_%d" % (self.depth - 1), self.depth - 1, self.width, self.int_delay, self.ext_delay,
  167. prep_time=self.prep_time, stats=self.stats)
  168. class HOmod(CoupledDEVS):
  169. def __init__(self, name: str, depth: int, width: int, int_delay: float, ext_delay: float, prep_time=0, stats=False):
  170. super().__init__(name)
  171. self.depth = depth
  172. self.width = width
  173. self.int_delay = int_delay
  174. self.ext_delay = ext_delay
  175. self.prep_time = prep_time
  176. self.stats = stats
  177. self.i_in = self.addInPort("i_in")
  178. self.i_in2 = self.addInPort("i_in2")
  179. self.o_out = self.addOutPort("o_out")
  180. if depth < 1:
  181. raise ValueError("Invalid depth")
  182. if width < 1:
  183. raise ValueError("Invalid width")
  184. if int_delay < 0:
  185. raise ValueError("Invalid int_delay")
  186. if ext_delay < 0:
  187. raise ValueError("Invalid ext_delay")
  188. if depth == 1:
  189. if self.stats:
  190. atomic = DelayedAtomicStats("Atomic_0_0", int_delay, ext_delay, add_out_port=True, prep_time=prep_time)
  191. else:
  192. atomic = DelayedAtomic("Atomic_0_0", int_delay, ext_delay, add_out_port=True, prep_time=prep_time)
  193. self.addSubModel(atomic)
  194. self.connectPorts(self.i_in, atomic.i_in)
  195. self.connectPorts(atomic.o_out, self.o_out)
  196. else:
  197. coupled = HOmod("Coupled_%d" % (self.depth - 1), self.depth - 1, self.width, self.int_delay, self.ext_delay, prep_time=prep_time, stats=stats)
  198. self.addSubModel(coupled)
  199. self.connectPorts(self.i_in, coupled.i_in)
  200. self.connectPorts(coupled.o_out, self.o_out)
  201. if width >= 2:
  202. atomics = defaultdict(list)
  203. # Generate atomic components
  204. for i in range(width):
  205. min_row_idx = 0 if i < 2 else i - 1
  206. for j in range(min_row_idx, width - 1):
  207. if self.stats:
  208. atomic = DelayedAtomicStats("Atomic_%d_%d_%d" % (depth - 1, i, j), int_delay, ext_delay,
  209. add_out_port=True, prep_time=prep_time)
  210. else:
  211. atomic = DelayedAtomic("Atomic_%d_%d_%d" % (depth - 1, i, j), int_delay, ext_delay,
  212. add_out_port=True, prep_time=prep_time)
  213. self.addSubModel(atomic)
  214. atomics[i].append(atomic)
  215. # Connect EIC
  216. for atomic in atomics[0]:
  217. self.connectPorts(self.i_in2, atomic.i_in)
  218. for i in range(1, width):
  219. atomic_set = atomics[i]
  220. self.connectPorts(self.i_in2, atomic_set[0].i_in)
  221. # Connect IC
  222. for atomic in atomics[0]: # First row to coupled component
  223. self.connectPorts(atomic.o_out, coupled.i_in2)
  224. for i in range(len(atomics[1])): # Second to first rows
  225. for j in range(len(atomics[0])):
  226. self.connectPorts(atomics[1][i].o_out, atomics[0][j].i_in)
  227. for i in range(2, width): # Rest of rows
  228. for j in range(len(atomics[i])):
  229. self.connectPorts(atomics[i][j].o_out, atomics[i - 1][j + 1].i_in)
  230. class LI2HI(DEVStoneWrapper):
  231. """
  232. Dynamic DEVStone variant that starts as an LI
  233. and (gradually??) transforms into an HI model
  234. """
  235. def __init__(self, name: str, depth: int, width: int, int_delay: float, ext_delay: float, prep_time=0, stats=False):
  236. super().__init__(name, depth, width, int_delay, ext_delay, add_atomic_out_ports=False, prep_time=prep_time,
  237. stats=stats, mode="LI2HI")
  238. for idx in range(1, len(self.component_set)):
  239. assert isinstance(self.component_set[idx], AtomicDEVS)
  240. self.connectPorts(self.i_in, self.component_set[idx].i_in)
  241. def gen_coupled(self):
  242. return LI2HI("Coupled_%d" % (self.depth - 1), self.depth - 1, self.width, self.int_delay, self.ext_delay,
  243. prep_time=self.prep_time, stats=self.stats)
  244. def modelTransition(self, state):
  245. if state.get("LI2HI", True):
  246. for i in range(len(self.component_set) - 1): # -1 because the last model can't be connected
  247. # basically checks if it is an atomic model and if it got the extra output port
  248. if hasattr(self.component_set[i], "out_ports") and len(self.component_set[i].out_ports) == 1:
  249. # print(f"Connect {self.component_set[i].name} to {self.component_set[i+1].name}")
  250. self.connectPorts(self.component_set[i].out_ports[0], self.component_set[i + 1].i_in)
  251. return False
  252. return False
  253. class HI2LI(DEVStoneWrapper):
  254. """
  255. Dynamic DEVStone variant that starts as an HI
  256. and (gradually??) transforms into an LI model
  257. """
  258. def __init__(self, name: str, depth: int, width: int, int_delay: float, ext_delay: float, prep_time=0, stats=False):
  259. super().__init__(name, depth, width, int_delay, ext_delay, add_atomic_out_ports=True, prep_time=prep_time,
  260. stats=stats, mode="HI2LI")
  261. if len(self.component_set) > 1:
  262. assert isinstance(self.component_set[-1], AtomicDEVS)
  263. self.connectPorts(self.i_in, self.component_set[-1].i_in)
  264. for idx in range(1, len(self.component_set) - 1):
  265. assert isinstance(self.component_set[idx], AtomicDEVS)
  266. self.connectPorts(self.component_set[idx].o_out, self.component_set[idx + 1].i_in)
  267. self.connectPorts(self.i_in, self.component_set[idx].i_in)
  268. def gen_coupled(self):
  269. return HI2LI("Coupled_%d" % (self.depth - 1), self.depth - 1, self.width, self.int_delay, self.ext_delay,
  270. prep_time=self.prep_time, stats=self.stats)
  271. class Fertilizer(AtomicDEVS):
  272. def __init__(self, name: str, period: float = 1.0, repeat: int = 1):
  273. super().__init__(name)
  274. self.period = period
  275. self.repeat = repeat
  276. self._counter = 0
  277. self._active = True
  278. def intTransition(self):
  279. return "passive" # state is unimportant
  280. def timeAdvance(self):
  281. # stop when counter >= repeat
  282. if self._counter >= self.repeat:
  283. return INFINITY
  284. return self.period
  285. def outputFnc(self):
  286. return {}
  287. def modelTransition(self, state):
  288. # every time TA elapses, this will be called on the atomic:
  289. # set a flag the parent coupled can read
  290. # increment counter and request modelTransition (return True)
  291. if self._counter >= self.repeat:
  292. return False
  293. self._counter += 1
  294. state["generate"] = True
  295. return True
  296. class dLI(DEVStoneWrapper):
  297. """
  298. A LI model which grows in width
  299. """
  300. def __init__(self, name: str, depth: int, width: int, int_delay: float, ext_delay: float, prep_time=0, stats=False):
  301. super().__init__(name, depth, width, int_delay, ext_delay, add_atomic_out_ports=False, prep_time=prep_time,
  302. stats=stats, mode="LI-Dynamic")
  303. self.max_gen = 1
  304. for idx in range(1, len(self.component_set)):
  305. assert isinstance(self.component_set[idx], AtomicDEVS)
  306. self.connectPorts(self.i_in, self.component_set[idx].i_in)
  307. self.generator = self.addSubModel(Fertilizer(self.name + "_gen", repeat=self.max_gen))
  308. def gen_coupled(self):
  309. return dLI("Coupled_%d" % (self.depth - 1), self.depth - 1, self.width, self.int_delay, self.ext_delay,
  310. prep_time=self.prep_time, stats=self.stats)
  311. def modelTransition(self, state):
  312. if state.get("generate", True):
  313. name = "Atomic_%d_%d" % (self.depth - 1, self.width) if self.depth > 1 else "Atomic_0_%d" % self.width
  314. if self.stats:
  315. new_atom = DelayedAtomicStats(name, self.int_delay, self.ext_delay,
  316. add_out_port=self.add_atomic_out_ports, prep_time=self.prep_time, mode="LI-Dynamic")
  317. else:
  318. new_atom = DelayedAtomic(name, self.int_delay, self.ext_delay,
  319. add_out_port=self.add_atomic_out_ports, prep_time=self.prep_time, mode="LI-Dynamic")
  320. self.addSubModel(new_atom)
  321. self.connectPorts(self.i_in, new_atom.i_in)
  322. self.width += 1
  323. # print(f"{self.name}: added {new_atom.name} (width is now {self.width})")
  324. return False
  325. return False
  326. class dHI(DEVStoneWrapper):
  327. """
  328. A HI model which grows in width
  329. """
  330. def __init__(self, name: str, depth: int, width: int, int_delay: float, ext_delay: float, prep_time=0, stats=False):
  331. super().__init__(name, depth, width, int_delay, ext_delay, add_atomic_out_ports=True, prep_time=prep_time,
  332. stats=stats, mode="HI-Dynamic")
  333. self.max_gen = 10
  334. if len(self.component_set) > 1:
  335. assert isinstance(self.component_set[-1], AtomicDEVS)
  336. self.connectPorts(self.i_in, self.component_set[-1].i_in)
  337. for idx in range(1, len(self.component_set) - 1):
  338. assert isinstance(self.component_set[idx], AtomicDEVS)
  339. self.connectPorts(self.component_set[idx].o_out, self.component_set[idx + 1].i_in)
  340. self.connectPorts(self.i_in, self.component_set[idx].i_in)
  341. self.generator = self.addSubModel(Fertilizer(self.name + "_gen", repeat=self.max_gen))
  342. def gen_coupled(self):
  343. return dHI("Coupled_%d" % (self.depth - 1), self.depth - 1, self.width, self.int_delay, self.ext_delay,
  344. prep_time=self.prep_time, stats=self.stats)
  345. def modelTransition(self, state):
  346. if state.get("generate", True):
  347. name = "Atomic_%d_%d" % (self.depth - 1, self.width) if self.depth > 1 else "Atomic_0_%d" % self.width
  348. if self.stats:
  349. new_atom = DelayedAtomicStats(name, self.int_delay, self.ext_delay,
  350. add_out_port=self.add_atomic_out_ports, prep_time=self.prep_time, mode="LI-Dynamic")
  351. else:
  352. new_atom = DelayedAtomic(name, self.int_delay, self.ext_delay,
  353. add_out_port=self.add_atomic_out_ports, prep_time=self.prep_time, mode="LI-Dynamic")
  354. last = self.component_set[-2]
  355. self.addSubModel(new_atom)
  356. if isinstance(last, AtomicDEVS) and hasattr(last, "o_out"):
  357. self.connectPorts(last.o_out, new_atom.i_in)
  358. self.connectPorts(self.i_in, new_atom.i_in)
  359. self.width += 1
  360. # print(f"{self.name}: added {new_atom.name} (width is now {self.width})")
  361. return False
  362. return False
  363. if __name__ == '__main__':
  364. import sys
  365. sys.setrecursionlimit(10000)
  366. root = HOmod("Root", 4, 3, 0, 0)
  367. sim = Simulator(root)
  368. sim.setVerbose(None)
  369. # sim.setTerminationTime(10.0)
  370. sim.setStateSaving("custom")
  371. sim.simulate()