ad-blackoil: Black-oil solvers

Models and examples that extend the MRST AD-OO framework found in the ad-core module to black-oil problems. More specifically, the module adds additional models that implement the black-oil equations for multiphase, miscible, compressible flow. Included in the module are also a wide variety of examples validating the solvers against a commercial simulator on standard test cases.

The module includes single-phase, two-phase and three-phase solvers. The three-phase solvers has optional support for problems where gas can dissolve into oil (Rs) and/or oil can vaporize into the gas phase (Rv). The solvers support source terms, boundary conditions and complex wells with changing controls, production limits and multiple segments. Standard benchmark cases (SPE1, SPE9) are included together with comparison results from a commercial simulator.

Models

Base classes

Contents

MODELS

Files
AquiferBlackOilModel - GenericBlackOilModel - ThreePhaseBlackOilModel - Three phase with optional dissolved gas and vaporized oil TwoPhaseOilWaterModel - Two phase oil/water system without dissolution WaterModel - Single phase water model. WaterThermalModel - Single phase water model with thermal effects. Should be considered
class ThreePhaseBlackOilModel(G, rock, fluid, varargin)

Bases: ReservoirModel

Three phase with optional dissolved gas and vaporized oil

getPrimaryVariables(model, state)

Get primary variables from state, before a possible initialization as AD.

getScalingFactorsCPR(model, problem, names, solver)

Get approximate, impes-like pressure scaling factors

validateModel(model, varargin)

Validate model.

validateState(model, state)

Check parent class

disgas = None

Flag deciding if oil can be vaporized into the gas phase

drsMaxRel = None

Maximum absolute Rs/Rv increment

vapoil = None

Maximum relative Rs/Rv increment

class TwoPhaseOilWaterModel(G, rock, fluid, varargin)

Bases: ThreePhaseBlackOilModel

Two phase oil/water system without dissolution

class WaterModel(G, rock, fluid, varargin)

Bases: ReservoirModel

Single phase water model.

class WaterThermalModel(G, rock, fluid, varargin)

Bases: WaterModel

Single phase water model with thermal effects. Should be considered experimental and intentionally undocumented, as this feature is subject to change in the future.

insertWellEquations(model, eqs, names, types, wellSol0, wellSol, wellVars, wellMap, p, mob, rho, hW, components, dt, opt)

Utility function for setting up the well equations and adding source terms for black-oil like models. Note that this currently assumes that the first nPh equations are the conservation equations, according to the canonical MRST W-O-G ordering,

Utilities

Contents

UTILS

Files
assignWellValuesFromControl - Assign wellSol values when values are set as controls calculateHydrocarbonsFromStatusBO - Compute solution variables for the gas/oil/rs/rv-variable in black-oil computeAquiferFluxes - Undocumented Utility Function computeFlashBlackOil - Compute flash for a black-oil model with disgas/vapoil computeInitAquifer - Undocumented Utility Function equationsBlackOil - Generate linearized problem for the black-oil equations equationsOilWater - Generate linearized problem for the two-phase oil-water model equationsWater - Generate linearized problem for the single-phase water model equationsWaterThermal - Get linearized problem for single phase water system with black oil-style getbG_BO - Utility function for evaluating the reciprocal gas formation volume getbO_BO - Utility function for evaluating the reciprocal oil formation volume getCapillaryPressureBO - Compute oil-water and oil-gas capillary pressures getCellStatusVO - Get status flags for each cell in a black-oil model getDeckEGG - Get the parsed deck for the EGG model getFluxAndPropsGas_BO - Get flux and properties for the gas phase for a black-oil problem getFluxAndPropsOil_BO - Get flux and properties for the oil phase for a black-oil problem getFluxAndPropsWater_BO - Get flux and properties for the water phase for a black-oil problem getPolymerShearMultiplier - Compute the flux multiplier due to polymer shear thinning/thickening getWellPolymer - Undocumented Utility Function updateStateBlackOilGeneric - Generic update function for blackoil-like models
assignWellValuesFromControl(model, wellSol, W, wi, oi, gi)

Assign wellSol values when values are set as controls

Synopsis:

wellSol = assignWellValuesFromControl(model, wellSol, W, wi, oi, gi)

Description:

Well rates and pressures can be both controls and solution variables, depending on the problem. For a subset of possible well controls, this function explicitly assigns the values to the wellSol.

Parameters:
  • model – ReservoirModel-derived subclass.
  • wellSol – wellSol to be updated.
  • W – Well struct used to create wellSol.
  • oi, gi (wi,) – Indices for water, oil and gas respectively in the .compi field of the well.
Returns:

wellSol – Updated wellSol where fields corresponding to assigned controls have been modified.

See also

WellModel

calculateHydrocarbonsFromStatusBO(fluid, status, sO, x, rs, rv, pressure, disgas, vapoil)

Compute solution variables for the gas/oil/rs/rv-variable in black-oil

Synopsis:

[sG, rs, rv, rsSat, rvSat] = ...

calculateHydrocarbonsFromStatusBO(model, status, sO, x, rs, rv, pressure)

Description:

The ThreePhaseBlackOil model has a single unknown that represents either gas, oil, oil in gas (rs) and gas in oil (rv) on a cell-by-cell basis. The purpose of this function is to easily compute the different quantities from the “x”-variable and other properties, with correct derivatives if any of them are AD-variables.

Parameters:
  • model – ThreePhaseBlackOilModel-derived class. Determines if vapoil/disgas is being used and contains the fluid model.
  • status – Status flag as defined by “getCellStatusVO”
  • sO – The tentative oil saturation.
  • x – Variable that is to be decomposed into sG, sO, rs, rv, …
  • rv (rs,) – Dissolved gas, vaporized oil
  • pressure – Reservoir oil pressure
  • disgas – true if dissolved gas should be taken into account
  • vapoil – true if vaporized oil should be taken into account
Keyword Arguments:
 

‘field’

RETURNS:

See also

equationsBlackOil, getCellStatusVO

computeAquiferFluxes(model, state, dt)

Undocumented Utility Function

computeFlashBlackOil(state, state0, model, status)

Compute flash for a black-oil model with disgas/vapoil

Synopsis:

state = computeFlashBlackOil(state, state0, model, status)

Description:

Compute flash to ensure that dissolved properties are within physically reasonable values, and simultanously avoid that properties go far beyond the saturated zone when they were initially unsaturated and vice versa.

Parameters:
  • state – State where saturations, rs, rv have been updated due to a Newton-step.
  • state0 – State from before the linearized update.
  • model – The ThreePhaseBlackOil derived model used to compute the update.
  • status – Status flags from getCellStatusVO, applied to state0.
Returns:

state – Updated state where saturations and values are chopped near phase transitions.

Note

Be mindful of the definition of state0. It is not necessarily the state at the previous timestep, but rather the state at the previous nonlinear iteration!

computeInitAquifer(model, initState, initaqvolumes)

Undocumented Utility Function

equationsBlackOil(state0, state, model, dt, drivingForces, varargin)

Generate linearized problem for the black-oil equations

Synopsis:

[problem, state] = equationsBlackOil(state0, state, model, dt, drivingForces)

Description:

This is the core function of the black-oil solver. This function assembles the residual equations for the conservation of water, oil and gas, as well as required well equations. By default, Jacobians are also provided by the use of automatic differentiation.

Oil can be vaporized into the gas phase if the model has the vapoil property enabled. Analogously, if the disgas property is enabled, gas is allowed to dissolve into the oil phase. Note that the fluid functions change depending on vapoil/disgas being active and may have to be updated when the property is changed in order to run a successful simulation.

Parameters:
  • state0 – Reservoir state at the previous timestep. Assumed to have physically reasonable values.
  • state – State at the current nonlinear iteration. The values do not need to be physically reasonable.
  • model – ThreePhaseBlackOilModel-derived class. Typically, equationsBlackOil will be called from the class getEquations member function.
  • dt – Scalar timestep in seconds.
  • drivingForces – Struct with fields: * W for wells. Can be empty for no wells. * bc for boundary conditions. Can be empty for no bc. * src for source terms. Can be empty for no sources.
Keyword Arguments:
 
  • ‘Verbose’ – Extra output if requested.
  • ‘reverseMode’ – Boolean indicating if we are in reverse mode, i.e. solving the adjoint equations. Defaults to false.
  • ‘resOnly’ – Only assemble residual equations, do not assemble the Jacobians. Can save some assembly time if only the values are required.
  • ‘iterations’ – Nonlinear iteration number. Special logic happens in the wells if it is the first iteration.
Returns:
  • problem – LinearizedProblemAD class instance, containing the water, oil and gas conservation equations, as well as well equations specified by the FacilityModel class.
  • state – Updated state. Primarily returned to handle changing well controls from the well model.

See also

equationsOilWater, ThreePhaseBlackOilModel

equationsOilWater(state0, state, model, dt, drivingForces, varargin)

Generate linearized problem for the two-phase oil-water model

Synopsis:

[problem, state] = equationsOilWater(state0, state, model, dt, drivingForces)

Description:

This is the core function of the two-phase oil-water solver. This function assembles the residual equations for the conservation of water and oil as well as required well equations. By default, Jacobians are also provided by the use of automatic differentiation.

Parameters:
  • state0 – Reservoir state at the previous timestep. Assumed to have physically reasonable values.
  • state – State at the current nonlinear iteration. The values do not need to be physically reasonable.
  • model – TwoPhaseOilWaterModel-derived class. Typically, equationsOilWater will be called from the class getEquations member function.
  • dt – Scalar timestep in seconds.
  • drivingForces – Struct with fields: * W for wells. Can be empty for no wells. * bc for boundary conditions. Can be empty for no bc. * src for source terms. Can be empty for no sources.
Keyword Arguments:
 
  • ‘Verbose’ – Extra output if requested.
  • ‘reverseMode’ – Boolean indicating if we are in reverse mode, i.e. solving the adjoint equations. Defaults to false.
  • ‘resOnly’ – Only assemble residual equations, do not assemble the Jacobians. Can save some assembly time if only the values are required.
  • ‘iterations’ – Nonlinear iteration number. Special logic happens in the wells if it is the first iteration.
Returns:
  • problem – LinearizedProblemAD class instance, containing the water and oil conservation equations, as well as well equations specified by the WellModel class.
  • state – Updated state. Primarily returned to handle changing well controls from the well model.

See also

equationsBlackOil, TwoPhaseOilWaterModel

equationsWater(state0, state, model, dt, drivingForces, varargin)

Generate linearized problem for the single-phase water model

Synopsis:

[problem, state] = equationsWater(state0, state, model, dt, drivingForces)

Description:

This is the core function of the single-phase water solver with black-oil style properties. This function assembles the residual equations for the conservation of water and oil as well as required well equations. By default, Jacobians are also provided by the use of automatic differentiation.

Parameters:
  • state0 – Reservoir state at the previous timestep. Assumed to have physically reasonable values.
  • state – State at the current nonlinear iteration. The values do not need to be physically reasonable.
  • model – WaterModel-derived class. Typically, equationsWater will be called from the class getEquations member function.
  • dt – Scalar timestep in seconds.
  • drivingForces – Struct with fields: * W for wells. Can be empty for no wells. * bc for boundary conditions. Can be empty for no bc. * src for source terms. Can be empty for no sources.
Keyword Arguments:
 
  • ‘Verbose’ – Extra output if requested.
  • ‘reverseMode’ – Boolean indicating if we are in reverse mode, i.e. solving the adjoint equations. Defaults to false.
  • ‘resOnly’ – Only assemble residual equations, do not assemble the Jacobians. Can save some assembly time if only the values are required.
  • ‘iterations’ – Nonlinear iteration number. Special logic happens in the wells if it is the first iteration.
Returns:
  • problem – LinearizedProblemAD class instance, containing the equation for the water pressure, as well as well equations specified by the WellModel class.
  • state – Updated state. Primarily returned to handle changing well controls from the well model.

See also

equationsBlackOil, ThreePhaseBlackOilModel

equationsWaterThermal(state0, state, model, dt, drivingForces, varargin)

Get linearized problem for single phase water system with black oil-style properties

getCapillaryPressureBO(fluid, sW, sG)

Compute oil-water and oil-gas capillary pressures

Synopsis:

[pcOW, pcOG] = getCapillaryPressureBO(f, sW, sG)

Description:

Compute the capillary pressures relative to the oil pressure

Parameters:
  • fluid – AD fluid model, from for example initDeckADIFluid or initSimpleADIFluid.
  • sW – Water saturation. Used to evaluate the water-oil capillary pressure.
  • sG – Gas saturation. Used to evaluate the oil-gas capillary pressure.
Returns:
  • pcOW – Pressure difference between the oil and water pressures. The water phase pressure can be computed as pW = pO - pcOW;
  • pcOG – Pressure difference between the oil and gas pressures. The water phase pressure can be computed as pG = pO + pcOG;

Note

Mind the different sign for the computation of the phase pressures above!

getCellStatusVO(sO, sW, sG, varargin)

Get status flags for each cell in a black-oil model

Synopsis:

st = getCellStatusVO(model, state, sO, sW, sG)

Description:

Get the status flags for the number of phases present.

Parameters:
  • sO – Oil saturation. One value per cell in the simulation grid.
  • sW – Water saturation. One value per cell in the simulation grid.
  • sG – Gas saturation. One value per cell in the simulation grid.
Keyword Arguments:
 
  • status – status that can be provided fom the state for which the status flags are to be computed.
  • vapoil – true if there can be vaporized oil
  • disgas – true if there can be dissolved gas
Returns:

st

Cell array with three columns with one entry per cell. The

interpretation of each flag:

Col 1: A cell is flagged as true if oil is present, but gas is not present. Col 2: A cell is flagged as true if gas is present, but oil is not present. Col 3: A cell is flagged as true if both gas and oil are present and true three-phase flow is occuring.

See also

ThreePhaseBlackOilModel

getDeckEGG(varargin)

Get the parsed deck for the EGG model

getFluxAndPropsGas_BO(model, pO, sG, krG, T, gdz, rv, isSat)

Get flux and properties for the gas phase for a black-oil problem

Synopsis:

[vG, bG, mobG, rhoG, pG, upcg, dpG] = ...
   getFluxAndPropsOil_BO(model, pO, sG, krG, T, gdz, rv, isSat)

Description:

Utility function for evaluating mobilities, densities and fluxes for the gas phase in the black-oil (BO)-model

Parameters:
  • model – ThreePhaseBlackOil or derived subclass instance
  • p – Oil phase pressure. One value per cell in simulation grid.
  • sG – Gas phase saturation. One value per cell in simulation grid.
  • krG – Gas phase relative permeability, possibly evaluated some three-phase relperm function. One value per cell in simulation grid.
  • T – Transmissibility for the internal interfaces.
  • gdz – Let dz be the gradient of the cell depths, on each interface.
  • gdz is the dot product of the dz with the gravity vector of the (Then) –
  • See getGravityGradient for more information. (model.) –
  • rv – Amount of oil vaporized into the gas phase. Use to evaluate the viscosity and density. One value per cell in simulation grid.
  • isSat – Boolean indicator if the gas in the cell is saturated with oil.
  • indicates that the maximum vaporized gas is present inside the gas (True) –
  • in the cell, and that we need to look at the saturated tables (phase) –
  • evaluating rv (when) – dependent properties. One value per cell in
  • grid. (simulation) –
Returns:
  • vG – Gas flux, at reservoir conditions, at each interface.
  • bG – Gas reciprocal formation volume factors. One value per cell in simulation grid.
  • rhoG – Gas density in each cell.
  • pG – Gas phase pressure. One value per cell in simulation grid. Pressure will be different from the oil pressure if capillary pressure is present in the model.
  • upcg – Upwind indicators for the gas phase. One value per interface.
  • dpG – Phase pressure differential for each interface. Was used to evaluate the phase flux.

See also

getFluxAndPropsOil_BO, getFluxAndPropsWater_BO

getFluxAndPropsOil_BO(model, p, sO, krO, T, gdz, rs, isSat)

Get flux and properties for the oil phase for a black-oil problem

Synopsis:

[vO, bO, mobO, rhoO, p, upco, dpO] = ...
   getFluxAndPropsOil_BO(model, p, sO, krO, T, gdz, rs, isSat)

Description:

Utility function for evaluating mobilities, densities and fluxes for the oil phase in the black-oil (BO)-model

Parameters:
  • model – ThreePhaseBlackOil or derived subclass instance
  • p – Oil phase pressure. One value per cell in simulation grid.
  • sO – Oil phase saturation. One value per cell in simulation grid.
  • krO – Oil phase relative permeability, possibly evaluated some three-phase relperm function. One value per cell in simulation grid.
  • T – Transmissibility for the internal interfaces.
  • gdz – Let dz be the gradient of the cell depths, on each interface.
  • gdz is the dot product of the dz with the gravity vector of the (Then) –
  • See getGravityGradient for more information. (model.) –
  • rs – Amount of gas dissolved into the oil phase. Use to evaluate the viscosity and density. One value per cell in simulation grid.
  • isSat – Boolean indicator if the oil in the cell is saturated with gas.
  • indicates that there is free gas in the cell, and that we need to (True) –
  • at the saturated tables when evaluating rs (look) – dependent properties.
  • value per cell in simulation grid. (One) –
Returns:
  • vO – Oil flux, at reservoir conditions, at each interface.
  • bO – Oil reciprocal formation volume factors. One value per cell in simulation grid.
  • rhoO – Oil density in each cell.
  • p – Oil phase pressure. One value per cell in simulation grid. Should be unmodified. Included simply to have parity with analogous gas and water functions.
  • upco – Upwind indicators for the oil phase. One value per interface.
  • dpO – Phase pressure differential for each interface. Was used to evaluate the phase flux.

See also

getFluxAndPropsGas_BO, getFluxAndPropsWater_BO

getFluxAndPropsWater_BO(model, pO, sW, krW, T, gdz)

Get flux and properties for the water phase for a black-oil problem

Synopsis:

[vW, bW, mobW, rhoW, pW, upcw, dpW] = ...
   getFluxAndPropsWater_BO(model, pO, sW, krW, T, gdz)

Description:

Utility function for evaluating mobilities, densities and fluxes for the water phase in the black-oil (BO)-model

Parameters:
  • model – ThreePhaseBlackOil or derived subclass instance
  • pO – Oil phase pressure. One value per cell in simulation grid.
  • sW – Water phase saturation. One value per cell in simulation grid.
  • krW – Water phase relative permeability, possibly evaluated some three-phase relperm function. One value per cell in simulation grid.
  • T – Transmissibility for the internal interfaces.
  • gdz – Let dz be the gradient of the cell depths, on each interface.
  • gdz is the dot product of the dz with the gravity vector of the (Then) –
  • See getGravityGradient for more information. (model.) –
Returns:
  • vW – Water flux, at reservoir conditions, at each interface.
  • bW – Water reciprocal formation volume factors. One value per cell in simulation grid.
  • rhoW – Water density in each cell.
  • pW – Water phase pressure. One value per cell in simulation grid. Pressure will be different from the oil pressure if capillary pressure is present in the model.
  • upcw – Upwind indicators for the water phase. One value per interface.
  • dpW – Phase pressure differential for each interface. Was used to evaluate the phase flux.

See also

getFluxAndPropsGas_BO, getFluxAndPropsOil_BO

getPolymerShearMultiplier(model, VW0, muWmult)

Compute the flux multiplier due to polymer shear thinning/thickening

Synopsis:

mult           = getPolymerShearMultiplier(model, VW0, muWmult)
[mult,VW]      = getPolymerShearMultiplier(...)
[mult,VW,iter] = getPolymerShearMultiplier(...)

Description:

The viscoisty of a polymer solution may be changed by the shear rate, which is related to the flow velocity. This function returns a flux multiplier mult, such that

vWsh = vW.*mult, and, vPsh = vP.*mult,

where vWsh and vPsh are the shear modified fluxes for pure water and water with polymer, respectively.

The input water face velocity is computed as
VW = vW/(poro*area)

where poro is the average porosity between neighboring cells, and area is the face area.

Parameters:
  • model – Model structure
  • VW0 – Water velocity on faces without shear thinning
  • muWmult – Viscosity multiplier on faces
  • iter – Number of non-linear iterations
Returns:
  • mult – Flux multiplier (reciprocal of the viscosity multiplier)
  • VW – Water velocity as solution of nonlinear shear equation
getWellPolymer(W)

Undocumented Utility Function

getbG_BO(model, p, rv, isLiquid)

Utility function for evaluating the reciprocal gas formation volume factor function.

getbO_BO(model, p, rs, isSaturated)

Utility function for evaluating the reciprocal oil formation volume factor function.

updateStateBlackOilGeneric(model, state, problem, dx, drivingForces)

Generic update function for blackoil-like models

Synopsis:

state = updateStateBlackOilGeneric(model, state, problem, dx)

Description:

This is a relatively generic update function that can dynamically work out where increments should go based on the model implementation. It can be used for simple models or used as inspiration for more exotic models.

Presently handles either 2/3-phase with disgas/vapoil or n-phase without dissolution.

Parameters:
  • model – PhysicalModel subclass.
  • state – State which is to be updated.
  • problem – Linearized problem from which increments were obtained
  • dx – Increments created by solving the linearized problem.
  • drivingForces – Wells etc.
Returns:

state – Updated state.

Examples

load modules

Generated from aquifertest.m

mrstModule add mrst-gui deckformat ad-props ad-core ad-blackoil

mrstVerbose true
gravity on
close all

run two test cases

datadir = getDatasetPath('aquifertest', 'download', true);

for icase = 1 : 2
fnroot = sprintf('2D_OILWATER_AQ_ECLIPSE_%d', icase);
    fn = fullfile(datadir, [fnroot, '.DATA']);
    [model, initState, schedule] = setupAquifertest(fn);
    [wellSols, states, schedulereport] = simulateScheduleAD(initState, model, ...
                                                      schedule);
    statesEcl = loadAquiferEclipseResult(datadir, fnroot);
No converter needed in section 'REGIONS'.
No converter needed in section 'SUMMARY'.
Adding 40 artificial cells at top/bottom

Processing regular i-faces
 Found 144 new regular faces
Elapsed time is 0.018308 seconds.

...
No converter needed in section 'REGIONS'.
No converter needed in section 'SUMMARY'.
Adding 40 artificial cells at top/bottom

Processing regular i-faces
 Found 144 new regular faces
Elapsed time is 0.007527 seconds.

...

plots

G = model.G;
    figure
    plotToolbar(G, states);
    view([2, 2]);
    colorbar
    title(sprintf('MRST computation - Case %d', icase));
    figure
    plotToolbar(G, statesEcl);
    view([2, 2]);
    colorbar
    title(sprintf('Eclipse computation - Case %d', icase));

    p = states{end}.pressure;
    pecl = statesEcl{end}.pressure;
    pecl = pecl*barsa;
    err = (p - pecl)./pecl;
    figure
    plotCellData(G, err);
    view([2, 2]);
    colorbar
    title(sprintf('Relative difference - Case %d', icase));
_images/aquifertest_01.png
_images/aquifertest_02.png
_images/aquifertest_03.png
_images/aquifertest_04.png
_images/aquifertest_05.png
_images/aquifertest_06.png
nd

% <html>
% <p><font size="-1

Example demonstrating use of boundary conditions for pressure support

Generated from blackoilSectorModelExample.m

We take the SPE1 fluid model to get a simple blackoil-model. We make the aqueous phase mobile by manually setting the relative permeability.

mrstModule add ad-core ad-blackoil ad-props
[~, ~, fluid, deck] = setupSPE1();
fluid.krW = @(s) s.^2;
No converter needed in section 'REGIONS'.
No converter needed in section 'SUMMARY'.
Adding 200 artificial cells at top/bottom

Processing regular i-faces
 Found 550 new regular faces
Elapsed time is 0.001533 seconds.

...

Set up a simple grid with an initial state

We create a small grid with initial water and oil saturation, as well as a Rs (dissolved gas in oil-ratio) of 200. This undersaturated reservoir does not have any free gas.

G = cartGrid([50, 50, 1], [1000, 1000, 100]*meter);
G = computeGeometry(G);

rock = makeRock(G, 0.3*darcy, 0.3);

% Define initial composition and pressure
s0 = [0.2, 0.8, 0];
p0 = 300*barsa;
state0 = initResSol(G, p0, s0);
state0.rs = repmat(200, G.cells.num, 1);
% Black oil with disgas
model = ThreePhaseBlackOilModel(G, rock, fluid, 'disgas', true);
Processing Cells    1 : 2500 of 2500 ... done (0.02 [s], 1.16e+05 cells/second)
Total 3D Geometry Processing Time = 0.022 [s]

Set up driving forces

We will operate a single producer well at a fixed rate. In addition, we define a set of boundary conditions at the vertical boundary of the domain with a fixed pressure and composition equal to the initial values of the reservoir itself.

% Time horizon
T = 10*year;
% Produce 1/4 PV at surface conditions controlled by oil rate
ij = ceil(G.cartDims./2);
W = verticalWell([], G, rock, ij(1), ij(2), [], ...
    'val',      -0.25*sum(model.operators.pv)/T, ...
    'type',     'orat',...
    'comp_i',   [1, 1, 1]/3, ...
    'sign',     -1, ...
    'name',     'Producer');
% Define a lower bhp limit so that we have a lower bound on the reservoir
% pressure during the simulation.
W.lims.bhp = 100*barsa;

% Define boundary conditions
bc = [];
sides = {'xmin', 'xmax', 'ymin', 'ymax'};
for side = 1:numel(sides)
    bc = pside(bc, G, sides{side}, p0, 'sat', s0);
end
% We define initial Rs value for the BC. We can either supply a single
% value per interface or one for all interfaces. In this case, we supply a
% single value for all interfaces since there is no variation in the
% initial conditions.
%
% To define dissolution per face, a matrix of dimensions numel(bc.face)x3x3
% would have to be set.
bc.dissolution = [1, 0,   0;...
% Water fractions in phases
                  0, 1,   0; ...% Oil fractions in phases
                  0, 200, 1];   % Gas fractions in phases
% Simple uniform schedule with initial rampup
dt = rampupTimesteps(T, 30*day);

% Define schedule with well and constant pressure boundary conditions
schedule = simpleSchedule(dt, 'W', W, 'bc', bc);
[ws, states] = simulateScheduleAD(state0, model, schedule);

% Define schedule with well and closed boundaries
schedule2 = simpleSchedule(dt, 'W', W, 'bc', []);
[ws_c, states_c] = simulateScheduleAD(state0, model, schedule2);
Defaulting reference depth to top of formation for well Producer. Please specify 'refDepth' optional argument to 'addWell' if you require bhp at a specific depth.
*****************************************************************
********** Starting simulation:   130 steps,  3650 days *********
*****************************************************************
Preparing model for simulation...
Model ready for simulation...
Preparing schedule for simulation...
Schedule ready for simulation...
...

Compare the two results

We observe that the simulation with constant boundary pressure has a very different pressure build up than the same problem with closed boundaries. Note also that once the bhp limit is reached, the well switches controls in the closed model, and the oil production rate changes.

plotWellSols({ws, ws_c}, cumsum(schedule.step.val), ...
    'datasetnames', {'Constant pressure', 'Closed boundaries'})
_images/blackoilSectorModelExample_01.png

Plot the gas in the well cells

If we do not have open boundaries, the pressure will eventually drop as a result of the large removed volumes of oil and dissolved gas. For the model with closed boundaries, we get drop-out of free gas.

as_open = cellfun(@(x) x.s(W.cells(1), 3), states);
gas_closed = cellfun(@(x) x.s(W.cells(1), 3), states_c);

clf;
plot([gas_open, gas_closed], 'linewidth', 2)
ylabel('S_g')
xlabel('Step #');
legend('Constant pressure', 'Closed boundaries');

% <html>
% <p><font size="-1
_images/blackoilSectorModelExample_02.png
mrstModule add ad-core ad-blackoil deckformat ad-props linearsolvers
mrstVerbose on
[G, rock, fluid, deck, state] = setupSPE1();
[state0, model, schedule] = initEclipseProblemAD(deck);
No converter needed in section 'REGIONS'.
No converter needed in section 'SUMMARY'.
Adding 200 artificial cells at top/bottom

Processing regular i-faces
 Found 550 new regular faces
Elapsed time is 0.006345 seconds.

...

Fully-implicit

Generated from blackoilTimeIntegrationExample.m

The default discretization in MRST is fully-implicit. Consequently, we can use the model as-is.

implicit = packSimulationProblem(state0, model, schedule, 'SPE1_ex', 'Name', 'Fully-Implicit');

Explicit solver

This solver has a time-step restriction based on the CFL condition in each cell. The solver estimates the time-step before each solution.

model_explicit = model.validateModel();
% Get the flux discretization
flux = model_explicit.FluxDiscretization;
% Use explicit form of flow state
fb = ExplicitFlowStateBuilder();
flux = flux.setFlowStateBuilder(fb);
model_explicit.FluxDiscretization = flux;
explicit = packSimulationProblem(state0, model_explicit, schedule, 'SPE1_ex', 'Name', 'Explicit');

Adaptive implicit

We can solve some cells implicitly and some cells explicitly based on the local CFL conditions. For many cases, this amounts to an explicit treatment far away from wells or driving forces. The values for estimated composition CFL and saturation CFL to trigger a switch to implicit status can be adjusted.

model_aim = model.validateModel();
flux = model_aim.FluxDiscretization;
fb = AdaptiveImplicitFlowStateBuilder();
flux = flux.setFlowStateBuilder(fb);
model_aim.FluxDiscretization = flux;
aim = packSimulationProblem(state0, model_aim, schedule, 'SPE1_ex', 'Name', 'Adaptive-Implicit (AIM)');

Simulate the problems

problems = {implicit, explicit, aim};
simulatePackedProblem(problems);
*******************************************************
* Case "SPE1_ex" (Fully-Implicit)                     *
* Description: "Fully-Implicit_GenericBlackOilModel"  *
*******************************************************
-> Complete output found, nothing to do here.

*************************************************
* Case "SPE1_ex" (Explicit)                     *
...

Get output and plot the well results

There are oscillations in the well curves. Increasing the CFL limit beyond unity will eventually lead to oscillations.

[ws, states, reports, names, T] = getMultiplePackedSimulatorOutputs(problems);
plotWellSols(ws, T, 'datasetnames', names);
Found complete data for SPE1_ex (Fully-Implicit): 120 steps present
Found complete data for SPE1_ex (Explicit): 120 steps present
Found complete data for SPE1_ex (Adaptive-Implicit (AIM)): 120 steps present
_images/blackoilTimeIntegrationExample_01.png

Plot the fraction of cells which are implicit

imp_frac = cellfun(@(x) sum(x.implicit)/numel(x.implicit), states{3});
figure;
plot(100*imp_frac);
title('Number of implicit cells in AIM')
ylabel('Percentage of implicit cells')
xlabel('Step index')
_images/blackoilTimeIntegrationExample_02.png

Plot the time-steps taken

The implicit solvers use the control steps while the explicit solver must solve many substeps.

igure; hold on
markers = {'o', '.', 'x'};
for i = 1:numel(reports)
    dt = getReportMinisteps(reports{i});
    x = (1:numel(dt))/numel(dt);
    plot(x, dt/day, 'marker', markers{i})
end
xlabel('Timestep length [day]')
legend(names)

% <html>
% <p><font size="-1
_images/blackoilTimeIntegrationExample_03.png

Gravity segregation using two phase AD solvers

Generated from blackoilTutorialGravSeg.m

This example demonstrates a simple, but classical test problem for a multiphase solver. The problem we are considering is that of gravity segregation: For a given domain, we let the initial fluid distribution consist of a dense fluid atop of a lighter one. Gravity is the only active force, and bouyancy will over time force the reservoir towards a stable equilibrium in which the denser fluid has switched place with the lighter fluid. The example also demonstrates how to set up a simple schedule (one or more timesteps with a collection of driving forces such as bc and wells).

mrstModule add ad-core ad-blackoil ad-props mrst-gui

Reservoir geometry and petrophysical properties

We begin by defining a simple homogenous reservoir of 1 by 1 by 5 meters and 250 simulation cells.

dims = [5 5 10];
G = cartGrid(dims, [1, 1, 5]*meter);
G = computeGeometry(G);

rock = makeRock(G, 100*milli*darcy, 0.5);
Processing Cells   1 : 250 of 250 ... done (0.00 [s], 6.98e+04 cells/second)
Total 3D Geometry Processing Time = 0.004 [s]

Defining the fluid properties

In this section, we set up a generic fluid suitable for solvers based on automatic differentiation. The fluid model is by default incompressible. We define two phases: Oil and water with significant differences in density and viscosity.

fluid = initSimpleADIFluid('phases', 'WO', ...
                           'mu', [1, 10]*centi*poise, ...
                           'n',  [1, 1], ...
                           'rho', [1000, 700]*kilogram/meter^3);

Defining the model

The model object contains all functions necessary to simulate a mathematical model using the AD-solvers. In our case, we are interested in a standard fully implicit two-phase oil/water model. By passing the grid, rock, and fluid into the model constructor we obtain a model with precomputed operators and quantities that can be passed on to the simulator. We also ensure that gravity is enabled before we call the constructor.

gravity reset on
model = TwoPhaseOilWaterModel(G, rock, fluid);

% Show the model
disp(model)
TwoPhaseOilWaterModel with properties:

                   disgas: 0
                   vapoil: 0
                drsMaxRel: Inf
                drsMaxAbs: Inf
                    fluid: [1×1 struct]
                     rock: [1×1 struct]
...

Set up initial state

We must also define the initial state. The state contains the unstable initial fluid distribution. We do this by setting the water saturation to 1 in all cells and then setting it to zero in cells below a datum point. We defined the water (first phase) density to be 1000 kg/m^3 when we set up the fluid model. Note that we let the pressure be uniform initially. The model is relatively simple, so we do not need to worry about the initial pressure being reasonable, but this is something that should be considered more carefullly for models with e.g., compressibility. To ensure that we got the distribution right, we make a plot of the initial saturation.

[ii, jj, kk] = gridLogicalIndices(G);

lower = kk > 5;
sW = ones(G.cells.num, 1);
sW(lower) = 0;
s = [sW, 1 - sW];

% Finally set up the state
state = initResSol(G, 100*barsa, s);

% Plot the initial water saturation
figure;
plotCellData(G, state.s(:, 1))
axis equal tight off
view(50, 20)
title('Initial water saturation')
colorbar, drawnow
_images/blackoilTutorialGravSeg_01.png

Set up boundary conditions and timesteps

Finally, we add a simple boundary condition of 100 bar pressure at the bottom of the reservoir. This is not strictly required, but the incompressible model will produce singular linear systems if we do not fix the pressure somehow. It is well known that the pressure is not unique for an incompressible model if there are no Dirichlet boundary conditions, as multiple pressures will give the same velocity field. Another alternative would be to add (some) compressibility to our fluid model. Once we have set up the boundary conditions, we define 20 timesteps of 90 days each, which is enough for the state to approach equilibrium. The simulator will cut timesteps if they do not converge, so we are not overly worried about the large initial timestep when the problem is the most stiff. Finally, we combine the timesteps and the boundary conditions into a schedule. A schedule collects multiple timesteps into a single struct and makes it easier to have for example different boundary conditions at different time periods. For this example, however, the schedule is very simple.

bc = [];
bc = pside(bc, G, 'ZMin', 100*barsa, 'sat', [0 1]);

dt = repmat(90*day, 20, 1);

schedule = simpleSchedule(dt, 'bc', bc);

Simulate the problem

Finally, we simulate the problem. We note that the three simple objects define the entire simulation: - state defines the initial values in the reservoir - model defines the partial differential equations, their physical constraints and the numerical discretizations required to advance from one time to the next. - schedule defines any time dependent input parameters as well as the timesteps.

[wellSols, states] = simulateScheduleAD(state, model, schedule, 'Verbose', true);
*****************************************************************
********** Starting simulation:    20 steps,  1800 days *********
*****************************************************************
Preparing model for simulation...
Model ready for simulation...
Preparing schedule for simulation...
Schedule ready for simulation...
Validating initial state...
...

Plot the segregation process

We finish by plotting the water saturation at each timestep, showing how the lighter fluid migrates upward and the heavier fluid downward.

colorbar off; title('T=0');
set(gca,'Position',[.13 .11 .335 .815]); subplot(1,2,2);
for i = 1:numel(states)
    %figure(h); clf;

    % Neat title
    str = ['t=', formatTimeRange(sum(dt(1:i)))];

    % Plotting
    plotCellData(G, states{i}.s(:, 1))
    title(str)

    % Make axis equal to show column structure
    axis equal tight off
    view(50, 20)
    colorbar
    caxis([0, 1])

    drawnow, pause(0.2)
end
_images/blackoilTutorialGravSeg_02.png

Example demonstrating accelerated assembly for faster simulation

Generated from blackoilTutorialMexAcceleration.m

Running larger cases in MRST is possible, but the default parameters of MRST’s solvers are optimized for ease-of-use out of the box with just a standard Matlab installation. We do not wish to require users to have C++ compilers, external software or expensive add-on toolboxes to make use of the software. However, simulation can be achieved much faster if one has access to both - A C++ compiler - An external linear solver with a Matlab interface. This examples demonstrates the use of MEX to seamlessly improve assembly speed for AD-solvers.

mrstModule add ad-core ad-blackoil deckformat ad-props linearsolvers

Define a 70x70x30 grid with uniform properties

Obviously, you can adjust this to test your setup

dims = [70, 70, 30];
pdims = [1000, 1000, 100]*meter;
G = cartGrid(dims, pdims);
G = computeGeometry(G);
rock = makeRock(G, 0.1*darcy, 0.3);
Processing Cells      1 :  18375 of 147000 ... done (0.16 [s], 1.14e+05 cells/second)
Processing Cells  18376 :  36750 of 147000 ... done (0.14 [s], 1.33e+05 cells/second)
Processing Cells  36751 :  55125 of 147000 ... done (0.14 [s], 1.34e+05 cells/second)
Processing Cells  55126 :  73500 of 147000 ... done (0.14 [s], 1.33e+05 cells/second)
Processing Cells  73501 :  91875 of 147000 ... done (0.14 [s], 1.33e+05 cells/second)
Processing Cells  91876 : 110250 of 147000 ... done (0.14 [s], 1.32e+05 cells/second)
Processing Cells 110251 : 128625 of 147000 ... done (0.14 [s], 1.34e+05 cells/second)
Processing Cells 128626 : 147000 of 147000 ... done (0.14 [s], 1.34e+05 cells/second)
...

Set up QFS water injection scenario

As the point of this example is on how to set up the solvers, we just set up a simple injection scenario for a 3D quarter-five-spot problem.

gravity reset on
time = 10*year;
irate = 0.3*sum(poreVolume(G, rock)/time);

W = [];
W = verticalWell(W, G, rock, 1, 1, [], 'comp_i', [1, 0, 0], ...
                'type', 'rate', 'val', irate);
W = verticalWell(W, G, rock, dims(1), dims(2), 1, 'comp_i', [1, 0, 0], ...
                 'type', 'bhp', 'val', 100*barsa);
Defaulting reference depth to top of formation for well W1. Please specify 'refDepth' optional argument to 'addWell' if you require bhp at a specific depth.
Defaulting reference depth to top of formation for well W2. Please specify 'refDepth' optional argument to 'addWell' if you require bhp at a specific depth.

Three-phase fluid model with compressibility and nonlinear flux

fluid = initSimpleADIFluid('n', [2, 3, 1], ...
                           'rho', [1000, 700, 10], ...
                           'mu', [1, 5, 0.1]*centi*poise, ...
                           'c', [0, 1e-6, 1e-4]/barsa);

Do 5 time-steps of 30 days each to test assembly speed

dt = repmat(30*day, 5, 1);
% Alternatively: Run the whole schedule with 200 time-steps.
% dt = rampupTimesteps(time, time/200);
schedule = simpleSchedule(dt, 'W', W);

model = GenericBlackOilModel(G, rock, fluid, 'disgas', false, 'vapoil', false);

Set up mex-accelerated backend and reduced variable set for wells

We use the Diagonal autodiff backend to calculate derivatives. By default, this uses only Matlab, so we also set the “useMex” flag to be true. It will then use C++ versions of most discrete operators during assembly.

model.AutoDiffBackend = DiagonalAutoDiffBackend('useMex', true);
model = model.validateModel();
model.FacilityModel.primaryVariableSet = 'bhp';

Run assembly tests (will compile backends if required)

Note: Benchmarks are obviously not accurate if compilation is performed.

testMexDiagonalOperators(model, 'block_size', 3);
********************************************************
* Face average
* Diagonal: 0.024513s, Diagonal-MEX: 0.013615s (1.80 speedup)
*   Sparse: 0.018307s, Diagonal-MEX: 0.013615s (1.34 speedup)
Value error 0, Jacobian error 0
********************************************************
********************************************************
* Single-point upwind
...

Set up a compiled linear solver

We use a AMGCL-based CPR solver to solve the problem. It is sufficient to have a working C++ compiler, the AMGCL repository and BOOST available to compile it. See the documentation of amgcl_matlab for more details on how to set up these paths.

ncomp = model.getNumberOfComponents();
solver = AMGCL_CPRSolverAD('tolerance', 1e-3, 'block_size', ncomp, ...
                            'useSYMRCMOrdering', true, ...
                            'coarsening', 'aggregation', 'relaxation', 'ilu0');

nls = NonLinearSolver('LinearSolver', solver);

Set up initial state

We define fluid contacts and datum pressure

region = getInitializationRegionsBlackOil(model, [70, 30], 'datum_pressure', 200*barsa);
state0 = initStateBlackOilAD(model, region);

figure(1); clf
plotCellData(G, state0.s(:, [2, 3, 1]));
view(30, 30);
plotWell(G, W);
title('Initial saturations');
_images/blackoilTutorialMexAcceleration_01.png

Run a serial simulation

maxNumCompThreads(1)
[ws0, states0, rep0] = simulateScheduleAD(state0, model, schedule, 'NonLinearSolver', nls);
ans =

     8

*****************************************************************
********** Starting simulation:     5 steps,   150 days *********
*****************************************************************
...

Run a parallel simulation with four threads

maxNumCompThreads(4)
[ws, states, rep] = simulateScheduleAD(state0, model, schedule, 'NonLinearSolver', nls);
% Reset max number of threads
N = maxNumCompThreads('automatic');
ans =

     1

*****************************************************************
********** Starting simulation:     5 steps,   150 days *********
*****************************************************************
...

Plot the final saturations

figure(1); clf
plotCellData(G, states{end}.s(:, [2, 3, 1]));
view(30, 30);
plotWell(G, W);
title('Final saturations');
_images/blackoilTutorialMexAcceleration_02.png

Plot timings

We plot the total time, the assembly (linearization) time, the linear solver time and the linear solver preparation time. The assembly and linear solver time are in general parallel, while the preparation is currently not. We likely observe that the linear solver is the place where most of the time is spent: Manipulating sparse matrices to fit them to an external solver can take significant time for a moderate-size problem.

serial = getReportTimings(rep0);
par = getReportTimings(rep);

total0 = sum(vertcat(serial.Total));
assembly0 = sum(vertcat(serial.Assembly));
lsolve0 = sum(vertcat(serial.LinearSolve));
lsolve_prep0 = sum(vertcat(serial.LinearSolvePrep));
its0 = rep0.Iterations;

total = sum(vertcat(par.Total));
assembly = sum(vertcat(par.Assembly));
lsolve = sum(vertcat(par.LinearSolve));
lsolve_prep = sum(vertcat(par.LinearSolvePrep));
its = rep.Iterations;

a = [total0, assembly0, lsolve0, lsolve_prep0];
b = [total, assembly, lsolve, lsolve_prep];

a(end+1) = a(1) - sum(a(2:end));
b(end+1) = b(1) - sum(b(2:end));

figure;
bar([a; b])
legend('Total time', 'Assembly', 'Linear solve', 'Linear solve preparation', 'Other functions')
set(gca, 'XTickLabel', {'Serial', [num2str(N), ' threads']})
_images/blackoilTutorialMexAcceleration_03.png

Other notes

We note that the more degrees-of-freedom you have per cell in your model, the more efficient the MEX operators are compared to Matlab. See e.g. the compositional module’s example “compositionalLargerProblemTutorial.m” for another example benchmark.

<html>
% <p><font size="-1

Example: Depletion of a closed or open reservoir compartment

Generated from blackoilTutorialOnePhase.m

In this tutorial, we will show how to set up a simulator from scratch in the automatic differentiation, object-oriented (AD-OO) framework without the use of input files. As an example we consider a 2D rectangular reservoir compartment with homogeneous properties, drained by a single producer at the midpoint of the top edge. The compartment is either closed (i.e., sealed by no-flow boundary conditions along all edges), or open with constant pressure support from an underlying, infinite aquifer, which we model as a constant-pressure boundary condition.

mrstModule add ad-props  ad-core ad-blackoil

Grid, petrophysics, and fluid objects

To create a complete model object using the AD-OO framework, we first need to define three standard MRST structures representing the grid and the rock and fluid properties

% The grid and rock model
G    = computeGeometry(cartGrid([50 50],[1000 100]));
rock = makeRock(G, 100*milli*darcy, 0.3);

% Fluid properties
pR  = 200*barsa;
fluid = initSimpleADIFluid('phases','W',           ...
% Fluid phase: water
                           'mu',  1*centi*poise,   ...
% Viscosity
                           'rho', 1000,            ...
% Surface density [kg/m^3]
                           'c',   1e-4/barsa,      ...
% Fluid compressibility
                           'cR',  1e-5/barsa       ...
% Rock compressibility
                           );
Computing normals, areas, and centroids...    Elapsed time is 0.002116 seconds.
Computing cell volumes and centroids...               Elapsed time is 0.007661 seconds.

Make Reservoir Model

We can now use the three objects defined above to instantiate the reservoir model. To this end, we will use a WaterModel, which is a specialization of the general ReservoirModel implemented in ad-core. The only extra thing we need to do is to explicitly set the gravity direction. By default, the gravity in MRST is a 3-component vector that points in the positive z-direction. Here, we set it to a 2-component vector pointing in the negative y-direction.

gravity reset on
wModel = WaterModel(G, rock, fluid,'gravity',[0 -norm(gravity)]);
% Prepare the model for simulation.
wModel = wModel.validateModel();

Drive mechansims and schedule

The second thing we need to specify is the mechanisms that will drive flow in the reservoir, i.e., the wells and boundary conditions. These may change over time and MRST therefore uses the concept of a schedule that describes how the drive mechansims change over time. In our case, we use the same setup for the whole simulation. The schedule also enables us to specify the time steps we wish the simulator to use, provided that these give convergent and stable computations. (If not, the simulator may cut the time step).

% Well: at the midpoint of the south edge
wc = sub2ind(G.cartDims, floor(G.cartDims(1)/2), G.cartDims(2));
W = addWell([], G, rock,  wc,     ...
        'Type', 'bhp', 'Val', pR-50*barsa, ...
        'Radius', 0.1, 'Name', 'P1','Comp_i',1,'sign',1);

% Boundary conditions: fixed pressure at bottom and no-flow elsewhere
bc=pside([],G,'South',200*barsa,'sat',1);

% Schedule: describing time intervals and corresponding drive mechanisms
schedule1 = simpleSchedule(diff(linspace(0,5*day,41)), 'bc', bc, 'W', W);
schedule2 = simpleSchedule(diff(linspace(0,5*day,41)), 'W', W);
Defaulting reference depth to top of formation for well P1. Please specify 'refDepth' optional argument to 'addWell' if you require bhp at a specific depth.

Reservoir state

The last component we need in order to specify our reservoir model is the reservoir state, i.e., the fluid pressure. For multiphase models, the state also includes the phase saturations and compositions. In our case, we first set a constant pressure, and call on a solver from ad-core to compute vertical equilibrium.

state = initResSol(G, pR); % Constant pressure
state.wellSol  = initWellSolAD([], wModel, state);      % No well initially

% Vertical equilibrium
verbose = false;
nonlinear = NonLinearSolver();
state = nonlinear.solveTimestep(state, 10000*day, wModel, 'bc', bc);

clf,
plotCellData(G,state.pressure/barsa,'EdgeColor','none');
colorbar
=======================
| It # | water (cell) |
=======================
|    1 | 2.01e-05     |
|    2 |*1.99e-08     |
=======================
Solved timestep with 1 accepted ministep (0 rejected, 1 total iteration)
_images/blackoilTutorialOnePhase_01.png

Run simulations

To make the case a bit more interesting, we compute the flow problem twice. The first simulation uses the prescribed boundary conditions, which will enable fluids to pass out of the north boundary. In the second simulation, we close the system by imposing no-flow conditions also on the north boundary

% Simulation pressure 200 bar at top
[wellSols1, states1] = simulateScheduleAD(state, wModel, schedule1);

% Simulation with no-flow at top
[wellSols2, states2] = simulateScheduleAD(state, wModel, schedule2);
*****************************************************************
********** Starting simulation:    40 steps,     5 days *********
*****************************************************************
Preparing model for simulation...
Model ready for simulation...
Preparing schedule for simulation...
Schedule ready for simulation...
Validating initial state...
...

Plot results

Prepare animation

wpos = G.cells.centroids(wc,:); clf
set(gcf,'Position',[600 400 800 400])

h1 = subplot('Position',[.1 .11 .34 .815]);
hp1 = plotCellData(G,state.pressure/barsa,'EdgeColor','none');
title('Open compartment w/aquifer'); caxis([pR/barsa-50 pR/barsa]);

h2 = subplot('Position',[.54 .11 .4213 .815]);
hp2 = plotCellData(G,state.pressure/barsa,'EdgeColor','none');
title('Closed compartment'); caxis([pR/barsa-50 pR/barsa]);

colormap(jet(32)); colorbar

% Animate solutions by resetting CData property of graphics handle
for i=1:numel(states1)
    set(hp1,'CData', states1{i}.pressure/barsa);
    set(hp2,'CData', states2{i}.pressure/barsa);
    drawnow, pause(0.1);
end

% Launch plotting of well responses
plotWellSols({wellSols1,wellSols2}, ...
    'Datasetnames',{'Aquifer','Closed'}, 'field','qWr');
_images/blackoilTutorialOnePhase_02.png
_images/blackoilTutorialOnePhase_03.png

Copyright notice

<html>
% <p><font size="-1

Example demonstrating in-situ plotting capabilities in MRST-AD

Generated from blackoilTutorialPlotHook.m

The AD-solvers allow for dynamic plotting during the simulation process. This example demonstrates this capability using “plotWellSols” and a panel showing simulation progress. We first set up a simulation model of SPE1 in the standard manner.

mrstModule add ad-core ad-blackoil ad-props mrst-gui deckformat

[G, rock, fluid, deck, state0] = setupSPE1();
model = selectModelFromDeck(G, rock, fluid, deck);
schedule = convertDeckScheduleToMRST(model, deck);
No converter needed in section 'REGIONS'.
No converter needed in section 'SUMMARY'.
Adding 200 artificial cells at top/bottom

Processing regular i-faces
 Found 550 new regular faces
Elapsed time is 0.010813 seconds.

...

Set up plotting function

We set up a function handle that takes in the current simulator variables, which will be run after each succesful control step. This function can also pause the simulation or change the maximum timestep as a proof of concept.

close all
fn = getPlotAfterStep(state0, model, schedule, 'plotWell', true,...
                                               'plotReservoir', false);
disp(fn)
@(model,states,reports,solver,schedule,simtime)afterStepFunction(model,states,reports,solver,schedule,simtime,injData,injectWell,hdata,hwell)
_images/blackoilTutorialPlotHook_01.png

Run the simulation with plotting function

linsolve = selectLinearSolverAD(model);
[wellSols, states, report] = simulateScheduleAD(state0, model, schedule, ...
    'Verbose', true, 'afterStepFn', fn, 'linearsolver', linsolve);
*****************************************************************
********** Starting simulation:   120 steps,  1216 days *********
*****************************************************************
Preparing model for simulation...
Model ready for simulation...
Preparing schedule for simulation...
Schedule ready for simulation...
Validating initial state...
...
_images/blackoilTutorialPlotHook_02.png
_images/blackoilTutorialPlotHook_03.png

Copyright notice

<html>
% <p><font size="-1
mrstModule add ad-core ad-blackoil deckformat ad-props linearsolvers
mrstVerbose on
L = 1000*meter;
G = cartGrid(150, L);
G = computeGeometry(G);

x = G.cells.centroids/L;

poro = repmat(0.5, G.cells.num, 1);
poro(x > 0.2 & x < 0.3) = 0.05;
poro(x > 0.7 & x < 0.8) = 0.05;
poro(x > 0.45 & x < 0.55) = 0.1;

rock = makeRock(G, 1*darcy, poro);

close all
plot(poro)
_images/immiscibleTimeIntegrationExample_01.png
time = 500*day;

% Default oil-water fluid with unit values
fluid = initSimpleADIFluid('phases', 'WO', 'n', [1 1]);

% Set up model and initial state.
model = GenericBlackOilModel(G, rock, fluid, 'gas', false);
state0 = initResSol(G, 50*barsa, [0, 1]);

% Set up drive mechanism: constant rate at x=0, constant pressure at x=L
pv = poreVolume(G, rock);
injRate = sum(pv)/time;
bc = fluxside([], G, 'xmin', injRate, 'sat', [1, 0]);
bc = pside(bc, G, 'xmax', 0*barsa, 'sat', [0, 1]);

n  = 150;
dT = time/n;
timesteps = rampupTimesteps(time, dT, 8);
schedule = simpleSchedule(timesteps, 'bc', bc);

Fully-implicit

Generated from immiscibleTimeIntegrationExample.m

The default discretization in MRST is fully-implicit. Consequently, we can use the model as-is.

implicit = packSimulationProblem(state0, model, schedule, 'immiscible_time', 'Name', 'Fully-Implicit');

Explicit solver

This solver has a time-step restriction based on the CFL condition in each cell. The solver estimates the time-step before each solution.

model_explicit = model.validateModel();
% Get the flux discretization
flux = model_explicit.FluxDiscretization;
% Use explicit form of flow state
fb = ExplicitFlowStateBuilder('Verbose', true);
fb.compositionCFL = inf;
flux = flux.setFlowStateBuilder(fb);
model_explicit.FluxDiscretization = flux;
explicit = packSimulationProblem(state0, model_explicit, schedule, 'immiscible_time', 'Name', 'Explicit');

Adaptive implicit

We can solve some cells implicitly and some cells explicitly based on the local CFL conditions. For many cases, this amounts to an explicit treatment far away from wells or driving forces. The values for estimated composition CFL and saturation CFL to trigger a switch to implicit status can be adjusted.

model_aim = model.validateModel();
flux = model_aim.FluxDiscretization;
fb = AdaptiveImplicitFlowStateBuilder('Verbose', true);
fb.compositionCFL = inf;
flux = flux.setFlowStateBuilder(fb);
model_aim.FluxDiscretization = flux;
aim = packSimulationProblem(state0, model_aim, schedule, 'immiscible_time', 'Name', 'Adaptive-Implicit (AIM)');

Make an explicit solver with larger CFL limit

Since the equation is linear, we can set the NonLinearSolver to use a single step. We bypass the convergence checks and can demonstrate the oscillations that result in taking a longer time-step than the stable limit.

model_explicit_largedt = model.validateModel();
% Get the flux discretization
flux = model_explicit_largedt.FluxDiscretization;
% Use explicit form of flow state
fb = ExplicitFlowStateBuilder();
fb.saturationCFL = 5;
fb.compositionCFL = inf; % Immiscible, saturation cfl is enough
flux = flux.setFlowStateBuilder(fb);
model_explicit_largedt.FluxDiscretization = flux;
model_explicit_largedt.stepFunctionIsLinear = true;
explicit_largedt = packSimulationProblem(state0, model_explicit_largedt, schedule, 'immiscible_time', 'Name', 'Explicit (CFL target 5)');

Simulate the problems

problems = {implicit, explicit, aim, explicit_largedt};
simulatePackedProblem(problems, 'continueOnError', false);
*******************************************************
* Case "immiscible_time" (Fully-Implicit)             *
* Description: "Fully-Implicit_GenericBlackOilModel"  *
*******************************************************
-> Complete output found, nothing to do here.

*************************************************
* Case "immiscible_time" (Explicit)             *
...

Get output and plot the well results

There are oscillations in the well curves. Increasing the CFL limit beyond unity will eventually lead to oscillations.

[ws, states, reports, names, T] = getMultiplePackedSimulatorOutputs(problems);
Found complete data for immiscible_time (Fully-Implicit): 158 steps present
Found complete data for immiscible_time (Explicit): 158 steps present
Found complete data for immiscible_time (Adaptive-Implicit (AIM)): 158 steps present
Found complete data for immiscible_time (Explicit (CFL target 5)): 158 steps present

Plot the results

Note oscillations for CFL > 1.

figure(1);
for stepNo = 1:numel(schedule.step.val)
    clf;
    subplot(2, 1, 1);
    hold on
    for j = 1:numel(states)
        plotCellData(G, states{j}{stepNo}.s(:, 1))
    end
    legend(names);

    subplot(2, 1, 2); hold on;
    plotCellData(G, rock.poro);
    dt = schedule.step.val(stepNo);
    state = states{1}{stepNo};
    cfl = estimateSaturationCFL(model, state, dt);
    plotCellData(G, cfl);
    plotCellData(G, ones(G.cells.num, 1), 'Color', 'k');
    legend('Porosity', 'CFL', 'CFL stable limit');
    drawnow
end
_images/immiscibleTimeIntegrationExample_02.png

Plot the fraction of cells which are implicit

imp_frac = cellfun(@(x) sum(x.implicit)/numel(x.implicit), states{3});
figure;
plot(100*imp_frac);
title('Number of implicit cells in AIM')
ylabel('Percentage of implicit cells')
xlabel('Step index')
_images/immiscibleTimeIntegrationExample_03.png

Plot the time-steps taken

The implicit solvers use the control steps while the explicit solver must solve many substeps.

igure; hold on
markers = {'o', '.', 'x', '.'};
for stepNo = 1:numel(reports)
    dt = getReportMinisteps(reports{stepNo});
    x = (1:numel(dt))/numel(dt);
    plot(x, dt/day, markers{stepNo})
end
ylabel('Timestep length [day]')
xlabel('Fraction of total simulation time');
legend(names)

% <html>
% <p><font size="-1
_images/immiscibleTimeIntegrationExample_04.png

Multi-segment well example based on SPE 1 benchmark model

Generated from multisegmentWellExample.m

This example demonstrates the use of multisegment wells in MRST. The default wells in MRST assume flow in the well happens on a very short time-scale compared to the reservoir and can thus be accurately modelled by an instantaneous model. For some problems, however, more sophisticated well models may be required. The multisegment well class in MRST supports transient flow in the well. In addition, the class also allows for a more fine-grained representation of the well itself where the effect of valves and friction models can be included per segment.

% Load necessary modules
mrstModule add ad-blackoil ad-props deckformat ad-core mrst-gui

Set up model based on SPE 1

We use the SPE 1 example to get a simple black-oil case to modify. For more details on this model, see the the example blackoilTutorialSPE1.m.

[G, rock, fluid, deck, state] = setupSPE1();
[nx, ny] = deal(G.cartDims(1),G.cartDims(2));

model = ThreePhaseBlackOilModel(G, rock, fluid, 'inputdata', deck);
% Add extra output
model.extraStateOutput = true;

% Convert the deck schedule into a MRST schedule
schedule = convertDeckScheduleToMRST(model, deck);
No converter needed in section 'REGIONS'.
No converter needed in section 'SUMMARY'.
Adding 200 artificial cells at top/bottom

Processing regular i-faces
 Found 550 new regular faces
Elapsed time is 0.008292 seconds.

...

Construct multisegment well, and replace producer in original example

The well has 13 nodes. Nodes correspond to volumes in the well and are analogous to the grid cells used when the reservoir itself is discretized. The well is perforated in six grid blocks and we add nodes both for the wellbore and the reservoir contacts. By modelling the nodes in the reservoir contacts and the wellbore separately, we can introduce a valve between the reservoir and the wellbore. Nodes 1-7 represents the wellbore (frictional pressure drop) Well segments 2-8, 3-9, …, 7-13 will represent valves Only nodes 8-13 are connected to reservoir (grid-cells c1-c6)

% connection grid cells (along edge of second layer)
c = nx*ny + (2:7)';

% Make a conceptual illustration of the multisegment horizontal well
clf
flag=true(G.cells.num,1); flag([2; c])=false;
plotGrid(G,flag,'FaceColor',[1 1 .7]);
plotGrid(G,c,'FaceColor','r','FaceAlpha',.3,'EdgeColor','r','LineWidth',1);
x = G.cells.centroids(c,[1 1 1]); x(:,2) = NaN;
y = G.cells.centroids(c,[2 2 2]); y(:,2) = NaN;
z = G.cells.centroids(c,[3 3 3]); z(:,2) = NaN;
z(:,1) = z(:,1)+1.5; z(:,3) = z(:,3)-1.5;
hold on
plot3(x(1,[1 1]), y(1,[1 1]), z(1,1) - [0 15], '-r', 'LineWidth',3);
plot3(x, y, z,'-or','MarkerSize',7,'MarkerFaceColor',[.6 .6 .6],'LineWidth',2);
plot3(x(:,[1 3 2])',y(:,[1 3 2])',z(:,[1 3 2])','-r','LineWidth',2);
hold off, view(-30,25), axis tight off
_images/multisegmentWellExample_01.png

First, initialize production well as “standard” well structure

prodS = addWell([], G, rock, c, 'name', 'prod', ...
                'refDepth', G.cells.centroids(1,3), ...
               'type', 'rate', 'val', -8e5*meter^3/day);

% Define additional properties needed for ms-well
% We have 12 node-to-node segments
topo = [1 2 3 4 5 6 2 3  4  5  6  7
        2 3 4 5 6 7 8 9 10 11 12 13]';
%      |   tubing  |    valves     |
% Create sparse cell-to-node mapping
cell2node = sparse((8:13)', (1:6)', 1, 13, 6);
% Segment lengths/diameters and node depths/volumes
lengths = [300*ones(6,1); nan(6,1)];
diam    = [.1*ones(6,1); nan(6,1)];
depths  = G.cells.centroids(c([1 1:end 1:end]), 3);
vols    = ones(13,1);
% Convert to ms-well
prodMS = convert2MSWell(prodS, 'cell2node', cell2node, 'topo', topo, 'G', G, 'vol', vols, ...
                   'nodeDepth', depths, 'segLength', lengths, 'segDiam', diam);

% Finally, we set flow model for each segment:
%   segments  1-6: wellbore friction model
%   segments 7-12: nozzle valve model
% The valves have 30 openings per connection
[wbix, vix]  = deal(1:6, 7:12);
[roughness, nozzleD, discharge, nValves] = deal(1e-4, .0025, .7, 30);

% Set up flow model as a function of velocity, density and viscosity
prodMS.segments.flowModel = @(v, rho, mu)...
    [wellBoreFriction(v(wbix), rho(wbix), mu(wbix), prodMS.segments.diam(wbix), ...
                      prodMS.segments.length(wbix), roughness, 'massRate'); ...
     nozzleValve(v(vix)/nValves, rho(vix), nozzleD, discharge, 'massRate')];

% In addition, we define a standard gas injector. Different well types can
% easily be combined in MRST, each with their own models for pressure drop
% and solution variables.
inj = addWell([], G, rock, 100, 'name', 'inj', 'type', 'rate', 'Comp_i', [0 0 1], ...
              'val', 2.5e6*meter^3/day,'refDepth', G.cells.centroids(1,3));

Run schedule with simple wells

We first simulate a baseline where the producer is treated as a simple well with instantaneous flow and one node per well-reservoir contact.

W_simple = [inj; prodS];
schedule.control.W = W_simple;

[wellSolsSimple, statesSimple] = simulateScheduleAD(state, model, schedule);
*****************************************************************
********** Starting simulation:   120 steps,  1216 days *********
*****************************************************************
Preparing model for simulation...
Model ready for simulation...
Preparing schedule for simulation...
Schedule ready for simulation...
Validating initial state...
...

Set up well models

We combine the simple and multisegment well together

W = combineMSwithRegularWells(inj, prodMS);
schedule.control.W = W;
% Initialize the facility model. This is normally done automatically by the
% simulator, but we do it explicitly on the outside to view the output
% classes. This approach is practical if per-well adjustments to e.g.
% tolerances are desired.

% First, validate the model to set up a FacilityModel
model = model.validateModel();
% Then apply the new wells to the FacilityModel
model.FacilityModel = model.FacilityModel.setupWells(W);

% View the simple injector
disp(model.FacilityModel.WellModels{1})
% View the multisegment producer
disp(model.FacilityModel.WellModels{2})
SimpleWell with properties:

                        W: [1×1 struct]
           allowCrossflow: 1
          allowSignChange: 0
    allowControlSwitching: 1
                 dpMaxRel: Inf
                 dpMaxAbs: Inf
...

Run the same simulation with multisegment wells

Note that if verbose output is enabled, the convergence reports will include additional fields corresponding to the well itself.

[wellSols, states, report] = simulateScheduleAD(state, model, schedule);
*****************************************************************
********** Starting simulation:   120 steps,  1216 days *********
*****************************************************************
Preparing model for simulation...
Model ready for simulation...
Preparing schedule for simulation...
Schedule ready for simulation...
Validating initial state...
...

Plot the well-bore pressure in the multisegment well

Plot pressure along wellbore for step 1 and step 120 (final step)

figure, hold all
for k =  [1 120]
    plot([wellSols{k}(2).bhp; wellSols{k}(2).nodePressure(1:6)]/barsa, ...
         '-o', 'LineWidth', 2);
end
legend('Step 1', 'Step 120', 'Location', 'northwest')
set(gca, 'Fontsize', 14), xlabel('Well node'), ylabel('Pressure [bar]')
_images/multisegmentWellExample_02.png

Launch interactive plotting

We can compare the two different well models interactively and observe the difference between the two modelling approaches.

plotWellSols({wellSols, wellSolsSimple}, report.ReservoirTime, ...
            'datasetnames', {'Multisegment', 'Standard'});
_images/multisegmentWellExample_03.png

Show the advancing displacement front

mrstModule add coarsegrid
pg = generateCoarseGrid(G,ones(G.cells.num,1));
figure, h = [];
plotGrid(pg,'FaceColor','none');
plotGrid(G,c,'FaceColor','none','EdgeColor','r');
plotWell(G,[inj; prodS]);
 caxis([0 .5]); view(37.5,34); caxis([0 .5]); axis tight; colorbar, drawnow;
for i=1:numel(states)
    sg = states{i}.s(:,3); inx = sg>1e-5;
    if sum(inx)>0
        delete(h)
        h=plotCellData(G,sg,sg>1e-4,'EdgeAlpha',.01);
        colorbar
    drawnow;
    end
end
_images/multisegmentWellExample_04.png

Visualize drawdown in well as function of time

We extract the pressure in all the nodes and visualize this, together with the pressure in the completed reservoir cells as function of time

[xx,tt]=meshgrid(1:6,report.ReservoirTime/day);
pr = cellfun(@(x) x.pressure(c)', states, 'UniformOutput',false);
pa = cellfun(@(x) x(2).nodePressure(7:12)', wellSols, 'UniformOutput',false);
pw = cellfun(@(x) x(2).nodePressure(1:6)', wellSols, 'UniformOutput',false);
figure
hold on
surfWithOutline(xx, tt, vertcat(pr{:})/barsa);
surfWithOutline(xx, tt, vertcat(pa{:})/barsa);
surfWithOutline(xx, tt, vertcat(pw{:})/barsa);
hold off, box on, axis tight
view(50,10), shading interp
set(gca,'Projection','Perspective');
akse = axis();
cax = caxis();
_images/multisegmentWellExample_05.png
r = cellfun(@(x) x.pressure(c)', statesSimple, 'UniformOutput',false);
pw = cellfun(@(x) x(2).bhp + x(2).cdp', wellSolsSimple, 'UniformOutput',false);
figure
hold on
surfWithOutline(xx, tt, vertcat(pr{:})/barsa);
surfWithOutline(xx, tt, vertcat(pw{:})/barsa);
hold off, box on, axis tight, axis(akse), caxis(cax)
view(50,10), shading interp
set(gca,'Projection','Perspective');

% <html>
% <p><font size="-1
_images/multisegmentWellExample_06.png

Workflow example for MRST-AD

Generated from simulatorWorkflowExample.m

This example aims to show the complete workflow for creating, running and analyzing a simulation model. Unlike the other examples, we will create all features of the model manually to get a self-contained script without any input files required. The model we setup is a slightly compressible two-phase oil/water model with multiple wells. The reservoir has a layered strategraphy and contains four intersecting faults. Note that this example features a simple conceptual model designed to show the workflow rather than a problem representing a realistic scenario in terms of well locations and fluid physics.

mrstModule add ad-core ad-blackoil ad-props mrst-gui
close all;

Reservoir geometry and petrophysical properties

We begin by setting up the grid and rock structures. The grid is created by “makeModel3”, which creates a structured model with intersecting faults. We assume a layered permeability structure with 300, 100, and 500 md in the lower, middle, and top layers. respectively.

% Define grid
grdecl = makeModel3([50, 50, 5], [1000, 1000, 5]*meter);
G = processGRDECL(grdecl);
G = computeGeometry(G);

% Set up permeability based on K-indices
[I, J, K] = gridLogicalIndices(G);

top = K < G.cartDims(3)/3;
lower = K > 2*G.cartDims(3)/3;
middle = ~(lower | top);

px = ones(G.cells.num, 1);
px(lower) = 300*milli*darcy;
px(middle) = 100*milli*darcy;
px(top) = 500*milli*darcy;

% Introduce anisotropy by setting K_x = 10*K_z.
perm = [px, px, 0.1*px];
rock = makeRock(G, perm, 0.3);

% Plot horizontal permeability and wells
figure(1); clf
plotCellData(G, rock.perm(:, 1)/(milli*darcy))
view(50, 50), axis tight
colorbar
title('K_x [mD]')
Adding 5000 artificial cells at top/bottom

Processing regular i-faces
 Found 14964 new regular faces
Elapsed time is 0.012135 seconds.

Processing i-faces on faults
 Found 58 faulted stacks
...
_images/simulatorWorkflowExample_01.png

Define wells and simulation schedule

Hydrocarbon is recovered from producers, operating at fixed bottom-hole pressure and perforated throughout all layers of the model. The producers are supported by a single water injector set to inject one pore volume over 10 years (the total simulation length). We also set up a schedule consisting of 5 small control steps initially, followed by 25 larger steps. We keep the well controls fixed throughout the simulation.

simTime = 10*year;
nstep   = 25;
refine  = 5;

% Producers
pv      = poreVolume(G, rock);
injRate = 1*sum(pv)/simTime;
offset  = 10;
W = verticalWell([], G, rock, offset, offset, [],...
                'Name', 'P1', 'comp_i', [1 0], 'Val', 250*barsa, 'Type', 'bhp', 'refDepth', -8);
W = verticalWell(W, G, rock,  offset, floor(G.cartDims(1)/2)+3, [],...
                'Name', 'P2', 'comp_i', [1 0], 'Val', 250*barsa, 'Type', 'bhp', 'refDepth', -8);
W = verticalWell(W, G, rock, offset, G.cartDims(2) - offset/2, [], ...
                'Name', 'P3', 'comp_i', [1 0], 'Val', 250*barsa, 'Type', 'bhp', 'refDepth', -8);
% Injectors
W = verticalWell(W, G, rock, G.cartDims(1)-5, offset, 1,...
                'Name', 'I1', 'comp_i', [1 0], 'Val', injRate, 'Type', 'rate', 'refDepth', -8);

% Compute the timesteps
startSteps = repmat((simTime/(nstep + 1))/refine, refine, 1);
restSteps =  repmat(simTime/(nstep + 1), nstep, 1);
timesteps = [startSteps; restSteps];

% Set up the schedule containing both the wells and the timesteps
schedule = simpleSchedule(timesteps, 'W', W);

% Plot the wells
plotWell(G, W)
axis tight
_images/simulatorWorkflowExample_02.png

Set up simulation model

We set up a two-phase oil-water simulation model based on automatic differentiation. The resulting object is a special case of a general three-phase model and to instantiate it, we start by constructing a two-phase fluid structure with properties given for oil and water. Water is assumed to be incompressible, whereas oil has constant compressibility, giving a reciprocal formation volume factor of the form,

. To define this relation, we set the ‘bo’ field of the fluid structure to be an anonymous function that calls the builtin ‘exp’ function with appropriate arguments. Since the fluid model is a struct containing function handles, it is simple to modify the fluid to use alternate functions. We then pass the fundamental structures (grid, rock and fluid) onto the two-phase oil/water model constructor.
% Three-phase template model
fluid = initSimpleADIFluid('mu',    [1, 5]*centi*poise, ...
                           'rho',   [1000, 700]*kilogram/meter^3, ...
                           'n',     [2, 2], ...
                           'cR',    1e-8/barsa, ...
                           'phases', 'wo');

% Constant oil compressibility
c        = 0.001/barsa;
p_ref    = 300*barsa;
fluid.bO = @(p, varargin) exp((p - p_ref)*c);
clf
p0 = (100:10:500)*barsa;
plot(p0/barsa, fluid.bO(p0))
xlabel('Pressure (bar)')
ylabel('Ratio')
title('Reciprocal formation volume factor for oil (bO)')

% Construct reservoir model
gravity reset on
model = TwoPhaseOilWaterModel(G, rock, fluid);
_images/simulatorWorkflowExample_03.png

Define initial state

Once we have a model, we need to set up an initial state. We set up a very simple initial state; we let the bottom part of the reservoir be completely water filled, and the top completely oil filled. MRST uses water, oil, gas ordering internally, so in this case we have water in the first column and oil in the second for the saturations.

region = getInitializationRegionsBlackOil(model, 5, 'datum_pressure', p_ref);
state0 = initStateBlackOilAD(model, region);
clf
plotCellData(G, state0.s(:,1))
plotWell(G,W)
view(50, 50), axis tight
_images/simulatorWorkflowExample_04.png

Simulate base case

Using the schedule giving dynamic controls and time steps, the model giving the mathematical description of how to advance the solution, and the initial state of the reservoir, we can simulate the problem. Since the simulation will consume some time, we launch a progress report and a plotting tool for the well solutions (well rates and bottom-hole pressures)

fn = getPlotAfterStep(state0, model, schedule,'view',[50 50], ...
                     'field','s:1','wells',W);
[wellSols, states, report] = ...
   simulateScheduleAD(state0, model, schedule,'afterStepFn',fn);
*****************************************************************
********** Starting simulation:    30 steps,  3650 days *********
*****************************************************************
Preparing model for simulation...
Model ready for simulation...
Preparing schedule for simulation...
Schedule ready for simulation...
Validating initial state...
...
_images/simulatorWorkflowExample_05.png
_images/simulatorWorkflowExample_06.png
_images/simulatorWorkflowExample_07.png

Plot reservoir states

We launch a plotting tool for the reservoir quantities (pressures and saturations, located in states).

figure(1)
plotToolbar(G, states)
view(50, 50);
plotWell(G,W);
_images/simulatorWorkflowExample_08.png

Create an upscaled, coarser model

The fine scale model has approximately 10000 cells. If we want a smaller model we can easily define an upscaled model. Here we set up a simple uniform partition of approximately 50 cells based on the IJK-indices of the grid.

close([2 3]);
mrstModule add coarsegrid
cdims = [5, 5, 2];
p0 = partitionUI(G, cdims);

figp = figure;
CG = generateCoarseGrid(G,p0);
plotCellData(CG,(1:CG.cells.num)', 'EdgeColor', 'none');
plotFaces(CG,1:CG.faces.num,'EdgeColor','k','FaceColor','none');
colormap(.5*colorcube(CG.cells.num)+.5*ones(CG.cells.num,3));
axis tight off
view(125, 55)
title('Straightforward index partition');
_images/simulatorWorkflowExample_09.png

Split blocks over the faultlines

We see that several coarse blocks cross the fault lines. To get hexahedral coarse blocks, we create a grid where the faults act as barriers and apply the “processPartition” routine to split any coarse blocks intersected by faults. Afterwards, we show the new partition and highlight blocks created due to the modification of the fault.

G_fault = makeInternalBoundary(G, find(G.faces.tag > 0));
p = processPartition(G_fault, p0);
plotGrid(G, p ~= p0, 'EdgeColor', 'w', 'FaceColor', 'none')
title('Splitting over fault lines');
_images/simulatorWorkflowExample_10.png

Upscale the model and run the coarser problem

We can now directly upscale the model, schedule, and initial state. By default, the upscaling routine uses the simplest possible options, i.e., harmonic averaging of permeabilities. It is possible to use more advanced options, but for the purpose of this example we will use the defaults. Once we have an upscaled model, we can again simulate the new schedule and observe that the time taken is greatly reduced. For instance, on a Intel Core i7 desktop computer, the fine model takes little under a minute to run, while the upscaled model takes 4 seconds.

clear CG; if ishandle(figp); close(figp); end;
model_c = upscaleModelTPFA(model, p);
G_c    = model_c.G;
rock_c = model_c.rock;

schedule_c = upscaleSchedule(model_c, schedule);
state0_c = upscaleState(model_c, model, state0);

[wellSols_c, states_c] = simulateScheduleAD(state0_c, model_c, schedule_c);
*****************************************************************
********** Starting simulation:    30 steps,  3650 days *********
*****************************************************************
Preparing model for simulation...
Model ready for simulation...
Preparing schedule for simulation...
Schedule ready for simulation...
Validating initial state...
...

Create a second upscaled model with flow-based upscaling

We use the upscaling module to create a tailored upscaled model. This upscaling routine uses a incompressible flow field with the wells of the problem to perform a global upscaling.

mrstModule add incomp agglom upscaling

[~, T_c, W_c] = upscaleTrans(G_c, model.operators.T_all, ...
    'Wells', W, 'bc_method', 'wells', 'fix_trans', true);

model_c2 = upscaleModelTPFA(model, p, 'transCoarse', T_c);
schedule_c2 = schedule;
for i = 1:numel(schedule_c2.control)
    schedule_c2.control(i).W = W_c;
end
[wellSols_c2, states_c2] = simulateScheduleAD(state0_c, model_c2, schedule_c2);
Setting up linear system...                   Elapsed time is 0.037044 seconds.
Solving linear system...                      Elapsed time is 0.027907 seconds.
Computing fluxes, face pressures etc...               Elapsed time is 0.010089 seconds.
Setting up linear system...                   Elapsed time is 0.006289 seconds.
Solving linear system...                      Elapsed time is 0.000382 seconds.
Computing fluxes, face pressures etc...               Elapsed time is 0.002976 seconds.
Warning: Set boundary face trans to zero
*****************************************************************
...

Plot the coarse results, and compare the well solutions

We plot the coarse solutions and compare the well solutions. Note that the upscaling will result in only 70 cells, which is unlikely to give good results with only simple harmonic averaging of permeabilities as used in the first upscaling. The flow-based upscaling gives much better result, but is dependent on having access to the well configuration.

figure(2); clf
plotToolbar(G_c, states_c); plotWell(G, W)
view(50, 50);

figure(3); clf
plotToolbar(G_c, states_c2); plotWell(G, W)
view(50, 50);

plotWellSols({wellSols, wellSols_c, wellSols_c2}, cumsum(schedule.step.val), ...
   'DatasetNames', {'Fine scale', 'Simple upscaling', 'Flow upscaling'}, 'Field', 'qOs');
_images/simulatorWorkflowExample_11.png
_images/simulatorWorkflowExample_12.png
_images/simulatorWorkflowExample_13.png

Compute flow diagnostics

As an alternative to looking at well curves, we can also look at the flow diagnostics of the models. Flow diagnostics are simple routines based on time-of-flight and tracer equations, which aim to give a qualitative understanding of reservoir dynamics. Here, we take the end-of-simulation states as a snapshot for both the fine and coarse model and compute time-of-flight and well tracers.

mrstModule add diagnostics
close(3)
D   = computeTOFandTracer(states{end},   G,   rock,   'Wells', schedule.control.W);
D_c = computeTOFandTracer(states_c{end}, G_c, rock_c, 'Wells', schedule_c.control.W);
D_c2 = computeTOFandTracer(states_c2{end}, G_c, rock_c, 'Wells', schedule_c.control.W);
Forward  maximal TOF set to 500.00 years.
Backward maximal TOF set to 496.10 years.
Forward  maximal TOF set to 500.01 years.
Backward maximal TOF set to 493.30 years.
Forward  maximal TOF set to 500.01 years.
Backward maximal TOF set to 495.97 years.

Plot total arrival times

We plot the residence time from injector to producer to separate high-flow regions, shown in dark red, from low-flow or stagnant regions, shown in yellow to white. Since the values vary by several orders of magnitude, we plot the logarithm of the values. We also use the same color axis to ensure that the plots can be compared.

figure(1); clf, cmap=colormap;
hf = plotCellData(G, log10(sum(D.tof, 2))); plotWell(G,W)
view(50, 50); colormap(hot.^.5);
title('Log of total travel time, fine model');
c = caxis();

figure(2); clf
hc = plotCellData(G_c, log10(sum(D_c.tof, 2))); plotWell(G,W)
view(50, 50); colormap(hot.^.5);
title('Log of total travel time, coarse model');
caxis(c)

figure(3); clf
hc2 = plotCellData(G_c, log10(sum(D_c2.tof, 2))); plotWell(G,W)
view(50, 50); colormap(hot.^.5);
title('Log of total travel time, coarse model');
caxis(c)
_images/simulatorWorkflowExample_14.png
_images/simulatorWorkflowExample_15.png
_images/simulatorWorkflowExample_16.png

Plot tracer partitioning

We can also look at the tracer partitioning for the producers, showing the drainage regions for the different wells. See the diagnostics module for more examples and more in-depth discussions of how flow diagnostics can be used.

figure(1); delete(hf), colormap(cmap)
plotCellData(G, D.ppart);
title('Drainage regions, fine model');

figure(2); delete(hc), colormap(cmap), caxis auto
plotCellData(G_c, D_c.ppart);
title('Drainage regions, simple upscaled model');

figure(3); delete(hc2), colormap(cmap), caxis auto
plotCellData(G_c, D_c2.ppart);
title('Drainage regions, flow upscaled model');
_images/simulatorWorkflowExample_17.png
_images/simulatorWorkflowExample_18.png
_images/simulatorWorkflowExample_19.png

Launch interactive diagnostics tools

We can also examine the diagnostics interactively using the diagnostics viewer.

close all;
interactiveDiagnostics(G, rock, schedule.control.W, 'state', states{end}, 'computeFlux', false, 'name', 'Fine model');
interactiveDiagnostics(G_c, rock_c, schedule_c.control.W, 'state', states_c{end}, 'computeFlux', false, 'name', 'Simple upscaled model');
interactiveDiagnostics(G_c, rock_c, schedule_c.control.W, 'state', states_c2{end}, 'computeFlux', false, 'name', 'Flow upscaled model');
New state encountered, computing diagnostics...
Forward  maximal TOF set to 500.00 years.
Backward maximal TOF set to 496.10 years.
New state encountered, computing diagnostics...
Forward  maximal TOF set to 500.01 years.
Backward maximal TOF set to 493.30 years.
New state encountered, computing diagnostics...
Forward  maximal TOF set to 500.01 years.
...
_images/simulatorWorkflowExample_20.png
_images/simulatorWorkflowExample_21.png
_images/simulatorWorkflowExample_22.png
_images/simulatorWorkflowExample_23.png
_images/simulatorWorkflowExample_24.png
_images/simulatorWorkflowExample_25.png

Copyright notice

<html>
% <p><font size="-1

Example demonstrating the two-phase oil-water Egg model

Generated from eggExample.m

This example sets up and runs the Egg model using the two-phase AD solvers. For details on the EggModel and the corresponding ensamble, see Jansen, J. D., et al. “The egg model–a geological ensemble for reservoir simulation.” Geoscience Data Journal 1.2 (2014): 192-195.

mrstModule add ad-core ad-blackoil deckformat
% Realizations can be set to 0 for base cae, or a number between 1 and 100
% for different permeabilities.
realization = 0;
[G, rock, fluid, deck] = setupEGG('realization', realization);
[state, model, schedule, nonlinear] = initEclipseProblemAD(deck, 'G', G, 'TimestepStrategy', 'none');
No converter needed in section 'REGIONS'.
No converter needed in section 'SUMMARY'.
Processing Cells     1 : 18553 of 18553 ... done (0.12 [s], 1.56e+05 cells/second)
Total 3D Geometry Processing Time = 0.120 [s]
No converter needed in section 'REGIONS'.
No converter needed in section 'SUMMARY'.
Ordering well 1 (INJECT1) with strategy "origin".
Ordering well 2 (INJECT2) with strategy "origin".
...

Run simulation

[wellSols, states] = simulateScheduleAD(state, model, schedule, 'NonLinearSolver', nonlinear);
*****************************************************************
********** Starting simulation:   123 steps,  3600 days *********
*****************************************************************
Preparing model for simulation...
Model ready for simulation...
Preparing schedule for simulation...
Schedule ready for simulation...
Validating initial state...
...

Plot the injector BHP and the well oil and water rates

Since the injectors are rate controlled and the producers are pressure controlled, we can plot the quantities that vary. We also plot the water cut.

T = convertTo(cumsum(schedule.step.val), year);
W = schedule.control(1).W;

inj = find(vertcat(W.sign) > 0);
prod = find(vertcat(W.sign) < 0);
bhp = getWellOutput(wellSols, 'bhp', inj);

clf,
plot(T, convertTo(bhp, barsa), 'linewidth', 2)
legend({W(inj).name})
xlabel('Time [years]')
ylabel('Injector bottom-hole pressure [bar]')

orat = getWellOutput(wellSols, 'qOs', prod);
wrat = getWellOutput(wellSols, 'qWs', prod);

figure;
plot(T, convertTo(-orat, meter^3/day), 'linewidth', 2)
legend({W(prod).name})
xlabel('Time [years]')
ylabel('Producer oil-rate [m^3/day]');

figure;
plot(T, convertTo(-wrat, meter^3/day), 'linewidth', 2)
legend({W(prod).name})
xlabel('Time [years]')
ylabel('Producer water-rate [m^3/day]');

figure;
plot(T, abs(wrat)./abs(wrat + orat), 'linewidth', 2)
legend({W(prod).name})
xlabel('Time [years]')
ylabel('Water cut');
_images/eggExample_01.png
_images/eggExample_02.png
_images/eggExample_03.png
_images/eggExample_04.png

Plot the pressure and water saturation through the simulation

df = get(0, 'DefaultFigurePosition');
h = figure('Position', df.*[1, 1, 2.25, 1]);
for i = 1:numel(states)
    % Plot the pressure
    timestr = formatTimeRange(sum(schedule.step.val(1:i)));
    figure(h); clf
    subplot(1, 2, 1)
    plotCellData(G, states{i}.pressure/barsa, 'EdgeColor', 'none');
    plotWell(G, W(inj), 'fontsize', 12, 'color', 'k')
    plotWell(G, W(prod), 'fontsize', 12, 'color', 'r')
    title(['Reservoir pressure after ', timestr]);
    view(-50, 70);
    axis tight off
    colorbar

    % Plot water saturation
    subplot(1, 2, 2)
    plotCellData(G, states{i}.s(:, 1), 'EdgeColor', 'none');
    plotWell(G, W(inj), 'fontsize', 12, 'color', 'k')
    plotWell(G, W(prod), 'fontsize', 12, 'color', 'r')
    title(['Water saturation after ', timestr]);
    view(-50, 70);
    axis tight off
    colorbar
    caxis([0, 1])
    drawnow
end
_images/eggExample_05.png

Launch interactive plotting

rstModule add mrst-gui
plotWellSols(wellSols, cumsum(schedule.step.val))

figure;
plotToolbar(G, states)
plotWell(G, W(inj), 'fontsize', 12, 'color', 'k')
plotWell(G, W(prod), 'fontsize', 12, 'color', 'r')

view(-50, 70);
axis tight off

% <html>
% <p><font size="-1
_images/eggExample_06.png
_images/eggExample_07.png

The Odeh Benchmark (SPE1)

Generated from blackoilTutorialSPE1.m

The first SPE project comparing black-oil reservoir simulators was organized by Odeh (1981) and describes a depletion problem with gas injection in a small 10x10x3 reservoir with a producer and an injector placed in diagonally opposite corners. The porosity is uniform and equal 0.3, whereas the permeability is isotropic with values 500, 50, and 200 md in the three layers with thickness 20, 30, and 50 ft. The reservoir is initially undersaturated with a pressure field that is constant in each layer, a uniform mixture of water (Sw = 0.12) and oil (So = 0.88) with no initial free gas (Sg = 0.0) and a constant dissolved gas-oil ratio (Rs ) throughout the model. The problem is specified using an industry-standard input format, and the solution computed by MRST is compared to that of a commercial reservoir simulator (Eclipse 100). The original problem was posed to study ten years of production; herein, we only report and compare solutions for the first 1216 days.

%
% Odeh, A.S. 1981. Comparison of Solutions to a Three-Dimensional Black-Oil
% Reservoir Simulation Problem. J Pet Technol 33 (1): 13–25. SPE-9723-PA.
% http://dx.doi.org/10.2118/9723-PA
%
mrstModule add ad-props deckformat mrst-gui ad-core ad-blackoil

Set up the problem

Because several examples use the SPE1 dataset, the initial setup is delegated to a helper function. See the inside for documentation.

[G, rock, fluid, deck, state] = setupSPE1();

% Determine the model automatically from the deck. It should be a
% three-phase black oil model with gas dissoluton.
model = selectModelFromDeck(G, rock, fluid, deck);

model %#ok, intentional display

% Convert the deck schedule into a MRST schedule by parsing the wells
schedule = convertDeckScheduleToMRST(model, deck);
No converter needed in section 'REGIONS'.
No converter needed in section 'SUMMARY'.
Adding 200 artificial cells at top/bottom

Processing regular i-faces
 Found 550 new regular faces
Elapsed time is 0.001601 seconds.

...

Plot well and permeability

The permeability consists of three layers going from high to low permeability along the z axis. The wells are completed in the upper and lower layer for the injector and producer respectively. To get a well object, we simply process the first control from the schedule.

figure;
plotCellData(G, convertTo(rock.perm(:,1), milli*darcy), ...
             'FaceAlpha', 0.5, 'EdgeAlpha', 0.3, 'EdgeColor', 'k');
plotWell(G, schedule.control(1).W, 'radius',.5); % Pick the only well control present
title('Permeability (mD)')
axis tight, view(35, 40), colorbar('SouthOutside');
_images/blackoilTutorialSPE1_01.png

Run the entire schedule

Here, we will run the schedule as it is described in the input file. Note that a schedule is not necessary to solve problems using the fully implicit solver. The function ‘solvefiADI’ from the ‘ad-fi’ module (which implements fully implicit ad solvers without object orientation) is capable of taking a well structure directly and solve for a single time step in a manner similar e.g., to the incompressible MRST solvers. schedule.step.val(1) = 10*year;

nls = NonLinearSolver('useLinesearch', true);
[wellSols, states, report] = simulateScheduleAD(state, model, schedule, 'nonlinearsolver', nls);
*****************************************************************
********** Starting simulation:   120 steps,  1216 days *********
*****************************************************************
Preparing model for simulation...
Model ready for simulation...
Preparing schedule for simulation...
Schedule ready for simulation...
Validating initial state...
...

Plot the well solutions and simulator states

We setup interactive viewers for both well solutions and the reservoir states.

plotWellSols(wellSols, report.ReservoirTime)

figure;
plotToolbar(G, states)
plotWell(G, schedule.control(1).W)
axis tight, view(-10, 60)
_images/blackoilTutorialSPE1_02.png
_images/blackoilTutorialSPE1_03.png

Make plots to compare solution with a commercial simulator

Load summary from binary file and find indices of the producer and injector.

load SPE1_smry

inj = find([wellSols{1}.sign] == 1);
prod = find([wellSols{1}.sign] == -1);

% Since there are zero values in the first step of the summary, we ignore
% the first entry to get better plot axes.
ind = 2:size(smry.data,2);

% Put the well solution data into a format more suitable for plotting
[qWs, qOs, qGs, bhp] = wellSolToVector(wellSols);

% Get timesteps for both the reference and the MRST solution
T = convertTo(cumsum(schedule.step.val), year);
Tcomp =  smry.get(':+:+:+:+', 'YEARS', ind);

Plot producer gas/oil ratio

The most interesting part of the SPE1 case is the gas/oil ratio at the producer. We convert the field units and plot the dimensionless ratio. As should be apparent from the figure, the implicit solver is able to qualitatively reproduce the same outflow profile.

figure(3)
clf
ecl = convertFrom(smry.get('PRODUCER', 'WGOR', ind), 1000*ft^3/stb)';
mrst = qGs(:,prod)./qOs(:,prod);

mrstarg = {'LineWidth', 2};
eclarg = {'ro','MarkerSize',5,'MarkerFaceColor',[.6 .6 .6]};
hold on
plot(T, mrst, mrstarg{:})
plot(Tcomp, ecl, eclarg{:});
legend({'MRST', 'Eclipse'})
xlabel('Time [Years]')
title('Gas rate / Oil rate')
_images/blackoilTutorialSPE1_04.png

Plot producer bottom-hole pressure

The injector is rate controlled and so the bottom-hole pressure is solved in the implicit loop. Plot it to verify accuracy.

figure(4)
clf
ecl = convertFrom(smry.get('PRODUCER', 'WBHP', ind), psia)';
mrst = bhp(:,prod);
hold on
plot(T,     convertTo(mrst, barsa), mrstarg{:})
plot(Tcomp, convertTo(ecl, barsa), eclarg{:});
legend({'MRST', 'Eclipse'})
xlabel('Time [Years]')
ylabel('bar')
title('Bottom-hole pressure (producer)')
_images/blackoilTutorialSPE1_05.png

Plot injector bottom-hole pressure

figure(5)
clf
ecl = convertFrom(smry.get('INJECTOR', 'WBHP', ind), psia)';
mrst = bhp(:,inj);
hold on
plot(T,     convertTo(mrst, barsa), mrstarg{:})
plot(Tcomp, convertTo(ecl, barsa),  eclarg{:});
legend({'MRST', 'Eclipse'})
xlabel('Time [Years]')
ylabel('bar')
title('Bottom-hole pressure (injector)')
_images/blackoilTutorialSPE1_06.png

Copyright notice

<html>
% <p><font size="-1

Automated Time-Step Control

Generated from timestepControlDemo.m

The ad-core module offers several options for automatic time-step control. The simplest approach is to run the time steps as prescribed and use a heuristic algorithm to reduce the time step if the number of nonlinear iterations exceeds a prescribed limit; this is commonly referred to as the Appleyard chop. In the modified version, one also monitors the saturation increments and reduces the time step if the saturation changes more than a prescribed limit within a single time step. MRST also offers more a sophisticated time-step control that monitors the convergence of the nonlinear solves and adjusts the time-step so that the number of nonlinear iterations stays close to a prescribed target. In this example, we will use the SPE1 benchmark to demonstrate this time step control.

mrstModule add ad-props deckformat mrst-gui ad-core ad-blackoil

Load and run the SPE1 model

Because several examples use the SPE1 dataset, the initial setup is delegated to a helper function. See the inside for documentation.

[G, rock, fluid, deck, state] = setupSPE1();

% Determine the model automatically from the deck. It should be a
% three-phase black oil model with gas dissoluton.
model = selectModelFromDeck(G, rock, fluid, deck);

model %#ok, intentional display

% Convert the deck schedule into a MRST schedule by parsing the wells
schedule = convertDeckScheduleToMRST(model, deck);

% Run base case
[wellSols, states, report] = simulateScheduleAD(state, model, schedule);
No converter needed in section 'REGIONS'.
No converter needed in section 'SUMMARY'.
Adding 200 artificial cells at top/bottom

Processing regular i-faces
 Found 550 new regular faces
Elapsed time is 0.001781 seconds.

...

Time-step control with iteration targets

Because the SPE1 benchmark uses the same well configuration throughout the entire simulation, we are (relatively) free to choose timesteps. To demonstrate this, we create a simpler schedule consisting of a single very long timestep and then let the time-step control automatically select the length of the individual time steps so that the number of iterations for each step stays close to a prescribed target. We will run four simulations with target values 4, 8, 15, and 25. Use the compressSchedule routine to reduce the schedule to the minimal number of timesteps required to honor the distinct control steps. In this case, we only have a single control step. To get a stable solution, we also need to start with a small time step to get the solver started. Here, we start with a time step of one day and then gradually increase the length of the time step to stay close to our iteration target.

% Reduce to a minimal schedule
schedule_small = compressSchedule(schedule);

% Run all four simulations
rampup = 1*day;
targetIts = [4 8 15 25];
[reports, ws] = deal(cell(numel(targetIts), 1));
for i = 1:numel(targetIts)
    % Set up time selection class
    timestepper = ...
       IterationCountTimeStepSelector('targetIterationCount', targetIts(i),...
                                      'minRelativeAdjustment', sqrt(eps),...
                                      'maxRelativeAdjustment', 4, ...
                                      'firstRampupStep',       rampup, ...
                                      'verbose', true);

    % Instantiate a nonlinear solver with the timestep class as a
    % construction argument.
    nonlinear = NonLinearSolver('timeStepSelector', timestepper, ...
                                'maxiterations', 4*targetIts(i));
    % Solve and store results.
    [ws{i}, ~, reports{i}] = simulateScheduleAD(state, model, schedule_small,...
                        'nonlinearSolver', nonlinear, 'outputMinisteps', true);
end
*****************************************************************
********** Starting simulation:     1 steps,  1216 days *********
*****************************************************************
Preparing model for simulation...
Model ready for simulation...
Preparing schedule for simulation...
Schedule ready for simulation...
Validating initial state...
...

Combine output

We now have reports and solutions for the base case and the cases using automatic time-stepping. We extract the length of each time step from the simulation reports and plot versus time

l = arrayfun(@(x) ['Target: ', num2str(x), ' its'], targetIts, 'UniformOutput', false);
l = horzcat('Base case', l);

wsols = vertcat({wellSols}, ws);

timesteps = cell(numel(reports) + 1, 1);
timesteps{1} = schedule.step.val;
for i = 1:numel(reports)
    % Read out the actually used timesteps
    [~, t] = convertReportToSchedule(reports{i}, schedule_small);
    timesteps{i+1} = t;
end
% Sum up all timesteps to get time at each datapoint
time = cellfun(@cumsum, timesteps, 'UniformOutput', false);

% Plot the timestep lengths
figure;
hold on
c = lines(numel(timesteps));
for i = 1:numel(timesteps);
    plot(time{i}/day, timesteps{i}/day, '-o', 'linewidth', 2, 'color', c(i, :))
    grid on
end
legend(l,'Location','NorthWest')
_images/timestepControlDemo_01.png

Compare solution accuracy (well curves)

Looking at the various well curves, we see that whereas the water rate and water production is accurately resolved by all simulations, the larger time steps fail to accurately resolve the strong gradients in the oil and gas production as the gas front approaches the producer. On the other hand, using a target of 15 or 25 iterations allows time steps between one and two years, which are extremely, and one should generally not expect that this will give very accurate solutions.

plotWellSols(wsols, time, 'datasetnames', l);
_images/timestepControlDemo_02.png

Compare computational efficiency

To get an overall view of the computational cost of each simulation, we need to extract the number of iterations and computational time of each substep. These can be extracted from the report->control step reports.

reps = vertcat({report}, reports);

[iterations, timesteps] = deal(zeros(numel(reps), 1));
for i = 1:numel(reps)
    r = reps{i};
    timesteps(i) = sum(r.SimulationTime);
    for j = 1:numel(reps{i})
        for k = 1:numel(r.ControlstepReports)
            if r.Converged
                iterations(i) = iterations(i) + r.ControlstepReports{k}.Iterations;
            end
        end
    end
end
figure;
bar([iterations, timesteps])
legend('Non-linear iterations', 'Time taken')
set(gca, 'XTicklabel', l)
_images/timestepControlDemo_03.png

Conclusion

The automatic time-step control enables us to run very large time steps. However, since this will also introduce large errors in the temporal discretization, a reasonable compromise between accuracy and efficiency would most likely be to select an iteration target somewhere in the interval 3 to 8.

Copyright notice

<html>
% <p><font size="-1

Example Simulation of Model One From Tenth SPE CSP

Generated from tenthCSP_Model_I.m

mrstModule add ad-core ad-blackoil ad-props mrst-gui spe10
gravity reset on

Model Geometry

Grid is a 100-by-1-by-20 Cartesian box with equally sized cells of dimensions 25-by-25-by-2.5 feet.

cartDims = [ 100, 1, 20 ];
physDims = cartDims .* [ 25, 25, 2.5 ]*ft;
G = computeGeometry(cartGrid(cartDims, physDims));
Processing Cells    1 : 2000 of 2000 ... done (0.02 [s], 1.06e+05 cells/second)
Total 3D Geometry Processing Time = 0.019 [s]

Petrophysical Properties

Porosity is constant (=0.2) throughout the formation. The permeability distribution is a correlated geostatistically generated field stored in a file supplied by the SPE.

rock = getSPE10_model_1_rock();

clf
plotCellData(G, log10(convertTo(rock.perm(:,1), milli*darcy)))
view(3), axis tight, grid on
_images/tenthCSP_Model_I_01.png

Define Sources and Sinks

Model is produced from a single producer located at the far end of the model (I==100) constrained at a bottom-hole pressure of 95 Psi. There is a single injector at the near end (I==1) providing pressure support. The injector fills the reservoir with 6.97 cubic metres of gas per day. Both wells have an internal diameter of 1 ft.

IJK = gridLogicalIndices(G);
I   = find(IJK{1}(:,1) == 1);
P   = find(IJK{1}(:,1) == G.cartDims(1));  clear IJK

W = addWell([], G, rock, I, 'Comp_i', [ 0, 1 ], 'Type', 'rate', ...
            'Val', 6.97*meter^3/day, 'Radius', (1/2)*ft, 'Dir', 'z', ...
            'Sign', +1, 'Name', 'I', 'refDepth', 0*ft);  clear I

W = addWell(W, G, rock, P, 'Comp_i', [ 1, 1 ], 'Type', 'bhp', ...
            'Val', 95*psia, 'Radius', (1/2)*ft, 'Dir', 'z', ...
            'Sign', -1, 'Name', 'P', 'refDepth', 0*ft);  clear P

Official Benchmark Relative Permeability Data

Build a reduced ECLIPSE-style input deck that contains just enough information to construct relative permeability curves based on the official benchmark data. In particular we use the fact that the relative permeability data is formatted in the same way as ECLIPSE’s ‘SGOF’ keyword data.

kr_deck = getSPE10_model_1_relperm();

clf
plot(kr_deck.PROPS.SGOF{1}(:, 1),            ...
     kr_deck.PROPS.SGOF{1}(:, [2, 3]), '*-', ...
     'LineWidth', 2, 'MarkerSize', 5)
legend({'kr_g', 'kr_o'}, 'Location', 'Best')
xlabel('S_g'), title('Relative Permeability, Model I 10th CSP')
_images/tenthCSP_Model_I_02.png

Fluid Properties

The fluids in this simulation model are incompressible and immiscible with constant viscosities. This means we can use MRST’s special purpose fluid constructor initSimpleADIFluid to create the fluid object. We will however need to use sampled relative permeability curves so we do not enter any relative permeability data in this call.

fluid = initSimpleADIFluid('mu'    , [  1, 0.01]*centi*poise, ...
                           'rho'   , [700, 1   ]*kilogram/meter^3, ...
                           'cR'    , 6.0e-5/barsa, ...
                           'phases', 'OG');

Replace the synthetic relative permeability curves created through function initSimpleADIFluid with the real benchmark values.

fluid_kr = assignSGOF(fluid, kr_deck.PROPS.SGOF, struct('sat', 1));
fluid.krG = fluid_kr.krG{1};
fluid.krO = fluid_kr.krOG{1};              clear fluid_kr
Warning: No relperm points found in assignment of SGOF.

The SPE 10 module contains the special purpose function getSPE10_model_1_fluid that performes the above fluid manipulations so one would generally not do this manually.

Form Reservoir Model

This is an incompressible, immiscible oil/gas system.

model = GenericBlackOilModel(G, rock, fluid, 'gravity', gravity, 'disgas', false,...
    'vapoil', false, 'water', false, 'oil', true, 'gas', true);

Initialise Formation

Formation is initially filled with oil and the initial pressure at the top of the model is 100 Psi.

region = getInitializationRegionsBlackOil(model, 0, 'datum_pressure', 100*psia);
state0 = initStateBlackOilAD(model, region);

clf
plotCellData(G, convertTo(state0.pressure, psia))
view(3), colorbar(), axis tight, grid on
xlabel('x'), zlabel('Depth'), title('Initial Pressure Distribution [Psi]')
_images/tenthCSP_Model_I_03.png

Ten years (3650*day), ramp-up time-steps.

timesteps = [ 0.1, 0.2, 0.3, 0.4, repmat(0.5, [1, 6]), ones([1, 6]), ...
              repmat(2, [1, 5]), repmat(5, [1, 8]), repmat(10, [1, 9]), ...
              repmat(20, [1, 5]), repmat(30, [1, 50]), ...
              repmat(50, [1, 38]) ]*day;

% Set up the schedule containing both the wells and the timesteps
schedule = simpleSchedule(timesteps, 'W', W);
n = getPlotAfterStep(state0, model, schedule, 'view', [50, 50], ...
                      'field', 's:1', 'wells', W);

[wellSols, states, report] = ...
   simulateScheduleAD(state0, model, schedule, 'afterStepFn', fn);

% <html>
% <p><font size="-1
*****************************************************************
********** Starting simulation:   131 steps,  3650 days *********
*****************************************************************
Preparing model for simulation...
Model ready for simulation...
Preparing schedule for simulation...
Schedule ready for simulation...
Validating initial state...
...
_images/tenthCSP_Model_I_04.png
_images/tenthCSP_Model_I_05.png
_images/tenthCSP_Model_I_06.png

Ninth Comparative Solution Project

Generated from blackoilTutorialSPE9.m

This example runs the model from the SPE9 benchmark, which was posed twenty years ago to compare contemporary black-oil simulators (Killough, 1995). The reservoir is described by a 24 × 25 × 15 grid, having a 10 degree dipping-angle in the x-direction. By current standards, the model is quite small, but contains a few features that will still pose challenges for black-oil simulators. The 25 producers initially operate at a maximum rate of 1500 STBO/D, which is lowered to 100 STBO/D from day 300 to 360, and then raised up again to its initial value until the end of simulation at 900 days. The single water injector is set to a maximum rate of 5000 STBW/D with a maximum bottom-hole pressure of 4000 psi at reference depth. This setup will cause free gas to form after ~100 days when the reservoir pressure is reduced below the original saturation pressure. The free gas migrates to the top of the reservoir. During the simulation most of the wells convert from rate control to pressure control. A second problem is a discontinuity in the water-oil capillary pressure curve, which may cause difficulties in the Newton solver when saturations are changing significantly. In this comprehensive example, we will discuss the various parameters that enter the model and show how to set up state-of-the-art simulation using a CPR preconditioner with algebraic multigrid solver.

mrstModule add ad-blackoil ad-core mrst-gui ad-props deckformat

Set up model

This SPE Comparative Solution Project consists of a water injection problem in a highly heterogenous reservoir. There is one injector and 25 producers. The problem is set up to be solved using a black-oil model. The data set we provide is a modified version of input files belonging to the course in reservoir engineering and petrophysics at NTNU (Trondheim, Norway) and available at http://www.ipt.ntnu.no/~kleppe/pub/SPE-COMPARATIVE/ECLIPSE_DATA/. We have put most of the boilerplate setup into the setupSPE9 function.

[G, rock, fluid, deck, state0] = setupSPE9();

% Determine the model automatically from the deck. It should be a
% three-phase black oil model with gas dissoluton.
model = selectModelFromDeck(G, rock, fluid, deck);

% Show the model
model %#ok, intentional display

% Convert the deck schedule into a MRST schedule by parsing the wells
schedule = convertDeckScheduleToMRST(model, deck);
No converter needed in section 'REGIONS'.
No converter needed in section 'SUMMARY'.
Adding 1200 artificial cells at top/bottom

Processing regular i-faces
 Found 10625 new regular faces
Elapsed time is 0.006496 seconds.

...

Adjust time-step control

To ensure more stable time stepping, we set limits on changes in Rs, pressure, and saturation

model.drsMaxRel = inf;
model.dpMaxRel  = .1;
model.dsMaxAbs  = .1;

Configure linear solver

We proceed to setup a CPR-type solver, using the AGMG linear solver as the multigrid preconditioner. The CPR preconditioner attempts to decouple the fully implicit equation set into a pressure component and a transport component. The pressure is mathematically elliptic/parabolic in nature, and multigrid is well suited for solving these highly coupled, challenging problems. The remainder of the linear system, corresponding to the hyperbolic part of the equations is localized in nature and is primarily concerned with moving the saturation between neighboring grid blocks. Setting up a preconditioner is not strictly required to solve this problem, as the 9000 cells for a three-phase system results in linear systems with 27000 unknowns (one equation per phase, per cell). However, it does improve the solution speed and is required for larger cases, where Matlab’s standard linear solvers scale poorly.

try
    mrstModule add agmg
    pressureSolver = AGMGSolverAD();
catch
    pressureSolver = BackslashSolverAD();
end
linsolve = CPRSolverAD('ellipticSolver', pressureSolver, 'tolerance', 1e-3, 'relativeTolerance', 1e-5);
No module mapping found for
  * agmg

Plot the rock permeability

The SPE9 data set has an anisotropic, inhomogenous permeability field. The vertical permeability is 1/10th of the horizontal values. We plot the permeability using a log10 transform to better see the contrast.

v = [15, 30];
[G, rock] = deal(model.G, model.rock);

clf
K = convertTo(rock.perm(:,1), milli*darcy);
plotCellData(G, log10(K)); view(v), axis tight, box on
set(gca,'Projection','Perspective');
colorbarHist(K, [.01 1e4], 'South', 51, true);
title('SPE9 Horizontal permeability');
_images/blackoilTutorialSPE9_01.png

Plot the grid porosity

clf
plotCellData(G, rock.poro), view(v), axis tight, box on
set(gca,'Projection','Perspective');
colorbarHist(rock.poro, [.06 .18], 'South',31);
title('SPE9 Porosity');
_images/blackoilTutorialSPE9_02.png

Plot a single vertical set of cells

While the grid is structured, the grid has varying cell size along the vertical axis. To show this in detail, we plot the porosity in a single column of cells. We get the underlying logical grid and extract the subset corresponding to the first column (upper left corner of the grid). By using axis equal we see the actual aspect ratio of the cells.

[ii, jj] = gridLogicalIndices(G);
plotGrid(G, ii==1 & jj==1, 'FaceColor','none','EdgeColor','w');
set(gca,'Position',[.05 .2 .8 .73]); axis off, title('');
axes('Position',[.7 .6 .3 .4]);
plotCellData(G, rock.poro, ii == 1 & jj == 1)
axis equal tight off, view(v)
_images/blackoilTutorialSPE9_03.png

Plot initial saturation of oil and water

Initially, the reservoir does not contain free gas. We plot the initial saturations using the RGB plotting feature, where a three column matrix sent to plotCellData is interpreted column wise as fractions of red, green and blue respectively. Since MRST convention is to order the phases in the order WOG we need to permute the column index slightly to get red oil, blue water and green gas.

s = state0.s(:, [2, 3, 1]);
clf
plotCellData(G, s)
axis tight off; view(v)
set(gca,'Projection','Perspective');

% Add ternary colorbar
axes('Position',[.15 .12 .15 .15]);
patch('Vertices', [0 0; 2 0; 1 2*sin(pi/3)], 'Faces',1:3, ...
    'FaceVertexCData', [0 0 1; 1 0 0; 0 1 0],'FaceColor','interp');
text(0,0,'W','HorizontalAlignment','right');
text(2,0,'O','HorizontalAlignment','left');
text(1,2*sin(pi/3)+.1,'G','HorizontalAlignment','center');
axis tight off
_images/blackoilTutorialSPE9_04.png

Examine gas dissolved in oil phase

Even though there is no free gas initially, there is significant amounts of gas dissolved in the oil phase. The dissolved gas will bubble into free gas when the pressure drops below the bubble point pressure. For a given pressure there is a fixed amount of gas that can be dissolved in the black-oil instantanous dissolution model. To illustrate how saturated the initial conditions are, we plot the function

g(p) = \frac{R_s}{R_s^{sat}(p)}

I.e. how close the oil phase is to being completely saturated for the current pressure. A value near one means that the liquid is close to saturated and any values above one will immediately lead to free gas appearing in the simulation model. As we can see from the figure, free gas will appear very quickly should the pressure drop.

Rs_sat = model.fluid.rsSat(state0.pressure);
Rs = state0.rs;

clf
plotCellData(G, Rs./Rs_sat)
axis tight off; colorbar, view(v)
set(gca,'Projection','Perspective');
title('Fraction of maximum gas saturation in oil phase - g(p)');
_images/blackoilTutorialSPE9_05.png

Plot the wells

Since there is a large number of wells, we plot the wells without any labels and simply color the injector in blue and the producers in red.

W = schedule.control(1).W;
sgn = [W.sign];
clf
plotGrid(G, 'FaceColor', 'none')
plotWell(G, W(sgn>0), 'fontsize', 0, 'color', 'b')
plotWell(G, W(sgn<0), 'fontsize', 0, 'color', 'r')
axis tight off; view(v)
set(gca,'Projection','Perspective');
_images/blackoilTutorialSPE9_06.png

Examine the schedule

The simulation schedule consists of three control periods. All 26 wells are present during the entire simulation, but their prescribed rates will change. The injector is injecting a constant water rate, while the producers all produce a constant oil rate, letting bottom hole pressures and gas/water production vary. Since all producers have the same controls, we can examine PROD2 in detail. We plot the controls, showing that the well rate drops sharply midway during the simulation

wno = find(strcmp({schedule.control(1).W.name}, 'PROD2'));

% Extract controls for all timesteps
P = arrayfun(@(ctrl) schedule.control(ctrl).W(wno).val, schedule.step.control);
T = cumsum(schedule.step.val);
stairs(T/year, convertTo(-P, stb/day), 'o-k','MarkerSize',6,'MarkerFaceColor',[.6 .6 .6])
set(gca,'FontSize',12)
xlabel('Time (years)')
title('Controls for PROD2: oil rate [stb/day]')
set(gca,'YLim',[0 1600]);
_images/blackoilTutorialSPE9_07.png

Examine well limits

Note that the well controls are not the only way of controlling a well. Limits can be imposed on wells, either due to physical or mathematical considerations. In this case, fixed oil rate is the default setting, but the well will switch controls if the pressure drops below a threshold. This is found in the lims field for each well. Since this is a producer, the bhp limit is considered a lower limit, whereas a bhp limit for an injector would be interpreted as a maximum limit to avoid either equipment failure or formation of rock fractures.

clc
disp(['Well limits for ', schedule.control(1).W(wno).name, ':'])
disp(schedule.control(1).W(wno).lims)
Well limits for PROD2:
    orat: -0.0028
    wrat: -Inf
    grat: -Inf
    lrat: -Inf
     bhp: 6.8948e+06
     thp: 0

Plot relative permeability curves

For a three-phase model we have four relative permeability curves. One for both gas and water and two curves for the oil phase. The oil relative permeability is tabulated for both water-oil and oil-gas systems, and as we can see from the following plot, this gives a number of kinks that will tend to pose challenges for the Newton solver.

f = model.fluid;
s = (0:0.01:1)';

figure;
plot(s, f.krW(s), 'linewidth', 2)
grid on
xlabel('Water saturation');
title('Water relative permeability curve')
ylabel('k_r')

figure;
plot(s, [f.krOW(s), f.krOG(s)], 'linewidth', 2)
grid on
xlabel('Oil saturation');
legend('Oil-Water system', 'Oil-Gas system', 'location', 'northwest')
title('Oil relative permeability curves')
ylabel('k_r')

figure;
plot(s, f.krG(s), 'linewidth', 2)
grid on
xlabel('Gas saturation');
title('Gas relative permeability curve')
ylabel('k_r')
_images/blackoilTutorialSPE9_08.png
_images/blackoilTutorialSPE9_09.png
_images/blackoilTutorialSPE9_10.png

Plot three-phase relative permeability

When all three phases are present simultaneously in a single cell, we need to use some functional relationship to combine the two-phase curves in a reasonable manner, resulting in a two-dimensional relative permeability model. Herein, we use a simple linear interpolation, which is also the default in Eclipse

close all

[x, y] = meshgrid(s);
krO = zeros(size(x));

for i = 1:size(x, 1)
    xi = x(i, :);
    yi = y(i, :);
    [~, krO(i, :), ~] = model.relPermWOG(xi, 1 - xi - yi, yi, f);
end
figure;
krO(x+y>1)=nan;
surfl(x, y, krO), shading interp
xlabel('sW')
ylabel('sG')
title('Oil relative permeability')
view(150, 50); axis tight, camlight headlight
_images/blackoilTutorialSPE9_11.png

Plot capillary pressure curves

SPE9 contains significant capillary pressure, making the problem more nonlinear as the flow directions and phase potential gradients are highly saturation dependent. Again we have two curves, one for the contact between oil and gas and another for the water-oil contact.

close all
figure;
[ax, l1, l2] = plotyy(s, f.pcOG(s), s, f.pcOW(1 - s));
set([l1, l2], 'LineWidth', 2);
grid on
legend('Oil-Gas capillary pressure', 'Oil-Water capillary pressure', 'location', 'southeast')
xlabel('Oil saturation (Two phase)')
_images/blackoilTutorialSPE9_12.png

Plot compressibility

The black-oil model treats fluid compressibility through tabulated functions often referred to as formation volume factors (or B-factors). To find the mass of a given volume at a specific reservoir pressure

, we write

M_\alpha = V_R \rho_\alpha^s b_\alpha (p_R)

where

refers to either the phase, V_R the volume occupied at reservoir conditions and

is the surface / reference density when the B-factor is 1.

Note that MRST by convention only uses small b to describe fluid models. The relation between B and b is simply the reciprocal

and will be calculated when needed.

We begin by plotting the B-factors/compressibility for the water and gas phases. Note that the water compressibility is minimal, as water is close to incompressible in most models. The gas compressibility varies several orders of magnitude. The rock compressibility is included as well. Rock compressibility is modelling the poroelastic expansion of the pore volume available for flow. As the rock itself shrinks, more fluid can fit inside it. Note that although the curves shown in this particular case are all approximately linear, there is no such requirement on the fluid model.

pressure = (50:10:500)'*barsa;

close all
figure;
plot(pressure/barsa, 1./f.bW(pressure), 'LineWidth', 2);
grid on
title('Water formation volume factor')
ylabel('B_w')
xlabel('Pressure [bar]');

figure;
plot(pressure/barsa, 1./f.bG(pressure), 'LineWidth', 2);
grid on
title('Gas formation volume factor')
ylabel('B_g')
xlabel('Pressure [bar]');

figure;
plot(pressure/barsa, f.pvMultR(pressure), 'LineWidth', 2);
grid on
title('Rock compressibility')
ylabel('1 + c_r (p - p_s)')
xlabel('Pressure [bar]');
_images/blackoilTutorialSPE9_13.png
_images/blackoilTutorialSPE9_14.png
_images/blackoilTutorialSPE9_15.png

Plot oil compressibility

Since we allow the gas phase to dissolve into the oil phase, compressibility does not only depend on the pressure: The amount of dissolved gas will also change how much the fluid can be compressed. We handle this by having saturated and undersatured tables for the formation volume factors (FVF). This is reflected in the figure: Unsaturated FVF curves will diverge into from the main downwards sloping trend into almost constant curves sloping downwards. Physically, the undersaturated oil will swell as more gas is being introduced into the oil, increasing the volume more than the pressure decreases the volume of the oil itself. When the oil is completely saturated, the volume decrease is due to the gas-oil mixture itself being compressed.

rs = 0:25:320;
[p_g, rs_g] = meshgrid(pressure, rs);
rssat = zeros(size(p_g));
for i = 1:size(p_g, 1)
    rssat(i, :) = f.rsSat(p_g(i, :));
end

saturated = rs_g >= rssat;
rs_g0 = rs_g;
rs_g(saturated) = rssat(saturated);

figure
plot(p_g'/barsa, 1./f.bO(p_g, rs_g, saturated)', 'LineWidth', 2)
grid on
title('Oil formation volume factor')
ylabel('B_o')
xlabel('Pressure [bar]')
_images/blackoilTutorialSPE9_16.png

Plot the viscosity

The viscosity can also depend on the pressure and dissolved components in a similar manner as the compressibility. Again, we note that the water phase is unaffected by the pressure, the gas changes viscosity quite a bit. As with

, the oil viscosibility depends more on the amount of dissolved gas than the pressure itself and we have undersatured tables to show.

SPE9 only allows gas to dissolve into oil, and not the other way around. Generally, the black-oil model is a pseudo-compositional model where both gas in oil (

) and oil in gas (

) can be included.

close all
figure;
plot(pressure/barsa, f.muW(pressure), 'LineWidth', 2);
grid on
title('Water viscosity')
ylabel('\mu_w')
xlabel('Pressure');
ylim([0, 1.5e-3])

figure;
plot(pressure/barsa, f.muG(pressure), 'LineWidth', 2);
grid on
title('Gas viscosity')
ylabel('\mu_g')
xlabel('Pressure');

figure;
plot(p_g'/barsa, f.muO(p_g, rs_g, saturated)', 'LineWidth', 2)
grid on
title('Oil viscosity')
ylabel('\mu_o')
xlabel('Pressure')
_images/blackoilTutorialSPE9_17.png
_images/blackoilTutorialSPE9_18.png
_images/blackoilTutorialSPE9_19.png

Simulate the schedule

We run the schedule. We provide the initial state, the model (containing the black oil model in this case) and the schedule with well controls, and control time steps. The simulator may use other timesteps internally, but it will always return values at the specified control steps.

close all
model.verbose = false;
fn = getPlotAfterStep(state0, model, schedule, ...
    'plotWell', false, 'plotReservoir', false);
[wellsols, states, reports] =...
    simulateScheduleAD(state0, model, schedule, ...
                       'LinearSolver', linsolve, 'afterStepFn', fn);
*****************************************************************
********** Starting simulation:    35 steps,   900 days *********
*****************************************************************
Preparing model for simulation...
Model ready for simulation...
Preparing schedule for simulation...
Schedule ready for simulation...
Validating initial state...
...
_images/blackoilTutorialSPE9_20.png

Launch interactive plot tool for well curves

The interactive viewer can be used to visualize the wells and is the best choice for interactive viewing.

plotWellSols(wellsols, cumsum(schedule.step.val))
h = gcf;
_images/blackoilTutorialSPE9_21.png

Load comparison data from commercial solver

To validate the simulator output, we load in a pre-run dataset from a industry standard commercial solver run using the same inputs.

addir = mrstPath('ad-blackoil');
compare = fullfile(addir, 'examples', 'spe9', 'compare');
smry = readEclipseSummaryUnFmt(fullfile(compare, 'SPE9'));

compd = 1:(size(smry.data, 2));
Tcomp =  smry.get(':+:+:+:+', 'YEARS', compd);
Reading info from roughly 38 ministeps:  100%
Actual number of ministeps: 37

Set up plotting functions

We will plot the timesteps with different colors to see the difference between the results clearly.

if ishandle(h)
    close(h);
end
T = convertTo(cumsum(schedule.step.val), year);

mrstplot = @(data) plot(T, data, '-b', 'linewidth', 2);
compplot = @(data) plot(Tcomp, data, 'ro', 'linewidth', 2);

Plot two different producers

We plot the bottom-hole pressures for two somewhat arbitrarily chosen producers to show the accuracy of the pressure.

figure;
names = {'PROD13', 'PROD18'};
nn = numel(names);
for i = 1:nn

    name = names{i};

    comp = convertFrom(smry.get(name, 'WBHP', compd), psia)';
    mrst = getWellOutput(wellsols, 'bhp', name);

    subplot(nn, 1, i)
    hold on
    mrstplot(mrst);
    compplot(comp);
    title(name)
    axis tight
    grid on

    xlabel('Time (years)')
    ylabel('Pressure (Pa)')
end
legend({'MRST', 'ECLIPSE'})
_images/blackoilTutorialSPE9_22.png

Plot the gas production rate

We plot the gas production rate (at surface conditions).

figure;

for i = 1:nn
    name = names{i};
    comp = convertFrom(smry.get(name, 'WGPR', compd), 1000*ft^3/day);
    mrst = abs(getWellOutput(wellsols, 'qGs', name));

    subplot(nn, 1, i)
    hold on
    mrstplot(mrst);
    compplot(comp);
    title(name)
    axis tight
    grid on

    xlabel('Time (years)')
    ylabel('Gas rate (m^3/s)')
end
legend({'MRST', 'ECLIPSE'})
_images/blackoilTutorialSPE9_23.png

Changing controls

We saw earlier that all wells are initially rate controlled, but in practice a large number of wells will switch controls during the simulation. To show how each well changes throughout the simulation, we will plot indicators per well as a colorized matrix. From this we can clearly see that: - The injector switched immediately to BHP controls and stays there throughout the simulation (Well #1) - The producers are mostly rate controlled in the beginning and mostly BHP controlled at the end as a result of the average field pressure dropping during the simulation as mass is removed from the reservoir. - The period with very low controls at 1 year is easy to see.

isbhp = @(ws) arrayfun(@(x) strcmpi(x.type, 'bhp'), ws);
ctrls = cellfun(isbhp, wellsols, 'UniformOutput', false);
ctrls = vertcat(ctrls{:});

nw = numel(wellsols{1});
nstep = numel(wellsols);

X = repmat(0:nw, nstep, 1)+.5;
Y = repmat(convertTo(convertFrom(T, year), day), 1, nw+1);
C = double(ctrls); C=C(:,[1:end end]);
clf
pcolor(X, Y, C); view(90, 90); axis tight
set(gca,'XTick',[],'FontSize',12);
ylabel('Time (days)')
text((X(1,2:end)+X(1,1:end-1))/2, Y(1,2:end)-5, {wellsols{1}.name},...
    'HorizontalAlignment','right','FontSize',8);
colormap(.55*lines(2)+.45*ones(2,3))
set(colorbar,'Ytick',[.25 .75],'YTickLabel',{'rate','bhp'})
_images/blackoilTutorialSPE9_24.png

Plot pressure before and after schedule

We plot the pressure after the very first timestep alongside the pressure after the final timestep. By scaling the color axis by the minimum of the final state and the maximum of the first state, we can clearly see how the pressure has dropped due to fluid extraction.

h1 = figure;
h2 = figure;

p_start = states{1}.pressure;
p_end = states{end}.pressure;
cscale = convertTo([min(p_end), max(p_start)],barsa);

figure(h1); clf;
plotCellData(G, convertTo(p_start,barsa))
axis tight; colorbar, view(v), caxis(cscale);
title('Pressure after first timestep')

figure(h2); clf;
plotCellData(G, convertTo(p_end,barsa))
axis tight; colorbar, view(v), caxis(cscale);
title('Pressure after final timestep')
_images/blackoilTutorialSPE9_25.png
_images/blackoilTutorialSPE9_26.png

Plot free gas

Since the pressure has dropped significantly and we know that gas is being produced from the initially nearly saturated reservoir, we will look at the free gas. We consider the initial and the last state and use the same coloring.

sg0 = state0.s(:, 3);
sg = states{end}.s(:, 3);
cscale = [0, max(sg)];

figure(h1); clf;
plotCellData(G, sg0)
axis tight off; colorbar; view(v); caxis(cscale);
title('Free gas after first timestep')

figure(h2); clf;
plotCellData(G, sg)
axis tight; colorbar, view(v), caxis(cscale);
title('Free gas after final timestep')
_images/blackoilTutorialSPE9_27.png
_images/blackoilTutorialSPE9_28.png

Plot dissolved gas

Since we did not inject any gas, the produced and free gas must come from the initially dissolved gas in oil (

). We plot the values before and after the simulation, scaling the color by the initial values. Note that the

values are interpreted as the fraction of gas present in the oil phase. As the fraction is calculated at standard conditions, the

value is typically much larger than 1. We weight by oil saturations to obtain a reasonable picture of how the gas in oil has evolved. Plotting just the

value is not meaningful if

is small.

gasinoil_0 = states{1}.rs.*states{1}.s(:, 2);
gasinoil = states{end}.rs.*states{end}.s(:, 2);
cscale = [0, max(gasinoil_0)];

figure(h1); clf;
plotCellData(G, gasinoil_0)
axis tight; colorbar; view(v); caxis(cscale);
title('Gas in after first timestep')

figure(h2); clf;
plotCellData(G, gasinoil)
axis tight; colorbar; view(v); caxis(cscale);
title('Gas in oil after final timestep')
_images/blackoilTutorialSPE9_29.png
_images/blackoilTutorialSPE9_30.png

Plot phase distribution

s0 = states{1}.s(:, [2, 3, 1]);
s = states{end}.s(:, [2, 3, 1]);

figure(h1); clf;
plotCellData(G, s0)
axis tight; view(v)
title('Phase distribution after first timestep')

figure(h2); clf;
plotCellData(G, s)
axis tight; view(v)
title('Phase distribution after final timstep')
_images/blackoilTutorialSPE9_31.png
_images/blackoilTutorialSPE9_32.png

Copyright notice

<html>
% <p><font size="-1