welleng.interpretation package

Directional-survey interpretation: raw sensors to survey, QC, multi-station analysis (MSA), and correction-uncertainty propagation.

welleng.interpretation.forward module

Sensor -> survey: the MWD navigation equations.

Compute inclination, azimuth and toolface from a triad of accelerometer readings (gravity) and a triad of magnetometer readings (Earth’s field), using the standard, public-domain minimum-set MWD equations (Williamson, SPE-67616).

The equations are scale-free in each sensor (they depend only on ratios), so the accelerometer and magnetometer readings may be supplied in any self-consistent units (e.g. mG and nT, or m/s^2 and T) – the result is identical.

Validated against an operator’s commercial-vendor survey (raw sensors -> the vendor’s own computed inc/azi) to 0.005 deg inclination and 0.03 deg azimuth.

welleng.interpretation.forward.EARTH_RATE = 15.041067

360 deg / 23.9344696 h.

Type:

Earth’s sidereal rotation rate (deg/hr)

welleng.interpretation.forward.earth_rate_components(latitude, rate: float = 15.041067)[source]

Local horizontal (true-north) and vertical Earth-rate components.

At latitude phi the Earth-rotation vector has a horizontal component rate*cos(phi) pointing true north and a vertical component rate*sin(phi) (up). These are the gyro reference values; the Earth-rate “dip” equals the latitude, so no spatial model/service is required (unlike the magnetic field).

Parameters:
  • latitude (float or array_like) – Geographic latitude (deg).

  • rate (float, default EARTH_RATE) – Earth-rotation rate magnitude (deg/hr).

Returns:

horizontal, vertical – Earth-rate components (deg/hr).

Return type:

ndarray

welleng.interpretation.forward.gyro_to_survey(g_xyz, w_xyz, *, grid_convergence: float = 0.0, axis: str = 'z', deg: bool = True)[source]

Inclination, azimuth and toolface from accelerometer + rate-gyro.

Gyrocompassing: the accelerometer gives inclination and toolface (as in sensor_to_survey()) and the rate-gyro measures the Earth-rotation vector in the tool frame, whose horizontal component points true north. The azimuth is obtained from the same minimum-set equation, with the gyro readings in place of the magnetometer.

Unlike a magnetic survey the gyro references true north directly, so no magnetic declination is applied (only grid convergence, if supplied).

Parameters:
  • g_xyz (array_like, shape (3,) or (n, 3)) – Accelerometer readings (gravity) in the tool frame.

  • w_xyz (array_like, shape (3,) or (n, 3)) – Rate-gyro readings (Earth-rotation rate) in the tool frame, any self-consistent angular-rate unit.

  • grid_convergence (float, default 0.0) – Grid convergence (deg), subtracted to give grid azimuth.

  • axis ({'z', 'x'}, default 'z') – Along-hole (axial) sensor axis, as in sensor_to_survey().

  • deg (bool, default True) – Return degrees if True, else radians.

Returns:

inc, azi, toolface – Inclination, azimuth (true/grid, wrapped to [0, 360)) and toolface.

Return type:

float or ndarray

welleng.interpretation.forward.sensor_to_survey(g_xyz, b_xyz, *, declination: float = 0.0, grid_convergence: float = 0.0, axis: str = 'z', deg: bool = True)[source]

Inclination, azimuth and toolface from accelerometer + magnetometer.

Parameters:
  • g_xyz (array_like, shape (3,) or (n, 3)) – Accelerometer readings (gravity vector) in the tool frame.

  • b_xyz (array_like, shape (3,) or (n, 3)) – Magnetometer readings (Earth field) in the tool frame, same station ordering as g_xyz.

  • declination (float, default 0.0) – Magnetic declination (deg, east-positive). Added to the magnetic azimuth to give azimuth relative to true north.

  • grid_convergence (float, default 0.0) – Grid convergence (deg). Subtracted to give grid azimuth. With both declination and grid_convergence zero the result is the raw magnetic azimuth.

  • axis ({'z', 'x'}, default 'z') – Which sensor axis is the along-hole (axial) axis. 'z' is the usual convention; 'x' handles tools/exports that list the axial axis first (seen in some vendor survey files).

  • deg (bool, default True) – If True (default) return degrees, else radians.

Returns:

inc, azi, toolface – Inclination (0 = vertical), azimuth (grid/true per the corrections above, wrapped to [0, 360)) and gravity toolface (wrapped to [0, 360)).

Return type:

float or ndarray

Notes

Classic minimum-set equations (Williamson, SPE-67616; textbook):

inc      = atan2(sqrt(Gx^2 + Gy^2), Gz)
toolface = atan2(Gy, Gx)
azi_mag  = atan2( (Gx By - Gy Bx) |G|,
                  Bz (Gx^2 + Gy^2) - Gz (Gx Bx + Gy By) )

welleng.interpretation.qc module

Georeference survey QC: total-gravity, total-field and dip checks.

The georeference tests compare the measured total gravity field, total magnetic field and magnetic dip against known reference values, and flag stations whose residuals exceed tolerance (Ekseth et al., SPE-133417). A total-field or dip failure indicates magnetic interference (e.g. insufficient non-magnetic spacing, or proximity to casing) so the azimuth at that station is unreliable; a total-gravity failure indicates an accelerometer problem.

Validated: reproduces a commercial vendor’s own per-station DE-QC pass/fail flags to 100% agreement, with the vendor’s total-field / dip tolerances recovered as ~826 nT and ~0.44 deg.

class welleng.interpretation.qc.DualDepthResult(depth_difference_error: ndarray, tolerance: ndarray, flag: ndarray)[source]

Bases: object

Dual-depth-difference QC test outcome.

depth_difference_error

The measured difference between the two independent depth measurements (pipe tally vs wireline), i.e. the quantity being tested.

Type:

ndarray

tolerance

Allowed difference: the RSS of the two error-model depth uncertainties.

Type:

ndarray

flag

True where the difference exceeds tolerance (station FAILS – a gross depth error is present).

Type:

ndarray of bool

__init__(depth_difference_error: ndarray, tolerance: ndarray, flag: ndarray) None
depth_difference_error: ndarray
flag: ndarray
property passed: ndarray
tolerance: ndarray
class welleng.interpretation.qc.GeomagReference(b_total: float, dip: float, g_total: float, declination: float = 0.0, grid_convergence: float = 0.0)[source]

Bases: object

Reference geomagnetic + gravity field at the survey location/time.

Units are the caller’s, but must match the sensor units passed to the QC functions (e.g. b_total in nT with magnetometer readings in nT).

Parameters:
  • b_total (float) – Reference total magnetic-field strength.

  • dip (float) – Reference magnetic dip angle (deg, down-positive).

  • g_total (float) – Reference total gravity-field strength.

  • declination (float, default 0.0) – Magnetic declination (deg, east-positive).

  • grid_convergence (float, default 0.0) – Grid convergence (deg).

__init__(b_total: float, dip: float, g_total: float, declination: float = 0.0, grid_convergence: float = 0.0) None
b_total: float
declination: float = 0.0
dip: float
g_total: float
grid_convergence: float = 0.0
class welleng.interpretation.qc.QCResult(d_g: ~numpy.ndarray, d_b: ~numpy.ndarray, d_dip: ~numpy.ndarray, flag_g: ~numpy.ndarray, flag_b: ~numpy.ndarray, flag_dip: ~numpy.ndarray, tolerances: dict = <factory>)[source]

Bases: object

Per-station georeference QC residuals and pass/fail flags.

d_g, d_b, d_dip

Measured-minus-reference residuals for total gravity, total field and dip.

Type:

ndarray

flag_g, flag_b, flag_dip

True where the residual exceeds tolerance (i.e. the station FAILS that check).

Type:

ndarray of bool

tolerances

The tolerances applied.

Type:

dict

__init__(d_g: ~numpy.ndarray, d_b: ~numpy.ndarray, d_dip: ~numpy.ndarray, flag_g: ~numpy.ndarray, flag_b: ~numpy.ndarray, flag_dip: ~numpy.ndarray, tolerances: dict = <factory>) None
d_b: ndarray
d_dip: ndarray
d_g: ndarray
flag_b: ndarray
flag_dip: ndarray
flag_g: ndarray
property passed: ndarray

True where the station passes all three checks.

tolerances: dict
welleng.interpretation.qc.dual_depth_difference(depth_difference_error, sigma_pipe, sigma_wll)[source]

Dual-depth-difference QC test (Ekseth et al., SPE-133417).

Where an independent second depth measurement exists (e.g. a wireline pass with a casing-collar locator alongside the drillpipe tally), the difference between the two depths is checked against the combined error-model uncertainty. A difference larger than tolerance indicates a gross depth error not explained by the error models.

The allowed difference is the root-sum-square of the two independent depth uncertainties (same sigma convention – e.g. both 3-sigma):

tolerance = sqrt(sigma_pipe**2 + sigma_wll**2)

Reproduces SPE-133417 Table 1a/1b/1c exactly.

Parameters:
  • depth_difference_error (float or array_like) – Measured pipe-vs-wireline depth-difference error.

  • sigma_pipe (float or array_like) – Error-model depth uncertainties (same sigma level) for the pipe-tally and wireline measurements respectively.

  • sigma_wll (float or array_like) – Error-model depth uncertainties (same sigma level) for the pipe-tally and wireline measurements respectively.

Return type:

DualDepthResult

welleng.interpretation.qc.georef_checks(g_xyz, b_xyz, ref: GeomagReference, tolerances: dict | None = None) QCResult[source]

Run the georeference QC tests on one or more survey stations.

Parameters:
  • g_xyz (array_like, shape (3,) or (n, 3)) – Accelerometer and magnetometer readings in the tool frame (same units as ref.g_total / ref.b_total).

  • b_xyz (array_like, shape (3,) or (n, 3)) – Accelerometer and magnetometer readings in the tool frame (same units as ref.g_total / ref.b_total).

  • ref (GeomagReference) – Reference field.

  • tolerances (dict, optional) – Keys 'g_total', 'b_total', 'dip'. Missing keys fall back to DEFAULT_TOLERANCES.

Return type:

QCResult

welleng.interpretation.msa module

Multi-station analysis (MSA): estimate the tool’s actual sensor errors.

Given many survey stations, MSA estimates the constant per-axis sensor bias and scale-factor errors of a triad (magnetometer or accelerometer) from the redundancy that every station’s measured total field must equal the reference (Ekseth et al., SPE-133417, multistation test). This is the inverse of the forward error model: instead of assuming the model’s error magnitudes, it measures them – so a survey’s actual performance can be checked against the error model applied to it (its EOU is only valid if the actual errors are within the model).

Closed form

The constraint |B_true| = B_ref with B_true = (B_meas - b) / (1 + s) linearises (small errors) to a linear equation in the six unknowns p = [bx, by, bz, sx, sy, sz] per station:

2 [Bx, By, Bz, Bx^2, By^2, Bz^2] . p = |B_meas|^2 - B_ref^2

Stacking the stations gives A p = d solved in closed form by ordinary least squares, p = (A^T A)^-1 A^T d – one matrix solve, no iteration. The estimate covariance (A^T A)^-1 A^T diag(sigma_d^2) A (A^T A)^-1 is available analytically and yields the per-component estimability and the correlation matrix, which gate poorly-observed components.

Estimability / the geometry gate

MSA estimability is governed by the survey’s directional (azimuth / toolface) diversity, not its inclination. In particular the axial sensor error is intrinsically weakly observed unless the well changes direction substantially; in the industry it is handled by a separate axial (drillstring) interference correction and in-field referencing. This module therefore returns the estimate with its analytical standard errors and a per-component estimable verdict, rather than a blanket, possibly-meaningless, error vector.

class welleng.interpretation.msa.MSAResult(bias: ndarray, scale: ndarray, covariance: ndarray, std: ndarray, correlation: ndarray, estimable: ndarray, n_stations: int, condition_number: float)[source]

Bases: object

Estimated tool sensor errors and their estimability.

bias

Estimated per-axis bias (sensor units).

Type:

ndarray, shape (3,)

scale

Estimated per-axis fractional scale-factor error.

Type:

ndarray, shape (3,)

covariance

Estimate covariance for [bx, by, bz, sx, sy, sz].

Type:

ndarray, shape (6, 6)

std

Standard errors (sqrt of the covariance diagonal).

Type:

ndarray, shape (6,)

correlation

Correlation matrix of the estimate.

Type:

ndarray, shape (6, 6)

estimable

Per-component verdict: False where the standard error exceeds the estimability threshold (that component is not reliably observed by this survey’s geometry).

Type:

ndarray of bool, shape (6,)

n_stations

Number of stations used.

Type:

int

condition_number

Condition number of A^T A (large => ill-conditioned geometry).

Type:

float

__init__(bias: ndarray, scale: ndarray, covariance: ndarray, std: ndarray, correlation: ndarray, estimable: ndarray, n_stations: int, condition_number: float) None
property axial_estimable: bool

Whether the axial (z) bias and scale are both reliably observed.

bias: ndarray
condition_number: float
correlation: ndarray
covariance: ndarray
estimable: ndarray
n_stations: int
scale: ndarray
std: ndarray
welleng.interpretation.msa.apply_sensor_errors(triad, result, *, only_estimable=False)[source]

Correct triad readings for the MSA-estimated bias and scale errors.

Closes the MSA loop: estimate_sensor_errors() measures the sensor errors, this removes them, B_true = (B_meas - b) / (1 + s) (the model of the closed form above), so the corrected triad can be re-run through sensor_to_survey() for a corrected survey. The correction is opt-in — MSA is often used to flag out-of-model tool performance, not to auto-correct raw sensors.

Parameters:
  • triad (array_like, shape (n, 3)) – Per-station triad readings (same sensor/units as passed to estimate_sensor_errors()).

  • result (MSAResult) – The estimate to apply.

  • only_estimable (bool, default False) – If True, apply a component’s bias/scale only where result.estimable is set (poorly-observed components — typically the axial term — are left uncorrected rather than corrected by a meaningless estimate). If False, apply all six.

Returns:

(n, 3) corrected triad.

Return type:

numpy.ndarray

welleng.interpretation.msa.estimate_sensor_errors(triad, ref: GeomagReference, *, sensor: str = 'mag', noise: float | None = None, bias_estimability: float = 500.0, scale_estimability: float = 0.01, min_stations: int = 10) MSAResult[source]

Closed-form multi-station estimate of tool bias + scale errors.

Parameters:
  • triad (array_like, shape (n, 3)) – Per-station triad readings (magnetometer if sensor='mag', else accelerometer), same units as the matching reference total.

  • ref (GeomagReference) – Reference field; ref.b_total (mag) or ref.g_total (accel) is the target total.

  • sensor ({'mag', 'accel'}, default 'mag') – Which triad is supplied (selects the reference total).

  • noise (float, optional) – Per-axis measurement noise standard deviation (same units), used to propagate the estimate covariance. If None, a unit noise is used so the relative estimability and the correlation matrix are still meaningful but the absolute standard errors are only proportional.

  • bias_estimability (float) – Standard-error thresholds above which a bias / scale component is judged not estimable by this geometry.

  • scale_estimability (float) – Standard-error thresholds above which a bias / scale component is judged not estimable by this geometry.

  • min_stations (int, default 10) – Minimum independent stations required (SPE-133417); fewer raises.

Return type:

MSAResult

welleng.interpretation.correction_uncertainty module

Correction-uncertainty covariance by Monte-Carlo input propagation.

A survey correction (BHA sag, a depth stretch model, an MSA correction) is a deterministic model of uncertain inputs. The residual uncertainty left after correcting is the covariance of the correction over those input uncertainties – propagated here by plain Monte-Carlo: draw perturbed inputs, re-evaluate the correction, take the covariance of the resulting correction vectors. Within one run the draws are single realisations, so the returned matrix carries the full cross-station correlation (a biased stabiliser OD mis-corrects every station the same way) – exactly the block a covariated error model consumes.

The correction model is injected as a callable, so this module stays a generic, model-agnostic propagation utility: welleng-core does not depend on any specific mechanics implementation (e.g. a BHA-sag beam model living in a consumer package plugs in at the call site). Standard uncertainty propagation (JCGM 101 / GUM Supplement 1 – Monte Carlo method).

Example (sketch):

def draw(rng):
    g = replace(base_geom,
                stabilizer_od=base_geom.stabilizer_od + rng.normal(0, od_sig),
                mud_weight=base_geom.mud_weight + rng.normal(0, mw_sig))
    return lambda: sag_correction(g, survey)     # consumer's model

res = correction_covariance_mc(draw, n_draws=1000, rng=rng)
# res.covariance -> (n_stations, n_stations) inc-correction covariance
class welleng.interpretation.correction_uncertainty.CorrectionUncertainty(mean: ndarray, covariance: ndarray, std: ndarray, n_draws: int, se_std: ndarray)[source]

Bases: object

Monte-Carlo correction-uncertainty result.

mean

Mean correction over the draws (the best-estimate correction under input uncertainty; generally ~ the nominal correction).

Type:

(n,) ndarray

covariance

Covariance of the correction across draws – the residual uncertainty the correction leaves behind, WITH cross-station correlation (each draw is one realisation of the inputs over the whole run).

Type:

(n, n) ndarray

std

Per-station 1-sigma (sqrt of the diagonal).

Type:

(n,) ndarray

n_draws

Draws used.

Type:

int

se_std

Monte-Carlo standard error of std (~ std / sqrt(2 (n-1))) – the convergence handle: enlarge n_draws until se_std is small against the sigma you are quoting.

Type:

(n,) ndarray

__init__(mean: ndarray, covariance: ndarray, std: ndarray, n_draws: int, se_std: ndarray) None
covariance: ndarray
mean: ndarray
n_draws: int
se_std: ndarray
std: ndarray
welleng.interpretation.correction_uncertainty.correction_covariance_mc(draw, n_draws=1000, rng=None)[source]

Covariance of a deterministic correction under uncertain inputs (MC).

Parameters:
  • draw (callable) – draw(rng) -> callable() -> (n,) array_like: draws ONE realisation of the uncertain inputs (applying each input’s own distribution and propagation character – e.g. a systematic stabiliser-OD offset drawn once per run vs a per-station random enlargement drawn per station inside the closure) and returns a zero-argument callable evaluating the correction for that realisation. The correction model itself is whatever the closure wraps – this function never imports it.

  • n_draws (int, default 1000) – Monte-Carlo draws. The result carries se_std to judge convergence.

  • rng (numpy.random.Generator, optional) – Source of randomness (default numpy.random.default_rng()).

Return type:

CorrectionUncertainty

Notes

  • Zero input uncertainty => zero covariance (the deterministic limit) – kept as a regression gate.

  • The full (n, n) matrix is returned (not just the diagonal) because the within-run input realisation correlates the stations; a covariated error model consumes exactly that block.

Module contents

Directional-survey interpretation: raw sensors -> survey, QC and multi-station analysis (MSA).

This is welleng’s survey-interpretation layer, upstream of the forward error model (which maps inc/azi -> ISCWSA covariance). It is the open, scalar reference implementation:

  • sensor_to_survey() – accelerometer + magnetometer -> inclination, azimuth, toolface (public-domain MWD navigation equations; Williamson, SPE-67616).

  • gyro_to_survey() – accelerometer + rate-gyro -> inclination, azimuth (true north, no declination), toolface (gyrocompassing). The gyro reference is Earth’s rotation rate – a constant, with local components a closed-form function of latitude (earth_rate_components()).

  • georef_checks() – georeference QC tests (total-gravity, total-field, dip) with pass/fail flags (Ekseth et al., SPE-133417).

  • dual_depth_difference() – dual-depth- difference depth QC test (pipe tally vs wireline), SPE-133417.

  • estimate_sensor_errors() – multi-station analysis: closed-form linear least-squares estimate of the tool’s actual sensor biases/scale-factors, with an analytical estimability (correlation) matrix that gates unreliable (poorly-observed) components (SPE-133417, multistation test).

References

Williamson, H.S. (2000) “Accuracy Prediction for Directional MWD”, SPE-67616. Ekseth, R. et al. (2010) “High-Integrity Wellbore Surveying”, SPE-133417. Grindrod, S.J. et al. (2016) OWSG survey-tool error models, IADC/SPE-178843.

class welleng.interpretation.CorrectionUncertainty(mean: ndarray, covariance: ndarray, std: ndarray, n_draws: int, se_std: ndarray)[source]

Bases: object

Monte-Carlo correction-uncertainty result.

mean

Mean correction over the draws (the best-estimate correction under input uncertainty; generally ~ the nominal correction).

Type:

(n,) ndarray

covariance

Covariance of the correction across draws – the residual uncertainty the correction leaves behind, WITH cross-station correlation (each draw is one realisation of the inputs over the whole run).

Type:

(n, n) ndarray

std

Per-station 1-sigma (sqrt of the diagonal).

Type:

(n,) ndarray

n_draws

Draws used.

Type:

int

se_std

Monte-Carlo standard error of std (~ std / sqrt(2 (n-1))) – the convergence handle: enlarge n_draws until se_std is small against the sigma you are quoting.

Type:

(n,) ndarray

__init__(mean: ndarray, covariance: ndarray, std: ndarray, n_draws: int, se_std: ndarray) None
covariance: ndarray
mean: ndarray
n_draws: int
se_std: ndarray
std: ndarray
class welleng.interpretation.DualDepthResult(depth_difference_error: ndarray, tolerance: ndarray, flag: ndarray)[source]

Bases: object

Dual-depth-difference QC test outcome.

depth_difference_error

The measured difference between the two independent depth measurements (pipe tally vs wireline), i.e. the quantity being tested.

Type:

ndarray

tolerance

Allowed difference: the RSS of the two error-model depth uncertainties.

Type:

ndarray

flag

True where the difference exceeds tolerance (station FAILS – a gross depth error is present).

Type:

ndarray of bool

__init__(depth_difference_error: ndarray, tolerance: ndarray, flag: ndarray) None
depth_difference_error: ndarray
flag: ndarray
property passed: ndarray
tolerance: ndarray
class welleng.interpretation.GeomagReference(b_total: float, dip: float, g_total: float, declination: float = 0.0, grid_convergence: float = 0.0)[source]

Bases: object

Reference geomagnetic + gravity field at the survey location/time.

Units are the caller’s, but must match the sensor units passed to the QC functions (e.g. b_total in nT with magnetometer readings in nT).

Parameters:
  • b_total (float) – Reference total magnetic-field strength.

  • dip (float) – Reference magnetic dip angle (deg, down-positive).

  • g_total (float) – Reference total gravity-field strength.

  • declination (float, default 0.0) – Magnetic declination (deg, east-positive).

  • grid_convergence (float, default 0.0) – Grid convergence (deg).

__init__(b_total: float, dip: float, g_total: float, declination: float = 0.0, grid_convergence: float = 0.0) None
b_total: float
declination: float = 0.0
dip: float
g_total: float
grid_convergence: float = 0.0
class welleng.interpretation.MSAResult(bias: ndarray, scale: ndarray, covariance: ndarray, std: ndarray, correlation: ndarray, estimable: ndarray, n_stations: int, condition_number: float)[source]

Bases: object

Estimated tool sensor errors and their estimability.

bias

Estimated per-axis bias (sensor units).

Type:

ndarray, shape (3,)

scale

Estimated per-axis fractional scale-factor error.

Type:

ndarray, shape (3,)

covariance

Estimate covariance for [bx, by, bz, sx, sy, sz].

Type:

ndarray, shape (6, 6)

std

Standard errors (sqrt of the covariance diagonal).

Type:

ndarray, shape (6,)

correlation

Correlation matrix of the estimate.

Type:

ndarray, shape (6, 6)

estimable

Per-component verdict: False where the standard error exceeds the estimability threshold (that component is not reliably observed by this survey’s geometry).

Type:

ndarray of bool, shape (6,)

n_stations

Number of stations used.

Type:

int

condition_number

Condition number of A^T A (large => ill-conditioned geometry).

Type:

float

__init__(bias: ndarray, scale: ndarray, covariance: ndarray, std: ndarray, correlation: ndarray, estimable: ndarray, n_stations: int, condition_number: float) None
property axial_estimable: bool

Whether the axial (z) bias and scale are both reliably observed.

bias: ndarray
condition_number: float
correlation: ndarray
covariance: ndarray
estimable: ndarray
n_stations: int
scale: ndarray
std: ndarray
class welleng.interpretation.QCResult(d_g: ~numpy.ndarray, d_b: ~numpy.ndarray, d_dip: ~numpy.ndarray, flag_g: ~numpy.ndarray, flag_b: ~numpy.ndarray, flag_dip: ~numpy.ndarray, tolerances: dict = <factory>)[source]

Bases: object

Per-station georeference QC residuals and pass/fail flags.

d_g, d_b, d_dip

Measured-minus-reference residuals for total gravity, total field and dip.

Type:

ndarray

flag_g, flag_b, flag_dip

True where the residual exceeds tolerance (i.e. the station FAILS that check).

Type:

ndarray of bool

tolerances

The tolerances applied.

Type:

dict

__init__(d_g: ~numpy.ndarray, d_b: ~numpy.ndarray, d_dip: ~numpy.ndarray, flag_g: ~numpy.ndarray, flag_b: ~numpy.ndarray, flag_dip: ~numpy.ndarray, tolerances: dict = <factory>) None
d_b: ndarray
d_dip: ndarray
d_g: ndarray
flag_b: ndarray
flag_dip: ndarray
flag_g: ndarray
property passed: ndarray

True where the station passes all three checks.

tolerances: dict
welleng.interpretation.apply_sensor_errors(triad, result, *, only_estimable=False)[source]

Correct triad readings for the MSA-estimated bias and scale errors.

Closes the MSA loop: estimate_sensor_errors() measures the sensor errors, this removes them, B_true = (B_meas - b) / (1 + s) (the model of the closed form above), so the corrected triad can be re-run through sensor_to_survey() for a corrected survey. The correction is opt-in — MSA is often used to flag out-of-model tool performance, not to auto-correct raw sensors.

Parameters:
  • triad (array_like, shape (n, 3)) – Per-station triad readings (same sensor/units as passed to estimate_sensor_errors()).

  • result (MSAResult) – The estimate to apply.

  • only_estimable (bool, default False) – If True, apply a component’s bias/scale only where result.estimable is set (poorly-observed components — typically the axial term — are left uncorrected rather than corrected by a meaningless estimate). If False, apply all six.

Returns:

(n, 3) corrected triad.

Return type:

numpy.ndarray

welleng.interpretation.correction_covariance_mc(draw, n_draws=1000, rng=None)[source]

Covariance of a deterministic correction under uncertain inputs (MC).

Parameters:
  • draw (callable) – draw(rng) -> callable() -> (n,) array_like: draws ONE realisation of the uncertain inputs (applying each input’s own distribution and propagation character – e.g. a systematic stabiliser-OD offset drawn once per run vs a per-station random enlargement drawn per station inside the closure) and returns a zero-argument callable evaluating the correction for that realisation. The correction model itself is whatever the closure wraps – this function never imports it.

  • n_draws (int, default 1000) – Monte-Carlo draws. The result carries se_std to judge convergence.

  • rng (numpy.random.Generator, optional) – Source of randomness (default numpy.random.default_rng()).

Return type:

CorrectionUncertainty

Notes

  • Zero input uncertainty => zero covariance (the deterministic limit) – kept as a regression gate.

  • The full (n, n) matrix is returned (not just the diagonal) because the within-run input realisation correlates the stations; a covariated error model consumes exactly that block.

welleng.interpretation.dual_depth_difference(depth_difference_error, sigma_pipe, sigma_wll)[source]

Dual-depth-difference QC test (Ekseth et al., SPE-133417).

Where an independent second depth measurement exists (e.g. a wireline pass with a casing-collar locator alongside the drillpipe tally), the difference between the two depths is checked against the combined error-model uncertainty. A difference larger than tolerance indicates a gross depth error not explained by the error models.

The allowed difference is the root-sum-square of the two independent depth uncertainties (same sigma convention – e.g. both 3-sigma):

tolerance = sqrt(sigma_pipe**2 + sigma_wll**2)

Reproduces SPE-133417 Table 1a/1b/1c exactly.

Parameters:
  • depth_difference_error (float or array_like) – Measured pipe-vs-wireline depth-difference error.

  • sigma_pipe (float or array_like) – Error-model depth uncertainties (same sigma level) for the pipe-tally and wireline measurements respectively.

  • sigma_wll (float or array_like) – Error-model depth uncertainties (same sigma level) for the pipe-tally and wireline measurements respectively.

Return type:

DualDepthResult

welleng.interpretation.earth_rate_components(latitude, rate: float = 15.041067)[source]

Local horizontal (true-north) and vertical Earth-rate components.

At latitude phi the Earth-rotation vector has a horizontal component rate*cos(phi) pointing true north and a vertical component rate*sin(phi) (up). These are the gyro reference values; the Earth-rate “dip” equals the latitude, so no spatial model/service is required (unlike the magnetic field).

Parameters:
  • latitude (float or array_like) – Geographic latitude (deg).

  • rate (float, default EARTH_RATE) – Earth-rotation rate magnitude (deg/hr).

Returns:

horizontal, vertical – Earth-rate components (deg/hr).

Return type:

ndarray

welleng.interpretation.estimate_sensor_errors(triad, ref: GeomagReference, *, sensor: str = 'mag', noise: float | None = None, bias_estimability: float = 500.0, scale_estimability: float = 0.01, min_stations: int = 10) MSAResult[source]

Closed-form multi-station estimate of tool bias + scale errors.

Parameters:
  • triad (array_like, shape (n, 3)) – Per-station triad readings (magnetometer if sensor='mag', else accelerometer), same units as the matching reference total.

  • ref (GeomagReference) – Reference field; ref.b_total (mag) or ref.g_total (accel) is the target total.

  • sensor ({'mag', 'accel'}, default 'mag') – Which triad is supplied (selects the reference total).

  • noise (float, optional) – Per-axis measurement noise standard deviation (same units), used to propagate the estimate covariance. If None, a unit noise is used so the relative estimability and the correlation matrix are still meaningful but the absolute standard errors are only proportional.

  • bias_estimability (float) – Standard-error thresholds above which a bias / scale component is judged not estimable by this geometry.

  • scale_estimability (float) – Standard-error thresholds above which a bias / scale component is judged not estimable by this geometry.

  • min_stations (int, default 10) – Minimum independent stations required (SPE-133417); fewer raises.

Return type:

MSAResult

welleng.interpretation.georef_checks(g_xyz, b_xyz, ref: GeomagReference, tolerances: dict | None = None) QCResult[source]

Run the georeference QC tests on one or more survey stations.

Parameters:
  • g_xyz (array_like, shape (3,) or (n, 3)) – Accelerometer and magnetometer readings in the tool frame (same units as ref.g_total / ref.b_total).

  • b_xyz (array_like, shape (3,) or (n, 3)) – Accelerometer and magnetometer readings in the tool frame (same units as ref.g_total / ref.b_total).

  • ref (GeomagReference) – Reference field.

  • tolerances (dict, optional) – Keys 'g_total', 'b_total', 'dip'. Missing keys fall back to DEFAULT_TOLERANCES.

Return type:

QCResult

welleng.interpretation.gyro_to_survey(g_xyz, w_xyz, *, grid_convergence: float = 0.0, axis: str = 'z', deg: bool = True)[source]

Inclination, azimuth and toolface from accelerometer + rate-gyro.

Gyrocompassing: the accelerometer gives inclination and toolface (as in sensor_to_survey()) and the rate-gyro measures the Earth-rotation vector in the tool frame, whose horizontal component points true north. The azimuth is obtained from the same minimum-set equation, with the gyro readings in place of the magnetometer.

Unlike a magnetic survey the gyro references true north directly, so no magnetic declination is applied (only grid convergence, if supplied).

Parameters:
  • g_xyz (array_like, shape (3,) or (n, 3)) – Accelerometer readings (gravity) in the tool frame.

  • w_xyz (array_like, shape (3,) or (n, 3)) – Rate-gyro readings (Earth-rotation rate) in the tool frame, any self-consistent angular-rate unit.

  • grid_convergence (float, default 0.0) – Grid convergence (deg), subtracted to give grid azimuth.

  • axis ({'z', 'x'}, default 'z') – Along-hole (axial) sensor axis, as in sensor_to_survey().

  • deg (bool, default True) – Return degrees if True, else radians.

Returns:

inc, azi, toolface – Inclination, azimuth (true/grid, wrapped to [0, 360)) and toolface.

Return type:

float or ndarray

welleng.interpretation.sensor_to_survey(g_xyz, b_xyz, *, declination: float = 0.0, grid_convergence: float = 0.0, axis: str = 'z', deg: bool = True)[source]

Inclination, azimuth and toolface from accelerometer + magnetometer.

Parameters:
  • g_xyz (array_like, shape (3,) or (n, 3)) – Accelerometer readings (gravity vector) in the tool frame.

  • b_xyz (array_like, shape (3,) or (n, 3)) – Magnetometer readings (Earth field) in the tool frame, same station ordering as g_xyz.

  • declination (float, default 0.0) – Magnetic declination (deg, east-positive). Added to the magnetic azimuth to give azimuth relative to true north.

  • grid_convergence (float, default 0.0) – Grid convergence (deg). Subtracted to give grid azimuth. With both declination and grid_convergence zero the result is the raw magnetic azimuth.

  • axis ({'z', 'x'}, default 'z') – Which sensor axis is the along-hole (axial) axis. 'z' is the usual convention; 'x' handles tools/exports that list the axial axis first (seen in some vendor survey files).

  • deg (bool, default True) – If True (default) return degrees, else radians.

Returns:

inc, azi, toolface – Inclination (0 = vertical), azimuth (grid/true per the corrections above, wrapped to [0, 360)) and gravity toolface (wrapped to [0, 360)).

Return type:

float or ndarray

Notes

Classic minimum-set equations (Williamson, SPE-67616; textbook):

inc      = atan2(sqrt(Gx^2 + Gy^2), Gz)
toolface = atan2(Gy, Gx)
azi_mag  = atan2( (Gx By - Gy Bx) |G|,
                  Bz (Gx^2 + Gy^2) - Gz (Gx Bx + Gy By) )