Академический Документы
Профессиональный Документы
Культура Документы
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
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
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).
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.
2¹ 2u 5 f in V, (1)
# 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 % ,
X h 5 H 10~ V ! ù P N, K~ V ! .
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.
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.
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
END SELECT
END FUNCTION give_matrix_e
ENDDO
ENDDO
END FUNCTION assemble_matrix_a
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’)
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.
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).
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
APPENDIX
!
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
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
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