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

Added docstring for make_est_times #104

Merged
merged 4 commits into from
Jan 7, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
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
Loading