765 lines
24 KiB
C++
765 lines
24 KiB
C++
// Copyright (c) 2010-2020, Lawrence Livermore National Security, LLC. Produced
|
|
// at the Lawrence Livermore National Laboratory. All Rights reserved. See files
|
|
// LICENSE and NOTICE for details. LLNL-CODE-806117.
|
|
//
|
|
// This file is part of the MFEM library. For more information and source code
|
|
// availability visit https://mfem.org.
|
|
//
|
|
// MFEM is free software; you can redistribute it and/or modify it under the
|
|
// terms of the BSD-3 license. We welcome feedback and contributions, see file
|
|
// CONTRIBUTING.md for details.
|
|
|
|
#ifndef MFEM_GECKO_HPP
|
|
#define MFEM_GECKO_HPP
|
|
|
|
// This file collects the sources of the Gecko library as a single module.
|
|
// The original library can be found at https://github.com/LLNL/gecko
|
|
// Used here with permission.
|
|
|
|
// ------------------------------------------------------------------------------
|
|
|
|
// BSD 3-Clause License
|
|
//
|
|
// Copyright (c) 2019-2020, Lawrence Livermore National Security, LLC
|
|
// All rights reserved.
|
|
//
|
|
// Redistribution and use in source and binary forms, with or without
|
|
// modification, are permitted provided that the following conditions are met:
|
|
//
|
|
// * Redistributions of source code must retain the above copyright notice, this
|
|
// list of conditions and the following disclaimer.
|
|
//
|
|
// * Redistributions in binary form must reproduce the above copyright notice,
|
|
// this list of conditions and the following disclaimer in the documentation
|
|
// and/or other materials provided with the distribution.
|
|
//
|
|
// * Neither the name of the copyright holder nor the names of its
|
|
// contributors may be used to endorse or promote products derived from
|
|
// this software without specific prior written permission.
|
|
//
|
|
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
|
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
|
// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
|
// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
|
// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
|
// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
|
// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
|
// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
|
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
|
|
// ------------------------------------------------------------------------------
|
|
|
|
// Copyright (c) 2019-2020, Lawrence Livermore National Security, LLC and other
|
|
// gecko project contributors. See the above license for details.
|
|
// SPDX-License-Identifier: BSD-3-Clause
|
|
// LLNL-CODE-800597
|
|
|
|
/* ------------------------------------------------------------------------------
|
|
|
|
Gecko
|
|
=====
|
|
|
|
Gecko is a C++ library for solving graph linear arrangement problems. Gecko
|
|
orders graph nodes, representing data elements, connected by undirected edges,
|
|
representing affinity relations, with the goal of minimizing a chosen
|
|
functional of edge length. Gecko was primarily designed to minimize the
|
|
product of edge lengths but can also be used to reduce bandwidth (maximum
|
|
edge length) and 1-sum (sum of edge lengths), among others.
|
|
Minimum-edge-product orderings generalize space-filling curve orderings to
|
|
geometryless graphs and find applications in data locality optimization,
|
|
graph partitioning, and dimensionality reduction.
|
|
|
|
|
|
Author
|
|
------
|
|
|
|
Gecko was written by [Peter Lindstrom](https://people.llnl.gov/pl) at
|
|
Lawrence Livermore National Laboratory.
|
|
|
|
|
|
Algorithm
|
|
=========
|
|
|
|
Gecko orders the nodes of an undirected and optionally weighted graph in an
|
|
effort to place nodes connected by an edge in consecutive positions in the
|
|
linear ordering (aka. *layout*). Such orderings promote good data locality,
|
|
e.g., to improve cache utilization, but also find applications in graph
|
|
partitioning and dimensionality reduction.
|
|
|
|
The gecko ordering method is inspired by algebraic multigrid methods, and
|
|
uses V-cycles to coarsen, optimize, and refine the graph layout.
|
|
The graph constitutes an abstract representation of the relationship
|
|
between elements in a data set, e.g., a graph node may represent a
|
|
vertex or a cell in a mesh, a pixel in an image, a node in a binary
|
|
search tree, an element in a sparse matrix, etc. The graph edges
|
|
represent node affinities, or a desire that adjacent nodes be stored
|
|
close together on linear storage (e.g., disk or main memory). Such a
|
|
data layout is likely to improve cache utilization in block-based
|
|
caches common on today's computer architectures. For instance, the
|
|
edges may connect adjacent pixels in an image, as many image
|
|
processing operations involve accessing local neighborhoods. The
|
|
resulting node layouts are "cache-oblivious" in the sense that no
|
|
particular knowledge of the cache parameters (number and size of
|
|
blocks, associativity, replacement policy, etc.) are accounted for.
|
|
Rather, the expectation is that the layouts will provide good
|
|
locality across all levels of cache. Note that the ordering method
|
|
accepts any undirected graph, whether it represent a structured or
|
|
unstructured data set, and is also oblivious of any geometric
|
|
structure inherent in the data set.
|
|
|
|
The optimization algorithm attempts to order the nodes of the graph
|
|
so as to minimize the geometric mean edge length, or equivalently
|
|
the product
|
|
|
|
product |p(i) - p(j)|^w(i, j)
|
|
|
|
or weighted sum
|
|
|
|
sum w(i, j) log(|p(i) - p(j)|)
|
|
|
|
where *i* and *j* are nodes joined by an edge, *w*(*i*, *j*) is a positive
|
|
edge weight (equal to one unless otherwise specified), *p*(*i*) is
|
|
the integer position of node *i* in the linear layout of the graph
|
|
(with *p*(*i*) = *p*(*j*) if and only if *i* = *j*), and where the product
|
|
or sum is over all edges of the graph.
|
|
|
|
The algorithm is described in further detail in the paper
|
|
|
|
* Peter Lindstrom
|
|
The Minimum Edge Product Linear Ordering Problem
|
|
LLNL technical report LLNL-TR-496076, August 26, 2011.
|
|
|
|
|
|
Ordering Parameters
|
|
-------------------
|
|
|
|
The `Graph::order()` function and the `gecko` command-line executable take a
|
|
number of parameters that govern the layout process. These parameters are
|
|
described below:
|
|
|
|
* The **functional** is the objective being optimized and expresses the cost
|
|
of the graph layout in terms of some average of its edge lengths
|
|
|*p*(*i*) - *p*(*j*)|. The predefined functionals are
|
|
* `h` (harmonic mean)
|
|
* `g` (geometric mean)
|
|
* `s` (square mean root)
|
|
* `a` (arithmetic mean)
|
|
* `r` (root mean square)
|
|
* `m` (maximum)
|
|
|
|
Note that the algorithm has not been well tuned or tested to optimize
|
|
functionals other than the geometric mean.
|
|
|
|
* The number of **iterations** specifies the number of multigrid V-cycles
|
|
to perform. Usually a handful of cycles is sufficient. The default is
|
|
a single cycle.
|
|
|
|
* The optimization **window** is the number of consecutive nodes optimized
|
|
concurrently using exhaustive search. The larger the window, the higher
|
|
the quality. Note that the running time increases exponentially with the
|
|
window size. Usually a window no larger than six nodes is sufficient.
|
|
The default is a window size of two nodes.
|
|
|
|
* The **period** is the number of V-cycles to run between increments of the
|
|
window size. Usually it is beneficial to start with a small window to get
|
|
a rough layout, and to then increase the window size to fine-tune the
|
|
layout. The default is a period of one cycle.
|
|
|
|
* The random **seed** allows injecting some randomness in the optimization
|
|
process. When the seed is nonzero, the nodes are randomly shuffled prior
|
|
to invoking the ordering algorithm, thereby affecting subsequent coarsening
|
|
and ordering decisions. In effect, this randomization allows different
|
|
directions to be explored in the combinatorial optimization space. Fixing
|
|
the seed allows for reproducibility, i.e., the same seed always leads to
|
|
the same layout. Since the global optimum is seldom (if ever) reached,
|
|
it often makes sense to run several instances of the algorithm, each with
|
|
a new random seed, and to pick the best layout found. In the gecko
|
|
executable, the current time is used as random seed if not specified.
|
|
|
|
A reasonable parameter choice for good-quality layouts is:
|
|
|
|
* iterations = 4
|
|
* window = 4
|
|
* period = 2
|
|
|
|
*/
|
|
|
|
#include <algorithm>
|
|
#include <string>
|
|
#include <utility>
|
|
#include <vector>
|
|
#include <cmath>
|
|
#include <cfloat>
|
|
#include <limits>
|
|
|
|
// ----- types.h ----------------------------------------------------------------
|
|
|
|
#define GECKO_FLOAT_EPSILON std::numeric_limits<Float>::epsilon()
|
|
#define GECKO_FLOAT_MAX std::numeric_limits<Float>::max()
|
|
|
|
namespace Gecko
|
|
{
|
|
|
|
typedef unsigned int uint;
|
|
|
|
// precision for node positions and computations
|
|
#if GECKO_WITH_DOUBLE_PRECISION
|
|
typedef double Float;
|
|
#else
|
|
typedef float Float;
|
|
#endif
|
|
}
|
|
|
|
// ----- functional.h -----------------------------------------------------------
|
|
|
|
namespace Gecko
|
|
{
|
|
|
|
// abstract base class for weighted terms and sums
|
|
class WeightedValue
|
|
{
|
|
public:
|
|
WeightedValue(Float value, Float weight) : value(value), weight(weight) {}
|
|
Float value;
|
|
Float weight;
|
|
};
|
|
|
|
// weighted sum of terms
|
|
class WeightedSum : public WeightedValue
|
|
{
|
|
public:
|
|
WeightedSum(Float value = 0, Float weight = 0) : WeightedValue(value, weight) {}
|
|
};
|
|
|
|
// abstract base class for ordering functionals
|
|
class Functional
|
|
{
|
|
public:
|
|
virtual ~Functional() {}
|
|
|
|
virtual WeightedSum sum(const WeightedSum& s, const WeightedValue& t) const
|
|
{
|
|
WeightedSum tot = s;
|
|
accumulate(tot, sum(t));
|
|
return tot;
|
|
}
|
|
|
|
virtual WeightedSum sum(const WeightedSum& s, const WeightedSum& t) const
|
|
{
|
|
WeightedSum tot = s;
|
|
accumulate(tot, t);
|
|
return tot;
|
|
}
|
|
|
|
// add weighted term to weighted sum
|
|
virtual void accumulate(WeightedSum& s, const WeightedValue& t) const
|
|
{
|
|
accumulate(s, sum(t));
|
|
}
|
|
|
|
// add two weighted sums
|
|
virtual void accumulate(WeightedSum& s, const WeightedSum& t) const
|
|
{
|
|
s.value += t.value;
|
|
s.weight += t.weight;
|
|
}
|
|
|
|
// is s potentially less than t?
|
|
virtual bool less(const WeightedSum& s, const WeightedSum& t) const
|
|
{
|
|
return s.value < t.value;
|
|
}
|
|
|
|
// transform term into weighted sum
|
|
virtual WeightedSum sum(const WeightedValue& term) const = 0;
|
|
|
|
// compute weighted mean from a weighted sum
|
|
virtual Float mean(const WeightedSum& sum) const = 0;
|
|
|
|
// compute k'th iteration bond for egde of length l and weight w
|
|
virtual Float bond(Float w, Float l, uint k) const = 0;
|
|
|
|
// compute position that minimizes weighted distance to a point set
|
|
virtual Float optimum(const std::vector<WeightedValue>& v) const = 0;
|
|
};
|
|
|
|
// functionals with quasiconvex terms, e.g., p-means with p < 1.
|
|
class FunctionalQuasiconvex : public Functional
|
|
{
|
|
protected:
|
|
Float optimum(const std::vector<WeightedValue>& v, Float lmin) const
|
|
{
|
|
// Compute the optimum as the node position that minimizes the
|
|
// functional. Any nodes coincident with each candidate position
|
|
// are excluded from the functional.
|
|
Float x = v[0].value;
|
|
Float min = GECKO_FLOAT_MAX;
|
|
switch (v.size())
|
|
{
|
|
case 1:
|
|
// Only one choice.
|
|
break;
|
|
case 2:
|
|
// Functional is the same for both nodes; pick node with
|
|
// larger weight.
|
|
if (v[1].weight > v[0].weight)
|
|
{
|
|
x = v[1].value;
|
|
}
|
|
break;
|
|
default:
|
|
for (std::vector<WeightedValue>::const_iterator p = v.begin(); p != v.end();
|
|
p++)
|
|
{
|
|
WeightedSum s;
|
|
for (std::vector<WeightedValue>::const_iterator q = v.begin(); q != v.end();
|
|
q++)
|
|
{
|
|
Float l = std::fabs(p->value - q->value);
|
|
if (l > lmin)
|
|
{
|
|
accumulate(s, WeightedValue(l, q->weight));
|
|
}
|
|
}
|
|
Float f = mean(s);
|
|
if (f < min)
|
|
{
|
|
min = f;
|
|
x = p->value;
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
return x;
|
|
}
|
|
private:
|
|
using Functional::optimum; // silence overload vs. override warning
|
|
};
|
|
|
|
// harmonic mean (p = -1)
|
|
class FunctionalHarmonic : public FunctionalQuasiconvex
|
|
{
|
|
public:
|
|
using Functional::sum;
|
|
bool less(const WeightedSum& s, const WeightedSum& t) const
|
|
{
|
|
// This is only a loose bound when s.weight < t.weight.
|
|
return s.value - s.weight > t.value - t.weight;
|
|
}
|
|
WeightedSum sum(const WeightedValue& term) const
|
|
{
|
|
return WeightedSum(term.weight / term.value, term.weight);
|
|
}
|
|
Float mean(const WeightedSum& sum) const
|
|
{
|
|
return sum.weight > 0 ? sum.weight / sum.value : 0;
|
|
}
|
|
Float bond(Float w, Float l, uint k) const
|
|
{
|
|
return w * std::pow(l, -Float(3) * Float(k) / Float(k + 1));
|
|
}
|
|
Float optimum(const std::vector<WeightedValue>& v) const
|
|
{
|
|
return FunctionalQuasiconvex::optimum(v, Float(0.5));
|
|
}
|
|
};
|
|
|
|
// geometric mean (p = 0)
|
|
class FunctionalGeometric : public FunctionalQuasiconvex
|
|
{
|
|
public:
|
|
using Functional::sum;
|
|
WeightedSum sum(const WeightedValue& term) const
|
|
{
|
|
return WeightedSum(term.weight * std::log(term.value), term.weight);
|
|
}
|
|
Float mean(const WeightedSum& sum) const
|
|
{
|
|
return sum.weight > 0 ? std::exp(sum.value / sum.weight) : 0;
|
|
}
|
|
Float bond(Float w, Float l, uint k) const
|
|
{
|
|
return w * std::pow(l, -Float(2) * Float(k) / Float(k + 1));
|
|
}
|
|
Float optimum(const std::vector<WeightedValue>& v) const
|
|
{
|
|
return FunctionalQuasiconvex::optimum(v, Float(0.5));
|
|
}
|
|
};
|
|
|
|
// square mean root (p = 1/2)
|
|
class FunctionalSMR : public FunctionalQuasiconvex
|
|
{
|
|
public:
|
|
using Functional::sum;
|
|
WeightedSum sum(const WeightedValue& term) const
|
|
{
|
|
return WeightedSum(term.weight * std::sqrt(term.value), term.weight);
|
|
}
|
|
Float mean(const WeightedSum& sum) const
|
|
{
|
|
return sum.weight > 0 ? (sum.value / sum.weight) * (sum.value / sum.weight) : 0;
|
|
}
|
|
Float bond(Float w, Float l, uint k) const
|
|
{
|
|
return w * std::pow(l, -Float(1.5) * Float(k) / Float(k + 1));
|
|
}
|
|
Float optimum(const std::vector<WeightedValue>& v) const
|
|
{
|
|
return FunctionalQuasiconvex::optimum(v, Float(0.0));
|
|
}
|
|
};
|
|
|
|
// arithmetic mean (p = 1)
|
|
class FunctionalArithmetic : public Functional
|
|
{
|
|
public:
|
|
using Functional::sum;
|
|
WeightedSum sum(const WeightedValue& term) const
|
|
{
|
|
return WeightedSum(term.weight * term.value, term.weight);
|
|
}
|
|
Float mean(const WeightedSum& sum) const
|
|
{
|
|
return sum.weight > 0 ? sum.value / sum.weight : 0;
|
|
}
|
|
Float bond(Float w, Float l, uint k) const
|
|
{
|
|
return w * std::pow(l, -Float(1) * Float(k) / Float(k + 1));
|
|
}
|
|
Float optimum(const std::vector<WeightedValue>& v) const
|
|
{
|
|
// Compute the optimum as the weighted median. Since the median may
|
|
// not be unique, the largest interval [x, y] is computed and its
|
|
// centroid is chosen. The optimum must occur at a node, and hence
|
|
// we consider each node position pi at a time and the relative
|
|
// positions of the remaining nodes pj.
|
|
Float x = 0;
|
|
Float y = 0;
|
|
Float min = GECKO_FLOAT_MAX;
|
|
for (std::vector<WeightedValue>::const_iterator p = v.begin(); p != v.end();
|
|
p++)
|
|
{
|
|
// Compute f = |sum_{j:pj<pi} wj - sum_{j:pj>pi} wj|.
|
|
Float f = 0;
|
|
for (std::vector<WeightedValue>::const_iterator q = v.begin(); q != v.end();
|
|
q++)
|
|
if (q->value < p->value)
|
|
{
|
|
f += q->weight;
|
|
}
|
|
else if (q->value > p->value)
|
|
{
|
|
f -= q->weight;
|
|
}
|
|
f = std::fabs(f);
|
|
// Update interval if f is minimal.
|
|
if (f <= min)
|
|
{
|
|
if (f < min)
|
|
{
|
|
min = f;
|
|
x = y = p->value;
|
|
}
|
|
else
|
|
{
|
|
x = std::min(x, p->value);
|
|
y = std::max(y, p->value);
|
|
}
|
|
}
|
|
}
|
|
return (x + y) / 2;
|
|
}
|
|
};
|
|
|
|
// root mean square (p = 2)
|
|
class FunctionalRMS : public Functional
|
|
{
|
|
public:
|
|
using Functional::sum;
|
|
WeightedSum sum(const WeightedValue& term) const
|
|
{
|
|
return WeightedSum(term.weight * term.value * term.value, term.weight);
|
|
}
|
|
Float mean(const WeightedSum& sum) const
|
|
{
|
|
return sum.weight > 0 ? std::sqrt(sum.value / sum.weight) : 0;
|
|
}
|
|
Float bond(Float w, Float, uint) const
|
|
{
|
|
return w;
|
|
}
|
|
Float optimum(const std::vector<WeightedValue>& v) const
|
|
{
|
|
// Compute the optimum as the weighted mean.
|
|
WeightedSum s;
|
|
for (std::vector<WeightedValue>::const_iterator p = v.begin(); p != v.end();
|
|
p++)
|
|
{
|
|
s.value += p->weight * p->value;
|
|
s.weight += p->weight;
|
|
}
|
|
return s.value / s.weight;
|
|
}
|
|
};
|
|
|
|
// maximum (p = infinity)
|
|
class FunctionalMaximum : public Functional
|
|
{
|
|
public:
|
|
using Functional::sum;
|
|
using Functional::accumulate;
|
|
WeightedSum sum(const WeightedValue& term) const
|
|
{
|
|
return WeightedSum(term.value, term.weight);
|
|
}
|
|
void accumulate(WeightedSum& s, const WeightedSum& t) const
|
|
{
|
|
s.value = std::max(s.value, t.value);
|
|
}
|
|
Float mean(const WeightedSum& sum) const
|
|
{
|
|
return sum.value;
|
|
}
|
|
Float bond(Float, Float, uint) const
|
|
{
|
|
return Float(1);
|
|
}
|
|
Float optimum(const std::vector<WeightedValue>& v) const
|
|
{
|
|
// Compute the optimum as the midrange.
|
|
Float min = v[0].value;
|
|
Float max = v[0].value;
|
|
for (std::vector<WeightedValue>::const_iterator p = v.begin() + 1; p != v.end();
|
|
p++)
|
|
{
|
|
if (p->value < min)
|
|
{
|
|
min = p->value;
|
|
}
|
|
else if (p->value > max)
|
|
{
|
|
max = p->value;
|
|
}
|
|
}
|
|
return (min + max) / 2;
|
|
}
|
|
};
|
|
|
|
}
|
|
|
|
// ----- progress.h -------------------------------------------------------------
|
|
|
|
namespace Gecko
|
|
{
|
|
|
|
class Graph;
|
|
|
|
// Callbacks between iterations and phases.
|
|
class Progress
|
|
{
|
|
public:
|
|
virtual ~Progress() {}
|
|
virtual void beginorder(const Graph* /*graph*/, Float /*cost*/) const {}
|
|
virtual void endorder(const Graph* /*graph*/, Float /*cost*/) const {}
|
|
virtual void beginiter(const Graph* /*graph*/, uint /*iter*/, uint /*maxiter*/,
|
|
uint /*window*/) const {}
|
|
virtual void enditer(const Graph* /*graph*/, Float /*mincost*/,
|
|
Float /*cost*/) const {}
|
|
virtual void beginphase(const Graph* /*graph*/, std::string /*name*/) const {};
|
|
virtual void endphase(const Graph* /*graph*/, bool /*show*/) const {};
|
|
virtual bool quit() const { return false; }
|
|
};
|
|
|
|
}
|
|
|
|
// ----- graph.h ----------------------------------------------------------------
|
|
|
|
namespace Gecko
|
|
{
|
|
|
|
// Multilevel graph arc.
|
|
class Arc
|
|
{
|
|
public:
|
|
typedef uint Index;
|
|
typedef std::vector<Index>::const_iterator ConstPtr;
|
|
enum { null = 0 };
|
|
};
|
|
|
|
// Multilevel graph node.
|
|
class Node
|
|
{
|
|
public:
|
|
typedef uint Index;
|
|
typedef std::vector<Node>::const_iterator ConstPtr;
|
|
enum { null = 0 };
|
|
|
|
// comparator for sorting node indices
|
|
class Comparator
|
|
{
|
|
public:
|
|
Comparator(ConstPtr node) : _node(node) {}
|
|
bool operator()(uint k, uint l) const { return _node[k].pos < _node[l].pos; }
|
|
private:
|
|
const ConstPtr _node;
|
|
};
|
|
|
|
// constructor
|
|
Node(Float pos = -1, Float length = 1, Arc::Index arc = Arc::null,
|
|
Node::Index parent = Node::null) : pos(pos), hlen(Float(0.5) * length),
|
|
arc(arc), parent(parent) {}
|
|
|
|
Float pos; // start position at full resolution
|
|
Float hlen; // half of node length (number of full res nodes)
|
|
Arc::Index arc; // one past index of last incident arc
|
|
Node::Index parent; // parent in next coarser resolution
|
|
};
|
|
|
|
// Multilevel graph.
|
|
class Graph
|
|
{
|
|
public:
|
|
// constructor of graph with given (initial) number of nodes
|
|
Graph(uint nodes = 0) : level(0), last_node(Node::null) { init(nodes); }
|
|
|
|
// number of nodes and edges
|
|
uint nodes() const { return uint(node.size() - 1); }
|
|
uint edges() const { return uint((adj.size() - 1) / 2); }
|
|
|
|
// insert node and return its index
|
|
Node::Index insert_node(Float length = 1);
|
|
|
|
// outgoing arcs {begin, ..., end-1} originating from node i
|
|
Arc::Index node_begin(Node::Index i) const { return node[i - 1].arc; }
|
|
Arc::Index node_end(Node::Index i) const { return node[i].arc; }
|
|
|
|
// node degree and neighbors
|
|
uint node_degree(Node::Index i) const { return node_end(i) - node_begin(i); }
|
|
std::vector<Node::Index> node_neighbors(Node::Index i) const;
|
|
|
|
// insert directed edge (i, j)
|
|
Arc::Index insert_arc(Node::Index i, Node::Index j, Float w = 1, Float b = 1);
|
|
|
|
// remove arc or edge
|
|
bool remove_arc(Arc::Index a);
|
|
bool remove_arc(Node::Index i, Node::Index j);
|
|
bool remove_edge(Node::Index i, Node::Index j);
|
|
|
|
// index of arc (i, j) or null if not present
|
|
Arc::Index arc_index(Node::Index i, Node::Index j) const;
|
|
|
|
// arc source and target nodes and weight
|
|
Node::Index arc_source(Arc::Index a) const;
|
|
Node::Index arc_target(Arc::Index a) const { return adj[a]; }
|
|
Float arc_weight(Arc::Index a) const { return weight[a]; }
|
|
|
|
// reverse arc (j, i) of arc a = (i, j)
|
|
Arc::Index reverse_arc(Arc::Index a) const;
|
|
|
|
// order graph
|
|
void order(Functional* functional, uint iterations = 1, uint window = 2,
|
|
uint period = 2, uint seed = 0, Progress* progress = 0);
|
|
|
|
// optimal permutation found
|
|
const std::vector<Node::Index>& permutation() const { return perm; }
|
|
|
|
// node of given rank in reordered graph (0 <= rank <= nodes() - 1)
|
|
Node::Index permutation(uint rank) const { return perm[rank]; }
|
|
|
|
// position of node i in reordered graph (1 <= i <= nodes())
|
|
uint rank(Node::Index i) const { return static_cast<uint>(std::floor(node[i].pos)); }
|
|
|
|
// cost of current layout
|
|
Float cost() const;
|
|
|
|
// return first directed arc if one exists or null otherwise
|
|
Arc::Index directed() const;
|
|
|
|
protected:
|
|
friend class Subgraph;
|
|
friend class Drawing;
|
|
|
|
// constructor/destructor
|
|
Graph(uint nodes, uint level) : level(level), last_node(Node::null) { init(nodes); }
|
|
|
|
// arc length
|
|
Float length(Node::Index i, Node::Index j) const { return std::fabs(node[i].pos - node[j].pos); }
|
|
Float length(Arc::Index a) const
|
|
{
|
|
Node::Index i = arc_source(a);
|
|
Node::Index j = arc_target(a);
|
|
return length(i, j);
|
|
}
|
|
|
|
// coarsen graph
|
|
Graph* coarsen();
|
|
|
|
// refine graph
|
|
void refine(const Graph* graph);
|
|
|
|
// perform m sweeps of compatible or Gauss-Seidel relaxation
|
|
void relax(bool compatible, uint m = 1);
|
|
|
|
// optimize using n-node window
|
|
void optimize(uint n);
|
|
|
|
// place all nodes according to their positions
|
|
void place(bool sort = false);
|
|
|
|
// place nodes {k, ..., k + n - 1} according to their positions
|
|
void place(bool sort, uint k, uint n);
|
|
|
|
// perform V cycle using n-node window
|
|
void vcycle(uint n, uint work = 0);
|
|
|
|
// randomly shuffle nodes
|
|
void shuffle(uint seed = 0);
|
|
|
|
// recompute arc bonds for iteration i
|
|
void reweight(uint i);
|
|
|
|
// compute cost
|
|
WeightedSum cost(const std::vector<Arc::Index>& subset, Float pos) const;
|
|
|
|
// node attributes
|
|
bool persistent(Node::Index i) const { return node[i].parent != Node::null; }
|
|
bool placed(Node::Index i) const { return node[i].pos >= Float(0); }
|
|
|
|
Functional* functional; // ordering functional
|
|
Progress* progress; // progress callbacks
|
|
std::vector<Node::Index> perm; // ordered list of indices to nodes
|
|
std::vector<Node> node; // statically ordered list of nodes
|
|
std::vector<Node::Index> adj; // statically ordered list of adjacent nodes
|
|
std::vector<Float> weight; // statically ordered list of arc weights
|
|
std::vector<Float> bond; // statically ordered list of coarsening weights
|
|
|
|
private:
|
|
// initialize graph with given number of nodes
|
|
void init(uint nodes);
|
|
|
|
// find optimal position of node i while fixing all other nodes
|
|
Float optimal(Node::Index i) const;
|
|
|
|
// add contribution of fine arc to coarse graph
|
|
void update(Node::Index i, Node::Index j, Float w, Float b);
|
|
|
|
// transfer contribution of fine arc a to coarse node p
|
|
void transfer(Graph* g, const std::vector<Float>& part, Node::Index p,
|
|
Arc::Index a, Float f = 1) const;
|
|
|
|
// swap the positions of nodes
|
|
void swap(uint k, uint l);
|
|
|
|
// random number generator
|
|
static uint random(uint seed = 0);
|
|
|
|
uint level; // level of coarsening
|
|
Node::Index last_node; // last node with outgoing arcs
|
|
};
|
|
|
|
}
|
|
|
|
#endif // MFEM_GECKO_HPP
|