use causasv::{AsvExplainer, CausasvError, Dag};
const EPS: f64 = 1e-9;
fn approx_eq(a: f64, b: f64, eps: f64) -> bool {
(a - b).abs() < eps
}
#[test]
fn test_not_rooted_tree_two_roots() {
let mut dag = Dag::new();
dag.add_node("a");
dag.add_node("b");
let explainer = AsvExplainer::new(dag);
assert!(matches!(
explainer.exact_tree(|s| Ok(s.len() as f64)),
Err(CausasvError::NotRootedTree)
));
}
#[test]
fn test_not_rooted_tree_diamond() {
let mut dag = Dag::new();
let a = dag.add_node("a");
let b = dag.add_node("b");
let c = dag.add_node("c");
dag.add_edge(a, b).unwrap();
dag.add_edge(a, c).unwrap();
dag.add_edge(b, c).unwrap();
let explainer = AsvExplainer::new(dag);
assert!(matches!(
explainer.exact_tree(|s| Ok(s.len() as f64)),
Err(CausasvError::NotRootedTree)
));
}
#[test]
fn test_chain_matches_bruteforce() {
let mut dag = Dag::new();
let nodes: Vec<_> = (0..4).map(|i| dag.add_node(&format!("n{i}"))).collect();
for i in 0..3 {
dag.add_edge(nodes[i], nodes[i + 1]).unwrap();
}
let dag2 = {
let mut d = Dag::new();
let ns: Vec<_> = (0..4).map(|i| d.add_node(&format!("n{i}"))).collect();
for i in 0..3 {
d.add_edge(ns[i], ns[i + 1]).unwrap();
}
d
};
let exact = AsvExplainer::new(dag)
.exact(|s| Ok(s.len() as f64))
.unwrap();
let tree = AsvExplainer::new(dag2)
.exact_tree(|s| Ok(s.len() as f64))
.unwrap();
for node in exact.values.keys() {
assert!(
approx_eq(exact.values[node], tree.values[node], EPS),
"mismatch at {:?}: exact={} tree={}",
node,
exact.values[node],
tree.values[node]
);
}
}
#[test]
fn test_rooted_tree_matches_bruteforce() {
let make_dag = || {
let mut dag = Dag::new();
let r = dag.add_node("r");
let a = dag.add_node("a");
let b = dag.add_node("b");
let c = dag.add_node("c");
dag.add_edge(r, a).unwrap();
dag.add_edge(r, b).unwrap();
dag.add_edge(a, c).unwrap();
dag
};
let exact = AsvExplainer::new(make_dag())
.exact(|s| Ok((s.len() as f64).powi(2)))
.unwrap();
let tree = AsvExplainer::new(make_dag())
.exact_tree(|s| Ok((s.len() as f64).powi(2)))
.unwrap();
for node in exact.values.keys() {
assert!(
approx_eq(exact.values[node], tree.values[node], EPS),
"mismatch at {:?}: exact={} tree={}",
node,
exact.values[node],
tree.values[node]
);
}
}
#[test]
fn test_balanced_tree_matches_bruteforce() {
let make_dag = || {
let mut dag = Dag::new();
let root = dag.add_node("root");
let l = dag.add_node("l");
let r = dag.add_node("r");
let ll = dag.add_node("ll");
let lr = dag.add_node("lr");
let rl = dag.add_node("rl");
let rr = dag.add_node("rr");
dag.add_edge(root, l).unwrap();
dag.add_edge(root, r).unwrap();
dag.add_edge(l, ll).unwrap();
dag.add_edge(l, lr).unwrap();
dag.add_edge(r, rl).unwrap();
dag.add_edge(r, rr).unwrap();
dag
};
let exact = AsvExplainer::new(make_dag())
.exact(|s| Ok((s.len() as f64).powi(2)))
.unwrap();
let tree = AsvExplainer::new(make_dag())
.exact_tree(|s| Ok((s.len() as f64).powi(2)))
.unwrap();
for node in exact.values.keys() {
assert!(
approx_eq(exact.values[node], tree.values[node], EPS),
"mismatch at {:?}: exact={} tree={}",
node,
exact.values[node],
tree.values[node]
);
}
}
#[test]
fn test_caterpillar_matches_bruteforce() {
let make_dag = || {
let mut dag = Dag::new();
let ns: Vec<_> = (0..5).map(|i| dag.add_node(&format!("n{i}"))).collect();
let ls: Vec<_> = (0..5).map(|i| dag.add_node(&format!("l{i}"))).collect();
for i in 0..4 {
dag.add_edge(ns[i], ns[i + 1]).unwrap();
}
for i in 0..5 {
dag.add_edge(ns[i], ls[i]).unwrap();
}
dag
};
let exact = AsvExplainer::new(make_dag())
.exact(|s| Ok((s.len() as f64).powi(2)))
.unwrap();
let tree = AsvExplainer::new(make_dag())
.exact_tree(|s| Ok((s.len() as f64).powi(2)))
.unwrap();
for node in exact.values.keys() {
assert!(
approx_eq(exact.values[node], tree.values[node], EPS),
"mismatch at {:?}: exact={} dp={}",
node,
exact.values[node],
tree.values[node]
);
}
}
#[test]
fn test_exact_tree_is_marked_exact() {
let mut dag = Dag::new();
let r = dag.add_node("r");
let a = dag.add_node("a");
dag.add_edge(r, a).unwrap();
let result = AsvExplainer::new(dag)
.exact_tree(|s| Ok(s.len() as f64))
.unwrap();
assert!(result.is_exact);
assert!(result.seed.is_none());
}
#[test]
fn test_star_matches_bruteforce() {
let make_dag = || {
let mut dag = Dag::new();
let r = dag.add_node("r");
for name in ["a", "b", "c", "d"] {
let child = dag.add_node(name);
dag.add_edge(r, child).unwrap();
}
dag
};
let exact = AsvExplainer::new(make_dag())
.exact(|s| Ok(s.len() as f64))
.unwrap();
let tree = AsvExplainer::new(make_dag())
.exact_tree(|s| Ok(s.len() as f64))
.unwrap();
for node in exact.values.keys() {
assert!(
approx_eq(exact.values[node], tree.values[node], EPS),
"star mismatch at {:?}: exact={} tree={}",
node,
exact.values[node],
tree.values[node]
);
}
}