Skip to main content

fastsim_core/vehicle/powertrain/
transmission.rs

1use super::*;
2
3#[serde_api]
4#[derive(Deserialize, Serialize, Debug, Clone, PartialEq, StateMethods, SetCumulative)]
5#[non_exhaustive]
6#[serde(deny_unknown_fields)]
7pub struct Transmission {
8    /// Transmission mass
9    #[serde(default)]
10    pub(crate) mass: Option<si::Mass>,
11
12    /// interpolator for calculating [Self] efficiency as a function of the following variants:  
13    /// - 0d -- constant
14    #[serde(serialize_with = "serialize_nested")]
15    pub eff_interp: InterpolatorEnum<f64>,
16    /// struct for tracking current state
17    #[serde(default)]
18    pub state: TransmissionState,
19    /// Custom vector of [Self::state]
20    #[serde(default, skip_serializing_if = "TransmissionStateHistoryVec::is_empty")]
21    pub history: TransmissionStateHistoryVec,
22    /// time step interval between saves. 1 is a good option. If None, no saving occurs.
23    pub save_interval: Option<usize>,
24}
25
26impl Transmission {
27    /// Constructor for Transmission
28    pub fn new(
29        mass: Option<si::Mass>,
30        eff_interp: InterpolatorEnum<f64>,
31        save_interval: Option<usize>,
32    ) -> anyhow::Result<Self> {
33        let mut transmission = Self {
34            mass,
35            eff_interp,
36            state: Default::default(),
37            history: Default::default(),
38            save_interval,
39        };
40        transmission.init()?;
41        Ok(transmission)
42    }
43}
44
45impl Powertrain for Transmission {
46    fn set_curr_pwr_prop_out_max(
47        &mut self,
48        pwr_upstream: (si::Power, si::Power),
49        _pwr_aux: si::Power,
50        _dt: si::Time,
51        _veh_state: &VehicleState,
52    ) -> anyhow::Result<()> {
53        self.state.pwr_out_fwd_max.update(
54            pwr_upstream.0
55                * (self
56                    .eff_interp
57                    .interpolate(&[])
58                    .with_context(|| format_dbg!())?
59                    * uc::R),
60            || format_dbg!(),
61        )?;
62        self.state.pwr_out_regen_max.update(
63            pwr_upstream.1
64                * (self
65                    .eff_interp
66                    .interpolate(&[])
67                    .with_context(|| format_dbg!())?
68                    * uc::R),
69            || format_dbg!(),
70        )?;
71        Ok(())
72    }
73
74    fn get_curr_pwr_prop_out_max(&self) -> anyhow::Result<(si::Power, si::Power)> {
75        Ok((
76            *self.state.pwr_out_fwd_max.get_fresh(|| format_dbg!())?,
77            *self.state.pwr_out_regen_max.get_fresh(|| format_dbg!())?,
78        ))
79    }
80
81    fn solve(
82        &mut self,
83        pwr_out_req: si::Power,
84        _enabled: bool,
85        _dt: si::Time,
86    ) -> anyhow::Result<Option<si::Power>> {
87        let state = &mut self.state;
88        // positive traction
89        ensure!(
90            pwr_out_req <= *state.pwr_out_fwd_max.get_fresh(|| format_dbg!())?,
91            "{}\n`pwr_out_req` ({} kW) exceeds `state.pwr_out_fwd_max` ({})",
92            format_dbg!(),
93            pwr_out_req.get::<si::kilowatt>().format_eng(None),
94            state
95                .pwr_out_fwd_max
96                .get_fresh(|| format_dbg!())?
97                .get::<si::kilowatt>()
98                .format_eng(None)
99        );
100        // no need for negative traction because that still includes component from friction brakes
101
102        let eff_pt: &[f64] = match self.eff_interp {
103            InterpolatorEnum::Interp0D(_) => &[],
104            _ => unimplemented!("Only Interp0D is currently implemented"),
105        };
106        state.eff.update(
107            self.eff_interp.interpolate(eff_pt)? * uc::R,
108            || format_dbg!(),
109        )?;
110        ensure!(
111            *state.eff.get_fresh(|| format_dbg!())? >= 0.0 * uc::R
112                && *state.eff.get_fresh(|| format_dbg!())? <= 1.0 * uc::R,
113            format!(
114                "{}\nTransmission efficiency ({}) must be between 0 and 1",
115                format_dbg!(
116                    *state.eff.get_fresh(|| format_dbg!())? >= 0.0 * uc::R
117                        && *state.eff.get_fresh(|| format_dbg!())? <= 1.0 * uc::R
118                ),
119                state.eff.get_fresh(|| format_dbg!())?.get::<si::ratio>()
120            )
121        );
122
123        state.pwr_out.update(pwr_out_req, || format_dbg!())?;
124        state.pwr_in.update(
125            if *state.pwr_out.get_fresh(|| format_dbg!())? > si::Power::ZERO {
126                *state.pwr_out.get_fresh(|| format_dbg!())?
127                    / *state.eff.get_fresh(|| format_dbg!())?
128            } else {
129                *state.pwr_out.get_fresh(|| format_dbg!())?
130                    * *state.eff.get_fresh(|| format_dbg!())?
131            },
132            || format_dbg!(),
133        )?;
134        state.pwr_loss.update(
135            (*state.pwr_in.get_fresh(|| format_dbg!())?
136                - *state.pwr_out.get_fresh(|| format_dbg!())?)
137            .abs(),
138            || format_dbg!(),
139        )?;
140
141        Ok(Some(*state.pwr_in.get_fresh(|| format_dbg!())?))
142    }
143
144    fn pwr_regen(&self) -> anyhow::Result<si::Power> {
145        Ok(-self
146            .state
147            .pwr_out
148            .get_fresh(|| format_dbg!())?
149            .max(si::Power::ZERO))
150    }
151}
152
153impl HistoryMethods for Transmission {
154    fn save_interval(&self) -> anyhow::Result<Option<usize>> {
155        Ok(self.save_interval)
156    }
157    fn set_save_interval(&mut self, save_interval: Option<usize>) -> anyhow::Result<()> {
158        self.save_interval = save_interval;
159        Ok(())
160    }
161    fn clear(&mut self) {
162        self.history.clear()
163    }
164}
165impl SerdeAPI for Transmission {}
166impl Init for Transmission {}
167
168impl Mass for Transmission {
169    fn mass(&self) -> anyhow::Result<Option<si::Mass>> {
170        Ok(self.mass)
171    }
172
173    fn set_mass(
174        &mut self,
175        new_mass: Option<si::Mass>,
176        _side_effect: MassSideEffect,
177    ) -> anyhow::Result<()> {
178        match new_mass {
179            Some(_) => {
180                ensure!(
181                    new_mass > Some(0.0 * uc::KG),
182                    "{} mass must be positive",
183                    stringify!(Transmission)
184                );
185                self.mass = new_mass;
186            }
187            None => {
188                self.mass = None;
189            }
190        }
191        Ok(())
192    }
193
194    fn derived_mass(&self) -> anyhow::Result<Option<si::Mass>> {
195        Ok(self.mass)
196    }
197
198    fn expunge_mass_fields(&mut self) {
199        self.mass = None;
200    }
201}
202
203#[serde_api]
204#[derive(
205    Clone,
206    Default,
207    Debug,
208    Deserialize,
209    Serialize,
210    PartialEq,
211    HistoryVec,
212    StateMethods,
213    SetCumulative,
214)]
215#[non_exhaustive]
216#[serde(default)]
217#[serde(deny_unknown_fields)]
218pub struct TransmissionState {
219    /// time step index
220    pub i: TrackedState<usize>,
221
222    /// max power output in the forward direction
223    pub pwr_out_fwd_max: TrackedState<si::Power>,
224
225    /// max power output in the backward/regen direction
226    pub pwr_out_regen_max: TrackedState<si::Power>,
227
228    /// efficiency at current time step
229    pub eff: TrackedState<si::Ratio>,
230
231    /// Power at output side of transmission.  Positive indicates forward power
232    /// (e.g. acceleration, ascent, working against dissipative forces)
233    pub pwr_out: TrackedState<si::Power>,
234    pub energy_out: TrackedState<si::Energy>,
235    /// Power at input side of transmission.  Positive indicates forward power
236    /// (e.g. acceleration, ascent, working against dissipative forces)
237    pub pwr_in: TrackedState<si::Power>,
238    pub energy_in: TrackedState<si::Energy>,
239
240    /// Power loss: [Self::pwr_in] - [Self::pwr_out]
241    pub pwr_loss: TrackedState<si::Power>,
242    pub energy_loss: TrackedState<si::Energy>,
243}
244
245impl Init for TransmissionState {}
246impl SerdeAPI for TransmissionState {}