1 // Copyright (c) 2010-2021, Lawrence Livermore National Security, LLC. Produced 2 // at the Lawrence Livermore National Laboratory. All Rights reserved. See files 3 // LICENSE and NOTICE for details. LLNL-CODE-806117. 4 // 5 // This file is part of the MFEM library. For more information and source code 6 // availability visit https://mfem.org. 7 // 8 // MFEM is free software; you can redistribute it and/or modify it under the 9 // terms of the BSD-3 license. We welcome feedback and contributions, see file 10 // CONTRIBUTING.md for details. 11 12 #ifndef MFEM_GECKO_HPP 13 #define MFEM_GECKO_HPP 14 15 // This file collects the sources of the Gecko library as a single module. 16 // The original library can be found at https://github.com/LLNL/gecko 17 // Used here with permission. 18 19 // ------------------------------------------------------------------------------ 20 21 // BSD 3-Clause License 22 // 23 // Copyright (c) 2019-2021, Lawrence Livermore National Security, LLC 24 // All rights reserved. 25 // 26 // Redistribution and use in source and binary forms, with or without 27 // modification, are permitted provided that the following conditions are met: 28 // 29 // * Redistributions of source code must retain the above copyright notice, this 30 // list of conditions and the following disclaimer. 31 // 32 // * Redistributions in binary form must reproduce the above copyright notice, 33 // this list of conditions and the following disclaimer in the documentation 34 // and/or other materials provided with the distribution. 35 // 36 // * Neither the name of the copyright holder nor the names of its 37 // contributors may be used to endorse or promote products derived from 38 // this software without specific prior written permission. 39 // 40 // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 41 // AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 42 // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 43 // DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 44 // FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 45 // DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 46 // SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 47 // CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 48 // OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 49 // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 50 51 // ------------------------------------------------------------------------------ 52 53 // Copyright (c) 2019-2021, Lawrence Livermore National Security, LLC and other 54 // gecko project contributors. See the above license for details. 55 // SPDX-License-Identifier: BSD-3-Clause 56 // LLNL-CODE-800597 57 58 /* ------------------------------------------------------------------------------ 59 60 Gecko 61 ===== 62 63 Gecko is a C++ library for solving graph linear arrangement problems. Gecko 64 orders graph nodes, representing data elements, connected by undirected edges, 65 representing affinity relations, with the goal of minimizing a chosen 66 functional of edge length. Gecko was primarily designed to minimize the 67 product of edge lengths but can also be used to reduce bandwidth (maximum 68 edge length) and 1-sum (sum of edge lengths), among others. 69 Minimum-edge-product orderings generalize space-filling curve orderings to 70 geometryless graphs and find applications in data locality optimization, 71 graph partitioning, and dimensionality reduction. 72 73 74 Author 75 ------ 76 77 Gecko was written by [Peter Lindstrom](https://people.llnl.gov/pl) at 78 Lawrence Livermore National Laboratory. 79 80 81 Algorithm 82 ========= 83 84 Gecko orders the nodes of an undirected and optionally weighted graph in an 85 effort to place nodes connected by an edge in consecutive positions in the 86 linear ordering (aka. *layout*). Such orderings promote good data locality, 87 e.g., to improve cache utilization, but also find applications in graph 88 partitioning and dimensionality reduction. 89 90 The gecko ordering method is inspired by algebraic multigrid methods, and 91 uses V-cycles to coarsen, optimize, and refine the graph layout. 92 The graph constitutes an abstract representation of the relationship 93 between elements in a data set, e.g., a graph node may represent a 94 vertex or a cell in a mesh, a pixel in an image, a node in a binary 95 search tree, an element in a sparse matrix, etc. The graph edges 96 represent node affinities, or a desire that adjacent nodes be stored 97 close together on linear storage (e.g., disk or main memory). Such a 98 data layout is likely to improve cache utilization in block-based 99 caches common on today's computer architectures. For instance, the 100 edges may connect adjacent pixels in an image, as many image 101 processing operations involve accessing local neighborhoods. The 102 resulting node layouts are "cache-oblivious" in the sense that no 103 particular knowledge of the cache parameters (number and size of 104 blocks, associativity, replacement policy, etc.) are accounted for. 105 Rather, the expectation is that the layouts will provide good 106 locality across all levels of cache. Note that the ordering method 107 accepts any undirected graph, whether it represent a structured or 108 unstructured data set, and is also oblivious of any geometric 109 structure inherent in the data set. 110 111 The optimization algorithm attempts to order the nodes of the graph 112 so as to minimize the geometric mean edge length, or equivalently 113 the product 114 115 product |p(i) - p(j)|^w(i, j) 116 117 or weighted sum 118 119 sum w(i, j) log(|p(i) - p(j)|) 120 121 where *i* and *j* are nodes joined by an edge, *w*(*i*, *j*) is a positive 122 edge weight (equal to one unless otherwise specified), *p*(*i*) is 123 the integer position of node *i* in the linear layout of the graph 124 (with *p*(*i*) = *p*(*j*) if and only if *i* = *j*), and where the product 125 or sum is over all edges of the graph. 126 127 The algorithm is described in further detail in the paper 128 129 * Peter Lindstrom 130 The Minimum Edge Product Linear Ordering Problem 131 LLNL technical report LLNL-TR-496076, August 26, 2011. 132 133 134 Ordering Parameters 135 ------------------- 136 137 The `Graph::order()` function and the `gecko` command-line executable take a 138 number of parameters that govern the layout process. These parameters are 139 described below: 140 141 * The **functional** is the objective being optimized and expresses the cost 142 of the graph layout in terms of some average of its edge lengths 143 |*p*(*i*) - *p*(*j*)|. The predefined functionals are 144 * `h` (harmonic mean) 145 * `g` (geometric mean) 146 * `s` (square mean root) 147 * `a` (arithmetic mean) 148 * `r` (root mean square) 149 * `m` (maximum) 150 151 Note that the algorithm has not been well tuned or tested to optimize 152 functionals other than the geometric mean. 153 154 * The number of **iterations** specifies the number of multigrid V-cycles 155 to perform. Usually a handful of cycles is sufficient. The default is 156 a single cycle. 157 158 * The optimization **window** is the number of consecutive nodes optimized 159 concurrently using exhaustive search. The larger the window, the higher 160 the quality. Note that the running time increases exponentially with the 161 window size. Usually a window no larger than six nodes is sufficient. 162 The default is a window size of two nodes. 163 164 * The **period** is the number of V-cycles to run between increments of the 165 window size. Usually it is beneficial to start with a small window to get 166 a rough layout, and to then increase the window size to fine-tune the 167 layout. The default is a period of one cycle. 168 169 * The random **seed** allows injecting some randomness in the optimization 170 process. When the seed is nonzero, the nodes are randomly shuffled prior 171 to invoking the ordering algorithm, thereby affecting subsequent coarsening 172 and ordering decisions. In effect, this randomization allows different 173 directions to be explored in the combinatorial optimization space. Fixing 174 the seed allows for reproducibility, i.e., the same seed always leads to 175 the same layout. Since the global optimum is seldom (if ever) reached, 176 it often makes sense to run several instances of the algorithm, each with 177 a new random seed, and to pick the best layout found. In the gecko 178 executable, the current time is used as random seed if not specified. 179 180 A reasonable parameter choice for good-quality layouts is: 181 182 * iterations = 4 183 * window = 4 184 * period = 2 185 186 */ 187 188 #include <algorithm> 189 #include <string> 190 #include <utility> 191 #include <vector> 192 #include <cmath> 193 #include <cfloat> 194 #include <limits> 195 196 // ----- types.h ---------------------------------------------------------------- 197 198 #define GECKO_FLOAT_EPSILON std::numeric_limits<Float>::epsilon() 199 #define GECKO_FLOAT_MAX std::numeric_limits<Float>::max() 200 201 namespace Gecko 202 { 203 204 typedef unsigned int uint; 205 206 // precision for node positions and computations 207 #if GECKO_WITH_DOUBLE_PRECISION 208 typedef double Float; 209 #else 210 typedef float Float; 211 #endif 212 } 213 214 // ----- functional.h ----------------------------------------------------------- 215 216 namespace Gecko 217 { 218 219 // abstract base class for weighted terms and sums 220 class WeightedValue 221 { 222 public: WeightedValue(Float value,Float weight)223 WeightedValue(Float value, Float weight) : value(value), weight(weight) {} 224 Float value; 225 Float weight; 226 }; 227 228 // weighted sum of terms 229 class WeightedSum : public WeightedValue 230 { 231 public: WeightedSum(Float value=0,Float weight=0)232 WeightedSum(Float value = 0, Float weight = 0) : WeightedValue(value, weight) {} 233 }; 234 235 // abstract base class for ordering functionals 236 class Functional 237 { 238 public: ~Functional()239 virtual ~Functional() {} 240 sum(const WeightedSum & s,const WeightedValue & t) const241 virtual WeightedSum sum(const WeightedSum& s, const WeightedValue& t) const 242 { 243 WeightedSum tot = s; 244 accumulate(tot, sum(t)); 245 return tot; 246 } 247 sum(const WeightedSum & s,const WeightedSum & t) const248 virtual WeightedSum sum(const WeightedSum& s, const WeightedSum& t) const 249 { 250 WeightedSum tot = s; 251 accumulate(tot, t); 252 return tot; 253 } 254 255 // add weighted term to weighted sum accumulate(WeightedSum & s,const WeightedValue & t) const256 virtual void accumulate(WeightedSum& s, const WeightedValue& t) const 257 { 258 accumulate(s, sum(t)); 259 } 260 261 // add two weighted sums accumulate(WeightedSum & s,const WeightedSum & t) const262 virtual void accumulate(WeightedSum& s, const WeightedSum& t) const 263 { 264 s.value += t.value; 265 s.weight += t.weight; 266 } 267 268 // is s potentially less than t? less(const WeightedSum & s,const WeightedSum & t) const269 virtual bool less(const WeightedSum& s, const WeightedSum& t) const 270 { 271 return s.value < t.value; 272 } 273 274 // transform term into weighted sum 275 virtual WeightedSum sum(const WeightedValue& term) const = 0; 276 277 // compute weighted mean from a weighted sum 278 virtual Float mean(const WeightedSum& sum) const = 0; 279 280 // compute k'th iteration bond for egde of length l and weight w 281 virtual Float bond(Float w, Float l, uint k) const = 0; 282 283 // compute position that minimizes weighted distance to a point set 284 virtual Float optimum(const std::vector<WeightedValue>& v) const = 0; 285 }; 286 287 // functionals with quasiconvex terms, e.g., p-means with p < 1. 288 class FunctionalQuasiconvex : public Functional 289 { 290 protected: optimum(const std::vector<WeightedValue> & v,Float lmin) const291 Float optimum(const std::vector<WeightedValue>& v, Float lmin) const 292 { 293 // Compute the optimum as the node position that minimizes the 294 // functional. Any nodes coincident with each candidate position 295 // are excluded from the functional. 296 Float x = v[0].value; 297 Float min = GECKO_FLOAT_MAX; 298 switch (v.size()) 299 { 300 case 1: 301 // Only one choice. 302 break; 303 case 2: 304 // Functional is the same for both nodes; pick node with 305 // larger weight. 306 if (v[1].weight > v[0].weight) 307 { 308 x = v[1].value; 309 } 310 break; 311 default: 312 for (std::vector<WeightedValue>::const_iterator p = v.begin(); p != v.end(); 313 p++) 314 { 315 WeightedSum s; 316 for (std::vector<WeightedValue>::const_iterator q = v.begin(); q != v.end(); 317 q++) 318 { 319 Float l = std::fabs(p->value - q->value); 320 if (l > lmin) 321 { 322 accumulate(s, WeightedValue(l, q->weight)); 323 } 324 } 325 Float f = mean(s); 326 if (f < min) 327 { 328 min = f; 329 x = p->value; 330 } 331 } 332 break; 333 } 334 return x; 335 } 336 private: 337 using Functional::optimum; // silence overload vs. override warning 338 }; 339 340 // harmonic mean (p = -1) 341 class FunctionalHarmonic : public FunctionalQuasiconvex 342 { 343 public: 344 using Functional::sum; less(const WeightedSum & s,const WeightedSum & t) const345 bool less(const WeightedSum& s, const WeightedSum& t) const 346 { 347 // This is only a loose bound when s.weight < t.weight. 348 return s.value - s.weight > t.value - t.weight; 349 } sum(const WeightedValue & term) const350 WeightedSum sum(const WeightedValue& term) const 351 { 352 return WeightedSum(term.weight / term.value, term.weight); 353 } mean(const WeightedSum & sum) const354 Float mean(const WeightedSum& sum) const 355 { 356 return sum.weight > 0 ? sum.weight / sum.value : 0; 357 } bond(Float w,Float l,uint k) const358 Float bond(Float w, Float l, uint k) const 359 { 360 return w * std::pow(l, -Float(3) * Float(k) / Float(k + 1)); 361 } optimum(const std::vector<WeightedValue> & v) const362 Float optimum(const std::vector<WeightedValue>& v) const 363 { 364 return FunctionalQuasiconvex::optimum(v, Float(0.5)); 365 } 366 }; 367 368 // geometric mean (p = 0) 369 class FunctionalGeometric : public FunctionalQuasiconvex 370 { 371 public: 372 using Functional::sum; sum(const WeightedValue & term) const373 WeightedSum sum(const WeightedValue& term) const 374 { 375 return WeightedSum(term.weight * std::log(term.value), term.weight); 376 } mean(const WeightedSum & sum) const377 Float mean(const WeightedSum& sum) const 378 { 379 return sum.weight > 0 ? std::exp(sum.value / sum.weight) : 0; 380 } bond(Float w,Float l,uint k) const381 Float bond(Float w, Float l, uint k) const 382 { 383 return w * std::pow(l, -Float(2) * Float(k) / Float(k + 1)); 384 } optimum(const std::vector<WeightedValue> & v) const385 Float optimum(const std::vector<WeightedValue>& v) const 386 { 387 return FunctionalQuasiconvex::optimum(v, Float(0.5)); 388 } 389 }; 390 391 // square mean root (p = 1/2) 392 class FunctionalSMR : public FunctionalQuasiconvex 393 { 394 public: 395 using Functional::sum; sum(const WeightedValue & term) const396 WeightedSum sum(const WeightedValue& term) const 397 { 398 return WeightedSum(term.weight * std::sqrt(term.value), term.weight); 399 } mean(const WeightedSum & sum) const400 Float mean(const WeightedSum& sum) const 401 { 402 return sum.weight > 0 ? (sum.value / sum.weight) * (sum.value / sum.weight) : 0; 403 } bond(Float w,Float l,uint k) const404 Float bond(Float w, Float l, uint k) const 405 { 406 return w * std::pow(l, -Float(1.5) * Float(k) / Float(k + 1)); 407 } optimum(const std::vector<WeightedValue> & v) const408 Float optimum(const std::vector<WeightedValue>& v) const 409 { 410 return FunctionalQuasiconvex::optimum(v, Float(0.0)); 411 } 412 }; 413 414 // arithmetic mean (p = 1) 415 class FunctionalArithmetic : public Functional 416 { 417 public: 418 using Functional::sum; sum(const WeightedValue & term) const419 WeightedSum sum(const WeightedValue& term) const 420 { 421 return WeightedSum(term.weight * term.value, term.weight); 422 } mean(const WeightedSum & sum) const423 Float mean(const WeightedSum& sum) const 424 { 425 return sum.weight > 0 ? sum.value / sum.weight : 0; 426 } bond(Float w,Float l,uint k) const427 Float bond(Float w, Float l, uint k) const 428 { 429 return w * std::pow(l, -Float(1) * Float(k) / Float(k + 1)); 430 } optimum(const std::vector<WeightedValue> & v) const431 Float optimum(const std::vector<WeightedValue>& v) const 432 { 433 // Compute the optimum as the weighted median. Since the median may 434 // not be unique, the largest interval [x, y] is computed and its 435 // centroid is chosen. The optimum must occur at a node, and hence 436 // we consider each node position pi at a time and the relative 437 // positions of the remaining nodes pj. 438 Float x = 0; 439 Float y = 0; 440 Float min = GECKO_FLOAT_MAX; 441 for (std::vector<WeightedValue>::const_iterator p = v.begin(); p != v.end(); 442 p++) 443 { 444 // Compute f = |sum_{j:pj<pi} wj - sum_{j:pj>pi} wj|. 445 Float f = 0; 446 for (std::vector<WeightedValue>::const_iterator q = v.begin(); q != v.end(); 447 q++) 448 if (q->value < p->value) 449 { 450 f += q->weight; 451 } 452 else if (q->value > p->value) 453 { 454 f -= q->weight; 455 } 456 f = std::fabs(f); 457 // Update interval if f is minimal. 458 if (f <= min) 459 { 460 if (f < min) 461 { 462 min = f; 463 x = y = p->value; 464 } 465 else 466 { 467 x = std::min(x, p->value); 468 y = std::max(y, p->value); 469 } 470 } 471 } 472 return (x + y) / 2; 473 } 474 }; 475 476 // root mean square (p = 2) 477 class FunctionalRMS : public Functional 478 { 479 public: 480 using Functional::sum; sum(const WeightedValue & term) const481 WeightedSum sum(const WeightedValue& term) const 482 { 483 return WeightedSum(term.weight * term.value * term.value, term.weight); 484 } mean(const WeightedSum & sum) const485 Float mean(const WeightedSum& sum) const 486 { 487 return sum.weight > 0 ? std::sqrt(sum.value / sum.weight) : 0; 488 } bond(Float w,Float,uint) const489 Float bond(Float w, Float, uint) const 490 { 491 return w; 492 } optimum(const std::vector<WeightedValue> & v) const493 Float optimum(const std::vector<WeightedValue>& v) const 494 { 495 // Compute the optimum as the weighted mean. 496 WeightedSum s; 497 for (std::vector<WeightedValue>::const_iterator p = v.begin(); p != v.end(); 498 p++) 499 { 500 s.value += p->weight * p->value; 501 s.weight += p->weight; 502 } 503 return s.value / s.weight; 504 } 505 }; 506 507 // maximum (p = infinity) 508 class FunctionalMaximum : public Functional 509 { 510 public: 511 using Functional::sum; 512 using Functional::accumulate; sum(const WeightedValue & term) const513 WeightedSum sum(const WeightedValue& term) const 514 { 515 return WeightedSum(term.value, term.weight); 516 } accumulate(WeightedSum & s,const WeightedSum & t) const517 void accumulate(WeightedSum& s, const WeightedSum& t) const 518 { 519 s.value = std::max(s.value, t.value); 520 } mean(const WeightedSum & sum) const521 Float mean(const WeightedSum& sum) const 522 { 523 return sum.value; 524 } bond(Float,Float,uint) const525 Float bond(Float, Float, uint) const 526 { 527 return Float(1); 528 } optimum(const std::vector<WeightedValue> & v) const529 Float optimum(const std::vector<WeightedValue>& v) const 530 { 531 // Compute the optimum as the midrange. 532 Float min = v[0].value; 533 Float max = v[0].value; 534 for (std::vector<WeightedValue>::const_iterator p = v.begin() + 1; p != v.end(); 535 p++) 536 { 537 if (p->value < min) 538 { 539 min = p->value; 540 } 541 else if (p->value > max) 542 { 543 max = p->value; 544 } 545 } 546 return (min + max) / 2; 547 } 548 }; 549 550 } 551 552 // ----- progress.h ------------------------------------------------------------- 553 554 namespace Gecko 555 { 556 557 class Graph; 558 559 // Callbacks between iterations and phases. 560 class Progress 561 { 562 public: ~Progress()563 virtual ~Progress() {} beginorder(const Graph *,Float) const564 virtual void beginorder(const Graph* /*graph*/, Float /*cost*/) const {} endorder(const Graph *,Float) const565 virtual void endorder(const Graph* /*graph*/, Float /*cost*/) const {} beginiter(const Graph *,uint,uint,uint) const566 virtual void beginiter(const Graph* /*graph*/, uint /*iter*/, uint /*maxiter*/, 567 uint /*window*/) const {} enditer(const Graph *,Float,Float) const568 virtual void enditer(const Graph* /*graph*/, Float /*mincost*/, 569 Float /*cost*/) const {} beginphase(const Graph *,std::string) const570 virtual void beginphase(const Graph* /*graph*/, std::string /*name*/) const {}; endphase(const Graph *,bool) const571 virtual void endphase(const Graph* /*graph*/, bool /*show*/) const {}; quit() const572 virtual bool quit() const { return false; } 573 }; 574 575 } 576 577 // ----- graph.h ---------------------------------------------------------------- 578 579 namespace Gecko 580 { 581 582 // Multilevel graph arc. 583 class Arc 584 { 585 public: 586 typedef uint Index; 587 typedef std::vector<Index>::const_iterator ConstPtr; 588 enum { null = 0 }; 589 }; 590 591 // Multilevel graph node. 592 class Node 593 { 594 public: 595 typedef uint Index; 596 typedef std::vector<Node>::const_iterator ConstPtr; 597 enum { null = 0 }; 598 599 // comparator for sorting node indices 600 class Comparator 601 { 602 public: Comparator(ConstPtr node_)603 Comparator(ConstPtr node_) : node(node_) {} operator ()(uint k,uint l) const604 bool operator()(uint k, uint l) const { return node[k].pos < node[l].pos; } 605 private: 606 const ConstPtr node; 607 }; 608 609 // constructor Node(Float pos=-1,Float length=1,Arc::Index arc=Arc::null,Node::Index parent=Node::null)610 Node(Float pos = -1, Float length = 1, Arc::Index arc = Arc::null, 611 Node::Index parent = Node::null) : pos(pos), hlen(Float(0.5) * length), 612 arc(arc), parent(parent) {} 613 614 Float pos; // start position at full resolution 615 Float hlen; // half of node length (number of full res nodes) 616 Arc::Index arc; // one past index of last incident arc 617 Node::Index parent; // parent in next coarser resolution 618 }; 619 620 // Multilevel graph. 621 class Graph 622 { 623 public: 624 // constructor of graph with given (initial) number of nodes Graph(uint nodes=0)625 Graph(uint nodes = 0) : level(0), last_node(Node::null) { init(nodes); } 626 627 // number of nodes and edges nodes() const628 uint nodes() const { return uint(node.size() - 1); } edges() const629 uint edges() const { return uint((adj.size() - 1) / 2); } 630 631 // insert node and return its index 632 Node::Index insert_node(Float length = 1); 633 634 // outgoing arcs {begin, ..., end-1} originating from node i node_begin(Node::Index i) const635 Arc::Index node_begin(Node::Index i) const { return node[i - 1].arc; } node_end(Node::Index i) const636 Arc::Index node_end(Node::Index i) const { return node[i].arc; } 637 638 // node degree and neighbors node_degree(Node::Index i) const639 uint node_degree(Node::Index i) const { return node_end(i) - node_begin(i); } 640 std::vector<Node::Index> node_neighbors(Node::Index i) const; 641 642 // insert directed edge (i, j) 643 Arc::Index insert_arc(Node::Index i, Node::Index j, Float w = 1, Float b = 1); 644 645 // remove arc or edge 646 bool remove_arc(Arc::Index a); 647 bool remove_arc(Node::Index i, Node::Index j); 648 bool remove_edge(Node::Index i, Node::Index j); 649 650 // index of arc (i, j) or null if not present 651 Arc::Index arc_index(Node::Index i, Node::Index j) const; 652 653 // arc source and target nodes and weight 654 Node::Index arc_source(Arc::Index a) const; arc_target(Arc::Index a) const655 Node::Index arc_target(Arc::Index a) const { return adj[a]; } arc_weight(Arc::Index a) const656 Float arc_weight(Arc::Index a) const { return weight[a]; } 657 658 // reverse arc (j, i) of arc a = (i, j) 659 Arc::Index reverse_arc(Arc::Index a) const; 660 661 // order graph 662 void order(Functional* functional, uint iterations = 1, uint window = 2, 663 uint period = 2, uint seed = 0, Progress* progress = 0); 664 665 // optimal permutation found permutation() const666 const std::vector<Node::Index>& permutation() const { return perm; } 667 668 // node of given rank in reordered graph (0 <= rank <= nodes() - 1) permutation(uint rank) const669 Node::Index permutation(uint rank) const { return perm[rank]; } 670 671 // position of node i in reordered graph (1 <= i <= nodes()) rank(Node::Index i) const672 uint rank(Node::Index i) const { return static_cast<uint>(std::floor(node[i].pos)); } 673 674 // cost of current layout 675 Float cost() const; 676 677 // return first directed arc if one exists or null otherwise 678 Arc::Index directed() const; 679 680 protected: 681 friend class Subgraph; 682 friend class Drawing; 683 684 // constructor/destructor Graph(uint nodes,uint level)685 Graph(uint nodes, uint level) : level(level), last_node(Node::null) { init(nodes); } 686 687 // arc length length(Node::Index i,Node::Index j) const688 Float length(Node::Index i, Node::Index j) const { return std::fabs(node[i].pos - node[j].pos); } length(Arc::Index a) const689 Float length(Arc::Index a) const 690 { 691 Node::Index i = arc_source(a); 692 Node::Index j = arc_target(a); 693 return length(i, j); 694 } 695 696 // coarsen graph 697 Graph* coarsen(); 698 699 // refine graph 700 void refine(const Graph* graph); 701 702 // perform m sweeps of compatible or Gauss-Seidel relaxation 703 void relax(bool compatible, uint m = 1); 704 705 // optimize using n-node window 706 void optimize(uint n); 707 708 // place all nodes according to their positions 709 void place(bool sort = false); 710 711 // place nodes {k, ..., k + n - 1} according to their positions 712 void place(bool sort, uint k, uint n); 713 714 // perform V cycle using n-node window 715 void vcycle(uint n, uint work = 0); 716 717 // randomly shuffle nodes 718 void shuffle(uint seed = 0); 719 720 // recompute arc bonds for iteration i 721 void reweight(uint i); 722 723 // compute cost 724 WeightedSum cost(const std::vector<Arc::Index>& subset, Float pos) const; 725 726 // node attributes persistent(Node::Index i) const727 bool persistent(Node::Index i) const { return node[i].parent != Node::null; } placed(Node::Index i) const728 bool placed(Node::Index i) const { return node[i].pos >= Float(0); } 729 730 Functional* functional; // ordering functional 731 Progress* progress; // progress callbacks 732 std::vector<Node::Index> perm; // ordered list of indices to nodes 733 std::vector<Node> node; // statically ordered list of nodes 734 std::vector<Node::Index> adj; // statically ordered list of adjacent nodes 735 std::vector<Float> weight; // statically ordered list of arc weights 736 std::vector<Float> bond; // statically ordered list of coarsening weights 737 738 private: 739 // initialize graph with given number of nodes 740 void init(uint nodes); 741 742 // find optimal position of node i while fixing all other nodes 743 Float optimal(Node::Index i) const; 744 745 // add contribution of fine arc to coarse graph 746 void update(Node::Index i, Node::Index j, Float w, Float b); 747 748 // transfer contribution of fine arc a to coarse node p 749 void transfer(Graph* g, const std::vector<Float>& part, Node::Index p, 750 Arc::Index a, Float f = 1) const; 751 752 // swap the positions of nodes 753 void swap(uint k, uint l); 754 755 // random number generator 756 static uint random(uint seed = 0); 757 758 uint level; // level of coarsening 759 Node::Index last_node; // last node with outgoing arcs 760 }; 761 762 } 763 764 #endif // MFEM_GECKO_HPP 765