1#![allow(clippy::many_single_char_names)] use crate::dag::Dag;
14use crate::error::GraphError;
15use crate::overlay::GraphOverlay;
16use crate::types::DenseNodeId;
17use crate::workspace::{BitSet, GraphWorkspace};
18
19#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash)]
21pub struct PathStep {
22 pub node: DenseNodeId,
24}
25
26#[derive(Clone, Debug, Eq, PartialEq)]
28pub struct SeparationCertificate {
29 pub conditioning: Vec<DenseNodeId>,
31}
32
33#[derive(Clone, Debug, Eq, PartialEq)]
35pub enum SeparationResult {
36 Separated {
38 conditioning: Vec<DenseNodeId>,
40 certificate: SeparationCertificate,
42 },
43 Connected {
46 active_path: Vec<PathStep>,
48 },
49}
50
51#[derive(Clone, Debug, Default)]
53pub struct DSeparationWorkspace {
54 pub ancestral: BitSet,
56 pub conditioning: BitSet,
58 pub undirected: Vec<Vec<DenseNodeId>>,
60 pub visited: BitSet,
62 pub frontier: Vec<DenseNodeId>,
64 pub pred: Vec<Option<DenseNodeId>>,
66 pub graph_ws: GraphWorkspace,
68}
69
70impl DSeparationWorkspace {
71 pub fn prepare(&mut self, n: usize) {
73 self.ancestral.resize(n);
74 self.conditioning.resize(n);
75 self.visited.resize(n);
76 self.undirected.resize(n, Vec::new());
77 for adj in &mut self.undirected {
78 adj.clear();
79 }
80 self.frontier.clear();
81 self.pred.clear();
82 self.pred.resize(n, None);
83 self.graph_ws.prepare(n);
84 }
85}
86
87impl Dag {
88 pub fn is_d_separated(
97 &self,
98 x: DenseNodeId,
99 y: DenseNodeId,
100 z: &[DenseNodeId],
101 ws: &mut DSeparationWorkspace,
102 ) -> Result<bool, GraphError> {
103 self.is_d_separated_with(x, y, z, ws, None)
104 }
105
106 pub(crate) fn is_d_separated_with(
108 &self,
109 x: DenseNodeId,
110 y: DenseNodeId,
111 z: &[DenseNodeId],
112 ws: &mut DSeparationWorkspace,
113 overlay: Option<&GraphOverlay>,
114 ) -> Result<bool, GraphError> {
115 self.validate_node_pub(x)?;
116 self.validate_node_pub(y)?;
117 for &v in z {
118 self.validate_node_pub(v)?;
119 }
120 if x == y {
121 return Ok(false);
122 }
123 if z.iter().any(|&v| v == x || v == y) {
124 return Ok(false);
125 }
126 Ok(self.d_sep_active_path(x, y, z, ws, overlay).is_none())
127 }
128
129 pub fn is_d_separated_batch(
135 &self,
136 queries: &[(DenseNodeId, DenseNodeId, &[DenseNodeId])],
137 out: &mut [bool],
138 ws: &mut DSeparationWorkspace,
139 ) -> Result<(), GraphError> {
140 if out.len() != queries.len() {
141 return Err(GraphError::InvalidEndpoints { message: "batch output length mismatch" });
142 }
143 for (i, &(x, y, z)) in queries.iter().enumerate() {
144 out[i] = self.is_d_separated(x, y, z, ws)?;
145 }
146 Ok(())
147 }
148
149 pub fn d_separation(
155 &self,
156 x: DenseNodeId,
157 y: DenseNodeId,
158 z: &[DenseNodeId],
159 ws: &mut DSeparationWorkspace,
160 ) -> Result<SeparationResult, GraphError> {
161 self.d_separation_with(x, y, z, ws, None)
162 }
163
164 pub(crate) fn d_separation_with(
166 &self,
167 x: DenseNodeId,
168 y: DenseNodeId,
169 z: &[DenseNodeId],
170 ws: &mut DSeparationWorkspace,
171 overlay: Option<&GraphOverlay>,
172 ) -> Result<SeparationResult, GraphError> {
173 self.validate_node_pub(x)?;
174 self.validate_node_pub(y)?;
175 for &v in z {
176 self.validate_node_pub(v)?;
177 }
178 if x == y {
179 return Ok(SeparationResult::Connected { active_path: vec![PathStep { node: x }] });
180 }
181 if z.iter().any(|&v| v == x || v == y) {
182 return Ok(SeparationResult::Connected {
183 active_path: vec![PathStep { node: x }, PathStep { node: y }],
184 });
185 }
186 if let Some(path) = self.d_sep_active_path(x, y, z, ws, overlay) {
187 Ok(SeparationResult::Connected {
188 active_path: path.into_iter().map(|node| PathStep { node }).collect(),
189 })
190 } else {
191 Ok(SeparationResult::Separated {
192 conditioning: z.to_vec(),
193 certificate: SeparationCertificate { conditioning: z.to_vec() },
194 })
195 }
196 }
197
198 fn d_sep_active_path(
200 &self,
201 x: DenseNodeId,
202 y: DenseNodeId,
203 z: &[DenseNodeId],
204 ws: &mut DSeparationWorkspace,
205 overlay: Option<&GraphOverlay>,
206 ) -> Option<Vec<DenseNodeId>> {
207 let n = self.node_count();
208 ws.prepare(n);
209
210 let mut seeds = Vec::with_capacity(2 + z.len());
212 seeds.push(x);
213 seeds.push(y);
214 seeds.extend_from_slice(z);
215 self.ancestors_of_with(&seeds, &mut ws.ancestral, &mut ws.graph_ws, overlay);
216
217 ws.conditioning.clear();
218 for &v in z {
219 ws.conditioning.insert(v);
220 }
221
222 for i in 0..n {
224 let u = DenseNodeId::from_raw(u32::try_from(i).expect("fit"));
225 if !ws.ancestral.contains(u) {
226 continue;
227 }
228 for &c in self.children(u) {
230 if overlay.is_some_and(|ov| !ov.edge_visible(u, c)) {
231 continue;
232 }
233 if ws.ancestral.contains(c) {
234 add_undirected(&mut ws.undirected, u, c);
235 }
236 }
237 let parents = self.parents(u);
239 for (a_idx, &a) in parents.iter().enumerate() {
240 if overlay.is_some_and(|ov| !ov.edge_visible(a, u)) {
241 continue;
242 }
243 if !ws.ancestral.contains(a) {
244 continue;
245 }
246 for &b in &parents[a_idx + 1..] {
247 if overlay.is_some_and(|ov| !ov.edge_visible(b, u)) {
248 continue;
249 }
250 if ws.ancestral.contains(b) {
251 add_undirected(&mut ws.undirected, a, b);
252 }
253 }
254 }
255 }
256
257 ws.visited.clear();
259 for p in &mut ws.pred {
260 *p = None;
261 }
262 ws.frontier.clear();
263 ws.frontier.push(x);
264 ws.visited.insert(x);
265 while let Some(u) = ws.frontier.pop() {
266 if u == y {
267 return Some(reconstruct_path(&ws.pred, x, y));
268 }
269 for &v in &ws.undirected[u.as_usize()] {
270 if ws.conditioning.contains(v) || ws.visited.contains(v) {
271 continue;
272 }
273 if !ws.ancestral.contains(v) {
274 continue;
275 }
276 ws.visited.insert(v);
277 ws.pred[v.as_usize()] = Some(u);
278 ws.frontier.push(v);
279 }
280 }
281 None
282 }
283}
284
285fn add_undirected(adj: &mut [Vec<DenseNodeId>], a: DenseNodeId, b: DenseNodeId) {
286 if a == b {
287 return;
288 }
289 let ai = a.as_usize();
290 let bi = b.as_usize();
291 if !adj[ai].contains(&b) {
292 adj[ai].push(b);
293 }
294 if !adj[bi].contains(&a) {
295 adj[bi].push(a);
296 }
297}
298
299fn reconstruct_path(
300 pred: &[Option<DenseNodeId>],
301 start: DenseNodeId,
302 end: DenseNodeId,
303) -> Vec<DenseNodeId> {
304 let mut path = vec![end];
305 let mut cur = end;
306 while cur != start {
307 cur = pred[cur.as_usize()].expect("path predecessor");
308 path.push(cur);
309 }
310 path.reverse();
311 path
312}
313
314#[cfg(test)]
315#[path = "dsep_tests.rs"]
316mod tests;