1use 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; 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, 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 pub fn len(&self) -> usize {
277 self.cyc.time_s.len()
278 }
279
280 pub fn is_empty(&self) -> bool {
282 self.cyc.time_s.is_empty()
283 }
284
285 pub fn init_arrays(&mut self) {
287 self.i = 1; let cyc_len = self.cyc0.time_s.len(); 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 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 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 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 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 (self.veh.max_soc + self.veh.min_soc) / 2.0
408 } else if self.veh.veh_pt_type == HEV {
409 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 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 let init_soc_auto = match self.veh.veh_pt_type.as_str() {
469 CONV => (self.veh.max_soc + self.veh.min_soc) / 2.0,
471 HEV => (self.veh.max_soc + self.veh.min_soc) / 2.0,
472 _ => 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 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 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 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(); }
562 self.i = 1; Ok(())
564 }
565
566 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 ¤t_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 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; Ok(())
605 }
606
607 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 pub fn set_misc_calcs(&mut self, i: usize) -> anyhow::Result<()> {
625 if self.aux_in_kw.slice(s![i..]).iter().all(|&x| x == 0.0) {
629 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 self.reached_buff[i] = self.soc[i - 1] >= (self.veh.min_soc + self.veh.perc_high_acc_buf);
638
639 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 pub fn set_comp_lims(&mut self, i: usize) -> anyhow::Result<()> {
653 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 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 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 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 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 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 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 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 pub fn set_ach_speed(&mut self, i: usize) -> anyhow::Result<()> {
942 if self.cyc_met[i] {
944 self.mps_ach[i] = self.cyc.mps[i];
945 }
946 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 let speed_guess = max(1.0, self.mps_ach[i - 1]);
1013 let max_iter = self.sim_params.newton_max_iter;
1015 let xtol = self.sim_params.newton_xtol;
1016 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 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 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 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 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 pub fn set_fc_forced_state_rust(&mut self, i: usize) -> anyhow::Result<()> {
1327 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 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 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 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 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 self.fc_forced_state[i] = 5;
1354 self.mc_mech_kw_4forced_fc[i] = 0.0;
1355 } else {
1356 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 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 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 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 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 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 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 - self.cyc0.time_s.last().unwrap())
1867 / 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}