1use crate::imports::*;
2use crate::proc_macros::{add_pyo3_api, HistoryVec};
3#[cfg(feature = "pyo3")]
4use crate::pyo3imports::*;
5#[cfg(feature = "pyo3")]
6use crate::utils;
7#[cfg(feature = "pyo3")]
8use crate::utils::Pyo3VecF64;
9use std::f64::consts::PI;
10
11#[derive(Debug, Clone, Deserialize, Serialize, PartialEq)]
13pub enum FcModelTypes {
14 Internal(FcTempEffModel, FcTempEffComponent),
16 External,
18}
19
20impl Default for FcModelTypes {
21 fn default() -> Self {
22 FcModelTypes::Internal(FcTempEffModel::default(), FcTempEffComponent::default())
23 }
24}
25
26#[derive(Default, Debug, Clone, Deserialize, Serialize, PartialEq, Eq)]
28pub enum FcTempEffComponent {
29 Catalyst,
31 CatAndFC,
33 #[default]
35 FuelConverter,
36}
37
38#[derive(Debug, Clone, Deserialize, Serialize, PartialEq)]
40pub enum FcTempEffModel {
41 Linear(FcTempEffModelLinear),
43 Exponential(FcTempEffModelExponential),
45}
46
47impl Default for FcTempEffModel {
48 fn default() -> Self {
49 FcTempEffModel::Exponential(FcTempEffModelExponential::default())
50 }
51}
52
53#[derive(Debug, Clone, Deserialize, Serialize, PartialEq)]
54pub struct FcTempEffModelLinear {
55 pub offset: f64,
56 pub slope: f64,
57 pub minimum: f64,
58}
59
60impl Default for FcTempEffModelLinear {
61 fn default() -> Self {
62 Self {
63 offset: 0.0,
64 slope: 25.0,
65 minimum: 0.2,
66 }
67 }
68}
69
70#[derive(Debug, Clone, Deserialize, Serialize, PartialEq)]
71pub struct FcTempEffModelExponential {
72 pub offset: f64,
74 pub lag: f64,
76 pub minimum: f64,
78}
79
80impl Default for FcTempEffModelExponential {
81 fn default() -> Self {
82 Self {
83 offset: 0.0,
84 lag: 25.0,
85 minimum: 0.2,
86 }
87 }
88}
89
90#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, HistoryVec)]
92#[add_pyo3_api(
93 #[staticmethod]
94 #[pyo3(name = "default")]
95 pub fn default_py() -> Self {
96 Self::default()
97 }
98)]
99pub struct HVACModel {
100 pub te_set_deg_c: f64,
102 pub p_cntrl_kw_per_deg_c: f64,
104 pub i_cntrl_kw_per_deg_c_scnds: f64,
106 pub d_cntrl_kj_per_deg_c: f64,
108 pub cntrl_max_kw: f64,
111 pub te_deadband_deg_c: f64,
114 pub p_cntrl_kw: f64,
116 pub i_cntrl_kw: f64,
118 pub d_cntrl_kw: f64,
120 pub frac_of_ideal_cop: f64,
123 pub use_fc_waste_heat: bool,
125 pub pwr_max_aux_load_for_cooling_kw: f64,
127 pub cop: f64,
129 #[serde(skip)]
130 orphaned: bool,
131}
132
133impl SerdeAPI for HVACModel {}
134
135impl Default for HVACModel {
136 fn default() -> Self {
137 Self {
138 te_set_deg_c: 22.0,
139 p_cntrl_kw_per_deg_c: 0.1,
140 i_cntrl_kw_per_deg_c_scnds: 0.01,
141 d_cntrl_kj_per_deg_c: 0.1,
142 cntrl_max_kw: 5.0,
143 te_deadband_deg_c: 1.0,
144 p_cntrl_kw: 0.0,
145 i_cntrl_kw: 0.0,
146 d_cntrl_kw: 0.0,
147 frac_of_ideal_cop: 0.075, use_fc_waste_heat: true,
149 pwr_max_aux_load_for_cooling_kw: 5.0,
150 cop: 0.0,
151 orphaned: Default::default(),
152 }
153 }
154}
155
156#[derive(Debug, Clone, Deserialize, Serialize, PartialEq)]
158pub enum CabinHvacModelTypes {
159 Internal(HVACModel),
161 External,
162}
163
164#[derive(Debug, Default, Clone, Deserialize, Serialize, PartialEq, Eq)]
166pub enum ComponentModelTypes {
167 #[default]
169 Internal,
170 External,
172}
173
174#[cfg_attr(feature = "pyo3", pyfunction)]
175pub fn get_sphere_conv_params(re: f64) -> (f64, f64) {
178 let (c, m) = if re < 4.0 {
179 (0.989, 0.330)
180 } else if re < 40.0 {
181 (0.911, 0.385)
182 } else if re < 4e3 {
183 (0.683, 0.466)
184 } else if re < 40e3 {
185 (0.193, 0.618)
186 } else {
187 (0.027, 0.805)
188 };
189 (c, m)
190}
191
192#[allow(non_snake_case)]
194#[derive(Deserialize, Serialize, Clone, Debug, PartialEq)]
195#[add_pyo3_api(
196 #[staticmethod]
197 #[pyo3(name = "default")]
198 pub fn default_py() -> Self {
199 Default::default()
200 }
201
202 pub fn set_cabin_hvac_model_internal(
203 &mut self,
204 hvac_model: HVACModel
205 ) -> anyhow::Result<()>{
206 check_orphaned_and_set!(self, cabin_hvac_model, CabinHvacModelTypes::Internal(hvac_model))
207 }
208
209 pub fn get_cabin_model_internal(&self, ) -> anyhow::Result<HVACModel> {
210 if let CabinHvacModelTypes::Internal(hvac_model) = &self.cabin_hvac_model {
211 Ok(hvac_model.clone())
212 } else {
213 bail!(PyAttributeError::new_err("HvacModelTypes::External variant currently used."))
214 }
215 }
216
217 pub fn set_cabin_hvac_model_external(&mut self) -> anyhow::Result<()> {
218 check_orphaned_and_set!(self, cabin_hvac_model, CabinHvacModelTypes::External)
219 }
220
221 pub fn set_fc_model_internal_exponential(
222 &mut self,
223 offset: f64,
224 lag: f64,
225 minimum: f64,
226 fc_temp_eff_component: String
227 ) -> anyhow::Result<()>{
228 let fc_temp_eff_comp = match fc_temp_eff_component.as_str() {
229 "FuelConverter" => FcTempEffComponent::FuelConverter,
230 "Catalyst" => FcTempEffComponent::Catalyst,
231 "CatAndFC" => FcTempEffComponent::CatAndFC,
232 _ => bail!("Invalid option for fc_temp_eff_component.")
233 };
234
235 check_orphaned_and_set!(
236 self,
237 fc_model,
238 FcModelTypes::Internal(
239 FcTempEffModel::Exponential(
240 FcTempEffModelExponential{ offset, lag, minimum }),
241 fc_temp_eff_comp
242 )
243 )
244 }
245
246 #[setter]
247 pub fn set_fc_exp_offset(&mut self, new_offset: f64) -> anyhow::Result<()> {
248 if !self.orphaned {
249 self.fc_model = if let FcModelTypes::Internal(fc_temp_eff_model, fc_temp_eff_comp) = &self.fc_model {
250 if let FcTempEffModel::Exponential(FcTempEffModelExponential{ offset: _, lag, minimum }) = fc_temp_eff_model {
252 FcModelTypes::Internal(FcTempEffModel::Exponential
254 (FcTempEffModelExponential{ offset: new_offset, lag: *lag, minimum: *minimum }),
255 fc_temp_eff_comp.clone())
256 } else {
257 FcModelTypes::Internal(FcTempEffModel::Exponential
259 (FcTempEffModelExponential{ offset: new_offset, ..FcTempEffModelExponential::default() }),
260 fc_temp_eff_comp.clone())
261 }
262 } else {
263 FcModelTypes::Internal(FcTempEffModel::Exponential
265 (FcTempEffModelExponential{ offset: new_offset, ..FcTempEffModelExponential::default() }),
266 FcTempEffComponent::default())
267 };
268 Ok(())
269 } else {
270 bail!(PyAttributeError::new_err(utils::NESTED_STRUCT_ERR))
271 }
272 }
273
274 #[setter]
275 pub fn set_fc_exp_lag(&mut self, new_lag: f64) -> anyhow::Result<()>{
276 if !self.orphaned {
277 self.fc_model = if let FcModelTypes::Internal(fc_temp_eff_model, fc_temp_eff_comp) = &self.fc_model {
278 if let FcTempEffModel::Exponential(FcTempEffModelExponential{ offset, lag: _, minimum }) = fc_temp_eff_model {
280 FcModelTypes::Internal(FcTempEffModel::Exponential
282 (FcTempEffModelExponential{ offset: *offset, lag: new_lag, minimum: *minimum }),
283 fc_temp_eff_comp.clone())
284 } else {
285 FcModelTypes::Internal(FcTempEffModel::Exponential
287 (FcTempEffModelExponential{ lag: new_lag, ..FcTempEffModelExponential::default() }),
288 fc_temp_eff_comp.clone())
289 }
290 } else {
291 FcModelTypes::Internal(FcTempEffModel::Exponential
293 (FcTempEffModelExponential{ lag: new_lag, ..FcTempEffModelExponential::default() }),
294 FcTempEffComponent::default())
295 };
296 Ok(())
297 } else {
298 bail!(PyAttributeError::new_err(utils::NESTED_STRUCT_ERR))
299 }
300 }
301
302 #[setter]
303 pub fn set_fc_exp_minimum(&mut self, new_minimum: f64) -> anyhow::Result<()> {
304 if !self.orphaned {
305 self.fc_model = if let FcModelTypes::Internal(fc_temp_eff_model, fc_temp_eff_comp) = &self.fc_model {
306 if let FcTempEffModel::Exponential(FcTempEffModelExponential{ offset, lag, minimum: _ }) = fc_temp_eff_model {
308 FcModelTypes::Internal(FcTempEffModel::Exponential
310 (FcTempEffModelExponential{ offset: *offset, lag: *lag, minimum: new_minimum }),
311 fc_temp_eff_comp.clone())
312 } else {
313 FcModelTypes::Internal(FcTempEffModel::Exponential
315 (FcTempEffModelExponential{ minimum: new_minimum, ..FcTempEffModelExponential::default() }),
316 fc_temp_eff_comp.clone())
317 }
318 } else {
319 FcModelTypes::Internal(FcTempEffModel::Exponential
321 (FcTempEffModelExponential{ minimum: new_minimum, ..FcTempEffModelExponential::default() }),
322 FcTempEffComponent::default())
323 };
324 Ok(())
325 } else {
326 bail!(PyAttributeError::new_err(utils::NESTED_STRUCT_ERR))
327 }
328 }
329
330 #[getter]
331 pub fn get_fc_exp_offset(&mut self) -> anyhow::Result<f64> {
332 if let FcModelTypes::Internal(FcTempEffModel::Exponential(FcTempEffModelExponential{ offset, ..}), ..) = &self.fc_model {
333 Ok(*offset)
334 } else {
335 bail!(PyAttributeError::new_err("fc_model is not Exponential"))
336 }
337 }
338
339 #[getter]
340 pub fn get_fc_exp_lag(&mut self) -> anyhow::Result<f64> {
341 if let FcModelTypes::Internal(FcTempEffModel::Exponential(FcTempEffModelExponential{ lag, ..}), ..) = &self.fc_model {
342 Ok(*lag)
343 } else {
344 bail!(PyAttributeError::new_err("fc_model is not Exponential"))
345 }
346 }
347
348 #[getter]
349 pub fn get_fc_exp_minimum(&mut self) -> anyhow::Result<f64> {
350 if let FcModelTypes::Internal(FcTempEffModel::Exponential(FcTempEffModelExponential{ minimum, ..}), ..) = &self.fc_model {
351 Ok(*minimum)
352 } else {
353 bail!(PyAttributeError::new_err("fc_model is not Exponential"))
354 }
355 }
356
357 )]
359pub struct VehicleThermal {
360 pub fc_c_kj__k: f64,
363 pub fc_l: f64,
365 pub fc_htc_to_amb_stop: f64,
367 pub fc_coeff_from_comb: f64,
370 pub tstat_te_sto_deg_c: f64,
372 pub tstat_te_delta_deg_c: f64,
374 pub rad_eps: f64,
377
378 #[api(skip_get, skip_set)]
381 pub fc_model: FcModelTypes,
382
383 pub ess_c_kj_k: f64,
386 pub ess_htc_to_amb: f64,
388 #[api(skip_get, skip_set)]
395 pub cabin_hvac_model: CabinHvacModelTypes,
396 pub cab_c_kj__k: f64,
398 pub cab_l_length: f64,
400 pub cab_l_width: f64,
402 pub cab_r_to_amb: f64,
404 pub cab_htc_to_amb_stop: f64,
407
408 #[api(skip_get, skip_set)]
412 pub exhport_model: ComponentModelTypes,
413 pub exhport_ha_to_amb: f64,
415 pub exhport_ha_int: f64,
417 pub exhport_c_kj__k: f64,
419
420 #[api(skip_get, skip_set)]
422 pub cat_model: ComponentModelTypes,
423 pub cat_l: f64,
425 pub cat_c_kj__K: f64,
427 pub cat_htc_to_amb_stop: f64,
430 pub cat_te_lightoff_deg_c: f64,
432 pub cat_fc_eta_coeff: f64,
434
435 #[serde(skip)]
437 pub orphaned: bool,
438}
439
440impl SerdeAPI for VehicleThermal {}
441
442impl Default for VehicleThermal {
443 fn default() -> Self {
444 VehicleThermal {
445 fc_c_kj__k: 150.0,
446 fc_l: 1.0,
447 fc_htc_to_amb_stop: 50.0,
448 fc_coeff_from_comb: 1e-4,
449 tstat_te_sto_deg_c: 85.0,
450 tstat_te_delta_deg_c: 5.0,
451 rad_eps: 5.0,
452 fc_model: FcModelTypes::default(),
453 ess_c_kj_k: 200.0, ess_htc_to_amb: 5.0, cabin_hvac_model: CabinHvacModelTypes::External, cab_c_kj__k: 125.0,
457 cab_l_length: 2.0,
458 cab_l_width: 2.0,
459 cab_r_to_amb: 0.02,
460 cab_htc_to_amb_stop: 10.0,
461 exhport_model: ComponentModelTypes::External, exhport_ha_to_amb: 5.0,
463 exhport_ha_int: 100.0,
464 exhport_c_kj__k: 10.0,
465 cat_model: ComponentModelTypes::External, cat_l: 0.50,
467 cat_c_kj__K: 15.0,
468 cat_htc_to_amb_stop: 10.0,
469 cat_te_lightoff_deg_c: 400.0,
470 cat_fc_eta_coeff: 0.3, orphaned: false,
472 }
473 }
474}
475
476impl VehicleThermal {
477 pub fn tstat_te_fo_deg_c(&self) -> f64 {
479 self.tstat_te_sto_deg_c + self.tstat_te_delta_deg_c
480 }
481
482 pub fn fc_area_ext(&self) -> f64 {
484 PI * self.fc_l.powf(2.0) / 4.0
485 }
486
487 pub fn cat_area_ext(&self) -> f64 {
489 PI * self.cat_l.powf(2.0 / 4.0)
490 }
491}