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// }