aGrUM  0.20.2
a C++ library for (probabilistic) graphical models
cliqueGraph.cpp
Go to the documentation of this file.
1 /**
2  *
3  * Copyright 2005-2020 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__) {
62  // for debugging purposes
63  GUM_CONS_CPY(CliqueGraph);
64  }
65 
66  /// destructor
67 
68  CliqueGraph::~CliqueGraph() {
69  // for debugging purposes
70  GUM_DESTRUCTOR(CliqueGraph);
71  }
72 
73  /// returns a path from a clique containing node1 to a clique containing node2
74 
75  std::vector< NodeId > CliqueGraph::containerPath(const NodeId node1,
76  const NodeId node2) const {
77  // get a path from a clique__ containing node1 to a clique__ containing
78  // node2
79  std::vector< NodeId > path
80  = undirectedPath(container(node1), container(node2));
81 
82  // it may happen that the path contains several nodes containing node1 and
83  // node2. Hence we shall remove the superfluous nodes
84  while ((path.size() >= 2) && (clique(path[path.size() - 2]).contains(node2)))
85  path.pop_back();
86 
87  while ((path.size() >= 2) && (clique(path[1]).contains(node1)))
88  path.erase(path.begin());
89 
90  return path;
91  }
92 
93  /// changes the set of nodes included into a given clique and returns the new
94  /// set
95  /// @throw DuplicateElement if node_id already is in clique_id
96 
97  void CliqueGraph::addToClique(const NodeId clique_id, const NodeId node_id) {
98  // get the current clique set
99  NodeSet& clique = cliques__[clique_id];
100 
101  // check if the node already exists, in which case throw an exception
102  if (clique.contains(node_id)) {
103  GUM_ERROR(DuplicateElement,
104  "the clique set already contains the node " << node_id);
105  }
106 
107  clique.insert(node_id);
108 
109  // update the separators__ adjacent to clique 'id'
110  for (const auto nei: neighbours(clique_id))
111  if (cliques__[nei].contains(node_id))
112  separators__[Edge(nei, clique_id)].insert(node_id);
113  }
114 
115  /// remove a node from a clique__
116 
117  void CliqueGraph::eraseFromClique(const NodeId clique_id, const NodeId node_id) {
118  // get the current clique__ set
119  NodeSet& clique = cliques__[clique_id];
120 
121  // check if the node does not exist, in which case throw an exception
122  if (clique.contains(node_id)) {
123  clique.erase(node_id);
124 
125  // update the separators__ adjacent to clique__ 'id'
126  for (const auto nei: neighbours(clique_id)) {
127  Edge edge(nei, clique_id);
128 
129  if (separators__[edge].contains(node_id))
130  separators__[edge].erase(node_id);
131  }
132  }
133  }
134 
135  /// DFS function for computing whether the running intersection property holds
136 
137  bool CliqueGraph::runningIntersectionDFS__(
138  const NodeId clique,
139  const NodeId from,
140  CliqueGraph::RunningIntersect__& infos_DFS) const {
141  // check that no node in the clique belongs to the set of nodes belonging to
142  // other connected components of the cliqueGraph
143  const NodeSet& nodes_clique = cliques__[clique];
144 
145  for (const auto node: nodes_clique)
146  if (infos_DFS.nodes_other_components.contains(node)) return false;
147 
148  // update the structure that keeps track of the cliques that still require
149  // chains to access some of their nodes
150  for (const auto node: nodes_clique)
151  if (!infos_DFS.nodes_DFS_forbidden.contains(node))
152  infos_DFS.cliques_DFS_chain[clique].erase(node);
153 
154  // if the clique had already been visited, no need to see its neighbours
155  // or to update the list of visited nodes
156  if (infos_DFS.visited_cliques.contains(clique)) return true;
157 
158  // update the list of nodes visited during the DFS
159  for (const auto node: nodes_clique)
160  if (!infos_DFS.nodes_DFS_seen.contains(node))
161  infos_DFS.nodes_DFS_seen.insert(node);
162 
163  // update the fact that the clique has been visited
164  infos_DFS.visited_cliques.insert(clique);
165 
166  // check the neighbours that are different from "from" and that have not
167  // been visited yet
168 
169  for (const auto otherID: neighbours(clique))
170  if (otherID != from) {
171  // update the list of forbidden nodes in the DFS, i.e., the nodes that
172  // belong to the clique but not to the separator
173  const Edge edge(otherID, clique);
174  const NodeSet& from_separ = separators__[edge];
175 
176  for (const auto node: nodes_clique) {
177  if (!from_separ.contains(node))
178  infos_DFS.nodes_DFS_forbidden.insert(node);
179  }
180 
181  // check the neighbour
182  if (!runningIntersectionDFS__(otherID, clique, infos_DFS)) return false;
183 
184  // remove from the forbidden list the nodes that belong to clique
185  for (const auto node: nodes_clique)
186  infos_DFS.nodes_DFS_forbidden.erase(node);
187 
188  // check again the structure that keeps track of the cliques that still
189  // require chains to access some of their nodes: the chain may be
190  // the neighbour we just encountered
191  for (const auto node: nodes_clique) {
192  if (!infos_DFS.nodes_DFS_forbidden.contains(node))
193  infos_DFS.cliques_DFS_chain[clique].erase(node);
194  }
195  }
196 
197  // when a node is terminal, i.e., it has at most one neighbour, add its
198  // nodes
199  // to the nodes forbidden by the DFS. It will prevent non adjacent extremal
200  // cliques to contain the same node while this one does not belong to any
201  // separator
202  if (neighbours(clique).size() <= 1)
203  for (const auto node: nodes_clique)
204  if (!infos_DFS.nodes_DFS_forbidden.contains(node))
205  infos_DFS.nodes_DFS_forbidden.insert(node);
206 
207  // here everything was OK. A priori, the running intersection property holds
208  return true;
209  }
210 
211  /// indicates whether the running intersection property holds
212 
213  bool CliqueGraph::hasRunningIntersection() const {
214  // create a RunningIntersect structure and initialize it
215  RunningIntersect__ infos_DFS;
216  infos_DFS.cliques_DFS_chain = cliques__;
217 
218  // while there exist unvisited cliques, perform a DFS on them
219  for (const auto DFSnode: nodes())
220  if (!infos_DFS.visited_cliques.contains(DFSnode)) {
221  // no nodes are forbidden a priori in the DFS
222  infos_DFS.nodes_DFS_forbidden.clear();
223 
224  // no node has already been seen in the DFS
225  infos_DFS.nodes_DFS_seen.clear();
226 
227  // here iter_DFS points on a clique that has not been visited yet
228  // visit the clique graph from this clique
229  if (!runningIntersectionDFS__(DFSnode, DFSnode, infos_DFS)) return false;
230 
231  // the nodes that were seen during the DFS belong to a connected
232  // component
233  // that is different from the connected components of the subsequent DFS
234  for (const auto node: infos_DFS.nodes_DFS_seen)
235  if (!infos_DFS.nodes_other_components.contains(node))
236  infos_DFS.nodes_other_components.insert(node);
237  }
238 
239  // check that no clique requires an additional chain to guarantee the
240  // running intersection property
241  for (const auto& elt: infos_DFS.cliques_DFS_chain)
242  if (!elt.second.empty()) return false;
243 
244  return true;
245  }
246 
247  /// checks whether two clique__ graphs are equal
248 
249  bool CliqueGraph::operator==(const CliqueGraph& from) const {
250  // check if both graphical structures are identical
251  if (UndiGraph::operator!=(from)) return false;
252 
253  // check if the cliques__ are identical
254  for (const auto& elt: cliques__)
255  if (elt.second != from.cliques__[elt.first]) return false;
256 
257  return true;
258  }
259 
260  std::string CliqueGraph::toString() const {
261  std::stringstream stream;
262  stream << "list of nodes:" << std::endl;
263 
264  for (const auto node: nodes()) {
265  stream << " -- node: " << node << std::endl << " clique:";
266 
267  for (const auto cliq: clique(node))
268  stream << " " << cliq;
269 
270  stream << std::endl;
271  }
272 
273  stream << "\n\nlist of edges:\n";
274 
275  for (const auto& edge: edges())
276  stream << edge << " ";
277 
278  return stream.str();
279  }
280 
281  const std::string expandCliqueContent(const NodeSet& clique) {
282  std::stringstream stream;
283  bool first = true;
284 
285  for (auto node: clique) {
286  if (!first) { stream << "-"; }
287 
288  stream << node;
289  first = false;
290  }
291 
292  return stream.str();
293  }
294  const std::string expandClique(const NodeId n, const NodeSet& clique) {
295  std::stringstream stream;
296  stream << '(' << n << ") " << expandCliqueContent(clique);
297  return stream.str();
298  }
299  const std::string expandSeparator(const NodeId n1,
300  const NodeSet& clique1,
301  const NodeId n2,
302  const NodeSet& clique2) {
303  std::stringstream stream;
304  stream << expandClique(n1, clique1) << "^" << expandClique(n2, clique2);
305  return stream.str();
306  }
307 
308  std::string CliqueGraph::toDot() const {
309  std::stringstream stream;
310  stream << "graph {" << std::endl;
311  stream << " node [style=\"filled\", fontcolor=\"black\"];" << std::endl;
312 
313  // cliques as nodes
314  for (auto node: nodes()) {
315  std::string nom = '"' + expandClique(node, clique(node)) + '"';
316  stream << " " << nom << " [label=\"" << expandCliqueContent(clique(node))
317  << "\",fillcolor =\"burlywood\"];" << std::endl;
318  }
319 
320  stream << std::endl;
321 
322  // separator as nodes
323  for (const auto& edge: edges()) {
324  stream << " \""
325  << expandSeparator(edge.first(),
326  clique(edge.first()),
327  edge.second(),
328  clique(edge.second()))
329  << "\" [label=\"" << expandCliqueContent(separator(edge)) << "\""
330  << ",shape=box,fillcolor=\"palegreen\",fontsize=8,"
331  "width=0,height=0];"
332  << std::endl;
333  }
334 
335  stream << std::endl;
336 
337  // edges now as c1--sep--c2
338  for (const auto& edge: edges())
339  stream << " \"" << expandClique(edge.first(), clique(edge.first()))
340  << "\"--\""
341  << expandSeparator(edge.first(),
342  clique(edge.first()),
343  edge.second(),
344  clique(edge.second()))
345  << "\"--\"" << expandClique(edge.second(), clique(edge.second()))
346  << "\";" << std::endl;
347 
348  stream << "}" << std::endl;
349 
350  return stream.str();
351  }
352 
353  /// a << operator for CliqueGraph
354 
355  std::ostream& operator<<(std::ostream& stream, const CliqueGraph& graph) {
356  stream << graph.toString();
357  return stream;
358  }
359 
360 } /* namespace gum */
361 
362 #endif /* DOXYGEN_SHOULD_SKIP_THIS */