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 = 1andct = .01). It stays that way exactly: $s = 1$ is a fixed point of the transport step (refResSim.storage_rate), whatever the wells do. Asserted below. - The rates are balanced (as they must be for the
ct = 0comparison run), whence the storage terms cancel and the mean pressure stays atP0exactly. 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 > 0curves are separated by exactly the difference of theirP0(asserted below) -- the absolute level is meaningful, and remembered. The twoct = 0curves instead coincide, at 0:P0is ignored, and the level is merely that of the grounded cell.
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 45import matplotlib.pyplot as plt 46import numpy as np 47 48from minires import ResSim 49from minires.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 = plt.subplots(num="Pressure diffusion", clear=True, 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) = plt.subplots(num="Pressure diffusion -- profiles & gauge", clear=True, 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()