use crate::imports::*;
#[cfg(feature = "pyo3")]
use crate::pyo3imports::*;
#[cfg(feature = "pyo3")]
use crate::utils;
#[cfg(feature = "pyo3")]
use crate::utils::Pyo3VecF64;
use proc_macros::{add_pyo3_api, HistoryVec};
use std::f64::consts::PI;
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq)]
pub enum FcModelTypes {
Internal(FcTempEffModel, FcTempEffComponent),
External,
}
impl Default for FcModelTypes {
fn default() -> Self {
FcModelTypes::Internal(FcTempEffModel::default(), FcTempEffComponent::default())
}
}
#[derive(Default, Debug, Clone, Deserialize, Serialize, PartialEq, Eq)]
pub enum FcTempEffComponent {
Catalyst,
CatAndFC,
#[default]
FuelConverter,
}
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq)]
pub enum FcTempEffModel {
Linear(FcTempEffModelLinear),
Exponential(FcTempEffModelExponential),
}
impl Default for FcTempEffModel {
fn default() -> Self {
FcTempEffModel::Exponential(FcTempEffModelExponential::default())
}
}
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq)]
pub struct FcTempEffModelLinear {
pub offset: f64,
pub slope: f64,
pub minimum: f64,
}
impl Default for FcTempEffModelLinear {
fn default() -> Self {
Self {
offset: 0.0,
slope: 25.0,
minimum: 0.2,
}
}
}
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq)]
pub struct FcTempEffModelExponential {
pub offset: f64,
pub lag: f64,
pub minimum: f64,
}
impl Default for FcTempEffModelExponential {
fn default() -> Self {
Self {
offset: 0.0,
lag: 25.0,
minimum: 0.2,
}
}
}
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, HistoryVec)]
#[add_pyo3_api(
#[classmethod]
#[pyo3(name = "default")]
pub fn default_py(_cls: &PyType) -> PyResult<Self> {
Ok(Self::default())
}
)]
pub struct HVACModel {
pub te_set_deg_c: f64,
pub p_cntrl_kw_per_deg_c: f64,
pub i_cntrl_kw_per_deg_c_scnds: f64,
pub d_cntrl_kj_per_deg_c: f64,
pub cntrl_max_kw: f64,
pub te_deadband_deg_c: f64,
pub p_cntrl_kw: f64,
pub i_cntrl_kw: f64,
pub d_cntrl_kw: f64,
pub frac_of_ideal_cop: f64,
pub use_fc_waste_heat: bool,
pub pwr_max_aux_load_for_cooling_kw: f64,
pub cop: f64,
#[serde(skip)]
orphaned: bool,
}
impl SerdeAPI for HVACModel {}
impl Default for HVACModel {
fn default() -> Self {
Self {
te_set_deg_c: 22.0,
p_cntrl_kw_per_deg_c: 0.1,
i_cntrl_kw_per_deg_c_scnds: 0.01,
d_cntrl_kj_per_deg_c: 0.1,
cntrl_max_kw: 5.0,
te_deadband_deg_c: 1.0,
p_cntrl_kw: 0.0,
i_cntrl_kw: 0.0,
d_cntrl_kw: 0.0,
frac_of_ideal_cop: 0.075, use_fc_waste_heat: true,
pwr_max_aux_load_for_cooling_kw: 5.0,
cop: 0.0,
orphaned: Default::default(),
}
}
}
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq)]
pub enum CabinHvacModelTypes {
Internal(HVACModel),
External,
}
#[derive(Debug, Default, Clone, Deserialize, Serialize, PartialEq, Eq)]
pub enum ComponentModelTypes {
#[default]
Internal,
External,
}
#[cfg_attr(feature = "pyo3", pyfunction)]
pub fn get_sphere_conv_params(re: f64) -> (f64, f64) {
let (c, m) = if re < 4.0 {
(0.989, 0.330)
} else if re < 40.0 {
(0.911, 0.385)
} else if re < 4e3 {
(0.683, 0.466)
} else if re < 40e3 {
(0.193, 0.618)
} else {
(0.027, 0.805)
};
(c, m)
}
#[allow(non_snake_case)]
#[derive(Deserialize, Serialize, Clone, Debug, PartialEq)]
#[add_pyo3_api(
#[classmethod]
#[pyo3(name = "default")]
pub fn default_py(_cls: &PyType) -> Self {
Default::default()
}
pub fn set_cabin_hvac_model_internal(
&mut self,
hvac_model: HVACModel
) -> PyResult<()>{
Ok(check_orphaned_and_set!(self, cabin_hvac_model, CabinHvacModelTypes::Internal(hvac_model))?)
}
pub fn get_cabin_model_internal(&self, ) -> PyResult<HVACModel> {
if let CabinHvacModelTypes::Internal(hvac_model) = &self.cabin_hvac_model {
Ok(hvac_model.clone())
} else {
Err(PyAttributeError::new_err("HvacModelTypes::External variant currently used."))
}
}
pub fn set_cabin_hvac_model_external(&mut self, ) -> PyResult<()> {
Ok(check_orphaned_and_set!(self, cabin_hvac_model, CabinHvacModelTypes::External)?)
}
pub fn set_fc_model_internal_exponential(
&mut self,
offset: f64,
lag: f64,
minimum: f64,
fc_temp_eff_component: String
) -> anyhow::Result<()>{
let fc_temp_eff_comp = match fc_temp_eff_component.as_str() {
"FuelConverter" => FcTempEffComponent::FuelConverter,
"Catalyst" => FcTempEffComponent::Catalyst,
"CatAndFC" => FcTempEffComponent::CatAndFC,
_ => bail!("Invalid option for fc_temp_eff_component.")
};
Ok(check_orphaned_and_set!(
self,
fc_model,
FcModelTypes::Internal(
FcTempEffModel::Exponential(
FcTempEffModelExponential{ offset, lag, minimum }),
fc_temp_eff_comp
)
)?)
}
#[setter]
pub fn set_fc_exp_offset(&mut self, new_offset: f64) -> PyResult<()> {
if !self.orphaned {
self.fc_model = if let FcModelTypes::Internal(fc_temp_eff_model, fc_temp_eff_comp) = &self.fc_model {
if let FcTempEffModel::Exponential(FcTempEffModelExponential{ offset: _, lag, minimum }) = fc_temp_eff_model {
FcModelTypes::Internal(FcTempEffModel::Exponential
(FcTempEffModelExponential{ offset: new_offset, lag: *lag, minimum: *minimum }),
fc_temp_eff_comp.clone())
} else {
FcModelTypes::Internal(FcTempEffModel::Exponential
(FcTempEffModelExponential{ offset: new_offset, ..FcTempEffModelExponential::default() }),
fc_temp_eff_comp.clone())
}
} else {
FcModelTypes::Internal(FcTempEffModel::Exponential
(FcTempEffModelExponential{ offset: new_offset, ..FcTempEffModelExponential::default() }),
FcTempEffComponent::default())
};
Ok(())
} else {
Err(PyAttributeError::new_err(utils::NESTED_STRUCT_ERR))
}
}
#[setter]
pub fn set_fc_exp_lag(&mut self, new_lag: f64) -> PyResult<()>{
if !self.orphaned {
self.fc_model = if let FcModelTypes::Internal(fc_temp_eff_model, fc_temp_eff_comp) = &self.fc_model {
if let FcTempEffModel::Exponential(FcTempEffModelExponential{ offset, lag: _, minimum }) = fc_temp_eff_model {
FcModelTypes::Internal(FcTempEffModel::Exponential
(FcTempEffModelExponential{ offset: *offset, lag: new_lag, minimum: *minimum }),
fc_temp_eff_comp.clone())
} else {
FcModelTypes::Internal(FcTempEffModel::Exponential
(FcTempEffModelExponential{ lag: new_lag, ..FcTempEffModelExponential::default() }),
fc_temp_eff_comp.clone())
}
} else {
FcModelTypes::Internal(FcTempEffModel::Exponential
(FcTempEffModelExponential{ lag: new_lag, ..FcTempEffModelExponential::default() }),
FcTempEffComponent::default())
};
Ok(())
} else {
Err(PyAttributeError::new_err(utils::NESTED_STRUCT_ERR))
}
}
#[setter]
pub fn set_fc_exp_minimum(&mut self, new_minimum: f64) -> PyResult<()> {
if !self.orphaned {
self.fc_model = if let FcModelTypes::Internal(fc_temp_eff_model, fc_temp_eff_comp) = &self.fc_model {
if let FcTempEffModel::Exponential(FcTempEffModelExponential{ offset, lag, minimum: _ }) = fc_temp_eff_model {
FcModelTypes::Internal(FcTempEffModel::Exponential
(FcTempEffModelExponential{ offset: *offset, lag: *lag, minimum: new_minimum }),
fc_temp_eff_comp.clone())
} else {
FcModelTypes::Internal(FcTempEffModel::Exponential
(FcTempEffModelExponential{ minimum: new_minimum, ..FcTempEffModelExponential::default() }),
fc_temp_eff_comp.clone())
}
} else {
FcModelTypes::Internal(FcTempEffModel::Exponential
(FcTempEffModelExponential{ minimum: new_minimum, ..FcTempEffModelExponential::default() }),
FcTempEffComponent::default())
};
Ok(())
} else {
Err(PyAttributeError::new_err(utils::NESTED_STRUCT_ERR))
}
}
#[getter]
pub fn get_fc_exp_offset(&mut self) -> PyResult<f64> {
if let FcModelTypes::Internal(FcTempEffModel::Exponential(FcTempEffModelExponential{ offset, ..}), ..) = &self.fc_model {
Ok(*offset)
} else {
Err(PyAttributeError::new_err("fc_model is not Exponential"))
}
}
#[getter]
pub fn get_fc_exp_lag(&mut self) -> PyResult<f64> {
if let FcModelTypes::Internal(FcTempEffModel::Exponential(FcTempEffModelExponential{ lag, ..}), ..) = &self.fc_model {
Ok(*lag)
} else {
Err(PyAttributeError::new_err("fc_model is not Exponential"))
}
}
#[getter]
pub fn get_fc_exp_minimum(&mut self) -> PyResult<f64> {
if let FcModelTypes::Internal(FcTempEffModel::Exponential(FcTempEffModelExponential{ minimum, ..}), ..) = &self.fc_model {
Ok(*minimum)
} else {
Err(PyAttributeError::new_err("fc_model is not Exponential"))
}
}
)]
pub struct VehicleThermal {
pub fc_c_kj__k: f64,
pub fc_l: f64,
pub fc_htc_to_amb_stop: f64,
pub fc_coeff_from_comb: f64,
pub tstat_te_sto_deg_c: f64,
pub tstat_te_delta_deg_c: f64,
pub rad_eps: f64,
#[api(skip_get, skip_set)]
pub fc_model: FcModelTypes,
pub ess_c_kj_k: f64,
pub ess_htc_to_amb: f64,
#[api(skip_get, skip_set)]
pub cabin_hvac_model: CabinHvacModelTypes,
pub cab_c_kj__k: f64,
pub cab_l_length: f64,
pub cab_l_width: f64,
pub cab_r_to_amb: f64,
pub cab_htc_to_amb_stop: f64,
#[api(skip_get, skip_set)]
pub exhport_model: ComponentModelTypes,
pub exhport_ha_to_amb: f64,
pub exhport_ha_int: f64,
pub exhport_c_kj__k: f64,
#[api(skip_get, skip_set)]
pub cat_model: ComponentModelTypes,
pub cat_l: f64,
pub cat_c_kj__K: f64,
pub cat_htc_to_amb_stop: f64,
pub cat_te_lightoff_deg_c: f64,
pub cat_fc_eta_coeff: f64,
#[serde(skip)]
pub orphaned: bool,
}
impl SerdeAPI for VehicleThermal {}
impl Default for VehicleThermal {
fn default() -> Self {
VehicleThermal {
fc_c_kj__k: 150.0,
fc_l: 1.0,
fc_htc_to_amb_stop: 50.0,
fc_coeff_from_comb: 1e-4,
tstat_te_sto_deg_c: 85.0,
tstat_te_delta_deg_c: 5.0,
rad_eps: 5.0,
fc_model: FcModelTypes::default(),
ess_c_kj_k: 200.0, ess_htc_to_amb: 5.0, cabin_hvac_model: CabinHvacModelTypes::External, cab_c_kj__k: 125.0,
cab_l_length: 2.0,
cab_l_width: 2.0,
cab_r_to_amb: 0.02,
cab_htc_to_amb_stop: 10.0,
exhport_model: ComponentModelTypes::External, exhport_ha_to_amb: 5.0,
exhport_ha_int: 100.0,
exhport_c_kj__k: 10.0,
cat_model: ComponentModelTypes::External, cat_l: 0.50,
cat_c_kj__K: 15.0,
cat_htc_to_amb_stop: 10.0,
cat_te_lightoff_deg_c: 400.0,
cat_fc_eta_coeff: 0.3, orphaned: false,
}
}
}
impl VehicleThermal {
pub fn tstat_te_fo_deg_c(&self) -> f64 {
self.tstat_te_sto_deg_c + self.tstat_te_delta_deg_c
}
pub fn fc_area_ext(&self) -> f64 {
PI * self.fc_l.powf(2.0 / 4.0)
}
pub fn cat_area_ext(&self) -> f64 {
PI * self.cat_l.powf(2.0 / 4.0)
}
}