fastsim_core/vehicle/
bev.rs1use super::*;
2
3#[serde_api]
4#[derive(Clone, Debug, PartialEq, Serialize, Deserialize, StateMethods, SetCumulative)]
5#[non_exhaustive]
6#[serde(deny_unknown_fields)]
7#[cfg_attr(feature = "pyo3", pyclass(module = "fastsim", subclass, eq))]
8pub struct BatteryElectricVehicle {
10 #[has_state]
11 pub res: ReversibleEnergyStorage,
12 #[has_state]
13 pub em: ElectricMachine,
14 #[has_state]
15 pub transmission: Transmission,
16 pub(crate) mass: Option<si::Mass>,
17}
18
19#[pyo3_api]
20impl BatteryElectricVehicle {}
21
22impl Init for BatteryElectricVehicle {
23 fn init(&mut self) -> Result<(), Error> {
24 self.res
25 .init()
26 .map_err(|err| Error::InitError(format_dbg!(err)))?;
27 self.em
28 .init()
29 .map_err(|err| Error::InitError(format_dbg!(err)))?;
30 self.transmission
31 .init()
32 .map_err(|err| Error::InitError(format_dbg!(err)))?;
33 Ok(())
34 }
35}
36
37impl SerdeAPI for BatteryElectricVehicle {}
38
39impl Mass for BatteryElectricVehicle {
40 fn mass(&self) -> anyhow::Result<Option<si::Mass>> {
41 let derived_mass = self
42 .derived_mass()
43 .with_context(|| anyhow!(format_dbg!()))?;
44 match (derived_mass, self.mass) {
45 (Some(derived_mass), Some(set_mass)) => {
46 ensure!(
47 utils::almost_eq_uom(&set_mass, &derived_mass, None),
48 format!(
49 "{}",
50 format_dbg!(utils::almost_eq_uom(&set_mass, &derived_mass, None)),
51 )
52 );
53 Ok(Some(set_mass))
54 }
55 _ => Ok(self.mass.or(derived_mass)),
56 }
57 }
58
59 fn set_mass(
60 &mut self,
61 new_mass: Option<si::Mass>,
62 side_effect: MassSideEffect,
63 ) -> anyhow::Result<()> {
64 ensure!(
65 side_effect == MassSideEffect::None,
66 "At the powertrain level, only `MassSideEffect::None` is allowed"
67 );
68 let derived_mass = self
69 .derived_mass()
70 .with_context(|| anyhow!(format_dbg!()))?;
71 self.mass = match new_mass {
72 Some(new_mass) => {
74 if let Some(dm) = derived_mass {
75 if dm != new_mass {
76 self.expunge_mass_fields();
77 }
78 }
79 Some(new_mass)
80 }
81 None => Some(derived_mass.with_context(|| {
83 format!(
84 "Not all mass fields in `{}` are set and no mass was provided.",
85 stringify!(BatteryElectricVehicle)
86 )
87 })?),
88 };
89 Ok(())
90 }
91
92 fn derived_mass(&self) -> anyhow::Result<Option<si::Mass>> {
93 let res_mass = self.res.mass().with_context(|| anyhow!(format_dbg!()))?;
94 let em_mass = self.em.mass().with_context(|| anyhow!(format_dbg!()))?;
95 let transmission_mass = self
96 .transmission
97 .mass()
98 .with_context(|| anyhow!(format_dbg!()))?;
99 match (res_mass, em_mass, transmission_mass) {
100 (Some(res_mass), Some(em_mass), Some(transmission_mass)) => {
101 Ok(Some(em_mass + res_mass + transmission_mass))
102 }
103 (None, None, None) => Ok(None),
104 _ => bail!(
105 "`{}` field masses are not consistently set to `Some` or `None`",
106 stringify!(BatteryElectricVehicle)
107 ),
108 }
109 }
110
111 fn expunge_mass_fields(&mut self) {
112 self.res.expunge_mass_fields();
113 self.em.expunge_mass_fields();
114 self.transmission.expunge_mass_fields();
115 self.mass = None;
116 }
117}
118
119impl HistoryMethods for BatteryElectricVehicle {
120 fn save_interval(&self) -> anyhow::Result<Option<usize>> {
121 bail!("`save_interval` is not implemented in BatteryElectricVehicle")
122 }
123 fn set_save_interval(&mut self, save_interval: Option<usize>) -> anyhow::Result<()> {
124 self.res.set_save_interval(save_interval)?;
125 self.em.set_save_interval(save_interval)?;
126 self.transmission.set_save_interval(save_interval)?;
127 Ok(())
128 }
129 fn clear(&mut self) {
130 self.res.clear();
131 self.em.clear();
132 self.transmission.clear();
133 }
134}
135
136impl Powertrain for BatteryElectricVehicle {
137 fn solve(
138 &mut self,
139 pwr_out_req: si::Power,
140 _enabled: bool,
141 dt: si::Time,
142 ) -> anyhow::Result<()> {
143 let pwr_in_transmission = self
144 .transmission
145 .get_pwr_in_req(pwr_out_req)
146 .with_context(|| anyhow!(format_dbg!()))?;
147 let pwr_in_em = self
148 .em
149 .get_pwr_in_req(pwr_in_transmission, dt)
150 .with_context(|| anyhow!(format_dbg!()))?;
151 self.res
152 .solve(pwr_in_em, dt)
153 .with_context(|| anyhow!(format_dbg!()))?;
154 Ok(())
155 }
156
157 fn get_curr_pwr_prop_out_max(&self) -> anyhow::Result<(si::Power, si::Power)> {
158 Ok((
159 *self
160 .em
161 .state
162 .pwr_mech_fwd_out_max
163 .get_fresh(|| format_dbg!())?,
164 *self
165 .em
166 .state
167 .pwr_mech_regen_max
168 .get_fresh(|| format_dbg!())?,
169 ))
170 }
171
172 fn set_curr_pwr_prop_out_max(
173 &mut self,
174 pwr_aux: si::Power,
175 dt: si::Time,
176 _veh_state: &VehicleState,
177 ) -> anyhow::Result<()> {
178 let disch_buffer = si::Energy::ZERO;
180 let chrg_buffer = si::Energy::ZERO;
181 self.res
182 .set_curr_pwr_out_max(dt, disch_buffer, chrg_buffer)
183 .with_context(|| anyhow!(format_dbg!()))?;
184
185 self.res
186 .set_curr_pwr_prop_max(pwr_aux)
187 .with_context(|| anyhow!(format_dbg!()))?;
188 self.em
189 .set_curr_pwr_prop_out_max(
190 *self.res.state.pwr_prop_max.get_fresh(|| format_dbg!())?,
191 *self.res.state.pwr_regen_max.get_fresh(|| format_dbg!())?,
192 dt,
193 )
194 .with_context(|| anyhow!(format_dbg!()))?;
195
196 Ok(())
197 }
198
199 fn pwr_regen(&self) -> anyhow::Result<si::Power> {
201 Ok(-self
205 .em
206 .state
207 .pwr_mech_prop_out
208 .get_fresh(|| format_dbg!())?
209 .max(si::Power::ZERO))
210 }
211}
212
213impl BatteryElectricVehicle {
214 pub fn solve_thermal(
221 &mut self,
222 te_amb: si::Temperature,
223 pwr_thrml_hvac_to_res: si::Power,
224 te_cab: Option<si::Temperature>,
225 dt: si::Time,
226 ) -> anyhow::Result<()> {
227 self.res
228 .solve_thermal(te_amb, pwr_thrml_hvac_to_res, te_cab, dt)
229 .with_context(|| format_dbg!())?;
230 Ok(())
231 }
232}