adds BFS shortest path

main
Ziyang Hu 2 years ago
parent 5c7bff171c
commit 03d23173cb

@ -14,11 +14,13 @@ use ordered_float::OrderedFloat;
use priority_queue::PriorityQueue;
use smartstring::{LazyCompact, SmartString};
use crate::fixed_rule::{FixedRule, FixedRuleInputRelation, FixedRulePayload, BadExprValueError, NodeNotFoundError};
use crate::data::expr::Expr;
use crate::data::symb::Symbol;
use crate::data::tuple::Tuple;
use crate::data::value::DataValue;
use crate::fixed_rule::{
BadExprValueError, FixedRule, FixedRuleInputRelation, FixedRulePayload, NodeNotFoundError,
};
use crate::parse::SourceSpan;
use crate::runtime::db::Poison;
use crate::runtime::temp_store::RegularTempStore;
@ -32,7 +34,7 @@ impl FixedRule for ShortestPathAStar {
out: &mut RegularTempStore,
poison: Poison,
) -> Result<()> {
let edges = payload.get_input(0)?.ensure_min_len(3)?;
let edges = payload.get_input(0)?.ensure_min_len(2)?;
let nodes = payload.get_input(1)?;
let starting = payload.get_input(2)?;
let goals = payload.get_input(3)?;
@ -124,13 +126,16 @@ fn astar(
for edge in edges.prefix_iter(&node)? {
let edge = edge?;
let edge_dst = &edge[1];
let edge_cost = edge[2].get_float().ok_or_else(|| {
BadExprValueError(
edge_dst.clone(),
edges.span(),
"edge cost must be a number".to_string(),
)
})?;
let edge_cost = match edge.get(2) {
None => 1.,
Some(cost) => cost.get_float().ok_or_else(|| {
BadExprValueError(
edge_dst.clone(),
edges.span(),
"edge cost must be a number".to_string(),
)
})?,
};
ensure!(
!edge_cost.is_nan(),
BadExprValueError(

@ -110,6 +110,6 @@ impl FixedRule for Bfs {
_rule_head: &[Symbol],
_span: SourceSpan,
) -> Result<usize> {
Ok(1)
Ok(3)
}
}

@ -113,6 +113,6 @@ impl FixedRule for Dfs {
_rule_head: &[Symbol],
_span: SourceSpan,
) -> Result<usize> {
Ok(1)
Ok(3)
}
}

@ -35,3 +35,5 @@ pub(crate) mod top_sort;
pub(crate) mod triangles;
#[cfg(feature = "graph-algo")]
pub(crate) mod yen;
#[cfg(feature = "graph-algo")]
pub(crate) mod shortest_path_bfs;

@ -0,0 +1,176 @@
/*
* Copyright 2022, The Cozo Project Authors.
*
* This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0.
* If a copy of the MPL was not distributed with this file,
* You can obtain one at https://mozilla.org/MPL/2.0/.
*/
/*
* Copyright 2022, The Cozo Project Authors.
*
* This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0.
* If a copy of the MPL was not distributed with this file,
* You can obtain one at https://mozilla.org/MPL/2.0/.
*/
use std::collections::{BTreeMap, BTreeSet, VecDeque};
use itertools::Itertools;
use miette::Result;
use smartstring::{LazyCompact, SmartString};
use crate::data::expr::Expr;
use crate::data::symb::Symbol;
use crate::data::value::DataValue;
use crate::fixed_rule::{FixedRule, FixedRulePayload};
use crate::parse::SourceSpan;
use crate::runtime::db::Poison;
use crate::runtime::temp_store::RegularTempStore;
pub(crate) struct ShortestPathBFS;
impl FixedRule for ShortestPathBFS {
fn run(
&self,
payload: FixedRulePayload<'_, '_>,
out: &mut RegularTempStore,
poison: Poison,
) -> Result<()> {
let edges = payload.get_input(0)?.ensure_min_len(2)?;
let starting_nodes: Vec<_> = payload
.get_input(1)?
.ensure_min_len(1)?
.iter()?
.map_ok(|n| n.into_iter().next().unwrap())
.try_collect()?;
let ending_nodes: BTreeSet<_> = payload
.get_input(2)?
.ensure_min_len(1)?
.iter()?
.map_ok(|n| n.into_iter().next().unwrap())
.try_collect()?;
for starting_node in starting_nodes.iter() {
let mut pending: BTreeSet<_> = ending_nodes.clone();
let mut visited: BTreeSet<DataValue> = Default::default();
let mut backtrace: BTreeMap<DataValue, DataValue> = Default::default();
visited.insert(starting_node.clone());
let mut queue: VecDeque<DataValue> = VecDeque::default();
queue.push_front(starting_node.clone());
while let Some(candidate) = queue.pop_back() {
for edge in edges.prefix_iter(&candidate)? {
let edge = edge?;
let to_node = &edge[1];
if visited.contains(to_node) {
continue;
}
visited.insert(to_node.clone());
backtrace.insert(to_node.clone(), candidate.clone());
pending.remove(to_node);
if pending.is_empty() {
break;
}
queue.push_front(to_node.clone());
}
}
for ending_node in ending_nodes.iter() {
if backtrace.contains_key(ending_node) {
let mut route = vec![];
let mut current = ending_node.clone();
while current != *starting_node {
route.push(current.clone());
current = backtrace.get(&current).unwrap().clone();
}
route.push(starting_node.clone());
route.reverse();
let tuple = vec![
starting_node.clone(),
ending_node.clone(),
DataValue::List(route),
];
out.put(tuple);
} else {
out.put(vec![
starting_node.clone(),
ending_node.clone(),
DataValue::Null,
])
}
}
poison.check()?;
}
Ok(())
}
fn arity(
&self,
_options: &BTreeMap<SmartString<LazyCompact>, Expr>,
_rule_head: &[Symbol],
_span: SourceSpan,
) -> Result<usize> {
Ok(3)
}
}
#[cfg(test)]
mod tests {
use serde_json::json;
use crate::new_cozo_mem;
#[test]
fn test_bfs_path() {
let db = new_cozo_mem().unwrap();
let res = db
.run_script(
r#"
love[loving, loved] <- [['alice', 'eve'],
['bob', 'alice'],
['eve', 'alice'],
['eve', 'bob'],
['eve', 'charlie'],
['charlie', 'eve'],
['david', 'george'],
['george', 'george']]
start[] <- [['alice']]
end[] <- [['bob']]
?[fr, to, path] <~ ShortestPathBFS(love[], start[], end[])
"#,
Default::default(),
)
.unwrap()
.rows;
println!("{:?}", res);
assert_eq!(res[0][2].as_array().unwrap().len(), 3);
let res = db
.run_script(
r#"
love[loving, loved] <- [['alice', 'eve'],
['bob', 'alice'],
['eve', 'alice'],
['eve', 'bob'],
['eve', 'charlie'],
['charlie', 'eve'],
['david', 'george'],
['george', 'george']]
start[] <- [['alice']]
end[] <- [['george']]
?[fr, to, path] <~ ShortestPathBFS(love[], start[], end[])
"#,
Default::default(),
)
.unwrap()
.rows;
assert_eq!(res[0][2], json!(null));
println!("{:?}", res);
}
}

@ -45,6 +45,8 @@ use crate::fixed_rule::algos::pagerank::PageRank;
#[cfg(feature = "graph-algo")]
use crate::fixed_rule::algos::prim::MinimumSpanningTreePrim;
#[cfg(feature = "graph-algo")]
use crate::fixed_rule::algos::shortest_path_bfs::ShortestPathBFS;
#[cfg(feature = "graph-algo")]
use crate::fixed_rule::algos::shortest_path_dijkstra::ShortestPathDijkstra;
#[cfg(feature = "graph-algo")]
use crate::fixed_rule::algos::strongly_connected_components::StronglyConnectedComponent;
@ -551,6 +553,11 @@ lazy_static! {
Arc::<Box<dyn FixedRule>>::new(Box::new(Bfs)),
),
#[cfg(feature = "graph-algo")]
(
"ShortestPathBFS".to_string(),
Arc::<Box<dyn FixedRule>>::new(Box::new(ShortestPathBFS)),
),
#[cfg(feature = "graph-algo")]
(
"ShortestPathDijkstra".to_string(),
Arc::<Box<dyn FixedRule>>::new(Box::new(ShortestPathDijkstra)),

Loading…
Cancel
Save