aGrUM 3.0.0
a C++ library for (probabilistic) graphical models
PAG.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
41
47#include <sstream>
48
50
51namespace gum {
52
53 // ##########################################################################
54 // Constructors / Destructors
55 // ##########################################################################
56
57 PAG::PAG() { GUM_CONSTRUCTOR(PAG); }
58
59 PAG::PAG(const PAG& other) : NodeGraphPart(other), UndiGraph(other), marks_(other.marks_) {
60 GUM_CONS_CPY(PAG);
61 }
62
63 PAG::PAG(PAG&& other) noexcept :
64 NodeGraphPart(std::move(other)), UndiGraph(std::move(other)),
65 marks_(std::move(other.marks_)) {
66 GUM_CONS_MOV(PAG);
67 }
68
69 PAG::~PAG() { GUM_DESTRUCTOR(PAG); }
70
71 PAG& PAG::operator=(const PAG& other) {
72 if (this != &other) {
74 marks_ = other.marks_;
75 }
76 return *this;
77 }
78
79 PAG& PAG::operator=(PAG&& other) noexcept {
80 if (this != &other) {
81 UndiGraph::operator=(std::move(other));
82 marks_ = std::move(other.marks_);
83 }
84 return *this;
85 }
86
87 // ##########################################################################
88 // Edge operations
89 // ##########################################################################
90
92
93 void PAG::addEdge(NodeId x, NodeId y, EdgeMark markAtX, EdgeMark markAtY) {
94 if (existsEdge(x, y)) { return; }
96 // marks_[Arc(x,y)] = mark at y; marks_[Arc(y,x)] = mark at x
97 marks_.insert(Arc(x, y), markAtY);
98 marks_.insert(Arc(y, x), markAtX);
99 }
100
101 void PAG::eraseEdge(const Edge& e) {
102 if (!existsEdge(e)) { return; }
103 if (marks_.exists(Arc(e.first(), e.second()))) { marks_.erase(Arc(e.first(), e.second())); }
104 if (marks_.exists(Arc(e.second(), e.first()))) { marks_.erase(Arc(e.second(), e.first())); }
106 }
107
109 marks_.clear();
111 }
112
114 if (existsNode(id)) {
115 // unvirtualizedEraseNeighbours (called by UndiGraph::eraseNode) bypasses
116 // PAG::eraseEdge, so we clean marks manually before delegating
117 for (const NodeId nb: neighbours(id)) {
118 if (marks_.exists(Arc(id, nb))) { marks_.erase(Arc(id, nb)); }
119 if (marks_.exists(Arc(nb, id))) { marks_.erase(Arc(nb, id)); }
120 }
121 }
123 }
124
125 void PAG::clear() {
126 marks_.clear();
128 }
129
130 // ##########################################################################
131 // Mark accessors
132 // ##########################################################################
133
134 EdgeMark PAG::markAt(NodeId src, NodeId dst) const { return marks_[Arc(src, dst)]; }
135
136 void PAG::setMarkAt(NodeId src, NodeId dst, EdgeMark m) { marks_.set(Arc(src, dst), m); }
137
138 // ##########################################################################
139 // Predicates
140 // ##########################################################################
141
142 bool PAG::isArrowhead(NodeId src, NodeId dst) const {
143 return markAt(src, dst) == EdgeMark::Arrowhead;
144 }
145
146 bool PAG::isTail(NodeId src, NodeId dst) const { return markAt(src, dst) == EdgeMark::Tail; }
147
148 bool PAG::isCircle(NodeId src, NodeId dst) const { return markAt(src, dst) == EdgeMark::Circle; }
149
151 // Tail at x (from y's perspective) and Arrowhead at y (from x's perspective)
152 return isTail(y, x) && isArrowhead(x, y);
153 }
154
155 bool PAG::isBidirected(NodeId x, NodeId y) const {
156 return isArrowhead(x, y) && isArrowhead(y, x);
157 }
158
160 // z is a definite collider on x-z-y iff both endpoints at z are Arrowhead
161 return isArrowhead(x, z) && isArrowhead(y, z);
162 }
163
164 // ##########################################################################
165 // Bulk operations
166 // ##########################################################################
167
169 for (const Edge& e: edges()) {
170 setMarkAt(e.first(), e.second(), m);
171 setMarkAt(e.second(), e.first(), m);
172 }
173 }
174
175 // ##########################################################################
176 // Conversion / display
177 // ##########################################################################
178
180 MixedGraph g;
181 for (const NodeId n: nodes()) {
182 g.addNodeWithId(n);
183 }
184
185 for (const Edge& e: edges()) {
186 const NodeId x = e.first();
187 const NodeId y = e.second();
188 const bool xArrow = isArrowhead(y, x); // arrowhead at x-endpoint
189 const bool yArrow = isArrowhead(x, y); // arrowhead at y-endpoint
190 if (xArrow && yArrow) {
191 g.addArc(x, y);
192 g.addArc(y, x);
193 } else if (yArrow) {
194 g.addArc(x, y);
195 } else if (xArrow) {
196 g.addArc(y, x);
197 } else {
198 g.addEdge(x, y);
199 }
200 }
201 return g;
202 }
203
204 namespace {
205 char markChar(EdgeMark m) {
206 switch (m) {
207 case EdgeMark::Circle : return 'o';
208 case EdgeMark::Tail : return '-';
209 case EdgeMark::Arrowhead : return '>';
210 }
211 return '?';
212 }
213 } // namespace
214
215 std::string PAG::toString() const {
216 std::ostringstream s;
217 s << std::format("PAG{{nodes: {}, edges: [", NodeGraphPart::toString());
218 bool first = true;
219 for (const Edge& e: edges()) {
220 if (!first) { s << ", "; }
221 first = false;
222 const NodeId x = e.first();
223 const NodeId y = e.second();
224 s << std::format("{} {}--{} {}", x, markChar(markAt(y, x)), markChar(markAt(x, y)), y);
225 }
226 s << "]}";
227 return s.str();
228 }
229
230 std::string PAG::toDot() const {
231 std::ostringstream out;
232 out << "digraph PAG {\n";
233 out << " node [shape=ellipse];\n";
234
235 for (const NodeId n: nodes()) {
236 out << std::format(" {}{};\n", n, dotNodeLabel(n));
237 }
238
239 for (const Edge& e: edges()) {
240 const NodeId x = e.first();
241 const NodeId y = e.second();
242 const bool xArrow = isArrowhead(y, x);
243 const bool yArrow = isArrowhead(x, y);
244 const bool xCircle = isCircle(y, x);
245 const bool yCircle = isCircle(x, y);
246
247 const std::string arrowhead = yArrow ? "normal" : (yCircle ? "odot" : "none");
248 const std::string arrowtail = xArrow ? "normal" : (xCircle ? "odot" : "none");
249
250 out << std::format(" {} -> {} [dir=both, arrowhead={}, arrowtail={}];\n",
251 x,
252 y,
253 arrowhead,
254 arrowtail);
255 }
256 out << "}\n";
257 return out.str();
258 }
259
260 std::ostream& operator<<(std::ostream& stream, const PAG& g) {
261 stream << g.toString();
262 return stream;
263 }
264
265} /* namespace gum */
Partial Ancestral Graph (PAG) — graph type for FCI output.
The base class for all directed edges.
void addArc(const NodeId tail, const NodeId head) override
insert a new arc into the directed graph
Definition diGraph_inl.h:59
virtual void eraseEdge(const Edge &edge)
removes an edge from the EdgeGraphPart
const EdgeSet & edges() const
returns the set of edges stored within the EdgeGraphPart
virtual void clearEdges()
removes all the edges from the EdgeGraphPart
bool existsEdge(const Edge &edge) const
indicates whether a given edge exists
const NodeSet & neighbours(NodeId id) const
returns the set of node neighbours to a given node
The base class for all undirected edges.
GUM_NODISCARD NodeId first() const
returns one extremal node ID (whichever one it is is unspecified)
GUM_NODISCARD NodeId second() const
returns the node ID of the other extremal node ID
Base class for mixed graphs.
Definition mixedGraph.h:146
Class for node sets in graph.
const NodeGraphPart & nodes() const
return *this as a NodeGraphPart
virtual std::string toString() const
a function to display the set of nodes
std::string dotNodeLabel(NodeId id) const
returns " [label=\"...\"]" with DOT-escaped name, or "" if no name
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
Partial Ancestral Graph: undirected topology with endpoint marks.
Definition PAG.h:90
bool isCircle(NodeId src, NodeId dst) const
true if mark at dst (from src) is Circle
Definition PAG.cpp:148
bool isBidirected(NodeId x, NodeId y) const
true if edge x-y is bidirected x↔y (Arrowhead on both endpoints)
Definition PAG.cpp:155
bool isDefCollider(NodeId x, NodeId z, NodeId y) const
true if z is a definite collider on path x-z-y (Arrowhead at z from both sides)
Definition PAG.cpp:159
PAG & operator=(const PAG &)
Definition PAG.cpp:71
void clear() override
remove all nodes, edges, and marks
Definition PAG.cpp:125
void reorientAllWith(EdgeMark m)
set all endpoint marks to m (used between FCI phases to reset to Circle)
Definition PAG.cpp:168
PAG()
Definition PAG.cpp:57
ArcProperty< EdgeMark > marks_
marks_[Arc(x,y)] = mark at y-endpoint of edge x-y
Definition PAG.h:197
void addEdge(NodeId x, NodeId y) override
add edge with Circle-Circle marks
Definition PAG.cpp:91
void setMarkAt(NodeId src, NodeId dst, EdgeMark m)
set mark at dst-endpoint when traversing from src
Definition PAG.cpp:136
bool isTail(NodeId src, NodeId dst) const
true if mark at dst (from src) is Tail
Definition PAG.cpp:146
void eraseEdge(const Edge &e) override
remove edge and its two mark entries
Definition PAG.cpp:101
EdgeMark markAt(NodeId src, NodeId dst) const
mark at dst-endpoint when traversing from src
Definition PAG.cpp:134
std::string toDot() const override
approximate conversion for learnDAG/learnPDAG: Tail-Arrowhead → arc; Arrowhead-Arrowhead → two arcs; ...
Definition PAG.cpp:230
void clearEdges() override
clear all edges (and their marks)
Definition PAG.cpp:108
bool isArrowhead(NodeId src, NodeId dst) const
true if mark at dst (from src) is Arrowhead
Definition PAG.cpp:142
~PAG() override
Definition PAG.cpp:69
MixedGraph toMixedGraph() const
approximate conversion for learnDAG/learnPDAG: Tail-Arrowhead → arc; Arrowhead-Arrowhead → two arcs; ...
Definition PAG.cpp:179
bool isDefinitelyDirected(NodeId x, NodeId y) const
true if edge x-y is definitely directed x→y (Tail@x, Arrowhead@y)
Definition PAG.cpp:150
void eraseNode(NodeId id) override
remove node and clean up marks for all its adjacent edges
Definition PAG.cpp:113
std::string toString() const override
approximate conversion for learnDAG/learnPDAG: Tail-Arrowhead → arc; Arrowhead-Arrowhead → two arcs; ...
Definition PAG.cpp:215
UndiGraph & operator=(const UndiGraph &g)
copy operator
void clear() override
removes all the nodes and edges from the graph
void addEdge(NodeId first, NodeId second) override
insert a new edge into the undirected graph
void eraseNode(NodeId id) override
remove a node and its adjacent edges from the graph
UndiGraph(Size nodes_size=HashTableConst::default_size, bool nodes_resize_policy=true, Size edges_size=HashTableConst::default_size, bool edges_resize_policy=true)
default constructor
Definition undiGraph.cpp:73
Size NodeId
Type for node ids.
gum is the global namespace for all aGrUM entities
Definition agrum.h:46
std::ostream & operator<<(std::ostream &stream, const AVLTree< Val, Cmp > &tree)
display the content of a tree
EdgeMark
Endpoint marks used in PAG edges.
Definition PAG.h:65
@ Arrowhead
Definition PAG.h:65