aGrUM  0.20.3
a C++ library for (probabilistic) graphical models
cliqueGraph.cpp
Go to the documentation of this file.
1 /**
2  *
3  * Copyright (c) 2005-2021 by Pierre-Henri WUILLEMIN(@LIP6) & Christophe GONZALES(@AMU)
4  * info_at_agrum_dot_org
5  *
6  * This library is free software: you can redistribute it and/or modify
7  * it under the terms of the GNU Lesser General Public License as published by
8  * the Free Software Foundation, either version 3 of the License, or
9  * (at your option) any later version.
10  *
11  * This library is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14  * GNU Lesser General Public License for more details.
15  *
16  * You should have received a copy of the GNU Lesser General Public License
17  * along with this library. If not, see <http://www.gnu.org/licenses/>.
18  *
19  */
20 
21 
22 /** @file
23  * @brief source of basic clique graphs (join trees, etc)
24  *
25  * @author Christophe GONZALES(@AMU) and Pierre-Henri WUILLEMIN(@LIP6)
26  */
27 #include <agrum/tools/graphs/cliqueGraph.h>
28 #include <sstream>
29 
30 #ifdef GUM_NO_INLINE
31 # include <agrum/tools/graphs/cliqueGraph_inl.h>
32 #endif // GUM_NOINLINE
33 
34 #ifndef DOXYGEN_SHOULD_SKIP_THIS
35 
36 namespace gum {
37 
38  /* =================================================================== */
39  /* =================================================================== */
40  /* === IMPLEMENTATION OF GUM_CLIQUE_GRAPH === */
41  /* =================================================================== */
42  /* =================================================================== */
43 
44  /// basic constructor: creates an empty clique graph
45 
46  CliqueGraph::CliqueGraph(Size nodes_size,
47  bool nodes_resize_policy,
48  Size edges_size,
49  bool edges_resize_policy) :
50  NodeGraphPart(nodes_size, nodes_resize_policy),
51  UndiGraph(nodes_size, nodes_resize_policy, edges_size, edges_resize_policy) {
52  // for debugging purposes
53  GUM_CONSTRUCTOR(CliqueGraph);
54  }
55 
56  /// copy constructor
57 
58  CliqueGraph::CliqueGraph(const CliqueGraph& from) :
59  NodeGraphPart(from), // needed because NodeGraphPart is a virtual inherited
60  UndiGraph(from), // class (see C++ FAQ Lite #25.12 for details)
61  _cliques_(from._cliques_), _separators_(from._separators_) { // for debugging purposes
62  GUM_CONS_CPY(CliqueGraph);
63  }
64 
65  /// destructor
66 
67  CliqueGraph::~CliqueGraph() { // for debugging purposes
68  GUM_DESTRUCTOR(CliqueGraph);
69  }
70 
71  /// returns a path from a clique containing node1 to a clique containing
72  /// node2
73 
74  std::vector< NodeId > CliqueGraph::containerPath(const NodeId node1, const NodeId node2) const {
75  // get a path from a _clique_ containing node1 to a _clique_ containing
76  // node2
77  std::vector< NodeId > path = undirectedPath(container(node1), container(node2));
78 
79  // it may happen that the path contains several nodes containing node1 and
80  // node2. Hence we shall remove the superfluous nodes
81  while ((path.size() >= 2) && (clique(path[path.size() - 2]).contains(node2)))
82  path.pop_back();
83 
84  while ((path.size() >= 2) && (clique(path[1]).contains(node1)))
85  path.erase(path.begin());
86 
87  return path;
88  }
89 
90  /// changes the set of nodes included into a given clique and returns the new
91  /// set
92  /// @throw DuplicateElement if node_id already is in clique_id
93 
94  void CliqueGraph::addToClique(const NodeId clique_id, const NodeId node_id) {
95  // get the current clique set
96  NodeSet& clique = _cliques_[clique_id];
97 
98  // check if the node already exists, in which case throw an exception
99  if (clique.contains(node_id)) {
100  GUM_ERROR(DuplicateElement, "the clique set already contains the node " << node_id)
101  }
102 
103  clique.insert(node_id);
104 
105  // update the _separators_ adjacent to clique 'id'
106  for (const auto nei: neighbours(clique_id))
107  if (_cliques_[nei].contains(node_id)) _separators_[Edge(nei, clique_id)].insert(node_id);
108  }
109 
110  /// remove a node from a _clique_
111 
112  void CliqueGraph::eraseFromClique(const NodeId clique_id, const NodeId node_id) {
113  // get the current _clique_ set
114  NodeSet& clique = _cliques_[clique_id];
115 
116  // check if the node does not exist, in which case throw an exception
117  if (clique.contains(node_id)) {
118  clique.erase(node_id);
119 
120  // update the _separators_ adjacent to _clique_ 'id'
121  for (const auto nei: neighbours(clique_id)) {
122  Edge edge(nei, clique_id);
123 
124  if (_separators_[edge].contains(node_id)) _separators_[edge].erase(node_id);
125  }
126  }
127  }
128 
129  /// DFS function for computing whether the running intersection property holds
130 
131  bool CliqueGraph::_runningIntersectionDFS_(const NodeId clique,
132  const NodeId from,
133  CliqueGraph::_RunningIntersect_& infos_DFS) const {
134  // check that no node in the clique belongs to the set of nodes belonging to
135  // other connected components of the cliqueGraph
136  const NodeSet& nodes_clique = _cliques_[clique];
137 
138  for (const auto node: nodes_clique)
139  if (infos_DFS.nodes_other_components.contains(node)) return false;
140 
141  // update the structure that keeps track of the cliques that still require
142  // chains to access some of their nodes
143  for (const auto node: nodes_clique)
144  if (!infos_DFS.nodes_DFS_forbidden.contains(node))
145  infos_DFS.cliques_DFS_chain[clique].erase(node);
146 
147  // if the clique had already been visited, no need to see its neighbours
148  // or to update the list of visited nodes
149  if (infos_DFS.visited_cliques.contains(clique)) return true;
150 
151  // update the list of nodes visited during the DFS
152  for (const auto node: nodes_clique)
153  if (!infos_DFS.nodes_DFS_seen.contains(node)) infos_DFS.nodes_DFS_seen.insert(node);
154 
155  // update the fact that the clique has been visited
156  infos_DFS.visited_cliques.insert(clique);
157 
158  // check the neighbours that are different from "from" and that have not
159  // been visited yet
160 
161  for (const auto otherID: neighbours(clique))
162  if (otherID != from) {
163  // update the list of forbidden nodes in the DFS, i.e., the nodes that
164  // belong to the clique but not to the separator
165  const Edge edge(otherID, clique);
166  const NodeSet& from_separ = _separators_[edge];
167 
168  for (const auto node: nodes_clique) {
169  if (!from_separ.contains(node)) infos_DFS.nodes_DFS_forbidden.insert(node);
170  }
171 
172  // check the neighbour
173  if (!_runningIntersectionDFS_(otherID, clique, infos_DFS)) return false;
174 
175  // remove from the forbidden list the nodes that belong to clique
176  for (const auto node: nodes_clique)
177  infos_DFS.nodes_DFS_forbidden.erase(node);
178 
179  // check again the structure that keeps track of the cliques that still
180  // require chains to access some of their nodes: the chain may be
181  // the neighbour we just encountered
182  for (const auto node: nodes_clique) {
183  if (!infos_DFS.nodes_DFS_forbidden.contains(node))
184  infos_DFS.cliques_DFS_chain[clique].erase(node);
185  }
186  }
187 
188  // when a node is terminal, i.e., it has at most one neighbour, add its
189  // nodes
190  // to the nodes forbidden by the DFS. It will prevent non adjacent extremal
191  // cliques to contain the same node while this one does not belong to any
192  // separator
193  if (neighbours(clique).size() <= 1)
194  for (const auto node: nodes_clique)
195  if (!infos_DFS.nodes_DFS_forbidden.contains(node))
196  infos_DFS.nodes_DFS_forbidden.insert(node);
197 
198  // here everything was OK. A priori, the running intersection property holds
199  return true;
200  }
201 
202  /// indicates whether the running intersection property holds
203 
204  bool CliqueGraph::hasRunningIntersection() const {
205  // create a RunningIntersect structure and initialize it
206  _RunningIntersect_ infos_DFS;
207  infos_DFS.cliques_DFS_chain = _cliques_;
208 
209  // while there exist unvisited cliques, perform a DFS on them
210  for (const auto DFSnode: nodes())
211  if (!infos_DFS.visited_cliques.contains(DFSnode)) {
212  // no nodes are forbidden a priori in the DFS
213  infos_DFS.nodes_DFS_forbidden.clear();
214 
215  // no node has already been seen in the DFS
216  infos_DFS.nodes_DFS_seen.clear();
217 
218  // here iter_DFS points on a clique that has not been visited yet
219  // visit the clique graph from this clique
220  if (!_runningIntersectionDFS_(DFSnode, DFSnode, infos_DFS)) return false;
221 
222  // the nodes that were seen during the DFS belong to a connected
223  // component
224  // that is different from the connected components of the subsequent DFS
225  for (const auto node: infos_DFS.nodes_DFS_seen)
226  if (!infos_DFS.nodes_other_components.contains(node))
227  infos_DFS.nodes_other_components.insert(node);
228  }
229 
230  // check that no clique requires an additional chain to guarantee the
231  // running intersection property
232  for (const auto& elt: infos_DFS.cliques_DFS_chain)
233  if (!elt.second.empty()) return false;
234 
235  return true;
236  }
237 
238  /// checks whether two _clique_ graphs are equal
239 
240  bool CliqueGraph::operator==(const CliqueGraph& from) const {
241  // check if both graphical structures are identical
242  if (UndiGraph::operator!=(from)) return false;
243 
244  // check if the _cliques_ are identical
245  for (const auto& elt: _cliques_)
246  if (elt.second != from._cliques_[elt.first]) return false;
247 
248  return true;
249  }
250 
251  std::string CliqueGraph::toString() const {
252  std::stringstream stream;
253  stream << "list of nodes:" << std::endl;
254 
255  for (const auto node: nodes()) {
256  stream << " -- node: " << node << std::endl << " clique:";
257 
258  for (const auto cliq: clique(node))
259  stream << " " << cliq;
260 
261  stream << std::endl;
262  }
263 
264  stream << "\n\nlist of edges:\n";
265 
266  for (const auto& edge: edges())
267  stream << edge << " ";
268 
269  return stream.str();
270  }
271 
272  const std::string expandCliqueContent(const NodeSet& clique) {
273  std::stringstream stream;
274  bool first = true;
275 
276  for (auto node: clique) {
277  if (!first) { stream << "-"; }
278 
279  stream << node;
280  first = false;
281  }
282 
283  return stream.str();
284  }
285  const std::string expandClique(const NodeId n, const NodeSet& clique) {
286  std::stringstream stream;
287  stream << '(' << n << ") " << expandCliqueContent(clique);
288  return stream.str();
289  }
290  const std::string expandSeparator(const NodeId n1,
291  const NodeSet& clique1,
292  const NodeId n2,
293  const NodeSet& clique2) {
294  std::stringstream stream;
295  stream << expandClique(n1, clique1) << "^" << expandClique(n2, clique2);
296  return stream.str();
297  }
298 
299  std::string CliqueGraph::toDot() const {
300  std::stringstream stream;
301  stream << "graph {" << std::endl;
302  stream << " node [style=\"filled\", fontcolor=\"black\"];" << std::endl;
303 
304  // cliques as nodes
305  for (auto node: nodes()) {
306  std::string nom = '"' + expandClique(node, clique(node)) + '"';
307  stream << " " << nom << " [label=\"" << expandCliqueContent(clique(node))
308  << "\",fillcolor =\"burlywood\"];" << std::endl;
309  }
310 
311  stream << std::endl;
312 
313  // separator as nodes
314  for (const auto& edge: edges()) {
315  stream << " \""
316  << expandSeparator(edge.first(),
317  clique(edge.first()),
318  edge.second(),
319  clique(edge.second()))
320  << "\" [label=\"" << expandCliqueContent(separator(edge)) << "\""
321  << ",shape=box,fillcolor=\"palegreen\",fontsize=8,"
322  "width=0,height=0];"
323  << std::endl;
324  }
325 
326  stream << std::endl;
327 
328  // edges now as c1--sep--c2
329  for (const auto& edge: edges())
330  stream << " \"" << expandClique(edge.first(), clique(edge.first())) << "\"--\""
331  << expandSeparator(edge.first(),
332  clique(edge.first()),
333  edge.second(),
334  clique(edge.second()))
335  << "\"--\"" << expandClique(edge.second(), clique(edge.second())) << "\";"
336  << std::endl;
337 
338  stream << "}" << std::endl;
339 
340  return stream.str();
341  }
342 
343  /// a << operator for CliqueGraph
344 
345  std::ostream& operator<<(std::ostream& stream, const CliqueGraph& graph) {
346  stream << graph.toString();
347  return stream;
348  }
349 
350 } /* namespace gum */
351 
352 #endif /* DOXYGEN_SHOULD_SKIP_THIS */