fastsim_core/simdrive/
simdrive_impl.rs

1//! Module containing implementations for [simdrive](crate::simdrive).
2
3use crate::cycle::{RustCycle, RustCycleCache};
4use crate::imports::*;
5use crate::params;
6use crate::simdrive::{RustSimDrive, RustSimDriveParams};
7use crate::utils::{arrmax, first_grtr, max, min};
8use crate::vehicle::*;
9
10pub struct RendezvousTrajectory {
11    pub found_trajectory: bool,
12    pub idx: usize,
13    pub n: usize,
14    pub full_brake_steps: usize,
15    pub jerk_m_per_s3: f64,
16    pub accel0_m_per_s2: f64,
17    pub accel_spread: f64,
18}
19
20pub struct CoastTrajectory {
21    pub found_trajectory: bool,
22    pub distance_to_stop_via_coast_m: f64,
23    pub start_idx: usize,
24    pub speeds_m_per_s: Option<Vec<f64>>,
25    pub distance_to_brake_m: Option<f64>,
26}
27
28impl RustSimDrive {
29    pub fn new(cyc: RustCycle, veh: RustVehicle) -> Self {
30        let hev_sim_count = 0;
31        let cyc0 = cyc.clone();
32        let sim_params = RustSimDriveParams::default();
33        let props = params::RustPhysicalProperties::default();
34        let i = 1; // 1 # initialize step counter for possible use outside sim_drive_walk()
35        let cyc_len = cyc.len();
36        let cur_max_fs_kw_out = Array::zeros(cyc_len);
37        let fc_trans_lim_kw = Array::zeros(cyc_len);
38        let cur_max_fc_kw_out = Array::zeros(cyc_len);
39        let ess_cap_lim_dischg_kw = Array::zeros(cyc_len);
40        let cur_ess_max_kw_out = Array::zeros(cyc_len);
41        let cur_max_avail_elec_kw = Array::zeros(cyc_len);
42        let ess_cap_lim_chg_kw = Array::zeros(cyc_len);
43        let cur_max_ess_chg_kw = Array::zeros(cyc_len);
44        let cur_max_elec_kw = Array::zeros(cyc_len);
45        let mc_elec_in_lim_kw = Array::zeros(cyc_len);
46        let mc_transi_lim_kw = Array::zeros(cyc_len);
47        let cur_max_mc_kw_out = Array::zeros(cyc_len);
48        let ess_lim_mc_regen_perc_kw = Array::zeros(cyc_len);
49        let cur_max_mech_mc_kw_in = Array::zeros(cyc_len);
50        let cur_max_trans_kw_out = Array::zeros(cyc_len);
51        let cyc_trac_kw_req = Array::zeros(cyc_len);
52        let cur_max_trac_kw = Array::zeros(cyc_len);
53        let spare_trac_kw = Array::zeros(cyc_len);
54        let cyc_whl_rad_per_sec = Array::zeros(cyc_len);
55        let cyc_tire_inertia_kw = Array::zeros(cyc_len);
56        let cyc_whl_kw_req = Array::zeros(cyc_len);
57        let regen_contrl_lim_kw_perc = Array::zeros(cyc_len);
58        let cyc_regen_brake_kw = Array::zeros(cyc_len);
59        let cyc_fric_brake_kw = Array::zeros(cyc_len);
60        let cyc_trans_kw_out_req = Array::zeros(cyc_len);
61        let cyc_met = Array::from_vec(vec![false; cyc_len]);
62        let trans_kw_out_ach = Array::zeros(cyc_len);
63        let trans_kw_in_ach = Array::zeros(cyc_len);
64        let cur_soc_target = Array::zeros(cyc_len);
65        let min_mc_kw_2help_fc = Array::zeros(cyc_len);
66        let mc_mech_kw_out_ach = Array::zeros(cyc_len);
67        let mc_elec_kw_in_ach = Array::zeros(cyc_len);
68        let aux_in_kw = Array::zeros(cyc_len);
69        let impose_coast = Array::from_vec(vec![false; cyc_len]);
70        let roadway_chg_kw_out_ach = Array::zeros(cyc_len);
71        let min_ess_kw_2help_fc = Array::zeros(cyc_len);
72        let ess_kw_out_ach = Array::zeros(cyc_len);
73        let fc_kw_out_ach = Array::zeros(cyc_len);
74        let fc_kw_out_ach_pct = Array::zeros(cyc_len);
75        let fc_kw_in_ach = Array::zeros(cyc_len);
76        let fs_kw_out_ach = Array::zeros(cyc_len);
77        let fs_kwh_out_ach = Array::zeros(cyc_len);
78        let ess_cur_kwh = Array::zeros(cyc_len);
79        let soc = Array::zeros(cyc_len);
80        let regen_buff_soc = Array::zeros(cyc_len);
81        let ess_regen_buff_dischg_kw = Array::zeros(cyc_len);
82        let max_ess_regen_buff_chg_kw = Array::zeros(cyc_len);
83        let ess_accel_buff_chg_kw = Array::zeros(cyc_len);
84        let accel_buff_soc = Array::zeros(cyc_len);
85        let max_ess_accell_buff_dischg_kw = Array::zeros(cyc_len);
86        let ess_accel_regen_dischg_kw = Array::zeros(cyc_len);
87        let mc_elec_in_kw_for_max_fc_eff = Array::zeros(cyc_len);
88        let elec_kw_req_4ae = Array::zeros(cyc_len);
89        let can_pwr_all_elec = Array::from_vec(vec![false; cyc_len]);
90        let desired_ess_kw_out_for_ae = Array::zeros(cyc_len);
91        let ess_ae_kw_out = Array::zeros(cyc_len);
92        let er_ae_kw_out = Array::zeros(cyc_len);
93        let ess_desired_kw_4fc_eff = Array::zeros(cyc_len);
94        let ess_kw_if_fc_req = Array::zeros(cyc_len);
95        let cur_max_mc_elec_kw_in = Array::zeros(cyc_len);
96        let fc_kw_gap_fr_eff = Array::zeros(cyc_len);
97        let er_kw_if_fc_req = Array::zeros(cyc_len);
98        let mc_elec_kw_in_if_fc_req = Array::zeros(cyc_len);
99        let mc_kw_if_fc_req = Array::zeros(cyc_len);
100        let fc_forced_on = Array::from_vec(vec![false; cyc_len]);
101        let fc_forced_state = Array::zeros(cyc_len);
102        let mc_mech_kw_4forced_fc = Array::zeros(cyc_len);
103        let fc_time_on = Array::zeros(cyc_len);
104        let prev_fc_time_on = Array::zeros(cyc_len);
105        let mps_ach = Array::zeros(cyc_len);
106        let mph_ach = Array::zeros(cyc_len);
107        let dist_m = Array::zeros(cyc_len);
108        let dist_mi = Array::zeros(cyc_len);
109        let high_acc_fc_on_tag = Array::from_vec(vec![false; cyc_len]);
110        let reached_buff = Array::from_vec(vec![false; cyc_len]);
111        let max_trac_mps = Array::zeros(cyc_len);
112        let add_kwh = Array::zeros(cyc_len);
113        let dod_cycs = Array::zeros(cyc_len);
114        let ess_perc_dead = Array::zeros(cyc_len);
115        let drag_kw = Array::zeros(cyc_len);
116        let ess_loss_kw = Array::zeros(cyc_len);
117        let accel_kw = Array::zeros(cyc_len);
118        let ascent_kw = Array::zeros(cyc_len);
119        let rr_kw = Array::zeros(cyc_len);
120        let cur_max_roadway_chg_kw = Array::zeros(cyc_len);
121        let trace_miss_iters = Array::zeros(cyc_len);
122        let newton_iters = Array::zeros(cyc_len);
123        let fuel_kj = 0.0;
124        let ess_dischg_kj = 0.0;
125        let energy_audit_error = 0.0;
126        let mpgge = 0.0;
127        let roadway_chg_kj = 0.0;
128        let battery_kwh_per_mi = 0.0;
129        let electric_kwh_per_mi = 0.0;
130        let ess2fuel_kwh = 0.0;
131        let drag_kj = 0.0;
132        let ascent_kj = 0.0;
133        let rr_kj = 0.0;
134        let brake_kj = 0.0;
135        let trans_kj = 0.0;
136        let mc_kj = 0.0;
137        let ess_eff_kj = 0.0;
138        let aux_kj = 0.0;
139        let fc_kj = 0.0;
140        let net_kj = 0.0;
141        let ke_kj = 0.0;
142        let trace_miss = false;
143        let trace_miss_dist_frac = 0.0;
144        let trace_miss_time_frac = 0.0;
145        let trace_miss_speed_mps = 0.0;
146        let coast_delay_index = Array::zeros(cyc_len);
147        let idm_target_speed_m_per_s = Array::zeros(cyc_len);
148        let cyc0_cache = RustCycleCache::new(&cyc0);
149        RustSimDrive {
150            hev_sim_count,
151            veh,
152            cyc,
153            cyc0,
154            sim_params,
155            props,
156            i, // 1 # initialize step counter for possible use outside sim_drive_walk()
157            cur_max_fs_kw_out,
158            fc_trans_lim_kw,
159            cur_max_fc_kw_out,
160            ess_cap_lim_dischg_kw,
161            cur_ess_max_kw_out,
162            cur_max_avail_elec_kw,
163            ess_cap_lim_chg_kw,
164            cur_max_ess_chg_kw,
165            cur_max_elec_kw,
166            mc_elec_in_lim_kw,
167            mc_transi_lim_kw,
168            cur_max_mc_kw_out,
169            ess_lim_mc_regen_perc_kw,
170            cur_max_mech_mc_kw_in,
171            cur_max_trans_kw_out,
172            cyc_trac_kw_req,
173            cur_max_trac_kw,
174            spare_trac_kw,
175            cyc_whl_rad_per_sec,
176            cyc_tire_inertia_kw,
177            cyc_whl_kw_req,
178            regen_contrl_lim_kw_perc,
179            cyc_regen_brake_kw,
180            cyc_fric_brake_kw,
181            cyc_trans_kw_out_req,
182            cyc_met,
183            trans_kw_out_ach,
184            trans_kw_in_ach,
185            cur_soc_target,
186            min_mc_kw_2help_fc,
187            mc_mech_kw_out_ach,
188            mc_elec_kw_in_ach,
189            aux_in_kw,
190            impose_coast,
191            roadway_chg_kw_out_ach,
192            min_ess_kw_2help_fc,
193            ess_kw_out_ach,
194            fc_kw_out_ach,
195            fc_kw_out_ach_pct,
196            fc_kw_in_ach,
197            fs_kw_out_ach,
198            fs_kwh_out_ach,
199            ess_cur_kwh,
200            soc,
201            regen_buff_soc,
202            ess_regen_buff_dischg_kw,
203            max_ess_regen_buff_chg_kw,
204            ess_accel_buff_chg_kw,
205            accel_buff_soc,
206            max_ess_accell_buff_dischg_kw,
207            ess_accel_regen_dischg_kw,
208            mc_elec_in_kw_for_max_fc_eff,
209            elec_kw_req_4ae,
210            can_pwr_all_elec,
211            desired_ess_kw_out_for_ae,
212            ess_ae_kw_out,
213            er_ae_kw_out,
214            ess_desired_kw_4fc_eff,
215            ess_kw_if_fc_req,
216            cur_max_mc_elec_kw_in,
217            fc_kw_gap_fr_eff,
218            er_kw_if_fc_req,
219            mc_elec_kw_in_if_fc_req,
220            mc_kw_if_fc_req,
221            fc_forced_on,
222            fc_forced_state,
223            mc_mech_kw_4forced_fc,
224            fc_time_on,
225            prev_fc_time_on,
226            mps_ach,
227            mph_ach,
228            dist_m,
229            dist_mi,
230            high_acc_fc_on_tag,
231            reached_buff,
232            max_trac_mps,
233            add_kwh,
234            dod_cycs,
235            ess_perc_dead,
236            drag_kw,
237            ess_loss_kw,
238            accel_kw,
239            ascent_kw,
240            rr_kw,
241            cur_max_roadway_chg_kw,
242            trace_miss_iters,
243            newton_iters,
244            fuel_kj,
245            ess_dischg_kj,
246            energy_audit_error,
247            mpgge,
248            roadway_chg_kj,
249            battery_kwh_per_mi,
250            electric_kwh_per_mi,
251            ess2fuel_kwh,
252            drag_kj,
253            ascent_kj,
254            rr_kj,
255            brake_kj,
256            trans_kj,
257            mc_kj,
258            ess_eff_kj,
259            aux_kj,
260            fc_kj,
261            net_kj,
262            ke_kj,
263            trace_miss,
264            trace_miss_dist_frac,
265            trace_miss_time_frac,
266            trace_miss_speed_mps,
267            orphaned: false,
268            coast_delay_index,
269            idm_target_speed_m_per_s,
270            cyc0_cache,
271            aux_in_kw_override: None,
272        }
273    }
274
275    /// Return length of time arrays
276    pub fn len(&self) -> usize {
277        self.cyc.time_s.len()
278    }
279
280    /// Is cycle empty?
281    pub fn is_empty(&self) -> bool {
282        self.cyc.time_s.is_empty()
283    }
284
285    // TODO: probably shouldn't be public...?
286    pub fn init_arrays(&mut self) {
287        self.i = 1; // initialize step counter for possible use outside sim_drive_walk()
288        let cyc_len = self.cyc0.time_s.len(); //get_len() as usize;
289
290        // Component Limits -- calculated dynamically
291        self.cur_max_fs_kw_out = Array::zeros(cyc_len);
292        self.fc_trans_lim_kw = Array::zeros(cyc_len);
293        self.cur_max_fc_kw_out = Array::zeros(cyc_len);
294        self.ess_cap_lim_dischg_kw = Array::zeros(cyc_len);
295        self.cur_ess_max_kw_out = Array::zeros(cyc_len);
296        self.cur_max_avail_elec_kw = Array::zeros(cyc_len);
297        self.ess_cap_lim_chg_kw = Array::zeros(cyc_len);
298        self.cur_max_ess_chg_kw = Array::zeros(cyc_len);
299        self.cur_max_elec_kw = Array::zeros(cyc_len);
300        self.mc_elec_in_lim_kw = Array::zeros(cyc_len);
301        self.mc_transi_lim_kw = Array::zeros(cyc_len);
302        self.cur_max_mc_kw_out = Array::zeros(cyc_len);
303        self.ess_lim_mc_regen_perc_kw = Array::zeros(cyc_len);
304        self.cur_max_mech_mc_kw_in = Array::zeros(cyc_len);
305        self.cur_max_trans_kw_out = Array::zeros(cyc_len);
306
307        // Drive Train
308        self.cyc_trac_kw_req = Array::zeros(cyc_len);
309        self.cur_max_trac_kw = Array::zeros(cyc_len);
310        self.spare_trac_kw = Array::zeros(cyc_len);
311        self.cyc_whl_rad_per_sec = Array::zeros(cyc_len);
312        self.cyc_tire_inertia_kw = Array::zeros(cyc_len);
313        self.cyc_whl_kw_req = Array::zeros(cyc_len);
314        self.regen_contrl_lim_kw_perc = Array::zeros(cyc_len);
315        self.cyc_regen_brake_kw = Array::zeros(cyc_len);
316        self.cyc_fric_brake_kw = Array::zeros(cyc_len);
317        self.cyc_trans_kw_out_req = Array::zeros(cyc_len);
318        self.cyc_met = Array::from_vec(vec![false; cyc_len]);
319        self.trans_kw_out_ach = Array::zeros(cyc_len);
320        self.trans_kw_in_ach = Array::zeros(cyc_len);
321        self.cur_soc_target = Array::zeros(cyc_len);
322        self.min_mc_kw_2help_fc = Array::zeros(cyc_len);
323        self.mc_mech_kw_out_ach = Array::zeros(cyc_len);
324        self.mc_elec_kw_in_ach = Array::zeros(cyc_len);
325        self.aux_in_kw = Array::zeros(cyc_len);
326        self.roadway_chg_kw_out_ach = Array::zeros(cyc_len);
327        self.min_ess_kw_2help_fc = Array::zeros(cyc_len);
328        self.ess_kw_out_ach = Array::zeros(cyc_len);
329        self.fc_kw_out_ach = Array::zeros(cyc_len);
330        self.fc_kw_out_ach_pct = Array::zeros(cyc_len);
331        self.fc_kw_in_ach = Array::zeros(cyc_len);
332        self.fs_kw_out_ach = Array::zeros(cyc_len);
333        self.fs_kwh_out_ach = Array::zeros(cyc_len);
334        self.ess_cur_kwh = Array::zeros(cyc_len);
335        self.soc = Array::zeros(cyc_len);
336
337        // Vehicle Attributes, Control Variables
338        self.regen_buff_soc = Array::zeros(cyc_len);
339        self.ess_regen_buff_dischg_kw = Array::zeros(cyc_len);
340        self.max_ess_regen_buff_chg_kw = Array::zeros(cyc_len);
341        self.ess_accel_buff_chg_kw = Array::zeros(cyc_len);
342        self.accel_buff_soc = Array::zeros(cyc_len);
343        self.max_ess_accell_buff_dischg_kw = Array::zeros(cyc_len);
344        self.ess_accel_regen_dischg_kw = Array::zeros(cyc_len);
345        self.mc_elec_in_kw_for_max_fc_eff = Array::zeros(cyc_len);
346        self.elec_kw_req_4ae = Array::zeros(cyc_len);
347        self.can_pwr_all_elec = Array::from_vec(vec![false; cyc_len]);
348        self.desired_ess_kw_out_for_ae = Array::zeros(cyc_len);
349        self.ess_ae_kw_out = Array::zeros(cyc_len);
350        self.er_ae_kw_out = Array::zeros(cyc_len);
351        self.ess_desired_kw_4fc_eff = Array::zeros(cyc_len);
352        self.ess_kw_if_fc_req = Array::zeros(cyc_len);
353        self.cur_max_mc_elec_kw_in = Array::zeros(cyc_len);
354        self.fc_kw_gap_fr_eff = Array::zeros(cyc_len);
355        self.er_kw_if_fc_req = Array::zeros(cyc_len);
356        self.mc_elec_kw_in_if_fc_req = Array::zeros(cyc_len);
357        self.mc_kw_if_fc_req = Array::zeros(cyc_len);
358        self.fc_forced_on = Array::from_vec(vec![false; cyc_len]);
359        self.fc_forced_state = Array::zeros(cyc_len);
360        self.mc_mech_kw_4forced_fc = Array::zeros(cyc_len);
361        self.fc_time_on = Array::zeros(cyc_len);
362        self.prev_fc_time_on = Array::zeros(cyc_len);
363
364        // Additional Variables
365        self.mps_ach = Array::zeros(cyc_len);
366        self.mph_ach = Array::zeros(cyc_len);
367        self.dist_m = Array::zeros(cyc_len);
368        self.dist_mi = Array::zeros(cyc_len);
369        self.high_acc_fc_on_tag = Array::from_vec(vec![false; cyc_len]);
370        self.reached_buff = Array::from_vec(vec![false; cyc_len]);
371        self.max_trac_mps = Array::zeros(cyc_len);
372        self.add_kwh = Array::zeros(cyc_len);
373        self.dod_cycs = Array::zeros(cyc_len);
374        self.ess_perc_dead = Array::zeros(cyc_len);
375        self.drag_kw = Array::zeros(cyc_len);
376        self.ess_loss_kw = Array::zeros(cyc_len);
377        self.accel_kw = Array::zeros(cyc_len);
378        self.ascent_kw = Array::zeros(cyc_len);
379        self.rr_kw = Array::zeros(cyc_len);
380        self.cur_max_roadway_chg_kw = Array::zeros(cyc_len);
381        self.trace_miss_iters = Array::zeros(cyc_len);
382        self.newton_iters = Array::zeros(cyc_len);
383        self.coast_delay_index = Array::zeros(cyc_len);
384        self.impose_coast = Array::from_vec(vec![false; cyc_len]);
385        self.idm_target_speed_m_per_s = Array::zeros(cyc_len);
386        self.cyc0_cache = self.cyc0.build_cache();
387    }
388
389    /// Initialize and run sim_drive_walk as appropriate for vehicle attribute vehPtType.
390    /// Arguments
391    /// ------------
392    /// init_soc: initial SOC for electrified vehicles.  
393    /// aux_in_kw: aux_in_kw override.  Array of same length as cyc.time_s.  
394    ///     Default of None causes veh.aux_kw to be used.
395    pub fn sim_drive(
396        &mut self,
397        init_soc: Option<f64>,
398        aux_in_kw_override: Option<Array1<f64>>,
399    ) -> anyhow::Result<()> {
400        self.hev_sim_count = 0;
401
402        let init_soc = match init_soc {
403            Some(x) => x,
404            None => {
405                if self.veh.veh_pt_type == CONV {
406                    // If no EV / Hybrid components, no SOC considerations.
407                    (self.veh.max_soc + self.veh.min_soc) / 2.0
408                } else if self.veh.veh_pt_type == HEV {
409                    // ####################################
410                    // ### Charge Balancing Vehicle SOC ###
411                    // ####################################
412                    // Charge balancing SOC for HEV vehicle types. Iterating init_soc and comparing to final SOC.
413                    // Iterating until tolerance met or 30 attempts made.
414                    let mut init_soc = (self.veh.max_soc + self.veh.min_soc) / 2.0;
415                    let mut ess_2fuel_kwh = 1.0;
416                    while ess_2fuel_kwh > self.veh.ess_to_fuel_ok_error
417                        && self.hev_sim_count < self.sim_params.sim_count_max
418                    {
419                        self.hev_sim_count += 1;
420                        self.walk(init_soc, aux_in_kw_override.clone())?;
421                        let fuel_kj = (&self.fs_kw_out_ach * self.cyc.dt_s()).sum();
422                        let roadway_chg_kj = (&self.roadway_chg_kw_out_ach * self.cyc.dt_s()).sum();
423                        if (fuel_kj + roadway_chg_kj) > 0.0 {
424                            ess_2fuel_kwh = ((self.soc[0]
425                                - self
426                                    .soc
427                                    .last()
428                                    .ok_or_else(|| anyhow!(format_dbg!(self.soc)))?)
429                                * self.veh.ess_max_kwh
430                                * 3.6e3
431                                / (fuel_kj + roadway_chg_kj))
432                                .abs();
433                        } else {
434                            ess_2fuel_kwh = 0.0;
435                        }
436                        init_soc = min(
437                            self.veh.max_soc,
438                            max(
439                                self.veh.min_soc,
440                                *self
441                                    .soc
442                                    .last()
443                                    .ok_or_else(|| anyhow!(format_dbg!(self.soc)))?,
444                            ),
445                        );
446                    }
447                    init_soc
448                } else if self.veh.veh_pt_type == PHEV || self.veh.veh_pt_type == BEV {
449                    // If EV, initializing initial SOC to maximum SOC.
450                    self.veh.max_soc
451                } else {
452                    bail!("Failed to properly initialize SOC.");
453                }
454            }
455        };
456
457        self.walk(init_soc, aux_in_kw_override)?;
458
459        self.set_post_scalars()
460    }
461
462    pub fn sim_drive_accel(
463        &mut self,
464        init_soc: Option<f64>,
465        aux_in_kw_override: Option<Array1<f64>>,
466    ) -> anyhow::Result<()> {
467        // Initialize and run sim_drive_walk as appropriate for vehicle attribute vehPtType.
468        let init_soc_auto = match self.veh.veh_pt_type.as_str() {
469            // If no EV / Hybrid components, no SOC considerations.
470            CONV => (self.veh.max_soc + self.veh.min_soc) / 2.0,
471            HEV => (self.veh.max_soc + self.veh.min_soc) / 2.0,
472            // If EV, initializing initial SOC to maximum SOC.
473            _ => self.veh.max_soc,
474        };
475        let init_soc = init_soc.unwrap_or(init_soc_auto);
476        self.walk(init_soc, aux_in_kw_override)?;
477        self.set_post_scalars()
478    }
479
480    /// Receives second-by-second cycle information, vehicle properties,
481    /// and an initial state of charge and runs sim_drive_step to perform a
482    /// backward facing powertrain simulation. Method `sim_drive` runs this
483    /// iteratively to achieve correct SOC initial and final conditions, as
484    /// needed.
485    ///
486    /// Arguments
487    /// ------------
488    /// init_soc: initial battery state-of-charge (SOC) for electrified vehicles
489    /// aux_in_kw: (Optional) aux_in_kw override.  Array of same length as cyc.time_s.
490    ///         None causes veh.aux_kw to be used.
491    pub fn walk(
492        &mut self,
493        init_soc: f64,
494        aux_in_kw_override: Option<Array1<f64>>,
495    ) -> anyhow::Result<()> {
496        self.init_for_step(init_soc, aux_in_kw_override)?;
497        while self.i < self.cyc.len() {
498            self.step()?;
499        }
500
501        if self.cyc.dt_s().iter().any(|&dt| dt > 5.0) {
502            if self.sim_params.missed_trace_correction {
503                #[cfg(feature = "logging")]
504                log::info!(
505                    "Max time dilation factor = {:.3}",
506                    (self.cyc.dt_s() / self.cyc0.dt_s()).max()?
507                );
508            }
509            #[cfg(feature = "logging")]
510            log::warn!(
511                "Large time steps affect accuracy significantly (max time step = {:.3})",
512                self.cyc.dt_s().max()?
513            );
514        }
515        Ok(())
516    }
517
518    /// This is a specialty method which should be called prior to using
519    /// sim_drive_step in a loop.
520    /// Arguments
521    /// ------------
522    /// init_soc: initial battery state-of-charge (SOC) for electrified vehicles
523    /// aux_in_kw: aux_in_kw override.  Array of same length as cyc.time_s.  
524    ///         Default of None causes veh.aux_kw to be used.
525    pub fn init_for_step(
526        &mut self,
527        init_soc: f64,
528        aux_in_kw_override: Option<Array1<f64>>,
529    ) -> anyhow::Result<()> {
530        ensure!(
531            self.veh.veh_pt_type == CONV
532                || (self.veh.min_soc..=self.veh.max_soc).contains(&init_soc),
533            "provided init_soc={} is outside range min_soc={} to max_soc={}",
534            init_soc,
535            self.veh.min_soc,
536            self.veh.max_soc
537        );
538
539        self.init_arrays();
540
541        // set `self.aux_in_kw_override` if it has been provided and not previously set
542        if let Some(arr) = aux_in_kw_override {
543            match self.aux_in_kw_override {
544                Some(_) => {}
545                None => self.aux_in_kw = arr,
546            }
547        }
548
549        self.cyc_met[0] = true;
550        self.cur_soc_target[0] = self.veh.max_soc;
551        self.ess_cur_kwh[0] = init_soc * self.veh.ess_max_kwh;
552        self.soc[0] = init_soc;
553        self.mps_ach[0] = self.cyc0.mps[0];
554        self.mph_ach[0] = self.cyc0.mph_at_i(0);
555
556        if self.sim_params.missed_trace_correction
557            || self.sim_params.idm_allow
558            || self.sim_params.coast_allow
559        {
560            self.cyc = self.cyc0.clone(); // reset the cycle in case it has been manipulated
561        }
562        self.i = 1; // time step counter
563        Ok(())
564    }
565
566    /// Step through 1 time step.
567    pub fn step(&mut self) -> anyhow::Result<()> {
568        if self.sim_params.idm_allow {
569            self.idm_target_speed_m_per_s[self.i] =
570                match &self.sim_params.idm_v_desired_in_m_per_s_by_distance_m {
571                    Some(vtgt_by_dist) => {
572                        let mut found_v_target = vtgt_by_dist[0].1;
573                        let current_d = self.cyc.dist_m().slice(s![0..self.i]).sum();
574                        for (d, v_target) in vtgt_by_dist {
575                            if &current_d >= d {
576                                found_v_target = *v_target;
577                            } else {
578                                break;
579                            }
580                        }
581                        found_v_target
582                    }
583                    None => self.sim_params.idm_v_desired_m_per_s,
584                };
585            self.set_speed_for_target_gap(self.i);
586        }
587        if self.sim_params.coast_allow {
588            self.set_coast_speed(self.i)?;
589        }
590        self.solve_step(self.i)?;
591
592        if self.sim_params.missed_trace_correction
593            && (self.cyc0.dist_m().slice(s![0..self.i]).sum() > 0.0)
594        {
595            self.set_time_dilation(self.i)?;
596        }
597        // TODO: shouldn't the below code always set cyc? Whether coasting or not?
598        if self.sim_params.coast_allow || self.sim_params.idm_allow {
599            self.cyc.mps[self.i] = self.mps_ach[self.i];
600            self.cyc.grade[self.i] = self.lookup_grade_for_step(self.i, None)?;
601        }
602
603        self.i += 1; // increment time step counter
604        Ok(())
605    }
606
607    /// Perform all the calculations to solve 1 time step.
608    pub fn solve_step(&mut self, i: usize) -> anyhow::Result<()> {
609        self.set_misc_calcs(i)?;
610        self.set_comp_lims(i)?;
611        self.set_power_calcs(i)?;
612        self.set_ach_speed(i)?;
613        self.set_hybrid_cont_calcs(i)?;
614        self.set_fc_forced_state_rust(i)?;
615        self.set_hybrid_cont_decisions(i)?;
616        self.set_fc_power(i)?;
617        Ok(())
618    }
619
620    /// Sets misc. calculations at time step 'i'
621    /// Arguments:
622    /// ----------
623    /// i: index of time step
624    pub fn set_misc_calcs(&mut self, i: usize) -> anyhow::Result<()> {
625        // if cycle iteration is used, auxInKw must be re-zeroed to trigger the below if statement
626        // TODO: this is probably computationally expensive and was probably a workaround for numba
627        // figure out a way to not need this
628        if self.aux_in_kw.slice(s![i..]).iter().all(|&x| x == 0.0) {
629            // if all elements after i-1 are zero, trigger default behavior; otherwise, use override value
630            self.aux_in_kw[i] = if self.veh.no_elec_aux {
631                self.veh.aux_kw / self.veh.alt_eff
632            } else {
633                self.veh.aux_kw
634            };
635        }
636        // Is SOC below min threshold?
637        self.reached_buff[i] = self.soc[i - 1] >= (self.veh.min_soc + self.veh.perc_high_acc_buf);
638
639        // Does the engine need to be on for low SOC or high acceleration
640        self.high_acc_fc_on_tag[i] = self.soc[i - 1] < self.veh.min_soc
641            || (self.high_acc_fc_on_tag[i - 1] && !(self.reached_buff[i]));
642        self.max_trac_mps[i] =
643            self.mps_ach[i - 1] + (self.veh.max_trac_mps2 * self.cyc.dt_s_at_i(i));
644        Ok(())
645    }
646
647    /// Sets component limits for time step 'i'
648    /// Arguments
649    /// ------------
650    /// i: index of time step
651    /// initSoc: initial SOC for electrified vehicles
652    pub fn set_comp_lims(&mut self, i: usize) -> anyhow::Result<()> {
653        // max fuel storage power output
654        self.cur_max_fs_kw_out[i] = min(
655            self.veh.fs_max_kw,
656            self.fs_kw_out_ach[i - 1]
657                + self.veh.fs_max_kw / self.veh.fs_secs_to_peak_pwr * self.cyc.dt_s_at_i(i),
658        );
659        // maximum fuel storage power output rate of change
660        self.fc_trans_lim_kw[i] = self.fc_kw_out_ach[i - 1]
661            + (self.veh.fc_max_kw / self.veh.fc_sec_to_peak_pwr * self.cyc.dt_s_at_i(i));
662
663        self.cur_max_fc_kw_out[i] = min(self.veh.fc_max_kw, self.fc_trans_lim_kw[i]);
664
665        self.ess_cap_lim_dischg_kw[i] =
666            if self.veh.ess_max_kwh == 0.0 || self.soc[i - 1] < self.veh.min_soc {
667                0.0
668            } else {
669                self.veh.ess_max_kwh
670                    * self.veh.ess_round_trip_eff.sqrt()
671                    * 3.6e3
672                    * (self.soc[i - 1] - self.veh.min_soc)
673                    / self.cyc.dt_s_at_i(i)
674            };
675        self.cur_ess_max_kw_out[i] = min(self.veh.ess_max_kw, self.ess_cap_lim_dischg_kw[i]);
676
677        self.ess_cap_lim_chg_kw[i] = if self.veh.ess_max_kwh == 0.0 || self.veh.ess_max_kw == 0.0 {
678            0.0
679        } else {
680            max(
681                (self.veh.max_soc - self.soc[i - 1]) * self.veh.ess_max_kwh
682                    / self.veh.ess_round_trip_eff.sqrt()
683                    / (self.cyc.dt_s_at_i(i) / 3.6e3),
684                0.0,
685            )
686        };
687
688        self.cur_max_ess_chg_kw[i] = min(self.ess_cap_lim_chg_kw[i], self.veh.ess_max_kw);
689
690        // Current maximum electrical power that can go toward propulsion, not including motor limitations
691        self.cur_max_elec_kw[i] = if self.veh.fc_eff_type == H2FC {
692            self.cur_max_fc_kw_out[i] + self.cur_max_roadway_chg_kw[i] + self.cur_ess_max_kw_out[i]
693                - self.aux_in_kw[i]
694        } else {
695            self.cur_max_roadway_chg_kw[i] + self.cur_ess_max_kw_out[i] - self.aux_in_kw[i]
696        };
697
698        // Current maximum electrical power that can go toward propulsion, including motor limitations
699        self.cur_max_avail_elec_kw[i] = min(self.cur_max_elec_kw[i], self.veh.mc_max_elec_in_kw);
700
701        self.mc_elec_in_lim_kw[i] = if self.cur_max_elec_kw[i] > 0.0 {
702            // limit power going into e-machine controller to
703            if self.cur_max_avail_elec_kw[i] == arrmax(&self.veh.mc_kw_in_array) {
704                min(
705                    *self
706                        .veh
707                        .mc_kw_out_array
708                        .last()
709                        .ok_or_else(|| anyhow!(format_dbg!(self.veh.mc_kw_out_array)))?,
710                    self.veh.mc_max_kw,
711                )
712            } else {
713                min(
714                    self.veh.mc_kw_out_array[first_grtr(
715                        &self.veh.mc_kw_in_array,
716                        min(
717                            arrmax(&self.veh.mc_kw_in_array) * 0.9999,
718                            self.cur_max_avail_elec_kw[i],
719                        ),
720                    )
721                    .ok_or_else(|| anyhow!(format_dbg!("`first_grtr` returned `None`")))?
722                        - 1_usize],
723                    self.veh.mc_max_kw,
724                )
725            }
726        } else {
727            0.0
728        };
729
730        // Motor transient power limit
731        self.mc_transi_lim_kw[i] = self.mc_mech_kw_out_ach[i - 1].abs()
732            + self.veh.mc_max_kw / self.veh.mc_sec_to_peak_pwr * self.cyc.dt_s_at_i(i);
733
734        self.cur_max_mc_kw_out[i] = max(
735            min(
736                min(self.mc_elec_in_lim_kw[i], self.mc_transi_lim_kw[i]),
737                if self.veh.stop_start { 0.0 } else { 1.0 } * self.veh.mc_max_kw,
738            ),
739            -self.veh.mc_max_kw,
740        );
741
742        self.cur_max_mc_elec_kw_in[i] = if self.cur_max_mc_kw_out[i] == 0.0 {
743            0.0
744        } else if self.cur_max_mc_kw_out[i] == self.veh.mc_max_kw {
745            self.cur_max_mc_kw_out[i]
746                / self
747                    .veh
748                    .mc_full_eff_array
749                    .last()
750                    .ok_or_else(|| anyhow!(format_dbg!(self.veh.mc_full_eff_array)))?
751        } else {
752            self.cur_max_mc_kw_out[i]
753                / self.veh.mc_full_eff_array[cmp::max(
754                    1,
755                    first_grtr(
756                        &self.veh.mc_kw_out_array,
757                        min(self.veh.mc_max_kw * 0.9999, self.cur_max_mc_kw_out[i]),
758                    )
759                    .ok_or_else(|| anyhow!(format_dbg!("`first_grtr` returned `None`")))?
760                        - 1,
761                )]
762        };
763
764        self.ess_lim_mc_regen_perc_kw[i] = if self.veh.mc_max_kw == 0.0 {
765            0.0
766        } else {
767            min(
768                (self.cur_max_ess_chg_kw[i] + self.aux_in_kw[i]) / self.veh.mc_max_kw,
769                1.0,
770            )
771        };
772        self.cur_max_mech_mc_kw_in[i] = if self.cur_max_ess_chg_kw[i] == 0.0 {
773            0.0
774        } else if self.veh.mc_max_kw == self.cur_max_ess_chg_kw[i] - self.cur_max_roadway_chg_kw[i]
775        {
776            min(
777                self.veh.mc_max_kw,
778                // this unwrap has already been checked above
779                self.cur_max_ess_chg_kw[i] / self.veh.mc_full_eff_array.last().unwrap(),
780            )
781        } else {
782            min(
783                self.veh.mc_max_kw,
784                self.cur_max_ess_chg_kw[i]
785                    / self.veh.mc_full_eff_array[cmp::max(
786                        1,
787                        first_grtr(
788                            &self.veh.mc_kw_out_array,
789                            min(
790                                self.veh.mc_max_kw * 0.9999,
791                                self.cur_max_ess_chg_kw[i] - self.cur_max_roadway_chg_kw[i],
792                            ),
793                        )
794                        .unwrap_or(0)
795                            - 1,
796                    )],
797            )
798        };
799
800        self.cur_max_trac_kw[i] = self.veh.wheel_coef_of_fric
801            * self.veh.drive_axle_weight_frac
802            * self.veh.veh_kg
803            * self.props.a_grav_mps2
804            / (1.0 + self.veh.veh_cg_m * self.veh.wheel_coef_of_fric / self.veh.wheel_base_m)
805            / 1e3
806            * self.max_trac_mps[i];
807
808        self.cur_max_trans_kw_out[i] = if self.veh.fc_eff_type == H2FC {
809            if self.veh.no_elec_sys || self.veh.no_elec_aux || self.high_acc_fc_on_tag[i] {
810                min(
811                    (self.cur_max_mc_kw_out[i] - self.aux_in_kw[i]) * self.veh.trans_eff,
812                    self.cur_max_trac_kw[i] / self.veh.trans_eff,
813                )
814            } else {
815                min(
816                    (self.cur_max_mc_kw_out[i] - min(self.cur_max_elec_kw[i], 0.0))
817                        * self.veh.trans_eff,
818                    self.cur_max_trac_kw[i] / self.veh.trans_eff,
819                )
820            }
821        } else if self.veh.no_elec_sys || self.veh.no_elec_aux || self.high_acc_fc_on_tag[i] {
822            min(
823                (self.cur_max_mc_kw_out[i] + self.cur_max_fc_kw_out[i] - self.aux_in_kw[i])
824                    * self.veh.trans_eff,
825                self.cur_max_trac_kw[i] / self.veh.trans_eff,
826            )
827        } else {
828            min(
829                (self.cur_max_mc_kw_out[i] + self.cur_max_fc_kw_out[i]
830                    - min(self.cur_max_elec_kw[i], 0.0))
831                    * self.veh.trans_eff,
832                self.cur_max_trac_kw[i] / self.veh.trans_eff,
833            )
834        };
835        if self.impose_coast[i] {
836            self.cur_max_trans_kw_out[i] = 0.0
837        };
838        Ok(())
839    }
840
841    /// Calculate power requirements to meet cycle and determine if
842    /// cycle can be met.
843    /// Arguments
844    /// ------------
845    /// i: index of time step
846    pub fn set_power_calcs(&mut self, i: usize) -> anyhow::Result<()> {
847        let mps_ach = if self.newton_iters[i] > 0u32 {
848            self.mps_ach[i]
849        } else {
850            self.cyc.mps[i]
851        };
852
853        let grade = self.lookup_grade_for_step(i, Some(mps_ach))?;
854
855        self.drag_kw[i] = 0.5
856            * self.props.air_density_kg_per_m3
857            * self.veh.drag_coef
858            * self.veh.frontal_area_m2
859            * ((self.mps_ach[i - 1] + mps_ach) / 2.0).powi(3)
860            / 1e3;
861        self.accel_kw[i] = self.veh.veh_kg / (2.0 * self.cyc.dt_s_at_i(i))
862            * (mps_ach.powi(2) - self.mps_ach[i - 1].powi(2))
863            / 1e3;
864        self.ascent_kw[i] = self.props.a_grav_mps2
865            * grade.atan().sin()
866            * self.veh.veh_kg
867            * (self.mps_ach[i - 1] + mps_ach)
868            / 2.0
869            / 1e3;
870        self.cyc_trac_kw_req[i] = self.drag_kw[i] + self.accel_kw[i] + self.ascent_kw[i];
871        self.spare_trac_kw[i] = self.cur_max_trac_kw[i] - self.cyc_trac_kw_req[i];
872        self.rr_kw[i] = self.veh.veh_kg
873            * self.props.a_grav_mps2
874            * self.veh.wheel_rr_coef
875            * grade.atan().cos()
876            * (self.mps_ach[i - 1] + mps_ach)
877            / 2.0
878            / 1e3;
879        self.cyc_whl_rad_per_sec[i] = mps_ach / self.veh.wheel_radius_m;
880        self.cyc_tire_inertia_kw[i] = (0.5
881            * self.veh.wheel_inertia_kg_m2
882            * self.veh.num_wheels
883            * (self.cyc_whl_rad_per_sec[i].powi(2)
884                - (self.mps_ach[i - 1] / self.veh.wheel_radius_m).powi(2))
885            / self.cyc.dt_s_at_i(i))
886            / 1e3;
887
888        self.cyc_whl_kw_req[i] =
889            self.cyc_trac_kw_req[i] + self.rr_kw[i] + self.cyc_tire_inertia_kw[i];
890        self.regen_contrl_lim_kw_perc[i] = self.veh.max_regen
891            / (1.0
892                + self.veh.regen_a
893                    * (-self.veh.regen_b
894                        * ((self.cyc.mph_at_i(i) + self.mps_ach[i - 1] * params::MPH_PER_MPS)
895                            / 2.0
896                            + 1.0))
897                        .exp());
898        self.cyc_regen_brake_kw[i] = max(
899            min(
900                self.cur_max_mech_mc_kw_in[i] * self.veh.trans_eff,
901                self.regen_contrl_lim_kw_perc[i] * -self.cyc_whl_kw_req[i],
902            ),
903            0.0,
904        );
905        self.cyc_fric_brake_kw[i] = -min(self.cyc_regen_brake_kw[i] + self.cyc_whl_kw_req[i], 0.0);
906        self.cyc_trans_kw_out_req[i] = self.cyc_whl_kw_req[i] + self.cyc_fric_brake_kw[i];
907
908        if self.cyc_trans_kw_out_req[i] <= self.cur_max_trans_kw_out[i] {
909            self.cyc_met[i] = true;
910            self.trans_kw_out_ach[i] = self.cyc_trans_kw_out_req[i];
911        } else {
912            self.cyc_met[i] = false;
913            self.trans_kw_out_ach[i] = self.cur_max_trans_kw_out[i];
914        }
915
916        self.trans_kw_in_ach[i] = if self.trans_kw_out_ach[i] > 0.0 {
917            self.trans_kw_out_ach[i] / self.veh.trans_eff
918        } else {
919            self.trans_kw_out_ach[i] * self.veh.trans_eff
920        };
921
922        self.min_mc_kw_2help_fc[i] = if self.cyc_met[i] {
923            if self.veh.fc_eff_type == H2FC {
924                max(self.trans_kw_in_ach[i], -self.cur_max_mech_mc_kw_in[i])
925            } else {
926                max(
927                    self.trans_kw_in_ach[i] - self.cur_max_fc_kw_out[i],
928                    -self.cur_max_mech_mc_kw_in[i],
929                )
930            }
931        } else {
932            max(self.cur_max_mc_kw_out[i], -self.cur_max_mech_mc_kw_in[i])
933        };
934        Ok(())
935    }
936
937    // Calculate actual speed achieved if vehicle hardware cannot achieve trace speed.
938    // Arguments
939    // ------------
940    // i: index of time step
941    pub fn set_ach_speed(&mut self, i: usize) -> anyhow::Result<()> {
942        // Cycle is met
943        if self.cyc_met[i] {
944            self.mps_ach[i] = self.cyc.mps[i];
945        }
946        //Cycle is not met
947        else {
948            let mut grade_estimate = self.estimate_grade_for_step(i)?;
949            let mut grade: f64;
950            let grade_tol = 1e-4;
951            let mut grade_diff = grade_tol + 1.0;
952            let max_grade_iter = 3;
953            let mut grade_iter = 0;
954            while grade_diff > grade_tol && grade_iter < max_grade_iter {
955                grade_iter += 1;
956                grade = grade_estimate;
957
958                let drag3 = 1.0 / 16.0
959                    * self.props.air_density_kg_per_m3
960                    * self.veh.drag_coef
961                    * self.veh.frontal_area_m2;
962                let accel2 = 0.5 * self.veh.veh_kg / self.cyc.dt_s_at_i(i);
963                let drag2 = 3.0 / 16.0
964                    * self.props.air_density_kg_per_m3
965                    * self.veh.drag_coef
966                    * self.veh.frontal_area_m2
967                    * self.mps_ach[i - 1];
968                let wheel2 = 0.5 * self.veh.wheel_inertia_kg_m2 * self.veh.num_wheels
969                    / (self.cyc.dt_s_at_i(i) * self.veh.wheel_radius_m.powi(2));
970                let drag1 = 3.0 / 16.0
971                    * self.props.air_density_kg_per_m3
972                    * self.veh.drag_coef
973                    * self.veh.frontal_area_m2
974                    * self.mps_ach[i - 1].powi(2);
975                let roll1 = 0.5
976                    * self.veh.veh_kg
977                    * self.props.a_grav_mps2
978                    * self.veh.wheel_rr_coef
979                    * grade.atan().cos();
980                let ascent1 = 0.5 * self.props.a_grav_mps2 * grade.atan().sin() * self.veh.veh_kg;
981                let accel0 =
982                    -0.5 * self.veh.veh_kg * self.mps_ach[i - 1].powi(2) / self.cyc.dt_s_at_i(i);
983                let drag0 = 1.0 / 16.0
984                    * self.props.air_density_kg_per_m3
985                    * self.veh.drag_coef
986                    * self.veh.frontal_area_m2
987                    * self.mps_ach[i - 1].powi(3);
988                let roll0 = 0.5
989                    * self.veh.veh_kg
990                    * self.props.a_grav_mps2
991                    * self.veh.wheel_rr_coef
992                    * grade.atan().cos()
993                    * self.mps_ach[i - 1];
994                let ascent0 = 0.5
995                    * self.props.a_grav_mps2
996                    * grade.atan().sin()
997                    * self.veh.veh_kg
998                    * self.mps_ach[i - 1];
999                let wheel0 = -0.5
1000                    * self.veh.wheel_inertia_kg_m2
1001                    * self.veh.num_wheels
1002                    * self.mps_ach[i - 1].powi(2)
1003                    / (self.cyc.dt_s_at_i(i) * self.veh.wheel_radius_m.powi(2));
1004
1005                let t3 = drag3 / 1e3;
1006                let t2 = (accel2 + drag2 + wheel2) / 1e3;
1007                let t1 = (drag1 + roll1 + ascent1) / 1e3;
1008                let t0 = (accel0 + drag0 + roll0 + ascent0 + wheel0) / 1e3
1009                    - self.cur_max_trans_kw_out[i];
1010
1011                // initial guess
1012                let speed_guess = max(1.0, self.mps_ach[i - 1]);
1013                // stop criteria
1014                let max_iter = self.sim_params.newton_max_iter;
1015                let xtol = self.sim_params.newton_xtol;
1016                // solver gain
1017                let g = self.sim_params.newton_gain;
1018                let pwr_err_fn = |speed_guess: f64| -> f64 {
1019                    t3 * speed_guess.powi(3) + t2 * speed_guess.powi(2) + t1 * speed_guess + t0
1020                };
1021                let pwr_err_per_speed_guess_fn = |speed_guess: f64| -> f64 {
1022                    3.0 * t3 * speed_guess.powi(2) + 2.0 * t2 * speed_guess + t1
1023                };
1024                let pwr_err = pwr_err_fn(speed_guess);
1025                let pwr_err_per_speed_guess = pwr_err_per_speed_guess_fn(speed_guess);
1026                let new_speed_guess = pwr_err - speed_guess * pwr_err_per_speed_guess;
1027                let mut speed_guesses = vec![speed_guess];
1028                let mut pwr_errs = vec![pwr_err];
1029                let mut d_pwr_err_per_d_speed_guesses = vec![pwr_err_per_speed_guess];
1030                let mut new_speed_guesses = vec![new_speed_guess];
1031                // speed achieved iteration counter
1032                let mut spd_ach_i = 1;
1033                let mut converged = false;
1034                while spd_ach_i < max_iter && !converged {
1035                    let speed_guess = speed_guesses
1036                        .iter()
1037                        .last()
1038                        .ok_or_else(|| anyhow!("{}", format_dbg!()))?
1039                        * (1.0 - g)
1040                        - g * new_speed_guesses
1041                            .iter()
1042                            .last()
1043                            .ok_or_else(|| anyhow!("{}", format_dbg!()))?
1044                            / d_pwr_err_per_d_speed_guesses[speed_guesses.len() - 1];
1045                    let pwr_err = pwr_err_fn(speed_guess);
1046                    let pwr_err_per_speed_guess = pwr_err_per_speed_guess_fn(speed_guess);
1047                    let new_speed_guess = pwr_err - speed_guess * pwr_err_per_speed_guess;
1048                    speed_guesses.push(speed_guess);
1049                    pwr_errs.push(pwr_err);
1050                    d_pwr_err_per_d_speed_guesses.push(pwr_err_per_speed_guess);
1051                    new_speed_guesses.push(new_speed_guess);
1052                    converged = ((speed_guesses
1053                        .iter()
1054                        .last()
1055                        .ok_or_else(|| anyhow!("{}", format_dbg!()))?
1056                        - speed_guesses[speed_guesses.len() - 2])
1057                        / speed_guesses[speed_guesses.len() - 2])
1058                        .abs()
1059                        < xtol;
1060                    spd_ach_i += 1;
1061                }
1062
1063                self.newton_iters[i] = spd_ach_i;
1064
1065                let _ys = Array::from_vec(pwr_errs).map(|x| x.abs());
1066                // Question: could we assume `speed_guesses.iter().last()` is the correct solution?
1067                // This would make for faster running.
1068                self.mps_ach[i] = max(
1069                    speed_guesses[_ys
1070                        .iter()
1071                        .position(|x| x == _ys.min().unwrap())
1072                        .ok_or_else(|| anyhow!(format_dbg!(_ys.min().unwrap())))?],
1073                    0.0,
1074                );
1075                grade_estimate = self.lookup_grade_for_step(i, Some(self.mps_ach[i]))?;
1076                grade_diff = (grade - grade_estimate).abs();
1077            }
1078            self.set_power_calcs(i)?;
1079        }
1080
1081        self.mph_ach[i] = self.mps_ach[i] * params::MPH_PER_MPS;
1082        self.dist_m[i] = self.mps_ach[i] * self.cyc.dt_s_at_i(i);
1083        self.dist_mi[i] = self.dist_m[i] * 1.0 / params::M_PER_MI;
1084        Ok(())
1085    }
1086
1087    /// Hybrid control calculations.
1088    /// Arguments
1089    /// ------------
1090    /// i: index of time step
1091    pub fn set_hybrid_cont_calcs(&mut self, i: usize) -> anyhow::Result<()> {
1092        if self.veh.no_elec_sys {
1093            self.regen_buff_soc[i] = 0.0;
1094        } else if self.veh.charging_on {
1095            self.regen_buff_soc[i] = max(
1096                self.veh.max_soc - (self.veh.max_regen_kwh / self.veh.ess_max_kwh),
1097                (self.veh.max_soc + self.veh.min_soc) / 2.0,
1098            );
1099        } else {
1100            self.regen_buff_soc[i] = max(
1101                (self.veh.ess_max_kwh * self.veh.max_soc
1102                    - 0.5
1103                        * self.veh.veh_kg
1104                        * (self.cyc.mps[i].powi(2))
1105                        * (1.0 / 1_000.0)
1106                        * (1.0 / 3_600.0)
1107                        * self.veh.mc_peak_eff()
1108                        * self.veh.max_regen)
1109                    / self.veh.ess_max_kwh,
1110                self.veh.min_soc,
1111            );
1112
1113            self.ess_regen_buff_dischg_kw[i] = min(
1114                self.cur_ess_max_kw_out[i],
1115                max(
1116                    0.0,
1117                    (self.soc[i - 1] - self.regen_buff_soc[i]) * self.veh.ess_max_kwh * 3_600.0
1118                        / self.cyc.dt_s_at_i(i),
1119                ),
1120            );
1121
1122            self.max_ess_regen_buff_chg_kw[i] = min(
1123                max(
1124                    0.0,
1125                    (self.regen_buff_soc[i] - self.soc[i - 1]) * self.veh.ess_max_kwh * 3.6e3
1126                        / self.cyc.dt_s_at_i(i),
1127                ),
1128                self.cur_max_ess_chg_kw[i],
1129            );
1130        }
1131        if self.veh.no_elec_sys {
1132            self.accel_buff_soc[i] = 0.0;
1133        } else {
1134            self.accel_buff_soc[i] = min(
1135                max(
1136                    ((self.veh.max_accel_buffer_mph / params::MPH_PER_MPS).powi(2)
1137                        - self.cyc.mps[i].powi(2))
1138                        / (self.veh.max_accel_buffer_mph / params::MPH_PER_MPS).powi(2)
1139                        * min(
1140                            self.veh.max_accel_buffer_perc_of_useable_soc
1141                                * (self.veh.max_soc - self.veh.min_soc),
1142                            self.veh.max_regen_kwh / self.veh.ess_max_kwh,
1143                        )
1144                        + self.veh.min_soc,
1145                    self.veh.min_soc,
1146                ),
1147                self.veh.max_soc,
1148            );
1149
1150            self.ess_accel_buff_chg_kw[i] = max(
1151                0.0,
1152                (self.accel_buff_soc[i] - self.soc[i - 1]) * self.veh.ess_max_kwh * 3.6e3
1153                    / self.cyc.dt_s_at_i(i),
1154            );
1155            self.max_ess_accell_buff_dischg_kw[i] = min(
1156                max(
1157                    0.0,
1158                    (self.soc[i - 1] - self.accel_buff_soc[i]) * self.veh.ess_max_kwh * 3.6e3
1159                        / self.cyc.dt_s_at_i(i),
1160                ),
1161                self.cur_ess_max_kw_out[i],
1162            );
1163        }
1164        self.ess_accel_regen_dischg_kw[i] = if self.regen_buff_soc[i] < self.accel_buff_soc[i] {
1165            max(
1166                min(
1167                    (self.soc[i - 1] - (self.regen_buff_soc[i] + self.accel_buff_soc[i]) / 2.0)
1168                        * self.veh.ess_max_kwh
1169                        * 3.6e3
1170                        / self.cyc.dt_s_at_i(i),
1171                    self.cur_ess_max_kw_out[i],
1172                ),
1173                -self.cur_max_ess_chg_kw[i],
1174            )
1175        } else if self.soc[i - 1] > self.regen_buff_soc[i] {
1176            max(
1177                min(self.ess_regen_buff_dischg_kw[i], self.cur_ess_max_kw_out[i]),
1178                -self.cur_max_ess_chg_kw[i],
1179            )
1180        } else if self.soc[i - 1] < self.accel_buff_soc[i] {
1181            max(
1182                min(
1183                    -1.0 * self.ess_accel_buff_chg_kw[i],
1184                    self.cur_ess_max_kw_out[i],
1185                ),
1186                -self.cur_max_ess_chg_kw[i],
1187            )
1188        } else {
1189            max(
1190                min(0.0, self.cur_ess_max_kw_out[i]),
1191                -self.cur_max_ess_chg_kw[i],
1192            )
1193        };
1194        self.fc_kw_gap_fr_eff[i] = (self.trans_kw_out_ach[i] - self.veh.max_fc_eff_kw()).abs();
1195
1196        self.mc_elec_in_kw_for_max_fc_eff[i] = if self.veh.no_elec_sys {
1197            0.0
1198        } else if self.trans_kw_out_ach[i] < self.veh.max_fc_eff_kw() {
1199            if self.fc_kw_gap_fr_eff[i] == self.veh.mc_max_kw {
1200                -self.fc_kw_gap_fr_eff[i]
1201                    / self
1202                        .veh
1203                        .mc_full_eff_array
1204                        .last()
1205                        .ok_or_else(|| anyhow!(format_dbg!(self.veh.mc_full_eff_array)))?
1206            } else {
1207                -self.fc_kw_gap_fr_eff[i]
1208                    / self.veh.mc_full_eff_array[cmp::max(
1209                        1,
1210                        first_grtr(
1211                            &self.veh.mc_kw_out_array,
1212                            min(self.veh.mc_max_kw * 0.9999, self.fc_kw_gap_fr_eff[i]),
1213                        )
1214                        .ok_or_else(|| anyhow!(format_dbg!("`first_grtr` returned `None`")))?
1215                            - 1,
1216                    )]
1217            }
1218        } else if self.fc_kw_gap_fr_eff[i] == self.veh.mc_max_kw {
1219            *self
1220                .veh
1221                .mc_kw_in_array
1222                .last()
1223                .ok_or_else(|| anyhow!(format_dbg!(self.veh.mc_kw_in_array)))?
1224        } else {
1225            self.veh.mc_kw_in_array[first_grtr(
1226                &self.veh.mc_kw_out_array,
1227                min(self.veh.mc_max_kw * 0.9999, self.fc_kw_gap_fr_eff[i]),
1228            )
1229            .unwrap_or(0)
1230                - 1]
1231        };
1232        self.elec_kw_req_4ae[i] = if self.veh.no_elec_sys {
1233            0.0
1234        } else if self.trans_kw_in_ach[i] > 0.0 {
1235            if self.trans_kw_in_ach[i] == self.veh.mc_max_kw {
1236                self.trans_kw_in_ach[i]
1237                    / self
1238                        .veh
1239                        .mc_full_eff_array
1240                        .last()
1241                        .ok_or_else(|| anyhow!(format_dbg!(self.veh.mc_full_eff_array)))?
1242                    + self.aux_in_kw[i]
1243            } else {
1244                self.trans_kw_in_ach[i]
1245                    / self.veh.mc_full_eff_array[cmp::max(
1246                        1,
1247                        first_grtr(
1248                            &self.veh.mc_kw_out_array,
1249                            min(self.veh.mc_max_kw * 0.9999, self.trans_kw_in_ach[i]),
1250                        )
1251                        .ok_or_else(|| anyhow!(format_dbg!("`first_grtr` returned `None`")))?
1252                            - 1,
1253                    )]
1254                    + self.aux_in_kw[i]
1255            }
1256        } else {
1257            0.0
1258        };
1259
1260        self.prev_fc_time_on[i] = self.fc_time_on[i - 1];
1261
1262        // some conditions in the following if statement have a buffer of 1e-6 to prevent false positives/negatives because these have been encountered in practice.
1263        self.can_pwr_all_elec[i] = if self.veh.fc_max_kw == 0.0 {
1264            self.accel_buff_soc[i] < self.soc[i - 1]
1265                && (self.trans_kw_in_ach[i] - 1e-6) <= self.cur_max_mc_kw_out[i]
1266                && (self.elec_kw_req_4ae[i] < self.cur_max_elec_kw[i] || self.veh.fc_max_kw == 0.0)
1267        } else {
1268            self.accel_buff_soc[i] < self.soc[i - 1]
1269                && (self.trans_kw_in_ach[i] - 1e-6) <= self.cur_max_mc_kw_out[i]
1270                && (self.elec_kw_req_4ae[i] < self.cur_max_elec_kw[i] || self.veh.fc_max_kw == 0.0)
1271                && ((self.cyc.mph_at_i(i) - 1e-6) <= self.veh.mph_fc_on || self.veh.charging_on)
1272                && self.elec_kw_req_4ae[i] <= self.veh.kw_demand_fc_on
1273        };
1274        self.desired_ess_kw_out_for_ae[i] = if self.can_pwr_all_elec[i] {
1275            if self.trans_kw_in_ach[i] < self.aux_in_kw[i] {
1276                self.aux_in_kw[i] + self.trans_kw_in_ach[i]
1277            } else if self.regen_buff_soc[i] < self.accel_buff_soc[i] {
1278                self.ess_accel_regen_dischg_kw[i]
1279            } else if self.soc[i - 1] > self.regen_buff_soc[i] {
1280                self.ess_regen_buff_dischg_kw[i]
1281            } else if self.soc[i - 1] < self.accel_buff_soc[i] {
1282                -self.ess_accel_buff_chg_kw[i]
1283            } else {
1284                self.trans_kw_in_ach[i] + self.aux_in_kw[i] - self.cur_max_roadway_chg_kw[i]
1285            }
1286        } else {
1287            0.0
1288        };
1289
1290        self.ess_ae_kw_out[i] = if self.can_pwr_all_elec[i] {
1291            max(
1292                -self.cur_max_ess_chg_kw[i],
1293                max(
1294                    -self.max_ess_regen_buff_chg_kw[i],
1295                    max(
1296                        min(
1297                            0.0,
1298                            self.cur_max_roadway_chg_kw[i] - self.trans_kw_in_ach[i]
1299                                + self.aux_in_kw[i],
1300                        ),
1301                        min(
1302                            self.cur_ess_max_kw_out[i],
1303                            self.desired_ess_kw_out_for_ae[i],
1304                        ),
1305                    ),
1306                ),
1307            )
1308        } else {
1309            0.0
1310        };
1311
1312        self.er_ae_kw_out[i] = min(
1313            max(
1314                0.0,
1315                self.trans_kw_in_ach[i] + self.aux_in_kw[i] - self.ess_ae_kw_out[i],
1316            ),
1317            self.cur_max_roadway_chg_kw[i],
1318        );
1319        Ok(())
1320    }
1321
1322    /// Calculate control variables related to engine on/off state
1323    /// Arguments
1324    /// ------------
1325    /// i: index of time step
1326    pub fn set_fc_forced_state_rust(&mut self, i: usize) -> anyhow::Result<()> {
1327        // force fuel converter on if it was on in the previous time step, but only if fc
1328        // has not been on longer than minFcTimeOn
1329        self.fc_forced_on[i] = self.prev_fc_time_on[i] > 0.0
1330            && self.prev_fc_time_on[i] < self.veh.min_fc_time_on - self.cyc.dt_s_at_i(i);
1331        if !self.fc_forced_on[i] || !self.can_pwr_all_elec[i] {
1332            // fc forced on because:
1333            // - it was on in the previous time step and hasn't been on long enough
1334            // - it can't power everything on it's own
1335            self.fc_forced_state[i] = 1;
1336            self.mc_mech_kw_4forced_fc[i] = 0.0;
1337        } else if self.trans_kw_in_ach[i] < 0.0 {
1338            // not forced on.  transmission needs negative power (i.e. regen)
1339            self.fc_forced_state[i] = 2;
1340            self.mc_mech_kw_4forced_fc[i] = self.trans_kw_in_ach[i];
1341        } else if self.veh.max_fc_eff_kw() == self.trans_kw_in_ach[i] {
1342            // if fc power at which maximum efficiency is achieved equals the transmission input power
1343            // fc possibly (???) forced on to be more efficient
1344            // this seems unlikely to ever happen
1345            self.fc_forced_state[i] = 3;
1346            self.mc_mech_kw_4forced_fc[i] = 0.0;
1347        } else if self.veh.idle_fc_kw > self.trans_kw_in_ach[i] && self.accel_kw[i] >= 0.0 {
1348            // accelerating but idle power is greater than accel power needed by trans
1349            self.fc_forced_state[i] = 4;
1350            self.mc_mech_kw_4forced_fc[i] = self.trans_kw_in_ach[i] - self.veh.idle_fc_kw;
1351        } else if self.veh.max_fc_eff_kw() > self.trans_kw_in_ach[i] {
1352            // if fc power at which maximum efficiency is achieved exceeds the transmission input power
1353            self.fc_forced_state[i] = 5;
1354            self.mc_mech_kw_4forced_fc[i] = 0.0;
1355        } else {
1356            // fc not forced on in previous time step or
1357            // transmission is not in braking state or
1358            // transmission input power is not exactly most efficient point on fc map or
1359            // acceleration is not >= 0 and/or idle power is not > trans input power or
1360            // fc peak eff point in map is <= trans input power
1361            self.fc_forced_state[i] = 6;
1362            self.mc_mech_kw_4forced_fc[i] = self.trans_kw_in_ach[i] - self.veh.max_fc_eff_kw();
1363        }
1364        Ok(())
1365    }
1366
1367    /// Hybrid control decisions.
1368    /// Arguments
1369    /// ------------
1370    /// i: index of time step
1371    pub fn set_hybrid_cont_decisions(&mut self, i: usize) -> anyhow::Result<()> {
1372        self.ess_desired_kw_4fc_eff[i] =
1373            if (-self.mc_elec_in_kw_for_max_fc_eff[i] - self.cur_max_roadway_chg_kw[i]) > 0.0 {
1374                (-self.mc_elec_in_kw_for_max_fc_eff[i] - self.cur_max_roadway_chg_kw[i])
1375                    * self.veh.ess_dischg_to_fc_max_eff_perc
1376            } else {
1377                (-self.mc_elec_in_kw_for_max_fc_eff[i] - self.cur_max_roadway_chg_kw[i])
1378                    * self.veh.ess_chg_to_fc_max_eff_perc
1379            };
1380
1381        self.ess_kw_if_fc_req[i] = if self.accel_buff_soc[i] > self.regen_buff_soc[i] {
1382            min(
1383                self.cur_ess_max_kw_out[i],
1384                min(
1385                    self.veh.mc_max_elec_in_kw + self.aux_in_kw[i],
1386                    min(
1387                        self.cur_max_mc_elec_kw_in[i] + self.aux_in_kw[i],
1388                        max(
1389                            -self.cur_max_ess_chg_kw[i],
1390                            self.ess_accel_regen_dischg_kw[i],
1391                        ),
1392                    ),
1393                ),
1394            )
1395        } else if self.ess_regen_buff_dischg_kw[i] > 0.0 {
1396            min(
1397                self.cur_ess_max_kw_out[i],
1398                min(
1399                    self.veh.mc_max_elec_in_kw + self.aux_in_kw[i],
1400                    min(
1401                        self.cur_max_mc_elec_kw_in[i] + self.aux_in_kw[i],
1402                        max(
1403                            -self.cur_max_ess_chg_kw[i],
1404                            min(
1405                                self.ess_accel_regen_dischg_kw[i],
1406                                min(
1407                                    self.mc_elec_in_lim_kw[i] + self.aux_in_kw[i],
1408                                    max(
1409                                        self.ess_regen_buff_dischg_kw[i],
1410                                        self.ess_desired_kw_4fc_eff[i],
1411                                    ),
1412                                ),
1413                            ),
1414                        ),
1415                    ),
1416                ),
1417            )
1418        } else if self.ess_accel_buff_chg_kw[i] > 0.0 {
1419            min(
1420                self.cur_ess_max_kw_out[i],
1421                min(
1422                    self.veh.mc_max_elec_in_kw + self.aux_in_kw[i],
1423                    min(
1424                        self.cur_max_mc_elec_kw_in[i] + self.aux_in_kw[i],
1425                        max(
1426                            -self.cur_max_ess_chg_kw[i],
1427                            max(
1428                                -self.max_ess_regen_buff_chg_kw[i],
1429                                min(
1430                                    -self.ess_accel_buff_chg_kw[i],
1431                                    self.ess_desired_kw_4fc_eff[i],
1432                                ),
1433                            ),
1434                        ),
1435                    ),
1436                ),
1437            )
1438        } else if self.ess_desired_kw_4fc_eff[i] > 0.0 {
1439            min(
1440                self.cur_ess_max_kw_out[i],
1441                min(
1442                    self.veh.mc_max_elec_in_kw + self.aux_in_kw[i],
1443                    min(
1444                        self.cur_max_mc_elec_kw_in[i] + self.aux_in_kw[i],
1445                        max(
1446                            -self.cur_max_ess_chg_kw[i],
1447                            min(
1448                                self.ess_desired_kw_4fc_eff[i],
1449                                self.max_ess_accell_buff_dischg_kw[i],
1450                            ),
1451                        ),
1452                    ),
1453                ),
1454            )
1455        } else {
1456            min(
1457                self.cur_ess_max_kw_out[i],
1458                min(
1459                    self.veh.mc_max_elec_in_kw + self.aux_in_kw[i],
1460                    min(
1461                        self.cur_max_mc_elec_kw_in[i] + self.aux_in_kw[i],
1462                        max(
1463                            -self.cur_max_ess_chg_kw[i],
1464                            max(
1465                                self.ess_desired_kw_4fc_eff[i],
1466                                -self.max_ess_regen_buff_chg_kw[i],
1467                            ),
1468                        ),
1469                    ),
1470                ),
1471            )
1472        };
1473
1474        self.er_kw_if_fc_req[i] = max(
1475            0.0,
1476            min(
1477                self.cur_max_roadway_chg_kw[i],
1478                min(
1479                    self.cur_max_mech_mc_kw_in[i],
1480                    self.ess_kw_if_fc_req[i] - self.mc_elec_in_lim_kw[i] + self.aux_in_kw[i],
1481                ),
1482            ),
1483        );
1484
1485        self.mc_elec_kw_in_if_fc_req[i] =
1486            self.ess_kw_if_fc_req[i] + self.er_kw_if_fc_req[i] - self.aux_in_kw[i];
1487
1488        self.mc_kw_if_fc_req[i] = if self.veh.no_elec_sys || self.mc_elec_kw_in_if_fc_req[i] == 0.0
1489        {
1490            0.0
1491        } else if self.mc_elec_kw_in_if_fc_req[i] > 0.0 {
1492            if self.mc_elec_kw_in_if_fc_req[i] == arrmax(&self.veh.mc_kw_in_array) {
1493                self.mc_elec_kw_in_if_fc_req[i]
1494                    * self
1495                        .veh
1496                        .mc_full_eff_array
1497                        .last()
1498                        .ok_or_else(|| anyhow!(format_dbg!(self.veh.mc_full_eff_array)))?
1499            } else {
1500                self.mc_elec_kw_in_if_fc_req[i]
1501                    * self.veh.mc_full_eff_array[cmp::max(
1502                        1,
1503                        first_grtr(
1504                            &self.veh.mc_kw_in_array,
1505                            min(
1506                                arrmax(&self.veh.mc_kw_in_array) * 0.9999,
1507                                self.mc_elec_kw_in_if_fc_req[i],
1508                            ),
1509                        )
1510                        .ok_or_else(|| anyhow!(format_dbg!("`first_grtr` returned `None`")))?
1511                            - 1,
1512                    )]
1513            }
1514        } else if -self.mc_elec_kw_in_if_fc_req[i] == arrmax(&self.veh.mc_kw_in_array) {
1515            self.mc_elec_kw_in_if_fc_req[i]
1516                / self
1517                    .veh
1518                    .mc_full_eff_array
1519                    .last()
1520                    .ok_or_else(|| anyhow!(format_dbg!(self.veh.mc_full_eff_array)))?
1521        } else {
1522            self.mc_elec_kw_in_if_fc_req[i]
1523                / self.veh.mc_full_eff_array[cmp::max(
1524                    1,
1525                    first_grtr(
1526                        &self.veh.mc_kw_in_array,
1527                        min(
1528                            arrmax(&self.veh.mc_kw_in_array) * 0.9999,
1529                            -self.mc_elec_kw_in_if_fc_req[i],
1530                        ),
1531                    )
1532                    .ok_or_else(|| anyhow!(format_dbg!("`first_grtr` returned `None`")))?
1533                        - 1,
1534                )]
1535        };
1536
1537        self.mc_mech_kw_out_ach[i] = if self.veh.mc_max_kw == 0.0 {
1538            0.0
1539        } else if self.fc_forced_on[i]
1540            && self.can_pwr_all_elec[i]
1541            && (self.veh.veh_pt_type == HEV || self.veh.veh_pt_type == PHEV)
1542            && (self.veh.fc_eff_type != H2FC)
1543        {
1544            self.mc_mech_kw_4forced_fc[i]
1545        } else if self.trans_kw_in_ach[i] <= 0.0 {
1546            if self.veh.fc_eff_type != H2FC && self.veh.fc_max_kw > 0.0 {
1547                if self.can_pwr_all_elec[i] {
1548                    -min(self.cur_max_mech_mc_kw_in[i], -self.trans_kw_in_ach[i])
1549                } else {
1550                    min(
1551                        -min(self.cur_max_mech_mc_kw_in[i], -self.trans_kw_in_ach[i]),
1552                        max(-self.cur_max_fc_kw_out[i], self.mc_kw_if_fc_req[i]),
1553                    )
1554                }
1555            } else {
1556                min(
1557                    -min(self.cur_max_mech_mc_kw_in[i], -self.trans_kw_in_ach[i]),
1558                    -self.trans_kw_in_ach[i],
1559                )
1560            }
1561        } else if self.can_pwr_all_elec[i] {
1562            self.trans_kw_in_ach[i]
1563        } else {
1564            max(self.min_mc_kw_2help_fc[i], self.mc_kw_if_fc_req[i])
1565        };
1566
1567        self.mc_elec_kw_in_ach[i] = if self.mc_mech_kw_out_ach[i] == 0.0 {
1568            0.0
1569        } else if self.mc_mech_kw_out_ach[i] < 0.0 {
1570            if -self.mc_mech_kw_out_ach[i] == arrmax(&self.veh.mc_kw_in_array) {
1571                // this unwrap call has already been checked above
1572                self.mc_mech_kw_out_ach[i] * self.veh.mc_full_eff_array.last().unwrap()
1573            } else {
1574                self.mc_mech_kw_out_ach[i]
1575                    * self.veh.mc_full_eff_array[cmp::max(
1576                        1,
1577                        first_grtr(
1578                            &self.veh.mc_kw_in_array,
1579                            min(
1580                                arrmax(&self.veh.mc_kw_in_array) * 0.9999,
1581                                -self.mc_mech_kw_out_ach[i],
1582                            ),
1583                        )
1584                        .ok_or_else(|| anyhow!(format_dbg!("`first_grtr` returned `None`")))?
1585                            - 1,
1586                    )]
1587            }
1588        } else if self.veh.mc_max_kw == self.mc_mech_kw_out_ach[i] {
1589            // this unwrap call has already been checked
1590            self.mc_mech_kw_out_ach[i] / self.veh.mc_full_eff_array.last().unwrap()
1591        } else {
1592            self.mc_mech_kw_out_ach[i]
1593                / self.veh.mc_full_eff_array[cmp::max(
1594                    1,
1595                    first_grtr(
1596                        &self.veh.mc_kw_out_array,
1597                        min(self.veh.mc_max_kw * 0.9999, self.mc_mech_kw_out_ach[i]),
1598                    )
1599                    .ok_or_else(|| anyhow!(format_dbg!("`first_grtr` returned `None`")))?
1600                        - 1,
1601                )]
1602        };
1603
1604        self.roadway_chg_kw_out_ach[i] = if self.cur_max_roadway_chg_kw[i] == 0.0 {
1605            0.0
1606        } else if self.veh.fc_eff_type == H2FC {
1607            max(
1608                0.0,
1609                max(
1610                    self.mc_elec_kw_in_ach[i],
1611                    max(
1612                        self.max_ess_regen_buff_chg_kw[i],
1613                        max(
1614                            self.ess_regen_buff_dischg_kw[i],
1615                            self.cur_max_roadway_chg_kw[i],
1616                        ),
1617                    ),
1618                ),
1619            )
1620        } else if self.can_pwr_all_elec[i] {
1621            self.er_ae_kw_out[i]
1622        } else {
1623            self.er_kw_if_fc_req[i]
1624        };
1625
1626        self.min_ess_kw_2help_fc[i] = self.mc_elec_kw_in_ach[i] + self.aux_in_kw[i]
1627            - self.cur_max_fc_kw_out[i]
1628            - self.roadway_chg_kw_out_ach[i];
1629
1630        self.ess_kw_out_ach[i] = if self.veh.ess_max_kw == 0.0 || self.veh.ess_max_kwh == 0.0 {
1631            0.0
1632        } else if self.veh.fc_eff_type == H2FC {
1633            if self.trans_kw_out_ach[i] >= 0.0 {
1634                min(
1635                    self.cur_ess_max_kw_out[i],
1636                    min(
1637                        self.mc_elec_kw_in_ach[i] + self.aux_in_kw[i]
1638                            - self.roadway_chg_kw_out_ach[i],
1639                        max(
1640                            self.min_ess_kw_2help_fc[i],
1641                            max(
1642                                self.ess_desired_kw_4fc_eff[i],
1643                                self.ess_accel_regen_dischg_kw[i],
1644                            ),
1645                        ),
1646                    ),
1647                )
1648            } else {
1649                self.mc_elec_kw_in_ach[i] + self.aux_in_kw[i] - self.roadway_chg_kw_out_ach[i]
1650            }
1651        } else if self.high_acc_fc_on_tag[i] || self.veh.no_elec_aux {
1652            self.mc_elec_kw_in_ach[i] - self.roadway_chg_kw_out_ach[i]
1653        } else {
1654            self.mc_elec_kw_in_ach[i] + self.aux_in_kw[i] - self.roadway_chg_kw_out_ach[i]
1655        };
1656
1657        self.ess_cur_kwh[i] = if self.veh.no_elec_sys {
1658            0.0
1659        } else if self.ess_kw_out_ach[i] < 0.0 {
1660            self.ess_cur_kwh[i - 1]
1661                - self.ess_kw_out_ach[i] * self.cyc.dt_s_at_i(i) / 3.6e3
1662                    * self.veh.ess_round_trip_eff.sqrt()
1663        } else {
1664            self.ess_cur_kwh[i - 1]
1665                - self.ess_kw_out_ach[i] * self.cyc.dt_s_at_i(i) / 3.6e3
1666                    * (1.0 / self.veh.ess_round_trip_eff.sqrt())
1667        };
1668
1669        self.soc[i] = if self.veh.ess_max_kwh == 0.0 {
1670            0.0
1671        } else {
1672            self.ess_cur_kwh[i] / self.veh.ess_max_kwh
1673        };
1674
1675        self.fc_time_on[i] =
1676            if self.can_pwr_all_elec[i] && !self.fc_forced_on[i] && self.fc_kw_out_ach[i] == 0.0 {
1677                0.0
1678            } else {
1679                self.fc_time_on[i - 1] + self.cyc.dt_s_at_i(i)
1680            };
1681        Ok(())
1682    }
1683
1684    /// Sets power consumption values for the current time step.
1685    /// Arguments
1686    /// ------------
1687    /// i: index of time step
1688    pub fn set_fc_power(&mut self, i: usize) -> anyhow::Result<()> {
1689        self.fc_kw_out_ach[i] = if self.veh.fc_max_kw == 0.0 {
1690            0.0
1691        } else if self.veh.fc_eff_type == H2FC {
1692            min(
1693                self.cur_max_fc_kw_out[i],
1694                max(
1695                    0.0,
1696                    self.mc_elec_kw_in_ach[i] + self.aux_in_kw[i]
1697                        - self.ess_kw_out_ach[i]
1698                        - self.roadway_chg_kw_out_ach[i],
1699                ),
1700            )
1701        } else if self.veh.no_elec_sys || self.veh.no_elec_aux || self.high_acc_fc_on_tag[i] {
1702            min(
1703                self.cur_max_fc_kw_out[i],
1704                max(
1705                    0.0,
1706                    self.trans_kw_in_ach[i] - self.mc_mech_kw_out_ach[i] + self.aux_in_kw[i],
1707                ),
1708            )
1709        } else {
1710            min(
1711                self.cur_max_fc_kw_out[i],
1712                max(0.0, self.trans_kw_in_ach[i] - self.mc_mech_kw_out_ach[i]),
1713            )
1714        };
1715
1716        self.fc_kw_out_ach_pct[i] = if self.veh.fc_max_kw == 0.0 {
1717            0.0
1718        } else {
1719            self.fc_kw_out_ach[i] / self.veh.fc_max_kw
1720        };
1721
1722        self.fc_kw_in_ach[i] = if self.fc_kw_out_ach[i] == 0.0 {
1723            0.0
1724        } else if self.veh.fc_eff_array[first_grtr(
1725            &self.veh.fc_kw_out_array,
1726            min(self.fc_kw_out_ach[i], self.veh.fc_max_kw),
1727        )
1728        .ok_or_else(|| anyhow!(format_dbg!("`first_grtr` returned `None`")))?
1729            - 1]
1730            != 0.0
1731        {
1732            self.fc_kw_out_ach[i]
1733                / (self.veh.fc_eff_array[first_grtr(
1734                    &self.veh.fc_kw_out_array,
1735                    min(self.fc_kw_out_ach[i], self.veh.fc_max_kw),
1736                )
1737                .ok_or_else(|| anyhow!(format_dbg!("`first_grtr` returned `None`")))?
1738                    - 1])
1739        } else {
1740            0.0
1741        };
1742
1743        self.fs_kw_out_ach[i] = self.fc_kw_in_ach[i];
1744
1745        self.fs_kwh_out_ach[i] = self.fs_kw_out_ach[i] * self.cyc.dt_s_at_i(i) / 3.6e3;
1746        Ok(())
1747    }
1748
1749    /// Sets scalar variables that can be calculated after a cycle is run.
1750    /// This includes mpgge, various energy metrics, and others
1751    pub fn set_post_scalars(&mut self) -> anyhow::Result<()> {
1752        self.mpgge = if self.fs_kwh_out_ach.sum() == 0.0 {
1753            0.0
1754        } else {
1755            self.dist_mi.sum() / (self.fs_kwh_out_ach.sum() / self.props.kwh_per_gge)
1756        };
1757        let dt_s = self.cyc.dt_s();
1758
1759        self.roadway_chg_kj = (&self.roadway_chg_kw_out_ach * &dt_s).sum();
1760        self.ess_dischg_kj = -1.0
1761            * (self
1762                .soc
1763                .last()
1764                .ok_or_else(|| anyhow!(format_dbg!(self.soc)))?
1765                - self.soc[0])
1766            * self.veh.ess_max_kwh
1767            * 3.6e3;
1768        let dist_mi = self.dist_mi.sum();
1769        self.battery_kwh_per_mi = if dist_mi > 0.0 {
1770            (self.ess_dischg_kj / 3.6e3) / dist_mi
1771        } else {
1772            0.0
1773        };
1774        self.electric_kwh_per_mi = if dist_mi > 0.0 {
1775            ((self.roadway_chg_kj + self.ess_dischg_kj) / 3.6e3) / dist_mi
1776        } else {
1777            0.0
1778        };
1779        self.fuel_kj = (&self.fs_kw_out_ach * &dt_s).sum();
1780
1781        self.ess2fuel_kwh = if (self.fuel_kj + self.roadway_chg_kj) == 0.0 {
1782            1.0
1783        } else {
1784            self.ess_dischg_kj / (self.fuel_kj + self.roadway_chg_kj)
1785        };
1786
1787        // energy audit calcs
1788        self.drag_kj = (&self.drag_kw * &dt_s).sum();
1789        self.ascent_kj = (&self.ascent_kw * &dt_s).sum();
1790        self.rr_kj = (&self.rr_kw * &dt_s).sum();
1791
1792        for i in 1..self.cyc.len() {
1793            self.ess_loss_kw[i] = if self.veh.ess_max_kw == 0.0 || self.veh.ess_max_kwh == 0.0 {
1794                0.0
1795            } else if self.ess_kw_out_ach[i] < 0.0 {
1796                -self.ess_kw_out_ach[i]
1797                    - (-self.ess_kw_out_ach[i] * self.veh.ess_round_trip_eff.sqrt())
1798            } else {
1799                self.ess_kw_out_ach[i] * (1.0 / self.veh.ess_round_trip_eff.sqrt())
1800                    - self.ess_kw_out_ach[i]
1801            };
1802        }
1803
1804        self.brake_kj = (&self.cyc_fric_brake_kw * &dt_s).sum();
1805        self.trans_kj = ((&self.trans_kw_in_ach - &self.trans_kw_out_ach) * &dt_s).sum();
1806        self.mc_kj = ((&self.mc_elec_kw_in_ach - &self.mc_mech_kw_out_ach) * &dt_s).sum();
1807        self.ess_eff_kj = (&self.ess_loss_kw * &dt_s).sum();
1808        self.aux_kj = (&self.aux_in_kw * &dt_s).sum();
1809        self.fc_kj = ((&self.fc_kw_in_ach - &self.fc_kw_out_ach) * &dt_s).sum();
1810
1811        self.net_kj = self.drag_kj
1812            + self.ascent_kj
1813            + self.rr_kj
1814            + self.brake_kj
1815            + self.trans_kj
1816            + self.mc_kj
1817            + self.ess_eff_kj
1818            + self.aux_kj
1819            + self.fc_kj;
1820
1821        self.ke_kj = 0.5
1822            * self.veh.veh_kg
1823            * (self
1824                .mps_ach
1825                .first()
1826                .ok_or_else(|| anyhow!(format_dbg!(self.mps_ach)))?
1827                .powi(2)
1828                - self
1829                    .mps_ach
1830                    .last()
1831                    .ok_or_else(|| anyhow!(format_dbg!(self.mps_ach)))?
1832                    .powi(2))
1833            / 1_000.0;
1834
1835        self.energy_audit_error =
1836            ((self.roadway_chg_kj + self.ess_dischg_kj + self.fuel_kj + self.ke_kj) - self.net_kj)
1837                / (self.roadway_chg_kj + self.ess_dischg_kj + self.fuel_kj + self.ke_kj);
1838
1839        if self.energy_audit_error.abs() > self.sim_params.energy_audit_error_tol {
1840            #[cfg(feature = "logging")]
1841            log::warn!(
1842                "problem detected with conservation of energy; \
1843                    energy audit error: {:.5}",
1844                self.energy_audit_error
1845            );
1846        }
1847        for i in 1..self.cyc.len() {
1848            self.accel_kw[i] = self.veh.veh_kg / (2.0 * self.cyc.dt_s_at_i(i))
1849                * (self.mps_ach[i].powi(2) - self.mps_ach[i - 1].powi(2))
1850                / 1_000.0;
1851        }
1852
1853        self.trace_miss = false;
1854        let dist_m = self.cyc0.dist_m().sum();
1855        self.trace_miss_dist_frac = if dist_m > 0.0 {
1856            (self.dist_m.sum() - dist_m).abs() / dist_m
1857        } else {
1858            bail!("Vehicle did not move forward.");
1859        };
1860        self.trace_miss_time_frac = (self
1861            .cyc
1862            .time_s
1863            .last()
1864            .ok_or_else(|| anyhow!(format_dbg!(self.cyc.time_s)))?
1865            // already checked above
1866            - self.cyc0.time_s.last().unwrap())
1867            // already checked above
1868            / self.cyc0.time_s.last().unwrap();
1869
1870        if !self.sim_params.missed_trace_correction {
1871            if self.trace_miss_dist_frac > self.sim_params.trace_miss_dist_tol {
1872                self.trace_miss = true;
1873                #[cfg(feature = "logging")]
1874                log::warn!(
1875                    "trace miss distance fraction {:.5} exceeds tolerance of {:.5}",
1876                    self.trace_miss_dist_frac,
1877                    self.sim_params.trace_miss_dist_tol
1878                );
1879            }
1880        } else if self.trace_miss_time_frac > self.sim_params.trace_miss_time_tol {
1881            self.trace_miss = true;
1882            #[cfg(feature = "logging")]
1883            log::warn!(
1884                "trace miss time fraction {:.5} exceeds tolerance of {:.5}",
1885                self.trace_miss_time_frac,
1886                self.sim_params.trace_miss_time_tol
1887            );
1888        }
1889
1890        self.trace_miss_speed_mps = *(&self.mps_ach - &self.cyc.mps).map(|x| x.abs()).max()?;
1891        if self.trace_miss_speed_mps > self.sim_params.trace_miss_speed_mps_tol {
1892            self.trace_miss = true;
1893            #[cfg(feature = "logging")]
1894            log::warn!(
1895                "trace miss speed {:.5} m/s exceeds tolerance of {:.5} m/s",
1896                self.trace_miss_speed_mps,
1897                self.sim_params.trace_miss_speed_mps_tol
1898            );
1899        }
1900        Ok(())
1901    }
1902}