Exercise 7.2
Non-Linear Vshale (Larionov Tertiary)
The linear gamma-ray index reads too much shale through the transition zone: sands look dirtier than they are, and marginal pay gets cut. On the Tertiary clastics of OML 58 (wells OD-001/OD-003/OD-007) the Larionov (1969) Tertiary correction is the field standard, because it bends the index down in the mid-range where the linear method overshoots.
You have the OD-003 triple-combo log. Build both Vshale curves and prove the Larionov curve is the gentler one.
Write three functions:
igr(gr, gr_clean, gr_shale): the linear gamma-ray index
IGR = (gr - gr_clean) / (gr_shale - gr_clean), clipped to 0..1. Works on a scalar or a numpy array.
vshale_linear(igr_val): the linear model. Vshale is the (already
clipped) index, so this is the identity: return igr_val.
vshale_larionov(igr_val): the Larionov Tertiary correction
Vsh = 0.083 (2 (3.7 IGR) - 1), clipped to 0..1. Must be vectorized (accept a numpy array and return an array).
Then, using clean/shale endpoints from the 5th / 95th percentiles of logs["GR"]:
- compute
igr_log = igr(logs["GR"].values, gr_clean_gapi, gr_shale_gapi), vsh_linear_log = vshale_linear(igr_log),vsh_larionov_log = vshale_larionov(igr_log).
The payoff: across the whole log, vsh_larionov_log <= vsh_linear_log elementwise. Larionov keeps the sands cleaner, so the wet and pay sands of OD-003 don't get over-shaled.
Stuck? Reveal hints one at a time — they progress from nudge to near-solution.
visibilityReveal reference solutionexpand_more
Try solving it yourself first — the hints walk you through it. The solution below is one valid approach; yours may differ and still be correct.
import numpy as np
import pandas as pd
np.random.seed(7)
depth = np.arange(7000.0, 7850.0, 0.5)
_cond = [depth < 7200, depth < 7400, depth < 7600, depth < 7700, depth >= 7700]
_gr = np.select(_cond, [105.0, 40.0, 32.0, 110.0, 45.0])
_rt = np.select(_cond, [4.0, 2.2, 35.0, 4.0, 2.5])
_rhob = np.select(_cond, [2.54, 2.31, 2.26, 2.55, 2.33])
_nphi = np.select(_cond, [0.33, 0.21, 0.17, 0.34, 0.22])
logs = pd.DataFrame({
"DEPTH": depth,
"GR": _gr + np.random.normal(0, 4.0, len(depth)),
"RT": _rt * np.exp(np.random.normal(0, 0.10, len(depth))),
"RHOB": _rhob + np.random.normal(0, 0.02, len(depth)),
"NPHI": np.clip(_nphi + np.random.normal(0, 0.015, len(depth)), 0.0, 1.0),
})
STEP_FT = 0.5
def igr(gr, gr_clean, gr_shale):
"""Linear gamma-ray index, clipped to 0..1. Scalar or array."""
raw = (np.asarray(gr, dtype=float) - gr_clean) / (gr_shale - gr_clean)
return np.clip(raw, 0.0, 1.0)
def vshale_linear(igr_val):
"""Linear model: Vshale is the (already clipped) index itself."""
return igr_val
def vshale_larionov(igr_val):
"""Larionov (Tertiary): 0.083 * (2**(3.7*IGR) - 1), clipped 0..1."""
igr_arr = np.asarray(igr_val, dtype=float)
vsh = 0.083 * (2.0 ** (3.7 * igr_arr) - 1.0)
return np.clip(vsh, 0.0, 1.0)
# Endpoints from the 5th / 95th percentile of GR.
gr_clean_gapi = logs["GR"].quantile(0.05)
gr_shale_gapi = logs["GR"].quantile(0.95)
igr_log = igr(logs["GR"].values, gr_clean_gapi, gr_shale_gapi)
vsh_linear_log = vshale_linear(igr_log)
vsh_larionov_log = vshale_larionov(igr_log)
print(f"gr_clean={gr_clean_gapi:.1f} gapi, gr_shale={gr_shale_gapi:.1f} gapi")
print(f"Larionov(IGR=1) = {vshale_larionov(np.array([1.0]))[0]:.4f}")
print(f"mean linear Vsh = {vsh_linear_log.mean():.4f}")
print(f"mean Larionov Vsh = {vsh_larionov_log.mean():.4f}")
lockCopying code is a Full Access feature.