866581da18
[SVN r10885]
319 lines
8.3 KiB
OpenEdge ABL
319 lines
8.3 KiB
OpenEdge ABL
\documentclass[11pt]{report}
|
|
|
|
\input{defs}
|
|
|
|
|
|
\setlength\overfullrule{5pt}
|
|
\tolerance=10000
|
|
\sloppy
|
|
\hfuzz=10pt
|
|
|
|
\makeindex
|
|
|
|
\begin{document}
|
|
|
|
\title{An Implementation of the Multiple Minimum Degree Algorithm}
|
|
\author{Jeremy G. Siek and Lie Quan Lee}
|
|
|
|
\maketitle
|
|
|
|
\section{Introduction}
|
|
|
|
The minimum degree ordering algorithm \cite{LIU:MMD,George:evolution}
|
|
reorders a matrix to reduce fill-in. Fill-in is the term used to refer
|
|
to the zero elements of a matrix that become non-zero during the
|
|
gaussian elimination process. Fill-in is bad because it makes the
|
|
matrix less sparse and therefore consume more time and space in later
|
|
stages of the elimintation and in computations that use the resulting
|
|
factorization. Reordering the rows of a matrix can have a dramatic
|
|
affect on the amount of fill-in that occurs. So instead of solving
|
|
\begin{eqnarray}
|
|
A x = b
|
|
\end{eqnarray}
|
|
we instead solve the reordered (but equivalent) system
|
|
\begin{eqnarray}
|
|
(P A P^T)(P x) = P b.
|
|
\end{eqnarray}
|
|
where $P$ is a permutation matrix.
|
|
|
|
Finding the optimal ordering is an NP-complete problem (e.i., it can
|
|
not be solved in a reasonable amount of time) so instead we just find
|
|
an ordering that is ``good enough'' using heuristics. The minimum
|
|
degree ordering algorithm is one such approach.
|
|
|
|
A symmetric matrix $A$ is typically represented with an undirected
|
|
graph, however for this function we require the input to be a directed
|
|
graph. Each nonzero matrix entry $A_{ij}$ must be represented by two
|
|
directed edges, $(i,j)$ and $(j,i)$, in $G$.
|
|
|
|
An \keyword{elimination graph} $G_v$ is formed by removing vertex $v$
|
|
and all of its incident edges from graph $G$ and then adding edges to
|
|
make the vertices adjacent to $v$ into a clique\footnote{A clique is a
|
|
complete subgraph. That is, it is a subgraph where each vertex is
|
|
adjacent to every other vertex in the subgraph}.
|
|
|
|
|
|
quotient graph
|
|
set of cliques in the graph
|
|
|
|
|
|
Mass elmination: if $y$ is selected as the minimum degree node then
|
|
the the vertices adjacent to $y$ with degree equal to $degree(y) - 1$
|
|
can be selected next (in any order).
|
|
|
|
Two nodes are \keyword{indistinguishable} if they have identical
|
|
neighborhood sets. That is,
|
|
\begin{eqnarray}
|
|
Adj[u] \cup \{ u \} = Adj[v] \bigcup \{ v \}
|
|
\end{eqnarray}
|
|
Nodes that are indistiguishable can be eliminated at the same time.
|
|
|
|
A representative for a set of indistiguishable nodes is called a
|
|
\keyword{supernode}.
|
|
|
|
incomplete degree update
|
|
external degree
|
|
|
|
|
|
|
|
|
|
\section{Algorithm Overview}
|
|
|
|
@d MMD Algorithm Overview @{
|
|
@<Eliminate isolated nodes@>
|
|
|
|
@}
|
|
|
|
|
|
|
|
|
|
@d Set up a mapping from integers to vertices @{
|
|
size_type vid = 0;
|
|
typename graph_traits<Graph>::vertex_iterator v, vend;
|
|
for (tie(v, vend) = vertices(G); v != vend; ++v, ++vid)
|
|
index_vertex_vec[vid] = *v;
|
|
index_vertex_map = IndexVertexMap(&index_vertex_vec[0]);
|
|
@}
|
|
|
|
|
|
@d Insert vertices into bucket sorter (bucket number equals degree) @{
|
|
for (tie(v, vend) = vertices(G); v != vend; ++v) {
|
|
put(degree, *v, out_degree(*v, G));
|
|
degreelists.push(*v);
|
|
}
|
|
@}
|
|
|
|
|
|
@d Eliminate isolated nodes (nodes with zero degree) @{
|
|
typename DegreeLists::stack list_isolated = degreelists[0];
|
|
while (!list_isolated.empty()) {
|
|
vertex_t node = list_isolated.top();
|
|
marker.mark_done(node);
|
|
numbering(node);
|
|
numbering.increment();
|
|
list_isolated.pop();
|
|
}
|
|
@}
|
|
|
|
|
|
@d Find first non-empty degree bucket
|
|
@{
|
|
size_type min_degree = 1;
|
|
typename DegreeLists::stack list_min_degree = degreelists[min_degree];
|
|
while (list_min_degree.empty()) {
|
|
++min_degree;
|
|
list_min_degree = degreelists[min_degree];
|
|
}
|
|
@}
|
|
|
|
|
|
|
|
@d Main Loop
|
|
@{
|
|
while (!numbering.all_done()) {
|
|
eliminated_nodes = work_space.make_stack();
|
|
if (delta >= 0)
|
|
while (true) {
|
|
@<Find next non-empty degree bucket@>
|
|
@<Select a node of minimum degree for elimination@>
|
|
eliminate(node);
|
|
}
|
|
if (numbering.all_done())
|
|
break;
|
|
update(eliminated_nodes, min_degree);
|
|
}
|
|
@}
|
|
|
|
|
|
@d Elimination Function
|
|
@{
|
|
void eliminate(vertex_t node)
|
|
{
|
|
remove out-edges from the node if the target vertex was
|
|
tagged or if it is numbered
|
|
add vertices adjacent to node to the clique
|
|
put all numbered adjacent vertices into the temporary neighbors stack
|
|
|
|
@<Perform element absorption optimization@>
|
|
}
|
|
@}
|
|
|
|
|
|
@d
|
|
@{
|
|
bool remove_out_edges_predicate::operator()(edge_t e)
|
|
{
|
|
vertex_t v = target(e, *g);
|
|
bool is_tagged = marker->is_tagged(dist);
|
|
bool is_numbered = numbering.is_numbered(v);
|
|
if (is_numbered)
|
|
neighbors->push(id[v]);
|
|
if (!is_tagged)
|
|
marker->mark_tagged(v);
|
|
return is_tagged || is_numbered;
|
|
}
|
|
@}
|
|
|
|
|
|
|
|
How does this work????
|
|
|
|
Does \code{is\_tagged} mean in the clique??
|
|
|
|
@d Perform element absorption optimization
|
|
@{
|
|
while (!neighbors.empty()) {
|
|
size_type n_id = neighbors.top();
|
|
vertex_t neighbor = index_vertex_map[n_id];
|
|
adjacency_iterator i, i_end;
|
|
for (tie(i, i_end) = adjacent_vertices(neighbor, G); i != i_end; ++i) {
|
|
vertex_t i_node = *i;
|
|
if (!marker.is_tagged(i_node) && !numbering.is_numbered(i_node)) {
|
|
marker.mark_tagged(i_node);
|
|
add_edge(node, i_node, G);
|
|
}
|
|
}
|
|
neighbors.pop();
|
|
}
|
|
@}
|
|
|
|
|
|
|
|
@d
|
|
@{
|
|
predicateRemoveEdge1<Graph, MarkerP, NumberingD,
|
|
typename Workspace::stack, VertexIndexMap>
|
|
p(G, marker, numbering, element_neighbor, vertex_index_map);
|
|
|
|
remove_out_edge_if(node, p, G);
|
|
@}
|
|
|
|
|
|
\section{The Interface}
|
|
|
|
|
|
@d Interface of the MMD function @{
|
|
template<class Graph, class DegreeMap,
|
|
class InversePermutationMap,
|
|
class PermutationMap,
|
|
class SuperNodeMap, class VertexIndexMap>
|
|
void minimum_degree_ordering
|
|
(Graph& G,
|
|
DegreeMap degree,
|
|
InversePermutationMap inverse_perm,
|
|
PermutationMap perm,
|
|
SuperNodeMap supernode_size,
|
|
int delta,
|
|
VertexIndexMap vertex_index_map)
|
|
@}
|
|
|
|
|
|
|
|
\section{Representing Disjoint Stacks}
|
|
|
|
Given a set of $N$ integers (where the integer values range from zero
|
|
to $N-1$), we want to keep track of a collection of stacks of
|
|
integers. It so happens that an integer will appear in at most one
|
|
stack at a time, so the stacks form disjoint sets. Because of these
|
|
restrictions, we can use one big array to store all the stacks,
|
|
intertwined with one another. No allocation/deallocation happens in
|
|
the \code{push()}/\code{pop()} methods so this is faster than using
|
|
\code{std::stack}.
|
|
|
|
|
|
\section{Bucket Sorting}
|
|
|
|
|
|
|
|
@d Bucket Sorter Class Interface @{
|
|
template <typename BucketMap, typename ValueIndexMap>
|
|
class bucket_sorter {
|
|
public:
|
|
typedef typename property_traits<BucketMap>::value_type bucket_type;
|
|
typedef typename property_traits<ValueIndex>::key_type value_type;
|
|
typedef typename property_traits<ValueIndex>::value_type size_type;
|
|
|
|
bucket_sorter(size_type length, bucket_type max_bucket,
|
|
const BucketMap& bucket = BucketMap(),
|
|
const ValueIndexMap& index_map = ValueIndexMap());
|
|
void remove(const value_type& x);
|
|
void push(const value_type& x);
|
|
void update(const value_type& x);
|
|
|
|
class stack {
|
|
public:
|
|
void push(const value_type& x);
|
|
void pop();
|
|
value_type& top();
|
|
const value_type& top() const;
|
|
bool empty() const;
|
|
private:
|
|
@<Bucket Stack Constructor@>
|
|
@<Bucket Stack Data Members@>
|
|
};
|
|
stack operator[](const bucket_type& i);
|
|
private:
|
|
@<Bucket Sorter Data Members@>
|
|
};
|
|
@}
|
|
|
|
\code{BucketMap} is a \concept{LvaluePropertyMap} that converts a
|
|
value object to a bucket number (an integer). The range of the mapping
|
|
must be finite. \code{ValueIndexMap} is a \concept{LvaluePropertyMap}
|
|
that maps from value objects to a unique integer. At the top of the
|
|
definition of \code{bucket\_sorter} we create some typedefs for the
|
|
bucket number type, the value type, and the index type.
|
|
|
|
@d Bucket Sorter Data Members @{
|
|
std::vector<size_type> head;
|
|
std::vector<size_type> next;
|
|
std::vector<size_type> prev;
|
|
std::vector<value_type> id_to_value;
|
|
BucketMap bucket_map;
|
|
ValueIndexMap index_map;
|
|
@}
|
|
|
|
|
|
\code{N} is the maximum integer that the \code{index\_map} will map a
|
|
value object to (the minimum is assumed to be zero).
|
|
|
|
@d Bucket Sorter Constructor @{
|
|
bucket_sorter::bucket_sorter
|
|
(size_type N, bucket_type max_bucket,
|
|
const BucketMap& bucket_map = BucketMap(),
|
|
const ValueIndexMap& index_map = ValueIndexMap())
|
|
: head(max_bucket, invalid_value()),
|
|
next(N, invalid_value()),
|
|
prev(N, invalid_value()),
|
|
id_to_value(N),
|
|
bucket_map(bucket_map), index_map(index_map) { }
|
|
@}
|
|
|
|
|
|
|
|
|
|
\bibliographystyle{abbrv}
|
|
\bibliography{jtran,ggcl,optimization,generic-programming,cad}
|
|
|
|
\end{document}
|