use std::collections::HashMap;
use std::collections::hash_map::Entry;
use crate::csr::CsrIndex;
use crate::overlay_delta::GraphOverlayDelta;
use crate::path_params::ShortestPathParams;
impl CsrIndex {
pub(crate) fn shortest_path_overlay(
&self,
params: ShortestPathParams<'_>,
overlay: &GraphOverlayDelta,
) -> Option<Vec<String>> {
let ShortestPathParams {
src,
dst,
label_filter,
max_depth,
max_visited,
frontier_bitmap,
} = params;
if src == dst {
return Some(vec![src.to_string()]);
}
let mut fwd_parent: HashMap<String, String> = HashMap::new();
let mut bwd_parent: HashMap<String, String> = HashMap::new();
fwd_parent.insert(src.to_string(), src.to_string());
bwd_parent.insert(dst.to_string(), dst.to_string());
let mut fwd_frontier: Vec<String> = vec![src.to_string()];
let mut bwd_frontier: Vec<String> = vec![dst.to_string()];
let label_id = label_filter.and_then(|l| self.label_id(l));
for _depth in 0..max_depth {
if fwd_parent.len() + bwd_parent.len() >= max_visited {
break;
}
let mut next_fwd = Vec::new();
for node in std::mem::take(&mut fwd_frontier) {
let neighbors =
self.forward_neighbors(&node, label_id, label_filter, frontier_bitmap, overlay);
for neighbor in neighbors {
if let Some(meeting) = relax(
&neighbor,
&node,
&mut fwd_parent,
&bwd_parent,
&mut next_fwd,
) {
return Some(reconstruct(&meeting, &fwd_parent, &bwd_parent));
}
}
}
fwd_frontier = next_fwd;
let mut next_bwd = Vec::new();
for node in std::mem::take(&mut bwd_frontier) {
let neighbors = self.backward_neighbors(
&node,
label_id,
label_filter,
frontier_bitmap,
overlay,
);
for neighbor in neighbors {
if let Some(meeting) = relax(
&neighbor,
&node,
&mut bwd_parent,
&fwd_parent,
&mut next_bwd,
) {
return Some(reconstruct(&meeting, &fwd_parent, &bwd_parent));
}
}
}
bwd_frontier = next_bwd;
if fwd_frontier.is_empty() && bwd_frontier.is_empty() {
break;
}
}
None
}
fn forward_neighbors(
&self,
node: &str,
label_id: Option<u32>,
label_filter: Option<&str>,
frontier_bitmap: Option<&nodedb_types::SurrogateBitmap>,
overlay: &GraphOverlayDelta,
) -> Vec<String> {
let mut out = Vec::new();
if let Some(&node_id) = self.node_to_id.get(node) {
self.record_access(node_id);
for (lid, dst) in self.dense_iter_out(node_id) {
if label_id.is_some_and(|f| f != lid) {
continue;
}
let dst_name = &self.id_to_node[dst as usize];
if overlay.is_tombstoned(node, self.label_name(lid), dst_name) {
continue;
}
if !frontier_bitmap.is_none_or(|bm| {
bm.contains(nodedb_types::Surrogate::new(self.node_surrogate_raw(dst)))
}) {
continue;
}
out.push(dst_name.clone());
}
}
for (_, dst) in overlay.out_neighbors(node, label_filter) {
out.push(dst.to_string());
}
out
}
fn backward_neighbors(
&self,
node: &str,
label_id: Option<u32>,
label_filter: Option<&str>,
frontier_bitmap: Option<&nodedb_types::SurrogateBitmap>,
overlay: &GraphOverlayDelta,
) -> Vec<String> {
let mut out = Vec::new();
if let Some(&node_id) = self.node_to_id.get(node) {
self.record_access(node_id);
for (lid, src) in self.dense_iter_in(node_id) {
if label_id.is_some_and(|f| f != lid) {
continue;
}
let src_name = &self.id_to_node[src as usize];
if overlay.is_tombstoned(src_name, self.label_name(lid), node) {
continue;
}
if !frontier_bitmap.is_none_or(|bm| {
bm.contains(nodedb_types::Surrogate::new(self.node_surrogate_raw(src)))
}) {
continue;
}
out.push(src_name.clone());
}
}
for (_, src) in overlay.in_neighbors(node, label_filter) {
out.push(src.to_string());
}
out
}
}
fn relax(
neighbor: &str,
parent: &str,
this_parent: &mut HashMap<String, String>,
other_parent: &HashMap<String, String>,
next_frontier: &mut Vec<String>,
) -> Option<String> {
if let Entry::Vacant(e) = this_parent.entry(neighbor.to_string()) {
e.insert(parent.to_string());
next_frontier.push(neighbor.to_string());
}
if other_parent.contains_key(neighbor) {
return Some(neighbor.to_string());
}
None
}
fn reconstruct(
meeting: &str,
fwd_parent: &HashMap<String, String>,
bwd_parent: &HashMap<String, String>,
) -> Vec<String> {
let mut path = walk_to_root(meeting, fwd_parent);
path.reverse();
let suffix_root = &bwd_parent[meeting];
if suffix_root != meeting {
let mut cur = suffix_root.clone();
loop {
path.push(cur.clone());
let parent = &bwd_parent[&cur];
if *parent == cur {
break;
}
cur = parent.clone();
}
}
path
}
fn walk_to_root(start: &str, parents: &HashMap<String, String>) -> Vec<String> {
let mut chain = Vec::new();
let mut cur = start.to_string();
loop {
chain.push(cur.clone());
let parent = &parents[&cur];
if *parent == cur {
break;
}
cur = parent.clone();
}
chain
}
#[cfg(test)]
mod tests {
use crate::csr::CsrIndex;
use crate::overlay_delta::GraphOverlayDelta;
use crate::path_params::ShortestPathParams;
use crate::traversal::DEFAULT_MAX_VISITED;
fn params<'a>(
src: &'a str,
dst: &'a str,
label_filter: Option<&'a str>,
max_depth: usize,
) -> ShortestPathParams<'a> {
ShortestPathParams {
src,
dst,
label_filter,
max_depth,
max_visited: DEFAULT_MAX_VISITED,
frontier_bitmap: None,
}
}
#[test]
fn staged_edge_completes_path() {
let mut csr = CsrIndex::new();
csr.add_edge("a", "KNOWS", "b").unwrap();
let mut ov = GraphOverlayDelta::new();
ov.stage_edge("b", "KNOWS", "c");
let path = csr
.shortest_path(params("a", "c", Some("KNOWS"), 5), Some(&ov))
.expect("staged edge should complete the path");
assert_eq!(path, vec!["a", "b", "c"]);
assert!(
csr.shortest_path(params("a", "c", Some("KNOWS"), 5), None)
.is_none()
);
}
#[test]
fn path_through_staged_only_node() {
let csr = CsrIndex::new();
let mut ov = GraphOverlayDelta::new();
ov.stage_edge("a", "KNOWS", "x");
ov.stage_edge("x", "KNOWS", "d");
let path = csr
.shortest_path(params("a", "d", Some("KNOWS"), 5), Some(&ov))
.expect("staged-only path should be found");
assert_eq!(path, vec!["a", "x", "d"]);
}
#[test]
fn tombstone_forces_detour() {
let mut csr = CsrIndex::new();
csr.add_edge("a", "KNOWS", "d").unwrap();
csr.add_edge("a", "KNOWS", "b").unwrap();
csr.add_edge("b", "KNOWS", "d").unwrap();
let mut ov = GraphOverlayDelta::new();
ov.stage_tombstone("a", "KNOWS", "d");
let path = csr
.shortest_path(params("a", "d", Some("KNOWS"), 5), Some(&ov))
.expect("detour path should be found");
assert_eq!(path, vec!["a", "b", "d"]);
}
#[test]
fn tombstone_removes_only_path() {
let mut csr = CsrIndex::new();
csr.add_edge("a", "KNOWS", "d").unwrap();
let mut ov = GraphOverlayDelta::new();
ov.stage_tombstone("a", "KNOWS", "d");
assert!(
csr.shortest_path(params("a", "d", Some("KNOWS"), 5), Some(&ov))
.is_none()
);
}
#[test]
fn empty_overlay_matches_dense() {
let mut csr = CsrIndex::new();
csr.add_edge("a", "KNOWS", "b").unwrap();
csr.add_edge("b", "KNOWS", "c").unwrap();
csr.add_edge("c", "KNOWS", "d").unwrap();
let ov = GraphOverlayDelta::new();
let dense = csr
.shortest_path(params("a", "d", Some("KNOWS"), 10), None)
.unwrap();
let overlaid = csr.shortest_path_overlay(params("a", "d", Some("KNOWS"), 10), &ov);
assert_eq!(overlaid, Some(dense));
}
#[test]
fn src_equals_dst() {
let mut csr = CsrIndex::new();
csr.add_edge("a", "KNOWS", "b").unwrap();
let mut ov = GraphOverlayDelta::new();
ov.stage_edge("a", "KNOWS", "x");
let path = csr
.shortest_path(params("a", "a", None, 5), Some(&ov))
.unwrap();
assert_eq!(path, vec!["a"]);
}
#[test]
fn unreachable_is_none() {
let mut csr = CsrIndex::new();
csr.add_edge("a", "KNOWS", "b").unwrap();
let mut ov = GraphOverlayDelta::new();
ov.stage_edge("m", "KNOWS", "n");
assert!(
csr.shortest_path(params("a", "n", Some("KNOWS"), 5), Some(&ov))
.is_none()
);
}
}