1use super::*;
2#[derive(
6 Clone,
7 Default,
8 Debug,
9 Serialize,
10 Deserialize,
11 PartialEq,
12 IsVariant,
13 derive_more::From,
14 TryInto,
15 derive_more::Display,
16)]
17pub enum CabinOption {
18 #[display("LumpedCabin")]
21 LumpedCabin(Box<LumpedCabin>),
22 #[display("LumpedCabinWithShell")]
24 LumpedCabinWithShell,
25 #[default]
27 #[display("None")]
28 None,
29}
30
31impl StateMethods for CabinOption {}
32
33impl SaveState for CabinOption {
34 fn save_state<F: Fn() -> String>(&mut self, loc: F) -> anyhow::Result<()> {
35 match self {
36 Self::LumpedCabin(lc) => lc.save_state(loc)?,
37 Self::LumpedCabinWithShell => {
38 todo!()
39 }
40 Self::None => {}
41 }
42 Ok(())
43 }
44}
45impl TrackedStateMethods for CabinOption {
46 fn check_and_reset<F: Fn() -> String>(&mut self, loc: F) -> anyhow::Result<()> {
47 match self {
48 Self::LumpedCabin(lc) => {
49 lc.check_and_reset(|| format!("{}\n{}", loc(), format_dbg!()))?
50 }
51 Self::LumpedCabinWithShell => {
52 todo!()
53 }
54 Self::None => {}
55 }
56 Ok(())
57 }
58
59 fn mark_fresh<F: Fn() -> String>(&mut self, loc: F) -> anyhow::Result<()> {
60 match self {
61 Self::LumpedCabin(lc) => lc.mark_fresh(|| format!("{}\n{}", loc(), format_dbg!()))?,
62 Self::LumpedCabinWithShell => {
63 todo!()
64 }
65 Self::None => {}
66 }
67 Ok(())
68 }
69}
70impl Step for CabinOption {
71 fn step<F: Fn() -> String>(&mut self, loc: F) -> anyhow::Result<()> {
72 match self {
73 Self::LumpedCabin(lc) => lc.step(|| format!("{}\n{}", loc(), format_dbg!())),
74 Self::LumpedCabinWithShell => {
75 todo!()
76 }
77 Self::None => Ok(()),
78 }
79 }
80
81 fn reset_step<F: Fn() -> String>(&mut self, loc: F) -> anyhow::Result<()> {
82 match self {
83 Self::LumpedCabin(lc) => lc.reset_step(|| format!("{}\n{}", loc(), format_dbg!())),
84 Self::LumpedCabinWithShell => {
85 todo!()
86 }
87 Self::None => Ok(()),
88 }
89 }
90}
91impl Init for CabinOption {
92 fn init(&mut self) -> Result<(), Error> {
93 match self {
94 Self::LumpedCabin(scc) => scc.init()?,
95 Self::LumpedCabinWithShell => {
96 todo!()
97 }
98 Self::None => {}
99 }
100 Ok(())
101 }
102}
103impl SerdeAPI for CabinOption {}
104impl HistoryMethods for CabinOption {
105 fn save_interval(&self) -> anyhow::Result<Option<usize>> {
106 match self {
107 CabinOption::LumpedCabin(lc) => lc.save_interval(),
108 CabinOption::LumpedCabinWithShell => todo!(),
109 CabinOption::None => Ok(None),
110 }
111 }
112 fn set_save_interval(&mut self, save_interval: Option<usize>) -> anyhow::Result<()> {
113 match self {
114 CabinOption::LumpedCabin(lc) => lc.set_save_interval(save_interval),
115 CabinOption::LumpedCabinWithShell => todo!(),
116 CabinOption::None => Ok(()),
117 }
118 }
119 fn clear(&mut self) {
120 match self {
121 CabinOption::LumpedCabin(lc) => lc.clear(),
122 CabinOption::LumpedCabinWithShell => todo!(),
123 CabinOption::None => {}
124 }
125 }
126}
127impl SetCumulative for CabinOption {
128 fn set_cumulative<F: Fn() -> String>(&mut self, dt: si::Time, loc: F) -> anyhow::Result<()> {
129 match self {
130 Self::LumpedCabin(lc) => {
131 lc.set_cumulative(dt, || format!("{}\n{}", loc(), format_dbg!()))?
132 }
133 Self::LumpedCabinWithShell => todo!(),
134 Self::None => {}
135 }
136 Ok(())
137 }
138
139 fn reset_cumulative<F: Fn() -> String>(&mut self, loc: F) -> anyhow::Result<()> {
140 match self {
141 Self::LumpedCabin(lc) => {
142 lc.reset_cumulative(|| format!("{}\n{}", loc(), format_dbg!()))?
143 }
144 Self::LumpedCabinWithShell => todo!(),
145 Self::None => {}
146 }
147 Ok(())
148 }
149}
150
151#[serde_api]
152#[derive(Default, Deserialize, Serialize, Debug, Clone, PartialEq, StateMethods, SetCumulative)]
153#[non_exhaustive]
154#[serde(deny_unknown_fields)]
155#[cfg_attr(feature = "pyo3", pyclass(module = "fastsim", subclass, eq))]
156pub struct LumpedCabin {
159 pub cab_shell_htc_to_amb: si::HeatTransferCoeff,
161 pub cab_htc_to_amb_stop: si::HeatTransferCoeff,
164 pub heat_capacitance: si::HeatCapacity,
166 pub length: si::Length,
168 pub width: si::Length,
170 #[serde(default)]
171 pub state: LumpedCabinState,
172 #[serde(default)]
173 pub history: LumpedCabinStateHistoryVec,
174 pub save_interval: Option<usize>,
176}
177
178#[pyo3_api]
179impl LumpedCabin {
180 #[staticmethod]
181 #[pyo3(name = "default")]
182 fn default_py() -> Self {
183 Default::default()
184 }
185}
186impl SerdeAPI for LumpedCabin {}
187impl Init for LumpedCabin {}
188impl HistoryMethods for LumpedCabin {
189 fn save_interval(&self) -> anyhow::Result<Option<usize>> {
190 Ok(self.save_interval)
191 }
192 fn set_save_interval(&mut self, save_interval: Option<usize>) -> anyhow::Result<()> {
193 self.save_interval = save_interval;
194 Ok(())
195 }
196 fn clear(&mut self) {
197 self.history.clear();
198 }
199}
200
201impl LumpedCabin {
202 pub fn solve(
212 &mut self,
213 te_amb_air: si::Temperature,
214 veh_state: &VehicleState,
215 pwr_thrml_from_hvac: si::Power,
216 pwr_thrml_to_res: si::Power,
217 dt: si::Time,
218 ) -> anyhow::Result<si::Temperature> {
219 self.state
220 .pwr_thrml_from_hvac
221 .update(pwr_thrml_from_hvac, || format_dbg!())?;
222 self.state
223 .pwr_thrml_to_res
224 .update(pwr_thrml_to_res, || format_dbg!())?;
225 let cab_te_film_ext: si::Temperature = 0.5
226 * (self
227 .state
228 .temperature
229 .get_stale(|| format_dbg!())?
230 .get::<si::kelvin_abs>()
231 + te_amb_air.get::<si::kelvin_abs>())
232 * uc::KELVIN;
233 self.state.reynolds_for_plate.update(
234 Air::get_density(
235 Some(cab_te_film_ext),
236 Some(*veh_state.elev_curr.get_stale(|| format_dbg!())?),
237 ) * *veh_state.speed_ach.get_stale(|| format_dbg!())?
238 * self.length
239 / Air::get_dyn_visc(cab_te_film_ext).with_context(|| format_dbg!())?,
240 || format_dbg!(),
241 )?;
242 let re_l_crit = 5.0e5 * uc::R; let nu_l_bar: si::Ratio =
245 if *self.state.reynolds_for_plate.get_fresh(|| format_dbg!())? < re_l_crit {
246 0.664
250 * self
251 .state
252 .reynolds_for_plate
253 .get_fresh(|| format_dbg!())?
254 .get::<si::ratio>()
255 .powf(0.5)
256 * Air::get_pr(cab_te_film_ext)
257 .with_context(|| format_dbg!())?
258 .get::<si::ratio>()
259 .powf(1.0 / 3.0)
260 * uc::R
261 } else {
262 let a = 871.0; (0.037
265 * self
266 .state
267 .reynolds_for_plate
268 .get_fresh(|| format_dbg!())?
269 .get::<si::ratio>()
270 .powf(0.8)
271 - a)
272 * Air::get_pr(cab_te_film_ext).with_context(|| format_dbg!())?
273 };
274
275 self.state.pwr_thrml_from_amb.update(
276 if *veh_state.speed_ach.get_stale(|| format_dbg!())? > 2.0 * uc::MPH {
277 let htc_overall_moving: si::HeatTransferCoeff = 1.0
278 / (1.0
279 / (nu_l_bar
280 * Air::get_therm_cond(cab_te_film_ext)
281 .with_context(|| format_dbg!())?
282 / self.length)
283 + 1.0 / self.cab_shell_htc_to_amb);
284 (self.length * self.width)
285 * htc_overall_moving
286 * (te_amb_air.get::<si::degree_celsius>()
287 - self
288 .state
289 .temperature
290 .get_stale(|| format_dbg!())?
291 .get::<si::degree_celsius>())
292 * uc::KELVIN_INT
293 } else {
294 (self.length * self.width)
295 / (1.0 / self.cab_htc_to_amb_stop + 1.0 / self.cab_shell_htc_to_amb)
296 * (te_amb_air.get::<si::degree_celsius>()
297 - self
298 .state
299 .temperature
300 .get_stale(|| format_dbg!())?
301 .get::<si::degree_celsius>())
302 * uc::KELVIN_INT
303 },
304 || format_dbg!(),
305 )?;
306
307 self.state.temp_prev.update(
308 *self.state.temperature.get_stale(|| format_dbg!())?,
309 || format_dbg!(),
310 )?;
311 self.state.temperature.update(
312 *self.state.temperature.get_stale(|| format_dbg!())?
313 + (*self.state.pwr_thrml_from_hvac.get_fresh(|| format_dbg!())?
314 + *self.state.pwr_thrml_from_amb.get_fresh(|| format_dbg!())?
315 - *self.state.pwr_thrml_to_res.get_fresh(|| format_dbg!())?)
316 / self.heat_capacitance
317 * dt,
318 || format_dbg!(),
319 )?;
320 Ok(*self.state.temperature.get_fresh(|| format_dbg!())?)
321 }
322}
323
324#[serde_api]
325#[derive(
326 Clone, Debug, Deserialize, Serialize, PartialEq, HistoryVec, StateMethods, SetCumulative,
327)]
328#[cfg_attr(feature = "pyo3", pyclass(module = "fastsim", subclass, eq))]
329#[serde(deny_unknown_fields)]
330pub struct LumpedCabinState {
331 pub i: TrackedState<usize>,
333 pub temperature: TrackedState<si::Temperature>,
335 pub temp_prev: TrackedState<si::Temperature>,
337 pub pwr_thrml_from_hvac: TrackedState<si::Power>,
340 pub energy_thrml_from_hvac: TrackedState<si::Energy>,
343 pub pwr_thrml_from_amb: TrackedState<si::Power>,
346 pub energy_thrml_from_amb: TrackedState<si::Energy>,
349 pub pwr_thrml_to_res: TrackedState<si::Power>,
352 pub energy_thrml_to_res: TrackedState<si::Energy>,
355 pub reynolds_for_plate: TrackedState<si::Ratio>,
357}
358
359#[pyo3_api]
360impl LumpedCabinState {
361 #[pyo3(name = "default")]
362 #[staticmethod]
363 fn default_py() -> Self {
364 Self::default()
365 }
366}
367
368impl Default for LumpedCabinState {
369 fn default() -> Self {
370 Self {
371 i: Default::default(),
372 temperature: TrackedState::new(*TE_STD_AIR),
373 temp_prev: TrackedState::new(*TE_STD_AIR),
374 pwr_thrml_from_hvac: Default::default(),
375 energy_thrml_from_hvac: Default::default(),
376 pwr_thrml_from_amb: Default::default(),
377 energy_thrml_from_amb: Default::default(),
378 pwr_thrml_to_res: Default::default(),
379 energy_thrml_to_res: Default::default(),
380 reynolds_for_plate: Default::default(),
381 }
382 }
383}
384impl Init for LumpedCabinState {}
385impl SerdeAPI for LumpedCabinState {}