fastsim_core/vehicle/
conv.rs1use super::*;
2
3#[serde_api]
4#[derive(Clone, Debug, PartialEq, Serialize, Deserialize, StateMethods, SetCumulative)]
5#[non_exhaustive]
6#[serde(deny_unknown_fields)]
7#[cfg_attr(feature = "pyo3", pyclass(module = "fastsim", subclass, eq))]
8pub struct ConventionalVehicle {
10 pub fs: FuelStorage,
11 #[has_state]
12 pub fc: FuelConverter,
13 #[has_state]
14 pub transmission: Transmission,
15 pub(crate) mass: Option<si::Mass>,
16 pub alt_eff: si::Ratio,
18}
19
20#[pyo3_api]
21impl ConventionalVehicle {}
22
23impl SerdeAPI for ConventionalVehicle {}
24impl Init for ConventionalVehicle {
25 fn init(&mut self) -> Result<(), Error> {
26 self.fc
27 .init()
28 .map_err(|err| Error::InitError(format_dbg!(err)))?;
29 self.fs
30 .init()
31 .map_err(|err| Error::InitError(format_dbg!(err)))?;
32 self.transmission
33 .init()
34 .map_err(|err| Error::InitError(format_dbg!(err)))?;
35 Ok(())
36 }
37}
38
39impl HistoryMethods for ConventionalVehicle {
40 fn save_interval(&self) -> anyhow::Result<Option<usize>> {
41 bail!("`save_interval` is not implemented in ConventionalVehicle")
42 }
43 fn set_save_interval(&mut self, save_interval: Option<usize>) -> anyhow::Result<()> {
44 self.fc.set_save_interval(save_interval)?;
46 self.transmission.set_save_interval(save_interval)?;
47 Ok(())
48 }
49 fn clear(&mut self) {
50 self.fc.clear();
51 self.transmission.clear();
52 }
53}
54
55impl Powertrain for Box<ConventionalVehicle> {
56 fn set_curr_pwr_prop_out_max(
57 &mut self,
58 pwr_aux: si::Power,
59 dt: si::Time,
60 _veh_state: &VehicleState,
61 ) -> anyhow::Result<()> {
62 self.fc
64 .set_curr_pwr_out_max(dt)
65 .with_context(|| anyhow!(format_dbg!()))?;
66 self.fc
67 .set_curr_pwr_prop_max(pwr_aux / self.alt_eff)
68 .with_context(|| anyhow!(format_dbg!()))?;
69 Ok(())
70 }
71
72 fn get_curr_pwr_prop_out_max(&self) -> anyhow::Result<(si::Power, si::Power)> {
73 Ok((
74 *self.fc.state.pwr_prop_max.get_fresh(|| format_dbg!())?,
75 si::Power::ZERO,
76 ))
77 }
78
79 fn solve(
80 &mut self,
81 pwr_out_req: si::Power,
82 _enabled: bool,
83 dt: si::Time,
84 ) -> anyhow::Result<()> {
85 let pwr_out_req = pwr_out_req.max(si::Power::ZERO);
87 let enabled = true; let pwr_in_transmission = self
89 .transmission
90 .get_pwr_in_req(pwr_out_req)
91 .with_context(|| anyhow!(format_dbg!()))?;
92 self.fc
93 .solve(pwr_in_transmission, enabled, dt)
94 .with_context(|| anyhow!(format_dbg!()))?;
95 Ok(())
96 }
97
98 fn pwr_regen(&self) -> anyhow::Result<si::Power> {
99 Ok(si::Power::ZERO)
100 }
101}
102
103impl ConventionalVehicle {
104 pub fn solve_thermal(
105 &mut self,
106 te_amb: si::Temperature,
107 pwr_thrml_fc_to_cab: Option<si::Power>,
108 veh_state: &mut VehicleState,
109 dt: si::Time,
110 ) -> anyhow::Result<()> {
111 self.fc
112 .solve_thermal(te_amb, pwr_thrml_fc_to_cab, veh_state, dt)
113 }
114}
115
116impl Mass for ConventionalVehicle {
117 fn mass(&self) -> anyhow::Result<Option<si::Mass>> {
118 let derived_mass = self
119 .derived_mass()
120 .with_context(|| anyhow!(format_dbg!()))?;
121 match (derived_mass, self.mass) {
122 (Some(derived_mass), Some(set_mass)) => {
123 ensure!(
124 utils::almost_eq_uom(&set_mass, &derived_mass, None),
125 format!(
126 "{}",
127 format_dbg!(utils::almost_eq_uom(&set_mass, &derived_mass, None)),
128 )
129 );
130 Ok(Some(set_mass))
131 }
132 _ => Ok(self.mass.or(derived_mass)),
133 }
134 }
135
136 fn set_mass(
137 &mut self,
138 new_mass: Option<si::Mass>,
139 side_effect: MassSideEffect,
140 ) -> anyhow::Result<()> {
141 ensure!(
142 side_effect == MassSideEffect::None,
143 "At the powertrain level, only `MassSideEffect::None` is allowed"
144 );
145 let derived_mass = self
146 .derived_mass()
147 .with_context(|| anyhow!(format_dbg!()))?;
148 self.mass = match new_mass {
149 Some(new_mass) => {
151 if let Some(dm) = derived_mass {
152 if dm != new_mass {
153 self.expunge_mass_fields();
154 }
155 }
156 Some(new_mass)
157 }
158 None => Some(derived_mass.with_context(|| {
160 format!(
161 "Not all mass fields in `{}` are set and no mass was provided.",
162 stringify!(ConventionalVehicle)
163 )
164 })?),
165 };
166 Ok(())
167 }
168
169 fn derived_mass(&self) -> anyhow::Result<Option<si::Mass>> {
170 let fc_mass = self.fc.mass().with_context(|| anyhow!(format_dbg!()))?;
171 let fs_mass = self.fs.mass().with_context(|| anyhow!(format_dbg!()))?;
172 let transmission_mass = self
173 .transmission
174 .mass()
175 .with_context(|| anyhow!(format_dbg!()))?;
176 match (fc_mass, fs_mass, transmission_mass) {
177 (Some(fc_mass), Some(fs_mass), Some(transmission_mass)) => {
178 Ok(Some(fc_mass + fs_mass + transmission_mass))
179 }
180 (None, None, None) => Ok(None),
181 _ => bail!(
182 "`{}` field masses are not consistently set to `Some` or `None`",
183 stringify!(ConventionalVehicle)
184 ),
185 }
186 }
187
188 fn expunge_mass_fields(&mut self) {
189 self.fc.expunge_mass_fields();
190 self.fs.expunge_mass_fields();
191 self.transmission.expunge_mass_fields();
192 self.mass = None;
193 }
194}