blackoil-sequential: Sequential implicit black-oil solvers

The blackoil-sequential module implements sequential solvers for the same set of equations that are implemented with a fully-implicit discretization in ad-blackoil. The solvers are based on a fractional flow formulation wherein pressure and transport are solved as separate steps. These solvers can be significantly faster than those found in ad-blackoil for many problems, especially problems where the total velocity changes slowly during the simulation.

Models

Base classes

Contents

MODELS

Files
AcceleratedSequentialModel - Accelerated sequential model inspired by “Nonlinear acceleration of PressureBlackOilModel - Pressure model for three-phase, blackoil equations PressureBlackOilPolymerModel - Polymer pressure model for blackoil PressureModel - PressureOilWaterModel - Pressure model for two phase oil/water system without dissolution PressureOilWaterPolymerModel - Two phase oil/water system with polymer SequentialPressureTransportModel - Sequential meta-model which solves pressure and transport SequentialPressureTransportModelPolymer - Call parent TransportBlackOilModel - Two phase oil/water system without dissolution TransportBlackOilPolymerModel - Two phase oil/water system with polymer TransportModel - TransportOilWaterModel - Two phase oil/water system without dissolution TransportOilWaterPolymerModel - Two phase oil/water system with polymer
class AcceleratedSequentialModel(pressureModel, transportModel, varargin)

Bases: SequentialPressureTransportModel

Accelerated sequential model inspired by “Nonlinear acceleration of sequential fully implicit (SFI) method for coupled flow and transport in porous media” by Jiang & Tchelepi, 2019

accelerationType = None

Choice for acceleration

includePressure = None

Include pressure updates in acceleration strategy

m = None

Algorithm parameter

w = None

Default relaxation - algorithm parameter

class PressureBlackOilModel(G, rock, fluid, varargin)

Bases: ThreePhaseBlackOilModel

Pressure model for three-phase, blackoil equations

PressureBlackOilModel(G, rock, fluid, varargin)

Construct pressure model

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

Rely on parent class for update, and store pressure increments in state.

incTolPressure = None

Boolean indicating if increment tolerance is being used

class PressureBlackOilPolymerModel(G, rock, fluid, varargin)

Bases: ThreePhaseBlackOilPolymerModel

Polymer pressure model for blackoil

class PressureOilWaterModel(G, rock, fluid, varargin)

Bases: TwoPhaseOilWaterModel

Pressure model for two phase oil/water system without dissolution

incTolPressure = None

Boolean indicating if increment tolerance is being used

class PressureOilWaterPolymerModel(G, rock, fluid, varargin)

Bases: OilWaterPolymerModel

Two phase oil/water system with polymer

class SequentialPressureTransportModel(pressureModel, transportModel, varargin)

Bases: ReservoirModel

Sequential meta-model which solves pressure and transport

checkOuterConvergence(model, state, state0, dt, drivingForces, iteration, pressure_state)

Alternate mode: If outer loop is enabled, we will revisit the pressue equation to verify that the equation remains converged after the transport step. This check ensures that the assumption of fixed total velocity is reasonable up to some tolerance.

getActivePhases(model)

Transport model solves for saturations, so that is where the active phases are defined

solvePressureTransport(model, state, state0, dt, drivingForces, nls, iteration)

Solve pressure and transport sequentially

validateState(model, state)

Pressure comes first, so validate that.

incTolSaturation = u'1e-3'

Tolerance for change in saturations in transport for convergence.

maxOuterIterations = u'inf'

Maximum outer loops for a given step before assuming convergence.

outerCheckParentConvergence = u'true'

Use parentModel, if present, to check convergence

parentModel = None

Parent class. Used for outer loop convergence if present

pressureModel = None

Model for computing the pressure

pressureNonLinearSolver = None

NonLinearSolver instance used for pressure updates

reupdatePressure = None

Update pressure based on new mobilities before proceeding to next step

transportModel = None

Model for the transport subproblem after pressure is found

transportNonLinearSolver = None

NonLinearSolver instance used for saturation/mole fraction updates

volumeDiscrepancyTolerance = u'1e-3'

Tolerance for volume error (|sum of saturations - 1|)

class TransportBlackOilModel(G, rock, fluid, varargin)

Bases: ThreePhaseBlackOilModel

Two phase oil/water system without dissolution

class TransportBlackOilPolymerModel(G, rock, fluid, varargin)

Bases: ThreePhaseBlackOilPolymerModel

Two phase oil/water system with polymer

class TransportOilWaterModel(G, rock, fluid, varargin)

Bases: TwoPhaseOilWaterModel

Two phase oil/water system without dissolution

class TransportOilWaterPolymerModel(G, rock, fluid, varargin)

Bases: OilWaterPolymerModel

Two phase oil/water system with polymer

Contents

EQUATIONS

Files
pressureEquationBlackOil - pressureEquationBlackOilPolymer - pressureEquationOilWater - pressureEquationOilWaterPolymer - Undocumented Utility Function transportEquationBlackOil - Generate linearized problem for a volatile 3Ph system (wet-gas, live-oil). transportEquationBlackOilPolymer - transportEquationOilWater - transportEquationOilWaterPolymer -
pressureEquationOilWaterPolymer(state0, state, model, dt, drivingForces, varargin)

Undocumented Utility Function

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

Generate linearized problem for a volatile 3Ph system (wet-gas, live-oil).

Utilities

Contents

UTILS

Files
computeSequentialFluxes - Undocumented Utility Function convertPressureBoundaryConditionsToFlux - Undocumented Utility Function getPressureTransportIterations - Get pressure, transport and outer loop iterations for sequential or getSaturationUpwind - Get upwind flags for viscous and gravity parts of flux function. getSequentialModelFromFI - For a given fully implicit model, output the corresponding pressure/transport model hybridUpwind - Hybrid upwinding - seperate gravity/capillary and viscous part multiphaseUpwindIndices - Implementation of algorithm from “UPSTREAM DIFFERENCING FOR
computeSequentialFluxes(state, G, vT, T, mob, rho, components, upstr, upwindType)

Undocumented Utility Function

convertPressureBoundaryConditionsToFlux(G, state, bc)

Undocumented Utility Function

getPressureTransportIterations(seqReport)

Get pressure, transport and outer loop iterations for sequential or fully-implicit model

getSaturationUpwind(upwtype, state, G, vT, T, K, upstr)

Get upwind flags for viscous and gravity parts of flux function.

getSequentialModelFromFI(fimodel, varargin)

For a given fully implicit model, output the corresponding pressure/transport model

hybridUpwind(G, vT, T, K, upstr)

Hybrid upwinding - seperate gravity/capillary and viscous part

multiphaseUpwindIndices(G, vT, T, K, upstr)

Implementation of algorithm from “UPSTREAM DIFFERENCING FOR MULTIPHASE FLOW IN RESERVOIR SIMULATIONS” by Brenier & Jaffre

Examples

Buckley-Leverett problem with multiple substeps

Generated from multipleSubstepsDemo.m

This example demonstrates how the sequential solvers perform on a one dimensional problem. In addition, we demonstrate how the sequential solvers can be configured to have a different time step selection for pressure and transport in order to reduce numerical diffusion from the temporal discretization.

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

Set up model

Construct 3D grid with 50 cells in the x-direction

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

% Homogenous rock properties
rock = struct('perm', darcy*ones(G.cells.num, 1), ...
              'poro', .3*ones(G.cells.num, 1));

% Default oil-water fluid with unit values, quadratic relative permeability
% curves and a 2:1 viscosity ratio between oil and water.
fluid = initSimpleADIFluid('phases', 'WO', 'n', [2 2],...
                           'mu', [1, 2]*centi*poise);

% Set up model and initial state.
model = TwoPhaseOilWaterModel(G, rock, fluid);
state0 = initResSol(G, 50*barsa, [0, 1]);
state0.wellSol = initWellSolAD([], model, state0);

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

Solve fully implicit base case

We first solve the problem using the standard fully implicit discretization found in ad-blackoil in the regular manner.

dT = 20*day;
schedule = simpleSchedule(repmat(dT,1,25), 'bc', bc);
[~, states] = simulateScheduleAD(state0, model, schedule);
Solving timestep 01/25:                  -> 20 Days
Solving timestep 02/25: 20 Days          -> 40 Days
Solving timestep 03/25: 40 Days          -> 60 Days
Solving timestep 04/25: 60 Days          -> 80 Days
Solving timestep 05/25: 80 Days          -> 100 Days
Solving timestep 06/25: 100 Days         -> 120 Days
Solving timestep 07/25: 120 Days         -> 140 Days
Solving timestep 08/25: 140 Days         -> 160 Days
...

Get a sequential model

We can set up a sequential model from the fully implicit model. The sequential model is a special model that solves each control step with a pressure and a transport model, each with their own linear and nonlinear solvers. The advantage of this abstraction is that it easy to create highly tailored solvers that exploit the properties of the underlying equations.

seqModel = getSequentialModelFromFI(model);
% We have two different models, each with their own properties and options,
% allowing for flexible configurations.
% Display the pressure model
disp(seqModel.pressureModel)
% Display the pressure model
disp(seqModel.transportModel);
PressureOilWaterModel with properties:

           incTolPressure: 1.0000e-03
                useIncTol: 1
                   disgas: 0
                   vapoil: 0
                drsMaxRel: Inf
                drsMaxAbs: Inf
...

Simulate sequential base case

We can now simply pass the sequential model to simulateScheduleAD just like we did with the regular fully implicit model.

[~, statesSeq] = simulateScheduleAD(state0, seqModel, schedule);
Solving timestep 01/25:                  -> 20 Days
Solving timestep 02/25: 20 Days          -> 40 Days
Solving timestep 03/25: 40 Days          -> 60 Days
Solving timestep 04/25: 60 Days          -> 80 Days
Solving timestep 05/25: 80 Days          -> 100 Days
Solving timestep 06/25: 100 Days         -> 120 Days
Solving timestep 07/25: 120 Days         -> 140 Days
Solving timestep 08/25: 140 Days         -> 160 Days
...

Set up timestep selection based on target change quantities

We will now compute a solution with refined time steps. As the time-steps become smaller, the solution becomes more accurate. To achieve increased accuracy without manually changing the timesteps, we can use an automatic time-step selector based on saturation change targets. We let the solver aim for a maximum saturation change of 1% in each cell during the timesteps to get very small steps.

stepSel = StateChangeTimeStepSelector(...
          'targetProps', 's',...
           % Saturation as change target
          'targetChangeAbs', 0.01,...
      % Target change of 0.01
          'firstRampupStepRelative', 0.01); % Initial rampup step is dt0/100

Simulate with refined timesteps

The main issue with small timesteps is that they can be very time-consuming to compute, especially as the full system has to be solved at every step. We therefore pass the step selector to the nonlinear solver corresponding to the transport subproblem to only take small steps in the transport solver itself, leaving the pressure only to be updated in the original control steps.

seqModel.transportNonLinearSolver.timeStepSelector = stepSel;
[~, statesFine, repFine] = simulateScheduleAD(state0, seqModel, schedule);
Solving timestep 01/25:                  -> 20 Days
Solving timestep 02/25: 20 Days          -> 40 Days
Solving timestep 03/25: 40 Days          -> 60 Days
Solving timestep 04/25: 60 Days          -> 80 Days
Solving timestep 05/25: 80 Days          -> 100 Days
Solving timestep 06/25: 100 Days         -> 120 Days
Solving timestep 07/25: 120 Days         -> 140 Days
Solving timestep 08/25: 140 Days         -> 160 Days
...

Plot and compare the three different solutions

By plotting the three solutions simultanously, we see that the fully implicit and sequential implicit give the same solution for the same timesteps. The solution that is refined in time is quite different, however, demonstrating that the numerical truncation error due to the temporal discretization can significantly impact the simulation results.

x = G.cells.centroids(:, 1);

for i = 1:numel(statesFine)
    figure(1); clf, hold on
    plot(x, states{i}.s(:, 1), '-')
    plot(x, statesSeq{i}.s(:, 1), '.')
    plot(x, statesFine{i}.s(:, 1), '--')
    ylim([0, 1])
    legend('Fully implicit', 'Sequential', 'Sequential \Delta s_{max} = 0.01');
    ylabel('Water saturation');
    xlabel('x coordinate');
    pause(0.1)
end
_images/multipleSubstepsDemo_01.png

Set up simulation problem

Generated from relaxedVolumeSPE10.m

totTime = 2000*day;

layers = 5;
nLayers = numel(layers);

% Offsets to move the wells if needed
ofs = [0, 0];
wloc     = [  1 + ofs(1),   60 - ofs(1),     1 + ofs(1),   60 - ofs(1),   30 ;
              1 + ofs(2),    1 + ofs(2),   220 - ofs(2),  220 - ofs(2),  110 ];

[G, W, rock] = getSPE10setup(layers, wloc);

mp = 0.01;
rock.poro(rock.poro < mp) = mp;

pv = poreVolume(G, rock);

% Either two-phase or three-phase
if 1
    compi = [.5, 0, .5];
    sat = [0, 1, 0];
    getmodel = @ThreePhaseBlackOilModel;
else
    compi = [1, 0];
    sat = [0, 1];
    getmodel = @TwoPhaseOilWaterModel;
end

for i = 1:numel(W)
    isinj = W(i).val > 400*barsa;
    if isinj
        W(i).val = sum(pv)/totTime;
        W(i).type = 'rate';
    else
        W(i).val = 4000*psia;
        W(i).type = 'bhp';
    end
    W(i).compi = compi;
    W(i).sign = 1 - 2*~isinj;
end

% Set up model with quadratic relperms and constant compressibility in oil
p0 = 300*barsa;
c = [1e-6, 1e-4, 1e-3]/barsa;
% c = [0,0,0];
fluid = initSimpleADIFluid('mu', [1, 5, 1]*centi*poise, ...
                           'c', c, ...
                           'rho', [1000, 700, 1], 'n', [2 2 2]);

% Fully implicit model
modelfi = getmodel(G, rock, fluid);
% Sequential pressure-transport model with same type


figure;
plotCellData(G, log10(rock.perm(:, 1)), 'edgec', 'none');
plotWell(G, W)
% Set up the schedule
nstep = 100;

dt = rampupTimesteps(totTime, totTime/nstep);
clear schedule;
schedule = simpleSchedule(dt, 'W', W);

state = initResSol(G, p0, sat);
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.
Defaulting reference depth to top of formation for well P2. Please specify 'refDepth' optional argument to 'addWell' if you require bhp at a specific depth.
Defaulting reference depth to top of formation for well P3. Please specify 'refDepth' optional argument to 'addWell' if you require bhp at a specific depth.
Defaulting reference depth to top of formation for well P4. Please specify 'refDepth' optional argument to 'addWell' if you require bhp at a specific depth.
Defaulting reference depth to top of formation for well I1. Please specify 'refDepth' optional argument to 'addWell' if you require bhp at a specific depth.
_images/relaxedVolumeSPE10_01.png

Solve a sequential solve

We first solve a pressure equation, fix total velocity and solve for n-1 phases. To find the remaining saturation, we use the relation

We naturally incur a bit of mass-balance error for the omitted phase pseudocomponent.

model = getSequentialModelFromFI(modelfi);
model.transportModel.useCNVConvergence = true;

[ws_seq, states_seq, rep_seq] = simulateScheduleAD(state, model, schedule);
*****************************************************************
********** Starting simulation:   108 steps,  2000 days *********
*****************************************************************
Preparing model for simulation...
Model ready for simulation...
Preparing schedule for simulation...
Schedule ready for simulation...
Validating initial state...
...

Solve a second sequential solve

We first solve a pressure equation, fix total velocity and solve for all phases/components. We relax the sum of saturations assumption to achieve this.

model.transportModel.conserveOil = true;
model.transportModel.conserveWater = true;
model.transportModel.useCNVConvergence = true;
[ws_seq2, states_seq2, rep_seq2] = simulateScheduleAD(state, model, schedule);
*****************************************************************
********** Starting simulation:   108 steps,  2000 days *********
*****************************************************************
Preparing model for simulation...
Model ready for simulation...
Preparing schedule for simulation...
Schedule ready for simulation...
Validating initial state...
...

Fully-implicit

[wsfi, statesfi] = simulateScheduleAD(state, modelfi, schedule);
*****************************************************************
********** Starting simulation:   108 steps,  2000 days *********
*****************************************************************
Preparing model for simulation...
Model ready for simulation...
Preparing schedule for simulation...
Schedule ready for simulation...
Validating initial state...
...

Plot well curves for fully implicit problem

plotWellSols({ws_seq, ws_seq2, wsfi}, cumsum(schedule.step.val), 'datasetnames', {'Sequential (relaxed mass)', 'Sequential (relaxed volume)', 'FI'})
_images/relaxedVolumeSPE10_02.png

Simulate the Egg model

Generated from sequentialEGG.m

The Egg model is a nearly incompressible waterflood problem. This example sets up a sequential solver and compares the results with the fully implicit discretization. As there are small changes in mobility, this problem is very well suited to sequential splitting. For details on the EggModel, 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 spe10 blackoil-sequential mrst-gui

% Set up pressure and transport linear solvers
if ~isempty(mrstPath('agmg'))
    mrstModule add agmg
    psolver = AGMGSolverAD();
else
    psolver = BackslashSolverAD();
end
tsolver = GMRES_ILUSolverAD();
% Select layer 1
layers = 1;
mrstModule add ad-core ad-blackoil blackoil-sequential spe10

% The base case for the model is 2000 days. This can be reduced to make the
% problem faster to run.
[G, rock, fluid, deck, state] = setupEGG();
model = selectModelFromDeck(G, rock, fluid, deck);

schedule = convertDeckScheduleToMRST(model, deck);
% Set up the sequential model
seqModel = getSequentialModelFromFI(model, 'pressureLinearSolver', psolver,....
                                           'transportLinearSolver', tsolver);
% Run problem
[wsSeq, statesSeq, repSeq] = simulateScheduleAD(state, seqModel, schedule);
No converter needed in section 'REGIONS'.
No converter needed in section 'SUMMARY'.
Processing Cells     1 : 18553 of 18553 ... done (0.11 [s], 1.65e+05 cells/second)
Total 3D Geometry Processing Time = 0.112 [s]
Ordering well 1 (INJECT1) with strategy "origin".
Ordering well 2 (INJECT2) with strategy "origin".
Ordering well 3 (INJECT3) with strategy "origin".
Ordering well 4 (INJECT4) with strategy "origin".
...

Simulate fully implicit

Solve the fully implicit version of the problem, with a CPR preconditioner that uses the same linear solver for the pressure as the sequential solver.

solver = NonLinearSolver();
solver.LinearSolver = CPRSolverAD('ellipticSolver', psolver);

[wsFIMP, statesFIMP, repFIMP] = simulateScheduleAD(state, model, schedule, 'nonlinearsolver', solver);
*****************************************************************
********** 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 simulation time taken

The sequential solver can be much faster than the fully implicit solver for certain problems.

figure(1); clf; hold on
plot(cumsum(repSeq.SimulationTime)/60, 'b-*')
plot(cumsum(repFIMP.SimulationTime)/60, 'r-o')
ylabel('Simulation time [minutes]')
xlabel('Control step #')
legend('Sequential implicit', 'Fully implicit')
_images/sequentialEGG_01.png

Plot the results in interactive viewers

G = model.G;
W = schedule.control(1).W;

% Plot the well curves
plotWellSols({wsSeq, wsFIMP}, cumsum(schedule.step.val), ...
            'datasetnames', {'Sequential', 'FIMP'}, 'field', 'qOs')

% Plot reservoir quantities
figure;
plotToolbar(G, statesSeq);
axis equal tight
view(90, 90);
plotWell(G, W);
title('Sequential implicit')

figure;
plotToolbar(G, statesFIMP);
axis equal tight
view(90, 90);
plotWell(G, W);
title('Fully-implicit')
_images/sequentialEGG_02.png
_images/sequentialEGG_03.png
_images/sequentialEGG_04.png

Plot time taken

figure;
subplot(1, 2, 1)
bar([sum(repFIMP.SimulationTime), sum(repSeq.SimulationTime)]/60)
set(gca, 'XTickLabel', {'Fully-implicit', 'Sequential'})
ylabel('Simulation time [minutes]')
set(gca, 'FontSize', 12)
axis tight
subplot(1, 2, 2)
plotCellData(model.G, statesSeq{60}.s(:, 1), 'edgecolor', 'none')
title('S_w')
set(gca, 'FontSize', 12)

axis equal tight off
_images/sequentialEGG_05.png

Copyright notice

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

Set up and run a five-spot problem with water-alternating-gas (WAG) drive

Generated from sequentialFiveSpotWAG.m

One approach to hydrocarbon recovery is to inject gas or water. In the water-alternating-gas approach, wells change between injection of gas and water to improve the sweep efficiency. In this example, we set up a five-spot injection pattern where the injector controls alternate between water and gas. The primary purpose of the example is not to demonstrate an ideal or realistic WAG-configuration, but rather how to set up a problem with changing well controls and to demonstrate how the sequential solvers perform on problems with large changes in mobility and well controls. We begin by loading the required modules

mrstModule add ad-core ad-blackoil ad-props ...
               blackoil-sequential spe10 mrst-gui

Set up grid and rock structure

We define a 50x50x1 grid, spanning a 1 km by 1km domain. The porosity is assigned via a Gaussian field and a synthethic permeability is computed using a standard correlation.

cartDims = [50, 50, 1];
physDims = [1000, 1000, 1]*meter;
G = cartGrid(cartDims, physDims);
G = computeGeometry(G);
gravity reset off

rng(0);
poro = gaussianField(cartDims, [0.05, 0.3], 3, 8);
poro = poro(:);
perm = poro.^3 .* (5e-5)^2 ./ (0.81 * 72 * (1 - poro).^2);
% Build rock object
rock = makeRock(G, perm, poro);
figure(1); clf
plotCellData(G, rock.poro)
axis equal tight
colorbar
title('Porosity')
Processing Cells    1 : 2500 of 2500 ... done (0.03 [s], 9.89e+04 cells/second)
Total 3D Geometry Processing Time = 0.025 [s]
_images/sequentialFiveSpotWAG_01.png

Set up wells and schedule

The injection scenario corresponds to injecting one pore volume of gas and water over 30 years at standard conditions. We first place wells in the corners of the domain set to fixed injection rates as well as a bottom hole pressure producer in the middle of the domain.

pv = poreVolume(G, rock);
T = 30*year;
irate = sum(pv)/(T*4);
% Function handle for easily creating multiple injectors
makeInj = @(W, name, I, J, compi) verticalWell(W, G, rock, I, J, [],...
    'Name', name, 'radius', 5*inch, 'sign', 1, 'Type', 'rate',...
    'Val', irate, 'comp_i', compi);
W = [];
W = makeInj(W, 'I1', 1,           1,           []);
W = makeInj(W, 'I3', cartDims(1), cartDims(2), []);
W = makeInj(W, 'I4', 1,           cartDims(2), []);
W = makeInj(W, 'I2', cartDims(1), 1,           []);

I = ceil(cartDims(1)/2);
J = ceil(cartDims(2)/2);
% Producer
W = verticalWell(W, G, rock, I, J, [], 'Name', 'P1', 'radius', 5*inch, ...
    'Type', 'bhp', 'Val', 100*barsa, 'comp_i', [1, 1, 1]/3, 'Sign', -1);
% Create two copies of the wells: The first copy is set to water injection
% and the second copy to gas injection.
[W_water, W_gas] = deal(W);
for i = 1:numel(W)
    if W(i).sign < 0
        % Skip producer
        continue
    end
    W_water(i).compi = [1, 0, 0];
    W_gas(i).compi   = [0, 0, 1];
end
% Simulate 90 day time steps, with a gradual increase of steps in the
% beginning as the mobility changes rapidly when wells begin injecting for
% the first time.
dT_target = 90*day;
dt = rampupTimesteps(T, dT_target, 10);

% Set up a schedule with two different controls. In the first control, the
% injectors are set to the copy of the wells we made earlier where water
% was injected. In the second control, gas is injected instead.
schedule = struct();
schedule.control = [struct('W', W_water);...
% Water control 1
                    struct('W', W_gas)]; % Gas control 2
% Set timesteps
schedule.step.val = dt;
% Alternate between the gas and water controls every 90 days
schedule.step.control = (mod(cumsum(dt), 2*dT_target) >= dT_target) + 1;

% Plot the changes in schedule control
figure(1), clf
ctrl = repmat(schedule.step.control', 2, 1);
x = repmat(cumsum(dt/year)', 2, 1);
y = repmat([0; 1], 1, size(ctrl, 2));
surf(x, y, ctrl)
colormap(jet)
view(0, 90)
axis equal tight
set(gca, 'YTick', []);
xlabel('Time [year]')
title('Control changes over time: Red for gas injection, blue for water')
Defaulting reference depth to top of formation for well I1. Please specify 'refDepth' optional argument to 'addWell' if you require bhp at a specific depth.
Defaulting reference depth to top of formation for well I3. Please specify 'refDepth' optional argument to 'addWell' if you require bhp at a specific depth.
Defaulting reference depth to top of formation for well I4. Please specify 'refDepth' optional argument to 'addWell' if you require bhp at a specific depth.
Defaulting reference depth to top of formation for well I2. Please specify 'refDepth' optional argument to 'addWell' if you require bhp at a specific depth.
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.
_images/sequentialFiveSpotWAG_02.png

Set up fluid and simulation model

We set up a three-phase fluid with quadratic relative permeability curves, significant viscosity contrast between the phases and compressibility for the oil and gas phases.

fluid = initSimpleADIFluid('phases',    'WOG', ...
                           'rho',       [1000, 700, 250], ...
                           'n',         [2, 2, 2], ...
                           'c',         [0, 1e-4, 1e-3]/barsa, ...
                           'mu',        [1, 4, 0.25]*centi*poise ...
                           );
% Set up three-phase, immiscible model with fully implicit discretization
model = ThreePhaseBlackOilModel(G, rock, fluid, 'disgas', false, 'vapoil', false);
% Create sequential model from fully implicit model
seqModel = getSequentialModelFromFI(model);
% Set up initial reservoir at 100 bar pressure and completely oil filled.
state = initResSol(G, 100*barsa, [0, 1, 0]);

Simulate the schedule using the sequential solver

We simulate the full schedule using simulateScheduleAD

[wsSeq, statesSeq] = simulateScheduleAD(state, seqModel, schedule);
*****************************************************************
********** Starting simulation:   132 steps, 10950 days *********
*****************************************************************
Preparing model for simulation...
Model ready for simulation...
Preparing schedule for simulation...
Schedule ready for simulation...
Validating initial state...
...

Simulate the schedule using a fully implicit solver

For comparison purposes, we also solve the fully implicit case

[wsFIMP, statesFIMP] = simulateScheduleAD(state, model, schedule);
*****************************************************************
********** Starting simulation:   132 steps, 10950 days *********
*****************************************************************
Preparing model for simulation...
Model ready for simulation...
Preparing schedule for simulation...
Schedule ready for simulation...
Validating initial state...
...

Plot producer rates

We plot the producer water, gas and oil rates. We can see the effect of the alternating controls directly on the production curves and we use a stairstep plot since the underlying data is not continuous.

df = get(0, 'DefaultFigurePosition');
figure('position', df.*[1, 1, 2, 1])
names = {'qWs', 'qOs', 'qGs'};
hold on
t = cumsum(schedule.step.val)/day;
colors = lines(3);

% q = getWellOutput(wsSeq, 'qWs'
for i = 1:numel(names)
%     subplot(1, 3, 1)
    qSeq = -getWellOutput(wsSeq, names{i}, 5);
    qFIMP = -getWellOutput(wsFIMP, names{i}, 5);
    stairs(t, qSeq*day, '-o', 'color', colors(i, :));
    stairs(t, qFIMP*day, '.', 'color', colors(i, :), 'linewidth', 2);
end
legend('Water, sequential', 'Water FIMP', 'Oil, sequential', ...
       'Oil FIMP', 'Gas, sequential', 'Gas FIMP');
xlabel('Time [days]')
ylabel('Production at standard conditions [m^3/day]')
axis tight
_images/sequentialFiveSpotWAG_03.png

Plot the injector bottom hole pressure

The injector bottom hole pressure has a bigger discrepancy between the two schemes, since the injectors use the cell total mobility to compute the relation between completion flux and bottom hole pressure. The deviation between the schemes is a direct result of the lagging mobility values in the pressure equation.

clf; hold on
colors = lines(4);

for i = 1:4
    bhpSeq = getWellOutput(wsSeq, 'bhp', i)/barsa;
    bhpFIMP = getWellOutput(wsFIMP, 'bhp', i)/barsa;

    stairs(t, bhpSeq, '-o', 'color', colors(i, :));
    stairs(t, bhpFIMP, '.', 'color', colors(i, :), 'linewidth', 2);
end
xlabel('Time [days]')
ylabel('Injector bottom hole pressure [bar]')
axis tight
_images/sequentialFiveSpotWAG_04.png

Launch interactive plotting

Finally we launch interactive viewers for the well curves and reservoir quantities.

plotWellSols({wsSeq, wsFIMP}, cumsum(schedule.step.val), ...
    'datasetnames', {'Sequential', 'FIMP'}, 'linestyles', {'-', '-.'})

figure;
plotToolbar(G, statesSeq);
axis tight
plotWell(G, W);
title('Sequential implicit')

figure;
plotToolbar(G, statesFIMP);
axis tight
plotWell(G, W);
title('Fully-implicit')
_images/sequentialFiveSpotWAG_05.png
_images/sequentialFiveSpotWAG_06.png
_images/sequentialFiveSpotWAG_07.png

Copyright notice

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

Compare sequential solver to fully implicit, applied to the SPE1 problem

Generated from sequentialSPE1.m

This example simulates the first SPE benchmark using both sequential and fully implicit solvers. The problem is a gas injection with significant density and mobility changes between phases, so the assumption of a constant total velocity for the sequential scheme is violated, and there are differences in production profiles between the two schemes. We also test a third approach, wherein the sequential scheme revisits the pressure solution if the mobilities change significantly, in order to convergence to the fully implicit solution.

mrstModule add ad-core ad-blackoil ad-props

Set up the initial simulation model

We use the existing setupSPE1 routine to handle the setup of all parameters, which are then converted into a fully implicit model and a schedule.

[G, rock, fluid, deck, state] = 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.006272 seconds.

...

Run the entire fully implicit schedule

We simulate the schedule with a fully implicit scheme, i.e. where we solve for both saturations and pressure simultanously.

[wsFIMP, statesFIMP, repFIMP] = 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 and simulate the schedule using a sequential implicit scheme

We convert the fully implicit model into a sequential model, a special model which contains submodels for both the pressure and transport. In this scheme, we first solve the pressure equation implicitly to obtain total volumetric fluxes at reservoir conditions, as well as total well rates. The pressure and fluxes are subsequently used as input for the transport scheme, which advects the saturations in the total velocity field using a fractional flow formulation.

seqModel = getSequentialModelFromFI(model);
[wsSeq, statesSeq, repSeq] = simulateScheduleAD(state, seqModel, 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...
...

Simulate the schedule with the outer loop option enabled

In the sequential implicit scheme, the transport equations are derived by assuming a fixed total velocity. For certain problems, this assumption is not reasonable, and the total velocity may be strongly coupled to the changes in saturation during a timestep. In this case, the problem is a gas injection scenario where the injected gas has a much higher mobility and much lower density, leading to significantly different results between the fully implicit and the sequential implicit schemes. One way to improve the results of the sequential implicit scheme is to employ an outer loop when needed. This amounts to revisiting the pressure equation after the transport has converged and checking if the pressure equation is still converged after the saturations have changed. If the pressure equation has a large residual after the transport, we then resolve the pressure with the new estimates for saturations.

% Make a copy of the model
seqModelOuter = seqModel;
% Disable the "stepFunctionIsLinear" option, which tells the
% NonLinearSolver that it is not sufficient to do a single pressure,
% transport step to obtain convergence. We set ds tolerance to 0.001
% (which is also the default) which is roughly equivalent to the maximum
% saturation error convergence criterion used in the fully implicit solver.
seqModelOuter.stepFunctionIsLinear = false;
seqModelOuter.incTolSaturation = 1.0000e-03;


[wsOuter, statesOuter, repOuter] = simulateScheduleAD(state, seqModelOuter, 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 results

We plot the results for the three different temporal discretization schemes. Since the water is close to immobile and the injector and producers are operated on gas and oil rates respectively, we plot the gas production and the bottom hole pressure for each well. We clearly see that there are substantial differences in the well curves due the changes in total velocity. The sequential implicit scheme with the outer loop enabled is very close to the fully implicit solution and will get closer if the tolerances are tightened.

T = cumsum(schedule.step.val);

wellSols = {wsFIMP, wsSeq, wsOuter};
states = {statesFIMP, statesSeq, statesOuter};
names = {'Fully-implicit', 'Sequential-implicit', 'Sequential-outer'};
markers = {'-', '--', '.'};

close all;
for i = 1:numel(wellSols)
    figure(1); hold on
    qo = -getWellOutput(wellSols{i}, 'qGs', 2)*day;
    plot(T/day, qo, markers{i}, 'linewidth', 2)
    xlabel('Time [days]');
    ylabel('Gas production [m^3/day]');

    figure(2); hold on
    bhp = getWellOutput(wellSols{i}, 'bhp', 2)/barsa;
    plot(T/day, bhp, markers{i}, 'linewidth', 2)
    xlabel('Time [days]');
    ylabel('Producer bottom hole pressure [bar]');

    figure(3); hold on
    bhp = getWellOutput(wellSols{i}, 'bhp', 1)/barsa;
    plot(T/day, bhp, markers{i}, 'linewidth', 2)
    xlabel('Time [days]');
    ylabel('Injector bottom hole pressure [bar]');
end

% Add legends to the plots
for i = 1:3
    figure(i);
    legend(names, 'location', 'northwest')
end

% Plot the different saturations and pressures
figure;
for i = 1:numel(names)
    s = states{i};

    subplot(2, numel(names), i)
    plotCellData(model.G, s{end}.s(:, 3))
    caxis([0, 1])
    axis tight
    title(names{i})
    xlabel('Gas saturation')

    subplot(2, numel(names), i + numel(names))
    plotCellData(model.G, s{end}.pressure)
    axis tight
    xlabel('Pressure')
end
_images/sequentialSPE1_01.png
_images/sequentialSPE1_02.png
_images/sequentialSPE1_03.png
_images/sequentialSPE1_04.png

Set up interactive plotting

We finish the example by launching interactive viewers for the well curves, as well as the different reservoir quantities.

mrstModule add mrst-gui
wsol = {wsSeq, wsFIMP, wsOuter};
wnames = {'Sequential', 'FIMP', 'Outer loop'};
states = {statesSeq, statesFIMP, statesOuter};

for i = 1:numel(states)
    figure(i), clf
    plotToolbar(G, states{i});
    title(wnames{i});
    axis tight
    view(20, 60);
    plotWell(G, schedule.control(1).W)
end

plotWellSols(wsol, T, 'datasetnames', wnames)
_images/sequentialSPE1_05.png
_images/sequentialSPE1_06.png
_images/sequentialSPE1_07.png
_images/sequentialSPE1_08.png

Copyright notice

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

Solve layers of SPE10 with sequential and fully implicit solver

Generated from sequentialSPE10.m

This is a short example demonstrating how to solve any number of layers of the SPE10, model 2 problem using both a sequential and a fully implicit solver.

mrstModule add ad-core ad-blackoil spe10 blackoil-sequential mrst-gui

% Set up pressure and transport linear solvers
if ~isempty(mrstPath('agmg'))
    mrstModule add agmg
    psolver = AGMGSolverAD();
else
    psolver = BackslashSolverAD();
end
tsolver = GMRES_ILUSolverAD();
% Select layer 1
layers = 1;
mrstModule add ad-core ad-blackoil blackoil-sequential spe10

% The base case for the model is 2000 days. This can be reduced to make the
% problem faster to run.
T = 2000*day;
[state, model, schedule] = setupSPE10_AD('layers', layers, 'dt', 30*day, ...
                                                           'T',  T);
% Set up the sequential model
seqModel = getSequentialModelFromFI(model, 'pressureLinearSolver', psolver,....
                                           'transportLinearSolver', tsolver);
% We set up a timestep selector that aims for timesteps where the
% maximum saturation change is equal to a fixed value.
stepSel = StateChangeTimeStepSelector('targetProps', {'s'},...
                                      'targetChangeAbs', 0.25);
% Run problem
solver = NonLinearSolver('timeStepSelector', stepSel);
[wsSeq, statesSeq, repSeq] = simulateScheduleAD(state, seqModel, schedule, 'NonLinearSolver', solver);
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.
Defaulting reference depth to top of formation for well P2. Please specify 'refDepth' optional argument to 'addWell' if you require bhp at a specific depth.
Defaulting reference depth to top of formation for well P3. Please specify 'refDepth' optional argument to 'addWell' if you require bhp at a specific depth.
Defaulting reference depth to top of formation for well P4. Please specify 'refDepth' optional argument to 'addWell' if you require bhp at a specific depth.
Defaulting reference depth to top of formation for well I1. Please specify 'refDepth' optional argument to 'addWell' if you require bhp at a specific depth.
*****************************************************************
********** Starting simulation:    75 steps,  2000 days *********
*****************************************************************
...

Simulate fully implicit

Solve the fully implicit version of the problem, with a CPR preconditioner that uses the same linear solver for the pressure as the sequential solver.

solver.timeStepSelector.reset();
solver.LinearSolver = CPRSolverAD('ellipticSolver', psolver);

[wsFIMP, statesFIMP, repFIMP] = simulateScheduleAD(state, model, schedule, 'NonLinearSolver', solver);
*****************************************************************
********** Starting simulation:    75 steps,  2000 days *********
*****************************************************************
Preparing model for simulation...
Model ready for simulation...
Preparing schedule for simulation...
Schedule ready for simulation...
Validating initial state...
...

Plot simulation time taken

The sequential solver can be much faster than the fully implicit solver for certain problems.

figure(1); clf; hold on
plot(cumsum(repSeq.SimulationTime)/60, 'b-*')
plot(cumsum(repFIMP.SimulationTime)/60, 'r-o')
ylabel('Simulation time [minutes]')
xlabel('Control step #')
legend('Sequential implicit', 'Fully implicit')
_images/sequentialSPE10_01.png

Plot the results in interactive viewers

G = model.G;
W = schedule.control(1).W;

% Plot the well curves
plotWellSols({wsSeq, wsFIMP}, cumsum(schedule.step.val), ...
            'datasetnames', {'Sequential', 'FIMP'}, 'field', 'qOs')

% Plot reservoir quantities
figure;
plotToolbar(G, statesSeq);
axis equal tight
view(90, 90);
plotWell(G, W);
title('Sequential implicit')

figure;
plotToolbar(G, statesFIMP);
axis equal tight
view(90, 90);
plotWell(G, W);
title('Fully-implicit')
_images/sequentialSPE10_02.png
_images/sequentialSPE10_03.png
_images/sequentialSPE10_04.png
figure;
subplot(1, 2, 1)
bar([sum(repFIMP.SimulationTime), sum(repSeq.SimulationTime)]/60)
set(gca, 'XTickLabel', {'Fully-implicit', 'Sequential'})
ylabel('Simulation time [minutes]')
set(gca, 'FontSize', 18)
axis tight
subplot(1, 2, 2)
plotCellData(model.G, statesSeq{60}.s(:, 1), 'edgecolor', 'none')
title('S_w')
set(gca, 'FontSize', 18)

axis equal tight off
_images/sequentialSPE10_05.png

Copyright notice

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