devstone.py 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500
  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.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. self.models = [] # stores all models part of the coupled model
  91. # added to deal with DS DEVS which don't keep it all in the component_set
  92. if depth < 1:
  93. raise ValueError("Invalid depth")
  94. if width < 1:
  95. raise ValueError("Invalid width")
  96. if int_delay < 0:
  97. raise ValueError("Invalid int_delay")
  98. if ext_delay < 0:
  99. raise ValueError("Invalid ext_delay")
  100. if depth == 1:
  101. if self.stats:
  102. atomic = DelayedAtomicStats("Atomic_0_0", int_delay, ext_delay, add_out_port=True, prep_time=1.0, mode=mode)
  103. else:
  104. atomic = DelayedAtomic("Atomic_0_0", int_delay, ext_delay, add_out_port=True, prep_time=1.0, mode=mode)
  105. self.models.append(self.addSubModel(atomic))
  106. self.connectPorts(self.i_in, atomic.i_in)
  107. self.connectPorts(atomic.o_out, self.o_out)
  108. else:
  109. coupled = self.gen_coupled()
  110. self.addSubModel(coupled)
  111. self.connectPorts(self.i_in, coupled.i_in)
  112. self.connectPorts(coupled.o_out, self.o_out)
  113. # + (idx * 1e-6) to address too many transitions at the same time
  114. # => problem for Classic DEVS only it seems
  115. for idx in range(width - 1):
  116. if self.stats:
  117. atomic = DelayedAtomicStats("Atomic_%d_%d" % (depth - 1, idx), int_delay, ext_delay,
  118. add_out_port=add_atomic_out_ports, prep_time=1.0 + (idx * 1e-6), mode=mode)
  119. else:
  120. atomic = DelayedAtomic("Atomic_%d_%d" % (depth - 1, idx), int_delay, ext_delay,
  121. add_out_port=add_atomic_out_ports, prep_time=1.0 + (idx * 1e-6), mode=mode)
  122. self.models.append(self.addSubModel(atomic))
  123. def extend(self, string: str, width: int):
  124. length = len(string)
  125. return string + " "*(width - length)
  126. def __str__(self):
  127. result = [self.extend(self.name, 16) + "\n", "-" * 20 + "\n"]
  128. first = self.component_set[0]
  129. coupled = ""
  130. if isinstance(first, CoupledDEVS):
  131. result.append("| " + self.extend(first.name, 16) + " |\n")
  132. coupled = "\n\n" + first.__str__()
  133. for model in self.models:
  134. result.append("| " + self.extend(model.name, 16) + " |\n")
  135. result.append("-"*20 + "\n")
  136. result.append(coupled)
  137. return "".join(result)
  138. @abstractmethod
  139. def gen_coupled(self):
  140. """ :return a coupled method with i_in and o_out ports"""
  141. pass
  142. class LI(DEVStoneWrapper):
  143. def __init__(self, name: str, depth: int, width: int, int_delay: float, ext_delay: float, prep_time=0, stats=False):
  144. super().__init__(name, depth, width, int_delay, ext_delay, add_atomic_out_ports=False, prep_time=prep_time,
  145. stats=stats)
  146. for idx in range(1, len(self.component_set)):
  147. assert isinstance(self.component_set[idx], AtomicDEVS)
  148. self.connectPorts(self.i_in, self.component_set[idx].i_in)
  149. def gen_coupled(self):
  150. return LI("Coupled_%d" % (self.depth - 1), self.depth - 1, self.width, self.int_delay, self.ext_delay,
  151. prep_time=self.prep_time, stats=self.stats)
  152. class HI(DEVStoneWrapper):
  153. def __init__(self, name: str, depth: int, width: int, int_delay: float, ext_delay: float, prep_time=0, stats=False):
  154. super().__init__(name, depth, width, int_delay, ext_delay, add_atomic_out_ports=True, prep_time=prep_time,
  155. stats=stats)
  156. if len(self.component_set) > 1:
  157. assert isinstance(self.component_set[-1], AtomicDEVS)
  158. self.connectPorts(self.i_in, self.component_set[-1].i_in)
  159. for idx in range(1, len(self.component_set) - 1):
  160. assert isinstance(self.component_set[idx], AtomicDEVS)
  161. self.connectPorts(self.component_set[idx].o_out, self.component_set[idx + 1].i_in)
  162. self.connectPorts(self.i_in, self.component_set[idx].i_in)
  163. def gen_coupled(self):
  164. return HI("Coupled_%d" % (self.depth - 1), self.depth - 1, self.width, self.int_delay, self.ext_delay,
  165. prep_time=self.prep_time, stats=self.stats)
  166. class HO(DEVStoneWrapper):
  167. def __init__(self, name: str, depth: int, width: int, int_delay: float, ext_delay: float, prep_time=0, stats=False):
  168. super().__init__(name, depth, width, int_delay, ext_delay, add_atomic_out_ports=True, prep_time=prep_time,
  169. stats=stats)
  170. self.i_in2 = self.addInPort("i_in2")
  171. self.o_out2 = self.addOutPort("o_out2")
  172. assert len(self.component_set) > 0
  173. if isinstance(self.component_set[0], CoupledDEVS):
  174. self.connectPorts(self.i_in, self.component_set[0].i_in2)
  175. if len(self.component_set) > 1:
  176. assert isinstance(self.component_set[-1], AtomicDEVS)
  177. self.connectPorts(self.i_in2, self.component_set[-1].i_in)
  178. self.connectPorts(self.component_set[-1].o_out, self.o_out2)
  179. for idx in range(1, len(self.component_set) - 1):
  180. assert isinstance(self.component_set[idx], AtomicDEVS)
  181. self.connectPorts(self.component_set[idx].o_out, self.component_set[idx + 1].i_in)
  182. self.connectPorts(self.i_in2, self.component_set[idx].i_in)
  183. self.connectPorts(self.component_set[idx].o_out, self.o_out2)
  184. def gen_coupled(self):
  185. return HO("Coupled_%d" % (self.depth - 1), self.depth - 1, self.width, self.int_delay, self.ext_delay,
  186. prep_time=self.prep_time, stats=self.stats)
  187. class HOmod(CoupledDEVS):
  188. def __init__(self, name: str, depth: int, width: int, int_delay: float, ext_delay: float, prep_time=0, stats=False):
  189. super().__init__(name)
  190. self.depth = depth
  191. self.width = width
  192. self.int_delay = int_delay
  193. self.ext_delay = ext_delay
  194. self.prep_time = prep_time
  195. self.stats = stats
  196. self.i_in = self.addInPort("i_in")
  197. self.i_in2 = self.addInPort("i_in2")
  198. self.o_out = self.addOutPort("o_out")
  199. if depth < 1:
  200. raise ValueError("Invalid depth")
  201. if width < 1:
  202. raise ValueError("Invalid width")
  203. if int_delay < 0:
  204. raise ValueError("Invalid int_delay")
  205. if ext_delay < 0:
  206. raise ValueError("Invalid ext_delay")
  207. if depth == 1:
  208. if self.stats:
  209. atomic = DelayedAtomicStats("Atomic_0_0", int_delay, ext_delay, add_out_port=True, prep_time=prep_time)
  210. else:
  211. atomic = DelayedAtomic("Atomic_0_0", int_delay, ext_delay, add_out_port=True, prep_time=prep_time)
  212. self.addSubModel(atomic)
  213. self.connectPorts(self.i_in, atomic.i_in)
  214. self.connectPorts(atomic.o_out, self.o_out)
  215. else:
  216. coupled = HOmod("Coupled_%d" % (self.depth - 1), self.depth - 1, self.width, self.int_delay, self.ext_delay, prep_time=prep_time, stats=stats)
  217. self.addSubModel(coupled)
  218. self.connectPorts(self.i_in, coupled.i_in)
  219. self.connectPorts(coupled.o_out, self.o_out)
  220. if width >= 2:
  221. atomics = defaultdict(list)
  222. # Generate atomic components
  223. for i in range(width):
  224. min_row_idx = 0 if i < 2 else i - 1
  225. for j in range(min_row_idx, width - 1):
  226. if self.stats:
  227. atomic = DelayedAtomicStats("Atomic_%d_%d_%d" % (depth - 1, i, j), int_delay, ext_delay,
  228. add_out_port=True, prep_time=prep_time)
  229. else:
  230. atomic = DelayedAtomic("Atomic_%d_%d_%d" % (depth - 1, i, j), int_delay, ext_delay,
  231. add_out_port=True, prep_time=prep_time)
  232. self.addSubModel(atomic)
  233. atomics[i].append(atomic)
  234. # Connect EIC
  235. for atomic in atomics[0]:
  236. self.connectPorts(self.i_in2, atomic.i_in)
  237. for i in range(1, width):
  238. atomic_set = atomics[i]
  239. self.connectPorts(self.i_in2, atomic_set[0].i_in)
  240. # Connect IC
  241. for atomic in atomics[0]: # First row to coupled component
  242. self.connectPorts(atomic.o_out, coupled.i_in2)
  243. for i in range(len(atomics[1])): # Second to first rows
  244. for j in range(len(atomics[0])):
  245. self.connectPorts(atomics[1][i].o_out, atomics[0][j].i_in)
  246. for i in range(2, width): # Rest of rows
  247. for j in range(len(atomics[i])):
  248. self.connectPorts(atomics[i][j].o_out, atomics[i - 1][j + 1].i_in)
  249. class LI2HI(DEVStoneWrapper):
  250. """
  251. Dynamic DEVStone variant that starts as an LI
  252. and (gradually??) transforms into an HI model
  253. """
  254. def __init__(self, name: str, depth: int, width: int, int_delay: float, ext_delay: float, prep_time=0, stats=False):
  255. super().__init__(name, depth, width, int_delay, ext_delay, add_atomic_out_ports=False, prep_time=prep_time,
  256. stats=stats, mode="LI2HI")
  257. for idx in range(1, len(self.component_set)):
  258. assert isinstance(self.component_set[idx], AtomicDEVS)
  259. self.connectPorts(self.i_in, self.component_set[idx].i_in)
  260. def gen_coupled(self):
  261. return LI2HI("Coupled_%d" % (self.depth - 1), self.depth - 1, self.width, self.int_delay, self.ext_delay,
  262. prep_time=self.prep_time, stats=self.stats)
  263. def modelTransition(self, state):
  264. if state.get("LI2HI", True):
  265. for i in range(len(self.component_set) - 1): # -1 because the last model can't be connected
  266. # basically checks if it is an atomic model and if it got the extra output port
  267. if hasattr(self.component_set[i], "out_ports") and len(self.component_set[i].out_ports) == 1:
  268. # print(f"Connect {self.component_set[i].name} to {self.component_set[i+1].name}")
  269. self.connectPorts(self.component_set[i].out_ports[0], self.component_set[i + 1].i_in)
  270. return False
  271. return False
  272. class HI2LI(DEVStoneWrapper):
  273. """
  274. Dynamic DEVStone variant that starts as an HI
  275. and (gradually??) transforms into an LI model
  276. """
  277. def __init__(self, name: str, depth: int, width: int, int_delay: float, ext_delay: float, prep_time=0, stats=False):
  278. super().__init__(name, depth, width, int_delay, ext_delay, add_atomic_out_ports=True, prep_time=prep_time,
  279. stats=stats, mode="HI2LI")
  280. if len(self.component_set) > 1:
  281. assert isinstance(self.component_set[-1], AtomicDEVS)
  282. self.connectPorts(self.i_in, self.component_set[-1].i_in)
  283. for idx in range(1, len(self.component_set) - 1):
  284. assert isinstance(self.component_set[idx], AtomicDEVS)
  285. self.connectPorts(self.component_set[idx].o_out, self.component_set[idx + 1].i_in)
  286. self.connectPorts(self.i_in, self.component_set[idx].i_in)
  287. def gen_coupled(self):
  288. return HI2LI("Coupled_%d" % (self.depth - 1), self.depth - 1, self.width, self.int_delay, self.ext_delay,
  289. prep_time=self.prep_time, stats=self.stats)
  290. class Fertilizer(AtomicDEVS):
  291. def __init__(self, name: str, period: float = 1.0, repeat: int = 1):
  292. super().__init__(name)
  293. self.period = period
  294. self.repeat = repeat
  295. self._counter = 0
  296. self._active = True
  297. def intTransition(self):
  298. return "passive" # state is unimportant
  299. def timeAdvance(self):
  300. # stop when counter >= repeat
  301. if self._counter >= self.repeat:
  302. return INFINITY
  303. return self.period
  304. def outputFnc(self):
  305. return {}
  306. def modelTransition(self, state):
  307. # every time TA elapses, this will be called on the atomic:
  308. # set a flag the parent coupled can read
  309. # increment counter and request modelTransition (return True)
  310. if self._counter >= self.repeat:
  311. return False
  312. self._counter += 1
  313. state["generate"] = True
  314. return True
  315. class dLI(DEVStoneWrapper):
  316. """
  317. A LI model which grows in width
  318. """
  319. def __init__(self, name: str, depth: int, width: int, int_delay: float, ext_delay: float, prep_time=0, stats=False):
  320. super().__init__(name, depth, width, int_delay, ext_delay, add_atomic_out_ports=False, prep_time=prep_time,
  321. stats=stats, mode="LI-Dynamic")
  322. self.max_gen = 5
  323. for idx in range(1, len(self.component_set)):
  324. assert isinstance(self.component_set[idx], AtomicDEVS)
  325. self.connectPorts(self.i_in, self.component_set[idx].i_in)
  326. self.generator = self.addSubModel(Fertilizer(self.name + "_gen", repeat=self.max_gen))
  327. def gen_coupled(self):
  328. return dLI("Coupled_%d" % (self.depth - 1), self.depth - 1, self.width, self.int_delay, self.ext_delay,
  329. prep_time=self.prep_time, stats=self.stats)
  330. def modelTransition(self, state):
  331. if state.get("generate", True) and self.depth > 1:
  332. # print("model", self.name)
  333. self.connectPorts(self.i_in, self.component_set[0].i_in)
  334. name = "Atomic_%d_%d" % (self.depth - 1, self.width) if self.depth > 1 else "Atomic_0_%d" % self.width
  335. if self.stats:
  336. new_atom = DelayedAtomicStats(name, self.int_delay, self.ext_delay,
  337. add_out_port=self.add_atomic_out_ports, prep_time=self.prep_time, mode="LI-Dynamic")
  338. else:
  339. new_atom = DelayedAtomic(name, self.int_delay, self.ext_delay,
  340. add_out_port=self.add_atomic_out_ports, prep_time=self.prep_time, mode="LI-Dynamic")
  341. self.models.append(self.addSubModel(new_atom))
  342. self.connectPorts(self.i_in, new_atom.i_in)
  343. self.width += 1
  344. # print(f"{self.name}: added {new_atom.name} (width is now {self.width})")
  345. return False
  346. return False
  347. class dHI(DEVStoneWrapper):
  348. """
  349. A HI model which grows in width
  350. """
  351. def __init__(self, name: str, depth: int, width: int, int_delay: float, ext_delay: float, prep_time=0, stats=False):
  352. super().__init__(name, depth, width, int_delay, ext_delay, add_atomic_out_ports=True, prep_time=prep_time,
  353. stats=stats, mode="HI-Dynamic")
  354. self.max_gen = 5
  355. if len(self.component_set) > 1:
  356. assert isinstance(self.component_set[-1], AtomicDEVS)
  357. self.connectPorts(self.i_in, self.component_set[-1].i_in)
  358. for idx in range(1, len(self.component_set) - 1):
  359. assert isinstance(self.component_set[idx], AtomicDEVS)
  360. self.connectPorts(self.component_set[idx].o_out, self.component_set[idx + 1].i_in)
  361. self.connectPorts(self.i_in, self.component_set[idx].i_in)
  362. self.generator = self.addSubModel(Fertilizer(self.name + "_gen", repeat=self.max_gen))
  363. def gen_coupled(self):
  364. return dHI("Coupled_%d" % (self.depth - 1), self.depth - 1, self.width, self.int_delay, self.ext_delay,
  365. prep_time=self.prep_time, stats=self.stats)
  366. def modelTransition(self, state):
  367. if state.get("generate", True) and self.depth > 1:
  368. name = "Atomic_%d_%d" % (self.depth - 1, self.width) if self.depth > 1 else "Atomic_0_%d" % self.width
  369. if self.stats:
  370. new_atom = DelayedAtomicStats(name, self.int_delay, self.ext_delay,
  371. add_out_port=self.add_atomic_out_ports, prep_time=self.prep_time, mode="LI-Dynamic")
  372. else:
  373. new_atom = DelayedAtomic(name, self.int_delay, self.ext_delay,
  374. add_out_port=self.add_atomic_out_ports, prep_time=self.prep_time, mode="LI-Dynamic")
  375. last = self.component_set[-2]
  376. self.models.append(self.addSubModel(new_atom))
  377. self.connectPorts(self.i_in, new_atom.i_in)
  378. if isinstance(last, AtomicDEVS) and hasattr(last, "o_out"):
  379. self.connectPorts(last.o_out, new_atom.i_in)
  380. self.width += 1
  381. # print(f"{self.name}: added {new_atom.name} (width is now {self.width})")
  382. return False
  383. return False
  384. if __name__ == '__main__':
  385. import sys
  386. sys.setrecursionlimit(10000)
  387. root = HOmod("Root", 4, 3, 0, 0)
  388. sim = Simulator(root)
  389. sim.setVerbose(None)
  390. # sim.setTerminationTime(10.0)
  391. sim.setStateSaving("custom")
  392. sim.simulate()