Skip to content

Commit

Permalink
Merge pull request #104 from NREL/feature/make_est_times-doc_string
Browse files Browse the repository at this point in the history
Added docstring for make_est_times
  • Loading branch information
calbaker authored Jan 7, 2025
2 parents 00a0d60 + 956208c commit 6d79f78
Show file tree
Hide file tree
Showing 6 changed files with 124 additions and 34 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -379,15 +379,16 @@ impl ReversibleEnergyStorage {
}

fn check_vol_consistent(&self) -> anyhow::Result<()> {
match &self.volume {
Some(vol) => match &self.energy_density {
Some(e) => {
ensure!(self.energy_capacity / *e == *vol,
format!("{}\n{}", format_dbg!(), "ReversibleEnergyStorage `energy_capacity`, `energy_density` and `volume` are not consistent"))
}
None => {}
},
None => {}
if let Some(vol) = &self.volume {
if let Some(e) = &self.energy_density {
ensure!(
self.energy_capacity / *e == *vol,
format!(
"{}\n{}",
format_dbg!(),
"ReversibleEnergyStorage `energy_capacity`, `energy_density` and `volume` are not consistent")
)
}
}
Ok(())
}
Expand Down
1 change: 1 addition & 0 deletions rust/altrios-core/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
// #![warn(missing_docs)]
// #![warn(missing_docs_in_private_items)]
#![allow(non_local_definitions)] // see https://github.com/PyO3/pyo3/discussions/4083 as this is a `pyo3` problem

//! Crate containing models for second-by-second fuel and energy consumption of simulation
//! of locomotive consists comprising collections of individual locomotives, which comprise
Expand Down
115 changes: 104 additions & 11 deletions rust/altrios-core/src/meet_pass/est_times/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -473,25 +473,74 @@ fn add_new_join_paths(
}
}

/// `make_est_times` function creates an estimated-time network (`EstTimeNet`) and train consist (`Consist`) from a
/// given `SpeedLimitTrainSim` and rail `network`. This function performs the following
/// major steps in a loop, until all trains (in `saved_sims`) are processed:
///
/// 1. **Initialize** a train simulation for each origin node and add it to the `saved_sims` stack.
/// 1. **Pop** a train from `saved_sims`, then:
/// 1. Run the simulation (`update_movement`) until a condition in `saved_sim.update_movement()`
/// in `saved_sims.update_movement()` is met.
/// 1. Convert the results into `EstTime` nodes.
/// - If the path diverges from the existing network, insert new `EstTime` events and
/// set `has_split = true`.
/// - If `has_split` is set, attempt to **join** back to an existing sequence in the
/// `EstTime` network (the “space and speed match” check). If successful, **break**
/// out of this train's processing loop.
/// 1. If the simulation reaches the final destination, add the last node to `est_idxs_end` and **break**.
/// 1. Otherwise, add the next link(s) that continues the path toward the destination. If
/// multiple branches exist, clone the train sim for the alternate path and push it to
/// `saved_sims`. (Processing continues until we reach a break.)
///
/// 1. **Post-process** the resulting `EstTimeNet` by fixing references, linking final nodes,
/// and updating times forward and backward.
/// 1. **Return** the completed `EstTimeNet` and the `Consist`.
///
/// # Arguments
///
/// * `speed_limit_train_sim` - `SpeedLimitTrainSim` is an instance of
/// train simulation in which speed is allowed to vary according to train
/// capabilities and speed limit.
/// * `network` - Network comprises an ensemble of links (path between junctions
/// along the rail with heading, grade, and location) this simulation
/// operates on.
///
/// # Returns
///
/// A tuple of:
/// * `EstTimeNet` - The fully built estimated-time network,
/// * `Consist` - The locomotive consist associated with the simulation.
///
/// # Errors
///
/// Returns an error if:
/// * The provided origins or next link options are invalid.
/// * The path is unexpectedly truncated.
/// * The simulation fails internally while updating movements or extending paths.
pub fn make_est_times<N: AsRef<[Link]>>(
mut speed_limit_train_sim: SpeedLimitTrainSim,
network: N,
) -> anyhow::Result<(EstTimeNet, Consist)> {
speed_limit_train_sim.set_save_interval(None);
let network = network.as_ref();
let dests = &speed_limit_train_sim.dests;
// Step 1a: Gather valid next-link indices for each origin/destination.
let (link_idx_options, origs) =
get_link_idx_options(&speed_limit_train_sim.origs, dests, network)
.with_context(|| format_dbg!())?;

// We'll store our estimated times and a map of link events here.
let mut est_times = Vec::with_capacity(network.len() * 10);
let mut consist_out = None;
let mut saved_sims: Vec<SavedSim> = vec![];
let mut link_event_map =
LinkEventMap::with_capacity_and_hasher(est_times.capacity(), Default::default());
// The departure time for all initial events
let time_depart = speed_limit_train_sim.state.time;

// Push initial fake nodes
// -----------------------------------------------------------------------
// Push initial "fake" nodes.
// These help define the start of our `EstTime` sequence.
// -----------------------------------------------------------------------
#[cfg(feature = "logging")]
log::debug!("{}", format_dbg!("Push initial fake nodes."));
est_times.push(EstTime {
Expand All @@ -504,7 +553,11 @@ pub fn make_est_times<N: AsRef<[Link]>>(
..Default::default()
});

// Add origin estimated times
// -----------------------------------------------------------------------
// Add origin estimated times:
// For each origin, we ensure offset=0, is_front_end=false, and a real link_idx.
// Then create two `EstTime` events: (Arrive + Clear) for the train's tail and head.
// -----------------------------------------------------------------------
#[cfg(feature = "logging")]
log::debug!("{}", format_dbg!("Add origin estimated times."));
for orig in origs {
Expand All @@ -527,6 +580,8 @@ pub fn make_est_times<N: AsRef<[Link]>>(

#[cfg(feature = "logging")]
log::debug!("{}", format_dbg!());

// Arrive event
insert_est_time(
&mut est_times,
&mut est_alt,
Expand All @@ -544,6 +599,8 @@ pub fn make_est_times<N: AsRef<[Link]>>(
);
#[cfg(feature = "logging")]
log::debug!("{}", format_dbg!());

// Clear event
insert_est_time(
&mut est_times,
&mut est_alt,
Expand All @@ -560,6 +617,7 @@ pub fn make_est_times<N: AsRef<[Link]>>(
},
);

// Save this train simulator state to be processed.
saved_sims.push(SavedSim {
train_sim: {
let mut train_sim = Box::new(speed_limit_train_sim.clone());
Expand All @@ -573,7 +631,10 @@ pub fn make_est_times<N: AsRef<[Link]>>(
});
}

// Fix distances for different origins
// -----------------------------------------------------------------------
// Reset distance to zero for any alternate "fake" nodes.
// This helps unify distance offsets for subsequent processing.
// -----------------------------------------------------------------------
#[cfg(feature = "logging")]
log::debug!("{}", format_dbg!("Fix distances for different origins"));
{
Expand All @@ -590,7 +651,9 @@ pub fn make_est_times<N: AsRef<[Link]>>(
let mut est_join_paths_save = Vec::<EstJoinPath>::with_capacity(16);
let mut est_idxs_end = Vec::<EstIdx>::with_capacity(8);

// Iterate and process all saved sims
// -----------------------------------------------------------------------
// Main loop: process each saved simulation until `saved_sims` is empty.
// -----------------------------------------------------------------------
#[cfg(feature = "logging")]
log::debug!("{}", format_dbg!("Iterate and process all saved sims"));
while let Some(mut sim) = saved_sims.pop() {
Expand All @@ -601,19 +664,31 @@ pub fn make_est_times<N: AsRef<[Link]>>(
sim.train_sim.link_points()
);

// -----------------------
// 'path loop: keep updating this train's movement until:
// - we find a join
// - the train finishes
// - or the path unexpectedly ends.
// -----------------------

'path: loop {
// Step 2a: simulate the train for one cycle of movement.
sim.update_movement(&mut movement)
.with_context(|| format_dbg!())?;
// Convert the new movement states into `EstTime` additions.
update_est_times_add(
&mut est_times_add,
&movement,
sim.train_sim.link_points(),
sim.train_sim.state.length,
);

// Step 2b: for each new EstTime, either insert it or try to join an existing path.
for est_time_add in &est_times_add {
// Check for joins only if it has split from an old path
// Only check for joins if we've already split from the main path.
if has_split {
// Attempt to join existing paths if there's a "space match".
// This checks alignment of Arrive events and ensures speeds match.
update_join_paths_space(
&sim.join_paths,
&mut est_join_paths_save,
Expand All @@ -630,6 +705,7 @@ pub fn make_est_times<N: AsRef<[Link]>>(
break 'path;
}

// If not joined, record new potential join paths.
add_new_join_paths(
&est_time_add.link_event,
&link_event_map,
Expand All @@ -639,6 +715,8 @@ pub fn make_est_times<N: AsRef<[Link]>>(
std::mem::swap(&mut sim.join_paths, &mut est_join_paths_save);
est_join_paths_save.clear();
}

// Insert event into the network. If it's a new branch, set `has_split = true`.
if insert_est_time(
&mut est_times,
&mut sim.est_alt,
Expand All @@ -649,15 +727,15 @@ pub fn make_est_times<N: AsRef<[Link]>>(
}
}

// If finished, add destination node to final processing (all links should be clear)
// If the train_sim finished, add destination node to final processing (all links should be clear)
if sim.train_sim.is_finished() {
est_idxs_end.push((est_times.len() - 1).try_into().unwrap());
if consist_out.is_none() {
consist_out = Some(sim.train_sim.loco_con);
}
break;
}
// Otherwise, append the next link options and continue simulating
// Step 2d: Otherwise, append the next link options and continue simulating
else {
let link_idx_prev = &sim.train_sim.link_idx_last().unwrap();
let link_idx_next = network[link_idx_prev.idx()].idx_next;
Expand All @@ -667,7 +745,7 @@ pub fn make_est_times<N: AsRef<[Link]>>(
"Link idx next cannot be fake when making est times! link_idx_prev={link_idx_prev:?}"
);

// all of the next links that will allow reaching destination
// Collect the valid links that will allow reaching destination
let link_idxs_next_valid = [link_idx_next, link_idx_next_alt]
.into_iter()
.filter(|link_idx| link_idx_options.contains(link_idx))
Expand Down Expand Up @@ -695,6 +773,7 @@ pub fn make_est_times<N: AsRef<[Link]>>(
);
}
};
// Extend the path for the current sim with the chosen next link.
sim.train_sim
.extend_path(network, &[link_idx_next])
.with_context(|| format_dbg!())?;
Expand All @@ -703,7 +782,11 @@ pub fn make_est_times<N: AsRef<[Link]>>(
}
}

// Finish the estimated time network
// -----------------------------------------------------------------------
// Link up the final "end" nodes into the estimated time network.
// Here, we add one node per end index and link them together as a chain,
// then mark the final arrival distances/times as zero.
// -----------------------------------------------------------------------
ensure!(est_times.len() < (EstIdx::MAX as usize) - est_idxs_end.len());

let mut est_idx_alt = EST_IDX_NA;
Expand All @@ -720,12 +803,16 @@ pub fn make_est_times<N: AsRef<[Link]>>(
est_times[est_idx_end.idx()].dist_to_next = si::Length::ZERO;
}

// Add the final (fake) node and shrink `est_times` to free unused capacity.
est_times.push(EstTime {
idx_prev: est_times.len() as EstIdx - 1,
..Default::default()
});
est_times.shrink_to_fit();

// -----------------------------------------------------------------------
// Validate that all linked indices are correct and consistent.
// -----------------------------------------------------------------------
for (idx, est_time) in est_times.iter().enumerate() {
// Verify that all prev idxs are valid
assert!((est_time.idx_prev != EST_IDX_NA) != (idx <= 1));
Expand Down Expand Up @@ -759,16 +846,22 @@ pub fn make_est_times<N: AsRef<[Link]>>(
);
}

// -----------------------------------------------------------------------
// Update times forward and backward to generate final scheduling info.
// -----------------------------------------------------------------------
update_times_forward(&mut est_times, time_depart);
update_times_backward(&mut est_times);

// Construct the final EstTimeNet.
let est_time_net = EstTimeNet::new(est_times);

// Sanity check: ensure not all times are zero.
ensure!(
!est_time_net.val.iter().all(|x| x.time_sched == 0. * uc::S),
"All times are 0.0 so something went wrong.\n{}",
format_dbg!()
);

// Return the finished network and the locomotive consist.
Ok((est_time_net, consist_out.unwrap()))
}

Expand Down
2 changes: 1 addition & 1 deletion rust/altrios-core/src/track/link/link_idx.rs
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ impl<'de> Deserialize<'de> for LinkIdx {
D: Deserializer<'de>,
{
struct LinkIdxVisitor;
impl<'de> Visitor<'de> for LinkIdxVisitor {
impl Visitor<'_> for LinkIdxVisitor {
type Value = LinkIdx;

fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
Expand Down
1 change: 0 additions & 1 deletion rust/altrios-core/src/train/set_speed_train_sim.rs
Original file line number Diff line number Diff line change
Expand Up @@ -403,7 +403,6 @@ impl SetSpeedTrainSim {
/// - drag
/// - inertia
/// - acceleration
/// (some of these aren't implemented yet)
pub fn solve_required_pwr(&mut self, dt: si::Time) -> anyhow::Result<()> {
// This calculates the maximum power from loco based on current power, ramp rate, and dt of model. will return 0 if this is negative.
let pwr_pos_max =
Expand Down
20 changes: 8 additions & 12 deletions rust/altrios-core/src/train/train_config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -110,12 +110,9 @@ pub struct TrainConfig {

impl SerdeAPI for TrainConfig {
fn init(&mut self) -> anyhow::Result<()> {
match &self.cd_area_vec {
Some(dcv) => {
// TODO: account for locomotive drag here, too
ensure!(dcv.len() as u32 == self.cars_total());
}
None => {}
if let Some(dcv) = &self.cd_area_vec {
// TODO: account for locomotive drag here, too
ensure!(dcv.len() as u32 == self.cars_total());
};
Ok(())
}
Expand Down Expand Up @@ -915,12 +912,11 @@ pub fn run_speed_limit_train_sims(
.loco_vec
.iter_mut()
.zip(departing_soc_pct_vec)
.for_each(
|(loco, soc)| match &mut loco.reversible_energy_storage_mut() {
Some(loco) => loco.state.soc = soc * uc::R,
None => {}
},
);
.for_each(|(loco, soc)| {
if let Some(loco) = &mut loco.reversible_energy_storage_mut() {
loco.state.soc = soc * uc::R
}
});
let _ = sim
.walk_timed_path(&network, &timed_paths[idx])
.map_err(|err| err.context(format!("train sim idx: {}", idx)));
Expand Down

0 comments on commit 6d79f78

Please sign in to comment.