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