blob: d231799f1c234233bb55becd490b68bbdba626a2 [file] [log] [blame]
\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}