use super::*;
#[serde_api]
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize, StateMethods, SetCumulative)]
#[non_exhaustive]
#[serde(deny_unknown_fields)]
#[cfg_attr(feature = "pyo3", pyclass(module = "fastsim", subclass, eq))]
pub struct BatteryElectricVehicle {
#[has_state]
pub res: ReversibleEnergyStorage,
#[has_state]
pub em: ElectricMachine,
#[has_state]
pub transmission: Transmission,
pub(crate) mass: Option<si::Mass>,
}
#[pyo3_api]
impl BatteryElectricVehicle {}
impl Init for BatteryElectricVehicle {
fn init(&mut self) -> Result<(), Error> {
self.res
.init()
.map_err(|err| Error::InitError(format_dbg!(err)))?;
self.em
.init()
.map_err(|err| Error::InitError(format_dbg!(err)))?;
self.transmission
.init()
.map_err(|err| Error::InitError(format_dbg!(err)))?;
Ok(())
}
}
impl SerdeAPI for BatteryElectricVehicle {}
impl Mass for BatteryElectricVehicle {
fn mass(&self) -> anyhow::Result<Option<si::Mass>> {
let derived_mass = self
.derived_mass()
.with_context(|| anyhow!(format_dbg!()))?;
match (derived_mass, self.mass) {
(Some(derived_mass), Some(set_mass)) => {
ensure!(
utils::almost_eq_uom(&set_mass, &derived_mass, None),
format!(
"{}",
format_dbg!(utils::almost_eq_uom(&set_mass, &derived_mass, None)),
)
);
Ok(Some(set_mass))
}
_ => Ok(self.mass.or(derived_mass)),
}
}
fn set_mass(
&mut self,
new_mass: Option<si::Mass>,
side_effect: MassSideEffect,
) -> anyhow::Result<()> {
ensure!(
side_effect == MassSideEffect::None,
"At the powertrain level, only `MassSideEffect::None` is allowed"
);
let derived_mass = self
.derived_mass()
.with_context(|| anyhow!(format_dbg!()))?;
self.mass = match new_mass {
Some(new_mass) => {
if let Some(dm) = derived_mass {
if dm != new_mass {
self.expunge_mass_fields();
}
}
Some(new_mass)
}
None => Some(derived_mass.with_context(|| {
format!(
"Not all mass fields in `{}` are set and no mass was provided.",
stringify!(BatteryElectricVehicle)
)
})?),
};
Ok(())
}
fn derived_mass(&self) -> anyhow::Result<Option<si::Mass>> {
let res_mass = self.res.mass().with_context(|| anyhow!(format_dbg!()))?;
let em_mass = self.em.mass().with_context(|| anyhow!(format_dbg!()))?;
let transmission_mass = self
.transmission
.mass()
.with_context(|| anyhow!(format_dbg!()))?;
match (res_mass, em_mass, transmission_mass) {
(Some(res_mass), Some(em_mass), Some(transmission_mass)) => {
Ok(Some(em_mass + res_mass + transmission_mass))
}
(None, None, None) => Ok(None),
_ => bail!(
"`{}` field masses are not consistently set to `Some` or `None`",
stringify!(BatteryElectricVehicle)
),
}
}
fn expunge_mass_fields(&mut self) {
self.res.expunge_mass_fields();
self.em.expunge_mass_fields();
self.transmission.expunge_mass_fields();
self.mass = None;
}
}
impl HistoryMethods for BatteryElectricVehicle {
fn save_interval(&self) -> anyhow::Result<Option<usize>> {
bail!("`save_interval` is not implemented in BatteryElectricVehicle")
}
fn set_save_interval(&mut self, save_interval: Option<usize>) -> anyhow::Result<()> {
self.res.set_save_interval(save_interval)?;
self.em.set_save_interval(save_interval)?;
self.transmission.set_save_interval(save_interval)?;
Ok(())
}
fn clear(&mut self) {
self.res.clear();
self.em.clear();
self.transmission.clear();
}
}
impl Powertrain for BatteryElectricVehicle {
fn solve(
&mut self,
pwr_out_req: si::Power,
_enabled: bool,
dt: si::Time,
) -> anyhow::Result<Option<si::Power>> {
let pwr_in_transmission = self
.transmission
.solve(pwr_out_req, true, dt)
.with_context(|| format_dbg!())?
.with_context(|| format!("{}\nExpected `Some`", format_dbg!()))?;
let pwr_in_em = self
.em
.solve(pwr_in_transmission, true, dt)
.with_context(|| {
format!(
"{}\ntransmission `pwr_out_req`: {} kW\n`self.transmission.state.pwr_out_fwd_max`: {} kW",
format_dbg!(),
pwr_out_req.get::<si::kilowatt>().format_eng(None),
self.transmission
.state
.pwr_out_fwd_max
.get_fresh(|| format_dbg!())
.unwrap()
.get::<si::kilowatt>()
.format_eng(None)
)
})?
.with_context(|| format!("{}\nExpected `Some`", format_dbg!()))?;
self.res
.solve(pwr_in_em, dt)
.with_context(|| format_dbg!())?;
Ok(None)
}
fn get_curr_pwr_prop_out_max(&self) -> anyhow::Result<(si::Power, si::Power)> {
self.transmission
.get_curr_pwr_prop_out_max()
.with_context(|| format_dbg!())
}
fn set_curr_pwr_prop_out_max(
&mut self,
_pwr_upstream: (si::Power, si::Power),
pwr_aux: si::Power,
dt: si::Time,
_veh_state: &VehicleState,
) -> anyhow::Result<()> {
let disch_buffer = si::Energy::ZERO;
let chrg_buffer = si::Energy::ZERO;
self.res
.set_curr_pwr_out_max(dt, disch_buffer, chrg_buffer)
.with_context(|| anyhow!(format_dbg!()))?;
self.res
.set_curr_pwr_prop_max(pwr_aux)
.with_context(|| anyhow!(format_dbg!()))?;
self.em
.set_curr_pwr_prop_out_max(
self.res
.get_curr_pwr_prop_out_max()
.with_context(|| format_dbg!())?,
f64::NAN * uc::W,
dt,
_veh_state,
)
.with_context(|| anyhow!(format_dbg!()))?;
self.transmission
.set_curr_pwr_prop_out_max(
self.em
.get_curr_pwr_prop_out_max()
.with_context(|| format_dbg!())?,
f64::NAN * uc::W,
dt,
_veh_state,
)
.with_context(|| anyhow!(format_dbg!()))?;
Ok(())
}
fn pwr_regen(&self) -> anyhow::Result<si::Power> {
self.transmission.pwr_regen().with_context(|| format_dbg!())
}
}
impl BatteryElectricVehicle {
pub fn solve_thermal(
&mut self,
te_amb: si::Temperature,
pwr_thrml_hvac_to_res: si::Power,
te_cab: Option<si::Temperature>,
dt: si::Time,
) -> anyhow::Result<()> {
self.res
.solve_thermal(te_amb, pwr_thrml_hvac_to_res, te_cab, dt)
.with_context(|| format_dbg!())?;
Ok(())
}
}
impl TryFrom<&fastsim_2::vehicle::RustVehicle> for BatteryElectricVehicle {
type Error = anyhow::Error;
fn try_from(f2veh: &fastsim_2::vehicle::RustVehicle) -> anyhow::Result<BatteryElectricVehicle> {
let bev = BatteryElectricVehicle {
res: ReversibleEnergyStorage::try_from(f2veh.clone()).with_context(|| format_dbg!())?,
em: ElectricMachine {
state: Default::default(),
eff_interp_achieved: InterpolatorEnum::new_1d(
f2veh.mc_pwr_out_perc.clone(),
f2veh.mc_eff_array.clone(),
strategy::Linear,
Extrapolate::Error,
)?,
eff_interp_at_max_input: Some(InterpolatorEnum::new_1d(
f2veh
.mc_pwr_out_perc
.iter()
.zip(f2veh.mc_eff_array.iter())
.map(|(x, y)| x / y)
.collect(),
f2veh.mc_eff_array.clone(),
strategy::Linear,
Extrapolate::Error,
)?),
pwr_out_max: f2veh.mc_max_kw * uc::KW,
specific_pwr: None,
mass: None,
save_interval: Some(1),
history: Default::default(),
},
transmission: Transmission::try_from(f2veh.clone())?,
mass: None,
};
Ok(bev)
}
}