aGrUM 3.0.0
a C++ library for (probabilistic) graphical models
separation.cpp
Go to the documentation of this file.
1/****************************************************************************
2 * This file is part of the aGrUM/pyAgrum library. *
3 * *
4 * Copyright (c) 2005-2026 by *
5 * - Pierre-Henri WUILLEMIN(_at_LIP6) *
6 * - Christophe GONZALES(_at_AMU) *
7 * *
8 * The aGrUM/pyAgrum library is free software; you can redistribute it *
9 * and/or modify it under the terms of either : *
10 * *
11 * - the GNU Lesser General Public License as published by *
12 * the Free Software Foundation, either version 3 of the License, *
13 * or (at your option) any later version, *
14 * - the MIT license (MIT), *
15 * - or both in dual license, as here. *
16 * *
17 * (see https://agrum.gitlab.io/articles/dual-licenses-lgplv3mit.html) *
18 * *
19 * This aGrUM/pyAgrum library is distributed in the hope that it will be *
20 * useful, but WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, *
21 * INCLUDING BUT NOT LIMITED TO THE WARRANTIES MERCHANTABILITY or FITNESS *
22 * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE *
23 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER *
24 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, *
25 * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR *
26 * OTHER DEALINGS IN THE SOFTWARE. *
27 * *
28 * See LICENCES for more details. *
29 * *
30 * SPDX-FileCopyrightText: Copyright 2005-2026 *
31 * - Pierre-Henri WUILLEMIN(_at_LIP6) *
32 * - Christophe GONZALES(_at_AMU) *
33 * SPDX-License-Identifier: LGPL-3.0-or-later OR MIT *
34 * *
35 * Contact : info_at_agrum_dot_org *
36 * homepage : http://agrum.gitlab.io *
37 * gitlab : https://gitlab.com/agrumery/agrum *
38 * *
39 ****************************************************************************/
40
45
46/****************************************************************************
47 * aGrUM/pyAgrum — Separation implementation *
48 ****************************************************************************/
49
50#include <algorithm>
51#include <vector>
52
56
57#include <unordered_set>
58
59namespace gum {
60
61 // --------------------------- small helpers -------------------------------
62
63 namespace {
64 // Remove every node of Z from the undirected graph
65 inline void _eraseAll(UndiGraph& ug, const NodeSet& Z) {
66 for (const auto z: Z)
67 if (ug.existsNode(z)) ug.eraseNode(z);
68 }
69
70 inline void removeConditioningNodes(UndiGraph& ug, const NodeSet& Z) { _eraseAll(ug, Z); }
71 } // namespace
72
73 // ==========================================================================
74 // Core public API
75 // ==========================================================================
76
81 const NodeSet& X,
82 const NodeSet& Y,
83 const NodeSet& Z) {
84 // delegate directly to aGrUM's built-in d-separation
85 return dag.dSeparation(X, Y, Z);
86 }
87
92 const NodeSet& X,
93 const NodeSet& Y,
94 const NodeSet& Z) {
95 // Build G_{\underline X}: remove all outgoing arcs from X
96 DAG g = dag;
97
98 for (auto x: X) {
99 // collect children first, then erase (avoid iterator invalidation)
100 std::vector< NodeId > ch;
101 ch.reserve(g.children(x).size());
102 for (auto c: g.children(x))
103 ch.push_back(c);
104
105 for (auto c: ch) {
106 if (g.existsArc(x, c)) g.eraseArc(gum::Arc(x, c));
107 }
108 }
109
110 // Now test standard d-separation in G_{\underline X}
111 return g.dSeparation(X, Y, Z);
112 }
113
118 const NodeSet& X,
119 const NodeSet& Y,
120 const NodeSet& Z) {
121 // 1) Moralize only on Anc(Y ∪ Z)
122 NodeSet interest = Y;
123 for (auto z: Z)
124 interest.insert(z);
125 UndiGraph ug = dag.moralizedAncestralGraph(interest);
126
127 // Ensure nodes in X exist in ug (without adding X—parent edges)
128 for (auto x: X)
129 if (!ug.existsNode(x)) ug.addNodeWithId(x);
130
131 // 2) Add edges X—c for children c of X that are already in ug
132 for (auto x: X) {
133 for (auto c: dag.children(x)) {
134 if (ug.existsNode(c) && !ug.existsEdge(x, c)) ug.addEdge(x, c);
135 }
136 }
137
138 // 3) Remove conditioning nodes Z
139 removeConditioningNodes(ug, Z);
140
141 // 4) Return true iff there is no undirected path between X and Y
142 return !anyUndirectedConnection(ug, X, Y);
143 }
144
145 // ==========================================================================
146 // Optional reduction for speed
147 // ==========================================================================
148
154 const NodeSet& X,
155 const NodeSet& Y,
156 const NodeSet& Z) {
157 // Targets = X ∪ Y
158 const NodeSet targets{X + Y};
159
160 // Configure finder
161 gum::BarrenNodesFinder finder(&dag);
162 finder.setTargets(&targets); // nodes we care about reaching
163 finder.setEvidence(&Z); // observed nodes
164
165 // Compute barren nodes and remove them from a copy
166 const NodeSet barren = finder.barrenNodes();
167 DAG reduced{dag};
168 for (auto n: barren)
169 if (reduced.existsNode(n)) reduced.eraseNode(n);
170
171 return reduced;
172 }
173
183 const NodeSet& evidenceZ,
184 const NodeSet& targetsXY) {
185 gum::BarrenNodesFinder finder(&dag);
186 finder.setTargets(&targetsXY);
187 finder.setEvidence(&evidenceZ);
188 return finder.barrenNodes();
189 }
190
191 // ==========================================================================
192 // Minimal structural helpers
193 // ==========================================================================
194
198 bool Separation::isAncestorOf(const DAG& dag, NodeId x, NodeId y) {
199 return dag.ancestors(y).contains(x);
200 }
201
206 return dag.descendants(y).contains(x);
207 }
208
209 // ==========================================================================
210 // Internal helper
211 // ==========================================================================
212
216 bool
218 return graph::areConnected(ug, A, B);
219 }
220
221} // namespace gum
d-separation utilities
Detect barren nodes for inference in Bayesian networks.
bool existsArc(const Arc &arc) const
indicates whether a given arc exists
NodeSet children(const NodeSet &ids) const
returns the set of nodes which consists in the node and its parents returns the set of children of a ...
virtual void eraseArc(const Arc &arc)
removes an arc from the ArcGraphPart
The base class for all directed edges.
Detect barren nodes for inference in Bayesian networks.
void setEvidence(const NodeSet *observed_nodes)
sets the observed nodes in the DAG
void setTargets(const NodeSet *target_nodes)
sets the set of target nodes we are interested in
NodeSet barrenNodes()
returns the set of barren nodes
Base class for dag.
Definition DAG.h:121
bool dSeparation(NodeId X, NodeId Y, const NodeSet &Z) const
check if node X and node Y are independent given nodes Z (in the sense of d-separation)
Definition DAG.cpp:80
UndiGraph moralizedAncestralGraph(const NodeSet &nodes) const
build a UndiGraph by moralizing the Ancestral Graph of a set of Nodes
Definition DAG.cpp:76
NodeSet ancestors(NodeId id) const
returns the set of all ancestors of id (nodes from which id is reachable)
void eraseNode(const NodeId id) override
remove a node and its adjacent arcs from the graph
Definition diGraph_inl.h:93
NodeSet descendants(NodeId id) const
returns the set of all descendants of id (nodes reachable from id)
bool existsEdge(const Edge &edge) const
indicates whether a given edge exists
bool existsNode(const NodeId id) const
returns true iff the NodeGraphPart contains the given nodeId
virtual void addNodeWithId(const NodeId id)
try to insert a node with the given id
static DAG reduceForDSeparation(const DAG &dag, const NodeSet &X, const NodeSet &Y, const NodeSet &Z)
Barren-node pruning relative to the interest set (X \cup Y \cup Z).
static bool isDSeparated(const DAG &dag, const NodeSet &X, const NodeSet &Y, const NodeSet &Z)
Test ( X \perp!!!\perp Y \mid Z ) (general d-separation).
static bool isAncestorOf(const DAG &dag, NodeId x, NodeId y)
Is x an ancestor of y?
static NodeSet findBarrenNodes(const DAG &dag, const NodeSet &evidenceZ, const NodeSet &targetsXY)
Compute barren nodes given evidence and targets.
static bool isForwardSeparated(const DAG &dag, const NodeSet &X, const NodeSet &Y, const NodeSet &Z)
Forward-style restriction: only paths whose first edge leaves X.
static bool isDescendantOf(const DAG &dag, NodeId x, NodeId y)
Is x a descendant of y?
static bool anyUndirectedConnection(const UndiGraph &ug, const NodeSet &A, const NodeSet &B)
Check if any node in A is undirected-connected to any node in B.
static bool isBackdoorSeparated(const DAG &dag, const NodeSet &X, const NodeSet &Y, const NodeSet &Z)
Backdoor-style restriction: only paths whose first edge points into X.
bool contains(const Key &k) const
Indicates whether a given elements belong to the set.
Definition set_tpl.h:468
void insert(const Key &k)
Inserts a new element into the set.
Definition set_tpl.h:510
Size size() const noexcept
Returns the number of elements in the set.
Definition set_tpl.h:607
Base class for undirected graphs.
Definition undiGraph.h:130
void addEdge(NodeId first, NodeId second) override
insert a new edge into the undirected graph
Size NodeId
Type for node ids.
Set< NodeId > NodeSet
Some typdefs and define for shortcuts ...
bool areConnected(const G &g, const NodeSet &A, const NodeSet &B)
Returns true iff some node in A can reach some node in B via undirected edges.
gum is the global namespace for all aGrUM entities
Definition agrum.h:46
Generic node-reachability algorithms for aGrUM graphs.