From dccb65158601885cd60e624984421b9b0da5d069 Mon Sep 17 00:00:00 2001 From: Kyle Carow Date: Wed, 18 Oct 2023 16:22:27 -0400 Subject: [PATCH 01/27] make much more use of anyhow, replacing most PyResults --- rust/fastsim-cli/src/bin/fastsim-cli.rs | 6 +- .../src/add_pyo3_api/mod.rs | 59 ++++---- .../src/add_pyo3_api/pyo3_api_utils.rs | 24 ++-- rust/fastsim-core/src/cycle.rs | 42 +++--- rust/fastsim-core/src/imports.rs | 5 +- rust/fastsim-core/src/macros.rs | 9 +- rust/fastsim-core/src/simdrive.rs | 72 +++++----- rust/fastsim-core/src/simdrive/cyc_mods.rs | 12 +- .../src/simdrive/simdrive_impl.rs | 136 +++++++++--------- rust/fastsim-core/src/simdrivelabel.rs | 15 +- rust/fastsim-core/src/thermal.rs | 90 ++++++------ rust/fastsim-core/src/traits.rs | 52 +++---- rust/fastsim-core/src/utils.rs | 2 +- rust/fastsim-core/src/vehicle.rs | 13 +- rust/fastsim-core/src/vehicle_thermal.rs | 40 +++--- 15 files changed, 287 insertions(+), 290 deletions(-) diff --git a/rust/fastsim-cli/src/bin/fastsim-cli.rs b/rust/fastsim-cli/src/bin/fastsim-cli.rs index 4dc31237..7e68702d 100644 --- a/rust/fastsim-cli/src/bin/fastsim-cli.rs +++ b/rust/fastsim-cli/src/bin/fastsim-cli.rs @@ -290,7 +290,7 @@ pub fn main() { traceMissInMph: sdl.0.trace_miss_speed_mph, h2AndDiesel: None, }; - println!("{}", res.to_json()); + println!("{}", res.to_json().unwrap()); } else if is_adopt_hd { let hd_cyc_filestring = include_str!(concat!( "..", @@ -358,7 +358,7 @@ pub fn main() { traceMissInMph: sim_drive.trace_miss_speed_mps * MPH_PER_MPS, h2AndDiesel: h2_diesel_results, }; - println!("{}", res.to_json()); + println!("{}", res.to_json().unwrap()); } else { let mut sim_drive = RustSimDrive::new(cyc, veh); // // this does nothing if it has already been called for the constructed `sim_drive` @@ -520,7 +520,7 @@ fn json_rewrite(x: String) -> (String, Option>, Option>) { parsed_data["stop_start"] = json!(false); - let adoptstring = ParsedValue(parsed_data).to_json(); + let adoptstring = ParsedValue(parsed_data).to_json().unwrap(); (adoptstring, fc_pwr_out_perc, hd_h2_diesel_ice_h2share) } diff --git a/rust/fastsim-core/fastsim-proc-macros/src/add_pyo3_api/mod.rs b/rust/fastsim-core/fastsim-proc-macros/src/add_pyo3_api/mod.rs index 48c9625b..9159bf48 100644 --- a/rust/fastsim-core/fastsim-proc-macros/src/add_pyo3_api/mod.rs +++ b/rust/fastsim-core/fastsim-proc-macros/src/add_pyo3_api/mod.rs @@ -108,15 +108,14 @@ pub fn add_pyo3_api(attr: TokenStream, item: TokenStream) -> TokenStream { if !is_state_or_history { py_impl_block.extend::(quote! { #[pyo3(name = "to_file")] - pub fn to_file_py(&self, filename: &str) -> PyResult<()> { - Ok(self.to_file(filename)?) + pub fn to_file_py(&self, filepath: &str) -> anyhow::Result<()> { + self.to_file(filepath) } #[classmethod] #[pyo3(name = "from_file")] - pub fn from_file_py(_cls: &PyType, json_str:String) -> PyResult { - // unwrap is ok here because it makes sense to stop execution if a file is not loadable - Ok(Self::from_file(&json_str)?) + pub fn from_file_py(_cls: &PyType, filepath: &str) -> anyhow::Result { + Self::from_file(filepath) } }); } @@ -168,9 +167,9 @@ pub fn add_pyo3_api(attr: TokenStream, item: TokenStream) -> TokenStream { pub fn __str__(&self) -> String { format!("{:?}", self.0) } - pub fn __getitem__(&self, idx: i32) -> PyResult<#contained_dtype> { + pub fn __getitem__(&self, idx: i32) -> anyhow::Result<#contained_dtype> { if idx >= self.0.len() as i32 { - Err(PyIndexError::new_err("Index is out of bounds")) + anyhow::bail!(PyIndexError::new_err("Index is out of bounds")) } else if idx >= 0 { Ok(self.0[idx as usize].clone()) } else { @@ -178,17 +177,17 @@ pub fn add_pyo3_api(attr: TokenStream, item: TokenStream) -> TokenStream { } } pub fn __setitem__(&mut self, _idx: usize, _new_value: #contained_dtype - ) -> PyResult<()> { - Err(PyNotImplementedError::new_err( + ) -> anyhow::Result<()> { + anyhow::bail!(PyNotImplementedError::new_err( "Setting value at index is not implemented. Run `tolist` method, modify value at index, and then set entire vector.", )) } - pub fn tolist(&self) -> PyResult> { + pub fn tolist(&self) -> anyhow::Result> { Ok(#tolist_body) } - pub fn __list__(&self) -> PyResult> { + pub fn __list__(&self) -> anyhow::Result> { Ok(#tolist_body) } pub fn __len__(&self) -> usize { @@ -214,7 +213,7 @@ pub fn add_pyo3_api(attr: TokenStream, item: TokenStream) -> TokenStream { // py_impl_block.extend::(quote! { // #[classmethod] // #[pyo3(name = "default")] - // pub fn default_py(_cls: &PyType) -> PyResult { + // pub fn default_py(_cls: &PyType) -> anyhow::Result { // Ok(Self::default()) // } // }); @@ -224,43 +223,43 @@ pub fn add_pyo3_api(attr: TokenStream, item: TokenStream) -> TokenStream { pub fn __copy__(&self) -> Self {self.clone()} pub fn __deepcopy__(&self, _memo: &PyDict) -> Self {self.clone()} - /// json serialization method. + /// JSON serialization method. #[pyo3(name = "to_json")] - pub fn to_json_py(&self) -> PyResult { - Ok(self.to_json()) + pub fn to_json_py(&self) -> anyhow::Result { + self.to_json() } #[classmethod] - /// json deserialization method. + /// JSON deserialization method. #[pyo3(name = "from_json")] - pub fn from_json_py(_cls: &PyType, json_str: &str) -> PyResult { - Ok(Self::from_json(json_str)?) + pub fn from_json_py(_cls: &PyType, json_str: &str) -> anyhow::Result { + Self::from_json(json_str) } - /// yaml serialization method. + /// YAML serialization method. #[pyo3(name = "to_yaml")] - pub fn to_yaml_py(&self) -> PyResult { - Ok(self.to_yaml()) + pub fn to_yaml_py(&self) -> anyhow::Result { + self.to_yaml() } #[classmethod] - /// yaml deserialization method. + /// YAML deserialization method. #[pyo3(name = "from_yaml")] - pub fn from_yaml_py(_cls: &PyType, yaml_str: &str) -> PyResult { - Ok(Self::from_yaml(yaml_str)?) + pub fn from_yaml_py(_cls: &PyType, yaml_str: &str) -> anyhow::Result { + Self::from_yaml(yaml_str) } /// bincode serialization method. #[pyo3(name = "to_bincode")] - pub fn to_bincode_py<'py>(&self, py: Python<'py>) -> PyResult<&'py PyBytes> { - Ok(PyBytes::new(py, &self.to_bincode())) + pub fn to_bincode_py<'py>(&self, py: Python<'py>) -> anyhow::Result<&'py PyBytes> { + Ok(PyBytes::new(py, &self.to_bincode()?)) } #[classmethod] /// bincode deserialization method. #[pyo3(name = "from_bincode")] - pub fn from_bincode_py(_cls: &PyType, encoded: &PyBytes) -> PyResult { - Ok(Self::from_bincode(encoded.as_bytes())?) + pub fn from_bincode_py(_cls: &PyType, encoded: &PyBytes) -> anyhow::Result { + Self::from_bincode(encoded.as_bytes()) } }); @@ -331,11 +330,11 @@ pub fn impl_getters_and_setters( "orphaned" => { impl_block.extend::(quote! { #[getter] - pub fn get_orphaned(&self) -> PyResult { + pub fn get_orphaned(&self) -> anyhow::Result { Ok(self.orphaned) } /// Reset the orphaned flag to false. - pub fn reset_orphaned(&mut self) -> PyResult<()> { + pub fn reset_orphaned(&mut self) -> anyhow::Result<()> { self.orphaned = false; Ok(()) } diff --git a/rust/fastsim-core/fastsim-proc-macros/src/add_pyo3_api/pyo3_api_utils.rs b/rust/fastsim-core/fastsim-proc-macros/src/add_pyo3_api/pyo3_api_utils.rs index e412b3cd..cc1317cd 100644 --- a/rust/fastsim-core/fastsim-proc-macros/src/add_pyo3_api/pyo3_api_utils.rs +++ b/rust/fastsim-core/fastsim-proc-macros/src/add_pyo3_api/pyo3_api_utils.rs @@ -4,7 +4,7 @@ macro_rules! impl_vec_get_set { let get_name: TokenStream2 = format!("get_{}", $fident).parse().unwrap(); $impl_block.extend::(quote! { #[getter] - pub fn #get_name(&self) -> PyResult<$wrapper_type> { + pub fn #get_name(&self) -> anyhow::Result<$wrapper_type> { Ok($wrapper_type::new(self.#$fident.clone())) } }); @@ -16,19 +16,19 @@ macro_rules! impl_vec_get_set { if $has_orphaned { $impl_block.extend(quote! { #[setter] - pub fn #set_name(&mut self, new_value: Vec<$contained_type>) -> PyResult<()> { + pub fn #set_name(&mut self, new_value: Vec<$contained_type>) -> anyhow::Result<()> { if !self.orphaned { self.#$fident = new_value; Ok(()) } else { - Err(PyAttributeError::new_err(crate::utils::NESTED_STRUCT_ERR)) + anyhow::bail!(PyAttributeError::new_err(crate::utils::NESTED_STRUCT_ERR)) } } }) } else { $impl_block.extend(quote! { #[setter] - pub fn #set_name(&mut self, new_value: Vec<$contained_type>) -> PyResult<()> { + pub fn #set_name(&mut self, new_value: Vec<$contained_type>) -> anyhow::Result<()> { self.#$fident = new_value; Ok(()) } @@ -39,19 +39,19 @@ macro_rules! impl_vec_get_set { if $has_orphaned { $impl_block.extend(quote! { #[setter] - pub fn #set_name(&mut self, new_value: Vec<$contained_type>) -> PyResult<()> { + pub fn #set_name(&mut self, new_value: Vec<$contained_type>) -> anyhow::Result<()> { if !self.orphaned { self.#$fident = Array1::from_vec(new_value); Ok(()) } else { - Err(PyAttributeError::new_err(crate::utils::NESTED_STRUCT_ERR)) + anyhow::bail!(PyAttributeError::new_err(crate::utils::NESTED_STRUCT_ERR)) } } }) } else { $impl_block.extend(quote! { #[setter] - pub fn #set_name(&mut self, new_value: Vec<$contained_type>) -> PyResult<()> { + pub fn #set_name(&mut self, new_value: Vec<$contained_type>) -> anyhow::Result<()> { self.#$fident = Array1::from_vec(new_value); Ok(()) } @@ -80,7 +80,7 @@ macro_rules! impl_get_body { let get_block = if $opts.field_has_orphaned { quote! { #[getter] - pub fn #get_name(&mut self) -> PyResult<#$type> { + pub fn #get_name(&mut self) -> anyhow::Result<#$type> { self.#$field.orphaned = true; Ok(self.#$field.clone()) } @@ -88,7 +88,7 @@ macro_rules! impl_get_body { } else { quote! { #[getter] - pub fn #get_name(&self) -> PyResult<#$type> { + pub fn #get_name(&self) -> anyhow::Result<#$type> { Ok(self.#$field.clone()) } } @@ -120,7 +120,7 @@ macro_rules! impl_set_body { self.#$field.orphaned = false; Ok(()) } else { - Err(PyAttributeError::new_err(crate::utils::NESTED_STRUCT_ERR)) + anyhow::bail!(PyAttributeError::new_err(crate::utils::NESTED_STRUCT_ERR)) } } } else if $has_orphaned { @@ -129,7 +129,7 @@ macro_rules! impl_set_body { self.#$field = new_value; Ok(()) } else { - Err(PyAttributeError::new_err(crate::utils::NESTED_STRUCT_ERR)) + anyhow::bail!(PyAttributeError::new_err(crate::utils::NESTED_STRUCT_ERR)) } } } else { @@ -141,7 +141,7 @@ macro_rules! impl_set_body { $impl_block.extend::(quote! { #[setter] - pub fn #set_name(&mut self, new_value: #$type) -> PyResult<()> { + pub fn #set_name(&mut self, new_value: #$type) -> anyhow::Result<()> { #orphaned_set_block } }); diff --git a/rust/fastsim-core/src/cycle.rs b/rust/fastsim-core/src/cycle.rs index 56087f9a..2c5d50af 100644 --- a/rust/fastsim-core/src/cycle.rs +++ b/rust/fastsim-core/src/cycle.rs @@ -351,7 +351,7 @@ pub fn extend_cycle( #[cfg(feature = "pyo3")] #[allow(unused)] -pub fn register(_py: Python<'_>, m: &PyModule) -> Result<(), anyhow::Error> { +pub fn register(_py: Python<'_>, m: &PyModule) -> anyhow::Result<()> { m.add_function(wrap_pyfunction!(calc_constant_jerk_trajectory, m)?)?; m.add_function(wrap_pyfunction!(accel_for_constant_jerk, m)?)?; m.add_function(wrap_pyfunction!(speed_for_constant_jerk, m)?)?; @@ -484,7 +484,7 @@ impl RustCycleCache { } #[allow(clippy::type_complexity)] - pub fn __getnewargs__(&self) -> PyResult<(Vec, Vec, Vec, Vec, &str)> { + pub fn __getnewargs__(&self) -> anyhow::Result<(Vec, Vec, Vec, Vec, &str)> { Ok((self.time_s.to_vec(), self.mps.to_vec(), self.grade.to_vec(), self.road_type.to_vec(), &self.name)) } @@ -516,7 +516,7 @@ impl RustCycleCache { n: usize, jerk_m_per_s3: f64, accel0_m_per_s2: f64, - ) -> PyResult { + ) -> anyhow::Result { Ok(self.modify_by_const_jerk_trajectory(idx, n, jerk_m_per_s3, accel0_m_per_s2)) } @@ -526,12 +526,12 @@ impl RustCycleCache { brake_accel_m_per_s2: f64, idx: usize, dts_m: Option - ) -> PyResult<(f64, usize)> { + ) -> anyhow::Result<(f64, usize)> { Ok(self.modify_with_braking_trajectory(brake_accel_m_per_s2, idx, dts_m)) } #[pyo3(name = "calc_distance_to_next_stop_from")] - pub fn calc_distance_to_next_stop_from_py(&self, distance_m: f64) -> PyResult { + pub fn calc_distance_to_next_stop_from_py(&self, distance_m: f64) -> anyhow::Result { Ok(self.calc_distance_to_next_stop_from(distance_m, None)) } @@ -540,17 +540,17 @@ impl RustCycleCache { &self, distance_start_m: f64, delta_distance_m: f64, - ) -> PyResult { + ) -> anyhow::Result { Ok(self.average_grade_over_range(distance_start_m, delta_distance_m, None)) } #[pyo3(name = "build_cache")] - pub fn build_cache_py(&self) -> PyResult { + pub fn build_cache_py(&self) -> anyhow::Result { Ok(self.build_cache()) } #[pyo3(name = "dt_s_at_i")] - pub fn dt_s_at_i_py(&self, i: usize) -> PyResult { + pub fn dt_s_at_i_py(&self, i: usize) -> anyhow::Result { if i == 0 { Ok(0.0) } else { @@ -559,31 +559,31 @@ impl RustCycleCache { } #[getter] - pub fn get_mph(&self) -> PyResult> { + pub fn get_mph(&self) -> anyhow::Result> { Ok((&self.mps * crate::params::MPH_PER_MPS).to_vec()) } #[setter] - pub fn set_mph(&mut self, new_value: Vec) -> PyResult<()> { + pub fn set_mph(&mut self, new_value: Vec) -> anyhow::Result<()> { self.mps = Array::from_vec(new_value) / MPH_PER_MPS; Ok(()) } #[getter] /// array of time steps - pub fn get_dt_s(&self) -> PyResult> { + pub fn get_dt_s(&self) -> anyhow::Result> { Ok(self.dt_s().to_vec()) } #[getter] /// cycle length - pub fn get_len(&self) -> PyResult { + pub fn get_len(&self) -> anyhow::Result { Ok(self.len()) } #[getter] /// distance for each time step based on final speed - pub fn get_dist_m(&self) -> PyResult> { + pub fn get_dist_m(&self) -> anyhow::Result> { Ok(self.dist_m().to_vec()) } #[getter] - pub fn get_delta_elev_m(&self) -> PyResult> { + pub fn get_delta_elev_m(&self) -> anyhow::Result> { Ok(self.delta_elev_m().to_vec()) } )] @@ -615,16 +615,16 @@ pub struct RustCycle { } impl SerdeAPI for RustCycle { - fn from_file(filename: &str) -> Result { + fn from_file(filepath: &str) -> anyhow::Result { // check if the extension is csv, and if it is, then call Self::from_csv_file - let pathbuf = PathBuf::from(filename); - let file = File::open(filename)?; + let pathbuf = PathBuf::from(filepath); + let file = File::open(filepath)?; let extension = pathbuf.extension().unwrap().to_str().unwrap(); match extension { "yaml" => Ok(serde_yaml::from_reader(file)?), "json" => Ok(serde_json::from_reader(file)?), - "csv" => Ok(Self::from_csv_file(filename)?), - _ => Err(anyhow!("Unsupported file extension {}", extension)), + "csv" => Ok(Self::from_csv_file(filepath)?), + _ => anyhow::bail!("Unsupported file extension {}", extension), } } } @@ -914,7 +914,7 @@ impl RustCycle { } /// Load cycle from csv file - pub fn from_csv_file(pathstr: &str) -> Result { + pub fn from_csv_file(pathstr: &str) -> anyhow::Result { let pathbuf = PathBuf::from(&pathstr); // create empty cycle to be populated @@ -942,7 +942,7 @@ impl RustCycle { } // load a cycle from a string representation of a csv file - pub fn from_csv_string(data: &str, name: String) -> Result { + pub fn from_csv_string(data: &str, name: String) -> anyhow::Result { let mut cyc = Self { name, ..Self::default() diff --git a/rust/fastsim-core/src/imports.rs b/rust/fastsim-core/src/imports.rs index 3317cf10..e48aceb1 100644 --- a/rust/fastsim-core/src/imports.rs +++ b/rust/fastsim-core/src/imports.rs @@ -1,5 +1,5 @@ -pub(crate) use anyhow::{anyhow, bail, ensure}; -pub(crate) use bincode::{deserialize, serialize}; +pub(crate) use anyhow; +pub(crate) use bincode; pub(crate) use log; pub(crate) use ndarray::{array, concatenate, s, Array, Array1, Axis}; pub(crate) use serde::{Deserialize, Serialize}; @@ -10,4 +10,3 @@ pub(crate) use std::path::{Path, PathBuf}; pub(crate) use crate::traits::*; pub(crate) use crate::utils::*; - diff --git a/rust/fastsim-core/src/macros.rs b/rust/fastsim-core/src/macros.rs index d853718e..9b2cb91f 100644 --- a/rust/fastsim-core/src/macros.rs +++ b/rust/fastsim-core/src/macros.rs @@ -37,11 +37,16 @@ macro_rules! print_to_py { #[macro_export] macro_rules! check_orphaned_and_set { ($struct_self: ident, $field: ident, $value: expr) => { + // TODO: This seems like it could be used instead, but raises an error + // anyhow::ensure!(!$struct_self.orphaned, utils::NESTED_STRUCT_ERR); + // $struct_self.$field = $value; + // Ok(()) + if !$struct_self.orphaned { $struct_self.$field = $value; - Ok(()) + anyhow::Ok(()) } else { - Err(anyhow!(utils::NESTED_STRUCT_ERR)) + anyhow::bail!(utils::NESTED_STRUCT_ERR) } }; } diff --git a/rust/fastsim-core/src/simdrive.rs b/rust/fastsim-core/src/simdrive.rs index c6be3a5f..874e66dd 100644 --- a/rust/fastsim-core/src/simdrive.rs +++ b/rust/fastsim-core/src/simdrive.rs @@ -152,7 +152,7 @@ impl Default for RustSimDriveParams { #[pyo3(name = "gap_to_lead_vehicle_m")] /// Provides the gap-with lead vehicle from start to finish - pub fn gap_to_lead_vehicle_m_py(&self) -> PyResult> { + pub fn gap_to_lead_vehicle_m_py(&self) -> anyhow::Result> { Ok(self.gap_to_lead_vehicle_m().to_vec()) } @@ -167,9 +167,9 @@ impl Default for RustSimDriveParams { &mut self, init_soc: Option, aux_in_kw_override: Option>, - ) -> PyResult<()> { + ) -> anyhow::Result<()> { let aux_in_kw_override = aux_in_kw_override.map(Array1::from); - Ok(self.sim_drive(init_soc, aux_in_kw_override)?) + self.sim_drive(init_soc, aux_in_kw_override) } /// Receives second-by-second cycle information, vehicle properties, @@ -187,9 +187,9 @@ impl Default for RustSimDriveParams { &mut self, init_soc: f64, aux_in_kw_override: Option>, - ) -> PyResult<()> { + ) -> anyhow::Result<()> { let aux_in_kw_override = aux_in_kw_override.map(Array1::from); - Ok(self.walk(init_soc, aux_in_kw_override)?) + self.walk(init_soc, aux_in_kw_override) } /// Sets the intelligent driver model parameters for an eco-cruise driving trajectory. @@ -206,13 +206,13 @@ impl Default for RustSimDriveParams { extend_fraction: Option, blend_factor: Option, min_target_speed_m_per_s: Option, - ) -> PyResult<()> { + ) -> anyhow::Result<()> { let by_microtrip: bool = by_microtrip.unwrap_or(false); let extend_fraction: f64 = extend_fraction.unwrap_or(0.1); let blend_factor: f64 = blend_factor.unwrap_or(0.0); let min_target_speed_m_per_s = min_target_speed_m_per_s.unwrap_or(8.0); - Ok(self.activate_eco_cruise_rust( - by_microtrip, extend_fraction, blend_factor, min_target_speed_m_per_s)?) + self.activate_eco_cruise_rust( + by_microtrip, extend_fraction, blend_factor, min_target_speed_m_per_s) } #[pyo3(name = "init_for_step")] @@ -227,20 +227,20 @@ impl Default for RustSimDriveParams { &mut self, init_soc:f64, aux_in_kw_override: Option> - ) -> PyResult<()> { + ) -> anyhow::Result<()> { let aux_in_kw_override = aux_in_kw_override.map(Array1::from); - Ok(self.init_for_step(init_soc, aux_in_kw_override)?) + self.init_for_step(init_soc, aux_in_kw_override) } /// Step through 1 time step. - pub fn sim_drive_step(&mut self) -> PyResult<()> { - Ok(self.step()?) + pub fn sim_drive_step(&mut self) -> anyhow::Result<()> { + self.step() } #[pyo3(name = "solve_step")] /// Perform all the calculations to solve 1 time step. - pub fn solve_step_py(&mut self, i: usize) -> PyResult<()> { - Ok(self.solve_step(i)?) + pub fn solve_step_py(&mut self, i: usize) -> anyhow::Result<()> { + self.solve_step(i) } #[pyo3(name = "set_misc_calcs")] @@ -248,8 +248,8 @@ impl Default for RustSimDriveParams { /// Arguments: /// ---------- /// i: index of time step - pub fn set_misc_calcs_py(&mut self, i: usize) -> PyResult<()> { - Ok(self.set_misc_calcs(i)?) + pub fn set_misc_calcs_py(&mut self, i: usize) -> anyhow::Result<()> { + self.set_misc_calcs(i) } #[pyo3(name = "set_comp_lims")] @@ -257,8 +257,8 @@ impl Default for RustSimDriveParams { // Arguments // ------------ // i: index of time step - pub fn set_comp_lims_py(&mut self, i: usize) -> PyResult<()> { - Ok(self.set_comp_lims(i)?) + pub fn set_comp_lims_py(&mut self, i: usize) -> anyhow::Result<()> { + self.set_comp_lims(i) } #[pyo3(name = "set_power_calcs")] @@ -267,8 +267,8 @@ impl Default for RustSimDriveParams { /// Arguments /// ------------ /// i: index of time step - pub fn set_power_calcs_py(&mut self, i: usize) -> PyResult<()> { - Ok(self.set_power_calcs(i)?) + pub fn set_power_calcs_py(&mut self, i: usize) -> anyhow::Result<()> { + self.set_power_calcs(i) } #[pyo3(name = "set_ach_speed")] @@ -276,8 +276,8 @@ impl Default for RustSimDriveParams { // Arguments // ------------ // i: index of time step - pub fn set_ach_speed_py(&mut self, i: usize) -> PyResult<()> { - Ok(self.set_ach_speed(i)?) + pub fn set_ach_speed_py(&mut self, i: usize) -> anyhow::Result<()> { + self.set_ach_speed(i) } #[pyo3(name = "set_hybrid_cont_calcs")] @@ -285,8 +285,8 @@ impl Default for RustSimDriveParams { /// Arguments /// ------------ /// i: index of time step - pub fn set_hybrid_cont_calcs_py(&mut self, i: usize) -> PyResult<()> { - Ok(self.set_hybrid_cont_calcs(i)?) + pub fn set_hybrid_cont_calcs_py(&mut self, i: usize) -> anyhow::Result<()> { + self.set_hybrid_cont_calcs(i) } #[pyo3(name = "set_fc_forced_state")] @@ -295,8 +295,8 @@ impl Default for RustSimDriveParams { /// ------------ /// i: index of time step /// `_py` extension is needed to avoid name collision with getter/setter methods - pub fn set_fc_forced_state_py(&mut self, i: usize) -> PyResult<()> { - Ok(self.set_fc_forced_state_rust(i)?) + pub fn set_fc_forced_state_py(&mut self, i: usize) -> anyhow::Result<()> { + self.set_fc_forced_state_rust(i) } #[pyo3(name = "set_hybrid_cont_decisions")] @@ -304,8 +304,8 @@ impl Default for RustSimDriveParams { /// Arguments /// ------------ /// i: index of time step - pub fn set_hybrid_cont_decisions_py(&mut self, i: usize) -> PyResult<()> { - Ok(self.set_hybrid_cont_decisions(i)?) + pub fn set_hybrid_cont_decisions_py(&mut self, i: usize) -> anyhow::Result<()> { + self.set_hybrid_cont_decisions(i) } #[pyo3(name = "set_fc_power")] @@ -313,8 +313,8 @@ impl Default for RustSimDriveParams { /// Arguments /// ------------ /// i: index of time step - pub fn set_fc_power_py(&mut self, i: usize) -> PyResult<()> { - Ok(self.set_fc_power(i)?) + pub fn set_fc_power_py(&mut self, i: usize) -> anyhow::Result<()> { + self.set_fc_power(i) } #[pyo3(name = "set_time_dilation")] @@ -322,15 +322,15 @@ impl Default for RustSimDriveParams { /// Arguments /// ------------ /// i: index of time step - pub fn set_time_dilation_py(&mut self, i: usize) -> PyResult<()> { - Ok(self.set_time_dilation(i)?) + pub fn set_time_dilation_py(&mut self, i: usize) -> anyhow::Result<()> { + self.set_time_dilation(i) } #[pyo3(name = "set_post_scalars")] /// Sets scalar variables that can be calculated after a cycle is run. /// This includes mpgge, various energy metrics, and others - pub fn set_post_scalars_py(&mut self) -> PyResult<()> { - Ok(self.set_post_scalars()?) + pub fn set_post_scalars_py(&mut self) -> anyhow::Result<()> { + self.set_post_scalars() } #[pyo3(name = "len")] @@ -346,13 +346,13 @@ impl Default for RustSimDriveParams { } #[getter] - pub fn get_fs_cumu_mj_out_ach(&self) -> PyResult { + pub fn get_fs_cumu_mj_out_ach(&self) -> anyhow::Result { Ok( Pyo3ArrayF64::new(ndarrcumsum(&(self.fs_kw_out_ach.clone() * self.cyc.dt_s() * 1e-3))) ) } #[getter] - pub fn get_fc_cumu_mj_out_ach(&self) -> PyResult { + pub fn get_fc_cumu_mj_out_ach(&self) -> anyhow::Result { Ok( Pyo3ArrayF64::new(ndarrcumsum(&(self.fc_kw_out_ach.clone() * self.cyc.dt_s() * 1e-3))) ) diff --git a/rust/fastsim-core/src/simdrive/cyc_mods.rs b/rust/fastsim-core/src/simdrive/cyc_mods.rs index de9d50fa..df23d61a 100644 --- a/rust/fastsim-core/src/simdrive/cyc_mods.rs +++ b/rust/fastsim-core/src/simdrive/cyc_mods.rs @@ -38,7 +38,7 @@ impl RustSimDrive { extend_fraction: f64, // 0.1 blend_factor: f64, // 0.0 min_target_speed_m_per_s: f64, // 8.0 - ) -> Result<(), anyhow::Error> { + ) -> anyhow::Result<()> { self.sim_params.idm_allow = true; if !by_microtrip { self.sim_params.idm_v_desired_m_per_s = @@ -53,13 +53,13 @@ impl RustSimDrive { }; } else { if !(0.0..=1.0).contains(&blend_factor) { - return Err(anyhow!( + return Err(anyhow::anyhow!( "blend_factor must be between 0 and 1 but got {}", blend_factor )); } if min_target_speed_m_per_s < 0.0 { - return Err(anyhow!( + return Err(anyhow::anyhow!( "min_target_speed_m_per_s must be >= 0 but got {}", min_target_speed_m_per_s )); @@ -73,7 +73,7 @@ impl RustSimDrive { } // Extend the duration of the base cycle if extend_fraction < 0.0 { - return Err(anyhow!( + return Err(anyhow::anyhow!( "extend_fraction must be >= 0.0 but got {}", extend_fraction )); @@ -231,7 +231,7 @@ impl RustSimDrive { ), } } - pub fn set_time_dilation(&mut self, i: usize) -> Result<(), anyhow::Error> { + pub fn set_time_dilation(&mut self, i: usize) -> anyhow::Result<()> { // if prescribed speed is zero, trace is met to avoid div-by-zero errors and other possible wackiness let mut trace_met = (self.cyc.dist_m().slice(s![0..(i + 1)]).sum() - self.dist_m.slice(s![0..(i + 1)]).sum()) @@ -962,7 +962,7 @@ impl RustSimDrive { /// Placeholder for method to impose coasting. /// Might be good to include logic for deciding when to coast. /// Solve for the next-step speed that will yield a zero roadload - pub fn set_coast_speed(&mut self, i: usize) -> Result<(), anyhow::Error> { + pub fn set_coast_speed(&mut self, i: usize) -> anyhow::Result<()> { let tol = 1e-6; let v0 = self.mps_ach[i - 1]; if v0 > tol && !self.impose_coast[i] && self.should_impose_coast(i) { diff --git a/rust/fastsim-core/src/simdrive/simdrive_impl.rs b/rust/fastsim-core/src/simdrive/simdrive_impl.rs index 75b2d196..a3d251a3 100644 --- a/rust/fastsim-core/src/simdrive/simdrive_impl.rs +++ b/rust/fastsim-core/src/simdrive/simdrive_impl.rs @@ -400,7 +400,7 @@ impl RustSimDrive { &mut self, init_soc: Option, aux_in_kw_override: Option>, - ) -> Result<(), anyhow::Error> { + ) -> anyhow::Result<()> { self.hev_sim_count = 0; let init_soc = match init_soc { @@ -429,7 +429,7 @@ impl RustSimDrive { - self .soc .last() - .ok_or_else(|| anyhow!(format_dbg!(self.soc)))?) + .ok_or_else(|| anyhow::anyhow!(format_dbg!(self.soc)))?) * self.veh.ess_max_kwh * 3.6e3 / (fuel_kj + roadway_chg_kj)) @@ -444,7 +444,7 @@ impl RustSimDrive { *self .soc .last() - .ok_or_else(|| anyhow!(format_dbg!(self.soc)))?, + .ok_or_else(|| anyhow::anyhow!(format_dbg!(self.soc)))?, ), ); } @@ -453,7 +453,7 @@ impl RustSimDrive { // If EV, initializing initial SOC to maximum SOC. self.veh.max_soc } else { - bail!("Failed to properly initialize SOC."); + anyhow::bail!("Failed to properly initialize SOC."); } } }; @@ -468,7 +468,7 @@ impl RustSimDrive { &mut self, init_soc: Option, aux_in_kw_override: Option>, - ) -> Result<(), anyhow::Error> { + ) -> anyhow::Result<()> { // Initialize and run sim_drive_walk as appropriate for vehicle attribute vehPtType. let init_soc_auto: f64 = match self.veh.veh_pt_type.as_str() { // If no EV / Hybrid components, no SOC considerations. @@ -497,7 +497,7 @@ impl RustSimDrive { &mut self, init_soc: f64, aux_in_kw_override: Option>, - ) -> Result<(), anyhow::Error> { + ) -> anyhow::Result<()> { self.init_for_step(init_soc, aux_in_kw_override)?; while self.i < self.cyc.time_s.len() { self.step()?; @@ -524,8 +524,8 @@ impl RustSimDrive { &mut self, init_soc: f64, aux_in_kw_override: Option>, - ) -> Result<(), anyhow::Error> { - ensure!( + ) -> anyhow::Result<()> { + anyhow::ensure!( self.veh.veh_pt_type == CONV || (self.veh.min_soc..=self.veh.max_soc).contains(&init_soc), "provided init_soc={} is outside range min_soc={} to max_soc={}", @@ -562,7 +562,7 @@ impl RustSimDrive { } /// Step through 1 time step. - pub fn step(&mut self) -> Result<(), anyhow::Error> { + pub fn step(&mut self) -> anyhow::Result<()> { if self.sim_params.idm_allow { self.idm_target_speed_m_per_s[self.i] = match &self.sim_params.idm_v_desired_in_m_per_s_by_distance_m { @@ -603,7 +603,7 @@ impl RustSimDrive { } /// Perform all the calculations to solve 1 time step. - pub fn solve_step(&mut self, i: usize) -> Result<(), anyhow::Error> { + pub fn solve_step(&mut self, i: usize) -> anyhow::Result<()> { self.set_misc_calcs(i)?; self.set_comp_lims(i)?; self.set_power_calcs(i)?; @@ -619,7 +619,7 @@ impl RustSimDrive { /// Arguments: /// ---------- /// i: index of time step - pub fn set_misc_calcs(&mut self, i: usize) -> Result<(), anyhow::Error> { + pub fn set_misc_calcs(&mut self, i: usize) -> anyhow::Result<()> { // if cycle iteration is used, auxInKw must be re-zeroed to trigger the below if statement // TODO: this is probably computationally expensive and was probably a workaround for numba // figure out a way to not need this @@ -656,7 +656,7 @@ impl RustSimDrive { /// ------------ /// i: index of time step /// initSoc: initial SOC for electrified vehicles - pub fn set_comp_lims(&mut self, i: usize) -> Result<(), anyhow::Error> { + pub fn set_comp_lims(&mut self, i: usize) -> anyhow::Result<()> { // max fuel storage power output self.cur_max_fs_kw_out[i] = min( self.veh.fs_max_kw, @@ -715,14 +715,13 @@ impl RustSimDrive { if self.cur_max_elec_kw[i] > 0.0 { // limit power going into e-machine controller to if self.cur_max_avail_elec_kw[i] == arrmax(&self.veh.mc_kw_in_array) { - self.mc_elec_in_lim_kw[i] = min( - *self - .veh - .mc_kw_out_array - .last() - .ok_or_else(|| anyhow!(format_dbg!(self.veh.mc_kw_out_array)))?, - self.veh.mc_max_kw, - ); + self.mc_elec_in_lim_kw[i] = + min( + *self.veh.mc_kw_out_array.last().ok_or_else(|| { + anyhow::anyhow!(format_dbg!(self.veh.mc_kw_out_array)) + })?, + self.veh.mc_max_kw, + ); } else { self.mc_elec_in_lim_kw[i] = min( self.veh.mc_kw_out_array[first_grtr( @@ -732,7 +731,7 @@ impl RustSimDrive { self.cur_max_avail_elec_kw[i], ), ) - .ok_or_else(|| anyhow!(format_dbg!("`first_grtr` returned `None`")))? + .ok_or_else(|| anyhow::anyhow!(format_dbg!("`first_grtr` returned `None`")))? - 1_usize], self.veh.mc_max_kw, ) @@ -761,7 +760,7 @@ impl RustSimDrive { .veh .mc_full_eff_array .last() - .ok_or_else(|| anyhow!(format_dbg!(self.veh.mc_full_eff_array)))?; + .ok_or_else(|| anyhow::anyhow!(format_dbg!(self.veh.mc_full_eff_array)))?; } else { self.cur_max_mc_elec_kw_in[i] = self.cur_max_mc_kw_out[i] / self.veh.mc_full_eff_array[cmp::max( @@ -770,7 +769,7 @@ impl RustSimDrive { &self.veh.mc_kw_out_array, min(self.veh.mc_max_kw * 0.9999, self.cur_max_mc_kw_out[i]), ) - .ok_or_else(|| anyhow!(format_dbg!("`first_grtr` returned `None`")))? + .ok_or_else(|| anyhow::anyhow!(format_dbg!("`first_grtr` returned `None`")))? - 1, )] } @@ -858,7 +857,7 @@ impl RustSimDrive { /// Arguments /// ------------ /// i: index of time step - pub fn set_power_calcs(&mut self, i: usize) -> Result<(), anyhow::Error> { + pub fn set_power_calcs(&mut self, i: usize) -> anyhow::Result<()> { let mps_ach = if self.newton_iters[i] > 0u32 { self.mps_ach[i] } else { @@ -959,7 +958,7 @@ impl RustSimDrive { // Arguments // ------------ // i: index of time step - pub fn set_ach_speed(&mut self, i: usize) -> Result<(), anyhow::Error> { + pub fn set_ach_speed(&mut self, i: usize) -> anyhow::Result<()> { // Cycle is met if self.cyc_met[i] { self.mps_ach[i] = self.cyc.mps[i]; @@ -1072,7 +1071,7 @@ impl RustSimDrive { xs[_ys .iter() .position(|&x| x == ndarrmin(&_ys)) - .ok_or_else(|| anyhow!(format_dbg!(ndarrmin(&_ys))))?], + .ok_or_else(|| anyhow::anyhow!(format_dbg!(ndarrmin(&_ys))))?], 0.0, ); grade_estimate = self.lookup_grade_for_step(i, Some(self.mps_ach[i])); @@ -1091,7 +1090,7 @@ impl RustSimDrive { /// Arguments /// ------------ /// i: index of time step - pub fn set_hybrid_cont_calcs(&mut self, i: usize) -> Result<(), anyhow::Error> { + pub fn set_hybrid_cont_calcs(&mut self, i: usize) -> anyhow::Result<()> { if self.veh.no_elec_sys { self.regen_buff_soc[i] = 0.0; } else if self.veh.charging_on { @@ -1202,12 +1201,11 @@ impl RustSimDrive { self.mc_elec_in_kw_for_max_fc_eff[i] = 0.0; } else if self.trans_kw_out_ach[i] < self.veh.max_fc_eff_kw() { if self.fc_kw_gap_fr_eff[i] == self.veh.mc_max_kw { - self.mc_elec_in_kw_for_max_fc_eff[i] = -self.fc_kw_gap_fr_eff[i] - / self - .veh - .mc_full_eff_array - .last() - .ok_or_else(|| anyhow!(format_dbg!(self.veh.mc_full_eff_array)))?; + self.mc_elec_in_kw_for_max_fc_eff[i] = + -self.fc_kw_gap_fr_eff[i] + / self.veh.mc_full_eff_array.last().ok_or_else(|| { + anyhow::anyhow!(format_dbg!(self.veh.mc_full_eff_array)) + })?; } else { self.mc_elec_in_kw_for_max_fc_eff[i] = -self.fc_kw_gap_fr_eff[i] / self.veh.mc_full_eff_array[cmp::max( @@ -1216,8 +1214,9 @@ impl RustSimDrive { &self.veh.mc_kw_out_array, min(self.veh.mc_max_kw * 0.9999, self.fc_kw_gap_fr_eff[i]), ) - .ok_or_else(|| anyhow!(format_dbg!("`first_grtr` returned `None`")))? - - 1, + .ok_or_else(|| { + anyhow::anyhow!(format_dbg!("`first_grtr` returned `None`")) + })? - 1, )]; } } else if self.fc_kw_gap_fr_eff[i] == self.veh.mc_max_kw { @@ -1225,7 +1224,7 @@ impl RustSimDrive { .veh .mc_kw_in_array .last() - .ok_or_else(|| anyhow!(format_dbg!(self.veh.mc_kw_in_array)))?; + .ok_or_else(|| anyhow::anyhow!(format_dbg!(self.veh.mc_kw_in_array)))?; } else { self.mc_elec_in_kw_for_max_fc_eff[i] = self.veh.mc_kw_in_array[first_grtr( &self.veh.mc_kw_out_array, @@ -1238,13 +1237,12 @@ impl RustSimDrive { self.elec_kw_req_4ae[i] = 0.0; } else if self.trans_kw_in_ach[i] > 0.0 { if self.trans_kw_in_ach[i] == self.veh.mc_max_kw { - self.elec_kw_req_4ae[i] = self.trans_kw_in_ach[i] - / self - .veh - .mc_full_eff_array - .last() - .ok_or_else(|| anyhow!(format_dbg!(self.veh.mc_full_eff_array)))? - + self.aux_in_kw[i]; + self.elec_kw_req_4ae[i] = + self.trans_kw_in_ach[i] + / self.veh.mc_full_eff_array.last().ok_or_else(|| { + anyhow::anyhow!(format_dbg!(self.veh.mc_full_eff_array)) + })? + + self.aux_in_kw[i]; } else { self.elec_kw_req_4ae[i] = self.trans_kw_in_ach[i] / self.veh.mc_full_eff_array[cmp::max( @@ -1253,8 +1251,9 @@ impl RustSimDrive { &self.veh.mc_kw_out_array, min(self.veh.mc_max_kw * 0.9999, self.trans_kw_in_ach[i]), ) - .ok_or_else(|| anyhow!(format_dbg!("`first_grtr` returned `None`")))? - - 1, + .ok_or_else(|| { + anyhow::anyhow!(format_dbg!("`first_grtr` returned `None`")) + })? - 1, )] + self.aux_in_kw[i]; } @@ -1329,7 +1328,7 @@ impl RustSimDrive { /// Arguments /// ------------ /// i: index of time step - pub fn set_fc_forced_state_rust(&mut self, i: usize) -> Result<(), anyhow::Error> { + pub fn set_fc_forced_state_rust(&mut self, i: usize) -> anyhow::Result<()> { // force fuel converter on if it was on in the previous time step, but only if fc // has not been on longer than minFcTimeOn if self.prev_fc_time_on[i] > 0.0 @@ -1380,7 +1379,7 @@ impl RustSimDrive { /// Arguments /// ------------ /// i: index of time step - pub fn set_hybrid_cont_decisions(&mut self, i: usize) -> Result<(), anyhow::Error> { + pub fn set_hybrid_cont_decisions(&mut self, i: usize) -> anyhow::Result<()> { if (-self.mc_elec_in_kw_for_max_fc_eff[i] - self.cur_max_roadway_chg_kw[i]) > 0.0 { self.ess_desired_kw_4fc_eff[i] = (-self.mc_elec_in_kw_for_max_fc_eff[i] - self.cur_max_roadway_chg_kw[i]) @@ -1502,12 +1501,11 @@ impl RustSimDrive { self.mc_kw_if_fc_req[i] = 0.0; } else if self.mc_elec_kw_in_if_fc_req[i] > 0.0 { if self.mc_elec_kw_in_if_fc_req[i] == arrmax(&self.veh.mc_kw_in_array) { - self.mc_kw_if_fc_req[i] = self.mc_elec_kw_in_if_fc_req[i] - * self - .veh - .mc_full_eff_array - .last() - .ok_or_else(|| anyhow!(format_dbg!(self.veh.mc_full_eff_array)))?; + self.mc_kw_if_fc_req[i] = + self.mc_elec_kw_in_if_fc_req[i] + * self.veh.mc_full_eff_array.last().ok_or_else(|| { + anyhow::anyhow!(format_dbg!(self.veh.mc_full_eff_array)) + })?; } else { self.mc_kw_if_fc_req[i] = self.mc_elec_kw_in_if_fc_req[i] * self.veh.mc_full_eff_array[cmp::max( @@ -1519,8 +1517,9 @@ impl RustSimDrive { self.mc_elec_kw_in_if_fc_req[i], ), ) - .ok_or_else(|| anyhow!(format_dbg!("`first_grtr` returned `None`")))? - - 1, + .ok_or_else(|| { + anyhow::anyhow!(format_dbg!("`first_grtr` returned `None`")) + })? - 1, )] } } else if -self.mc_elec_kw_in_if_fc_req[i] == arrmax(&self.veh.mc_kw_in_array) { @@ -1529,7 +1528,7 @@ impl RustSimDrive { .veh .mc_full_eff_array .last() - .ok_or_else(|| anyhow!(format_dbg!(self.veh.mc_full_eff_array)))?; + .ok_or_else(|| anyhow::anyhow!(format_dbg!(self.veh.mc_full_eff_array)))?; } else { self.mc_kw_if_fc_req[i] = self.mc_elec_kw_in_if_fc_req[i] / self.veh.mc_full_eff_array[cmp::max( @@ -1541,7 +1540,7 @@ impl RustSimDrive { -self.mc_elec_kw_in_if_fc_req[i], ), ) - .ok_or_else(|| anyhow!(format_dbg!("`first_grtr` returned `None`")))? + .ok_or_else(|| anyhow::anyhow!(format_dbg!("`first_grtr` returned `None`")))? - 1, )]; } @@ -1595,8 +1594,9 @@ impl RustSimDrive { -self.mc_mech_kw_out_ach[i], ), ) - .ok_or_else(|| anyhow!(format_dbg!("`first_grtr` returned `None`")))? - - 1, + .ok_or_else(|| { + anyhow::anyhow!(format_dbg!("`first_grtr` returned `None`")) + })? - 1, )]; } } else if self.veh.mc_max_kw == self.mc_mech_kw_out_ach[i] { @@ -1611,7 +1611,7 @@ impl RustSimDrive { &self.veh.mc_kw_out_array, min(self.veh.mc_max_kw * 0.9999, self.mc_mech_kw_out_ach[i]), ) - .ok_or_else(|| anyhow!(format_dbg!("`first_grtr` returned `None`")))? + .ok_or_else(|| anyhow::anyhow!(format_dbg!("`first_grtr` returned `None`")))? - 1, )]; } @@ -1701,7 +1701,7 @@ impl RustSimDrive { /// Arguments /// ------------ /// i: index of time step - pub fn set_fc_power(&mut self, i: usize) -> Result<(), anyhow::Error> { + pub fn set_fc_power(&mut self, i: usize) -> anyhow::Result<()> { if self.veh.fc_max_kw == 0.0 { self.fc_kw_out_ach[i] = 0.0; } else if self.veh.fc_eff_type == H2FC { @@ -1742,7 +1742,7 @@ impl RustSimDrive { &self.veh.fc_kw_out_array, min(self.fc_kw_out_ach[i], self.veh.fc_max_kw), ) - .ok_or_else(|| anyhow!(format_dbg!("`first_grtr` returned `None`")))? + .ok_or_else(|| anyhow::anyhow!(format_dbg!("`first_grtr` returned `None`")))? - 1] != 0.0 { @@ -1751,7 +1751,7 @@ impl RustSimDrive { &self.veh.fc_kw_out_array, min(self.fc_kw_out_ach[i], self.veh.fc_max_kw), ) - .ok_or_else(|| anyhow!(format_dbg!("`first_grtr` returned `None`")))? + .ok_or_else(|| anyhow::anyhow!(format_dbg!("`first_grtr` returned `None`")))? - 1]); } else { self.fc_kw_in_ach[i] = 0.0 @@ -1765,7 +1765,7 @@ impl RustSimDrive { /// Sets scalar variables that can be calculated after a cycle is run. /// This includes mpgge, various energy metrics, and others - pub fn set_post_scalars(&mut self) -> Result<(), anyhow::Error> { + pub fn set_post_scalars(&mut self) -> anyhow::Result<()> { if self.fs_kwh_out_ach.sum() == 0.0 { self.mpgge = 0.0; } else { @@ -1777,7 +1777,7 @@ impl RustSimDrive { * (self .soc .last() - .ok_or_else(|| anyhow!(format_dbg!(self.soc)))? + .ok_or_else(|| anyhow::anyhow!(format_dbg!(self.soc)))? - self.soc[0]) * self.veh.ess_max_kwh * 3.6e3; @@ -1845,12 +1845,12 @@ impl RustSimDrive { * (self .mps_ach .first() - .ok_or_else(|| anyhow!(format_dbg!(self.mps_ach)))? + .ok_or_else(|| anyhow::anyhow!(format_dbg!(self.mps_ach)))? .powf(2.0) - self .mps_ach .last() - .ok_or_else(|| anyhow!(format_dbg!(self.mps_ach)))? + .ok_or_else(|| anyhow::anyhow!(format_dbg!(self.mps_ach)))? .powf(2.0)) / 1_000.0; @@ -1882,7 +1882,7 @@ impl RustSimDrive { .cyc .time_s .last() - .ok_or_else(|| anyhow!(format_dbg!(self.cyc.time_s)))? + .ok_or_else(|| anyhow::anyhow!(format_dbg!(self.cyc.time_s)))? // already checked above - self.cyc0.time_s.last().unwrap()) // already checked above diff --git a/rust/fastsim-core/src/simdrivelabel.rs b/rust/fastsim-core/src/simdrivelabel.rs index 4515a598..23d29924 100644 --- a/rust/fastsim-core/src/simdrivelabel.rs +++ b/rust/fastsim-core/src/simdrivelabel.rs @@ -141,10 +141,7 @@ pub fn make_accel_trace_py() -> RustCycle { make_accel_trace() } -pub fn get_net_accel( - sd_accel: &mut RustSimDrive, - scenario_name: &String, -) -> Result { +pub fn get_net_accel(sd_accel: &mut RustSimDrive, scenario_name: &String) -> anyhow::Result { log::debug!("running `sim_drive_accel`"); sd_accel.sim_drive_accel(None, None)?; if sd_accel.mph_ach.iter().any(|&x| x >= 60.) { @@ -163,7 +160,7 @@ 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 { +pub fn get_net_accel_py(sd_accel: &mut RustSimDrive, scenario_name: &str) -> anyhow::Result { let result = get_net_accel(sd_accel, &scenario_name.to_string())?; Ok(result) } @@ -426,7 +423,7 @@ pub fn get_label_fe_py( veh: &vehicle::RustVehicle, full_detail: Option, verbose: Option, -) -> PyResult<(LabelFe, Option>)> { +) -> anyhow::Result<(LabelFe, Option>)> { let result: (LabelFe, Option>) = get_label_fe(veh, full_detail, verbose)?; Ok(result) @@ -439,7 +436,7 @@ pub fn get_label_fe_phev( adj_params: &AdjCoef, sim_params: &RustSimDriveParams, props: &RustPhysicalProperties, -) -> Result { +) -> anyhow::Result { // PHEV-specific function for label fe. // // Arguments: @@ -722,7 +719,7 @@ pub fn get_label_fe_phev( match *key { "udds" => phev_calcs.udds = phev_calc.clone(), "hwy" => phev_calcs.hwy = phev_calc.clone(), - &_ => return Err(anyhow!("No field for cycle {}", key)), + &_ => return Err(anyhow::anyhow!("No field for cycle {}", key)), }; } @@ -740,7 +737,7 @@ pub fn get_label_fe_phev_py( long_params: RustLongParams, sim_params: &RustSimDriveParams, props: RustPhysicalProperties, -) -> Result { +) -> anyhow::Result { let mut sd_mut = HashMap::new(); for (key, value) in sd { sd_mut.insert(key, value); diff --git a/rust/fastsim-core/src/thermal.rs b/rust/fastsim-core/src/thermal.rs index ba7fa12a..dbde5c14 100644 --- a/rust/fastsim-core/src/thermal.rs +++ b/rust/fastsim-core/src/thermal.rs @@ -26,7 +26,7 @@ use crate::vehicle_thermal::*; #[pyo3(name = "gap_to_lead_vehicle_m")] /// Provides the gap-with lead vehicle from start to finish - pub fn gap_to_lead_vehicle_m_py(&self) -> PyResult> { + pub fn gap_to_lead_vehicle_m_py(&self) -> anyhow::Result> { Ok(self.gap_to_lead_vehicle_m().to_vec()) } #[pyo3(name = "sim_drive")] @@ -40,9 +40,9 @@ use crate::vehicle_thermal::*; &mut self, init_soc: Option, aux_in_kw_override: Option>, - ) -> PyResult<()> { + ) -> anyhow::Result<()> { let aux_in_kw_override = aux_in_kw_override.map(Array1::from); - Ok(self.sim_drive(init_soc, aux_in_kw_override)?) + self.sim_drive(init_soc, aux_in_kw_override) } /// Receives second-by-second cycle information, vehicle properties, @@ -60,10 +60,9 @@ use crate::vehicle_thermal::*; &mut self, init_soc: f64, aux_in_kw_override: Option>, - ) -> PyResult<()> { + ) -> anyhow::Result<()> { let aux_in_kw_override = aux_in_kw_override.map(Array1::from); - self.walk(init_soc, aux_in_kw_override); - Ok(()) + Ok(self.walk(init_soc, aux_in_kw_override)) } #[pyo3(name = "init_for_step")] @@ -78,21 +77,19 @@ use crate::vehicle_thermal::*; &mut self, init_soc:f64, aux_in_kw_override: Option> - ) -> PyResult<()> { + ) -> anyhow::Result<()> { let aux_in_kw_override = aux_in_kw_override.map(Array1::from); - self.init_for_step(init_soc, aux_in_kw_override); - Ok(()) + Ok(self.init_for_step(init_soc, aux_in_kw_override)) } /// Step through 1 time step. - pub fn sim_drive_step(&mut self) -> PyResult<()> { - Ok(self.step()?) + pub fn sim_drive_step(&mut self) -> anyhow::Result<()> { + self.step() } #[pyo3(name = "solve_step")] /// Perform all the calculations to solve 1 time step. - pub fn solve_step_py(&mut self, i: usize) -> PyResult<()> { - self.solve_step(i); - Ok(()) + pub fn solve_step_py(&mut self, i: usize) -> anyhow::Result<()> { + Ok(self.solve_step(i)) } #[pyo3(name = "set_misc_calcs")] @@ -100,9 +97,8 @@ use crate::vehicle_thermal::*; /// Arguments: /// ---------- /// i: index of time step - pub fn set_misc_calcs_py(&mut self, i: usize) -> PyResult<()> { - self.set_misc_calcs(i); - Ok(()) + pub fn set_misc_calcs_py(&mut self, i: usize) -> anyhow::Result<()> { + Ok(self.set_misc_calcs(i)) } #[pyo3(name = "set_comp_lims")] @@ -110,8 +106,8 @@ use crate::vehicle_thermal::*; // Arguments // ------------ // i: index of time step - pub fn set_comp_lims_py(&mut self, i: usize) -> PyResult<()> { - Ok(self.set_comp_lims(i)?) + pub fn set_comp_lims_py(&mut self, i: usize) -> anyhow::Result<()> { + self.set_comp_lims(i) } #[pyo3(name = "set_power_calcs")] @@ -120,8 +116,8 @@ use crate::vehicle_thermal::*; /// Arguments /// ------------ /// i: index of time step - pub fn set_power_calcs_py(&mut self, i: usize) -> PyResult<()> { - Ok(self.set_power_calcs(i)?) + pub fn set_power_calcs_py(&mut self, i: usize) -> anyhow::Result<()> { + self.set_power_calcs(i) } #[pyo3(name = "set_ach_speed")] @@ -129,8 +125,8 @@ use crate::vehicle_thermal::*; // Arguments // ------------ // i: index of time step - pub fn set_ach_speed_py(&mut self, i: usize) -> PyResult<()> { - Ok(self.set_ach_speed(i)?) + pub fn set_ach_speed_py(&mut self, i: usize) -> anyhow::Result<()> { + self.set_ach_speed(i) } #[pyo3(name = "set_hybrid_cont_calcs")] @@ -138,8 +134,8 @@ use crate::vehicle_thermal::*; /// Arguments /// ------------ /// i: index of time step - pub fn set_hybrid_cont_calcs_py(&mut self, i: usize) -> PyResult<()> { - Ok(self.set_hybrid_cont_calcs(i)?) + pub fn set_hybrid_cont_calcs_py(&mut self, i: usize) -> anyhow::Result<()> { + self.set_hybrid_cont_calcs(i) } #[pyo3(name = "set_fc_forced_state")] @@ -148,8 +144,8 @@ use crate::vehicle_thermal::*; /// ------------ /// i: index of time step /// `_py` extension is needed to avoid name collision with getter/setter methods - pub fn set_fc_forced_state_py(&mut self, i: usize) -> PyResult<()> { - Ok(self.set_fc_forced_state_rust(i)?) + pub fn set_fc_forced_state_py(&mut self, i: usize) -> anyhow::Result<()> { + self.set_fc_forced_state_rust(i) } #[pyo3(name = "set_hybrid_cont_decisions")] @@ -157,8 +153,8 @@ use crate::vehicle_thermal::*; /// Arguments /// ------------ /// i: index of time step - pub fn set_hybrid_cont_decisions_py(&mut self, i: usize) -> PyResult<()> { - Ok(self.set_hybrid_cont_decisions(i)?) + pub fn set_hybrid_cont_decisions_py(&mut self, i: usize) -> anyhow::Result<()> { + self.set_hybrid_cont_decisions(i) } #[pyo3(name = "set_fc_power")] @@ -166,8 +162,8 @@ use crate::vehicle_thermal::*; /// Arguments /// ------------ /// i: index of time step - pub fn set_fc_power_py(&mut self, i: usize) -> PyResult<()> { - Ok(self.set_fc_power(i)?) + pub fn set_fc_power_py(&mut self, i: usize) -> anyhow::Result<()> { + self.set_fc_power(i) } #[pyo3(name = "set_time_dilation")] @@ -175,15 +171,15 @@ use crate::vehicle_thermal::*; /// Arguments /// ------------ /// i: index of time step - pub fn set_time_dilation_py(&mut self, i: usize) -> PyResult<()> { - Ok(self.set_time_dilation(i)?) + pub fn set_time_dilation_py(&mut self, i: usize) -> anyhow::Result<()> { + self.set_time_dilation(i) } #[pyo3(name = "set_post_scalars")] /// Sets scalar variables that can be calculated after a cycle is run. /// This includes mpgge, various energy metrics, and others - pub fn set_post_scalars_py(&mut self) -> PyResult<()> { - Ok(self.set_post_scalars()?) + pub fn set_post_scalars_py(&mut self) -> anyhow::Result<()> { + self.set_post_scalars() } )] #[derive(Serialize, Deserialize, Clone, PartialEq, Debug)] @@ -259,7 +255,7 @@ impl SimDriveHot { &mut self, init_soc: Option, aux_in_kw_override: Option>, - ) -> Result<(), anyhow::Error> { + ) -> anyhow::Result<()> { self.sd.hev_sim_count = 0; let init_soc = match init_soc { @@ -302,7 +298,7 @@ impl SimDriveHot { // If EV, initializing initial SOC to maximum SOC. self.sd.veh.max_soc } else { - bail!("Failed to properly initialize SOC."); + anyhow::bail!("Failed to properly initialize SOC."); } } }; @@ -339,7 +335,7 @@ impl SimDriveHot { self.sd.set_speed_for_target_gap(i); } - pub fn step(&mut self) -> Result<(), anyhow::Error> { + pub fn step(&mut self) -> anyhow::Result<()> { self.set_thermal_calcs(self.sd.i); self.set_misc_calcs(self.sd.i); self.set_comp_lims(self.sd.i)?; @@ -844,31 +840,31 @@ impl SimDriveHot { self.sd.mps_ach[i - 1] + (self.sd.veh.max_trac_mps2 * self.sd.cyc.dt_s_at_i(i)); } - pub fn set_comp_lims(&mut self, i: usize) -> Result<(), anyhow::Error> { + pub fn set_comp_lims(&mut self, i: usize) -> anyhow::Result<()> { self.sd.set_comp_lims(i) } - pub fn set_power_calcs(&mut self, i: usize) -> Result<(), anyhow::Error> { + pub fn set_power_calcs(&mut self, i: usize) -> anyhow::Result<()> { self.sd.set_power_calcs(i) } - pub fn set_ach_speed(&mut self, i: usize) -> Result<(), anyhow::Error> { + pub fn set_ach_speed(&mut self, i: usize) -> anyhow::Result<()> { self.sd.set_ach_speed(i) } - pub fn set_hybrid_cont_calcs(&mut self, i: usize) -> Result<(), anyhow::Error> { + pub fn set_hybrid_cont_calcs(&mut self, i: usize) -> anyhow::Result<()> { self.sd.set_hybrid_cont_calcs(i) } - pub fn set_fc_forced_state_rust(&mut self, i: usize) -> Result<(), anyhow::Error> { + pub fn set_fc_forced_state_rust(&mut self, i: usize) -> anyhow::Result<()> { self.sd.set_fc_forced_state_rust(i) } - pub fn set_hybrid_cont_decisions(&mut self, i: usize) -> Result<(), anyhow::Error> { + pub fn set_hybrid_cont_decisions(&mut self, i: usize) -> anyhow::Result<()> { self.sd.set_hybrid_cont_decisions(i) } - pub fn set_fc_power(&mut self, i: usize) -> Result<(), anyhow::Error> { + pub fn set_fc_power(&mut self, i: usize) -> anyhow::Result<()> { if self.sd.veh.fc_max_kw == 0.0 { self.sd.fc_kw_out_ach[i] = 0.0; } else if self.sd.veh.fc_eff_type == vehicle::H2FC { @@ -984,11 +980,11 @@ impl SimDriveHot { Ok(()) } - pub fn set_time_dilation(&mut self, i: usize) -> Result<(), anyhow::Error> { + pub fn set_time_dilation(&mut self, i: usize) -> anyhow::Result<()> { self.sd.set_time_dilation(i) } - pub fn set_post_scalars(&mut self) -> Result<(), anyhow::Error> { + pub fn set_post_scalars(&mut self) -> anyhow::Result<()> { self.sd.set_post_scalars() } } diff --git a/rust/fastsim-core/src/traits.rs b/rust/fastsim-core/src/traits.rs index 0507a66b..94cf1103 100644 --- a/rust/fastsim-core/src/traits.rs +++ b/rust/fastsim-core/src/traits.rs @@ -13,14 +13,14 @@ pub trait SerdeAPI: Serialize + for<'a> Deserialize<'a> { /// /// # Argument: /// - /// * `filename`: a `str` storing the targeted file name. Currently `.json` and `.yaml` suffixes are + /// * `filepath`: a `str` storing the targeted file name. Currently `.json` and `.yaml` suffixes are /// supported /// /// # Returns: /// /// A Rust Result - fn to_file(&self, filename: &str) -> Result<(), anyhow::Error> { - let file = PathBuf::from(filename); + fn to_file(&self, filepath: &str) -> anyhow::Result<()> { + let file = PathBuf::from(filepath); match file.extension().unwrap().to_str().unwrap() { "json" => serde_json::to_writer(&File::create(file)?, self)?, "yaml" => serde_yaml::to_writer(&File::create(file)?, self)?, @@ -35,62 +35,66 @@ pub trait SerdeAPI: Serialize + for<'a> Deserialize<'a> { /// /// # Argument: /// - /// * `filename`: a `str` storing the targeted file name. Currently `.json` and `.yaml` suffixes are + /// * `filepath`: a `str` storing the targeted file name. Currently `.json` and `.yaml` suffixes are /// supported /// /// # Returns: /// /// A Rust Result wrapping data structure if method is called successfully; otherwise a dynamic /// Error. - fn from_file(filename: &str) -> Result + fn from_file(filepath: &str) -> anyhow::Result where Self: std::marker::Sized, for<'de> Self: Deserialize<'de>, { - let extension = Path::new(filename) + let extension = Path::new(filepath) .extension() .and_then(OsStr::to_str) .unwrap_or(""); - let file = File::open(filename)?; + let file = File::open(filepath)?; // deserialized file let mut file_de: Self = match extension { "yaml" => serde_yaml::from_reader(file)?, "json" => serde_json::from_reader(file)?, - _ => bail!("Unsupported file extension {}", extension), + _ => anyhow::bail!("Unsupported file extension {}", extension), }; file_de.init()?; Ok(file_de) } - /// json serialization method. - fn to_json(&self) -> String { - serde_json::to_string(&self).unwrap() + // /// JSON serialization method + // fn to_json(&self) -> anyhow::Result { + // Ok(serde_json::to_string(&self)?) + // } + /// JSON serialization method. + fn to_json(&self) -> anyhow::Result { + Ok(serde_json::to_string(&self)?) } - /// json deserialization method. - fn from_json(json_str: &str) -> Result { + /// JSON deserialization method + fn from_json(json_str: &str) -> anyhow::Result { Ok(serde_json::from_str(json_str)?) } - /// yaml serialization method. - fn to_yaml(&self) -> String { - serde_yaml::to_string(&self).unwrap() + /// YAML serialization method + fn to_yaml(&self) -> anyhow::Result { + Ok(serde_yaml::to_string(&self)?) } - /// yaml deserialization method. - fn from_yaml(yaml_str: &str) -> Result { + /// YAML deserialization method + fn from_yaml(yaml_str: &str) -> anyhow::Result { Ok(serde_yaml::from_str(yaml_str)?) } - /// bincode serialization method. - fn to_bincode(&self) -> Vec { - serialize(&self).unwrap() + /// bincode serialization method + fn to_bincode(&self) -> anyhow::Result> { + Ok(bincode::serialize(&self)?) } - /// bincode deserialization method. - fn from_bincode(encoded: &[u8]) -> Result { - Ok(deserialize(encoded)?) + /// bincode deserialization method + fn from_bincode(encoded: &[u8]) -> anyhow::Result { + Ok(bincode::deserialize(encoded)?) } } diff --git a/rust/fastsim-core/src/utils.rs b/rust/fastsim-core/src/utils.rs index 01da1c35..a81ecf9b 100644 --- a/rust/fastsim-core/src/utils.rs +++ b/rust/fastsim-core/src/utils.rs @@ -240,7 +240,7 @@ fn find_interp_indices(query: &f64, axis: &[f64]) -> anyhow::Result<(usize, usiz } else if query >= &axis[axis_size - 1] { Ok((axis_size - 1, axis_size - 1)) } else { - bail!("Unable to find where the query fits in the values, check grid.") + anyhow::bail!("Unable to find where the query fits in the values, check grid.") } } } diff --git a/rust/fastsim-core/src/vehicle.rs b/rust/fastsim-core/src/vehicle.rs index 7a44f9b4..41e8392f 100644 --- a/rust/fastsim-core/src/vehicle.rs +++ b/rust/fastsim-core/src/vehicle.rs @@ -78,7 +78,7 @@ lazy_static! { /// An identify function to allow RustVehicle to be used as a python vehicle and respond to this method /// Returns a clone of the current object - pub fn to_rust(&self) -> PyResult { + pub fn to_rust(&self) -> anyhow::Result { Ok(self.clone()) } @@ -677,12 +677,9 @@ impl RustVehicle { /// - `fs_mass_kg` /// - `veh_kg` /// - `max_trac_mps2` - pub fn set_derived(&mut self) -> Result<(), anyhow::Error> { + pub fn set_derived(&mut self) -> anyhow::Result<()> { // Vehicle input validation - match self.validate() { - Ok(_) => (), - Err(e) => bail!(e), - }; + self.validate()?; if self.scenario_name != "Template Vehicle for setting up data types" { if self.veh_pt_type == BEV { @@ -975,8 +972,8 @@ impl RustVehicle { v } - pub fn from_json_str(filename: &str) -> Result { - let mut veh_res: Result = Ok(serde_json::from_str(filename)?); + pub fn from_json_str(json_str: &str) -> anyhow::Result { + let mut veh_res: anyhow::Result = Ok(serde_json::from_str(json_str)?); veh_res.as_mut().unwrap().set_derived()?; veh_res } diff --git a/rust/fastsim-core/src/vehicle_thermal.rs b/rust/fastsim-core/src/vehicle_thermal.rs index 314ef6f8..d71e1b41 100644 --- a/rust/fastsim-core/src/vehicle_thermal.rs +++ b/rust/fastsim-core/src/vehicle_thermal.rs @@ -92,7 +92,7 @@ impl Default for FcTempEffModelExponential { #[add_pyo3_api( #[classmethod] #[pyo3(name = "default")] - pub fn default_py(_cls: &PyType) -> PyResult { + pub fn default_py(_cls: &PyType) -> anyhow::Result { Ok(Self::default()) } )] @@ -202,19 +202,19 @@ pub fn get_sphere_conv_params(re: f64) -> (f64, f64) { pub fn set_cabin_hvac_model_internal( &mut self, hvac_model: HVACModel - ) -> PyResult<()>{ + ) -> anyhow::Result<()>{ Ok(check_orphaned_and_set!(self, cabin_hvac_model, CabinHvacModelTypes::Internal(hvac_model))?) } - pub fn get_cabin_model_internal(&self, ) -> PyResult { + pub fn get_cabin_model_internal(&self, ) -> anyhow::Result { if let CabinHvacModelTypes::Internal(hvac_model) = &self.cabin_hvac_model { Ok(hvac_model.clone()) } else { - Err(PyAttributeError::new_err("HvacModelTypes::External variant currently used.")) + anyhow::bail!(PyAttributeError::new_err("HvacModelTypes::External variant currently used.")) } } - pub fn set_cabin_hvac_model_external(&mut self, ) -> PyResult<()> { + pub fn set_cabin_hvac_model_external(&mut self, ) -> anyhow::Result<()> { Ok(check_orphaned_and_set!(self, cabin_hvac_model, CabinHvacModelTypes::External)?) } @@ -229,10 +229,10 @@ pub fn get_sphere_conv_params(re: f64) -> (f64, f64) { "FuelConverter" => FcTempEffComponent::FuelConverter, "Catalyst" => FcTempEffComponent::Catalyst, "CatAndFC" => FcTempEffComponent::CatAndFC, - _ => bail!("Invalid option for fc_temp_eff_component.") + _ => anyhow::bail!("Invalid option for fc_temp_eff_component.") }; - Ok(check_orphaned_and_set!( + check_orphaned_and_set!( self, fc_model, FcModelTypes::Internal( @@ -240,11 +240,11 @@ pub fn get_sphere_conv_params(re: f64) -> (f64, f64) { FcTempEffModelExponential{ offset, lag, minimum }), fc_temp_eff_comp ) - )?) + ) } #[setter] - pub fn set_fc_exp_offset(&mut self, new_offset: f64) -> PyResult<()> { + pub fn set_fc_exp_offset(&mut self, new_offset: f64) -> anyhow::Result<()> { if !self.orphaned { self.fc_model = if let FcModelTypes::Internal(fc_temp_eff_model, fc_temp_eff_comp) = &self.fc_model { // If model is internal @@ -267,12 +267,12 @@ pub fn get_sphere_conv_params(re: f64) -> (f64, f64) { }; Ok(()) } else { - Err(PyAttributeError::new_err(utils::NESTED_STRUCT_ERR)) + anyhow::bail!(PyAttributeError::new_err(utils::NESTED_STRUCT_ERR)) } } #[setter] - pub fn set_fc_exp_lag(&mut self, new_lag: f64) -> PyResult<()>{ + pub fn set_fc_exp_lag(&mut self, new_lag: f64) -> anyhow::Result<()>{ if !self.orphaned { self.fc_model = if let FcModelTypes::Internal(fc_temp_eff_model, fc_temp_eff_comp) = &self.fc_model { // If model is internal @@ -295,12 +295,12 @@ pub fn get_sphere_conv_params(re: f64) -> (f64, f64) { }; Ok(()) } else { - Err(PyAttributeError::new_err(utils::NESTED_STRUCT_ERR)) + anyhow::bail!(PyAttributeError::new_err(utils::NESTED_STRUCT_ERR)) } } #[setter] - pub fn set_fc_exp_minimum(&mut self, new_minimum: f64) -> PyResult<()> { + pub fn set_fc_exp_minimum(&mut self, new_minimum: f64) -> anyhow::Result<()> { if !self.orphaned { self.fc_model = if let FcModelTypes::Internal(fc_temp_eff_model, fc_temp_eff_comp) = &self.fc_model { // If model is internal @@ -323,34 +323,34 @@ pub fn get_sphere_conv_params(re: f64) -> (f64, f64) { }; Ok(()) } else { - Err(PyAttributeError::new_err(utils::NESTED_STRUCT_ERR)) + anyhow::bail!(PyAttributeError::new_err(utils::NESTED_STRUCT_ERR)) } } #[getter] - pub fn get_fc_exp_offset(&mut self) -> PyResult { + pub fn get_fc_exp_offset(&mut self) -> anyhow::Result { if let FcModelTypes::Internal(FcTempEffModel::Exponential(FcTempEffModelExponential{ offset, ..}), ..) = &self.fc_model { Ok(*offset) } else { - Err(PyAttributeError::new_err("fc_model is not Exponential")) + anyhow::bail!(PyAttributeError::new_err("fc_model is not Exponential")) } } #[getter] - pub fn get_fc_exp_lag(&mut self) -> PyResult { + pub fn get_fc_exp_lag(&mut self) -> anyhow::Result { if let FcModelTypes::Internal(FcTempEffModel::Exponential(FcTempEffModelExponential{ lag, ..}), ..) = &self.fc_model { Ok(*lag) } else { - Err(PyAttributeError::new_err("fc_model is not Exponential")) + anyhow::bail!(PyAttributeError::new_err("fc_model is not Exponential")) } } #[getter] - pub fn get_fc_exp_minimum(&mut self) -> PyResult { + pub fn get_fc_exp_minimum(&mut self) -> anyhow::Result { if let FcModelTypes::Internal(FcTempEffModel::Exponential(FcTempEffModelExponential{ minimum, ..}), ..) = &self.fc_model { Ok(*minimum) } else { - Err(PyAttributeError::new_err("fc_model is not Exponential")) + anyhow::bail!(PyAttributeError::new_err("fc_model is not Exponential")) } } From fd113faa436b10a77a7732385a52633b48b6ae13 Mon Sep 17 00:00:00 2001 From: Kyle Carow Date: Wed, 18 Oct 2023 17:30:48 -0400 Subject: [PATCH 02/27] remove extraneous comment --- rust/fastsim-core/src/traits.rs | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/rust/fastsim-core/src/traits.rs b/rust/fastsim-core/src/traits.rs index 94cf1103..3886f207 100644 --- a/rust/fastsim-core/src/traits.rs +++ b/rust/fastsim-core/src/traits.rs @@ -63,11 +63,7 @@ pub trait SerdeAPI: Serialize + for<'a> Deserialize<'a> { Ok(file_de) } - // /// JSON serialization method - // fn to_json(&self) -> anyhow::Result { - // Ok(serde_json::to_string(&self)?) - // } - /// JSON serialization method. + /// JSON serialization method fn to_json(&self) -> anyhow::Result { Ok(serde_json::to_string(&self)?) } From 021d72e6aa9f2cd009d571c31eac401ab49a5b32 Mon Sep 17 00:00:00 2001 From: Kyle Carow Date: Wed, 18 Oct 2023 17:58:33 -0400 Subject: [PATCH 03/27] anyhowize fastsim-cli --- rust/fastsim-cli/Cargo.toml | 1 + rust/fastsim-cli/src/bin/fastsim-cli.rs | 42 ++++++++++++------------- 2 files changed, 22 insertions(+), 21 deletions(-) diff --git a/rust/fastsim-cli/Cargo.toml b/rust/fastsim-cli/Cargo.toml index d734c2b0..3ad583e3 100644 --- a/rust/fastsim-cli/Cargo.toml +++ b/rust/fastsim-cli/Cargo.toml @@ -12,6 +12,7 @@ repository = "https://github.com/NREL/fastsim" [dependencies] fastsim-core = { path = "../fastsim-core", version = "~0" } +anyhow = { workspace = true } serde = { workspace = true } serde_json = { workspace = true } project-root = "0.2.2" diff --git a/rust/fastsim-cli/src/bin/fastsim-cli.rs b/rust/fastsim-cli/src/bin/fastsim-cli.rs index 7e68702d..fa9e180e 100644 --- a/rust/fastsim-cli/src/bin/fastsim-cli.rs +++ b/rust/fastsim-cli/src/bin/fastsim-cli.rs @@ -1,3 +1,4 @@ +use anyhow; use clap::{ArgGroup, Parser}; use serde::{Deserialize, Serialize}; use serde_json::{json, Value}; @@ -178,7 +179,7 @@ pub fn integrate_power_to_kwh(dts_s: &Vec, ps_kw: &Vec) -> Vec { energy_kwh } -pub fn main() { +pub fn main() -> anyhow::Result<()> { let fastsim_api = FastSimApi::parse(); if let Some(_cyc_json_str) = fastsim_api.cyc { @@ -215,7 +216,7 @@ pub fn main() { ); println!("Drag Coefficient: {}", drag_coeff); println!("Wheel RR Coefficient: {}", wheel_rr_coeff); - return; + return Ok(()); } else { panic!("Need to provide coastdown test coefficients for drag and wheel rr coefficient calculation"); } @@ -231,10 +232,9 @@ pub fn main() { vec![0.0], vec![0.0], vec![0.0], - String::from("") + String::from(""), )) - } - .unwrap(); + }?; // TODO: put in logic here for loading vehicle for adopt-hd // with same file format as regular adopt and same outputs retured @@ -243,7 +243,7 @@ pub fn main() { let mut hd_h2_diesel_ice_h2share: Option> = None; let veh = if let Some(veh_string) = fastsim_api.veh { if is_adopt || is_adopt_hd { - let (veh_string, pwr_out_perc, h2share) = json_rewrite(veh_string); + 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_json_str(&veh_string) @@ -252,8 +252,8 @@ pub fn main() { } } else if let Some(veh_file_path) = fastsim_api.veh_file { if is_adopt || is_adopt_hd { - let vehstring = fs::read_to_string(veh_file_path).unwrap(); - let (vehstring, pwr_out_perc, h2share) = json_rewrite(vehstring); + let vehstring = fs::read_to_string(veh_file_path)?; + let (vehstring, pwr_out_perc, h2share) = json_rewrite(vehstring)?; hd_h2_diesel_ice_h2share = h2share; fc_pwr_out_perc = pwr_out_perc; RustVehicle::from_json_str(&vehstring) @@ -262,8 +262,7 @@ pub fn main() { } } else { Ok(RustVehicle::mock_vehicle()) - } - .unwrap(); + }?; #[cfg(not(windows))] macro_rules! path_separator { @@ -280,7 +279,7 @@ pub fn main() { } if is_adopt { - let sdl = get_label_fe(&veh, Some(false), Some(false)).unwrap(); + let sdl = get_label_fe(&veh, Some(false), Some(false))?; let res = AdoptResults { adjCombMpgge: sdl.0.adj_comb_mpgge, rangeMiles: sdl.0.net_range_miles, @@ -290,7 +289,7 @@ pub fn main() { traceMissInMph: sdl.0.trace_miss_speed_mph, h2AndDiesel: None, }; - println!("{}", res.to_json().unwrap()); + println!("{}", res.to_json()?); } else if is_adopt_hd { let hd_cyc_filestring = include_str!(concat!( "..", @@ -314,12 +313,12 @@ pub fn main() { let cyc = if adopt_hd_has_cycle { cyc } else { - RustCycle::from_csv_string(hd_cyc_filestring, "HHDDTCruiseSmooth".to_string()).unwrap() + RustCycle::from_csv_string(hd_cyc_filestring, "HHDDTCruiseSmooth".to_string())? }; let mut sim_drive = RustSimDrive::new(cyc, veh.clone()); - sim_drive.sim_drive(None, None).unwrap(); + sim_drive.sim_drive(None, None)?; 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 net_accel = get_net_accel(&mut sim_drive_accel, &veh.scenario_name)?; let mut mpgge = sim_drive.mpgge; let h2_diesel_results = if let Some(hd_h2_diesel_ice_h2share) = hd_h2_diesel_ice_h2share { @@ -358,16 +357,17 @@ pub fn main() { traceMissInMph: sim_drive.trace_miss_speed_mps * MPH_PER_MPS, h2AndDiesel: h2_diesel_results, }; - println!("{}", res.to_json().unwrap()); + println!("{}", res.to_json()?); } else { let mut sim_drive = RustSimDrive::new(cyc, veh); // // this does nothing if it has already been called for the constructed `sim_drive` - sim_drive.sim_drive(None, None).unwrap(); + sim_drive.sim_drive(None, None)?; println!("{}", sim_drive.mpgge); } // else { // println!("Invalid option `{}` for `--res-fmt`", res_fmt); // } + Ok(()) } fn translate_veh_pt_type(x: i64) -> String { @@ -422,13 +422,13 @@ struct ParsedValue(Value); impl SerdeAPI for ParsedValue {} /// Rewrites the ADOPT JSON string to be in compliance with what FASTSim expects for JSON input. -fn json_rewrite(x: String) -> (String, Option>, Option>) { +fn json_rewrite(x: String) -> anyhow::Result<(String, Option>, Option>)> { let adoptstring = x; let mut fc_pwr_out_perc: Option> = None; let mut hd_h2_diesel_ice_h2share: Option> = None; - let mut parsed_data: Value = serde_json::from_str(&adoptstring).unwrap(); + let mut parsed_data: Value = serde_json::from_str(&adoptstring)?; let veh_pt_type_raw = &parsed_data["vehPtType"]; if veh_pt_type_raw.is_i64() { @@ -520,7 +520,7 @@ fn json_rewrite(x: String) -> (String, Option>, Option>) { parsed_data["stop_start"] = json!(false); - let adoptstring = ParsedValue(parsed_data).to_json().unwrap(); + let adoptstring = ParsedValue(parsed_data).to_json()?; - (adoptstring, fc_pwr_out_perc, hd_h2_diesel_ice_h2share) + Ok((adoptstring, fc_pwr_out_perc, hd_h2_diesel_ice_h2share)) } From d9aa2d8a48d8d9953f152cd3c73f29fa2b580a96 Mon Sep 17 00:00:00 2001 From: Kyle Carow Date: Thu, 19 Oct 2023 10:29:16 -0400 Subject: [PATCH 04/27] change classmethods to staticmethods, no longer need PyType --- .../fastsim-proc-macros/src/add_pyo3_api/mod.rs | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/rust/fastsim-core/fastsim-proc-macros/src/add_pyo3_api/mod.rs b/rust/fastsim-core/fastsim-proc-macros/src/add_pyo3_api/mod.rs index 9159bf48..8b46284c 100644 --- a/rust/fastsim-core/fastsim-proc-macros/src/add_pyo3_api/mod.rs +++ b/rust/fastsim-core/fastsim-proc-macros/src/add_pyo3_api/mod.rs @@ -112,9 +112,9 @@ pub fn add_pyo3_api(attr: TokenStream, item: TokenStream) -> TokenStream { self.to_file(filepath) } - #[classmethod] + #[staticmethod] #[pyo3(name = "from_file")] - pub fn from_file_py(_cls: &PyType, filepath: &str) -> anyhow::Result { + pub fn from_file_py(filepath: &str) -> anyhow::Result { Self::from_file(filepath) } }); @@ -229,10 +229,10 @@ pub fn add_pyo3_api(attr: TokenStream, item: TokenStream) -> TokenStream { self.to_json() } - #[classmethod] + #[staticmethod] /// JSON deserialization method. #[pyo3(name = "from_json")] - pub fn from_json_py(_cls: &PyType, json_str: &str) -> anyhow::Result { + pub fn from_json_py(json_str: &str) -> anyhow::Result { Self::from_json(json_str) } @@ -242,10 +242,10 @@ pub fn add_pyo3_api(attr: TokenStream, item: TokenStream) -> TokenStream { self.to_yaml() } - #[classmethod] + #[staticmethod] /// YAML deserialization method. #[pyo3(name = "from_yaml")] - pub fn from_yaml_py(_cls: &PyType, yaml_str: &str) -> anyhow::Result { + pub fn from_yaml_py(yaml_str: &str) -> anyhow::Result { Self::from_yaml(yaml_str) } @@ -255,10 +255,10 @@ pub fn add_pyo3_api(attr: TokenStream, item: TokenStream) -> TokenStream { Ok(PyBytes::new(py, &self.to_bincode()?)) } - #[classmethod] + #[staticmethod] /// bincode deserialization method. #[pyo3(name = "from_bincode")] - pub fn from_bincode_py(_cls: &PyType, encoded: &PyBytes) -> anyhow::Result { + pub fn from_bincode_py(encoded: &PyBytes) -> anyhow::Result { Self::from_bincode(encoded.as_bytes()) } From 1f2368a850a0769052d09c455049f7b9c47b3661 Mon Sep 17 00:00:00 2001 From: Kyle Carow Date: Thu, 19 Oct 2023 13:37:52 -0400 Subject: [PATCH 05/27] anyhowize simdrivelabel and cyc_mods --- rust/fastsim-core/src/cycle.rs | 74 ++++++++++++---------- rust/fastsim-core/src/simdrive/cyc_mods.rs | 43 +++++++------ rust/fastsim-core/src/simdrivelabel.rs | 61 ++++-------------- 3 files changed, 78 insertions(+), 100 deletions(-) diff --git a/rust/fastsim-core/src/cycle.rs b/rust/fastsim-core/src/cycle.rs index 2c5d50af..bf98bf9a 100644 --- a/rust/fastsim-core/src/cycle.rs +++ b/rust/fastsim-core/src/cycle.rs @@ -34,9 +34,9 @@ pub fn calc_constant_jerk_trajectory( dr: f64, vr: f64, dt: f64, -) -> (f64, f64) { - assert!(n > 1); - assert!(dr > d0); +) -> anyhow::Result<(f64, f64)> { + anyhow::ensure!(n > 1); + anyhow::ensure!(dr > d0); let n = n as f64; let ddr = dr - d0; let dvr = vr - v0; @@ -48,7 +48,7 @@ pub fn calc_constant_jerk_trajectory( - n * v0 - ((1.0 / 6.0) * n * (n - 1.0) * (n - 2.0) * dt + 0.25 * n * (n - 1.0) * dt * dt) * k) / (0.5 * n * n * dt); - (k, a0) + Ok((k, a0)) } #[cfg_attr(feature = "pyo3", pyfunction)] @@ -527,7 +527,7 @@ impl RustCycleCache { idx: usize, dts_m: Option ) -> anyhow::Result<(f64, usize)> { - Ok(self.modify_with_braking_trajectory(brake_accel_m_per_s2, idx, dts_m)) + self.modify_with_braking_trajectory(brake_accel_m_per_s2, idx, dts_m) } #[pyo3(name = "calc_distance_to_next_stop_from")] @@ -859,10 +859,10 @@ impl RustCycle { brake_accel_m_per_s2: f64, i: usize, dts_m: Option, - ) -> (f64, usize) { - assert!(brake_accel_m_per_s2 < 0.0); + ) -> anyhow::Result<(f64, usize)> { + anyhow::ensure!(brake_accel_m_per_s2 < 0.0); if i >= self.time_s.len() { - return (*self.mps.last().unwrap(), 0); + return Ok((*self.mps.last().unwrap(), 0)); } let v0 = self.mps[i - 1]; let dt = self.dt_s_at_i(i); @@ -878,7 +878,7 @@ impl RustCycle { None => -0.5 * v0 * v0 / brake_accel_m_per_s2, }; if dts_m <= 0.0 { - return (v0, 0); + return Ok((v0, 0)); } // time-to-stop (s) let tts_s = -v0 / brake_accel_m_per_s2; @@ -886,11 +886,11 @@ impl RustCycle { let n: usize = (tts_s / dt).round() as usize; let n: usize = if n < 2 { 2 } else { n }; // need at least 2 steps let (jerk_m_per_s3, accel_m_per_s2) = - calc_constant_jerk_trajectory(n, 0.0, v0, dts_m, 0.0, dt); - ( + calc_constant_jerk_trajectory(n, 0.0, v0, dts_m, 0.0, dt)?; + Ok(( self.modify_by_const_jerk_trajectory(i, n, jerk_m_per_s3, accel_m_per_s2), n, - ) + )) } /// rust-internal time steps @@ -914,15 +914,22 @@ impl RustCycle { } /// Load cycle from csv file - pub fn from_csv_file(pathstr: &str) -> anyhow::Result { - let pathbuf = PathBuf::from(&pathstr); + pub fn from_csv_file(filepath: &str) -> anyhow::Result { + let pathbuf = PathBuf::from(&filepath); // create empty cycle to be populated let mut cyc = Self::default(); // unwrap is ok because if statement checks existence - let file = File::open(&pathbuf).unwrap(); - let name = String::from(pathbuf.file_stem().unwrap().to_str().unwrap()); + let file = File::open(&pathbuf) + .with_context(|| format!("File could not be opened: {filepath:?}"))?; + let name = String::from( + pathbuf + .file_stem() + .with_context(|| format!("Could not parse file stem: {filepath:?}"))? + .to_str() + .with_context(|| format!("File stem is not valid unicode: {filepath:?}"))?, + ); cyc.name = name; let mut rdr = csv::ReaderBuilder::new() .has_headers(true) @@ -944,7 +951,7 @@ impl RustCycle { // load a cycle from a string representation of a csv file pub fn from_csv_string(data: &str, name: String) -> anyhow::Result { let mut cyc = Self { - name, + name: String::from(name), ..Self::default() }; @@ -1058,13 +1065,14 @@ mod tests { use super::*; #[test] - fn test_dist() { + fn test_dist() -> anyhow::Result<()> { let cyc = RustCycle::test_cyc(); - assert_eq!(cyc.dist_m().sum(), 45.0); + anyhow::ensure!(cyc.dist_m().sum() == 45.0); + Ok(()) } #[test] - fn test_average_speeds_and_distances() { + fn test_average_speeds_and_distances() -> anyhow::Result<()> { let time_s = vec![0.0, 10.0, 30.0, 34.0, 40.0]; let speed_mps = vec![0.0, 10.0, 10.0, 0.0, 0.0]; let grade = Array::zeros(5).to_vec(); @@ -1073,31 +1081,33 @@ mod tests { let cyc = RustCycle::new(time_s, speed_mps, grade, road_type, name); let avg_mps = average_step_speeds(&cyc); let expected_avg_mps = Array::from_vec(vec![0.0, 5.0, 10.0, 5.0, 0.0]); - assert_eq!(expected_avg_mps.len(), avg_mps.len()); + anyhow::ensure!(expected_avg_mps.len() == avg_mps.len()); for (expected, actual) in expected_avg_mps.iter().zip(avg_mps.iter()) { - assert_eq!(expected, actual); + anyhow::ensure!(expected == actual); } let dist_m = trapz_step_distances(&cyc); let expected_dist_m = Array::from_vec(vec![0.0, 50.0, 200.0, 20.0, 0.0]); - assert_eq!(expected_dist_m.len(), dist_m.len()); + anyhow::ensure!(expected_dist_m.len() == dist_m.len()); for (expected, actual) in expected_dist_m.iter().zip(dist_m.iter()) { - assert_eq!(expected, actual); + anyhow::ensure!(expected == actual); } + Ok(()) } #[test] - fn test_loading_a_cycle_from_the_filesystem() { + fn test_loading_a_cycle_from_the_filesystem() -> anyhow::Result<()> { let mut cyc_file_path = resources_path(); cyc_file_path.push("cycles/udds.csv"); let expected_udds_length: usize = 1370; let cyc = RustCycle::from_csv_file(cyc_file_path.as_os_str().to_str().unwrap()).unwrap(); - assert_eq!(cyc.name, String::from("udds")); + anyhow::ensure!(cyc.name == String::from("udds")); let num_entries = cyc.time_s.len(); - assert!(num_entries > 0); - assert_eq!(num_entries, cyc.time_s.len()); - assert_eq!(num_entries, cyc.mps.len()); - assert_eq!(num_entries, cyc.grade.len()); - assert_eq!(num_entries, cyc.road_type.len()); - assert_eq!(num_entries, expected_udds_length); + anyhow::ensure!(num_entries > 0); + anyhow::ensure!(num_entries == cyc.time_s.len()); + anyhow::ensure!(num_entries == cyc.mps.len()); + anyhow::ensure!(num_entries == cyc.grade.len()); + anyhow::ensure!(num_entries == cyc.road_type.len()); + anyhow::ensure!(num_entries == expected_udds_length); + Ok(()) } } diff --git a/rust/fastsim-core/src/simdrive/cyc_mods.rs b/rust/fastsim-core/src/simdrive/cyc_mods.rs index df23d61a..52a5c65a 100644 --- a/rust/fastsim-core/src/simdrive/cyc_mods.rs +++ b/rust/fastsim-core/src/simdrive/cyc_mods.rs @@ -340,7 +340,7 @@ impl RustSimDrive { } } - fn apply_coast_trajectory(&mut self, coast_traj: CoastTrajectory) { + fn apply_coast_trajectory(&mut self, coast_traj: CoastTrajectory) -> anyhow::Result<()> { if coast_traj.found_trajectory { let num_speeds = match coast_traj.speeds_m_per_s { Some(speeds_m_per_s) => { @@ -359,12 +359,13 @@ impl RustSimDrive { self.sim_params.coast_brake_accel_m_per_s2, coast_traj.start_idx + num_speeds, coast_traj.distance_to_brake_m, - ); + )?; for di in 0..(self.cyc0.mps.len() - coast_traj.start_idx) { let idx = coast_traj.start_idx + di; self.impose_coast[idx] = di < num_speeds + n; } } + Ok(()) } /// Generate a coast trajectory without actually modifying the cycle. @@ -619,7 +620,7 @@ impl RustSimDrive { i: usize, min_accel_m_per_s2: f64, max_accel_m_per_s2: f64, - ) -> (bool, usize, f64, f64) { + ) -> anyhow::Result<(bool, usize, f64, f64)> { let tol = 1e-6; // v0 is where n=0, i.e., idx-1 let v0 = self.cyc.mps[i - 1]; @@ -638,7 +639,7 @@ impl RustSimDrive { ); if v0 < (brake_start_speed_m_per_s + tol) { // don't process braking - return not_found; + return Ok(not_found); } let (min_accel_m_per_s2, max_accel_m_per_s2) = if min_accel_m_per_s2 > max_accel_m_per_s2 { (max_accel_m_per_s2, min_accel_m_per_s2) @@ -654,7 +655,7 @@ impl RustSimDrive { .calc_distance_to_next_stop_from(d0, Some(&self.cyc0_cache)); if dts0 < 0.0 { // no stop to coast towards or we're there... - return not_found; + return Ok(not_found); } let dt = self.cyc.dt_s_at_i(i); // distance to brake from the brake start speed (m/s) @@ -663,7 +664,7 @@ impl RustSimDrive { // distance to brake initiation from start of time-step (m) let dtbi0 = dts0 - dtb; if dtbi0 < 0.0 { - return not_found; + return Ok(not_found); } // Now, check rendezvous trajectories let mut step_idx = i; @@ -699,7 +700,7 @@ impl RustSimDrive { dtbi0, brake_start_speed_m_per_s, dt, - ); + )?; if r_bi_accel_m_per_s2 < max_accel_m_per_s2 && r_bi_accel_m_per_s2 > min_accel_m_per_s2 && r_bi_jerk_m_per_s3 >= 0.0 @@ -731,14 +732,14 @@ impl RustSimDrive { step_idx += 1; } if r_best_found { - return ( + return Ok(( r_best_found, r_best_n, r_best_jerk_m_per_s3, r_best_accel_m_per_s2, - ); + )); } - not_found + Ok(not_found) } /// Coast Delay allows us to represent coasting to a stop when the lead @@ -815,11 +816,11 @@ impl RustSimDrive { /// - i: int, index for consideration /// - passing_tol_m: None | float, tolerance for how far we have to go past the lead vehicle to be considered "passing" /// RETURN: Bool, True if cyc was modified - fn prevent_collisions(&mut self, i: usize, passing_tol_m: Option) -> bool { + fn prevent_collisions(&mut self, i: usize, passing_tol_m: Option) -> anyhow::Result { let passing_tol_m = passing_tol_m.unwrap_or(1.0); let collision: PassingInfo = detect_passing(&self.cyc, &self.cyc0, i, Some(passing_tol_m)); if !collision.has_collision { - return false; + return Ok(false); } let mut best: RendezvousTrajectory = RendezvousTrajectory { found_trajectory: false, @@ -871,7 +872,7 @@ impl RustSimDrive { collision.distance_m, collision.speed_m_per_s, dt, - ); + )?; let mut accels_m_per_s2: Vec = vec![]; let mut trace_accels_m_per_s2: Vec = vec![]; for ni in 0..n { @@ -931,7 +932,7 @@ impl RustSimDrive { passing_tol_m + 5.0 }; if new_passing_tol_m > 60.0 { - return false; + return Ok(false); } return self.prevent_collisions(i, Some(new_passing_tol_m)); } @@ -956,7 +957,7 @@ impl RustSimDrive { self.impose_coast[idx] = false; self.coast_delay_index[idx] = 0; } - true + Ok(true) } /// Placeholder for method to impose coasting. @@ -974,10 +975,10 @@ impl RustSimDrive { self.impose_coast[idx] = false; } } else { - self.apply_coast_trajectory(ct); + self.apply_coast_trajectory(ct)?; } if !self.sim_params.coast_allow_passing { - self.prevent_collisions(i, None); + self.prevent_collisions(i, None)?; } } } @@ -1038,7 +1039,7 @@ impl RustSimDrive { self.sim_params.coast_brake_accel_m_per_s2, i, None, - ); + )?; for idx in i..self.cyc.time_s.len() { self.impose_coast[idx] = idx < (i + num_steps); } @@ -1049,7 +1050,7 @@ impl RustSimDrive { i, self.sim_params.coast_brake_accel_m_per_s2, min(accel_proposed, 0.0), - ); + )?; if traj_found { // adjust cyc to perform the trajectory let final_speed_m_per_s = self.cyc.modify_by_const_jerk_trajectory( @@ -1070,7 +1071,7 @@ impl RustSimDrive { self.sim_params.coast_brake_accel_m_per_s2, i_for_brake, None, - ); + )?; for idx in i_for_brake..self.cyc0.mps.len() { self.impose_coast[idx] = idx < i_for_brake + num_steps; } @@ -1089,7 +1090,7 @@ impl RustSimDrive { } if adjusted_current_speed { if !self.sim_params.coast_allow_passing { - self.prevent_collisions(i, None); + self.prevent_collisions(i, None)?; } self.solve_step(i)?; self.newton_iters[i] = 0; // reset newton iters diff --git a/rust/fastsim-core/src/simdrivelabel.rs b/rust/fastsim-core/src/simdrivelabel.rs index 23d29924..f070458e 100644 --- a/rust/fastsim-core/src/simdrivelabel.rs +++ b/rust/fastsim-core/src/simdrivelabel.rs @@ -197,43 +197,8 @@ pub fn get_label_fe( // load the cycles and intstantiate simdrive objects cyc.insert("accel", make_accel_trace()); - #[cfg(not(windows))] - macro_rules! path_separator { - () => { - "/" - }; - } - - #[cfg(windows)] - macro_rules! path_separator { - () => { - r#"\"# - }; - } - - let udds_filestring = include_str!(concat!( - "..", - path_separator!(), - "resources", - path_separator!(), - "udds.csv" - )); - let hwy_filestring = include_str!(concat!( - "..", - path_separator!(), - "resources", - path_separator!(), - "hwfet.csv" - )); - - cyc.insert( - "udds", - RustCycle::from_csv_string(udds_filestring, "udds".to_string())?, - ); - cyc.insert( - "hwy", - RustCycle::from_csv_string(hwy_filestring, "hwfet".to_string())?, - ); + cyc.insert("udds", RustCycle::from_csv_file("resources/udds.csv")?); + cyc.insert("hwy", RustCycle::from_csv_file("resources/hwfet.csv")?); // run simdrive for non-phev powertrains sd.insert("udds", RustSimDrive::new(cyc["udds"].clone(), veh.clone())); @@ -719,7 +684,7 @@ pub fn get_label_fe_phev( match *key { "udds" => phev_calcs.udds = phev_calc.clone(), "hwy" => phev_calcs.hwy = phev_calc.clone(), - &_ => return Err(anyhow::anyhow!("No field for cycle {}", key)), + &_ => anyhow::bail!("No field for cycle {}", key), }; } @@ -758,9 +723,9 @@ mod simdrivelabel_tests { use super::*; #[test] - fn test_get_label_fe_conv() { + fn test_get_label_fe_conv() -> anyhow::Result<()> { let veh: vehicle::RustVehicle = vehicle::RustVehicle::mock_vehicle(); - let (mut label_fe, _) = get_label_fe(&veh, None, None).unwrap(); + let (mut label_fe, _) = get_label_fe(&veh, None, None)?; // 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(); @@ -800,10 +765,11 @@ mod simdrivelabel_tests { // 100. * (label_fe_truth.net_accel - label_fe.net_accel) / label_fe_truth.net_accel // ); - assert!(label_fe.approx_eq(&label_fe_truth, 1e-10)); + anyhow::ensure!(label_fe.approx_eq(&label_fe_truth, 1e-10)); + Ok(()) } #[test] - fn test_get_label_fe_phev() { + fn test_get_label_fe_phev() -> anyhow::Result<()> { let mut veh = vehicle::RustVehicle { props: RustPhysicalProperties { air_density_kg_per_m3: 1.2, @@ -946,9 +912,9 @@ mod simdrivelabel_tests { orphaned: false, ..Default::default() }; - veh.set_derived().unwrap(); + veh.set_derived()?; - let (mut label_fe, _) = get_label_fe(&veh, None, None).unwrap(); + let (mut label_fe, _) = get_label_fe(&veh, None, None)?; // 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(); @@ -1162,15 +1128,16 @@ mod simdrivelabel_tests { }; let tol = 1e-8; - assert!(label_fe.veh.approx_eq(&label_fe_truth.veh, tol)); - assert!( + anyhow::ensure!(label_fe.veh.approx_eq(&label_fe_truth.veh, tol)); + anyhow::ensure!( 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)); + anyhow::ensure!(label_fe.approx_eq(&label_fe_truth, tol)); + Ok(()) } } From ec6ab43f8a54bd91030054f375048bb51c357a00 Mon Sep 17 00:00:00 2001 From: Kyle Carow Date: Thu, 19 Oct 2023 15:14:31 -0400 Subject: [PATCH 06/27] minor reorg of cycle.rs methods --- rust/fastsim-core/src/cycle.rs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/rust/fastsim-core/src/cycle.rs b/rust/fastsim-core/src/cycle.rs index bf98bf9a..0ee26d93 100644 --- a/rust/fastsim-core/src/cycle.rs +++ b/rust/fastsim-core/src/cycle.rs @@ -913,6 +913,11 @@ impl RustCycle { self.mps[i] * MPH_PER_MPS } + /// elevation change w.r.t. to initial + pub fn delta_elev_m(&self) -> Array1 { + ndarrcumsum(&(self.dist_m() * self.grade.clone())) + } + /// Load cycle from csv file pub fn from_csv_file(filepath: &str) -> anyhow::Result { let pathbuf = PathBuf::from(&filepath); @@ -943,13 +948,8 @@ impl RustCycle { Ok(cyc) } - /// elevation change w.r.t. to initial - pub fn delta_elev_m(&self) -> Array1 { - ndarrcumsum(&(self.dist_m() * self.grade.clone())) - } - // load a cycle from a string representation of a csv file - pub fn from_csv_string(data: &str, name: String) -> anyhow::Result { + pub fn from_csv_string(data: &str, name: &str) -> anyhow::Result { let mut cyc = Self { name: String::from(name), ..Self::default() From 0ab74d6557714fbad6aed557c7a5371c3006fa9d Mon Sep 17 00:00:00 2001 From: Kyle Carow Date: Thu, 19 Oct 2023 15:19:14 -0400 Subject: [PATCH 07/27] improve fastsim-cli --- rust/fastsim-cli/src/bin/fastsim-cli.rs | 57 ++++++------------------- 1 file changed, 12 insertions(+), 45 deletions(-) diff --git a/rust/fastsim-cli/src/bin/fastsim-cli.rs b/rust/fastsim-cli/src/bin/fastsim-cli.rs index fa9e180e..2104b8b2 100644 --- a/rust/fastsim-cli/src/bin/fastsim-cli.rs +++ b/rust/fastsim-cli/src/bin/fastsim-cli.rs @@ -121,9 +121,9 @@ pub fn calculate_mpgge_for_h2_diesel_ice( fs_kwh_out_ach: &Vec, fc_pwr_out_perc: &Vec, h2share: &Vec, -) -> H2AndDieselResults { - assert!(fc_kw_out_ach.len() == fs_kwh_out_ach.len()); - assert!(fc_pwr_out_perc.len() == h2share.len()); +) -> anyhow::Result { + anyhow::ensure!(fc_kw_out_ach.len() == fs_kwh_out_ach.len()); + anyhow::ensure!(fc_pwr_out_perc.len() == h2share.len()); let kwh_per_gallon_diesel = 37.95; let gge_per_kwh = 1.0 / kwh_per_gge; let mut total_diesel_kwh = 0.0; @@ -149,7 +149,7 @@ pub fn calculate_mpgge_for_h2_diesel_ice( total_diesel_gals += diesel_gals; total_diesel_gge += diesel_gge; } - H2AndDieselResults { + Ok(H2AndDieselResults { h2_kwh: total_h2_kwh, h2_gge: total_h2_gge, h2_mpgge: if total_h2_gge > 0.0 { @@ -165,25 +165,25 @@ pub fn calculate_mpgge_for_h2_diesel_ice( } else { 0.0 }, - } + }) } -pub fn integrate_power_to_kwh(dts_s: &Vec, ps_kw: &Vec) -> Vec { - assert!(dts_s.len() == ps_kw.len()); +pub fn integrate_power_to_kwh(dts_s: &Vec, ps_kw: &Vec) -> anyhow::Result> { + anyhow::ensure!(dts_s.len() == ps_kw.len()); let mut energy_kwh = Vec::::with_capacity(dts_s.len()); for idx in 0..dts_s.len() { let dt_s = dts_s[idx]; let p_kw = ps_kw[idx]; energy_kwh.push(p_kw * dt_s / 3600.0); } - energy_kwh + Ok(energy_kwh) } pub fn main() -> anyhow::Result<()> { let fastsim_api = FastSimApi::parse(); if let Some(_cyc_json_str) = fastsim_api.cyc { - panic!("Need to implement: let cyc = RustCycle::from_json(cyc_json_str)"); + anyhow::bail!("Need to implement: let cyc = RustCycle::from_json(cyc_json_str)"); } let (is_adopt_hd, adopt_hd_string, adopt_hd_has_cycle) = if let Some(adopt_hd_string) = &fastsim_api.adopt_hd { @@ -218,7 +218,7 @@ pub fn main() -> anyhow::Result<()> { println!("Wheel RR Coefficient: {}", wheel_rr_coeff); return Ok(()); } else { - panic!("Need to provide coastdown test coefficients for drag and wheel rr coefficient calculation"); + anyhow::bail!("Need to provide coastdown test coefficients for drag and wheel rr coefficient calculation"); } } else { RustCycle::from_file(&cyc_file_path) @@ -264,20 +264,6 @@ pub fn main() -> anyhow::Result<()> { Ok(RustVehicle::mock_vehicle()) }?; - #[cfg(not(windows))] - macro_rules! path_separator { - () => { - "/" - }; - } - - #[cfg(windows)] - macro_rules! path_separator { - () => { - r#"\"# - }; - } - if is_adopt { let sdl = get_label_fe(&veh, Some(false), Some(false))?; let res = AdoptResults { @@ -291,29 +277,10 @@ pub fn main() -> anyhow::Result<()> { }; println!("{}", res.to_json()?); } else if is_adopt_hd { - let hd_cyc_filestring = include_str!(concat!( - "..", - path_separator!(), - "..", - path_separator!(), - "..", - path_separator!(), - "..", - path_separator!(), - "python", - path_separator!(), - "fastsim", - path_separator!(), - "resources", - path_separator!(), - "cycles", - path_separator!(), - "HHDDTCruiseSmooth.csv" - )); let cyc = if adopt_hd_has_cycle { cyc } else { - RustCycle::from_csv_string(hd_cyc_filestring, "HHDDTCruiseSmooth".to_string())? + RustCycle::from_csv_file("../../python/fastsim/resources/cycles/HHDDTCruiseSmooth.csv")? }; let mut sim_drive = RustSimDrive::new(cyc, veh.clone()); sim_drive.sim_drive(None, None)?; @@ -332,7 +299,7 @@ pub fn main() -> anyhow::Result<()> { &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 { From dec94718fd463652772150f71c48763bb792d0bf Mon Sep 17 00:00:00 2001 From: Kyle Carow Date: Fri, 20 Oct 2023 13:16:05 -0400 Subject: [PATCH 08/27] revamp serde trait and anyhowize some misc functions --- rust/fastsim-cli/src/bin/fastsim-cli.rs | 2 +- rust/fastsim-core/Cargo.toml | 1 + rust/fastsim-core/build.rs | 4 +- .../src/add_pyo3_api/mod.rs | 8 +- .../resources/cycles/HHDDTCruiseSmooth.csv | 0 .../resources/{ => cycles}/hwfet.csv | 0 .../resources/{ => cycles}/udds.csv | 0 rust/fastsim-core/src/cycle.rs | 153 ++++++++++-------- rust/fastsim-core/src/lib.rs | 1 + rust/fastsim-core/src/pyo3imports.rs | 2 +- rust/fastsim-core/src/resources.rs | 2 + rust/fastsim-core/src/simdrivelabel.rs | 4 +- rust/fastsim-core/src/traits.rs | 75 ++++++--- 13 files changed, 157 insertions(+), 95 deletions(-) rename {python/fastsim => rust/fastsim-core}/resources/cycles/HHDDTCruiseSmooth.csv (100%) rename rust/fastsim-core/resources/{ => cycles}/hwfet.csv (100%) rename rust/fastsim-core/resources/{ => cycles}/udds.csv (100%) create mode 100644 rust/fastsim-core/src/resources.rs diff --git a/rust/fastsim-cli/src/bin/fastsim-cli.rs b/rust/fastsim-cli/src/bin/fastsim-cli.rs index 2104b8b2..aa2785f3 100644 --- a/rust/fastsim-cli/src/bin/fastsim-cli.rs +++ b/rust/fastsim-cli/src/bin/fastsim-cli.rs @@ -280,7 +280,7 @@ pub fn main() -> anyhow::Result<()> { let cyc = if adopt_hd_has_cycle { cyc } else { - RustCycle::from_csv_file("../../python/fastsim/resources/cycles/HHDDTCruiseSmooth.csv")? + RustCycle::from_resource("cycles/HHDDTCruiseSmooth.csv")? }; let mut sim_drive = RustSimDrive::new(cyc, veh.clone()); sim_drive.sim_drive(None, None)?; diff --git a/rust/fastsim-core/Cargo.toml b/rust/fastsim-core/Cargo.toml index 53c6442a..54e9f705 100644 --- a/rust/fastsim-core/Cargo.toml +++ b/rust/fastsim-core/Cargo.toml @@ -32,6 +32,7 @@ validator = { version = "0.16", features = ["derive"] } lazy_static = "1.4.0" regex = "1.7.1" rayon = "1.7.0" +include_dir = "0.7.3" [package.metadata] include = [ diff --git a/rust/fastsim-core/build.rs b/rust/fastsim-core/build.rs index ed6ee294..59113740 100644 --- a/rust/fastsim-core/build.rs +++ b/rust/fastsim-core/build.rs @@ -47,11 +47,11 @@ fn main() { env::current_dir().unwrap().as_os_str().to_str().unwrap() ), format!( - "{}/resources/udds.csv", + "{}/resources/cycles/udds.csv", env::current_dir().unwrap().as_os_str().to_str().unwrap() ), format!( - "{}/resources/hwfet.csv", + "{}/resources/cycles/hwfet.csv", env::current_dir().unwrap().as_os_str().to_str().unwrap() ), ]; diff --git a/rust/fastsim-core/fastsim-proc-macros/src/add_pyo3_api/mod.rs b/rust/fastsim-core/fastsim-proc-macros/src/add_pyo3_api/mod.rs index 8b46284c..b1aaf2cb 100644 --- a/rust/fastsim-core/fastsim-proc-macros/src/add_pyo3_api/mod.rs +++ b/rust/fastsim-core/fastsim-proc-macros/src/add_pyo3_api/mod.rs @@ -108,14 +108,14 @@ pub fn add_pyo3_api(attr: TokenStream, item: TokenStream) -> TokenStream { if !is_state_or_history { py_impl_block.extend::(quote! { #[pyo3(name = "to_file")] - pub fn to_file_py(&self, filepath: &str) -> anyhow::Result<()> { - self.to_file(filepath) + pub fn to_file_py(&self, filepath: &PyAny) -> anyhow::Result<()> { + self.to_file(PathBuf::extract(filepath)?) } #[staticmethod] #[pyo3(name = "from_file")] - pub fn from_file_py(filepath: &str) -> anyhow::Result { - Self::from_file(filepath) + pub fn from_file_py(filepath: &PyAny) -> anyhow::Result { + Self::from_file(PathBuf::extract(filepath)?) } }); } diff --git a/python/fastsim/resources/cycles/HHDDTCruiseSmooth.csv b/rust/fastsim-core/resources/cycles/HHDDTCruiseSmooth.csv similarity index 100% rename from python/fastsim/resources/cycles/HHDDTCruiseSmooth.csv rename to rust/fastsim-core/resources/cycles/HHDDTCruiseSmooth.csv diff --git a/rust/fastsim-core/resources/hwfet.csv b/rust/fastsim-core/resources/cycles/hwfet.csv similarity index 100% rename from rust/fastsim-core/resources/hwfet.csv rename to rust/fastsim-core/resources/cycles/hwfet.csv diff --git a/rust/fastsim-core/resources/udds.csv b/rust/fastsim-core/resources/cycles/udds.csv similarity index 100% rename from rust/fastsim-core/resources/udds.csv rename to rust/fastsim-core/resources/cycles/udds.csv diff --git a/rust/fastsim-core/src/cycle.rs b/rust/fastsim-core/src/cycle.rs index 0ee26d93..7bbef2d8 100644 --- a/rust/fastsim-core/src/cycle.rs +++ b/rust/fastsim-core/src/cycle.rs @@ -5,7 +5,6 @@ extern crate ndarray; #[cfg(feature = "pyo3")] use std::collections::HashMap; use std::fs::File; -use std::path::PathBuf; // local use crate::imports::*; @@ -488,10 +487,10 @@ impl RustCycleCache { Ok((self.time_s.to_vec(), self.mps.to_vec(), self.grade.to_vec(), self.road_type.to_vec(), &self.name)) } - #[classmethod] - #[pyo3(name = "from_csv_file")] - pub fn from_csv_file_py(_cls: &PyType, pathstr: String) -> anyhow::Result { - Self::from_csv_file(&pathstr) + #[staticmethod] + #[pyo3(name = "from_csv")] + pub fn from_csv_py(filepath: &PyAny) -> anyhow::Result { + Self::from_csv_file(PathBuf::extract(filepath)?) } pub fn to_rust(&self) -> anyhow::Result { @@ -615,17 +614,56 @@ pub struct RustCycle { } impl SerdeAPI for RustCycle { - fn from_file(filepath: &str) -> anyhow::Result { + /// Load cycle from file, not parsing cycle name from filepath + fn from_file>(filepath: P) -> anyhow::Result { // check if the extension is csv, and if it is, then call Self::from_csv_file - let pathbuf = PathBuf::from(filepath); - let file = File::open(filepath)?; - let extension = pathbuf.extension().unwrap().to_str().unwrap(); - match extension { - "yaml" => Ok(serde_yaml::from_reader(file)?), - "json" => Ok(serde_json::from_reader(file)?), - "csv" => Ok(Self::from_csv_file(filepath)?), - _ => anyhow::bail!("Unsupported file extension {}", extension), - } + let filepath = filepath.as_ref(); + let extension = filepath + .extension() + .and_then(OsStr::to_str) + .with_context(|| { + format!( + "File extension could not be parsed: \"{}\"", + filepath.display() + ) + })?; + let file = File::open(filepath).with_context(|| { + if !filepath.exists() { + format!("File not found: \"{}\"", filepath.display()) + } else { + format!("Could not open file: \"{}\"", filepath.display()) + } + })?; + Self::from_reader(file, extension) + } + + fn from_reader(rdr: R, format: &str) -> anyhow::Result { + Ok(match format { + "yaml" => serde_yaml::from_reader(rdr)?, + "json" => serde_json::from_reader(rdr)?, + "csv" => { + // Create empty cycle to be populated + let mut cyc = Self::default(); + let mut rdr = csv::ReaderBuilder::new().has_headers(true).from_reader(rdr); + for result in rdr.deserialize() { + let cyc_elem: crate::cycle::RustCycleElement = result?; + cyc.push(cyc_elem); + } + cyc + } + _ => anyhow::bail!("Unsupported file format: {format:?}"), + }) + } + + // Note that using this method to instantiate a RustCycle from CSV instead of + // the `from_csv_str` method directly sets the cycle name to an empty string. + fn from_str(contents: &str, format: &str) -> anyhow::Result { + Ok(match format { + "yaml" => serde_yaml::from_str(contents)?, + "json" => serde_json::from_str(contents)?, + "csv" => Self::from_csv_str(contents, "")?, + _ => anyhow::bail!("Unsupported file format: {format:?}"), + }) } } @@ -652,6 +690,36 @@ impl RustCycle { } } + /// Load cycle from CSV file, parsing name from filepath + pub fn from_csv_file>(filepath: P) -> anyhow::Result { + let filepath = filepath.as_ref(); + let name = String::from(filepath.file_stem().and_then(OsStr::to_str).with_context( + || { + format!( + "Could not parse cycle name from filepath: \"{}\"", + filepath.display() + ) + }, + )?); + let file = File::open(filepath).with_context(|| { + if !filepath.exists() { + format!("File not found: \"{}\"", filepath.display()) + } else { + format!("Could not open file: \"{}\"", filepath.display()) + } + })?; + let mut cyc = Self::from_reader(file, "csv")?; + cyc.name = name; + Ok(cyc) + } + + /// Load cycle from CSV string + pub fn from_csv_str(csv_str: &str, name: &str) -> anyhow::Result { + let mut cyc = Self::from_reader(csv_str.as_bytes(), "csv")?; + cyc.name = name.to_string(); + Ok(cyc) + } + pub fn build_cache(&self) -> RustCycleCache { RustCycleCache::new(self) } @@ -917,54 +985,6 @@ impl RustCycle { pub fn delta_elev_m(&self) -> Array1 { ndarrcumsum(&(self.dist_m() * self.grade.clone())) } - - /// Load cycle from csv file - pub fn from_csv_file(filepath: &str) -> anyhow::Result { - let pathbuf = PathBuf::from(&filepath); - - // create empty cycle to be populated - let mut cyc = Self::default(); - - // unwrap is ok because if statement checks existence - let file = File::open(&pathbuf) - .with_context(|| format!("File could not be opened: {filepath:?}"))?; - let name = String::from( - pathbuf - .file_stem() - .with_context(|| format!("Could not parse file stem: {filepath:?}"))? - .to_str() - .with_context(|| format!("File stem is not valid unicode: {filepath:?}"))?, - ); - cyc.name = name; - let mut rdr = csv::ReaderBuilder::new() - .has_headers(true) - .from_reader(file); - for result in rdr.deserialize() { - // TODO: make this more elegant than unwrap - let cyc_elem: RustCycleElement = result?; - cyc.push(cyc_elem); - } - - Ok(cyc) - } - - // load a cycle from a string representation of a csv file - pub fn from_csv_string(data: &str, name: &str) -> anyhow::Result { - let mut cyc = Self { - name: String::from(name), - ..Self::default() - }; - - let mut rdr = csv::ReaderBuilder::new() - .has_headers(true) - .from_reader(data.as_bytes()); - for result in rdr.deserialize() { - let cyc_elem: RustCycleElement = result?; - cyc.push(cyc_elem); - } - - Ok(cyc) - } } pub struct PassingInfo { @@ -1096,12 +1116,11 @@ mod tests { #[test] fn test_loading_a_cycle_from_the_filesystem() -> anyhow::Result<()> { - let mut cyc_file_path = resources_path(); - cyc_file_path.push("cycles/udds.csv"); + let cyc_file_path = resources_path().join("cycles/udds.csv"); let expected_udds_length: usize = 1370; - let cyc = RustCycle::from_csv_file(cyc_file_path.as_os_str().to_str().unwrap()).unwrap(); - anyhow::ensure!(cyc.name == String::from("udds")); + let cyc = RustCycle::from_csv_file(cyc_file_path)?; let num_entries = cyc.time_s.len(); + anyhow::ensure!(cyc.name == String::from("udds")); anyhow::ensure!(num_entries > 0); anyhow::ensure!(num_entries == cyc.time_s.len()); anyhow::ensure!(num_entries == cyc.mps.len()); diff --git a/rust/fastsim-core/src/lib.rs b/rust/fastsim-core/src/lib.rs index 3457ed1f..e6e6bd8d 100644 --- a/rust/fastsim-core/src/lib.rs +++ b/rust/fastsim-core/src/lib.rs @@ -40,6 +40,7 @@ pub mod params; pub mod pyo3imports; pub mod simdrive; pub use simdrive::simdrive_impl; +pub mod resources; pub mod simdrivelabel; pub mod thermal; pub mod traits; diff --git a/rust/fastsim-core/src/pyo3imports.rs b/rust/fastsim-core/src/pyo3imports.rs index ee55a242..06a52ed9 100644 --- a/rust/fastsim-core/src/pyo3imports.rs +++ b/rust/fastsim-core/src/pyo3imports.rs @@ -1,4 +1,4 @@ #![cfg(feature = "pyo3")] pub use pyo3::exceptions::*; pub use pyo3::prelude::*; -pub use pyo3::types::{PyBytes, PyDict, PyType}; +pub use pyo3::types::{PyAny, PyBytes, PyDict, PyType}; diff --git a/rust/fastsim-core/src/resources.rs b/rust/fastsim-core/src/resources.rs new file mode 100644 index 00000000..0cf74313 --- /dev/null +++ b/rust/fastsim-core/src/resources.rs @@ -0,0 +1,2 @@ +use include_dir::{include_dir, Dir}; +pub const RESOURCES_DIR: Dir = include_dir!("$CARGO_MANIFEST_DIR/resources"); diff --git a/rust/fastsim-core/src/simdrivelabel.rs b/rust/fastsim-core/src/simdrivelabel.rs index f070458e..ef37eae6 100644 --- a/rust/fastsim-core/src/simdrivelabel.rs +++ b/rust/fastsim-core/src/simdrivelabel.rs @@ -197,8 +197,8 @@ pub fn get_label_fe( // load the cycles and intstantiate simdrive objects cyc.insert("accel", make_accel_trace()); - cyc.insert("udds", RustCycle::from_csv_file("resources/udds.csv")?); - cyc.insert("hwy", RustCycle::from_csv_file("resources/hwfet.csv")?); + cyc.insert("udds", RustCycle::from_resource("cycles/udds.csv")?); + cyc.insert("hwy", RustCycle::from_resource("cycles/hwfet.csv")?); // run simdrive for non-phev powertrains sd.insert("udds", RustSimDrive::new(cyc["udds"].clone(), veh.clone())); diff --git a/rust/fastsim-core/src/traits.rs b/rust/fastsim-core/src/traits.rs index 3886f207..46fd1a08 100644 --- a/rust/fastsim-core/src/traits.rs +++ b/rust/fastsim-core/src/traits.rs @@ -19,8 +19,8 @@ pub trait SerdeAPI: Serialize + for<'a> Deserialize<'a> { /// # Returns: /// /// A Rust Result - fn to_file(&self, filepath: &str) -> anyhow::Result<()> { - let file = PathBuf::from(filepath); + fn to_file>(&self, filepath: P) -> anyhow::Result<()> { + let file = PathBuf::from(filepath.as_ref()); match file.extension().unwrap().to_str().unwrap() { "json" => serde_json::to_writer(&File::create(file)?, self)?, "yaml" => serde_yaml::to_writer(&File::create(file)?, self)?, @@ -42,25 +42,64 @@ pub trait SerdeAPI: Serialize + for<'a> Deserialize<'a> { /// /// A Rust Result wrapping data structure if method is called successfully; otherwise a dynamic /// Error. - fn from_file(filepath: &str) -> anyhow::Result - where - Self: std::marker::Sized, - for<'de> Self: Deserialize<'de>, - { - let extension = Path::new(filepath) + fn from_file>(filepath: P) -> anyhow::Result { + let filepath = filepath.as_ref(); + let extension = filepath .extension() .and_then(OsStr::to_str) - .unwrap_or(""); - - let file = File::open(filepath)?; + .with_context(|| { + format!( + "File extension could not be parsed: \"{}\"", + filepath.display() + ) + })?; + let file = File::open(filepath).with_context(|| { + if !filepath.exists() { + format!("File not found: \"{}\"", filepath.display()) + } else { + format!("Could not open file: \"{}\"", filepath.display()) + } + })?; // deserialized file - let mut file_de: Self = match extension { - "yaml" => serde_yaml::from_reader(file)?, - "json" => serde_json::from_reader(file)?, - _ => anyhow::bail!("Unsupported file extension {}", extension), - }; - file_de.init()?; - Ok(file_de) + let mut deserialized = Self::from_reader(file, extension)?; + deserialized.init()?; + Ok(deserialized) + } + + fn from_resource>(filepath: P) -> anyhow::Result { + let filepath = filepath.as_ref(); + let contents = crate::resources::RESOURCES_DIR + .get_file(filepath) + .and_then(include_dir::File::contents_utf8) + .with_context(|| format!("File not found in resources: \"{}\"", filepath.display()))?; + let extension = filepath + .extension() + .and_then(OsStr::to_str) + .with_context(|| { + format!( + "File extension could not be parsed: \"{}\"", + filepath.display() + ) + })?; + let mut deserialized = Self::from_str(contents, extension)?; + deserialized.init()?; + Ok(deserialized) + } + + fn from_reader(rdr: R, format: &str) -> anyhow::Result { + Ok(match format { + "yaml" => serde_yaml::from_reader(rdr)?, + "json" => serde_json::from_reader(rdr)?, + _ => anyhow::bail!("Unsupported file format: {format:?}"), + }) + } + + fn from_str(contents: &str, format: &str) -> anyhow::Result { + Ok(match format { + "yaml" => serde_yaml::from_str(contents)?, + "json" => serde_json::from_str(contents)?, + _ => anyhow::bail!("Unsupported file format: {format:?}"), + }) } /// JSON serialization method From d0a15aef11d8468ba276e5cccb7a2130fbafaaa5 Mon Sep 17 00:00:00 2001 From: Kyle Carow Date: Fri, 20 Oct 2023 13:31:45 -0400 Subject: [PATCH 09/27] clean up serde api to_file --- rust/fastsim-core/src/traits.rs | 23 ++++++++++++++++------- 1 file changed, 16 insertions(+), 7 deletions(-) diff --git a/rust/fastsim-core/src/traits.rs b/rust/fastsim-core/src/traits.rs index 46fd1a08..d63f309b 100644 --- a/rust/fastsim-core/src/traits.rs +++ b/rust/fastsim-core/src/traits.rs @@ -20,13 +20,22 @@ pub trait SerdeAPI: Serialize + for<'a> Deserialize<'a> { /// /// A Rust Result fn to_file>(&self, filepath: P) -> anyhow::Result<()> { - let file = PathBuf::from(filepath.as_ref()); - match file.extension().unwrap().to_str().unwrap() { - "json" => serde_json::to_writer(&File::create(file)?, self)?, - "yaml" => serde_yaml::to_writer(&File::create(file)?, self)?, - _ => serde_json::to_writer(&File::create(file)?, self)?, - }; - Ok(()) + let filepath = filepath.as_ref(); + let extension = filepath + .extension() + .and_then(OsStr::to_str) + .with_context(|| { + format!( + "File extension could not be parsed: \"{}\"", + filepath.display() + ) + })?; + Ok(match extension { + "json" => serde_json::to_writer(&File::create(filepath)?, self)?, + "yaml" => serde_yaml::to_writer(&File::create(filepath)?, self)?, + // TODO: do we want a default behavior for this? + _ => serde_json::to_writer(&File::create(filepath)?, self)?, + }) } /// Read from file and return instantiated struct. Method adaptively calls deserialization From 6857a034e0ab2c0dbd0b6adc65412d6d1260ac38 Mon Sep 17 00:00:00 2001 From: Kyle Carow Date: Fri, 3 Nov 2023 12:30:27 -0600 Subject: [PATCH 10/27] remove results in tests --- rust/fastsim-core/src/cycle.rs | 35 ++++++++++++-------------- rust/fastsim-core/src/simdrivelabel.rs | 20 +++++++-------- rust/fastsim-core/src/utils.rs | 30 +++++++++------------- 3 files changed, 37 insertions(+), 48 deletions(-) diff --git a/rust/fastsim-core/src/cycle.rs b/rust/fastsim-core/src/cycle.rs index 7bbef2d8..852e2363 100644 --- a/rust/fastsim-core/src/cycle.rs +++ b/rust/fastsim-core/src/cycle.rs @@ -1085,14 +1085,13 @@ mod tests { use super::*; #[test] - fn test_dist() -> anyhow::Result<()> { + fn test_dist() { let cyc = RustCycle::test_cyc(); - anyhow::ensure!(cyc.dist_m().sum() == 45.0); - Ok(()) + assert_eq!(cyc.dist_m().sum(), 45.0); } #[test] - fn test_average_speeds_and_distances() -> anyhow::Result<()> { + fn test_average_speeds_and_distances() { let time_s = vec![0.0, 10.0, 30.0, 34.0, 40.0]; let speed_mps = vec![0.0, 10.0, 10.0, 0.0, 0.0]; let grade = Array::zeros(5).to_vec(); @@ -1101,32 +1100,30 @@ mod tests { let cyc = RustCycle::new(time_s, speed_mps, grade, road_type, name); let avg_mps = average_step_speeds(&cyc); let expected_avg_mps = Array::from_vec(vec![0.0, 5.0, 10.0, 5.0, 0.0]); - anyhow::ensure!(expected_avg_mps.len() == avg_mps.len()); + assert_eq!(expected_avg_mps.len(), avg_mps.len()); for (expected, actual) in expected_avg_mps.iter().zip(avg_mps.iter()) { - anyhow::ensure!(expected == actual); + assert_eq!(expected, actual); } let dist_m = trapz_step_distances(&cyc); let expected_dist_m = Array::from_vec(vec![0.0, 50.0, 200.0, 20.0, 0.0]); - anyhow::ensure!(expected_dist_m.len() == dist_m.len()); + assert_eq!(expected_dist_m.len(), dist_m.len()); for (expected, actual) in expected_dist_m.iter().zip(dist_m.iter()) { - anyhow::ensure!(expected == actual); + assert_eq!(expected, actual); } - Ok(()) } #[test] - fn test_loading_a_cycle_from_the_filesystem() -> anyhow::Result<()> { + fn test_loading_a_cycle_from_the_filesystem() { let cyc_file_path = resources_path().join("cycles/udds.csv"); let expected_udds_length: usize = 1370; - let cyc = RustCycle::from_csv_file(cyc_file_path)?; + let cyc = RustCycle::from_csv_file(cyc_file_path).unwrap(); let num_entries = cyc.time_s.len(); - anyhow::ensure!(cyc.name == String::from("udds")); - anyhow::ensure!(num_entries > 0); - anyhow::ensure!(num_entries == cyc.time_s.len()); - anyhow::ensure!(num_entries == cyc.mps.len()); - anyhow::ensure!(num_entries == cyc.grade.len()); - anyhow::ensure!(num_entries == cyc.road_type.len()); - anyhow::ensure!(num_entries == expected_udds_length); - Ok(()) + assert_eq!(cyc.name, String::from("udds")); + assert!(num_entries > 0); + assert_eq!(num_entries, cyc.time_s.len()); + assert_eq!(num_entries, cyc.mps.len()); + assert_eq!(num_entries, cyc.grade.len()); + assert_eq!(num_entries, cyc.road_type.len()); + assert_eq!(num_entries, expected_udds_length); } } diff --git a/rust/fastsim-core/src/simdrivelabel.rs b/rust/fastsim-core/src/simdrivelabel.rs index ef37eae6..57d5a357 100644 --- a/rust/fastsim-core/src/simdrivelabel.rs +++ b/rust/fastsim-core/src/simdrivelabel.rs @@ -723,9 +723,9 @@ mod simdrivelabel_tests { use super::*; #[test] - fn test_get_label_fe_conv() -> anyhow::Result<()> { + fn test_get_label_fe_conv() { let veh: vehicle::RustVehicle = vehicle::RustVehicle::mock_vehicle(); - let (mut label_fe, _) = get_label_fe(&veh, None, None)?; + 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(); @@ -765,11 +765,10 @@ mod simdrivelabel_tests { // 100. * (label_fe_truth.net_accel - label_fe.net_accel) / label_fe_truth.net_accel // ); - anyhow::ensure!(label_fe.approx_eq(&label_fe_truth, 1e-10)); - Ok(()) + assert!(label_fe.approx_eq(&label_fe_truth, 1e-10)); } #[test] - fn test_get_label_fe_phev() -> anyhow::Result<()> { + fn test_get_label_fe_phev() { let mut veh = vehicle::RustVehicle { props: RustPhysicalProperties { air_density_kg_per_m3: 1.2, @@ -912,9 +911,9 @@ mod simdrivelabel_tests { orphaned: false, ..Default::default() }; - veh.set_derived()?; + veh.set_derived().unwrap(); - let (mut label_fe, _) = get_label_fe(&veh, None, None)?; + 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(); @@ -1128,16 +1127,15 @@ mod simdrivelabel_tests { }; let tol = 1e-8; - anyhow::ensure!(label_fe.veh.approx_eq(&label_fe_truth.veh, tol)); - anyhow::ensure!( + 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 ); - anyhow::ensure!(label_fe.approx_eq(&label_fe_truth, tol)); - Ok(()) + assert!(label_fe.approx_eq(&label_fe_truth, tol)); } } diff --git a/rust/fastsim-core/src/utils.rs b/rust/fastsim-core/src/utils.rs index a81ecf9b..f82490d7 100644 --- a/rust/fastsim-core/src/utils.rs +++ b/rust/fastsim-core/src/utils.rs @@ -339,7 +339,7 @@ mod tests { use super::*; #[test] - fn test_interp2d() -> anyhow::Result<()> { + fn test_interp2d() { // specified (x, y) point at which to interpolate value let point = [0.5, 0.5]; // grid coordinates: (x0, x1), (y0, y1) @@ -355,12 +355,11 @@ mod tests { 1.0, // upper right (x1, y1) ], ]; - anyhow::ensure!(interp2d(&point, &grid, &values)? == 0.5); - Ok(()) + assert_eq!(interp2d(&point, &grid, &values).unwrap(), 0.5); } #[test] - fn test_interp2d_offset() -> anyhow::Result<()> { + fn test_interp2d_offset() { // specified (x, y) point at which to interpolate value let point = [0.25, 0.75]; // grid coordinates: (x0, x1), (y0, y1) @@ -376,12 +375,11 @@ mod tests { 1.0, // upper right (x1, y1) ], ]; - anyhow::ensure!(interp2d(&point, &grid, &values)? == 0.375); - Ok(()) + assert_eq!(interp2d(&point, &grid, &values).unwrap(), 0.375); } #[test] - fn test_interp2d_exact_value_lower() -> anyhow::Result<()> { + fn test_interp2d_exact_value_lower() { // specified (x, y) point at which to interpolate value let point = [0.0, 0.0]; // grid coordinates: (x0, x1), (y0, y1) @@ -397,12 +395,11 @@ mod tests { 1.0, // upper right (x1, y1) ], ]; - anyhow::ensure!(interp2d(&point, &grid, &values)? == 1.0); - Ok(()) + assert_eq!(interp2d(&point, &grid, &values).unwrap(), 1.0); } #[test] - fn test_interp2d_below_value_lower() -> anyhow::Result<()> { + fn test_interp2d_below_value_lower() { // specified (x, y) point at which to interpolate value let point = [-1.0, -1.0]; // grid coordinates: (x0, x1), (y0, y1) @@ -418,12 +415,11 @@ mod tests { 1.0, // upper right (x1, y1) ], ]; - anyhow::ensure!(interp2d(&point, &grid, &values)? == 1.0); - Ok(()) + assert_eq!(interp2d(&point, &grid, &values).unwrap(), 1.0); } #[test] - fn test_interp2d_above_value_upper() -> anyhow::Result<()> { + fn test_interp2d_above_value_upper() { // specified (x, y) point at which to interpolate value let point = [2.0, 2.0]; // grid coordinates: (x0, x1), (y0, y1) @@ -439,12 +435,11 @@ mod tests { 1.0, // upper right (x1, y1) ], ]; - anyhow::ensure!(interp2d(&point, &grid, &values)? == 1.0); - Ok(()) + assert_eq!(interp2d(&point, &grid, &values).unwrap(), 1.0); } #[test] - fn test_interp2d_exact_value_upper() -> anyhow::Result<()> { + fn test_interp2d_exact_value_upper() { // specified (x, y) point at which to interpolate value let point = [1.0, 1.0]; // grid coordinates: (x0, x1), (y0, y1) @@ -460,8 +455,7 @@ mod tests { 1.0, // upper right (x1, y1) ], ]; - anyhow::ensure!(interp2d(&point, &grid, &values)? == 1.0); - Ok(()) + assert_eq!(interp2d(&point, &grid, &values).unwrap(), 1.0); } #[test] From e30636a819337a999b02369427dcfc34cded3571 Mon Sep 17 00:00:00 2001 From: Kyle Carow Date: Fri, 3 Nov 2023 13:07:50 -0600 Subject: [PATCH 11/27] clean up some more anyhow calls --- .../src/add_pyo3_api/mod.rs | 6 ++-- rust/fastsim-core/src/simdrive/cyc_mods.rs | 33 +++++++++---------- .../src/simdrive/simdrive_impl.rs | 6 ++-- rust/fastsim-core/src/vehicle.rs | 4 +-- rust/fastsim-core/src/vehicle_thermal.rs | 16 ++++----- 5 files changed, 31 insertions(+), 34 deletions(-) diff --git a/rust/fastsim-core/fastsim-proc-macros/src/add_pyo3_api/mod.rs b/rust/fastsim-core/fastsim-proc-macros/src/add_pyo3_api/mod.rs index 1745ce0d..ec15280c 100644 --- a/rust/fastsim-core/fastsim-proc-macros/src/add_pyo3_api/mod.rs +++ b/rust/fastsim-core/fastsim-proc-macros/src/add_pyo3_api/mod.rs @@ -210,10 +210,10 @@ pub fn add_pyo3_api(attr: TokenStream, item: TokenStream) -> TokenStream { }; // py_impl_block.extend::(quote! { - // #[classmethod] + // #[staticmethod] // #[pyo3(name = "default")] - // pub fn default_py(_cls: &PyType) -> anyhow::Result { - // Ok(Self::default()) + // pub fn default_py() -> Self { + // Self::default() // } // }); diff --git a/rust/fastsim-core/src/simdrive/cyc_mods.rs b/rust/fastsim-core/src/simdrive/cyc_mods.rs index bc71c53b..59a3544e 100644 --- a/rust/fastsim-core/src/simdrive/cyc_mods.rs +++ b/rust/fastsim-core/src/simdrive/cyc_mods.rs @@ -52,18 +52,16 @@ impl RustSimDrive { 0.0 }; } else { - if !(0.0..=1.0).contains(&blend_factor) { - return Err(anyhow!( - "blend_factor must be between 0 and 1 but got {}", - blend_factor - )); - } - if min_target_speed_m_per_s < 0.0 { - return Err(anyhow!( - "min_target_speed_m_per_s must be >= 0 but got {}", - min_target_speed_m_per_s - )); - } + ensure!( + (0.0..=1.0).contains(&blend_factor), + "blend_factor must be between 0 and 1 but got {}", + blend_factor + ); + ensure!( + min_target_speed_m_per_s >= 0.0, + "min_target_speed_m_per_s must be >= 0.0 but got {}", + min_target_speed_m_per_s + ); self.sim_params.idm_v_desired_in_m_per_s_by_distance_m = Some(create_dist_and_target_speeds_by_microtrip( &self.cyc0, @@ -72,12 +70,11 @@ impl RustSimDrive { )); } // Extend the duration of the base cycle - if extend_fraction < 0.0 { - return Err(anyhow!( - "extend_fraction must be >= 0.0 but got {}", - extend_fraction - )); - } + ensure!( + extend_fraction >= 0.0, + "extend_fraction must be >= 0.0 but got {}", + extend_fraction + ); if extend_fraction > 0.0 { self.cyc0 = extend_cycle(&self.cyc0, None, Some(extend_fraction)); self.cyc = self.cyc0.clone(); diff --git a/rust/fastsim-core/src/simdrive/simdrive_impl.rs b/rust/fastsim-core/src/simdrive/simdrive_impl.rs index cff9d2c8..0e7d80f2 100644 --- a/rust/fastsim-core/src/simdrive/simdrive_impl.rs +++ b/rust/fastsim-core/src/simdrive/simdrive_impl.rs @@ -1043,12 +1043,12 @@ impl RustSimDrive { let speed_guess = speed_guesses .iter() .last() - .ok_or(anyhow!("{}", format_dbg!()))? + .ok_or_else(|| anyhow!("{}", format_dbg!()))? * (1.0 - g) - g * new_speed_guesses .iter() .last() - .ok_or(anyhow!("{}", format_dbg!()))? + .ok_or_else(|| anyhow!("{}", format_dbg!()))? / d_pwr_err_per_d_speed_guesses[speed_guesses.len() - 1]; let pwr_err = pwr_err_fn(speed_guess); let pwr_err_per_speed_guess = pwr_err_per_speed_guess_fn(speed_guess); @@ -1060,7 +1060,7 @@ impl RustSimDrive { converged = ((speed_guesses .iter() .last() - .ok_or(anyhow!("{}", format_dbg!()))? + .ok_or_else(|| anyhow!("{}", format_dbg!()))? - speed_guesses[speed_guesses.len() - 2]) / speed_guesses[speed_guesses.len() - 2]) .abs() diff --git a/rust/fastsim-core/src/vehicle.rs b/rust/fastsim-core/src/vehicle.rs index 41e8392f..dcf7dc80 100644 --- a/rust/fastsim-core/src/vehicle.rs +++ b/rust/fastsim-core/src/vehicle.rs @@ -82,9 +82,9 @@ lazy_static! { Ok(self.clone()) } - #[classmethod] + #[staticmethod] #[pyo3(name = "mock_vehicle")] - fn mock_vehicle_py(_cls: &PyType) -> Self { + fn mock_vehicle_py() -> Self { Self::mock_vehicle() } )] diff --git a/rust/fastsim-core/src/vehicle_thermal.rs b/rust/fastsim-core/src/vehicle_thermal.rs index e6e78313..bc40adac 100644 --- a/rust/fastsim-core/src/vehicle_thermal.rs +++ b/rust/fastsim-core/src/vehicle_thermal.rs @@ -90,10 +90,10 @@ impl Default for FcTempEffModelExponential { /// Struct containing parameters and one time-varying variable for HVAC model #[derive(Debug, Clone, Deserialize, Serialize, PartialEq, HistoryVec)] #[add_pyo3_api( - #[classmethod] + #[staticmethod] #[pyo3(name = "default")] - pub fn default_py(_cls: &PyType) -> anyhow::Result { - Ok(Self::default()) + pub fn default_py() -> Self { + Self::default() } )] pub struct HVACModel { @@ -193,9 +193,9 @@ pub fn get_sphere_conv_params(re: f64) -> (f64, f64) { #[allow(non_snake_case)] #[derive(Deserialize, Serialize, Clone, Debug, PartialEq)] #[add_pyo3_api( - #[classmethod] + #[staticmethod] #[pyo3(name = "default")] - pub fn default_py(_cls: &PyType) -> Self { + pub fn default_py() -> Self { Default::default() } @@ -203,7 +203,7 @@ pub fn get_sphere_conv_params(re: f64) -> (f64, f64) { &mut self, hvac_model: HVACModel ) -> anyhow::Result<()>{ - Ok(check_orphaned_and_set!(self, cabin_hvac_model, CabinHvacModelTypes::Internal(hvac_model))?) + check_orphaned_and_set!(self, cabin_hvac_model, CabinHvacModelTypes::Internal(hvac_model)) } pub fn get_cabin_model_internal(&self, ) -> anyhow::Result { @@ -214,8 +214,8 @@ pub fn get_sphere_conv_params(re: f64) -> (f64, f64) { } } - pub fn set_cabin_hvac_model_external(&mut self, ) -> anyhow::Result<()> { - Ok(check_orphaned_and_set!(self, cabin_hvac_model, CabinHvacModelTypes::External)?) + pub fn set_cabin_hvac_model_external(&mut self) -> anyhow::Result<()> { + check_orphaned_and_set!(self, cabin_hvac_model, CabinHvacModelTypes::External) } pub fn set_fc_model_internal_exponential( From b6976f55f838b448d23584d451f6924574ab2867 Mon Sep 17 00:00:00 2001 From: Kyle Carow Date: Fri, 3 Nov 2023 13:43:32 -0600 Subject: [PATCH 12/27] remove from_json_str method --- rust/fastsim-cli/src/bin/fastsim-cli.rs | 16 +++++++++++----- rust/fastsim-core/src/vehicle.rs | 6 ------ 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/rust/fastsim-cli/src/bin/fastsim-cli.rs b/rust/fastsim-cli/src/bin/fastsim-cli.rs index aa2785f3..25dbfcb3 100644 --- a/rust/fastsim-cli/src/bin/fastsim-cli.rs +++ b/rust/fastsim-cli/src/bin/fastsim-cli.rs @@ -246,17 +246,23 @@ pub fn main() -> anyhow::Result<()> { 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_json_str(&veh_string) + let mut veh = RustVehicle::from_json(&veh_string)?; + veh.set_derived()?; + Ok(veh) } else { - RustVehicle::from_json_str(&veh_string) + let mut veh = RustVehicle::from_json(&veh_string)?; + veh.set_derived()?; + Ok(veh) } } else if let Some(veh_file_path) = fastsim_api.veh_file { if is_adopt || is_adopt_hd { - let vehstring = fs::read_to_string(veh_file_path)?; - let (vehstring, pwr_out_perc, h2share) = json_rewrite(vehstring)?; + let veh_string = fs::read_to_string(veh_file_path)?; + 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_json_str(&vehstring) + let mut veh = RustVehicle::from_json(&veh_string)?; + veh.set_derived()?; + Ok(veh) } else { RustVehicle::from_file(&veh_file_path) } diff --git a/rust/fastsim-core/src/vehicle.rs b/rust/fastsim-core/src/vehicle.rs index dcf7dc80..6ef535f8 100644 --- a/rust/fastsim-core/src/vehicle.rs +++ b/rust/fastsim-core/src/vehicle.rs @@ -971,12 +971,6 @@ impl RustVehicle { v.set_derived().unwrap(); v } - - pub fn from_json_str(json_str: &str) -> anyhow::Result { - let mut veh_res: anyhow::Result = Ok(serde_json::from_str(json_str)?); - veh_res.as_mut().unwrap().set_derived()?; - veh_res - } } impl SerdeAPI for RustVehicle { From 7665bb94b0f256fd5d92a08ffabecf63e2b52e2f Mon Sep 17 00:00:00 2001 From: Kyle Carow Date: Fri, 3 Nov 2023 13:47:39 -0600 Subject: [PATCH 13/27] from_str now calls from_yaml or from_json --- rust/fastsim-core/src/cycle.rs | 10 +++++----- rust/fastsim-core/src/traits.rs | 8 ++++---- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/rust/fastsim-core/src/cycle.rs b/rust/fastsim-core/src/cycle.rs index bd7f7180..35f8697c 100644 --- a/rust/fastsim-core/src/cycle.rs +++ b/rust/fastsim-core/src/cycle.rs @@ -658,12 +658,12 @@ impl SerdeAPI for RustCycle { // Note that using this method to instantiate a RustCycle from CSV instead of // the `from_csv_str` method directly sets the cycle name to an empty string. fn from_str(contents: &str, format: &str) -> anyhow::Result { - Ok(match format { - "yaml" => serde_yaml::from_str(contents)?, - "json" => serde_json::from_str(contents)?, - "csv" => Self::from_csv_str(contents, "")?, + match format { + "yaml" => Self::from_yaml(contents), + "json" => Self::from_json(contents), + "csv" => Self::from_csv_str(contents, ""), _ => bail!("Unsupported file format: {format:?}"), - }) + } } } diff --git a/rust/fastsim-core/src/traits.rs b/rust/fastsim-core/src/traits.rs index 828d837e..0a358f1f 100644 --- a/rust/fastsim-core/src/traits.rs +++ b/rust/fastsim-core/src/traits.rs @@ -104,11 +104,11 @@ pub trait SerdeAPI: Serialize + for<'a> Deserialize<'a> { } fn from_str(contents: &str, format: &str) -> anyhow::Result { - Ok(match format { - "yaml" => serde_yaml::from_str(contents)?, - "json" => serde_json::from_str(contents)?, + match format { + "yaml" => Self::from_yaml(contents), + "json" => Self::from_json(contents), _ => bail!("Unsupported file format: {format:?}"), - }) + } } /// JSON serialization method From 428abee71ef9bf1dccc2f0f53b644c5bbc8a1fa8 Mon Sep 17 00:00:00 2001 From: Kyle Carow Date: Fri, 3 Nov 2023 13:58:40 -0600 Subject: [PATCH 14/27] shut annoying import warning up (it IS actually used) --- rust/fastsim-core/src/imports.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/rust/fastsim-core/src/imports.rs b/rust/fastsim-core/src/imports.rs index ce464068..071a4766 100644 --- a/rust/fastsim-core/src/imports.rs +++ b/rust/fastsim-core/src/imports.rs @@ -6,7 +6,9 @@ pub(crate) use serde::{Deserialize, Serialize}; pub(crate) use std::cmp; pub(crate) use std::ffi::OsStr; pub(crate) use std::fs::File; -pub(crate) use std::path::{Path, PathBuf}; +pub(crate) use std::path::Path; +#[allow(unused_imports)] +pub(crate) use std::path::PathBuf; pub(crate) use crate::traits::*; pub(crate) use crate::utils::*; From 8dd2f8d72b509b568ca0b9de8ff347f284080c12 Mon Sep 17 00:00:00 2001 From: Kyle Carow Date: Fri, 3 Nov 2023 14:30:55 -0600 Subject: [PATCH 15/27] more anyhow and result cleanup, removed some unnecessary result returns --- rust/fastsim-cli/tests/integration-tests.rs | 10 ++- .../src/add_pyo3_api/mod.rs | 7 +-- .../src/add_pyo3_api/pyo3_api_utils.rs | 6 +- rust/fastsim-core/src/cycle.rs | 62 +++++++++---------- .../src/simdrive/simdrive_impl.rs | 3 +- rust/fastsim-core/src/vehicle.rs | 16 ++--- 6 files changed, 48 insertions(+), 56 deletions(-) diff --git a/rust/fastsim-cli/tests/integration-tests.rs b/rust/fastsim-cli/tests/integration-tests.rs index 48d794c0..30675062 100644 --- a/rust/fastsim-cli/tests/integration-tests.rs +++ b/rust/fastsim-cli/tests/integration-tests.rs @@ -5,8 +5,8 @@ use assert_cmd::prelude::{CommandCargoExt, OutputAssertExt}; use predicates::prelude::predicate; #[test] -fn test_that_cli_app_produces_result() -> Result<(), Box> { - let mut cmd = Command::cargo_bin("fastsim-cli")?; +fn test_that_cli_app_produces_result() { + let mut cmd = Command::cargo_bin("fastsim-cli").unwrap(); let mut cyc_file = project_root::get_project_root().unwrap(); cyc_file.push(Path::new("../python/fastsim/resources/cycles/udds.csv")); cyc_file = cyc_file.canonicalize().unwrap(); @@ -28,17 +28,16 @@ fn test_that_cli_app_produces_result() -> Result<(), Box> cmd.assert() .success() .stdout(predicate::str::contains("33.8")); - Ok(()) } #[test] -fn test_that_adopt_hd_option_works_as_expected() -> Result<(), Box> { +fn test_that_adopt_hd_option_works_as_expected() { let expected_results = vec![ ("adoptstring.json", "0.245"), // 0.245 kWh/mile ("adoptstring2.json", "7.906"), // 7.906 mpgge ("adoptstring3.json", "6.882"), // 6.882 mpgge ]; - let mut cmd = Command::cargo_bin("fastsim-cli")?; + let mut cmd = Command::cargo_bin("fastsim-cli").unwrap(); for (veh_file, expected_result) in expected_results.iter() { let mut adopt_veh_file = project_root::get_project_root().unwrap(); let mut adopt_str_path = String::from("../rust/fastsim-cli/tests/assets/"); @@ -59,5 +58,4 @@ fn test_that_adopt_hd_option_works_as_expected() -> Result<(), Box { impl_block.extend::(quote! { #[getter] - pub fn get_orphaned(&self) -> anyhow::Result { - Ok(self.orphaned) + pub fn get_orphaned(&self) -> bool { + self.orphaned } /// Reset the orphaned flag to false. - pub fn reset_orphaned(&mut self) -> anyhow::Result<()> { + pub fn reset_orphaned(&mut self) { self.orphaned = false; - Ok(()) } }) } diff --git a/rust/fastsim-core/fastsim-proc-macros/src/add_pyo3_api/pyo3_api_utils.rs b/rust/fastsim-core/fastsim-proc-macros/src/add_pyo3_api/pyo3_api_utils.rs index e3e3d737..463fc2f3 100644 --- a/rust/fastsim-core/fastsim-proc-macros/src/add_pyo3_api/pyo3_api_utils.rs +++ b/rust/fastsim-core/fastsim-proc-macros/src/add_pyo3_api/pyo3_api_utils.rs @@ -28,9 +28,8 @@ macro_rules! impl_vec_get_set { } else { $impl_block.extend(quote! { #[setter] - pub fn #set_name(&mut self, new_value: Vec<$contained_type>) -> anyhow::Result<()> { + pub fn #set_name(&mut self, new_value: Vec<$contained_type>) { self.#$fident = new_value; - Ok(()) } }) } @@ -51,9 +50,8 @@ macro_rules! impl_vec_get_set { } else { $impl_block.extend(quote! { #[setter] - pub fn #set_name(&mut self, new_value: Vec<$contained_type>) -> anyhow::Result<()> { + pub fn #set_name(&mut self, new_value: Vec<$contained_type>) { self.#$fident = Array1::from_vec(new_value); - Ok(()) } }) } diff --git a/rust/fastsim-core/src/cycle.rs b/rust/fastsim-core/src/cycle.rs index 35f8697c..2e2373e7 100644 --- a/rust/fastsim-core/src/cycle.rs +++ b/rust/fastsim-core/src/cycle.rs @@ -196,7 +196,7 @@ pub fn time_spent_moving(cyc: &RustCycle, stopped_speed_m_per_s: Option) -> /// to subsequent stop plus any idle (stopped time). /// Arguments: /// ---------- -/// cycle: drive cycle converted to dictionary by cycle.get_cyc_dict() +/// cycle: drive cycle /// stop_speed_m__s: speed at which vehicle is considered stopped for trip /// separation /// keep_name: (optional) bool, if True and cycle contains "name", adds @@ -483,8 +483,8 @@ impl RustCycleCache { } #[allow(clippy::type_complexity)] - pub fn __getnewargs__(&self) -> anyhow::Result<(Vec, Vec, Vec, Vec, &str)> { - Ok((self.time_s.to_vec(), self.mps.to_vec(), self.grade.to_vec(), self.road_type.to_vec(), &self.name)) + pub fn __getnewargs__(&self) -> (Vec, Vec, Vec, Vec, &str) { + (self.time_s.to_vec(), self.mps.to_vec(), self.grade.to_vec(), self.road_type.to_vec(), &self.name) } #[staticmethod] @@ -493,19 +493,18 @@ impl RustCycleCache { Self::from_csv_file(PathBuf::extract(filepath)?) } - pub fn to_rust(&self) -> anyhow::Result { - Ok(self.clone()) + pub fn to_rust(&self) -> Self { + self.clone() } /// Return a HashMap representing the cycle - pub fn get_cyc_dict(&self) -> anyhow::Result>> { - let dict: HashMap> = HashMap::from([ + pub fn get_cyc_dict(&self) -> HashMap> { + HashMap::from([ ("time_s".to_string(), self.time_s.to_vec()), ("mps".to_string(), self.mps.to_vec()), ("grade".to_string(), self.grade.to_vec()), ("road_type".to_string(), self.road_type.to_vec()), - ]); - Ok(dict) + ]) } #[pyo3(name = "modify_by_const_jerk_trajectory")] @@ -515,8 +514,8 @@ impl RustCycleCache { n: usize, jerk_m_per_s3: f64, accel0_m_per_s2: f64, - ) -> anyhow::Result { - Ok(self.modify_by_const_jerk_trajectory(idx, n, jerk_m_per_s3, accel0_m_per_s2)) + ) -> f64 { + self.modify_by_const_jerk_trajectory(idx, n, jerk_m_per_s3, accel0_m_per_s2) } #[pyo3(name = "modify_with_braking_trajectory")] @@ -530,8 +529,8 @@ impl RustCycleCache { } #[pyo3(name = "calc_distance_to_next_stop_from")] - pub fn calc_distance_to_next_stop_from_py(&self, distance_m: f64) -> anyhow::Result { - Ok(self.calc_distance_to_next_stop_from(distance_m, None)) + pub fn calc_distance_to_next_stop_from_py(&self, distance_m: f64) -> f64 { + self.calc_distance_to_next_stop_from(distance_m, None) } #[pyo3(name = "average_grade_over_range")] @@ -539,51 +538,50 @@ impl RustCycleCache { &self, distance_start_m: f64, delta_distance_m: f64, - ) -> anyhow::Result { - Ok(self.average_grade_over_range(distance_start_m, delta_distance_m, None)) + ) -> f64 { + self.average_grade_over_range(distance_start_m, delta_distance_m, None) } #[pyo3(name = "build_cache")] - pub fn build_cache_py(&self) -> anyhow::Result { - Ok(self.build_cache()) + pub fn build_cache_py(&self) -> RustCycleCache { + self.build_cache() } #[pyo3(name = "dt_s_at_i")] - pub fn dt_s_at_i_py(&self, i: usize) -> anyhow::Result { + pub fn dt_s_at_i_py(&self, i: usize) -> f64 { if i == 0 { - Ok(0.0) + 0.0 } else { - Ok(self.dt_s_at_i(i)) + self.dt_s_at_i(i) } } #[getter] - pub fn get_mph(&self) -> anyhow::Result> { - Ok((&self.mps * crate::params::MPH_PER_MPS).to_vec()) + pub fn get_mph(&self) -> Vec { + (&self.mps * crate::params::MPH_PER_MPS).to_vec() } #[setter] - pub fn set_mph(&mut self, new_value: Vec) -> anyhow::Result<()> { + pub fn set_mph(&mut self, new_value: Vec) { self.mps = Array::from_vec(new_value) / MPH_PER_MPS; - Ok(()) } #[getter] /// array of time steps - pub fn get_dt_s(&self) -> anyhow::Result> { - Ok(self.dt_s().to_vec()) + pub fn get_dt_s(&self) -> Vec { + self.dt_s().to_vec() } #[getter] /// cycle length - pub fn get_len(&self) -> anyhow::Result { - Ok(self.len()) + pub fn get_len(&self) -> usize { + self.len() } #[getter] /// distance for each time step based on final speed - pub fn get_dist_m(&self) -> anyhow::Result> { - Ok(self.dist_m().to_vec()) + pub fn get_dist_m(&self) -> Vec { + self.dist_m().to_vec() } #[getter] - pub fn get_delta_elev_m(&self) -> anyhow::Result> { - Ok(self.delta_elev_m().to_vec()) + pub fn get_delta_elev_m(&self) -> Vec { + self.delta_elev_m().to_vec() } )] /// Struct for containing: diff --git a/rust/fastsim-core/src/simdrive/simdrive_impl.rs b/rust/fastsim-core/src/simdrive/simdrive_impl.rs index 0e7d80f2..a519e76f 100644 --- a/rust/fastsim-core/src/simdrive/simdrive_impl.rs +++ b/rust/fastsim-core/src/simdrive/simdrive_impl.rs @@ -460,8 +460,7 @@ impl RustSimDrive { self.walk(init_soc, aux_in_kw_override)?; - self.set_post_scalars()?; - Ok(()) + self.set_post_scalars() } pub fn sim_drive_accel( diff --git a/rust/fastsim-core/src/vehicle.rs b/rust/fastsim-core/src/vehicle.rs index 6ef535f8..0c07ed45 100644 --- a/rust/fastsim-core/src/vehicle.rs +++ b/rust/fastsim-core/src/vehicle.rs @@ -834,16 +834,17 @@ impl RustVehicle { } // check that efficiencies are not violating the first law of thermo - assert!( + // TODO: this could perhaps be done in the input validators + ensure!( arrmin(&self.fc_eff_array) >= 0.0, - "min MC eff < 0 is not allowed" + "minimum FC efficiency < 0 is not allowed" ); - assert!(self.fc_peak_eff() < 1.0, "fcPeakEff >= 1 is not allowed."); - assert!( + ensure!(self.fc_peak_eff() < 1.0, "fc_peak_eff >= 1 is not allowed"); + ensure!( arrmin(&self.mc_full_eff_array) >= 0.0, - "min MC eff < 0 is not allowed" + "minimum MC efficiency < 0 is not allowed" ); - assert!(self.mc_peak_eff() < 1.0, "mcPeakEff >= 1 is not allowed."); + ensure!(self.mc_peak_eff() < 1.0, "mc_peak_eff >= 1 is not allowed"); self.set_veh_mass(); @@ -975,8 +976,7 @@ impl RustVehicle { impl SerdeAPI for RustVehicle { fn init(&mut self) -> anyhow::Result<()> { - self.set_derived()?; - Ok(()) + self.set_derived() } } From 9e5ec49a6fee6f6236444a102502b54caeb6858b Mon Sep 17 00:00:00 2001 From: Kyle Carow Date: Fri, 3 Nov 2023 14:49:09 -0600 Subject: [PATCH 16/27] refactor code that clippy didn't like --- rust/fastsim-cli/src/bin/fastsim-cli.rs | 4 ++-- .../src/add_pyo3_api/pyo3_api_utils.rs | 12 ++++++------ rust/fastsim-core/src/simdrive.rs | 12 ++++-------- rust/fastsim-core/src/thermal.rs | 16 ++++++++-------- rust/fastsim-core/src/traits.rs | 5 +++-- rust/fastsim-core/src/vehicle.rs | 4 ++-- 6 files changed, 25 insertions(+), 28 deletions(-) diff --git a/rust/fastsim-cli/src/bin/fastsim-cli.rs b/rust/fastsim-cli/src/bin/fastsim-cli.rs index 25dbfcb3..334ff477 100644 --- a/rust/fastsim-cli/src/bin/fastsim-cli.rs +++ b/rust/fastsim-cli/src/bin/fastsim-cli.rs @@ -1,4 +1,3 @@ -use anyhow; use clap::{ArgGroup, Parser}; use serde::{Deserialize, Serialize}; use serde_json::{json, Value}; @@ -224,7 +223,7 @@ pub fn main() -> anyhow::Result<()> { RustCycle::from_file(&cyc_file_path) } } else if is_adopt_hd && adopt_hd_has_cycle { - RustCycle::from_file(&adopt_hd_string) + RustCycle::from_file(adopt_hd_string) } else { //TODO? use pathbuff to string, for robustness Ok(RustCycle::new( @@ -395,6 +394,7 @@ struct ParsedValue(Value); impl SerdeAPI for ParsedValue {} /// Rewrites the ADOPT JSON string to be in compliance with what FASTSim expects for JSON input. +#[allow(clippy::type_complexity)] fn json_rewrite(x: String) -> anyhow::Result<(String, Option>, Option>)> { let adoptstring = x; diff --git a/rust/fastsim-core/fastsim-proc-macros/src/add_pyo3_api/pyo3_api_utils.rs b/rust/fastsim-core/fastsim-proc-macros/src/add_pyo3_api/pyo3_api_utils.rs index 463fc2f3..219770bc 100644 --- a/rust/fastsim-core/fastsim-proc-macros/src/add_pyo3_api/pyo3_api_utils.rs +++ b/rust/fastsim-core/fastsim-proc-macros/src/add_pyo3_api/pyo3_api_utils.rs @@ -4,8 +4,8 @@ macro_rules! impl_vec_get_set { let get_name: TokenStream2 = format!("get_{}", $fident).parse().unwrap(); $impl_block.extend::(quote! { #[getter] - pub fn #get_name(&self) -> anyhow::Result<$wrapper_type> { - Ok($wrapper_type::new(self.#$fident.clone())) + pub fn #get_name(&self) -> $wrapper_type { + $wrapper_type::new(self.#$fident.clone()) } }); } @@ -78,16 +78,16 @@ macro_rules! impl_get_body { let get_block = if $opts.field_has_orphaned { quote! { #[getter] - pub fn #get_name(&mut self) -> anyhow::Result<#$type> { + pub fn #get_name(&mut self) -> #$type { self.#$field.orphaned = true; - Ok(self.#$field.clone()) + self.#$field.clone() } } } else { quote! { #[getter] - pub fn #get_name(&self) -> anyhow::Result<#$type> { - Ok(self.#$field.clone()) + pub fn #get_name(&self) -> #$type { + self.#$field.clone() } } }; diff --git a/rust/fastsim-core/src/simdrive.rs b/rust/fastsim-core/src/simdrive.rs index 874e66dd..1ca6ae35 100644 --- a/rust/fastsim-core/src/simdrive.rs +++ b/rust/fastsim-core/src/simdrive.rs @@ -346,16 +346,12 @@ impl Default for RustSimDriveParams { } #[getter] - pub fn get_fs_cumu_mj_out_ach(&self) -> anyhow::Result { - Ok( - Pyo3ArrayF64::new(ndarrcumsum(&(self.fs_kw_out_ach.clone() * self.cyc.dt_s() * 1e-3))) - ) + pub fn get_fs_cumu_mj_out_ach(&self) -> Pyo3ArrayF64 { + Pyo3ArrayF64::new(ndarrcumsum(&(self.fs_kw_out_ach.clone() * self.cyc.dt_s() * 1e-3))) } #[getter] - pub fn get_fc_cumu_mj_out_ach(&self) -> anyhow::Result { - Ok( - Pyo3ArrayF64::new(ndarrcumsum(&(self.fc_kw_out_ach.clone() * self.cyc.dt_s() * 1e-3))) - ) + pub fn get_fc_cumu_mj_out_ach(&self) -> Pyo3ArrayF64 { + Pyo3ArrayF64::new(ndarrcumsum(&(self.fc_kw_out_ach.clone() * self.cyc.dt_s() * 1e-3))) } )] pub struct RustSimDrive { diff --git a/rust/fastsim-core/src/thermal.rs b/rust/fastsim-core/src/thermal.rs index 43b20795..98b786c9 100644 --- a/rust/fastsim-core/src/thermal.rs +++ b/rust/fastsim-core/src/thermal.rs @@ -60,9 +60,9 @@ use crate::vehicle_thermal::*; &mut self, init_soc: f64, aux_in_kw_override: Option>, - ) -> anyhow::Result<()> { + ) { let aux_in_kw_override = aux_in_kw_override.map(Array1::from); - Ok(self.walk(init_soc, aux_in_kw_override)) + self.walk(init_soc, aux_in_kw_override); } #[pyo3(name = "init_for_step")] @@ -77,9 +77,9 @@ use crate::vehicle_thermal::*; &mut self, init_soc:f64, aux_in_kw_override: Option> - ) -> anyhow::Result<()> { + ) { let aux_in_kw_override = aux_in_kw_override.map(Array1::from); - Ok(self.init_for_step(init_soc, aux_in_kw_override)) + self.init_for_step(init_soc, aux_in_kw_override); } /// Step through 1 time step. @@ -88,8 +88,8 @@ use crate::vehicle_thermal::*; } #[pyo3(name = "solve_step")] /// Perform all the calculations to solve 1 time step. - pub fn solve_step_py(&mut self, i: usize) -> anyhow::Result<()> { - Ok(self.solve_step(i)) + pub fn solve_step_py(&mut self, i: usize) { + self.solve_step(i); } #[pyo3(name = "set_misc_calcs")] @@ -97,8 +97,8 @@ use crate::vehicle_thermal::*; /// Arguments: /// ---------- /// i: index of time step - pub fn set_misc_calcs_py(&mut self, i: usize) -> anyhow::Result<()> { - Ok(self.set_misc_calcs(i)) + pub fn set_misc_calcs_py(&mut self, i: usize) { + self.set_misc_calcs(i); } #[pyo3(name = "set_comp_lims")] diff --git a/rust/fastsim-core/src/traits.rs b/rust/fastsim-core/src/traits.rs index 0a358f1f..57bffc14 100644 --- a/rust/fastsim-core/src/traits.rs +++ b/rust/fastsim-core/src/traits.rs @@ -30,12 +30,13 @@ pub trait SerdeAPI: Serialize + for<'a> Deserialize<'a> { filepath.display() ) })?; - Ok(match extension { + match extension { "json" => serde_json::to_writer(&File::create(filepath)?, self)?, "yaml" => serde_yaml::to_writer(&File::create(filepath)?, self)?, // TODO: do we want a default behavior for this? _ => serde_json::to_writer(&File::create(filepath)?, self)?, - }) + } + Ok(()) } /// Read from file and return instantiated struct. Method adaptively calls deserialization diff --git a/rust/fastsim-core/src/vehicle.rs b/rust/fastsim-core/src/vehicle.rs index 0c07ed45..d7a8b4ee 100644 --- a/rust/fastsim-core/src/vehicle.rs +++ b/rust/fastsim-core/src/vehicle.rs @@ -78,8 +78,8 @@ lazy_static! { /// An identify function to allow RustVehicle to be used as a python vehicle and respond to this method /// Returns a clone of the current object - pub fn to_rust(&self) -> anyhow::Result { - Ok(self.clone()) + pub fn to_rust(&self) -> Self { + self.clone() } #[staticmethod] From 54146536068cf59df34169891ab12aa3c09bf235 Mon Sep 17 00:00:00 2001 From: Kyle Carow Date: Fri, 3 Nov 2023 16:06:02 -0600 Subject: [PATCH 17/27] expose from_resource to Python API --- .../fastsim-proc-macros/src/add_pyo3_api/mod.rs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/rust/fastsim-core/fastsim-proc-macros/src/add_pyo3_api/mod.rs b/rust/fastsim-core/fastsim-proc-macros/src/add_pyo3_api/mod.rs index 9681c603..f56729df 100644 --- a/rust/fastsim-core/fastsim-proc-macros/src/add_pyo3_api/mod.rs +++ b/rust/fastsim-core/fastsim-proc-macros/src/add_pyo3_api/mod.rs @@ -117,6 +117,12 @@ pub fn add_pyo3_api(attr: TokenStream, item: TokenStream) -> TokenStream { pub fn from_file_py(filepath: &PyAny) -> anyhow::Result { Self::from_file(PathBuf::extract(filepath)?) } + + #[staticmethod] + #[pyo3(name = "from_resource")] + pub fn from_resource_py(filepath: &PyAny) -> anyhow::Result { + Self::from_resource(PathBuf::extract(filepath)?) + } }); } } else if let syn::Fields::Unnamed(syn::FieldsUnnamed { unnamed, .. }) = &mut ast.fields { From 12d3a56069dbd5a13729f1688760c0dbe77564b9 Mon Sep 17 00:00:00 2001 From: Kyle Carow Date: Fri, 3 Nov 2023 16:42:49 -0600 Subject: [PATCH 18/27] make solve_step return a Result in thermal.rs instead of unwrapping --- rust/fastsim-core/src/thermal.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/rust/fastsim-core/src/thermal.rs b/rust/fastsim-core/src/thermal.rs index 98b786c9..c3398cbc 100644 --- a/rust/fastsim-core/src/thermal.rs +++ b/rust/fastsim-core/src/thermal.rs @@ -88,8 +88,8 @@ use crate::vehicle_thermal::*; } #[pyo3(name = "solve_step")] /// Perform all the calculations to solve 1 time step. - pub fn solve_step_py(&mut self, i: usize) { - self.solve_step(i); + pub fn solve_step_py(&mut self, i: usize) -> anyhow::Result<()> { + self.solve_step(i) } #[pyo3(name = "set_misc_calcs")] @@ -357,8 +357,8 @@ impl SimDriveHot { Ok(()) } - pub fn solve_step(&mut self, i: usize) { - self.sd.solve_step(i).unwrap(); + pub fn solve_step(&mut self, i: usize) -> anyhow::Result<()> { + self.sd.solve_step(i) } pub fn set_thermal_calcs(&mut self, i: usize) { From 88d6e0709d8790f54f270f4962060b45e7354817 Mon Sep 17 00:00:00 2001 From: Chad Baker Date: Mon, 6 Nov 2023 09:54:36 -0700 Subject: [PATCH 19/27] set up mdbook to publish via gh action --- .github/workflows/deploy-book.yaml | 65 ++++++++++++++++++++++++++++++ docs/.gitignore | 1 + docs/book.toml | 10 +++++ docs/src/SUMMARY.md | 4 ++ docs/src/how-to-update.md | 15 +++++++ docs/src/intro.md | 3 ++ 6 files changed, 98 insertions(+) create mode 100644 .github/workflows/deploy-book.yaml create mode 100644 docs/.gitignore create mode 100644 docs/book.toml create mode 100644 docs/src/SUMMARY.md create mode 100644 docs/src/how-to-update.md create mode 100644 docs/src/intro.md diff --git a/.github/workflows/deploy-book.yaml b/.github/workflows/deploy-book.yaml new file mode 100644 index 00000000..4d3a415c --- /dev/null +++ b/.github/workflows/deploy-book.yaml @@ -0,0 +1,65 @@ +name: Deploy mdBook site to Pages + +on: + # Runs on pushes targeting the default branch + push: + branches: ["main"] + paths: + - "docs/**" + - ".github/workflows/deploy-book.yaml" + pull_request: + branches: ["main"] + paths: + - "docs/**" + - ".github/workflows/deploy-book.yaml" + + # Allows you to run this workflow manually from the Actions tab + workflow_dispatch: + +# Sets permissions of the GITHUB_TOKEN to allow deployment to GitHub Pages +permissions: + contents: read + pages: write + id-token: write + +# Allow only one concurrent deployment, skipping runs queued between the run in-progress and latest queued. +# However, do NOT cancel in-progress runs as we want to allow these production deployments to complete. +concurrency: + group: "pages" + cancel-in-progress: false + +jobs: + # Build job + build: + runs-on: [ self-hosted ] + env: + MDBOOK_VERSION: 0.4.21 + steps: + - uses: actions/checkout@v3 + - name: Install mdBook + run: | + curl --proto '=https' --tlsv1.2 https://sh.rustup.rs -sSf -y | sh + rustup update + cargo install --version ${MDBOOK_VERSION} mdbook + - name: Setup Pages + id: pages + uses: actions/configure-pages@v3 + - name: Build with mdBook + working-directory: ${{runner.workspace}}/mbap-computing/docs/ + run: mdbook build + - name: Upload artifact + uses: actions/upload-pages-artifact@v1 + with: + path: ./docs/book + + # Deployment job + deploy: + environment: + name: github-pages + url: ${{ steps.deployment.outputs.page_url }} + runs-on: [ self-hosted ] + needs: build + steps: + - name: Deploy to GitHub Pages + id: deployment + uses: actions/deploy-pages@v2 diff --git a/docs/.gitignore b/docs/.gitignore new file mode 100644 index 00000000..4e42a1bc --- /dev/null +++ b/docs/.gitignore @@ -0,0 +1 @@ +book/ \ No newline at end of file diff --git a/docs/book.toml b/docs/book.toml new file mode 100644 index 00000000..bae0594b --- /dev/null +++ b/docs/book.toml @@ -0,0 +1,10 @@ +[book] +authors = ["Chad Baker"] +language = "en" +multilingual = false +src = "src" +title = "FASTSim Documentation" + +[output.html.fold] +enable = true +level = 0 \ No newline at end of file diff --git a/docs/src/SUMMARY.md b/docs/src/SUMMARY.md new file mode 100644 index 00000000..d19171dc --- /dev/null +++ b/docs/src/SUMMARY.md @@ -0,0 +1,4 @@ +# Summary + +- [Introduction](./intro.md) +- [How to Update This Book](./how-to-update.md) diff --git a/docs/src/how-to-update.md b/docs/src/how-to-update.md new file mode 100644 index 00000000..65cf96d0 --- /dev/null +++ b/docs/src/how-to-update.md @@ -0,0 +1,15 @@ +# How to Update This Markdown Book + +[mdBook Documentation](https://rust-lang.github.io/mdBook/) + +## Setup + +1. If not already done, [install mdbook](https://rust-lang.github.io/mdBook/guide/installation.html) + +## Publishing + +1. Update `book.toml` or files in `docs/src/` +1. Make sure the docs look good locally: `mdbook build docs/ --open` +1. Commit files and push to `main` branch + +After that, a GitHub action will build the book and publish it [here](https://pages.github.nrel.gov/MBAP/mbap-computing/) diff --git a/docs/src/intro.md b/docs/src/intro.md new file mode 100644 index 00000000..1652ea2f --- /dev/null +++ b/docs/src/intro.md @@ -0,0 +1,3 @@ +# Introduction + +This is the overall FASTSim documentation. We're working toward making this a fully integrated document that includes both the Python API and Rust core documentation for the `fastsim-2` branch and eventually also for the `fastsim-3` branch. From 6580eb87b68a690499846d7e3b5552a73991f771 Mon Sep 17 00:00:00 2001 From: Chad Baker Date: Mon, 6 Nov 2023 09:59:57 -0700 Subject: [PATCH 20/27] fixed branch --- .github/workflows/deploy-book.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/deploy-book.yaml b/.github/workflows/deploy-book.yaml index 4d3a415c..9ff9ea1c 100644 --- a/.github/workflows/deploy-book.yaml +++ b/.github/workflows/deploy-book.yaml @@ -3,12 +3,12 @@ name: Deploy mdBook site to Pages on: # Runs on pushes targeting the default branch push: - branches: ["main"] + branches: ["fastsim-2"] paths: - "docs/**" - ".github/workflows/deploy-book.yaml" pull_request: - branches: ["main"] + branches: ["fastsim-2"] paths: - "docs/**" - ".github/workflows/deploy-book.yaml" From 7511ef0eb48f110ab873803fe61f46255d32d5fc Mon Sep 17 00:00:00 2001 From: Steuteville Date: Wed, 8 Nov 2023 13:09:03 -0700 Subject: [PATCH 21/27] updating readme and rust folder readme to include altrios-style test and info badges --- README.md | 2 ++ rust/README.md | 1 + 2 files changed, 3 insertions(+) diff --git a/README.md b/README.md index f962f5f8..a8f0bc5c 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,7 @@ ![FASTSim Logo](https://www.nrel.gov/transportation/assets/images/icon-fastsim.jpg) +[![Tests](https://github.com/NREL/fastsim/actions/workflows/tests.yaml/badge.svg)](https://github.com/NREL/fastsim/actions/workflows/tests.yaml) [![wheels](https://github.com/NREL/fastsim/actions/workflows/wheels.yaml/badge.svg)](https://github.com/NREL/fastsim/actions/workflows/wheels.yaml?event=release) ![Release](https://img.shields.io/badge/release-v0.1.0-blue) ![Python](https://img.shields.io/badge/python-3.9%20%7C%203.10-blue) [![Documentation](https://img.shields.io/badge/documentation-custom-blue.svg)](https://nrel.github.io/fastsim/) [![GitHub](https://img.shields.io/badge/GitHub-fastsim-blue.svg)](https://github.com/NREL/fastsim) + # Description This is the python/rust flavor of [NREL's FASTSimTM](https://www.nrel.gov/transportation/fastsim.html), which is based on the original Excel implementation. Effort will be made to keep the core methodology between this software and the Excel flavor in line with one another. diff --git a/rust/README.md b/rust/README.md index 00742ff8..3eb2c2e6 100644 --- a/rust/README.md +++ b/rust/README.md @@ -1,4 +1,5 @@ # Crate Architecture +[![Tests](https://github.com/NREL/fastsim/actions/workflows/tests.yaml/badge.svg)](https://github.com/NREL/fastsim/actions/workflows/tests.yaml) [![wheels](https://github.com/NREL/fastsim/actions/workflows/wheels.yaml/badge.svg)](https://github.com/NREL/fastsim/actions/workflows/wheels.yaml?event=release) ![Release](https://img.shields.io/badge/release-v0.1.0-blue) ![Python](https://img.shields.io/badge/python-3.9%20%7C%203.10-blue) [![Documentation](https://img.shields.io/badge/documentation-custom-blue.svg)](https://nrel.github.io/fastsim/) [![GitHub](https://img.shields.io/badge/GitHub-fastsim-blue.svg)](https://github.com/NREL/fastsim) FASTSim Rust crates are organized as a cargo [workspace](Cargo.toml) as follows: 1. `fastsim-core`: a pure rust lib crate with optional `pyo3` feature. This crate is intended to be used by other crates and is not in itself an app. 1. `fastsim-cli`: a wrapper around `fastsim-core` to enable a standalone CLI app for running fastsim and getting results out. From aaeb01ae3b59bf7bb592d9acecebfe8c5c55bb83 Mon Sep 17 00:00:00 2001 From: Steuteville Date: Wed, 8 Nov 2023 13:11:21 -0700 Subject: [PATCH 22/27] updating readme format --- rust/README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/rust/README.md b/rust/README.md index 3eb2c2e6..e8375932 100644 --- a/rust/README.md +++ b/rust/README.md @@ -1,5 +1,6 @@ # Crate Architecture [![Tests](https://github.com/NREL/fastsim/actions/workflows/tests.yaml/badge.svg)](https://github.com/NREL/fastsim/actions/workflows/tests.yaml) [![wheels](https://github.com/NREL/fastsim/actions/workflows/wheels.yaml/badge.svg)](https://github.com/NREL/fastsim/actions/workflows/wheels.yaml?event=release) ![Release](https://img.shields.io/badge/release-v0.1.0-blue) ![Python](https://img.shields.io/badge/python-3.9%20%7C%203.10-blue) [![Documentation](https://img.shields.io/badge/documentation-custom-blue.svg)](https://nrel.github.io/fastsim/) [![GitHub](https://img.shields.io/badge/GitHub-fastsim-blue.svg)](https://github.com/NREL/fastsim) + FASTSim Rust crates are organized as a cargo [workspace](Cargo.toml) as follows: 1. `fastsim-core`: a pure rust lib crate with optional `pyo3` feature. This crate is intended to be used by other crates and is not in itself an app. 1. `fastsim-cli`: a wrapper around `fastsim-core` to enable a standalone CLI app for running fastsim and getting results out. From 65b7d6f92fd71de79e16ee98b388daba9b6b11a7 Mon Sep 17 00:00:00 2001 From: Steuteville Date: Thu, 16 Nov 2023 08:31:35 -0700 Subject: [PATCH 23/27] deleting release badges --- README.md | 2 +- rust/README.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index a8f0bc5c..98bbcf1e 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ ![FASTSim Logo](https://www.nrel.gov/transportation/assets/images/icon-fastsim.jpg) -[![Tests](https://github.com/NREL/fastsim/actions/workflows/tests.yaml/badge.svg)](https://github.com/NREL/fastsim/actions/workflows/tests.yaml) [![wheels](https://github.com/NREL/fastsim/actions/workflows/wheels.yaml/badge.svg)](https://github.com/NREL/fastsim/actions/workflows/wheels.yaml?event=release) ![Release](https://img.shields.io/badge/release-v0.1.0-blue) ![Python](https://img.shields.io/badge/python-3.9%20%7C%203.10-blue) [![Documentation](https://img.shields.io/badge/documentation-custom-blue.svg)](https://nrel.github.io/fastsim/) [![GitHub](https://img.shields.io/badge/GitHub-fastsim-blue.svg)](https://github.com/NREL/fastsim) +[![Tests](https://github.com/NREL/fastsim/actions/workflows/tests.yaml/badge.svg)](https://github.com/NREL/fastsim/actions/workflows/tests.yaml) [![wheels](https://github.com/NREL/fastsim/actions/workflows/wheels.yaml/badge.svg)](https://github.com/NREL/fastsim/actions/workflows/wheels.yaml?event=release) ![Python](https://img.shields.io/badge/python-3.9%20%7C%203.10-blue) [![Documentation](https://img.shields.io/badge/documentation-custom-blue.svg)](https://nrel.github.io/fastsim/) [![GitHub](https://img.shields.io/badge/GitHub-fastsim-blue.svg)](https://github.com/NREL/fastsim) # Description This is the python/rust flavor of [NREL's FASTSimTM](https://www.nrel.gov/transportation/fastsim.html), which is based on the original Excel implementation. Effort will be made to keep the core methodology between this software and the Excel flavor in line with one another. diff --git a/rust/README.md b/rust/README.md index e8375932..e85caf4c 100644 --- a/rust/README.md +++ b/rust/README.md @@ -1,5 +1,5 @@ # Crate Architecture -[![Tests](https://github.com/NREL/fastsim/actions/workflows/tests.yaml/badge.svg)](https://github.com/NREL/fastsim/actions/workflows/tests.yaml) [![wheels](https://github.com/NREL/fastsim/actions/workflows/wheels.yaml/badge.svg)](https://github.com/NREL/fastsim/actions/workflows/wheels.yaml?event=release) ![Release](https://img.shields.io/badge/release-v0.1.0-blue) ![Python](https://img.shields.io/badge/python-3.9%20%7C%203.10-blue) [![Documentation](https://img.shields.io/badge/documentation-custom-blue.svg)](https://nrel.github.io/fastsim/) [![GitHub](https://img.shields.io/badge/GitHub-fastsim-blue.svg)](https://github.com/NREL/fastsim) +[![Tests](https://github.com/NREL/fastsim/actions/workflows/tests.yaml/badge.svg)](https://github.com/NREL/fastsim/actions/workflows/tests.yaml) [![wheels](https://github.com/NREL/fastsim/actions/workflows/wheels.yaml/badge.svg)](https://github.com/NREL/fastsim/actions/workflows/wheels.yaml?event=release) ![Python](https://img.shields.io/badge/python-3.9%20%7C%203.10-blue) [![Documentation](https://img.shields.io/badge/documentation-custom-blue.svg)](https://nrel.github.io/fastsim/) [![GitHub](https://img.shields.io/badge/GitHub-fastsim-blue.svg)](https://github.com/NREL/fastsim) FASTSim Rust crates are organized as a cargo [workspace](Cargo.toml) as follows: 1. `fastsim-core`: a pure rust lib crate with optional `pyo3` feature. This crate is intended to be used by other crates and is not in itself an app. From 2d2bc1754f85c9375aa25e52c567c460f5d1898e Mon Sep 17 00:00:00 2001 From: Kyle Carow Date: Thu, 16 Nov 2023 16:12:00 -0700 Subject: [PATCH 24/27] move to/from_file functions to other block --- .../src/add_pyo3_api/mod.rs | 40 +++++++++---------- 1 file changed, 18 insertions(+), 22 deletions(-) diff --git a/rust/fastsim-core/fastsim-proc-macros/src/add_pyo3_api/mod.rs b/rust/fastsim-core/fastsim-proc-macros/src/add_pyo3_api/mod.rs index f56729df..7166f16e 100644 --- a/rust/fastsim-core/fastsim-proc-macros/src/add_pyo3_api/mod.rs +++ b/rust/fastsim-core/fastsim-proc-macros/src/add_pyo3_api/mod.rs @@ -9,7 +9,7 @@ pub fn add_pyo3_api(attr: TokenStream, item: TokenStream) -> TokenStream { let mut ast = syn::parse_macro_input!(item as syn::ItemStruct); // println!("{}", ast.ident.to_string()); let ident = &ast.ident; - let is_state_or_history: bool = + let _is_state_or_history: bool = ident.to_string().contains("State") || ident.to_string().contains("HistoryVec"); let mut impl_block = TokenStream2::default(); @@ -104,27 +104,6 @@ pub fn add_pyo3_api(attr: TokenStream, item: TokenStream) -> TokenStream { ); } } - - if !is_state_or_history { - py_impl_block.extend::(quote! { - #[pyo3(name = "to_file")] - pub fn to_file_py(&self, filepath: &PyAny) -> anyhow::Result<()> { - self.to_file(PathBuf::extract(filepath)?) - } - - #[staticmethod] - #[pyo3(name = "from_file")] - pub fn from_file_py(filepath: &PyAny) -> anyhow::Result { - Self::from_file(PathBuf::extract(filepath)?) - } - - #[staticmethod] - #[pyo3(name = "from_resource")] - pub fn from_resource_py(filepath: &PyAny) -> anyhow::Result { - Self::from_resource(PathBuf::extract(filepath)?) - } - }); - } } else if let syn::Fields::Unnamed(syn::FieldsUnnamed { unnamed, .. }) = &mut ast.fields { // tuple struct if ast.ident.to_string().contains("Vec") || ast.ident.to_string().contains("Array") { @@ -227,6 +206,23 @@ pub fn add_pyo3_api(attr: TokenStream, item: TokenStream) -> TokenStream { pub fn copy(&self) -> Self {self.clone()} pub fn __copy__(&self) -> Self {self.clone()} pub fn __deepcopy__(&self, _memo: &PyDict) -> Self {self.clone()} + + #[pyo3(name = "to_file")] + pub fn to_file_py(&self, filepath: &PyAny) -> anyhow::Result<()> { + self.to_file(PathBuf::extract(filepath)?) + } + + #[staticmethod] + #[pyo3(name = "from_file")] + pub fn from_file_py(filepath: &PyAny) -> anyhow::Result { + Self::from_file(PathBuf::extract(filepath)?) + } + + #[staticmethod] + #[pyo3(name = "from_resource")] + pub fn from_resource_py(filepath: &PyAny) -> anyhow::Result { + Self::from_resource(PathBuf::extract(filepath)?) + } /// JSON serialization method. #[pyo3(name = "to_json")] From b8bdfa2e545cb3932a69ef2cb026f169e47708d1 Mon Sep 17 00:00:00 2001 From: Kyle Carow Date: Thu, 16 Nov 2023 17:09:44 -0700 Subject: [PATCH 25/27] cargo fmt, to_str and from_str exposed to Python, better error messages for serde formats, better format matching --- .../src/add_pyo3_api/mod.rs | 17 +++- rust/fastsim-core/src/cycle.rs | 85 +++++++++++-------- rust/fastsim-core/src/traits.rs | 70 ++++++++------- 3 files changed, 101 insertions(+), 71 deletions(-) diff --git a/rust/fastsim-core/fastsim-proc-macros/src/add_pyo3_api/mod.rs b/rust/fastsim-core/fastsim-proc-macros/src/add_pyo3_api/mod.rs index 7166f16e..a37d6636 100644 --- a/rust/fastsim-core/fastsim-proc-macros/src/add_pyo3_api/mod.rs +++ b/rust/fastsim-core/fastsim-proc-macros/src/add_pyo3_api/mod.rs @@ -206,6 +206,12 @@ pub fn add_pyo3_api(attr: TokenStream, item: TokenStream) -> TokenStream { pub fn copy(&self) -> Self {self.clone()} pub fn __copy__(&self) -> Self {self.clone()} pub fn __deepcopy__(&self, _memo: &PyDict) -> Self {self.clone()} + + #[staticmethod] + #[pyo3(name = "from_resource")] + pub fn from_resource_py(filepath: &PyAny) -> anyhow::Result { + Self::from_resource(PathBuf::extract(filepath)?) + } #[pyo3(name = "to_file")] pub fn to_file_py(&self, filepath: &PyAny) -> anyhow::Result<()> { @@ -218,10 +224,15 @@ pub fn add_pyo3_api(attr: TokenStream, item: TokenStream) -> TokenStream { Self::from_file(PathBuf::extract(filepath)?) } + #[pyo3(name = "to_str")] + pub fn to_str_py(&self, format: &str) -> anyhow::Result { + self.to_str(format) + } + #[staticmethod] - #[pyo3(name = "from_resource")] - pub fn from_resource_py(filepath: &PyAny) -> anyhow::Result { - Self::from_resource(PathBuf::extract(filepath)?) + #[pyo3(name = "from_str")] + pub fn from_str_py(contents: &str, format: &str) -> anyhow::Result { + Self::from_str(contents, format) } /// JSON serialization method. diff --git a/rust/fastsim-core/src/cycle.rs b/rust/fastsim-core/src/cycle.rs index 2e2373e7..0959d281 100644 --- a/rust/fastsim-core/src/cycle.rs +++ b/rust/fastsim-core/src/cycle.rs @@ -611,6 +611,8 @@ pub struct RustCycle { pub orphaned: bool, } +const ACCEPTED_FILE_FORMATS: [&str; 3] = ["yaml", "json", "csv"]; + impl SerdeAPI for RustCycle { /// Load cycle from file, not parsing cycle name from filepath fn from_file>(filepath: P) -> anyhow::Result { @@ -619,48 +621,61 @@ impl SerdeAPI for RustCycle { let extension = filepath .extension() .and_then(OsStr::to_str) - .with_context(|| { - format!( - "File extension could not be parsed: \"{}\"", - filepath.display() - ) - })?; + .with_context(|| format!("File extension could not be parsed: {filepath:?}"))?; let file = File::open(filepath).with_context(|| { if !filepath.exists() { - format!("File not found: \"{}\"", filepath.display()) + format!("File not found: {filepath:?}") } else { - format!("Could not open file: \"{}\"", filepath.display()) + format!("Could not open file: {filepath:?}") } })?; Self::from_reader(file, extension) } fn from_reader(rdr: R, format: &str) -> anyhow::Result { - Ok(match format { - "yaml" => serde_yaml::from_reader(rdr)?, - "json" => serde_json::from_reader(rdr)?, - "csv" => { - // Create empty cycle to be populated - let mut cyc = Self::default(); - let mut rdr = csv::ReaderBuilder::new().has_headers(true).from_reader(rdr); - for result in rdr.deserialize() { - let cyc_elem: crate::cycle::RustCycleElement = result?; - cyc.push(cyc_elem); + Ok( + match format.trim_start_matches('.').to_lowercase().as_str() { + "yaml" | "yml" => serde_yaml::from_reader(rdr)?, + "json" => serde_json::from_reader(rdr)?, + "csv" => { + // Create empty cycle to be populated + let mut cyc = Self::default(); + let mut rdr = csv::ReaderBuilder::new().has_headers(true).from_reader(rdr); + for result in rdr.deserialize() { + let cyc_elem: crate::cycle::RustCycleElement = result?; + cyc.push(cyc_elem); + } + cyc } - cyc - } - _ => bail!("Unsupported file format: {format:?}"), - }) + _ => bail!( + "Unsupported file format {format:?}, must be one of {ACCEPTED_FILE_FORMATS:?}" + ), + }, + ) } + // TODO: add method for creating CSV string here + // fn to_str(&self, format: &str) -> anyhow::Result { + // // match format.trim_start_matches('.').to_lowercase().as_str() { + // // "yaml" | "yml" => Self::from_yaml(contents), + // // "json" => Self::from_json(contents), + // // "csv" => Self::from_csv_str(contents, ""), + // // _ => bail!( + // // "Unsupported file format {format:?}, must be one of {ACCEPTED_FILE_FORMATS:?}" + // // ), + // // } + // } + // Note that using this method to instantiate a RustCycle from CSV instead of // the `from_csv_str` method directly sets the cycle name to an empty string. fn from_str(contents: &str, format: &str) -> anyhow::Result { - match format { - "yaml" => Self::from_yaml(contents), + match format.trim_start_matches('.').to_lowercase().as_str() { + "yaml" | "yml" => Self::from_yaml(contents), "json" => Self::from_json(contents), "csv" => Self::from_csv_str(contents, ""), - _ => bail!("Unsupported file format: {format:?}"), + _ => bail!( + "Unsupported file format {format:?}, must be one of {ACCEPTED_FILE_FORMATS:?}" + ), } } } @@ -691,19 +706,19 @@ impl RustCycle { /// Load cycle from CSV file, parsing name from filepath pub fn from_csv_file>(filepath: P) -> anyhow::Result { let filepath = filepath.as_ref(); - let name = String::from(filepath.file_stem().and_then(OsStr::to_str).with_context( - || { - format!( - "Could not parse cycle name from filepath: \"{}\"", - filepath.display() - ) - }, - )?); + let name = String::from( + filepath + .file_stem() + .and_then(OsStr::to_str) + .with_context(|| { + format!("Could not parse cycle name from filepath: {filepath:?}") + })?, + ); let file = File::open(filepath).with_context(|| { if !filepath.exists() { - format!("File not found: \"{}\"", filepath.display()) + format!("File not found: {filepath:?}") } else { - format!("Could not open file: \"{}\"", filepath.display()) + format!("Could not open file: {filepath:?}") } })?; let mut cyc = Self::from_reader(file, "csv")?; diff --git a/rust/fastsim-core/src/traits.rs b/rust/fastsim-core/src/traits.rs index 57bffc14..437c0efd 100644 --- a/rust/fastsim-core/src/traits.rs +++ b/rust/fastsim-core/src/traits.rs @@ -1,6 +1,8 @@ use crate::imports::*; use std::collections::HashMap; +pub(crate) const ACCEPTED_FILE_FORMATS: [&str; 2] = ["yaml", "json"]; + pub trait SerdeAPI: Serialize + for<'a> Deserialize<'a> { /// runs any initialization steps that might be needed fn init(&mut self) -> anyhow::Result<()> { @@ -24,17 +26,13 @@ pub trait SerdeAPI: Serialize + for<'a> Deserialize<'a> { let extension = filepath .extension() .and_then(OsStr::to_str) - .with_context(|| { - format!( - "File extension could not be parsed: \"{}\"", - filepath.display() - ) - })?; - match extension { + .with_context(|| format!("File extension could not be parsed: {filepath:?}"))?; + match extension.trim_start_matches('.').to_lowercase().as_str() { + "yaml" | "yml" => serde_yaml::to_writer(&File::create(filepath)?, self)?, "json" => serde_json::to_writer(&File::create(filepath)?, self)?, - "yaml" => serde_yaml::to_writer(&File::create(filepath)?, self)?, - // TODO: do we want a default behavior for this? - _ => serde_json::to_writer(&File::create(filepath)?, self)?, + _ => bail!( + "Unsupported file format {extension:?}, must be one of {ACCEPTED_FILE_FORMATS:?}" + ), } Ok(()) } @@ -57,17 +55,12 @@ pub trait SerdeAPI: Serialize + for<'a> Deserialize<'a> { let extension = filepath .extension() .and_then(OsStr::to_str) - .with_context(|| { - format!( - "File extension could not be parsed: \"{}\"", - filepath.display() - ) - })?; + .with_context(|| format!("File extension could not be parsed: {filepath:?}"))?; let file = File::open(filepath).with_context(|| { if !filepath.exists() { - format!("File not found: \"{}\"", filepath.display()) + format!("File not found: {filepath:?}") } else { - format!("Could not open file: \"{}\"", filepath.display()) + format!("Could not open file: {filepath:?}") } })?; // deserialized file @@ -81,34 +74,45 @@ pub trait SerdeAPI: Serialize + for<'a> Deserialize<'a> { let contents = crate::resources::RESOURCES_DIR .get_file(filepath) .and_then(include_dir::File::contents_utf8) - .with_context(|| format!("File not found in resources: \"{}\"", filepath.display()))?; + .with_context(|| format!("File not found in resources: {filepath:?}"))?; let extension = filepath .extension() .and_then(OsStr::to_str) - .with_context(|| { - format!( - "File extension could not be parsed: \"{}\"", - filepath.display() - ) - })?; + .with_context(|| format!("File extension could not be parsed: {filepath:?}"))?; let mut deserialized = Self::from_str(contents, extension)?; deserialized.init()?; Ok(deserialized) } fn from_reader(rdr: R, format: &str) -> anyhow::Result { - Ok(match format { - "yaml" => serde_yaml::from_reader(rdr)?, - "json" => serde_json::from_reader(rdr)?, - _ => bail!("Unsupported file format: {format:?}"), - }) + Ok( + match format.trim_start_matches('.').to_lowercase().as_str() { + "yaml" | "yml" => serde_yaml::from_reader(rdr)?, + "json" => serde_json::from_reader(rdr)?, + _ => bail!( + "Unsupported file format {format:?}, must be one of {ACCEPTED_FILE_FORMATS:?}" + ), + }, + ) + } + + fn to_str(&self, format: &str) -> anyhow::Result { + match format.trim_start_matches('.').to_lowercase().as_str() { + "yaml" | "yml" => self.to_yaml(), + "json" => self.to_json(), + _ => bail!( + "Unsupported file format {format:?}, must be one of {ACCEPTED_FILE_FORMATS:?}" + ), + } } fn from_str(contents: &str, format: &str) -> anyhow::Result { - match format { - "yaml" => Self::from_yaml(contents), + match format.trim_start_matches('.').to_lowercase().as_str() { + "yaml" | "yml" => Self::from_yaml(contents), "json" => Self::from_json(contents), - _ => bail!("Unsupported file format: {format:?}"), + _ => bail!( + "Unsupported file format {format:?}, must be one of {ACCEPTED_FILE_FORMATS:?}" + ), } } From e39c5f05b62fb4425b109fbfcd4caf5d3ea010a8 Mon Sep 17 00:00:00 2001 From: Kyle Carow Date: Thu, 16 Nov 2023 20:31:33 -0700 Subject: [PATCH 26/27] new to_file method for CSVs! also to_str works for CSVs now --- rust/fastsim-core/src/cycle.rs | 83 +++++++++++++++++++++------------ rust/fastsim-core/src/traits.rs | 13 +----- 2 files changed, 56 insertions(+), 40 deletions(-) diff --git a/rust/fastsim-core/src/cycle.rs b/rust/fastsim-core/src/cycle.rs index 0959d281..729ed191 100644 --- a/rust/fastsim-core/src/cycle.rs +++ b/rust/fastsim-core/src/cycle.rs @@ -4,7 +4,6 @@ extern crate ndarray; #[cfg(feature = "pyo3")] use std::collections::HashMap; -use std::fs::File; // local use crate::imports::*; @@ -614,22 +613,21 @@ pub struct RustCycle { const ACCEPTED_FILE_FORMATS: [&str; 3] = ["yaml", "json", "csv"]; impl SerdeAPI for RustCycle { - /// Load cycle from file, not parsing cycle name from filepath - fn from_file>(filepath: P) -> anyhow::Result { - // check if the extension is csv, and if it is, then call Self::from_csv_file + fn to_file>(&self, filepath: P) -> anyhow::Result<()> { let filepath = filepath.as_ref(); let extension = filepath .extension() .and_then(OsStr::to_str) .with_context(|| format!("File extension could not be parsed: {filepath:?}"))?; - let file = File::open(filepath).with_context(|| { - if !filepath.exists() { - format!("File not found: {filepath:?}") - } else { - format!("Could not open file: {filepath:?}") - } - })?; - Self::from_reader(file, extension) + match extension.trim_start_matches('.').to_lowercase().as_str() { + "yaml" | "yml" => serde_yaml::to_writer(&File::create(filepath)?, self)?, + "json" => serde_json::to_writer(&File::create(filepath)?, self)?, + "csv" => self.write_csv(&mut csv::Writer::from_path(filepath)?)?, + _ => bail!( + "Unsupported file format {extension:?}, must be one of {ACCEPTED_FILE_FORMATS:?}" + ), + } + Ok(()) } fn from_reader(rdr: R, format: &str) -> anyhow::Result { @@ -640,10 +638,9 @@ impl SerdeAPI for RustCycle { "csv" => { // Create empty cycle to be populated let mut cyc = Self::default(); - let mut rdr = csv::ReaderBuilder::new().has_headers(true).from_reader(rdr); + let mut rdr = csv::Reader::from_reader(rdr); for result in rdr.deserialize() { - let cyc_elem: crate::cycle::RustCycleElement = result?; - cyc.push(cyc_elem); + cyc.push(result?); } cyc } @@ -654,20 +651,25 @@ impl SerdeAPI for RustCycle { ) } - // TODO: add method for creating CSV string here - // fn to_str(&self, format: &str) -> anyhow::Result { - // // match format.trim_start_matches('.').to_lowercase().as_str() { - // // "yaml" | "yml" => Self::from_yaml(contents), - // // "json" => Self::from_json(contents), - // // "csv" => Self::from_csv_str(contents, ""), - // // _ => bail!( - // // "Unsupported file format {format:?}, must be one of {ACCEPTED_FILE_FORMATS:?}" - // // ), - // // } - // } - - // Note that using this method to instantiate a RustCycle from CSV instead of - // the `from_csv_str` method directly sets the cycle name to an empty string. + fn to_str(&self, format: &str) -> anyhow::Result { + Ok( + match format.trim_start_matches('.').to_lowercase().as_str() { + "yaml" | "yml" => self.to_yaml()?, + "json" => self.to_json()?, + "csv" => { + let mut wtr = csv::Writer::from_writer(Vec::with_capacity(self.len())); + self.write_csv(&mut wtr)?; + String::from_utf8(wtr.into_inner()?)? + } + _ => bail!( + "Unsupported file format {format:?}, must be one of {ACCEPTED_FILE_FORMATS:?}" + ), + }, + ) + } + + /// Note that using this method to instantiate a RustCycle from CSV, rather + /// than the `from_csv_str` method, sets the cycle name to an empty string fn from_str(contents: &str, format: &str) -> anyhow::Result { match format.trim_start_matches('.').to_lowercase().as_str() { "yaml" | "yml" => Self::from_yaml(contents), @@ -733,6 +735,20 @@ impl RustCycle { Ok(cyc) } + /// Write cycle data to a CSV writer + fn write_csv(&self, wtr: &mut csv::Writer) -> anyhow::Result<()> { + for i in 0..self.len() { + wtr.serialize(RustCycleElement { + time_s: self.time_s[i], + mps: self.mps[i], + grade: Some(self.grade[i]), + road_type: Some(self.road_type[i]), + })?; + } + wtr.flush()?; + Ok(()) + } + pub fn build_cache(&self) -> RustCycleCache { RustCycleCache::new(self) } @@ -1139,4 +1155,13 @@ mod tests { assert_eq!(num_entries, cyc.road_type.len()); assert_eq!(num_entries, expected_udds_length); } + + #[test] + fn test_str_serde() { + let format = "csv"; + let cyc = RustCycle::test_cyc(); + println!("{cyc:?}"); + let csv_str = cyc.to_str(format).unwrap(); + RustCycle::from_str(&csv_str, format).unwrap(); + } } diff --git a/rust/fastsim-core/src/traits.rs b/rust/fastsim-core/src/traits.rs index 437c0efd..211d7827 100644 --- a/rust/fastsim-core/src/traits.rs +++ b/rust/fastsim-core/src/traits.rs @@ -4,23 +4,14 @@ use std::collections::HashMap; pub(crate) const ACCEPTED_FILE_FORMATS: [&str; 2] = ["yaml", "json"]; pub trait SerdeAPI: Serialize + for<'a> Deserialize<'a> { - /// runs any initialization steps that might be needed + /// Runs any initialization steps that might be needed fn init(&mut self) -> anyhow::Result<()> { Ok(()) } #[allow(clippy::wrong_self_convention)] /// Save current data structure to file. Method adaptively calls serialization methods - /// dependent on the suffix of the file given as str. - /// - /// # Argument: - /// - /// * `filepath`: a `str` storing the targeted file name. Currently `.json` and `.yaml` suffixes are - /// supported - /// - /// # Returns: - /// - /// A Rust Result + /// dependent on the suffix of the filepath. fn to_file>(&self, filepath: P) -> anyhow::Result<()> { let filepath = filepath.as_ref(); let extension = filepath From 4a73581ab6b440baae31f6577023e12c425c92c6 Mon Sep 17 00:00:00 2001 From: Kyle Carow Date: Thu, 16 Nov 2023 22:04:53 -0700 Subject: [PATCH 27/27] bin file serde working, but not for vehicles for some reason --- .../src/add_pyo3_api/mod.rs | 2 +- rust/fastsim-core/src/cycle.rs | 2 ++ rust/fastsim-core/src/traits.rs | 18 +++++++++++++----- 3 files changed, 16 insertions(+), 6 deletions(-) diff --git a/rust/fastsim-core/fastsim-proc-macros/src/add_pyo3_api/mod.rs b/rust/fastsim-core/fastsim-proc-macros/src/add_pyo3_api/mod.rs index a37d6636..35850811 100644 --- a/rust/fastsim-core/fastsim-proc-macros/src/add_pyo3_api/mod.rs +++ b/rust/fastsim-core/fastsim-proc-macros/src/add_pyo3_api/mod.rs @@ -212,7 +212,7 @@ pub fn add_pyo3_api(attr: TokenStream, item: TokenStream) -> TokenStream { pub fn from_resource_py(filepath: &PyAny) -> anyhow::Result { Self::from_resource(PathBuf::extract(filepath)?) } - + #[pyo3(name = "to_file")] pub fn to_file_py(&self, filepath: &PyAny) -> anyhow::Result<()> { self.to_file(PathBuf::extract(filepath)?) diff --git a/rust/fastsim-core/src/cycle.rs b/rust/fastsim-core/src/cycle.rs index 729ed191..61c5d4e1 100644 --- a/rust/fastsim-core/src/cycle.rs +++ b/rust/fastsim-core/src/cycle.rs @@ -622,6 +622,7 @@ impl SerdeAPI for RustCycle { match extension.trim_start_matches('.').to_lowercase().as_str() { "yaml" | "yml" => serde_yaml::to_writer(&File::create(filepath)?, self)?, "json" => serde_json::to_writer(&File::create(filepath)?, self)?, + "bin" => bincode::serialize_into(&File::create(filepath)?, self)?, "csv" => self.write_csv(&mut csv::Writer::from_path(filepath)?)?, _ => bail!( "Unsupported file format {extension:?}, must be one of {ACCEPTED_FILE_FORMATS:?}" @@ -635,6 +636,7 @@ impl SerdeAPI for RustCycle { match format.trim_start_matches('.').to_lowercase().as_str() { "yaml" | "yml" => serde_yaml::from_reader(rdr)?, "json" => serde_json::from_reader(rdr)?, + "bin" => bincode::deserialize_from(rdr)?, "csv" => { // Create empty cycle to be populated let mut cyc = Self::default(); diff --git a/rust/fastsim-core/src/traits.rs b/rust/fastsim-core/src/traits.rs index 211d7827..4d69395e 100644 --- a/rust/fastsim-core/src/traits.rs +++ b/rust/fastsim-core/src/traits.rs @@ -21,6 +21,7 @@ pub trait SerdeAPI: Serialize + for<'a> Deserialize<'a> { match extension.trim_start_matches('.').to_lowercase().as_str() { "yaml" | "yml" => serde_yaml::to_writer(&File::create(filepath)?, self)?, "json" => serde_json::to_writer(&File::create(filepath)?, self)?, + "bin" => bincode::serialize_into(&File::create(filepath)?, self)?, _ => bail!( "Unsupported file format {extension:?}, must be one of {ACCEPTED_FILE_FORMATS:?}" ), @@ -62,15 +63,21 @@ pub trait SerdeAPI: Serialize + for<'a> Deserialize<'a> { fn from_resource>(filepath: P) -> anyhow::Result { let filepath = filepath.as_ref(); - let contents = crate::resources::RESOURCES_DIR - .get_file(filepath) - .and_then(include_dir::File::contents_utf8) - .with_context(|| format!("File not found in resources: {filepath:?}"))?; let extension = filepath .extension() .and_then(OsStr::to_str) .with_context(|| format!("File extension could not be parsed: {filepath:?}"))?; - let mut deserialized = Self::from_str(contents, extension)?; + let file = crate::resources::RESOURCES_DIR + .get_file(filepath) + .with_context(|| format!("File not found in resources: {filepath:?}"))?; + let mut deserialized = match extension.trim_start_matches('.').to_lowercase().as_str() { + "bin" => Self::from_bincode(include_dir::File::contents(file))?, + _ => Self::from_str( + include_dir::File::contents_utf8(file) + .with_context(|| format!("File could not be parsed to UTF-8: {filepath:?}"))?, + extension, + )?, + }; deserialized.init()?; Ok(deserialized) } @@ -80,6 +87,7 @@ pub trait SerdeAPI: Serialize + for<'a> Deserialize<'a> { match format.trim_start_matches('.').to_lowercase().as_str() { "yaml" | "yml" => serde_yaml::from_reader(rdr)?, "json" => serde_json::from_reader(rdr)?, + "bin" => bincode::deserialize_from(rdr)?, _ => bail!( "Unsupported file format {format:?}, must be one of {ACCEPTED_FILE_FORMATS:?}" ),