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
phithe Earth-rotation vector has a horizontal componentrate*cos(phi)pointing true north and a vertical componentrate*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
declinationandgrid_convergencezero 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:
objectDual-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:
objectReference 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_totalin 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:
objectPer-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:
- 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 toDEFAULT_TOLERANCES.
- Return type:
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:
objectEstimated 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 throughsensor_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.estimableis 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) orref.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:
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:
objectMonte-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: enlargen_drawsuntilse_stdis 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_stdto judge convergence.rng (numpy.random.Generator, optional) – Source of randomness (default
numpy.random.default_rng()).
- Return type:
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:
objectMonte-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: enlargen_drawsuntilse_stdis 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:
objectDual-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:
objectReference 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_totalin 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:
objectEstimated 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:
objectPer-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 throughsensor_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.estimableis 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_stdto judge convergence.rng (numpy.random.Generator, optional) – Source of randomness (default
numpy.random.default_rng()).
- Return type:
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:
- welleng.interpretation.earth_rate_components(latitude, rate: float = 15.041067)[source]
Local horizontal (true-north) and vertical Earth-rate components.
At latitude
phithe Earth-rotation vector has a horizontal componentrate*cos(phi)pointing true north and a vertical componentrate*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) orref.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:
- 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 toDEFAULT_TOLERANCES.
- Return type:
- 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
declinationandgrid_convergencezero 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) )