Вы находитесь на странице: 1из 18

Fortran 90: An Entry to Object-Oriented

Programming for the Solution of Partial


Differential Equations
L. MACHIELS and M.O. DEVILLE
LMF, DGM, Swiss Federal Institute of Technology, Lausanne

The aim of this work is to set up a programming model suitable for numerical computing while
taking benefit of Fortran 90’s features. The use of concepts from object-oriented programming
avoids the weaknesses of the traditional global data programming model of Fortran 77. This
work supports the view that object-oriented concepts are not in contradiction with good
Fortran 77 programming practices but complement them. These concepts can be embodied in a
module-based programming style and result in an efficient and easy-to-maintain code (main-
tainability means code clarity, scope for further enhancements, and ease of debugging). After
introducing the terminology associated with object-oriented programming, it is shown how
these concepts are implemented in the framework of Fortran 90. Then, we present an
object-oriented implementation of a spectral element solver for a Poisson equation.
Categories and Subject Descriptors: D.1.5 [Programming Techniques]: Object-Oriented
Programming; D.3.2 [Programming Languages]: Language Classifications—Fortran 90;
G.1.8 [Numerical Analysis]: Partial Differential Equations
General Terms: Languages
Additional Key Words and Phrases: Spectral element method

1. INTRODUCTION
During the past decades, many computer programs have been written for
solving systems of partial differential equations for different types of
applications in engineering. Nowadays, due to a combination of improved
algorithms and faster computers, the range of problems that can be solved
by computers has increased dramatically. Consequently, industrial codes
have become intricate and hard to maintain, in the attempt to be as flexible
as possible regarding the problems they solve, the methods they propose,
and the geometrical configurations they treat. In this article, we identify

The work of L. Machiels was partially supported by the Swiss National Foundation for
Scientific Research.
Authors’ address: LMF, DGM, Swiss Federal Institute of Technology, Ch-1015 Lausanne,
Switzerland; email; {machiels; deville}@dgm.epfl.ch.
Permission to make digital / hard copy of part or all of this work for personal or classroom use
is granted without fee provided that the copies are not made or distributed for profit or
commercial advantage, the copyright notice, the title of the publication, and its date appear,
and notice is given that copying is by permission of the ACM, Inc. To copy otherwise, to
republish, to post on servers, or to redistribute to lists, requires prior specific permission
and / or a fee.
© 1997 ACM 0098-3500/97/0300 –0032 $03.50

ACM Transactions on Mathematical Software, Vol. 23, No. 1, March 1997, Pages 32–49.
Fortran 90: An Entry to Object-Oriented Programming • 33

the main drawback of these codes to be their global data representation


model. We propose to address this drawback by extending the practices of
Fortran 77 programmers with a set of concepts borrowed from object-
oriented programming.
Basically, writing a computational code consists in translating an algo-
rithm into groups of statements called procedures (for the sake of simplic-
ity, the term procedure will be used to denote either a function or a
subroutine). The data set handled by the program can be viewed as a
logical flow going through the statements in a fixed order. In the mind of a
numerical analyst this sequential nature of the code seems inevitable. More
precisely, the Fortran 77 programmer usually begins by choosing a global
data structure (a set of arrays) and then he or she writes, tests, and
progressively assembles the procedures. This programming style will be
subsequently referred to as the global data model. If some design rules are
followed [Metcalf 1985], the debugging is facilitated. The difficulties appear
when a more complex data set is required: small changes can lead to
extensive code revision, since data types have to be identically defined in
all procedures. Furthermore, modifications are invariably followed by a
fastidious series of checks in order to verify if the sequence of statements is
preserved.
The object-oriented model relies on a decomposition of the problem in
autonomous data pieces with which some operations are associated. It is
based on representations and manipulations of data that are not familiar to
the Fortran 77 programmer. The number of concepts is small, and they will
be defined in the next section with the terminology featured in Goldberg
and Robson’s book about the SMALLTALK language [Goldberg and Robson
1983].
One of Fortran 77’s major strengths are the libraries of carefully imple-
mented classical algorithms which have accumulated over the years. The
impossibility of replacing or rewriting these libraries, coupled with the
commonly held belief that object-oriented programming is the opposite of
the procedural modularity of Fortran 77, has hindered object-oriented
programming in scientific computing. Our work shows the fallacy of that
view by making evident that object-oriented programming provides nothing
more than a set of new concepts that enhances the traditional good
programming practices of Fortran 77. In our approach, the calls to library
subroutines are naturally included in the methods associated to an object.
Moreover, in this work we show that the Fortran 90 standard supports
most of the object-oriented concepts. Since the standard includes Fortran
77, it was the natural choice of programming language for our purpose. An
example is provided with a spectral element solver for a Poisson equation
in one and two dimensions. An object-oriented approach was used by
Dubois-Pèlerin et al. in a similar context (for a finite-element code) with
other languages: SMALLTALK and C11 [Dubois-Pèlerin and Zimmerman
1993; Dubois-Pèlerin et al. 1992; Zimmerman et al. 1992]. This last work
shows how object-oriented programming leads to enhanced modularity,
ACM Transactions on Mathematical Software, Vol. 23, No. 1, March 1997.
34 • L. Machiels and M. O. Deville

which helps to produce extendible, easy-to-maintain, and reusable soft-


ware.
Section 2 introduces the basic material required to understand the
object-oriented programming paradigm. In Section 3, we present two im-
portant pieces of code design provided by Fortran 90: the module and the
interface block. And we show how these features can be exploited to
implement object-oriented concepts. Section 4 gives a presentation of the
spectral element method, and within Section 5, we describe an implemen-
tation of the algorithm. Finally, in Section 6 some concluding remarks are
drawn.

2. BASIC CONCEPTS
The aim of this section is to define the concepts of object-oriented program-
ming. The reader may find a more detailed discussion in Goldberg and
Robson [1983].
Object-oriented programming focuses on information management. An
object consists of some private memory and some operations, i.e., a set of
variables (also referred to as the set of attributes) and an associated set of
operations called the set of methods. An object can be a number or a
character and the associated operations, but it can also be a more complex
entity, such as a Poisson problem for instance. The information contained
by the attributes at a given time is called the state of the object. The
nature of a method depends on the type of variables it acts on (e.g., objects
representing numbers compute arithmetic functions). One can imagine that
the Poisson object defines and solves the Poisson equation on a specific
domain. The methods are accompanied by messages. A message is a
request for an object to carry out one or more of its operations. The set of
messages to which an object can respond is called its protocol (or inter-
face). A crucial property of messages is that they are the only way to act on
the content of an object. A message specifies the desired operation, but it
does not say anything about how this operation should be performed. This
mechanism provides a complete modularity in the sense that the attributes
of an object can only be modified by the methods of the object itself. The
methods and the attributes are the object’s private entities, i.e., they are
hidden from the outside world.
Another useful concept is that of class. A class defines a kind of object.
The individual objects belonging to the same class are called instances of
that class. More precisely a class consists of
(1) the definition of the attributes;
(2) a set of methods which are the procedures implementing the operations
applicable to the attributes contained in an object of the class;
(3) a set of messages (the protocol)— each message initiates a specific
method of the class.
Each class contains at least two methods called constructor and destructor,
respectively. The purpose of the constructor is to initialize an object. The
ACM Transactions on Mathematical Software, Vol. 23, No. 1, March 1997.
Fortran 90: An Entry to Object-Oriented Programming • 35

destructor frees the memory occupied by an object. An important part of


object-oriented program design is to determine which classes should be
defined and which messages provide a useful vocabulary of interaction
among the objects of these classes. Finally, each class can be fully tested
independently; therefore we avoid a lot of programming errors.
In order to clarify the previous concepts, we give the example of the
Poisson problem object mentioned earlier. The definition of Poisson_class
can be stated as follows:

(1) At least four attributes are required: the computational domain, a


matrix representing the discretized Poisson operator, the right-hand
side, and the solution.
(2) The identification of the program tasks gives the methods. We have a
method that allows the user to define the equation on a given domain,
say new_Poisson. A method for deallocating the memory reserved for
the object: dealloc_Poisson. One finds also build_Poisson which assem-
bles the Poisson operator on the defined domain, factorize_Poisson
which factorizes the Poisson matrix, and solve_Poisson which returns
the solution.
(3) The protocol contains three messages: new_Poisson, dealloc_Poisson,
and solve_Poisson.

Another concept to be introduced is the one related to the capability for


objects of different classes to respond to exactly the same protocol. This is
referred to as polymorphism. It enables us to send the same message to
objects of different classes, and therefore it leads to a more uniform
treatment of these objects. To illustrate polymorphism we extend the
example of the previous paragraph defining a new class for solving Helm-
holtz equations instead of Poisson equations: the Helmholtz_class. The
design of this new class is close to the one of Poisson_class. Its methods are
new_Helmholtz, build_Helmholtz, factorize_Helmholtz, and solve_Helmholtz.
The generality of the program is enhanced if solve_Poisson and solve_
Helmholtz could be called by the same message, say solve. In the program,
wherever this message is found, one of the two methods is automatically
chosen according to the object type.
One of the major drawbacks of the global-data model is the sequential
nature of the code: each of the procedures is expected to be called at a given
foreseeable instant during the execution. When the code is modified for a
particular reason, the programmer has to ensure that the order of the
instruction sequence is preserved. This rapidly becomes a critical issue
when the code has thousands of lines dispersed over several procedures: we
often lose the control on the state of the data set, since procedures may act
on variables that are not present in the calling sequence. The nonantici-
pation principle addresses this problem [Dubois-Pèlerin 1992]. This
principle states that a method, when applied to a given object, should not
depend on whether or not a particular task has already been performed. It
means that no assumption about the previous state of the object should be
ACM Transactions on Mathematical Software, Vol. 23, No. 1, March 1997.
36 • L. Machiels and M. O. Deville

made when sending a message and that each method is responsible for
bringing the object into the correct state before performing any operation.
Note that this programming principle does not belong exclusively to object-
oriented programming; the implementation of many Fortran 77 libraries
relies on a similar concept.
The following example shows practical consequences of such a principle
for programmers and users of modules for solving partial differential
equations. For such problems, the sequence of operations is as follows:
assemble the matrix for a given computational domain; factorize it; and
compute the solution of the linear system. If a nonanticipation discipline is
followed, the user does not have to pay any attention to the state of the
linear system: when the message “solve the problem” is sent the method is
responsible for checking if the matrix is already assembled and factorized;
if not, the method itself sends a message to assemble and factorize the
matrix and only afterward is the solution computed and returned.
The last concept usually introduced in the context of object-oriented
programming is the so-called class hierarchy. We will introduce here the
concept of simple inheritance which is the simplest case of class hierar-
chy. Simple inheritance consists of reusing a class (or parts of a class) to
define enriched classes where attributes and/or methods are added. This
concept allows a very economic use of code lines, e.g., one can associate the
classes that have attributes or methods in common in a general class. Then
the definitions of the classes are achieved by the enrichment of the general
class with particular attributes and methods.
In summary, the class concept provides a complete encapsulation of the
data structure. Moreover, the nonanticipation principle helps to produce
operations that are independent of any execution context. It means that the
objects are autonomous in both “space” (organization of variables) and
“time” (sequence of operations).

3. MODULES AND INTERFACE BLOCKS


In the present section, we show that the previous concepts (except class
hierarchy) are naturally implemented in module-based Fortran 90 pro-
grams.
The module is an important enhancement of Fortran 90 [Adams et al.
1992; Kerrigan 1993; Metcalf and Reid 1992]. This new program unit
allows us to encapsulate definitions of types and variables with their
associated procedures. Another innovation of Fortran 90 is the interface
block. Interface blocks, among other features, can define a generic name
for a set of procedures. A third important addition is the private/public
statements, which give the ability to specify some entities (variables,
constants, type definitions, procedures) to be hidden from outside of the
module.
In the light of the previous section, we notice that we are very close to the
object-oriented concepts introduced. The classes are defined by the means
of modules; the set of attributes is defined in a derived type; the protocol is
ACM Transactions on Mathematical Software, Vol. 23, No. 1, March 1997.
Fortran 90: An Entry to Object-Oriented Programming • 37

given by listing its procedures in a public statement; and the polymorphism


is achieved using generic interfaces.
The following piece of code shows the definition of the Poisson_class in a
pseudolanguage close to Fortran 90.
MODULE Poisson_class
PRIVATE
PUBLIC:: new_Poisson, dealloc_Poisson, solve_Poisson
TYPE, PUBLIC:: Poisson
PRIVATE
TYPE(domain) :: dom
TYPE(matrix) :: mtx
TYPE(right_hand_side) :: rhs
TYPE(solution) :: sol
END TYPE
CONTAINS
FUNCTION new_Poisson(arguments) RESULT(p)
TYPE(Poisson) :: p
statements
END FUNCTION new_Poisson
SUBROUTINE dealloc_Poisson(p)
TYPE(Poisson) :: p
statements
END SUBROUTINE dealloc_Poisson
FUNCTION solve_Poisson(p) RESULT(s)
TYPE(Poisson) :: p
TYPE(solution) :: s
statements
END FUNCTION solve_Poisson
SUBROUTINE assemble_Poisson(p)
TYPE(Poisson) :: p
statements
END SUBROUTINE assemble_Poisson
SUBROUTINE factorize_Poisson(p)
TYPE(Poisson) :: p
statements
END SUBROUTINE factorize_Poisson
END MODULE Poisson_class

The public/private statements specify the protocol. The declarations of


the attributes are embodied in a type definition. The private statement in
the derived type definition guarantees that attributes are hidden from
outside of the module. The remainder of the module is devoted to method
coding. The purpose of new_Poisson is to initialize and return an object of
the class. It is important to notice that an object is not considered to be
ACM Transactions on Mathematical Software, Vol. 23, No. 1, March 1997.
38 • L. Machiels and M. O. Deville

defined until the initialization function new_Poisson has been called. This
function takes the role of the C11 constructor. Destruction is achieved by
the dealloc_Poisson subroutine. Poisson_class objects can be defined in
program units that have a use Poisson_class statement.
Now suppose that a second class is added: the Helmholtz_class with an
implementation similar to Poisson_class. As noticed in the previous sec-
tion, in order to improve the generality of the code, polymorphism should be
used. More precisely, one would like to refer to the two distinct procedures
solve_Poisson and solve_Helmholtz through a generic name, say solve.
Fortran 90 allows us to define separately the two classes and to join them
with an interface block as shown in the following program segment:
INTERFACE solve
MODULE PROCEDURE solve_Poisson, solve_Helmholtz
END INTERFACE
Sending the message solve of the generic protocol to a given object auto-
matically initiates the correct method.
The only drawback of using this technique within the framework of
Fortran 90 is the lack of an explicit mechanism to implement class
hierarchy, which is a crucial property of an object-oriented approach with
respect to the level of abstraction it provides. From a pragmatic point of
view, complete modularity means that while composing a particular appli-
cation it is possible to use previously developed code with no knowledge of
implementation details. In that respect, we give the classical example of a
situation for which hierarchy may help: when classes share a common set
of attributes or methods, e.g., different kinds of iterative solvers may share
attributes (the number of degrees of freedom, the right-hand side, the
solution) and methods (computation of preconditioners, . . .). In that case,
the hierarchy mechanism allows us to define a virtual class so that the
general structure of the class is written only once and so that the particular
classes are defined by enrichments of this virtual class. For this example
the USE ONLY statement might be helpful to a certain extent, since it
allows the use of a subset of a module in the definition of a new module.
But it does not provide the flexibility to add attributes in the definition of
an object and therefore cannot be interpreted as an inheritance mechanism.
Finally, noting that object-oriented programming is based on complete
data encapsulation provides interesting prospects, since this is a desirable
feature for efficient implementation of algorithms on parallel computers
with distributed memory. For example, if we consider domain decomposi-
tion techniques, the information about the domain can be carefully divided
and stored in different objects. If these objects are distributed on different
processors, object-oriented programming gives a new and elegant way
implementing parallel algorithms.

4. THE SPECTRAL ELEMENT METHOD


The spectral element method [Maday and Patera 1989] is a weighted
residual technique for the solution of differential equations that combines
ACM Transactions on Mathematical Software, Vol. 23, No. 1, March 1997.
Fortran 90: An Entry to Object-Oriented Programming • 39

the geometric flexibility of low-order finite-element methods with the


convergence rate of spectral methods.
For a spectral element discretization, the computational domain is di-
vided into macroelements, and within each of these subdomains the solu-
tion is approximated by a high-order polynomial interpolant. A Galerkin
variational projection provides the discrete equations. Convergence may be
achieved by increasing the degree N of the polynomial approximation while
the number of elements K remains fixed.
We consider here the two-dimensional Poisson equation on a rectilinear
domain V with homogeneous Dirichlet boundary conditions on the domain
boundary ­V,

2¹ 2u 5 f in V, (1)

u50 on ­V. (2)

The variational statement equivalent to (1, 2) is

find u [ H 10~ V ! such that E V


¹u z ¹v 5 EV
fv v [ H 10~ V ! . (3)

H 10 (V) is the Sobolev space of functions and their first-order derivatives


which are square integrable and vanish on the boundary.
The spectral element discretization proceeds by breaking up the domain
V into K rectilinear elements,
K

# 5
V ø # k,
V V k 5 # a k , a9k@ 3 # b k ,b9k@ ,
k51

such that the intersection of two adjacent elements is either a whole edge
or a vertex. We require that the variational statement (3) be satisfied for a
polynomial subspace of H 10 . We define the space

P N,K~ V ! 5 $ f [ L 2~ V ! ; f uVk [ P N~ V k! , k 5 1, . . . , K % ,

where P N (V k ) denotes the tensor-product space of all polynomials of degree


less than or equal to N in each space variable. The spectral element space
X h consists of

X h 5 H 10~ V ! ù P N, K~ V ! .

The discrete problem is given by

find u h [ X h such that EV


¹u h z ¹v h 5 E
V
fv h v h [ X h.

ACM Transactions on Mathematical Software, Vol. 23, No. 1, March 1997.


40 • L. Machiels and M. O. Deville

Finally, the discrete equations are obtained by using the Gauss-Lobatto-


Legendre quadrature rule to compute the integrals of the discrete problem.
We get the following linear system

Au 5 Bf,

where A and B are respectively the stiffness and the mass matrix. In the
sequel of the presentation and in the code these matrices will be denoted by
a and b.

5. IMPLEMENTATION OF A POISSON EQUATION SOLVER


A key issue in object-oriented analysis is the choice of a good set of classes
in order to split the problem while keeping sufficient generality. This choice
is mostly a matter of practice although some guidelines can be followed
[Stroustrup 1991]. In the code given below, the classes are implemented
more with a concern of clarity than efficiency. We will give the global
structure of the program, a short presentation of each class, and a detailed
description of the module implementing one of these classes (namely the
grid_class).1

5.1 Global Description of the Program


The code is composed of two simple tool modules (used_precision and
mat_operations), five other modules implementing a class (grid_class, el-
ement_class, assembling_class, poisson_1d_class, poisson_2d_class), a
main program, and some Fortran 77 subroutines.
The module used_precision gives a good illustration of the use of portable
mechanisms to set up computation accuracy. This module is written around
the selected_real_kind intrinsic function and is used in all program units so
that a specified numerical accuracy can be chosen regardless of the proces-
sor characteristics. Some useful constants are defined as well:
MODULE used_precision ! This module specifies the numerical model
IMPLICIT NONE
INTEGER, PARAMETER :: float 5 selected_real_kind(13,99)
REAL(float), PARAMETER :: zero 5 0.0_float
REAL(float), PARAMETER :: one 5 1.0_float
REAL(float), PARAMETER :: two 5 2.0_float
REAL(float), PARAMETER :: pi 5 3.141592653589793_float
END MODULE used_precision

Within the module mat_operations, useful matrix operations (e.g., the


tensor product) are implemented, and new and overloaded operators are
defined using interface blocks.

1
The program is available through the following anonymous FTP address: ftp.epfl.ch in the
directory pub/doc/semo. The files do not include the Specmit and Lapack libraries, which can
be obtained by other means.

ACM Transactions on Mathematical Software, Vol. 23, No. 1, March 1997.


Fortran 90: An Entry to Object-Oriented Programming • 41

The classes are arranged such that the Poisson operator matrix is built
progressively. A grid_class method computes the operator matrix on the
canonical element (the canonical element is the specific element where the
interpolant polynomials are defined on ] 2 1,1[). Within element_class this
operator matrix is generalized for the size of each element. Then the
different elemental contributions are assembled on the computational
domain calling a method of the assembling_class. Finally, the one-dimen-
sional Poisson problem (two-dimensional Poisson problem) is defined and
solved in the poisson_1d_class method (poisson_2d_class method, respec-
tively).
The foundation of the numerical method lies in the definition of the
interpolation basis and the integration rules. In the program, this informa-
tion is stored in a grid type object. More precisely, such an object contains
the integration nodes and weights as well as the evaluation at these nodes
of the Lagrangian interpolant derivatives. This object also builds a discrete
Poisson operator on the canonical element. The main tasks of grid are to
compute and to return (nodes and weights) vectors and (derivative and
Poisson) matrices if need be. The implementation of this class is given in
the Appendix.
An element type object is of geometrical nature. It contains the location
and the size of an element, the number of nodes, the coordinates of the
nodes, and the grid defined on this element:
TYPE, PUBLIC :: element
PRIVATE
REAL(float) l ! left boundary
REAL(float) es ! size of the element
INTEGER n ! number of nodes
REAL(float), dimension(:), pointer :: x ! coordinates of nodes
TYPE(grid) :: g
END TYPE element

Its main task is to build the local Poisson operator which is merely the
canonical element matrix times a geometrical scale factor. The element_
class is designed in a way that new operator matrices can be easily
generated to solve new problems by adding new computational methods. In
the present version of the program only the Poisson operator matrix is
available through the call to the function give_matrix_e, but other operator
matrices can be easily added:
FUNCTION give_matrix_e(e,which) RESULT(mat)
TYPE(element), INTENT(in) :: e
CHARACTER(LEN5p) which
REAL(float), DIMENSION(e%n,e%n) :: mat
SELECT CASE (which)
CASE(’poisson’)
mat 5 give_matrix_g(e%g, ’poisson’)ptwo/e%es

ACM Transactions on Mathematical Software, Vol. 23, No. 1, March 1997.


42 • L. Machiels and M. O. Deville

END SELECT
END FUNCTION give_matrix_e

The function give_matrix_g is part of the protocol of grid_class and returns


operators defined on the canonical element.
The assembling_class is a more difficult case. An object of this class
consists basically of the domain on which the computation is to be per-
formed. Its attributes are a set of elements and the information about the
interconnection between these elements. To understand how this informa-
tion is stored, note that each discretization node can be numbered locally at
the elemental level or globally as a node inside the full computational
domain. The connection between elements is given by the mapping between
this local and global node numbering. The mapping is represented by a
two-dimensional array called index (index(i, j) holds the global index of the
node i of element j). The assembling type is defined by
TYPE, PUBLIC :: assembling
PRIVATE
INTEGER n_el ! number of elements
INTEGER :: n ! number of global variables
INTEGER, DIMENSION(:,:), POINTER :: index
TYPE(element), DIMENSION(:), POINTER :: el ! set of elements
END TYPE assembling

The assembling_class messages return vectors and discretized operators


assembled on the computational domain. For instance, there is a function
called assemble_matrix_a which returns operators on a set of elements. This
function corresponds to give_matrix_e (respectively, give_matrix_g) which
gives the Poisson operator at the elemental level (respectively, grid level):
FUNCTION assemble_matrix_a(a,which) RESULT(mat)
TYPE(assembling),INTENT(IN) :: a
CHARACTER(LEN5p),INTENT(IN) :: which
REAL(float),DIMENSION(a%n,a%n) :: mat
REAL(float),DIMENSION(:,:),ALLOCATABLE :: m
INTEGER n,i,j,iel
mat 5 zero
n 5 give_n_e(a%el(1))
ALLOCATE(m(n,n))
DO iel51,a%n_el ! assembling over the elements
m 5 give_matrix_e(a%el(iel),which)
DO j51,n
DO i51,n
mat(a%index(i,iel),a%index(j,iel)) 5 &
& mat(a%index(i,iel),a%index(j,iel)) 1 &
& m(i,j)
ENDDO

ACM Transactions on Mathematical Software, Vol. 23, No. 1, March 1997.


Fortran 90: An Entry to Object-Oriented Programming • 43

ENDDO
ENDDO
END FUNCTION assemble_matrix_a

A poisson_1d_class object factorizes the matrix and sends back the


solution of the equation. The main attributes of the class are the Poisson
matrix, the right-hand side, and an assembling object (or domain):
TYPE, PUBLIC :: poisson_1d
PRIVATE
REAL(float),DIMENSION(:,:),POINTER :: a
! a stands for the Poisson operator
REAL(float),DIMENSION(:),POINTER :: f,b
! f is the right-hand side
! b is the diagonal mass matrix
TYPE(assembling) :: assble
END TYPE poisson_1d

For the one-dimensional problem, the matrix to be inverted is the one given
by the assembling object, whereas for the two-dimensional problem, two
assembling objects are required (one in each direction), and the full matrix
is obtained by means of tensor products:
SUBROUTINE assemble_abf_2d(p)
TYPE(poisson_2d) :: p
INTEGER nx,ny,info
REAL(float),DIMENSION(:),ALLOCATABLE :: bx,by,xx,xy
! bx and by are the mass matrices in each direction
! xx and xy are the coordinates of the nodes in the domain
REAL(float),DIMENSION(:,:),ALLOCATABLE :: ax,ay
! ax and ay are the Poisson matrices in each direction
nx 5 give_n_a(p%assble_x)
ny 5 give_n_a(p%assble_y)
ALLOCATE(ax(nx,nx),bx(nx))
ax 5 assemble_matrix(p%assble_x,’poisson’)
bx 5 assemble_vector(p%assble_x,’b’)
ALLOCATE(ay(ny,ny),by(ny))
ay 5 assemble_matrix(p%assble_y,’poisson’)
by 5 assemble_vector(p%assble_y,’b’)
ALLOCATE(p%a(nxpny,nxpny),p%b(nxpny))
p%a 5 (by.TEP.ax) 1 (ay.TEP.bx)
! .TEP. stands for the tensor product
p%b 5 by.TEP.bx
ALLOCATE(xx(nx),xy(ny))
ALLOCATE(p%f(nxpny))
xx 5 give_vector_a(p%assble_x,’x’)

ACM Transactions on Mathematical Software, Vol. 23, No. 1, March 1997.


44 • L. Machiels and M. O. Deville

xy 5 give_vector_a(p%assble_y,’x’)
p%f 5 p%bprhs2(xx,xy,nx,ny)
CALL boundary2(p%a,p%f,nx,ny) ! boundary conditions
CALL spotrf(’u’,nxpny,p%a,nxpny,info) ! matrix factorization
PRINT p,’info spotrf ’,info
END SUBROUTINE assemble_abf_2d

The first part of this function builds the Poisson matrix and the mass
matrix for a two-dimensional problem; then the right-hand side is com-
puted, and the Poisson matrix is factorized using the spotrf subroutine from
the LAPACK package.
Some additional subroutines give the right-hand side of the linear system
and impose boundary conditions. The main program is formed by the
definition and initialization of a Poisson object and a call to its solving
procedure.
The program contains four calls to external Fortran 77 subroutines. The
subroutines zwgljd and dgljd2 in the grid_class compute the integration
nodes, the integration weights, and the derivatives of Lagrangian interpol-
ants. The Cholesky factorization and the substitution performed in the
Poisson_class are achieved by calls to spotrf and spotrs from the LAPACK
library.

5.2 Implementation of grid_class


grid_class merits detailed description, since it exemplifies the concepts we
wish to emphasize (the full listing of this class is given in the Appendix).
An important topic of this section will be to illustrate the nonanticipation
principle.
Inside a module, the contains statement separates the definitions of the
attributes and the protocol from the implementation of the methods. The
module used_precision specifies the numerical model by the means of the
float constant which indicates the floating-point arithmetic chosen for the
computations. The protocol contains the initialization function (new_grid),
the deallocation subroutine (dealloc_grid), and the functions by which some
information is returned (give_n_g, give_vector_g, give_matrix_g). The com-
putation procedures are private. The definitions of the attributes are
embedded in a type definition. The meaning of the private statement inside
the definition is that only procedures of the module are allowed to access
the content of a variable of this type. The integration nodes and weights are
stored in the arrays x and w; d is the derivative matrix of Lagrangian
interpolants; dt is its transpose; and a is the canonical element Poisson
operator.
It is useful to distinguish between three kinds of methods: the construc-
tor-destructor, the private, and the public methods. The methods of the
first type are new_grid and dealloc_grid. The function new_grid initializes a

2
The routines are included in the Specmit library at MIT. Information about this library can
be obtained from Einar Ronquist (er@simeon.mit.edu).

ACM Transactions on Mathematical Software, Vol. 23, No. 1, March 1997.


Fortran 90: An Entry to Object-Oriented Programming • 45

grid by fixing its number of nodes n. If n is not given in the calling


statement, the function will ask the user interactively. Afterward, the
arrays are nullified, i.e., they are defined but not associated. The purpose of
dealloc_grid is to free the memory space occupied by the arrays when the
information is no longer needed. The private methods are mainly computa-
tional routines. We find the ones which allocate and compute the arrays x,
b, d, and dt: compute_xb and compute_ddt (these computations are per-
formed by calls to the Fortran 77 Specmit library already mentioned). The
subroutine compute_a builds up the Poisson operator. For the last kind of
methods, we have give_vector_g, matrix_g, and give_n_g which return,
respectively, vectors, matrices, and the number of nodes of the grid.
Going through the subroutines give_vector_g and give_matrix_g provides
the opportunity to illustrate an application of the nonanticipation principle.
When a call to any of these subroutines occurs, the only requirement is that
the grid is initialized (i.e., the new_grid function has been called). There is
no assumption about what has already been computed. This explains the
tests which verify if the needed vector or matrix is associated or not. In
case of no association, the memory space is allocated and filled in by calling
a computational subroutine. This nonanticipation principle allows the user
to keep in mind a minimum of information: if the initialization function has
already been processed, any message can be sent without any further
assumption.
Before closing this section, we note that a constructor should be applied
automatically when an object is created, but Fortran 90 does not provide
any mechanism to implement “automatic” methods. In Fortran 95, it is
permissible to define initial values for components of types: e.g.,
TYPE grid
PRIVATE
INTEGER :: n50 ! number of nodes
REAL(float),DIMENSION(:),POINTER :: z5.null( ) ! nodes
END TYPE grid

Using that feature, it is easy to test, in a method, if the object has been
properly initialized. For example, one can test the value of n, and if it is
equal to zero the function new_grid is called.

6. CONCLUDING REMARKS
In this article, we explored the Fortran 90 standard in order to show how
some of its features can improve code design for solving partial differential
equations. It is shown that modules, interface blocks, and derived data
types allow us to adopt a programming style that is very close to object-
oriented programming where a high level of abstraction and ease of code
maintainability are achieved.
Treating the example of a Poisson problem solver, we have observed that
implementing object-oriented concepts within Fortran 90 requires us to set
up a precise programming model and to follow a rigorous discipline. Special
ACM Transactions on Mathematical Software, Vol. 23, No. 1, March 1997.
46 • L. Machiels and M. O. Deville

attention has been devoted to the nonanticipation principle, which is as


important as data abstraction in order to build autonomous objects.
Finally, a significant advantage of Fortran 90 is that it is built up on top
of Fortran 77, which is entirely supported by the new standard. So a
programmer can plan a smooth transition toward object-oriented program-
ming by a progressive rewriting while keeping Fortran 77 routines as
implementation of methods in class definitions.

APPENDIX

MODULE grid_class ! Spectral primitives


! for a Gauss-Lobatto-Legendre grid
USE used_precision
IMPLICIT NONE
!
PUBLIC :: new_grid,dealloc_grid
PUBLIC :: give_n_g,give_vector_g,give_matrix_g
PRIVATE :: compute_xb,compute_ddt, compute_a
!
TYPE, PUBLIC :: grid
PRIVATE
INTEGER n ! number of nodes
REAL(float),DIMENSION(:),POINTER :: x ! nodes
REAL(float),DIMENSION(:),POINTER :: b ! weights
REAL(float),DIMENSION(:,:),POINTER :: d ! derivatives
REAL(float),DIMENSION(:,:),POINTER :: dt ! transpose(d)
REAL(float),DIMENSION(:,:),POINTER :: a ! Poisson operator
END TYPE grid
!
CONTAINS
!
FUNCTION new_grid(n) RESULT(g)! initialization
INTEGER,INTENT(IN),OPTIONAL :: n
TYPE(grid) :: g
IF (PRESENT(n)) THEN
g%n5n
ELSE
PRINT p,“New grid”
PRINT p,“--------”
PRINT p,“Number of nodes ?”
READ p, g%n
ENDIF
NULLIFY(g%x,g%b,g%d,g%dt,g%a)
END FUNCTION new_grid

ACM Transactions on Mathematical Software, Vol. 23, No. 1, March 1997.


Fortran 90: An Entry to Object-Oriented Programming • 47

!
SUBROUTINE dealloc_grid(g) ! destruction
TYPE(grid) g
g%n50
IF (ASSOCIATED(g%b)) DEALLOCATE(g%x,g%b)
IF (ASSOCIATED(g%d)) DEALLOCATE(g%d,g%dt)
IF (ASSOCIATED(g%a)) DEALLOCATE(g%a)
END SUBROUTINE dealloc_grid
!
SUBROUTINE compute_xb(g)
! computation of Gauss-Lobatto-Legendre nodes and weights
TYPE(grid) g
INTERFACE
SUBROUTINE zwgljd(z,w,nzd,alpha,beta)
INTEGER nzd
REAL w(nzd),z(nzd),alpha,beta
END SUBROUTINE zwgljd
END INTERFACE
ALLOCATE(g%x(g%n),g%b(g%n))
CALL zwgljd(g%x,g%b,g%n,zero,zero)
END SUBROUTINE compute_xb
!
SUBROUTINE compute_ddt(g)
! computation of Gauss-Lobatto-Legendre derivatives
TYPE(grid) g
INTERFACE
SUBROUTINE dgljd(d,dt,z,nz,nzd,alpha,beta)
INTEGER nz,nzd
REAL d(nzd,nzd),dt(nzd,nzd),z(nzd),alpha,beta
END SUBROUTINE dgljd
END INTERFACE
ALLOCATE(g%d(g%n,g%n),g%dt(g%n,g%n))
CALL dgljd(g%d,g%dt,g%x,g%n,g%n,zero,zero)
END SUBROUTINE compute_ddt
!
SUBROUTINE compute_a(g)
! computation of the Poisson operator
! on a canonical element
TYPE(grid) g
INTEGER n,i,j
IF(.NOT.(ASSOCIATED(g%b))) CALL compute_xb(g)
IF(.NOT.(ASSOCIATED(g%d))) CALL compute_ddt(g)
n 5 g%n

ACM Transactions on Mathematical Software, Vol. 23, No. 1, March 1997.


48 • L. Machiels and M. O. Deville

ALLOCATE(g%a(n,n))
DO j51,n
DO i51,j
g%a(i,j)5 SUM(g%b(:)pg%d(:,j)pg%d(:,i))
ENDDO
ENDDO
SUBROUTINE compute_a
!
FUNCTION give_vector_g(g,which) RESULT(vec)
TYPE(grid) :: g
CHARACTER(LEN5p) which
REAL(float),DIMENSION(g%n) :: vec
SELECT CASE (which)
CASE(’b’)
IF(.NOT.(ASSOCIATED(g%b))) CALL compute_xb(g)
vec 5 g%b
CASE(’x’)
IF(.NOT.(ASSOCIATED(g%x))) CALL compute_xb(g)
vec 5 g%x
END SELECT
END FUNCTION give_vector_g
!
FUNCTION give_matrix_g(g,which) RESULT(mat)
TYPE(grid) :: g
CHARACTER(LEN5p) which
REAL(float),DIMENSION(g%n,g%n) :: mat
SELECT CASE (which)
CASE(’a’)
IF(.NOT.(ASSOCIATED(g%a))) CALL compute_a(g)
mat 5 g%a
CASE(’d’)
IF(.NOT.(ASSOCIATED(g%d))) CALL compute_ddt(g)
mat 5 g%d
CASE(’dt’)
IF(.NOT.(ASSOCIATED(g%dt))) CALL compute_ddt(g)
mat 5 g%dt
END SELECT
END FUNCTION give_matrix_g
!
FUNCTION give_n_g(g)
! give_n_g is the number of nodes
! of the grid
INTEGER give_n_g

ACM Transactions on Mathematical Software, Vol. 23, No. 1, March 1997.


Fortran 90: An Entry to Object-Oriented Programming • 49

TYPE(grid),INTENT(IN) :: g
give_n_g5g%n
END FUNCTION give_n_g
!
END MODULE grid_class

ACKNOWLEDGMENTS
We wish to thank J.K. Reid and B. Einarsson for their comments and for
running the code on several test cases.

REFERENCES

ADAMS, J. C., BRAINERD, W. S., MARTIN, J. T., SMITH, B. T., AND WAGENER, J. L. 1992.
Fortran 90 Handbook. McGraw-Hill, New York.
DUBOIS-P`ELERIN, Y. 1992. Object-oriented finite elements: Programming concepts and im-
plementation. Ph.D. thesis, Swiss Federal Inst. of Technology, Lausanne, Switzerland.
DUBOIS-P`ELERIN, Y. AND ZIMMERMANN, T. 1993. Object-Oriented Finite Element Program-
ming: III. An Efficient Implementation in C11. Computer Methods in Applied Mechanics
and Engineering, vol. 108. Elsevier Science, Amsterdam, 165–183.
DUBOIS-P`ELERIN, Y., ZIMMERMANN, T., AND BOMME, P. 1992. Object-Oriented Finite Element
Programming: II. A Prototype Program in Smalltalk. Computer Methods in Applied Mechan-
ics and Engineering, vol. 98. Elsevier Science, Amsterdam, 361–397.
GOLDBERG, A. AND ROBSON, D. 1983. SMALLTALK-80. The Language and Its Implementa-
tion. Addison-Wesley, Reading, Mass.
KERRIGAN, J. 1993. Migrating to Fortran 90. O’Reilly and Assoc., Sebastopol, Calif.
MADAY, Y. AND PATERA, A. T. 1989. Spectral element methods for incompressible Navier-
Stokes equations. In State-of-the-Art Surveys on Computational Mechanics, A. K. Noor and
J. T. Oden, Eds. ASME, New York, 71–143.
METCALF, M. 1985. Effective FORTRAN 77. Clarendon Press, Oxford, U.K., 162–176.
METCALF, M. AND REID, J. 1992. Fortran 90 Explained. Oxford University Press, Oxford,
U.K.
STROUSTRUP, B. 1991. The C11 Programming Language. Addison-Wesley, Reading, Mass.
ZIMMERMANN, T., DUBOIS-P`ELERIN, Y., AND BOMME, P. 1992. Object-Oriented Finite Element
Programming: I. Governing Principles. Computer Methods in Applied Mechanics and
Engineering, vol. 98. Elsevier Science, Amsterdam, 291–303.

Received June 1994; revised April and July 1995, January, February, March, and July 1996;
accepted July 1996

ACM Transactions on Mathematical Software, Vol. 23, No. 1, March 1997.

Вам также может понравиться