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¶
A Capacitance Resistance Model history matcher. |
|
Bottom-hole pressure compensated CRM. |
Functions¶
|
Calculate primary production contribution. |
|
Calculate per injector-producer pair production. |
|
Calculate per injector-producer pair production (simplified tank). |
|
Calculate the production effect from bottom-hole pressure variation. |
|
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
gainandtaufor 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
- class pywaterflood.crm.CrmCompensated(primary: bool = True, tau_selection: str = 'per-pair', constraints: str = 'positive')[source]¶
Bases:
CRMBottom-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