diff --git a/python/fastsim/fastsimrust.pyi b/python/fastsim/fastsimrust.pyi index 8ec297b4..43062d03 100644 --- a/python/fastsim/fastsimrust.pyi +++ b/python/fastsim/fastsimrust.pyi @@ -1046,4 +1046,270 @@ def abc_to_drag_coeffs( simdrive_optimize: Optional[bool], _show_plots: Optional[bool], ) -> Tuple[float, float]: - ... \ No newline at end of file + ... + +class LabelFe: + veh: RustVehicle + adj_params: AdjCoef + lab_udds_mpgge: float + lab_hwy_mpgge: float + lab_comb_mpgge: float + lab_udds_kwh_per_mi: float + lab_hwy_kwh_per_mi: float + lab_comb_kwh_per_mi: float + adj_udds_mpgge: float + adj_hwy_mpgge: float + adj_comb_mpgge: float + adj_udds_kwh_per_mi: float + adj_hwy_kwh_per_mi: float + adj_comb_kwh_per_mi: float + adj_udds_ess_kwh_per_mi: float + adj_hwy_ess_kwh_per_mi: float + adj_comb_ess_kwh_per_mi: float + net_range_miles: float + uf: float + net_accel: float + res_found: str + phev_calcs: Optional[LabelFePHEV] + adj_cs_comb_mpgge: Optional[float] + adj_cd_comb_mpgge: Optional[float] + net_phev_cd_miles: Optional[float] + trace_miss_speed_mph: float + + + @classmethod + def from_bincode(cls, encoded: ByteString) -> Self: + ... + + def to_bincode(self) -> ByteString: + ... + + @classmethod + def from_yaml(cls, yaml_str: str) -> Self: + ... + + def to_yaml(self) -> str: + ... + + @classmethod + def from_json(cls, json_str: str) -> Self: + ... + + def to_json(self) -> str: + ... + + @classmethod + def from_file(cls, filename: str) -> Self: + ... + + def to_file(self, filename: str) -> Self: + ... + +class LabelFePHEV: + regen_soc_buffer: float + udds: PHEVCycleCalc + hwy: PHEVCycleCalc + + + @classmethod + def from_bincode(cls, encoded: ByteString) -> Self: + ... + + def to_bincode(self) -> ByteString: + ... + + @classmethod + def from_yaml(cls, yaml_str: str) -> Self: + ... + + def to_yaml(self) -> str: + ... + + @classmethod + def from_json(cls, json_str: str) -> Self: + ... + + def to_json(self) -> str: + ... + + @classmethod + def from_file(cls, filename: str) -> Self: + ... + + def to_file(self, filename: str) -> Self: + ... + + +class PHEVCycleCalc: + cd_ess_kwh: float + cd_ess_kwh_per_mi: float + cd_fs_gal: float + cd_fs_kwh: float + cd_mpg: float + cd_cycs: float + cd_miles: float + cd_lab_mpg: float + cd_adj_mpg: float + cd_frac_in_trans: float + trans_init_soc: float + trans_ess_kwh: float + trans_ess_kwh_per_mi: float + trans_fs_gal: float + trans_fs_kwh: float + cs_ess_kwh: float + cs_ess_kwh_per_mi: float + cs_fs_gal: float + cs_fs_kwh: float + cs_mpg: float + lab_mpgge: float + lab_kwh_per_mi: float + lab_uf: float + lab_uf_gpm: List[float] + lab_iter_uf: List[float] + lab_iter_uf_kwh_per_mi: List[float] + lab_iter_kwh_per_mi: List[float] + adj_iter_mpgge: List[float] + adj_iter_kwh_per_mi: List[float] + adj_iter_cd_miles: List[float] + adj_iter_uf: List[float] + adj_iter_uf_gpm: List[float] + adj_iter_uf_kwh_per_mi: List[float] + adj_cd_miles: float + adj_cd_mpgge: float + adj_cs_mpgge: float + adj_uf: float + adj_mpgge: float + adj_kwh_per_mi: float + adj_ess_kwh_per_mi: float + delta_soc: float + total_cd_miles: float + + + @classmethod + def from_bincode(cls, encoded: ByteString) -> Self: + ... + + def to_bincode(self) -> ByteString: + ... + + @classmethod + def from_yaml(cls, yaml_str: str) -> Self: + ... + + def to_yaml(self) -> str: + ... + + @classmethod + def from_json(cls, json_str: str) -> Self: + ... + + def to_json(self) -> str: + ... + + @classmethod + def from_file(cls, filename: str) -> Self: + ... + + def to_file(self, filename: str) -> Self: + ... + +def make_accel_trace() -> RustCycle: + ... +def get_net_accel(sd_accel: RustSimDrive, scenario_name: str) -> float: + ... +class AdjCoef: + def __init__(self): + self.city_intercept: float = 0.0 + self.city_slope: float = 0.0 + self.hwy_intercept: float = 0.0 + self.hwy_slope: float = 0.0 + + @classmethod + def from_bincode(cls, encoded: ByteString) -> Self: + ... + + def to_bincode(self) -> ByteString: + ... + + @classmethod + def from_yaml(cls, yaml_str: str) -> Self: + ... + + def to_yaml(self) -> str: + ... + + @classmethod + def from_json(cls, json_str: str) -> Self: + ... + + def to_json(self) -> str: + ... + + @classmethod + def from_file(cls, filename: str) -> Self: + ... + + def to_file(self, filename: str) -> Self: + ... + +class AdjCoefMap: + def __init__(self): + self.adj_coef_map: Dict[str, AdjCoef] = {} + + +class RustLongParams: + def __init__(self): + self.rechg_freq_miles: List[float] = [] + self.uf_array: List[float] = [] + self.ld_fe_adj_coef: AdjCoefMap = {} + + @classmethod + def from_bincode(cls, encoded: ByteString) -> Self: + ... + + def to_bincode(self) -> ByteString: + ... + + @classmethod + def from_yaml(cls, yaml_str: str) -> Self: + ... + + def to_yaml(self) -> str: + ... + + @classmethod + def from_json(cls, json_str: str) -> Self: + ... + + def to_json(self) -> str: + ... + + @classmethod + def from_file(cls, filename: str) -> Self: + ... + + def to_file(self, filename: str) -> Self: + ... + + +def get_label_fe( + veh: RustVehicle, + full_detail: Optional[bool], + verbose: Optional[bool], +) -> Tuple[LabelFe, Optional[Dict[str, RustSimDrive]]]: + ... + +def get_label_fe_phev( + veh: RustVehicle, + sd: Dict[str, RustSimDrive], + long_params: RustLongParams, + adj_params: AdjCoef, + sim_params: RustSimDriveParams, + props: RustPhysicalProperties, +) -> LabelFePHEV: + ... + +def get_label_fe_conv(veh: RustVehicle) -> LabelFe: + ... + + diff --git a/python/fastsim/tests/test_simdrivelabel.py b/python/fastsim/tests/test_simdrivelabel.py new file mode 100644 index 00000000..3132c7f1 --- /dev/null +++ b/python/fastsim/tests/test_simdrivelabel.py @@ -0,0 +1,25 @@ +import unittest +import numpy as np + +from fastsim import fastsimrust as fsr + + +class TestSimDriveLabel(unittest.TestCase): + def test_get_label_fe_conv(self): + veh = fsr.RustVehicle.mock_vehicle() + label_fe, _ = fsr.get_label_fe(veh, False, False) # Unpack the tuple + # Because the full test is already implemented in Rust, we + # don't need a comprehensive check here. + self.assertEqual(label_fe.lab_udds_mpgge, 32.47503766676829) + self.assertEqual(label_fe.lab_hwy_mpgge, 42.265348793379445) + self.assertEqual(label_fe.lab_comb_mpgge, 36.25407690819302) + + def test_get_label_fe_phev(self): + # Set up the required parameters and objects needed for testing get_label_fe_phev + veh = fsr.RustVehicle.mock_vehicle() + label_fe, _ = fsr.get_label_fe(veh, False, False) + self.assertEqual(label_fe.adj_udds_mpgge, 25.246151811422468) + + +if __name__ == '__main__': + unittest.main() \ No newline at end of file diff --git a/python/fastsim/tests/test_utils.py b/python/fastsim/tests/test_utils.py index 3ca14546..55360a68 100644 --- a/python/fastsim/tests/test_utils.py +++ b/python/fastsim/tests/test_utils.py @@ -1,7 +1,7 @@ import unittest from fastsim import utils -class test_utils(unittest.TestCase): +class TestUtils(unittest.TestCase): def setUp(self): utils.disable_logging() diff --git a/rust/fastsim-core/src/simdrivelabel.rs b/rust/fastsim-core/src/simdrivelabel.rs index 201f6a9c..25186b84 100644 --- a/rust/fastsim-core/src/simdrivelabel.rs +++ b/rust/fastsim-core/src/simdrivelabel.rs @@ -805,7 +805,377 @@ 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, + 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 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 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)); + } } + #[cfg(feature = "pyo3")] #[pyfunction(name = "get_label_fe_conv")] /// pyo3 version of [get_label_fe_conv] @@ -815,372 +1185,3 @@ pub fn get_label_fe_conv_py() -> LabelFe { 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_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 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 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 53d5e510..21b6c8c7 100644 --- a/rust/fastsim-core/src/vehicle.rs +++ b/rust/fastsim-core/src/vehicle.rs @@ -81,6 +81,12 @@ lazy_static! { pub fn to_rust(&self) -> PyResult { Ok(self.clone()) } + + #[classmethod] + #[pyo3(name = "mock_vehicle")] + fn mock_vehicle_py(_cls: &PyType) -> Self { + Self::mock_vehicle() + } )] /// Struct containing vehicle attributes /// # Python Examples diff --git a/rust/fastsim-py/src/lib.rs b/rust/fastsim-py/src/lib.rs index aec3ab8c..9d9a2bf5 100644 --- a/rust/fastsim-py/src/lib.rs +++ b/rust/fastsim-py/src/lib.rs @@ -9,6 +9,8 @@ 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::()?; m.add_class::()?; m.add_class::()?; @@ -24,6 +26,7 @@ fn fastsimrust(py: Python, m: &PyModule) -> PyResult<()> { 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)?)?; @@ -31,5 +34,7 @@ fn fastsimrust(py: Python, m: &PyModule) -> PyResult<()> { m.add_function(wrap_pyfunction!(get_label_fe_phev_py, m)?)?; m.add_function(wrap_pyfunction!(get_label_fe_conv_py, m)?)?; + + Ok(()) }