From 43a6dfa85c29b3f8ed96c94c401e0a16a156985e Mon Sep 17 00:00:00 2001 From: Michael O'Keefe Date: Tue, 6 Aug 2024 17:45:00 -0600 Subject: [PATCH 1/4] Add list_resources for RustVehicle --- rust/fastsim-core/src/resources.rs | 31 ++++++++++++++++++++++++++++++ rust/fastsim-core/src/traits.rs | 20 +++++++++++++++---- rust/fastsim-core/src/vehicle.rs | 12 +++++++++--- 3 files changed, 56 insertions(+), 7 deletions(-) diff --git a/rust/fastsim-core/src/resources.rs b/rust/fastsim-core/src/resources.rs index 00fd59a6..5308be21 100644 --- a/rust/fastsim-core/src/resources.rs +++ b/rust/fastsim-core/src/resources.rs @@ -2,3 +2,34 @@ use include_dir::{include_dir, Dir}; pub const RESOURCES_DIR: Dir = include_dir!("$CARGO_MANIFEST_DIR/resources"); + +/// List the available resources in the resources directory +/// - subdir: &str, a subdirectory to choose from the resources directory +/// if it cannot be resolved, then the top level is used to list resources +/// NOTE: if you want the top level, a good way to get that is to pass "". +/// RETURNS: a vector of strings for resources that can be loaded +pub fn list_resources(subdir: &str) -> Vec { + let resources_path = if let Some(rp) = RESOURCES_DIR.get_dir(subdir) { + rp + } else { + &RESOURCES_DIR + }; + let mut file_names: Vec = resources_path + .files() + .filter_map(|entry| entry.path().file_name()?.to_str().map(String::from)) + .collect(); + file_names.sort(); + file_names +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_list_resources() { + let result = list_resources("cycles"); + assert!(result.len() == 3); + assert!(result[0] == "HHDDTCruiseSmooth.csv"); + } +} diff --git a/rust/fastsim-core/src/traits.rs b/rust/fastsim-core/src/traits.rs index 8362f7c6..de4174f0 100644 --- a/rust/fastsim-core/src/traits.rs +++ b/rust/fastsim-core/src/traits.rs @@ -1,4 +1,4 @@ -use crate::imports::*; +use crate::{imports::*, resources}; use std::collections::HashMap; use ureq; @@ -13,6 +13,14 @@ pub trait SerdeAPI: Serialize + for<'a> Deserialize<'a> { Ok(()) } + /// List available (compiled) resources (stored in the rust binary) + /// RESULT: + /// vector of string of resource names that can be loaded + #[cfg(feature = "resources")] + fn list_resources() -> Vec { + resources::list_resources(Self::RESOURCE_PREFIX) + } + /// Read (deserialize) an object from a resource file packaged with the `fastsim-core` crate /// /// # Arguments: @@ -55,7 +63,7 @@ pub trait SerdeAPI: Serialize + for<'a> Deserialize<'a> { "toml" => { let toml_string = self.to_toml()?; wtr.write_all(toml_string.as_bytes())?; - }, + } #[cfg(feature = "bincode")] "bin" => bincode::serialize_into(wtr, self)?, _ => bail!( @@ -135,7 +143,11 @@ pub trait SerdeAPI: Serialize + for<'a> Deserialize<'a> { /// * `rdr` - The reader from which to read object data /// * `format` - The source format, any of those listed in [`ACCEPTED_BYTE_FORMATS`](`SerdeAPI::ACCEPTED_BYTE_FORMATS`) /// - fn from_reader(mut rdr: R, format: &str, skip_init: bool) -> anyhow::Result { + fn from_reader( + mut rdr: R, + format: &str, + skip_init: bool, + ) -> anyhow::Result { let mut deserialized: Self = match format.trim_start_matches('.').to_lowercase().as_str() { "yaml" | "yml" => serde_yaml::from_reader(rdr)?, "json" => serde_json::from_reader(rdr)?, @@ -143,7 +155,7 @@ pub trait SerdeAPI: Serialize + for<'a> Deserialize<'a> { let mut buf = String::new(); rdr.read_to_string(&mut buf)?; Self::from_toml(buf, skip_init)? - }, + } #[cfg(feature = "bincode")] "bin" => bincode::deserialize_from(rdr)?, _ => bail!( diff --git a/rust/fastsim-core/src/vehicle.rs b/rust/fastsim-core/src/vehicle.rs index edecda66..486a87ff 100644 --- a/rust/fastsim-core/src/vehicle.rs +++ b/rust/fastsim-core/src/vehicle.rs @@ -83,6 +83,11 @@ lazy_static! { self.clone() } + #[pyo3(name = "list_resources")] + pub fn list_resources_py(&self) -> Vec { + RustVehicle::list_resources() + } + #[staticmethod] #[pyo3(name = "mock_vehicle")] fn mock_vehicle_py() -> Self { @@ -771,7 +776,8 @@ impl RustVehicle { self.modern_max = MODERN_MAX; } let modern_diff = self.modern_max - arrmax(&LARGE_BASELINE_EFF); - let large_baseline_eff_adj: Vec = LARGE_BASELINE_EFF.iter().map(|x| x + modern_diff).collect(); + let large_baseline_eff_adj: Vec = + LARGE_BASELINE_EFF.iter().map(|x| x + modern_diff).collect(); let mc_kw_adj_perc = max( 0.0, min( @@ -1068,8 +1074,8 @@ impl RustVehicle { } None => Self::VEHICLE_DIRECTORY_URL.to_string() + vehicle_file_name.as_ref(), }; - let mut vehicle = - Self::from_url(&url_internal, false).with_context(|| "Could not parse vehicle from url")?; + let mut vehicle = Self::from_url(&url_internal, false) + .with_context(|| "Could not parse vehicle from url")?; let vehicle_origin = "Vehicle from ".to_owned() + url_internal.as_str(); vehicle.doc = Some(vehicle_origin); Ok(vehicle) From ef9d245e41627dfa6f28019350a998240bd6223d Mon Sep 17 00:00:00 2001 From: Michael O'Keefe Date: Wed, 7 Aug 2024 15:14:46 -0600 Subject: [PATCH 2/4] Add list_resources python method on RustCycle Also add unit test to confirm it works. NOTE: there are (apparently) no vehicle data files for FASTSim-2. Therefoe, the list_resources call on RustVehicle was removed. --- python/fastsim/tests/test_resources.py | 15 +++++++++++++++ rust/fastsim-core/src/cycle.rs | 22 ++++++++++++++++++---- rust/fastsim-core/src/vehicle.rs | 5 ----- 3 files changed, 33 insertions(+), 9 deletions(-) create mode 100644 python/fastsim/tests/test_resources.py diff --git a/python/fastsim/tests/test_resources.py b/python/fastsim/tests/test_resources.py new file mode 100644 index 00000000..a019bc2d --- /dev/null +++ b/python/fastsim/tests/test_resources.py @@ -0,0 +1,15 @@ +"""Test getting resource lists via Rust API""" +import unittest + +import fastsim as fsim +from fastsim import cycle, vehicle + +class TestListResources(unittest.TestCase): + def test_list_resources_for_cycle(self): + "check if list_resources works and yields results for Cycle" + c = cycle.Cycle.from_dict({ + "cycSecs": [0.0, 1.0], + "cycMps": [0.0, 0.0]}) + rc = c.to_rust() + resources = rc.list_resources() + self.assertTrue(len(resources) > 0) diff --git a/rust/fastsim-core/src/cycle.rs b/rust/fastsim-core/src/cycle.rs index 0d8bf0bd..8204154c 100644 --- a/rust/fastsim-core/src/cycle.rs +++ b/rust/fastsim-core/src/cycle.rs @@ -607,6 +607,12 @@ impl RustCycleCache { pub fn get_delta_elev_m(&self) -> Vec { self.delta_elev_m().to_vec() } + + #[pyo3(name = "list_resources")] + /// list available cycle resources + pub fn list_resources_py(&self) -> Vec { + RustCycle::list_resources() + } )] /// Struct for containing: /// * time_s, cycle time, $s$ @@ -652,7 +658,7 @@ impl SerdeAPI for RustCycle { "toml" => { let toml_string = self.to_toml()?; wtr.write_all(toml_string.as_bytes())?; - }, + } #[cfg(feature = "bincode")] "bin" => bincode::serialize_into(wtr, self)?, "csv" => { @@ -709,7 +715,11 @@ impl SerdeAPI for RustCycle { ) } - fn from_reader(mut rdr: R, format: &str, skip_init: bool) -> anyhow::Result { + fn from_reader( + mut rdr: R, + format: &str, + skip_init: bool, + ) -> anyhow::Result { let mut deserialized = match format.trim_start_matches('.').to_lowercase().as_str() { "yaml" | "yml" => serde_yaml::from_reader(rdr)?, "json" => serde_json::from_reader(rdr)?, @@ -717,7 +727,7 @@ impl SerdeAPI for RustCycle { let mut buf = String::new(); rdr.read_to_string(&mut buf)?; Self::from_toml(buf, skip_init)? - }, + } #[cfg(feature = "bincode")] "bin" => bincode::deserialize_from(rdr)?, "csv" => { @@ -823,7 +833,11 @@ impl RustCycle { } /// Load cycle from CSV string - pub fn from_csv_str>(csv_str: S, name: String, skip_init: bool) -> anyhow::Result { + pub fn from_csv_str>( + csv_str: S, + name: String, + skip_init: bool, + ) -> anyhow::Result { let mut cyc = Self::from_str(csv_str, "csv", skip_init)?; cyc.name = name; Ok(cyc) diff --git a/rust/fastsim-core/src/vehicle.rs b/rust/fastsim-core/src/vehicle.rs index 486a87ff..09defdc5 100644 --- a/rust/fastsim-core/src/vehicle.rs +++ b/rust/fastsim-core/src/vehicle.rs @@ -83,11 +83,6 @@ lazy_static! { self.clone() } - #[pyo3(name = "list_resources")] - pub fn list_resources_py(&self) -> Vec { - RustVehicle::list_resources() - } - #[staticmethod] #[pyo3(name = "mock_vehicle")] fn mock_vehicle_py() -> Self { From 7d5b7d8bc509b54bb824db75c6a3099014573278 Mon Sep 17 00:00:00 2001 From: Michael O'Keefe Date: Wed, 7 Aug 2024 17:49:04 -0600 Subject: [PATCH 3/4] Enable listing resources for cycle and vehicle Also add python tests to confirm results --- python/fastsim/tests/test_resources.py | 14 +++++++++++- rust/fastsim-core/src/resources.rs | 30 +++++++++++++++----------- rust/fastsim-core/src/vehicle.rs | 6 ++++++ 3 files changed, 37 insertions(+), 13 deletions(-) diff --git a/python/fastsim/tests/test_resources.py b/python/fastsim/tests/test_resources.py index a019bc2d..44e888c9 100644 --- a/python/fastsim/tests/test_resources.py +++ b/python/fastsim/tests/test_resources.py @@ -6,10 +6,22 @@ class TestListResources(unittest.TestCase): def test_list_resources_for_cycle(self): - "check if list_resources works and yields results for Cycle" + "check if list_resources works for RustCycle" c = cycle.Cycle.from_dict({ "cycSecs": [0.0, 1.0], "cycMps": [0.0, 0.0]}) rc = c.to_rust() resources = rc.list_resources() self.assertTrue(len(resources) > 0) + + def test_list_resources_for_vehicles(self): + "check if list_resources works for RustVehicle" + # NOTE: at the time of writing this test, + # there are no vehicle assets in resources. + # Therefore, we expect to get an empty vector. + # If resources are committed, this test should + # fail and we should use the following assert: + # self.assertTrue(len(resources) > 0) + rv = vehicle.Vehicle.from_vehdb(1).to_rust() + resources = rv.list_resources() + self.assertTrue(len(resources) == 0) diff --git a/rust/fastsim-core/src/resources.rs b/rust/fastsim-core/src/resources.rs index 5308be21..da2282cc 100644 --- a/rust/fastsim-core/src/resources.rs +++ b/rust/fastsim-core/src/resources.rs @@ -5,21 +5,21 @@ pub const RESOURCES_DIR: Dir = include_dir!("$CARGO_MANIFEST_DIR/resources"); /// List the available resources in the resources directory /// - subdir: &str, a subdirectory to choose from the resources directory -/// if it cannot be resolved, then the top level is used to list resources -/// NOTE: if you want the top level, a good way to get that is to pass "". +/// NOTE: if subdir cannot be resolved, returns an empty list /// RETURNS: a vector of strings for resources that can be loaded pub fn list_resources(subdir: &str) -> Vec { - let resources_path = if let Some(rp) = RESOURCES_DIR.get_dir(subdir) { - rp + if subdir.is_empty() { + Vec::::new() + } else if let Some(resources_path) = RESOURCES_DIR.get_dir(subdir) { + let mut file_names: Vec = resources_path + .files() + .filter_map(|entry| entry.path().file_name()?.to_str().map(String::from)) + .collect(); + file_names.sort(); + file_names } else { - &RESOURCES_DIR - }; - let mut file_names: Vec = resources_path - .files() - .filter_map(|entry| entry.path().file_name()?.to_str().map(String::from)) - .collect(); - file_names.sort(); - file_names + Vec::::new() + } } #[cfg(test)] @@ -31,5 +31,11 @@ mod tests { let result = list_resources("cycles"); assert!(result.len() == 3); assert!(result[0] == "HHDDTCruiseSmooth.csv"); + // NOTE: at the time of writing this test, there is no + // vehicles subdirectory. The agreed-upon behavior in + // that case is that list_resources should return an + // empty vector of string. + let another_result = list_resources("vehicles"); + assert!(another_result.len() == 0); } } diff --git a/rust/fastsim-core/src/vehicle.rs b/rust/fastsim-core/src/vehicle.rs index 09defdc5..4eabfe27 100644 --- a/rust/fastsim-core/src/vehicle.rs +++ b/rust/fastsim-core/src/vehicle.rs @@ -83,6 +83,12 @@ lazy_static! { self.clone() } + #[pyo3(name = "list_resources")] + /// list available vehicle resources + pub fn list_resources_py(&self) -> Vec { + RustVehicle::list_resources() + } + #[staticmethod] #[pyo3(name = "mock_vehicle")] fn mock_vehicle_py() -> Self { From b2c333084675406ade8cdc1a3b78d4ae606f14d7 Mon Sep 17 00:00:00 2001 From: Chad Baker Date: Tue, 27 Aug 2024 15:00:40 -0600 Subject: [PATCH 4/4] removed unused feature --- rust/fastsim-core/src/lib.rs | 3 --- 1 file changed, 3 deletions(-) diff --git a/rust/fastsim-core/src/lib.rs b/rust/fastsim-core/src/lib.rs index 649187d5..35a1fb14 100644 --- a/rust/fastsim-core/src/lib.rs +++ b/rust/fastsim-core/src/lib.rs @@ -53,9 +53,6 @@ pub mod vehicle_import; pub mod vehicle_thermal; pub mod vehicle_utils; -#[cfg(feature = "dev-proc-macros")] -pub use dev_proc_macros as proc_macros; -#[cfg(not(feature = "dev-proc-macros"))] pub use fastsim_proc_macros as proc_macros; #[cfg_attr(feature = "pyo3", pyo3imports::pyfunction)]