diff --git a/python/fastsim/fastsimrust.pyi b/python/fastsim/fastsimrust.pyi index 1f01dc23..8ec297b4 100644 --- a/python/fastsim/fastsimrust.pyi +++ b/python/fastsim/fastsimrust.pyi @@ -480,6 +480,7 @@ class RustSimDrive: i: int impose_coast: Pyo3ArrayBool ke_kj: float + long_params: RustLongParams max_ess_accell_buff_dischg_kw: Pyo3ArrayF64 max_ess_regen_buff_chg_kw: Pyo3ArrayF64 max_trac_mps: Pyo3ArrayF64 diff --git a/python/fastsim/parameters.py b/python/fastsim/parameters.py index 00cc170c..70a262cb 100644 --- a/python/fastsim/parameters.py +++ b/python/fastsim/parameters.py @@ -10,6 +10,8 @@ from pathlib import Path import copy from copy import deepcopy +import json + import fastsim.fastsimrust as fsr from . import inspect_utils @@ -100,7 +102,7 @@ def copy_physical_properties(p: PhysicalProperties, return_type: str = None, dee elif return_type == 'python': return PhysicalProperties.from_dict(p_dict) elif return_type == 'rust': - return fsr.RustPhysicalProperties(**p_dict) + return fsr.RustPhysicalProperties.from_json(json.dumps(p_dict)) else: raise ValueError(f"Invalid return_type: '{return_type}'") diff --git a/rust/Cargo.toml b/rust/Cargo.toml index 61ed446e..2dd9aa1b 100644 --- a/rust/Cargo.toml +++ b/rust/Cargo.toml @@ -14,8 +14,8 @@ codegen-units = 1 # optimize connection between modules [workspace.dependencies] anyhow = "1.0.57" -pyo3 = "0.17.2" +pyo3 = "0.19" pyo3-log = "*" serde = "1.0.143" serde_json = "1.0.83" -serde_yaml = "0.8.24" \ No newline at end of file +serde_yaml = "0.9.22" \ No newline at end of file diff --git a/rust/fastsim-cli/src/bin/fastsim-cli.rs b/rust/fastsim-cli/src/bin/fastsim-cli.rs index d15c95a9..0a492604 100644 --- a/rust/fastsim-cli/src/bin/fastsim-cli.rs +++ b/rust/fastsim-cli/src/bin/fastsim-cli.rs @@ -191,7 +191,7 @@ pub fn main() { let adopt_hd_str_lc = adopt_hd_string.to_lowercase(); let true_string = String::from("true"); let false_string = String::from("false"); - let adopt_hd_has_cycle = adopt_hd_str_lc.len() > 0 + let adopt_hd_has_cycle = !adopt_hd_str_lc.is_empty() && adopt_hd_str_lc != true_string && adopt_hd_str_lc != false_string; (true, adopt_hd_string.clone(), adopt_hd_has_cycle) @@ -246,9 +246,9 @@ pub fn main() { let (veh_string, pwr_out_perc, h2share) = json_rewrite(veh_string); hd_h2_diesel_ice_h2share = h2share; fc_pwr_out_perc = pwr_out_perc; - RustVehicle::from_str(&veh_string) + RustVehicle::from_json_str(&veh_string) } else { - RustVehicle::from_str(&veh_string) + RustVehicle::from_json_str(&veh_string) } } else if let Some(veh_file_path) = fastsim_api.veh_file { if is_adopt || is_adopt_hd { @@ -256,7 +256,7 @@ pub fn main() { let (vehstring, pwr_out_perc, h2share) = json_rewrite(vehstring); hd_h2_diesel_ice_h2share = h2share; fc_pwr_out_perc = pwr_out_perc; - RustVehicle::from_str(&vehstring) + RustVehicle::from_json_str(&vehstring) } else { RustVehicle::from_file(&veh_file_path) } @@ -312,31 +312,37 @@ pub fn main() { "HHDDTCruiseSmooth.csv" )); let cyc = if adopt_hd_has_cycle { - cyc.clone() + cyc } else { RustCycle::from_csv_string(hd_cyc_filestring, "HHDDTCruiseSmooth".to_string()).unwrap() }; - let mut sim_drive = RustSimDrive::new(cyc.clone(), veh.clone()); + let mut sim_drive = RustSimDrive::new(cyc, veh.clone()); sim_drive.sim_drive(None, None).unwrap(); let mut sim_drive_accel = RustSimDrive::new(make_accel_trace(), veh.clone()); let net_accel = get_net_accel(&mut sim_drive_accel, &veh.scenario_name).unwrap(); let mut mpgge = sim_drive.mpgge; - let h2_diesel_results = if hd_h2_diesel_ice_h2share.is_some() && fc_pwr_out_perc.is_some() { - let dist_mi = sim_drive.dist_mi.sum(); - let r = calculate_mpgge_for_h2_diesel_ice( - dist_mi, - sim_drive.veh.fc_max_kw, - sim_drive.props.kwh_per_gge, - &sim_drive.fc_kw_out_ach.to_vec(), - &sim_drive.fs_kwh_out_ach.to_vec(), - &fc_pwr_out_perc.unwrap(), - &hd_h2_diesel_ice_h2share.unwrap(), - ); - mpgge = dist_mi / (r.diesel_gge + r.h2_gge); - Some(r) + + let h2_diesel_results = if let Some(hd_h2_diesel_ice_h2share) = hd_h2_diesel_ice_h2share { + if let Some(fc_pwr_out_perc) = fc_pwr_out_perc { + let dist_mi = sim_drive.dist_mi.sum(); + let r = calculate_mpgge_for_h2_diesel_ice( + dist_mi, + sim_drive.veh.fc_max_kw, + sim_drive.props.kwh_per_gge, + &sim_drive.fc_kw_out_ach.to_vec(), + &sim_drive.fs_kwh_out_ach.to_vec(), + &fc_pwr_out_perc, + &hd_h2_diesel_ice_h2share, + ); + mpgge = dist_mi / (r.diesel_gge + r.h2_gge); + Some(r) + } else { + None + } } else { None }; + let res = AdoptResults { adjCombMpgge: mpgge, rangeMiles: if mpgge > 0.0 { @@ -397,8 +403,7 @@ fn array_to_object_representation(xs: &Vec) -> ArrayObject { fn transform_array_of_value_to_vec_of_f64(array_of_values: &Vec) -> Vec { let mut vec_of_f64 = Vec::::new(); - for idx in 0..array_of_values.len() { - let item_raw = &array_of_values[idx]; + for item_raw in array_of_values { if item_raw.is_number() { let item = item_raw.as_f64().unwrap(); vec_of_f64.push(item); @@ -437,7 +442,7 @@ fn json_rewrite(x: String) -> (String, Option>, Option>) { let fc_eff_type_value = fc_eff_type_raw.as_str().unwrap(); let fc_eff_type = String::from(fc_eff_type_value); parsed_data["fcEffType"] = Value::String(fc_eff_type.clone()); - if fc_eff_type == String::from("HDH2DieselIce") { + if fc_eff_type == *"HDH2DieselIce" { let fc_pwr_out_perc_raw = &parsed_data["fuelConverter"]["fcPwrOutPerc"]; if fc_pwr_out_perc_raw.is_array() { fc_pwr_out_perc = Some(transform_array_of_value_to_vec_of_f64( @@ -517,5 +522,5 @@ fn json_rewrite(x: String) -> (String, Option>, Option>) { let adoptstring = ParsedValue(parsed_data).to_json(); - return (adoptstring, fc_pwr_out_perc, hd_h2_diesel_ice_h2share); + (adoptstring, fc_pwr_out_perc, hd_h2_diesel_ice_h2share) } diff --git a/rust/fastsim-core/proc-macros/src/add_pyo3_api.rs b/rust/fastsim-core/proc-macros/src/add_pyo3_api.rs index 36675b2e..cb174d69 100644 --- a/rust/fastsim-core/proc-macros/src/add_pyo3_api.rs +++ b/rust/fastsim-core/proc-macros/src/add_pyo3_api.rs @@ -11,7 +11,7 @@ pub fn add_pyo3_api(attr: TokenStream, item: TokenStream) -> TokenStream { let mut impl_block = TokenStream2::default(); let mut py_impl_block = TokenStream2::default(); py_impl_block.extend::(crate::utilities::parse_ts_as_fn_defs( - attr.into(), + attr, vec![], false, vec![], diff --git a/rust/fastsim-core/proc-macros/src/utilities.rs b/rust/fastsim-core/proc-macros/src/utilities.rs index 72baf33e..914d551a 100644 --- a/rust/fastsim-core/proc-macros/src/utilities.rs +++ b/rust/fastsim-core/proc-macros/src/utilities.rs @@ -265,26 +265,23 @@ pub fn parse_ts_as_fn_defs( let sig_str = &item_meth.sig.ident.to_token_stream().to_string(); fn_from_attr.extend(item_meth.clone().to_token_stream()); // check signature - if expected_exclusive { - if forbidden_fn_names.contains(sig_str) || !expected_fn_names.contains(sig_str) - { - emit_error!( - &item_meth.sig.ident.span(), - format!("Function name `{}` is forbidden", sig_str) - ) - } + if expected_exclusive + && (forbidden_fn_names.contains(sig_str) + || !expected_fn_names.contains(sig_str)) + { + emit_error!( + &item_meth.sig.ident.span(), + format!("Function name `{}` is forbidden", sig_str) + ) } let index = expected_fn_names.iter().position(|x| x == sig_str); - match index { - Some(i) => { - expected_fn_names.remove(i); - } - _ => {} - } // remove the matching name from the vec to avoid checking again // at the end of iteration, this vec should be empty + if let Some(i) = index { + expected_fn_names.remove(i); + } } _ => abort_call_site!(ONLY_FN_MSG), } diff --git a/rust/fastsim-core/src/fastsim b/rust/fastsim-core/src/fastsim new file mode 160000 index 00000000..26edd86c --- /dev/null +++ b/rust/fastsim-core/src/fastsim @@ -0,0 +1 @@ +Subproject commit 26edd86c100a03fc31586da2e93f5cfbd4fa4d63 diff --git a/rust/fastsim-core/src/params.rs b/rust/fastsim-core/src/params.rs index 9edda1ac..7a01f7b0 100644 --- a/rust/fastsim-core/src/params.rs +++ b/rust/fastsim-core/src/params.rs @@ -121,7 +121,8 @@ pub const SMALL_BASELINE_EFF: [f64; 11] = [ pub const CHG_EFF: f64 = 0.86; // charger efficiency for PEVs, this should probably not be hard coded long term -#[derive(Debug, Serialize, Deserialize, PartialEq, ApproxEq)] +#[add_pyo3_api] +#[derive(Debug, Serialize, Deserialize, PartialEq, Clone, ApproxEq)] pub struct RustLongParams { #[serde(rename = "rechgFreqMiles")] pub rechg_freq_miles: Vec, @@ -131,12 +132,18 @@ pub struct RustLongParams { pub ld_fe_adj_coef: AdjCoefMap, } -#[derive(Debug, Serialize, Deserialize, PartialEq, ApproxEq)] +impl SerdeAPI for RustLongParams {} + +#[add_pyo3_api] +#[derive(Default, Debug, Serialize, Deserialize, PartialEq, Clone, ApproxEq)] pub struct AdjCoefMap { #[serde(flatten)] pub adj_coef_map: HashMap, } +impl SerdeAPI for AdjCoefMap {} + +#[add_pyo3_api] #[derive(Default, Debug, Serialize, Deserialize, PartialEq, Clone, ApproxEq)] pub struct AdjCoef { #[serde(rename = "City Intercept")] @@ -148,6 +155,7 @@ pub struct AdjCoef { #[serde(rename = "Highway Slope")] pub hwy_slope: f64, } +impl SerdeAPI for AdjCoef {} impl Default for RustLongParams { fn default() -> Self { diff --git a/rust/fastsim-core/src/simdrivelabel.rs b/rust/fastsim-core/src/simdrivelabel.rs index afb9dcae..934a6ebc 100644 --- a/rust/fastsim-core/src/simdrivelabel.rs +++ b/rust/fastsim-core/src/simdrivelabel.rs @@ -1,18 +1,24 @@ //! Module containing classes and methods for calculating label fuel economy. use ndarray::Array; +use serde::Serialize; use std::collections::HashMap; // crate local use crate::cycle::RustCycle; use crate::imports::*; use crate::params::*; +use crate::proc_macros::add_pyo3_api; use crate::proc_macros::ApproxEq; + +#[cfg(feature = "pyo3")] +use crate::pyo3imports::*; + use crate::simdrive::{RustSimDrive, RustSimDriveParams}; use crate::vehicle; +#[add_pyo3_api] #[derive(Default, Serialize, Deserialize, Clone, Debug, PartialEq, ApproxEq)] -/// Label fuel economy values pub struct LabelFe { pub veh: vehicle::RustVehicle, pub adj_params: AdjCoef, @@ -31,9 +37,7 @@ pub struct LabelFe { pub adj_udds_ess_kwh_per_mi: f64, pub adj_hwy_ess_kwh_per_mi: f64, pub adj_comb_ess_kwh_per_mi: f64, - /// Range for combined city/highway pub net_range_miles: f64, - /// Utility factor pub uf: f64, pub net_accel: f64, pub res_found: String, @@ -44,6 +48,9 @@ pub struct LabelFe { pub trace_miss_speed_mph: f64, } +impl SerdeAPI for LabelFe {} + +#[add_pyo3_api] #[derive(Default, Serialize, Deserialize, Clone, Debug, PartialEq, ApproxEq)] /// Label fuel economy values for a PHEV vehicle pub struct LabelFePHEV { @@ -52,6 +59,9 @@ pub struct LabelFePHEV { pub hwy: PHEVCycleCalc, } +impl SerdeAPI for LabelFePHEV {} + +#[add_pyo3_api] #[derive(Default, Serialize, Deserialize, Clone, Debug, PartialEq, ApproxEq)] /// Label fuel economy calculations for a specific cycle of a PHEV vehicle pub struct PHEVCycleCalc { @@ -108,6 +118,8 @@ pub struct PHEVCycleCalc { pub total_cd_miles: f64, } +impl SerdeAPI for PHEVCycleCalc {} + pub fn make_accel_trace() -> RustCycle { let accel_cyc_secs = Array::range(0., 300., 0.1); let mut accel_cyc_mps = Array::ones(accel_cyc_secs.len()) * 90.0 / MPH_PER_MPS; @@ -122,6 +134,13 @@ pub fn make_accel_trace() -> RustCycle { ) } +#[cfg(feature = "pyo3")] +#[pyfunction(name = "make_accel_trace")] +/// pyo3 version of [make_accel_trace] +pub fn make_accel_trace_py() -> RustCycle { + make_accel_trace() +} + pub fn get_net_accel( sd_accel: &mut RustSimDrive, scenario_name: &String, @@ -141,6 +160,14 @@ pub fn get_net_accel( } } +#[cfg(feature = "pyo3")] +#[pyfunction(name = "get_net_accel")] +/// pyo3 version of [get_net_accel] +pub fn get_net_accel_py(sd_accel: &mut RustSimDrive, scenario_name: &str) -> PyResult { + let result = get_net_accel(sd_accel, &scenario_name.to_string())?; + Ok(result) +} + pub fn get_label_fe( veh: &vehicle::RustVehicle, full_detail: Option, @@ -237,9 +264,9 @@ pub fn get_label_fe( for (k, val) in sd.iter_mut() { val.sim_drive(None, None)?; - let key = String::from(*k); + let key = k; let trace_miss_speed_mph = val.trace_miss_speed_mps * MPH_PER_MPS; - if (key == *"udds" || key == *"hwy") && trace_miss_speed_mph > max_trace_miss_in_mph { + if (key == &"udds" || key == &"hwy") && trace_miss_speed_mph > max_trace_miss_in_mph { max_trace_miss_in_mph = trace_miss_speed_mph; } } @@ -412,6 +439,19 @@ pub fn get_label_fe( } } +#[cfg(feature = "pyo3")] +#[pyfunction(name = "get_label_fe")] +/// pyo3 version of [get_label_fe] +pub fn get_label_fe_py( + veh: &vehicle::RustVehicle, + full_detail: Option, + verbose: Option, +) -> PyResult<(LabelFe, Option>)> { + let result: (LabelFe, Option>) = + get_label_fe(veh, full_detail, verbose)?; + Ok(result) +} + pub fn get_label_fe_phev( veh: &vehicle::RustVehicle, sd: &mut HashMap<&str, RustSimDrive>, @@ -442,7 +482,7 @@ pub fn get_label_fe_phev( / veh.ess_max_kwh, (veh.max_soc - veh.min_soc) / 2.0, ), - ..LabelFePHEV::default() + ..Default::default() }; // charge sustaining behavior @@ -581,17 +621,18 @@ pub fn get_label_fe_phev( * (1.0 - sim_params.max_epa_adj), )); - for (c, lab_iter_kwh_per_mi) in phev_calc.lab_iter_kwh_per_mi.iter().enumerate() { - if *lab_iter_kwh_per_mi == 0.0 { + for (c, _) in phev_calc.lab_iter_kwh_per_mi.iter().enumerate() { + if phev_calc.lab_iter_kwh_per_mi[c] == 0.0 { adj_iter_kwh_per_mi_vals[c] = 0.0; } else { adj_iter_kwh_per_mi_vals[c] = (1.0 / max( 1.0 / (adj_params.city_intercept + (adj_params.city_slope - / ((1.0 / lab_iter_kwh_per_mi) * props.kwh_per_gge))), + / ((1.0 / phev_calc.lab_iter_kwh_per_mi[c]) + * props.kwh_per_gge))), (1.0 - sim_params.max_epa_adj) - * ((1.0 / lab_iter_kwh_per_mi) * props.kwh_per_gge), + * ((1.0 / phev_calc.lab_iter_kwh_per_mi[c]) * props.kwh_per_gge), )) * props.kwh_per_gge; } } @@ -611,17 +652,18 @@ pub fn get_label_fe_phev( * (1.0 - sim_params.max_epa_adj), )); - for (c, lab_iter_kwh_per_mi) in phev_calc.lab_iter_kwh_per_mi.iter().enumerate() { - if *lab_iter_kwh_per_mi == 0.0 { + for (c, _) in phev_calc.lab_iter_kwh_per_mi.iter().enumerate() { + if phev_calc.lab_iter_kwh_per_mi[c] == 0.0 { adj_iter_kwh_per_mi_vals[c] = 0.0; } else { adj_iter_kwh_per_mi_vals[c] = (1.0 / max( 1.0 / (adj_params.hwy_intercept + (adj_params.hwy_slope - / ((1.0 / lab_iter_kwh_per_mi) * props.kwh_per_gge))), + / ((1.0 / phev_calc.lab_iter_kwh_per_mi[c]) + * props.kwh_per_gge))), (1.0 - sim_params.max_epa_adj) - * ((1.0 / lab_iter_kwh_per_mi) * props.kwh_per_gge), + * ((1.0 / phev_calc.lab_iter_kwh_per_mi[c]) * props.kwh_per_gge), )) * props.kwh_per_gge; } } @@ -707,6 +749,33 @@ pub fn get_label_fe_phev( Ok(phev_calcs) } +#[cfg(feature = "pyo3")] +#[pyfunction(name = "get_label_fe_phev")] + +/// pyo3 version of [get_label_fe_phev] +pub fn get_label_fe_phev_py( + veh: &vehicle::RustVehicle, + sd: HashMap<&str, RustSimDrive>, + adj_params: AdjCoef, + long_params: RustLongParams, + sim_params: &RustSimDriveParams, + props: RustPhysicalProperties, +) -> Result { + let mut sd_mut = HashMap::new(); + for (key, value) in sd { + sd_mut.insert(key, value); + } + + get_label_fe_phev( + veh, + &mut sd_mut, + &long_params, + &adj_params, + sim_params, + &props, + ) +} + #[cfg(test)] mod simdrivelabel_tests { use super::*; @@ -756,374 +825,382 @@ mod simdrivelabel_tests { assert!(label_fe.approx_eq(&label_fe_truth, 1e-10)); } - - #[test] - fn test_get_label_fe_phev() { - let mut veh = vehicle::RustVehicle { - props: RustPhysicalProperties { - air_density_kg_per_m3: 1.2, - a_grav_mps2: 9.81, - kwh_per_gge: 33.7, - fuel_rho_kg__L: 0.75, - fuel_afr_stoich: 14.7, - orphaned: false, - }, - veh_kg: Default::default(), - scenario_name: "2016 Chevrolet Volt".into(), - selection: 13, - veh_year: 2016, - veh_pt_type: "PHEV".into(), - drag_coef: 0.3, - frontal_area_m2: 2.565, - glider_kg: 950.564, - veh_cg_m: 0.53, - drive_axle_weight_frac: 0.59, - wheel_base_m: 2.6, - cargo_kg: 136.0, - veh_override_kg: None, - comp_mass_multiplier: 1.4, - fs_max_kw: 2000.0, - fs_secs_to_peak_pwr: 1.0, - fs_kwh: 297.0, - fs_kwh_per_kg: 9.89, - fc_max_kw: 75.0, - fc_pwr_out_perc: Array1::from(vec![ - 0.0, 0.005, 0.015, 0.04, 0.06, 0.1, 0.14, 0.2, 0.4, 0.6, 0.8, 1.0, - ]), - fc_eff_map: Array1::from(vec![ - 0.1, 0.12, 0.16, 0.22, 0.28, 0.33, 0.35, 0.36, 0.35, 0.34, 0.32, 0.3, - ]), - fc_eff_type: "SI".into(), - fc_sec_to_peak_pwr: 6.0, - fc_base_kg: 61.0, - fc_kw_per_kg: 2.13, - min_fc_time_on: 30.0, - idle_fc_kw: 1.5, - mc_max_kw: 111.0, - mc_pwr_out_perc: Array1::from(vec![ - 0.0, 0.02, 0.04, 0.06, 0.08, 0.1, 0.2, 0.4, 0.6, 0.8, 1.0, - ]), - mc_eff_map: Array1::from(vec![ - 0.84, 0.86, 0.88, 0.9, 0.91, 0.92, 0.94, 0.95, 0.95, 0.94, 0.93, - ]), - mc_sec_to_peak_pwr: 3.0, - mc_pe_kg_per_kw: 0.833, - mc_pe_base_kg: 21.6, - ess_max_kw: 115.0, - ess_max_kwh: 18.4, - ess_kg_per_kwh: 8.0, - ess_base_kg: 75.0, - ess_round_trip_eff: 0.97, - ess_life_coef_a: 110.0, - ess_life_coef_b: -0.6811, - min_soc: 0.15, - max_soc: 0.9, - ess_dischg_to_fc_max_eff_perc: 1.0, - ess_chg_to_fc_max_eff_perc: 0.0, - wheel_inertia_kg_m2: 0.815, - num_wheels: 4.0, - wheel_rr_coef: 0.007, - wheel_radius_m: 0.336, - wheel_coef_of_fric: 0.7, - max_accel_buffer_mph: 60.0, - max_accel_buffer_perc_of_useable_soc: 0.2, - perc_high_acc_buf: 0.0, - mph_fc_on: 85.0, - kw_demand_fc_on: 120.0, - max_regen: 0.98, - stop_start: false, - force_aux_on_fc: false, - alt_eff: 1.0, - chg_eff: 0.86, - aux_kw: 0.3, - trans_kg: 114.0, - trans_eff: 0.98, - ess_to_fuel_ok_error: 0.005, - small_motor_power_kw: 7.5, - large_motor_power_kw: 75.0, - fc_perc_out_array: FC_PERC_OUT_ARRAY.into(), - mc_kw_out_array: Default::default(), - mc_max_elec_in_kw: Default::default(), - mc_full_eff_array: Default::default(), - max_trac_mps2: Default::default(), - ess_mass_kg: Default::default(), - mc_mass_kg: Default::default(), - fc_mass_kg: Default::default(), - fs_mass_kg: Default::default(), - mc_perc_out_array: Default::default(), - regen_a: 500.0, - regen_b: 0.99, - charging_on: false, - no_elec_sys: false, - no_elec_aux: false, - max_roadway_chg_kw: Array1::from(vec![0.0, 0.0, 0.0, 0.0, 0.0, 0.0]), - input_kw_out_array: Array1::from(vec![ - 0.0, - 0.375, - 1.125, - 3.0, - 4.5, - 7.5, - 10.500000000000002, - 15.0, - 30.0, - 45.0, - 60.0, - 75.0, - ]), - fc_kw_out_array: Default::default(), - fc_eff_array: Default::default(), - modern_max: 0.95, - mc_eff_array: Array1::from(vec![ - 0.84, 0.86, 0.88, 0.9, 0.91, 0.92, 0.94, 0.95, 0.95, 0.94, 0.93, - ]), - mc_kw_in_array: Default::default(), - val_udds_mpgge: f64::NAN, - val_hwy_mpgge: f64::NAN, - val_comb_mpgge: 42.0, - val_udds_kwh_per_mile: f64::NAN, - val_hwy_kwh_per_mile: f64::NAN, - val_comb_kwh_per_mile: 0.31, - val_cd_range_mi: 53.0, - val_const65_mph_kwh_per_mile: f64::NAN, - val_const60_mph_kwh_per_mile: f64::NAN, - val_const55_mph_kwh_per_mile: f64::NAN, - val_const45_mph_kwh_per_mile: f64::NAN, - val_unadj_udds_kwh_per_mile: f64::NAN, - val_unadj_hwy_kwh_per_mile: f64::NAN, - val0_to60_mph: 8.4, - val_ess_life_miles: 120000.0, - val_range_miles: f64::NAN, - val_veh_base_cost: 17000.0, - val_msrp: 33170.0, - fc_peak_eff_override: None, - mc_peak_eff_override: None, +} +#[cfg(feature = "pyo3")] +#[pyfunction(name = "get_label_fe_conv")] +/// pyo3 version of [get_label_fe_conv] +pub fn get_label_fe_conv_py() -> LabelFe { + let veh: vehicle::RustVehicle = vehicle::RustVehicle::mock_vehicle(); + let (mut label_fe, _) = get_label_fe(&veh, None, None).unwrap(); + label_fe.veh = vehicle::RustVehicle::default(); + label_fe +} +#[test] +fn test_get_label_fe_phev() { + let mut veh = vehicle::RustVehicle { + props: RustPhysicalProperties { + air_density_kg_per_m3: 1.2, + a_grav_mps2: 9.81, + kwh_per_gge: 33.7, + fuel_rho_kg__L: 0.75, + fuel_afr_stoich: 14.7, orphaned: false, - }; - veh.set_derived().unwrap(); - - let (mut label_fe, _) = get_label_fe(&veh, None, None).unwrap(); - // For some reason, RustVehicle::mock_vehicle() != RustVehicle::mock_vehicle() - // Therefore, veh field in both structs replaced with Default for comparison purposes - label_fe.veh = vehicle::RustVehicle::default(); - // TODO: Figure out why net_accel values are different - println!("Calculated net accel: {}", label_fe.net_accel); - println!( - "Percent diff to Python calc: {:.3}%", - 100. * (9.451683946821882 - label_fe.net_accel) / 9.451683946821882 - ); - label_fe.net_accel = 1000.; - - let udds: PHEVCycleCalc = PHEVCycleCalc { - cd_ess_kwh: 13.799999999999999, - cd_ess_kwh_per_mi: 0.1670807863534209, - cd_fs_gal: 0.0, - cd_fs_kwh: 0.0, - cd_mpg: 65.0128437991813, - cd_cycs: 11.083418864860784, - cd_miles: 89.42523198551896, - cd_lab_mpg: 59.77814990568397, - cd_adj_mpg: 2968.1305812156647, - cd_frac_in_trans: 0.08341886486078387, - trans_init_soc: 0.15564484203010176, - trans_ess_kwh: 0.10386509335387073, - trans_ess_kwh_per_mi: 0.013937689537649522, - trans_fs_gal: 0.105063189381161, - trans_fs_kwh: 3.5406294821451265, - cs_ess_kwh: -27.842875966770062, - cs_ess_kwh_per_mi: -0.001037845633667792, - cs_fs_gal: 0.11462508375235472, - cs_fs_kwh: 3.8628653224543545, - cs_mpg: 65.01284379918131, - lab_mpgge: 370.06411942132064, - lab_kwh_per_mi: 0.16342111007981494, - lab_uf: 0.8427800000000001, - lab_uf_gpm: Array::from_vec(vec![0.00028394, 0.00241829]), - lab_iter_uf: Array::from_vec(vec![ - 0., 0.16268, 0.28152, 0.41188, 0.51506, 0.59611, 0.64532, 0.69897, 0.74176, - 0.77648, 0.79825, 0.82264, 0.84278, - ]), - lab_iter_uf_kwh_per_mi: Array::from_vec(vec![ - 0., 0.0271807, 0.01985588, 0.02178065, 0.0172394, 0.0135419, 0.00822205, - 0.00896388, 0.00714939, 0.00580104, 0.00363735, 0.0040751, 0.00028071, 0., - ]), - lab_iter_kwh_per_mi: Array::from_vec(vec![ - 0., 0.16708079, 0.16708079, 0.16708079, 0.16708079, 0.16708079, 0.16708079, - 0.16708079, 0.16708079, 0.16708079, 0.16708079, 0.16708079, 0.01393769, 0., - ]), - adj_iter_mpgge: Array::from_vec(vec![ - 0., - 0., - 0., - 0., - 0., - 0., - 0., - 0., - 0., - 0., - 0., - 50.2456134, - 46.69198818, - ]), - adj_iter_kwh_per_mi: Array::from_vec(vec![ - 0., 0.23868684, 0.23868684, 0.23868684, 0.23868684, 0.23868684, 0.23868684, - 0.23868684, 0.23868684, 0.23868684, 0.23868684, 0.23868684, 0.01991099, 0., - ]), - adj_iter_cd_miles: Array::from_vec(vec![ - 0., - 5.21647187, - 10.43294373, - 15.6494156, - 20.86588746, - 26.08235933, - 31.29883119, - 36.51530306, - 41.73177493, - 46.94824679, - 52.16471866, - 57.38119052, - 62.59766239, - 0., - ]), - adj_iter_uf: Array::from_vec(vec![ - 0., 0.11878, 0.2044, 0.31698, 0.38194, 0.46652, 0.53737, 0.57771, 0.62998, 0.6599, - 0.69897, 0.73185, 0.75126, 0., - ]), - adj_iter_uf_gpm: vec![ - 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.0003863, 0.00532725, - ], - adj_iter_uf_kwh_per_mi: Array::from_vec(vec![ - 0., 0.02835122, 0.02043637, 0.02687136, 0.0155051, 0.02018813, 0.01691096, - 0.00962863, 0.01247616, 0.00714151, 0.00932549, 0.00784802, 0.00038647, 0., - ]), - adj_cd_miles: 62.59766238986325, - adj_cd_mpgge: 1944.7459827561047, - adj_cs_mpgge: 46.69198818435928, - adj_uf: 0.75126, - adj_mpgge: 175.0223917643415, - adj_kwh_per_mi: 0.27097024959679444, - adj_ess_kwh_per_mi: 0.23303441465324323, - delta_soc: 0.0676686507245362, - total_cd_miles: 82.59477526523773, - }; + }, + veh_kg: Default::default(), + scenario_name: "2016 Chevrolet Volt".into(), + selection: 13, + veh_year: 2016, + veh_pt_type: "PHEV".into(), + drag_coef: 0.3, + frontal_area_m2: 2.565, + glider_kg: 950.564, + veh_cg_m: 0.53, + drive_axle_weight_frac: 0.59, + wheel_base_m: 2.6, + cargo_kg: 136.0, + veh_override_kg: None, + comp_mass_multiplier: 1.4, + fs_max_kw: 2000.0, + fs_secs_to_peak_pwr: 1.0, + fs_kwh: 297.0, + fs_kwh_per_kg: 9.89, + fc_max_kw: 75.0, + fc_pwr_out_perc: Array1::from(vec![ + 0.0, 0.005, 0.015, 0.04, 0.06, 0.1, 0.14, 0.2, 0.4, 0.6, 0.8, 1.0, + ]), + fc_eff_map: Array1::from(vec![ + 0.1, 0.12, 0.16, 0.22, 0.28, 0.33, 0.35, 0.36, 0.35, 0.34, 0.32, 0.3, + ]), + fc_eff_type: "SI".into(), + fc_sec_to_peak_pwr: 6.0, + fc_base_kg: 61.0, + fc_kw_per_kg: 2.13, + min_fc_time_on: 30.0, + idle_fc_kw: 1.5, + mc_max_kw: 111.0, + mc_pwr_out_perc: Array1::from(vec![ + 0.0, 0.02, 0.04, 0.06, 0.08, 0.1, 0.2, 0.4, 0.6, 0.8, 1.0, + ]), + mc_eff_map: Array1::from(vec![ + 0.84, 0.86, 0.88, 0.9, 0.91, 0.92, 0.94, 0.95, 0.95, 0.94, 0.93, + ]), + mc_sec_to_peak_pwr: 3.0, + mc_pe_kg_per_kw: 0.833, + mc_pe_base_kg: 21.6, + ess_max_kw: 115.0, + ess_max_kwh: 18.4, + ess_kg_per_kwh: 8.0, + ess_base_kg: 75.0, + ess_round_trip_eff: 0.97, + ess_life_coef_a: 110.0, + ess_life_coef_b: -0.6811, + min_soc: 0.15, + max_soc: 0.9, + ess_dischg_to_fc_max_eff_perc: 1.0, + ess_chg_to_fc_max_eff_perc: 0.0, + wheel_inertia_kg_m2: 0.815, + num_wheels: 4.0, + wheel_rr_coef: 0.007, + wheel_radius_m: 0.336, + wheel_coef_of_fric: 0.7, + max_accel_buffer_mph: 60.0, + max_accel_buffer_perc_of_useable_soc: 0.2, + perc_high_acc_buf: 0.0, + mph_fc_on: 85.0, + kw_demand_fc_on: 120.0, + max_regen: 0.98, + stop_start: false, + force_aux_on_fc: false, + alt_eff: 1.0, + chg_eff: 0.86, + aux_kw: 0.3, + trans_kg: 114.0, + trans_eff: 0.98, + ess_to_fuel_ok_error: 0.005, + small_motor_power_kw: 7.5, + large_motor_power_kw: 75.0, + fc_perc_out_array: FC_PERC_OUT_ARRAY.into(), + mc_kw_out_array: Default::default(), + mc_max_elec_in_kw: Default::default(), + mc_full_eff_array: Default::default(), + max_trac_mps2: Default::default(), + ess_mass_kg: Default::default(), + mc_mass_kg: Default::default(), + fc_mass_kg: Default::default(), + fs_mass_kg: Default::default(), + mc_perc_out_array: Default::default(), + regen_a: 500.0, + regen_b: 0.99, + charging_on: false, + no_elec_sys: false, + no_elec_aux: false, + max_roadway_chg_kw: Array1::from(vec![0.0, 0.0, 0.0, 0.0, 0.0, 0.0]), + input_kw_out_array: Array1::from(vec![ + 0.0, + 0.375, + 1.125, + 3.0, + 4.5, + 7.5, + 10.500000000000002, + 15.0, + 30.0, + 45.0, + 60.0, + 75.0, + ]), + fc_kw_out_array: Default::default(), + fc_eff_array: Default::default(), + modern_max: 0.95, + mc_eff_array: Array1::from(vec![ + 0.84, 0.86, 0.88, 0.9, 0.91, 0.92, 0.94, 0.95, 0.95, 0.94, 0.93, + ]), + mc_kw_in_array: Default::default(), + val_udds_mpgge: f64::NAN, + val_hwy_mpgge: f64::NAN, + val_comb_mpgge: 42.0, + val_udds_kwh_per_mile: f64::NAN, + val_hwy_kwh_per_mile: f64::NAN, + val_comb_kwh_per_mile: 0.31, + val_cd_range_mi: 53.0, + val_const65_mph_kwh_per_mile: f64::NAN, + val_const60_mph_kwh_per_mile: f64::NAN, + val_const55_mph_kwh_per_mile: f64::NAN, + val_const45_mph_kwh_per_mile: f64::NAN, + val_unadj_udds_kwh_per_mile: f64::NAN, + val_unadj_hwy_kwh_per_mile: f64::NAN, + val0_to60_mph: 8.4, + val_ess_life_miles: 120000.0, + val_range_miles: f64::NAN, + val_veh_base_cost: 17000.0, + val_msrp: 33170.0, + fc_peak_eff_override: None, + mc_peak_eff_override: None, + orphaned: false, + }; + veh.set_derived().unwrap(); + + let (mut label_fe, _) = get_label_fe(&veh, None, None).unwrap(); + // For some reason, RustVehicle::mock_vehicle() != RustVehicle::mock_vehicle() + // Therefore, veh field in both structs replaced with Default for comparison purposes + label_fe.veh = vehicle::RustVehicle::default(); + // TODO: Figure out why net_accel values are different + println!("Calculated net accel: {}", label_fe.net_accel); + println!( + "Percent diff to Python calc: {:.3}%", + 100. * (9.451683946821882 - label_fe.net_accel) / 9.451683946821882 + ); + label_fe.net_accel = 1000.; + + let udds: PHEVCycleCalc = PHEVCycleCalc { + cd_ess_kwh: 13.799999999999999, + cd_ess_kwh_per_mi: 0.1670807863534209, + cd_fs_gal: 0.0, + cd_fs_kwh: 0.0, + cd_mpg: 65.0128437991813, + cd_cycs: 11.083418864860784, + cd_miles: 89.42523198551896, + cd_lab_mpg: 59.77814990568397, + cd_adj_mpg: 2968.1305812156647, + cd_frac_in_trans: 0.08341886486078387, + trans_init_soc: 0.15564484203010176, + trans_ess_kwh: 0.10386509335387073, + trans_ess_kwh_per_mi: 0.013937689537649522, + trans_fs_gal: 0.105063189381161, + trans_fs_kwh: 3.5406294821451265, + cs_ess_kwh: -27.842875966770062, + cs_ess_kwh_per_mi: -0.001037845633667792, + cs_fs_gal: 0.11462508375235472, + cs_fs_kwh: 3.8628653224543545, + cs_mpg: 65.01284379918131, + lab_mpgge: 370.06411942132064, + lab_kwh_per_mi: 0.16342111007981494, + lab_uf: 0.8427800000000001, + lab_uf_gpm: Array::from_vec(vec![0.00028394, 0.00241829]), + lab_iter_uf: Array::from_vec(vec![ + 0., 0.16268, 0.28152, 0.41188, 0.51506, 0.59611, 0.64532, 0.69897, 0.74176, 0.77648, + 0.79825, 0.82264, 0.84278, + ]), + lab_iter_uf_kwh_per_mi: Array::from_vec(vec![ + 0., 0.0271807, 0.01985588, 0.02178065, 0.0172394, 0.0135419, 0.00822205, 0.00896388, + 0.00714939, 0.00580104, 0.00363735, 0.0040751, 0.00028071, 0., + ]), + lab_iter_kwh_per_mi: Array::from_vec(vec![ + 0., 0.16708079, 0.16708079, 0.16708079, 0.16708079, 0.16708079, 0.16708079, 0.16708079, + 0.16708079, 0.16708079, 0.16708079, 0.16708079, 0.01393769, 0., + ]), + adj_iter_mpgge: Array::from_vec(vec![ + 0., + 0., + 0., + 0., + 0., + 0., + 0., + 0., + 0., + 0., + 0., + 50.2456134, + 46.69198818, + ]), + adj_iter_kwh_per_mi: Array::from_vec(vec![ + 0., 0.23868684, 0.23868684, 0.23868684, 0.23868684, 0.23868684, 0.23868684, 0.23868684, + 0.23868684, 0.23868684, 0.23868684, 0.23868684, 0.01991099, 0., + ]), + adj_iter_cd_miles: Array::from_vec(vec![ + 0., + 5.21647187, + 10.43294373, + 15.6494156, + 20.86588746, + 26.08235933, + 31.29883119, + 36.51530306, + 41.73177493, + 46.94824679, + 52.16471866, + 57.38119052, + 62.59766239, + 0., + ]), + adj_iter_uf: Array::from_vec(vec![ + 0., 0.11878, 0.2044, 0.31698, 0.38194, 0.46652, 0.53737, 0.57771, 0.62998, 0.6599, + 0.69897, 0.73185, 0.75126, 0., + ]), + adj_iter_uf_gpm: vec![ + 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.0003863, 0.00532725, + ], + adj_iter_uf_kwh_per_mi: Array::from_vec(vec![ + 0., 0.02835122, 0.02043637, 0.02687136, 0.0155051, 0.02018813, 0.01691096, 0.00962863, + 0.01247616, 0.00714151, 0.00932549, 0.00784802, 0.00038647, 0., + ]), + adj_cd_miles: 62.59766238986325, + adj_cd_mpgge: 1944.7459827561047, + adj_cs_mpgge: 46.69198818435928, + adj_uf: 0.75126, + adj_mpgge: 175.0223917643415, + adj_kwh_per_mi: 0.27097024959679444, + adj_ess_kwh_per_mi: 0.23303441465324323, + delta_soc: 0.0676686507245362, + total_cd_miles: 82.59477526523773, + }; - let hwy: PHEVCycleCalc = PHEVCycleCalc { - cd_ess_kwh: 13.799999999999999, - cd_ess_kwh_per_mi: 0.19912462736394723, - cd_fs_gal: 0.0, - cd_fs_kwh: 0.0, - cd_mpg: 61.75832757157714, - cd_cycs: 6.75533367335913, - cd_miles: 71.81337619240335, - cd_lab_mpg: 199.76659107309018, - cd_adj_mpg: 4975.506626976092, - cd_frac_in_trans: 0.7553336733591296, - trans_init_soc: 0.23385969996618272, - trans_ess_kwh: 1.5430184793777608, - trans_ess_kwh_per_mi: 0.15040553624307812, - trans_fs_gal: 0.040643020828268026, - trans_fs_kwh: 1.3696698019126325, - cs_ess_kwh: -27.84287564320177, - cs_ess_kwh_per_mi: -0.0007538835761840731, - cs_fs_gal: 0.1661161198039534, - cs_fs_kwh: 5.59811323739323, - cs_mpg: 61.75832757157714, - lab_mpgge: 282.75893721314793, - lab_kwh_per_mi: 0.19665299886733625, - lab_uf: 0.7914100000000001, - lab_uf_gpm: Array::from_vec(vec![0.00015906, 0.00337752]), - lab_iter_uf: Array::from_vec(vec![ - 0., 0.2044, 0.38194, 0.51506, 0.62998, 0.69897, 0.75126, 0.79141, - ]), - lab_iter_uf_kwh_per_mi: Array::from_vec(vec![ - 0., 0.04070107, 0.03535259, 0.02650747, 0.0228834, 0.01373761, 0.01041223, - 0.00603878, 0., - ]), - lab_iter_kwh_per_mi: Array::from_vec(vec![ - 0., 0.19912463, 0.19912463, 0.19912463, 0.19912463, 0.19912463, 0.19912463, - 0.15040554, 0., - ]), - adj_iter_mpgge: Array::from_vec(vec![0., 0., 0., 0., 0., 0., 176.69300837, 43.2308293]), - adj_iter_kwh_per_mi: Array::from_vec(vec![ - 0., 0.28446375, 0.28446375, 0.28446375, 0.28446375, 0.28446375, 0.28446375, - 0.21486505, 0., - ]), - adj_iter_cd_miles: Array::from_vec(vec![ - 0., - 7.18133762, - 14.36267524, - 21.54401286, - 28.72535048, - 35.9066881, - 43.08802572, - 50.26936333, - 0., - ]), - adj_iter_uf: Array::from_vec(vec![ - 0., 0.16268, 0.28152, 0.41188, 0.49148, 0.57771, 0.64532, 0.68662, 0., - ]), - adj_iter_uf_gpm: vec![0., 0., 0., 0., 0., 0., 0.00023374, 0.00724899], - adj_iter_uf_kwh_per_mi: Array::from_vec(vec![ - 0., 0.04627656, 0.03380567, 0.03708269, 0.02264331, 0.02452931, 0.01923259, - 0.00887393, 0., - ]), - adj_cd_miles: 50.26936333468235, - adj_cd_mpgge: 2937.5533511975764, - adj_cs_mpgge: 43.230829300104, - adj_uf: 0.68662, - adj_mpgge: 133.64102451254365, - adj_kwh_per_mi: 0.3259039663244739, - adj_ess_kwh_per_mi: 0.2802774110390475, - delta_soc: 0.11102338333896955, - total_cd_miles: 69.30333119859274, - }; + let hwy: PHEVCycleCalc = PHEVCycleCalc { + cd_ess_kwh: 13.799999999999999, + cd_ess_kwh_per_mi: 0.19912462736394723, + cd_fs_gal: 0.0, + cd_fs_kwh: 0.0, + cd_mpg: 61.75832757157714, + cd_cycs: 6.75533367335913, + cd_miles: 71.81337619240335, + cd_lab_mpg: 199.76659107309018, + cd_adj_mpg: 4975.506626976092, + cd_frac_in_trans: 0.7553336733591296, + trans_init_soc: 0.23385969996618272, + trans_ess_kwh: 1.5430184793777608, + trans_ess_kwh_per_mi: 0.15040553624307812, + trans_fs_gal: 0.040643020828268026, + trans_fs_kwh: 1.3696698019126325, + cs_ess_kwh: -27.84287564320177, + cs_ess_kwh_per_mi: -0.0007538835761840731, + cs_fs_gal: 0.1661161198039534, + cs_fs_kwh: 5.59811323739323, + cs_mpg: 61.75832757157714, + lab_mpgge: 282.75893721314793, + lab_kwh_per_mi: 0.19665299886733625, + lab_uf: 0.7914100000000001, + lab_uf_gpm: Array::from_vec(vec![0.00015906, 0.00337752]), + lab_iter_uf: Array::from_vec(vec![ + 0., 0.2044, 0.38194, 0.51506, 0.62998, 0.69897, 0.75126, 0.79141, + ]), + lab_iter_uf_kwh_per_mi: Array::from_vec(vec![ + 0., 0.04070107, 0.03535259, 0.02650747, 0.0228834, 0.01373761, 0.01041223, 0.00603878, + 0., + ]), + lab_iter_kwh_per_mi: Array::from_vec(vec![ + 0., 0.19912463, 0.19912463, 0.19912463, 0.19912463, 0.19912463, 0.19912463, 0.15040554, + 0., + ]), + adj_iter_mpgge: Array::from_vec(vec![0., 0., 0., 0., 0., 0., 176.69300837, 43.2308293]), + adj_iter_kwh_per_mi: Array::from_vec(vec![ + 0., 0.28446375, 0.28446375, 0.28446375, 0.28446375, 0.28446375, 0.28446375, 0.21486505, + 0., + ]), + adj_iter_cd_miles: Array::from_vec(vec![ + 0., + 7.18133762, + 14.36267524, + 21.54401286, + 28.72535048, + 35.9066881, + 43.08802572, + 50.26936333, + 0., + ]), + adj_iter_uf: Array::from_vec(vec![ + 0., 0.16268, 0.28152, 0.41188, 0.49148, 0.57771, 0.64532, 0.68662, 0., + ]), + adj_iter_uf_gpm: vec![0., 0., 0., 0., 0., 0., 0.00023374, 0.00724899], + adj_iter_uf_kwh_per_mi: Array::from_vec(vec![ + 0., 0.04627656, 0.03380567, 0.03708269, 0.02264331, 0.02452931, 0.01923259, 0.00887393, + 0., + ]), + adj_cd_miles: 50.26936333468235, + adj_cd_mpgge: 2937.5533511975764, + adj_cs_mpgge: 43.230829300104, + adj_uf: 0.68662, + adj_mpgge: 133.64102451254365, + adj_kwh_per_mi: 0.3259039663244739, + adj_ess_kwh_per_mi: 0.2802774110390475, + delta_soc: 0.11102338333896955, + total_cd_miles: 69.30333119859274, + }; - let phev_calcs: LabelFePHEV = LabelFePHEV { - regen_soc_buffer: 0.00957443430586049, - udds, - hwy, - }; + let phev_calcs: LabelFePHEV = LabelFePHEV { + regen_soc_buffer: 0.00957443430586049, + udds, + hwy, + }; - let label_fe_truth: LabelFe = LabelFe { - veh: vehicle::RustVehicle::default(), - adj_params: RustLongParams::default().ld_fe_adj_coef.adj_coef_map["2008"].clone(), - lab_udds_mpgge: 370.06411942132064, - lab_hwy_mpgge: 282.75893721314793, - lab_comb_mpgge: 324.91895455274005, - lab_udds_kwh_per_mi: 0.16342111007981494, - lab_hwy_kwh_per_mi: 0.19665299886733625, - lab_comb_kwh_per_mi: 0.17837546003419952, - adj_udds_mpgge: 175.0223917643415, - adj_hwy_mpgge: 133.64102451254365, - adj_comb_mpgge: 153.61727461480555, - adj_udds_kwh_per_mi: 0.27097024959679444, - adj_hwy_kwh_per_mi: 0.3259039663244739, - adj_comb_kwh_per_mi: 0.29569042212425023, - adj_udds_ess_kwh_per_mi: 0.23303441465324323, - adj_hwy_ess_kwh_per_mi: 0.2802774110390475, - adj_comb_ess_kwh_per_mi: 0.25429376302685514, - net_range_miles: 453.1180867180584, - uf: 0.73185, - // net_accel: 7.962519496024332, <- Correct accel value - net_accel: 1000., - res_found: String::from("model needs to be implemented for this"), - phev_calcs: Some(phev_calcs), - adj_cs_comb_mpgge: Some(45.06826741586106), - adj_cd_comb_mpgge: Some(2293.5675017498143), - net_phev_cd_miles: Some(57.04992781503185), - trace_miss_speed_mph: 0.0, - }; + let label_fe_truth: LabelFe = LabelFe { + veh: vehicle::RustVehicle::default(), + adj_params: RustLongParams::default().ld_fe_adj_coef.adj_coef_map["2008"].clone(), + lab_udds_mpgge: 370.06411942132064, + lab_hwy_mpgge: 282.75893721314793, + lab_comb_mpgge: 324.91895455274005, + lab_udds_kwh_per_mi: 0.16342111007981494, + lab_hwy_kwh_per_mi: 0.19665299886733625, + lab_comb_kwh_per_mi: 0.17837546003419952, + adj_udds_mpgge: 175.0223917643415, + adj_hwy_mpgge: 133.64102451254365, + adj_comb_mpgge: 153.61727461480555, + adj_udds_kwh_per_mi: 0.27097024959679444, + adj_hwy_kwh_per_mi: 0.3259039663244739, + adj_comb_kwh_per_mi: 0.29569042212425023, + adj_udds_ess_kwh_per_mi: 0.23303441465324323, + adj_hwy_ess_kwh_per_mi: 0.2802774110390475, + adj_comb_ess_kwh_per_mi: 0.25429376302685514, + net_range_miles: 453.1180867180584, + uf: 0.73185, + // net_accel: 7.962519496024332, <- Correct accel value + net_accel: 1000., + res_found: String::from("model needs to be implemented for this"), + phev_calcs: Some(phev_calcs), + adj_cs_comb_mpgge: Some(45.06826741586106), + adj_cd_comb_mpgge: Some(2293.5675017498143), + net_phev_cd_miles: Some(57.04992781503185), + trace_miss_speed_mph: 0.0, + }; - let tol = 1e-8; - assert!(label_fe.veh.approx_eq(&label_fe_truth.veh, tol)); - assert!( - label_fe - .phev_calcs - .approx_eq(&label_fe_truth.phev_calcs, tol), - "label_fe.phev_calcs: {:?}", - &label_fe.phev_calcs - ); - assert!(label_fe.approx_eq(&label_fe_truth, tol)); - } + let tol = 1e-8; + assert!(label_fe.veh.approx_eq(&label_fe_truth.veh, tol)); + assert!( + label_fe + .phev_calcs + .approx_eq(&label_fe_truth.phev_calcs, tol), + "label_fe.phev_calcs: {:?}", + &label_fe.phev_calcs + ); + assert!(label_fe.approx_eq(&label_fe_truth, tol)); } diff --git a/rust/fastsim-core/src/vehicle.rs b/rust/fastsim-core/src/vehicle.rs index d0d6349e..53d5e510 100644 --- a/rust/fastsim-core/src/vehicle.rs +++ b/rust/fastsim-core/src/vehicle.rs @@ -37,6 +37,8 @@ lazy_static! { #[add_pyo3_api( #[pyo3(name = "set_veh_mass")] pub fn set_veh_mass_py(&mut self) { + // TODO: not urgent, but I think it'd better for all instances + // of `set_veh_mass` to be `update_veh_mass` self.set_veh_mass() } @@ -49,17 +51,9 @@ lazy_static! { self.mc_peak_eff() } - // TODO: refactor this to have a non-py and `_py` version - #[setter] - pub fn set_mc_peak_eff(&mut self, new_peak: f64) { - let mc_max_eff = ndarrmax(&self.mc_eff_array); - self.mc_eff_array *= new_peak / mc_max_eff; - let mc_max_full_eff = arrmax(&self.mc_full_eff_array); - self.mc_full_eff_array = self - .mc_full_eff_array - .iter() - .map(|e: &f64| -> f64 { e * (new_peak / mc_max_full_eff) }) - .collect(); + #[setter("mc_peak_eff")] + pub fn set_mc_peak_eff_py(&mut self, new_peak: f64) { + self.set_mc_peak_eff(new_peak); } #[getter] @@ -67,27 +61,16 @@ lazy_static! { self.max_fc_eff_kw() } + #[setter("fc_peak_eff")] + pub fn set_fc_peak_eff_py(&mut self, new_peak: f64) { + self.set_fc_peak_eff(new_peak); + } + #[getter] pub fn get_fc_peak_eff(&self) -> f64 { self.fc_peak_eff() } - #[setter] - pub fn set_fc_peak_eff(&mut self, new_peak: f64) { - let old_fc_peak_eff = self.fc_peak_eff(); - let multiplier = new_peak / old_fc_peak_eff; - self.fc_eff_array = self - .fc_eff_array - .iter() - .map(|eff: &f64| -> f64 { eff * multiplier }) - .collect(); - let new_fc_peak_eff = self.fc_peak_eff(); - let eff_map_multiplier = new_peak / new_fc_peak_eff; - self.fc_eff_map = self - .fc_eff_map - .map(|eff| -> f64 { eff * eff_map_multiplier }); - } - #[pyo3(name = "set_derived")] pub fn set_derived_py(&mut self) { self.set_derived().unwrap() @@ -141,7 +124,7 @@ pub struct RustVehicle { #[serde(alias = "gliderKg")] #[validate(range(min = 0))] pub glider_kg: f64, - /// Vehicle center of mass height, $m$ + /// Vehicle center of mass height, $m$ /// **NOTE:** positive for FWD, negative for RWD, AWD, 4WD #[serde(alias = "vehCgM")] pub veh_cg_m: f64, @@ -191,7 +174,7 @@ pub struct RustVehicle { /// Fuel converter efficiency map #[serde(default)] pub fc_eff_map: Array1, - /// Fuel converter efficiency type, one of \[[SI](SI), [ATKINSON](ATKINSON), [DIESEL](DIESEL), [H2FC](H2FC), [HD_DIESEL](HD_DIESEL)\] + /// Fuel converter efficiency type, one of \[[SI](SI), [ATKINSON](ATKINSON), [DIESEL](DIESEL), [H2FC](H2FC), [HD_DIESEL](HD_DIESEL)\] /// Used for calculating [fc_eff_map](RustVehicle::fc_eff_map), and other calculations if H2FC #[serde(alias = "fcEffType")] #[validate(regex( @@ -568,6 +551,7 @@ impl RustVehicle { arrmax(&self.mc_full_eff_array) } + /// Returns _first_ FC output power at which peak efficiency occurs pub fn max_fc_eff_kw(&self) -> f64 { let fc_eff_arr_max_i = first_eq(&self.fc_eff_array, arrmax(&self.fc_eff_array)).unwrap_or(0); @@ -578,6 +562,32 @@ impl RustVehicle { arrmax(&self.fc_eff_array) } + pub fn set_mc_peak_eff(&mut self, new_peak: f64) { + let mc_max_eff = ndarrmax(&self.mc_eff_array); + self.mc_eff_array *= new_peak / mc_max_eff; + let mc_max_full_eff = arrmax(&self.mc_full_eff_array); + self.mc_full_eff_array = self + .mc_full_eff_array + .iter() + .map(|e: &f64| -> f64 { e * (new_peak / mc_max_full_eff) }) + .collect(); + } + + pub fn set_fc_peak_eff(&mut self, new_peak: f64) { + let old_fc_peak_eff = self.fc_peak_eff(); + let multiplier = new_peak / old_fc_peak_eff; + self.fc_eff_array = self + .fc_eff_array + .iter() + .map(|eff: &f64| -> f64 { eff * multiplier }) + .collect(); + let new_fc_peak_eff = self.fc_peak_eff(); + let eff_map_multiplier = new_peak / new_fc_peak_eff; + self.fc_eff_map = self + .fc_eff_map + .map(|eff| -> f64 { eff * eff_map_multiplier }); + } + /// Sets derived parameters: /// - `no_elec_sys` /// - `no_elec_aux` @@ -907,8 +917,7 @@ impl RustVehicle { v } - #[allow(clippy::should_implement_trait)] - pub fn from_str(filename: &str) -> Result { + pub fn from_json_str(filename: &str) -> Result { let mut veh_res: Result = Ok(serde_json::from_str(filename)?); veh_res.as_mut().unwrap().set_derived()?; veh_res diff --git a/rust/fastsim-py/src/lib.rs b/rust/fastsim-py/src/lib.rs index 1ab03d5e..aec3ab8c 100644 --- a/rust/fastsim-py/src/lib.rs +++ b/rust/fastsim-py/src/lib.rs @@ -1,5 +1,5 @@ use fastsim_core::*; - +use fastsim_core::simdrivelabel::*; use pyo3imports::*; /// Function for adding Rust structs as Python Classes @@ -19,7 +19,17 @@ fn fastsimrust(py: Python, m: &PyModule) -> PyResult<()> { m.add_class::()?; m.add_class::()?; m.add_class::()?; + m.add_class::()?; + m.add_class::()?; + m.add_class::()?; + cycle::register(py, m)?; m.add_function(wrap_pyfunction!(vehicle_utils::abc_to_drag_coeffs, m)?)?; + m.add_function(wrap_pyfunction!(make_accel_trace_py, m)?)?; + m.add_function(wrap_pyfunction!(get_net_accel_py, m)?)?; + m.add_function(wrap_pyfunction!(get_label_fe_py, m)?)?; + m.add_function(wrap_pyfunction!(get_label_fe_phev_py, m)?)?; + m.add_function(wrap_pyfunction!(get_label_fe_conv_py, m)?)?; + Ok(()) }