diff --git a/cal_and_val/thermal/f3-vehicles/2021_Hyundai_Sonata_Hybrid_Blue.yaml b/cal_and_val/thermal/f3-vehicles/2021_Hyundai_Sonata_Hybrid_Blue.yaml index 450b4c57..7a61ed67 100644 --- a/cal_and_val/thermal/f3-vehicles/2021_Hyundai_Sonata_Hybrid_Blue.yaml +++ b/cal_and_val/thermal/f3-vehicles/2021_Hyundai_Sonata_Hybrid_Blue.yaml @@ -15,8 +15,8 @@ pt_type: energy_capacity_joules: 5760000.0 eff_interp: Interp0D: 0.9848857801796105 - min_soc: 0.0 - max_soc: 1.0 + min_soc: 0.5 + max_soc: 0.95 save_interval: 1 fs: pwr_out_max_watts: 2000000.0 diff --git a/fastsim-core/src/vehicle/cabin.rs b/fastsim-core/src/vehicle/cabin.rs index d9cb72f6..6c47da43 100644 --- a/fastsim-core/src/vehicle/cabin.rs +++ b/fastsim-core/src/vehicle/cabin.rs @@ -13,6 +13,28 @@ pub enum CabinOption { #[default] None, } +impl SaveState for CabinOption { + fn save_state(&mut self) { + match self { + Self::LumpedCabin(lc) => lc.save_state(), + Self::LumpedCabinWithShell => { + todo!() + } + Self::None => {} + } + } +} +impl Step for CabinOption { + fn step(&mut self) { + match self { + Self::LumpedCabin(lc) => lc.step(), + Self::LumpedCabinWithShell => { + todo!() + } + Self::None => {} + } + } +} impl Init for CabinOption { fn init(&mut self) -> anyhow::Result<()> { match self { @@ -133,9 +155,7 @@ impl LumpedCabin { } #[fastsim_api] -#[derive( - Clone, Copy, Debug, Default, Deserialize, Serialize, PartialEq, HistoryVec, SetCumulative, -)] +#[derive(Clone, Copy, Debug, Deserialize, Serialize, PartialEq, HistoryVec, SetCumulative)] #[serde(default)] pub struct LumpedCabinState { /// time step counter @@ -161,5 +181,19 @@ pub struct LumpedCabinState { pub reynolds_for_plate: si::Ratio, } +impl Default for LumpedCabinState { + fn default() -> Self { + Self { + i: Default::default(), + temperature: *TE_STD_AIR, + temp_prev: *TE_STD_AIR, + pwr_thermal_from_hvac: Default::default(), + energy_thermal_from_hvac: Default::default(), + pwr_thermal_from_amb: Default::default(), + energy_thermal_from_amb: Default::default(), + reynolds_for_plate: Default::default(), + } + } +} impl Init for LumpedCabinState {} impl SerdeAPI for LumpedCabinState {} diff --git a/fastsim-core/src/vehicle/hev.rs b/fastsim-core/src/vehicle/hev.rs index 7f4cf1d3..eb483cf2 100644 --- a/fastsim-core/src/vehicle/hev.rs +++ b/fastsim-core/src/vehicle/hev.rs @@ -603,6 +603,7 @@ impl HEVPowertrainControls { handle_fc_on_causes_for_temp(fc, rgwdb, hev_state)?; handle_fc_on_causes_for_speed(veh_state, rgwdb, hev_state)?; handle_fc_on_causes_for_low_soc(res, rgwdb, hev_state, veh_state)?; + // `handle_fc_*` below here are asymmetrical for positive tractive power only handle_fc_on_causes_for_pwr_demand( rgwdb, pwr_out_req, @@ -618,13 +619,15 @@ impl HEVPowertrainControls { // split, cannot exceed ElectricMachine max output power. // Excess demand will be handled by `fc`. Favors drawing // power from `em` before engine - let em_pwr = pwr_out_req.min(em_state.pwr_mech_fwd_out_max); + let mut em_pwr = pwr_out_req.min(em_state.pwr_mech_fwd_out_max); let fc_pwr: si::Power = if hev_state.fc_on_causes.is_empty() { // engine does not need to be on si::Power::ZERO } else { // engine has been forced on - // power demand from engine before adjusting for efficient operating point + // power demand from engine such that engine handles all + // power demand beyond the em capability, before adjusting for efficient operating + // point let mut fc_pwr_req = pwr_out_req - em_pwr; // if the engine is on, load it up to get closer to peak // efficiency @@ -640,7 +643,7 @@ impl HEVPowertrainControls { fc_pwr_req }; // recalculate `em_pwr` based on `fc_pwr` - let em_pwr = pwr_out_req - fc_pwr; + em_pwr = pwr_out_req - fc_pwr; ensure!( fc_pwr >= si::Power::ZERO, @@ -859,7 +862,6 @@ impl Init for RESGreedyWithDynamicBuffers { self.speed_fc_forced_on = self.speed_fc_forced_on.or(Some(uc::MPH * 75.)); self.frac_pwr_demand_fc_forced_on = self.frac_pwr_demand_fc_forced_on.or(Some(uc::R * 0.75)); - // TODO: consider changing this default self.frac_of_most_eff_pwr_to_run_fc = self.frac_of_most_eff_pwr_to_run_fc.or(Some(1.0 * uc::R)); Ok(()) diff --git a/fastsim-core/src/vehicle/hvac/hvac_sys_for_lumped_cabin.rs b/fastsim-core/src/vehicle/hvac/hvac_sys_for_lumped_cabin.rs index 4b2565df..906aa828 100644 --- a/fastsim-core/src/vehicle/hvac/hvac_sys_for_lumped_cabin.rs +++ b/fastsim-core/src/vehicle/hvac/hvac_sys_for_lumped_cabin.rs @@ -253,7 +253,13 @@ pub enum CabinHeatSource { impl Init for CabinHeatSource {} impl SerdeAPI for CabinHeatSource {} -#[fastsim_api] +#[fastsim_api( + #[pyo3(name = "default")] + #[staticmethod] + fn default_py() -> Self { + Self::default() + } +)] #[derive( Clone, Copy, Debug, Default, Deserialize, Serialize, PartialEq, HistoryVec, SetCumulative, )] diff --git a/fastsim-core/src/vehicle/powertrain/electric_machine.rs b/fastsim-core/src/vehicle/powertrain/electric_machine.rs index 83a37845..d0b6dd03 100755 --- a/fastsim-core/src/vehicle/powertrain/electric_machine.rs +++ b/fastsim-core/src/vehicle/powertrain/electric_machine.rs @@ -224,10 +224,10 @@ impl ElectricMachine { ), ); ensure!( - -pwr_out_req <= self.state.pwr_mech_bwd_out_max, + almost_le_uom(&pwr_out_req.abs(), &self.state.pwr_mech_bwd_out_max, None), format!( "{}\nedrv required charge power ({:.6} kW) exceeds current max charge power ({:.6} kW)", - format_dbg!(pwr_out_req <= self.state.pwr_mech_bwd_out_max), + format_dbg!(pwr_out_req.abs() <= self.state.pwr_mech_bwd_out_max), pwr_out_req.get::(), self.state.pwr_mech_bwd_out_max.get::() ), diff --git a/fastsim-core/src/vehicle/powertrain/fuel_converter.rs b/fastsim-core/src/vehicle/powertrain/fuel_converter.rs index 7020134a..31ba3793 100755 --- a/fastsim-core/src/vehicle/powertrain/fuel_converter.rs +++ b/fastsim-core/src/vehicle/powertrain/fuel_converter.rs @@ -54,6 +54,7 @@ use std::f64::consts::PI; pub struct FuelConverter { /// [Self] Thermal plant, including thermal management controls #[serde(default, skip_serializing_if = "FuelConverterThermalOption::is_none")] + #[has_state] pub thrml: FuelConverterThermalOption, /// [Self] mass #[serde(default)] @@ -407,6 +408,22 @@ pub enum FuelConverterThermalOption { None, } +impl SaveState for FuelConverterThermalOption { + fn save_state(&mut self) { + match self { + Self::FuelConverterThermal(fct) => fct.save_state(), + Self::None => {} + } + } +} +impl Step for FuelConverterThermalOption { + fn step(&mut self) { + match self { + Self::FuelConverterThermal(fct) => fct.step(), + Self::None => {} + } + } +} impl Init for FuelConverterThermalOption { fn init(&mut self) -> anyhow::Result<()> { match self { diff --git a/fastsim-core/src/vehicle/powertrain/reversible_energy_storage.rs b/fastsim-core/src/vehicle/powertrain/reversible_energy_storage.rs index f32052ba..8ff7050b 100644 --- a/fastsim-core/src/vehicle/powertrain/reversible_energy_storage.rs +++ b/fastsim-core/src/vehicle/powertrain/reversible_energy_storage.rs @@ -85,6 +85,7 @@ const TOL: f64 = 1e-3; pub struct ReversibleEnergyStorage { /// [Self] Thermal plant, including thermal management controls #[serde(default, skip_serializing_if = "RESThermalOption::is_none")] + #[has_state] pub thrml: RESThermalOption, /// ReversibleEnergyStorage mass #[serde(default)] @@ -722,6 +723,22 @@ pub enum RESThermalOption { #[default] None, } +impl SaveState for RESThermalOption { + fn save_state(&mut self) { + match self { + Self::RESLumpedThermal(rlt) => rlt.save_state(), + Self::None => {} + } + } +} +impl Step for RESThermalOption { + fn step(&mut self) { + match self { + Self::RESLumpedThermal(rlt) => rlt.step(), + Self::None => {} + } + } +} impl Init for RESThermalOption { fn init(&mut self) -> anyhow::Result<()> { match self { @@ -823,7 +840,13 @@ impl RESLumpedThermal { } } -#[fastsim_api] +#[fastsim_api( + #[pyo3(name = "default")] + #[staticmethod] + fn default_py() -> Self { + Self::default() + } +)] #[derive(Clone, Copy, Debug, Deserialize, Serialize, PartialEq, HistoryVec, SetCumulative)] #[serde(default)] pub struct RESLumpedThermalState { diff --git a/fastsim-core/src/vehicle/vehicle_model.rs b/fastsim-core/src/vehicle/vehicle_model.rs index 1f4defb3..3111c256 100644 --- a/fastsim-core/src/vehicle/vehicle_model.rs +++ b/fastsim-core/src/vehicle/vehicle_model.rs @@ -135,6 +135,7 @@ pub struct Vehicle { /// Cabin thermal model #[serde(default, skip_serializing_if = "CabinOption::is_none")] + #[has_state] pub cabin: CabinOption, /// HVAC model diff --git a/python/fastsim/demos/demo_hev_thrml.py b/python/fastsim/demos/demo_hev_thrml.py index c91488ac..7f034670 100644 --- a/python/fastsim/demos/demo_hev_thrml.py +++ b/python/fastsim/demos/demo_hev_thrml.py @@ -42,10 +42,7 @@ # simulation start time t0 = time.perf_counter() # run simulation -try: - sd.walk() -except Exception as err: - print(f"still need to fix {err}") +sd.walk() # simulation end time t1 = time.perf_counter() t_fsim3_si1 = t1 - t0 @@ -54,9 +51,60 @@ # %% df = sd.to_dataframe(allow_partial=True) +sd_dict = sd.to_pydict(flatten=True) # # Visualize results +def plot_temperatures() -> Tuple[Figure, Axes]: + fig, ax = plt.subplots(2, 1, sharex=True, figsize=figsize_3_stacked) + plt.suptitle("Component Temperatures") + + ax[0].set_prop_cycle(get_uni_cycler()) + ax[0].plot( + df["cyc.time_seconds"], + df["cyc.temp_amb_air_kelvin"] - 273.15, + label="amb", + ) + ax[0].plot( + df["cyc.time_seconds"], + df["veh.cabin.LumpedCabin.history.temperature_kelvin"] - 273.15, + label="cabin", + ) + ax[0].plot( + df["cyc.time_seconds"], + df["veh.pt_type.HybridElectricVehicle.res.thrml." + + "RESLumpedThermal.history.temperature_kelvin"] - 273.15, + label="res", + ) + ax[0].plot( + df["cyc.time_seconds"], + df["veh.pt_type.HybridElectricVehicle.fc.thrml." + + "FuelConverterThermal.history.temperature_kelvin"] - 273.15, + label="fc", + ) + ax[0].set_ylabel("Temperatures [°C]") + ax[0].legend() + + ax[-1].set_prop_cycle(get_paired_cycler()) + ax[-1].plot( + df["cyc.time_seconds"], + df["veh.history.speed_ach_meters_per_second"], + label="ach", + ) + ax[-1].legend() + ax[-1].set_xlabel("Time [s]") + ax[-1].set_ylabel("Ach Speed [m/s]") + x_min, x_max = ax[-1].get_xlim()[0], ax[-1].get_xlim()[1] + x_max = (x_max - x_min) * 1.15 + ax[-1].set_xlim([x_min, x_max]) + + plt.tight_layout() + if SAVE_FIGS: + plt.savefig(Path("./plots/temps.svg")) + + return fig, ax + + def plot_fc_pwr() -> Tuple[Figure, Axes]: fig, ax = plt.subplots(3, 1, sharex=True, figsize=figsize_3_stacked) plt.suptitle("Fuel Converter Power") @@ -106,7 +154,7 @@ def plot_fc_pwr() -> Tuple[Figure, Axes]: ax[-1].plot( df["cyc.time_seconds"], df["veh.history.speed_ach_meters_per_second"], - label="f3", + label="ach", ) ax[-1].legend() ax[-1].set_xlabel("Time [s]") @@ -145,7 +193,7 @@ def plot_fc_energy() -> Tuple[Figure, Axes]: ax[-1].plot( df["cyc.time_seconds"], df["veh.history.speed_ach_meters_per_second"], - label="f3", + label="ach", ) ax[-1].legend() ax[-1].set_xlabel("Time [s]") @@ -271,7 +319,7 @@ def plot_road_loads() -> Tuple[Figure, Axes]: ax[-1].plot( df["cyc.time_seconds"][::veh.save_interval], df["veh.history.speed_ach_meters_per_second"], - label="f3", + label="ach", ) ax[-1].legend() ax[-1].set_xlabel("Time [s]") @@ -289,6 +337,7 @@ def plot_road_loads() -> Tuple[Figure, Axes]: fig_fc_energy, ax_fc_energy = plot_fc_energy() fig_res_pwr, ax_res_pwr = plot_res_pwr() fig_res_energy, ax_res_energy = plot_res_energy() + fig_temps, ax_temps = plot_temperatures() # fig, ax = plot_road_loads() plt.show() # %%