#[cfg(feature = "default")]
use argmin::core::{CostFunction, Executor, State};
#[cfg(feature = "default")]
use argmin::solver::neldermead::NelderMead;
use std::{result::Result, thread, time::Duration};
use ureq::{Error as OtherError, Error::Status, Response};
#[cfg(feature = "default")]
use crate::air::*;
#[cfg(feature = "default")]
use crate::cycle::RustCycle;
use crate::imports::*;
#[cfg(feature = "default")]
use crate::params::*;
#[cfg(all(feature = "pyo3", feature = "default"))]
use crate::pyo3imports::*;
#[cfg(feature = "default")]
use crate::simdrive::RustSimDrive;
#[cfg(feature = "default")]
use crate::vehicle::RustVehicle;
#[allow(non_snake_case)]
#[cfg_attr(feature = "pyo3", pyfunction)]
#[allow(clippy::too_many_arguments)]
#[cfg(feature = "default")]
pub fn abc_to_drag_coeffs(
veh: &mut RustVehicle,
a_lbf: f64,
b_lbf__mph: f64,
c_lbf__mph2: f64,
custom_rho: Option<bool>,
custom_rho_temp_degC: Option<f64>,
custom_rho_elevation_m: Option<f64>,
simdrive_optimize: Option<bool>,
_show_plots: Option<bool>,
) -> (f64, f64) {
let air_props = AirProperties::default();
let props = RustPhysicalProperties::default();
let cur_ambient_air_density_kg__m3 = if custom_rho.unwrap_or(false) {
air_props.get_rho(custom_rho_temp_degC.unwrap_or(20.0), custom_rho_elevation_m)
} else {
props.air_density_kg_per_m3
};
let vmax_mph = 70.0;
let a_newton = a_lbf * super::params::N_PER_LBF;
let _b_newton__mps = b_lbf__mph * super::params::N_PER_LBF * super::params::MPH_PER_MPS;
let c_newton__mps2 = c_lbf__mph2
* super::params::N_PER_LBF
* super::params::MPH_PER_MPS
* super::params::MPH_PER_MPS;
let cd_len = 300;
let cyc = RustCycle {
time_s: (0..cd_len as i32).map(f64::from).collect(),
mps: Array::linspace(vmax_mph / super::params::MPH_PER_MPS, 0.0, cd_len),
grade: Array::zeros(cd_len),
road_type: Array::zeros(cd_len),
name: String::from("cycle"),
orphaned: false,
};
let dyno_func_lb = |x: &f64| a_lbf + b_lbf__mph * x + c_lbf__mph2 * x.powi(2);
let drag_coef: f64;
let wheel_rr_coef: f64;
if simdrive_optimize.unwrap_or(true) {
let cost = GetError {
cycle: &cyc,
vehicle: veh,
dyno_func_lb: &dyno_func_lb,
};
let solver = NelderMead::new(vec![array![0.0, 0.0], array![0.5, 0.0], array![0.5, 0.1]]);
let res = Executor::new(cost, solver)
.configure(|state| state.max_iters(100))
.run()
.unwrap();
let best_param = res.state().get_best_param().unwrap();
drag_coef = best_param[0];
wheel_rr_coef = best_param[1];
} else {
drag_coef = c_newton__mps2 / (0.5 * veh.frontal_area_m2 * cur_ambient_air_density_kg__m3);
wheel_rr_coef = a_newton / veh.veh_kg / props.a_grav_mps2;
}
veh.drag_coef = drag_coef;
veh.wheel_rr_coef = wheel_rr_coef;
(drag_coef, wheel_rr_coef)
}
pub fn get_error_val(model: Array1<f64>, test: Array1<f64>, time_steps: Array1<f64>) -> f64 {
assert!(
model.len() == test.len() && test.len() == time_steps.len(),
"{}, {}, {}",
model.len(),
test.len(),
time_steps.len()
);
let mut err = 0.0;
let y = (model - test).mapv(f64::abs);
for index in 0..time_steps.len() - 1 {
err += 0.5 * (time_steps[index + 1] - time_steps[index]) * (y[index] + y[index + 1]);
}
return err / (time_steps.last().unwrap() - time_steps[0]);
}
#[cfg(feature = "default")]
struct GetError<'a, F>
where
F: Fn(&f64) -> f64,
{
cycle: &'a RustCycle,
vehicle: &'a RustVehicle,
dyno_func_lb: &'a F,
}
#[cfg(feature = "default")]
impl<F> CostFunction for GetError<'_, F>
where
F: Fn(&f64) -> f64,
{
type Param = Array1<f64>;
type Output = f64;
fn cost(&self, x: &Self::Param) -> anyhow::Result<Self::Output> {
let mut veh = self.vehicle.clone();
let cyc = self.cycle.clone();
veh.drag_coef = x[0];
veh.wheel_rr_coef = x[1];
let mut sd_coast = RustSimDrive::new(self.cycle.clone(), veh);
sd_coast.impose_coast = Array::from_vec(vec![true; sd_coast.impose_coast.len()]);
let _sim_drive_result = sd_coast.sim_drive(None, None);
let cutoff_vec: Vec<usize> = sd_coast
.mps_ach
.indexed_iter()
.filter_map(|(index, &item)| (item < 0.1).then_some(index))
.collect();
let cutoff = if cutoff_vec.is_empty() {
sd_coast.mps_ach.len()
} else {
cutoff_vec[0]
};
Ok(get_error_val(
(Array::from_vec(vec![1000.0; sd_coast.mps_ach.len()])
* (sd_coast.drag_kw + sd_coast.rr_kw)
/ sd_coast.mps_ach)
.slice_move(s![0..cutoff]),
(sd_coast.mph_ach.map(self.dyno_func_lb)
* Array::from_vec(vec![super::params::N_PER_LBF; sd_coast.mph_ach.len()]))
.slice_move(s![0..cutoff]),
cyc.time_s.slice_move(s![0..cutoff]),
))
}
}
pub fn list_zip_contents(filepath: &Path) -> anyhow::Result<()> {
let f = File::open(filepath)?;
let mut zip = zip::ZipArchive::new(f)?;
for i in 0..zip.len() {
let file = zip.by_index(i)?;
println!("Filename: {}", file.name());
}
Ok(())
}
pub fn extract_zip(filepath: &Path, dest_dir: &Path) -> anyhow::Result<()> {
let f = File::open(filepath)?;
let mut zip = zip::ZipArchive::new(f)?;
zip.extract(dest_dir)?;
Ok(())
}
#[derive(Deserialize)]
pub struct ObjectLinks {
#[serde(rename = "self")]
pub self_url: Option<String>,
pub git: Option<String>,
pub html: Option<String>,
}
#[derive(Deserialize)]
pub struct GitObjectInfo {
pub name: String,
pub path: String,
pub sha: Option<String>,
pub size: Option<i64>,
pub url: String,
pub html_url: Option<String>,
pub git_url: Option<String>,
pub download_url: Option<String>,
#[serde(rename = "type")]
pub url_type: String,
#[serde(rename = "_links")]
pub links: Option<ObjectLinks>,
}
const VEHICLE_REPO_LIST_URL: &str =
"https://api.github.com/repos/NREL/fastsim-vehicles/contents/public";
fn get_response<S: AsRef<str>>(url: S) -> Result<Response, OtherError> {
for _ in 1..4 {
match ureq::get(url.as_ref()).call() {
Err(Status(503, r)) | Err(Status(429, r)) | Err(Status(403, r)) => {
let retry: Option<u64> = r.header("retry-after").and_then(|h| h.parse().ok());
let retry = retry.unwrap_or(5);
eprintln!("{} for {}, retry in {}", r.status(), r.get_url(), retry);
thread::sleep(Duration::from_secs(retry));
}
result => return result,
};
}
ureq::get(url.as_ref()).call()
}
pub fn fetch_github_list(repo_url: Option<String>) -> anyhow::Result<Vec<String>> {
let repo_url = repo_url.unwrap_or(VEHICLE_REPO_LIST_URL.to_string());
let response = get_response(repo_url)?.into_reader();
let github_list: Vec<GitObjectInfo> =
serde_json::from_reader(response).with_context(|| "Cannot parse github vehicle list.")?;
let mut vehicle_name_list = Vec::new();
for object in github_list.iter() {
if object.url_type == "dir" {
let url = &object.url;
let vehicle_name_sublist = fetch_github_list(Some(url.to_owned()))?;
for name in vehicle_name_sublist.iter() {
vehicle_name_list.push(name.to_owned());
}
} else if object.url_type == "file" {
let url = url::Url::parse(&object.url)?;
let path = &object.path;
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:?}")?;
match format.trim_start_matches('.').to_lowercase().as_str() {
"yaml" | "yml" => vehicle_name_list.push(path.to_owned()),
"json" => vehicle_name_list.push(path.to_owned()),
_ => continue,
}
} else {
continue;
}
}
Ok(vehicle_name_list)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_get_error_val() {
let time_steps = array![0.0, 1.0, 2.0, 3.0, 4.0];
let model = array![1.1, 4.6, 2.5, 3.7, 5.0];
let test = array![2.1, 4.5, 3.4, 4.8, 6.3];
let error_val = get_error_val(model, test, time_steps);
println!("Error Value: {}", error_val);
assert!(error_val.approx_eq(&0.8124999999999998, 1e-10));
}
#[cfg(feature = "default")]
#[test]
fn test_abc_to_drag_coeffs() {
let mut veh = RustVehicle::mock_vehicle();
let a = 25.91;
let b = 0.1943;
let c = 0.01796;
let (drag_coef, wheel_rr_coef) = abc_to_drag_coeffs(
&mut veh,
a,
b,
c,
Some(false),
None,
None,
Some(true),
Some(false),
);
println!("Drag Coef: {}", drag_coef);
println!("Wheel RR Coef: {}", wheel_rr_coef);
assert!(drag_coef.approx_eq(&0.24676817210529464, 1e-5));
assert!(wheel_rr_coef.approx_eq(&0.0068603812443132645, 1e-6));
assert_eq!(drag_coef, veh.drag_coef);
assert_eq!(wheel_rr_coef, veh.wheel_rr_coef);
}
#[test]
fn test_fetch_github_list() {
let list = fetch_github_list(Some(
"https://api.github.com/repos/NREL/fastsim-vehicles/contents".to_owned(),
))
.unwrap();
let other_list = fetch_github_list(None).unwrap();
println!("{:?}", list);
println!("{:?}", other_list);
}
}