fastsim_core/vehicle/powertrain/
fuel_storage.rs1use super::*;
2
3#[derive(Deserialize, Serialize, Debug, Clone, PartialEq)]
4pub enum FuelType {
5 Gasoline,
6 Diesel,
7 Hydrogen,
8}
9
10#[serde_api]
11#[derive(Deserialize, Serialize, Debug, Clone, PartialEq, SetCumulative)]
12#[cfg_attr(feature = "pyo3", pyclass(module = "fastsim", subclass, eq))]
13#[non_exhaustive]
14#[serde(deny_unknown_fields)]
15pub struct FuelStorage {
16 pub pwr_out_max: si::Power,
18 pub pwr_ramp_lag: si::Time,
20 #[serde(default)]
22 pub fuel_type: Option<FuelType>,
23 pub energy_capacity: si::Energy,
25 pub(crate) specific_energy: Option<si::SpecificEnergy>,
27 #[serde(default)]
29 pub(crate) mass: Option<si::Mass>,
30 }
33
34#[pyo3_api]
35impl FuelStorage {
36 }
48
49impl FuelStorage {
50 pub fn new(
51 pwr_out_max: si::Power,
52 pwr_ramp_lag: si::Time,
53 fuel_type: Option<FuelType>,
54 energy_capacity: si::Energy,
55 specific_energy: Option<si::SpecificEnergy>,
56 mass: Option<si::Mass>,
57 ) -> anyhow::Result<Self> {
58 let mut fs = Self {
59 pwr_out_max,
60 pwr_ramp_lag,
61 fuel_type,
62 energy_capacity,
63 specific_energy,
64 mass,
65 };
66 fs.init()?;
67 Ok(fs)
68 }
69}
70
71impl SerdeAPI for FuelStorage {}
72impl Init for FuelStorage {}
73
74impl Mass for FuelStorage {
75 fn mass(&self) -> anyhow::Result<Option<si::Mass>> {
76 let derived_mass = self
77 .derived_mass()
78 .with_context(|| anyhow!(format_dbg!()))?;
79 if let (Some(derived_mass), Some(set_mass)) = (derived_mass, self.mass) {
80 ensure!(
81 utils::almost_eq_uom(&set_mass, &derived_mass, None),
82 format!(
83 "{}",
84 format_dbg!(utils::almost_eq_uom(&set_mass, &derived_mass, None)),
85 )
86 );
87 }
88 Ok(self.mass)
89 }
90
91 fn set_mass(
92 &mut self,
93 new_mass: Option<si::Mass>,
94 side_effect: MassSideEffect,
95 ) -> anyhow::Result<()> {
96 let derived_mass = self
97 .derived_mass()
98 .with_context(|| anyhow!(format_dbg!()))?;
99 self.mass = match (new_mass, derived_mass) {
100 (Some(new_mass), Some(dm)) => {
102 if dm != new_mass {
103 match side_effect {
104 MassSideEffect::Extensive => {
105 self.energy_capacity = self.specific_energy.with_context(|| {
106 format!(
107 "{}\nExpected `self.specific_energy` to be `Some`.",
108 format_dbg!()
109 )
110 })? * new_mass;
111 }
112 MassSideEffect::Intensive => {
113 self.specific_energy = Some(self.energy_capacity / new_mass);
114 }
115 MassSideEffect::None => {
116 self.specific_energy = None;
117 }
118 }
119 }
120 Some(new_mass)
121 }
122 (Some(new_mass), None) => Some(new_mass),
123 (None, Some(dm)) => Some(dm),
124 (None, None) => bail!(
125 "Not all mass fields in `{}` are set and no mass was provided.",
126 stringify!(FuelStorage)
127 ),
128 };
129 ensure!(
130 self.mass > Some(0.0 * uc::KG),
131 "{} mass must be positive",
132 stringify!(FuelStorage)
133 );
134 Ok(())
135 }
136
137 fn derived_mass(&self) -> anyhow::Result<Option<si::Mass>> {
138 Ok(self
139 .specific_energy
140 .map(|specific_energy| self.energy_capacity / specific_energy))
141 }
142
143 fn expunge_mass_fields(&mut self) {
144 self.mass = None;
145 self.specific_energy = None;
146 }
147}