Skip to content

Commit

Permalink
Merge pull request #15 from andyquinterom/T14
Browse files Browse the repository at this point in the history
Add find_all_paths for DG
  • Loading branch information
andyquinterom authored Jun 18, 2024
2 parents d6731fe + dfcf695 commit cf95679
Show file tree
Hide file tree
Showing 4 changed files with 125 additions and 70 deletions.
9 changes: 9 additions & 0 deletions benches/directed_graph.rs
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,15 @@ pub fn criterion_benchmark(c: &mut Criterion) {
})
});

c.bench_function("dg_find_all_paths", |b| {
b.iter(|| {
graph_dg.find_all_paths(
black_box("1781f676dedf5767f3243db0a9738b35"),
black_box("eb85851afd251bd7c7eaf725d0d19360"),
)
})
});

c.bench_function("dg_least_common_parents", |b| {
b.iter(|| graph_dg.least_common_parents(black_box(&graph_all_nodes)))
});
Expand Down
108 changes: 40 additions & 68 deletions src/directed/acyclic/mod.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
use crate::{directed::DirectedGraph, prelude::*};
use std::ops::Deref;
use std::{ops::Deref};
mod topological_sort;
#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};
Expand Down Expand Up @@ -41,57 +41,14 @@ impl DirectedAcyclicGraph {
*self.dg
}

/// Finds path using topological sort
pub fn find_path(
&self,
from: impl AsRef<str>,
to: impl AsRef<str>,
) -> GraphInteractionResult<Vec<&str>> {
let from = self.get_internal(from)?;
let to = self.get_internal(to)?;

if from == to {
return Ok(vec![self.resolve(from)]);
}

let topo_order = self.topological_sort.as_slice();
let start_index = topo_order
.iter()
.position(|id| id == &from)
.expect("Node must be included in topo_order");
let goal_index = topo_order
.iter()
.position(|id| id == &to)
.expect("Node must be included in topo_order");

if goal_index > start_index {
return Ok(vec![]); // No path from start to goal in a DAG if start comes after goal in topo order
}

let path = unsafe { self.dg.u32x1_vec_0() };
let mut current = to;
path.push(current);

// Explore the path using the topological order
for &node_id in &topo_order[goal_index..=start_index] {
if self.edge_exists(node_id, current) {
path.push(node_id);
current = node_id;
if current == from {
path.reverse();
return Ok(self.resolve_mul(path.drain(..)));
}
}
}

Ok(vec![])
}

/// Finds all paths on a DAG using DFS
pub fn find_all_paths(
&self,
from: impl AsRef<str>,
to: impl AsRef<str>,
) -> GraphInteractionResult<Vec<Vec<&str>>> {
const PATH_DELIM: u32 = 0;

// Helper function to perform DFS
#[inline]
fn dfs(
Expand All @@ -108,26 +65,23 @@ impl DirectedAcyclicGraph {
// Check if the current node is the goal
if current == goal_id {
all_paths.extend_from_slice(current_path);
all_paths.push(0);
all_paths.push(PATH_DELIM);
} else {
let children_start_index_local = children_buffer.len();
// Continue to next nodes that can be visited from the current node
graph.children_u32(&[current], children_buffer);
while let Some(child) = children_buffer.pop() {
dfs(
graph,
child,
goal_id,
current_path,
all_paths,
children_buffer,
);
// The use of this buffer is to stop additional
// uneeded allocations
if children_buffer.len() == children_start_index_local {
break;
}
}
(children_start_index_local..children_buffer.len()).for_each(|_| {
match children_buffer.pop() {
Some(child) => dfs(
graph,
child,
goal_id,
current_path,
all_paths,
children_buffer,
),
None => unsafe { std::hint::unreachable_unchecked() },
};
});
}

// Backtrack to explore another path
Expand All @@ -145,8 +99,8 @@ impl DirectedAcyclicGraph {
dfs(self, from, to, current_path, all_paths, children);

Ok(all_paths
.split(|&n| n == 0)
.filter(|path| !path.is_empty())
.split(|&n| n == PATH_DELIM)
.filter(|p| !p.is_empty())
.map(|path| self.resolve_mul(path.iter().copied()))
.collect())
}
Expand Down Expand Up @@ -196,8 +150,26 @@ mod tests {

let path = graph.find_path("0", "4").unwrap();

assert_eq!(path.len(), 5);
assert_eq!(path, ["0", "1", "2", "3", "4"]);
assert_eq!(path.len(), 2);
assert_eq!(path, ["0", "4"]);
}

#[test]
fn test_find_path_no_paths() {
let mut builder = DirectedGraphBuilder::new();
let _ = builder.add_edge("0", "1");
let _ = builder.add_edge("1", "2");
let _ = builder.add_edge("2", "3");
let _ = builder.add_edge("3", "4");
let _ = builder.add_edge("0", "4");
let _ = builder.add_edge("999", "111");

let graph = builder.build_acyclic().unwrap();

let path = graph.find_path("0", "999").unwrap();

assert_eq!(path.len(), 0);
assert_eq!(path, Vec::<&str>::new());
}

#[test]
Expand Down
2 changes: 1 addition & 1 deletion src/directed/get_rel2_on_rel1.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ pub(crate) fn get_values_on_rel_map<H: BuildHasher>(
) {
ids.iter().for_each(|id| {
if let Some(values) = map.get(id) {
values.iter().for_each(|val| out.push(*val))
out.extend(values.iter().copied());
}
})
}
76 changes: 75 additions & 1 deletion src/directed/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ pub(crate) struct InternalBufs {
pub(crate) u32x2_vec_0: UnsafeCell<Vec<(u32, u32)>>,
pub(crate) u32x1_queue_0: UnsafeCell<VecDeque<u32>>,
pub(crate) u32x1_set_0: UnsafeCell<HashSet<u32, FxBuildHasher>>,
pub(crate) usizex2_queue_0: UnsafeCell<VecDeque<(usize, usize)>>,
}

#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
Expand Down Expand Up @@ -245,6 +246,7 @@ impl DirectedGraph {
impl_buf!(u32x2_vec_0, Vec<(u32, u32)>);
impl_buf!(u32x1_queue_0, VecDeque<u32>);
impl_buf!(u32x1_set_0, HashSet<u32, FxBuildHasher>);
impl_buf!(usizex2_queue_0, VecDeque<(usize, usize)>);

#[inline(always)]
pub(crate) fn resolve(&self, val: u32) -> &str {
Expand Down Expand Up @@ -283,6 +285,7 @@ impl DirectedGraph {
.unwrap_or(false)
}

#[inline]
pub(crate) fn children_u32(&self, ids: &[u32], out: &mut Vec<u32>) {
// Gets the children for the given parent
get_values_on_rel_map(ids, &self.children_map, out)
Expand All @@ -306,6 +309,7 @@ impl DirectedGraph {
Ok(self.resolve_mul(res.drain(..)))
}

#[inline]
pub(crate) fn parents_u32(&self, ids: &[u32], out: &mut Vec<u32>) {
// Gets the parents for the given children
get_values_on_rel_map(ids, &self.parent_map, out)
Expand Down Expand Up @@ -419,22 +423,70 @@ impl DirectedGraph {
Ok(self.resolve_mul(path_buf.drain(..)))
}

/// Finds all paths on a DG using BFS
pub fn find_all_paths(
&self,
from: impl AsRef<str>,
to: impl AsRef<str>,
) -> GraphInteractionResult<Vec<Vec<&str>>> {
const PATH_DELIM: u32 = 0;

let from = self.get_internal(from)?;
let to = self.get_internal(to)?;

let path_buf = unsafe { self.u32x1_vec_0() };
let children = unsafe { self.u32x1_vec_1() };
let all_paths = unsafe { self.u32x1_vec_2() };
let queue = unsafe { self.usizex2_queue_0() };

path_buf.push(from);
queue.push_back((0, 0));

while let Some((starti, endi)) = queue.pop_front() {
let last = path_buf[endi];

if last == to {
all_paths.extend_from_slice(&path_buf[starti..=endi]);
all_paths.push(PATH_DELIM);
} else {
self.children_u32(&[last], children);
for child in children.drain(..) {
if !path_buf[starti..=endi].contains(&child) {
let start = path_buf.len();
path_buf.extend_from_within(starti..=endi);
path_buf.push(child);
let end = path_buf.len() - 1;
queue.push_back((start, end));
}
}
}
}

Ok(all_paths
.split(|&n| n == PATH_DELIM)
.filter(|p| !p.is_empty())
.map(|path| self.resolve_mul(path.iter().copied()))
.collect())
}

pub fn least_common_parents(
&self,
selected: impl IntoIterator<Item = impl AsRef<str>>,
) -> GraphInteractionResult<Vec<&str>> {
// Declare used buffers
let selected_buf = unsafe { self.u32x1_vec_0() };
let selected_buf_set = unsafe { self.u32x1_set_0() };
let parents = unsafe { self.u32x1_vec_1() };
let least_common_parents = unsafe { self.u32x1_vec_2() };

self.get_internal_mul(selected, selected_buf)?;
selected_buf_set.extend(selected_buf.iter().copied());

selected_buf.iter().for_each(|&child| {
self.parents_u32(&[child], parents);
let parent_not_in_selection = parents
.drain(..)
.any(|parent| selected_buf.contains(&parent))
.any(|parent| selected_buf_set.contains(&parent))
.not();
if parent_not_in_selection {
least_common_parents.push(child);
Expand Down Expand Up @@ -861,4 +913,26 @@ mod tests {
"# of nodes: 12\n# of edges: 12\n# of roots: 1\n# of leaves: 1\n\n| Parent | Child |\n| ---------- | ---------- |\n| 0000000010 | 0000000011 |\n| 0000000007 | 0000000008 |\n| 0000000004 | 0000000005 |\n| 0000000001 | 0000000002 |\n| 0000000011 | 0000000012 |\n| 0000000008 | 0000000009 |\n| 0000000005 | 0000000006 |\n| 0000000002 | 0000000003 |\n| 0000000012 | 0000000013 |\n| 0000000009 | 0000000010 |\nOmitted 2 nodes\n"
)
}

#[test]
fn test_find_all_paths_many_paths() {
let mut builder = DirectedGraphBuilder::new();
builder.add_path(["0", "111", "222", "333", "444", "4"]);
builder.add_path(["0", "999", "4"]);
builder.add_path(["0", "1", "2", "3", "4"]);
builder.add_path(["0", "4"]);
let graph = builder.build_acyclic().unwrap();

let paths = graph.find_all_paths("0", "4").unwrap();

assert_eq!(
paths,
vec![
vec!["0", "4"],
vec!["0", "999", "4"],
vec!["0", "111", "222", "333", "444", "4"],
vec!["0", "1", "2", "3", "4"],
]
);
}
}

0 comments on commit cf95679

Please sign in to comment.