examples.water_cut_gradient

The gradient of the water cut at one producer, at one time, wrt. the field of $\log K$.

A five-spot: one injector at the centre, four producers in the corners, on a heterogeneous (smoothed, log-normal) permeability. The objective is the water cut at the NE producer at a time index just after its breakthrough, where it is rising fastest. TPFA_ResSim.tlm.adjoint returns its gradient with respect to every cell's $\log K$ (and the initial state) at the cost of about one more simulation -- here checked against a finite difference in a random direction, which agrees to about 1e-8.

The water cut at a producer, $ f_w(s) $ in its cell (the fraction of water in what it produces; at a fixed rate, proportional to its water rate), is a function of the saturation there alone, so the adjoint is seeded by the single entry $ ∂J/∂s_k[i_\mathrm{prd}] = f_w'(s) $ -- which tlm.fractional_flow supplies.

In the figure:

  • Left: the $\log K$ field, with the wells.
  • Middle: the water cut of each producer over time, the objective marked.
  • Right: the gradient. It is positive along the flow path from the injector to the NE producer -- more permeable rock there brings the water sooner -- and negative along the paths to the other producers, and around the NE path: more permeable rock there diverts the water from the NE well, or lets it sweep a wider area (later arrival at the well itself). Outside the drainage area of the injector-NE pair, it vanishes: what happens there has not yet had time to affect the NE well.
The gradient is with respect to the isotropic $\log K$.

model.K holds both components, so the adjoint returns a gradient for each; as they are set equal here, the gradient of the single field is their sum, ref tlm.Gradient.logK.

Water-cut gradient
Water-cut gradient
  1"""The gradient of the water cut at one producer, at one time, wrt. the field of $\\log K$.
  2
  3A five-spot: one injector at the centre, four producers in the corners, on a
  4heterogeneous (smoothed, log-normal) permeability. The objective is the water
  5cut at the NE producer at a time index just after its breakthrough, where it is
  6rising fastest. `TPFA_ResSim.tlm.adjoint` returns its gradient with respect to
  7every cell's $\\log K$ (and the initial state) at the cost of about one more
  8simulation -- here checked against a finite difference in a random direction,
  9which agrees to about 1e-8.
 10
 11The water cut at a producer, $ f_w(s) $ in its cell (the fraction of water in
 12what it produces; at a fixed rate, proportional to its water rate), is a
 13function of the saturation there alone, so the adjoint is seeded by the single
 14entry $ ∂J/∂s_k[i_\\mathrm{prd}] = f_w'(s) $ -- which `tlm.fractional_flow`
 15supplies.
 16
 17In the figure:
 18
 19- Left: the $\\log K$ field, with the wells.
 20- Middle: the water cut of each producer over time, the objective marked.
 21- Right: the gradient. It is positive along the flow path from the injector
 22  to the NE producer -- more permeable rock there brings the water sooner --
 23  and negative along the paths to the *other* producers, and around the NE
 24  path: more permeable rock there diverts the water from the NE well, or lets
 25  it sweep a wider area (later arrival at the well itself). Outside the
 26  drainage area of the injector-NE pair, it vanishes: what happens there has
 27  not yet had time to affect the NE well.
 28
 29.. note:: The gradient is with respect to the *isotropic* $\\log K$.
 30
 31    `model.K` holds both components, so the adjoint returns a gradient for
 32    each; as they are set equal here, the gradient of the single field is
 33    their sum, ref `tlm.Gradient.logK`.
 34"""
 35
 36from mpl_tools.place import freshfig
 37import matplotlib.pyplot as plt
 38import numpy as np
 39from scipy.ndimage import uniform_filter as smooth
 40
 41from TPFA_ResSim import ResSim
 42from TPFA_ResSim.plotting import show
 43from TPFA_ResSim.tlm import adjoint, fractional_flow
 44
 45rng = np.random.default_rng(1)  # Reproducibility (the values are regression tested)
 46
 47## Model: a five-spot on heterogeneous permeability
 48model = ResSim(Lx=1, Ly=1, Nx=32, Ny=32, wells=[
 49    dict(xy=[.5, .5], rate=+1  , name="inj"),
 50    dict(xy=[1 , 1 ], rate=-.25, name="NE"),
 51    dict(xy=[0 , 1 ], rate=-.25, name="NW"),
 52    dict(xy=[0 , 0 ], rate=-.25, name="SW"),
 53    dict(xy=[1 , 0 ], rate=-.25, name="SE"),
 54])
 55logK = 3 * smooth(smooth(rng.standard_normal(model.shape)))
 56model.K = np.exp(logK)  # isotropic: broadcast to both components
 57
 58dt, nSteps = .05, 30
 59S0 = np.zeros(model.Nxy)
 60SS, PP = model.sim(dt, nSteps, S0, pbar=False)
 61
 62## The objective: water cut at the NE producer at time `k`
 63producers = model.wells.names[1:]
 64prd = model.xy2ind(*model.wells.xy[1:].T)  # their cells
 65
 66
 67def water_cut(model, SS):
 68    """`(nSteps+1, nPrd)` water cut at each producer, for each stored time."""
 69    return np.array([fractional_flow(model, S)[0][prd] for S in SS])
 70
 71
 72fw = water_cut(model, SS)
 73well, k = 0, 15  # NE, just after breakthrough (fw ≈ .5)
 74J = fw[k, well]
 75
 76## Its gradient, by the adjoint
 77dJ_dSS = np.zeros_like(SS)
 78dJ_dSS[k, prd[well]] = fractional_flow(model, SS[k])[1][prd[well]]  # f_w'(s)
 79grad = adjoint(model, dt, SS, PP, dJ_dSS)
 80G = grad.logK.sum(0)  # isotropic ⇒ sum the components
 81
 82## Check: a finite difference in a random direction of log K
 83direction = rng.standard_normal(model.shape)
 84eps = 1e-5
 85def J_of(logK):  # noqa: E302
 86    m = ResSim(Lx=1, Ly=1, Nx=32, Ny=32, wells=model.wells)
 87    m.K = np.exp(logK)
 88    return water_cut(m, m.sim(dt, nSteps, S0, pbar=False)[0])[k, well]
 89fd = (J_of(logK + eps*direction) - J_of(logK - eps*direction)) / (2*eps)
 90directional = (G * direction).sum()
 91assert abs(fd - directional) < 1e-4 * abs(directional), (fd, directional)
 92
 93## Plot
 94fig, axs = freshfig("Water-cut gradient", ncols=3, figsize=(13, 4),
 95                    gridspec_kw={'width_ratios': (1, 1.2, 1)})
 96
 97ax = axs[0]
 98model.plt_field(ax, logK, title="$\\log K$", cmap="viridis", levels=17,
 99                wells="color", finalize=False)
100
101ax = axs[1]
102tt = dt * np.arange(nSteps + 1)
103for i, name in enumerate(producers):
104    ax.plot(tt, fw[:, i], label=name, c=f"C{i}")
105ax.plot(tt[k], J, "o", c="k", mfc="none", ms=10, zorder=3,
106        label=f"objective: {producers[well]} @ t={tt[k]:.2f}")
107ax.set(title="Water cut", xlabel="Time", ylabel="$f_w$", ylim=(-.02, 1))
108ax.legend(loc="upper left")
109
110ax = axs[2]
111# The few cells next to the wells dominate; clip the color scale (the cmap's
112# `over`/`under` make `plt_field` extend the colorbar, rather than leave blanks).
113m = np.percentile(abs(G), 98)
114cmap = plt.get_cmap("RdBu_r")
115cmap = cmap.with_extremes(over=cmap(1.0), under=cmap(0.0))
116model.plt_field(ax, G, title="$∂J/∂\\log K$", cmap=cmap,
117                levels=np.linspace(-m, m, 21), cticks=[-m, 0, m],
118                wells="color", finalize=False)
119
120fig.tight_layout()
121
122# Regression values, checked by `tests/test_examples.py`.
123__digest__ = dict(water_cut   = fw,
124                  gradient    = G,
125                  directional = [directional, fd])
126
127if __name__ == "__main__":
128    show()