pywaterflood.crm

Analyze waterfloods with capacitance-resistance models.

This is the central module in pywaterflood, based around the CRM class, which implements the standard capacitance-resistance models. For most cases, the best performance comes from selecting CRM(primary=True, tau_selection="per-pair", constraints="up-to one"). In the literature, this is referred to as CRM-IP (injector producer).

If the data is too sparse, then change tau_selection to “per-producer”. This reduces the number of variables to fit by nearly half by using only one time constant for all well connections influencing a producer. This is referred to as CRM-P in the literature.

If the data is still too sparse, you can sum all the injectors, all the producers, or both. This greatly decreases the utility of the model and is not recommended. In the literature, it is known as CRM-T.

The base class assumes constant bottomhole pressures for the producing wells. If you know the pressures for these wells or at least the trend, consider using CrmCompensated.

Classes

CRM

A Capacitance Resistance Model history matcher.

CrmCompensated

Bottom-hole pressure compensated CRM.

Functions

q_primary(→ numpy.typing.NDArray)

Calculate primary production contribution.

q_CRM_perpair(→ numpy.typing.NDArray)

Calculate per injector-producer pair production.

q_CRM_perproducer(→ numpy.typing.NDArray)

Calculate per injector-producer pair production (simplified tank).

q_bhp(→ numpy.typing.NDArray)

Calculate the production effect from bottom-hole pressure variation.

random_weights(→ numpy.typing.NDArray)

Generate random weights for producer-injector gains.

Module Contents

pywaterflood.crm.q_primary(production: numpy.typing.NDArray, time: numpy.typing.NDArray, gain_producer: float, tau_producer: float) numpy.typing.NDArray[source]

Calculate primary production contribution.

Uses Arps equation with \(b=0\)

\[q_{p}(t) = q_i e^{-bt}\]
Parameters:
  • production (NDArray) – Production, size: Number of time steps

  • time (NDArray) – Producing times to forecast, size: Number of time steps

  • gain_producer (float) – Arps \(q_i\) factor

  • tau_producer (float) – Arps time constant

Returns:

q_hat – Calculated production, \(\hat q\), size: Number of time steps

Return type:

NDArray

pywaterflood.crm.q_CRM_perpair(injection: numpy.typing.NDArray, time: numpy.typing.NDArray, gains: numpy.typing.NDArray, taus: numpy.typing.NDArray) numpy.typing.NDArray[source]

Calculate per injector-producer pair production.

Runs for influences of each injector on one producer, assuming individual gain and tau for each pair

Parameters:
  • injection (NDArray) – Injected fluid, size: Number of time steps

  • time (NDArray) – Producing times to forecast, size: Number of time steps

  • gains (NDArray) – Connectivities between each injector and the producer, size: Number of injectors

  • taus (NDArray) – Time constants between each injector and the producer, size: Number of injectors

Returns:

q_hat – Calculated production \(\hat q\), size: Number of time steps

Return type:

NDArray

pywaterflood.crm.q_CRM_perproducer(injection: numpy.typing.NDArray, time: numpy.typing.NDArray, gain: numpy.typing.NDArray, tau: float) numpy.typing.NDArray[source]

Calculate per injector-producer pair production (simplified tank).

Uses simplified CRMP model that assumes a single tau for each producer

Parameters:
  • injection (NDArray) – injected fluid in reservoir volumes, size: Number of time steps

  • time (NDArray) – Producing times to forecast, size: Number of time steps

  • gains (NDArray) – Connectivities between each injector and the producer size: Number of injectors

  • tau (float) – Time constants all injectors and the producer

Returns:

q_hat – Calculated production \(\hat q\)

shape: Number of time steps

Return type:

NDArray

pywaterflood.crm.q_bhp(pressure_local: numpy.typing.NDArray, pressure: numpy.typing.NDArray, v_matrix: numpy.typing.NDArray) numpy.typing.NDArray[source]

Calculate the production effect from bottom-hole pressure variation.

This looks like

\[q_{BHP,j}(t_i) = \sum_{k} v_{kj}\left[ p_j(t_{i-1}) - p_k(t_i) \right]\]
Parameters:
  • pressure_local (NDArray) – pressure for the well in question, shape: n_time

  • pressure (NDArray) – bottomhole pressure, shape: n_time, n_producers

  • v_matrix (NDArray) – connectivity between one producer and all producers, shape: n_producers

Returns:

q – production from changing BHP, shape: n_time

Return type:

NDArray

pywaterflood.crm.random_weights(n_prod: int, n_inj: int, axis: int = 0, seed: int | None = None) numpy.typing.NDArray[source]

Generate random weights for producer-injector gains.

Parameters:
  • n_prod (int) – Number of producing wells

  • n_inj (int) – Number of injecting wells

  • axis (int, default is 0) – 0 corresponds to normalizing among producers, 1 to normalizing among injectors

  • seed (int, default is None) – state for random number generator

Returns:

gains_guess – shape: n_prod, n_inj

Return type:

NDArray

class pywaterflood.crm.CRM(primary: bool = True, tau_selection: str = 'per-pair', constraints: str = 'positive')[source]

A Capacitance Resistance Model history matcher.

CRM uses a physics-inspired mass balance approach to explain production for waterfloods. It treats each injector-producer well pair as a system with mass input, output, and pressure related to the mass balance. Several versions exist and can be selected from the arguments.

The default arguments give the best results for most scenarios, but they can be sub-optimal if there is insufficient data, and they run slower than models with more simplifying assumptions.

Parameters:
  • primary (bool) – Whether to model primary production (True is strongly recommended)

  • tau_selection (str) –

    How many tau values to select
    • If ‘per-pair’, fit tau for each producer-injector pair

    • If ‘per-producer’, fit tau for each producer (CRMP model)

  • constraints (str) –

    How to constrain the gains
    • If ‘up-to one’ (default), let gains vary from 0 (no connection) to 1 (all injection goes to producer)

    • If ‘positive’, require each gain to be positive (It is unlikely to go negative in real life)

    • If ‘sum-to-one’, require the gains for each injector to sum to one (all production accounted for)

    • If ‘sum-to-one injector’ (not implemented), require each injector’s gains to sum to one (all injection accounted for)

Examples

>>> crm = CRM(True, "per-pair", "up-to one")

References

“A State-of-the-Art Literature Review on Capacitance Resistance Models for Reservoir Characterization and Performance Forecasting” - Wanderley de Holanda et al., 2018. https://www.mdpi.com/1996-1073/11/12/3368

primary
constraints
tau_selection
fit(production: numpy.typing.NDArray, injection: numpy.typing.NDArray, time: numpy.typing.NDArray, initial_guess: numpy.typing.NDArray = None, num_cores: int = 1, random: bool = False, **kwargs)[source]

Build a CRM model from the production and injection data.

Parameters:
  • production (NDArray) – production rates for each time period, shape: (n_time, n_producers)

  • injection (NDArray) – injection rates for each time period, shape: (n_time, n_injectors)

  • time (NDArray) – relative time for each rate measurement, starting from 0, shape: (n_time)

  • initial_guess (NDArray) – initial guesses for gains, taus, primary production contribution shape: (len(guess), n_producers)

  • num_cores (int) – number of cores to run fitting procedure on, defaults to 1

  • random (bool) – whether to randomly initialize the gains

  • **kwargs – keyword arguments to pass to scipy.optimize fitting routine

Returns:

self

Return type:

trained model

Example

>>> gh_url = (
...     "https://raw.githubusercontent.com/frank1010111/pywaterflood/master/testing/data/"
... )
>>> prod = pd.read_csv(gh_url + "production.csv", header=None).values
>>> inj = pd.read_csv(gh_url + "injection.csv", header=None).values
>>> time = pd.read_csv(gh_url + "time.csv", header=None).values[:, 0]
>>> crm = CRM(True, "per-pair", "up-to one")
>>> crm.fit(prod, inj, time)
predict(injection=None, time=None, connections=None, production=None)[source]

Predict production for a trained model.

If the injection and time are not provided, this will use the training values

Parameters:
  • injection (Optional NDArray) – The injection rates to input to the system, shape (n_time, n_inj)

  • time (Optional NDArray) – The timesteps to predict

  • connections (Optional dict) – if present, the gains, tau, gains_producer, tau_producer matrices

  • production (Optional NDArray) – The production (only takes first row to use for primary production decline)

Returns:

q_hat – The predicted values, shape (n_time, n_producers)

Return type:

NDArray

Example

Using the synthetic reservoir:

>>> gh_url = (
...     "https://raw.githubusercontent.com/frank1010111/pywaterflood/master/testing/data/"
... )
>>> prod = pd.read_csv(gh_url + "production.csv", header=None).values
>>> inj = pd.read_csv(gh_url + "injection.csv", header=None).values
>>> time = pd.read_csv(gh_url + "time.csv", header=None).values[:, 0]
>>> crm = CRM(True, "per-producer", "up-to one")
>>> crm.fit(prod, inj, time)
>>> crm.predict()

Starting from a known model:

>>> injection = np.ones((100, 2))
>>> production = np.ones((1, 1)) * 2
>>> time = np.arange(100, dtype=float)
>>> connections = {
...     "gains": np.ones((2, 1)) * 0.95,
...     "tau": np.ones((2, 1)) * 3,
...     "gains_producer": np.zeros(1),
...     "tau_producer": np.ones(1),
... }
>>> crm = CRM(False, "per-pair")
>>> crm.predict(injection, time, connections=connections, production=production)
set_rates(production=None, injection=None, time=None)[source]

Set production and injection rates and time array.

Parameters:
  • production (NDArray) – production rates with shape (n_time, n_producers)

  • injection (NDArray) – injection rates with shape (n_time, n_injectors)

  • time (NDArray) – timesteps with shape n_time

Example

>>> injection = np.ones((100,2))
>>> production = np.full((100,1), 2.0)
>>> time = np.arange(100, dtype=float)
>>> crm = CRM()
>>> crm.set_rates(production, injection, time)
set_connections(gains=None, tau=None, gains_producer=None, tau_producer=None)[source]

Set waterflood properties.

Parameters:
  • gains (NDArray) – connectivity between injector and producer shape: n_gains, n_producers

  • tau (NDArray) – time-constant for injection to be felt by production shape: either n_producers or (n_gains, n_producers)

  • gains_producer (NDArray) – gain on primary production, shape: n_producers

  • tau_producer (NDArray) – Arps time constant for primary production, shape: n_producers

Example

>>> crm = CRM(False, "per-pair")
>>> gains = np.full((2, 1),0.95)
>>> tau = np.full((2, 1), 3.0)
>>> crm.set_connections(gains, tau)
residual(production=None, injection=None, time=None)[source]

Calculate the production minus the predicted production for a trained model.

If the production, injection, and time are not provided, this will use the training values

Parameters:
  • production (NDArray) – The production rates observed, shape: (n_timesteps, n_producers)

  • injection (NDArray) – The injection rates to input to the system, shape: (n_timesteps, n_injectors)

  • time (NDArray) – The timesteps to predict

Returns:

residual – The true production data minus the predictions, shape (n_time, n_producers)

Return type:

NDArray

to_excel(fname: str)[source]

Write trained model to an Excel file.

Parameters:

fname (str) – Excel file to write out

to_pickle(fname: str)[source]

Write trained model to a pickle file.

Parameters:

fname (str) – pickle file to write out

class pywaterflood.crm.CrmCompensated(primary: bool = True, tau_selection: str = 'per-pair', constraints: str = 'positive')[source]

Bases: CRM

Bottom-hole pressure compensated CRM.

fit(production: numpy.typing.NDArray, pressure: numpy.typing.NDArray, injection: numpy.typing.NDArray, time: numpy.typing.NDArray, initial_guess: numpy.typing.NDArray = None, num_cores: int = 1, random: bool = False, **kwargs)[source]

Fit a CRM model from the production, pressure, and injection data.

Parameters:
  • production (NDArray) – production rates for each time period, shape: (n_time, n_producers)

  • pressure (NDArray) – average pressure for each producer for each time period, shape: (n_time, n_producers)

  • injection (NDArray) – injection rates for each time period, shape: (n_time, n_injectors)

  • time (NDArray) – relative time for each rate measurement, starting from 0, shape: (n_time)

  • initial_guess (NDArray) – initial guesses for gains, taus, primary production contribution shape: (len(guess), n_producers)

  • num_cores (int) – number of cores to run fitting procedure on, defaults to 1

  • random (bool) – whether to randomly initialize the gains

  • **kwargs – keyword arguments to pass to scipy.optimize fitting routine

Returns:

self

Return type:

trained model

predict(injection=None, time=None, connections=None, production=None, pressure=None)[source]

Predict production for a trained model.

If the injection and time are not provided, this will use the training values

Parameters:
  • injection (Optional NDArray) – The injection rates to input to the system, shape (n_time, n_inj)

  • time (Optional NDArray) – The timesteps to predict

  • connections (Optional dict) – if present, the gains, tau, gains_producer, tau_producer matrices

  • production (Optional NDArray) – The production (only takes first row to use for primary production decline)

Returns:

q_hat – The predicted values, shape (n_time, n_producers)

Return type:

NDArray

Example

Using the synthetic reservoir:

>>> gh_url = (
...     "https://raw.githubusercontent.com/frank1010111/pywaterflood/master/testing/data/"
... )
>>> prod = pd.read_csv(gh_url + "production.csv", header=None).values
>>> inj = pd.read_csv(gh_url + "injection.csv", header=None).values
>>> time = pd.read_csv(gh_url + "time.csv", header=None).values[:, 0]
>>> pressure = 1000 - prod * 0.1
>>> crm = CrmCompensated(True, "per-producer", "up-to one")
>>> crm.fit(prod, pressure, inj, time)
>>> crm.predict()

Starting from a known model:

>>> injection = np.ones((100, 2))
>>> production = np.ones((1, 1)) * 2
>>> pressure = 1000 - production * 0.1
>>> time = np.arange(100, dtype=float)
>>> connections = {
...     "gains": np.ones((2, 1)) * 0.95,
...     "tau": np.ones((2, 1)) * 3,
...     "gains_producer": np.zeros(1),
...     "tau_producer": np.ones(1),
... }
>>> crm = CRM(False, "per-pair")
>>> crm.predict(injection, time, connections=connections, production=production)
primary
constraints
tau_selection
set_rates(production=None, injection=None, time=None)

Set production and injection rates and time array.

Parameters:
  • production (NDArray) – production rates with shape (n_time, n_producers)

  • injection (NDArray) – injection rates with shape (n_time, n_injectors)

  • time (NDArray) – timesteps with shape n_time

Example

>>> injection = np.ones((100,2))
>>> production = np.full((100,1), 2.0)
>>> time = np.arange(100, dtype=float)
>>> crm = CRM()
>>> crm.set_rates(production, injection, time)
set_connections(gains=None, tau=None, gains_producer=None, tau_producer=None)

Set waterflood properties.

Parameters:
  • gains (NDArray) – connectivity between injector and producer shape: n_gains, n_producers

  • tau (NDArray) – time-constant for injection to be felt by production shape: either n_producers or (n_gains, n_producers)

  • gains_producer (NDArray) – gain on primary production, shape: n_producers

  • tau_producer (NDArray) – Arps time constant for primary production, shape: n_producers

Example

>>> crm = CRM(False, "per-pair")
>>> gains = np.full((2, 1),0.95)
>>> tau = np.full((2, 1), 3.0)
>>> crm.set_connections(gains, tau)
residual(production=None, injection=None, time=None)

Calculate the production minus the predicted production for a trained model.

If the production, injection, and time are not provided, this will use the training values

Parameters:
  • production (NDArray) – The production rates observed, shape: (n_timesteps, n_producers)

  • injection (NDArray) – The injection rates to input to the system, shape: (n_timesteps, n_injectors)

  • time (NDArray) – The timesteps to predict

Returns:

residual – The true production data minus the predictions, shape (n_time, n_producers)

Return type:

NDArray

to_excel(fname: str)

Write trained model to an Excel file.

Parameters:

fname (str) – Excel file to write out

to_pickle(fname: str)

Write trained model to a pickle file.

Parameters:

fname (str) – pickle file to write out