API Reference

Summary

Primary Phase Models

THM(qi, Di, bi, bf, telf, bterm, tterm, ...)

Transient Hyperbolic Model

MH(qi, Di, bi, Dterm, validate_params)

Modified Hyperbolic Model

Hyperbolic(qi, Di, bi, validate_params)

Hyperbolic Model

GeneralizedHyperbolic(qi, Di, bi, segments, ...)

Generalized Multi-Segment Hyperbolic Model

IncliningHyperbolic(qi, Di, bi, validate_params)

Inclining Hyperbolic Model

PLE(qi, Di, Dinf, n, validate_params)

Power-Law Exponential Model

SE(qi, tau, n, validate_params)

Stretched Exponential

Duong(qi, a, m, validate_params)

Duong Model

Associated Phase Models

Secondary Phase Models

PLYield(c, m0, m, t0, min, max, validate_params)

Power-Law Associated Phase Model.

GeneralizedPLYield(c, m0, segments, min, ...)

Generalized Power-Law Associated Phase Model.

Water Phase Models

PLYield(c, m0, m, t0, min, max, validate_params)

Power-Law Associated Phase Model.

GeneralizedPLYield(c, m0, segments, min, ...)

Generalized Power-Law Associated Phase Model.

Model Functions

All Models

rate(t)

Defines the model rate function:

cum(t, **kwargs)

Defines the model cumulative volume function:

interval_vol(t[, t0])

Defines the model interval volume function:

monthly_vol(t, **kwargs)

Defines the model fixed monthly interval volume function.

monthly_vol_equiv(t[, t0])

Defines the model equivalent monthly interval volume function:

D(t)

Defines the model D-parameter function:

beta(t)

Defines the model beta-parameter function.

b(t)

Defines the model b-parameter function:

get_param_desc(name)

Get a single parameter description.

get_param_descs()

Get the parameter descriptions.

from_params(params)

Construct a model from a sequence of parameters.

Primary Phase Models

add_secondary(secondary)

Attach a secondary phase model to this primary phase model.

add_water(water)

Attach a water phase model to this primary phase model.

Associated Phase Models

Secondary Phase Models

gor(t)

Defines the model GOR function.

cgr(t)

Defines the model CGR function.

Water Phase Models

wor(t)

Defines the model WOR function.

wgr(t)

Defines the model WGR function.

Multisegment Hyperbolic Specific

time_at_rate(q)

The earliest time at which the forecast rate equals q -- the inverse of rate().

Transient Hyperbolic Specific

transient_rate(t, **kwargs)

Compute the rate function using full definition.

transient_cum(t, **kwargs)

Compute the cumulative volume function using full definition.

transient_D(t)

Compute the D-parameter function using full definition.

transient_beta(t)

Compute the beta-parameter function using full definition.

transient_b(t)

Compute the b-parameter function using full definition.

Utility Functions

bourdet(y, x[, L, xlog, ylog])

Bourdet Derivative Smoothing

get_time([start, end, n])

Get a time array to evaluate with.

get_time_monthly_vol([start, end])

Get a time array to evaluate with.

Utility Constants

DAYS_PER_MONTH

365.25 / 12 = 30.4375

DAYS_PER_YEAR

365.25

Detailed Reference

Base Classes

These classes define the basic functions that are exposed by all decline curve models.

class petbox.dca.DeclineCurve(*args: float)[source]

Base class for decline curve models. Each model must implement the defined abstract methods.

rate(t: float | Sequence[float] | ndarray[tuple[Any, ...], dtype[floating[Any]]] | ndarray[tuple[Any, ...], dtype[integer[Any]]]) ndarray[tuple[Any, ...], dtype[float64]][source]

Defines the model rate function:

\[q(t) = f(t)\]

where \(f(t)\) is defined by each model.

Parameters:

t (FloatLike) – An array of times at which to evaluate the function.

Returns:

rate

Return type:

numpy.NDFloat

cum(t: float | Sequence[float] | ndarray[tuple[Any, ...], dtype[floating[Any]]] | ndarray[tuple[Any, ...], dtype[integer[Any]]], **kwargs: Any) ndarray[tuple[Any, ...], dtype[float64]][source]

Defines the model cumulative volume function:

\[N(t) = \int_0^t q \, dt\]
Parameters:
  • t (FloatLike) – An array of times at which to evaluate the function.

  • n_grid (int) – For models evaluated by numerical integration (PLE, associated-phase yields, and SE at very small n), the number of log-spaced grid points used. Defaults to 10,000 (~1e-6 relative accuracy); must be >= 2. A smaller value (e.g. 2,000, ~5e-5) trades accuracy for speed. Ignored by models with a closed-form cumulative.

Returns:

cumulative volume

Return type:

numpy.NDFloat

interval_vol(t: float | Sequence[float] | ndarray[tuple[Any, ...], dtype[floating[Any]]] | ndarray[tuple[Any, ...], dtype[integer[Any]]], t0: float | Sequence[float] | ndarray[tuple[Any, ...], dtype[floating[Any]]] | ndarray[tuple[Any, ...], dtype[integer[Any]]] | None = None, **kwargs: Any) ndarray[tuple[Any, ...], dtype[float64]][source]

Defines the model interval volume function:

\[N(t) = \int_{t_{i-1}}^{t_i} q \, dt\]

for each element of t.

Parameters:
  • t (FloatLike) – An array of interval end times at which to evaluate the function.

  • t0 (FloatLike | None) – A start time of the first interval. If not given, the first element of t is used.

  • n_grid (int) – Number of log-spaced integration points, for models evaluated by numerical integration; see cum(). Must be >= 2.

Returns:

interval volume

Return type:

numpy.NDFloat

monthly_vol(t: float | Sequence[float] | ndarray[tuple[Any, ...], dtype[floating[Any]]] | ndarray[tuple[Any, ...], dtype[integer[Any]]], **kwargs: Any) ndarray[tuple[Any, ...], dtype[float64]][source]

Defines the model fixed monthly interval volume function. If t < 1 month, the interval begin at zero:

\[N(t) = \int_{t-{1 \, month}}^{t} q \, dt\]
Parameters:
  • t (FloatLike) – An array of interval end times at which to evaluate the function.

  • n_grid (int) – Number of log-spaced integration points, for models evaluated by numerical integration; see cum(). Must be >= 2.

Returns:

monthly equivalent volume

Return type:

numpy.NDFloat

monthly_vol_equiv(t: float | Sequence[float] | ndarray[tuple[Any, ...], dtype[floating[Any]]] | ndarray[tuple[Any, ...], dtype[integer[Any]]], t0: float | Sequence[float] | ndarray[tuple[Any, ...], dtype[floating[Any]]] | ndarray[tuple[Any, ...], dtype[integer[Any]]] | None = None, **kwargs: Any) ndarray[tuple[Any, ...], dtype[float64]][source]

Defines the model equivalent monthly interval volume function:

\[N(t) = \frac{\frac{365.25}{12}}{t-(t-1 \, month)} \int_{t-{1 \, month}}^{t} q \, dt\]
Parameters:
  • t (FloatLike) – An array of interval end times at which to evaluate the function.

  • t0 (FloatLike | None) – A start time of the first interval. If not given, assumed to be zero.

  • n_grid (int) – Number of log-spaced integration points, for models evaluated by numerical integration; see cum(). Must be >= 2.

Returns:

monthly equivalent volume

Return type:

numpy.NDFloat

D(t: float | Sequence[float] | ndarray[tuple[Any, ...], dtype[floating[Any]]] | ndarray[tuple[Any, ...], dtype[integer[Any]]]) ndarray[tuple[Any, ...], dtype[float64]][source]

Defines the model D-parameter function:

\[D(t) \equiv \frac{d}{dt}\textrm{ln} \, q \equiv \frac{1}{q}\frac{dq}{dt}\]
Parameters:

t (FloatLike) – An array of times at which to evaluate the function.

Returns:

D-parameter

Return type:

numpy.NDFloat

beta(t: float | Sequence[float] | ndarray[tuple[Any, ...], dtype[floating[Any]]] | ndarray[tuple[Any, ...], dtype[integer[Any]]]) ndarray[tuple[Any, ...], dtype[float64]][source]

Defines the model beta-parameter function.

\[\beta(t) \equiv \frac{d \, \textrm{ln} \, q}{d \, \textrm{ln} \, t} \equiv \frac{t}{q}\frac{dq}{dt} \equiv t \, D(t)\]
Parameters:

t (FloatLike) – An array of times at which to evaluate the function.

Returns:

beta-parameter

Return type:

numpy.NDFloat

b(t: float | Sequence[float] | ndarray[tuple[Any, ...], dtype[floating[Any]]] | ndarray[tuple[Any, ...], dtype[integer[Any]]]) ndarray[tuple[Any, ...], dtype[float64]][source]

Defines the model b-parameter function:

\[b(t) \equiv \frac{d}{dt}\frac{1}{D}\]
Parameters:

t (FloatLike) – An array of times at which to evaluate the function.

Returns:

b-parameter

Return type:

numpy.NDFloat

classmethod get_param_desc(name: str) ParamDesc[source]

Get a single parameter description.

Parameters:

name (str) – The parameter name.

Returns:

parameter description – A parameter description.

Return type:

ParamDesc

abstract classmethod get_param_descs() list[petbox.dca.base.ParamDesc][source]

Get the parameter descriptions.

Returns:

parameter description – A list of parameter descriptions.

Return type:

List[ParamDesc]

classmethod from_params(params: Sequence[float]) _Self[source]

Construct a model from a sequence of parameters.

Returns:

decline curve – The constructed decline curve model class.

Return type:

DeclineCurve

class petbox.dca.PrimaryPhase(*args: float)[source]

Extends DeclineCurve for a primary phase forecast. Adds the capability to link a secondary (associated) phase model.

add_secondary(secondary: SecondaryPhase) None[source]

Attach a secondary phase model to this primary phase model.

Parameters:

secondary (SecondaryPhase) – A model that inherits the SecondaryPhase class.

add_water(water: WaterPhase) None[source]

Attach a water phase model to this primary phase model.

Parameters:

water (WaterPhase) – A model that inherits the WaterPhase class.

class petbox.dca.SecondaryPhase(*args: float)[source]

Extends DeclineCurve for a secondary (associated) phase forecast. Adds the capability to link a primary phase model. Defines the gor() and cgr() functions. Each model must implement the defined abstract method.

gor(t: float | Sequence[float] | ndarray[tuple[Any, ...], dtype[floating[Any]]] | ndarray[tuple[Any, ...], dtype[integer[Any]]]) ndarray[tuple[Any, ...], dtype[float64]][source]

Defines the model GOR function. Implementation is idential to CGR function.

Parameters:

t (FloatLike) – An array of times at which to evaluate the function.

Returns:

GOR – The gas-oil ratio function in units of Mscf / Bbl.

Return type:

numpy.NDFloat

cgr(t: float | Sequence[float] | ndarray[tuple[Any, ...], dtype[floating[Any]]] | ndarray[tuple[Any, ...], dtype[integer[Any]]]) ndarray[tuple[Any, ...], dtype[float64]][source]

Defines the model CGR function. Implementation is identical to GOR function.

Parameters:

t (FloatLike) – An array of times at which to evaluate the function.

Returns:

CGR – The condensate-gas ratio in units of Bbl / Mscf.

Return type:

numpy.NDFloat

class petbox.dca.WaterPhase(*args: float)[source]

Extends DeclineCurve for a water (associated) phase forecast. Adds the capability to link a primary phase model. Defines the wor() function. Each model must implement the defined abstract method.

wor(t: float | Sequence[float] | ndarray[tuple[Any, ...], dtype[floating[Any]]] | ndarray[tuple[Any, ...], dtype[integer[Any]]]) ndarray[tuple[Any, ...], dtype[float64]][source]

Defines the model WOR function.

Parameters:

t (FloatLike) – An array of times at which to evaluate the function.

Returns:

WOR – The water-oil ratio function in units of Bbl / Bbl.

Return type:

numpy.NDFloat

wgr(t: float | Sequence[float] | ndarray[tuple[Any, ...], dtype[floating[Any]]] | ndarray[tuple[Any, ...], dtype[integer[Any]]]) ndarray[tuple[Any, ...], dtype[float64]][source]

Defines the model WGR function.

Parameters:

t (FloatLike) – An array of times at which to evaluate the function.

Returns:

WOR – The water-gas ratio function in units of Bbl / Mscf.

Return type:

numpy.NDFloat

Primary Phase Models

Implementations of primary phase decline curve models

class petbox.dca.THM(qi: float, Di: float, bi: float, bf: float, telf: float, bterm: float = 0.0, tterm: float = 0.0, validate_params: ~collections.abc.Iterable[bool] = <factory>)[source]

Transient Hyperbolic Model

Fulford, D. S., and Blasingame, T. A. 2013. Evaluation of Time-Rate Performance of Shale Wells using the Transient Hyperbolic Relation. Presented at SPE Unconventional Resources Conference - Canada in Calgary, Alberta, Canda, 5-7 November. SPE-167242-MS. https://doi.org/10.2118/167242-MS.

Analytic Approximation

Fulford, D.S. 2018. A Model-Based Diagnostic Workflow for Time-Rate Performance of Unconventional Wells. Presented at Unconventional Resources Conference in Houston, Texas, USA, 23-25 July. URTeC-2903036. https://doi.org/10.15530/urtec-2018-2903036.

Parameters:
  • qi (float) – The initial production rate in units of volume / day.

  • Di (float) –

    The initial decline rate in secant effective decline aka annual effective percent decline, i.e.

    \[D_i = 1 - \frac{q(t=1 \, year)}{qi}\]
    \[D_i = 1 - (1 + 365.25 \, D_{nom} \, b) ^ \frac{-1}{b}\]

    where Dnom is defined as \(\frac{d}{dt}\textrm{ln} \, q\) and has units of 1 / day.

    Must be positive, and large enough that the conversion to Dnom does not floor to zero. A forecast that does not decline is not a hyperbolic model: use GeneralizedHyperbolic for a flat one, or IncliningHyperbolic for a rising one.

  • bi (float) – The initial hyperbolic parameter, defined as \(\frac{d}{dt}\frac{1}{D}\). This parameter is dimensionless. Advised to always be set to 2.0 to represent transient linear flow. See literature for more details.

  • bf (float) – The final hyperbolic parameter after transition. Represents the boundary-dominated or boundary-influenced flow regime.

  • telf (float) – The time to end of linear flow in units of day, or more specifically the time at which b(t) < bi. Visual end of half slope occurs ~2.5x after telf.

  • bterm (float | None = None) –

    The terminal value of the hyperbolic parameter. Has two interpretations:

    If tterm > 0 then the terminal regime is a hyperbolic regime with b = bterm and the parameter is given as the hyperbolic parameter.

    If tterm = 0 then the terminal regime is an exponential regime with Dterm = bterm and the parameter is given as secant effective decline.

  • tterm (float | None = None) – The time to start of the terminal regime in years. Setting tterm = 0.0 creates an exponential terminal regime, while setting tterm > 0.0 creates a hyperbolic terminal regime.

rate(t: float | Sequence[float] | ndarray[tuple[Any, ...], dtype[floating[Any]]] | ndarray[tuple[Any, ...], dtype[integer[Any]]]) ndarray[tuple[Any, ...], dtype[float64]]

Defines the model rate function:

\[q(t) = f(t)\]

where \(f(t)\) is defined by each model.

Parameters:

t (FloatLike) – An array of times at which to evaluate the function.

Returns:

rate

Return type:

numpy.NDFloat

time_at_rate(q: float | Sequence[float] | ndarray[tuple[Any, ...], dtype[floating[Any]]] | ndarray[tuple[Any, ...], dtype[integer[Any]]]) ndarray[tuple[Any, ...], dtype[float64]]

The earliest time at which the forecast rate equals q – the inverse of rate().

This answers the forward question, time to an economic limit, and the backward one, how far a forecast can be extrapolated, with the same call. The pole is simply the infinite-rate limit, so it needs no separate accessor:

  • MH(1000, 0.8, 1.5).time_at_rate(inf) is -35.87798, exactly -1 / (b D), the earliest evaluable time – to within a ULP or two. rate at the returned pole is sometimes still finite, turning to inf and then nan one or two steps earlier.

  • An exponential segment (b = 0) gives -inf: it has no pole and can be backed up indefinitely.

  • An inclining segment (D < 0, b < 0) gives +inf: it has no backward pole, since an inclining rate diverges forward instead.

A t_min would therefore be a misnomer: the pole bounds whichever direction the rate grows in, and this states that without a direction-specific name.

Accuracy degrades approaching the pole, where expm1 saturates and the offset from it loses precision. Recovering a rate 9.4e6 times qi still round-trips to 1e-6 on MH(1000, 0.8, 1.5), but only 14x does on GeneralizedHyperbolic with bi = 10 – the larger the exponent, the nearer the pole a given rate multiple sits. Backing a forecast up by a plausible amount is unaffected; recovering an extreme rate multiple is not a precise operation.

Each segment is inverted only over the times it actually governs, using the same bracketing as rate(). That matters: on MH(1000, 0.8, 1.5, 0.08), whose terminal segment begins at 2884 days, rate(5000) is 32.84892, and inverting that with the initial segment’s parameters gives 5990 – wrong by 990 days. A rate above qi is extrapolated backwards off the first segment, giving a negative time.

For a model that mixes inclining and declining segments the rate is not monotonic, and a given q may occur several times. The earliest is returned, which is what backing a forecast up wants.

Parameters:

q (FloatLike) – An array of rates, in units of volume / day.

Returns:

time – Days, nan where no segment attains q.

Return type:

numpy.NDFloat

cum(t: float | Sequence[float] | ndarray[tuple[Any, ...], dtype[floating[Any]]] | ndarray[tuple[Any, ...], dtype[integer[Any]]], **kwargs: Any) ndarray[tuple[Any, ...], dtype[float64]]

Defines the model cumulative volume function:

\[N(t) = \int_0^t q \, dt\]
Parameters:
  • t (FloatLike) – An array of times at which to evaluate the function.

  • n_grid (int) – For models evaluated by numerical integration (PLE, associated-phase yields, and SE at very small n), the number of log-spaced grid points used. Defaults to 10,000 (~1e-6 relative accuracy); must be >= 2. A smaller value (e.g. 2,000, ~5e-5) trades accuracy for speed. Ignored by models with a closed-form cumulative.

Returns:

cumulative volume

Return type:

numpy.NDFloat

D(t: float | Sequence[float] | ndarray[tuple[Any, ...], dtype[floating[Any]]] | ndarray[tuple[Any, ...], dtype[integer[Any]]]) ndarray[tuple[Any, ...], dtype[float64]]

Defines the model D-parameter function:

\[D(t) \equiv \frac{d}{dt}\textrm{ln} \, q \equiv \frac{1}{q}\frac{dq}{dt}\]
Parameters:

t (FloatLike) – An array of times at which to evaluate the function.

Returns:

D-parameter

Return type:

numpy.NDFloat

beta(t: float | Sequence[float] | ndarray[tuple[Any, ...], dtype[floating[Any]]] | ndarray[tuple[Any, ...], dtype[integer[Any]]]) ndarray[tuple[Any, ...], dtype[float64]]

Defines the model beta-parameter function.

\[\beta(t) \equiv \frac{d \, \textrm{ln} \, q}{d \, \textrm{ln} \, t} \equiv \frac{t}{q}\frac{dq}{dt} \equiv t \, D(t)\]
Parameters:

t (FloatLike) – An array of times at which to evaluate the function.

Returns:

beta-parameter

Return type:

numpy.NDFloat

b(t: float | Sequence[float] | ndarray[tuple[Any, ...], dtype[floating[Any]]] | ndarray[tuple[Any, ...], dtype[integer[Any]]]) ndarray[tuple[Any, ...], dtype[float64]]

Defines the model b-parameter function:

\[b(t) \equiv \frac{d}{dt}\frac{1}{D}\]
Parameters:

t (FloatLike) – An array of times at which to evaluate the function.

Returns:

b-parameter

Return type:

numpy.NDFloat

transient_rate(t: float | Sequence[float] | ndarray[tuple[Any, ...], dtype[floating[Any]]] | ndarray[tuple[Any, ...], dtype[integer[Any]]], **kwargs: Any) ndarray[tuple[Any, ...], dtype[float64]][source]

Compute the rate function using full definition. Numerically integrates transient_D().

\[q(t) = e^{-\int_0^t D(t) \, dt}\]
Parameters:
  • t (FloatLike) – An array of time values to evaluate.

  • **kwargs – Additional keyword arguments (currently unused, reserved for future use).

Return type:

numpy.NDFloat

transient_cum(t: float | Sequence[float] | ndarray[tuple[Any, ...], dtype[floating[Any]]] | ndarray[tuple[Any, ...], dtype[integer[Any]]], **kwargs: Any) ndarray[tuple[Any, ...], dtype[float64]][source]

Compute the cumulative volume function using full definition. Numerically integrates transient_rate().

\[N(t) = \int_0^t q(t) \, dt\]
Parameters:
  • t (FloatLike) – An array of time values to evaluate.

  • **kwargs – Additional keyword arguments (currently unused, reserved for future use).

Return type:

numpy.NDFloat

transient_D(t: float | Sequence[float] | ndarray[tuple[Any, ...], dtype[floating[Any]]] | ndarray[tuple[Any, ...], dtype[integer[Any]]]) ndarray[tuple[Any, ...], dtype[float64]][source]

Compute the D-parameter function using full definition.

\[D(t) = \frac{1}{\frac{1}{Di} + b_i t + \frac{bi - bf}{c} (\textrm{Ei}[-e^{-c \, (t -t_{elf}) + e^(\gamma)}] - \textrm{Ei}[-e^{c \, t_{elf} + e^(\gamma)}])}\]
Parameters:

t (FloatLike) – An array of time values to evaluate.

Return type:

numpy.NDFloat

transient_beta(t: float | Sequence[float] | ndarray[tuple[Any, ...], dtype[floating[Any]]] | ndarray[tuple[Any, ...], dtype[integer[Any]]]) ndarray[tuple[Any, ...], dtype[float64]][source]

Compute the beta-parameter function using full definition.

\[\beta(t) = \frac{t}{\frac{1}{Di} + b_i t + \frac{bi - bf}{c} (\textrm{Ei}[-e^{-c \, (t -t_{elf}) + e^(\gamma)}] - \textrm{Ei}[-e^{c \, t_{elf} + e^(\gamma)}])}\]
Parameters:

t (FloatLike) – An array of time values to evaluate.

Return type:

numpy.NDFloat

transient_b(t: float | Sequence[float] | ndarray[tuple[Any, ...], dtype[floating[Any]]] | ndarray[tuple[Any, ...], dtype[integer[Any]]]) ndarray[tuple[Any, ...], dtype[float64]][source]

Compute the b-parameter function using full definition.

\[b(t) = b_i - (b_i - b_f) e^{-\textrm{exp}[{-c * (t - t_{elf}) + e^{\gamma}}]}\]

where:

\[\begin{split}c & = \frac{e^{\gamma}}{1.5 \, t_{elf}} \\ \gamma & = 0.57721566... \; \textrm{(Euler-Mascheroni constant)}\end{split}\]
Parameters:

t (FloatLike) – An array of time values to evaluate.

Return type:

numpy.NDFloat

class petbox.dca.MH(qi: float, Di: float, bi: float, Dterm: float = 0.0, validate_params: ~collections.abc.Iterable[bool] = <factory>)[source]

Modified Hyperbolic Model

Robertson, S. 1988. Generalized Hyperbolic Equation. Available from SPE, Richardson, Texas, USA. SPE-18731-MS.

Parameters:
  • qi (float) – The initial production rate in units of volume / day.

  • Di (float) –

    The initial decline rate in secant effective decline aka annual effective percent decline, i.e.

    \[D_i = 1 - \frac{q(t=1 \, year)}{qi}\]
    \[D_i = 1 - (1 + 365.25 \, D_{nom} \, b) ^ \frac{-1}{b}\]

    where Dnom is defined as \(\frac{d}{dt}\textrm{ln} \, q\) and has units of 1 / day.

    Must be positive, and large enough that the conversion to Dnom does not floor to zero. A forecast that does not decline is not a hyperbolic model: use GeneralizedHyperbolic for a flat one, or IncliningHyperbolic for a rising one.

  • bi (float) – The (initial) hyperbolic parameter, defined as \(\frac{d}{dt}\frac{1}{D}\). This parameter is dimensionless.

  • Dterm (float) – The terminal secant effective decline rate aka annual effective percent decline.

rate(t: float | Sequence[float] | ndarray[tuple[Any, ...], dtype[floating[Any]]] | ndarray[tuple[Any, ...], dtype[integer[Any]]]) ndarray[tuple[Any, ...], dtype[float64]]

Defines the model rate function:

\[q(t) = f(t)\]

where \(f(t)\) is defined by each model.

Parameters:

t (FloatLike) – An array of times at which to evaluate the function.

Returns:

rate

Return type:

numpy.NDFloat

time_at_rate(q: float | Sequence[float] | ndarray[tuple[Any, ...], dtype[floating[Any]]] | ndarray[tuple[Any, ...], dtype[integer[Any]]]) ndarray[tuple[Any, ...], dtype[float64]]

The earliest time at which the forecast rate equals q – the inverse of rate().

This answers the forward question, time to an economic limit, and the backward one, how far a forecast can be extrapolated, with the same call. The pole is simply the infinite-rate limit, so it needs no separate accessor:

  • MH(1000, 0.8, 1.5).time_at_rate(inf) is -35.87798, exactly -1 / (b D), the earliest evaluable time – to within a ULP or two. rate at the returned pole is sometimes still finite, turning to inf and then nan one or two steps earlier.

  • An exponential segment (b = 0) gives -inf: it has no pole and can be backed up indefinitely.

  • An inclining segment (D < 0, b < 0) gives +inf: it has no backward pole, since an inclining rate diverges forward instead.

A t_min would therefore be a misnomer: the pole bounds whichever direction the rate grows in, and this states that without a direction-specific name.

Accuracy degrades approaching the pole, where expm1 saturates and the offset from it loses precision. Recovering a rate 9.4e6 times qi still round-trips to 1e-6 on MH(1000, 0.8, 1.5), but only 14x does on GeneralizedHyperbolic with bi = 10 – the larger the exponent, the nearer the pole a given rate multiple sits. Backing a forecast up by a plausible amount is unaffected; recovering an extreme rate multiple is not a precise operation.

Each segment is inverted only over the times it actually governs, using the same bracketing as rate(). That matters: on MH(1000, 0.8, 1.5, 0.08), whose terminal segment begins at 2884 days, rate(5000) is 32.84892, and inverting that with the initial segment’s parameters gives 5990 – wrong by 990 days. A rate above qi is extrapolated backwards off the first segment, giving a negative time.

For a model that mixes inclining and declining segments the rate is not monotonic, and a given q may occur several times. The earliest is returned, which is what backing a forecast up wants.

Parameters:

q (FloatLike) – An array of rates, in units of volume / day.

Returns:

time – Days, nan where no segment attains q.

Return type:

numpy.NDFloat

cum(t: float | Sequence[float] | ndarray[tuple[Any, ...], dtype[floating[Any]]] | ndarray[tuple[Any, ...], dtype[integer[Any]]], **kwargs: Any) ndarray[tuple[Any, ...], dtype[float64]]

Defines the model cumulative volume function:

\[N(t) = \int_0^t q \, dt\]
Parameters:
  • t (FloatLike) – An array of times at which to evaluate the function.

  • n_grid (int) – For models evaluated by numerical integration (PLE, associated-phase yields, and SE at very small n), the number of log-spaced grid points used. Defaults to 10,000 (~1e-6 relative accuracy); must be >= 2. A smaller value (e.g. 2,000, ~5e-5) trades accuracy for speed. Ignored by models with a closed-form cumulative.

Returns:

cumulative volume

Return type:

numpy.NDFloat

D(t: float | Sequence[float] | ndarray[tuple[Any, ...], dtype[floating[Any]]] | ndarray[tuple[Any, ...], dtype[integer[Any]]]) ndarray[tuple[Any, ...], dtype[float64]]

Defines the model D-parameter function:

\[D(t) \equiv \frac{d}{dt}\textrm{ln} \, q \equiv \frac{1}{q}\frac{dq}{dt}\]
Parameters:

t (FloatLike) – An array of times at which to evaluate the function.

Returns:

D-parameter

Return type:

numpy.NDFloat

beta(t: float | Sequence[float] | ndarray[tuple[Any, ...], dtype[floating[Any]]] | ndarray[tuple[Any, ...], dtype[integer[Any]]]) ndarray[tuple[Any, ...], dtype[float64]]

Defines the model beta-parameter function.

\[\beta(t) \equiv \frac{d \, \textrm{ln} \, q}{d \, \textrm{ln} \, t} \equiv \frac{t}{q}\frac{dq}{dt} \equiv t \, D(t)\]
Parameters:

t (FloatLike) – An array of times at which to evaluate the function.

Returns:

beta-parameter

Return type:

numpy.NDFloat

b(t: float | Sequence[float] | ndarray[tuple[Any, ...], dtype[floating[Any]]] | ndarray[tuple[Any, ...], dtype[integer[Any]]]) ndarray[tuple[Any, ...], dtype[float64]]

Defines the model b-parameter function:

\[b(t) \equiv \frac{d}{dt}\frac{1}{D}\]
Parameters:

t (FloatLike) – An array of times at which to evaluate the function.

Returns:

b-parameter

Return type:

numpy.NDFloat

classmethod get_param_desc(name: str) ParamDesc

Get a single parameter description.

Parameters:

name (str) – The parameter name.

Returns:

parameter description – A parameter description.

Return type:

ParamDesc

classmethod get_param_descs() list[petbox.dca.base.ParamDesc][source]

Get the parameter descriptions.

Returns:

parameter description – A list of parameter descriptions.

Return type:

List[ParamDesc]

classmethod from_params(params: Sequence[float]) _Self

Construct a model from a sequence of parameters.

Returns:

decline curve – The constructed decline curve model class.

Return type:

DeclineCurve

class petbox.dca.Hyperbolic(qi: float, Di: float, bi: float, validate_params: ~collections.abc.Iterable[bool] = <factory>)[source]

Hyperbolic Model

Arps, J. J. 1945. Analysis of Decline Curves. Transactions of the AIME 160 (1): 228-247. SPE-945228-G. https://doi.org/10.2118/945228-G

A single Arps hyperbolic segment, declining for all time:

\[q(t) = q_i \, (1 + b_i \, D_{nom} \, t) ^ \frac{-1}{b_i}\]

where \(D_{nom}\) is the nominal per-day decline the model stores, not the secant Di it is constructed from; the two are related by the definition of Di below.

This is the plain relation, with no terminal segment and no segments after the first. It is the same forecast as MH(qi, Di, bi) – a MH given no terminal decline is a hyperbolic, and the two are bit-for-bit identical – but it says so in its type and will not accept a Dterm. Reach for it when the forecast is a plain hyperbolic and you want that visible at the call site rather than implied by an omitted argument.

Because the decline is never capped, the rate falls for all time rather than flattening onto a terminal exponential. Whether that leaves an EUR depends on bi: the cumulative volume converges to \(q_i / ((1 - b_i) \, D_{nom})\) for bi < 1 – 295,493.457 for Hyperbolic(1000, 0.8, 0.5) – and diverges for bi >= 1, where the integral of the tail does not converge. Against an MH given a terminal decline, the volumes are equal up to that model’s terminal time and this one is larger past it: for bi = 1.5 against MH(1000, 0.8, 1.5, 0.08), whose terminal segment begins at 2884.43 days, both recover 358,827.905 there, and by 30 years it is 617,999 against 555,128 and still widening. Use MH where the tail has to terminate, or time_at_rate() to find the economic limit that bounds it.

The rest of the family:

  • MH is this model plus a terminal exponential segment, appended once the decline falls to Dterm.

  • THM interpolates the exponent from bi down to bf across a transient-to-boundary transition, and optionally terminates.

  • GeneralizedHyperbolic takes an arbitrary number of segments, which may decline, incline, or be flat, and accepts an unbounded exponent. It is the most general of the five, and a superset of MH.

  • IncliningHyperbolic is the mirror of this model: a single rising segment.

Parameters:
  • qi (float) – The initial production rate in units of volume / day.

  • Di (float) –

    The initial decline rate in secant effective decline aka annual effective percent decline, i.e.

    \[D_i = 1 - \frac{q(t=1 \, year)}{qi}\]

    Must be positive, and large enough that the conversion to Dnom does not floor to zero. A forecast that does not decline is not a hyperbolic model: use GeneralizedHyperbolic for a flat one, or IncliningHyperbolic for a rising one.

  • bi (float) – The hyperbolic parameter, defined as \(\frac{d}{dt}\frac{1}{D}\). This parameter is dimensionless. Bounded to [0, 2] as in MH and THM; 0 is the exponential limit of the relation and 2 the transient-linear-flow limit. GeneralizedHyperbolic is the model that accepts an unbounded exponent.

rate(t: float | Sequence[float] | ndarray[tuple[Any, ...], dtype[floating[Any]]] | ndarray[tuple[Any, ...], dtype[integer[Any]]]) ndarray[tuple[Any, ...], dtype[float64]]

Defines the model rate function:

\[q(t) = f(t)\]

where \(f(t)\) is defined by each model.

Parameters:

t (FloatLike) – An array of times at which to evaluate the function.

Returns:

rate

Return type:

numpy.NDFloat

time_at_rate(q: float | Sequence[float] | ndarray[tuple[Any, ...], dtype[floating[Any]]] | ndarray[tuple[Any, ...], dtype[integer[Any]]]) ndarray[tuple[Any, ...], dtype[float64]]

The earliest time at which the forecast rate equals q – the inverse of rate().

This answers the forward question, time to an economic limit, and the backward one, how far a forecast can be extrapolated, with the same call. The pole is simply the infinite-rate limit, so it needs no separate accessor:

  • MH(1000, 0.8, 1.5).time_at_rate(inf) is -35.87798, exactly -1 / (b D), the earliest evaluable time – to within a ULP or two. rate at the returned pole is sometimes still finite, turning to inf and then nan one or two steps earlier.

  • An exponential segment (b = 0) gives -inf: it has no pole and can be backed up indefinitely.

  • An inclining segment (D < 0, b < 0) gives +inf: it has no backward pole, since an inclining rate diverges forward instead.

A t_min would therefore be a misnomer: the pole bounds whichever direction the rate grows in, and this states that without a direction-specific name.

Accuracy degrades approaching the pole, where expm1 saturates and the offset from it loses precision. Recovering a rate 9.4e6 times qi still round-trips to 1e-6 on MH(1000, 0.8, 1.5), but only 14x does on GeneralizedHyperbolic with bi = 10 – the larger the exponent, the nearer the pole a given rate multiple sits. Backing a forecast up by a plausible amount is unaffected; recovering an extreme rate multiple is not a precise operation.

Each segment is inverted only over the times it actually governs, using the same bracketing as rate(). That matters: on MH(1000, 0.8, 1.5, 0.08), whose terminal segment begins at 2884 days, rate(5000) is 32.84892, and inverting that with the initial segment’s parameters gives 5990 – wrong by 990 days. A rate above qi is extrapolated backwards off the first segment, giving a negative time.

For a model that mixes inclining and declining segments the rate is not monotonic, and a given q may occur several times. The earliest is returned, which is what backing a forecast up wants.

Parameters:

q (FloatLike) – An array of rates, in units of volume / day.

Returns:

time – Days, nan where no segment attains q.

Return type:

numpy.NDFloat

cum(t: float | Sequence[float] | ndarray[tuple[Any, ...], dtype[floating[Any]]] | ndarray[tuple[Any, ...], dtype[integer[Any]]], **kwargs: Any) ndarray[tuple[Any, ...], dtype[float64]]

Defines the model cumulative volume function:

\[N(t) = \int_0^t q \, dt\]
Parameters:
  • t (FloatLike) – An array of times at which to evaluate the function.

  • n_grid (int) – For models evaluated by numerical integration (PLE, associated-phase yields, and SE at very small n), the number of log-spaced grid points used. Defaults to 10,000 (~1e-6 relative accuracy); must be >= 2. A smaller value (e.g. 2,000, ~5e-5) trades accuracy for speed. Ignored by models with a closed-form cumulative.

Returns:

cumulative volume

Return type:

numpy.NDFloat

D(t: float | Sequence[float] | ndarray[tuple[Any, ...], dtype[floating[Any]]] | ndarray[tuple[Any, ...], dtype[integer[Any]]]) ndarray[tuple[Any, ...], dtype[float64]]

Defines the model D-parameter function:

\[D(t) \equiv \frac{d}{dt}\textrm{ln} \, q \equiv \frac{1}{q}\frac{dq}{dt}\]
Parameters:

t (FloatLike) – An array of times at which to evaluate the function.

Returns:

D-parameter

Return type:

numpy.NDFloat

beta(t: float | Sequence[float] | ndarray[tuple[Any, ...], dtype[floating[Any]]] | ndarray[tuple[Any, ...], dtype[integer[Any]]]) ndarray[tuple[Any, ...], dtype[float64]]

Defines the model beta-parameter function.

\[\beta(t) \equiv \frac{d \, \textrm{ln} \, q}{d \, \textrm{ln} \, t} \equiv \frac{t}{q}\frac{dq}{dt} \equiv t \, D(t)\]
Parameters:

t (FloatLike) – An array of times at which to evaluate the function.

Returns:

beta-parameter

Return type:

numpy.NDFloat

b(t: float | Sequence[float] | ndarray[tuple[Any, ...], dtype[floating[Any]]] | ndarray[tuple[Any, ...], dtype[integer[Any]]]) ndarray[tuple[Any, ...], dtype[float64]]

Defines the model b-parameter function:

\[b(t) \equiv \frac{d}{dt}\frac{1}{D}\]
Parameters:

t (FloatLike) – An array of times at which to evaluate the function.

Returns:

b-parameter

Return type:

numpy.NDFloat

classmethod get_param_desc(name: str) ParamDesc

Get a single parameter description.

Parameters:

name (str) – The parameter name.

Returns:

parameter description – A parameter description.

Return type:

ParamDesc

classmethod get_param_descs() list[petbox.dca.base.ParamDesc][source]

Get the parameter descriptions.

Returns:

parameter description – A list of parameter descriptions.

Return type:

List[ParamDesc]

classmethod from_params(params: Sequence[float]) _Self

Construct a model from a sequence of parameters.

Returns:

decline curve – The constructed decline curve model class.

Return type:

DeclineCurve

class petbox.dca.GeneralizedHyperbolic(qi: float, Di: float, bi: float, segments: ~collections.abc.Sequence[~petbox.dca.primary.HyperbolicSegment] = (), Dterm: float = 0.0, validate_params: ~collections.abc.Iterable[bool] = <factory>)[source]

Generalized Multi-Segment Hyperbolic Model

Implementation of a hyperbolic model with an arbitrary number of caller-specified segments. Each segment is an Arps hyperbolic with its own exponent, and by default is continuous in rate and decline with the one before it:

\[q(t) = q_i \, (1 + b \, D_i \, (t - t_i)) ^ \frac{-1}{b}\]

A segment may instead override its rate or its decline, which is how a restimulation or a prescribed decline is expressed. Cumulative volume is always continuous.

With an empty segment list this model is exactly MH, including the terminal exponential segment. Unlike MH, a Dterm steeper than the last segment’s decline is clamped rather than rejected – see the note under Dterm.

The purpose of this model is to let a caller express any series of Arps-style segments that is physically meaningful, so it rejects only what is not:

  • A negative rate, which no forecast has.

  • A decline of 100% per year or more, which consumes the whole rate within the year. An arbitrarily steep incline is permitted: D = -1 doubles the rate over a year and D = -9 is a tenfold rise, both of which a well can do after a restimulation.

  • A segment whose D and b disagree in sign. A segment either declines (D > 0, b >= 0) or inclines (D < 0, b <= 0), and a flat segment (D == 0) must have b == 0. See _validate_decline_signs().

Everything else is allowed. b is bounded only by finiteness – THM enforces bi >= bf >= bterm because its segments model one specific transient-to-boundary transition, and this model makes no such claim, so neither the magnitude of b nor its monotonicity between segments is constrained. An exponent that increases between segments is a restimulation.

Parameters:
  • qi (float) – The initial production rate in units of volume / day.

  • Di (float) –

    The initial decline rate in secant effective decline aka annual effective percent decline, i.e.

    \[D_i = 1 - \frac{q(t=1 \, year)}{qi}\]

    Negative to incline, zero for a flat forecast. Must be less than 1.

  • bi (float) – The initial hyperbolic parameter, defined as \(\frac{d}{dt}\frac{1}{D}\). This parameter is dimensionless. It must agree in sign with Di, and must be zero when Di is.

  • segments (Sequence[HyperbolicSegment] = ()) – The segments after the initial one, in strictly increasing time order. Each is a HyperbolicSegment; use from_segments() to build them from plain tuples. An empty sequence reduces this model to MH.

  • Dterm (float = 0.0) –

    The terminal secant effective decline rate aka annual effective percent decline. The terminal exponential segment begins where the last segment’s decline reaches it. If it has already been reached before that segment begins, the terminal segment is pulled forward to the segment’s own start time rather than raising – MH raises Di < Dterm instead, so the two models agree only over the range MH accepts.

    A terminal decline caps a hyperbolic tail, whose decline falls with time until it reaches Dterm. A last segment that is already exponential, flat, or inclining has no such crossing – its decline is constant or rising – so Dterm cannot be applied and is ignored, with a RuntimeWarning saying so. Note that for a flat tail this means the forecast produces volume forever.

rate(t: float | Sequence[float] | ndarray[tuple[Any, ...], dtype[floating[Any]]] | ndarray[tuple[Any, ...], dtype[integer[Any]]]) ndarray[tuple[Any, ...], dtype[float64]]

Defines the model rate function:

\[q(t) = f(t)\]

where \(f(t)\) is defined by each model.

Parameters:

t (FloatLike) – An array of times at which to evaluate the function.

Returns:

rate

Return type:

numpy.NDFloat

time_at_rate(q: float | Sequence[float] | ndarray[tuple[Any, ...], dtype[floating[Any]]] | ndarray[tuple[Any, ...], dtype[integer[Any]]]) ndarray[tuple[Any, ...], dtype[float64]]

The earliest time at which the forecast rate equals q – the inverse of rate().

This answers the forward question, time to an economic limit, and the backward one, how far a forecast can be extrapolated, with the same call. The pole is simply the infinite-rate limit, so it needs no separate accessor:

  • MH(1000, 0.8, 1.5).time_at_rate(inf) is -35.87798, exactly -1 / (b D), the earliest evaluable time – to within a ULP or two. rate at the returned pole is sometimes still finite, turning to inf and then nan one or two steps earlier.

  • An exponential segment (b = 0) gives -inf: it has no pole and can be backed up indefinitely.

  • An inclining segment (D < 0, b < 0) gives +inf: it has no backward pole, since an inclining rate diverges forward instead.

A t_min would therefore be a misnomer: the pole bounds whichever direction the rate grows in, and this states that without a direction-specific name.

Accuracy degrades approaching the pole, where expm1 saturates and the offset from it loses precision. Recovering a rate 9.4e6 times qi still round-trips to 1e-6 on MH(1000, 0.8, 1.5), but only 14x does on GeneralizedHyperbolic with bi = 10 – the larger the exponent, the nearer the pole a given rate multiple sits. Backing a forecast up by a plausible amount is unaffected; recovering an extreme rate multiple is not a precise operation.

Each segment is inverted only over the times it actually governs, using the same bracketing as rate(). That matters: on MH(1000, 0.8, 1.5, 0.08), whose terminal segment begins at 2884 days, rate(5000) is 32.84892, and inverting that with the initial segment’s parameters gives 5990 – wrong by 990 days. A rate above qi is extrapolated backwards off the first segment, giving a negative time.

For a model that mixes inclining and declining segments the rate is not monotonic, and a given q may occur several times. The earliest is returned, which is what backing a forecast up wants.

Parameters:

q (FloatLike) – An array of rates, in units of volume / day.

Returns:

time – Days, nan where no segment attains q.

Return type:

numpy.NDFloat

cum(t: float | Sequence[float] | ndarray[tuple[Any, ...], dtype[floating[Any]]] | ndarray[tuple[Any, ...], dtype[integer[Any]]], **kwargs: Any) ndarray[tuple[Any, ...], dtype[float64]]

Defines the model cumulative volume function:

\[N(t) = \int_0^t q \, dt\]
Parameters:
  • t (FloatLike) – An array of times at which to evaluate the function.

  • n_grid (int) – For models evaluated by numerical integration (PLE, associated-phase yields, and SE at very small n), the number of log-spaced grid points used. Defaults to 10,000 (~1e-6 relative accuracy); must be >= 2. A smaller value (e.g. 2,000, ~5e-5) trades accuracy for speed. Ignored by models with a closed-form cumulative.

Returns:

cumulative volume

Return type:

numpy.NDFloat

D(t: float | Sequence[float] | ndarray[tuple[Any, ...], dtype[floating[Any]]] | ndarray[tuple[Any, ...], dtype[integer[Any]]]) ndarray[tuple[Any, ...], dtype[float64]]

Defines the model D-parameter function:

\[D(t) \equiv \frac{d}{dt}\textrm{ln} \, q \equiv \frac{1}{q}\frac{dq}{dt}\]
Parameters:

t (FloatLike) – An array of times at which to evaluate the function.

Returns:

D-parameter

Return type:

numpy.NDFloat

beta(t: float | Sequence[float] | ndarray[tuple[Any, ...], dtype[floating[Any]]] | ndarray[tuple[Any, ...], dtype[integer[Any]]]) ndarray[tuple[Any, ...], dtype[float64]]

Defines the model beta-parameter function.

\[\beta(t) \equiv \frac{d \, \textrm{ln} \, q}{d \, \textrm{ln} \, t} \equiv \frac{t}{q}\frac{dq}{dt} \equiv t \, D(t)\]
Parameters:

t (FloatLike) – An array of times at which to evaluate the function.

Returns:

beta-parameter

Return type:

numpy.NDFloat

b(t: float | Sequence[float] | ndarray[tuple[Any, ...], dtype[floating[Any]]] | ndarray[tuple[Any, ...], dtype[integer[Any]]]) ndarray[tuple[Any, ...], dtype[float64]]

Defines the model b-parameter function:

\[b(t) \equiv \frac{d}{dt}\frac{1}{D}\]
Parameters:

t (FloatLike) – An array of times at which to evaluate the function.

Returns:

b-parameter

Return type:

numpy.NDFloat

classmethod get_param_desc(name: str) ParamDesc

Get a single parameter description.

Parameters:

name (str) – The parameter name.

Returns:

parameter description – A parameter description.

Return type:

ParamDesc

classmethod get_param_descs() list[petbox.dca.base.ParamDesc][source]

Get the parameter descriptions.

Returns:

parameter description – A list of parameter descriptions.

Return type:

List[ParamDesc]

classmethod from_params(params: Sequence[float]) _Self

Construct a model from a sequence of parameters.

Returns:

decline curve – The constructed decline curve model class.

Return type:

DeclineCurve

classmethod from_segments(qi: float, Di: float, bi: float, segments: Iterable[Sequence[float | None]], Dterm: float = 0.0) GeneralizedHyperbolic[source]

Construct from plain tuples instead of HyperbolicSegment instances.

Each entry is (t, b), (t, D, b) or (t, q, D, b). The constructor itself accepts only HyperbolicSegment, which keeps the field type free of unions – this is the loose-tuple entry point.

Parameters:
Returns:

model

Return type:

GeneralizedHyperbolic

class petbox.dca.HyperbolicSegment(t: float, *, q: float | None = None, D: float | None = None, b: float | None = None)[source]

One segment of a GeneralizedHyperbolic forecast.

None means “continuous from the previous segment”: an omitted b continues the preceding exponent, an omitted D leaves the decline continuous at t, and an omitted q leaves the rate continuous. Supplying q steps the rate to that value at t – a restimulation, say – and supplying D prescribes the decline there.

Cumulative volume is never overridable. It is always inherited, because production already recovered cannot change when the rate does.

The optional fields are keyword-only on purpose. Positionally, HyperbolicSegment(365.0, 0.3) would set q, while the equivalent builder tuple (365.0, 0.3) means b – the same two values meaning different things depending on which entry point was used.

Parameters:
  • t (float) – The segment start time in days. Must be finite and positive; a segment at t = 0 is rejected, since the model’s own initial conditions start there.

  • q (float | None = None) – The rate at t, in units of volume / day. None leaves the rate continuous. Must be finite and positive when given.

  • D (float | None = None) – The decline at t in secant effective decline, i.e. annual effective percent decline, matching Di and Dterm. None leaves the decline continuous. Negative to incline, zero for a flat segment. Must be finite and less than 1 when given, and must agree in sign with the segment’s resolved b.

  • b (float | None = None) – The hyperbolic exponent from t onward. None continues the previous exponent. Must be finite when given; it is otherwise unbounded. It must agree in sign with the segment’s D, and must be zero where that D is zero – including where either is inherited from the preceding segment.

classmethod from_tuple(spec: Sequence[float | None]) HyperbolicSegment[source]

Build one segment from a loose tuple. Arity selects the meaning, following one rule: the shape parameter is always last, and short forms omit the level. (t, b) inherits both rate and decline, (t, D, b) inherits the rate, and (t, q, D, b) is fully specified.

An explicit None inherits exactly as a short form does, so (t, None, D, b) is (t, D, b) and (t, None, None, b) is (t, b).

t is the one field with no inherit semantics – there is no previous segment to continue a start time from – so it is required.

Parameters:

spec (Sequence[float | None]) – A (t, b), (t, D, b) or (t, q, D, b) tuple.

Returns:

segment

Return type:

HyperbolicSegment

class petbox.dca.IncliningHyperbolic(qi: float, Di: float, bi: float, validate_params: ~collections.abc.Iterable[bool] = <factory>)[source]

Inclining Hyperbolic Model

Arps, J. J. 1945. Analysis of Decline Curves. Transactions of the AIME 160 (1): 228-247. SPE-945228-G. https://doi.org/10.2118/945228-G

The hyperbolic relation is Arps’; applying it with a negative decline to describe a build-up is an empirical extension, not from that paper.

An Arps hyperbolic run in reverse: the decline and the exponent are both negative, so the rate rises with time,

\[q(t) = q_i \, (1 + b_i \, D_i \, t) ^ \frac{-1}{b_i}\]

With D_i < 0 and b_i < 0 the product b_i D_i is positive and the exponent -1/b_i is positive, so the base grows and the power is taken in the same direction – a power-law build-up rather than a decay. It models a period of increasing rate: a well cleaning up after completion, ramping onto compression, or recovering after an offset frac hit.

This model is the pure build-up and has no terminal decline. A rising rate never reaches a terminal decline, so there is nothing for a Dterm to cap. Consequently both the rate and the cumulative volume are unbounded as t grows: it is a model of one period, not of a whole well’s life, and it has no EUR on its own. To incline and then decline – the physical case – use GeneralizedHyperbolic, whose segments accept both signs:

GeneralizedHyperbolic.from_segments(qi, Di, bi, [(t_peak, D_decline, b_decline)])

IncliningHyperbolic(qi, Di, bi) is exactly GeneralizedHyperbolic(qi, Di, bi, ()) – this model is the named, bound-checked case of it, the mirror of what MH is for a declining forecast.

Parameters:
  • qi (float) – The initial production rate in units of volume / day.

  • Di (float) –

    The initial decline rate in secant effective decline aka annual effective percent decline, i.e.

    \[D_i = 1 - \frac{q(t=1 \, year)}{qi}\]

    Must be negative, which is what makes the rate rise: Di = -0.5 is a 1.5x rate after one year and Di = -9 a tenfold rise. There is no lower bound.

  • bi (float) – The hyperbolic parameter, defined as \(\frac{d}{dt}\frac{1}{D}\). This parameter is dimensionless. Must be negative, matching Di; a mixed pair would drive the decline through zero rather than describing a build-up. It is otherwise unbounded.

rate(t: float | Sequence[float] | ndarray[tuple[Any, ...], dtype[floating[Any]]] | ndarray[tuple[Any, ...], dtype[integer[Any]]]) ndarray[tuple[Any, ...], dtype[float64]]

Defines the model rate function:

\[q(t) = f(t)\]

where \(f(t)\) is defined by each model.

Parameters:

t (FloatLike) – An array of times at which to evaluate the function.

Returns:

rate

Return type:

numpy.NDFloat

time_at_rate(q: float | Sequence[float] | ndarray[tuple[Any, ...], dtype[floating[Any]]] | ndarray[tuple[Any, ...], dtype[integer[Any]]]) ndarray[tuple[Any, ...], dtype[float64]]

The earliest time at which the forecast rate equals q – the inverse of rate().

This answers the forward question, time to an economic limit, and the backward one, how far a forecast can be extrapolated, with the same call. The pole is simply the infinite-rate limit, so it needs no separate accessor:

  • MH(1000, 0.8, 1.5).time_at_rate(inf) is -35.87798, exactly -1 / (b D), the earliest evaluable time – to within a ULP or two. rate at the returned pole is sometimes still finite, turning to inf and then nan one or two steps earlier.

  • An exponential segment (b = 0) gives -inf: it has no pole and can be backed up indefinitely.

  • An inclining segment (D < 0, b < 0) gives +inf: it has no backward pole, since an inclining rate diverges forward instead.

A t_min would therefore be a misnomer: the pole bounds whichever direction the rate grows in, and this states that without a direction-specific name.

Accuracy degrades approaching the pole, where expm1 saturates and the offset from it loses precision. Recovering a rate 9.4e6 times qi still round-trips to 1e-6 on MH(1000, 0.8, 1.5), but only 14x does on GeneralizedHyperbolic with bi = 10 – the larger the exponent, the nearer the pole a given rate multiple sits. Backing a forecast up by a plausible amount is unaffected; recovering an extreme rate multiple is not a precise operation.

Each segment is inverted only over the times it actually governs, using the same bracketing as rate(). That matters: on MH(1000, 0.8, 1.5, 0.08), whose terminal segment begins at 2884 days, rate(5000) is 32.84892, and inverting that with the initial segment’s parameters gives 5990 – wrong by 990 days. A rate above qi is extrapolated backwards off the first segment, giving a negative time.

For a model that mixes inclining and declining segments the rate is not monotonic, and a given q may occur several times. The earliest is returned, which is what backing a forecast up wants.

Parameters:

q (FloatLike) – An array of rates, in units of volume / day.

Returns:

time – Days, nan where no segment attains q.

Return type:

numpy.NDFloat

cum(t: float | Sequence[float] | ndarray[tuple[Any, ...], dtype[floating[Any]]] | ndarray[tuple[Any, ...], dtype[integer[Any]]], **kwargs: Any) ndarray[tuple[Any, ...], dtype[float64]]

Defines the model cumulative volume function:

\[N(t) = \int_0^t q \, dt\]
Parameters:
  • t (FloatLike) – An array of times at which to evaluate the function.

  • n_grid (int) – For models evaluated by numerical integration (PLE, associated-phase yields, and SE at very small n), the number of log-spaced grid points used. Defaults to 10,000 (~1e-6 relative accuracy); must be >= 2. A smaller value (e.g. 2,000, ~5e-5) trades accuracy for speed. Ignored by models with a closed-form cumulative.

Returns:

cumulative volume

Return type:

numpy.NDFloat

D(t: float | Sequence[float] | ndarray[tuple[Any, ...], dtype[floating[Any]]] | ndarray[tuple[Any, ...], dtype[integer[Any]]]) ndarray[tuple[Any, ...], dtype[float64]]

Defines the model D-parameter function:

\[D(t) \equiv \frac{d}{dt}\textrm{ln} \, q \equiv \frac{1}{q}\frac{dq}{dt}\]
Parameters:

t (FloatLike) – An array of times at which to evaluate the function.

Returns:

D-parameter

Return type:

numpy.NDFloat

beta(t: float | Sequence[float] | ndarray[tuple[Any, ...], dtype[floating[Any]]] | ndarray[tuple[Any, ...], dtype[integer[Any]]]) ndarray[tuple[Any, ...], dtype[float64]]

Defines the model beta-parameter function.

\[\beta(t) \equiv \frac{d \, \textrm{ln} \, q}{d \, \textrm{ln} \, t} \equiv \frac{t}{q}\frac{dq}{dt} \equiv t \, D(t)\]
Parameters:

t (FloatLike) – An array of times at which to evaluate the function.

Returns:

beta-parameter

Return type:

numpy.NDFloat

b(t: float | Sequence[float] | ndarray[tuple[Any, ...], dtype[floating[Any]]] | ndarray[tuple[Any, ...], dtype[integer[Any]]]) ndarray[tuple[Any, ...], dtype[float64]]

Defines the model b-parameter function:

\[b(t) \equiv \frac{d}{dt}\frac{1}{D}\]
Parameters:

t (FloatLike) – An array of times at which to evaluate the function.

Returns:

b-parameter

Return type:

numpy.NDFloat

classmethod get_param_desc(name: str) ParamDesc

Get a single parameter description.

Parameters:

name (str) – The parameter name.

Returns:

parameter description – A parameter description.

Return type:

ParamDesc

classmethod get_param_descs() list[petbox.dca.base.ParamDesc][source]

Get the parameter descriptions.

Returns:

parameter description – A list of parameter descriptions.

Return type:

List[ParamDesc]

classmethod from_params(params: Sequence[float]) _Self

Construct a model from a sequence of parameters.

Returns:

decline curve – The constructed decline curve model class.

Return type:

DeclineCurve

class petbox.dca.PLE(qi: float, Di: float, Dinf: float, n: float, validate_params: ~collections.abc.Iterable[bool] = <factory>)[source]

Power-Law Exponential Model

Ilk, D., Perego, A. D., Rushing, J. A., and Blasingame, T. A. 2008. Exponential vs. Hyperbolic Decline in Tight Gas Sands - Understanding the Origin and Implications for Reserve Estimates Using Arps Decline Curves. Presented at SPE Annual Technical Conference and Exhibition in Denver, Colorado, USA, 21-24 September. SPE-116731-MS. https://doi.org/10.2118/116731-MS.

Ilk, D., Rushing, J. A., and Blasingame, T. A. 2009. Decline Curve Analysis for HP/HT Gas Wells: Theory and Applications. Presented at SPE Annual Technical Conference and Exhibition in New Orleands, Louisiana, USA, 4-7 October. SPE-125031-MS. https://doi.org/10.2118/125031-MS.

Parameters:
  • qi (float) – The initial production rate in units of volume / day.

  • Di (float) – The initial decline rate in nominal decline rate defined as d[ln q] / dt and has units of 1 / day.

  • Dterm (float) – The terminal decline rate in nominal decline rate, has units of 1 / day.

  • n (float) – The n exponent.

rate(t: float | Sequence[float] | ndarray[tuple[Any, ...], dtype[floating[Any]]] | ndarray[tuple[Any, ...], dtype[integer[Any]]]) ndarray[tuple[Any, ...], dtype[float64]]

Defines the model rate function:

\[q(t) = f(t)\]

where \(f(t)\) is defined by each model.

Parameters:

t (FloatLike) – An array of times at which to evaluate the function.

Returns:

rate

Return type:

numpy.NDFloat

cum(t: float | Sequence[float] | ndarray[tuple[Any, ...], dtype[floating[Any]]] | ndarray[tuple[Any, ...], dtype[integer[Any]]], **kwargs: Any) ndarray[tuple[Any, ...], dtype[float64]]

Defines the model cumulative volume function:

\[N(t) = \int_0^t q \, dt\]
Parameters:
  • t (FloatLike) – An array of times at which to evaluate the function.

  • n_grid (int) – For models evaluated by numerical integration (PLE, associated-phase yields, and SE at very small n), the number of log-spaced grid points used. Defaults to 10,000 (~1e-6 relative accuracy); must be >= 2. A smaller value (e.g. 2,000, ~5e-5) trades accuracy for speed. Ignored by models with a closed-form cumulative.

Returns:

cumulative volume

Return type:

numpy.NDFloat

D(t: float | Sequence[float] | ndarray[tuple[Any, ...], dtype[floating[Any]]] | ndarray[tuple[Any, ...], dtype[integer[Any]]]) ndarray[tuple[Any, ...], dtype[float64]]

Defines the model D-parameter function:

\[D(t) \equiv \frac{d}{dt}\textrm{ln} \, q \equiv \frac{1}{q}\frac{dq}{dt}\]
Parameters:

t (FloatLike) – An array of times at which to evaluate the function.

Returns:

D-parameter

Return type:

numpy.NDFloat

beta(t: float | Sequence[float] | ndarray[tuple[Any, ...], dtype[floating[Any]]] | ndarray[tuple[Any, ...], dtype[integer[Any]]]) ndarray[tuple[Any, ...], dtype[float64]]

Defines the model beta-parameter function.

\[\beta(t) \equiv \frac{d \, \textrm{ln} \, q}{d \, \textrm{ln} \, t} \equiv \frac{t}{q}\frac{dq}{dt} \equiv t \, D(t)\]
Parameters:

t (FloatLike) – An array of times at which to evaluate the function.

Returns:

beta-parameter

Return type:

numpy.NDFloat

b(t: float | Sequence[float] | ndarray[tuple[Any, ...], dtype[floating[Any]]] | ndarray[tuple[Any, ...], dtype[integer[Any]]]) ndarray[tuple[Any, ...], dtype[float64]]

Defines the model b-parameter function:

\[b(t) \equiv \frac{d}{dt}\frac{1}{D}\]
Parameters:

t (FloatLike) – An array of times at which to evaluate the function.

Returns:

b-parameter

Return type:

numpy.NDFloat

classmethod get_param_desc(name: str) ParamDesc

Get a single parameter description.

Parameters:

name (str) – The parameter name.

Returns:

parameter description – A parameter description.

Return type:

ParamDesc

classmethod get_param_descs() list[petbox.dca.base.ParamDesc][source]

Get the parameter descriptions.

Returns:

parameter description – A list of parameter descriptions.

Return type:

List[ParamDesc]

classmethod from_params(params: Sequence[float]) _Self

Construct a model from a sequence of parameters.

Returns:

decline curve – The constructed decline curve model class.

Return type:

DeclineCurve

class petbox.dca.SE(qi: float, tau: float, n: float, validate_params: ~collections.abc.Iterable[bool] = <factory>)[source]

Stretched Exponential

Valkó, P. P. Assigning Value to Stimulation in the Barnett Shale: A Simultaneous Analysis of 7000 Plus Production Histories and Well Completion Records. 2009. Presented at SPE Hydraulic Fracturing Technology Conference in College Station, Texas, USA, 19-21 January. SPE-119369-MS. https://doi.org/10.2118/119369-MS.

Parameters:
  • qi (float) – The initial production rate in units of volume / day.

  • tau (float) –

    The tau parameter in units of day ** n. Equivalent to:

    \[\tau = D^n\]

  • n (float) – The n exponent.

rate(t: float | Sequence[float] | ndarray[tuple[Any, ...], dtype[floating[Any]]] | ndarray[tuple[Any, ...], dtype[integer[Any]]]) ndarray[tuple[Any, ...], dtype[float64]]

Defines the model rate function:

\[q(t) = f(t)\]

where \(f(t)\) is defined by each model.

Parameters:

t (FloatLike) – An array of times at which to evaluate the function.

Returns:

rate

Return type:

numpy.NDFloat

cum(t: float | Sequence[float] | ndarray[tuple[Any, ...], dtype[floating[Any]]] | ndarray[tuple[Any, ...], dtype[integer[Any]]], **kwargs: Any) ndarray[tuple[Any, ...], dtype[float64]]

Defines the model cumulative volume function:

\[N(t) = \int_0^t q \, dt\]
Parameters:
  • t (FloatLike) – An array of times at which to evaluate the function.

  • n_grid (int) – For models evaluated by numerical integration (PLE, associated-phase yields, and SE at very small n), the number of log-spaced grid points used. Defaults to 10,000 (~1e-6 relative accuracy); must be >= 2. A smaller value (e.g. 2,000, ~5e-5) trades accuracy for speed. Ignored by models with a closed-form cumulative.

Returns:

cumulative volume

Return type:

numpy.NDFloat

D(t: float | Sequence[float] | ndarray[tuple[Any, ...], dtype[floating[Any]]] | ndarray[tuple[Any, ...], dtype[integer[Any]]]) ndarray[tuple[Any, ...], dtype[float64]]

Defines the model D-parameter function:

\[D(t) \equiv \frac{d}{dt}\textrm{ln} \, q \equiv \frac{1}{q}\frac{dq}{dt}\]
Parameters:

t (FloatLike) – An array of times at which to evaluate the function.

Returns:

D-parameter

Return type:

numpy.NDFloat

beta(t: float | Sequence[float] | ndarray[tuple[Any, ...], dtype[floating[Any]]] | ndarray[tuple[Any, ...], dtype[integer[Any]]]) ndarray[tuple[Any, ...], dtype[float64]]

Defines the model beta-parameter function.

\[\beta(t) \equiv \frac{d \, \textrm{ln} \, q}{d \, \textrm{ln} \, t} \equiv \frac{t}{q}\frac{dq}{dt} \equiv t \, D(t)\]
Parameters:

t (FloatLike) – An array of times at which to evaluate the function.

Returns:

beta-parameter

Return type:

numpy.NDFloat

b(t: float | Sequence[float] | ndarray[tuple[Any, ...], dtype[floating[Any]]] | ndarray[tuple[Any, ...], dtype[integer[Any]]]) ndarray[tuple[Any, ...], dtype[float64]]

Defines the model b-parameter function:

\[b(t) \equiv \frac{d}{dt}\frac{1}{D}\]
Parameters:

t (FloatLike) – An array of times at which to evaluate the function.

Returns:

b-parameter

Return type:

numpy.NDFloat

classmethod get_param_desc(name: str) ParamDesc

Get a single parameter description.

Parameters:

name (str) – The parameter name.

Returns:

parameter description – A parameter description.

Return type:

ParamDesc

classmethod get_param_descs() list[petbox.dca.base.ParamDesc][source]

Get the parameter descriptions.

Returns:

parameter description – A list of parameter descriptions.

Return type:

List[ParamDesc]

classmethod from_params(params: Sequence[float]) _Self

Construct a model from a sequence of parameters.

Returns:

decline curve – The constructed decline curve model class.

Return type:

DeclineCurve

class petbox.dca.Duong(qi: float, a: float, m: float, validate_params: ~collections.abc.Iterable[bool] = <factory>)[source]

Duong Model

Duong, A. N. 2001. Rate-Decline Analysis for Fracture-Dominated Shale Reservoirs. SPE Res Eval & Eng 14 (3): 377-387. SPE-137748-PA. https://doi.org/10.2118/137748-PA.

Parameters:
  • qi (float) – The initial production rate in units of volume / day defined at ``t=1 day``.

  • a (float) – The a parameter. Roughly speaking, controls slope of the :func:q(t) function.

  • m (float) – The m parameter. Roughly speaking, controls curvature of the:func:q(t) function.

rate(t: float | Sequence[float] | ndarray[tuple[Any, ...], dtype[floating[Any]]] | ndarray[tuple[Any, ...], dtype[integer[Any]]]) ndarray[tuple[Any, ...], dtype[float64]]

Defines the model rate function:

\[q(t) = f(t)\]

where \(f(t)\) is defined by each model.

Parameters:

t (FloatLike) – An array of times at which to evaluate the function.

Returns:

rate

Return type:

numpy.NDFloat

cum(t: float | Sequence[float] | ndarray[tuple[Any, ...], dtype[floating[Any]]] | ndarray[tuple[Any, ...], dtype[integer[Any]]], **kwargs: Any) ndarray[tuple[Any, ...], dtype[float64]]

Defines the model cumulative volume function:

\[N(t) = \int_0^t q \, dt\]
Parameters:
  • t (FloatLike) – An array of times at which to evaluate the function.

  • n_grid (int) – For models evaluated by numerical integration (PLE, associated-phase yields, and SE at very small n), the number of log-spaced grid points used. Defaults to 10,000 (~1e-6 relative accuracy); must be >= 2. A smaller value (e.g. 2,000, ~5e-5) trades accuracy for speed. Ignored by models with a closed-form cumulative.

Returns:

cumulative volume

Return type:

numpy.NDFloat

D(t: float | Sequence[float] | ndarray[tuple[Any, ...], dtype[floating[Any]]] | ndarray[tuple[Any, ...], dtype[integer[Any]]]) ndarray[tuple[Any, ...], dtype[float64]]

Defines the model D-parameter function:

\[D(t) \equiv \frac{d}{dt}\textrm{ln} \, q \equiv \frac{1}{q}\frac{dq}{dt}\]
Parameters:

t (FloatLike) – An array of times at which to evaluate the function.

Returns:

D-parameter

Return type:

numpy.NDFloat

beta(t: float | Sequence[float] | ndarray[tuple[Any, ...], dtype[floating[Any]]] | ndarray[tuple[Any, ...], dtype[integer[Any]]]) ndarray[tuple[Any, ...], dtype[float64]]

Defines the model beta-parameter function.

\[\beta(t) \equiv \frac{d \, \textrm{ln} \, q}{d \, \textrm{ln} \, t} \equiv \frac{t}{q}\frac{dq}{dt} \equiv t \, D(t)\]
Parameters:

t (FloatLike) – An array of times at which to evaluate the function.

Returns:

beta-parameter

Return type:

numpy.NDFloat

b(t: float | Sequence[float] | ndarray[tuple[Any, ...], dtype[floating[Any]]] | ndarray[tuple[Any, ...], dtype[integer[Any]]]) ndarray[tuple[Any, ...], dtype[float64]]

Defines the model b-parameter function:

\[b(t) \equiv \frac{d}{dt}\frac{1}{D}\]
Parameters:

t (FloatLike) – An array of times at which to evaluate the function.

Returns:

b-parameter

Return type:

numpy.NDFloat

classmethod get_param_desc(name: str) ParamDesc

Get a single parameter description.

Parameters:

name (str) – The parameter name.

Returns:

parameter description – A parameter description.

Return type:

ParamDesc

classmethod get_param_descs() list[petbox.dca.base.ParamDesc][source]

Get the parameter descriptions.

Returns:

parameter description – A list of parameter descriptions.

Return type:

List[ParamDesc]

classmethod from_params(params: Sequence[float]) _Self

Construct a model from a sequence of parameters.

Returns:

decline curve – The constructed decline curve model class.

Return type:

DeclineCurve

Associated Phase Models

Implementations of associated (secondary and water) phase GOR/CGR/WOR/WGR models

class petbox.dca.PLYield(c: float, m0: float, m: float, t0: float, min: float | None = None, max: float | None = None, validate_params: ~collections.abc.Iterable[bool] = <factory>)[source]

Power-Law Associated Phase Model.

Fulford, D.S. 2018. A Model-Based Diagnostic Workflow for Time-Rate Performance of Unconventional Wells. Presented at Unconventional Resources Conference in Houston, Texas, USA, 23-25 July. URTeC-2903036. https://doi.org/10.15530/urtec-2018-2903036.

Has the general form of

\[GOR = c \, t^m\]

and allows independent early-time and late-time slopes m0 and m respectively.

Parameters:
  • c (float) – The value of GOR/CGR/WOR/WGR that acts as the anchor or pivot at t=t0. Units should be correctly specified for the respective yield function. Assumed volumes units per phase must be Bbl for oil and water and Mscf for gas in order to resolve any inconsistencies in unit magnitude.

  • m0 (float) – Early-time power-law slope.

  • m (float) – Late-time power-law slope.

  • t0 (float) – The time of the anchor or pivot value c.

  • min (float | None = None) – The minimum allowed value. Would be used e.g. to limit minimum CGR.

  • max (float | None = None) – The maximum allowed value. Would be used e.g. to limit maximum GOR.

gor(t: float | Sequence[float] | ndarray[tuple[Any, ...], dtype[floating[Any]]] | ndarray[tuple[Any, ...], dtype[integer[Any]]]) ndarray[tuple[Any, ...], dtype[float64]]

Defines the model GOR function. Implementation is idential to CGR function.

Parameters:

t (FloatLike) – An array of times at which to evaluate the function.

Returns:

GOR – The gas-oil ratio function in units of Mscf / Bbl.

Return type:

numpy.NDFloat

cgr(t: float | Sequence[float] | ndarray[tuple[Any, ...], dtype[floating[Any]]] | ndarray[tuple[Any, ...], dtype[integer[Any]]]) ndarray[tuple[Any, ...], dtype[float64]]

Defines the model CGR function. Implementation is identical to GOR function.

Parameters:

t (FloatLike) – An array of times at which to evaluate the function.

Returns:

CGR – The condensate-gas ratio in units of Bbl / Mscf.

Return type:

numpy.NDFloat

rate(t: float | Sequence[float] | ndarray[tuple[Any, ...], dtype[floating[Any]]] | ndarray[tuple[Any, ...], dtype[integer[Any]]]) ndarray[tuple[Any, ...], dtype[float64]]

Defines the model rate function:

\[q(t) = f(t)\]

where \(f(t)\) is defined by each model.

Parameters:

t (FloatLike) – An array of times at which to evaluate the function.

Returns:

rate

Return type:

numpy.NDFloat

cum(t: float | Sequence[float] | ndarray[tuple[Any, ...], dtype[floating[Any]]] | ndarray[tuple[Any, ...], dtype[integer[Any]]], **kwargs: Any) ndarray[tuple[Any, ...], dtype[float64]]

Defines the model cumulative volume function:

\[N(t) = \int_0^t q \, dt\]
Parameters:
  • t (FloatLike) – An array of times at which to evaluate the function.

  • n_grid (int) – For models evaluated by numerical integration (PLE, associated-phase yields, and SE at very small n), the number of log-spaced grid points used. Defaults to 10,000 (~1e-6 relative accuracy); must be >= 2. A smaller value (e.g. 2,000, ~5e-5) trades accuracy for speed. Ignored by models with a closed-form cumulative.

Returns:

cumulative volume

Return type:

numpy.NDFloat

D(t: float | Sequence[float] | ndarray[tuple[Any, ...], dtype[floating[Any]]] | ndarray[tuple[Any, ...], dtype[integer[Any]]]) ndarray[tuple[Any, ...], dtype[float64]]

Defines the model D-parameter function:

\[D(t) \equiv \frac{d}{dt}\textrm{ln} \, q \equiv \frac{1}{q}\frac{dq}{dt}\]
Parameters:

t (FloatLike) – An array of times at which to evaluate the function.

Returns:

D-parameter

Return type:

numpy.NDFloat

beta(t: float | Sequence[float] | ndarray[tuple[Any, ...], dtype[floating[Any]]] | ndarray[tuple[Any, ...], dtype[integer[Any]]]) ndarray[tuple[Any, ...], dtype[float64]]

Defines the model beta-parameter function.

\[\beta(t) \equiv \frac{d \, \textrm{ln} \, q}{d \, \textrm{ln} \, t} \equiv \frac{t}{q}\frac{dq}{dt} \equiv t \, D(t)\]
Parameters:

t (FloatLike) – An array of times at which to evaluate the function.

Returns:

beta-parameter

Return type:

numpy.NDFloat

b(t: float | Sequence[float] | ndarray[tuple[Any, ...], dtype[floating[Any]]] | ndarray[tuple[Any, ...], dtype[integer[Any]]]) ndarray[tuple[Any, ...], dtype[float64]]

Defines the model b-parameter function:

\[b(t) \equiv \frac{d}{dt}\frac{1}{D}\]
Parameters:

t (FloatLike) – An array of times at which to evaluate the function.

Returns:

b-parameter

Return type:

numpy.NDFloat

shift(dt: float) PLYield[source]

Return a copy with the pivot time moved later by dt days.

Use when a fit was anchored to the wrong first-production date: shifting by the correction moves the power law’s origin to true first production, so the model is defined over the period the original could only reach at negative t, where a power law is not real-valued.

This re-anchors rather than reproducing the original curve. Late-time yield changes by roughly (t0 / (t0 + dt)) ** m, because the origin has moved. The original parameters were biased by the wrong axis, so the shifted model is the more correct one, but a rigorous correction is a re-fit.

Parameters:

dt (float) – Days to move the pivot later. Negative moves it earlier.

Returns:

yield model

Return type:

PLYield

classmethod get_param_desc(name: str) ParamDesc

Get a single parameter description.

Parameters:

name (str) – The parameter name.

Returns:

parameter description – A parameter description.

Return type:

ParamDesc

classmethod get_param_descs() list[petbox.dca.base.ParamDesc][source]

Get the parameter descriptions.

Returns:

parameter description – A list of parameter descriptions.

Return type:

List[ParamDesc]

classmethod from_params(params: Sequence[float]) _Self

Construct a model from a sequence of parameters.

Returns:

decline curve – The constructed decline curve model class.

Return type:

DeclineCurve

class petbox.dca.GeneralizedPLYield(c: float, m0: float, segments: ~collections.abc.Sequence[~petbox.dca.associated.PLYieldSegment], min: float | None = None, max: float | None = None, validate_params: ~collections.abc.Iterable[bool] = <factory>)[source]

Generalized Power-Law Associated Phase Model.

Fulford, D.S. 2018. A Model-Based Diagnostic Workflow for Time-Rate Performance of Unconventional Wells. Presented at Unconventional Resources Conference in Houston, Texas, USA, 23-25 July. URTeC-2903036. https://doi.org/10.15530/urtec-2018-2903036.

Extends PLYield to an arbitrary number of segments. Within each segment, has the general form of

\[GOR = c \, t^m\]

with an independent slope m per segment. The yield function is continuous across every segment boundary unless that segment overrides c, in which case it steps to the override there. The single-breakpoint case is identical to PLYield,

\[PLYield(c, m_0, m, t_0) \equiv GeneralizedPLYield(c, m_0, (PLYieldSegment(t_0, m=m),))\]
Parameters:
  • c (float) – The value of GOR/CGR/WOR/WGR that acts as the anchor or pivot at the first breakpoint time, segments[0].t. Units should be correctly specified for the respective yield function. Assumed volumes units per phase must be Bbl for oil and water and Mscf for gas in order to resolve any inconsistencies in unit magnitude.

  • m0 (float) – Early-time power-law slope, applied before the first breakpoint.

  • segments (Sequence[PLYieldSegment]) – A sequence of PLYieldSegment. At least one is required; the first segment’s time is the anchor time of c. Times must be finite, positive and strictly increasing. Use from_segments() to build these from plain (t, m) or (t, c, m) tuples.

  • min (float | None = None) – The minimum allowed value. Would be used e.g. to limit minimum CGR.

  • max (float | None = None) – The maximum allowed value. Would be used e.g. to limit maximum GOR.

gor(t: float | Sequence[float] | ndarray[tuple[Any, ...], dtype[floating[Any]]] | ndarray[tuple[Any, ...], dtype[integer[Any]]]) ndarray[tuple[Any, ...], dtype[float64]]

Defines the model GOR function. Implementation is idential to CGR function.

Parameters:

t (FloatLike) – An array of times at which to evaluate the function.

Returns:

GOR – The gas-oil ratio function in units of Mscf / Bbl.

Return type:

numpy.NDFloat

cgr(t: float | Sequence[float] | ndarray[tuple[Any, ...], dtype[floating[Any]]] | ndarray[tuple[Any, ...], dtype[integer[Any]]]) ndarray[tuple[Any, ...], dtype[float64]]

Defines the model CGR function. Implementation is identical to GOR function.

Parameters:

t (FloatLike) – An array of times at which to evaluate the function.

Returns:

CGR – The condensate-gas ratio in units of Bbl / Mscf.

Return type:

numpy.NDFloat

rate(t: float | Sequence[float] | ndarray[tuple[Any, ...], dtype[floating[Any]]] | ndarray[tuple[Any, ...], dtype[integer[Any]]]) ndarray[tuple[Any, ...], dtype[float64]]

Defines the model rate function:

\[q(t) = f(t)\]

where \(f(t)\) is defined by each model.

Parameters:

t (FloatLike) – An array of times at which to evaluate the function.

Returns:

rate

Return type:

numpy.NDFloat

cum(t: float | Sequence[float] | ndarray[tuple[Any, ...], dtype[floating[Any]]] | ndarray[tuple[Any, ...], dtype[integer[Any]]], **kwargs: Any) ndarray[tuple[Any, ...], dtype[float64]]

Defines the model cumulative volume function:

\[N(t) = \int_0^t q \, dt\]
Parameters:
  • t (FloatLike) – An array of times at which to evaluate the function.

  • n_grid (int) – For models evaluated by numerical integration (PLE, associated-phase yields, and SE at very small n), the number of log-spaced grid points used. Defaults to 10,000 (~1e-6 relative accuracy); must be >= 2. A smaller value (e.g. 2,000, ~5e-5) trades accuracy for speed. Ignored by models with a closed-form cumulative.

Returns:

cumulative volume

Return type:

numpy.NDFloat

D(t: float | Sequence[float] | ndarray[tuple[Any, ...], dtype[floating[Any]]] | ndarray[tuple[Any, ...], dtype[integer[Any]]]) ndarray[tuple[Any, ...], dtype[float64]]

Defines the model D-parameter function:

\[D(t) \equiv \frac{d}{dt}\textrm{ln} \, q \equiv \frac{1}{q}\frac{dq}{dt}\]
Parameters:

t (FloatLike) – An array of times at which to evaluate the function.

Returns:

D-parameter

Return type:

numpy.NDFloat

beta(t: float | Sequence[float] | ndarray[tuple[Any, ...], dtype[floating[Any]]] | ndarray[tuple[Any, ...], dtype[integer[Any]]]) ndarray[tuple[Any, ...], dtype[float64]]

Defines the model beta-parameter function.

\[\beta(t) \equiv \frac{d \, \textrm{ln} \, q}{d \, \textrm{ln} \, t} \equiv \frac{t}{q}\frac{dq}{dt} \equiv t \, D(t)\]
Parameters:

t (FloatLike) – An array of times at which to evaluate the function.

Returns:

beta-parameter

Return type:

numpy.NDFloat

b(t: float | Sequence[float] | ndarray[tuple[Any, ...], dtype[floating[Any]]] | ndarray[tuple[Any, ...], dtype[integer[Any]]]) ndarray[tuple[Any, ...], dtype[float64]]

Defines the model b-parameter function:

\[b(t) \equiv \frac{d}{dt}\frac{1}{D}\]
Parameters:

t (FloatLike) – An array of times at which to evaluate the function.

Returns:

b-parameter

Return type:

numpy.NDFloat

shift(dt: float) GeneralizedPLYield[source]

Return a copy with every breakpoint moved later by dt days. Value overrides, slopes, c and m0 are unchanged. See PLYield.shift() for when to use this, and for the caveat that it re-anchors rather than reproducing the original.

Parameters:

dt (float) – Days to move every breakpoint later. Negative moves them earlier.

Returns:

yield model

Return type:

GeneralizedPLYield

classmethod get_param_desc(name: str) ParamDesc

Get a single parameter description.

Parameters:

name (str) – The parameter name.

Returns:

parameter description – A parameter description.

Return type:

ParamDesc

classmethod get_param_descs() list[petbox.dca.base.ParamDesc][source]

Get the parameter descriptions.

Returns:

parameter description – A list of parameter descriptions.

Return type:

List[ParamDesc]

classmethod from_params(params: Sequence[float]) _Self

Construct a model from a sequence of parameters.

Returns:

decline curve – The constructed decline curve model class.

Return type:

DeclineCurve

classmethod from_segments(c: float, m0: float, segments: Iterable[Sequence[float | None]], min: float | None = None, max: float | None = None) GeneralizedPLYield[source]

Construct from plain tuples instead of PLYieldSegment instances.

Each entry is (t, m) or (t, c, m). The constructor itself accepts only PLYieldSegment, which keeps the field type free of unions – this is the loose-tuple entry point.

Parameters:
Returns:

yield model

Return type:

GeneralizedPLYield

class petbox.dca.PLYieldSegment(t: float, *, c: float | None = None, m: float | None = None)[source]

One segment of a GeneralizedPLYield forecast.

None means “continuous from the previous segment”: an omitted m continues the preceding slope, which for the first segment is the model’s m0, and an omitted c leaves the yield value continuous at t. Supplying c steps the yield to that value at t and restarts the anchor chain there.

The optional fields are keyword-only on purpose. Positionally, PLYieldSegment(180.0, 0.6) would set c, while the equivalent builder tuple (180.0, 0.6) means m – the same two values meaning different things depending on which entry point was used.

Parameters:
  • t (float) – The breakpoint time in days. Must be finite and positive.

  • c (float | None = None) – The yield value at t, in the same units as the model’s c. None leaves the yield continuous. Must be finite and positive when given, and is rejected on the first segment, where the model’s c already defines the value at that time.

  • m (float | None = None) – The power-law slope from t onward. None continues the previous slope. Must be finite and within [-10, 10] when given.

classmethod from_tuple(spec: Sequence[float | None]) PLYieldSegment[source]

Build one segment from a loose tuple. Arity selects the meaning: (t, m) inherits the yield value, (t, c, m) sets it. An explicit None inherits exactly as a short form does, so (t, None, m) is (t, m).

t is the one field with no inherit semantics – there is no previous segment to continue a start time from – so it is required.

Parameters:

spec (Sequence[float | None]) – A (t, m) or (t, c, m) tuple.

Returns:

segment

Return type:

PLYieldSegment

Utility Functions

petbox.dca.bourdet(y: ndarray[tuple[Any, ...], dtype[float64]], x: ndarray[tuple[Any, ...], dtype[float64]], L: float = 0.0, xlog: bool = True, ylog: bool = False) ndarray[tuple[Any, ...], dtype[float64]][source]

Bourdet Derivative Smoothing

Compute the smoothed derivative using the Bourdet three-point algorithm (Eq. 8 of SPE-12777):

\[\left(\frac{dp}{dX}\right)_i = \frac{\frac{\Delta p_1}{\Delta X_1} \Delta X_2 + \frac{\Delta p_2}{\Delta X_2} \Delta X_1} {\Delta X_1 + \Delta X_2}\]

where points 1 and 2 are the first neighbors outside the smoothing window of distance L from point i on the log10(x) axis.

At interior points, the left neighbor is the last point j < i such that log10(x[i]) - log10(x[j]) > L, and the right neighbor is the first point j > i such that log10(x[j]) - log10(x[i]) > L. At left-edge points (where no left neighbor beyond L exists), a forward difference to the right neighbor is used. At right-edge points, a backward difference to the left neighbor is used.

Bourdet, D., Ayoub, J. A., and Pirard, Y. M. 1989. Use of Pressure Derivative in Well-Test Interpretation. SPE Form Eval 4 (2): 293-302. SPE-12777-PA. https://doi.org/10.2118/12777-PA.

Parameters:
  • y (numpy.NDFloat) – An array of y values to compute the derivative for.

  • x (numpy.NDFloat) – An array of x values.

  • L (float = 0.0) –

    Smoothing factor in units of log10 cycles (i.e., decades). A value of zero returns the point-by-point first-order difference derivative.

    Note

    The original paper (SPE-12777) defines L in natural-log units. To convert: L_log10 = L_paper / ln(10). For example, the paper’s L=0.1 corresponds to L=0.0434 in this function.

  • xlog (bool = True) – Calculate the derivative with respect to the log of x, i.e. dy / d[ln x].

  • ylog (bool = False) – Calculate the derivative with respect to the log of y, i.e. d[ln y] / dx.

Returns:

der – The calculated derivative.

Return type:

numpy.NDFloat

petbox.dca.get_time(start: float = 1.0, end: float = 100000.0, n: int = 101) ndarray[tuple[Any, ...], dtype[float64]][source]

Get a time array to evaluate with.

Parameters:
  • start (float) – The first time value of the array.

  • end (float) – The last time value of the array.

  • n (int) – The number of element in the array.

Returns:

time – An evenly-logspaced time series.

Return type:

numpy.NDFloat

petbox.dca.get_time_monthly_vol(start: float = 1, end: int = 10000) ndarray[tuple[Any, ...], dtype[float64]][source]

Get a time array to evaluate with.

Parameters:
  • start (float) – The first time value of the array.

  • end (float) – The last time value of the array.

Returns:

time – An evenly-monthly-spaced time series

Return type:

numpy.NDFloat

Other Classes

class petbox.dca.AssociatedPhase(*args: float)[source]

Extends DeclineCurve for an associated phase forecast. Each model must implement the defined abstract _yieldfn() method.

class petbox.dca.BothAssociatedPhase(*args: float)[source]

Extends DeclineCurve for a general yield model used for both secondary phase and water phase.

class petbox.dca.base.ParamDesc(name: str, description: str, lower_bound: float | None, upper_bound: float | None, naive_gen: collections.abc.Callable[[numpy.random._generator.Generator, int], numpy.ndarray[tuple[Any, ...], numpy.dtype[numpy.float64]]], exclude_lower_bound: bool = False, exclude_upper_bound: bool = False)[source]

Type Aliases

petbox.dca.base.FloatLike

The argument type of every public time and rate function – float | Sequence[float] | NDArray[floating] | NDArray[integer].

All of these are accepted: a scalar, a list, a tuple, a range, or a NumPy array of any float or integer width. Each is funnelled through np.atleast_1d(x).astype(np.float64), so the return is always a 1-d float64 array regardless of what went in.

Nested sequences, strings, and None are deliberately excluded, which is what makes this narrower than numpy.typing.ArrayLike.

petbox.dca.base.NDFloat

NDArray[numpy.float64] – the return type of every model function.

petbox.dca.base.NDBool

NDArray[numpy.bool_] – a segment mask, used internally.