aGrUM 3.1.1
a C++ library for (probabilistic) graphical models
graphChangesSelector4DiGraph_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#ifndef DOXYGEN_SHOULD_SKIP_THIS
52
53# include <limits>
54
55namespace gum {
56
57 namespace learning {
58
60 template < typename INVARIABLE_CONSTRAINT_TYPE, typename VARIABLE_CONSTRAINT_TYPE >
63 INVARIABLE_CONSTRAINT_TYPE& invariable_constraints,
64 VARIABLE_CONSTRAINT_TYPE& variable_constraints) :
65 _score_(&score), _invariable_constraints_(&invariable_constraints),
66 _variable_constraints_(&variable_constraints) {
67 GUM_CONSTRUCTOR(GraphChangesSelector4DiGraph);
68 }
69
71 template < typename INVARIABLE_CONSTRAINT_TYPE, typename VARIABLE_CONSTRAINT_TYPE >
72 GraphChangesSelector4DiGraph< INVARIABLE_CONSTRAINT_TYPE, VARIABLE_CONSTRAINT_TYPE >::
73 GraphChangesSelector4DiGraph(
74 const GraphChangesSelector4DiGraph< INVARIABLE_CONSTRAINT_TYPE,
75 VARIABLE_CONSTRAINT_TYPE >& from) :
76 _score_(from._score_), _invariable_constraints_(from._invariable_constraints_),
77 _variable_constraints_(from._variable_constraints_), _graph_(from._graph_),
78 _node_scores_(from._node_scores_), _node_parents_(from._node_parents_),
79 _sorted_changes_(from._sorted_changes_), _removed_nodes_(from._removed_nodes_),
80 _use_arc_additions_(from._use_arc_additions_),
81 _use_arc_deletions_(from._use_arc_deletions_),
82 _use_arc_reversals_(from._use_arc_reversals_),
83 _use_arc_triangle_deletions_(from._use_arc_triangle_deletions_) {
84 // for debugging
85 GUM_CONS_CPY(GraphChangesSelector4DiGraph);
86 }
87
89 template < typename INVARIABLE_CONSTRAINT_TYPE, typename VARIABLE_CONSTRAINT_TYPE >
90 GraphChangesSelector4DiGraph< INVARIABLE_CONSTRAINT_TYPE, VARIABLE_CONSTRAINT_TYPE >::
91 GraphChangesSelector4DiGraph(
92 GraphChangesSelector4DiGraph< INVARIABLE_CONSTRAINT_TYPE, VARIABLE_CONSTRAINT_TYPE >&&
93 from) :
94 _score_(from._score_), _invariable_constraints_(from._invariable_constraints_),
95 _variable_constraints_(from._variable_constraints_), _graph_(from._graph_),
96 _node_scores_(std::move(from._node_scores_)),
97 _node_parents_(std::move(from._node_parents_)),
98 _sorted_changes_(std::move(from._sorted_changes_)),
99 _removed_nodes_(std::move(from._removed_nodes_)),
100 _use_arc_additions_(from._use_arc_additions_),
101 _use_arc_deletions_(from._use_arc_deletions_),
102 _use_arc_reversals_(from._use_arc_reversals_),
103 _use_arc_triangle_deletions_(from._use_arc_triangle_deletions_) {
104 // for debugging
105 GUM_CONS_MOV(GraphChangesSelector4DiGraph);
106 }
107
109 template < typename INVARIABLE_CONSTRAINT_TYPE, typename VARIABLE_CONSTRAINT_TYPE >
110 GraphChangesSelector4DiGraph< INVARIABLE_CONSTRAINT_TYPE,
111 VARIABLE_CONSTRAINT_TYPE >::~GraphChangesSelector4DiGraph() {
112 GUM_DESTRUCTOR(GraphChangesSelector4DiGraph);
113 }
114
116 template < typename INVARIABLE_CONSTRAINT_TYPE, typename VARIABLE_CONSTRAINT_TYPE >
117 GraphChangesSelector4DiGraph< INVARIABLE_CONSTRAINT_TYPE, VARIABLE_CONSTRAINT_TYPE >&
118 GraphChangesSelector4DiGraph< INVARIABLE_CONSTRAINT_TYPE, VARIABLE_CONSTRAINT_TYPE >::
119 operator=(const GraphChangesSelector4DiGraph< INVARIABLE_CONSTRAINT_TYPE,
120 VARIABLE_CONSTRAINT_TYPE >& from) {
121 if (this != &from) {
122 _score_ = from._score_;
123 _invariable_constraints_ = from._invariable_constraints_;
124 _variable_constraints_ = from._variable_constraints_;
125 _graph_ = from._graph_;
126 _node_scores_ = from._node_scores_;
127 _node_parents_ = from._node_parents_;
128 _sorted_changes_ = from._sorted_changes_;
129 _removed_nodes_ = from._removed_nodes_;
130 _use_arc_additions_ = from._use_arc_additions_;
131 _use_arc_deletions_ = from._use_arc_deletions_;
132 _use_arc_reversals_ = from._use_arc_reversals_;
133 _use_arc_triangle_deletions_ = from._use_arc_triangle_deletions_;
134 }
135
136 return *this;
137 }
138
140 template < typename INVARIABLE_CONSTRAINT_TYPE, typename VARIABLE_CONSTRAINT_TYPE >
141 GraphChangesSelector4DiGraph< INVARIABLE_CONSTRAINT_TYPE, VARIABLE_CONSTRAINT_TYPE >&
142 GraphChangesSelector4DiGraph< INVARIABLE_CONSTRAINT_TYPE, VARIABLE_CONSTRAINT_TYPE >::
143 operator=(GraphChangesSelector4DiGraph< INVARIABLE_CONSTRAINT_TYPE,
144 VARIABLE_CONSTRAINT_TYPE >&& from) {
145 if (this != &from) {
146 _score_ = from._score_;
147 _invariable_constraints_ = from._invariable_constraints_;
148 _variable_constraints_ = from._variable_constraints_;
149 _graph_ = from._graph_;
150 _node_scores_ = std::move(from._node_scores_);
151 _node_parents_ = std::move(from._node_parents_);
152 _sorted_changes_ = std::move(from._sorted_changes_);
153 _removed_nodes_ = std::move(from._removed_nodes_);
154 _use_arc_additions_ = from._use_arc_additions_;
155 _use_arc_deletions_ = from._use_arc_deletions_;
156 _use_arc_reversals_ = from._use_arc_reversals_;
157 _use_arc_triangle_deletions_ = from._use_arc_triangle_deletions_;
158 }
159
160 return *this;
161 }
162
164 template < typename INVARIABLE_CONSTRAINT_TYPE, typename VARIABLE_CONSTRAINT_TYPE >
165 INVARIABLE_CONSTRAINT_TYPE&
166 GraphChangesSelector4DiGraph< INVARIABLE_CONSTRAINT_TYPE,
167 VARIABLE_CONSTRAINT_TYPE >::invariableConstraints() {
168 return *_invariable_constraints_;
169 }
170
172 template < typename INVARIABLE_CONSTRAINT_TYPE, typename VARIABLE_CONSTRAINT_TYPE >
173 VARIABLE_CONSTRAINT_TYPE&
174 GraphChangesSelector4DiGraph< INVARIABLE_CONSTRAINT_TYPE,
175 VARIABLE_CONSTRAINT_TYPE >::variableConstraints() {
176 return *_variable_constraints_;
177 }
178
180 template < typename INVARIABLE_CONSTRAINT_TYPE, typename VARIABLE_CONSTRAINT_TYPE >
181 void GraphChangesSelector4DiGraph< INVARIABLE_CONSTRAINT_TYPE,
182 VARIABLE_CONSTRAINT_TYPE >::setGraph(DiGraph& graph) {
183 _graph_ = &graph;
184
185 // remove the nodes of grapĥ that belong neither to the database nor to nodeId2Columns
186 const DatabaseTable& database = _score_->database();
187 const auto& nodeId2Columns = _score_->nodeId2Columns();
188
189 _removed_nodes_.clear();
190 if (nodeId2Columns.empty()) {
191 const auto nb_nodes = NodeId(database.nbVariables());
192 for (auto node: graph) {
193 if (node >= nb_nodes) {
194 graph.eraseNode(node);
195 _removed_nodes_.insert(node);
196 }
197 }
198 } else {
199 for (auto node: graph) {
200 if (!nodeId2Columns.existsFirst(node)) {
201 graph.eraseNode(node);
202 _removed_nodes_.insert(node);
203 }
204 }
205 }
206
207 // fill the DAG with all the missing nodes. This is convenient to allow the user
208 // to just specify a partial graph
209 if (nodeId2Columns.empty()) {
210 const auto nb_nodes = NodeId(database.nbVariables());
211 for (auto i = NodeId(0); i < nb_nodes; ++i) {
212 if (!graph.existsNode(i)) { graph.addNodeWithId(i); }
213 }
214 } else {
215 for (auto iter = nodeId2Columns.cbegin(); iter != nodeId2Columns.cend(); ++iter) {
216 const NodeId id = iter.first();
217 if (!graph.existsNode(id)) { graph.addNodeWithId(id); }
218 }
219 }
220
221 // indicate to the constraints the new graph to which the learning will be applied
222 _invariable_constraints_->setGraph(graph);
223 _variable_constraints_->setGraph(graph);
224
225 // save the set of parents of each node (this will speed-up the computations of the scores)
226 const auto nb_nodes = graph.size();
227 _node_parents_.clear();
228 _node_parents_.resize(nb_nodes);
229 {
230 const std::vector< NodeId > empty_pars;
231 for (const auto node: graph) {
232 auto& node_parents = _node_parents_.insert(node, empty_pars).second;
233 const NodeSet& dag_parents = graph.parents(node);
234 if (!dag_parents.empty()) {
235 node_parents.resize(dag_parents.size());
236 Idx j = Idx(0);
237 for (const auto par: dag_parents) {
238 node_parents[j++] = par;
239 }
240 }
241 }
242 }
243
244 // assign a score to each node given its parents in the current graph
245 _score_->clearCache();
246 _node_scores_.clear();
247 _node_scores_.resize(nb_nodes);
248 for (const auto node: graph) {
249 _node_scores_.insert(node, _score_->score(node, _node_parents_[node]));
250 }
251
252 // compute all the possible changes
253 _sorted_changes_.clear();
254 _sorted_changes_.resize(nb_nodes * nb_nodes);
255
256 if (_use_arc_additions_) _initSortedChangesWithArcAdditions_(graph);
257 if (_use_arc_deletions_) _initSortedChangesWithArcDeletions_(graph);
258 if (_use_arc_reversals_) _initSortedChangesWithArcReversals_(graph);
259 if (_use_arc_triangle_deletions_) _initSortedChangesWithArcTriangleDeletions_(graph);
260 }
261
263 template < typename INVARIABLE_CONSTRAINT_TYPE, typename VARIABLE_CONSTRAINT_TYPE >
264 void GraphChangesSelector4DiGraph< INVARIABLE_CONSTRAINT_TYPE,
265 VARIABLE_CONSTRAINT_TYPE >::useArcAdditions(bool use) {
266 if (use) {
267 // do something only if we did not yet allow the application of arc additions
268 if (!_use_arc_additions_) {
269 // add the arc additions to _sorted_changes_
270 if (_graph_ != nullptr) _initSortedChangesWithArcAdditions_(*_graph_);
271 _use_arc_additions_ = true;
272 }
273 } else {
274 // do something only if we currently allowed the application of arc additions
275 if (_use_arc_additions_) {
276 // remove the arc additions from _sorted_changes_
277 for (auto& change: _sorted_changes_) {
278 if (change.type() == GraphChangeType::ARC_ADDITION) {
279 _sorted_changes_.erase(change, true);
280 }
281 }
282 _use_arc_additions_ = false;
283 }
284 }
285 }
286
288 template < typename INVARIABLE_CONSTRAINT_TYPE, typename VARIABLE_CONSTRAINT_TYPE >
289 void GraphChangesSelector4DiGraph< INVARIABLE_CONSTRAINT_TYPE,
290 VARIABLE_CONSTRAINT_TYPE >::useArcDeletions(bool use) {
291 if (use) {
292 // do something only if we did not yet allow the application of arc deletions
293 if (!_use_arc_deletions_) {
294 // add the arc deletions to _sorted_changes_
295 if (_graph_ != nullptr) _initSortedChangesWithArcDeletions_(*_graph_);
296 _use_arc_deletions_ = true;
297 }
298 } else {
299 // do something only if we currently allowed the application of arc deletions
300 if (_use_arc_deletions_) {
301 // remove the arc deletions from _sorted_changes_
302 for (auto& change: _sorted_changes_) {
303 if (change.type() == GraphChangeType::ARC_DELETION) {
304 _sorted_changes_.erase(change, true);
305 }
306 }
307 _use_arc_deletions_ = false;
308 }
309 }
310 }
311
313 template < typename INVARIABLE_CONSTRAINT_TYPE, typename VARIABLE_CONSTRAINT_TYPE >
314 void GraphChangesSelector4DiGraph< INVARIABLE_CONSTRAINT_TYPE,
315 VARIABLE_CONSTRAINT_TYPE >::useArcReversals(bool use) {
316 if (use) {
317 // do something only if we did not yet allow the application of arc reversals
318 if (!_use_arc_reversals_) {
319 // add the arc reversals to _sorted_changes_
320 if (_graph_ != nullptr) _initSortedChangesWithArcReversals_(*_graph_);
321 _use_arc_reversals_ = true;
322 }
323 } else {
324 // do something only if we currently allowed the application of arc reversals
325 if (_use_arc_reversals_) {
326 // remove the arc reversals from _sorted_changes_
327 for (auto& change: _sorted_changes_) {
328 if (change.type() == GraphChangeType::ARC_REVERSAL) {
329 _sorted_changes_.erase(change, true);
330 }
331 }
332 _use_arc_reversals_ = false;
333 }
334 }
335 }
336
338 template < typename INVARIABLE_CONSTRAINT_TYPE, typename VARIABLE_CONSTRAINT_TYPE >
339 void GraphChangesSelector4DiGraph< INVARIABLE_CONSTRAINT_TYPE, VARIABLE_CONSTRAINT_TYPE >::
340 useArcTriangleDeletions(bool use) {
341 if (use) {
342 // do something only if we did not yet allow the application of arc triangle deletions
343 if (!_use_arc_triangle_deletions_) {
344 // add the arc triangle deletions to _sorted_changes_
345 if (_graph_ != nullptr) _initSortedChangesWithArcTriangleDeletions_(*_graph_);
346 _use_arc_triangle_deletions_ = true;
347 }
348 } else {
349 // do something only if we currently allowed the application of arc triangle deletions
350 if (_use_arc_triangle_deletions_) {
351 // remove the arc triangle deletions from _sorted_changes_
352 for (auto& change: _sorted_changes_) {
353 if ((change.type() == GraphChangeType::ARC_TRIANGLE_DELETION1)
354 || (change.type() == GraphChangeType::ARC_TRIANGLE_DELETION2)) {
355 _sorted_changes_.erase(change, true);
356 }
357 }
358 _use_arc_triangle_deletions_ = false;
359 }
360 }
361 }
362
363 // indicates whether the selector allows the application of arc additions
364 template < typename INVARIABLE_CONSTRAINT_TYPE, typename VARIABLE_CONSTRAINT_TYPE >
365 bool GraphChangesSelector4DiGraph< INVARIABLE_CONSTRAINT_TYPE,
366 VARIABLE_CONSTRAINT_TYPE >::arcAdditionsEnabled() const {
367 return _use_arc_additions_;
368 }
369
370 // indicates whether the selector allows the application of arc deletions
371 template < typename INVARIABLE_CONSTRAINT_TYPE, typename VARIABLE_CONSTRAINT_TYPE >
372 bool GraphChangesSelector4DiGraph< INVARIABLE_CONSTRAINT_TYPE,
373 VARIABLE_CONSTRAINT_TYPE >::arcDeletionsEnabled() const {
374 return _use_arc_deletions_;
375 }
376
377 // indicates whether the selector allows the application of arc reversals
378 template < typename INVARIABLE_CONSTRAINT_TYPE, typename VARIABLE_CONSTRAINT_TYPE >
379 bool GraphChangesSelector4DiGraph< INVARIABLE_CONSTRAINT_TYPE,
380 VARIABLE_CONSTRAINT_TYPE >::arcReversalsEnabled() const {
381 return _use_arc_reversals_;
382 }
383
384 // indicates whether the selector allows the application of arc triangle deletions
385 template < typename INVARIABLE_CONSTRAINT_TYPE, typename VARIABLE_CONSTRAINT_TYPE >
386 bool GraphChangesSelector4DiGraph< INVARIABLE_CONSTRAINT_TYPE,
387 VARIABLE_CONSTRAINT_TYPE >::arcTriangleDeletionsEnabled()
388 const {
389 return _use_arc_triangle_deletions_;
390 }
391
392 // compute the new score of node given that we added it a new parent
393 template < typename INVARIABLE_CONSTRAINT_TYPE, typename VARIABLE_CONSTRAINT_TYPE >
394 double GraphChangesSelector4DiGraph< INVARIABLE_CONSTRAINT_TYPE, VARIABLE_CONSTRAINT_TYPE >::
395 _scoreAfterAddingParent_(const NodeId node, const NodeId new_parent) {
396 auto& parents = _node_parents_[node];
397 parents.push_back(new_parent);
398 const double new_score = _score_->score(node, parents);
399 parents.pop_back();
400 return new_score;
401 }
402
404 template < typename INVARIABLE_CONSTRAINT_TYPE, typename VARIABLE_CONSTRAINT_TYPE >
405 void GraphChangesSelector4DiGraph< INVARIABLE_CONSTRAINT_TYPE,
406 VARIABLE_CONSTRAINT_TYPE >::finalizeGraph(DiGraph& graph) {
407 for (const auto node: _removed_nodes_) {
408 graph.addNodeWithId(node);
409 }
410 }
411
413 template < typename INVARIABLE_CONSTRAINT_TYPE, typename VARIABLE_CONSTRAINT_TYPE >
414 double GraphChangesSelector4DiGraph< INVARIABLE_CONSTRAINT_TYPE, VARIABLE_CONSTRAINT_TYPE >::
415 _scoreAfterRemovingParent_(const NodeId node, const NodeId parent) {
416 auto& parents = _node_parents_[node];
417 for (auto& par: parents) {
418 if (par == parent) {
419 par = *(parents.rbegin());
420 parents.pop_back();
421 break;
422 }
423 }
424 const double new_score = _score_->score(node, parents);
425 parents.push_back(parent);
426 return new_score;
427 }
428
430 template < typename INVARIABLE_CONSTRAINT_TYPE, typename VARIABLE_CONSTRAINT_TYPE >
431 double GraphChangesSelector4DiGraph< INVARIABLE_CONSTRAINT_TYPE, VARIABLE_CONSTRAINT_TYPE >::
432 _scoreAfterAddingParents_(const NodeId node,
433 const NodeId new_parent1,
434 const NodeId new_parent2) {
435 auto& parents = _node_parents_[node];
436 parents.push_back(new_parent1);
437 parents.push_back(new_parent2);
438 const double new_score = _score_->score(node, parents);
439 parents.pop_back();
440 parents.pop_back();
441 return new_score;
442 }
443
445 template < typename INVARIABLE_CONSTRAINT_TYPE, typename VARIABLE_CONSTRAINT_TYPE >
446 double GraphChangesSelector4DiGraph< INVARIABLE_CONSTRAINT_TYPE, VARIABLE_CONSTRAINT_TYPE >::
447 _scoreAfterRemovingParents_(const NodeId node, const NodeId parent1, const NodeId parent2) {
448 auto& parents = _node_parents_[node];
449 for (auto& par: parents) {
450 if (par == parent1) {
451 par = *(parents.rbegin());
452 parents.pop_back();
453 break;
454 }
455 }
456 for (auto& par: parents) {
457 if (par == parent2) {
458 par = *(parents.rbegin());
459 parents.pop_back();
460 break;
461 }
462 }
463 const double new_score = _score_->score(node, parents);
464 parents.push_back(parent1);
465 parents.push_back(parent2);
466 return new_score;
467 }
468
470 template < typename INVARIABLE_CONSTRAINT_TYPE, typename VARIABLE_CONSTRAINT_TYPE >
471 void GraphChangesSelector4DiGraph< INVARIABLE_CONSTRAINT_TYPE, VARIABLE_CONSTRAINT_TYPE >::
472 _addArcAdditionToSortedChanges_(const ArcAddition& change) {
473 // only add the arc addition if this is allowed by the graph and the constraints
474 if (!_invariable_constraints_->isAlwaysInvalid(change)) {
475 const NodeId node1 = change.node1();
476 const NodeId node2 = change.node2();
477
478 // compute the delta in the score resulting from adding node1 to the set
479 // of parents of the node2
480 // currently invalid changes are assigned a -infty score
481 const double delta = !_graph_->existsArc(Arc(node1, node2))
482 ? _scoreAfterAddingParent_(node2, node1) - _node_scores_[node2]
483 : std::numeric_limits< double >::lowest();
484 _sorted_changes_.insert(change, delta);
485 }
486 }
487
489 template < typename INVARIABLE_CONSTRAINT_TYPE, typename VARIABLE_CONSTRAINT_TYPE >
490 void GraphChangesSelector4DiGraph< INVARIABLE_CONSTRAINT_TYPE, VARIABLE_CONSTRAINT_TYPE >::
491 _addArcDeletionToSortedChanges_(const ArcDeletion& change) {
492 // only add the arc reversal if this is allowed by the constraints
493 if (!_invariable_constraints_->isAlwaysInvalid(change)) {
494 const NodeId tail = change.node1();
495 const NodeId head = change.node2();
496
497 // compute the delta in the score resulting from removing the tail from
498 // the set of parents of the head
499 // currently invalid changes are assigned a -infty score
500 const double delta = _graph_->existsArc(Arc(tail, head))
501 ? _scoreAfterRemovingParent_(head, tail) - _node_scores_[head]
502 : std::numeric_limits< double >::lowest();
503 _sorted_changes_.insert(change, delta);
504 }
505 }
506
508 template < typename INVARIABLE_CONSTRAINT_TYPE, typename VARIABLE_CONSTRAINT_TYPE >
509 void GraphChangesSelector4DiGraph< INVARIABLE_CONSTRAINT_TYPE, VARIABLE_CONSTRAINT_TYPE >::
510 _addArcReversalToSortedChanges_(const ArcReversal& change) {
511 // only add the arc reversal if this is allowed by the constraints
512 if (!_invariable_constraints_->isAlwaysInvalid(change)) {
513 const NodeId tail = change.node1();
514 const NodeId head = change.node2();
515
516 // compute the delta in the score resulting from removing the tail from the
517 // set of parents of the head and that resulting from adding the head to
518 // the tail
519 // currently invalid changes are assigned a -infty score
520 const double delta
521 = _graph_->existsArc(Arc(tail, head)) && !_graph_->existsArc(Arc(head, tail))
522 ? _scoreAfterRemovingParent_(head, tail) - _node_scores_[head]
523 + _scoreAfterAddingParent_(tail, head) - _node_scores_[tail]
524 : std::numeric_limits< double >::lowest();
525 _sorted_changes_.insert(change, delta);
526 }
527 }
528
530 template < typename INVARIABLE_CONSTRAINT_TYPE, typename VARIABLE_CONSTRAINT_TYPE >
531 void GraphChangesSelector4DiGraph< INVARIABLE_CONSTRAINT_TYPE, VARIABLE_CONSTRAINT_TYPE >::
532 _addArcTriangleDeletion1ToSortedChanges_(const ArcTriangleDeletion1& change) {
533 // only add the arc reversal if this is allowed by the constraints
534 if (!_invariable_constraints_->isAlwaysInvalid(change)) {
535 const auto node1 = change.node1();
536 const auto node2 = change.node2();
537 const auto node3 = change.node3();
538
539 // the arc triangle deletion will substitute triangle
540 // node1 -> node2 -> node3 + node1 -> node3 into the following v-structure:
541 // node2 -> node1 <- node3. The modifications will therefore be:
542 // 1/ add node2 and node3 to the set of parents of node1
543 // 2/ remove node1 from the set of parents of node2
544 // 3/ remove node1 and node2 from the set of parents of node3
545 // compute the update in the score due to these 3 operations
546 // But currently invalid changes are assigned a -infty score
547
548 const double delta
549 = _graph_->existsArc(Arc(node1, node2)) && _graph_->existsArc(Arc(node2, node3))
550 && _graph_->existsArc(Arc(node1, node3))
551 && !_graph_->existsArc(Arc(node2, node1))
552 && !_graph_->existsArc(Arc(node3, node1))
553 ? _scoreAfterAddingParents_(node1, node2, node3) - _node_scores_[node1]
554 + _scoreAfterRemovingParent_(node2, node1) - _node_scores_[node2]
555 + _scoreAfterRemovingParents_(node3, node1, node2) - _node_scores_[node3]
556 : std::numeric_limits< double >::lowest();
557 _sorted_changes_.insert(change, delta);
558 }
559 }
560
562 template < typename INVARIABLE_CONSTRAINT_TYPE, typename VARIABLE_CONSTRAINT_TYPE >
563 void GraphChangesSelector4DiGraph< INVARIABLE_CONSTRAINT_TYPE, VARIABLE_CONSTRAINT_TYPE >::
564 _addArcTriangleDeletion2ToSortedChanges_(const ArcTriangleDeletion2& change) {
565 // only add the arc reversal if this is allowed by the constraints
566 if (!_invariable_constraints_->isAlwaysInvalid(change)) {
567 const auto node1 = change.node1();
568 const auto node2 = change.node2();
569 const auto node3 = change.node3();
570
571 // the arc triangle deletion will substitute triangle
572 // node1 -> node2 -> node3 + node1 -> node3 into the following v-structure:
573 // node1 -> node2 <- node3. The modifications will therefore be:
574 // 1/ add node3 to the set of parents of node2
575 // 2/ remove node1 and node2 from the set of parents of node3
576 // compute the update in the score due to these 2 operations
577 // But currently invalid changes are assigned a -infty score
578
579 const double delta
580 = _graph_->existsArc(Arc(node1, node2)) && _graph_->existsArc(Arc(node2, node3))
581 && _graph_->existsArc(Arc(node1, node3))
582 && !_graph_->existsArc(Arc(node3, node2))
583 ? _scoreAfterAddingParent_(node2, node3) - _node_scores_[node2]
584 + _scoreAfterRemovingParents_(node3, node1, node2) - _node_scores_[node3]
585 : std::numeric_limits< double >::lowest();
586 _sorted_changes_.insert(change, delta);
587 }
588 }
589
591 template < typename INVARIABLE_CONSTRAINT_TYPE, typename VARIABLE_CONSTRAINT_TYPE >
592 void GraphChangesSelector4DiGraph< INVARIABLE_CONSTRAINT_TYPE, VARIABLE_CONSTRAINT_TYPE >::
593 _updateArcAdditionScore_(const NodeId tail, const NodeId head) {
594 const auto addition = _sorted_changes_.tryGet(ArcAddition(tail, head));
595 if (addition.has_value()) {
596 // currently unavailable changes have a -infty priority
597 const double delta = !_graph_->existsArc(tail, head)
598 ? _scoreAfterAddingParent_(head, tail) - _node_scores_[head]
599 : std::numeric_limits< double >::lowest();
600 _sorted_changes_.setPriority(addition.value(), delta, true);
601 }
602 }
603
605 template < typename INVARIABLE_CONSTRAINT_TYPE, typename VARIABLE_CONSTRAINT_TYPE >
606 void GraphChangesSelector4DiGraph< INVARIABLE_CONSTRAINT_TYPE, VARIABLE_CONSTRAINT_TYPE >::
607 _updateArcDeletionScore_(const NodeId tail, const NodeId head) {
608 const auto deletion = _sorted_changes_.tryGet(ArcDeletion(tail, head));
609 if (deletion.has_value()) {
610 // currently unavailable changes have a -infty priority
611 const double delta = _graph_->existsArc(tail, head)
612 ? _scoreAfterRemovingParent_(head, tail) - _node_scores_[head]
613 : std::numeric_limits< double >::lowest();
614 _sorted_changes_.setPriority(deletion.value(), delta, true);
615 }
616 }
617
619 template < typename INVARIABLE_CONSTRAINT_TYPE, typename VARIABLE_CONSTRAINT_TYPE >
620 void GraphChangesSelector4DiGraph< INVARIABLE_CONSTRAINT_TYPE, VARIABLE_CONSTRAINT_TYPE >::
621 _updateArcReversalScore_(const NodeId tail, const NodeId head) {
622 const auto reversal = _sorted_changes_.tryGet(ArcReversal(tail, head));
623 if (reversal.has_value()) {
624 const double delta = _graph_->existsArc(tail, head) && !_graph_->existsArc(head, tail)
625 ? _scoreAfterRemovingParent_(head, tail) - _node_scores_[head]
626 + _scoreAfterAddingParent_(tail, head) - _node_scores_[tail]
627 : std::numeric_limits< double >::lowest();
628 _sorted_changes_.setPriority(reversal.value(), delta, true);
629 }
630 }
631
634 template < typename INVARIABLE_CONSTRAINT_TYPE, typename VARIABLE_CONSTRAINT_TYPE >
635 void GraphChangesSelector4DiGraph< INVARIABLE_CONSTRAINT_TYPE, VARIABLE_CONSTRAINT_TYPE >::
636 _updateTriangleDeletionsScoresFromNeighborhood_(const NodeId changed_node) {
637 const auto& children_changed_node = _graph_->children(changed_node);
638 const auto& parents_changed_node = _graph_->parents(changed_node);
639 const auto score_changed_node = _node_scores_[changed_node];
640
641 // we consider here triangles node1 -> node2 -> node3 + node1 -> node3
642 // Three cases can occur: changed_node is 1/ node1, 2/ node2, 3/ node3
643 // case1:
644 for (const auto node2: children_changed_node) {
645 for (const auto node3: children_changed_node) {
646 if (_graph_->parents(node3).contains(node2)) { // changed_node -> node2 -> node3
647 double delta3 = std::numeric_limits< double >::lowest();
648
649 // case 1 for ArcTriangleDeletion1
650 {
651 auto change
652 = _sorted_changes_.tryGet(ArcTriangleDeletion1(changed_node, node2, node3));
653 if (change.has_value()) {
654 const bool is_valid = !_graph_->existsArc(node2, changed_node)
655 && !_graph_->existsArc(node3, changed_node);
656 // the delta score at node3
657 delta3 = is_valid ? _scoreAfterRemovingParents_(node3, changed_node, node2)
658 - _node_scores_[node3]
659 : delta3;
660 // the following delta score just computes the score of the
661 // v-structure that we would get if we applied the arc triangle
662 // deletion minus the current score of the 3 nodes involved
663 // in the triangle
664 const double delta = is_valid
665 ? _scoreAfterAddingParents_(changed_node, node3, node2)
666 - score_changed_node
667 + _scoreAfterRemovingParent_(node2, changed_node)
668 - _node_scores_[node2] + delta3
669 : std::numeric_limits< double >::lowest();
670 _sorted_changes_.setPriority(change.value(), delta, true);
671 }
672 }
673
674 // case 1 for ArcTriangleDeletion2
675 {
676 auto change
677 = _sorted_changes_.tryGet(ArcTriangleDeletion2(changed_node, node2, node3));
678 if (change.has_value()) {
679 const bool is_valid
680 = !_graph_->existsArc(changed_node, node2) && !_graph_->existsArc(node3, node2);
681 delta3 = is_valid && (delta3 == std::numeric_limits< double >::lowest())
682 ? _scoreAfterRemovingParents_(node3, changed_node, node2)
683 - _node_scores_[node3]
684 : delta3;
685 const double delta = is_valid ? _scoreAfterAddingParent_(node2, node3)
686 - _node_scores_[node2] + delta3
687 : std::numeric_limits< double >::lowest();
688 _sorted_changes_.setPriority(change.value(), delta, true);
689 }
690 }
691 }
692 }
693 }
694
695 // case2:
696 for (const auto node1: parents_changed_node) {
697 for (const auto node3: children_changed_node) {
698 if (_graph_->parents(node3).contains(node1)) { // node1 -> changed_node -> node3
699 double delta3 = std::numeric_limits< double >::lowest();
700
701 // case 2 for ArcTriangleDeletion1
702 {
703 auto change
704 = _sorted_changes_.tryGet(ArcTriangleDeletion1(node1, changed_node, node3));
705 if (change.has_value()) {
706 const bool is_valid
707 = !_graph_->existsArc(changed_node, node1) && !_graph_->existsArc(node3, node1);
708 // the delta score at node3
709 delta3 = is_valid ? _scoreAfterRemovingParents_(node3, node1, changed_node)
710 - _node_scores_[node3]
711 : delta3;
712 const double delta = is_valid
713 ? _scoreAfterAddingParents_(node1, changed_node, node3)
714 - _node_scores_[node1]
715 + _scoreAfterRemovingParent_(changed_node, node1)
716 - score_changed_node + delta3
717 : std::numeric_limits< double >::lowest();
718 _sorted_changes_.setPriority(change.value(), delta, true);
719 }
720 }
721
722 // case 2 for ArcTriangleDeletion2
723 {
724 auto change
725 = _sorted_changes_.tryGet(ArcTriangleDeletion2(node1, changed_node, node3));
726 if (change.has_value()) {
727 const bool is_valid = !_graph_->existsArc(node1, changed_node)
728 && !_graph_->existsArc(node3, changed_node);
729 delta3 = is_valid && (delta3 == std::numeric_limits< double >::lowest())
730 ? _scoreAfterRemovingParents_(node3, node1, changed_node)
731 - _node_scores_[node3]
732 : delta3;
733 const double delta = is_valid ? _scoreAfterAddingParent_(changed_node, node3)
734 - score_changed_node + delta3
735 : std::numeric_limits< double >::lowest();
736 _sorted_changes_.setPriority(change.value(), delta, true);
737 }
738 }
739 }
740 }
741 }
742
743 // case 3:
744 for (const auto node1: parents_changed_node) {
745 for (const auto node2: parents_changed_node) {
746 if (_graph_->parents(node2).contains(node1)) { // node1 -> node2 -> changed_node
747 double delta3 = std::numeric_limits< double >::lowest();
748
749 // case 3 for ArcTriangleDeletion1
750 {
751 auto change
752 = _sorted_changes_.tryGet(ArcTriangleDeletion1(node1, node2, changed_node));
753 if (change.has_value()) {
754 const bool is_valid
755 = !_graph_->existsArc(node2, node1) && !_graph_->existsArc(changed_node, node1);
756 // the delta score at changed_node
757 delta3 = is_valid ? _scoreAfterRemovingParents_(changed_node, node1, node2)
758 - score_changed_node
759 : delta3;
760 const double delta = is_valid
761 ? _scoreAfterAddingParents_(node1, changed_node, node2)
762 - _node_scores_[node1]
763 + _scoreAfterRemovingParent_(node2, node1)
764 - _node_scores_[node2] + delta3
765 : std::numeric_limits< double >::lowest();
766 _sorted_changes_.setPriority(change.value(), delta, true);
767 }
768 }
769
770 // case 3 for ArcTriangleDeletion2
771 {
772 auto change
773 = _sorted_changes_.tryGet(ArcTriangleDeletion2(node1, node2, changed_node));
774 if (change.has_value()) {
775 const bool is_valid
776 = !_graph_->existsArc(node1, node2) && !_graph_->existsArc(changed_node, node2);
777 delta3 = is_valid && (delta3 == std::numeric_limits< double >::lowest())
778 ? _scoreAfterRemovingParents_(changed_node, node1, node2)
779 - score_changed_node
780 : delta3;
781 const double delta = is_valid ? _scoreAfterAddingParent_(node2, changed_node)
782 - _node_scores_[node2] + delta3
783 : std::numeric_limits< double >::lowest();
784 _sorted_changes_.setPriority(change.value(), delta, true);
785 }
786 }
787 }
788 }
789 }
790 }
791
793 template < typename INVARIABLE_CONSTRAINT_TYPE, typename VARIABLE_CONSTRAINT_TYPE >
794 void GraphChangesSelector4DiGraph< INVARIABLE_CONSTRAINT_TYPE, VARIABLE_CONSTRAINT_TYPE >::
795 _initSortedChangesWithArcAdditions_(const DiGraph& graph) {
796 // for each pair of nodes (node1,node2) that are not linked by an arc node1 -> node2 or
797 // node2 -> node1, try the addition of this missing arc
798 for (auto iter1 = graph.begin(); iter1 != graph.end(); ++iter1) {
799 const NodeId node1 = *iter1;
800
801 // here, we enforce that node2 > node to avoid having to check the adjacency twice:
802 // one for pair (node1,node2) and the second for pair (node2,node1)
803 auto iter2 = iter1;
804 for (++iter2; iter2 != graph.end(); ++iter2) {
805 const NodeId node2 = *iter2;
806 if (!graph.existsArc(node1, node2)) {
807 _addArcAdditionToSortedChanges_(ArcAddition(node1, node2));
808 }
809 if (!graph.existsArc(node2, node1)) {
810 _addArcAdditionToSortedChanges_(ArcAddition(node2, node1));
811 }
812 }
813 }
814 }
815
817 template < typename INVARIABLE_CONSTRAINT_TYPE, typename VARIABLE_CONSTRAINT_TYPE >
818 void GraphChangesSelector4DiGraph< INVARIABLE_CONSTRAINT_TYPE, VARIABLE_CONSTRAINT_TYPE >::
819 _initSortedChangesWithArcDeletions_(const DiGraph& graph) {
820 // for each arc in graph, try to add an arc deletion
821 for (const auto& arc: graph.arcs()) {
822 _addArcDeletionToSortedChanges_(ArcDeletion(arc.tail(), arc.head()));
823 }
824 }
825
827 template < typename INVARIABLE_CONSTRAINT_TYPE, typename VARIABLE_CONSTRAINT_TYPE >
828 void GraphChangesSelector4DiGraph< INVARIABLE_CONSTRAINT_TYPE, VARIABLE_CONSTRAINT_TYPE >::
829 _initSortedChangesWithArcReversals_(const DiGraph& graph) {
830 // for each arc in graph, try to add an arc reversal
831 for (const auto& arc: graph.arcs()) {
832 _addArcReversalToSortedChanges_(ArcReversal(arc.tail(), arc.head()));
833 }
834 }
835
837 template < typename INVARIABLE_CONSTRAINT_TYPE, typename VARIABLE_CONSTRAINT_TYPE >
838 void GraphChangesSelector4DiGraph< INVARIABLE_CONSTRAINT_TYPE, VARIABLE_CONSTRAINT_TYPE >::
839 _initSortedChangesWithArcTriangleDeletions_(const DiGraph& graph) {
840 // for each node "node1" having two children node2 and node3 such that node3 is
841 // also a child of node2, we have a triangle
842 for (const auto node1: graph) {
843 const auto& children1 = graph.children(node1);
844 for (const auto node2: children1) {
845 const auto& children2 = graph.children(node2);
846 for (const auto node3: children2) {
847 if (children1.exists(node3)) {
848 // here, we have a triangle node1 -> node2 -> node3 + node1 -> node3.
849 // add the two possible arc triangle deletions (the v-structure cannot
850 // be on node3 since this would correspond to a deletion of arc
851 // node1 -> node2)
852 _addArcTriangleDeletion1ToSortedChanges_(ArcTriangleDeletion1(node1, node2, node3));
853 _addArcTriangleDeletion2ToSortedChanges_(ArcTriangleDeletion2(node1, node2, node3));
854 }
855 }
856 }
857 }
858 }
859
861 template < typename INVARIABLE_CONSTRAINT_TYPE, typename VARIABLE_CONSTRAINT_TYPE >
862 const GraphChange& GraphChangesSelector4DiGraph< INVARIABLE_CONSTRAINT_TYPE,
863 VARIABLE_CONSTRAINT_TYPE >::bestChange() {
864 for (const auto& _sorted_change: _sorted_changes_) {
865 if (_variable_constraints_->checkModification(_sorted_change)) { return _sorted_change; }
866 }
867
868 GUM_ERROR(NotFound, "there exists no graph change applicable")
869 }
870
872 template < typename INVARIABLE_CONSTRAINT_TYPE, typename VARIABLE_CONSTRAINT_TYPE >
873 double GraphChangesSelector4DiGraph< INVARIABLE_CONSTRAINT_TYPE,
874 VARIABLE_CONSTRAINT_TYPE >::bestDeltaScore() {
875 for (auto iter = _sorted_changes_.begin(); iter != _sorted_changes_.end(); ++iter) {
876 if (_variable_constraints_->checkModification(*iter)) { return iter.priority(); }
877 }
878
879 GUM_ERROR(NotFound, "there exists no graph change applicable")
880 }
881
883 template < typename INVARIABLE_CONSTRAINT_TYPE, typename VARIABLE_CONSTRAINT_TYPE >
884 double GraphChangesSelector4DiGraph< INVARIABLE_CONSTRAINT_TYPE, VARIABLE_CONSTRAINT_TYPE >::
885 deltaScore(const GraphChange& change, const bool internal_change) const {
886 try {
887 return _sorted_changes_.priority(change, internal_change);
888 } catch (NotFound&) {
889 GUM_ERROR(NotFound, "Change " << change.toString() << " will never be applicable")
890 }
891 }
892
894 template < typename INVARIABLE_CONSTRAINT_TYPE, typename VARIABLE_CONSTRAINT_TYPE >
895 double
896 GraphChangesSelector4DiGraph< INVARIABLE_CONSTRAINT_TYPE, VARIABLE_CONSTRAINT_TYPE >::score(
897 const NodeId node) const {
898 return _node_scores_[node];
899 }
900
902 template < typename INVARIABLE_CONSTRAINT_TYPE, typename VARIABLE_CONSTRAINT_TYPE >
903 bool GraphChangesSelector4DiGraph< INVARIABLE_CONSTRAINT_TYPE, VARIABLE_CONSTRAINT_TYPE >::
904 isChangeValid(const GraphChange& change) const {
905 return !_invariable_constraints_->isAlwaysInvalid(change)
906 && _variable_constraints_->checkModification(change);
907 }
908
910 template < typename INVARIABLE_CONSTRAINT_TYPE, typename VARIABLE_CONSTRAINT_TYPE >
911 void GraphChangesSelector4DiGraph< INVARIABLE_CONSTRAINT_TYPE, VARIABLE_CONSTRAINT_TYPE >::
912 _applyArcAddition_(const ArcAddition& change, bool update_contraints) {
913 // get the delta score of the change and update the score of the head
914 const auto tail = change.node1();
915 const auto head = change.node2();
916 const auto delta_score = _sorted_changes_.priority(change);
917 _node_scores_[head] += delta_score;
918
919 // update the graph
920 _graph_->addArc(tail, head);
921 _node_parents_[head].push_back(tail);
922 const auto& children_head = _graph_->children(head);
923 const auto& parents_head = _graph_->parents(head);
924
925 // indicate to the constraints that we added a new arc
926 if (update_contraints) {
927 _invariable_constraints_->modifyGraph(change);
928 _variable_constraints_->modifyGraph(change);
929 }
930
931 // now, we have to update the _sorted_changes_. Here, several modifications
932 // are needed:
933 // 1/ Remove the arc addition tail -> head we just applied
934 // 2/ Update the scores of the changes impacted by head's additional
935 // ingoing arc:
936 // 2.a/ all the additions of arcs whose heads are equal to "head"
937 // should be updated
938 // 2.b/ The scores of all the deletions of arcs whose heads are equal
939 // to "head" should be updated
940 // 2.c/ The scores of all the arc reversals involving "head" should
941 // be updated
942 // 2.d/ The scores of all the arc triangle deletions in which one of the
943 // nodes is head should be updated
944 // 3/ Add the changes induced by the application of the arc addition:
945 // 3.a/ Add the reverse change: Arc tail->head's deletion
946 // 3.b/ Arc tail->head's reversal should be added
947 // 3.c/ The new arc may have created some new triangles, hence we should
948 // also add their possible deletions to the _sorted_changes_
949
950 _sorted_changes_.erase(change, true); // case 1
951
952 // case 2.a:
953 if (_use_arc_additions_) {
954 for (const auto node: *_graph_) {
955 // here, no need to check whether node is different from tail because,
956 // in case 1, we just removed ArcAddition(tail, head)
957 _updateArcAdditionScore_(node, head);
958 }
959 }
960
961 // cases 2.b and 3.a
962 if (_use_arc_deletions_) {
963 // case 2.b:
964 for (const auto node: parents_head) {
965 // here, no need to check whether node is different from tail because,
966 // as we just added Arc tail -> head, this arc did not belonged yet to
967 // _sorted_changes_, hence the latter cannot contain yet
968 // ArcDeletion tail -> head
969 _updateArcDeletionScore_(node, head);
970 }
971
972 // case 3.a:
973 const ArcDeletion deletion(tail, head);
974 if (!_invariable_constraints_->isAlwaysInvalid(deletion)) {
975 _sorted_changes_.insert(deletion, -delta_score);
976 }
977 }
978
979 // cases 2.c and 3.b
980 if (_use_arc_reversals_) {
981 // case 2.c: hre, we consider the reversals of arcs node -> head
982 for (const auto node: parents_head) {
983 // here, no need to check whether node is different from tail because
984 // we just added Arc tail -> head, hence ArcReversal(tail, head) cannot
985 // belong yet to _sorted_changes_
986 _updateArcReversalScore_(node, head);
987 }
988
989 // case 2.c: we consider the reversals of arcs head -> node
990 for (const auto node: children_head) {
991 _updateArcReversalScore_(head, node);
992 }
993
994 // case 3.b:
995 // we must add an arc reversal tail->head
996 _addArcReversalToSortedChanges_(ArcReversal(tail, head));
997 }
998
999 // cases 2.d and 3.c:
1000 if (_use_arc_triangle_deletions_) {
1001 // case 2.d:
1002 _updateTriangleDeletionsScoresFromNeighborhood_(head);
1003
1004 // case 3.c:
1005 // there are 3 types of triangles: a/ tail->node->head, b/ tail->head->node
1006 // and c/ node->tail->head. We should examine these three cases
1007
1008 // cases a/ and c/
1009 const auto& children_tail = _graph_->children(tail);
1010 const auto& parents_tail = _graph_->parents(tail);
1011 for (const auto node: parents_head) {
1012 if (children_tail.contains(node)) { // case a
1013 _addArcTriangleDeletion1ToSortedChanges_(ArcTriangleDeletion1(tail, node, head));
1014 _addArcTriangleDeletion2ToSortedChanges_(ArcTriangleDeletion2(tail, node, head));
1015 } else if (parents_tail.contains(node)) { // case c
1016 _addArcTriangleDeletion1ToSortedChanges_(ArcTriangleDeletion1(node, tail, head));
1017 _addArcTriangleDeletion2ToSortedChanges_(ArcTriangleDeletion2(node, tail, head));
1018 }
1019 }
1020
1021 // case b/
1022 for (const auto node: children_tail) {
1023 if (children_head.exists(node)) {
1024 _addArcTriangleDeletion1ToSortedChanges_(ArcTriangleDeletion1(tail, head, node));
1025 _addArcTriangleDeletion2ToSortedChanges_(ArcTriangleDeletion2(tail, head, node));
1026 }
1027 }
1028 }
1029 }
1030
1032 template < typename INVARIABLE_CONSTRAINT_TYPE, typename VARIABLE_CONSTRAINT_TYPE >
1033 void GraphChangesSelector4DiGraph< INVARIABLE_CONSTRAINT_TYPE, VARIABLE_CONSTRAINT_TYPE >::
1034 _applyArcDeletion_(const ArcDeletion& change, bool update_contraints) {
1035 // get the delta score of the change and update the score of the head
1036 const auto tail = change.node1();
1037 const auto head = change.node2();
1038 const auto delta_score = _sorted_changes_.priority(change);
1039 _node_scores_[head] += delta_score;
1040
1041 // update the graph and the set of parents of the head
1042 _graph_->eraseArc(Arc(tail, head));
1043 auto& parents = _node_parents_[head];
1044 for (auto& par: parents) {
1045 if (par == tail) {
1046 par = *(parents.rbegin());
1047 parents.pop_back();
1048 break;
1049 }
1050 }
1051 const auto& children_head = _graph_->children(head);
1052 const auto& parents_head = _graph_->parents(head);
1053
1054 // indicate to the constraints that we removed the arc
1055 if (update_contraints) {
1056 _invariable_constraints_->modifyGraph(change);
1057 _variable_constraints_->modifyGraph(change);
1058 }
1059
1060 // now, we have to update the _sorted_changes_. Here, several modifications
1061 // are needed:
1062 // 1/ Remove arc deletion tail -> head we just applied
1063 // 2/ Update the scores of the changes impacted by the deletion:
1064 // 2.a/ The scores of all the additions of arcs whose heads are equal to
1065 // "head" should be updated
1066 // 2.b/ The scores of all the deletions of arcs whose heads are equal to
1067 // "head" should be updated
1068 // 2.c/ The scores of all the arc reversals involving "head" should
1069 // be updated
1070 // 2.d/ The scores of the triangle deletions which include head as one of
1071 // their nodes but not arc tail -> head should be updated
1072 // 3/ Add/remove the changes resulting from the application of the arc deletion:
1073 // 3.a/ add Arc addition tail -> head to _sorted_changes_
1074 // 3.b/ remove Arc reversal tail -> head from _sorted_changes_
1075 // 3.c/ The removed arc may have been involved into some triangles, hence we
1076 // should also remove them from _sorted_changes_
1077
1078 _sorted_changes_.erase(change); // case 1
1079
1080 // cases 2.a and 3.a
1081 if (_use_arc_additions_) {
1082 // case 2.a
1083 for (const auto node: *_graph_) {
1084 // here, no need to check whether node is different from tail because
1085 // we just added the arc tail -> head to the graph, hence
1086 // ArcAddition(tail, head) does not belong yet to _sorted_changes_
1087 _updateArcAdditionScore_(node, head);
1088 }
1089
1090 // case 3.a
1091 const ArcAddition addition(tail, head);
1092 if (!_invariable_constraints_->isAlwaysInvalid(addition)) {
1093 _sorted_changes_.insert(addition, -delta_score);
1094 }
1095 }
1096
1097 // case 2.b
1098 if (_use_arc_deletions_) {
1099 for (const auto node: parents_head) {
1100 // here, no need to check whether node is different from tail because
1101 // we just erased in case 1 ArcDeletion(tail, head)
1102 _updateArcDeletionScore_(node, head);
1103 }
1104 }
1105
1106 // cases 2.c and 3.b
1107 if (_use_arc_reversals_) {
1108 _sorted_changes_.erase(ArcReversal(tail, head)); // case 3.b
1109
1110 // case 2.c: we consider the reversals of arcs node -> head
1111 for (const auto node: parents_head) {
1112 // here, no need to check whether node is different from tail because
1113 // we just erased in case 2 ArcReversal(tail, head)
1114 _updateArcReversalScore_(node, head);
1115 }
1116
1117 // case 2.c: we consider the reversals of arcs head -> node
1118 for (const auto node: children_head) {
1119 _updateArcReversalScore_(head, node);
1120 }
1121 }
1122
1123 // cases 2.d and 3.c:
1124 if (_use_arc_triangle_deletions_) {
1125 // case 3.c:
1126 // there are 3 types of triangles: a/ tail->node->head, b/ tail->head->node
1127 // and c/ node->tail->head. We should examine these three cases
1128
1129 // cases a/ and c/
1130 const auto& children_tail = _graph_->children(tail);
1131 const auto& parents_tail = _graph_->parents(tail);
1132 for (const auto node: parents_head) {
1133 if (children_tail.contains(node)) { // case a
1134 _sorted_changes_.erase(ArcTriangleDeletion1(tail, node, head));
1135 _sorted_changes_.erase(ArcTriangleDeletion2(tail, node, head));
1136 }
1137 if (parents_tail.contains(node)) { // case c
1138 _sorted_changes_.erase(ArcTriangleDeletion1(node, tail, head));
1139 _sorted_changes_.erase(ArcTriangleDeletion2(node, tail, head));
1140 }
1141 }
1142
1143 // case b/
1144 for (const auto node: children_tail) {
1145 if (children_head.contains(node)) {
1146 _sorted_changes_.erase(ArcTriangleDeletion1(tail, head, node));
1147 _sorted_changes_.erase(ArcTriangleDeletion2(tail, head, node));
1148 }
1149 }
1150
1151 // case 2.d:
1152 _updateTriangleDeletionsScoresFromNeighborhood_(head);
1153 }
1154 }
1155
1157 template < typename INVARIABLE_CONSTRAINT_TYPE, typename VARIABLE_CONSTRAINT_TYPE >
1158 void GraphChangesSelector4DiGraph< INVARIABLE_CONSTRAINT_TYPE, VARIABLE_CONSTRAINT_TYPE >::
1159 _applyArcReversal_(const ArcReversal& change, bool update_contraints) {
1160 // update the graph
1161 const auto tail = change.node1();
1162 const auto head = change.node2();
1163 _graph_->eraseArc(Arc(tail, head));
1164 _graph_->addArc(head, tail);
1165 const auto& children_head = _graph_->children(head);
1166 const auto& parents_head = _graph_->parents(head);
1167 const auto& children_tail = _graph_->children(tail);
1168 const auto& parents_tail = _graph_->parents(tail);
1169
1170 auto& node_parents_tail = _node_parents_[tail];
1171 node_parents_tail.push_back(head);
1172 auto& node_parents_head = _node_parents_[head];
1173 for (auto& par: node_parents_head) {
1174 if (par == tail) {
1175 par = *(node_parents_head.rbegin());
1176 node_parents_head.pop_back();
1177 break;
1178 }
1179 }
1180
1181 // update the scores of the head and of the tail
1182 const auto overall_delta_score = _sorted_changes_.priority(change);
1183 const auto delta_score_tail = _score_->score(tail, node_parents_tail) - _node_scores_[tail];
1184
1185 _node_scores_[head] += overall_delta_score - delta_score_tail;
1186 _node_scores_[tail] += delta_score_tail;
1187
1188 // indicate to the constraints that we reversed an arc
1189 if (update_contraints) {
1190 _invariable_constraints_->modifyGraph(change);
1191 _variable_constraints_->modifyGraph(change);
1192 }
1193
1194 // now, we have to update the _sorted_changes_. Here, several modifications
1195 // are needed:
1196 // 1/ Remove Arc reversal tail -> head from _sorted_changes_
1197 // 2/ Update the scores of the changes impacted by the arc reversal:
1198 // 2.a/ The scores of all the additions of arcs whose heads are equal to
1199 // "head" or "tail" should be updated
1200 // 2.b/ The scores of all the deletions of arcs whose heads are equal to
1201 // "head" or "tail" should be updated
1202 // 2.c/ The scores of all the arc reversals involving "head" or "tail"
1203 // should be updated
1204 // 2.d/ The scores of the triangle deletions which include head as one of
1205 // their nodes but not arc tail -> head should be updated
1206 // 2.e/ The scores of all the arc triangle deletions in which one of the
1207 // nodes is tail should be updated
1208 // 3/ Add/remove the changes resulting from the application of the arc reversal:
1209 // 3.a/ since arc tail -> head existed, its Arc deletion tail -> head should
1210 // be also removed from _sorted_changes_.
1211 // 3.b/ since arc head -> tail did not exit, Arc addition head -> tail should
1212 // be removed
1213 // 3.c/ Arc addition tail -> head should be added to _sorted_changes_
1214 // 3.d/ Arc head->tail's deletion should be added
1215 // 3.e/ Arc head->tail's reversal should be added
1216 // 3.f/ Arc tail -> head has been removed. It may have been involved into
1217 // some triangles, hence we should also remove them from _sorted_changes_
1218 // 3.g/ Arc head -> tail has been added. It may have created some triangles,
1219 // hence we should also add their possible removals to the
1220 // _sorted_changes_
1221 _sorted_changes_.erase(change); // case 1
1222
1223 // cases 2.a, 3.b and 3.c
1224 if (_use_arc_additions_) {
1225 // case 3.b:
1226 _sorted_changes_.erase(ArcAddition(head, tail));
1227
1228 // case 2.a:
1229 for (const auto node: *_graph_) {
1230 _updateArcAdditionScore_(node, head);
1231 _updateArcAdditionScore_(node, tail);
1232 }
1233
1234 // case 3.c:
1235 _addArcAdditionToSortedChanges_(ArcAddition(tail, head));
1236 }
1237
1238 // cases 2.b, 3.a and 3.d:
1239 if (_use_arc_deletions_) {
1240 // case 3.a
1241 _sorted_changes_.erase(ArcDeletion(tail, head));
1242
1243 // case 2.b: we consider the deletions of arcs node -> head
1244 for (const auto node: parents_head) {
1245 _updateArcDeletionScore_(node, head);
1246 }
1247
1248 // case 2.b: we consider the deletions of arcs node -> tail
1249 for (const auto node: parents_tail) {
1250 _updateArcDeletionScore_(node, tail);
1251 }
1252
1253 // case 3.d:
1254 const ArcDeletion deletion(head, tail);
1255 if (!_invariable_constraints_->isAlwaysInvalid(deletion)) {
1256 _sorted_changes_.insert(deletion, -delta_score_tail);
1257 }
1258 }
1259
1260 // cases 2.c and 3.e:
1261 if (_use_arc_reversals_) {
1262 // case 2.c: we consider the reversals of arcs node -> head
1263 for (const auto node: parents_head) {
1264 _updateArcReversalScore_(node, head);
1265 }
1266
1267 // case 2.c: we consider the reversals of arcs head -> node
1268 for (const auto node: children_head) {
1269 _updateArcReversalScore_(head, node);
1270 }
1271
1272 // case 2.c: we consider the reversals of arcs node -> tail
1273 for (const auto node: parents_tail) {
1274 _updateArcReversalScore_(node, tail);
1275 }
1276
1277 // case 2.c: we consider the reversals of arcs tail -> node
1278 for (const auto node: children_tail) {
1279 _updateArcReversalScore_(tail, node);
1280 }
1281
1282 // case 3.e:
1283 const ArcReversal reversal(head, tail);
1284 if (!_invariable_constraints_->isAlwaysInvalid(reversal)) {
1285 _sorted_changes_.insert(reversal, -overall_delta_score);
1286 }
1287 }
1288
1289 // cases 2.d, 2.e, 3.f and 3.g:
1290 if (_use_arc_triangle_deletions_) {
1291 // case 3.f:
1292 // there are 3 types of triangles: a/ tail->node->head, b/ tail->head->node
1293 // and c/ node->tail->head. We should examine these three cases
1294 for (const auto node: parents_head) {
1295 if (children_tail.contains(node)) { // case a
1296 _sorted_changes_.erase(ArcTriangleDeletion1(tail, node, head));
1297 _sorted_changes_.erase(ArcTriangleDeletion2(tail, node, head));
1298 }
1299 if (parents_tail.exists(node)) { // case c
1300 _sorted_changes_.erase(ArcTriangleDeletion1(node, tail, head));
1301 _sorted_changes_.erase(ArcTriangleDeletion2(node, tail, head));
1302 }
1303 }
1304
1305 for (const auto node: children_tail) { // case b/
1306 if (children_head.contains(node)) {
1307 _sorted_changes_.erase(ArcTriangleDeletion1(tail, head, node));
1308 _sorted_changes_.erase(ArcTriangleDeletion2(tail, head, node));
1309 }
1310 }
1311
1312 // cases 2.d and 2.e:
1313 _updateTriangleDeletionsScoresFromNeighborhood_(head);
1314 _updateTriangleDeletionsScoresFromNeighborhood_(tail);
1315
1316 // case 3.g:
1317 // there are 3 types of triangles: a/ head->node->tail, b/ head->tail->node
1318 // and c/ node->head->tail. We should examine these three cases
1319 for (const auto node: parents_tail) {
1320 if (children_head.contains(node)) { // case a
1321 _addArcTriangleDeletion1ToSortedChanges_(ArcTriangleDeletion1(head, node, tail));
1322 _addArcTriangleDeletion2ToSortedChanges_(ArcTriangleDeletion2(head, node, tail));
1323 }
1324 if (parents_head.exists(node)) { // case c
1325 _addArcTriangleDeletion1ToSortedChanges_(ArcTriangleDeletion1(node, head, tail));
1326 _addArcTriangleDeletion2ToSortedChanges_(ArcTriangleDeletion2(node, head, tail));
1327 }
1328 }
1329
1330 // case b/
1331 for (const auto node: children_head) {
1332 if (children_tail.contains(node)) {
1333 _addArcTriangleDeletion1ToSortedChanges_(ArcTriangleDeletion1(head, tail, node));
1334 _addArcTriangleDeletion2ToSortedChanges_(ArcTriangleDeletion2(head, tail, node));
1335 }
1336 }
1337 }
1338 }
1339
1341 template < typename INVARIABLE_CONSTRAINT_TYPE, typename VARIABLE_CONSTRAINT_TYPE >
1342 void GraphChangesSelector4DiGraph< INVARIABLE_CONSTRAINT_TYPE, VARIABLE_CONSTRAINT_TYPE >::
1343 _applyArcTriangleDeletion1_(const ArcTriangleDeletion1& xchange, bool update_contraints) {
1344 // save the change because we will remove it before updating the constraints
1345 // and we need the change for these updates
1346 ArcTriangleDeletion1 change(xchange);
1347
1348 const auto node1 = change.node1();
1349 const auto node2 = change.node2();
1350 const auto node3 = change.node3();
1351
1352 // remove the arc triangle deletion
1353 _sorted_changes_.erase(xchange);
1354
1355 // apply the deletion:
1356 // 1/ remove arc node2 -> node3
1357 _applyArcDeletion_(ArcDeletion(node2, node3), false);
1358
1359 // 2/ reverse arc node1 -> node2
1360 _applyArcReversal_(ArcReversal(node1, node2), false);
1361
1362 // 3/ reverse node1 -> node3
1363 _applyArcReversal_(ArcReversal(node1, node3), false);
1364
1365 if (update_contraints) {
1366 _invariable_constraints_->modifyGraph(change);
1367 _variable_constraints_->modifyGraph(change);
1368 }
1369 }
1370
1372 template < typename INVARIABLE_CONSTRAINT_TYPE, typename VARIABLE_CONSTRAINT_TYPE >
1373 void GraphChangesSelector4DiGraph< INVARIABLE_CONSTRAINT_TYPE, VARIABLE_CONSTRAINT_TYPE >::
1374 _applyArcTriangleDeletion2_(const ArcTriangleDeletion2& xchange, bool update_contraints) {
1375 // save the change because we will remove it before updating the constraints
1376 // and we need the change for these updates
1377 ArcTriangleDeletion2 change(xchange);
1378
1379 const auto node1 = change.node1();
1380 const auto node2 = change.node2();
1381 const auto node3 = change.node3();
1382
1383 // remove the arc triangle deletion
1384 _sorted_changes_.erase(change);
1385
1386 // apply the deletion:
1387 // 1/ remove arc node1 -> node3
1388 _applyArcDeletion_(ArcDeletion(node1, node3), false);
1389
1390 // 2/ try to reverse arc node2 -> node4
1391 _applyArcReversal_(ArcReversal(node2, node3), false);
1392
1393 if (update_contraints) {
1394 _invariable_constraints_->modifyGraph(change);
1395 _variable_constraints_->modifyGraph(change);
1396 }
1397 }
1398
1400 template < typename INVARIABLE_CONSTRAINT_TYPE, typename VARIABLE_CONSTRAINT_TYPE >
1401 void GraphChangesSelector4DiGraph< INVARIABLE_CONSTRAINT_TYPE, VARIABLE_CONSTRAINT_TYPE >::
1402 applyChange(const GraphChange& change) {
1403 switch (change.type()) {
1404 case GraphChangeType::ARC_ADDITION :
1405 _applyArcAddition_(static_cast< const ArcAddition& >(change), true);
1406 break;
1407
1408 case GraphChangeType::ARC_DELETION :
1409 _applyArcDeletion_(static_cast< const ArcDeletion& >(change), true);
1410 break;
1411
1412 case GraphChangeType::ARC_REVERSAL :
1413 _applyArcReversal_(static_cast< const ArcReversal& >(change), true);
1414 break;
1415
1416 case GraphChangeType::ARC_TRIANGLE_DELETION1 :
1417 _applyArcTriangleDeletion1_(static_cast< const ArcTriangleDeletion1& >(change), true);
1418 break;
1419
1420 case GraphChangeType::ARC_TRIANGLE_DELETION2 :
1421 _applyArcTriangleDeletion2_(static_cast< const ArcTriangleDeletion2& >(change), true);
1422 break;
1423
1424 default :
1426 "Method applyChange of GraphChangesSelector4DiGraph "
1427 << "does not handle yet graph change of type " << change.typeAsString());
1428 }
1429 }
1430
1431
1432 } /* namespace learning */
1433
1434} /* namespace gum */
1435
1436#endif /* DOXYGEN_SHOULD_SKIP_THIS */
Exception : the element we looked for cannot be found.
Exception : there is something wrong with an implementation.
GraphChangesSelector4DiGraph(Score &score, INVARIABLE_CONSTRAINT_TYPE &invariable_constraints, VARIABLE_CONSTRAINT_TYPE &variable_constraints)
default constructor
The base class for all the scores used for learning (BIC, BDeu, etc).
Definition score.h:68
#define GUM_ERROR(type, msg)
Definition exceptions.h:76
The mecanism to compute the next available graph changes for directed structure learning search algor...
Set< NodeId > NodeSet
Some typdefs and define for shortcuts ...
include the inlined functions if necessary
Definition CSVParser.h:55
gum is the global namespace for all aGrUM entities
Definition agrum.h:46
STL namespace.