Skip to content

Commit

Permalink
Merge pull request #74 from NREL/fix/mass-and-dependents
Browse files Browse the repository at this point in the history
updated `Mass` trait to be more explicit and robust
  • Loading branch information
mbbruch authored Jun 17, 2024
2 parents 36fc45b + 16c13f9 commit bae5289
Show file tree
Hide file tree
Showing 25 changed files with 654 additions and 303 deletions.
2 changes: 2 additions & 0 deletions python/altrios/altrios_pyo3.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ from typing_extensions import Self
from typing import Union, Tuple
from dataclasses import dataclass

# TODO: udpate to include api for `force_max` and `mu`


class SerdeAPI(object):
@classmethod
Expand Down
16 changes: 8 additions & 8 deletions python/altrios/optimization/cal_and_val.py
Original file line number Diff line number Diff line change
Expand Up @@ -66,9 +66,9 @@ class ModelError(object):
Dataclass class for calculating model error of various ALTRIOS objects w.r.t. test data.
Fields:
- `bincode_model_dict`: `dict` variable in which:
- `ser_model_dict`: `dict` variable in which:
- key: a `str` representing trip keyword string
- value: a `str` converted from Rust locomotive models' `to_bincode()` method
- value: a `str` converted from Rust locomotive models' serialization method
- `model_type`: `str` that can only be `'ConsistSimulation'`, `'SetSpeedTrainSim'` or `'LocomotiveSimulation'`;
indicates which model to instantiate during optimization process
Expand All @@ -87,8 +87,8 @@ class ModelError(object):
- `verbose`: `bool`; if `True`, the verbose of error calculation will be printed
"""
# `bincode_model_dict` and `dfs` should have the same keys
bincode_model_dict: Dict[str, str]
# `ser_model_dict` and `dfs` should have the same keys
ser_model_dict: Dict[str, str]
# model_type: tells what model if class instance tries to intantiate when `get_errors()` is called
model_type: str
# dictionary of test data
Expand All @@ -105,8 +105,8 @@ class ModelError(object):
allow_partial: bool = True

def __post_init__(self):
assert (len(self.dfs) == len(self.bincode_model_dict))
self.n_obj = len(self.objectives) * len(self.bincode_model_dict)
assert (len(self.dfs) == len(self.ser_model_dict))
self.n_obj = len(self.objectives) * len(self.ser_model_dict)
# current placeholder function; to be over-written to provide constraint violation function

@classmethod
Expand Down Expand Up @@ -322,8 +322,8 @@ def update_params(
else:
raise AttributeError('cannot initialize models')

for key, value in self.bincode_model_dict.items():
return_model_dict[key] = model_cls.from_bincode(value)
for key, value in self.ser_model_dict.items():
return_model_dict[key] = model_cls.from_json(value)

for path, x in zip(self.params, xs):
for key in return_model_dict.keys():
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# [Tier 4](https://www.wabteccorp.com/media/3641/download?inline)

# max steady state power consist fuel converters can produce
pwr_out_max_watts: 3.255e6
pwr_out_max_watts: 3.356e6
# time to ramp from min to max power
pwr_ramp_lag_seconds: 25
# idle fuel power usage
Expand Down
4 changes: 2 additions & 2 deletions python/altrios/tests/test_multi_obj_cal_and_val.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,9 @@ def test_pymoo_mod_err_build(self):
loco_unit=Locomotive.default(),
power_trace=pt,
save_interval=1)
loco_sim_bincode = loco_sim.to_bincode()
loco_sim_ser = loco_sim.to_json()
mod_err = cval.ModelError(
bincode_model_dict={0: loco_sim_bincode},
ser_model_dict={0: loco_sim_ser},
dfs={0: mock_df},
objectives=[(
"Fuel Power [W]",
Expand Down
2 changes: 1 addition & 1 deletion python/altrios/tests/test_powertrain_fuel_conv.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ def test_load_from_yaml_absolute_path(self):
)
fc = alt.FuelConverter.from_file(str(test_file))

self.assertEqual(fc.pwr_out_max_watts, 3.255e6)
self.assertEqual(fc.pwr_out_max_watts, 3.356e6)
self.assertEqual(fc.pwr_idle_fuel_watts, 1.97032784e+04)

def test_to_from_json(self):
Expand Down
28 changes: 6 additions & 22 deletions python/altrios/utilities.py
Original file line number Diff line number Diff line change
Expand Up @@ -79,26 +79,6 @@ def cumutrapz(x, y):
return z


R_air = np.float64(287) # J/(kg*K)


def get_rho_air(temperature_degC, elevation_m=180):
"""Returns air density [kg/m**3] for given elevation and temperature.
Source: https://www.grc.nasa.gov/WWW/K-12/rocket/atmosmet.html
Arguments:
----------
temperature_degC : ambient temperature [°C]
elevation_m : elevation above sea level [m].
Default 180 m is for Chicago, IL"""
# T = 15.04 - .00649 * h
# p = 101.29 * [(T + 273.1)/288.08]^5.256
T_standard = 15.04 - 0.00649 * elevation_m # nasa [degC]
p = 101.29e3 * ((T_standard + 273.1) / 288.08) ** 5.256 # nasa [Pa]
rho = p / (R_air * (temperature_degC + 273.15)) # [kg/m**3]

return rho


def set_param_from_path_dict(mod_dict: dict, path: str, value: float) -> Dict:
cur_mod_dict = mod_dict
path_list = path.split(".")
Expand Down Expand Up @@ -143,8 +123,12 @@ def set_param_from_path(
"""
Set parameter `value` on `model` for `path` to parameter
Example usage:
todo
# Example usage
```python
import altrios as alt
res = alt.ReversibleEnergyStorage.default()
alt.set_param_from_path(res, "state.soc", 1.0)
```
"""
path_list = path.split(".")

Expand Down
59 changes: 30 additions & 29 deletions rust/altrios-core/src/consist/consist_model.rs
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,8 @@ pub struct Consist {
#[api(skip_set)] // setter needs to also apply to individual locomotives
/// whether to panic if TPC requires more power than consist can deliver
assert_limits: bool,
#[serde(default)]
#[serde(skip_serializing_if = "EqDefault::eq_default")]
pub state: ConsistState,
/// Custom vector of [Self::state]
pub history: ConsistStateHistoryVec,
Expand All @@ -122,7 +124,7 @@ pub struct Consist {

impl SerdeAPI for Consist {
fn init(&mut self) -> anyhow::Result<()> {
self.check_mass_consistent()?;
let _mass = self.mass().with_context(|| format_dbg!())?;
self.set_pwr_dyn_brake_max();
self.loco_vec.init()?;
self.pdct.init()?;
Expand Down Expand Up @@ -181,8 +183,8 @@ impl Consist {
0. * uc::N,
|f_sum, (i, loco)| -> anyhow::Result<si::Force> {
Ok(loco
.force_max()?
.ok_or_else(|| anyhow!("Locomotive {i} does not have `force_max` set"))?
.force_max()
.with_context(|| format!("{}\n{}{}", format_dbg!(), "locomotive: ", i))?
+ f_sum)
},
)
Expand Down Expand Up @@ -413,7 +415,7 @@ impl Default for Consist {
};
// ensure propagation to nested components
consist.set_save_interval(Some(1));
consist.check_mass_consistent().unwrap();
let _mass = consist.mass().unwrap();
consist
}
}
Expand Down Expand Up @@ -495,47 +497,46 @@ impl LocoTrait for Consist {

impl Mass for Consist {
fn mass(&self) -> anyhow::Result<Option<si::Mass>> {
self.derived_mass()
}

fn derived_mass(&self) -> anyhow::Result<Option<si::Mass>> {
let mass = self.loco_vec.iter().enumerate().try_fold(
0. * uc::KG,
|m_acc, (i, loco)| -> anyhow::Result<si::Mass> {
let loco_mass = loco
.mass()?
.ok_or_else(|| anyhow!("Locomotive {i} does not have `mass` set"))?;
.mass()
.with_context(|| format!("locomotive at index {i}"))?
.with_context(|| format!("Locomotive {i} does not have `mass` set"))?;
let new_mass: si::Mass = loco_mass + m_acc;
Ok(new_mass)
},
)?;
Ok(Some(mass))
}

fn update_mass(&mut self, _mass: Option<si::Mass>) -> anyhow::Result<()> {
fn expunge_mass_fields(&mut self) {
self.loco_vec
.iter_mut()
.enumerate()
.try_for_each(|(i, loco)| -> anyhow::Result<()> {
loco.update_mass(None).map_err(|e| {
anyhow!("{e}").context(format!("{}\nfailed at loco: {}", format_dbg!(), i))
})
})
.for_each(|l| l.expunge_mass_fields())
}

fn check_mass_consistent(&self) -> anyhow::Result<()> {
for (i, loco) in self.loco_vec.iter().enumerate() {
match loco.check_mass_consistent() {
Ok(res) => res,
Err(e) => bail!(
"{e}\n{}",
format!(
"{}\nfailed at loco: {}\n{}",
format_dbg!(),
i,
"Try running `update_mass` method."
)
),
};
}
fn set_mass_specific_property(&mut self) -> anyhow::Result<()> {
Err(anyhow!(
"Setting mass specific properties not enabled at {} level",
stringify!(Consist)
))
}

Ok(())
fn set_mass(
&mut self,
_mass: Option<si::Mass>,
_side_effect: MassSideEffect,
) -> anyhow::Result<()> {
Err(anyhow!(
"Setting mass not enabled at {} level",
stringify!(Consist)
))
}
}
/// Locomotive State
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use super::powertrain::electric_drivetrain::ElectricDrivetrain;
use super::powertrain::reversible_energy_storage::ReversibleEnergyStorage;
use super::powertrain::ElectricMachine;
use super::LocoTrait;
use super::{LocoTrait, Mass, MassSideEffect};
use crate::imports::*;

#[derive(Default, Clone, Debug, PartialEq, Serialize, Deserialize, HistoryMethods)]
Expand Down Expand Up @@ -64,6 +64,31 @@ impl BatteryElectricLoco {
}
}

impl Mass for BatteryElectricLoco {
fn mass(&self) -> anyhow::Result<Option<si::Mass>> {
self.derived_mass().with_context(|| format_dbg!())
}

fn set_mass(
&mut self,
_new_mass: Option<si::Mass>,
_side_effect: MassSideEffect,
) -> anyhow::Result<()> {
Err(anyhow!(
"`set_mass` not enabled for {}",
stringify!(BatteryElectricLoco)
))
}

fn derived_mass(&self) -> anyhow::Result<Option<si::Mass>> {
self.res.mass().with_context(|| format_dbg!())
}

fn expunge_mass_fields(&mut self) {
self.res.expunge_mass_fields();
}
}

impl SerdeAPI for BatteryElectricLoco {
fn init(&mut self) -> anyhow::Result<()> {
self.res.init()?;
Expand Down
28 changes: 27 additions & 1 deletion rust/altrios-core/src/consist/locomotive/conventional_loco.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use super::powertrain::electric_drivetrain::ElectricDrivetrain;
use super::powertrain::fuel_converter::FuelConverter;
use super::powertrain::generator::Generator;
use super::powertrain::ElectricMachine;
use super::powertrain::{ElectricMachine, Mass, MassSideEffect};
use super::LocoTrait;
use crate::imports::*;

Expand Down Expand Up @@ -84,6 +84,32 @@ impl ConventionalLoco {
}
}

impl Mass for ConventionalLoco {
fn mass(&self) -> anyhow::Result<Option<si::Mass>> {
self.derived_mass().with_context(|| format_dbg!())
}

fn set_mass(
&mut self,
_new_mass: Option<si::Mass>,
_side_effect: MassSideEffect,
) -> anyhow::Result<()> {
Err(anyhow!(
"`set_mass` not enabled for {}",
stringify!(ConventionalLoco)
))
}

fn derived_mass(&self) -> anyhow::Result<Option<si::Mass>> {
self.fc.mass().with_context(|| format_dbg!())
}

fn expunge_mass_fields(&mut self) {
self.fc.expunge_mass_fields();
self.gen.expunge_mass_fields();
}
}

impl SerdeAPI for ConventionalLoco {
fn init(&mut self) -> anyhow::Result<()> {
self.fc.init()?;
Expand Down
29 changes: 28 additions & 1 deletion rust/altrios-core/src/consist/locomotive/hybrid_loco.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ use super::powertrain::fuel_converter::FuelConverter;
use super::powertrain::generator::Generator;
use super::powertrain::reversible_energy_storage::ReversibleEnergyStorage;
use super::powertrain::ElectricMachine;
use super::LocoTrait;
use super::{LocoTrait, Mass, MassSideEffect};
use crate::imports::*;
use uom::ConstZero; // this should be covered in `crate::imports` but is needed

Expand Down Expand Up @@ -90,6 +90,33 @@ impl Default for HybridLoco {
}
}

impl Mass for HybridLoco {
fn mass(&self) -> anyhow::Result<Option<si::Mass>> {
self.derived_mass().with_context(|| format_dbg!())
}

fn set_mass(
&mut self,
_new_mass: Option<si::Mass>,
_side_effect: MassSideEffect,
) -> anyhow::Result<()> {
Err(anyhow!(
"`set_mass` not enabled for {}",
stringify!(HybridLoco)
))
}

fn derived_mass(&self) -> anyhow::Result<Option<si::Mass>> {
self.fc.mass().with_context(|| format_dbg!())
}

fn expunge_mass_fields(&mut self) {
self.fc.expunge_mass_fields();
self.gen.expunge_mass_fields();
self.res.expunge_mass_fields();
}
}

impl LocoTrait for Box<HybridLoco> {
fn set_cur_pwr_max_out(
&mut self,
Expand Down
Loading

0 comments on commit bae5289

Please sign in to comment.