Dynamic Memory De-allocation in Fortran 95 / 2003 derived type calculus

Abstract data types developed for computational science and engineering are frequently modeled after physical objects whose state variables must satisfy governing differential equations. Generalizing the associated algebraic and differential operators to operate on the abstract data types facilitates high-level program constructs that mimic standard mathematical notation. For non-trivial expressions, multiple object instantiations must occur to hold intermediate results during the expression’s evaluation. When the dimension of each object’s state space is not specified at compile-time, the programmer becomes responsible for dynamically allocating and de-allocating memory for each instantiation. With the advent of allocatable components in Fortran 2003 derived types, the potential exists for these intermediate results to occupy a substantial fraction of a program’s footprint in memory. This issue becomes particularly acute at the highest levels of abstraction where coarse-grained data structures predominate. This paper proposes a set of rules for de-allocating memory that has been dynamically allocated for intermediate results in derived type calculus, while distinguishing that memory from more persistent objects. The new rules are applied to the design of a polymorphic time integrator for integrating evolution equations governing dynamical systems. Associated issues of efficiency and design robustness are discussed.


Introduction
A central activity in object-oriented software design involves constructing abstract data types (ADTs) appropriate for a given application domain.In computational science and engineering (CS&E), these ADTs often represent physical entities whose state variables admit a standard calculus.One typically uses this calculus to formulate differential equations that govern the system dynamics.Implementing the associated algebraic and differential operators in a high-level language facilitates semantics mimicking standard mathematical notation.As the formulas expressed become increasingly complicated, one frequently finds the need to allocate temporary storage for intermediate results.To avoid memory leaks, a strategy must be adopted for de-allocating storage once it is no longer needed.As with any task performed ubiquitously in a given application domain, one expects common idioms to arise for expressing the associated algorithms.Since the addition of dynamic memory management to Fortran lagged that of many other popular languages, there has been less time for such algorithms and idioms to be promulgated.This paper presents an attempt to fill this gap and discusses the attendant issues of efficiency and design robustness.
The design patterns to be presented have been heavily influenced by a series of papers published by Decyk, Norton and Szymanski [4][5][6][7].They outlined techniques for object-oriented programming (OOP) in Fortran 90/95, including strategies for implementing encapsulation, information hiding, inheritance, static polymorphism and run-time polymorphism.An additional influence on the current work has been the recent text on OOP in Fortran 90/95 by Akin [1].In particular, he inspired our design of object constructors as wrappers for Fortran's intrinsic constructors.Akin also gave a particularly lucid illustration of emulating run-time polymorphism via dynamic dispatching.A third influence is the work of Leftantzi, Ray and Najm [9] in developing reacting flow simulation codes using the Common Component Architecture (CCA).In integrating stiff partial differential equations forward in time, they find it useful to separate algorithms from physics.For example, they create essentially stateless, polymorphic time integrators for marching forward more stateful modules containing physical data.The current paper attempts to resolve memory management issues encountered in the development of a polymorphic time integrator for several ADTs in Fortran 95/2003.
The Fortran 95 standard provided numerous constructs useful in creating ADTs [9,13].These include modules, derived types and private data.ADTs can be implemented in Fortran 95 as modules that encapsulate derived types with public procedures for operating on the types' private data.Operator overloading facilitates performing arithmetic on the derived types.Generic function interfaces (module procedures) facilitate generalizing that arithmetic to a more feature-rich calculus with polymorphic integration and differentiation functions.We will refer to these functions as differential operators by analogy with the mathematical operators they implement.
When overloaded arithmetic and differential operators are strung together in a single expression, the call tree follows a pattern wherein the result of each operator of higher precedence gets passed up the call tree to an operator of lower precedence, typically terminating with a call to the assignment operator.Each operator result is an instance of the given derived type.The memory that must be allocated for each result falls into two categories: derived type components whose size can be determined at compile-time and those whose size must be determined at run-time.
The Fortran 95 standard provided only one mechanism for derived type components whose memory requirements are not known at compile-time.That mechanism was pointer components.Given the havoc pointers potentially wreak on optimizing compilers and the resulting performance penalty, this mechanism offered limited utility for the class of CS&E programs in which long loops over fine-grained data objects form the dominant activity (cf.[15]).This deficiency was recognized soon after the publication of the Fortran 95 standard, and the standards committee promised in a 1998 techni-cal report to include allocatable components in derived types in its next standard [10].Allocatable components are now officially a feature of Fortran 2003 [11].
Due to its importance for CS&E applications, compiler vendors began providing this feature in advance of the publication of the Fortran 2003 standard (cf.[17]), albeit without adding what Metcalfe, Reid and Cohen [13] indicate was one of the primary motivations for its inclusion: facilitating automatic de-allocation of memory allocated to hold intermediate results in derived type arithmetic.Metcalfe, Reid and Cohen point out that automatic de-allocation for pointer components is infeasible due to the difficulty of determining whether the associated memory is the target of another pointer [10]; whereas allocatable components apparently circumvent this difficulty at least in the isolated case of derived type arithmetic.
Until compiler vendors provide for automatic deallocation, developers must supply this capability.Section 2 presents a case study on a class of CS&E applications for which dynamic de-allocation of intermediate results is critical: defining a polymorphic time integrator class for evolving dynamical systems.Section 3 proposes a bottom-up de-allocation scheme in which results are marked as temporary upon creation and freed at the immediately higher level in the call tree.A simple convention facilitates freeing all temporary storage while allowing chosen objects to persist.Section 4 discusses relevant trade-offs between execution time, memory usage and design robustness.

Physics
Consider a set of ADTs embodying the state and behavior of various lower-dimensional objects immersed in a moving three-dimensional (3D) fluid.Two examples of current interest include zero-dimensional point masses and one-dimensional line vortices.More specifically, we are interested in simulating clouds of water droplets being transported through turbulent combustion reactants for purposes of fire suppression.We abstract from this the idealized case of point masses immersed in a turbulent flow with buoyancy driven by temperature gradients.The motion of sufficiently small droplets is governed by Stokes' drag law: where r droplet , v droplet and St are the droplet position, velocity and Stokes number, respectively; and v gas is the local gas velocity.The gas velocity is interpolated from velocity fields produced by a Navier-Stokes solver.The Stokes number is a dimensionless measure of a droplet's dynamic response time.The droplet state space is thus 7-dimensional, including three components each of r droplet and v droplet plus one scalar St.
Since we will typically simulate millions of droplets, a large number of objects are needed to represent the full problem state space, and the droplet abstraction can be considered fine-grained according to the definition of Rouson and Xiong [15].It will prove convenient, however, to gather large collections of droplets into a "cloud" abstraction, at the heart of which lies a droplet array or linked list.At this level, the data structure is coarse-grained and each instantiation occupies substantial amounts of memory.
As mentioned above, we are also interested in tracking the motion of complicated networks, or "tangles", of quantized vortices in superfluid liquid helium ( 4 He).Below a critical transition temperature of 2.17 K, helium behaves as a two-fluid mixture of interpenetrating normal fluid and superfluid.The superfluid is characterized by vortices swirling around evacuated cores of approximately 1 Angstrom in diameter, so in the context of classical physics simulations, they can be represented quite well by curvilinear, one-dimensional objects that induce a velocity in the surrounding fluid according to the Biot-Savart law.The equation of motion for the vortex lines themselves is [16]: where v s is the superfluid velocity imposed by boundary and initial conditions; v i is the velocity induced by other vortex segments; S(ξ, t) is the position of a point on the vortex filament; S is the first derivative of S with respect to vortex filament arc-length ξ; α and α are temperature-dependent constants; and finally v n is the local velocity of the normal fluid.Neglecting point connectivity information, the vortex point state space is thus three-dimensional, comprising the three components of S, all other variables being provided by other ADTs.
As with droplets, it proves convenient to gather large collections of vortex points into a vortex tangle ADT.Again if we desire to simulate millions of vortex points, the data structure is coarse-grained at the tangle level, and memory management will prove paramount.(See [15] for more detail on the tangle data structure design.) In both the droplet and superfluid problems,a "Fluid" ADT provides the local velocities at the droplet and vortex core locations via interpolations on a velocity field produced by solving the Navier-Stokes equations: supplemented by the initial conditions u(x, 0) = u 0 (x), p(x, 0) = p 0 and periodic boundary conditions where u generically represents v gas or v n ; p is the fluid pressure; Re is the Reynolds number; and the body force, f , generically represents buoyancy in hot gases, collective drag reactions from droplets, or collective mutual friction between normal fluid and superfluid.Since we are interested in high-fidelity representations of fluid turbulence, we model the fluid state variables with globally smooth Fourier basis functions.This builds the desired periodic boundary conditions into our basis.We also recast Eq. (3) in a form that eliminates the pressure term while identically guaranteeing the divergence-free condition (cf.[14]).Time advancement takes place in Fourier space; whereas the nonlinear terms in Eq. ( 3) are computed pseudospectrally in physical space [3].Transformation between these two representations via 3D Fast Fourier Transform (FFT) dominates the simulation's operation count.Since these transforms are most easily performed in shared memory, our simulations require gigabyte-sized arrays.Thus, our fluid representation is also coarsegrained and economy receives high priority.

Numerical algorithms
Each of the above problems requires modeling the evolution of a dynamical system governed by a coupled system of nonlinear ordinary differential equations of the form where Y is the global state vector, Y 0 is the vector of initial conditions, and T is the final value of the time variable t.For droplets, Y contains all droplet positions, velocities and Stokes numbers.For vortex points, it contains position vectors.For a fluid, it contains 3D Fourier coefficient vectors.(Recall that time advancement for our fluids takes place in Fourier space.)For mixtures, Y contains all of the above.Advancing Eq. ( 4) from the kth time step, t k , to time Each choice of quadrature scheme for the above integral yields a corresponding marching scheme.For example, choosing the rectangle rule yields the forward Euler method: For stiff systems, it is often useful to split the time derivative in Eq. ( 4) into a linear operator, L, and a nonlinear operator, N .Assuming L and N are timeinvariant, we write: For the fluid, the stiff terms lie in L. The above splitting facilitates advancing this term with an implicit scheme to circumvent the otherwise stringent stability restrictions, while N can be advanced by an explicit scheme to avoid iteration.Spalart et al. [19] proposed just such a marching algorithm, choosing trapezoidal quadrature for L and a third-order Runge-Kutta (RK3) method for N .Their method can be written in the three-step predictor-corrector form: which we write in a slightly modified form for clarity.
Comparing a Taylor expansion of Eq. ( 7) to Eq. ( 8) yields 11 nonlinear constraints on the coefficient vectors α, β, γ and ζ that must be satisfied to achieve the desired order of accuracy.Our coefficient values were provided by Alan Wray of NASA Ames Research Center (private communication).In what follows, we will explore the dynamic memory de-allocation requirements of both the explicit Euler and the Wray RK3 scheme.

Software architecture
Figure 1 depicts a typical class diagram using the Unified Modeling Language (UML) architectural description standard [2].At the top of the diagram is a representation of our Integrand class.This essentially stateless, polymorphic class is implemented as a MODULE containing the derived type Integrand, whose only components are pointers to instances of the derived types to be integrated.The relationship between the Integrand class and each of its pointer targets is represented as UML association.
Just below the Integrand class in Fig. 1 are three coarse-grained ADTs: Cloud, Mixture, and Fluid.The first and last of these are built up from two more fine-grained ADTs.A Cloud contains a very fine-grained array of Droplets; whereas a Fluid contains an array of three Fields, each of which contains one component of the fluid velocity vector field.Grouping fine-grained instances of an ADT into a more coarse-grained collection ADT facilitates changing the relationship between each instance.For example, an alternative implementation of a Cloud might create a linked list by adding pointer components to the Droplets.Alternatively, without affecting the Droplet definition, one might insert individual Droplet instances into linked list ADT as described by Akin [1].Although the linked list abstraction would be somewhat artificial for the Droplet dispersion simulations, it proves quite natural in our quantum vortex tangle simulations.In the latter, the corresponding class diagram is analogous to that of Fig. 1  Although the Cloud/Droplet and Tangle/ Vortex Point relationships are not inheritance hierarchies in the strictest sense, they share some of the properties of such hierarchies.Decyk et al. [5,7], express inheritance relationships in Fortran 90/95 As mentioned above, our use of globally smooth basis functions makes it less natural to decompose the fluid state into the sort of fine-grained data structures that might be more typical of, say, a finite element code.Hence, the Fluid could be implemented as a traditional inheritance hierarchy as defined in [5,7] -that is, the three component Fields could be aggregated into a single Vector Field abstraction, exactly one copy of which would be stored in a Fluid.Since inheritance relationships are frequently described as "is a" relationships, we could then state that a Fluid is a Vector Field that satisfies the Navier-Stokes equations.For our purposes, however, the Vector Field class would be too simplistic to justify the additional layer of abstraction.Furthermore, the relationships would be more complicated because Fluids would still need direct access to individual Fields for storing scalar quantities such as temperature.
Note that a Field abstracts a purely mathematical construct.Following a design pattern similar to Lefantzi et al. [12], we have separated physics from data.The Fluid function d dt() calculates the righthand side of the Navier-Stokes equations using the differentiation function d dx() provided by its Fields.The differentiation details and the associated grid and basis functions are not exposed to the Fluid.Neither are the equations being solved exposed to the Fields.This design enhances the reusability of both modules.One could reuse the Fluid class, while replacing the Fourier representation in Field with a finite element method.Likewise, one could reuse the Field class, while replacing the Navier-Stokes equations in Fluid with an acoustic wave equation.
As shown in Fig. 1, another service a Field object provide its Fluid is the interpolation algorithm velocity at(position), which returns a 3D velocity vector at the location of a passed 3D position vector.This yields v gas at the droplet positions in Eq. ( 1) and v n at vortex point locations in Eq. ( 2).Finally, note that all state variables are private as indicated by the "−" symbols; whereas public methods are designated by "+"; and all classes implement the polymorphic constructor new(), destructor delete(), and input/output procedures print() and read().

Derived type calculus
We can now specify the requirements for an ADT calculus using Fortran 95/2003 derived types.We seek to develop a polymorphic time integrator class for ap-proximating the solution to Eq. ( 5) using the algorithms in Eqs ( 6) and (8).Such a class must be agnostic with respect to the private implementation details described in the previous section.The class must be able to operate on any physical object in the architecture of Fig. 1. (Here we distinguish between "physical" objects for which there exists an evolution equation and purely mathematical abstractions such as the Field class.)For example, it must be possible to integrate a Cloud object without knowing that a Cloud is composed of Droplets, nor whether those Droplets are organized into an array or a linked list.
We further require the syntax of the resulting derived type calculus to map naturally from the mathematical notation of Eqs ( 4)- (8).For example, we expect that after a Cloud instantiation of the form where "this" is pseudocode for a generic pointer to an object of any one of several desired classes.Since no such generic pointers exist in Fortran 95, the actual code differs in ways that will be discussed below.Note that, following Decyk, Norton and Szymanski [5], our procedures mimic C++ by making the first argument in all ADT methods an object named "this" whose type is the derived type defined in the same module.
Successful compilation of the above code requires overloading the +, * and = operators and implementing the differentiation function d dt().More significantly, it also requires run-time polymorphism.Decyk, Norton and Szymanski [7] first outlined a strategy for expressing run-time polymorphism via dynamic dispatching in Fortran 90/95.Our Integrand class implementation follows an example of their technique from Akin [1] as explained next.
An Integrand must be instantiated using the Integrand constructor as follows: whereupon purple haze can now be marched forward in time by repeated calls to euler Integrator (kernel) or RK3 Integrator(kernel).Above we have made use of the constructor convention proposed by Akin [1], whereby the Integrand () constructor calls the Fortran 90/95 default constructor Integrand(), the latter being inaccessible from outside the Cloud MODULE due to the privacy of the Integrand data members (see [1] for more detail).Ultimately, the results can be output by a call to print(purple haze), after which the results can later be recovered by a call to read(purple haze).
Appendix A shows an excerpt of the code with complete details for the explicit Euler and RK3 integration of Fluid and Cloud objects.Note that Integrand instantiations take place through assignment procedures that guarantee that at any given time, all but one of the pointers points to NULL() as suggested by Decyk et al. [7] and Akin [1].The euler Integrator() and RK3 Integrator() methods use this fact to determine what type of object is being passed.An IF-THEN-ELSE construct then dispatches the appropriate code for the given object.The code blocks inside each clause are semantically equivalent, with the only differences being the class of object being manipulated.
Note that in Fortran 2003, it will likely be much easier to implement similar functionality using so-called "polymorphic entities", i.e. objects whose actual type might vary during program execution.However, commercial compiler vendors have only recently provided full Fortran 95 compliance (cf.[17,18]), and compiler projects in the public domain have not even reached that point (cf.[20,21]).It is likely there will continue to be a need for strategies to emulate this technology in Fortran 95 for the next several years.Regardless, the memory de-allocation issues discussed next exist in either version of the language -as they likely would in any language without garbage collection.
Without loss of generality, it will suffice to focus on Cloud objects for the remainder of this paper.At the heart of the Cloud clause in euler Integrator() is the code this%cloud_ptr = this%cloud_ptr + dt*d_dt(this%cloud_ptr) Execution of the above line results in a calling sequence in which the result of the differentiation function d dt() is passed to an overloaded multiplication operator, the result of which is passed to an overloaded addition operator, the result of which is passed to an overloaded assignment operator.A typical interface and function signature for one of these calls might take the form To avoid memory leaks, each such allocation must have a corresponding de-allocation.This issue arises four times in the call tree of the above explicit Euler expression (see Fig. 2).It arises many more times during a typical RK3 time step (see Fig. 3).The question to be addressed in the next section is where best to accomplish the requisite de-allocations.

Memory de-allocation rules
The software developer's vantage point frames our central dilemma.Looking from the top of the call tree down, it is clear to the designer of euler Integrator() which results in the explicit Euler formula can be deleted immediately after their first use.However, by the time the ultimate result has been computed, copied into the object on the left of the assignment operator, and control has been returned to euler Integrator(), all variable names associated with intermediate results have gone out of scope.Thus, the program name space contains neither ALLOCATABLE arrays nor POINTERs on which the DEALLOCATE intrinsic can operate to free the desired memory.
Alternatively, looking from the middle of the call tree down, it may not be clear to the designer of an overloaded arithmetic or differential operator whether the arguments passed up to it are temporary or persistent.Deleting all arguments would waste clock cycles if some arguments need to be recomputed later.For example, the RK3 code for a Cloud is  8) as its first argument along with β i ∆t as its second argument for RK3 substep i.Note that each of the above assignments represents an implicit instantiation, including any requisite dynamic memory allocations under programmer control.
In tallying persistent results in the above code, a very simple pattern emerges: all objects on the left side of an assignment operator are used more than once after assignment.Clearly erroneous results would obtain were one to assume all operator arguments are temporary and can therefore be deleted after their first use.
It is worth mentioning here that Fortran 95 requires an arithmetic OPERATOR to be a Fortran FUNCTION taking one or two non-optional arguments declared with INTENT(IN), which precludes alteration of the arguments inside the procedure.Note that there is no need to alter something that will not be used subse- Again, the only reason to modify an argument is if that modification will somehow be used subsequently.Thus, modification implies persistence.
The latter reasoning suggests a solution to the deallocation dilemma from the bottom-up vantage point.Starting at the bottom of the call tree, one marks each object as temporary or persistent at the point of creation before it is passed up the call tree.Temporary objects are deleted immediately after their first use.To codify this approach, we introduce a simple definition and its corollary: Definition: Given an object that appears as the result of an arithmetic or differential operator, we define the object as temporary if it can be deleted at the termination of execution of the first subsequent operator in which it appears as an argument.
Corollary: All objects that are not temporary are persistent.
Four simple rules comprise the proposed memory de-allocation scheme: Rule 1: All results of arithmetic and differential operator FUNCTIONs are marked as temporary upon creation.Rule 2: Left-hand arguments to assignment operator SUBROUTINEs are marked as persistent.Rule 3: Temporary objects are deleted prior to the termination of any arithmetic or differential operator in which they appear as an argument.Rule 4: Persistent objects are deleted prior to the termination of the procedure that instantiated them.
It might be worth noting the above definition and corollary are the reverse of how the terms "temporary" and "persistent" are defined in [2] for UML.There, an object that survives beyond the procedure that creates it is termed "persistent".An object deleted by the proce-dure that instantiates it is termed "temporary".Again the issue is vantage point.Our definition seems more natural from the standpoint of the arithmetic operator designer, who would otherwise be required to delete "persistent" objects upon first use, while the designer of the overall time-advancement algorithm might use "temporary" objects multiple times before deletion.
To facilitate imposition of the above rules, we store information in each ADT about its persistence.The binary nature of persistence naturally lends itself to representation by a Fortran LOGICAL variable.For example, our Cloud derived type definition is We can therefore mark an operator result as temporary immediately subsequent to its instantiation or direct memory allocation inside the operator.Returning to the discussion of the scalar times Cloud() operator at the end of the previous section, we would simply include the following line immediately after the ALLOCATE statement presented near the end of the previous section: product%temporary = .TRUE.
At the end of scalar times Cloud(), we would release the memory associated with any temporary arguments before their variable names go out of scope by executing a line of the form We place similar statements in all arithmetic and differential operators; whereas in the assignment operator, we write left%temporary = .FALSE.just after the corresponding ALLOCATE.Then, just before termination of the assignment operator, we write We tested the above strategy in several of the classes described in Fig. 1.Our tests indicate that the memory usage of simulations with constant-sized objects remains constant over time.
One important caveat relates to syntax.We have found considerable variation amongst compil-ers with regards to one's ability to DEALLOCATE allocatable components of derived type arguments declared with INTENT(IN).Some compilers allow the DEALLOCATE command and perform it successfully for sufficiently small sets of objects.Other compilers allow it, but appear not to perform it.Still other compilers disallow it.To ensure portability, we currently define one MODULE PROCEDURE to replace each desired operator.This circumvents the requirement to declare INTENT.Since the resulting expressions are somewhat less readable, we always insert an adjacent comment with the syntax used throughout this paper.For example, we write and likewise for the times operator.We hope the standards committee will eventually relax the INTENT requirement on operator arguments, in which case we can rapidly revise our code by replacing the above executable lines with the corresponding commented lines.

Efficiency and robustness
We make no claim regarding the uniqueness of our proposed rule set.However, it appears to offer a reasonable trade-off between execution time and memory utilization.In this regard, considering two alternatives might be instructive.A bottom-up alternative would be to simply de-allocate all intermediate results, marking none as persistent.When coupled with judicious overwriting of results that can be cheaply recalculated, it might be possible to reduce the memory require-ments at the expense of increased execution time.A top-down alternative might be to daisy-chain objects together into a linked list as they percolate up the call tree.When coupled with a mechanism for enumerating and labeling the nature of the data contained in each object, one could then recursively tunnel through the list, deleting all non-persistent data and obviating any recalculations at the expense of increased memory and additional logic for constructing the linked list.
At some level, designs based on the two latter strategies would exhibit greater robustness.The bottomup alternative would require slightly simpler logic, eliminating the temporary marker.The top-down alternative would likewise eliminate this marker and gather all the de-allocation decisions into one place: the Integrator module, which as previously noted is the place where it is most obvious to the software engineer whether a particular object is temporary or persistent.By contrast, distributing the de-allocation decisions across all operators on all ADTs requires coordination and compliance in the development of each ADT.Verifying compliance would require exposing implementation details to a greater level than might be desirable in a team development effort.All of this implies that the best solution would be for compiler vendors to provide an automatic de-allocation capability at least for overloaded arithmetic operators.
We have investigated the capabilities of several compilers.Currently, the Fortran 95 compiler from ST Micro Portland Group [17] and Intel [18] do not perform any automatic de-allocations for intermediate results in derived type arithmetic.The Numerical Algorithms Group Fortran 95-to-C translator does perform the deallocations, but only at the top of the call tree [22].As mentioned, this solution is costly for coarse-grained data structures.Finally, two public domain Fortran 95 compiler projects do not appear to be far enough along to compile our code [20,21].

Conclusions
We have proposed a set of rules for de-allocating memory associated with temporary intermediate results of arithmetic and differential operators on abstract data types, while preserving objects desired to be persistent.Constructs have been presented for using such operators to write Fortran expressions that naturally mimic standard mathematical notation.We applied these constructs to the development of a polymorphic class for integrating equations that govern evolving dynamical systems.
As complicated expressions are evaluated, our rules work from the bottom of the call tree upwards, marking each intermediate result as temporary or persistent immediately after instantiation.Each temporary object is deleted at the immediately lower level of operator precedence (higher level on the call tree) just before the final name associated with the object goes out of scope.Doing so avoids memory leaks that would be particularly costly at the highest levels of abstraction, where coarse-grained data structures predominate.We tested the rules to ensure that simulations with constant-sized objects exhibit constant memory utilization over time.We also proposed a workaround for compilers that do not allow de-allocation of allocatable components of objects declared with INTENT(IN).
Although the resulting algorithm is not unique, comparisons to alternative bottom-up and top-down approaches indicate it represents a reasonable trade-off between execution time and storage requirements.A minor compromise in robustness stems from of the required coordination and compliance by the developer of each operator for each abstract data type.Nonetheless, we believe the proposed rules fill an important need in the absence of automatic de-allocation by compilers and makes more efficient use of memory than automatic de-allocation at the top of the call tree.[19] with constants specified by Wray (private !comm.).Each integrand must provide functions Linear() ! and Nonlinear() to calculate the corresponding part of ! the differential operator, each taking as their sole !argument one instance of the class and returning !another instance of the same class containing its !derivative.In addition, the inverse of !1-beta(i)*dt*Linear() must be provided, taking an !instance of the integrand class as its 1st argument ! and beta(i)*dt as its 2nd argument for substep i.
Figure1depicts a typical class diagram using the Unified Modeling Language (UML) architectural description standard[2].At the top of the diagram is a representation of our Integrand class.This essentially stateless, polymorphic class is implemented as a MODULE containing the derived type Integrand, whose only components are pointers to instances of the derived types to be integrated.The relationship between the Integrand class and each of its pointer targets is represented as UML association.Just below the Integrand class in Fig.1are three coarse-grained ADTs: Cloud, Mixture, and Fluid.The first and last of these are built up from two more fine-grained ADTs.A Cloud contains a very fine-grained array of Droplets; whereas a Fluid contains an array of three Fields, each of which contains one component of the fluid velocity vector field.Grouping fine-grained instances of an ADT into a more coarse-grained collection ADT facilitates changing the relationship between each instance.For example, an alternative implementation of a Cloud might create a linked list by adding pointer components to the Droplets.Alternatively, without affecting the Droplet definition, one might insert individual Droplet instances into linked list ADT as described by Akin[1].Although the linked list abstraction would be somewhat artificial for the Droplet dispersion simulations, it proves quite natural in our quantum vortex tangle simulations.In the latter, the corresponding class diagram is analogous to that of Fig.1with a Tangle class replacing the Cloud class and a Vortex Point class replacing the Droplet class.Although the Cloud/Droplet and Tangle/ Vortex Point relationships are not inheritance hierarchies in the strictest sense, they share some of the properties of such hierarchies.Decyk et al.[5,7], express inheritance relationships in Fortran 90/95