516 lines
20 KiB
TeX
516 lines
20 KiB
TeX
%==========================================================================
|
|
\chapter{Design}
|
|
\label{Design}
|
|
|
|
%==========================================================================
|
|
\section{Software Architecture}
|
|
\label{Software Architecture}
|
|
|
|
\subsection{More detail on the programming model}
|
|
|
|
|
|
\hypre{} is more than just a
|
|
library of solver routines: it
|
|
is actually a collection of pieces that can be "programmed" in different ways.
|
|
Technically speaking,
|
|
\hypre{} is a "component model." It is not necessary to have any formal training
|
|
in "components" to be able
|
|
to use \hypre{}, however. In our experience, looking at some examples and
|
|
programming a simple example
|
|
or two quickly gives users an intuitive feel for how this model works. In
|
|
brief, the major "mental shift"
|
|
necessary is to accept that \hypre{} represents all of the parts of a linear
|
|
solver library as "encapsulated
|
|
objects", called "components". This includes the obvious candidates such as
|
|
matrices, but also the less
|
|
obvious things such as the algorithms used to implement preconditioning. Each
|
|
object is opaque and can be
|
|
used only through the advertised "services" that it supports. All function
|
|
calls within \hypre{} are
|
|
considered to be a function "of some object/component", and the functions that
|
|
work on a particular
|
|
component are exactly the set of "services" that that component provides. In C
|
|
language, that component is
|
|
always the first parameter in the function call, for example. Other parameters
|
|
in a function call are normal
|
|
parameters, but the first one is always the component that the function call is
|
|
"acting on". The list of
|
|
services provided by each component are easily discovered by looking at the
|
|
header files for each
|
|
component, as well as other centralized documentation.
|
|
|
|
In many ways, the ``component design'' of HYPRE is simpler than object-oriented
|
|
design, and it can be explained in an analogy to traditional subroutine
|
|
libraries.
|
|
As mentioned above, there are two entities in HYPRE: lists of services, and
|
|
pieces of software that implement some subset of those services.
|
|
This is a direct analog of old-fashioned procedural libraries, for example,
|
|
the Basic Linear Algebra Subroutine (BLAS): the ``lists of
|
|
services'' are the functions that a library might implement, and the pieces
|
|
of software that implement the services are the specific library implementations.
|
|
The BLAS are not an exact fit to this analogy, because typically a library
|
|
that implements part of the BLAS implements it all.
|
|
Imagine, though, that different BLAS libraries implemented only the portion
|
|
of the BLAS that the developers chose to implement, either due to manpower,
|
|
area of expertise, interest, need, etc.
|
|
As a user, the way you would use such a system would be to figure out which
|
|
functions you want to call (i.e. which services you would like to use), make
|
|
a call to that routine in your program, and then look at all of the available
|
|
BLAS implementations to find the ones that have implemented the call that you
|
|
made and choose one.
|
|
One way to think about HYPRE is exactly analagous: the BLAS functions are
|
|
the analog of the abstract interfaces within HYPRE, and the analog of a BLAS
|
|
library is a particular HYPRE component.
|
|
|
|
Besides for the services that a component provides, there is only one other use
|
|
of a given component within
|
|
\hypre{}, and that is as an input to other components. For example, \hypre{} provides
|
|
several components
|
|
that implement preconditioning algorithms. A user may very well set up a
|
|
preconditioner but never call any
|
|
services from the preconditioner directly. Instead, the preconditioner is
|
|
handed to a solver within \hypre{}
|
|
and the solver uses that preconditioner. Thus, the way to decide if you want to
|
|
use a particular component
|
|
is to ask "do I want to use any of its services" and "do I need it as input to
|
|
another component that I want to
|
|
use?" If the answer to either question is "yes", then you need that component.
|
|
|
|
The components are then put together as inputs to each other to collaborate on
|
|
performing the desired
|
|
action. The "desired action" is typically the solution of a single linear
|
|
system of equations and \hypre{} is
|
|
specialized to make that extremely easy, but one of the benefits of \hypre{}'s
|
|
programming model is that
|
|
the parts can be combined together in different ways to perform different
|
|
actions. Examples might be:
|
|
reusing preconditioners over several timesteps of a time-dependent calculation,
|
|
using a solver as a
|
|
preconditioner, or providing user-defined matrices or preconditioners for use
|
|
within \hypre{}. Generally,
|
|
these more complex usages are performed by more expert users, including
|
|
developers of other solver
|
|
components.
|
|
|
|
|
|
%==========================================================================
|
|
\section{Object Model}
|
|
\label{Object Model}
|
|
|
|
|
|
{\bf Abstract versus concrete types:}
|
|
|
|
In the current version of HYPRE, we use a capitalization scheme
|
|
to differentiate types in
|
|
\hypre{}: capital \code{HYPRE} refers to abstract classes, that is, services,
|
|
while lower \code{hypre} refers to concrete classes.
|
|
If you
|
|
declare something to be of
|
|
type \code{HYPRE_Solver}, for instance (see declaration of components below), it is
|
|
equivalent to stating that you
|
|
will be using a component that supports the services listed in the
|
|
\code{HYPRE_Solver}
|
|
class. On the other hand,
|
|
lowercase \code{hypre} indicates a specific component, that is, a "concrete class".
|
|
The \code{hypre_StructSMGSolve}r,
|
|
for instance, is a particular component. Just as in object-oriented libraries,
|
|
"upcasting" is always allowed.
|
|
This means that in the bulk of your program code, you can refer to components
|
|
as if they were the abstract
|
|
types. Only in the declaration of the component is the concrete class
|
|
mentioned. This technique is
|
|
invaluable for promoting plug and play. In this example, then, you could
|
|
declare
|
|
|
|
\begin{display}
|
|
\begin{verbatim}
|
|
|
|
HYPRE_Solver A;
|
|
hypre_StructSMGSolver a;
|
|
A = (HYPRE_Solver) a;
|
|
|
|
\end{verbatim}
|
|
\end{display}
|
|
|
|
Everywhere else in the code, you
|
|
can refer to \code{a} as if it
|
|
was an \code{HYPRE_Solver}. Then, should you decide you would like to, say,
|
|
use the PFMG solver
|
|
instead of SMG (these
|
|
algorithms are described in more detail in the "Solvers Available" section),
|
|
the only line of code that needs
|
|
to change is
|
|
|
|
\begin{display}
|
|
\begin{verbatim}
|
|
|
|
hypre_StructPFMGSolver a;
|
|
|
|
\end{verbatim}
|
|
\end{display}
|
|
|
|
In this way, users can isolate any
|
|
potential changes to a single
|
|
place in their code.
|
|
|
|
\subsection{The life cycle of a component}
|
|
|
|
Every encapsulated object or component in \hypre{} has the same basic "life
|
|
cycle", and many user
|
|
problems can be prevented simply by understanding this cycle and making sure
|
|
that each component has
|
|
had all of the proper steps taken in the user code.
|
|
|
|
\begin{display}
|
|
\begin{verbatim}
|
|
|
|
Add an example here to use for illustration of the exact sequence and syntax of
|
|
steps.
|
|
|
|
\end{verbatim}
|
|
\end{display}
|
|
|
|
\begin{enumerate}
|
|
|
|
\item
|
|
{\bf Declaration of the "handle" to the component.} Each \hypre{} component is
|
|
represented by a
|
|
"handle", and users must declare these handles just like any other data. They
|
|
can either be declared
|
|
statically or through dynamic memory mechanism such as \code{MALLOC} in C language,
|
|
and the same
|
|
scope rules apply to \hypre{} components as to other data types (though see the
|
|
note below on
|
|
reference counting). Though this information is technically "opaque" to users,
|
|
generally \hypre{}
|
|
handles are either pointers or integers, and thus, these handles are safe to be
|
|
passed around by users in
|
|
parameter lists without worrying about excessive copying going on underneath.
|
|
The handles are all of the form
|
|
\code{HYPRE_Service}, where
|
|
\code{Service} is replaced by the name of the family of services that you want your
|
|
\hypre{} object to
|
|
provide. For example, a \code{HYPRE_Solve}r is a component that provides solver
|
|
services.
|
|
|
|
\item
|
|
{\bf Binding the handle to a concrete type.} Declaration of the handle by
|
|
|
|
\begin{display}
|
|
\begin{verbatim}
|
|
|
|
HYPRE_Solver solver;
|
|
|
|
\end{verbatim}
|
|
\end{display}
|
|
|
|
says the
|
|
following to \hypre{}: "I will be using a \hypre{} component that provides the Solver
|
|
service, and I will
|
|
be calling that component "solver"". It does not, however, tell \hypre{} exactly
|
|
which component that
|
|
provides the Solver service "solver" should refer to. The most common way for
|
|
this information to be
|
|
declared is to set the handle equal to the handle of a specific component. For
|
|
example,
|
|
|
|
\begin{display}
|
|
\begin{verbatim}
|
|
|
|
HYPRE_Solver solver = (HYPRE_Solver) hypre_StructSMGSolver;
|
|
|
|
\end{verbatim}
|
|
\end{display}
|
|
|
|
In the OO world, this is the
|
|
same thing as
|
|
"instantiating the concrete type", while the service is the "abstract
|
|
type". The benefit of this
|
|
system is that the choice of concrete type appears exactly once in the user's
|
|
code, and that everywhere
|
|
else, the component is accessed knowing only the services it provides. This is
|
|
the mechanism that
|
|
enables "plug and play": a user can switch solvers (or other components) by
|
|
changing a single line of
|
|
their code (in fact, mechanisms can be set up to allow runtime switching; this
|
|
will be discussed later)
|
|
because everywhere else in the code the components are used through the "common
|
|
interfaces"
|
|
defined by the appropriate \hypre{} service.
|
|
|
|
\item
|
|
{\bf Bring the component "to life" through a "new" call.} The first call that must
|
|
be made on every
|
|
\hypre{} component is a \code{new} call (though see the section on "Construction
|
|
Components" below), as
|
|
in
|
|
|
|
\begin{display}
|
|
\begin{verbatim}
|
|
|
|
return_code = HYPRE_Create( solver );
|
|
|
|
\end{verbatim}
|
|
\end{display}
|
|
|
|
Essentially, these routines allocate
|
|
space for the object and
|
|
set up defaults.
|
|
|
|
\item
|
|
{\bf Set parameters. Important note:} all parameters have reasonable defaults
|
|
that will be used if not
|
|
explicitly set by the user.
|
|
|
|
\item
|
|
{\bf Pass in needed information for construction of the component.} The
|
|
information required depends
|
|
on the component. Matrices need the coefficients that define the matrix; these
|
|
are passed in through
|
|
repeated calls to the chosen conceptual interface. Preconditioners need a
|
|
"matrix" that provides the
|
|
necessary access pattern service. Preconditioned solvers need a preconditioner.
|
|
|
|
\item
|
|
{\bf Construct the object.} After the necessary construction information has been
|
|
passed in, \hypre{} must
|
|
be instructed to construct the object, currently through the Setup call as in
|
|
|
|
\begin{display}
|
|
\begin{verbatim}
|
|
|
|
HYPRE_Setup (solver);
|
|
|
|
\end{verbatim}
|
|
\end{display}
|
|
|
|
\item
|
|
{\bf Use the object.} After construction, the object is ready to be used through
|
|
its advertised services, or to
|
|
be handed to other components as parameters. Matrices can be used to do
|
|
matrix-vector multiplication,
|
|
or be given to solvers/preconditioners (for their construction); solvers can be
|
|
used to solve systems, or
|
|
handed to other solvers as preconditioners; etc.
|
|
|
|
\item
|
|
{\bf "Kill" the object.} This is the opposite of the "bringing to life" phase.
|
|
Here, the call is to "Free" the
|
|
object as in
|
|
|
|
\begin{display}
|
|
\begin{verbatim}
|
|
|
|
HYPRE_Destroy( solver );
|
|
|
|
\end{verbatim}
|
|
\end{display}
|
|
|
|
NOTE: \hypre{} uses reference counting to
|
|
manage memory, and
|
|
thus \code{HYPRE_Free} does not actually deallocate the object unless this was the
|
|
last remaining reference
|
|
to the object. This allows users to safely free an object in one part of the
|
|
code without worrying about
|
|
whether it is still being used by some other objects that are still alive.
|
|
|
|
\end{enumerate}
|
|
|
|
\subsection{Construction versus Use}
|
|
|
|
All of the \hypre{} interfaces (or sets of services) can be divided into two
|
|
categories: {\bf construction}
|
|
interfaces or {\bf use} interfaces. Every concrete component in \hypre{}
|
|
(designated by
|
|
\code{hypre}) must support at
|
|
least one construction interface (note: if a component supports more than one
|
|
construction interface, you
|
|
cannot mix and match calls from those interfaces. One and only one interface
|
|
must be chosen and called, or
|
|
else errors may occur) and may support any number of use interfaces. In terms
|
|
of the lifecycle given above,
|
|
"construction" is steps 2-6, and "use" is step 7. Thus, for a given concrete
|
|
component, to figure out how to
|
|
perform steps 2-6 on it, you must look up the \hypre{} construction interface that
|
|
it supports, and to see
|
|
which operations you may call on it in step 7, you must look up the use
|
|
interfaces that that component
|
|
supports.
|
|
|
|
\subsubsection{Motivation} The motivation for separate construction and use interfaces in
|
|
\hypre{} is flexibility, both for
|
|
users and the algorithm developers that contribute algorithms to both \hypre{} and
|
|
libraries that are
|
|
compatible with \hypre{} (i.e. "ESI compliant" libraries). The separation is
|
|
motivated by the observation
|
|
that different objects might be constructed through the same construction
|
|
process and thus it is not possible
|
|
to mandate, for example, that all objects built through the StructuredGrid
|
|
interface should necessarily be of
|
|
a particular type. Indeed, there is a good example: the StructuredGrid's most
|
|
natural function is to produce
|
|
a matrix that then can be input to solvers, but there are also solver writers
|
|
who would like to produce
|
|
solvers directly from the StructuredGrid interface because this allows them to
|
|
control construction of the
|
|
matrix in a way that is optimized for their solver. In the second case, then,
|
|
the component implements not only the
|
|
\code{StructuredGrid} construction interface, but also the \code{Solver} use interface.
|
|
|
|
\subsubsection{Construction Components}
|
|
|
|
There is a subset of components within \hypre{} whose main function is simply to
|
|
build other components.
|
|
We call these "construction" components. These components are easily
|
|
recognizable: they are the
|
|
components that provide the \code{Build} service whose major function is
|
|
\code{GetConstructedObject}. They are
|
|
also easily explainable in the context of the component lifecycle:
|
|
a construction component A
|
|
handles steps 2-6 for component B, and the "use" of component A (i.e. step 7)
|
|
is to return component B (in
|
|
a state where it is ready to be used). Thus, if component B is built through a
|
|
construction component A,
|
|
steps 2-6 of B are done by A and the user does not have to do them explicitly.
|
|
An example speaks a
|
|
thousand words:
|
|
|
|
\hypre{} has several components that build matrices, such as the
|
|
\code{hypre_StructGridStructMatrix} builder
|
|
component. This component implements the \code{HYPRE_StructGridInput} interface, but
|
|
its only "use" function
|
|
is the Builder interface with the \code{GetConstructedObject} function. Clients use
|
|
this component in the normal
|
|
way in steps 1-6. However, when step 7 is reached, the user calls
|
|
\code{GetConstructedObject} and is returned a
|
|
second component B. For the component B, the user still has to perform step 1,
|
|
the declaration of the
|
|
handle to B. Steps 2-6 are performed by the \code{hypre_StructGridStructMatrix}
|
|
component for B, however.
|
|
When B is returned from the component, it is ready to be used. In this case,
|
|
the returned object is of type
|
|
\code{hypre_StructMatrix}, and by looking up its headers we see that it supports the
|
|
matrix-vector multiplication
|
|
use. It can also be passed as a parameter into various solvers such as
|
|
\code{hypre_StructSMG} and
|
|
\code{hypre_StructPFMG}.
|
|
|
|
|
|
\section{User-defined components (experts only)}
|
|
|
|
%==========================================================================
|
|
\section{A Glimpse at Future Directions (this needs work)}
|
|
|
|
In the future, there will be an interface to \hypre{} based on
|
|
a project at LLNL called ``Babel''.
|
|
Babel is based on the concept of a Scientific Interface
|
|
Definition Language (SIDL).
|
|
The new interface will offer two major features:
|
|
automatic support for different language bindings, including
|
|
C, Fortran, C++, and in the future, potentially Java, scripting
|
|
languages, Corba/COM, etc.
|
|
The second benefit is that the new interface will provide an
|
|
object oriented model for \hypre{}, even though the bulk of
|
|
the library is written in C.
|
|
|
|
it is important to note that the new interface will not replace
|
|
the current interface but rather will be an additional interface.
|
|
Existing users should not be affected by the addition of the new
|
|
interface.
|
|
|
|
This chapter is for those users with OO experience, who are interested
|
|
in the object model that will be present in this view of \hypre{}.
|
|
|
|
{\bf Status:} As of February 29, 200, the interfaces to the structured
|
|
grid matrices and solvers are completed and are in testing, and implementation
|
|
of solver interfaces is underway.
|
|
|
|
As of January 31, 2000, this interface to \hypre{}
|
|
is underway, with structured grids (matrices and solvers) under
|
|
beta development.
|
|
|
|
|
|
\subsection{Operators: Matrices, Solvers and Preconditioners}
|
|
|
|
I'm going to rewrite this whole section someday... Andy, 3/1 2000.
|
|
|
|
The defining characteristics of Operators are that they take a vector as input
|
|
and return another vector as
|
|
output. Thus, this generalization also includes matrices, solvers, and preconditioners,
|
|
as well as nonlinear
|
|
operators, transpose matrices,
|
|
and other less common operators.
|
|
The fundamental use function of an Operator is the \code{Apply} function, which
|
|
is the function that transforms an input vector to an output vector.
|
|
For traditional matrices, the \code{Apply} function corresponds to
|
|
matrix-vector multiplication. For
|
|
solvers, \code{Apply} is the traditional solve function.
|
|
Preconditioners are traditionally
|
|
referred to as being applied,
|
|
and the \code{Apply} function quite naturally corresponds to this functionality.
|
|
|
|
Operators also have auxiliary uses that are available for more
|
|
expert users.
|
|
Operators can be multiplied or composed to form a new operator.
|
|
For example, given Operator A and Operator B, the result of composing
|
|
them is an operator C whose action on a vector x is equivalent to
|
|
applying B to x, and then applying A to the result, i.e. C(x)=A(B(x)).
|
|
All Operators support the Compose function.
|
|
Most support it simply by chaining \code{Apply} functions together according
|
|
to the definition, though it is possible that the equivalent of matrix
|
|
multiplication is implemented in certain cases.
|
|
|
|
The converse of the composition function for Operators is the Split function.
|
|
Only the Operators that also support the OperatorSplittable interface support
|
|
this function.
|
|
The Split function returns two Operators whose composition is equal to
|
|
the original Operator.
|
|
Typically, Operators that are formed through a Compose function are Splittable.
|
|
The other major use of Splittable Operators is for preconditioners that can be
|
|
used as split preconditioners, such as incomplete factorization methods and
|
|
factorized sparse approximate inverse functions.
|
|
|
|
\subsection{Preconditioners are Solvers}
|
|
|
|
Users of \hypre{} will quickly notice that there is
|
|
no class or interface called
|
|
preconditioner. Instead, we have chosen to interpret preconditioners as
|
|
solvers, based on the fact that
|
|
both traditional solvers and preconditioners are in some senses approximations
|
|
to the inverse of the linear
|
|
operator defining the system to be solved. The difference between them is one
|
|
of degrees more than a fundamental distinction, in that
|
|
preconditioners generally are very crude and very easy to compute
|
|
approximations to the inverse, while
|
|
solvers are more accurate, i.e. within the desired convergence tolerance. By
|
|
treating solvers and
|
|
preconditioners uniformly within \hypre{}, we make their use uniform for users and
|
|
reduce the complexity
|
|
of \hypre{}'s object model. We also enable novel algorithmic possibilities such as
|
|
using what are
|
|
traditionally solvers as preconditioners. In fact, since Operators include
|
|
solvers and matrices, it is
|
|
straightforward to use matrices as preconditioners, should a direct
|
|
approximation to the inverse exist.
|
|
|
|
\subsection{Setup of Solvers}
|
|
|
|
Most components that correspond to the traditional matrix, as well as
|
|
some solvers in which the
|
|
construction of the underlying matrix is handled by the solver for the user,
|
|
are set up through one or more
|
|
of the conceptual interfaces discussed in a later chapter. Here we will
|
|
concentrate on other interfaces for
|
|
constructing Operators.
|
|
|
|
\subsubsection{The SolverBuilder interface}
|
|
|
|
The main function in the SolverBuilder interface
|
|
|
|
|
|
Various Krylov solvers are included in \hypre{}.
|
|
Such methods are very flexible in that they can be used to attempt
|
|
the solution of any system defined by any component that supports the
|
|
Operator interface.
|
|
|