gtsam
Loading...
Searching...
No Matches
FindSeparator-inl.h
1/*
2 * FindSeparator-inl.h
3 *
4 * Created on: Nov 23, 2010
5 * Updated: Feb 20. 2014
6 * Author: nikai
7 * Author: Andrew Melim
8 * Description: find the separator of bisectioning for a given graph
9 */
10
11#pragma once
12
13#include <stdexcept>
14#include <iostream>
15#include <string>
16#include <vector>
17#include <optional>
18#include <cassert>
19#include <boost/shared_array.hpp>
20
21#include <gtsam/base/timing.h>
22
23#include "FindSeparator.h"
24
25#include <metis.h>
26
27namespace gtsam { namespace partition {
28
29 typedef boost::shared_array<idx_t> sharedInts;
30
31 /* ************************************************************************* */
38 std::pair<idx_t, sharedInts> separatorMetis(idx_t n, const sharedInts& xadj,
39 const sharedInts& adjncy, const sharedInts& adjwgt, bool verbose) {
40
41 // control parameters
42 std::vector<idx_t> vwgt; // the weights of the vertices
43 idx_t options[METIS_NOPTIONS];
44 METIS_SetDefaultOptions(options); // use defaults
45 idx_t sepsize; // the size of the separator, output
46 sharedInts part_(new idx_t[n]); // the partition of each vertex, output
47
48 // set uniform weights on the vertices
49 vwgt.assign(n, 1);
50
51 // TODO: Fix at later time
52 //boost::timer::cpu_timer TOTALTmr;
53 if (verbose) {
54 printf("**********************************************************************\n");
55 printf("Graph Information ---------------------------------------------------\n");
56 printf(" #Vertices: %d, #Edges: %u\n", n, *(xadj.get()+n) / 2);
57 printf("\nND Partitioning... -------------------------------------------\n");
58 //TOTALTmr.start()
59 }
60
61 // call metis parition routine
62 METIS_ComputeVertexSeparator(&n, xadj.get(), adjncy.get(),
63 &vwgt[0], options, &sepsize, part_.get());
64
65 if (verbose) {
66 //boost::cpu_times const elapsed_times(timer.elapsed());
67 //printf("\nTiming Information --------------------------------------------------\n");
68 //printf(" Total: \t\t %7.3f\n", elapsed_times);
69 printf(" Sep size: \t\t %d\n", sepsize);
70 printf("**********************************************************************\n");
71 }
72
73 return std::make_pair(sepsize, part_);
74 }
75
76 /* ************************************************************************* */
82 std::pair<int, sharedInts> edgeMetis(idx_t n, const sharedInts& xadj, const sharedInts& adjncy,
83 const sharedInts& adjwgt, bool verbose) {
84
85 // control parameters
86 std::vector<idx_t> vwgt; // the weights of the vertices
87 idx_t options[METIS_NOPTIONS];
88 METIS_SetDefaultOptions(options); // use defaults
89 options[METIS_OPTION_IPTYPE] = METIS_IPTYPE_GROW;
90 options[METIS_OPTION_NCUTS] = 1;
91 options[METIS_OPTION_UFACTOR] = 200;
92 idx_t edgecut; // the number of edge cuts, output
93 sharedInts part_(new idx_t[n]); // the partition of each vertex, output
94
95 // set uniform weights on the vertices
96 vwgt.assign(n, 1);
97
98 //TODO: Fix later
99 //boost::timer TOTALTmr;
100 if (verbose) {
101 printf("**********************************************************************\n");
102 printf("Graph Information ---------------------------------------------------\n");
103 printf(" #Vertices: %d, #Edges: %u\n", n, *(xadj.get()+n) / 2);
104 printf("\nND Partitioning... -------------------------------------------\n");
105 //cleartimer(TOTALTmr);
106 //starttimer(TOTALTmr);
107 }
108
109 idx_t ncon = 1;
110 idx_t nparts = 2;
111 // Deliberately preserve the existing behavior: the private implementation
112 // ignored adjwgt and treated every edge as having uniform weight.
113 static_cast<void>(adjwgt);
114 const int status = METIS_PartGraphRecursive(
115 &n, &ncon, xadj.get(), adjncy.get(), &vwgt[0], nullptr, nullptr,
116 &nparts, nullptr, nullptr, options, &edgecut, part_.get());
117 if (status != METIS_OK) {
118 throw std::runtime_error(
119 "METIS_PartGraphRecursive failed with error code " +
120 std::to_string(status) +
121 "; check the graph inputs and METIS installation");
122 }
123 std::cout << "Finished bisection:" << edgecut << std::endl;
124
125 if (verbose) {
126 //stoptimer(TOTALTmr);
127 printf("\nTiming Information --------------------------------------------------\n");
128 //printf(" Total: \t\t %7.3f\n", gettimer(TOTALTmr));
129 printf(" Edge cuts: \t\t %d\n", edgecut);
130 printf("**********************************************************************\n");
131 }
132
133 return std::make_pair(edgecut, part_);
134 }
135
136 /* ************************************************************************* */
142 template <class GenericGraph>
143 void prepareMetisGraph(const GenericGraph& graph, const std::vector<size_t>& keys, WorkSpace& workspace,
144 sharedInts* ptr_xadj, sharedInts* ptr_adjncy, sharedInts* ptr_adjwgt) {
145
146 typedef std::vector<int> Weights;
147 typedef std::vector<int> Neighbors;
148 typedef std::pair<Neighbors, Weights> NeighborsInfo;
149
150 // set up dictionary
151 std::vector<int>& dictionary = workspace.dictionary;
152 workspace.prepareDictionary(keys);
153
154 // prepare for {adjacencyMap}, a pair of neighbor indices and the correponding edge weights
155 int numNodes = keys.size();
156 int numEdges = 0;
157 std::vector<NeighborsInfo> adjacencyMap;
158 adjacencyMap.resize(numNodes);
159 std::cout << "Number of nodes: " << adjacencyMap.size() << std::endl;
160 int index1, index2;
161
162 for(const typename GenericGraph::value_type& factor: graph){
163 index1 = dictionary[factor->key1.index];
164 index2 = dictionary[factor->key2.index];
165 std::cout << "index1: " << index1 << std::endl;
166 std::cout << "index2: " << index2 << std::endl;
167 // if both nodes are in the current graph, i.e. not a joint factor between frontal and separator
168 if (index1 >= 0 && index2 >= 0) {
169 std::pair<Neighbors, Weights>& adjacencyMap1 = adjacencyMap[index1];
170 std::pair<Neighbors, Weights>& adjacencyMap2 = adjacencyMap[index2];
171 try{
172 adjacencyMap1.first.push_back(index2);
173 adjacencyMap1.second.push_back(factor->weight);
174 adjacencyMap2.first.push_back(index1);
175 adjacencyMap2.second.push_back(factor->weight);
176 }catch(std::exception& e){
177 std::cout << e.what() << std::endl;
178 }
179 numEdges++;
180 }
181 }
182
183 // prepare for {xadj}, {adjncy}, and {adjwgt}
184 *ptr_xadj = sharedInts(new idx_t[numNodes+1]);
185 *ptr_adjncy = sharedInts(new idx_t[numEdges*2]);
186 *ptr_adjwgt = sharedInts(new idx_t[numEdges*2]);
187 sharedInts& xadj = *ptr_xadj;
188 sharedInts& adjncy = *ptr_adjncy;
189 sharedInts& adjwgt = *ptr_adjwgt;
190 int ind_xadj = 0, ind_adjncy = 0;
191 for(const NeighborsInfo& info: adjacencyMap) {
192 *(xadj.get() + ind_xadj) = ind_adjncy;
193 std::copy(info.first .begin(), info.first .end(), adjncy.get() + ind_adjncy);
194 std::copy(info.second.begin(), info.second.end(), adjwgt.get() + ind_adjncy);
195 assert(info.first.size() == info.second.size());
196 ind_adjncy += info.first.size();
197 ind_xadj ++;
198 }
199 if (ind_xadj != numNodes) throw std::runtime_error("prepareMetisGraph_: ind_xadj != numNodes");
200 *(xadj.get() + ind_xadj) = ind_adjncy;
201 }
202
203 /* ************************************************************************* */
204 template<class GenericGraph>
205 std::optional<MetisResult> separatorPartitionByMetis(const GenericGraph& graph,
206 const std::vector<size_t>& keys, WorkSpace& workspace, bool verbose) {
207 // create a metis graph
208 size_t numKeys = keys.size();
209 if (verbose)
210 std::cout << graph.size() << " factors,\t" << numKeys << " nodes;\t" << std::endl;
211
212 sharedInts xadj, adjncy, adjwgt;
213
214 prepareMetisGraph<GenericGraph>(graph, keys, workspace, &xadj, &adjncy, &adjwgt);
215
216 // run ND on the graph
217 const auto [sepsize, part] = separatorMetis(numKeys, xadj, adjncy, adjwgt, verbose);
218 if (!sepsize) return std::optional<MetisResult>();
219
220 // convert the 0-1-2 from Metis to 1-2-0, so that the separator is 0, as later
221 // we will have more submaps
222 MetisResult result;
223 result.C.reserve(sepsize);
224 result.A.reserve(numKeys - sepsize);
225 result.B.reserve(numKeys - sepsize);
226 int* ptr_part = part.get();
227 std::vector<size_t>::const_iterator itKey = keys.begin();
228 std::vector<size_t>::const_iterator itKeyLast = keys.end();
229 while(itKey != itKeyLast) {
230 switch(*(ptr_part++)) {
231 case 0: result.A.push_back(*(itKey++)); break;
232 case 1: result.B.push_back(*(itKey++)); break;
233 case 2: result.C.push_back(*(itKey++)); break;
234 default: throw std::runtime_error("separatorPartitionByMetis: invalid results from Metis ND!");
235 }
236 }
237
238 if (verbose) {
239 std::cout << "total key: " << keys.size()
240 << " result(A,B,C) = " << result.A.size() << ", " << result.B.size() << ", "
241 << result.C.size() << "; sepsize from Metis = " << sepsize << std::endl;
242 //throw runtime_error("separatorPartitionByMetis:stop for debug");
243 }
244
245 if(result.C.size() != size_t(sepsize)) {
246 std::cout << "total key: " << keys.size()
247 << " result(A,B,C) = " << result.A.size() << ", " << result.B.size() << ", " << result.C.size()
248 << "; sepsize from Metis = " << sepsize << std::endl;
249 throw std::runtime_error("separatorPartitionByMetis: invalid sepsize from Metis ND!");
250 }
251
252 return result;
253 }
254
255 /* *************************************************************************/
256 template<class GenericGraph>
257 std::optional<MetisResult> edgePartitionByMetis(const GenericGraph& graph,
258 const std::vector<size_t>& keys, WorkSpace& workspace, bool verbose) {
259
260 // a small hack for handling the camera1-camera2 case used in the unit tests
261 if (graph.size() == 1 && keys.size() == 2) {
262 MetisResult result;
263 result.A.push_back(keys.front());
264 result.B.push_back(keys.back());
265 return result;
266 }
267
268 // create a metis graph
269 size_t numKeys = keys.size();
270 if (verbose) std::cout << graph.size() << " factors,\t" << numKeys << " nodes;\t" << std::endl;
271 sharedInts xadj, adjncy, adjwgt;
272 prepareMetisGraph<GenericGraph>(graph, keys, workspace, &xadj, &adjncy, &adjwgt);
273
274 // run metis on the graph
275 const auto [edgecut, part] = edgeMetis(numKeys, xadj, adjncy, adjwgt, verbose);
276
277 // convert the 0-1-2 from Metis to 1-2-0, so that the separator is 0, as later we will have more submaps
278 MetisResult result;
279 result.A.reserve(numKeys);
280 result.B.reserve(numKeys);
281 int* ptr_part = part.get();
282 std::vector<size_t>::const_iterator itKey = keys.begin();
283 std::vector<size_t>::const_iterator itKeyLast = keys.end();
284 while(itKey != itKeyLast) {
285 if (*ptr_part != 0 && *ptr_part != 1)
286 std::cout << *ptr_part << "!!!" << std::endl;
287 switch(*(ptr_part++)) {
288 case 0: result.A.push_back(*(itKey++)); break;
289 case 1: result.B.push_back(*(itKey++)); break;
290 default: throw std::runtime_error("edgePartitionByMetis: invalid results from Metis ND!");
291 }
292 }
293
294 if (verbose) {
295 std::cout << "the size of two submaps in the reduced graph: " << result.A.size()
296 << " " << result.B.size() << std::endl;
297 int edgeCut = 0;
298
299 for(const typename GenericGraph::value_type& factor: graph){
300 int key1 = factor->key1.index;
301 int key2 = factor->key2.index;
302 // print keys and their subgraph assignment
303 std::cout << key1;
304 if (std::find(result.A.begin(), result.A.end(), key1) != result.A.end()) std::cout <<"A ";
305 if (std::find(result.B.begin(), result.B.end(), key1) != result.B.end()) std::cout <<"B ";
306
307 std::cout << key2;
308 if (std::find(result.A.begin(), result.A.end(), key2) != result.A.end()) std::cout <<"A ";
309 if (std::find(result.B.begin(), result.B.end(), key2) != result.B.end()) std::cout <<"B ";
310 std::cout << "weight " << factor->weight;;
311
312 // find vertices that were assigned to sets A & B. Their edge will be cut
313 if ((std::find(result.A.begin(), result.A.end(), key1) != result.A.end() &&
314 std::find(result.B.begin(), result.B.end(), key2) != result.B.end()) ||
315 (std::find(result.B.begin(), result.B.end(), key1) != result.B.end() &&
316 std::find(result.A.begin(), result.A.end(), key2) != result.A.end())){
317 edgeCut ++;
318 std::cout << " CUT ";
319 }
320 std::cout << std::endl;
321 }
322 std::cout << "edgeCut: " << edgeCut << std::endl;
323 }
324
325 return result;
326 }
327
328 /* ************************************************************************* */
329 bool isLargerIsland(const std::vector<size_t>& island1, const std::vector<size_t>& island2) {
330 return island1.size() > island2.size();
331 }
332
333 /* ************************************************************************* */
334 // debug functions
335 void printIsland(const std::vector<size_t>& island) {
336 std::cout << "island: ";
337 for(const size_t key: island)
338 std::cout << key << " ";
339 std::cout << std::endl;
340 }
341
342 void printIslands(const std::list<std::vector<size_t> >& islands) {
343 for(const std::vector<std::size_t>& island: islands)
344 printIsland(island);
345 }
346
347 void printNumCamerasLandmarks(const std::vector<size_t>& keys, const std::vector<Symbol>& int2symbol) {
348 int numCamera = 0, numLandmark = 0;
349 for(const size_t key: keys)
350 if (int2symbol[key].chr() == 'x')
351 numCamera++;
352 else
353 numLandmark++;
354 std::cout << "numCamera: " << numCamera << " numLandmark: " << numLandmark << std::endl;
355 }
356
357 /* ************************************************************************* */
358 template<class GenericGraph>
359 void addLandmarkToPartitionResult(const GenericGraph& graph, const std::vector<size_t>& landmarkKeys,
360 MetisResult& partitionResult, WorkSpace& workspace) {
361
362 // set up cameras in the dictionary
363 std::vector<size_t>& A = partitionResult.A;
364 std::vector<size_t>& B = partitionResult.B;
365 std::vector<size_t>& C = partitionResult.C;
366 std::vector<int>& dictionary = workspace.dictionary;
367 std::fill(dictionary.begin(), dictionary.end(), -1);
368 for(const size_t a: A)
369 dictionary[a] = 1;
370 for(const size_t b: B)
371 dictionary[b] = 2;
372 if (!C.empty())
373 throw std::runtime_error("addLandmarkToPartitionResult: C is not empty");
374
375 // set up landmarks
376 size_t i,j;
377 for(const typename GenericGraph::value_type& factor: graph) {
378 i = factor->key1.index;
379 j = factor->key2.index;
380 if (dictionary[j] == 0) // if the landmark is already in the separator, continue
381 continue;
382 else if (dictionary[j] == -1)
383 dictionary[j] = dictionary[i];
384 else {
385 if (dictionary[j] != dictionary[i])
386 dictionary[j] = 0;
387 }
388// if (j == 67980)
389// std::cout << "dictionary[67980]" << dictionary[j] << std::endl;
390 }
391
392 for(const size_t j: landmarkKeys) {
393 switch(dictionary[j]) {
394 case 0: C.push_back(j); break;
395 case 1: A.push_back(j); break;
396 case 2: B.push_back(j); break;
397 default: std::cout << j << ": " << dictionary[j] << std::endl;
398 throw std::runtime_error("addLandmarkToPartitionResult: wrong status for landmark");
399 }
400 }
401 }
402
403#define REDUCE_CAMERA_GRAPH
404
405 /* ************************************************************************* */
406 template<class GenericGraph>
407 std::optional<MetisResult> findPartitoning(const GenericGraph& graph, const std::vector<size_t>& keys,
408 WorkSpace& workspace, bool verbose,
409 const std::optional<std::vector<Symbol> >& int2symbol, const bool reduceGraph) {
410 std::optional<MetisResult> result;
411 GenericGraph reducedGraph;
412 std::vector<size_t> keyToPartition;
413 std::vector<size_t> cameraKeys, landmarkKeys;
414 if (reduceGraph) {
415 if (!int2symbol.has_value())
416 throw std::invalid_argument("findSeparator: int2symbol must be valid!");
417
418 // find out all the landmark keys, which are to be eliminated
419 cameraKeys.reserve(keys.size());
420 landmarkKeys.reserve(keys.size());
421 for(const size_t key: keys) {
422 if((*int2symbol)[key].chr() == 'x')
423 cameraKeys.push_back(key);
424 else
425 landmarkKeys.push_back(key);
426 }
427
428 keyToPartition = cameraKeys;
429 workspace.prepareDictionary(keyToPartition);
430 const std::vector<int>& dictionary = workspace.dictionary;
431 reduceGenericGraph(graph, cameraKeys, landmarkKeys, dictionary, reducedGraph);
432 std::cout << "original graph: V" << keys.size() << ", E" << graph.size()
433 << " --> reduced graph: V" << cameraKeys.size() << ", E" << reducedGraph.size() << std::endl;
434 result = edgePartitionByMetis(reducedGraph, keyToPartition, workspace, verbose);
435 } else // call Metis to partition the graph to A, B, C
436 result = separatorPartitionByMetis(graph, keys, workspace, verbose);
437
438 if (!result.has_value()) {
439 std::cout << "metis failed!" << std::endl;
440 return {};
441 }
442
443 if (reduceGraph) {
444 addLandmarkToPartitionResult(graph, landmarkKeys, *result, workspace);
445 std::cout << "the separator size: " << result->C.size() << " landmarks" << std::endl;
446 }
447
448 return result;
449 }
450
451 /* ************************************************************************* */
452 template<class GenericGraph>
453 int findSeparator(const GenericGraph& graph, const std::vector<size_t>& keys,
454 const int minNodesPerMap, WorkSpace& workspace, bool verbose,
455 const std::optional<std::vector<Symbol> >& int2symbol, const bool reduceGraph,
456 const int minNrConstraintsPerCamera, const int minNrConstraintsPerLandmark) {
457
458 std::optional<MetisResult> result = findPartitoning(graph, keys, workspace,
459 verbose, int2symbol, reduceGraph);
460
461 // find the island in A and B, and make them separated submaps
462 typedef std::vector<size_t> Island;
463 std::list<Island> islands;
464
465 std::list<Island> islands_in_A = findIslands(graph, result->A, workspace,
466 minNrConstraintsPerCamera, minNrConstraintsPerLandmark);
467
468 std::list<Island> islands_in_B = findIslands(graph, result->B, workspace,
469 minNrConstraintsPerCamera, minNrConstraintsPerLandmark);
470
471 islands.insert(islands.end(), islands_in_A.begin(), islands_in_A.end());
472 islands.insert(islands.end(), islands_in_B.begin(), islands_in_B.end());
473 islands.sort(isLargerIsland);
474 size_t numIsland0 = islands.size();
475
476#ifdef NDEBUG
477// verbose = true;
478// if (!int2symbol) throw std::invalid_argument("findSeparator: int2symbol is not set!");
479// std::cout << "sep size: " << result->C.size() << "; ";
480// printNumCamerasLandmarks(result->C, *int2symbol);
481// std::cout << "no. of island: " << islands.size() << "; ";
482// std::cout << "island size: ";
483// for(const Island& island: islands)
484// std::cout << island.size() << " ";
485// std::cout << std::endl;
486
487// for(const Island& island: islands) {
488// printNumCamerasLandmarks(island, int2symbol);
489// }
490#endif
491
492 // absorb small components into the separator
493 size_t oldSize = islands.size();
494 while(true) {
495 if (islands.size() < 2) {
496 std::cout << "numIsland: " << numIsland0 << std::endl;
497 throw std::runtime_error("findSeparator: found fewer than 2 submaps!");
498 }
499
500 std::list<Island>::reference island = islands.back();
501 if ((int)island.size() >= minNodesPerMap) break;
502 result->C.insert(result->C.end(), island.begin(), island.end());
503 islands.pop_back();
504 }
505 if (islands.size() != oldSize){
506 if (verbose) std::cout << oldSize << "-" << oldSize - islands.size() << " submap(s);\t" << std::endl;
507 }
508 else{
509 if (verbose) std::cout << oldSize << " submap(s);\t" << std::endl;
510 }
511
512 // generate the node map
513 std::vector<int>& partitionTable = workspace.partitionTable;
514 std::fill(partitionTable.begin(), partitionTable.end(), -1);
515 for(const size_t key: result->C)
516 partitionTable[key] = 0;
517 int idx = 0;
518 for(const Island& island: islands) {
519 idx++;
520 for(const size_t key: island) {
521 partitionTable[key] = idx;
522 }
523 }
524
525 return islands.size();
526 }
527
528}} //namespace
Timing utilities.
Global functions in a separate testing namespace.
Definition chartTesting.h:28
the metis Nest dissection result
Definition FindSeparator.h:23
Definition PartitionWorkSpace.h:19