use crate::error::CausasvError;
use crate::graph::{Dag, NodeId};
use indexmap::IndexMap;
#[derive(Clone, Debug)]
pub struct Cpdag {
name_to_id: IndexMap<String, NodeId>,
names: Vec<String>,
directed_children: Vec<Vec<NodeId>>,
directed_parents: Vec<Vec<NodeId>>,
undirected: Vec<Vec<NodeId>>,
}
impl Cpdag {
pub fn new() -> Self {
Self {
name_to_id: IndexMap::new(),
names: Vec::new(),
directed_children: Vec::new(),
directed_parents: Vec::new(),
undirected: Vec::new(),
}
}
pub fn add_node(&mut self, name: &str) -> NodeId {
if let Some(&id) = self.name_to_id.get(name) {
return id;
}
let id = NodeId(self.names.len() as u32);
self.name_to_id.insert(name.to_string(), id);
self.names.push(name.to_string());
self.directed_children.push(Vec::new());
self.directed_parents.push(Vec::new());
self.undirected.push(Vec::new());
id
}
pub fn add_directed_edge(&mut self, from: NodeId, to: NodeId) -> Result<(), CausasvError> {
if from == to {
return Err(CausasvError::SelfLoop(from));
}
self.check_id(from)?;
self.check_id(to)?;
if self.directed_children[from.idx()].contains(&to) {
return Err(CausasvError::DuplicateEdge(from, to));
}
if self.directed_children[to.idx()].contains(&from)
|| self.undirected[from.idx()].contains(&to)
{
return Err(CausasvError::ConflictingEdge(from, to));
}
self.directed_children[from.idx()].push(to);
self.directed_parents[to.idx()].push(from);
Ok(())
}
pub fn add_undirected_edge(&mut self, a: NodeId, b: NodeId) -> Result<(), CausasvError> {
if a == b {
return Err(CausasvError::SelfLoop(a));
}
self.check_id(a)?;
self.check_id(b)?;
if self.undirected[a.idx()].contains(&b) {
return Err(CausasvError::DuplicateEdge(a, b));
}
if self.directed_children[a.idx()].contains(&b)
|| self.directed_children[b.idx()].contains(&a)
{
return Err(CausasvError::ConflictingEdge(a, b));
}
self.undirected[a.idx()].push(b);
self.undirected[b.idx()].push(a);
Ok(())
}
pub fn node_count(&self) -> usize {
self.names.len()
}
pub fn node_name(&self, id: NodeId) -> Option<&str> {
self.names.get(id.idx()).map(String::as_str)
}
pub fn node_id(&self, name: &str) -> Option<NodeId> {
self.name_to_id.get(name).copied()
}
pub fn all_nodes(&self) -> impl Iterator<Item = NodeId> + '_ {
(0..self.names.len()).map(|i| NodeId(i as u32))
}
pub fn directed_edges(&self) -> impl Iterator<Item = (NodeId, NodeId)> + '_ {
self.all_nodes().flat_map(move |from| {
self.directed_children[from.idx()]
.iter()
.map(move |&to| (from, to))
})
}
pub fn undirected_edges(&self) -> impl Iterator<Item = (NodeId, NodeId)> + '_ {
self.all_nodes().flat_map(move |a| {
self.undirected[a.idx()]
.iter()
.filter(move |&&b| b > a)
.map(move |&b| (a, b))
})
}
pub fn validate_pdag(&self) -> Result<(), CausasvError> {
if self.names.is_empty() {
return Err(CausasvError::EmptyGraph);
}
let mut in_deg: Vec<usize> = self.directed_parents.iter().map(Vec::len).collect();
let mut queue: std::collections::VecDeque<NodeId> = in_deg
.iter()
.enumerate()
.filter(|&(_, &d)| d == 0)
.map(|(i, _)| NodeId(i as u32))
.collect();
let mut visited = 0usize;
while let Some(node) = queue.pop_front() {
visited += 1;
for &child in &self.directed_children[node.idx()] {
in_deg[child.idx()] -= 1;
if in_deg[child.idx()] == 0 {
queue.push_back(child);
}
}
}
if visited != self.names.len() {
return Err(CausasvError::CycleDetected);
}
Ok(())
}
pub fn validate_cpdag(&self) -> Result<(), CausasvError> {
self.validate_pdag()?;
self.consistent_extension().map(|_| ())
}
pub fn consistent_extension(&self) -> Result<Dag, CausasvError> {
let n = self.node_count();
if n == 0 {
return Err(CausasvError::EmptyGraph);
}
let mut active = vec![true; n];
let mut remaining_undirected: Vec<Vec<NodeId>> = self.undirected.clone();
let mut oriented: Vec<(NodeId, NodeId)> = self.directed_edges().collect();
for _ in 0..n {
let v = self.find_extension_candidate(&active, &remaining_undirected)?;
for &w in remaining_undirected[v.idx()].iter() {
if active[w.idx()] {
oriented.push((w, v));
}
}
let neighbors = std::mem::take(&mut remaining_undirected[v.idx()]);
for w in neighbors {
remaining_undirected[w.idx()].retain(|&x| x != v);
}
active[v.idx()] = false;
}
let mut dag = Dag::new();
for name in &self.names {
dag.add_node(name);
}
for (from, to) in oriented {
dag.add_edge(from, to)?;
}
dag.validate()?;
Ok(dag)
}
fn find_extension_candidate(
&self,
active: &[bool],
remaining_undirected: &[Vec<NodeId>],
) -> Result<NodeId, CausasvError> {
'candidates: for i in 0..active.len() {
if !active[i] {
continue;
}
let v = NodeId(i as u32);
if self.directed_children[v.idx()]
.iter()
.any(|&w| active[w.idx()])
{
continue;
}
let directed_neighbors: Vec<NodeId> = self.directed_parents[v.idx()]
.iter()
.copied()
.filter(|&w| active[w.idx()])
.collect();
let undirected_neighbors = &remaining_undirected[v.idx()];
for &w in undirected_neighbors {
let other_neighbors = directed_neighbors
.iter()
.copied()
.chain(undirected_neighbors.iter().copied())
.filter(|&y| y != w);
for y in other_neighbors {
if !self.adjacent(w, y) {
continue 'candidates;
}
}
}
return Ok(v);
}
Err(CausasvError::NotExtendable)
}
fn adjacent(&self, a: NodeId, b: NodeId) -> bool {
self.directed_children[a.idx()].contains(&b)
|| self.directed_children[b.idx()].contains(&a)
|| self.undirected[a.idx()].contains(&b)
}
pub fn induced_subgraph(&self, keep: &[NodeId]) -> Result<Cpdag, CausasvError> {
let mut keep_mask = vec![false; self.node_count()];
for &id in keep {
self.check_id(id)?;
keep_mask[id.idx()] = true;
}
let mut sub = Cpdag::new();
for id in self.all_nodes() {
if keep_mask[id.idx()] {
sub.add_node(&self.names[id.idx()]);
}
}
for (from, to) in self.directed_edges() {
if keep_mask[from.idx()] && keep_mask[to.idx()] {
let new_from = sub.node_id(&self.names[from.idx()]).unwrap();
let new_to = sub.node_id(&self.names[to.idx()]).unwrap();
sub.add_directed_edge(new_from, new_to)?;
}
}
for (a, b) in self.undirected_edges() {
if keep_mask[a.idx()] && keep_mask[b.idx()] {
let new_a = sub.node_id(&self.names[a.idx()]).unwrap();
let new_b = sub.node_id(&self.names[b.idx()]).unwrap();
sub.add_undirected_edge(new_a, new_b)?;
}
}
Ok(sub)
}
pub fn strong_d_convex_hull(
&self,
required: &[NodeId],
) -> Result<indexmap::IndexSet<NodeId>, CausasvError> {
self.consistent_extension()?.strong_d_convex_hull(required)
}
fn check_id(&self, id: NodeId) -> Result<(), CausasvError> {
if id.idx() >= self.names.len() {
Err(CausasvError::InvalidNodeId(id))
} else {
Ok(())
}
}
}
impl Default for Cpdag {
fn default() -> Self {
Self::new()
}
}
trait NodeIdExt {
fn idx(self) -> usize;
}
impl NodeIdExt for NodeId {
fn idx(self) -> usize {
self.0 as usize
}
}