gtsam
Loading...
Searching...
No Matches
ClusterTree-inst.h
Go to the documentation of this file.
1
9
10#pragma once
11
14#include <gtsam/base/timing.h>
16
17#ifdef GTSAM_USE_TBB
18#include <mutex>
19#endif
20#include <queue>
21#include <cassert>
22
23namespace gtsam {
24
25/* ************************************************************************* */
26template<class GRAPH>
27void ClusterTree<GRAPH>::Cluster::print(const std::string& s,
28 const KeyFormatter& keyFormatter) const {
29 std::cout << s << " (" << problemSize_ << ")";
31}
32
33/* ************************************************************************* */
34template <class GRAPH>
36 std::vector<size_t> nrFrontals;
37 nrFrontals.reserve(nrChildren());
38 for (const sharedNode& child : children)
39 nrFrontals.push_back(child->nrFrontals());
40 return nrFrontals;
41}
42
43/* ************************************************************************* */
44template <class GRAPH>
45KeySet ClusterTree<GRAPH>::Cluster::separatorKeys(KeySetMap* cache) const {
46 if (cache) {
47 auto it = cache->find(this);
48 if (it != cache->end()) return it->second;
49 }
50
51 KeySet keys;
52 for (const auto& factor : factors) {
53 if (!factor) continue;
54 keys.insert(factor->begin(), factor->end());
55 }
56 for (const auto& child : children) {
57 KeySet childSeparators = child->separatorKeys(cache);
58 keys.insert(childSeparators.begin(), childSeparators.end());
59 }
60 for (Key key : orderedFrontalKeys) {
61 keys.erase(key);
62 }
63
64 if (cache) {
65 auto result = cache->emplace(this, std::move(keys));
66 return result.first->second;
67 }
68 return keys;
69}
70
71/* ************************************************************************* */
72template <class GRAPH>
73void ClusterTree<GRAPH>::Cluster::merge(const std::shared_ptr<Cluster>& cluster) {
74 // Merge keys. For efficiency, we add keys in reverse order at end, calling reverse after..
75 orderedFrontalKeys.insert(orderedFrontalKeys.end(), cluster->orderedFrontalKeys.rbegin(),
76 cluster->orderedFrontalKeys.rend());
77 factors.push_back(cluster->factors);
78 children.insert(children.end(), cluster->children.begin(), cluster->children.end());
79 // Increment problem size
80 problemSize_ = std::max(problemSize_, cluster->problemSize_);
81}
82
83/* ************************************************************************* */
84template <class GRAPH>
89
90/* ************************************************************************* */
91template <class GRAPH>
93 const Children& selected) {
94 gttic(Cluster_mergeChildren);
95 // Merge selected children into this node while preserving unselected children.
96 if (selected.empty()) return;
97
98 FastSet<const Cluster*> selectedSet;
99 for (const auto& child : selected) {
100 if (child) {
101 selectedSet.insert(child.get());
102 }
103 }
104 if (selectedSet.empty()) return;
105
106 // Count how many keys, factors and children we'll end up with
107 size_t nrKeys = orderedFrontalKeys.size();
108 size_t nrFactors = factors.size();
109 size_t nrNewChildren = 0;
110 for (const sharedNode& child : this->children) {
111 if (child && selectedSet.count(child.get()) != 0) {
112 nrKeys += child->orderedFrontalKeys.size();
113 nrFactors += child->factors.size();
114 nrNewChildren += child->nrChildren();
115 } else {
116 nrNewChildren += 1; // we keep the child
117 }
118 }
119
120 // now reserve space, and really merge
121 auto oldChildren = this->children;
122 this->children.clear();
123 this->children.reserve(nrNewChildren);
124 orderedFrontalKeys.reserve(nrKeys);
125 factors.reserve(nrFactors);
126 for (const sharedNode& child : oldChildren) {
127 if (child && selectedSet.count(child.get()) != 0) {
128 this->merge(child);
129 } else {
130 this->addChild(child); // we keep the child
131 }
132 }
133 // merge() appends keys in reverse order to defer a final reverse.
134 std::reverse(orderedFrontalKeys.begin(), orderedFrontalKeys.end());
135}
136
137/* ************************************************************************* */
138template <class GRAPH>
143
144/* ************************************************************************* */
145template <class GRAPH>
147 const Children& selected) {
148 gttic(Cluster_mergeChildrenSiblings);
149 // Merge selected siblings into a new child while keeping unselected children.
150 if (selected.empty()) return;
151
152 FastSet<const Cluster*> selectedSet;
153 for (const auto& child : selected) {
154 if (child) {
155 selectedSet.insert(child.get());
156 }
158 const size_t selectedCount = selectedSet.size();
159 // Nothing to merge (0 or 1 selected), so keep children unchanged.
160 if (selectedCount <= 1) return;
161
162 auto oldChildren = this->children;
163 Children newChildren;
164 newChildren.reserve(oldChildren.size() - selectedCount + 1);
165 auto merged = std::make_shared<Cluster>();
166 bool inserted = false;
167
168 for (const sharedNode& child : oldChildren) {
169 if (child && selectedSet.count(child.get()) != 0) {
170 // Merge selected siblings into a single new cluster.
171 merged->merge(child);
172 if (!inserted) {
173 // Insert merged cluster at the first selected child's position.
174 newChildren.push_back(merged);
175 inserted = true;
176 }
177 } else {
178 newChildren.push_back(child);
179 }
180 }
181
182 // merge() appends keys in reverse order to defer a final reverse.
183 std::reverse(merged->orderedFrontalKeys.begin(),
184 merged->orderedFrontalKeys.end());
185 this->children.swap(newChildren);
186}
187
188/* ************************************************************************* */
189template <class GRAPH>
192 const std::vector<bool>& merge) const {
193 assert(merge.size() == this->children.size());
194 // Translate a boolean mask into the corresponding child pointers.
195 Children selected;
196 for (size_t i = 0; i < children.size(); ++i) {
197 if (merge[i]) {
198 selected.push_back(children[i]);
199 }
200 }
201 return selected;
202}
203
204/* ************************************************************************* */
205template <class GRAPH>
206void ClusterTree<GRAPH>::print(const std::string& s, const KeyFormatter& keyFormatter) const {
207 treeTraversal::PrintForest(*this, s, keyFormatter);
208}
209
210/* ************************************************************************* */
211
212/* Destructor.
213 * Using default destructor causes stack overflow for large trees due to recursive destruction of nodes;
214 * so we manually decrease the reference count of each node in the tree through a BFS, and the nodes with
215 * reference count 0 will be deleted. Please see [PR-1441](https://github.com/borglab/gtsam/pull/1441) for more details.
216 */
217template <class GRAPH>
218ClusterTree<GRAPH>::~ClusterTree() {
219 // For each tree, we first move the root into a queue; then we do a BFS on the tree with the queue;
220
221 for (auto&& root : roots_) {
222 std::queue<sharedNode> bfs_queue;
223
224 // first, steal the root and move it to the queue. This invalidates root
225 bfs_queue.push(std::move(root));
226
227 // for each node iterated, if its reference count is 1, it will be deleted while its children are still in the queue.
228 // so that the recursive deletion will not happen.
229 while (!bfs_queue.empty()) {
230 // move the ownership of the front node from the queue to the current variable, invalidating the sharedClique at the front of the queue
231 auto node = std::move(bfs_queue.front());
232 bfs_queue.pop();
233
234 // add the children of the current node to the queue, so that the queue will also own the children nodes.
235 for (auto child : node->children) {
236 bfs_queue.push(std::move(child));
237 } // leaving the scope of current will decrease the reference count of the current node by 1, and if the reference count is 0,
238 // the node will be deleted. Because the children are in the queue, the deletion of the node will not trigger a recursive
239 // deletion of the children.
240 }
241 }
242
243}
244
245/* ************************************************************************* */
246template <class GRAPH>
248 // Start by duplicating the tree.
250 return *this;
251}
252
253/* ************************************************************************* */
254// Elimination traversal data - stores a pointer to the parent data and collects
255// the factors resulting from elimination of the children. Also sets up BayesTree
256// cliques with parent and child pointers.
257template<class CLUSTERTREE>
258struct EliminationData {
259 // Typedefs
260 typedef typename CLUSTERTREE::sharedFactor sharedFactor;
261 typedef typename CLUSTERTREE::FactorType FactorType;
262 typedef typename CLUSTERTREE::FactorGraphType FactorGraphType;
263 typedef typename CLUSTERTREE::ConditionalType ConditionalType;
264 typedef typename CLUSTERTREE::BayesTreeType::Node BTNode;
265
266 EliminationData* const parentData;
267 size_t myIndexInParent;
268 FastVector<sharedFactor> childFactors;
269 std::shared_ptr<BTNode> bayesTreeNode;
270#ifdef GTSAM_USE_TBB
271 std::shared_ptr<std::mutex> writeLock;
272#endif
273
274 EliminationData(EliminationData* _parentData, size_t nChildren) :
275 parentData(_parentData), bayesTreeNode(std::make_shared<BTNode>())
276#ifdef GTSAM_USE_TBB
277 , writeLock(std::make_shared<std::mutex>())
278#endif
279 {
280 if (parentData) {
281#ifdef GTSAM_USE_TBB
282 parentData->writeLock->lock();
283#endif
284 myIndexInParent = parentData->childFactors.size();
285 parentData->childFactors.push_back(sharedFactor());
286#ifdef GTSAM_USE_TBB
287 parentData->writeLock->unlock();
288#endif
289 } else {
290 myIndexInParent = 0;
291 }
292 // Set up BayesTree parent and child pointers
293 if (parentData) {
294 if (parentData->parentData) // If our parent is not the dummy node
295 bayesTreeNode->parent_ = parentData->bayesTreeNode;
296 parentData->bayesTreeNode->children.push_back(bayesTreeNode);
297 }
298 }
299
300 // Elimination pre-order visitor - creates the EliminationData structure for the visited node.
301 static EliminationData EliminationPreOrderVisitor(
302 const typename CLUSTERTREE::sharedNode& node,
303 EliminationData& parentData) {
304 assert(node);
305 EliminationData myData(&parentData, node->nrChildren());
306 myData.bayesTreeNode->problemSize_ = node->problemSize();
307 return myData;
308 }
309
310 // Elimination post-order visitor - combine the child factors with our own factors, add the
311 // resulting conditional to the BayesTree, and add the remaining factor to the parent.
312 class EliminationPostOrderVisitor {
313 const typename CLUSTERTREE::Eliminate& eliminationFunction_;
314 typename CLUSTERTREE::BayesTreeType::Nodes& nodesIndex_;
315
316 public:
317 // Construct functor
318 EliminationPostOrderVisitor(
319 const typename CLUSTERTREE::Eliminate& eliminationFunction,
320 typename CLUSTERTREE::BayesTreeType::Nodes& nodesIndex) :
321 eliminationFunction_(eliminationFunction), nodesIndex_(nodesIndex) {
322 }
323
324 // Function that does the HEAVY lifting
325 void operator()(const typename CLUSTERTREE::sharedNode& node, EliminationData& myData) {
326 assert(node);
327
328 // Gather factors
329 FactorGraphType gatheredFactors;
330 gatheredFactors.reserve(node->factors.size() + node->nrChildren());
331 gatheredFactors.push_back(node->factors);
332 gatheredFactors.push_back(myData.childFactors);
333
334 // Check for Bayes tree orphan subtrees, and add them to our children
335 // TODO(frank): should this really happen here?
336 for (const sharedFactor& factor: node->factors) {
337 auto asSubtree = dynamic_cast<const BayesTreeOrphanWrapper<BTNode>*>(factor.get());
338 if (asSubtree) {
339 myData.bayesTreeNode->children.push_back(asSubtree->clique);
340 asSubtree->clique->parent_ = myData.bayesTreeNode;
341 }
342 }
343
344 // >>>>>>>>>>>>>> Do dense elimination step >>>>>>>>>>>>>>>>>>>>>>>>>>>>>
345 auto eliminationResult = eliminationFunction_(gatheredFactors, node->orderedFrontalKeys);
346 // <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
347
348 // Store conditional in BayesTree clique, and in the case of ISAM2Clique also store the
349 // remaining factor
350 myData.bayesTreeNode->setEliminationResult(eliminationResult);
351
352 // Fill nodes index - we do this here instead of calling insertRoot at the end to avoid
353 // putting orphan subtrees in the index - they'll already be in the index of the ISAM2
354 // object they're added to.
355 for (const Key& j : myData.bayesTreeNode->conditional()->frontals()) {
356#ifdef GTSAM_USE_TBB
357 nodesIndex_.insert({j, myData.bayesTreeNode});
358#else
359 nodesIndex_.emplace(j, myData.bayesTreeNode);
360#endif
361 }
362 // Store remaining factor in parent's gathered factors
363 if (!eliminationResult.second->empty()) {
364#ifdef GTSAM_USE_TBB
365 myData.parentData->writeLock->lock();
366#endif
367 myData.parentData->childFactors[myData.myIndexInParent] = eliminationResult.second;
368#ifdef GTSAM_USE_TBB
369 myData.parentData->writeLock->unlock();
370#endif
371 }
372 }
373 };
374};
375
376/* ************************************************************************* */
377template<class BAYESTREE, class GRAPH>
379 const This& other) {
381
382 // Assign the remaining factors - these are pointers to factors in the original factor graph and
383 // we do not clone them.
384 remainingFactors_ = other.remainingFactors_;
385
386 return *this;
387}
388
389/* ************************************************************************* */
390template <class BAYESTREE, class GRAPH>
391std::pair<std::shared_ptr<BAYESTREE>, std::shared_ptr<GRAPH> >
393 gttic(ClusterTree_eliminate);
394 // Do elimination (depth-first traversal). The rootsContainer stores a 'dummy' BayesTree node
395 // that contains all of the roots as its children. rootsContainer also stores the remaining
396 // un-eliminated factors passed up from the roots.
397 std::shared_ptr<BayesTreeType> result = std::make_shared<BayesTreeType>();
398
399 typedef EliminationData<This> Data;
400 Data rootsContainer(0, this->nrRoots());
401
402 typename Data::EliminationPostOrderVisitor visitorPost(function, result->nodes_);
403 {
404 TbbOpenMPMixedScope threadLimiter; // Limits OpenMP threads since we're mixing TBB and OpenMP
405 treeTraversal::DepthFirstForestParallel(*this, rootsContainer, Data::EliminationPreOrderVisitor,
406 visitorPost, 10);
407 }
408
409 // Create BayesTree from roots stored in the dummy BayesTree node.
410 result->roots_.insert(result->roots_.end(), rootsContainer.bayesTreeNode->children.begin(),
411 rootsContainer.bayesTreeNode->children.end());
412
413 // Add remaining factors that were not involved with eliminated variables
414 std::shared_ptr<FactorGraphType> remaining = std::make_shared<FactorGraphType>();
415 remaining->reserve(remainingFactors_.size() + rootsContainer.childFactors.size());
416 remaining->push_back(remainingFactors_.begin(), remainingFactors_.end());
417 for (const sharedFactor& factor : rootsContainer.childFactors) {
418 if (factor)
419 remaining->push_back(factor);
420 }
421
422 // Return result
423 return {result, remaining};
424}
425
426} // namespace gtsam
Timing utilities.
Bayes Tree is a tree of cliques of a Bayes Chain.
Collects factorgraph fragments defined on variable clusters, arranged in a tree.
std::vector< T, typename internal::FastDefaultVectorAllocator< T >::type > FastVector
FastVector is a type alias to a std::vector with a custom memory allocator.
Definition FastVector.h:33
Global functions in a separate testing namespace.
Definition chartTesting.h:28
void PrintKeyVector(const KeyVector &keys, const string &s, const KeyFormatter &keyFormatter)
Utility function to print sets of keys with optional prefix.
Definition Key.cpp:84
std::function< std::string(Key)> KeyFormatter
Typedef for a function to format a key, i.e. to convert it to a string.
Definition Key.h:35
std::uint64_t Key
Integer nonlinear key type.
Definition types.h:43
FastVector< std::shared_ptr< typename FOREST::Node > > CloneForest(const FOREST &forest)
Clone a tree, copy-constructing new nodes (calling std::make_shared) and setting up child pointers fo...
Definition treeTraversal-inst.h:246
void PrintForest(const FOREST &forest, std::string str, const KeyFormatter &keyFormatter)
Print a tree, prefixing each line with str, and formatting keys using keyFormatter.
Definition treeTraversal-inst.h:276
void DepthFirstForestParallel(FOREST &forest, DATA &rootData, VISITOR_PRE &visitorPre, VISITOR_POST &visitorPost, int problemSizeThreshold=10)
Traverse a forest depth-first with pre-order and post-order visits.
Definition treeTraversal-inst.h:181
FastSet is a thin wrapper around std::set that uses the boost fast_pool_allocator instead of the defa...
Definition FastSet.h:54
An object whose scope defines a block where TBB and OpenMP parallelism are mixed.
Definition types.h:87
EliminatableClusterTree< BAYESTREE, GRAPH > This
This class.
Definition ClusterTree.h:209
This & operator=(const This &other)
Assignment operator - makes a deep copy of the tree structure, but only pointers to factors are copie...
Definition ClusterTree-inst.h:378
EliminatableClusterTree(const This &other)
Copy constructor - makes a deep copy of the tree structure, but only pointers to factors are copied,...
Definition ClusterTree.h:228
std::shared_ptr< FactorType > sharedFactor
Shared pointer to a factor.
Definition ClusterTree.h:218
std::pair< std::shared_ptr< BayesTreeType >, std::shared_ptr< FactorGraphType > > eliminate(const Eliminate &function) const
Eliminate the factors to a Bayes tree and remaining factor graph.
Definition ClusterTree-inst.h:392
GRAPH::Eliminate Eliminate
Typedef for an eliminate subroutine.
Definition ClusterTree.h:216
Definition BayesTree.h:405
Definition ClusterTree-inst.h:258
This & operator=(const This &other)
Assignment operator - makes a deep copy of the tree structure, but only pointers to factors are copie...
Definition ClusterTree-inst.h:247
ClusterTree< GRAPH > This
This class.
Definition ClusterTree.h:30
FastVector< sharedNode > roots_
concept check
Definition ClusterTree.h:135
void print(const std::string &s="", const KeyFormatter &keyFormatter=DefaultKeyFormatter) const
Print the cluster tree.
Definition ClusterTree-inst.h:206
ClusterTree(const This &other)
Copy constructor - makes a deep copy of the tree structure, but only pointers to factors are copied,...
Definition ClusterTree.h:142
Children children
sub-trees
Definition ClusterTree.h:40
virtual void print(const std::string &s="", const KeyFormatter &keyFormatter=DefaultKeyFormatter) const
print this node
Definition ClusterTree-inst.h:27
void merge(const std::shared_ptr< Cluster > &cluster)
Merge in given cluster.
Definition ClusterTree-inst.h:73
KeySet separatorKeys(KeySetMap *cache=nullptr) const
Return the separator keys (subtree keys minus frontals), optionally cached.
Definition ClusterTree-inst.h:45
void mergeChildrenSiblings(const std::vector< bool > &merge)
Merge selected siblings into a new child cluster.
Definition ClusterTree-inst.h:139
Keys orderedFrontalKeys
Frontal keys of this node.
Definition ClusterTree.h:43
void mergeChildren(const std::vector< bool > &merge)
Merge all children for which bit is set into this node.
Definition ClusterTree-inst.h:85
std::vector< size_t > nrFrontalsOfChildren() const
Return a vector with nrFrontal keys for each child.
Definition ClusterTree-inst.h:35
FactorGraphType factors
Factors associated with this node.
Definition ClusterTree.h:45
void addChild(const std::shared_ptr< Cluster > &cluster)
Definition ClusterTree.h:73
Children childrenFromMask(const std::vector< bool > &merge) const
Convert a child-selection mask into the selected child pointers.
Definition ClusterTree-inst.h:191