| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260 |
- import numpy as np
- from pypdevs.DEVS import AtomicDEVS, CoupledDEVS
- from pypdevs.infinity import INFINITY
- from dataclasses import dataclass, field
- import random
- import pandas as pd
- from de2.routing import get_graph, get_closest_vertex
- TUGS = pd.read_excel("20230405_Tugs.xlsx", dtype={"MMSI": str, "NAME": str, "category": str})
- @dataclass
- class Vessel:
- mmsi: str
- velocity: float = 0.0
- source: tuple = None
- target: tuple = None
- task: str = None
- distance_left: float = 0.0
- total_distance: float = 0.0
- time_until_departure: float = 0.0
- start: float = 0
- ETA: float = 0
- name: str = ""
- def tuple(self):
- return self.mmsi, self.name,\
- self.source, self.target,\
- self.task, self.total_distance, self.distance_left, self.velocity
- @staticmethod
- def knots_to_mps(knots):
- return knots * 1852. / 3600.
- @staticmethod
- def ecological():
- return Vessel.knots_to_mps(6), Vessel.knots_to_mps(7)
- class Pool(AtomicDEVS):
- def __init__(self, name):
- super(Pool, self).__init__(name)
- self.state = {
- "waiting": {},
- "should_exit": [],
- "delayed": [],
- "time": 0.0,
- "errors": []
- }
- self.req_in = self.addInPort("req_in")
- self.vessel_in = self.addInPort("vessel_in")
- self.vessel_out = self.addOutPort("vessel_out")
- self.error = self.addOutPort("error")
- # prefill with all vessels of the simulation
- for mmsi in TUGS["MMSI"]:
- if isinstance(mmsi, str):
- self.state["waiting"][mmsi] = Vessel(mmsi)
- def request_vessel(self, vessel, request):
- vessel.velocity = request["velocity"]
- if vessel.velocity >= 0: # requests for idling should be ignored!
- vessel.distance_left = request["distance"]
- vessel.total_distance = vessel.distance_left
- # vessel.time_until_departure = vessel.distance_left / vessel.velocity
- vessel.source = request["source_lon"], request["source_lat"]
- vessel.target = request["target_lon"], request["target_lat"]
- vessel.task = request["task"]
- vessel.start = request["start"]
- vessel.ETA = request["ETA"]
- vessel.name = request["name"]
- self.state["should_exit"].append(vessel)
- if vessel.mmsi in self.state["waiting"]:
- del self.state["waiting"][vessel.mmsi]
- def extTransition(self, inputs):
- self.state["time"] += self.elapsed
- if self.req_in in inputs:
- for request in inputs[self.req_in]:
- if request["mmsi"] in self.state["waiting"]:
- vessel = self.state["waiting"][request["mmsi"]]
- self.request_vessel(vessel, request)
- else:
- self.state["errors"].append("%4.4f Vessel %s does not exist in pool - delaying request" % (self.state["time"], str(request["mmsi"])))
- self.state["delayed"].append(request)
- if self.vessel_in in inputs:
- for vessel in inputs[self.vessel_in]:
- if vessel.mmsi in self.state["waiting"]:
- self.state["errors"].append("[WARN] %4.4f Cannot create duplicate vessels (mmsi = %s; d = %f)" %
- (self.state["time"], str(vessel.mmsi), vessel.total_distance))
- else:
- vessel.source = vessel.target
- vessel.target = None
- self.state["waiting"][vessel.mmsi] = vessel
- for ix, r in enumerate(self.state["delayed"]):
- if r["mmsi"] == vessel.mmsi:
- self.request_vessel(vessel, r)
- self.state["delayed"].pop(ix)
- print("[INFO] %4.4f Delay for vessel %s cleared" % (self.state["time"], r["mmsi"]))
- break
- return self.state
- def timeAdvance(self):
- if len(self.state["should_exit"]) > 0 or len(self.state["errors"]) > 0:
- return 0.0
- return INFINITY
- def outputFnc(self):
- res = {}
- if len(self.state["should_exit"]) > 0:
- res[self.vessel_out] = [self.state["should_exit"][0]]
- if len(self.state["errors"]) > 0:
- res[self.error] = self.state["errors"]
- return res
- def intTransition(self):
- self.state["time"] += self.timeAdvance()
- if len(self.state["should_exit"]) > 0:
- self.state["should_exit"].pop(0)
- if len(self.state["errors"]) > 0:
- self.state["errors"].clear()
- return self.state
- class Sailer(AtomicDEVS):
- def __init__(self, name):
- super(Sailer, self).__init__(name)
- self.state = {
- "vessels": [],
- "time": 0.0
- }
- self.vessel_in = self.addInPort("vessel_in")
- self.vessel_out = self.addOutPort("vessel_out")
- def extTransition(self, inputs):
- self.state["time"] += self.elapsed
- self.update_vessels(self.elapsed)
- if self.vessel_in in inputs:
- for vessel in inputs[self.vessel_in]:
- if vessel.distance_left == 0.0:
- vessel.time_until_departure = 0.0
- else:
- vessel.time_until_departure = vessel.distance_left / vessel.velocity
- self.state["vessels"].append(vessel)
- self.state["vessels"].sort(key=lambda v: v.time_until_departure)
- return self.state
- def update_vessels(self, elapsed):
- for vessel in self.state["vessels"]:
- x = vessel.velocity * elapsed
- vessel.distance_left = max(0.0, vessel.distance_left - x)
- vessel.time_until_departure = round(max(0.0, vessel.time_until_departure - elapsed), 6)
- def timeAdvance(self):
- if len(self.state["vessels"]) > 0:
- v = self.state["vessels"][0]
- return v.time_until_departure
- return INFINITY
- def outputFnc(self):
- if len(self.state["vessels"]) > 0:
- return {
- self.vessel_out: [self.state["vessels"][0]]
- }
- return {}
- def intTransition(self):
- elapsed = self.timeAdvance()
- self.state["time"] += elapsed
- self.state["vessels"].pop(0)
- self.update_vessels(elapsed)
- if len(self.state["vessels"]) > 0:
- self.state["vessels"].sort(key=lambda v: v.time_until_departure)
- return self.state
- class RoutePlanner(AtomicDEVS):
- def __init__(self, name):
- super(RoutePlanner, self).__init__(name)
- self.graph = get_graph()
- self.state = {
- "request": []
- }
- self.req_in = self.addInPort("req_in")
- self.req_out = self.addOutPort("req_out")
- def timeAdvance(self):
- if len(self.state["request"]) == 0:
- return INFINITY
- return 0.0
- def extTransition(self, inputs):
- if self.req_in in inputs:
- for request in inputs[self.req_in]:
- tug = TUGS[TUGS["MMSI"] == str(request["mmsi"])].iloc[0]
- request["name"] = tug["NAME"]
- request["source_lon"], request["source_lat"] = eval(request["source"])
- request["target_lon"], request["target_lat"] = eval(request["target"])
- src_vertex, _ = get_closest_vertex(self.graph, request["source_lon"], request["source_lat"])
- tgt_vertex, _ = get_closest_vertex(self.graph, request["target_lon"], request["target_lat"])
- request["distance"] = self.graph.distances([src_vertex.index], [tgt_vertex.index], weights="distance", mode="all")[0][0]
- # TODO: Use the fastest possible ecological velocity to apply to the trajectory
- # Doing the task faster than ETA-start is also allowed!
- request["velocity"] = request["distance"] / (request["ETA"] - request["start"])
- request["velocity"] = max(request["velocity"], Vessel.ecological()[1])
- self.state["request"].append(request)
- return self.state
- def outputFnc(self):
- if len(self.state["request"]) == 0:
- return {}
- return { self.req_out: [self.state["request"][0]] }
- def intTransition(self):
- if len(self.state["request"]) > 0:
- self.state["request"].pop(0)
- return self.state
- class Clock(AtomicDEVS):
- def __init__(self, name, interval=1):
- super(Clock, self).__init__(name)
- self.interval = interval
- self.state = {
- "time": 0.0
- }
- self.outp = self.addOutPort("outp")
- def timeAdvance(self):
- return self.interval
- def outputFnc(self):
- return {
- self.outp: [True]
- }
- def intTransition(self):
- self.state["time"] += self.timeAdvance()
- return self.state
|