"""Interwell connectivity through geometric considerations."""
from __future__ import annotations
import numpy as np
import pandas as pd
import scipy.linalg as sl
from numpy import ndarray
from pywaterflood import _core
idx = pd.IndexSlice
[docs]
def calc_gains_homogeneous(locations: pd.DataFrame, x_e: float, y_e: float) -> pd.DataFrame:
r"""Calculate gains from injectors to producers using multiwell productivity index.
The equation for the influence of injection on production is
.. math::
\mathbf{\Lambda} = \frac{\mathbf{A}_p^{-1}}{\sum \mathbf{A}_p^{-1}}
\times \left(\mathbf{1} \times \mathbf{A}_p^{-1} \times \mathbf{A}_c^T - 1\right)
- \left( \mathbf{A}_p^{-1} \times \mathbf{A}_c^T \right)
Args
----------
locations : pd.DataFrame
columns include:
`X` : x-location for well
`Y` : y-location for well
`Type`: ("Producer" or "Injector")
x_e : float
scaling in x-direction (unit size)
y_e : float
scaling in y-direction (unit size)
Returns
-------
Lambda : pd.DataFrame
contains the production-injection multiwell productivity index
Notes
-----
This assumes a roughly rectangular unit with major axes at x and y. You might want
to rotate your locations.
Lambda is negative. Not sure why. Negate it to get positive injection leading to
positive production.
References
----------
Kaviani, D. and Valkó, P.P., 2010. Inferring interwell connectivity using \
multiwell productivity index (MPI). Journal of Petroleum Science and Engineering, \
73(1-2), p.48-58.
"""
locations = locations.copy()
locations[["X", "Y"]] /= x_e
y_D = y_e / x_e
A_prod = calc_influence_matrix(locations, y_D, "prod")
A_conn = calc_influence_matrix(locations, y_D, "conn")
A_prod_inv = sl.inv(A_prod.to_numpy())
term1 = A_prod_inv / np.sum(A_prod_inv)
term2 = np.ones_like(A_prod_inv) @ A_prod_inv @ A_conn.to_numpy() - 1
term3 = A_prod_inv @ A_conn.to_numpy()
Lambda = term1 @ term2 - term3
connectivity_df = pd.DataFrame(Lambda, index=A_prod.index, columns=A_conn.columns)
return connectivity_df.rename_axis(index="Producers", columns="Injectors")
[docs]
def translate_locations(
locations: pd.DataFrame, x_col: str, y_col: str, type_col: str
) -> pd.DataFrame:
"""Translate locations to prepare for building connectivity matrix.
Moves the lower left edge of the reservoir to (0, 0), and sets up the matrix columns
to work with `calc_gains_homogeneous`
Args
----------
locations : pd.DataFrame
Has the x,y locations and type assignment for all wells
x_col : str
Column in `locations` for the x-location
y_col : str
Column in `locations` for the y-location
type_col : str
Column in `locations` for the type (producer or injector)
Returns
-------
locations_out: pd.DataFrame
columns are `X` (x-location), `Y` (y-location), `Type` (producer or injector)
"""
locations_out = pd.DataFrame(index=locations.index, columns=["X", "Y", "Type"])
locations_out["X"] = locations[x_col] - locations[x_col].min()
locations_out["Y"] = locations[y_col] - locations[y_col].min()
locations_out["Type"] = locations[type_col]
return locations_out
[docs]
def calc_influence_matrix(
locations: pd.DataFrame, y_D: float, matrix_type: str = "conn", m_max: int = 100
) -> pd.DataFrame:
"""Calculate influence matrix A.
Args
----------
locations : pd.DataFrame
Has the x,y locations and type assignment for all wells
columns are `X` (x-location), `Y` (y-location), `Type` (producer or injector)
y_D : float
dimensionless scaling for y-direction
matrix_type : str, choice of `conn` or `prod`
injector-producer matrix or producer-producer matrix
m_max : int > 0
number of terms in the series to calculate. 100 is a good default.
Returns
-------
influence_matrix : pd.DataFrame
a matrix with the influences between wells
"""
if matrix_type not in ["conn", "prod"]:
msg = "matrix_type must be either `conn` or `prod`"
raise ValueError(msg)
XA = locations[locations.Type == "Producer"]
XB = XA.copy() if matrix_type == "prod" else locations[locations.Type == "Injector"]
influence_matrix = pd.DataFrame(
index=pd.MultiIndex.from_product([XA.index, XB.index]), columns=["A"]
)
m = np.arange(1, m_max + 1, dtype="uint64") # elements of sum
for i, j in influence_matrix.index:
x_i, y_i = XA.loc[i, ["X", "Y"]]
x_j, y_j = XB.loc[j, ["X", "Y"]] + 1e-6
influence_matrix.loc[idx[i, j], "A"] = calc_A_ij(x_i, y_i, x_j, y_j, y_D, m)
return influence_matrix["A"].unstack().astype("float64")
[docs]
def calc_A_ij(x_i: float, y_i: float, x_j: float, y_j: float, y_D: float, m: ndarray) -> float:
r"""Calculate element in the influence matrix.
.. math::
A_{ij} = 2 \pi y_D (\frac13 - \frac{y_i}{y_D} +
\frac{y_i^2 + y_j^2}{2 y_D^2})
+ \sum_{m=1}^\infty \frac{t_m}m \cos(m\pi \tilde x_i)
\cos(m \pi \tilde x_j)
where
.. math::
t_m = \frac{\cosh\left(m\pi (y_D - |\tilde y_i - \tilde y_j|)\right)
+ \cosh\left(m\pi (y_D - \tilde y_i - \tilde y_j\right)}
{\sinh\left(m\pi y_D \right)}
Args
----
x_i : float
x-location of i'th well
y_i : float
y-location of i'th well
x_j : float
x-location of j'th well
y_j : float
y-location of j'th well
y_D : float
dimensionless parameter for y-direction
m : ndarray
series terms, from 1 to m_max
Returns
-------
A_ij : float
References
----------
Kaviani, D. and Valkó, P.P., 2010. Inferring interwell connectivity using \
multiwell productivity index (MPI). Journal of Petroleum Science and Engineering, \
73(1-2), p.48-58. https://doi.org/10.1016/j.petrol.2010.05.006
"""
# Symmetry properties, see https://doi.org/10.1016/j.petrol.2010.05.006, A5-A6
y_eD = y_D
x_D = max([x_i, x_j])
y_D = max([y_i, y_j])
x_wD = min([x_i, x_j])
y_wD = min([y_i, y_j])
if not ((x_D - x_wD) > (y_D - y_wD)):
y_eD = 1.0 / y_eD
return _core.calc_A_ij(x_D, y_D, x_wD, y_wD, y_eD, m)