use crate::brcd::brcd_error::{BrcdError, BrcdErrorEnum};
use crate::brcd::brcd_validity::{baseline_parents, is_valid_configuration};
use deep_causality_tensor::CausalTensor;
use deep_causality_topology::MixedGraph;
use std::collections::BTreeSet;
pub const MAX_CONFIG_EDGES: usize = 16;
pub fn f_node_indicator(n_normal: usize, n_anomalous: usize) -> Vec<bool> {
let mut f = vec![false; n_normal];
f.extend(std::iter::repeat_n(true, n_anomalous));
f
}
pub fn get_configurations_multi<N: Clone>(
cpdag: &MixedGraph<N>,
targets: &[usize],
) -> Result<Vec<MixedGraph<N>>, BrcdError> {
let n = cpdag.num_vertices();
if targets.iter().any(|&t| t >= n) {
return Err(BrcdError(BrcdErrorEnum::NodeOutOfBounds));
}
let incident = incident_undirected_edges(cpdag, targets);
let e = incident.len();
if e > MAX_CONFIG_EDGES {
return Err(BrcdError(BrcdErrorEnum::ConfigSpaceTooLarge { edges: e }));
}
let baseline = baseline_parents(cpdag, targets);
let mut configs = Vec::new();
for combo in 0..(1usize << e) {
let mut g = cpdag.clone();
for (i, &(a, b)) in incident.iter().enumerate() {
if (combo >> i) & 1 == 0 {
g.orient(a, b)
.expect("incident edge is undirected in the clone");
} else {
g.orient(b, a)
.expect("incident edge is undirected in the clone");
}
}
if is_valid_configuration(&mut g, targets, &baseline) {
configs.push(g);
}
}
Ok(configs)
}
pub fn augmented_graph<N>(
config: &MixedGraph<N>,
roots: &[usize],
) -> Result<MixedGraph<()>, BrcdError> {
let n = config.num_vertices();
if roots.iter().any(|&r| r >= n) {
return Err(BrcdError(BrcdErrorEnum::NodeOutOfBounds));
}
let fnode = n;
let data = CausalTensor::new(vec![(); n + 1], vec![n + 1])
.expect("unit payload of length n+1 is a valid 1-D tensor");
let mut aug = MixedGraph::<()>::new(n + 1, data, 0)
.expect("n+1 ≥ 1 with a matching payload and cursor 0");
for (&(a, b), edge) in config.edges() {
aug.add_edge(a, b, edge.lo, edge.hi)
.expect("copying a canonical edge into a fresh graph cannot conflict");
}
for &root in roots {
aug.add_arc(fnode, root)
.expect("FNODE → root is a fresh edge to a new vertex");
}
Ok(aug)
}
pub(crate) fn incident_undirected_edges<N>(
cpdag: &MixedGraph<N>,
targets: &[usize],
) -> Vec<(usize, usize)> {
let target_set: BTreeSet<usize> = targets.iter().copied().collect();
cpdag
.undirected_edges()
.into_iter()
.filter(|&(a, b)| target_set.contains(&a) || target_set.contains(&b))
.collect()
}