diff --git a/nrel/hive/dispatcher/instruction/instructions.py b/nrel/hive/dispatcher/instruction/instructions.py index d4129e89..57610e5b 100644 --- a/nrel/hive/dispatcher/instruction/instructions.py +++ b/nrel/hive/dispatcher/instruction/instructions.py @@ -21,6 +21,7 @@ from nrel.hive.state.vehicle_state.dispatch_trip import DispatchTrip from nrel.hive.state.vehicle_state.idle import Idle from nrel.hive.state.vehicle_state.repositioning import Repositioning +from nrel.hive.state.vehicle_state.out_of_service import OutOfService from nrel.hive.state.vehicle_state.reserve_base import ReserveBase from nrel.hive.state.vehicle_state.servicing_pooling_trip import ServicingPoolingTrip from nrel.hive.util.exception import SimulationStateError, InstructionError @@ -341,3 +342,22 @@ def apply_instruction( next_state = ReserveBase.build(self.vehicle_id, self.base_id) return None, InstructionResult(prev_state, next_state) + + +@dataclass(frozen=True) +class OutOfServiceInstruction(Instruction): + vehicle_id: VehicleId + + def apply_instruction( + self, sim_state: SimulationState, env: Environment + ) -> Tuple[Optional[Exception], Optional[InstructionResult]]: + vehicle = sim_state.vehicles.get(self.vehicle_id) + if not vehicle: + msg = ( + f"vehicle {self.vehicle_id} not found; context: applying out of service instruction" + ) + return (SimulationStateError(msg), None) + + prev_state = vehicle.vehicle_state + next_state = OutOfService.build(self.vehicle_id) + return None, InstructionResult(prev_state, next_state) diff --git a/nrel/hive/model/roadnetwork/link_id.py b/nrel/hive/model/roadnetwork/link_id.py index 45dc1783..b8df5b73 100644 --- a/nrel/hive/model/roadnetwork/link_id.py +++ b/nrel/hive/model/roadnetwork/link_id.py @@ -1,6 +1,4 @@ -from ast import literal_eval from typing import Optional, Tuple, Any - from nrel.hive.util.typealiases import LinkId NodeId = Any @@ -39,14 +37,36 @@ def extract_node_ids( ) else: try: - src = literal_eval(result[0]) - dst = literal_eval(result[1]) + src = str(result[0]) + dst = str(result[1]) except ValueError: return Exception(f"LinkId {link_id} cannot be parsed."), None return None, (src, dst) +def extract_node_ids_int(link_id: LinkId) -> Tuple[Optional[Exception], Optional[Tuple[int, int]]]: + """node ids can be strings for the haversine case but for networkx graphs they must be + integers as they are treated as indices. + + :param link_id: link id to read + :type link_id: LinkId + :return: _description_ + :rtype: Tuple[Optional[Exception], Optional[Tuple[int, int]]] + """ + err, ids = extract_node_ids(link_id) + if err is not None or ids is None: + return err, None + else: + try: + src_str, dst_str = ids + src = int(src_str) + dst = int(dst_str) + return None, (src, dst) + except ValueError as e: + return e, None + + def reverse_link_id( link_id: LinkId, ) -> Tuple[Optional[Exception], Optional[LinkId]]: diff --git a/nrel/hive/model/roadnetwork/osm/osm_roadnetwork.py b/nrel/hive/model/roadnetwork/osm/osm_roadnetwork.py index afa7a541..dd683505 100644 --- a/nrel/hive/model/roadnetwork/osm/osm_roadnetwork.py +++ b/nrel/hive/model/roadnetwork/osm/osm_roadnetwork.py @@ -10,7 +10,7 @@ from nrel.hive.model.entity_position import EntityPosition from nrel.hive.model.roadnetwork.link import Link -from nrel.hive.model.roadnetwork.link_id import extract_node_ids +from nrel.hive.model.roadnetwork.link_id import extract_node_ids_int from nrel.hive.model.roadnetwork.osm.osm_builders import osm_graph_from_polygon from nrel.hive.model.roadnetwork.osm.osm_road_network_link_helper import OSMRoadNetworkLinkHelper from nrel.hive.model.roadnetwork.osm.osm_roadnetwork_ops import ( @@ -50,6 +50,7 @@ def __init__( # validate network # road network must be strongly connected + log.info(f"testing graph connectivity") if not nx.is_strongly_connected(graph): raise RuntimeError("Only strongly connected graphs are allowed.") @@ -66,6 +67,7 @@ def _valid_node_id(nid: Union[int, tuple]) -> bool: raise TypeError("all node ids must be either an integer or a tuple of integers") # check to make sure the graph has the right information on the links + log.info(f"testing graph speed data") missing_speed = 0 missing_time = 0 for u, v, d in graph.edges(data=True): @@ -98,6 +100,7 @@ def _valid_node_id(nid: Union[int, tuple]) -> bool: ) # build tables on the network edges for spatial lookup and LinkId lookup + log.info(f"building spatial index for graph") link_helper_error, link_helper = OSMRoadNetworkLinkHelper.build( graph, sim_h3_resolution, default_speed_kmph ) @@ -155,6 +158,7 @@ def from_file( if road_network_path.suffix == ".json": with road_network_path.open("r") as f: graph = nx.node_link_graph(json.load(f)) + log.info(f"loaded graph with {len(graph.edges)} edges") return OSMRoadNetwork(graph, sim_h3_resolution, default_speed_kmph) else: raise TypeError( @@ -188,8 +192,8 @@ def _astar_cost_heuristic(source, dest) -> float: # start path search from the end of the origin link, terminate search at the start of the # destination link - extract_src_err, src_nodes = extract_node_ids(origin.link_id) - extract_dst_err, dst_nodes = extract_node_ids(destination.link_id) + extract_src_err, src_nodes = extract_node_ids_int(origin.link_id) + extract_dst_err, dst_nodes = extract_node_ids_int(destination.link_id) if extract_src_err: log.error(extract_src_err) return empty_route() diff --git a/nrel/hive/model/station/station.py b/nrel/hive/model/station/station.py index 71f0cff4..f5f3b9b7 100644 --- a/nrel/hive/model/station/station.py +++ b/nrel/hive/model/station/station.py @@ -70,7 +70,7 @@ def geoid(self) -> GeoId: @classmethod def build( cls, - id: StationId, + station_id: StationId, geoid: GeoId, road_network: RoadNetwork, chargers: immutables.Map[ChargerId, int], @@ -100,7 +100,7 @@ def _chargers(acc, charger_data): charger = env.chargers.get(charger_id) if charger is None: msg = ( - f"attempting to create station {id} with charger type {charger_id} " + f"attempting to create station {station_id} with charger type {charger_id} " f"but that charger type has not been defined for this scenario" ) return TypeError(msg), None @@ -114,7 +114,7 @@ def _chargers(acc, charger_data): if error is not None: raise error if charger_states is None: - msg = f"internal error after building station chargers for station {id}" + msg = f"internal error after building station chargers for station {station_id}" raise Exception(msg) energy_dispensed = immutables.Map({energy_type: 0.0 for energy_type in EnergyType}) @@ -122,12 +122,12 @@ def _chargers(acc, charger_data): if position is None: msg = ( "could not find a road network position matching the position " - f"provided for station {id}" + f"provided for station {station_id}" ) raise H3Error(msg) return Station( - id=id, + id=station_id, position=position, state=charger_states, energy_dispensed=energy_dispensed, @@ -222,7 +222,7 @@ def from_row( elif station_id not in builder: # create this station return Station.build( - id=station_id, + station_id=station_id, geoid=geoid, road_network=road_network, chargers=immutables.Map({charger_id: charger_count}), diff --git a/nrel/hive/model/vehicle/mechatronics/bev.py b/nrel/hive/model/vehicle/mechatronics/bev.py index d37b36b9..91f70b2c 100644 --- a/nrel/hive/model/vehicle/mechatronics/bev.py +++ b/nrel/hive/model/vehicle/mechatronics/bev.py @@ -151,8 +151,10 @@ def is_full(self, vehicle: Vehicle) -> bool: :param vehicle: :return: """ + battery = vehicle.energy[EnergyType.ELECTRIC] full_kwh = self.battery_capacity_kwh - self.battery_full_threshold_kwh - return vehicle.energy[EnergyType.ELECTRIC] >= full_kwh + is_full = battery >= full_kwh + return is_full def consume_energy(self, vehicle: Vehicle, route: Route) -> Vehicle: """ diff --git a/nrel/hive/model/vehicle/mechatronics/powercurve/tabular_powercurve.py b/nrel/hive/model/vehicle/mechatronics/powercurve/tabular_powercurve.py index aa67e7cc..6565a8bd 100644 --- a/nrel/hive/model/vehicle/mechatronics/powercurve/tabular_powercurve.py +++ b/nrel/hive/model/vehicle/mechatronics/powercurve/tabular_powercurve.py @@ -55,8 +55,14 @@ def __init__( self.step_size_seconds = data["step_size_seconds"] # seconds if self.energy_type is None: + pt = data.get("power_type") + if pt is None: + pt_msg = "no power_type argument" + else: + pt_msg = f"invalid energy type '{data['power_type']}'" raise AttributeError( - f"TabularPowercurve initialized with invalid energy type {self.energy_type}" + f"TabularPowercurve configuration has {pt_msg}; " + f"should be one of {{electric, gasoline}} " ) charging_model = sorted(data["power_curve"], key=lambda x: x["energy_kwh"]) diff --git a/nrel/hive/resources/mock_lobster.py b/nrel/hive/resources/mock_lobster.py index 2738606a..00e65ac5 100644 --- a/nrel/hive/resources/mock_lobster.py +++ b/nrel/hive/resources/mock_lobster.py @@ -341,7 +341,7 @@ def mock_station_from_geoid( env = mock_env() return Station.build( - id=station_id, + station_id=station_id, geoid=geoid, road_network=road_network, chargers=chargers,