Skip to content

Commit

Permalink
Added docstring for make_est_times
Browse files Browse the repository at this point in the history
  • Loading branch information
sakhtar committed Jan 4, 2025
1 parent ddd1fd3 commit 6bcbf8f
Showing 1 changed file with 103 additions and 11 deletions.
114 changes: 103 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,73 @@ 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.
/// 2. **Pop** a train from `saved_sims`, then:
/// a) Run the simulation (`update_movement`) until `condition()` in `saved_sim.update_movement()`
/// in `saved_sims.update_movement()` is met.
/// b) 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.
/// c) If the simulation reaches the final destination, add the last node to `est_idxs_end` and **break**.
/// d) 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.)
///
/// 3. **Post-process** the resulting `EstTimeNet` by fixing references, linking final nodes,
/// and updating times forward and backward.
/// 4. **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 of 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 +552,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 +579,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 +598,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 +616,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 +630,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 +650,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 +663,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 +704,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 +714,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 +726,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 +744,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 +772,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 +781,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 +802,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 +845,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

0 comments on commit 6bcbf8f

Please sign in to comment.