use crate::error::CausasvError;
use crate::graph::{Dag, NodeId};
use indexmap::IndexSet;
use std::collections::{HashMap, VecDeque};
impl Dag {
pub fn d_convex_hull(&self, required: &[NodeId]) -> Result<IndexSet<NodeId>, CausasvError> {
for &id in required {
self.check_id(id)?;
}
let h: IndexSet<NodeId> = required.iter().copied().collect();
self.d_convex_closure(h)
}
pub fn strong_d_convex_hull(
&self,
required: &[NodeId],
) -> Result<IndexSet<NodeId>, CausasvError> {
for &id in required {
self.check_id(id)?;
}
let mut h: IndexSet<NodeId> = required.iter().copied().collect();
let max_iterations = self.node_count() + 1;
for _ in 0..max_iterations {
h = self.d_convex_closure(h)?;
let absorb = self.linear_ordering_violation_parents(&h);
if absorb.is_empty() {
return Ok(h);
}
h.extend(absorb);
}
Err(CausasvError::HullFixedPointNotReached)
}
fn d_convex_closure(&self, mut h: IndexSet<NodeId>) -> Result<IndexSet<NodeId>, CausasvError> {
let max_iterations = self.node_count() + 1;
for _ in 0..max_iterations {
let absorbed = self.shortest_inducing_path_vertices(&h);
let before = h.len();
h.extend(absorbed);
if h.len() == before {
return Ok(h);
}
}
Err(CausasvError::HullFixedPointNotReached)
}
fn shortest_inducing_path_vertices(&self, required: &IndexSet<NodeId>) -> IndexSet<NodeId> {
let marginalized: IndexSet<NodeId> =
self.all_nodes().filter(|n| !required.contains(n)).collect();
let boundary = self.markov_boundary_of_set(&marginalized);
let candidates: Vec<NodeId> = required
.iter()
.copied()
.filter(|n| boundary.contains(n))
.collect();
let mut collected = IndexSet::new();
for i in 0..candidates.len() {
for &b in &candidates[i + 1..] {
let a = candidates[i];
if self.adjacent(a, b) {
continue;
}
let pair: IndexSet<NodeId> = [a, b].into_iter().collect();
let ancestral = self.ancestors_or_self(&pair);
let moralized = self.moralize(&ancestral);
let allowed: IndexSet<NodeId> = pair
.iter()
.copied()
.chain(marginalized.iter().copied())
.filter(|n| ancestral.contains(n))
.collect();
if let Some(path) = shortest_path_restricted(&moralized, a, b, &allowed) {
collected.extend(path);
}
}
}
collected
}
fn linear_ordering_violation_parents(&self, h: &IndexSet<NodeId>) -> IndexSet<NodeId> {
let marginalized: IndexSet<NodeId> = self.all_nodes().filter(|n| !h.contains(n)).collect();
let children_of_marginalized = self.children_or_self(&marginalized);
let ancestors_of_h = self.ancestors_or_self(h);
let mut absorb = IndexSet::new();
for &w in children_of_marginalized.intersection(&ancestors_of_h) {
let parents = self.parents_raw(w);
for i in 0..parents.len() {
for &p2 in &parents[i + 1..] {
let p1 = parents[i];
if h.contains(&p1) && h.contains(&p2) {
continue;
}
if !self.adjacent(p1, p2) {
absorb.insert(p1);
absorb.insert(p2);
}
}
}
}
absorb
}
fn ancestors_or_self(&self, seeds: &IndexSet<NodeId>) -> IndexSet<NodeId> {
let mut result: IndexSet<NodeId> = seeds.clone();
let mut queue: VecDeque<NodeId> = seeds.iter().copied().collect();
while let Some(v) = queue.pop_front() {
for &p in self.parents_raw(v) {
if result.insert(p) {
queue.push_back(p);
}
}
}
result
}
fn children_or_self(&self, seeds: &IndexSet<NodeId>) -> IndexSet<NodeId> {
let mut result: IndexSet<NodeId> = seeds.clone();
for &v in seeds {
for &c in self.children_raw(v) {
result.insert(c);
}
}
result
}
fn markov_boundary_of_set(&self, m: &IndexSet<NodeId>) -> IndexSet<NodeId> {
let closure = self.children_or_self(m);
let mut result = IndexSet::new();
for &v in &closure {
result.insert(v);
for &p in self.parents_raw(v) {
result.insert(p);
}
}
result.retain(|n| !m.contains(n));
result
}
fn adjacent(&self, a: NodeId, b: NodeId) -> bool {
self.children_raw(a).contains(&b) || self.children_raw(b).contains(&a)
}
fn moralize(&self, nodes: &IndexSet<NodeId>) -> Vec<Vec<NodeId>> {
let mut adj: Vec<Vec<NodeId>> = vec![Vec::new(); self.node_count()];
for &w in nodes {
for &c in self.children_raw(w) {
if nodes.contains(&c) {
add_undirected(&mut adj, w, c);
}
}
}
for &w in nodes {
let parents: Vec<NodeId> = self
.parents_raw(w)
.iter()
.copied()
.filter(|p| nodes.contains(p))
.collect();
for i in 0..parents.len() {
for &p2 in &parents[i + 1..] {
add_undirected(&mut adj, parents[i], p2);
}
}
}
adj
}
}
fn add_undirected(adj: &mut [Vec<NodeId>], a: NodeId, b: NodeId) {
if a != b && !adj[a.0 as usize].contains(&b) {
adj[a.0 as usize].push(b);
adj[b.0 as usize].push(a);
}
}
fn shortest_path_restricted(
adj: &[Vec<NodeId>],
from: NodeId,
to: NodeId,
allowed: &IndexSet<NodeId>,
) -> Option<Vec<NodeId>> {
if from == to {
return Some(vec![from]);
}
let mut visited: IndexSet<NodeId> = IndexSet::new();
visited.insert(from);
let mut prev: HashMap<NodeId, NodeId> = HashMap::new();
let mut queue: VecDeque<NodeId> = VecDeque::new();
queue.push_back(from);
while let Some(u) = queue.pop_front() {
for &v in &adj[u.0 as usize] {
if !allowed.contains(&v) || !visited.insert(v) {
continue;
}
prev.insert(v, u);
if v == to {
let mut path = vec![to];
let mut cur = to;
while cur != from {
cur = prev[&cur];
path.push(cur);
}
path.reverse();
return Some(path);
}
queue.push_back(v);
}
}
None
}