rta-production
SKILL.md
Rate Transient Analysis and Production Decline
Computational skill for decline curve analysis (DCA) and rate transient analysis (RTA) of oil and gas wells. Calibrated for Appalachian unconventional wells (Marcellus, Utica) but applicable to any reservoir.
Key distinction: Arps DCA (empirical, boundary-dominated flow, EUR/reserves) vs. RTA (analytical, combines transient + BDF, estimates k, S, OGIP from flowing data). Both are implemented here.
Module 1 — Arps Decline Curves
import math
def arps_rate(q_i, d_i, b, t):
"""
Arps hyperbolic decline rate.
q(t) = q_i / (1 + b*d_i*t)^(1/b) (0 < b < 2, hyperbolic)
q(t) = q_i * exp(-d_i * t) (b = 0, exponential)
q(t) = q_i / (1 + d_i * t) (b = 1, harmonic)
q_i: initial rate (any unit; d_i and t must share same time base)
d_i: initial nominal decline rate (fraction/time, e.g. 1/yr)
b: hyperbolic exponent (0=exp, 1=harmonic; shale typically 1.0-2.0 early)
t: time since start of decline (years if d_i in 1/yr)
Returns: rate at time t
"""
if b < 1e-6:
return q_i * math.exp(-d_i * t)
elif abs(b - 1.0) < 1e-6:
return q_i / (1.0 + d_i * t)
else:
return q_i / (1.0 + b * d_i * t) ** (1.0 / b)
def arps_cumulative(q_i, d_i, b, t):
"""
Arps cumulative production from t=0 to t.
Exponential: Np = (q_i / d_i) * (1 - exp(-d_i*t))
Harmonic: Np = (q_i / d_i) * ln(1 + d_i*t)
Hyperbolic: Np = q_i^b / ((1-b)*d_i) * (q_i^(1-b) - q_t^(1-b))
Returns: cumulative production (same unit as q_i * time)
"""
if b < 1e-6:
return (q_i / d_i) * (1.0 - math.exp(-d_i * t))
elif abs(b - 1.0) < 1e-6:
return (q_i / d_i) * math.log(1.0 + d_i * t)
else:
q_t = arps_rate(q_i, d_i, b, t)
return q_i**b / ((1.0 - b) * d_i) * (q_i**(1.0 - b) - q_t**(1.0 - b))
def eur_with_terminal_switch(q_i, d_i, b, d_min, q_aban=0.0):
"""
EUR for hyperbolic decline switching to exponential at d_min.
Industry practice: shale wells start hyperbolic (b=1.2-1.8),
switch to exponential at d_min = 0.05-0.10 / yr to prevent infinite EUR.
d_min: terminal exponential decline rate (1/yr)
q_aban: abandonment rate (same unit as q_i); 0 means decline to d_min only
Returns: total EUR (same unit as q_i * years)
"""
if b < 1e-6: # already exponential
if q_aban > 0:
t_aban = -math.log(q_aban / q_i) / d_i
return arps_cumulative(q_i, d_i, 0, t_aban)
return q_i / d_i
# Time when instantaneous decline rate reaches d_min
# d_inst(t) = d_i / (1 + b*d_i*t) => set = d_min => t_switch
t_switch = (d_i / d_min - 1.0) / (b * d_i)
q_switch = arps_rate(q_i, d_i, b, t_switch)
np_hyp = arps_cumulative(q_i, d_i, b, t_switch)
# Exponential tail at d_min from q_switch
if q_aban > 0 and q_aban < q_switch:
np_exp = (q_switch / d_min) * (1.0 - q_aban / q_switch)
else:
np_exp = q_switch / d_min # decline to zero (economic limit separately)
return np_hyp + np_exp
Appalachian DCA Reference:
| Formation | Typical b | d_i (1/yr) | d_min (1/yr) | Notes |
|---|---|---|---|---|
| Marcellus (dry gas) | 1.2–1.8 | 1.5–3.0 | 0.05–0.10 | BDF transition ~2–4 yr |
| Utica/PP (wet gas) | 1.0–1.5 | 1.2–2.5 | 0.06–0.12 | Higher initial rates |
| Bakken (oil) | 1.0–1.3 | 0.8–1.5 | 0.05–0.08 |
Module 2 — Loss-Ratio and b-Factor from Production History
def instantaneous_decline_rate(q1, q2, dt):
"""
D = -(1/q) * (dq/dt) ≈ -(q2 - q1) / (q_avg * dt)
Used to estimate current decline rate from consecutive rate measurements.
q1, q2: rates at times t1, t2 (any consistent unit)
dt: time interval (years)
Returns: instantaneous decline rate (1/yr)
"""
q_avg = 0.5 * (q1 + q2)
dq = q2 - q1
return -dq / (q_avg * dt)
def estimate_b_factor(rates, times):
"""
Estimate b-factor from rate history using loss-ratio method.
b = d(1/D)/dt (constant for Arps decline)
rates: list of production rates (consistent units)
times: list of corresponding times (years)
Returns: estimated b-factor and list of instantaneous D values
"""
D_list = []
for i in range(len(rates) - 1):
D = instantaneous_decline_rate(rates[i], rates[i+1],
times[i+1] - times[i])
D_list.append((times[i], D))
# b from slope of 1/D vs. time
if len(D_list) < 2:
return None, D_list
# Linear regression on 1/D vs t
x_vals = [d[0] for d in D_list]
y_vals = [1.0/d[1] for d in D_list if d[1] > 1e-9]
n = len(y_vals)
if n < 2:
return None, D_list
x_mean = sum(x_vals[:n]) / n
y_mean = sum(y_vals) / n
num = sum((x_vals[i] - x_mean) * (y_vals[i] - y_mean) for i in range(n))
den = sum((x_vals[i] - x_mean)**2 for i in range(n))
b = num / den if den > 1e-12 else 0.0
return b, D_list
Module 3 — Blasingame Flowing Material Balance (FMB)
def material_balance_time(Np_cumulative, q_current):
"""
Blasingame material balance pseudo-time: tc = Np / q
Normalizes transient and boundary-dominated flow data onto one straight line
when q/(Pi-Pwf) is plotted vs. tc.
For gas: tc = Gp [Mscf] / q_g [Mscf/day] (days)
For oil: tc = Np [bbl] / q_o [bbl/day] (days)
Returns: material balance time (same units as Np/q)
"""
if q_current <= 0:
return None
return Np_cumulative / q_current
def fmb_normalized_rate(q, delta_p):
"""
Normalized rate for FMB plot: q / delta_p
For gas (low P approximation): delta_p = P_avg - P_wf (using P^2/muZ for real gas)
For oil: delta_p = P_res - P_wf (BHP)
Plot q/delta_p vs. tc:
- Straight line intercept at q/delta_p = 0 gives tc = Gp or Np (OGIP/OOIP)
- Slope gives (1/J) where J is productivity index
Returns: normalized rate (unit/psi)
"""
if delta_p <= 0:
return None
return q / delta_p
def fmb_ogip_from_intercept(q_over_dp_initial, slope_fmb):
"""
From the FMB straight line: q/delta_p = intercept - slope * tc
x-intercept (q/delta_p = 0) occurs at tc = intercept/slope = OGIP (or OOIP).
Returns: OGIP estimate (same units as cumulative production)
"""
if abs(slope_fmb) < 1e-12:
return None
return q_over_dp_initial / slope_fmb
Module 4 — Production Forecast Schedule
def production_forecast(q_i, d_i, b, t_max_months,
d_min=0.0, q_aban=0.0):
"""
Monthly production forecast using Arps decline with optional terminal switch.
q_i: initial rate (Mscf/day or bbl/day)
d_i: initial nominal decline (1/yr)
b: hyperbolic exponent
t_max_months: forecast horizon (months)
d_min: terminal exponential decline (1/yr); 0 = no switch
q_aban: economic abandonment rate; 0 = forecast all months
Returns: list of (month, rate, cumulative) — rate and cum in same unit*yr
"""
forecast = []
dt_yr = 1.0 / 12.0
np_cum = 0.0
in_terminal = False
t_switch_yr = None
q_switch = None
if d_min > 0 and b > 1e-6:
t_switch_yr = (d_i / d_min - 1.0) / (b * d_i)
q_switch = arps_rate(q_i, d_i, b, t_switch_yr)
for month in range(1, t_max_months + 1):
t_yr = month * dt_yr
if not in_terminal and t_switch_yr and t_yr >= t_switch_yr:
in_terminal = True
if in_terminal and q_switch is not None:
t_since = t_yr - t_switch_yr
q = q_switch * math.exp(-d_min * t_since)
else:
q = arps_rate(q_i, d_i, b, t_yr)
if q_aban > 0 and q <= q_aban:
break
np_cum += q * dt_yr
forecast.append((month, round(q, 2), round(np_cum, 2)))
return forecast
Learning Resources
Textbooks
| Author(s) | Title | Notes |
|---|---|---|
| Lee & Wattenbarger | Gas Reservoir Engineering, SPE Vol. 5 (1996) | ISBN 978-1-55563-059-0 — foundational RTA theory |
| Craft, Hawkins & Terry | Applied Petroleum Reservoir Engineering, 3rd ed. (2015) | ISBN 978-0-13-315558-7 — reservoir theory underpinning RTA |
| Ahmed & McKinney | Advanced Reservoir Engineering (2005) | ISBN 978-0-7506-7733-4 — includes DCA chapter |
Key SPE References
| Paper | Title | Why It Matters |
|---|---|---|
| SPE 116731 (Ilk et al., 2008) | Exponential vs. Hyperbolic Decline in Tight Gas | b-factor debate for shale |
| SPE 107967 (Clarkson, 2007) | Dynamic Flowing Material Balance for CBM | FMB methodology |
| Blasingame & Lee (1993) | "Analysis of Variable Rate Well Test Pressure Data" | Original FMB derivation |
Free Online Resources
| Resource | URL | Content |
|---|---|---|
| SPE PetroWiki — RTA | petrowiki.spe.org/Rate_transient_analysis | Equations, references, free |
| Texas A&M Blasingame group | petroleum.tamu.edu/facultystaff/blasingame-thomas | Course notes PETE 663 |
| Fekete tech white papers | ihsenergy.com (archived) | "Practical Guide to DCA" PDF |
| SPE student membership | spe.org/en/membership/student | $25/yr unlocks all SPE papers |
WVU Courses
- PNGE 321 Reservoir Engineering I — material balance, Darcy flow, DCA intro
- PNGE 411 Petroleum Economics — reserves classification, EUR-based economics
Output Format
## Production Decline Analysis — [Well / Formation]
### Decline Parameters
| Parameter | Value | Units |
|-----------|-------|-------|
| Initial rate (q_i) | | Mscf/d or bbl/d |
| Initial decline (d_i) | | 1/yr |
| b-factor | | dimensionless |
| Terminal decline (d_min) | | 1/yr |
| Abandonment rate | | Mscf/d or bbl/d |
### EUR Estimate
| Metric | Value | Units |
|--------|-------|-------|
| Hyperbolic EUR (to d_min) | | Bcf or MBbl |
| EUR to abandonment | | Bcf or MBbl |
| Switch to exponential at | | years |
### 5-Year Forecast (Annual)
| Year | Rate (Mscf/d or bbl/d) | Annual Prod | Cumulative |
|------|----------------------|-------------|------------|
| 1 | | | |
...
**Appalachian Context:** [Compare to typical Marcellus or Utica well EUR if applicable]
**Certainty:** MEDIUM — empirical fit; b-factor uncertainty dominates EUR range
**Bias:** Hyperbolic extrapolation overestimates EUR if b > 1 applied to BDF without terminal switch
Error Handling
| Condition | Cause | Action |
|---|---|---|
| b > 2 | Numerically unstable; infinite EUR | Cap at b=2; note in output |
| d_i very low (less than 0.1/yr) | May be in transient, not BDF | Note: RTA more appropriate than DCA |
| EUR exceeds 10x analogs | b too high, no terminal switch | Apply d_min = 0.05-0.10/yr terminal switch |
| Negative rates returned | Abandonment rate exceeds initial | Check q_aban vs. q_i |
Caveats
- Arps DCA assumes boundary-dominated flow. Shale wells can remain in transient (linear or bilinear flow) for years. Applying Arps to transient data gives inflated EUR. RTA/FMB is more rigorous for early-life data.
- b > 1 is theoretically invalid in BDF for single-layer reservoirs. In practice, b > 1 in shale reflects superposition of multiple transient flow regimes, not a true BDF hyperbolic. Always apply terminal switch.
- EUR ≠ Reserves. Reserves require economic conditions and regulatory definitions (SEC 5-year drilling rule for proved undeveloped).
- FMB requires average reservoir pressure estimation. Without P/Z plots or material balance, FMB is approximate.
- Decline curve fitting is non-unique: many (q_i, d_i, b) combinations can fit the same data. Report a range of EUR scenarios (P10/P50/P90).
Weekly Installs
1
Repository
jpfielding/claude.pngeFirst Seen
4 days ago
Security Audits
Installed on
amp1
cline1
opencode1
cursor1
kimi-cli1
codex1