elements.py 7.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260
  1. import numpy as np
  2. from pypdevs.DEVS import AtomicDEVS, CoupledDEVS
  3. from pypdevs.infinity import INFINITY
  4. from dataclasses import dataclass, field
  5. import random
  6. import pandas as pd
  7. from de2.routing import get_graph, get_closest_vertex
  8. TUGS = pd.read_excel("20230405_Tugs.xlsx", dtype={"MMSI": str, "NAME": str, "category": str})
  9. @dataclass
  10. class Vessel:
  11. mmsi: str
  12. velocity: float = 0.0
  13. source: tuple = None
  14. target: tuple = None
  15. task: str = None
  16. distance_left: float = 0.0
  17. total_distance: float = 0.0
  18. time_until_departure: float = 0.0
  19. start: float = 0
  20. ETA: float = 0
  21. name: str = ""
  22. def tuple(self):
  23. return self.mmsi, self.name,\
  24. self.source, self.target,\
  25. self.task, self.total_distance, self.distance_left, self.velocity
  26. @staticmethod
  27. def knots_to_mps(knots):
  28. return knots * 1852. / 3600.
  29. @staticmethod
  30. def ecological():
  31. return Vessel.knots_to_mps(6), Vessel.knots_to_mps(7)
  32. class Pool(AtomicDEVS):
  33. def __init__(self, name):
  34. super(Pool, self).__init__(name)
  35. self.state = {
  36. "waiting": {},
  37. "should_exit": [],
  38. "delayed": [],
  39. "time": 0.0,
  40. "errors": []
  41. }
  42. self.req_in = self.addInPort("req_in")
  43. self.vessel_in = self.addInPort("vessel_in")
  44. self.vessel_out = self.addOutPort("vessel_out")
  45. self.error = self.addOutPort("error")
  46. # prefill with all vessels of the simulation
  47. for mmsi in TUGS["MMSI"]:
  48. if isinstance(mmsi, str):
  49. self.state["waiting"][mmsi] = Vessel(mmsi)
  50. def request_vessel(self, vessel, request):
  51. vessel.velocity = request["velocity"]
  52. if vessel.velocity >= 0: # requests for idling should be ignored!
  53. vessel.distance_left = request["distance"]
  54. vessel.total_distance = vessel.distance_left
  55. # vessel.time_until_departure = vessel.distance_left / vessel.velocity
  56. vessel.source = request["source_lon"], request["source_lat"]
  57. vessel.target = request["target_lon"], request["target_lat"]
  58. vessel.task = request["task"]
  59. vessel.start = request["start"]
  60. vessel.ETA = request["ETA"]
  61. vessel.name = request["name"]
  62. self.state["should_exit"].append(vessel)
  63. if vessel.mmsi in self.state["waiting"]:
  64. del self.state["waiting"][vessel.mmsi]
  65. def extTransition(self, inputs):
  66. self.state["time"] += self.elapsed
  67. if self.req_in in inputs:
  68. for request in inputs[self.req_in]:
  69. if request["mmsi"] in self.state["waiting"]:
  70. vessel = self.state["waiting"][request["mmsi"]]
  71. self.request_vessel(vessel, request)
  72. else:
  73. self.state["errors"].append("%4.4f Vessel %s does not exist in pool - delaying request" % (self.state["time"], str(request["mmsi"])))
  74. self.state["delayed"].append(request)
  75. if self.vessel_in in inputs:
  76. for vessel in inputs[self.vessel_in]:
  77. if vessel.mmsi in self.state["waiting"]:
  78. self.state["errors"].append("[WARN] %4.4f Cannot create duplicate vessels (mmsi = %s; d = %f)" %
  79. (self.state["time"], str(vessel.mmsi), vessel.total_distance))
  80. else:
  81. vessel.source = vessel.target
  82. vessel.target = None
  83. self.state["waiting"][vessel.mmsi] = vessel
  84. for ix, r in enumerate(self.state["delayed"]):
  85. if r["mmsi"] == vessel.mmsi:
  86. self.request_vessel(vessel, r)
  87. self.state["delayed"].pop(ix)
  88. print("[INFO] %4.4f Delay for vessel %s cleared" % (self.state["time"], r["mmsi"]))
  89. break
  90. return self.state
  91. def timeAdvance(self):
  92. if len(self.state["should_exit"]) > 0 or len(self.state["errors"]) > 0:
  93. return 0.0
  94. return INFINITY
  95. def outputFnc(self):
  96. res = {}
  97. if len(self.state["should_exit"]) > 0:
  98. res[self.vessel_out] = [self.state["should_exit"][0]]
  99. if len(self.state["errors"]) > 0:
  100. res[self.error] = self.state["errors"]
  101. return res
  102. def intTransition(self):
  103. self.state["time"] += self.timeAdvance()
  104. if len(self.state["should_exit"]) > 0:
  105. self.state["should_exit"].pop(0)
  106. if len(self.state["errors"]) > 0:
  107. self.state["errors"].clear()
  108. return self.state
  109. class Sailer(AtomicDEVS):
  110. def __init__(self, name):
  111. super(Sailer, self).__init__(name)
  112. self.state = {
  113. "vessels": [],
  114. "time": 0.0
  115. }
  116. self.vessel_in = self.addInPort("vessel_in")
  117. self.vessel_out = self.addOutPort("vessel_out")
  118. def extTransition(self, inputs):
  119. self.state["time"] += self.elapsed
  120. self.update_vessels(self.elapsed)
  121. if self.vessel_in in inputs:
  122. for vessel in inputs[self.vessel_in]:
  123. if vessel.distance_left == 0.0:
  124. vessel.time_until_departure = 0.0
  125. else:
  126. vessel.time_until_departure = vessel.distance_left / vessel.velocity
  127. self.state["vessels"].append(vessel)
  128. self.state["vessels"].sort(key=lambda v: v.time_until_departure)
  129. return self.state
  130. def update_vessels(self, elapsed):
  131. for vessel in self.state["vessels"]:
  132. x = vessel.velocity * elapsed
  133. vessel.distance_left = max(0.0, vessel.distance_left - x)
  134. vessel.time_until_departure = round(max(0.0, vessel.time_until_departure - elapsed), 6)
  135. def timeAdvance(self):
  136. if len(self.state["vessels"]) > 0:
  137. v = self.state["vessels"][0]
  138. return v.time_until_departure
  139. return INFINITY
  140. def outputFnc(self):
  141. if len(self.state["vessels"]) > 0:
  142. return {
  143. self.vessel_out: [self.state["vessels"][0]]
  144. }
  145. return {}
  146. def intTransition(self):
  147. elapsed = self.timeAdvance()
  148. self.state["time"] += elapsed
  149. self.state["vessels"].pop(0)
  150. self.update_vessels(elapsed)
  151. if len(self.state["vessels"]) > 0:
  152. self.state["vessels"].sort(key=lambda v: v.time_until_departure)
  153. return self.state
  154. class RoutePlanner(AtomicDEVS):
  155. def __init__(self, name):
  156. super(RoutePlanner, self).__init__(name)
  157. self.graph = get_graph()
  158. self.state = {
  159. "request": []
  160. }
  161. self.req_in = self.addInPort("req_in")
  162. self.req_out = self.addOutPort("req_out")
  163. def timeAdvance(self):
  164. if len(self.state["request"]) == 0:
  165. return INFINITY
  166. return 0.0
  167. def extTransition(self, inputs):
  168. if self.req_in in inputs:
  169. for request in inputs[self.req_in]:
  170. tug = TUGS[TUGS["MMSI"] == str(request["mmsi"])].iloc[0]
  171. request["name"] = tug["NAME"]
  172. request["source_lon"], request["source_lat"] = eval(request["source"])
  173. request["target_lon"], request["target_lat"] = eval(request["target"])
  174. src_vertex, _ = get_closest_vertex(self.graph, request["source_lon"], request["source_lat"])
  175. tgt_vertex, _ = get_closest_vertex(self.graph, request["target_lon"], request["target_lat"])
  176. request["distance"] = self.graph.distances([src_vertex.index], [tgt_vertex.index], weights="distance", mode="all")[0][0]
  177. # TODO: Use the fastest possible ecological velocity to apply to the trajectory
  178. # Doing the task faster than ETA-start is also allowed!
  179. request["velocity"] = request["distance"] / (request["ETA"] - request["start"])
  180. request["velocity"] = max(request["velocity"], Vessel.ecological()[1])
  181. self.state["request"].append(request)
  182. return self.state
  183. def outputFnc(self):
  184. if len(self.state["request"]) == 0:
  185. return {}
  186. return { self.req_out: [self.state["request"][0]] }
  187. def intTransition(self):
  188. if len(self.state["request"]) > 0:
  189. self.state["request"].pop(0)
  190. return self.state
  191. class Clock(AtomicDEVS):
  192. def __init__(self, name, interval=1):
  193. super(Clock, self).__init__(name)
  194. self.interval = interval
  195. self.state = {
  196. "time": 0.0
  197. }
  198. self.outp = self.addOutPort("outp")
  199. def timeAdvance(self):
  200. return self.interval
  201. def outputFnc(self):
  202. return {
  203. self.outp: [True]
  204. }
  205. def intTransition(self):
  206. self.state["time"] += self.timeAdvance()
  207. return self.state