diff --git a/nrel/hive/initialization/sample_vehicles.py b/nrel/hive/initialization/sample_vehicles.py index 7c9342e1..1f2b687f 100644 --- a/nrel/hive/initialization/sample_vehicles.py +++ b/nrel/hive/initialization/sample_vehicles.py @@ -75,6 +75,8 @@ def _inner( vehicle_id = f"v{i}" initial_soc = soc_sampling_function() energy = mechatronics.initial_energy(initial_soc) + energy_expended = mechatronics.initial_energy(0) + energy_gained = mechatronics.initial_energy(0) link = location_sampling_function(s) position = EntityPosition(link.link_id, link.start) vehicle_state = Idle.build(vehicle_id) @@ -83,6 +85,8 @@ def _inner( id=vehicle_id, mechatronics_id=mechatronics_id, energy=energy, + energy_expended=energy_expended, + energy_gained=energy_gained, position=position, vehicle_state=vehicle_state, driver_state=driver_state, diff --git a/nrel/hive/model/station/station.py b/nrel/hive/model/station/station.py index 894f5a7c..d0f7d5c9 100644 --- a/nrel/hive/model/station/station.py +++ b/nrel/hive/model/station/station.py @@ -11,6 +11,7 @@ from returns.result import ResultE, Success, Failure from nrel.hive.model.energy.charger import Charger +from nrel.hive.model.energy.energytype import EnergyType from nrel.hive.model.entity import Entity from nrel.hive.model.entity_position import EntityPosition from nrel.hive.model.membership import Membership @@ -57,6 +58,7 @@ class Station(Entity): position: EntityPosition membership: Membership state: immutables.Map[ChargerId, ChargerState] + energy_dispensed: immutables.Map[EnergyType, float] on_shift_access_chargers: FrozenSet[ChargerId] balance: Currency = 0.0 @@ -114,6 +116,7 @@ def _chargers(acc, charger_data): msg = f"internal error after building station chargers for station {id}" raise Exception(msg) + energy_dispensed = immutables.Map({energy_type: 0.0 for energy_type in EnergyType}) position = road_network.position_from_geoid(geoid) if position is None: msg = ( @@ -121,10 +124,12 @@ def _chargers(acc, charger_data): f"provided for station {id}" ) raise H3Error(msg) + return Station( id=id, position=position, state=charger_states, + energy_dispensed=energy_dispensed, on_shift_access_chargers=on_shift_access, membership=membership, ) @@ -408,6 +413,19 @@ def receive_payment(self, currency_received: Currency) -> Station: """ return replace(self, balance=self.balance + currency_received) + def tick_energy_dispensed(self, delta_energy: immutables.Map[EnergyType, float]) -> Station: + """ + adds energy dispensed to vehicle + + :param delta_energy: the energy dispensed for a charge event + :return: the updated Station + """ + energy_dispensed = immutables.Map({ + k: self.energy_dispensed[k] + delta_energy.get(k, 0) + for k in self.energy_dispensed.keys() + }) + return replace(self, energy_dispensed=energy_dispensed) + def enqueue_for_charger(self, charger_id: ChargerId) -> ErrorOr[Station]: """ increment the count of vehicles enqueued for a specific charger_id type - no limit diff --git a/nrel/hive/model/vehicle/mechatronics/bev.py b/nrel/hive/model/vehicle/mechatronics/bev.py index 07ad4ae4..d37b36b9 100644 --- a/nrel/hive/model/vehicle/mechatronics/bev.py +++ b/nrel/hive/model/vehicle/mechatronics/bev.py @@ -173,6 +173,9 @@ def consume_energy(self, vehicle: Vehicle, route: Route) -> Vehicle: updated_vehicle = vehicle.modify_energy( immutables.Map({EnergyType.ELECTRIC: new_energy_kwh}) ) + updated_vehicle = updated_vehicle.tick_energy_expended( + immutables.Map({EnergyType.ELECTRIC: vehicle_energy_kwh - new_energy_kwh}) + ) return updated_vehicle def idle(self, vehicle: Vehicle, time_seconds: Seconds) -> Vehicle: @@ -191,6 +194,9 @@ def idle(self, vehicle: Vehicle, time_seconds: Seconds) -> Vehicle: updated_vehicle = vehicle.modify_energy( immutables.Map({EnergyType.ELECTRIC: new_energy_kwh}) ) + updated_vehicle = updated_vehicle.tick_energy_expended( + immutables.Map({EnergyType.ELECTRIC: vehicle_energy_kwh - new_energy_kwh}) + ) return updated_vehicle @@ -234,5 +240,8 @@ def add_energy( updated_vehicle = vehicle.modify_energy( immutables.Map({EnergyType.ELECTRIC: new_energy_kwh}) ) + updated_vehicle = updated_vehicle.tick_energy_gained( + immutables.Map({EnergyType.ELECTRIC: new_energy_kwh - start_energy_kwh}) + ) return updated_vehicle, time_charging_seconds diff --git a/nrel/hive/model/vehicle/mechatronics/ice.py b/nrel/hive/model/vehicle/mechatronics/ice.py index 3f36b6dc..329e04b8 100644 --- a/nrel/hive/model/vehicle/mechatronics/ice.py +++ b/nrel/hive/model/vehicle/mechatronics/ice.py @@ -150,6 +150,9 @@ def consume_energy(self, vehicle: Vehicle, route: Route) -> Vehicle: updated_vehicle = vehicle.modify_energy( immutables.Map({EnergyType.GASOLINE: new_energy_gal_gas}) ) + updated_vehicle = vehicle.tick_energy_expended( + immutables.Map({EnergyType.GASOLINE: vehicle_energy_gal_gas - new_energy_gal_gas}) + ) return updated_vehicle def idle(self, vehicle: Vehicle, time_seconds: Seconds) -> Vehicle: @@ -168,6 +171,9 @@ def idle(self, vehicle: Vehicle, time_seconds: Seconds) -> Vehicle: updated_vehicle = vehicle.modify_energy( immutables.Map({EnergyType.GASOLINE: new_energy_gal_gas}) ) + updated_vehicle = vehicle.tick_energy_expended( + immutables.Map({EnergyType.GASOLINE: vehicle_energy_gal_gas - new_energy_gal_gas}) + ) return updated_vehicle @@ -196,5 +202,8 @@ def add_energy( new_gal_gas = min(self.tank_capacity_gallons, pump_gal_gas) updated_vehicle = vehicle.modify_energy(immutables.Map({EnergyType.GASOLINE: new_gal_gas})) + updated_vehicle = updated_vehicle.tick_energy_gained( + immutables.Map({EnergyType.GASOLINE: new_gal_gas - start_gal_gas}) + ) return updated_vehicle, time_seconds diff --git a/nrel/hive/model/vehicle/vehicle.py b/nrel/hive/model/vehicle/vehicle.py index ee050b8e..da550fb2 100644 --- a/nrel/hive/model/vehicle/vehicle.py +++ b/nrel/hive/model/vehicle/vehicle.py @@ -45,6 +45,8 @@ class Vehicle(Entity): # mechatronic properties mechatronics_id: MechatronicsId energy: immutables.Map[EnergyType, float] + energy_gained: immutables.Map[EnergyType, float] + energy_expended: immutables.Map[EnergyType, float] # vehicle planning/operational properties vehicle_state: VehicleState @@ -101,6 +103,8 @@ def from_row( f"was not able to find mechatronics '{mechatronics_id}' for vehicle {vehicle_id} in environment: found {found}" ) energy = mechatronics.initial_energy(float(row["initial_soc"])) + energy_expended = mechatronics.initial_energy(0.0) + energy_gained = mechatronics.initial_energy(0.0) schedule_id = row.get( "schedule_id" @@ -130,6 +134,8 @@ def from_row( id=vehicle_id, mechatronics_id=mechatronics_id, energy=energy, + energy_expended=energy_expended, + energy_gained=energy_gained, membership=Membership(), position=start_position, vehicle_state=Idle.build(vehicle_id), @@ -207,6 +213,26 @@ def tick_distance_traveled_km(self, delta_d_km: Kilometers) -> Vehicle: """ return replace(self, distance_traveled_km=self.distance_traveled_km + delta_d_km) + def tick_energy_expended(self, delta_energy: immutables.Map[EnergyType, float]) -> Vehicle: + """ + adds energy expenditure to vehicle + + :param delta_energy: + :return: + """ + energy_expended = immutables.Map({k: self.energy_expended[k] + delta_energy[k] for k in self.energy.keys()}) + return replace(self, energy_expended=energy_expended) + + def tick_energy_gained(self, delta_energy: immutables.Map[EnergyType, float]) -> Vehicle: + """ + adds energy gain to vehicle + + :param delta_energy: + :return: + """ + energy_gained = {k: self.energy_gained[k] + delta_energy[k] for k in self.energy.keys()} + return replace(self, energy_gained=energy_gained) + def set_membership(self, member_ids: Tuple[str, ...]) -> Vehicle: """ sets the membership(s) of the vehicle diff --git a/nrel/hive/reporting/handler/stateful_handler.py b/nrel/hive/reporting/handler/stateful_handler.py index 1e47a919..13ba6807 100644 --- a/nrel/hive/reporting/handler/stateful_handler.py +++ b/nrel/hive/reporting/handler/stateful_handler.py @@ -102,6 +102,7 @@ def station_asdict(station: Station) -> dict: out_dict = asdict(station) del out_dict["id"] del out_dict["state"] + del out_dict["energy_dispensed"] out_dict["station_id"] = station.id out_dict["memberships"] = str(station.membership) diff --git a/nrel/hive/reporting/handler/summary_stats.py b/nrel/hive/reporting/handler/summary_stats.py index ad752ffe..f041a02c 100644 --- a/nrel/hive/reporting/handler/summary_stats.py +++ b/nrel/hive/reporting/handler/summary_stats.py @@ -5,8 +5,12 @@ from dataclasses import dataclass, field from functools import reduce from statistics import mean + +from nrel.hive.model.energy.energytype import EnergyType + from typing import TYPE_CHECKING, Dict, Any + if TYPE_CHECKING: from nrel.hive.runner.runner_payload import RunnerPayload @@ -29,6 +33,12 @@ class SummaryStats: station_revenue: float = 0 fleet_revenue: float = 0 + total_vkwh_expended: float = 0 + total_vgge_expended: float = 0 + + total_skwh_dispensed: float = 0 + total_sgge_dispensed: float = 0 + def compile_stats(self, rp: RunnerPayload) -> Dict[str, Any]: """ computes all stats based on values accumulated throughout this run @@ -76,11 +86,33 @@ def compile_stats(self, rp: RunnerPayload) -> Dict[str, Any]: data = {"observed_percent": observed_pct, "vkt": vkt} vehicle_state_output.update({v: data}) + total_vkwh_expended = 0.0 + total_vgge_expended = 0.0 + for vehicle in rp.s.get_vehicles(): + total_vkwh_expended += vehicle.energy_expended.get(EnergyType.ELECTRIC, 0.0) + total_vgge_expended += vehicle.energy_expended.get(EnergyType.GASOLINE, 0.0) + + self.total_vkwh_expended = total_vkwh_expended + self.total_vgge_expended = total_vgge_expended + + total_skwh_dispensed = 0.0 + total_sgge_dispensed = 0.0 + for station in rp.s.get_stations(): + total_skwh_dispensed += station.energy_dispensed.get(EnergyType.ELECTRIC, 0.0) + total_sgge_dispensed += station.energy_dispensed.get(EnergyType.GASOLINE, 0.0) + + self.total_skwh_dispensed = total_skwh_dispensed + self.total_sgge_dispensed = total_sgge_dispensed + output = { "mean_final_soc": self.mean_final_soc, "requests_served_percent": requests_served_percent, "vehicle_state": vehicle_state_output, "total_vkt": total_vkt, + "total_kwh_expended": total_vkwh_expended, + "total_gge_expended": total_vgge_expended, + "total_kwh_dispensed": total_skwh_dispensed, + "total_gge_dispensed": total_sgge_dispensed, "station_revenue_dollars": self.station_revenue, "fleet_revenue_dollars": self.fleet_revenue, "final_vehicle_count": len(sim_state.vehicles), @@ -109,6 +141,18 @@ def log(self): for s, v in self.vkt.items(): table.add_row(f"Kilometers Traveled in State {s}", f"{round(v, 2)} km") + table.add_row("Total kWh Expended By Vehicles", f"{round(self.total_vkwh_expended, 2)} kWh") + table.add_row( + "Total Gasoline Expended By Vehicles", f"{round(self.total_vgge_expended, 2)} Gal" + ) + + table.add_row( + "Total kWh Dispensed By Stations", f"{round(self.total_skwh_dispensed, 2)} kWh" + ) + table.add_row( + "Total Gasoline Dispensed By Stations", f"{round(self.total_sgge_dispensed, 2)} Gal" + ) + table.add_row("Station Revenue", f"$ {round(self.station_revenue, 2)}") table.add_row("Fleet Revenue", f"$ {round(self.fleet_revenue, 2)}") diff --git a/nrel/hive/resources/mock_lobster.py b/nrel/hive/resources/mock_lobster.py index a69aeadd..70d6d6d0 100644 --- a/nrel/hive/resources/mock_lobster.py +++ b/nrel/hive/resources/mock_lobster.py @@ -371,6 +371,8 @@ def mock_vehicle( v_state = vehicle_state if vehicle_state else Idle.build(vehicle_id) road_network = mock_network(h3_res) initial_energy = mechatronics.initial_energy(soc) + energy_expended = mechatronics.initial_energy(0.0) + energy_gained = mechatronics.initial_energy(0.0) geoid = h3.geo_to_h3(lat, lon, road_network.sim_h3_resolution) d_state = ( driver_state @@ -386,6 +388,8 @@ def mock_vehicle( id=vehicle_id, mechatronics_id=mechatronics.mechatronics_id, energy=initial_energy, + energy_expended=energy_expended, + energy_gained=energy_gained, position=position, vehicle_state=v_state, driver_state=d_state, @@ -406,6 +410,9 @@ def mock_vehicle_from_geoid( ) -> Vehicle: state = vehicle_state if vehicle_state else Idle.build(vehicle_id) initial_energy = mechatronics.initial_energy(soc) + energy_expended = mechatronics.initial_energy(0.0) + energy_gained = mechatronics.initial_energy(0.0) + d_state = ( driver_state if driver_state @@ -420,6 +427,8 @@ def mock_vehicle_from_geoid( id=vehicle_id, mechatronics_id=mechatronics.mechatronics_id, energy=initial_energy, + energy_expended=energy_expended, + energy_gained=energy_gained, position=position, vehicle_state=state, driver_state=d_state, diff --git a/nrel/hive/state/vehicle_state/vehicle_state_ops.py b/nrel/hive/state/vehicle_state/vehicle_state_ops.py index e9dd4ab5..214e0e46 100644 --- a/nrel/hive/state/vehicle_state/vehicle_state_ops.py +++ b/nrel/hive/state/vehicle_state/vehicle_state_ops.py @@ -1,7 +1,7 @@ from __future__ import annotations from typing import Tuple, Optional, NamedTuple, TYPE_CHECKING - +import immutables from nrel.hive.model.entity_position import EntityPosition from nrel.hive.model.roadnetwork.route import empty_route from nrel.hive.model.roadnetwork.routetraversal import traverse, RouteTraversal @@ -97,6 +97,9 @@ def charge( # perform updates updated_vehicle = charged_vehicle.send_payment(charging_price) updated_station = station.receive_payment(charging_price) + updated_station = updated_station.tick_energy_dispensed( + immutables.Map({charger.energy_type: kwh_transacted}) + ) veh_error, sim_with_vehicle = simulation_state_ops.modify_vehicle(sim, updated_vehicle) if veh_error: