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
140#[serde_api]
141#[derive(Default, Deserialize, Serialize, Debug, Clone, PartialEq, StateMethods, SetCumulative)]
142#[non_exhaustive]
143#[serde(deny_unknown_fields)]
144#[cfg_attr(feature = "pyo3", pyclass(module = "fastsim", subclass, eq))]
145pub struct LumpedCabin {
148 pub cab_shell_htc_to_amb: si::HeatTransferCoeff,
150 pub cab_htc_to_amb_stop: si::HeatTransferCoeff,
153 pub heat_capacitance: si::HeatCapacity,
155 pub length: si::Length,
157 pub width: si::Length,
159 #[serde(default)]
160 pub state: LumpedCabinState,
161 #[serde(default, skip_serializing_if = "LumpedCabinStateHistoryVec::is_empty")]
162 pub history: LumpedCabinStateHistoryVec,
163 pub save_interval: Option<usize>,
165}
166
167#[pyo3_api]
168impl LumpedCabin {
169 #[staticmethod]
170 #[pyo3(name = "default")]
171 fn default_py() -> Self {
172 Default::default()
173 }
174}
175impl SerdeAPI for LumpedCabin {}
176impl Init for LumpedCabin {}
177impl HistoryMethods for LumpedCabin {
178 fn save_interval(&self) -> anyhow::Result<Option<usize>> {
179 Ok(self.save_interval)
180 }
181 fn set_save_interval(&mut self, save_interval: Option<usize>) -> anyhow::Result<()> {
182 self.save_interval = save_interval;
183 Ok(())
184 }
185 fn clear(&mut self) {
186 self.history.clear();
187 }
188}
189
190impl LumpedCabin {
191 pub fn solve(
201 &mut self,
202 te_amb_air: si::Temperature,
203 veh_state: &VehicleState,
204 pwr_thrml_from_hvac: si::Power,
205 pwr_thrml_to_res: si::Power,
206 dt: si::Time,
207 ) -> anyhow::Result<si::Temperature> {
208 self.state
209 .pwr_thrml_from_hvac
210 .update(pwr_thrml_from_hvac, || format_dbg!())?;
211 self.state
212 .pwr_thrml_to_res
213 .update(pwr_thrml_to_res, || format_dbg!())?;
214 let cab_te_film_ext: si::Temperature = 0.5
217 * (self
218 .state
219 .temperature
220 .get_stale(|| format_dbg!())?
221 .get::<si::kelvin_abs>()
222 + te_amb_air.get::<si::kelvin_abs>())
223 * uc::KELVIN;
224 self.state.reynolds_for_plate.update(
225 Air::get_density(
226 Some(cab_te_film_ext),
227 Some(*veh_state.elev_curr.get_stale(|| format_dbg!())?),
228 ) * *veh_state.speed_ach.get_stale(|| format_dbg!())?
229 * self.length
230 / Air::get_dyn_visc(cab_te_film_ext).with_context(|| format_dbg!())?,
231 || format_dbg!(),
232 )?;
233 let re_l_crit = 5.0e5 * uc::R; let nu_l_bar: si::Ratio =
236 if *self.state.reynolds_for_plate.get_fresh(|| format_dbg!())? < re_l_crit {
237 0.664
239 * self
240 .state
241 .reynolds_for_plate
242 .get_fresh(|| format_dbg!())?
243 .get::<si::ratio>()
244 .powf(0.5)
245 * Air::get_pr(cab_te_film_ext)
246 .with_context(|| format_dbg!())?
247 .get::<si::ratio>()
248 .powf(1.0 / 3.0)
249 * uc::R
250 } else {
251 let a = 871.0; (0.037
254 * self
255 .state
256 .reynolds_for_plate
257 .get_fresh(|| format_dbg!())?
258 .get::<si::ratio>()
259 .powf(0.8)
260 - a)
261 * Air::get_pr(cab_te_film_ext).with_context(|| format_dbg!())?
262 };
263
264 self.state.pwr_thrml_from_amb.update(
265 if *veh_state.speed_ach.get_stale(|| format_dbg!())? > 2.0 * uc::MPH {
266 let htc_overall_moving: si::HeatTransferCoeff = 1.0
267 / (1.0
268 / (nu_l_bar
269 * Air::get_therm_cond(cab_te_film_ext)
270 .with_context(|| format_dbg!())?
271 / self.length)
272 + 1.0 / self.cab_shell_htc_to_amb);
273 (self.length * self.width)
274 * htc_overall_moving
275 * (te_amb_air.get::<si::degree_celsius>()
276 - self
277 .state
278 .temperature
279 .get_stale(|| format_dbg!())?
280 .get::<si::degree_celsius>())
281 * uc::KELVIN_INT
282 } else {
283 (self.length * self.width)
284 / (1.0 / self.cab_htc_to_amb_stop + 1.0 / self.cab_shell_htc_to_amb)
285 * (te_amb_air.get::<si::degree_celsius>()
286 - self
287 .state
288 .temperature
289 .get_stale(|| format_dbg!())?
290 .get::<si::degree_celsius>())
291 * uc::KELVIN_INT
292 },
293 || format_dbg!(),
294 )?;
295
296 self.state.temp_prev.update(
297 *self.state.temperature.get_stale(|| format_dbg!())?,
298 || format_dbg!(),
299 )?;
300 self.state.temperature.update(
301 *self.state.temperature.get_stale(|| format_dbg!())?
302 + (*self.state.pwr_thrml_from_hvac.get_fresh(|| format_dbg!())?
303 + *self.state.pwr_thrml_from_amb.get_fresh(|| format_dbg!())?
304 - *self.state.pwr_thrml_to_res.get_fresh(|| format_dbg!())?)
305 / self.heat_capacitance
306 * dt,
307 || format_dbg!(),
308 )?;
309 Ok(*self.state.temperature.get_fresh(|| format_dbg!())?)
310 }
311}
312
313#[serde_api]
314#[derive(
315 Clone, Debug, Deserialize, Serialize, PartialEq, HistoryVec, StateMethods, SetCumulative,
316)]
317#[cfg_attr(feature = "pyo3", pyclass(module = "fastsim", subclass, eq))]
318#[serde(deny_unknown_fields)]
319pub struct LumpedCabinState {
320 pub i: TrackedState<usize>,
322 pub temperature: TrackedState<si::Temperature>,
324 pub temp_prev: TrackedState<si::Temperature>,
326 pub pwr_thrml_from_hvac: TrackedState<si::Power>,
329 pub energy_thrml_from_hvac: TrackedState<si::Energy>,
332 pub pwr_thrml_from_amb: TrackedState<si::Power>,
335 pub energy_thrml_from_amb: TrackedState<si::Energy>,
338 pub pwr_thrml_to_res: TrackedState<si::Power>,
341 pub energy_thrml_to_res: TrackedState<si::Energy>,
344 pub reynolds_for_plate: TrackedState<si::Ratio>,
346}
347
348#[pyo3_api]
349impl LumpedCabinState {
350 #[pyo3(name = "default")]
351 #[staticmethod]
352 fn default_py() -> Self {
353 Self::default()
354 }
355}
356
357impl Default for LumpedCabinState {
358 fn default() -> Self {
359 Self {
360 i: Default::default(),
361 temperature: TrackedState::new(*TE_STD_AIR),
362 temp_prev: TrackedState::new(*TE_STD_AIR),
363 pwr_thrml_from_hvac: Default::default(),
364 energy_thrml_from_hvac: Default::default(),
365 pwr_thrml_from_amb: Default::default(),
366 energy_thrml_from_amb: Default::default(),
367 pwr_thrml_to_res: Default::default(),
368 energy_thrml_to_res: Default::default(),
369 reynolds_for_plate: Default::default(),
370 }
371 }
372}
373impl Init for LumpedCabinState {}
374impl SerdeAPI for LumpedCabinState {}