Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

adding new from_github vehicle method #86

Merged
merged 28 commits into from
Feb 7, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
fc74da8
adding new from_github vehicle method
Dec 19, 2023
7f2d22f
merging in create_project_subdir function
robinsteuteville Dec 21, 2023
5d2c1d4
adding from_url and to_cache methods to SerdeAPI
robinsteuteville Dec 21, 2023
84754c3
fixing caching functions
robinsteuteville Dec 25, 2023
c81eca6
fixing from_url function
robinsteuteville Dec 26, 2023
f68d055
updating from_url and from_str
robinsteuteville Dec 26, 2023
8d556f3
adding from_github_or_url method
robinsteuteville Jan 2, 2024
f6896f6
updating error messages
robinsteuteville Jan 3, 2024
1ff4316
merge fastsim-2 into vehicle-fetching-caching
Jan 3, 2024
474df42
adding fetch_github_list function
robinsteuteville Jan 3, 2024
ea631fd
updating fetch_github_list
robinsteuteville Jan 3, 2024
5275708
adding caching of vehicle if not downloaded to from_github_or_url
robinsteuteville Jan 4, 2024
24a11ac
updating from_github_or_url doc string
robinsteuteville Jan 4, 2024
7e9a51c
updating doc strings
robinsteuteville Jan 4, 2024
05fea86
adding temporary tests/file to support the tests
robinsteuteville Jan 5, 2024
da1f8a4
updating test to produce test file for comparison
robinsteuteville Jan 5, 2024
1301482
troubleshooting fetch_github_list
robinsteuteville Jan 6, 2024
f6c607f
pulling fastsim-2 updates to get tests to pass
robinsteuteville Jan 10, 2024
d729966
updating fetch_github_list
robinsteuteville Jan 18, 2024
364395a
fixing looping problem with fetch_github_list
robinsteuteville Jan 18, 2024
49ba054
fixing fetch_github_list test so it runs
robinsteuteville Jan 18, 2024
1f9860e
adding path_to_cache and from_cache
robinsteuteville Jan 19, 2024
6086641
ading clear cache function
robinsteuteville Jan 23, 2024
4c4f1d9
updating tests so they pass
robinsteuteville Jan 23, 2024
512284a
adding 403 error to retry errors for fetch_github_list
robinsteuteville Jan 23, 2024
7960663
deleted deprecated comment
calbaker Feb 6, 2024
4fb10f3
minor tweaks
calbaker Feb 6, 2024
867f72f
reorganize reference vehicle for github caching
Feb 6, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions rust/fastsim-core/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,9 @@ include_dir = "0.7.3"
itertools = "0.12.0"
ndarray-stats = "0.5.1"
tempfile = "3.8.1"
url = "2.5.0"
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@kylecarow, could you feature gate this stuff?

ureq = "2.9.1"
isahc = "1.7.2"

[package.metadata]
include = [
Expand Down
1 change: 1 addition & 0 deletions rust/fastsim-core/src/cycle.rs
Original file line number Diff line number Diff line change
Expand Up @@ -631,6 +631,7 @@ pub struct RustCycle {
impl SerdeAPI for RustCycle {
const ACCEPTED_BYTE_FORMATS: &'static [&'static str] = &["yaml", "json", "bin", "csv"];
const ACCEPTED_STR_FORMATS: &'static [&'static str] = &["yaml", "json", "csv"];
const CACHE_FOLDER: &'static str = &"cycles";

fn init(&mut self) -> anyhow::Result<()> {
ensure!(!self.is_empty(), "Deserialized cycle is empty");
Expand Down
1 change: 1 addition & 0 deletions rust/fastsim-core/src/imports.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,3 +13,4 @@ pub(crate) use std::path::PathBuf;

pub(crate) use crate::traits::*;
pub(crate) use crate::utils::*;
pub(crate) use crate::vehicle_utils::*;
79 changes: 79 additions & 0 deletions rust/fastsim-core/src/traits.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
use crate::imports::*;
use std::collections::HashMap;
use ureq;

pub trait SerdeAPI: Serialize + for<'a> Deserialize<'a> {
const ACCEPTED_BYTE_FORMATS: &'static [&'static str] = &["yaml", "json", "bin"];
const ACCEPTED_STR_FORMATS: &'static [&'static str] = &["yaml", "json"];
const CACHE_FOLDER: &'static str = &"";

/// Specialized code to execute upon initialization
fn init(&mut self) -> anyhow::Result<()> {
Expand Down Expand Up @@ -185,6 +187,83 @@ pub trait SerdeAPI: Serialize + for<'a> Deserialize<'a> {
bincode_de.init()?;
Ok(bincode_de)
}

/// Instantiates an object from a url. Accepts yaml and json file types
/// # Arguments
/// - url: URL (either as a string or url type) to object
/// Note: The URL needs to be a URL pointing directly to a file, for example
/// a raw github URL.
fn from_url<S: AsRef<str>>(url: S) -> anyhow::Result<Self> {
let url = url::Url::parse(url.as_ref())?;
let format = url
.path_segments()
.and_then(|segments| segments.last())
.and_then(|filename| Path::new(filename).extension())
.and_then(OsStr::to_str)
.with_context(|| "Could not parse file format from URL: {url:?}")?;
let response = ureq::get(url.as_ref()).call()?.into_reader();
Self::from_reader(response, format)
}

/// Takes an instantiated Rust object and saves it in the FASTSim data directory in
/// a rust_objects folder.
/// WARNING: If there is a file already in the data subdirectory with the
/// same name, it will be replaced by the new file.
/// # Arguments
/// - self (rust object)
/// - file_path: path to file within subdirectory. If only the file name is
/// listed, file will sit directly within the subdirectory of
/// the FASTSim data directory. If a path is given, the file will live
/// within the path specified, within the subdirectory CACHE_FOLDER of the
/// FASTSim data directory.
fn to_cache<P: AsRef<Path>>(&self, file_path: P) -> anyhow::Result<()> {
let file_name = file_path
.as_ref()
.file_name()
.with_context(|| "Could not determine file name")?
.to_str()
.context("Could not determine file name.")?;
let mut subpath = PathBuf::new();
let file_path_internal = file_path
.as_ref()
.to_str()
.context("Could not determine file name.")?;
if file_name == file_path_internal {
subpath = PathBuf::from(Self::CACHE_FOLDER);
} else {
subpath = Path::new(Self::CACHE_FOLDER).join(
file_path_internal
.strip_suffix(file_name)
.context("Could not determine path to subdirectory.")?,
);
}
let data_subdirectory = create_project_subdir(subpath)
.with_context(|| "Could not find or build Fastsim data subdirectory.")?;
let file_path = data_subdirectory.join(file_name);
self.to_file(file_path)
}

/// Instantiates a Rust object from the subdirectory within the FASTSim data
/// directory corresponding to the Rust Object ("vehices" for a RustVehice,
/// "cycles" for a RustCycle, and the root folder of the data directory for
/// all other objects).
/// # Arguments
/// - file_path: subpath to object, including file name, within subdirectory.
/// If the file sits directly in the subdirectory, this will just be the
/// file name.
/// Note: This function will work for all objects cached using the
/// to_cache() method. If a file has been saved manually to a different
/// subdirectory than the correct one for the object type (for instance a
/// RustVehicle saved within a subdirectory other than "vehicles" using the
/// utils::url_to_cache() function), then from_cache() will not be able to
/// find and instantiate the object. Instead, use the from_file method, and
/// use the utils::path_to_cache() to find the FASTSim data directory
/// location if needed.
fn from_cache<P: AsRef<Path>>(file_path: P) -> anyhow::Result<Self> {
let full_file_path = Path::new(Self::CACHE_FOLDER).join(file_path);
let path_including_directory = path_to_cache()?.join(full_file_path);
Self::from_file(path_including_directory)
}
}

pub trait ApproxEq<Rhs = Self> {
Expand Down
78 changes: 78 additions & 0 deletions rust/fastsim-core/src/utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ use ndarray::*;
use ndarray_stats::QuantileExt;
use regex::Regex;
use std::collections::HashSet;
use url::Url;

use crate::imports::*;
#[cfg(feature = "pyo3")]
Expand Down Expand Up @@ -525,6 +526,59 @@ pub fn create_project_subdir<P: AsRef<Path>>(subpath: P) -> anyhow::Result<PathB
Ok(path)
}

/// Returns the path to the OS-specific data directory, if it exists.
pub fn path_to_cache() -> anyhow::Result<PathBuf> {
let proj_dirs = ProjectDirs::from("gov", "NREL", "fastsim").ok_or_else(|| {
anyhow!("Could not build path to project directory: \"gov.NREL.fastsim\"")
})?;
Ok(PathBuf::from(proj_dirs.config_dir()))
}

/// Deletes FASTSim data directory, clearing its contents. If subpath is
/// provided, will only delete the subdirectory pointed to by the subpath,
/// rather than deleting the whole data directory. If the subpath is an empty
/// string, deletes the entire FASTSim directory.
/// USE WITH CAUTION, as this function deletes ALL objects stored in the FASTSim
/// data directory or provided subdirectory.
/// # Arguments
/// - subpath: Subpath to a subdirectory within the FASTSim data directory. If
/// an empty string, the function will delete the whole FASTSim data
/// directory, clearing all its contents.
/// Note: it is not possible to delete single files using this function, only
/// directories. If a single file needs deleting, the path_to_cache() function
/// can be used to find the FASTSim data directory location. The file can then
/// be found and manually deleted.
pub fn clear_cache<P: AsRef<Path>>(subpath: P) -> anyhow::Result<()> {
let path = path_to_cache()?.join(subpath);
Ok(std::fs::remove_dir_all(path)?)
}

/// takes an object from a url and saves it in the FASTSim data directory in a
/// rust_objects folder
/// WARNING: if there is a file already in the data subdirectory with the same
/// name, it will be replaced by the new file
/// to save to a folder other than rust_objects, define constant CACHE_FOLDER to
/// be the desired folder name
/// # Arguments
/// - url: url (either as a string or url type) to object
/// - subpath: path to subdirectory within FASTSim data directory. Suggested
/// paths are "vehicles" for a RustVehicle, "cycles" for a RustCycle, and
/// "rust_objects" for other Rust objects.
/// Note: In order for the file to be save in the proper format, the URL needs
/// to be a URL pointing directly to a file, for example a raw github URL.
pub fn url_to_cache<S: AsRef<str>, P: AsRef<Path>>(url: S, subpath: P) -> anyhow::Result<()> {
let url = Url::parse(url.as_ref())?;
let file_name = url
.path_segments()
.and_then(|segments| segments.last())
.with_context(|| "Could not parse filename from URL: {url:?}")?;
let data_subdirectory = create_project_subdir(subpath)
.with_context(|| "Could not find or build Fastsim data subdirectory.")?;
let file_path = data_subdirectory.join(file_name);
download_file_from_url(url.as_ref(), &file_path)?;
Ok(())
}

#[cfg(feature = "pyo3")]
pub mod array_wrappers {
use crate::proc_macros::add_pyo3_api;
Expand Down Expand Up @@ -732,4 +786,28 @@ mod tests {
let y_lookup = interpolate_vectors(&x, &xs.to_vec(), &ys.to_vec(), false);
assert_eq!(expected_y_lookup, y_lookup);
}

#[test]
fn test_path_to_cache() {
let path = path_to_cache().unwrap();
println!("{:?}", path);
}

#[test]
fn test_clear_cache() {
let temp_sub_dir = tempfile::TempDir::new_in(create_project_subdir("").unwrap()).unwrap();
let sub_dir_path = temp_sub_dir.path().to_str().unwrap();
let still_exists_before = std::fs::metadata(sub_dir_path).is_ok();
assert_eq!(still_exists_before, true);
url_to_cache("https://raw.githubusercontent.com/NREL/fastsim-vehicles/main/public/1110_2022_Tesla_Model_Y_RWD_opt45017.yaml", "").unwrap();
clear_cache(sub_dir_path).unwrap();
let still_exists = std::fs::metadata(sub_dir_path).is_ok();
assert_eq!(still_exists, false);
let path_to_vehicle = path_to_cache()
.unwrap()
.join("1110_2022_Tesla_Model_Y_RWD_opt45017.yaml");
let vehicle_still_exists = std::fs::metadata(&path_to_vehicle).is_ok();
assert_eq!(vehicle_still_exists, true);
std::fs::remove_file(path_to_vehicle).unwrap();
}
}
63 changes: 63 additions & 0 deletions rust/fastsim-core/src/vehicle.rs
Original file line number Diff line number Diff line change
Expand Up @@ -545,6 +545,8 @@ pub struct RustVehicle {

/// RustVehicle rust methods
impl RustVehicle {
const VEHICLE_DIRECTORY_URL: &'static str =
&"https://raw.githubusercontent.com/NREL/fastsim-vehicles/main/";
/// Sets the following parameters:
/// - `ess_mass_kg`
/// - `mc_mass_kg`
Expand Down Expand Up @@ -1023,6 +1025,43 @@ impl RustVehicle {
v.set_derived().unwrap();
v
}

/// Downloads specified vehicle from FASTSim vehicle repo or url and
/// instantiates it into a RustVehicle. Notes in vehicle.doc the origin of
/// the vehicle. Returns vehicle.
/// # Arguments
/// - vehicle_file_name: file name for vehicle to be downloaded, including
/// path from url directory or FASTSim repository (if applicable)
/// - url: url for vehicle repository where vehicle will be downloaded from,
/// if None, assumed to be downloaded from vehicle FASTSim repo
/// Note: The URL needs to be a URL pointing directly to a file, for example
/// a raw github URL, split up so that the "url" argument is the path to the
/// directory, and the "vehicle_file_name" is the path within the directory
/// to the file.
/// Note: If downloading from the FASTSim Vehicle Repo, the
/// vehicle_file_name should include the path to the file from the root of
/// the Repo, as listed in the output of the
/// vehicle_utils::fetch_github_list() function.
/// Note: the url should not include the file name, only the path to the
/// file or a root directory of the file.
pub fn from_github_or_url<S: AsRef<str>>(
vehicle_file_name: S,
url: Option<S>,
) -> anyhow::Result<Self> {
let url_internal = match url {
Some(s) => {
s.as_ref().trim_end_matches('/').to_owned()
+ "/"
+ &vehicle_file_name.as_ref().trim_start_matches('/')
}
None => Self::VEHICLE_DIRECTORY_URL.to_string() + vehicle_file_name.as_ref(),
};
let mut vehicle =
Self::from_url(&url_internal).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)
}
}

impl Default for RustVehicle {
Expand Down Expand Up @@ -1051,9 +1090,33 @@ impl Default for RustVehicle {
}

impl SerdeAPI for RustVehicle {
const CACHE_FOLDER: &'static str = &"vehicles";

fn init(&mut self) -> anyhow::Result<()> {
self.set_derived()
}

/// instantiates a vehicle from a url, and notes in vehicle.doc the origin
/// of the vehicle.
/// accepts yaml and json file types
/// # Arguments
/// - url: URL (either as a string or url type) to object
/// Note: The URL needs to be a URL pointing directly to a file, for example
/// a raw github URL.
fn from_url<S: AsRef<str>>(url: S) -> anyhow::Result<Self> {
let url = url::Url::parse(url.as_ref())?;
let format = url
.path_segments()
.and_then(|segments| segments.last())
.and_then(|filename| Path::new(filename).extension())
.and_then(OsStr::to_str)
.with_context(|| "Could not parse file format from URL: {url:?}")?;
let response = ureq::get(url.as_ref()).call()?.into_reader();
let mut vehicle = Self::from_reader(response, format)?;
let vehicle_origin = "Vehicle from ".to_owned() + url.as_ref();
vehicle.doc = Some(vehicle_origin);
Ok(vehicle)
}
}

#[cfg(test)]
Expand Down
Loading
Loading