pycsamt.ai.domain_gap.survey_fit#
Fit plausible CorruptionConfig ranges from real data.
This module is the bridge between a real field survey (AMT, CSAMT, MT,
or otherwise) and the numpy-only pycsamt.ai.domain_gap.simulator.
It is deliberately split in two:
survey_data_from_sites()Converts EDI/
Sites/APISurveyinput into a canonicalSurveyData, using only the quantities the M1 data contract defines. This path stays numpy-only once the bridge itself has run.fit_corruption_config()Derives plausible noise/error-floor/dropout ranges purely from a
SurveyData’s own declared errors and coverage. No EDI or pandas dependency is required at this stage.fit_distortion_priors_from_sites()Calls the heavier, pandas-based
pycsamt.emtools.gb/pycsamt.emtools.ssdiagnostics directly on real sites to estimate empirical static-shift and galvanic-distortion spreads. This is the one part of M3 that genuinely depends on a real survey’s own QC diagnostics rather than literature defaults.
The frequency grid required by SurveyData is not silently
interpolated across stations with different sampling: mismatched
frequency grids raise a clear error, per the plan’s non-negotiable
principles.
Functions
|
Derive plausible noise/dropout ranges from a real survey's QC. |
|
Estimate empirical static-shift and distortion spreads from real EDI. |
|
Fit a station+frequency+component logistic missingness model. |
|
Bridge EDI/ |
Classes
|
Fitted station+frequency+component logistic missingness model. |
- pycsamt.ai.domain_gap.survey_fit.survey_data_from_sites(sites, *, crs=None, freq_rtol=1e-06, station_spacing=500.0, recursive=True, on_dup='replace', verbose=0, metadata=None)[source]
Bridge EDI/
Sites/APISurveyinput to canonicalSurveyData.- Parameters:
sites (Any) – Anything accepted by
pycsamt.emtools._core.ensure_sites(): a filesystem path/glob/directory,EDIFile/EDICollection,Site/Sites,APISurvey, or an iterable of these.crs (str, optional) – Coordinate reference system identifier to record. Station positions are always projected with a local equirectangular approximation (see
pycsamt.ai.inversion._sites_bridge.sites_to_coords_3d()); pass a CRS string only if it genuinely describes that projection.freq_rtol (float, default=1e-6) – Relative tolerance used when checking that every station shares the same frequency grid.
station_spacing (float, default=500.0) – Forwarded to the coordinate bridge as a uniform-grid fallback spacing, used only when no station reports finite coordinates.
recursive (bool) – Forwarded to
ensure_sites.on_dup (str) – Forwarded to
ensure_sites.verbose (int) – Forwarded to
ensure_sites.metadata (dict, optional) – Extra provenance recorded on the returned survey.
- Returns:
Canonical survey with full
xx, xy, yx, yycomponents, with impedance and its declared error converted fromSite’s EDI-native[mV/km]/[nT]convention to SI (V/A), matchingSurveyData’s defaultImpedanceConvention.- Return type:
- Raises:
ValueError – If no station has usable impedance data, or stations do not share a common frequency grid within
freq_rtol.
Notes
This function performs no frequency interpolation: a survey whose stations were sampled on different frequency grids must be resolved by an explicit, survey-matched frequency selector (an M1 concern) before reaching this bridge.
Examples
>>> survey = survey_data_from_sites( ... "data/AMT/WILLY_DATA/L18PLT", recursive=False, verbose=0 ... ) >>> survey.components ('xx', 'xy', 'yx', 'yy')
- pycsamt.ai.domain_gap.survey_fit.fit_corruption_config(survey, *, severity_scale=1.0, components=None, noise_sampling='lognormal')[source]
Derive plausible noise/dropout ranges from a real survey’s QC.
Only quantities already present in the canonical
SurveyDatacontract are used: theimpedance_error-to-|Z|ratio for heteroscedastic noise and error floor, andcoverage()for dropout rates.- Parameters:
survey (SurveyData) – Real (or realistically corrupted) survey to profile.
severity_scale (float, default=1.0) – Multiplier applied to every fitted range/rate, letting a caller derive a milder or harsher preset from the same empirical fit.
components (sequence of str, optional) – Impedance components whose error ratio calibrates the noise range and error floor, e.g.
("xy", "yx"). Calibrate on the components that will later be compared: diagonal components usually carry much larger relative errors, and including them inflates the fitted range for an off-diagonal comparison.Noneuses every component present. Dropout rates always use the full coverage mask.noise_sampling ({"lognormal", "loguniform", "uniform"}, default="lognormal") – Stored on the returned configuration. The default treats the fitted range as the interquartile range of a log-normal error distribution, which reproduces the median and spread of declared field errors;
"uniform"restores a flat draw between the quartiles.
- Returns:
Configuration whose noise range spans the interquartile range of the observed relative error, whose error floor is the fifth percentile of that ratio, and whose dropout rates equal the observed missing fractions. Distortion and outlier parameters are left at zero; see
fit_distortion_priors_from_sites()for those.- Return type:
- Raises:
ValueError – If
surveyhas no declaredimpedance_errorto profile.
Examples
>>> import numpy as np >>> from pycsamt.ai.data.contracts import SurveyData >>> z = np.full((4, 6, 2), 100 + 50j) >>> err = np.full((4, 6, 2), 3.0) >>> survey = SurveyData( ... z, ... np.linspace(1000, 1, 6), ... ["A", "B", "C", "D"], ... ["xy", "yx"], ... np.zeros((4, 2)), ... impedance_error=err, ... ) >>> config = fit_corruption_config(survey) >>> config.noise_level_range[0] >= 0.0 True
- pycsamt.ai.domain_gap.survey_fit.fit_distortion_priors_from_sites(sites, *, recursive=True, on_dup='replace', verbose=0, **kwargs)[source]
Estimate empirical static-shift and distortion spreads from real EDI.
This is the one M3 entry point that genuinely depends on the heavier, pandas-based EM diagnostics in
pycsamt.emtools.gbandpycsamt.emtools.ss, run directly on real sites (e.g. a WILLY line) rather than on the numpy-onlySurveyDatacontract.- Parameters:
sites (Any) – Anything accepted by
pycsamt.emtools._core.ensure_sites().recursive (bool) – Forwarded to the underlying diagnostics.
on_dup (str) – Forwarded to the underlying diagnostics.
verbose (int) – Forwarded to the underlying diagnostics.
**kwargs (Any) – Forwarded to
pycsamt.emtools.gb.groom_bailey_table().
- Returns:
static_shift_log10_sigma,distortion_gain_log10_sigma,distortion_twist_deg_sigma,distortion_shear_sigma, anddistortion_anisotropy_sigma, each the population standard deviation of the corresponding per-station fitted parameter across stations with a successful fit. A parameter is0.0when fewer than two stations produced a usable fit.- Return type:
Examples
>>> priors = fit_distortion_priors_from_sites( ... "data/AMT/WILLY_DATA/L18PLT", recursive=False, verbose=0 ... ) >>> sorted(priors) ['distortion_anisotropy_sigma', 'distortion_gain_log10_sigma', 'distortion_shear_sigma', 'distortion_twist_deg_sigma', 'static_shift_log10_sigma']
- class pycsamt.ai.domain_gap.survey_fit.StructuredMissingness(alpha, station_effect, frequency_effect, component_effect, station_names, frequencies_hz, components, l2=1.0)[source]
Bases:
objectFitted station+frequency+component logistic missingness model.
logit(pi_sjc) = alpha + a_s + b_j + q_c, with the station, frequency, and component effectsa,b,qeach constrained to sum to zero for identifiability (an effect relative to its own group’s mean, not to an arbitrary reference level). This replaces the three independent scalar dropout rates offit_corruption_config()/apply_dropout()– which are mathematically forced to be numerically identical whenever each is the mean of the same overall coverage deficit taken along a different axis – with an explicit model of where missingness concentrates. Fit byfit_structured_missingness().- Parameters:
alpha (float) – Intercept: logit of the fitted overall mean dropout probability.
station_effect (tuple of float) – Per-station, per-frequency, and per-component effects, each summing to zero by construction.
frequency_effect (tuple of float) – Per-station, per-frequency, and per-component effects, each summing to zero by construction.
component_effect (tuple of float) – Per-station, per-frequency, and per-component effects, each summing to zero by construction.
station_names (tuple) – Identities the effects are indexed by, kept for provenance. The fitted probability field is tied to this specific grid; it is not meant to be reused against a differently shaped survey.
frequencies_hz (tuple) – Identities the effects are indexed by, kept for provenance. The fitted probability field is tied to this specific grid; it is not meant to be reused against a differently shaped survey.
components (tuple) – Identities the effects are indexed by, kept for provenance. The fitted probability field is tied to this specific grid; it is not meant to be reused against a differently shaped survey.
l2 (float, default=1.0) – L2 penalty weight used during fitting (not applied to
alpha).
Examples
>>> import numpy as np >>> from pycsamt.ai.data.contracts import SurveyData >>> z = np.ones((3, 4, 1), dtype=complex) >>> z[0, :2] = np.nan >>> survey = SurveyData( ... z, [4.0, 3.0, 2.0, 1.0], ["A", "B", "C"], ["xy"], ... np.zeros((3, 2)), ... ) >>> model = fit_structured_missingness(survey) >>> model.dropout_probability().shape (3, 4, 1)
- alpha: float
- l2: float = 1.0
- dropout_probability()[source]
Return the fitted per-cell Bernoulli dropout probability.
- Returns:
sigmoid(alpha + a_s + b_j + q_c), ready to pass toapply_structured_dropout().- Return type:
Examples
>>> model = StructuredMissingness( ... alpha=0.0, station_effect=(0.0,), frequency_effect=(0.0,), ... component_effect=(0.0,), station_names=("A",), ... frequencies_hz=(1.0,), components=("xy",), ... ) >>> model.dropout_probability() array([[[0.5]]])
- to_dict()[source]
Return a JSON-serializable representation.
- Returns:
All fields as plain Python types, with a schema discriminator.
- Return type:
Examples
>>> model = StructuredMissingness( ... alpha=0.0, station_effect=(0.0,), frequency_effect=(0.0,), ... component_effect=(0.0,), station_names=("A",), ... frequencies_hz=(1.0,), components=("xy",), ... ) >>> model.to_dict()["schema_version"] 1
- classmethod from_dict(data)[source]
Restore a serialized model.
- Parameters:
data (mapping) – State previously returned by
to_dict().- Returns:
Reconstructed, validated model.
- Return type:
Examples
>>> model = StructuredMissingness( ... alpha=0.0, station_effect=(0.0,), frequency_effect=(0.0,), ... component_effect=(0.0,), station_names=("A",), ... frequencies_hz=(1.0,), components=("xy",), ... ) >>> StructuredMissingness.from_dict(model.to_dict()) == model True
- pycsamt.ai.domain_gap.survey_fit.fit_structured_missingness(survey, *, l2=1.0, max_iter=500)[source]
Fit a station+frequency+component logistic missingness model.
Replaces the three scalar dropout rates of
fit_corruption_config()with an explicit model of where a survey’s missingness is concentrated. WithY_sjc = 1 - survey.valid[s, j, c]the observed missingness indicator andpi_sjc = sigmoid(alpha + a_s + b_j + q_c), the fit solves the penalized Bernoulli maximum-likelihood problem\[\hat\vartheta = \arg\min_\vartheta \Bigl[ -\sum_{sjc}\bigl\{Y_{sjc}\log\pi_{sjc} +(1-Y_{sjc})\log(1-\pi_{sjc})\bigr\} +\lambda\bigl(\lVert a\rVert_2^2+\lVert b\rVert_2^2 +\lVert q\rVert_2^2\bigr)\Bigr],\]with
a,b,qeach parameterized through a sum-to-zero contrast (_sum_zero_contrast()) so the fit is identified without an arbitrary reference level, optimized by L-BFGS-B using the closed-form gradient of the penalized negative log-likelihood (d(nll)/dz_sjc = pi_sjc - Y_sjc, the standard logistic-regression result).- Parameters:
survey (SurveyData) – Survey whose
validmask defines the real missingness pattern to fit. Should carry a genuine, disclosed missing-data mask (e.g. a coverage-aware, snap-or-missing survey), not one already interpolated/filled – interpolating over every gap removes the very signal this fit depends on.l2 (float, default=1.0) – Ridge penalty weight \(\lambda\) on the station, frequency, and component effects (the intercept is not penalized).
max_iter (int, default=500) – Maximum L-BFGS-B iterations.
- Returns:
Fitted model. Its
dropout_probability()gives the full(n_station, n_frequency, n_component)probability field forapply_structured_dropout().- Return type:
Examples
>>> import numpy as np >>> from pycsamt.ai.data.contracts import SurveyData >>> z = np.ones((3, 4, 1), dtype=complex) >>> z[0, :2] = np.nan >>> survey = SurveyData( ... z, [4.0, 3.0, 2.0, 1.0], ["A", "B", "C"], ["xy"], ... np.zeros((3, 2)), ... ) >>> model = fit_structured_missingness(survey) >>> abs(sum(model.station_effect)) < 1e-6 True >>> abs(sum(model.frequency_effect)) < 1e-6 True