use causasv::{AsvExplainer, Dag, NodeId};
fn additive_value(coalition: &[NodeId]) -> Result<f64, causasv::CausasvError> {
Ok(coalition.len() as f64)
}
fn diamond_dag() -> Dag {
let mut dag = Dag::new();
let a = dag.add_node("a");
let b = dag.add_node("b");
let c = dag.add_node("c");
let d = dag.add_node("d");
dag.add_edge(a, b).unwrap();
dag.add_edge(a, c).unwrap();
dag.add_edge(b, d).unwrap();
dag.add_edge(c, d).unwrap();
dag
}
fn chain_dag(n: usize) -> Dag {
let mut dag = Dag::new();
let mut prev = dag.add_node("n0");
for i in 1..n {
let cur = dag.add_node(&format!("n{i}"));
dag.add_edge(prev, cur).unwrap();
prev = cur;
}
dag
}
#[test]
fn test_sparse_matches_dense_diamond() {
let dag = diamond_dag();
let explainer = AsvExplainer::new(dag);
let dense = explainer.exact_dag(additive_value).unwrap();
let sparse = explainer.exact_dag_sparse(additive_value).unwrap();
for (id, &dv) in &dense.values {
let sv = sparse.values[id];
assert!(
(dv - sv).abs() < 1e-9,
"mismatch on {id:?}: dense={dv}, sparse={sv}"
);
}
assert!(sparse.is_exact);
assert!(sparse.n_order_ideals.unwrap() <= dense.n_samples);
}
#[test]
fn test_sparse_efficiency_axiom() {
let dag = diamond_dag();
let explainer = AsvExplainer::new(dag);
let result = explainer.exact_dag_sparse(additive_value).unwrap();
let sum: f64 = result.values.values().sum();
assert!(
(sum - 4.0).abs() < 1e-9,
"efficiency axiom: expected 4.0, got {sum}"
);
}
#[test]
fn test_sparse_chain_n22() {
let dag = chain_dag(22);
let explainer = AsvExplainer::new(dag);
let result = explainer.exact_dag_sparse(additive_value).unwrap();
assert!(result.is_exact);
assert_eq!(result.n_order_ideals, Some(23)); for &v in result.values.values() {
assert!((v - 1.0).abs() < 1e-9, "chain value should be 1.0, got {v}");
}
}
#[test]
fn test_sparse_chain_state_ratio() {
let dag = chain_dag(22);
let explainer = AsvExplainer::new(dag);
let result = explainer.exact_dag_sparse(additive_value).unwrap();
let ratio = result.state_ratio.unwrap();
assert!(
ratio < 0.001,
"chain state_ratio should be tiny, got {ratio}"
);
}
#[test]
fn test_sparse_memory_mb_reported() {
let dag = diamond_dag();
let explainer = AsvExplainer::new(dag);
let result = explainer.exact_dag_sparse(additive_value).unwrap();
let mb = result.memory_mb.unwrap();
assert!(mb > 0.0, "memory_mb should be positive");
}
#[test]
#[ignore = "slow: 2^21 BFS states; run with --ignored to verify overflow guard"]
fn test_sparse_overflow_on_large_antichain() {
use causasv::ExactDagConfig;
let mut dag = Dag::new();
for i in 0..21usize {
dag.add_node(&format!("n{i}"));
}
let explainer = AsvExplainer::new(dag);
let config = ExactDagConfig {
max_nodes: 28,
memory_limit_bytes: 4 * 1024 * 1024 * 1024, };
let result = explainer.exact_dag_sparse_with_config(additive_value, &config);
match result {
Err(causasv::CausasvError::Overflow(_)) => {} Err(e) => panic!("expected Overflow, got {e:?}"),
Ok(_) => panic!("expected Overflow error but got Ok — overflow guard is missing"),
}
}