40. Adding new solvers

Adding new solvers (either for new or existing physics) to FLASH starts with a new subdirectory at an appropriate location in the source tree. If the solver provides an alternative implementation for the already supported physics, then the subdirectory should be added below the main sub-unit directory for that sub-unit. For example, adding a Constant implementation to the Heatexchange unit requires adding a subdirectory Constant under “source/physics/sourceTerms/Heatexchange/HeatexchangeMain”. If the solver is adding new physics to the code, a new capitalized subdirectory should be placed under the physics subdirectory, effectively creating a new unit, and its API should be defined by placing the null implementations of the relevant functions in the subdirectory. The implementations themselves should go into the UnitMain subdirectory placed in the unit. For example adding a Heat unit the to code requires putting a directory Heat under “source/physics”, and adding files Heat_init.F90, Heat_finalize.F90 and at least one “doer” function such as Heat.F90 with null implementations of the corresponding functions. Other than the source files, a Makefile and a Config file may need to be added to the subdirectory. If the solver is a new unit, both those files will be needed at the Unit and UnitMain levels. If the solver is an alternative implementation, the files may or may not be needed. See http://flash.rochester.edu/site/flashcode/user_support/flash_howtos/ExampleFlashUnit/ for the architecture of a unit.

Makefile: The Makefile snippet added to any directory should reflect all the files in the subdirectory that are not inherited from a higher level directory. At the API level, the Makefile should include all the functions that constitute the API for the unit. At any lower level, those functions should not be included in the Makefile, instead all the local files at that level should be included. The implemented source files appear at UnitMain or some other peer level (if there is more than one sub-unit in the unit). An example of Makefile for a unit “Heat” with only three functions is as follows:

 

# Makefile for Heat unit API
Heat = Heat_init.o Heat_finalize.o Heat.o

All the subdirectories lying below this level have their Makefiles use macro concatenation to add to the list. For example, if the implementation of the Heat unit adds one file he_getTemp.F90 in the HeatMain implementation, the corresponding Makefile will be

 

# Makefile for HeatMain sub-unit
Heat += Heat_data.o he_getTemp.o

Note that the sub-unit's Makefile only makes reference to files provided at its own level of the directory hierarchy, and the units will have at least one data module (in this instance Heat_data) which contains all the unit scope data. If the sub-unit provides special versions of routines to override those supplied by the any implementation at a higher level directory in the unit, they do not need to be mentioned again in the object list,

Config: Create a configuration file for the unit, sub-unit or alternative implementation you are creating. All configuration files in a sub-unit path are used by setup, so a sub-unit inherits its parent unit's configuration. Config should declare any runtime parameters you wish to make available to the code when this unit is included. It should indicate which (if any) other units your unit requires in order to function, and it should indicate which (if any) of its sub-units should be used as a default if none is specified when setup is run. The configuration file format is described in Sec:Configuration files.

This is all that is necessary to add a unit or sub-unit to the code. If you are creating a new solver for an existing physics unit, the unit itself should provide the interface layer to the rest of the code. As long as your sub-unit provides the routines expected by the interface layer, the sub-unit should be ready to work. However, if you are adding a new unit, you will need to add calls to your API routines elsewhere in the code as needed. The most likely place where these calls will need to the be placed will be in the Driver unit

It is difficult to give complete general guidance; here we simply note a few things to keep in mind. If you wish to be able to turn your unit on or off without recompiling the code, create a new runtime parameter (e.g., useUnit) at the API level of the your unit. You can then test the value of this parameter before calling your API routines from the main code. For example, the Burn unit routines are only called if (useBurn==.true.). (Of course, if the Burn unit is not included in the code, setting useBurn to true will result in empty subroutine calls.)

You will need to add Unit_data.F90 file which will contain all the data you want to have visible everywhere within the unit. Note that this data is not supposed to be visible outside the unit. You may wish have Driver_initFlash call your unit's initialization routine and Driver_finalizeFlash to call your unit's finalize routine.

If your solver introduces a constraint on the timestep, you should create a routine named Unit_computeDt() that computes this constraint. Add a call to this routine in Drive_computeDt.F90 (part of the Driver unit). Your routine should operate on a single block and take three parameters: the timestep variable (a real variable which you should set to the smaller of itself and your constraint before returning), the minimum timestep location (an integer array with five elements), and the block identifier (an integer). Returning anything for the minimum location is optional, but the other timestep routines interpret it in the following way. The first three elements are set to the coordinates within the block of the cell contributing the minimum timestep. The fourth element is set to the block identifier, and the fifth is set to the current processor identifier. This information tags, along with the timestep constraint, when blocks and solvers are compared across processors, and it is printed on stdout by the master processor along with the timestep information as FLASH advances.

If your solver is time-dependent, you will need to add a call to your solver in the Drive_evolveFlash routine. Limit the entry points into your unit to the API functions. It is a FLASH architecture requirement, violating this will make the code unmaintainable and will make it hard to get support from the code developers.