1use super::*;
2#[derive(
6 Clone, Default, Debug, Serialize, Deserialize, PartialEq, IsVariant, derive_more::From, TryInto,
7)]
8pub enum CabinOption {
9 LumpedCabin(Box<LumpedCabin>),
12 LumpedCabinWithShell,
14 #[default]
16 None,
17}
18impl SaveState for CabinOption {
19 fn save_state<F: Fn() -> String>(&mut self, loc: F) -> anyhow::Result<()> {
20 match self {
21 Self::LumpedCabin(lc) => lc.save_state(loc)?,
22 Self::LumpedCabinWithShell => {
23 todo!()
24 }
25 Self::None => {}
26 }
27 Ok(())
28 }
29}
30impl CheckAndResetState for CabinOption {
31 fn check_and_reset<F: Fn() -> String>(&mut self, loc: F) -> anyhow::Result<()> {
32 match self {
33 Self::LumpedCabin(lc) => {
34 lc.check_and_reset(|| format!("{}\n{}", loc(), format_dbg!()))?
35 }
36 Self::LumpedCabinWithShell => {
37 todo!()
38 }
39 Self::None => {}
40 }
41 Ok(())
42 }
43}
44impl Step for CabinOption {
45 fn step<F: Fn() -> String>(&mut self, loc: F) -> anyhow::Result<()> {
46 match self {
47 Self::LumpedCabin(lc) => lc.step(|| format!("{}\n{}", loc(), format_dbg!())),
48 Self::LumpedCabinWithShell => {
49 todo!()
50 }
51 Self::None => Ok(()),
52 }
53 }
54}
55impl Init for CabinOption {
56 fn init(&mut self) -> Result<(), Error> {
57 match self {
58 Self::LumpedCabin(scc) => scc.init()?,
59 Self::LumpedCabinWithShell => {
60 todo!()
61 }
62 Self::None => {}
63 }
64 Ok(())
65 }
66}
67impl SerdeAPI for CabinOption {}
68impl HistoryMethods for CabinOption {
69 fn save_interval(&self) -> anyhow::Result<Option<usize>> {
70 match self {
71 CabinOption::LumpedCabin(lc) => lc.save_interval(),
72 CabinOption::LumpedCabinWithShell => todo!(),
73 CabinOption::None => Ok(None),
74 }
75 }
76 fn set_save_interval(&mut self, save_interval: Option<usize>) -> anyhow::Result<()> {
77 match self {
78 CabinOption::LumpedCabin(lc) => lc.set_save_interval(save_interval),
79 CabinOption::LumpedCabinWithShell => todo!(),
80 CabinOption::None => Ok(()),
81 }
82 }
83 fn clear(&mut self) {
84 match self {
85 CabinOption::LumpedCabin(lc) => lc.clear(),
86 CabinOption::LumpedCabinWithShell => todo!(),
87 CabinOption::None => {}
88 }
89 }
90}
91impl SetCumulative for CabinOption {
92 fn set_cumulative(&mut self, dt: si::Time) -> anyhow::Result<()> {
93 match self {
94 Self::LumpedCabin(lc) => lc.set_cumulative(dt)?,
95 Self::LumpedCabinWithShell => todo!(),
96 Self::None => {}
97 }
98 Ok(())
99 }
100}
101
102#[serde_api]
103#[derive(Default, Deserialize, Serialize, Debug, Clone, PartialEq, StateMethods, SetCumulative)]
104#[non_exhaustive]
105#[serde(deny_unknown_fields)]
106#[cfg_attr(feature = "pyo3", pyclass(module = "fastsim", subclass, eq))]
107pub struct LumpedCabin {
110 pub cab_shell_htc_to_amb: si::HeatTransferCoeff,
112 pub cab_htc_to_amb_stop: si::HeatTransferCoeff,
115 pub heat_capacitance: si::HeatCapacity,
117 pub length: si::Length,
119 pub width: si::Length,
121 #[serde(default)]
122 pub state: LumpedCabinState,
123 #[serde(default, skip_serializing_if = "LumpedCabinStateHistoryVec::is_empty")]
124 pub history: LumpedCabinStateHistoryVec,
125 pub save_interval: Option<usize>,
127}
128
129#[named_struct_pyo3_api]
130impl LumpedCabin {
131 #[staticmethod]
132 #[pyo3(name = "default")]
133 fn default_py() -> Self {
134 Default::default()
135 }
136}
137impl SerdeAPI for LumpedCabin {}
138impl Init for LumpedCabin {}
139impl HistoryMethods for LumpedCabin {
140 fn save_interval(&self) -> anyhow::Result<Option<usize>> {
141 Ok(self.save_interval)
142 }
143 fn set_save_interval(&mut self, save_interval: Option<usize>) -> anyhow::Result<()> {
144 self.save_interval = save_interval;
145 Ok(())
146 }
147 fn clear(&mut self) {
148 self.history.clear();
149 }
150}
151
152impl LumpedCabin {
153 pub fn solve(
163 &mut self,
164 te_amb_air: si::Temperature,
165 veh_state: &VehicleState,
166 pwr_thrml_from_hvac: si::Power,
167 pwr_thrml_to_res: si::Power,
168 dt: si::Time,
169 ) -> anyhow::Result<si::Temperature> {
170 self.state
171 .pwr_thrml_from_hvac
172 .update(pwr_thrml_from_hvac, || format_dbg!())?;
173 self.state
174 .pwr_thrml_to_res
175 .update(pwr_thrml_to_res, || format_dbg!())?;
176 let cab_te_film_ext: si::Temperature = 0.5
179 * (self
180 .state
181 .temperature
182 .get_stale(|| format_dbg!())?
183 .get::<si::kelvin_abs>()
184 + te_amb_air.get::<si::kelvin_abs>())
185 * uc::KELVIN;
186 self.state.reynolds_for_plate.update(
187 Air::get_density(
188 Some(cab_te_film_ext),
189 Some(*veh_state.elev_curr.get_stale(|| format_dbg!())?),
190 ) * *veh_state.speed_ach.get_stale(|| format_dbg!())?
191 * self.length
192 / Air::get_dyn_visc(cab_te_film_ext).with_context(|| format_dbg!())?,
193 || format_dbg!(),
194 )?;
195 let re_l_crit = 5.0e5 * uc::R; let nu_l_bar: si::Ratio =
198 if *self.state.reynolds_for_plate.get_fresh(|| format_dbg!())? < re_l_crit {
199 0.664
201 * self
202 .state
203 .reynolds_for_plate
204 .get_fresh(|| format_dbg!())?
205 .get::<si::ratio>()
206 .powf(0.5)
207 * Air::get_pr(cab_te_film_ext)
208 .with_context(|| format_dbg!())?
209 .get::<si::ratio>()
210 .powf(1.0 / 3.0)
211 * uc::R
212 } else {
213 let a = 871.0; (0.037
216 * self
217 .state
218 .reynolds_for_plate
219 .get_fresh(|| format_dbg!())?
220 .get::<si::ratio>()
221 .powf(0.8)
222 - a)
223 * Air::get_pr(cab_te_film_ext).with_context(|| format_dbg!())?
224 };
225
226 self.state.pwr_thrml_from_amb.update(
227 if *veh_state.speed_ach.get_stale(|| format_dbg!())? > 2.0 * uc::MPH {
228 let htc_overall_moving: si::HeatTransferCoeff = 1.0
229 / (1.0
230 / (nu_l_bar
231 * Air::get_therm_cond(cab_te_film_ext)
232 .with_context(|| format_dbg!())?
233 / self.length)
234 + 1.0 / self.cab_shell_htc_to_amb);
235 (self.length * self.width)
236 * htc_overall_moving
237 * (te_amb_air.get::<si::degree_celsius>()
238 - self
239 .state
240 .temperature
241 .get_stale(|| format_dbg!())?
242 .get::<si::degree_celsius>())
243 * uc::KELVIN_INT
244 } else {
245 (self.length * self.width)
246 / (1.0 / self.cab_htc_to_amb_stop + 1.0 / self.cab_shell_htc_to_amb)
247 * (te_amb_air.get::<si::degree_celsius>()
248 - self
249 .state
250 .temperature
251 .get_stale(|| format_dbg!())?
252 .get::<si::degree_celsius>())
253 * uc::KELVIN_INT
254 },
255 || format_dbg!(),
256 )?;
257
258 self.state.temp_prev.update(
259 *self.state.temperature.get_stale(|| format_dbg!())?,
260 || format_dbg!(),
261 )?;
262 self.state.temperature.update(
263 *self.state.temperature.get_stale(|| format_dbg!())?
264 + (*self.state.pwr_thrml_from_hvac.get_fresh(|| format_dbg!())?
265 + *self.state.pwr_thrml_from_amb.get_fresh(|| format_dbg!())?
266 - *self.state.pwr_thrml_to_res.get_fresh(|| format_dbg!())?)
267 / self.heat_capacitance
268 * dt,
269 || format_dbg!(),
270 )?;
271 Ok(*self.state.temperature.get_fresh(|| format_dbg!())?)
272 }
273}
274
275#[serde_api]
276#[derive(
277 Clone, Debug, Deserialize, Serialize, PartialEq, HistoryVec, StateMethods, SetCumulative,
278)]
279#[cfg_attr(feature = "pyo3", pyclass(module = "fastsim", subclass, eq))]
280#[serde(deny_unknown_fields)]
281pub struct LumpedCabinState {
282 pub i: TrackedState<usize>,
284 pub temperature: TrackedState<si::Temperature>,
286 pub temp_prev: TrackedState<si::Temperature>,
288 pub pwr_thrml_from_hvac: TrackedState<si::Power>,
291 pub energy_thrml_from_hvac: TrackedState<si::Energy>,
294 pub pwr_thrml_from_amb: TrackedState<si::Power>,
297 pub energy_thrml_from_amb: TrackedState<si::Energy>,
300 pub pwr_thrml_to_res: TrackedState<si::Power>,
303 pub energy_thrml_to_res: TrackedState<si::Energy>,
306 pub reynolds_for_plate: TrackedState<si::Ratio>,
308}
309
310#[named_struct_pyo3_api]
311impl LumpedCabinState {
312 #[pyo3(name = "default")]
313 #[staticmethod]
314 fn default_py() -> Self {
315 Self::default()
316 }
317}
318
319impl Default for LumpedCabinState {
320 fn default() -> Self {
321 Self {
322 i: Default::default(),
323 temperature: TrackedState::new(*TE_STD_AIR),
324 temp_prev: TrackedState::new(*TE_STD_AIR),
325 pwr_thrml_from_hvac: Default::default(),
326 energy_thrml_from_hvac: Default::default(),
327 pwr_thrml_from_amb: Default::default(),
328 energy_thrml_from_amb: Default::default(),
329 pwr_thrml_to_res: Default::default(),
330 energy_thrml_to_res: Default::default(),
331 reynolds_for_plate: Default::default(),
332 }
333 }
334}
335impl Init for LumpedCabinState {}
336impl SerdeAPI for LumpedCabinState {}