aGrUM 3.1.1
a C++ library for (probabilistic) graphical models
BayesNetFragment_tpl.h
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#pragma once
42
43
51#include <agrum/BN/BayesNet.h>
53
54namespace gum {
55 template < GUM_Numeric GUM_SCALAR >
60
61 template < GUM_Numeric GUM_SCALAR >
63 GUM_DESTRUCTOR(BayesNetFragment)
64
65 for (auto node: nodes())
66 if (_localCPTs_.exists(node)) uninstallCPT_(node);
67 }
68
69 //============================================================
70 // signals to keep consistency with the referred BayesNet
71 template < GUM_Numeric GUM_SCALAR >
73 // nothing to do
74 }
75
76 template < GUM_Numeric GUM_SCALAR >
80
81 template < GUM_Numeric GUM_SCALAR >
83 // nothing to do
84 }
85
86 template < GUM_Numeric GUM_SCALAR >
88 if (this->internalDag().existsArc(from, to)) uninstallArc_(from, to);
89 }
90
91 //============================================================
92 // IBayesNet interface : BayesNetFragment here is a decorator for the bn
93
94 template < GUM_Numeric GUM_SCALAR >
95 const Tensor< GUM_SCALAR >& BayesNetFragment< GUM_SCALAR >::cpt(NodeId id) const {
96 if (!isInstalledNode(id)) GUM_ERROR(NotFound, "NodeId " << id << " is not installed")
97
98 if (_localCPTs_.exists(id)) return *_localCPTs_[id];
99 else return _bn_.cpt(id);
100 }
101
102 template < GUM_Numeric GUM_SCALAR >
104 return this->_bn_.variableNodeMap();
105 }
106
107 template < GUM_Numeric GUM_SCALAR >
109 if (!isInstalledNode(id)) GUM_ERROR(NotFound, "NodeId " << id << " is not installed")
110
111 return _bn_.variable(id);
112 }
113
114 template < GUM_Numeric GUM_SCALAR >
116 NodeId id = _bn_.nodeId(var);
117
118 if (!isInstalledNode(id)) GUM_ERROR(NotFound, "variable " << var.name() << " is not installed")
119
120 return id;
121 }
122
123 template < GUM_Numeric GUM_SCALAR >
125 NodeId id = _bn_.idFromName(name);
126
127 if (!isInstalledNode(id)) GUM_ERROR(NotFound, "variable " << name << " is not installed")
128
129 return id;
130 }
131
132 template < GUM_Numeric GUM_SCALAR >
133 const DiscreteVariable&
135 NodeId id = idFromName(name);
136
137 if (!isInstalledNode(id)) GUM_ERROR(NotFound, "variable " << name << " is not installed")
138
139 return _bn_.variable(id);
140 }
141
142 //============================================================
143 // specific API for BayesNetFragment
144 template < GUM_Numeric GUM_SCALAR >
146 return this->internalDag().existsNode(id);
147 }
148
149 template < GUM_Numeric GUM_SCALAR >
151 if (!_bn_.internalDag().existsNode(id))
152 GUM_ERROR(NotFound, "Node " << id << " does not exist in referred BayesNet")
153
154 if (!isInstalledNode(id)) {
155 this->dag_.addNodeWithId(id);
156
157 // adding arcs with id as a tail
158 for (auto pa: this->_bn_.parents(id)) {
159 if (isInstalledNode(pa)) this->dag_.addArc(pa, id);
160 }
161
162 // adding arcs with id as a head
163 for (auto son: this->_bn_.children(id))
164 if (isInstalledNode(son)) this->dag_.addArc(id, son);
165 }
166 }
167
168 template < GUM_Numeric GUM_SCALAR >
170 installNode(id);
171
172 // bn is a dag => this will have an end ...
173 for (auto pa: this->_bn_.parents(id))
175 }
176
177 template < GUM_Numeric GUM_SCALAR >
179 if (isInstalledNode(id)) {
180 uninstallCPT(id);
181 this->dag_.eraseNode(id);
182 }
183 }
184
185 template < GUM_Numeric GUM_SCALAR >
187 this->dag_.eraseArc(Arc(from, to));
188 }
189
190 template < GUM_Numeric GUM_SCALAR >
192 this->dag_.addArc(from, to);
193 }
194
195 template < GUM_Numeric GUM_SCALAR >
196 void BayesNetFragment< GUM_SCALAR >::installCPT_(NodeId id, const Tensor< GUM_SCALAR >& pot) {
197 // topology
198 const auto& parents = this->parents(id);
199 for (auto node_it = parents.beginSafe(); node_it != parents.endSafe();
200 ++node_it) // safe iterator needed here
201 uninstallArc_(*node_it, id);
202
203 for (Idx i = 1; i < pot.nbrDim(); i++) {
204 NodeId parent = _bn_.idFromName(pot.variable(i).name());
205
206 if (isInstalledNode(parent)) installArc_(parent, id);
207 }
208
209 // local cpt
210 if (_localCPTs_.exists(id)) uninstallCPT_(id);
211
212 _localCPTs_.insert(id, new gum::Tensor< GUM_SCALAR >(pot));
213 }
214
215 template < GUM_Numeric GUM_SCALAR >
216 void BayesNetFragment< GUM_SCALAR >::installCPT(NodeId id, const Tensor< GUM_SCALAR >& pot) {
217 if (!this->internalDag().existsNode(id))
218 GUM_ERROR(NotFound, "Node " << id << " is not installed in the fragment")
219
220 if (&(pot.variable(0)) != &(variable(id))) {
222 "The tensor is not a marginal for _bn_.variable <" << variable(id).name() << ">")
223 }
224
225 const NodeSet& parents = _bn_.parents(id);
226
227 for (Idx i = 1; i < pot.nbrDim(); i++) {
228 if (!parents.contains(_bn_.idFromName(pot.variable(i).name())))
230 "Variable <" << pot.variable(i).name() << "> is not in the parents of node "
231 << id)
232 }
233
234 installCPT_(id, pot);
235 }
236
237 template < GUM_Numeric GUM_SCALAR >
239 delete _localCPTs_[id];
240 _localCPTs_.erase(id);
241 }
242
243 template < GUM_Numeric GUM_SCALAR >
245 if (_localCPTs_.exists(id)) {
246 uninstallCPT_(id);
247
248 // re-create arcs from referred tensor
249 const Tensor< GUM_SCALAR >& pot = cpt(id);
250
251 for (Idx i = 1; i < pot.nbrDim(); i++) {
252 NodeId parent = _bn_.idFromName(pot.variable(i).name());
253
254 if (isInstalledNode(parent)) installArc_(parent, id);
255 }
256 }
257 }
258
259 template < GUM_Numeric GUM_SCALAR >
260 void BayesNetFragment< GUM_SCALAR >::installMarginal(NodeId id, const Tensor< GUM_SCALAR >& pot) {
261 if (!isInstalledNode(id)) {
262 GUM_ERROR(NotFound, "The node " << id << " is not part of this fragment")
263 }
264
265 if (pot.nbrDim() > 1) {
266 GUM_ERROR(OperationNotAllowed, "The tensor is not a marginal :" << pot)
267 }
268
269 if (&(pot.variable(0)) != &(_bn_.variable(id))) {
271 "The tensor is not a marginal for _bn_.variable <" << _bn_.variable(id).name()
272 << ">")
273 }
274
275 installCPT_(id, pot);
276 }
277
278 template < GUM_Numeric GUM_SCALAR >
280 if (!isInstalledNode(id))
281 GUM_ERROR(NotFound, "The node " << id << " is not part of this fragment")
282
283 const auto& node_cpt = this->cpt(id);
284 NodeSet cpt_parents;
285
286 for (Idx i = 1; i < node_cpt.nbrDim(); i++) {
287 cpt_parents.insert(_bn_.idFromName(node_cpt.variable(i).name()));
288 }
289
290 return (this->parents(id) == cpt_parents);
291 }
292
293 template < GUM_Numeric GUM_SCALAR >
295 for (auto node: nodes())
296 if (!checkConsistency(node)) return false;
297
298 return true;
299 }
300
301 template < GUM_Numeric GUM_SCALAR >
303 std::stringstream output;
304
305 std::string bn_name;
306
307 static std::string inFragmentStyle = "fillcolor=\"#ffffaa\","
308 "color=\"#000000\","
309 "fontcolor=\"#000000\"";
310 static std::string styleWithLocalCPT = "fillcolor=\"#ffddaa\","
311 "color=\"#000000\","
312 "fontcolor=\"#000000\"";
313 static std::string notConsistantStyle = "fillcolor=\"#ff0000\","
314 "color=\"#000000\","
315 "fontcolor=\"#ffff00\"";
316 static std::string outFragmentStyle = "fillcolor=\"#f0f0f0\","
317 "color=\"#f0f0f0\","
318 "fontcolor=\"#000000\"";
319
320 bn_name = _bn_.propertyWithDefault("name", "no_name");
321
322 bn_name = "Fragment of " + bn_name;
323
324 output << std::format("digraph \"{}\" {{\n", bn_name);
325 output << std::format(" graph [bgcolor=transparent,label=\"{}\"];\n", bn_name);
326 output << " node [style=filled];" << std::endl << std::endl;
327
328 for (auto node: _bn_.nodes()) {
329 output << "\"" << _bn_.variable(node).name() << "\" [comment=\"" << node << ":"
330 << _bn_.variable(node) << ", \"";
331
332 if (isInstalledNode(node)) {
333 if (!checkConsistency(node)) {
334 output << notConsistantStyle;
335 } else if (_localCPTs_.exists(node)) output << styleWithLocalCPT;
336 else output << inFragmentStyle;
337 } else output << outFragmentStyle;
338
339 output << "];" << std::endl;
340 }
341
342 output << std::endl;
343
344 std::string tab = " ";
345
346 for (auto node: _bn_.nodes()) {
347 if (_bn_.children(node).size() > 0) {
348 for (auto child: _bn_.children(node)) {
349 output << std::format(" \"{}\" -> \"{}\" [",
350 _bn_.variable(node).name(),
351 _bn_.variable(child).name());
352
353 if (this->internalDag().existsArc(Arc(node, child))) output << inFragmentStyle;
354 else output << outFragmentStyle;
355
356 output << "];" << std::endl;
357 }
358 }
359 }
360
361 output << "}" << std::endl;
362
363 return output.str();
364 }
365
366 template < GUM_Numeric GUM_SCALAR >
368 if (!checkConsistency()) {
369 GUM_ERROR(OperationNotAllowed, "The fragment contains un-consistent node(s)")
370 }
372 for (const auto nod: nodes()) {
373 res.add(variable(nod), nod);
374 }
375 for (const auto& arc: this->internalDag().arcs()) {
376 res.addArc(arc.tail(), arc.head());
377 }
378 for (const auto nod: nodes()) {
379 res.cpt(nod).fillWith(cpt(nod));
380 }
381
382 return res;
383 }
384
385 template < GUM_Numeric GUM_SCALAR >
386 const Tensor< GUM_SCALAR >& BayesNetFragment< GUM_SCALAR >::cpt(std::string_view name) const {
387 return cpt(idFromName(name));
388 }
389
390 template < GUM_Numeric GUM_SCALAR >
391 const DiscreteVariable& BayesNetFragment< GUM_SCALAR >::variable(std::string_view name) const {
392 return variable(idFromName(name));
393 }
394
395 template < GUM_Numeric GUM_SCALAR >
396 bool BayesNetFragment< GUM_SCALAR >::isInstalledNode(std::string_view name) const {
397 return isInstalledNode(idFromName(name));
398 }
399
400 template < GUM_Numeric GUM_SCALAR >
402 installNode(_bn_.idFromName(name));
403 }
404
405 template < GUM_Numeric GUM_SCALAR >
407 installAscendants(_bn_.idFromName(name));
408 }
409
410 template < GUM_Numeric GUM_SCALAR >
413 }
414
415 template < GUM_Numeric GUM_SCALAR >
417 const Tensor< GUM_SCALAR >& pot) {
418 installMarginal(_bn_.idFromName(name), pot);
419 }
420
421 template < GUM_Numeric GUM_SCALAR >
423 const Tensor< GUM_SCALAR >& pot) {
424 installCPT(_bn_.idFromName(name), pot);
425 }
426
427 template < GUM_Numeric GUM_SCALAR >
430 }
431
432 template < GUM_Numeric GUM_SCALAR >
433 bool BayesNetFragment< GUM_SCALAR >::checkConsistency(std::string_view name) const {
434 return checkConsistency(idFromName(name));
435 }
436
437} // namespace gum
Class representing Fragment of Bayesian networks.
Class representing Bayesian networks.
The base class for all directed edges.
void installNode(NodeId id)
install a node referenced by its nodeId
void installMarginal(NodeId id, const Tensor< GUM_SCALAR > &pot)
install a local marginal BY COPY for a node into the fragment.
const DiscreteVariable & variableFromName(std::string_view name) const final
Getter by name.
void uninstallArc_(NodeId from, NodeId to)
void whenNodeDeleted(const void *src, NodeId id) final
the action to take when a node has just been removed from the graph
void installCPT_(NodeId id, const Tensor< GUM_SCALAR > &pot)
const IBayesNet< GUM_SCALAR > & _bn_
The referred BayesNet.
gum::BayesNet< GUM_SCALAR > toBN() const
create a brand new BayesNet from a fragment.
void installArc_(NodeId from, NodeId to)
void whenNodeAdded(const void *src, NodeId id) final
the action to take when a new node is inserted into the graph
void whenArcDeleted(const void *src, NodeId from, NodeId to) final
the action to take when an arc has just been removed from the graph
NodeId idFromName(std::string_view name) const final
Getter by name.
const DiscreteVariable & variable(NodeId id) const final
Returns a constant reference over a variabe given it's node id.
bool isInstalledNode(NodeId id) const
check if a certain NodeId exists in the fragment
void whenArcAdded(const void *src, NodeId from, NodeId to) final
the action to take when a new arc is inserted into the graph
NodeProperty< const Tensor< GUM_SCALAR > * > _localCPTs_
Mapping between the variable's id and their CPT specific to this Fragment.
bool checkConsistency(NodeId id) const
returns true if the nodeId's (local or not) cpt is consistent with its parents in the fragment
void uninstallNode(NodeId id)
uninstall a node referenced by its nodeId
bool checkConsistency() const
returns true if all nodes in the fragment are consistent
std::string toDot() const final
creates a dot representing the whole referred BN hilighting the fragment.
void installAscendants(NodeId id)
install a node and all its ascendants
NodeId nodeId(const DiscreteVariable &var) const final
Return id node from discrete var pointer.
void uninstallCPT(NodeId id)
uninstall a local CPT.
const VariableNodeMap & variableNodeMap() const final
Returns a constant reference to the VariableNodeMap of this BN.
const Tensor< GUM_SCALAR > & cpt(NodeId varId) const final
Returns the CPT of a variable.
void uninstallCPT_(NodeId id)
uninstall a local CPT.
void installCPT(NodeId id, const Tensor< GUM_SCALAR > &pot)
install a local cpt BY COPYfor a node into the fragment.
Class representing a Bayesian network.
Definition BayesNet.h:99
const Tensor< GUM_SCALAR > & cpt(NodeId varId) const final
Returns the CPT of a variable.
void addArc(NodeId tail, NodeId head)
Add an arc in the BN, and update arc.head's CPT.
NodeId add(const DiscreteVariable &var)
Add a variable to the gum::BayesNet.
DAG dag_
The DAG of this Directed Graphical Model.
Definition DAGmodel.h:284
const ArcSet & arcs() const
return true if the arc tail->head exists in the DAGmodel
bool existsArc(const NodeId tail, const NodeId head) const
return true if the arc tail->head exists in the DAGmodel
const NodeSet & parents(const NodeId id) const
returns the set of nodes with arc ingoing to a given node
const NodeGraphPart & nodes() const final
Returns a named copy of the internal DAG: each node id is assigned the name of the corresponding vari...
const DAG & internalDag() const
Returns a const reference to the internal (unnamed) DAG. O(1), no copy. Use for stable references or ...
DiGraphListener(const DiGraph *g)
default constructor
Base class for discrete random variable.
IBayesNet()
Default constructor.
Exception : the element we looked for cannot be found.
Exception : operation not allowed.
void insert(const Key &k)
Inserts a new element into the set.
Definition set_tpl.h:510
aGrUM's Tensor is a multi-dimensional array with tensor operators.
Definition tensor.h:85
Container used to map discrete variables with nodes.
const std::string & name() const
returns the name of the variable
#define GUM_ERROR(type, msg)
Definition exceptions.h:76
Size Idx
Type for indexes.
Definition types.h:79
Size NodeId
Type for node ids.
Set< NodeId > NodeSet
Some typdefs and define for shortcuts ...
gum is the global namespace for all aGrUM entities
Definition agrum.h:46
Header of the Tensor class.