examples.pressure_diffusion

Finite-speed pressure propagation, and the pressure "gauge".

With ct = 0 the pressure equation is elliptic: the whole field re-adjusts instantaneously whenever a rate changes, and (since the boundaries are closed) the solution is only determined up to an additive constant.

With ct > 0 it is parabolic -- a diffusion equation with diffusivity

$$ η = K λ / (φ c_t) $$

so a rate change is felt only after a delay of the order $ r^2/η $ at distance $r$. The elliptic solution is recovered as $ t → ∞ $ (or $ c_t → 0 $), as shown below. Moreover, the pressure level is now pinned by the initial condition P0 (the model keeps track of absolute pressure, not just its gradients).

Notes on the setup:

  • The reservoir is initialised fully water-saturated, so that the total mobility $λ = 1$ is uniform and constant, isolating the pressure physics (i.e. $η = 100$ everywhere, since K = por = 1 and ct = .01). It stays that way exactly: $s = 1$ is a fixed point of the transport step (ref ResSim.storage_rate), whatever the wells do. Asserted below.
  • The rates are balanced (as they must be for the ct = 0 comparison run), whence the storage terms cancel and the mean pressure stays at P0 exactly. This conveniently fixes the (otherwise arbitrary) level of the elliptic solution: we centre it on its own mean.

In the figures:

  • "Pressure diffusion": at t = 0.0002 only the immediate surroundings of the two wells have responded -- the middle of the domain is still at P0 (white) -- while by t = 0.005 the field is barely distinguishable from the elliptic one (bottom right). The four panels share their colour scale.
  • "profiles & gauge" (left): the same, quantified along the y = 0 edge. At the earliest time, the far half of that line has attained under 2% of its eventual (elliptic) response; at the latest time, some 98%. The dotted verticals mark the diffusion length $\sqrt{ηt}$: an indication of scale, not a sharp front.
  • "profiles & gauge" (right): the two ct > 0 curves are separated by exactly the difference of their P0 (asserted below) -- the absolute level is meaningful, and remembered. The two ct = 0 curves instead coincide, at 0: P0 is ignored, and the level is merely that of the grounded cell.
Pressure diffusion
Pressure diffusion
Pressure diffusion -- profiles & gauge
Pressure diffusion -- profiles & gauge
  1"""Finite-speed pressure propagation, and the pressure "gauge".
  2
  3With `ct = 0` the pressure equation is *elliptic*: the whole field re-adjusts
  4**instantaneously** whenever a rate changes, and (since the boundaries are closed)
  5the solution is only determined up to an additive constant.
  6
  7With `ct > 0` it is *parabolic* -- a diffusion equation with diffusivity
  8
  9$$ η = K λ / (φ c_t) $$
 10
 11so a rate change is felt only after a delay of the order $ r^2/η $ at distance $r$.
 12The elliptic solution is recovered as $ t → ∞ $ (or $ c_t → 0 $), as shown below.
 13Moreover, the pressure level is now pinned by the initial condition `P0`
 14(the model keeps track of *absolute* pressure, not just its gradients).
 15
 16Notes on the setup:
 17
 18- The reservoir is initialised **fully water-saturated**, so that the total mobility
 19  $λ = 1$ is uniform and constant, isolating the pressure physics
 20  (i.e. $η = 100$ everywhere, since `K = por = 1` and `ct = .01`).
 21  It stays that way *exactly*: $s = 1$ is a fixed point of the transport step
 22  (ref `ResSim.storage_rate`), whatever the wells do. Asserted below.
 23- The rates are balanced (as they must be for the `ct = 0` comparison run),
 24  whence the storage terms cancel and the *mean* pressure stays at `P0` exactly.
 25  This conveniently fixes the (otherwise arbitrary) level of the elliptic solution:
 26  we centre it on its own mean.
 27
 28In the figures:
 29
 30- "Pressure diffusion": at t = 0.0002 only the immediate surroundings of the
 31  two wells have responded -- the middle of the domain is still at `P0` (white)
 32  -- while by t = 0.005 the field is barely distinguishable from the elliptic
 33  one (bottom right). The four panels share their colour scale.
 34- "profiles & gauge" (left): the same, quantified along the y = 0 edge. At the
 35  earliest time, the far half of that line has attained under 2% of its
 36  eventual (elliptic) response; at the latest time, some 98%. The dotted
 37  verticals mark the diffusion length $\\sqrt{ηt}$: an indication of scale,
 38  not a sharp front.
 39- "profiles & gauge" (right): the two `ct > 0` curves are separated by exactly
 40  the difference of their `P0` (asserted below) -- the absolute level is
 41  meaningful, and remembered. The two `ct = 0` curves instead coincide, at 0:
 42  `P0` is ignored, and the level is merely that of the grounded cell.
 43"""
 44
 45from mpl_tools.place import freshfig
 46import numpy as np
 47
 48from TPFA_ResSim import ResSim
 49from TPFA_ResSim.plotting import show
 50
 51## Setup
 52wells = [dict(xy=[0, 0], rate=+.25), dict(xy=[1, 1], rate=-.25)]
 53grid: dict = dict(Lx=1, Ly=1, Nx=32, Ny=32)
 54
 55model = ResSim(**grid, wells=wells, ct=.01)
 56model_inc = ResSim(**grid, wells=wells)  # ct = 0
 57
 58eta = 1/model.ct  # Diffusivity (since K = por = λ = 1)
 59dt = 2e-4
 60nSteps = 25
 61water_sat0 = np.ones(model.Nxy)
 62P0 = np.ones(model.Nxy)
 63
 64## Simulate
 65SS, PP = model.sim(dt, nSteps, water_sat0, P0=P0, pbar=False)
 66assert (SS == 1).all(), "The reservoir should remain fully water-saturated."
 67_, PP_inc = model_inc.sim(dt, nSteps, water_sat0, pbar=False)
 68# Same, but starting from a higher pressure level
 69_, PP_hi = model.sim(dt, nSteps, water_sat0, P0=P0 + 1, pbar=False)
 70_, PP_inc_hi = model_inc.sim(dt, nSteps, water_sat0, P0=P0 + 1, pbar=False)
 71
 72# The elliptic solution is the same at all times here (mobility is constant).
 73# Its level is arbitrary: centre it, matching the (conserved) mean of `PP`.
 74elliptic = PP_inc[1] - PP_inc[1].mean()
 75
 76## Plot: the pressure disturbance, dp, spreading out
 77snapshots = [1, 5, nSteps]
 78vmax = 1.05*np.abs(elliptic).max()
 79kws: dict = dict(levels=np.linspace(-vmax, vmax, 21), cmap="RdBu_r",
 80                 colorbar=False, finalize=False, wells=dict(size=.4))
 81
 82fig, axs = freshfig("Pressure diffusion", nrows=2, ncols=2,
 83                    sharex=True, sharey=True, figsize=(7, 6))
 84for ax, k in zip(axs.ravel(), snapshots + [None]):
 85    if k is None:
 86        cc = model.plt_field(ax, elliptic, **kws, title="$c_t = 0$: instant")
 87    else:
 88        cc = model.plt_field(ax, PP[k] - P0, **kws,
 89                             title=f"t = {k*dt:.4f}   "
 90                                   f"($\\sqrt{{ηt}}$ = {np.sqrt(eta*k*dt):.2f})")
 91    ax.title.set_fontsize("medium")
 92fig.colorbar(cc, ax=axs, shrink=.6, label="$p - p_0$")
 93
 94## Plot: how much of the eventual response has arrived, and where
 95fig, (ax1, ax2) = freshfig("Pressure diffusion -- profiles & gauge",
 96                           ncols=2, figsize=(10, 4))
 97
 98# Sample along the y=0 edge, stopping short of the (anti-symmetric) corner,
 99# where the elliptic reference vanishes and the ratio below is meaningless.
100xx = np.linspace(0, .75, 13)
101line = [model.xy2ind(x, 0) for x in xx]
102
103for k in [1, 3, 9, nSteps]:
104    h, = ax1.plot(xx, ((PP[k] - P0)/elliptic)[line], "-o", ms=3,
105                  label=f"t = {k*dt:.4f}")
106    # The diffusion length -- an indication of scale, not a sharp front
107    ax1.axvline(np.sqrt(eta*k*dt), c=h.get_color(), ls=":", lw=1)
108ax1.axhline(1, c="k", ls="--", lw=1, label="$c_t = 0$ (elliptic)")
109ax1.set(title="Fraction of the elliptic response attained\n"
110              "(along $y=0$; dotted: diffusion length $\\sqrt{ηt}$)",
111        xlabel="x", ylabel="$(p - p_0) \\, / \\, p_\\mathrm{elliptic}$")
112ax1.legend(fontsize="small")
113
114# Gauge: is the absolute pressure level meaningful?
115tt = dt*np.arange(nSteps + 1)
116iw = model.xy2ind(*model.wells.xy[0])
117ax2.plot(tt, PP[:, iw]   , "-" , c="C0", label="$c_t>0$, $p_0=1$")
118ax2.plot(tt, PP_hi[:, iw], "--", c="C1", label="$c_t>0$, $p_0=2$")
119ax2.plot(tt[1:], PP_inc[1:, iw]   , "-" , c="C2", label="$c_t=0$, $p_0=1$")
120ax2.plot(tt[1:], PP_inc_hi[1:, iw], "--", c="C3", label="$c_t=0$, $p_0=2$")
121ax2.set(title="Pressure at the injector", xlabel="Time", ylabel="p")
122ax2.legend(fontsize="small")
123fig.tight_layout()
124
125# With ct > 0 the two curves are offset by exactly the offset in `P0`:
126assert np.allclose(PP_hi - PP, 1)
127# With ct = 0, `P0` is simply ignored, and the level is that of the "grounding"
128# of cell 0 (ref article p. 13). Indeed, summing all rows of that (modified)
129# system leaves `2 K p[0] = sum(Q) = 0`, i.e. it pins the pressure of cell (0,0):
130assert np.allclose(PP_inc[1:], PP_inc_hi[1:])
131assert np.allclose(PP_inc[1:, 0], 0)
132
133# Regression values, checked by `tests/test_examples.py`.
134__digest__ = dict(dp_early = PP[1] - P0,
135                  dp_late  = PP[nSteps] - P0,
136                  elliptic = elliptic,
137                  p_inj    = PP[:, iw])
138
139if __name__ == "__main__":
140    show()