Solving the equations
By default, Jutul solves a system as a fully-coupled implicit system of equations discretized with a two-point flux approximation with single-point upwind.
Newton's method
The standard way of solving a system of non-linear equations is by Newton's method (also known as Newton-Raphson's method). A quick recap: For a vector valued residual
JutulDarcy solves systems that generally have both non-smooth behavior and physical constraints on the values for
Here,
Starting with
Linear solvers and linear systems
For most practical applications it is not feasible or efficient to invert the Jacobian. JutulDarcy uses preconditioned iterative solvers by default, but it is possible to use direct solvers as well when working with smaller models. The high level interface for setting up a reservoir model setup_reservoir_model
has an optional block_backend=true
keyword argument that determines the matrix format, and consequently the linear solver type to be used.
Direct solvers
If block_backend
is set to false
, Jutul will assemble into the standard Julia CSC sparse matrix with Float64
elements and Julia's default direct solver will be used. It is also possible to use other Julia solvers on this system, but the default preconditioners assume that block backend is enabled.
Iterative solver
If block_backend
is set to true
, Jutul will by default use a constrained-pressure residual (CPR) preconditioner for BiCGStab. Jutul relies on Krylov.jl for iterative solvers. The main function that selects the linear solver is reservoir_linsolve
that allows for the selection of different preconditioners and linear solvers.
JutulDarcy.reservoir_linsolve Function
reservoir_linsolve(model, precond = :cpr; <keyword arguments>)
Set up iterative linear solver for a reservoir model from setup_reservoir_model
.
Arguments
model
: Reservoir model that will linearize the equations for the linear solverprecond=:cpr
: Preconditioner type to use: Either :cpr (Constrained-Pressure-Residual) or :ilu0 (block-incomplete-LU) (no effect ifsolver = :direct
).v=0
: verbosity (can lead to a large amount of output)solver=:bicgstab
: the symbol of a Krylov.jl solver (typically :gmres or :bicgstab)update_interval=:once
: how often the CPR AMG hierarchy is reconstructed (:once, :iteration, :ministep, :step)update_interval_partial=:iteration
: how often the pressure system is updated in CPRmax_coarse
: max size of coarse level if using AMGcpr_type=nothing
: type of CPR (:true_impes
,:quasi_impes
ornothing
for automatic)partial_update=true
: perform partial update of CPR preconditioner outside of AMG update (see above)rtol=1e-3
: relative tolerance for the linear solvermax_iterations=100
: limit for linear solver iterations
Additional keywords are passed onto the linear solver constructor.
Jutul.GenericKrylov Type
GenericKrylov(solver = :gmres; preconditioner = nothing; <kwarg>)
Solver that wraps Krylov.jl
with support for preconditioning.
Single model (only porous medium)
If the model is a single model (e.g. only a reservoir) the matrix format is a block-CSC matrix that combines Julia's builtin sparse matrix format with statically sized elements from the StaticArrays.jl package. If we consider the two-phase immiscible system from Multi-phase, immiscible flow we have a pair of equations
This block system has several advantages:
We immediately get access to more powerful version of standard Julia preconditioners provided that all operations used are applicable for matrices and are applied in the right commutative order. For example, JutulDarcy uses the ILUZero.jl package when a CSC linear system is preconditioned with incomplete LU factorization with zero fill-in.
Sparse matrix vector products are much more efficient as less indicies need to be looked up for each element wise multiplication.
Performing local reductions over variables is much easier when they are located in a local matrix.
Constrained Pressure Residual
The CPR preconditioner [3, 4] CPRPreconditioner
is a multi-stage physics-informed preconditioner that seeks to decouple the global pressure part of the system from the local transport part. In the limits of incompressible flow without gravity it can be thought of as an elliptic / hyperbolic splitting. We also implement a special variant for the adjoint system that is similar to the treatment described in [5].
JutulDarcy.CPRPreconditioner Type
CPRPreconditioner(p = default_psolve(), s = ILUZeroPreconditioner(); strategy = :quasi_impes, weight_scaling = :unit, update_frequency = 1, update_interval = :iteration, partial_update = true)
Construct a constrained pressure residual (CPR) preconditioner.
By default, this is a AMG-BILU(0) version (algebraic multigrid for pressure, block-ILU(0) for the global system).
The short version of the CPR preconditioner can be motivated by our test system:
For simplicity, we assume that there is no gravity, source terms, or compressibility. Each equation can then be divided by their respective densities and summed up to produce a pressure equation:
The final equation is the variable coefficient Poisson equation and is referred to as the incompressible pressure equation for a porous media. We know that algebraic multigrid preconditioners (AMG) are highly efficient for linear systems made by discretizing this equation. The idea in CPR is to exploit this by constructing an approximate pressure equation that is suited for AMG inside the preconditioner.
Constructing the preconditioner is done in two stages:
First, weights for each equation is found locally in each cell that decouples the time derivative from the non-pressure variables. In the above example, this was the true IMPES weights (dividing by density). JutulDarcy supports analytical true IMPES weights for some systems, numerical true IMPES weights for all systems and quasi IMPES weights for all systems.
A pressure equation is formed by weighting each equation by the respective weights and summing. We then have two systems: The pressure system
with scalar entries and the full system that has block structure.
During the linear solve, the preconditioner is then made up of two broad stages: First, a preconditioner is applied to the pressure part (typically AMG), then the full system is preconditioned (typically ILU(0)) after the residual has been corrected by the pressure estimate:
Form weighted pressure residual
. Apply pressure preconditioer
: . Correct global residual
where expands the pressure update to the full system vector, with zero entries outside the pressure indices. Precondition the full system
Correct the global update with the pressure to obtain the final update:
Multi model (porous medium with wells)
If a model is a porous medium with wells, the same preconditioners can be used, but an additional step is required to incorporate the well system. In practical terms, this means that our linearized system is expanded to multiple linear systems:
Here,
The well system is generally much smaller than the reservoir system and can be solved by a direct solver. We would like to reuse the block preconditioners defined for the base system. The approach we use is a Schur complement approach to solve the full system. If we linearly eliminate the dependence of the reservoir equations on the well primary variables, we obtain the reduced system:
We can then solve the system in terms of the reservoir degrees of freedom where the system is a block linear system and we already have a working preconditioner:
Once that system is solved for
Efficiency of Schur complement
Explicitly forming the matrix