332 lines
12 KiB
Rust
332 lines
12 KiB
Rust
//! Shortest path computation over a topology.
|
||
//!
|
||
//! 某拓扑上的最短路径计算。
|
||
//!
|
||
//! The algorithm is Dijkstra's, using a binary heap keyed by the tentative
|
||
//! distance. Costs are positive, so a node's distance is final when it is
|
||
//! first removed from the heap, which makes the algorithm exact.
|
||
//!
|
||
//! The heap is a minimum heap over a reversed ordering key, because the
|
||
//! standard library's [`BinaryHeap`](std::collections::BinaryHeap) is a
|
||
//! maximum heap. The key is the tentative distance, and ties are broken by
|
||
//! the node index so that the result is deterministic for a given topology.
|
||
|
||
use crate::error::{RoutingError, RoutingErrorKind};
|
||
use crate::route::{Route, RouteLeg, RouteStep};
|
||
use apsis_topology::{Topology, TopologySeries};
|
||
use core::cmp::Ordering;
|
||
use std::collections::BinaryHeap;
|
||
|
||
/// A heap entry holding a node and its tentative distance.
|
||
///
|
||
/// 堆中的一项,保存某个节点及其暂定距离。
|
||
#[derive(Debug, Clone, Copy, PartialEq)]
|
||
struct Candidate {
|
||
/// The tentative distance from the source.
|
||
///
|
||
/// 自源点出发的暂定距离。
|
||
distance: f64,
|
||
/// The node index.
|
||
///
|
||
/// 节点下标。
|
||
node: usize,
|
||
}
|
||
|
||
impl Eq for Candidate {}
|
||
|
||
impl Ord for Candidate {
|
||
fn cmp(&self, other: &Self) -> Ordering {
|
||
// Reverse the distance comparison so that the smallest distance is
|
||
// the greatest element, which is what the maximum heap needs. Equal
|
||
// distances are ordered by node index so that the sequence of
|
||
// relaxations is reproducible.
|
||
// 反转距离比较,使最小距离成为最大元素,这正是最大堆所需。距离
|
||
// 相等时按节点下标排序,使松弛的次序可复现。
|
||
other
|
||
.distance
|
||
.partial_cmp(&self.distance)
|
||
.unwrap_or(Ordering::Equal)
|
||
.then_with(|| other.node.cmp(&self.node))
|
||
}
|
||
}
|
||
|
||
impl PartialOrd for Candidate {
|
||
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
|
||
Some(self.cmp(other))
|
||
}
|
||
}
|
||
|
||
/// The result of a single source shortest path computation.
|
||
///
|
||
/// 单源最短路计算的结果。
|
||
///
|
||
/// The distances are to every node reachable from the source, and the
|
||
/// predecessor map records the edge by which each node was reached. Both are
|
||
/// what a routing table needs, and a single computation fills a whole row of
|
||
/// it.
|
||
#[derive(Debug, Clone, PartialEq)]
|
||
pub struct PathTree {
|
||
/// The source node.
|
||
///
|
||
/// 源节点。
|
||
pub source: usize,
|
||
/// The distance to each node, or infinity when it is unreachable.
|
||
///
|
||
/// 到各节点的距离;不可达时为无穷。
|
||
pub distances: Vec<f64>,
|
||
/// The predecessor of each node on the shortest path, if any.
|
||
///
|
||
/// 各节点在最短路上的前驱,若有。
|
||
pub predecessors: Vec<Option<usize>>,
|
||
}
|
||
|
||
impl PathTree {
|
||
/// Returns the distance to a node.
|
||
///
|
||
/// 返回到某节点的距离。
|
||
///
|
||
/// Returns `None` when the node index is out of range or the node is
|
||
/// unreachable.
|
||
pub fn distance(&self, node: usize) -> Option<f64> {
|
||
let distance = *self.distances.get(node)?;
|
||
distance.is_finite().then_some(distance)
|
||
}
|
||
|
||
/// Returns `true` when the node is reachable from the source.
|
||
///
|
||
/// 当该节点自源点可达时返回 `true`。
|
||
pub fn is_reachable(&self, node: usize) -> bool {
|
||
self.distance(node).is_some()
|
||
}
|
||
|
||
/// Returns the number of reachable nodes, including the source itself.
|
||
///
|
||
/// 返回可达节点的数量,含源点自身。
|
||
///
|
||
/// 返回可达节点的数量,含源点自身。
|
||
pub fn reachable_count(&self) -> usize {
|
||
self.distances
|
||
.iter()
|
||
.filter(|value| value.is_finite())
|
||
.count()
|
||
}
|
||
|
||
/// Reconstructs the route to a node.
|
||
///
|
||
/// 重建到某节点的路由。
|
||
///
|
||
/// Returns an error when the node is out of range or unreachable.
|
||
pub fn route_to(&self, destination: usize, topology: &Topology) -> Result<Route, RoutingError> {
|
||
if destination >= self.distances.len() {
|
||
return Err(RoutingError::new(RoutingErrorKind::NoSuchNode)
|
||
.with_detail(format!("node {destination} of {}", self.distances.len())));
|
||
}
|
||
if !self.is_reachable(destination) {
|
||
return Err(RoutingError::new(RoutingErrorKind::NoRoute)
|
||
.with_detail(format!("no route from {} to {destination}", self.source)));
|
||
}
|
||
|
||
// Walk the predecessor chain back to the source, then reverse it.
|
||
// 沿前驱链回溯到源点,然后反转。
|
||
let mut nodes = vec![destination];
|
||
let mut current = destination;
|
||
while current != self.source {
|
||
let previous = self.predecessors[current].ok_or_else(|| {
|
||
RoutingError::new(RoutingErrorKind::NoRoute)
|
||
.with_detail(format!("the predecessor chain breaks at {current}"))
|
||
})?;
|
||
nodes.push(previous);
|
||
current = previous;
|
||
}
|
||
nodes.reverse();
|
||
|
||
let mut steps = Vec::with_capacity(nodes.len().saturating_sub(1));
|
||
for pair in nodes.windows(2) {
|
||
let cost = topology.cost(pair[0], pair[1]).ok_or_else(|| {
|
||
RoutingError::new(RoutingErrorKind::NoRoute).with_detail(format!(
|
||
"the link from {} to {} is absent",
|
||
pair[0], pair[1]
|
||
))
|
||
})?;
|
||
steps.push(RouteStep::new(pair[0], pair[1], cost));
|
||
}
|
||
Ok(Route::new(nodes, steps))
|
||
}
|
||
}
|
||
|
||
/// Computes the shortest paths from one source to every reachable node.
|
||
///
|
||
/// Returns an error when the source node is out of range.
|
||
///
|
||
/// 计算自一个源点到每个可达节点的最短距离。
|
||
///
|
||
/// 源节点超出取值范围时返回错误。
|
||
///
|
||
/// Source: Dijkstra, E. W., *A note on two problems in connexion with
|
||
/// graphs*, Numerische Mathematik 1, 1959.
|
||
pub fn shortest_paths_from(topology: &Topology, source: usize) -> Result<PathTree, RoutingError> {
|
||
if source >= topology.node_count() {
|
||
return Err(RoutingError::new(RoutingErrorKind::NoSuchNode)
|
||
.with_detail(format!("node {source} of {}", topology.node_count())));
|
||
}
|
||
|
||
let mut distances = vec![f64::INFINITY; topology.node_count()];
|
||
let mut predecessors = vec![None; topology.node_count()];
|
||
let mut settled = vec![false; topology.node_count()];
|
||
distances[source] = 0.0;
|
||
|
||
let mut heap = BinaryHeap::new();
|
||
heap.push(Candidate {
|
||
distance: 0.0,
|
||
node: source,
|
||
});
|
||
|
||
while let Some(Candidate { distance, node }) = heap.pop() {
|
||
// A stale entry: the node already has a shorter distance.
|
||
// 过时项:该节点已有更短的距离。
|
||
if settled[node] {
|
||
continue;
|
||
}
|
||
settled[node] = true;
|
||
|
||
for edge in topology.edges().iter().filter(|edge| edge.from == node) {
|
||
let candidate = distance + edge.cost;
|
||
if candidate < distances[edge.to] {
|
||
distances[edge.to] = candidate;
|
||
predecessors[edge.to] = Some(node);
|
||
heap.push(Candidate {
|
||
distance: candidate,
|
||
node: edge.to,
|
||
});
|
||
}
|
||
}
|
||
}
|
||
|
||
Ok(PathTree {
|
||
source,
|
||
distances,
|
||
predecessors,
|
||
})
|
||
}
|
||
|
||
/// 返回两节点之间的最短路由。
|
||
///
|
||
/// Returns an error when either node is out of range, when they are the
|
||
/// same node, or when no route exists.
|
||
pub fn shortest_path(
|
||
topology: &Topology,
|
||
source: usize,
|
||
destination: usize,
|
||
) -> Result<Route, RoutingError> {
|
||
if source == destination {
|
||
return Err(RoutingError::new(RoutingErrorKind::SameEndpoint)
|
||
.with_detail(format!("both endpoints are node {source}")));
|
||
}
|
||
let tree = shortest_paths_from(topology, source)?;
|
||
tree.route_to(destination, topology)
|
||
}
|
||
|
||
/// Returns the shortest route valid across an interval of a topology series.
|
||
///
|
||
/// The route is computed over the links that are present throughout the
|
||
/// interval, so it remains valid for the whole of it. The interval is taken
|
||
/// from the start of the sample in force at `start_seconds` to the start of
|
||
/// the sample in force at `end_seconds`, and the links of every sample in
|
||
/// that span are intersected.
|
||
///
|
||
/// The returned leg records the interval the route is valid for, which is
|
||
/// bounded by the next topology change after `end_seconds`.
|
||
///
|
||
/// Returns an error when either node is out of range or the same, when the
|
||
/// interval is not ordered, or when no route survives the whole interval.
|
||
///
|
||
/// Source: the store and forward routing model over a time-varying graph is
|
||
/// the one described in Fraire, J. A. et al., *Routing in Delay-Tolerant
|
||
/// Networks Comprising Heterogeneous Populations of Deterministic Link
|
||
/// Dynamics*, IEEE Transactions on Mobile Computing, 2020.
|
||
///
|
||
/// 返回在拓扑序列某区间上有效的最短路由。
|
||
///
|
||
/// 该路由在贯穿整个区间都存在的链路上计算,故它在整个区间上保持有效。
|
||
/// 该区间取自 `start_seconds` 处生效样本的起点,到 `end_seconds` 处生效
|
||
/// 样本的起点,并对该跨度内每个样本的链路求交。
|
||
///
|
||
/// 所返回的路段记录该路由有效的区间,它以 `end_seconds` 之后的下一处
|
||
/// 拓扑变化为界。
|
||
///
|
||
/// 任一节点越界或两者相同、区间次序不对,或没有路由能存活整个区间时
|
||
/// 返回错误。
|
||
///
|
||
/// 出处:时变图上的存储转发路由模型见 Fraire, J. A. 等,*Routing in
|
||
/// Delay-Tolerant Networks Comprising Heterogeneous Populations of
|
||
/// Deterministic Link Dynamics*,IEEE Transactions on Mobile Computing,
|
||
/// 2020。
|
||
pub fn route_over_series(
|
||
series: &TopologySeries,
|
||
source: usize,
|
||
destination: usize,
|
||
start_seconds: f64,
|
||
end_seconds: f64,
|
||
) -> Result<RouteLeg, RoutingError> {
|
||
if !start_seconds.is_finite() || !end_seconds.is_finite() || end_seconds < start_seconds {
|
||
return Err(
|
||
RoutingError::new(RoutingErrorKind::InvalidInterval).with_detail(format!(
|
||
"the interval runs from {start_seconds} to {end_seconds}"
|
||
)),
|
||
);
|
||
}
|
||
if source >= series.node_count() || destination >= series.node_count() {
|
||
return Err(
|
||
RoutingError::new(RoutingErrorKind::NoSuchNode).with_detail(format!(
|
||
"nodes {source} and {destination} of {}",
|
||
series.node_count()
|
||
)),
|
||
);
|
||
}
|
||
if source == destination {
|
||
return Err(RoutingError::new(RoutingErrorKind::SameEndpoint)
|
||
.with_detail(format!("both endpoints are node {source}")));
|
||
}
|
||
|
||
let first = series.index_at(start_seconds).ok_or_else(|| {
|
||
RoutingError::new(RoutingErrorKind::InvalidInterval)
|
||
.with_detail("the series has no sample at the start of the interval")
|
||
})?;
|
||
let last = series.index_at(end_seconds).ok_or_else(|| {
|
||
RoutingError::new(RoutingErrorKind::InvalidInterval)
|
||
.with_detail("the series has no sample at the end of the interval")
|
||
})?;
|
||
if last > series.len() - 1 {
|
||
return Err(RoutingError::new(RoutingErrorKind::SeriesTooShort)
|
||
.with_detail("the interval extends past the series"));
|
||
}
|
||
|
||
// Intersect the links of every sample spanned by the interval, taking
|
||
// the cost from the first sample that has each link.
|
||
// 对该区间所跨的每个样本的链路求交,代价取自拥有每条链路的第一
|
||
// 个样本。
|
||
let mut intersection = Topology::new(series.node_count());
|
||
let reference = &series.samples()[first].topology;
|
||
for edge in reference.edges() {
|
||
let present_everywhere = (first..=last).all(|index| {
|
||
series.samples()[index]
|
||
.topology
|
||
.has_edge(edge.from, edge.to)
|
||
});
|
||
if present_everywhere {
|
||
// 链路已在上方校验,故此处的失败意味着
|
||
// reference topology contradicts itself.
|
||
let _ = intersection.add_edge(edge.from, edge.to, edge.cost);
|
||
}
|
||
}
|
||
|
||
let route = shortest_path(&intersection, source, destination)?;
|
||
|
||
// The leg is valid until the topology changes after the interval ends.
|
||
// 该路段有效直到区间结束之后的拓扑变化。
|
||
let end_of_validity = series
|
||
.interval_of(last)
|
||
.map_or(end_seconds, |(_, next)| next);
|
||
Ok(RouteLeg::new(route, start_seconds, end_of_validity))
|
||
}
|