A FLASH unit defines its own Application Programming Interface (API), which is a collection of routines the unit exposes to other units in the code. A unit API is usually a mix of accessor functions and routines which modify the state of the simulation.
A good example to examine is the Grid unit API. Some of the accessor
functions in this unit are Grid_getCellCoords,
Grid_getBlkData, and Grid_putBlkData,
while Grid_fillGuardCells and
Grid_updateRefinement are examples of API routines which
modify data in the Grid unit.
A unit can have more than one implementation of its API. The Grid Unit, for example, has both an Adaptive Grid and a Uniform Grid implementation. Although the implementations are different, they both conform to the Grid API, and therefore appear the same to the outside units. This feature allows users to easily swap various unit implementations in and out of a simulation without affecting they way other units communicate. Code does not have to be rewritten if the users decides to implement the uniform grid instead of the adaptive grid.
subroutine Grid_getListOfBlocks(blockType, listOfBlocks, count) implicit none integer, intent(in) :: blockType integer,dimension(*),intent(out) :: listOfBlocks integer, intent(out) :: count count=0 listOfBlocks(1)=0 return end subroutine Grid_getListOfBlocks
While a set of null implementation routines at the top level of a unit may seem like an unnecessary added layer, this arrangement allows FLASH to include or exclude units without the need to modify any existing code. If a unit is not included in a simulation, the application will be built with its stub functions. Similarly, if a specific implementation of the unit finds some of the API functions irrelevant, it need not provide any implementations for them. In those situations, the applications include stubs for the unimplemented functions, and full implementations of all the other ones. Since the stub functions do return valid values when called, unexpected crashes from un-initialized output arguments are avoided.
The Grid_updateRefinement routine is a good example of how stub functions can be useful. In the case of a simulation using an adaptive grid, such as PARAMESH, the routine Driver_evolveFlash calls Grid_updateRefinement to update the grid's spacing. The Uniform Grid however, needs no such routine because its grid is fixed. There is no error, however, when Driver_evolveFlash calls Grid_updateRefinement during a Uniform Grid simulation, because the stub routine steps in and simply returns without doing anything. Thus the stub layer allows the same Driver_evolveFlash routine to work with both the Adaptive Grid and Uniform Grid implementations.
In addition to the subunits, the top level unit directory may contain a subdirectory called localAPI. This subdirectory allows a subunit to create a public interface to other subunits within its own unit; all stub implementations of the subunit public interfaces are placed in localAPI. External units should not call routines listed in the localAPI; for this reason these local interfaces are not shown in the general source API tree.
A subunit can have a hierarchy of its own. It may have more than one unit implementation directories with alternative implementations of some of its functions while other functions may be common between them. FLASH exploits the inheritance rules described in Architecture Tips. For example, the Grid unit has three implementations for GridMain: the Uniform Grid (UG), PARAMESH 2, and PARAMESH 4. The procedures to apply boundary conditions are common to all three implementations, and are therefore placed directly in GridMain. In addition, GridMain contains two subdirectories. One is UG, which has all the remaining implementations of the API specific to the Uniform Grid. The other directory is organized as paramesh, which in turn contains two directories for the package of PARAMESH 2 and another organizational directory paramesh4. Finally, paramesh4 has two subdirectories with alternative implementations of the PARAMESH 4 package. The directory paramesh also contains all the function implementations that are common between PARAMESH 2 and PARAMESH 4. Following the naming convention described in Architecture Tips, paramesh is all lowercase, since it has child directories that have some API implementation. The namespace directories Paramesh2, Paramesh4.0 andParamesh4dev contain functions unique to each implementation. An example of a unit hierarchy is shown in Figure 4.1. The kernels are described below in Sec:privateRoutines.
Each unit must have a F90 data module to store its unit-scope local data and an Unit_init file to initialize it. The Unit_init routines are called by the Driver unit once by the routine Driver_initFlash at the start of a simulation. They get unit specific runtime parameters from the RuntimeParameters unit and store them in the unit data module.
Every unit implementation directory of UnitMain, must either inherit a Unit_data module, or have its own. There is no restriction on additional unit scope data modules, and individual Units determine how best to manage their data. Other subunits and the underlying computational kernels can have their own data modules, but the developers are encouraged to keep these data modules local to their subunits and kernels for clarity and maintainability of the code. It is strongly recommended that only the data modules in the Main subunit be accessible everywhere in the unit. However, no data module of a unit may be known to any other unit. This restriction is imposed to keep the units encapsulated and their data private. If another part of the code needs access to any of the unit data, it must do so through accessor functions.
Additionally, when routines use data from the unit's data module the
convention is to indicate what particular data is being used with the
ONLY keyword, as in
use Unit_data, ONLY : un_someData. See the snippet of code
below for the correct convention for using data from a unit's FORTRAN Data Module.
subroutine Driver_evolveFlash() use Driver_data, ONLY: dr_myPE, dr_numProcs, dr_nbegin, & dr_nend, dr_dt, dr_wallClockTimeLimit, & dr_tmax, dr_simTime, dr_redshift, & dr_nstep, dr_dtOld, dr_dtNew, dr_restart, dr_elapsedWCTime implicit none integer :: localNumBlocks
Each unit must also have a Unit_finalize routine to clean up the unit at the termination of a FLASH run. The finalization routines might deallocate space or write out completion messages.
The concept of kernels, on the other hand, facilitates easy import of third party solvers and software into FLASH. The kernels are not required to follow either the naming convention or the inheritance rules of the FLASH architecture. They can have their own hierarchy and data modules, and the top level of the kernel typically resides at leaf level of the FLASH unit hierarchy. This arrangement allows FLASH to import a solver without having to modify its internal code, since API and helper functions hide the higher level details from it, and hide its details from other units. However, developers are encouraged to follow the helper function naming convention in the kernel where possible to ease code maintenance.
The Grid unit and the Hydro unit both provide very good examples of private routines that are clearly distinguishable between helper functions and kernel. The AMR version of the Grid unit imports the PARAMESH version 2 library as a vendor supplied branch in our repository. It sits under the lowest namespace directory Paramesh2 in Grid hierarchy and maintains the library's original structure. All other private functions in the paramesh branch of Grid are helper functions and their names start with gr_. In the Hydro unit the entire hydrodynamic solver resides under the directory PPM, which was imported from the PROMETHEUS code (see Sec:PPM algorithm). PPM is a directional solver and requires that data be passed to it in vector form. Routines like hy_sweep and hy_block are helper functions that collect data from the Grid unit, and put it in the format required by PPM. These routines also make sure that data stay in thermodynamic equilibrium through calls to the Eos unit. Neither PARAMESH 2, nor PPM has any knowledge of units outside their own.