Source code for nrel.hive.model.vehicle.mechatronics.ice

from __future__ import annotations

import logging
from dataclasses import dataclass
from typing import Any, Callable, TYPE_CHECKING, Optional, Tuple

import immutables

from nrel.hive.model.energy.energytype import EnergyType
from nrel.hive.model.vehicle.mechatronics.mechatronics_interface import MechatronicsInterface
from nrel.hive.model.vehicle.mechatronics.powertrain import build_powertrain
from nrel.hive.util.typealiases import MechatronicsId
from nrel.hive.util.units import *

if TYPE_CHECKING:
    from nrel.hive.model.energy.charger.charger import Charger
    from nrel.hive.model.vehicle.vehicle import Vehicle
    from nrel.hive.model.roadnetwork.route import Route
    from nrel.hive.model.vehicle.mechatronics.powertrain.powertrain import Powertrain

log = logging.getLogger(__name__)


[docs]@dataclass(frozen=True) class ICE(MechatronicsInterface): """ Mechatronics for an internal combustion engine (ICE) """ mechatronics_id: MechatronicsId tank_capacity_gallons: GallonGasoline idle_gallons_per_hour: GallonPerHour powertrain: Powertrain nominal_miles_per_gallon: MilesPerGallon
[docs] @classmethod def from_dict( cls, d: Dict, custom_powertrain_constructor: Optional[Callable[[Dict[str, Any]], Powertrain]] = None, ) -> ICE: """ build from a dictionary :param d: the dictionary to build from :return: the built Mechatronics object """ tank_capacity_gallons = float(d["tank_capacity_gallons"]) idle_gallons_per_hour = float(d["idle_gallons_per_hour"]) nominal_miles_per_gallon = float(d["nominal_miles_per_gallon"]) # set scale factor in config dict so the tabular powertrain can use it to scale the normalized lookup updated_d = d.copy() updated_d["scale_factor"] = 1 / nominal_miles_per_gallon if custom_powertrain_constructor is None: powertrain = build_powertrain(updated_d) else: powertrain = custom_powertrain_constructor(updated_d) return ICE( mechatronics_id=updated_d["mechatronics_id"], tank_capacity_gallons=tank_capacity_gallons, idle_gallons_per_hour=idle_gallons_per_hour, powertrain=powertrain, nominal_miles_per_gallon=nominal_miles_per_gallon, )
[docs] def valid_charger(self, charger: Charger) -> bool: """ checks to make sure charger is gasoline energy type :param charger: the charger to check :return: true/false """ return charger.energy_type == EnergyType.GASOLINE
[docs] def initial_energy(self, percent_full: Ratio) -> immutables.Map[EnergyType, float]: """ return an energy dictionary from an initial soc :param percent_full: :return: """ return immutables.Map({EnergyType.GASOLINE: self.tank_capacity_gallons * percent_full})
[docs] def range_remaining_km(self, vehicle: Vehicle) -> Kilometers: """ how much range remains, in kilometers :return: """ energy_gal_gas = vehicle.energy[EnergyType.GASOLINE] miles = energy_gal_gas * self.nominal_miles_per_gallon km = miles * MILE_TO_KM return km
[docs] def calc_required_soc(self, required_range: Kilometers) -> Ratio: """ what is the required tank capacity to travel a given distance :param required_range: the distance the vehicle needs to travel :return: """ miles = required_range / MILE_TO_KM required_energy_gal_gas = miles / self.nominal_miles_per_gallon return required_energy_gal_gas / self.tank_capacity_gallons
[docs] def fuel_source_soc(self, vehicle: Vehicle) -> Ratio: """ what is the level of the fuel tank :return: """ energy_gal_gas = vehicle.energy[EnergyType.GASOLINE] return energy_gal_gas / self.tank_capacity_gallons
[docs] def is_empty(self, vehicle: Vehicle) -> bool: """ is the vehicle empty :param vehicle: :return: """ return vehicle.energy[EnergyType.GASOLINE] <= 0
[docs] def is_full(self, vehicle: Vehicle) -> bool: """ is the vehicle full :param vehicle: :return: """ return vehicle.energy[EnergyType.GASOLINE] >= self.tank_capacity_gallons
[docs] def consume_energy(self, vehicle: Vehicle, route: Route) -> Vehicle: """ consume energy over a route :param vehicle: :param route: :return: """ energy_used = self.powertrain.energy_cost(route) energy_used_gal_gas = energy_used * get_unit_conversion( self.powertrain.energy_units, Unit.GALLON_GASOLINE ) vehicle_energy_gal_gas = vehicle.energy[EnergyType.GASOLINE] new_energy_gal_gas = max(0.0, vehicle_energy_gal_gas - energy_used_gal_gas) 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
[docs] def idle(self, vehicle: Vehicle, time_seconds: Seconds) -> Vehicle: """ idle for a set amount of time :param vehicle: :param time_seconds: :return: """ idle_energy_gal_gas = self.idle_gallons_per_hour * time_seconds * SECONDS_TO_HOURS vehicle_energy_gal_gas = vehicle.energy[EnergyType.GASOLINE] new_energy_gal_gas = max(0.0, vehicle_energy_gal_gas - idle_energy_gal_gas) 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
[docs] def add_energy( self, vehicle: Vehicle, charger: Charger, time_seconds: Seconds ) -> Tuple[Vehicle, Seconds]: """ add energy into the system. units for the charger are gallons per second :param vehicle: :param charger: :param time_seconds: :return: the updated vehicle, along with the time spent charging """ if not self.valid_charger(charger): log.warning( f"ICE vehicle attempting to use charger of energy type: {charger.energy_type}. Not charging." ) return vehicle, 0 start_gal_gas = vehicle.energy[EnergyType.GASOLINE] pump_gal_gas = start_gal_gas + charger.rate * time_seconds 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