fastsim_core/
simdrive.rs

1//! Module containing vehicle struct and related functions.
2// crate local
3use crate::cycle::{RustCycle, RustCycleCache};
4use crate::imports::*;
5use crate::params::RustPhysicalProperties;
6use crate::proc_macros::add_pyo3_api;
7#[cfg(feature = "pyo3")]
8use crate::pyo3imports::*;
9use crate::vehicle::*;
10pub mod cyc_mods;
11pub mod simdrive_impl;
12pub mod simdrive_iter;
13
14#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
15#[add_pyo3_api(
16    pub fn __getnewargs__(&self) {
17        todo!();
18    }
19)]
20/// Struct containing time trace data
21pub struct RustSimDriveParams {
22    /// if true, accuracy will be favored over performance for grade per step estimates
23    /// Specifically, for performance, grade for a step will be assumed to be the grade
24    /// looked up at step start distance. For accuracy, the actual elevations will be
25    /// used. This distinciton only makes a difference for CAV maneuvers.
26    pub favor_grade_accuracy: bool,
27    /// if true, missed trace correction is active, default = False.  If missed
28    /// trace correction is active, time step will be "dilated" to be long enough for
29    /// vehicle to "catch up" with trace.
30    pub missed_trace_correction: bool,
31    /// maximum time dilation factor to "catch up" with trace -- e.g. 1.0 means 100% increase in step size
32    pub max_time_dilation: f64,
33    /// minimum time dilation margin to let trace "catch up" -- e.g. -0.5 means 50% reduction in step size
34    pub min_time_dilation: f64,
35    /// convergence criteria for time dilation in iterating on time step size to achieve distance parity
36    pub time_dilation_tol: f64,
37    /// number of iterations to achieve time dilation correction
38    pub max_trace_miss_iters: u32,
39    /// threshold for triggering warning log message if vehicle speed deficit [m/s]
40    /// relative to prescribed speed exceeds this amount
41    pub trace_miss_speed_mps_tol: f64,
42    /// threshold for triggering warning log message if achieved elapsed time
43    /// relative to prescribed elapsed time exceeds this fractional amount
44    pub trace_miss_time_tol: f64,
45    /// threshold for triggering warning log message if achieved distance
46    /// relative to prescribed distance exceeds this fractional amount
47    pub trace_miss_dist_tol: f64,
48    /// max allowable number of HEV SOC iterations
49    pub sim_count_max: usize,
50    /// newton solver gain
51    pub newton_gain: f64,
52    /// newton solver max iterations
53    pub newton_max_iter: u32,
54    /// newton solver tolerance
55    pub newton_xtol: f64,
56    /// tolerance for energy audit error warning, i.e. 0.1%
57    pub energy_audit_error_tol: f64,
58    // Eco-Coasting Maneuver Parameters
59    /// if true, coasting to stops are allowed
60    pub coast_allow: bool,
61    /// if true, coasting vehicle can eclipse the shadow trace (i.e., reference vehicle in front)
62    pub coast_allow_passing: bool,
63    /// maximum allowable speed under coast (m/s)
64    pub coast_max_speed_m_per_s: f64,
65    /// acceleration assumed during braking for coast maneuvers (m/s2). note: should be negative
66    pub coast_brake_accel_m_per_s2: f64,
67    /// speed when friction braking will initiate during coasting maneuvers (m/s)
68    pub coast_brake_start_speed_m_per_s: f64,
69    /// initiates coast when vehicle hits this speed if > 0; this is mainly for forceing coasting to initiate for testing. (m/s)
70    pub coast_start_speed_m_per_s: f64,
71    /// "look-ahead" time for speed changes to be considered to feature coasting to hit a given stopping distance mark (s)
72    pub coast_time_horizon_for_adjustment_s: f64,
73    // IDM - Intelligent Driver Model, Adaptive Cruise Control version
74    /// if true, initiates the IDM - Intelligent Driver Model, Adaptive Cruise Control version
75    pub idm_allow: bool,
76    /// IDM algorithm: desired speed (m/s)
77    pub idm_v_desired_m_per_s: f64,
78    /// IDM algorithm: headway time desired to vehicle in front (s)
79    pub idm_dt_headway_s: f64,
80    /// IDM algorithm: minimum desired gap between vehicle and lead vehicle (m)
81    pub idm_minimum_gap_m: f64,
82    /// IDM algorithm: delta parameter
83    pub idm_delta: f64,
84    /// IDM algorithm: acceleration parameter
85    pub idm_accel_m_per_s2: f64,
86    /// IDM algorithm: deceleration parameter
87    pub idm_decel_m_per_s2: f64,
88    /// IDM algorithm: a way to specify desired speed by course distance
89    /// traveled. Can simulate changing speed limits over a driving cycle
90    /// optional list of (distance (m), desired speed (m/s))
91    pub idm_v_desired_in_m_per_s_by_distance_m: Option<Vec<(f64, f64)>>,
92    // Other, Misc.
93    /// EPA fuel economy adjustment parameters; maximum EPA adjustment factor
94    pub max_epa_adj: f64,
95    #[serde(skip)]
96    pub orphaned: bool,
97}
98
99impl SerdeAPI for RustSimDriveParams {}
100
101impl Default for RustSimDriveParams {
102    fn default() -> Self {
103        // if True, accuracy will be favored over performance for grade per step estimates
104        // Specifically, for performance, grade for a step will be assumed to be the grade
105        // looked up at step start distance. For accuracy, the actual elevations will be
106        // used. This distinciton only makes a difference for CAV maneuvers.
107        let favor_grade_accuracy = true;
108        // if true, missed trace correction is active, default = false
109        let missed_trace_correction = false;
110        // maximum time dilation factor to "catch up" with trace -- e.g. 1.0 means 100% increase in step size
111        let max_time_dilation = 1.0;
112        // minimum time dilation margin to let trace "catch up" -- e.g. -0.5 means 50% reduction in step size
113        let min_time_dilation = -0.5;
114        let time_dilation_tol = 5e-4; // convergence criteria for time dilation
115        let max_trace_miss_iters = 5; // number of iterations to achieve time dilation correction
116        let trace_miss_speed_mps_tol = 1.0; // # threshold of error in speed [m/s] that triggers warning
117        let trace_miss_time_tol = 1e-3; // threshold for printing warning when time dilation is active
118        let trace_miss_dist_tol = 1e-3; // threshold of fractional eror in distance that triggers warning
119        let sim_count_max = 30; // max allowable number of HEV SOC iterations
120        let newton_gain = 0.9; // newton solver gain
121        let newton_max_iter = 100; // newton solver max iterations
122        let newton_xtol = 1e-9; // newton solver tolerance
123        let energy_audit_error_tol = 0.002; // tolerance for energy audit error warning, i.e. 0.1%
124                                            // Coasting
125        let coast_allow = false;
126        let coast_allow_passing = false;
127        let coast_max_speed_m_per_s = 40.0;
128        let coast_brake_accel_m_per_s2 = -2.5;
129        let coast_brake_start_speed_m_per_s = 7.5;
130        let coast_start_speed_m_per_s = 0.0; // m/s, if > 0, initiates coast when vehicle hits this speed; mostly for testing
131        let coast_time_horizon_for_adjustment_s = 20.0;
132        // Following
133        let idm_allow = false;
134        // IDM - Intelligent Driver Model, Adaptive Cruise Control version
135        let idm_v_desired_m_per_s = 33.33;
136        let idm_dt_headway_s = 1.0;
137        let idm_minimum_gap_m = 2.0;
138        let idm_delta = 4.0;
139        let idm_accel_m_per_s2 = 1.0;
140        let idm_decel_m_per_s2 = 1.5;
141        let idm_v_desired_in_m_per_s_by_distance_m = None;
142        // EPA fuel economy adjustment parameters
143        let max_epa_adj = 0.3; // maximum EPA adjustment factor
144        Self {
145            favor_grade_accuracy,
146            missed_trace_correction,
147            max_time_dilation,
148            min_time_dilation,
149            time_dilation_tol,
150            max_trace_miss_iters,
151            trace_miss_speed_mps_tol,
152            trace_miss_time_tol,
153            trace_miss_dist_tol,
154            sim_count_max,
155            newton_gain,
156            newton_max_iter,
157            newton_xtol,
158            energy_audit_error_tol,
159            coast_allow,
160            coast_allow_passing,
161            coast_max_speed_m_per_s,
162            coast_brake_accel_m_per_s2,
163            coast_brake_start_speed_m_per_s,
164            coast_start_speed_m_per_s,
165            coast_time_horizon_for_adjustment_s,
166            idm_allow,
167            idm_v_desired_m_per_s,
168            idm_dt_headway_s,
169            idm_minimum_gap_m,
170            idm_delta,
171            idm_accel_m_per_s2,
172            idm_decel_m_per_s2,
173            idm_v_desired_in_m_per_s_by_distance_m,
174            max_epa_adj,
175            orphaned: false,
176        }
177    }
178}
179
180#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
181#[add_pyo3_api(
182    /// method for instantiating SimDriveRust
183    #[new]
184    pub fn __new__(cyc: RustCycle, veh: RustVehicle) -> Self {
185        Self::new(cyc, veh)
186    }
187
188    pub fn __getnewargs__(&self) {
189        todo!();
190    }
191
192    // wrappers for core methods
193
194    #[pyo3(name = "gap_to_lead_vehicle_m")]
195    /// Provides the gap-with lead vehicle from start to finish
196    pub fn gap_to_lead_vehicle_m_py(&self) -> anyhow::Result<Vec<f64>> {
197        Ok(self.gap_to_lead_vehicle_m().to_vec())
198    }
199
200    #[pyo3(name = "sim_drive")]
201    #[pyo3(signature = (init_soc=None, aux_in_kw_override=None))]
202    /// Initialize and run sim_drive_walk as appropriate for vehicle attribute vehPtType.
203    /// Arguments
204    /// ------------
205    /// init_soc: initial SOC for electrified vehicles.
206    /// aux_in_kw: aux_in_kw override.  Array of same length as cyc.time_s.
207    ///     Default of None causes veh.aux_kw to be used.
208    pub fn sim_drive_py(
209        &mut self,
210        init_soc: Option<f64>,
211        aux_in_kw_override: Option<Vec<f64>>,
212    ) -> anyhow::Result<()> {
213        let aux_in_kw_override = aux_in_kw_override.map(Array1::from);
214        self.sim_drive(init_soc, aux_in_kw_override)
215    }
216
217    #[pyo3(signature = (init_soc, aux_in_kw_override=None))]
218    /// Receives second-by-second cycle information, vehicle properties,
219    /// and an initial state of charge and runs sim_drive_step to perform a
220    /// backward facing powertrain simulation. Method 'sim_drive' runs this
221    /// iteratively to achieve correct SOC initial and final conditions, as
222    /// needed.
223    ///
224    /// Arguments
225    /// ------------
226    /// init_soc (optional): initial battery state-of-charge (SOC) for electrified vehicles
227    /// aux_in_kw: aux_in_kw override.  Array of same length as cyc.time_s.
228    ///         None causes veh.aux_kw to be used.
229    pub fn sim_drive_walk(
230        &mut self,
231        init_soc: f64,
232        aux_in_kw_override: Option<Vec<f64>>,
233    ) -> anyhow::Result<()> {
234        let aux_in_kw_override = aux_in_kw_override.map(Array1::from);
235        self.walk(init_soc, aux_in_kw_override)
236    }
237
238    #[pyo3(signature = (by_microtrip=None, extend_fraction=None, blend_factor=None, min_target_speed_m_per_s=None))]
239    /// Sets the intelligent driver model parameters for an eco-cruise driving trajectory.
240    /// This is a convenience method instead of setting the sim_params.idm* parameters yourself.
241    /// - by_microtrip: bool, if True, target speed is set by microtrip, else by cycle
242    /// - extend_fraction: float, the fraction of time to extend the cycle to allow for catch-up
243    ///     of the following vehicle
244    /// - blend_factor: float, a value between 0 and 1; only used of by_microtrip is True, blends
245    ///     between microtrip average speed and microtrip average speed when moving. Must be
246    ///     between 0 and 1 inclusive
247    pub fn activate_eco_cruise(
248        &mut self,
249        by_microtrip: Option<bool>,
250        extend_fraction: Option<f64>,
251        blend_factor: Option<f64>,
252        min_target_speed_m_per_s: Option<f64>,
253    ) -> anyhow::Result<()> {
254        let by_microtrip = by_microtrip.unwrap_or(false);
255        let extend_fraction = extend_fraction.unwrap_or(0.1);
256        let blend_factor = blend_factor.unwrap_or(0.0);
257        let min_target_speed_m_per_s = min_target_speed_m_per_s.unwrap_or(8.0);
258            self.activate_eco_cruise_rust(
259                by_microtrip, extend_fraction, blend_factor, min_target_speed_m_per_s)
260    }
261
262    #[pyo3(name = "init_for_step")]
263    #[pyo3(signature = (init_soc, aux_in_kw_override=None))]
264    /// This is a specialty method which should be called prior to using
265    /// sim_drive_step in a loop.
266    /// Arguments
267    /// ------------
268    /// init_soc: initial battery state-of-charge (SOC) for electrified vehicles
269    /// aux_in_kw: aux_in_kw override.  Array of same length as cyc.time_s.
270    ///         Default of None causes veh.aux_kw to be used.
271    pub fn init_for_step_py(
272        &mut self,
273        init_soc:f64,
274        aux_in_kw_override: Option<Vec<f64>>
275    ) -> anyhow::Result<()> {
276        let aux_in_kw_override = aux_in_kw_override.map(Array1::from);
277        self.init_for_step(init_soc, aux_in_kw_override)
278    }
279
280    /// Step through 1 time step.
281    pub fn sim_drive_step(&mut self) -> anyhow::Result<()> {
282        self.step()
283    }
284
285    #[pyo3(name = "solve_step")]
286    /// Perform all the calculations to solve 1 time step.
287    pub fn solve_step_py(&mut self, i: usize) -> anyhow::Result<()> {
288        self.solve_step(i)
289    }
290
291    #[pyo3(name = "set_misc_calcs")]
292    /// Sets misc. calculations at time step 'i'
293    /// Arguments:
294    /// ----------
295    /// i: index of time step
296    pub fn set_misc_calcs_py(&mut self, i: usize) -> anyhow::Result<()> {
297        self.set_misc_calcs(i)
298    }
299
300    #[pyo3(name = "set_comp_lims")]
301    // Calculate actual speed achieved if vehicle hardware cannot achieve trace speed.
302    // Arguments
303    // ------------
304    // i: index of time step
305    pub fn set_comp_lims_py(&mut self, i: usize) -> anyhow::Result<()> {
306        self.set_comp_lims(i)
307    }
308
309    #[pyo3(name = "set_power_calcs")]
310    /// Calculate power requirements to meet cycle and determine if
311    /// cycle can be met.
312    /// Arguments
313    /// ------------
314    /// i: index of time step
315    pub fn set_power_calcs_py(&mut self, i: usize) -> anyhow::Result<()> {
316        self.set_power_calcs(i)
317    }
318
319    #[pyo3(name = "set_ach_speed")]
320    // Calculate actual speed achieved if vehicle hardware cannot achieve trace speed.
321    // Arguments
322    // ------------
323    // i: index of time step
324    pub fn set_ach_speed_py(&mut self, i: usize) -> anyhow::Result<()> {
325        self.set_ach_speed(i)
326    }
327
328    #[pyo3(name = "set_hybrid_cont_calcs")]
329    /// Hybrid control calculations.
330    /// Arguments
331    /// ------------
332    /// i: index of time step
333    pub fn set_hybrid_cont_calcs_py(&mut self, i: usize) -> anyhow::Result<()> {
334        self.set_hybrid_cont_calcs(i)
335    }
336
337    #[pyo3(name = "set_fc_forced_state")]
338    /// Calculate control variables related to engine on/off state
339    /// Arguments
340    /// ------------
341    /// i: index of time step
342    /// `_py` extension is needed to avoid name collision with getter/setter methods
343    pub fn set_fc_forced_state_py(&mut self, i: usize) -> anyhow::Result<()> {
344        self.set_fc_forced_state_rust(i)
345    }
346
347    #[pyo3(name = "set_hybrid_cont_decisions")]
348    /// Hybrid control decisions.
349    /// Arguments
350    /// ------------
351    /// i: index of time step
352    pub fn set_hybrid_cont_decisions_py(&mut self, i: usize) -> anyhow::Result<()> {
353        self.set_hybrid_cont_decisions(i)
354    }
355
356    #[pyo3(name = "set_fc_power")]
357    /// Sets power consumption values for the current time step.
358    /// Arguments
359    /// ------------
360    /// i: index of time step
361    pub fn set_fc_power_py(&mut self, i: usize) -> anyhow::Result<()> {
362        self.set_fc_power(i)
363    }
364
365    #[pyo3(name = "set_time_dilation")]
366    /// Sets the time dilation for the current step.
367    /// Arguments
368    /// ------------
369    /// i: index of time step
370    pub fn set_time_dilation_py(&mut self, i: usize) -> anyhow::Result<()> {
371        self.set_time_dilation(i)
372    }
373
374    #[pyo3(name = "set_post_scalars")]
375    /// Sets scalar variables that can be calculated after a cycle is run.
376    /// This includes mpgge, various energy metrics, and others
377    pub fn set_post_scalars_py(&mut self) -> anyhow::Result<()> {
378        self.set_post_scalars()
379    }
380
381    #[pyo3(name = "len")]
382    pub fn len_py(&self) -> usize {
383        self.len()
384    }
385
386    #[pyo3(name = "is_empty")]
387    pub fn is_empty_py(&self) -> bool {
388        self.is_empty()
389    }
390
391    #[getter]
392    pub fn get_fs_cumu_mj_out_ach(&self) -> Pyo3ArrayF64 {
393        Pyo3ArrayF64::new(ndarrcumsum(&(&self.fs_kw_out_ach * self.cyc.dt_s() * 1e-3)))
394    }
395    #[getter]
396    pub fn get_fc_cumu_mj_out_ach(&self) -> Pyo3ArrayF64 {
397        Pyo3ArrayF64::new(ndarrcumsum(&(&self.fc_kw_out_ach * self.cyc.dt_s() * 1e-3)))
398    }
399)]
400pub struct RustSimDrive {
401    pub hev_sim_count: usize,
402    #[api(has_orphaned)]
403    pub veh: RustVehicle,
404    #[api(has_orphaned)]
405    pub cyc: RustCycle,
406    #[api(has_orphaned)]
407    pub cyc0: RustCycle,
408    #[api(has_orphaned)]
409    pub sim_params: RustSimDriveParams,
410    #[serde(skip)]
411    #[api(has_orphaned)]
412    pub props: RustPhysicalProperties,
413    pub i: usize, // 1 # initialize step counter for possible use outside sim_drive_walk()
414    /// Current maximum fuel storage output power,
415    /// considering `veh.fs_max_kw` and transient limit,
416    /// as determined by achieved fuel storage power output and `veh.fs_secs_to_peak_pwr`
417    pub cur_max_fs_kw_out: Array1<f64>,
418    /// Transient fuel converter output power limit,
419    /// as determined by achieved fuel converter power output, `veh.fc_max_kw`, and `veh.fs_secs_to_peak_pwr`
420    pub fc_trans_lim_kw: Array1<f64>,
421    /// Current maximum fuel converter output power,
422    /// considering `veh.fc_max_kw` and transient limit `fc_trans_lim_kw`
423    pub cur_max_fc_kw_out: Array1<f64>,
424    /// ESS discharging power limit,
425    /// considering remaining ESS energy and ESS efficiency
426    pub ess_cap_lim_dischg_kw: Array1<f64>,
427    /// Current maximum ESS output power,
428    /// considering `ess_cap_lim_dischg_kw` and `veh.ess_max_kw`
429    pub cur_ess_max_kw_out: Array1<f64>,
430    /// Current maximum electrical power that can go toward propulsion,
431    /// `cur_max_elec_kw` limited by the maximum theoretical motor input power `veh.mc_max_elec_in_kw`
432    pub cur_max_avail_elec_kw: Array1<f64>,
433    /// ESS charging power limit,
434    /// considering unused energy capacity and ESS efficiency
435    pub ess_cap_lim_chg_kw: Array1<f64>,
436    /// ESS charging power limit,
437    /// considering `ess_cap_lim_chg_kw` and `veh.ess_max_kw`
438    pub cur_max_ess_chg_kw: Array1<f64>,
439    /// Current maximum electrical power that can go toward propulsion:
440    /// if FCEV, equal to `cur_max_fc_kw_out` + `cur_max_roadway_chg_kw` + `cur_ess_max_kw_out` - `aux_in_kw`,
441    /// otherwise equal to `cur_max_roadway_chg_kw` + `cur_ess_max_kw_out` - `aux_in_kw`
442    pub cur_max_elec_kw: Array1<f64>,
443    pub mc_elec_in_lim_kw: Array1<f64>,
444    /// Transient electric motor output power limit,
445    /// as determined by achieved motor mechanical power output, `veh.mc_max_kw`, and `veh.ms_secs_to_peak_pwr`
446    pub mc_transi_lim_kw: Array1<f64>,
447    pub cur_max_mc_kw_out: Array1<f64>,
448    pub ess_lim_mc_regen_perc_kw: Array1<f64>,
449    /// ESS limit on electricity regeneration,
450    /// considering `veh.mc_max_kw`, or `cur_max_ess_chg_kw` and motor efficiency
451    pub cur_max_mech_mc_kw_in: Array1<f64>,
452    pub cur_max_trans_kw_out: Array1<f64>,
453    /// Required tractive power to meet cycle,
454    /// equal to `drag_kw` + `accel_kw` + `ascent_kw`
455    pub cyc_trac_kw_req: Array1<f64>,
456    pub cur_max_trac_kw: Array1<f64>,
457    pub spare_trac_kw: Array1<f64>,
458    pub cyc_whl_rad_per_sec: Array1<f64>,
459    /// Power to change wheel rotational speed,
460    /// calculated with `veh.wheel_inertia_kg_m2` and `veh.num_wheels`
461    pub cyc_tire_inertia_kw: Array1<f64>,
462    /// Required power to wheels to meet cycle,
463    /// equal to `cyc_trac_kw_req` + `rr_kw` + `cyc_tire_inertia_kw`
464    pub cyc_whl_kw_req: Array1<f64>,
465    pub regen_contrl_lim_kw_perc: Array1<f64>,
466    pub cyc_regen_brake_kw: Array1<f64>,
467    /// Power lost to friction braking,
468    /// only nonzero when `cyc_whl_kw_req` is negative and regenerative braking cannot provide enough braking,
469    pub cyc_fric_brake_kw: Array1<f64>,
470    /// Required transmission output power to meet cycle,
471    /// equal to `cyc_whl_kw_req` + `cyc_fric_brake_kw`
472    pub cyc_trans_kw_out_req: Array1<f64>,
473    /// `true` if `cyc_trans_kw_out_req` <= `cur_max_trans_kw_out`
474    pub cyc_met: Array1<bool>,
475    /// Achieved transmission output power,
476    /// either `cyc_trans_kw_out_req` if cycle is met,
477    /// or `cur_max_trans_kw_out` if it is not
478    pub trans_kw_out_ach: Array1<f64>,
479    /// Achieved transmission input power, accounting for `veh.trans_eff`
480    pub trans_kw_in_ach: Array1<f64>,
481    pub cur_soc_target: Array1<f64>,
482    pub min_mc_kw_2help_fc: Array1<f64>,
483    /// Achieved electric motor mechanical output power to transmission
484    pub mc_mech_kw_out_ach: Array1<f64>,
485    /// Achieved electric motor electrical input power,
486    /// accounting for electric motor efficiency
487    pub mc_elec_kw_in_ach: Array1<f64>,
488    /// Auxiliary power load,
489    /// optionally overridden with an input array,
490    /// or if aux loads are forced to go through alternator (when `veh.no_elec_aux` is `true`) equal to `veh.aux_kw` / `veh.alt_eff`
491    /// otherwise equal to `veh.aux_kw`
492    pub aux_in_kw: Array1<f64>,
493    pub impose_coast: Array1<bool>,
494    pub roadway_chg_kw_out_ach: Array1<f64>,
495    pub min_ess_kw_2help_fc: Array1<f64>,
496    pub ess_kw_out_ach: Array1<f64>,
497    pub fc_kw_out_ach: Array1<f64>,
498    pub fc_kw_out_ach_pct: Array1<f64>,
499    pub fc_kw_in_ach: Array1<f64>,
500    pub fs_kw_out_ach: Array1<f64>,
501    pub fs_kwh_out_ach: Array1<f64>,
502    pub ess_cur_kwh: Array1<f64>,
503    /// Current ESS state of charge,
504    /// multiply by `veh.ess_max_kwh` to calculate remaining ESS energy
505    pub soc: Array1<f64>,
506    pub regen_buff_soc: Array1<f64>,
507    pub ess_regen_buff_dischg_kw: Array1<f64>,
508    pub max_ess_regen_buff_chg_kw: Array1<f64>,
509    pub ess_accel_buff_chg_kw: Array1<f64>,
510    pub accel_buff_soc: Array1<f64>,
511    pub max_ess_accell_buff_dischg_kw: Array1<f64>,
512    pub ess_accel_regen_dischg_kw: Array1<f64>,
513    pub mc_elec_in_kw_for_max_fc_eff: Array1<f64>,
514    /// Electrical power requirement for all-electric operation,
515    /// only applicable if vehicle has electrified powertrain,
516    /// equal to `aux_in_kw` + `trans_kw_in_ach` / motor efficiency
517    pub elec_kw_req_4ae: Array1<f64>,
518    pub can_pwr_all_elec: Array1<bool>,
519    pub desired_ess_kw_out_for_ae: Array1<f64>,
520    pub ess_ae_kw_out: Array1<f64>,
521    /// Charging power received from electric roadway (er), if enabled,
522    /// for all electric (ae) operation.
523    pub er_ae_kw_out: Array1<f64>,
524    pub ess_desired_kw_4fc_eff: Array1<f64>,
525    pub ess_kw_if_fc_req: Array1<f64>,
526    pub cur_max_mc_elec_kw_in: Array1<f64>,
527    pub fc_kw_gap_fr_eff: Array1<f64>,
528    pub er_kw_if_fc_req: Array1<f64>,
529    pub mc_elec_kw_in_if_fc_req: Array1<f64>,
530    pub mc_kw_if_fc_req: Array1<f64>,
531    pub fc_forced_on: Array1<bool>,
532    pub fc_forced_state: Array1<u32>,
533    /// Power the motor (mc) must provide if the engine (fc) is being
534    /// forced on. If the engine just turned on and triggers a regen
535    /// event, it'll be negative.
536    pub mc_mech_kw_4forced_fc: Array1<f64>,
537    pub fc_time_on: Array1<f64>,
538    pub prev_fc_time_on: Array1<f64>,
539    pub mps_ach: Array1<f64>,
540    pub mph_ach: Array1<f64>,
541    pub dist_m: Array1<f64>,
542    pub dist_mi: Array1<f64>,
543    pub high_acc_fc_on_tag: Array1<bool>,
544    pub reached_buff: Array1<bool>,
545    pub max_trac_mps: Array1<f64>,
546    pub add_kwh: Array1<f64>,
547    pub dod_cycs: Array1<f64>,
548    pub ess_perc_dead: Array1<f64>,
549    /// Power lost to aerodynamic drag according to the drag equation, `1/2 * rho * Cd * A * v_avg³ / 1000`
550    pub drag_kw: Array1<f64>,
551    pub ess_loss_kw: Array1<f64>,
552    /// Power to accelerate, `veh.veh_kg * (v_current² - v_prev²)/2 / dt / 1000`
553    pub accel_kw: Array1<f64>,
554    /// Power expended to ascend a grade, `sin(atan(grade)) * props.a_grav_mps2 * veh.veh_kg * v_avg / 1000`
555    pub ascent_kw: Array1<f64>,
556    /// Power lost to rolling resistance, `normal force * veh.wheel_rr_coef * v_avg / 1000`,
557    /// with normal force calculated as `cos(atan(grade)) * veh.veh_kg * props.a_grav_mps2`
558    pub rr_kw: Array1<f64>,
559    pub cur_max_roadway_chg_kw: Array1<f64>,
560    pub trace_miss_iters: Array1<u32>,
561    pub newton_iters: Array1<u32>,
562    pub fuel_kj: f64,
563    pub ess_dischg_kj: f64,
564    pub energy_audit_error: f64,
565    pub mpgge: f64,
566    pub roadway_chg_kj: f64,
567    pub battery_kwh_per_mi: f64,
568    pub electric_kwh_per_mi: f64,
569    pub ess2fuel_kwh: f64,
570    pub drag_kj: f64,
571    pub ascent_kj: f64,
572    pub rr_kj: f64,
573    pub brake_kj: f64,
574    pub trans_kj: f64,
575    pub mc_kj: f64,
576    pub ess_eff_kj: f64,
577    pub aux_kj: f64,
578    pub fc_kj: f64,
579    pub net_kj: f64,
580    pub ke_kj: f64,
581    /// `true` when the vehicle misses the prescribed speed trace
582    pub trace_miss: bool,
583    /// fractional difference between achieved cumulative distance
584    /// and prescribed cumulative distance
585    pub trace_miss_dist_frac: f64,
586    /// fractional difference between achieved time when trace miss is
587    /// and prescribed cumulative distance
588    pub trace_miss_time_frac: f64,
589    /// Maximum speed by which vehicle's speed falls behind prescribed
590    /// speed trace
591    pub trace_miss_speed_mps: f64,
592    #[serde(skip)]
593    pub orphaned: bool,
594    pub coast_delay_index: Array1<i32>,
595    pub idm_target_speed_m_per_s: Array1<f64>,
596    #[serde(skip)]
597    pub cyc0_cache: RustCycleCache,
598    #[api(skip_get, skip_set)]
599    #[serde(skip)]
600    aux_in_kw_override: Option<Vec<f64>>,
601}
602
603impl SerdeAPI for RustSimDrive {
604    fn init(&mut self) -> anyhow::Result<()> {
605        self.veh.init()?;
606        Ok(())
607    }
608}
609
610// #[cfg(test)]
611// mod tests {
612//     use super::*;
613
614//     #[test]
615//     fn test_walk() {
616//         // CYCLE
617//         let cyc = RustCycle::test_cyc();
618//         let cycle_length = cyc.len();
619
620//         // VEHICLE
621
622//         let veh = RustVehicle::test_veh();
623
624//         // SIM DRIVE
625//         let mut sd = RustSimDrive::__new__(cyc, veh);
626//         let init_soc = 0.5;
627//         sd.walk(init_soc);
628
629//         let expected_final_i = cycle_length;
630//         assert_eq!(sd.i, expected_final_i);
631//     }
632// }