Skip to content

Commit

Permalink
Merge pull request #128 from NREL/update-vehicle-import
Browse files Browse the repository at this point in the history
update vehicle import file
  • Loading branch information
calbaker authored Jan 17, 2025
2 parents 98eba11 + e3e42e2 commit 651ab3f
Show file tree
Hide file tree
Showing 4 changed files with 306 additions and 4 deletions.
118 changes: 118 additions & 0 deletions rust/fastsim-core/src/calibration.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
use crate::imports::*;

/// Skews the peak of a curve to a specified new x-value, redistributing other
/// x-values linearly, preserving relative distances between peak and endpoints.
/// Arguments:
/// ----------
/// x: x-values of original curve (i.e. mc_pwr_out_perc when skewing motor
/// efficiency map for RustVehicle)
/// y: y-values of the original curve (i.e. mc_eff_map when skewing motor
/// efficiency map RustVehicle)
/// new_peak_x: new x-value at which to relocate peak
pub fn skewness_shift(
x: &Array1<f64>,
y: &Array1<f64>,
new_peak_x: f64,
) -> anyhow::Result<(Array1<f64>, Array1<f64>)> {
let y_max = y
.clone()
.into_iter()
.reduce(f64::max)
.with_context(|| "could not find maximum of y array")?;

// Get index for maximum y-value. Use greatest index, if maximum occurs at
// multiple indexes.
let index_y_max = get_index_for_value(&y, y_max)?;

// making vector versions of x and y arrays to manipulate
let x_vec = x.to_vec();
let y_vec = y.to_vec();
let mut x_new_left = vec![];
let mut y_new_left = vec![];
let mut x_new_right = vec![];
let mut y_new_right = vec![];

// If points exist to the left of the peak
if (index_y_max != 0) && (new_peak_x != y[0]) {
for x_val in x_vec[0..index_y_max].iter() {
x_new_left.push(
x_vec[0] + (x_val - x_vec[0]) / (x_vec[index_y_max] - x[0]) * (new_peak_x - x[0]),
)
}
y_new_left.append(y_vec[0..index_y_max].to_vec().as_mut());
}

// If points exist to the right of the peak
if (index_y_max != y.len() - 1) && (new_peak_x != x_vec[x.len() - 1]) {
for x_val in x_vec[index_y_max + 1..x.len()].iter() {
x_new_right.push(
new_peak_x
+ (x_val - x[index_y_max]) / (x[x.len() - 1] - x[index_y_max])
* (x[x.len() - 1] - new_peak_x),
)
}
y_new_right.append(y_vec[index_y_max + 1..y.len()].to_vec().as_mut());
}

let mut x_new = vec![];
x_new.append(x_new_left.as_mut());
x_new.push(new_peak_x);
x_new.append(x_new_right.as_mut());

let mut y_new = vec![];
y_new.append(y_new_left.as_mut());
y_new.push(y_max);
y_new.append(y_new_right.as_mut());

// Quality checks
if x_new.len() != y_new.len() {
return Err(anyhow!(
"New x array and new y array do not have same length."
));
}
if x_new[0] != x[0] {
return Err(anyhow!(
"The first value of the new x array does not match the first value of the old x array."
));
}
let y_new_max = y_new
.clone()
.into_iter()
.reduce(f64::max)
.with_context(|| "could not find maximum of new y array")?;
let new_index_y_max = get_index_for_value(&y_new.clone().try_into()?, y_new_max)?;
if x_new[new_index_y_max] != new_peak_x {
return Err(anyhow!(
"The maximum in the new y array is not in the correct location."
));
}
if x_new[x_new.len() - 1] != x[x.len() - 1] {
return Err(anyhow!(
"The last value of the new x array {} does not equal the last value of the old x array. {}", x_new[x_new.len() - 1], x[x.len() - 1]
));
}

Ok((x_new.try_into()?, y_new.try_into()?))
}

/// Gets the index for the a value in an array. If the value occurs more than
/// once in the array, chooses the largest index for which the value occurs.
/// Arguments:
/// ----------
/// array: array to get index for
/// value: value to check array for and get index of
fn get_index_for_value(array: &Array1<f64>, value: f64) -> anyhow::Result<usize> {
let mut index: usize = 0;
let mut max_index_vec = vec![];
for val in array.iter() {
if val == &value {
max_index_vec.push(index);
}
index = index + 1;
}
Ok(max_index_vec
.iter()
.max()
.with_context(|| "Value not found in array.")?
.to_owned())
}
1 change: 1 addition & 0 deletions rust/fastsim-core/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ pub mod imports;
pub mod params;
pub mod pyo3imports;
pub mod simdrive;
mod calibration;
pub use simdrive::simdrive_impl;
pub mod simdrivelabel;
pub mod thermal;
Expand Down
181 changes: 180 additions & 1 deletion rust/fastsim-core/src/vehicle.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
//! Module containing vehicle struct and related functions.
use crate::calibration::skewness_shift;
// local
use crate::imports::*;
use crate::params::*;
Expand Down Expand Up @@ -56,6 +57,26 @@ lazy_static! {
pub fn set_mc_peak_eff_py(&mut self, new_peak: f64) {
self.set_mc_peak_eff(new_peak);
}
#[getter]
pub fn get_mc_eff_range_py(&self) -> anyhow::Result<f64> {
self.get_mc_eff_range()
}
#[setter("mc_eff_range")]
pub fn set_mc_eff_range_py(&mut self, new_range: f64) -> anyhow::Result<()> {
self.set_mc_eff_range(new_range)
}
#[getter]
pub fn get_fc_eff_range_py(&self) -> anyhow::Result<f64> {
self.get_fc_eff_range()
}
#[setter("fc_eff_range")]
pub fn set_fc_eff_range_py(&mut self, new_range: f64) -> anyhow::Result<()> {
self.set_fc_eff_range(new_range)
}
#[getter]
pub fn get_max_fc_eff_kw(&self) -> f64 {
Expand Down Expand Up @@ -94,6 +115,22 @@ lazy_static! {
fn mock_vehicle_py() -> Self {
Self::mock_vehicle()
}
#[setter("mc_eff_peak_pwr")]
pub fn set_mc_eff_peak_pwr_py<'py>(
&mut self,
new_peak_x: f64,
) -> anyhow::Result<()> {
self.set_mc_eff_peak_pwr(new_peak_x)
}
#[setter("fc_eff_peak_pwr")]
pub fn set_fc_eff_peak_pwr_py<'py>(
&mut self,
new_peak_x: f64,
) -> anyhow::Result<()> {
self.set_fc_eff_peak_pwr(new_peak_x)
}
)]
#[cfg_attr(feature = "validation", derive(Validate))]
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, ApproxEq)]
Expand Down Expand Up @@ -642,8 +679,9 @@ impl RustVehicle {
}

pub fn set_mc_peak_eff(&mut self, new_peak: f64) {
let mc_max_eff = self.mc_eff_array.max().unwrap();
let mc_max_eff = self.mc_eff_array.max().unwrap().clone();
self.mc_eff_array *= new_peak / mc_max_eff;
self.mc_eff_map *= new_peak / mc_max_eff;
let mc_max_full_eff = arrmax(&self.mc_full_eff_array);
self.mc_full_eff_array = self
.mc_full_eff_array
Expand All @@ -652,6 +690,96 @@ impl RustVehicle {
.collect();
}

/// Gets the minimum value of mc_eff_array
pub fn get_mc_eff_min(&self) -> anyhow::Result<&f64> {
self.mc_eff_array.min()
}

/// Gets the max value of mc_eff_array
pub fn get_mc_eff_max(&self) -> anyhow::Result<&f64> {
self.mc_eff_array.max()
}

/// Gets the range of mc_eff_array
pub fn get_mc_eff_range(&self) -> anyhow::Result<f64> {
Ok(self.get_mc_eff_max()? - self.get_mc_eff_min()?)
}

/// Changes the range (max value - min value) of mc_eff_map and mc_eff_array
/// # Arguments
/// - new_range: new range for the mc_eff_map and mc_eff_array
pub fn set_mc_eff_range(&mut self, new_range: f64) -> anyhow::Result<()> {
let mc_eff_max = *self.get_mc_eff_max()?;
if new_range == 0.0 {
self.mc_eff_map = Array::zeros(self.mc_eff_map.len()) + mc_eff_max;
self.mc_eff_array = Array::zeros(self.mc_eff_array.len()) + mc_eff_max;
Ok(())
} else if (0.0..=1.0).contains(&new_range) {
let old_range = self.get_mc_eff_range()?;
self.mc_eff_map = mc_eff_max + (&self.mc_eff_map - mc_eff_max) * new_range / old_range;
if self.get_mc_eff_min()? < &0.0 {
bail!("`mc_eff_min` ({:.3}) must not be negative", self.get_mc_eff_min()?)
}
ensure!(
self.get_mc_eff_max()? <= &1.0,
format!(
"{}\n`mc_eff_max` ({:.3}) must be no greater than 1.0",
format_dbg!(self.get_mc_eff_max()? <= &1.0),
self.get_mc_eff_max()?
)
);
self.mc_eff_array = self.mc_eff_map.clone();
Ok(())
} else {
bail!("`new_range` ({:.3}) must be between 0.0 and 1.0", new_range)
}
}

/// Gets the minimum value of fc_eff_array
pub fn get_fc_eff_min(&self) -> anyhow::Result<f64> {
Ok(self.fc_eff_array.iter().copied().fold(f64::NAN, f64::min))
}

/// Gets the max value of fc_eff_array
pub fn get_fc_eff_max(&self) -> anyhow::Result<f64> {
Ok(self.fc_eff_array.iter().copied().fold(f64::NAN, f64::max))
}

/// Gets the range of fc_eff_array
pub fn get_fc_eff_range(&self) -> anyhow::Result<f64> {
Ok(self.get_fc_eff_max()? - self.get_fc_eff_min()?)
}

/// Changes the range (max value - min value) of fc_eff_map and fc_eff_array
/// # Arguments
/// - new_range: new range for the fc_eff_map and fc_eff_array
pub fn set_fc_eff_range(&mut self, new_range: f64) -> anyhow::Result<()> {
let fc_eff_max = self.get_fc_eff_max()?;
if new_range == 0.0 {
self.fc_eff_map = Array::zeros(self.fc_eff_map.len()) + fc_eff_max;
self.fc_eff_array = (Array::zeros(self.fc_eff_array.len()) + fc_eff_max).to_vec();
Ok(())
} else if (0.0..=1.0).contains(&new_range) {
let old_range = self.get_fc_eff_range()?;
self.fc_eff_map = fc_eff_max + (&self.fc_eff_map - fc_eff_max) * new_range / old_range;
if self.get_fc_eff_min()? < 0.0 {
bail!("`fc_eff_min` ({:.3}) must not be negative", self.get_fc_eff_min()?)
}
ensure!(
self.get_fc_eff_max()? <= 1.0,
format!(
"{}\n`fc_eff_max` ({:.3}) must be no greater than 1.0",
format_dbg!(self.get_fc_eff_max()? <= 1.0),
self.get_fc_eff_max()?
)
);
self.fc_eff_array = self.fc_eff_map.to_vec();
Ok(())
} else {
bail!("`new_range` ({:.3}) must be between 0.0 and 1.0", new_range)
}
}

pub fn set_fc_peak_eff(&mut self, new_peak: f64) {
let old_fc_peak_eff = self.fc_peak_eff();
let multiplier = new_peak / old_fc_peak_eff;
Expand Down Expand Up @@ -1082,6 +1210,46 @@ impl RustVehicle {
vehicle.doc = Some(vehicle_origin);
Ok(vehicle)
}

/// Skews the peak of motor efficiency curve to new x-value, redistributing other
/// x-values linearly, preserving relative distances between peak and endpoints.
/// Arguments:
/// ----------
/// new_peak_x: new x-value at which to relocate peak
pub fn set_mc_eff_peak_pwr(&mut self, new_peak_x: f64) -> anyhow::Result<()> {
let short_arrays = skewness_shift(&self.mc_pwr_out_perc, &self.mc_eff_map, new_peak_x)?;
self.mc_pwr_out_perc = short_arrays.0;
self.mc_eff_map = short_arrays.1.clone();
self.mc_eff_array = short_arrays.1;
self.mc_full_eff_array = self
.mc_perc_out_array
.iter()
.enumerate()
.map(|(idx, &x): (usize, &f64)| -> f64 {
if idx == 0 {
0.0
} else {
interpolate(&x, &self.mc_pwr_out_perc, &self.mc_eff_array, false)
}
})
.collect();
Ok(())
}

/// Skews the peak of fc efficiency curve to new x-value, redistributing other
/// x-values linearly, preserving relative distances between peak and endpoints.
/// Arguments:
/// ----------
/// new_peak_x: new x-value at which to relocate peak
pub fn set_fc_eff_peak_pwr(&mut self, new_peak_x: f64) -> anyhow::Result<()> {
let short_arrays = skewness_shift(&self.fc_pwr_out_perc, &self.fc_eff_map, new_peak_x)?;
self.fc_pwr_out_perc = short_arrays.0;
self.fc_eff_array = short_arrays.1.to_vec();
self.fc_eff_map = short_arrays.1;
Ok(())
}
}

impl Default for RustVehicle {
Expand Down Expand Up @@ -1153,6 +1321,17 @@ mod tests {
assert!(veh.veh_kg > 0.0);
}

#[test]
fn test_set_mc_eff_range() {
let mut veh = RustVehicle::mock_vehicle();
veh.set_mc_eff_range(0.7).unwrap();
assert!(0.699 < veh.get_mc_eff_range().unwrap() && veh.get_mc_eff_range().unwrap() <= 0.701);
veh.set_mc_eff_range(0.5).unwrap();
assert!(0.499 < veh.get_mc_eff_range().unwrap() && veh.get_mc_eff_range().unwrap() <= 0.501);
veh.set_mc_eff_range(0.).unwrap();
assert!(veh.get_mc_eff_range().unwrap() == 0.);
}

#[test]
fn test_veh_kg_override() {
let veh_file = resources_path().join("vehdb/test_overrides.yaml");
Expand Down
Loading

0 comments on commit 651ab3f

Please sign in to comment.