examples.buildup

Primary depletion, then pressure buildup after shut-in -- a "well test". In metric units.

The producer flows for a while, and is then shut in (rate set to 0). The pressure then builds up, asymptotically towards the average pressure, which is now constant since nothing enters or leaves the (closed) reservoir.

Everything here is a consequence of ct > 0:

  • The flow period is primary depletion: production without injection. Impossible with ct = 0 (the rates must balance, ref Wells.rates); with ct > 0 the deficit is drawn from storage, i.e. from the expansion of the rock and fluids as the pressure drops -- at exactly the rate that material balance dictates, $ dp̄/dt = -q / (c_t V_p) $ ($V_p$ = pore volume; asserted below).
  • Monitor points further from the well respond later (and, after shut-in, keep declining for a while before turning around: they have not yet "heard" that the well was shut).
  • The relaxation is gradual, i.e. it has a memory of the flow history -- this is what makes well testing a viable inference method.

For contrast, we also run the incompressible model -- where a shut-in is felt everywhere immediately, and completely: the pressure (which is then only defined up to a constant, ref examples.pressure_diffusion) instantly becomes uniform. NB: since ct = 0 demands balanced rates, that run needs an active injector, whose rate is switched off at the same time.

No water is present, so S = 0 throughout -- effectively a single-phase example, the lone producer being (rates being signed) the only well there is. Every pressure plotted here is a cell pressure, not a wellbore one: for the latter, give the well an rw, ref examples.well_control.

Units

Unlike the other examples -- which leave the units unspecified (default) this one is posed in practical metric units: metres, days, bar, mD, cP, by changing ResSim.cdarcy to $ C = 0.008527 $.

Moreover, a 2 km square of 100 mD rock at 250 bar, produced at 20 m³/day/m, has a pressure diffusivity of

$$ η = C K λ / (φ c_t) ≈ 4.3 \times 10^4 \; \mathrm{m^2/day} \,, $$

so the well feels the boundary, 1 km away, after some $ r^2/η ≈ 23 $ days -- whence the 12-day flow period and 28-day buildup simulated here.

It also lets the run be interpreted the way a real well test is. During radial transient flow the line-source solution gives

$$ p_i - p = \frac{q μ}{4 π C K} \left[ \ln \frac{4 η t}{e^γ r^2} \right] \,, $$

so the semilog derivative $ dp / d\ln t $ plateaus at $ q μ / (4 π C K) $ -- the classic diagnostic of pressure-transient analysis. Being independent of $ r $, the cell pressure will do (no well model needed), and reading $ K $ off that plateau recovers the 100 mD that went in, to within 4%. The shortfall is the time discretization, not the units: at dt = .05 it is 1%.

Nothing here knows that the numbers are metric.

The axis labels below say "[bar]" because this script says so. cdarcy fixes the arithmetic, not the nomenclature.

In the figures:

  • "time series" (left): the delay of the response with distance -- by the time the well (r = 0) has dropped by 12 bar, the r = 1000 m point has barely moved. After the shut-in the near-well pressure recovers at once, whereas the distant points keep declining for a while before turning around: they have not yet heard of it. All then converge on $\bar{p}$ (dashed), which falls along the material-balance line while the well flows, and is constant after.
  • "time series" (middle): for ct > 0 the drawdown decays smoothly over the remainder of the run. For ct = 0 it is a rectangle: rate on, rate off, and nothing in between.
  • "time series" (right): the semilog derivative, and the plateau read off its minimum. It is a shallow minimum rather than a flat stretch, being squeezed from both sides: at early times the cell average has not yet resolved the transient, and at late times (dotted) the closed boundary ends the radial regime. Hence the 4%.
  • "pressure": the depression cone, growing while the well flows (the first two panels), then filling in -- the sharp, near-well part first, the broad remainder last. The colour scale is shared.
Buildup -- time series
Buildup -- time series
Buildup -- pressure
Buildup -- pressure
  1"""Primary depletion, then pressure buildup after shut-in -- a "well test". **In metric units.**
  2
  3The producer flows for a while, and is then shut in (rate set to 0).
  4The pressure then *builds up*, asymptotically towards the average pressure,
  5which is now constant since nothing enters or leaves the (closed) reservoir.
  6
  7Everything here is a consequence of `ct > 0`:
  8
  9- The flow period is *primary depletion*: production **without injection**.
 10  Impossible with `ct = 0` (the rates must balance, ref `Wells.rates`); with
 11  `ct > 0` the deficit is drawn from *storage*, i.e. from the expansion of the
 12  rock and fluids as the pressure drops -- at exactly the rate that material
 13  balance dictates, $ dp̄/dt = -q / (c_t V_p) $ ($V_p$ = pore volume; asserted below).
 14- Monitor points further from the well respond later
 15  (and, after shut-in, keep declining for a while before turning around:
 16  they have not yet "heard" that the well was shut).
 17- The relaxation is gradual, i.e. it has a memory of the flow history --
 18  this is what makes well testing a viable inference method.
 19
 20For contrast, we also run the incompressible model -- where a shut-in is
 21felt everywhere immediately, and completely: the pressure (which is then only
 22defined up to a constant, ref `examples.pressure_diffusion`) instantly
 23becomes uniform. NB: since `ct = 0` demands balanced rates, that run needs an
 24active injector, whose rate is switched off at the same time.
 25
 26No water is present, so `S = 0` throughout -- effectively a single-phase
 27example, the lone producer being (rates being signed) the only well there is.
 28Every pressure plotted here is a *cell* pressure, not a wellbore one: for the
 29latter, give the well an `rw`, ref `examples.well_control`.
 30
 31## Units
 32
 33Unlike the other examples -- which leave the units unspecified (default)
 34this one is posed in practical **metric** units: metres, days, bar, mD, cP,
 35by changing `ResSim.cdarcy` to $ C = 0.008527 $.
 36
 37Moreover, a 2 km square of 100 mD rock
 38at 250 bar, produced at 20 m³/day/m, has a pressure diffusivity of
 39
 40$$ η = C K λ / (φ c_t) ≈ 4.3 \\times 10^4 \\; \\mathrm{m^2/day} \\,, $$
 41
 42so the well feels the boundary, 1 km away, after some $ r^2/η ≈ 23 $ days --
 43whence the 12-day flow period and 28-day buildup simulated here.
 44
 45It also lets the run be *interpreted* the way a real well test is. During
 46radial transient flow the line-source solution gives
 47
 48$$ p_i - p = \\frac{q μ}{4 π C K} \\left[ \\ln \\frac{4 η t}{e^γ r^2} \\right] \\,, $$
 49
 50so the semilog derivative $ dp / d\\ln t $ **plateaus** at $ q μ / (4 π C K) $ --
 51the classic diagnostic of pressure-transient analysis. Being independent of
 52$ r $, the *cell* pressure will do (no well model needed), and reading $ K $ off
 53that plateau recovers the 100 mD that went in, to within 4%. The shortfall is
 54the time discretization, not the units: at `dt = .05` it is 1%.
 55
 56.. note:: Nothing here knows that the numbers are metric.
 57
 58    The axis labels below say "[bar]" because *this script* says so.
 59    `cdarcy` fixes the arithmetic, not the nomenclature.
 60
 61In the figures:
 62
 63- "time series" (left): the delay of the response with distance -- by the time
 64  the well (r = 0) has dropped by 12 bar, the r = 1000 m point has barely moved.
 65  After the shut-in the near-well pressure recovers at once, whereas the
 66  distant points keep *declining* for a while before turning around: they have
 67  not yet heard of it. All then converge on $\\bar{p}$ (dashed), which falls
 68  along the material-balance line while the well flows, and is constant after.
 69- "time series" (middle): for `ct > 0` the drawdown decays smoothly over the
 70  remainder of the run. For `ct = 0` it is a rectangle: rate on, rate off,
 71  and nothing in between.
 72- "time series" (right): the semilog derivative, and the plateau read off its
 73  minimum. It is a *shallow* minimum rather than a flat stretch, being squeezed
 74  from both sides: at early times the cell average has not yet resolved the
 75  transient, and at late times (dotted) the closed boundary ends the radial
 76  regime. Hence the 4%.
 77- "pressure": the depression cone, growing while the well flows (the first two
 78  panels), then filling in -- the sharp, near-well part first, the broad
 79  remainder last. The colour scale is shared.
 80"""
 81
 82from mpl_tools.place import freshfig
 83import numpy as np
 84
 85from minires import ResSim
 86from minires.plotting import show
 87
 88## Setup -- a 2 km square of 100 mD rock at 250 bar
 89L      = 2000  # m
 90N      = 64
 91q      = 20    # m²/day, i.e. m³/day per metre of thickness
 92perm   = 100   # mD
 93mu     = 1     # cP
 94por    = .2
 95ct     = 1e-4  # 1/bar
 96p_i    = 250   # bar
 97dt     = .2    # day
 98nSteps = 200
 99kShut  = 60    # Time index of shut-in (t = 12 day)
100tt     = dt*np.arange(nSteps + 1)
101
102schedule = np.where(np.arange(nSteps) < kShut, q, 0)
103# Aside: feedback control (e.g. shut-in upon water breakthrough) would instead
104# be implemented by overriding `ResSim.well_controls`.
105
106# `cdarcy` for m/day/bar/mD/cP: the darcy itself (9.869233e-16 m²),
107# expressed in the system -- i.e. 0.008527. Ref `ResSim.cdarcy`.
108C = 86400 * 9.869233e-16 * 1e5 / 1e-3
109
110grid: dict = dict(Lx=L, Ly=L, Nx=N, Ny=N, cdarcy=C,
111                  K=perm, por=por*np.ones((N, N)), fluid=dict(vw=mu, vo=mu))  # fmt: skip
112
113model = ResSim(**grid, ct=ct,
114               wells=[dict(name="P1", xy=[L/2, L/2], rate=-schedule)])  # fmt: skip
115
116# Incompressible analogue: the injector must match the producer at all times.
117model_inc = ResSim(**grid,
118                   wells=[dict(name="I1", xy=[0, 0], rate=+schedule),
119                          dict(name="P1", xy=[L/2, L/2], rate=-schedule)])  # fmt: skip
120
121oil_only = np.zeros(model.Nxy)
122P0 = np.full(model.Nxy, p_i)
123
124eta = C*perm/mu/(por*ct)  # Diffusivity (λ = 1, there being no water)
125unit_p, unit_t = " [bar]", " [day]"
126
127## Simulate
128SS, PP = model.sim(dt, nSteps, oil_only, P0=P0, pbar=False)
129_ , PP_inc = model_inc.sim(dt, nSteps, oil_only, pbar=False)
130
131iw = model.xy2ind(*model.wells.xy[0])
132p_mean = PP.mean(axis=1)
133p_cell = PP[:, iw]
134
135## Well test: the permeability, read off the semilog derivative's plateau
136kk = np.arange(2, kShut)  # NB: skip t = 0, whose log is -inf
137dp_dlnt = -(p_cell[kk+1] - p_cell[kk-1]) / (np.log(tt[kk+1]) - np.log(tt[kk-1]))
138plateau = dp_dlnt.min()
139perm_est = q*mu / (4*np.pi*C*plateau)
140
141## Plot: monitor points, the drawdown, and the well test
142fig, (ax1, ax2, ax3) = freshfig("Buildup -- time series", ncols=3, figsize=(14, 4))
143
144for r in [0, 200, 500, 1000]:
145    i = model.xy2ind(L/2 + r, L/2)
146    ax1.plot(tt, PP[:, i], label=f"r = {r} m")  # r = 0 is the well's cell
147ax1.plot(tt, p_mean, "k--", lw=1,
148         label="Mean, $\\bar{p}$: $p_i - qt/(c_t V_p)$, then constant")
149ax1.axvline(kShut*dt, c="k", lw=1, alpha=.4)
150ax1.annotate("shut-in", (kShut*dt, PP.min()), fontsize="small",
151             xytext=(4, 0), textcoords="offset points")
152ax1.set(title="Pressure at increasing distance from the well",
153        xlabel=f"Time{unit_t}", ylabel=f"p{unit_p}")
154ax1.legend(fontsize="small")
155
156drawdown     = p_mean - p_cell
157drawdown_inc = PP_inc.mean(axis=1) - PP_inc[:, iw]
158ax2.plot(tt[1:], drawdown[1:]    , label="$c_t > 0$")
159ax2.plot(tt[1:], drawdown_inc[1:], label="$c_t = 0$")
160ax2.axvline(kShut*dt, c="k", lw=1, alpha=.4)
161ax2.set(title="Drawdown, $\\bar{p} - p_\\mathrm{cell}$", xlabel=f"Time{unit_t}",
162        ylabel=f"$\\Delta p${unit_p}")
163ax2.legend()
164
165ax3.plot(tt[kk], dp_dlnt, "-o", ms=3)
166ax3.axhline(plateau, c="k", ls="--", lw=1,
167            label=f"Plateau ⇒ K = {perm_est:.0f} mD")
168ax3.axvline((L/2)**2/eta, c="C2", ls=":", lw=1, label="$r^2/η$ (boundary)")
169ax3.set(title="Well test: $dp / d\\ln t$", xlabel=f"Time{unit_t}", xscale="log",
170        ylabel=f"$dp/d\\ln t${unit_p}")
171ax3.legend(fontsize="small")
172fig.tight_layout()
173
174## Plot: the depression cone growing, then filling in
175fig, axs = freshfig("Buildup -- pressure", ncols=5, sharex=True, sharey=True,
176                    figsize=(13.5, 3.2))
177kws: dict = dict(levels=np.linspace(PP.min(), p_i, 21), cmap="viridis",
178                 colorbar=False, finalize=False, wells=dict(size=.4))
179snapshots = [kShut // 6, kShut, kShut + 2, kShut + 10, nSteps]
180for i, (ax, k) in enumerate(zip(axs, snapshots)):
181    cc = model.plt_field(ax, PP[k], **kws, labels=(i == 0),
182                         title=f"t = {k*dt:.1f} day" + (" (shut-in)" if k == kShut else ""))
183fig.colorbar(cc, ax=axs, shrink=.5, label=f"p{unit_p}")
184
185# While the well flows, the average pressure declines exactly as material
186# balance dictates (see also `tests/test_compressible.py`) ...
187Vp = L*L*por  # pore volume (areal, like `q`)
188assert np.allclose(p_mean[:kShut + 1], p_i - q*tt[:kShut + 1]/(ct*Vp))
189# ... and after shut-in it is constant (nothing enters or leaves) ...
190assert np.allclose(p_mean[kShut:], p_mean[kShut])
191# ... and the pressure equilibrates towards it: by the end of the run, the
192# spread has decayed to less than 1% of what it was at shut-in.
193assert np.ptp(PP[-1]) < .01 * np.ptp(PP[kShut])
194# Whereas the incompressible model forgets everything in a single step:
195assert np.allclose(PP_inc[kShut + 1:], 0)
196# The well test recovers the permeability that went in, to within 4%
197assert abs(perm_est/perm - 1) < .04
198
199# Regression values, checked by `tests/test_examples.py`.
200__digest__ = dict(p_cell   = p_cell[::10],
201                  p_far    = PP[::10, model.xy2ind(L, L/2)],
202                  p_mean   = p_mean[::10],
203                  p_final  = PP[-1],
204                  perm_est = perm_est)
205
206if __name__ == "__main__":
207    show()