ad-mechanics: Coupled flow and mechanics

Examples

function [model, states] = run2DCase(varargin)
%
% SYNOPSIS:
%   function run2DCase(varargin)
%
% DESCRIPTION:
%    Example which setups poroelasticity computations for a two dimensional domain.
%
%    Simple well setup: One injection well in the middle.
%
%
% PARAMETERS:
%   varargin - see options below
%
% SEE ALSO: runAllNorneExamples

2D example for poroelasticity

Generated from run2DCase.m

Options that can be chosen in this example (see opt structure below) - fluid model :

  • solver :
  • Cartesian grid :
  • boundary conditions for the mechanics (only one choice here)

Load required modules

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

Setup default options

opt = struct('cartDim'            , [100, 10], ...
                 'L'                  , [100, 10], ...
                 'fluid_model'        , 'water', ...
                 'method'             , 'fully coupled', ...
                 'bc_case'            , 'bottom fixed', ...
                 'nonlinearTolerance' , 1e-6, ...
                 'splittingTolerance' , 1e-6, ...
                 'verbose'            , false, ...
                 'splittingVerbose'   , false);

    opt = merge_options(opt, varargin{:});

Setup grid

G = cartGrid(opt.cartDim, opt.L);
    G = computeGeometry(G);
Computing normals, areas, and centroids...    Elapsed time is 0.001092 seconds.
Computing cell volumes and centroids...               Elapsed time is 0.004061 seconds.

Setup rock parameters (for flow)

rock.perm = darcy*ones(G.cells.num, 1);
    rock.poro = 0.3*ones(G.cells.num, 1);

Setup fluid parameters from SPE1

pRef = 270*barsa;
    switch opt.fluid_model
      case 'blackoil'
        pth = getDatasetPath('spe1');
        fn  = fullfile(pth, 'BENCH_SPE1.DATA');
        deck = readEclipseDeck(fn);
        deck = convertDeckUnits(deck);
        fluid = initDeckADIFluid(deck);
        if isfield(fluid, 'pcOW')
            fluid = rmfield(fluid, 'pcOW');
        end
        if isfield(fluid, 'pcOG')
            fluid = rmfield(fluid, 'pcOG');
        end

        % Setup quadratic relative permeabilities, since SPE1 relperm are a bit rough.
        fluid.krW = @(s) s.^2;
        fluid.krG = @(s) s.^2;
        fluid.krOW = @(s) s.^2;
        fluid.krOG = @(s) s.^2;
        pRef = deck.PROPS.PVTW(1);

      case {'oil water'}
        fluid = initSimpleADIFluid('phases', 'WO', 'mu', [1, 10]*centi*poise, ...
                                   'n',  [1, 1], 'rho', [1000, 700]*kilogram/ ...
                                   meter^3, 'c', 1e-10*[1, 1], 'cR', 4e-10, ...
                                   'pRef', pRef);

      case {'water'}
        fluid = initSimpleADIFluid('phases', 'W', 'mu', 1*centi*poise, 'rho', ...
                                   1000*kilogram/meter^3, 'c', 1e-10, 'cR', ...
                                   4e-10, 'pRef', pRef);
      otherwise
        error('fluid_model  not recognized.');
    end

Setup material parameters for Biot and mechanics

E          = 1 * giga * Pascal; % Young's module
    nu         = 0.3;               % Poisson's ratio
    alpha      = 1;                 % Biot's coefficient

    % Transform these global properties (uniform) to cell values.
    E          = repmat(E, G.cells.num, 1);
    nu         = repmat(nu, G.cells.num, 1);
    rock.alpha = repmat(alpha, G.cells.num, 1);

Setup boundary conditions for mechanics (no displacement)

switch opt.bc_case

      case 'no displacement'
        error('not implemented yet');
        ind = (G.faces.neighbors(:, 1) == 0 | G.faces.neighbors(:, 2) == 0);
        ind = find(ind);
        nodesind = mcolon(G.faces.nodePos(ind), G.faces.nodePos(ind + 1) - 1);
        nodes = G.faces.nodes(nodesind);
        bcnodes = zeros(G.nodes.num);
        bcnodes(nodes) = 1;
        bcnodes = find(bcnodes == 1);
        nn = numel(bcnodes);
        u = zeros(nn, 2);
        m = ones(nn, 2);
        disp_bc = struct('nodes', bcnodes, 'uu', u, 'mask', m);
        force_bc = [];

      case 'bottom fixed'

        nx = G.cartDims(1);
        ny = G.cartDims(2);

        % Find the bottom nodes. On these nodes, we impose zero displacement

        c = zeros(prod(G.cartDims), 1);
        c(G.cells.indexMap) = (1 : numel(G.cells.indexMap))';

        bc = pside([], G, 'Ymin', 100);
        bottomfaces = bc.face;
        indbottom_nodes = mcolon(G.faces.nodePos(bottomfaces), ...
                                 G.faces.nodePos(bottomfaces + 1) - 1);
        bottom_nodes = G.faces.nodes(indbottom_nodes);
        bottom_nodes = unique(bottom_nodes);

        nn = numel(bottom_nodes);
        u = zeros(nn, G.griddim);
        m = ones(nn, G.griddim);
        disp_bc = struct('nodes', bottom_nodes, 'uu', u, 'mask', m);

        % Find outer faces that are not at the bottom. On these faces, we impose
        % a given pressure.

        bc = pside([], G, 'Xmin', 100);
        bc = pside(bc, G, 'Xmax', 100);
        bc = pside(bc, G, 'Ymax', 100);
        sidefaces = bc.face;

        signcoef = (G.faces.neighbors(sidefaces, 1) == 0) - (G.faces.neighbors(sidefaces, ...
                                                          2) == 0);
        n = bsxfun(@times, G.faces.normals(sidefaces, :), signcoef./ ...
                   G.faces.areas(sidefaces));
        force = bsxfun(@times, n, pRef);

        force_bc = struct('faces', sidefaces, 'force', force);


      otherwise
        error('bc_cases not recognized')
    end

    el_bc = struct('disp_bc' , disp_bc, ...
                   'force_bc', force_bc);

Setup load for mechanics

% In this example we do not impose any volumetric force
    loadfun = @(x) (0*x);

Gather all the mechanical parameters in a struct

mech = struct('E', E, 'nu', nu, 'el_bc', el_bc, 'load', loadfun);

Gravity

The gravity in this option affects only the fluid behavior

gravity off;

Setup model

modeltype = [opt.method, ' and ', opt.fluid_model];
    fullycoupledOptions = {'verbose', opt.verbose};
    splittingOptions = {'splittingTolerance', opt.splittingTolerance, ...
                        'splittingVerbose', opt.splittingVerbose};
    switch modeltype

      case 'fully coupled and blackoil'
        model = MechBlackOilModel(G, rock, fluid, mech, fullycoupledOptions{: ...
                   });

      case 'fixed stress splitting and blackoil'
        model = MechFluidFixedStressSplitModel(G, rock, fluid, mech, ...
                                               'fluidModelType', 'blackoil', ...
                                               splittingOptions{:});

      case 'fully coupled and oil water'
        model = MechOilWaterModel(G, rock, fluid, mech, fullycoupledOptions{: ...
                   });

      case 'fixed stress splitting and oil water'
        model = MechFluidFixedStressSplitModel(G, rock, fluid, mech, ...
                                               'fluidModelType', 'oil water', ...
                                               splittingOptions{:});

      case 'fully coupled and water'
        model = MechWaterModel(G, rock, fluid, mech, fullycoupledOptions{: });

      case 'fixed stress splitting and water'
        model = MechFluidFixedStressSplitModel(G, rock, fluid, mech, ...
                                               'fluidModelType', 'water', ...
                                               splittingOptions{:});

      otherwise
        error('modeltype not recognized.');
    end

Setup wells

W = [];
    refdepth = G.cells.centroids(1, G.griddim); % for example...
                                                % injcell  = 1; % for example...
                                                % prodcell = G.cells.num; % for example...

    ind = ceil(G.cartDims/2);
    injcell = sub2ind(G.cartDims, ind(1), ind(2));

    W = addWell(W, G, rock, injcell, ...
                'Type'    , 'rate', ...
                'Val'     , 1e2/day, ...
                'Sign'    , 1,  ...
                'Comp_i'  , [0, 0, 1], ...
% inject gas
                'Name'    , 'inj',  ...
                'refDepth', refdepth);

    % W = addWell(W, G, rock, prodcell, ...
    %             'Type'    ,'bhp', ...
    %             'Val'     , pRef, ...
    %             'Sign'    , -1,  ...
    %             'Comp_i'  , [0, 1, 0], ...
% one-phase test case
    %             'Name'    , 'prod',  ...
    %             'refDepth', refdepth);

    switch opt.fluid_model
      case 'blackoil'
        W(1).compi = [0, 0, 1];
      case 'oil water'
        W(1).compi = [1 0];
        W(1).val   = 1e4/day;
      case 'water'
        W(1).compi = [1];
        W(1).val  = 1e-3/day;
      otherwise
        error('fluid_model not recognized.')
    end

    facilityModel = FacilityModel(model.fluidModel);
    facilityModel = facilityModel.setupWells(W);
    model.FacilityModel = facilityModel;
Warning: Reference depth for well BHP in well 'inj' is set below well's top-most
reservoir connection

Setup schedule

schedule.step.val     = [1*day*ones(1, 1); 5*day*ones(20, 1)];
    schedule.step.control = ones(numel(schedule.step.val), 1);
    schedule.control      = struct('W', W);

Setup initial state

clear initState;
    initState.pressure = pRef*ones(G.cells.num, 1);
    switch opt.fluid_model
      case 'blackoil'
        init_sat = [0, 1, 0];
        initState.rs  = 0.5*fluid.rsSat(initState.pressure);
      case 'oil water'
        init_sat = [0, 1];
      case 'water'
        init_sat = [1];
      otherwise
        error('fluid_model not recognized.')
    end
    initState.s = ones(G.cells.num, 1)*init_sat;
    initState.xd = zeros(nnz(~model.mechModel.operators.isdirdofs), 1);
    initState = addDerivedQuantities(model.mechModel, initState);

    solver = NonLinearSolver('maxIterations', 100);
    [wsol, states] = simulateScheduleAD(initState, model, schedule, 'nonlinearsolver', ...
                                        solver);
*****************************************************************
********** Starting simulation:    21 steps,   101 days *********
*****************************************************************
Preparing model for simulation...
Model ready for simulation...
Preparing schedule for simulation...
Schedule ready for simulation...
Validating initial state...
...
end
ans =

  MechWaterModel with properties:

                mechModel: [1×1 MechanicMechModel]
          MechPrimaryVars: []
                  mechfds: {'xd'  'uu'  'u'  'stress'  'strain'  'vdiv'}
...

Computation of Gradients using Adjoint simulations

Generated from runAdjointExample.m

In this example, we demonstrate how one can setup adjoint simulations in a poroelastic simulation in order to compute gradients (or sensitivities) of a given quantity. Here, we will compute the gradient of the vertical displacement (uplift) at a node with respect to the injection rates. We consider a 2D domain, with two injection wells located on the sides and one production well in the middle. The injection rate is given by a schedule (see below). At the production well, we impose a constant pressure. We compute the gradient of the uplift (vertical displacement) at the top of the domain with respect to the injection rates at every time step. We use this gradient information to update the schedule so that the uplift is reduced. Different measures of the time average for the uplift are considered, and they produce different result, see the last plot in this script. Different fluid model can be used (two phases or single phase).

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

Setup geometry

% We consider a  2D regular cartesian domain

cartDim = [31, 30];
L       = [30, 10];
G = cartGrid(cartDim, L);
G = computeGeometry(G);
Computing normals, areas, and centroids...    Elapsed time is 0.000201 seconds.
Computing cell volumes and centroids...               Elapsed time is 0.001227 seconds.

Setup fluid

% We can consider different fluid models

opt.fluid_model = 'single phase';
pRef = 100*barsa;
switch opt.fluid_model
  case 'single phase'
    fluid = initSimpleADIFluid('phases', 'W', 'mu', 1*centi*poise, 'rho', ...
                               1000*kilogram/meter^3, 'c', 1e-4, 'cR', ...
                               4e-10, 'pRef', pRef);
  case 'oil water'
    fluid = initSimpleADIFluid('phases', 'WO', 'mu', [1, 100]*centi*poise, 'n', ...
                               [1, 1], 'rho', [1000, 700]*kilogram/ meter^2, 'c', ...
                               1e-10*[1, 1], 'cR', 1e-10, 'pRef', pRef);
  case 'blackoil'
    error('not yet implemented, but could be easily done!')
  otherwise
    error('fluid_model not recognized.')
end

Setup rock parameters (for flow)

rock.perm = darcy*ones(G.cells.num, 1);
rock.poro = 0.3*ones(G.cells.num, 1);

Setup material parameters for Biot and mechanics

E          = 1e-2*giga*Pascal; % Young's module
nu         = 0.3;              % Poisson's ratio
alpha      = 1;                % Biot's coefficient
% Convert global properties to cell values
E          = repmat(E, G.cells.num, 1);
nu         = repmat(nu, G.cells.num, 1);
rock.alpha = repmat(alpha, G.cells.num, 1);

Setup boundary conditions for mechanics (no displacement)

zero displacement at bottom, left and right sides. We impose a given pressure at the top.

% Gather the Dirichlet boundary faces (zero displacement) at left, bottom and right.
dummyval = 100; % We use pside to recover face at bottom, we use a dummy
                % value for pressure in this function.
bc = pside([], G, 'Xmin', dummyval);
bc = pside(bc, G, 'Xmax', dummyval);
bc = pside(bc, G, 'Ymin', dummyval);
indfacebc = bc.face;

% Get the nodes that belong to the Dirichlet boundary faces.
facetonode = accumarray([G.faces.nodes, rldecode((1 : G.faces.num)', ...
                                                 diff(G.faces.nodePos))], ...
                        ones(numel(G.faces.nodes), 1), [G.nodes.num, ...
                    G.faces.num]);
isbcface = zeros(G.faces.num, 1);
isbcface(indfacebc) = 1;
bcnodes  = find(facetonode*isbcface);
nn       = numel(bcnodes);
u        = zeros(nn, G.griddim);
m        = ones(nn,  G.griddim);
disp_bc  = struct('nodes', bcnodes, 'uu', u, 'mask', m);

% Set a given pressure on the  face at the top.
dummyval = 100; % We use pside to recover face at bottom, we use a dummy
                % value for pressure in this function.
bc = pside([], G, 'Ymax', dummyval);
sidefaces = bc.face;
signcoef = (G.faces.neighbors(sidefaces, 1) == 0) - (G.faces.neighbors(sidefaces, ...
                                                  2) == 0);
n = bsxfun(@times, G.faces.normals(sidefaces, :), signcoef./ ...
           G.faces.areas(sidefaces));
force = bsxfun(@times, n, pRef);
force_bc = struct('faces', sidefaces, 'force', force);

% Construct the boundary conidtion structure for the  mechanical system
el_bc = struct('disp_bc' , disp_bc, 'force_bc', force_bc);

Setup volumetric load for mechanics

In this example we do not impose any volumetric force

loadfun = @(x) (0*x);

Gather all the mechanical parameters in a struct

mech = struct('E', E, 'nu', nu, 'el_bc', el_bc, 'load', loadfun);

Set gravity off

gravity off

Setup model

switch opt.fluid_model
  case 'single phase'
    model = MechWaterModel(G, rock, fluid, mech);
  case 'oil water'
    model = MechOilWaterModel(G, rock, fluid, mech);
  case 'blackoil'
    error('not yet implemented')
  otherwise
    error('fluid_model not recognized.')
end

Set up initial reservoir state

The initial fluid pressure is set to a constant. We have zero displacement initially.

clear initState;
initState.pressure = pRef*ones(G.cells.num, 1);
switch opt.fluid_model
  case 'single phase'
    init_sat = [1];
  case 'oil water'
    init_sat = [0, 1];
  case 'blackoil'
    error('not yet implemented')
    % init_sat = [0, 1, 0];
    % initState.rs  = 0.5*fluid.rsSat(initState.pressure);
  otherwise
    error('fluid_model not recognized.')
end
% set up initial saturations
initState.s  = ones(G.cells.num, 1)*init_sat;
initState.xd = zeros(nnz(~model.mechModel.operators.isdirdofs), 1);
% We compute the corresponding displacement field using the dedicated
% function computeInitDisp (actually not necessary as the solution should be zero).
initState    = computeInitDisp(model, initState, [], 'pressure', initState.pressure);
initState    = addDerivedQuantities(model.mechModel, initState);
===========================
| It # | disp (disp_dofs) |
===========================
|    1 | 1.17e+09         |
|    2 |*4.06e-07         |
===========================

Setup the wells

Two injection wells on the sides and one production well in the middle.

nx = G.cartDims(1);
ny = G.cartDims(2);
switch opt.fluid_model
  case 'single phase'
    comp_inj  = [1];
    comp_prod = [1];
  case 'oil water'
    comp_inj  = [1, 0];
    comp_prod = [0, 1];
  case 'blackoil'
    error('not yet implemented')
  otherwise
    error('fluid_model not recognized.')
end

W = [];
wellopt = {'type', 'rate', 'Sign', 1, 'comp_i', comp_inj};
% Two injection wells vertically aligned, near the bottom
W = addWell(W, G, rock, round(nx/4)   + floor(1/4*ny)*nx, wellopt{:});
W = addWell(W, G, rock, nx + 1 - round(nx/4) + floor(1/4*ny)*nx, wellopt{:});
% production well in the center
wellopt = {'type', 'bhp', 'val', pRef, 'Sign', -1, 'comp_i', comp_prod};
W = addWell(W, G, rock, round(nx/2)   + floor(1/4*ny)*nx, wellopt{:});

% We plot the well location
wellcells = zeros(G.cells.num, 1);
wellcells(W(1).cells) = 1;
wellcells(W(2).cells) = 1;
wellcells(W(3).cells) = 2;
figure
clf
plotCellData(G, wellcells);
comment = ['The connection of the wells are colored' char(10) 'We have two ' ...
          'injection wells and on production well'];
text(0.5, 0.9, comment, 'units', 'normalized', 'horizontalalignment', 'center', ...
     'backgroundcolor', 'white');

% We incorporate the well in a FacilityModel which takes care of coupling all
% the well equations with the reservoir equations

facilityModel = FacilityModel(model.fluidModel);
facilityModel = facilityModel.setupWells(W);
model.FacilityModel = facilityModel;
model = model.validateModel(); % setup consistent fields for model (in
                               % particular the facility model for the fluid
                               % submodel)
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.
Defaulting reference depth to top of formation for well W3. Please specify 'refDepth' optional argument to 'addWell' if you require bhp at a specific depth.
_images/runAdjointExample_01.png

Setup a schedule

We set up a schedule where we gradually decrease from a maximum to a minimum injection rate value. Then, we keep the injection rate constant. See plot below.

clear schedule
schedule.step.val     = [1*day*ones(1, 1); 10*day*ones(30, 1)];
nsteps = numel(schedule.step.val);
schedule.step.control = (1 : nsteps)';
valmax = 1*meter^3/day;
valmin = 1e-1*meter^3/day;
ctime = cumsum(schedule.step.val);
flattentime   = 150*day; % Time when we reach the minimal rate value
qW = zeros(nsteps, 1);
for i = 1 : numel(schedule.step.control)
    if ctime(i) < flattentime
        qW(i) = valmin*ctime(i)/flattentime + valmax*(flattentime - ctime(i))/flattentime;
    else
        qW(i) = valmin;
    end
    W(1).val = qW(i);
    W(2).val = qW(i);
    schedule.control(i) = struct('W', W);
end
% We plot the injection schedule
figure
plot(ctime/day, qW*day);
axis([0, ctime(end)/day, 0, 1])
title('Initial schedule');
xlabel('time (day)')
ylabel('Injection rate (m^3/day)');
_images/runAdjointExample_02.png

Run the schedule

We run the simulation for the given model, initial state and schedule.

[wellSols, states] = simulateScheduleAD(initState, model, schedule);

% We start a visualization tool to inspect the result of the simulation
figure
plotToolbar(G, states);
colorbar
*****************************************************************
********** Starting simulation:    31 steps,   301 days *********
*****************************************************************
Preparing model for simulation...
Model ready for simulation...
Preparing schedule for simulation...
Schedule ready for simulation...
Validating initial state...
...
_images/runAdjointExample_03.png

We plot the evolution of the uplift

% Get index of a node belonging to the cell at the middle on the top layer.
topcell = floor(nx/2) + nx*(ny - 1);
topface = G.cells.faces(G.cells.facePos(topcell) : (G.cells.facePos(topcell + ...
                                                  1)  - 1), :);
topface = topface(topface(:, 2) == 4, 1);
topnode = G.faces.nodes(G.faces.nodePos(topface)); % takes one node from the
                                                   % top face (the first listed)

laststep = numel(states);
uplift = @(step) (computeUpliftForState(model, states{step}, topnode));
uplifts = arrayfun(uplift, (1 : laststep)');
figure
plot(ctime/day, uplifts, 'o-');
title('uplift values');
xlabel('time (in days)');
_images/runAdjointExample_04.png

Setup of the objective function given by averaged uplift values

The adoint framework is general and computes the derivative of a (scalar) objective function of following forms,

This special form of this objective function allows for a recursive computation of the adjoint variables. We set up our objective function as being a time average of weighted uplift in a node in the middle at the top Given an exponent p, we take

See function objUpliftAver. We consider three values for the exponent p.

Compute gradients using the adjoint formulation for three different exponents

np               = 3;
exponents        = cell(np, 1);
adjointGradients = cell(np, 1);

exponents = {1; 10; 50};

for p = 1 : np
    C = 1e3; % For large exponents we need to scale the values to avoid very large
             % or very small objective function
    objUpliftFunc = @(tstep) objUpliftAver(model, states, schedule, topnode, ...
                                           'tStep', tstep, 'computePartials', true, ...
                                           'exponent', exponents{p}, ...
                                           'normalizationConstant', C);
    fprintf('\n***\n*  Start adjoint simulation for exponent p=%g\n*\n', exponents{p});
    adjointGradients{p} = computeGradientAdjointAD(initState, states, model, ...
                                                   schedule, objUpliftFunc);
end
***
*  Start adjoint simulation for exponent p=1
*
Preparing model for simulation...
Model ready for simulation...
Validating initial state...
Initial state ready for simulation.
...

We can check the results from the adjoint computation by using finite difference

The function computeGradientAdjointAD sets up this computation check for us. It should be used with a smaller schedule, otherwise the computation is very long.

compute_numerical_derivative = false;
if compute_numerical_derivative
    objUpliftFunc2 = @(wellSols, states, schedule) objUplift(model, states, ...
                                                      schedule, topnode, ...
                                                      'computePartials', ...
                                                      false);
    fdgrad = computeGradientPerturbationAD(initState, model, schedule, ...
                                           objUpliftFunc2, 'perturbation', ...
                                           1e-7);
end

Plots of the results.

We plot the uplift values with the gradients (normalized for comparison).

figure
clf
plot(ctime/day, uplifts, 'o-');
ylabel('uplift values');
xlabel('time (in days)');
hold on
yyaxis right

legendtext = {'uplift value'};
set(gca,'ColorOrder',hsv(3));
for p = 1 : np
    grads = cell2mat(adjointGradients{p});
    qWgrad = grads(1, :);
    % we renormalize the gradients to compare the two series of values
    qWgrad = 1/max(qWgrad)*qWgrad;
    plot(ctime/day, qWgrad, '*-');
    legendtext{end + 1} = sprintf('p = %g', exponents{p});
end

ylabel('gradient value')
legend(legendtext);
_images/runAdjointExample_05.png

Update of the schedule based on gradient values

We use the gradient values computed above to update the schedule so that the uplift, as measured with the three different values of the exponents, is decreased. The absolute minimum uplift is of course zero and it is obtained when we do not inject anything. To avoid this trivial case, we impose the extra requirement that, in all cases, we will have the same decrease in the total injection.

inituplifts = uplifts;
initschedule = schedule;
dts = schedule.step.val;
nsteps = numel(dts);
ctime = cumsum(schedule.step.val);
tottime = ctime(end);

np      = numel(adjointGradients);
uplifts = cell(np, 1);
qWs     = cell(np, 1);

dcqW = 3e-2*meter^3/day*tottime; % this is the total amount of injected fluid we
                                 % are willing to decrease.

for p = 1 : np

    grads = cell2mat(adjointGradients{p});

    qW = zeros(nsteps, 1);
    dqW = cell(2, 1);
    for i = 1 : 2
        dqW{i} = grads(i, :);
        dqW{i} = dcqW/(dqW{i}*dts)*dqW{i};
    end

    schedule = initschedule;
    for i = 1 : numel(schedule.step.control)
        W = schedule.control(i).W;
        % we update the values of the injection rate at each time step using
        % the gradient values.
        W(1).val = W(1).val - dqW{1}(i);
        W(2).val = W(2).val - dqW{2}(i);
        schedule.control(i) = struct('W', W);
        qW(i) = W(1).val;
    end

    qWs{p} = qW;

    figure
    clf
    plot(ctime/day, qW*day);
    title(sprintf(' Schedule obtained using p=%g', exponents{p}));
    xlabel('time (day)');
    ylabel('Injection rate (m^3/day)');

    fprintf('\n***\n*  Start simulation and compute uplift for updated schedule using exponent p=%g\n*\n', exponents{p});
    [wellSols, states] = simulateScheduleAD(initState, model, schedule);

    nsteps = numel(states);
    uplift = @(step) (computeUpliftForState(model, states{step}, topnode));
    uplifts{p} = arrayfun(uplift, (1 : nsteps)');

end

% plot of the results.
figure
set(gcf, 'position', [100, 100, 1500, 600]);
clf
subplot(1, 2, 2)
hold on
plot(ctime/day, inituplifts);
legendtext = {'original uplift value'};
for p = 1 : np
    plot(ctime/day, uplifts{p});
    legendtext{end + 1} = sprintf('updated uplift (p=%g)', exponents{p});
end
xlabel('time (in days)');
legend(legendtext);

% Add some comments to the plot
[y, ind] = max(inituplifts);
x        = ctime(ind)/day;
set(gca, 'position', [0.5, 0.1, 0.4, 0.8], 'units', 'normalized')
xlim     = get(gca, 'xlim');
ylim     = get(gca, 'ylim');
apos  = get(gca, 'position');
xn = apos(1) + (x - xlim(1))/(xlim(2) - xlim(1))*apos(3);
yn = apos(2) + (y - ylim(1))/(ylim(2) - ylim(1))*apos(4);
annotation('textarrow', [xn, xn], [0.5, yn], 'string', 'maximum uplift');

subplot(1, 2, 1)
axis off
comments =  fileread('commentsToPlot.tex');
annotation('textbox', 'units', 'normalized', 'position', [0.05 0.1 0.4 0.8], ...
            'interpreter', 'tex', 'string', comments)
***
*  Start simulation and compute uplift for updated schedule using exponent p=1
*
*****************************************************************
********** Starting simulation:    31 steps,   301 days *********
*****************************************************************
Preparing model for simulation...
...
_images/runAdjointExample_06.png
_images/runAdjointExample_07.png
_images/runAdjointExample_08.png
_images/runAdjointExample_09.png

Example of poelasticity simulation on 2D grids

Generated from runAll2DCases.m

The implementation of the poroelasticty solver is done in such a way that it is easy to switch between different fluid models and different solving strategy (fully coupled versus splitting). In this example, we run all the combination of these choices for a 2D case. See run2DCase for a presentation of the test case. Options that are run are - fluid model :

  • solver :
mrstModule add ad-mechanics

clear opt

opt.fluid_model        = 'water';
opt.cartDim            = [100, 10];
opt.L                  = [100, 10];
opt.method             = 'fully coupled';
opt.bc_case            = 'bottom fixed';

opt.nonlinearTolerance = 1e-6;
opt.splittingTolerance = 1e-6;
opt.verbose            = false;
opt.splittingVerbose   = false;

% write intro text for each case
writeIntroText = @(opt)(fprintf('\n*** Start new simulation with\n* fluid model : %s\n* method : %s\n\n', opt.fluid_model, opt.method));

water cases

opt.fluid_model = 'water';

opt.method      = 'fully coupled';
writeIntroText(opt);

optvals = cellfun(@(x) opt.(x), fieldnames(opt), 'uniformoutput', false);
optlist = reshape(vertcat(fieldnames(opt)', optvals'), [], 1);
[model, states] = run2DCase(optlist{:});
plot2DPoroExample(model, states, opt);


opt.method      = 'fixed stress splitting';
writeIntroText(opt);
optvals = cellfun(@(x) opt.(x), fieldnames(opt), 'uniformoutput', false);
optlist = reshape(vertcat(fieldnames(opt)', optvals'), [], 1);
[model, states] = run2DCase(optlist{:});
plot2DPoroExample(model, states, opt);
*** Start new simulation with
* fluid model : water
* method : fully coupled

Computing normals, areas, and centroids...    Elapsed time is 0.000176 seconds.
Computing cell volumes and centroids...               Elapsed time is 0.001461 seconds.
Warning: Reference depth for well BHP in well 'inj' is set below well's top-most
...
_images/runAll2DCases_01.png
_images/runAll2DCases_02.png

Two phase oil water phases cases

opt.fluid_model = 'oil water';


opt.method      = 'fully coupled';
writeIntroText(opt);
optvals = cellfun(@(x) opt.(x), fieldnames(opt), 'uniformoutput', false);
optlist = reshape(vertcat(fieldnames(opt)', optvals'), [], 1);
[model, states] = run2DCase(optlist{:});
plot2DPoroExample(model, states, opt);


opt.method      = 'fixed stress splitting';
writeIntroText(opt);
optvals = cellfun(@(x) opt.(x), fieldnames(opt), 'uniformoutput', false);
optlist = reshape(vertcat(fieldnames(opt)', optvals'), [], 1);
[model, states] = run2DCase(optlist{:});
plot2DPoroExample(model, states, opt);
*** Start new simulation with
* fluid model : oil water
* method : fully coupled

Computing normals, areas, and centroids...    Elapsed time is 0.000202 seconds.
Computing cell volumes and centroids...               Elapsed time is 0.001247 seconds.
Warning: Reference depth for well BHP in well 'inj' is set below well's top-most
...
_images/runAll2DCases_03.png
_images/runAll2DCases_04.png

Three phases Black-Oil phases cases

opt.fluid_model = 'blackoil';


opt.method      = 'fully coupled';
writeIntroText(opt);
optvals = cellfun(@(x) opt.(x), fieldnames(opt), 'uniformoutput', false);
optlist = reshape(vertcat(fieldnames(opt)', optvals'), [], 1);
[model, states] = run2DCase(optlist{:});
plot2DPoroExample(model, states, opt);


opt.method      = 'fixed stress splitting';
writeIntroText(opt);
optvals = cellfun(@(x) opt.(x), fieldnames(opt), 'uniformoutput', false);
optlist = reshape(vertcat(fieldnames(opt)', optvals'), [], 1);
[model, states] = run2DCase(optlist{:});
plot2DPoroExample(model, states, opt);
*** Start new simulation with
* fluid model : blackoil
* method : fully coupled

Computing normals, areas, and centroids...    Elapsed time is 0.000166 seconds.
Computing cell volumes and centroids...               Elapsed time is 0.002171 seconds.
No converter needed in section 'REGIONS'.
...
_images/runAll2DCases_05.png
_images/runAll2DCases_06.png
_images/runAll2DCases_07.png

Example: Simulation of poroelasticity on a subset of the Norne case

Generated from runAllNorneExamples.m

Same setting as in runAll2Dcases but now for a subset of the Norne case See: runNorneExample and setupNorneExamples

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

Summary of the options

% opt.norne_case = 'mini Norne'; % 'full' or 'mini Norne'
%
% 'full'       : 7392 cells
% 'mini Norne' :  605 cells
%
% opt.bc_case    = 'bottom fixed'; % 'no displacement' or 'bottom fixed'
%
% 'no displacement' : All nodes belonging to external faces have displacement
%                     equal to zero
% 'bottom fixed'    : The nodes that belong to the bottom have zero
%                     displacement, while a given pressure is imposed on
%                     the external faces that are not bottom faces.
%
% opt.method     = 'fully coupled'; % 'fully coupled' 'fixed stress splitting'
%
% 'fully coupled'          : The mechanical and flow equations are solved fully couplde.
%
% 'fixed stress splitting' : The mechanical and flow equations are solved
%                            sequentially using a fixed stress splitting
%
% opt.fluid_model = 'oil water'; % 'blackoil' 'water' 'oil water'
%
% 'blackoil'     : blackoil model is used for the fluid (gas is injected, see
%                  schedule below)
% 'oil water'    : Two phase oil-water
% 'water' : water model is used for the fluid

clear opt
opt.verbose          = false;
opt.splittingVerbose = false;
opt.norne_case       = 'mini Norne';
opt.bc_case          = 'bottom fixed';

% write intro text for each case
writeIntroText = @(opt)(fprintf('\n*** Start new simulation\n* fluid model : %s\n* method : %s\n\n', opt.fluid_model, opt.method));

water cases

opt.fluid_model = 'water';

opt.method = 'fully coupled';
writeIntroText(opt);
optvals = cellfun(@(x) opt.(x), fieldnames(opt), 'uniformoutput', false);
optlist = reshape(vertcat(fieldnames(opt)', optvals'), [], 1);
[model, states] = runNorneExample(optlist{:});
plotNornePoroExample(model, states, opt);

opt.method = 'fixed stress splitting';
writeIntroText(opt);
optvals = cellfun(@(x) opt.(x), fieldnames(opt), 'uniformoutput', false);
optlist = reshape(vertcat(fieldnames(opt)', optvals'), [], 1);
[model, states] = runNorneExample(optlist{:});
plotNornePoroExample(model, states, opt);
*** Start new simulation
* fluid model : water
* method : fully coupled

Reading keyword 'INCLUDE'
 -> '/home/francesca/mrstReleaseCleanRepo/mrst-bitbucket/mrst-core/examples/data/Norne/INCLUDE/IRAP_1005.GRDECL'
Reading keyword 'SPECGRID'
...
_images/runAllNorneExamples_01.png
_images/runAllNorneExamples_02.png

Two phase oil water phases cases

opt.fluid_model = 'oil water';

opt.method = 'fully coupled';
writeIntroText(opt);
optvals = cellfun(@(x) opt.(x), fieldnames(opt), 'uniformoutput', false);
optlist = reshape(vertcat(fieldnames(opt)', optvals'), [], 1);
[model, states] = runNorneExample(optlist{:});
plotNornePoroExample(model, states, opt);

opt.method = 'fixed stress splitting';
writeIntroText(opt);
optvals = cellfun(@(x) opt.(x), fieldnames(opt), 'uniformoutput', false);
optlist = reshape(vertcat(fieldnames(opt)', optvals'), [], 1);
[model, states] = runNorneExample(optlist{:});
plotNornePoroExample(model, states, opt);
*** Start new simulation
* fluid model : oil water
* method : fully coupled

Reading keyword 'INCLUDE'
 -> '/home/francesca/mrstReleaseCleanRepo/mrst-bitbucket/mrst-core/examples/data/Norne/INCLUDE/IRAP_1005.GRDECL'
Reading keyword 'SPECGRID'
...
_images/runAllNorneExamples_03.png
_images/runAllNorneExamples_04.png

Three phases Black-Oil phases cases

opt.fluid_model = 'blackoil';

opt.method = 'fully coupled';
writeIntroText(opt);
optvals = cellfun(@(x) opt.(x), fieldnames(opt), 'uniformoutput', false);
optlist = reshape(vertcat(fieldnames(opt)', optvals'), [], 1);
[model, states] = runNorneExample(optlist{:});
plotNornePoroExample(model, states, opt);

opt.method = 'fixed stress splitting';
writeIntroText(opt);
opt.splittingTolerance = 1e-3;
optvals = cellfun(@(x) opt.(x), fieldnames(opt), 'uniformoutput', false);
optlist = reshape(vertcat(fieldnames(opt)', optvals'), [], 1);
[model, states] = runNorneExample(optlist{:});
plotNornePoroExample(model, states, opt);
*** Start new simulation
* fluid model : blackoil
* method : fully coupled

Reading keyword 'INCLUDE'
 -> '/home/francesca/mrstReleaseCleanRepo/mrst-bitbucket/mrst-core/examples/data/Norne/INCLUDE/IRAP_1005.GRDECL'
Reading keyword 'SPECGRID'
...
_images/runAllNorneExamples_05.png
_images/runAllNorneExamples_06.png
function [model, states] = runNorneExample(varargin)

Example: Poroelasticity simulation applied to the Norne case.

Generated from runNorneExample.m

The simulation options are gathered in the opt structure. If opt=[] the simulation is run with the default options defined below Summary of the options option ‘norne_case’ :

option ‘bc_case’ :

option ‘method’ :

option ‘fluid_model’ :

%{

Load Norne grid

if ~ (makeNorneSubsetAvailable() && makeNorneGRDECL()),
        error('Unable to obtain simulation model subset');
    end


    grdecl = fullfile(getDatasetPath('norne'), 'NORNE.GRDECL');
    grdecl = readGRDECL(grdecl);
    fullCartDims = grdecl.cartDims;
    usys   = getUnitSystem('METRIC');
    grdecl = convertInputUnits(grdecl, usys);
    switch opt.norne_case
      case 'full'
        grdecl = cutGrdecl(grdecl, [10 25; 35 55; 1 22]);
      case 'full padded'
        grdeclo = cutGrdecl(grdecl, [10 25; 35 55; 1 22]);
        oncells = prod(grdeclo.cartDims);
        oindex = 1:oncells;
        oindex = reshape(oindex, grdeclo.cartDims);
        grdecl = padGrdecl(grdeclo, [true, true, true], [[60 50; 40 40] * 10; 10 10], 'relative', true);
        grdecl.ACTNUM = ones(prod(grdecl.cartDims), 1);
        indexmapn = zeros(grdecl.cartDims);
        indexmapn(2:end - 1, 2:end - 1, 2:end - 1) = oindex;
        indexmapn(indexmapn == 0) = prod(grdeclo.cartDims) + 1;
        ocells = indexmapn < prod(grdeclo.cartDims);
        oindex = indexmapn;
      case 'mini Norne'
        grdecl = cutGrdecl(grdecl, [10 20; 35 45; 1 5]);
      otherwise
        error('norne_case not recognized');
    end
    grdecl.ACTNUM = ones(size(grdecl.ACTNUM));
    G = processGRDECL(grdecl);
    G = G(1);
    G = computeGeometry(G);
Reading keyword 'INCLUDE'
 -> '/home/francesca/mrstReleaseCleanRepo/mrst-bitbucket/mrst-core/examples/data/Norne/INCLUDE/IRAP_1005.GRDECL'
Reading keyword 'SPECGRID'
Reading keyword 'COORD'
Reading keyword 'ZCORN'
 <- '/home/francesca/mrstReleaseCleanRepo/mrst-bitbucket/mrst-core/examples/data/Norne/INCLUDE/IRAP_1005.GRDECL'
Reading keyword 'INCLUDE'
 -> '/home/francesca/mrstReleaseCleanRepo/mrst-bitbucket/mrst-core/examples/data/Norne/INCLUDE/ACTNUM_0704.prop'
...

Setup rock parameters (for flow)

switch opt.norne_case
      case {'full', 'mini Norne'}
        perm = [grdecl.PERMX, grdecl.PERMY, grdecl.PERMZ];
        rock.perm = perm(G.cells.indexMap, :);
        rock.poro = max(grdecl.PORO(G.cells.indexMap), 0.1);
      case 'full padded'
        perm = [grdeclo.PERMX, grdeclo.PERMY, grdeclo.PERMZ;
                [1 1 1]*milli*darcy ];
        poro = [grdeclo.PORO;0.1];
        rock.perm = perm(oindex(G.cells.indexMap), :);
        rock.poro = max(poro(oindex(G.cells.indexMap)), 0.1);
        [i, j, k] = ind2sub(G.cartDims, G.cells.indexMap);
        ind = (k>1 & k<G.cartDims(3) &  oindex(G.cells.indexMap) == numel(poro));
        rock.perm(ind, :) = rock.perm(ind, :) * 100;
      otherwise
        error('norne_case not recognized');
    end

Setup fluid parameters from SPE1

pRef = 270*barsa;
    switch opt.fluid_model
      case 'blackoil'
        pth = getDatasetPath('spe1');
        fn  = fullfile(pth, 'BENCH_SPE1.DATA');
        deck = readEclipseDeck(fn);
        deck = convertDeckUnits(deck);
        fluid = initDeckADIFluid(deck);
        if isfield(fluid, 'pcOW')
            fluid = rmfield(fluid, 'pcOW');
        end
        if isfield(fluid, 'pcOG')
            fluid = rmfield(fluid, 'pcOG');
        end

        % Setup quadratic relative permeabilities, since SPE1 relperm are a bit rough.
        fluid.krW = @(s) s.^2;
        fluid.krG = @(s) s.^2;
        fluid.krOW = @(s) s.^2;
        fluid.krOG = @(s) s.^2;
        pRef = deck.PROPS.PVTW(1);

      case {'oil water'}
        fluid = initSimpleADIFluid('phases', 'WO', 'mu', [1, 10]*centi*poise, ...
                                   'n',  [1, 1], 'rho', [1000, 700]*kilogram/ ...
                                   meter^3, 'c', 1e-10*[1, 1], 'cR', 4e-10, ...
                                   'pRef', pRef);

      case {'water'}
        fluid = initSimpleADIFluid('phases', 'W', 'mu', 1*centi*poise, 'rho', ...
                                   1000*kilogram/meter^3, 'c', 1e-10, ...
                                   'cR', 4e-10, 'pRef', pRef);
      otherwise
        error('fluid_model  not recognized.');
    end

Setup material parameters for Biot and mechanics

E          = 1 * giga * Pascal; % Young's module
    nu         = 0.3;               % Poisson's ratio
    alpha      = 1;                 % Biot's coefficient

    % Transform these global properties (uniform) to cell values.
    E          = repmat(E, G.cells.num, 1);
    nu         = repmat(nu, G.cells.num, 1);
    rock.alpha = repmat(alpha, G.cells.num, 1);

Setup boundary conditions for mechanics (no displacement)

switch opt.bc_case

      case 'no displacement'

        ind = (G.faces.neighbors(:, 1) == 0 | G.faces.neighbors(:, 2) == 0);
        ind = find(ind);
        nodesind = mcolon(G.faces.nodePos(ind), G.faces.nodePos(ind + 1) - 1);
        nodes = G.faces.nodes(nodesind);
        bcnodes = zeros(G.nodes.num);
        bcnodes(nodes) = 1;
        bcnodes = find(bcnodes == 1);
        nn = numel(bcnodes);
        u = zeros(nn, 3);
        m = ones(nn, 3);
        disp_bc = struct('nodes', bcnodes, 'uu', u, 'mask', m);
        force_bc = [];

      case 'bottom fixed'

        nx = G.cartDims(1);
        ny = G.cartDims(2);
        nz = G.cartDims(3);

        % Find the bottom nodes. On these nodes, we impose zero displacement

        c = zeros(nx*ny*nz, 1);
        c(G.cells.indexMap) = (1 : numel(G.cells.indexMap))';
        bottomcells = c(nx*ny*(nz - 1) +  (1 : (nx*ny))');
        faces = G.cells.faces(mcolon(G.cells.facePos(bottomcells), G.cells.facePos(bottomcells ...
                                                          + 1) - 1), :);
        bottomfaces = faces( faces(:, 2) == 6  , 1);
        indbottom_nodes = mcolon(G.faces.nodePos(bottomfaces), G.faces.nodePos(bottomfaces ...
                                                          + 1) - 1);
        bottom_nodes = G.faces.nodes(indbottom_nodes);
        isbottom_node = false(G.nodes.num, 1);
        isbottom_node(bottom_nodes) = true;
        bcnodes = find(isbottom_node);

        nn = numel(bcnodes);
        u = zeros(nn, 3);
        m = ones(nn, 3);
        disp_bc = struct('nodes', bcnodes, 'uu', u, 'mask', m);

        % Find outer faces that are not at the bottom. On these faces, we impose
        % a given pressure.

        is_outerface1 = (G.faces.neighbors(:, 1) == 0);
        is_outerface1(bottomfaces) = false;
        is_outerface2 = G.faces.neighbors(:, 2) == 0;
        is_outerface2(bottomfaces) = false;

        is_outerface = is_outerface1 | is_outerface2;

        outer_faces = find(is_outerface);

        outer_pressure = pRef;
        signcoef = (G.faces.neighbors(outer_faces, 1) == 0) - ...
            (G.faces.neighbors(outer_faces, 2) == 0);
        n = bsxfun(@times, G.faces.normals(outer_faces, :), signcoef./ ...
                   G.faces.areas(outer_faces));
        force = bsxfun(@times, n, outer_pressure);

        force_bc = struct('faces', outer_faces, 'force', force);


      otherwise
        error('bc_cases not recognized')
    end

    el_bc = struct('disp_bc' , disp_bc, ...
                   'force_bc', force_bc);

Setup load for mechanics

% In this example we do not impose any volumetric force
    loadfun = @(x) (0*x);

Gather all the mechanical parameters in a struct

mech = struct('E', E, 'nu', nu, 'el_bc', el_bc, 'load', loadfun);

Gravity

The gravity in this option affects only the fluid behavior

gravity on;

Setup model

modeltype = [opt.method, ' and ', opt.fluid_model];
    fullycoupledOptions = {'verbose', opt.verbose};
    splittingOptions = {'splittingTolerance', opt.splittingTolerance, ...
                        'splittingVerbose', opt.splittingVerbose};
    switch modeltype

      case 'fully coupled and blackoil'
        model = MechBlackOilModel(G, rock, fluid, mech, fullycoupledOptions{: ...
                   });

      case 'fixed stress splitting and blackoil'
        model = MechFluidFixedStressSplitModel(G, rock, fluid, mech, ...
                                               'fluidModelType', 'blackoil', ...
                                               splittingOptions{:});

      case 'fully coupled and oil water'
        model = MechOilWaterModel(G, rock, fluid, mech, fullycoupledOptions{: ...
                   });

      case 'fixed stress splitting and oil water'
        model = MechFluidFixedStressSplitModel(G, rock, fluid, mech, ...
                                               'fluidModelType', 'oil water', ...
                                               splittingOptions{:});

      case 'fully coupled and water'
        model = MechWaterModel(G, rock, fluid, mech, fullycoupledOptions{: });

      case 'fixed stress splitting and water'
        model = MechFluidFixedStressSplitModel(G, rock, fluid, mech, ...
                                               'fluidModelType', 'water', ...
                                               splittingOptions{:});

      otherwise
        error('modeltype not recognized.');
    end

Setup wells

W = [];
    refdepth = G.cells.centroids(1, 3); % for example...
    injcell  = 10; % for example...
    prodcell = G.cells.num;

    if strcmp(opt.norne_case, 'full padded')
        injcello = 10; % for example
        pos = [0.457517456153883   7.321672863418036   0.002564025720821] * 1e6;
        [~, injcell] = min(sum(bsxfun(@minus, G.cells.centroids, pos).^2, 2))
        injcell = find(oindex(:) == injcello);
        prodcello = 7392; % for example
        pos = [0.458944373173747   7.322630019653659   0.002783994408426] * 1e6;
        [~, prodcell] = min(sum(bsxfun(@minus, G.cells.centroids, pos).^2, 2));
        prodcell = find(oindex(:) == prodcello);
    end

    W = addWell(W, G, rock, injcell, ...
                'Type'    , 'rate', ...
                'Val'     , 2.5e6/day, ...
                'Sign'    , 1,  ...
                'Comp_i'   , [0, 0, 1], ...
% inject gas
                'Name'    , 'inj',  ...
                'refDepth', refdepth);


    W = addWell(W, G, rock, prodcell, ...
                'Type'    ,'bhp', ...
                'Val'     , pRef, ...
                'Sign'    , -1,  ...
                'Comp_i'   , [0, 1, 0], ...
% one-phase test case
                'Name'    , 'prod',  ...
                'refDepth', refdepth);

    switch opt.fluid_model
      case 'blackoil'
        W(1).compi = [0, 0, 1];
        W(2).compi = [0, 1, 0];
      case 'oil water'
        W(1).compi = [1 0];
        W(1).val   = 1e4/day;
        W(2).compi = [0 1];
      case 'water'
        W(1).compi = [1];
        W(1).val   = 1e4/day;
        W(2).compi = [1];
      otherwise
        error('fluid_model not recognized.')
    end

    facilityModel = FacilityModel(model.fluidModel);
    facilityModel = facilityModel.setupWells(W);
    model.FacilityModel = facilityModel;

Setup schedule

schedule.step.val     = [1*day*ones(1, 1); 5*day*ones(20, 1)];
    schedule.step.control = ones(numel(schedule.step.val), 1);
    schedule.control      = struct('W', W);

Setup initial state

clear initState;
    initState.pressure = pRef*ones(G.cells.num, 1);
    switch opt.fluid_model
      case 'blackoil'
        init_sat = [0, 1, 0];
        initState.rs  = 0.5*fluid.rsSat(initState.pressure);
      case 'oil water'
        init_sat = [0, 1];
      case 'water'
        init_sat = [1];
      otherwise
        error('fluid_model not recognized.')
    end
    initState.s = ones(G.cells.num, 1)*init_sat;
    initState   = computeInitDisp(model, initState, []);

    [wellSols, states, schedulereport] = simulateScheduleAD(initState, model, schedule);
===========================
| It # | disp (disp_dofs) |
===========================
|    1 | 6.45e+12         |
|    2 | 8.02e-03         |
|    3 |*1.21e-13         |
===========================
*****************************************************************
...
end
ans =

  MechWaterModel with properties:

                mechModel: [1×1 MechanicMechModel]
          MechPrimaryVars: []
                  mechfds: {'xd'  'uu'  'u'  'stress'  'strain'  'vdiv'}
...