implicit NONE statement in every routine. We strongly
recommend this practice for clarity. The driver routines have to assure that the modules below are fed with all necessary arrays and variables set:
Modules can hold both data and routines. The module data is accessible to all
module routines and maybe to the outside (depending on declaration as public or private).
Any public variable or module routine may be accessed via a statement like:
USE module, only : variable, routineGeneral information on Fortran 90 is available.
Newton_Raphson module
The Newton-Raphson solver module consists of the routines solution, get_corrections, apply_corrections and calls the solver package. The first two are mostly driver routines. Only solution is directly accessible from the module. The routine get_corrections is the driver for the solver package. Apply_corrections implements the correction strategy which can affect the convergence of the iteration severely, therefore, it is a user-modifiable routine. The whole dataflow between the core modules is shown in the diagram to the right.
After setting up problem_size (and, maybe, switches), solution can be called with the following variables:
call solution (model_no, y, restart)
| model_no | IN | The current model number, currently used only for output, but could be used for model-dependent correction schemes. |
| y | INOUT | The solution array of size nb_rows x nequ. Should be preset with an estimate for the iteration start, returns the solution, if converged, otherwise gets undefined. |
| restart | OUT | logical, returns .true. if convergence failed (after max_iterations, a parameter defined inside solution) and restart is requested by the module, otherwise .false. |
The solver package module consists of the routines init_solver, set_jacobi, preconditioner, [analyser], lin_solver, mul_m_v and relies on proper setup of the problem_size module. The init_solver routine must be called first and for every solution if changes of one of the sizes in problem_size are expected during runtime. Then automatic reallocation is initiated. Set_jacobi automatically sets the Jacobian matrix for the Newton_Raphson solver. It calls the user-supplied routines equations, perturbation from the module problem. Preconditioner balances the matrix for stable inversion. [Analyser analyses the matrix for possible stability problems and tries to hint at possible causes. It uses lin_solver and mul_m_v.] The lin_solver routine is a special block band diagonal solver for arbitrary bandwidth and a variety of boundary forms. It uses a variant of Gauss elimination with partial or full pivoting on successive frames. It is also available as a standalone. Mul_m_v is a utility routine to multiply the special matrix form with a vector and is used by analyser.
The solver package routines are normally called by get_corrections from the Newton-Raphson module. For reference, below are the calling parameters. For details on the arrays, consult the Lin_solver documentation.
call init_solver ([reallocate]) :
| reallocate | IN,optional | logical, .true. requests explicit reallocation of all arrays. Usually, reallocation is done automatically when one of the relevant sizes in problem_size changes. All data in the arrays En,S is lost. |
call set_jacobi (S, y, update_jacobi) :
| S | OUT | Array size nequ x nb+2 x nequ x 0:nb_rows, returns the Jacobi matrix. |
| y | IN | Array size nequ x nb_rows, inputs the current solution vector. |
| [update_jacobi] | IN (reserved) | logical, .true. if the full Jacobian matrix shall be updated, not only the right hand side. (Not in effect yet). |
call preconditioner (S) :
| S | INOUT | Array size nequ x nb+2 x nequ x 0:nb_rows, inputs the raw Jacobian matrix and returns the conditioned one. |
[call analyser (S, failed, full_piv)] (not fully operational yet) :
| S | IN | Array size nequ x nb+2 x nequ x 0:nb_rows, inputs the Jacobian matrix to analyse. |
| failed | OUT | logical, .true. on return if analyser has detected a problem in matrix inversion. A detailed report is given. |
| full_piv | IN | logical, set to .true. if analyser should analyse stability for full pivoting. |
call lin_solver (S, sol, failed, debug, full_piv) :
| S | IN | Array size nequ x nb+2 x nequ x 0:nb_rows, inputs the matrix to solve. |
| sol | OUT | Array size nequ x nb_rows x nrhs, returns the solution. If used not in conjunction with set_jacobi, the number of right hand sides nrhs to solve for can be different from 1. |
| failed | OUT | logical, .true. on return if lin_solver failed to solve the matrix (zero pivot). |
| debug | IN | set to .true. if extensive debugging output is desired. |
| full_piv | IN | set to .true. if full pivoting should be used when solving the matrix. |
call mul_m_v (S, v) :
| S | IN | Array size nequ x nb+2 x nequ x 0:nb_rows, inputs the matrix to multiply with. |
| v | INOUT | Array size nequ x nb_rows x nrhs, the vector the matrix is multiplied with. On return, holds the result of the multiplication. |
This module holds all problem dependent sizes and may be extended by any related variables the user likes to place there. These can be variables or constants, depending on whether they are to be changed during runtime or not. All modules should adapt automatically when one of these sizes change. As most of these variables affect the solver package, the Lin_solver documentation should be consulted for further details. See below for correct choices for selected discretization schemes.
| nb_rows | The total number of your equation nodes (loosely speaking, grid points, but this depends on discretization type). |
| ngrid | The number of gridpoints, which may be identical to nb_rows, depending on discretization. It is used only for the grid generation and maybe for your own routines. The Newton_Raphson and solver packages solely rely on nb_rows. |
| nequ | The number of equations per node (may equal to the number of dependent variables, but that can vary with discretization type). |
| neqs | The total number of equations, including boundary conditions. |
| nb | The maximum block bandwidth of your equations, counted in nequ x nequ blocks, or number of involved nodes per equation (excluding boundaries). The blocks need not be arranged symmetrically around the principal diagonal, but if the arrangement varies from node to node, the bandwidth has to be extended to a (not completely filled) regular pattern. Boundary equations can use the full bandwidth of the regular equations ! The bandwidth can be choosen larger than it actually is. This may improves numerical stability in certain circumstances, but also increases memory requirements and runtime. |
| jb1 | The number of upper boundary equations supposed to be in En(0,:). If there are less than nequ boundary equations, they have to be there, if zero or nequ, they can be placed in En(0,:) (jb1=nequ) or En(1,:) (jb1=0). |
| nbe | The number of upper equations supposed to use (maybe only virtually, depending on bandwidth choice) one of the first nequ dependent variables (first block). |
| nvars | The total number of variables affecting the equations at each node. This number is larger than the number of dependent variables if secondary variables are involved computing the equations. Because, for automatic Jacobi evaluation, these variables depend on the perturbed primary dependent variables, they have to be computed along with them and are handed over to the user supplied equations routine. See below. |
| npar | The number of eigenvalue variables of the problem. Eigenvalues are always appended to y as the last variables, all equations can depend on them. Maximum: nequ. |
| nrhs | The number of right hand sides for a given problem. Preset with 1. Can only be different if lin_solver is used as a standalone. Maximum: nequ. |
This module holds all problem specific routines and is supplied by the user. It must contain at least two routines: equations and perturbation. These are called from the solver package routine set_jacobi and are imported therein by use of the module problem. Below you find some (skeleton) sample code.
subroutine equations (u, var, compute_structure, compute_equations, En, unperturbed)
USE precision , only : dp, zero, one
USE problem_size, only : nequ
USE physics , only : structure
real (KIND=dp), intent ( IN) : u(:)
real (KIND=dp), intent (INOUT) : var(:,:)
real (KIND=dp), intent ( OUT) : En(0:,:)
logical , intent ( IN) : compute_structure, compute_equations, unperturbed
real (KIND=dp) :: rhs(SIZE(En,DIM=1)), dydu(SIZE(En,DIM=1))
if (compute_structure) then
call structure (var)
endif
if (compute_equations) then
rhs = .....
dydu = .....
En(..,..) = rhs-dydu
endif
end subroutine equations
module physics
CONTAINS
subroutine structure (var)
USE precision , only : dp
USE problem_size, only : nequ
implicit NONE
real (KIND=dp), intent(INOUT) :: var(:,:)
real (KIND=dp), pointer :: rho(SIZE(var,DIM=1))
rho => var(:,nequ+1)
rho = ...
end subroutine structure
end module physics
In equations, the user has to fill up an array En(0:nb_rows,1:nequ), which is supplied from the calling routine, with the nequ equations En = rhs - dy/du at each equation node. u(nb_rows) is the independent variable supplied from above and computed by the user somewhere else before calling the Newton-Raphson module. rhs stands for the right hand side of the differential equations system to be computed by the user. The nequ dependent variables y at the nb_rows nodes in their current state are supplied in the array var(1:nb_rows,1:nequ) (note that possible eigenvalues are supplied in the array locations var(nb_rows-npar+1:nb_rows,:)). The zero index in En has to be filled if the first boundary equations do not fill up a whole block jb1<nequ,jb1>0). In the other cases it's the users choice if to start in En(0,*)(jb1=nequ) or En(1,*)(jb1=0).
Additionally, there are two logical switches, compute_structure and compute_equations, which request action from the user. These serve for the special case when there are more than the original nequ dependent variables y entering the equations. These may be secondary variables like, e.g., the density, which depend on the basic dependent ones. In this case, the user must compute these secondary variables first (for compute_structure = .true.) for the given basic dependent ones and has to put them into the additional space supplied for that purpose in var(*,nequ+1:nvars) (nvars has to be specified in problem_size). Thereafter, they can be used to compute the equations, which has to be done only on request of compute_equations = .true.. Note that the two branches should not be separated by an 'else if' construct, as they may be called concurrently. Due to the automatic Jacobian derivative computation, equations is called nequ+1+npar times.
The third logical switch, unperturbed, indicates that an unperturbed set of variables is provided in var. This switch may be used to store the computed secondary variables into another array which is exported via a module to other routines, e.g., for output to a file.
Computation of, e.g., microphysics, as in a stellar evolution code, may be placed inside an additional module named,say, physics, which is then used inside equations and can simply be called by call structure(var).
subroutine perturbation (y, delta_y)
USE precision, only : dp, zero
implicit NONE
real (KIND=dp), intent (IN) :: y (:)
real (KIND=dp), intent(OUT) :: delta_y (:)
real (KIND=dp), parameter :: factor = 0.001_dp
real (KIND=dp) :: ymin
ymin = MINVAL( ABS(y), MASK=(y /= zero) )
delta_y = factor*(y + SIGN(ymin,y))
end subroutine PERTURBATION
This subroutine gets the y(nb_rows,nequ) array of solution estimates as input and should return an array delta_y(nb_rows,nequ) with the perturbations for y. How to perturb reasonably depends largely on the problem and can even vary during the course of the iteration. As a first try, a relative perturbation of 10-3 to 10-6 may suffices. At zeros, the perturbation should be based on the adjacent nonzero data points.
This module holds some option switches for informational output and have default settings:
| variable | meaning | default |
|---|---|---|
| debug_solver | logical, set .true. if extensive output from the solver is required. | .false. |
| debug_iteration | logical, set .true. for detailed output of the Newton-Raphson iteration process. | .true. |
| timing | logical, set .true. if timing output is desired. | .true. |
This module contains constants for KIND - selection of variables and other useful ones. The KIND parameters should be used instead of the obsolete double precision or even the nonstandard real*8 declarations. This also applies to constants, e.g., 6.67E-8_dp instead of 6.67D-8. This ensures portability and easy maintenance.
| sp | integer, KIND - number for single precision. |
| dp | integer, KIND - number for double precision. |
| one...four | Kind dp constants 1,2,3,4 |