use std::collections::HashMap;
use crate::asv::AsvResult;
use crate::cache::value_cached;
use crate::error::CausasvError;
use crate::graph::{Dag, NodeId};
pub(crate) fn dag_exact_asv<F>(dag: &Dag, value_fn: F) -> Result<AsvResult, CausasvError>
where
F: Fn(&[NodeId]) -> Result<f64, CausasvError>,
{
let n = dag.node_count();
if n > 20 {
return Err(CausasvError::InvalidConfig(format!(
"exact_dag requires n ≤ 20 (2^n DP states), got {n}"
)));
}
let parents_mask: Vec<u64> = (0..n)
.map(|i| {
dag.parents_raw(NodeId(i as u32))
.iter()
.fold(0u64, |m, &p| m | (1u64 << p.0))
})
.collect();
let total_masks = 1usize << n;
let full_mask = total_masks - 1;
let mut dp_fwd = vec![0u64; total_masks];
dp_fwd[0] = 1;
for mask in 0..total_masks {
if dp_fwd[mask] == 0 {
continue;
}
let mask64 = mask as u64;
for (i, &pmask) in parents_mask.iter().enumerate() {
if mask64 & (1 << i) != 0 {
continue; }
if pmask & mask64 == pmask {
let next = mask | (1 << i);
dp_fwd[next] = dp_fwd[next].checked_add(dp_fwd[mask]).ok_or_else(|| {
CausasvError::Overflow(format!(
"dp_fwd[{mask:#b}] overflowed u64 — use approx for this DAG"
))
})?;
}
}
}
let mut dp_ind = vec![0u64; total_masks];
dp_ind[0] = 1;
for mask in 1..total_masks {
let mask64 = mask as u64;
for (i, &pmask) in parents_mask.iter().enumerate() {
if mask64 & (1u64 << i) == 0 {
continue; }
if pmask & mask64 == 0 {
let prev = dp_ind[mask ^ (1usize << i)];
dp_ind[mask] = dp_ind[mask].checked_add(prev).ok_or_else(|| {
CausasvError::Overflow(format!(
"dp_ind[{mask:#b}] overflowed u64 — use approx for this DAG"
))
})?;
}
}
}
let total = dp_ind[full_mask] as f64;
let mut phi = vec![0.0f64; n];
let mut cache = HashMap::<u64, f64>::new();
for (mask, &fwd_count) in dp_fwd.iter().enumerate() {
if fwd_count == 0 {
continue; }
let mask64 = mask as u64;
let w_prefix = fwd_count as f64;
for i in 0..n {
if mask64 & (1u64 << i) != 0 {
continue; }
if parents_mask[i] & mask64 != parents_mask[i] {
continue; }
let with_i_mask = mask | (1usize << i);
let suffix_mask = full_mask ^ with_i_mask; let w_suffix = dp_ind[suffix_mask] as f64;
let mask_with_i = mask64 | (1u64 << i);
phi[i] += w_prefix
* w_suffix
* (value_cached(&mut cache, &value_fn, mask_with_i)?
- value_cached(&mut cache, &value_fn, mask64)?);
}
}
let values = (0..n).map(|i| (NodeId(i as u32), phi[i] / total)).collect();
Ok(AsvResult {
values,
n_samples: total_masks,
seed: None,
is_exact: true,
effective_sample_size: None,
converged: None,
stderr: None,
n_order_ideals: None,
state_ratio: None,
memory_mb: None,
})
}