examples.voidage_replacement

Waterflooding while under-injecting -- and what it does to the front.

The "voidage replacement ratio" (VRR) is the injected volume divided by the produced one. The incompressible model can only ever run VRR = 1 (time_stepper asserts it). With ct > 0 we may inject less than we produce, the difference being made up by expansion (storage), i.e. by declining pressure.

Here the same producer rate is run at VRR = 1 and at VRR = ½, showing that compressibility affects the saturation history, not just the pressure:

  • The front advances more slowly, delaying water breakthrough.
  • Some of the oil is instead driven by expansion, from all directions, so the sweep is not simply a "slowed down" version of VRR = 1: per unit of water injected, it is also less efficient.
  • The pressure declines at the rate given by material balance (see examples.depletion), which is the price paid for the deferral.

Both the pressure and the transport equation carry their $O(c_t)$ terms (ref ResSim.ct), so the saturations stay bounded and, at VRR = 1, converge to the incompressible solution as $c_t → 0$ -- verified below.

This example is deliberately extreme.

It produces two pore volumes while injecting one, so the fluids are asked to expand by 100%, i.e. $ c_t Δ\bar{p} = 1 $, rather than the $ \ll 1 $ that "slightly compressible" implies. The behaviour below is self-consistent and qualitatively right, but not quantitatively trustworthy. NB: lowering ct does not remedy it, the voidage being what sets the demanded expansion (ref the "Compressibility" section of the docs).

In the figures:

  • "saturation": the VRR = ½ row lags from the outset (only half the water has gone in), and by t = 8 it has still not swept the NE corner, whereas VRR = 1 has (mean saturation 0.79, against 0.89). Note also the shape: compared at equal injected volume (VRR = 1 at t = 4 against VRR = ½ at t = 8, both one pore volume) the two sweeps are close, but not equal -- an rms difference of 0.06 in saturation.
  • "histories" (left): breakthrough (dotted) is deferred from t = 2.8 to t = 3.9. Measured in injected volume, though, it arrives sooner: after 0.49 pore volumes, against 0.69. Under-injecting buys time, but sweeps less efficiently, the expansion drive adding a drift towards the producer.
  • "histories" (right): the price. $\bar{p}$ falls from 15 to 5, along the material-balance line (dashed), while VRR = 1 holds it at 15.
Voidage replacement -- saturation
Voidage replacement -- saturation
Voidage replacement -- histories
Voidage replacement -- histories
  1"""Waterflooding while *under*-injecting -- and what it does to the front.
  2
  3The "voidage replacement ratio" (VRR) is the injected volume divided by the
  4produced one. The incompressible model can only ever run `VRR = 1`
  5(`time_stepper` asserts it). With `ct > 0` we may inject less than we produce,
  6the difference being made up by expansion (storage), i.e. by declining pressure.
  7
  8Here the same producer rate is run at `VRR = 1` and at `VRR = ½`, showing that
  9compressibility affects the *saturation* history, not just the pressure:
 10
 11- The front advances more slowly, delaying water breakthrough.
 12- Some of the oil is instead driven by expansion, from all directions, so the
 13  sweep is not simply a "slowed down" version of `VRR = 1`: per unit of water
 14  injected, it is also *less efficient*.
 15- The pressure declines at the rate given by material balance
 16  (see `examples.depletion`), which is the price paid for the deferral.
 17
 18Both the pressure and the transport equation carry their $O(c_t)$ terms
 19(ref `ResSim.ct`), so the saturations stay bounded and, at `VRR = 1`, converge
 20to the incompressible solution as $c_t → 0$ -- verified below.
 21
 22.. warning:: This example is deliberately extreme.
 23
 24    It produces two pore volumes while injecting one,
 25    so the fluids are asked to expand by 100%, i.e.
 26    $ c_t Δ\\bar{p} = 1 $, rather than the $ \\ll 1 $ that "slightly
 27    compressible" implies. The behaviour below is self-consistent and
 28    qualitatively right, but not quantitatively trustworthy. NB: lowering `ct`
 29    does *not* remedy it, the voidage being what sets the demanded expansion
 30    (ref the "Compressibility" section of the docs).
 31
 32In the figures:
 33
 34- "saturation": the VRR = ½ row lags from the outset (only half the water has
 35  gone in), and by t = 8 it has still not swept the NE corner, whereas VRR = 1
 36  has (mean saturation 0.79, against 0.89). Note also the *shape*: compared at
 37  equal injected volume (VRR = 1 at t = 4 against VRR = ½ at t = 8, both one
 38  pore volume) the two sweeps are close, but not equal -- an rms difference of
 39  0.06 in saturation.
 40- "histories" (left): breakthrough (dotted) is deferred from t = 2.8 to t = 3.9.
 41  Measured in injected volume, though, it arrives *sooner*: after 0.49 pore
 42  volumes, against 0.69. Under-injecting buys time, but sweeps less
 43  efficiently, the expansion drive adding a drift towards the producer.
 44- "histories" (right): the price. $\\bar{p}$ falls from 15 to 5, along the
 45  material-balance line (dashed), while VRR = 1 holds it at 15.
 46"""
 47
 48from mpl_tools.place import freshfig
 49import numpy as np
 50
 51from TPFA_ResSim import ResSim
 52from TPFA_ResSim.plotting import show
 53
 54## Setup
 55q = .25
 56ct = .1
 57dt = .04
 58nSteps = 200
 59tt = dt*np.arange(nSteps + 1)
 60oil_only = np.zeros(32*32)
 61
 62
 63def waterflood(vrr, ct=ct, P0=15.):
 64    """Produce at rate `q`, inject at `vrr*q`."""
 65    model = ResSim(Lx=1, Ly=1, Nx=32, Ny=32, ct=ct,
 66                   wells=[dict(xy=[0, 0], rate=+vrr*q),
 67                          dict(xy=[1, 1], rate=-q)])
 68    kwargs: dict = dict(P0=P0*np.ones(model.Nxy)) if ct else {}
 69    return (model,) + model.sim(dt, nSteps, oil_only, pbar=False, **kwargs)
 70
 71
 72## Simulate
 73model, SS_full, PP_full = waterflood(vrr=1)
 74_    , SS_half, PP_half = waterflood(vrr=.5)
 75# At VRR = 1 there is no net voidage, so the incompressible model is the
 76# `ct → 0` limit. Check that the deviation from it is indeed O(ct), i.e. that
 77# dividing `ct` by ten divides the deviation by (about) ten.
 78_, SS_inc , _ = waterflood(vrr=1, ct=0)
 79_, SS_lowc, _ = waterflood(vrr=1, ct=ct/10)
 80dev = [abs(S - SS_inc).max() for S in [SS_full, SS_lowc]]
 81assert 8 < dev[0]/dev[1] < 12, dev
 82
 83iprd = model.xy2ind(*model.wells.xy[1])
 84breakthrough = [dt*np.argmax(S[:, iprd] > .01) for S in [SS_full, SS_half]]
 85
 86## Plot: the front, at equal times
 87fig, axs = freshfig("Voidage replacement -- saturation", nrows=2, ncols=3,
 88                    sharex=True, sharey=True, figsize=(9, 6))
 89for row, (SS, vrr) in enumerate(zip([SS_full, SS_half], ["1", "½"])):
 90    for col, t in enumerate([2, 4, 8]):
 91        model.plt_field(axs[row, col], SS[int(t/dt)], "oil", wells=dict(size=.4),
 92                        colorbar=False, finalize=False, labels=False,
 93                        title=(f"t = {t}" if row == 0 else ""))
 94    axs[row, 0].set_ylabel(f"VRR = {vrr}\ny")
 95axs[1, 0].set_xlabel("x")
 96fig.tight_layout()
 97
 98## Plot: breakthrough, and the pressure paid for it
 99fig, (ax1, ax2) = freshfig("Voidage replacement -- histories",
100                           ncols=2, figsize=(10, 4))
101
102for SS, vrr, t_bt in zip([SS_full, SS_half], ["1", "½"], breakthrough):
103    h, = ax1.plot(tt, 1 - SS[:, iprd], label=f"VRR = {vrr}")
104    ax1.axvline(t_bt, c=h.get_color(), ls=":", lw=1)
105ax1.set(title="Oil saturation in the producer\n(dotted: water breakthrough)",
106        xlabel="Time", ylabel="$1 - S$")
107ax1.legend()
108
109for PP, vrr in zip([PP_full, PP_half], ["1", "½"]):
110    ax2.plot(tt, PP.mean(axis=1), label=f"VRR = {vrr}")
111ax2.plot(tt, 15 - .5*q*tt/(ct*model.h2*model.por.sum()), "k--", lw=1,
112         label="$p_0 - (1 - \\mathrm{VRR}) q t / (c_t V_p)$")
113ax2.set(title="Mean pressure", xlabel="Time", ylabel="$\\bar{p}$")
114ax2.legend(fontsize="small")
115fig.tight_layout()
116
117# Under-injecting defers breakthrough, but not for free:
118assert breakthrough[1] > breakthrough[0]
119assert PP_half[-1].mean() < PP_full[-1].mean()
120
121# Regression values, checked by `tests/test_examples.py`.
122__digest__ = dict(sat_full = SS_full[-1],
123                  sat_half = SS_half[-1],
124                  prd_full = SS_full[:, iprd],
125                  prd_half = SS_half[:, iprd],
126                  p_half   = PP_half.mean(axis=1))
127
128if __name__ == "__main__":
129    show()