Version 2.4.0#
pyCSAMT 2.4.0 New Fix Enhancement Docs Build#
Released 2026-08-19.
Four independent pieces of work landed in this release. First, the
station confidence profile
(plot_confidence_profile(),
station_confidence_table(),
frequency_confidence_table()): station distance
is now computed from real survey coordinates instead of silently
defaulting to a hardcoded spacing, with an opt-in escape hatch for
surveys whose coordinates cannot be trusted, and decluttered point
labels when many stations are flagged low-confidence. Thanks to
@shahidalishah130-hub for
reporting this with a clear reproducing figure in issue #76.
Second, Site/Sites
– the station wrapper used across pycsamt.emtools, the CLI, and
the desktop app – now accepts EMTF-XML transfer functions
(EMTF) symmetrically with the historical
SEG-EDI format, with no changes required anywhere that already consumes
Site/Sites. Building the real, oracle-validated round trip behind
this feature surfaced and fixed five further real bugs, three of them
pre-existing and unrelated to XML at all – including one where
Sites.write() had been silently writing placeholder files instead of
real EDI content on every call, for as long as the method has existed.
Third, pycsamt.airborne – the MobileMT/ZTEM/AFMAG package added in
2.3.x – went through a full guideline-compliance pass: a new shared
pycsamt.airborne.validation module now centralizes boundary
validation that each technology adapter had independently duplicated
(frequency/period resolution, matrix-shape checks, record masks,
reference-station metadata mapping), every public class and function
across the package gained complete NumPy-style docstrings, and one real
inheritance mismatch was corrected. Alongside it, a new
pycsamt.emtools.afmag module adds AFMAG-specific processing,
diagnostics, and plotting – including a from-scratch implementation of
the rotation-matrix motion-induced-noise method of Liu et al. (2018),
grounded directly in that paper’s own equations.
Fourth, a new pycsamt.models.occam1d package brings a native,
1-D Occam smooth-model inversion engine into pyCSAMT: forward physics,
analytic Jacobian, roughness regularization, and the nonlinear Occam
loop itself are all implemented in Python/NumPy, with no external
Occam1D binary required (Occam1DRunner
remains available for those who still want one). Per-station and
whole-survey batch inversion are both covered, backed by a new bundled
example, examples/occam1_demo/, against three real Gabbs Valley
soundings.
Fifth, a docs-only follow-up completes the story the first two pieces
above started: a new, six-page Airborne EM Guide user
guide and a new pycsamt.airborne reference document
pycsamt.airborne in full, and Loading electromagnetic data
now explains, in one place, that the same Site/Sites loading
boundary handles EMTF-XML exactly like EDI, and that airborne surveys
load through a separate, parallel boundary of their own.
Real EMTF-XML support in Site/Sites New#
Site previously only wrapped a parsed SEG-EDI object. It now accepts
either an EDI object or an EMTF XML
document, via a lazy dual-backend design: edi
and tf are both always available on any
Site regardless of which one it was built from – the representation
not natively supplied is materialized from the other on first access and
cached, using the existing, oracle-validated
edi_to_emtf()/emtf_to_edi
converters. Because edi always resolves
to a real EDI-shaped object either way, every existing piece of code that
reads .edi/.edi.Z/.edi.Tipper directly – across
pycsamt.emtools, the CLI, and the desktop app – keeps working
completely unmodified for an XML-native Site, with no code changes
required anywhere outside pycsamt.site.
New surface: Site.from_xml() /
Site.to_xml() (the from_edi
constructor is now spelled out explicitly too, for symmetry),
Site.backend ("edi" or
"xml"), and typed metadata properties –
site_meta,
site_layout,
provenance,
processing,
copyright,
quality_meta – that are the same
pycsamt.metadata objects the EMTF document itself already
models (SiteMeta, SiteLayout, ProvenanceMeta, and so on), not
a parallel copy. Sites gained the matching bulk operations,
to_emtf_list() and
write_xml(), mirroring the existing EDI-side
to_edis/write. And the coercion layer behind
to_sites() and
ensure_sites() – the single entry point every
emtools function validates its input through – now recognizes a
single .xml path, an EMTF object, a list mixing either with EDI
sources, and a directory containing *.xml files alongside (or instead
of) *.edi files, with no changes needed in ensure_sites itself.
This was validated end to end, both numerically and by literally reading
the files back off disk, in a new example,
examples/emtf_xml_roundtrip_demo/ (see its README.md): three
real field stations converted EDI -> XML -> EDI through both the
Site/Sites API and the raw EMTF document API side by side,
comparing periods, impedance, tipper, and variance at every step.
Separately, the underlying EDI<->EMTF-XML conversion itself was
validated against a real, locally compiled reference implementation
(USGS EMTF-FCU v4.1) for the first time – periods, impedance, variance,
site coordinates, channel geometry, and rotation math all matched to
floating-point precision on real field data
(pycsamt/emtf/tests/test_fcu_oracle.py, skipped rather than failed
when no local FCU build is present).
The Site Tools pages are updated to match: Site Containers gains a “Working With EMTF-XML” section (real captured output against the bundled Gabbs Valley station, already used throughout Metadata and EMTF), and Site Metadata, Export And Reporting, and Site Selection each note where their existing, EDI-specific tooling still applies unchanged and where the new EMTF-XML surface takes over.
Five real bugs found and fixed Fix#
Building and validating the above surfaced five real, previously unnoticed bugs – two specific to the new XML coercion logic, and three pre-existing ones with no relation to XML support at all:
ensure_sites/to_sitessilently returned an emptySitesfor a plain Python list of already-constructedSiteobjects (a bareSitedoesn’t look EDI-shaped itself, so it was dropped by the duck-typed unwrapping step) – a real limitation noted, but not root-caused, in an earlier audit. Fixed.ordered()andselect()forced every site through its materialized EDI view on every non-inplacecall, silently downgrading an XML-native site back to EDI – and sinceensure_sitesalways calls.ordered(...)on its way out, this meant newly-XML-awareSiteswere downgraded again immediately after being built correctly. Fixed to preserve each site’s native backend.``Sites.write()`` had been writing placeholder files, not real EDI content, since the method was introduced – it looked for an
EDIFile.to_file()method that has never existed (the real serializer isEDIFile.write()), silently fell back to a one-line placeholder, and the only existing test checked that the output file existed, never its content. Fixed to usepycsamt.emtf.converters.edi.write_edi(), the same writer already used elsewhere.The EDI reader (
pycsamt.seg.edi) mapped the historical EDI missing-data sentinel (EMPTY, typically1.0E+32) to0.0instead ofNaNfor impedance, tipper, and variance – inventing a measured-exactly-zero value in place of a genuinely missing period. This affects any EDI with real gaps (dead bands, dropped channels), not only XML-related code paths. Fixed to emitNaN.That fix then exposed a second, latent bug it had been masking:
pycsamt.z.resphase.ResPhase.compute_resistivity_phase()had a guard that unconditionally rejected anyNaNuncertainty paired with a valid impedance value, even though the surrounding per-component logic was already written to tolerate exactly that case gracefully. A measured impedance with an unavailable uncertainty is common in real field data. Relaxed to only reject genuinely malformed (negative or infinite) uncertainties.
Real inter-station distance, not a hardcoded spacing Fix#
The reported symptom: a real ~2050 m AMT line (41 stations, L48PLT)
plotted with an x-axis stretched out to ~8000 m, even though the station
names and their order were correct.
The root cause lived in the shared _station_positions helper. It only
looked for east/north (or x/y, easting/northing)
attributes directly on each station object – but real EDI-backed
Site objects never carry those; they only expose lat/lon
(read from the EDI >HEAD section). So that lookup always failed, and
the function silently fell back to laying every station out at a flat
spacing_m default of 200 m: 41 stations x 200 m = 8000 m, exactly
matching the reported figure.
Station distance is now derived from real coordinates whenever at least
two stations carry usable ones: EDI HEAD latitude/longitude is projected
to true UTM easting/northing using the existing, dependency-free
pycsamt.gis.utils.ll_to_utm() (no GDAL/pyproj required), then
projected onto the bearing from the first to the last valid station –
real chainage, the same convention already used for station ordering
elsewhere in the project. An earlier internal draft of this fix used a
naive equirectangular approximation (multiplying absolute longitude by a
per-point cos(lat) factor); a regression test comparing against a
known span caught a ~6% distortion from that shortcut before it shipped,
which is why the final fix goes through a real UTM projection instead.
Each station’s projected offset is cached on its underlying EDI object
(keyed by the exact lat/lon it was derived from), so repeated calls
across a session – e.g. several plot_confidence_profile calls on the
same survey, each of which gets a freshly re-wrapped Sites instance
– do not re-parse the header or redo the projection every time.
spacing_m is now only ever a fallback: for individual stations
without usable coordinates, or for the whole line when none have any.
Two new opt-in controls New Enhancement#
Force uniform spacing. Real coordinates are not always trustworthy
– a bad GPS fix, a copy-paste error in the header, or a placeholder
value can all corrupt station positions. force_spacing=True on
plot_confidence_profile(),
station_confidence_table(), and
frequency_confidence_table() skips coordinate
lookup entirely and lays every station out at uniform spacing_m
steps, so a user who knows their coordinates are unreliable can trust
their own spacing value instead.
Decluttered low-confidence labels. plot_confidence_profile
already drew a rotated station-name label above every point below
ci_lo (annotate_low=True) on top of the station names already on
the top axis. For a survey where most stations are flagged low –
exactly the L48PLT case in the original report – that meant nearly
every point got its own label, stacked on top of the top-axis labels.
The new annotate_low_step thins these the same way
station_label_step already thins the top axis: left at its default
None, it auto-thins once more than 18 stations are flagged low;
annotate_low_step=1 restores the previous label-every-point
behaviour; annotate_low=False turns per-point labels off entirely.
Complete pycsamt.airborne user guide and API reference Docs#
pycsamt.airborne had real docstrings after the guideline pass
above, but no narrative user guide and no dedicated API reference page
at all – Airborne EM Guide previously covered only the
data model in a single page, with no toctree children, and
pycsamt.airborne did not exist. Both gaps are closed. The user
guide is now six pages: Airborne Data Model Overview (the
technology-neutral container map and the AFMAG/ZTEM/MobileMT
subpackage table), Flight Lines and Datasets
(NavigationTrack/AirborneEMRecord/AirborneEMLine/
AirborneEMDataset built from scratch, including a real
terrain-following flight-profile figure derived from
NavigationTrack.clearance_values),
The Airborne Site View (AirborneSite/
AirborneSites reading all four committed synthetic sample surveys,
and a three-panel diagnostic figure calling each technology’s own
literature-grounded plotting function –
plot_ztem_map(),
plot_original_afmag_dual_frequency_profile(),
plot_mobilemt_conductivity_psection() –
directly on the same containers this page builds, with no conversion
step), Technologies, Formats, and Native I/O (the technology/
format registry and native-I/O dispatch, including a real, previously
undocumented naming inconsistency: EMTF.subtype spells the two
AFMAG generations "afmag_original"/"afmag_airmt", while
identify_airborne_technologies() canonicalizes
them to "afmag"/"airmt"), and
Structural Quality Control
(assess_airborne_qc()’s severity philosophy,
demonstrated on a real dataset built with five independent, genuine
structural defects). Every code example across all four deep pages was
verified byte-exact against a live interpreter session before being
committed to the page.
pycsamt.airborne is a new automodule/autosummary reference page
covering pycsamt.airborne’s core containers, site view,
registry/I/O, QC, shared validation helpers, and all three technology
adapters, registered in API reference alongside every other
top-level package.
Finally, Loading electromagnetic data – previously EDI-only in
both content and framing – now states plainly that
ensure_sites() normalizes EMTF-XML exactly like
EDI into the same Sites container (demonstrated again here against
the bundled Gabbs Valley station, with a symmetric write-back example
converting the same Sites to both EDI and EMTF-XML), and introduces
ensure_asites() as the separate,
parallel boundary airborne surveys load through, with a short, real
example showing why an airborne station’s z is always None
rather than a loading defect. read_edi()/
read_edis themselves are unchanged – they remain the deliberately
narrow, EDI-only boundary they always were.
AFMAG motion-noise physics and diagnostics New#
pycsamt.emtools.afmag is a new module bringing AFMAG-specific
processing into pycsamt.emtools, following that package’s
existing conventions throughout: every processing function accepts
sites (coerced through the same
ensure_sites() every other emtools function
uses) and every plot function accepts ax/axes and returns
Axes/Figure – the latter is not just a style guideline, it is
mechanically enforced by the package’s existing
test_plot_api_signatures.py gate, which now also covers this module.
AFMAG is magnetic-field-only – there is no electric-field channel, so
the module is built entirely on Site.tipper (the classical AFMAG tilt-angle readout
is, in modern MT terms, the same object), never on Site.z. Three
pieces:
Motion-coupling physics – a from-scratch, paper-grounded implementation of Liu et al. (2018)’s rotation-matrix method for simulating and removing platform-motion-induced noise:
euler_rotation_matrix(),geomagnetic_field_direction(),coil_normal_direction(),motion_coupling_cosine()/_angle,simulate_motion_induced_voltage(), andcorrect_motion_induced_noise(). These operate on raw attitude (yaw/pitch/roll) and geomagnetic geometry rather than onSites–Sitehas no time axis or attitude field to run them on – and are validated both against hand-derived analytic cases (identity, 90-degree, orthogonality) and against the paper’s own qualitative findings (Fig. 2: theta(t) is linear in roll, and inclination shifts rather than reshapes the curve; yaw has no effect at zero pitch/roll for a z-axis coil).Tilt-angle diagnostics –
afmag_tilt_angles()derives the classical in-phase/quadrature AFMAG tilt angle directly fromSite.tipper, and three new plots present it the way AFMAG data has traditionally been read:plot_afmag_tilt_profile()(the classic flight-line profile),plot_afmag_tilt_psection()(station x log-period pseudosection), andplot_afmag_tilt_polar().Motion-coupling QC – the bridge between the two:
motion_susceptibility_table()scores each station’s exposure to motion noise from a nominal attitude-amplitude envelope and the survey’s geomagnetic geometry, andflag_motion_susceptible_band()is the one mutating,Sites-in/Sites-out function in the module – it masks or drops a susceptible station’s low-frequency tipper band, following the sameensure_sites/mutation contract asnotch_powerline().plot_motion_susceptibility_map()andplot_afmag_correction_comparison()(a before/after/delta pseudosection triptych) visualize both.
No apparent-resistivity/conductivity formula is derived from tilt angle
– unlike full MT, nothing in Ward (1959) or Liu et al. (2018) provides
one, so none is invented – and the module does not attempt the paper’s
literal time-domain “subtract predicted noise from raw movement data”
step against a frequency-domain Sites object, which has no raw time
series to run it on; that math is exposed as the reusable physics
functions above instead.
Native Occam1D Inversion Engine New Docs#
pycsamt.models.occam1d is pyCSAMT’s own Occam1D engine.
Unlike occam2d, which prepares files for an
external Fortran executable, Occam1D’s forward model, analytic
Jacobian, and nonlinear Occam iteration are implemented natively:
Occam1DForwardModel recurses the
isotropic layered-earth impedance from basement to surface (optionally
Numba-compiled, roughly an order of magnitude faster per evaluation
when the optional perf extra is installed), and
Occam1DInversion runs the Lagrange-
multiplier search, scoring every trial candidate through the full
nonlinear forward model rather than a linearized estimate, and
keeping every rejected candidate in the result rather than discarding
it silently. Every station is inverted independently – there is no
lateral mesh – so an entire survey can be built and inverted in two
calls, build_all() /
invert_all(), the latter
optionally dispatching stations to separate processes via
joblib (also part of perf).
The rest of the package rounds out the workflow around that core:
Occam1DConfig for layer geometry and
iteration control; Occam1DInputBuilder
to build native data/model/startup files from EDI or site sources;
Occam1DRunner to drive an external
Occam1D-compatible binary instead of the native engine, when one
is preferred; Occam1DResult and
restart() to load
completed runs and checkpoint/resume in-progress ones; and
PlotModel/PlotResponse/PlotConvergence/PlotSummary for
model, fit, convergence, and combined-summary figures, customizable
through the shared PYCSAMT_OCCAM1D style registry
without touching the inversion itself. mode="determinant" – fitting
the rotation-invariant \(Z_d=\sqrt{-Z_{xy}Z_{yx}}\) response – is
the default, chosen for field data whose true dimensionality is not yet
known; "xy"/"yx" single-polarization modes remain available.
The new Occam1D inversion page walks through
configuration, native-file construction, a single-station inversion,
and batch inversion, with real captured output and figures against
three stations (gv100, gv130, gv163) of the same public
Gabbs Valley survey already used elsewhere in the documentation. The
bundled examples/occam1_demo/ runs the same three-station workflow
end to end from the command line, writing native files, text/JSON
result products, and review figures per station. As with
pycsamt.models.occam1d’s Numba/joblib acceleration, this is
purely additive: the pycsamt invert CLI does not yet cover Occam1D
(--solver there still accepts only occam2d and modem) –
use the Python API shown on the new page until CLI support is added.
One real bug turned up while writing this section: PYCSAMT_OCCAM1D
and the rest of pycsamt.api.occam1d’s public style API were
never wired into pycsamt.api’s top-level namespace, unlike every
other style registry in that package (PYCSAMT_STYLE,
PYCSAMT_MESH, PYCSAMT_SECTION, and so on) – so
from pycsamt.api import PYCSAMT_OCCAM1D, exactly as shown in
examples/occam1_demo/README.md, raised ImportError. Fixed by
adding the same from .occam1d import (...) wiring every sibling
style module already has.
Added#
New Low-confidence point label decluttering –
plot_confidence_profilegainsannotate_low_step, analogous tostation_label_stepbut applied only to the (typically much smaller) subset of points belowci_lo.New EMTF-XML support in ``Site``/``Sites`` –
from_xml(),to_xml(),tf,backend, and typedpycsamt.metadataproperties (site_meta,site_layout,provenance,processing,copyright,quality_meta) onSite;to_emtf_list()andwrite_xml()onSites.to_sites()/ensure_sitesnow recognize.xmlpaths,EMTFobjects, and directories mixing*.ediand*.xmlfiles.New ``examples/emtf_xml_roundtrip_demo/`` – converts three real field stations through EDI -> XML -> EDI via both the
Site/SitesAPI and the rawEMTFdocument API, with full numeric verification including a re-read of the files actually written to disk.New ``pycsamt/emtf/tests/test_fcu_oracle.py`` – validates pycsamt’s EDI<->EMTF-XML conversion against a real, locally compiled EMTF-FCU v4.1 reference implementation; skips (doesn’t fail) when no local build is present.
New ``pycsamt.emtools.afmag`` – AFMAG-specific processing, diagnostics, and plotting: a from-scratch implementation of the Liu et al. (2018) rotation-matrix motion-induced-noise method (
euler_rotation_matrix,motion_coupling_cosine,simulate_motion_induced_voltage,correct_motion_induced_noise, and friends), classical tilt-angle diagnostics fromSite.tipper(afmag_tilt_angles), motion-coupling QC (motion_susceptibility_table,flag_motion_susceptible_band), and six new plots. See the module’s own documentation for the full list.New ``pycsamt.airborne.validation`` – shared boundary-validation and reference-metadata-mapping module for
mobilemt/ztem/afmag, and ``pycsamt.emtf.datatypes.ensure_emtf_datatype_registered``, a shared idempotent EMTF-datatype-registration helper factored out of two adapters that had each reimplemented it.New ``pycsamt.models.occam1d`` – native 1-D Occam inversion engine (forward model, analytic Jacobian, regularization, nonlinear inversion loop, native-file I/O, results, plotting, and validation); see the summary above and Occam1D inversion for the full package map. Optional Numba/joblib acceleration via
pip install pycsamt[perf].New ``examples/occam1_demo/`` – builds and inverts three real Gabbs Valley EDI soundings with the native Occam1D engine end to end, writing native files, text/JSON result products, and review figures.
Fixed#
Fix Station confidence-profile distance defaulted to a hardcoded 200 m spacing – station distance is now derived from real EDI coordinates (east/north, or lat/lon projected through a real UTM transform) whenever at least two stations carry usable ones, cached per EDI object.
spacing_mis only ever a per-station fallback.Fix ``ensure_sites``/``to_sites`` dropped a list of ``Site`` objects – a plain Python list of already-constructed
Siteinstances silently resolved to an emptySites, with no error.Fix ``Sites.ordered()``/``Sites.select()`` silently downgraded XML-native sites to EDI on every non-
inplacecall, including the oneensure_sitesalways performs on its way out.Fix ``Sites.write()`` wrote placeholder files instead of real EDI content – it looked for a nonexistent
EDIFile.to_file()method and had been silently falling back to a one-line placeholder since the method was introduced; the only existing test never checked file content. Now usespycsamt.emtf.converters.edi.write_edi().Fix EDI missing-data sentinel mapped to ``0.0`` instead of ``NaN`` (
pycsamt.seg.edi) – affects any EDI with real gaps (dead bands, dropped channels), not only EMTF-XML code paths.Fix ``compute_resistivity_phase`` rejected legitimate missing uncertainty (
pycsamt.z.resphase) – a valid impedance measurement with an unavailable (NaN) uncertainty raised instead of propagating the missing value, exposed once the EDI sentinel fix above landed.Fix ``pycsamt.api.occam1d``’s public style API was never wired into ``pycsamt.api``’s top-level namespace – unlike every other style registry in the package,
PYCSAMT_OCCAM1Dand friends were not importable viafrom pycsamt.api import PYCSAMT_OCCAM1D, the exact form documented inexamples/occam1_demo/README.md.
Changed#
Enhancement User-controlled station spacing –
station_confidence_table,frequency_confidence_table, andplot_confidence_profilegain aforce_spacingparameter to bypass coordinate lookup entirely and trust a user-suppliedspacing_minstead.Enhancement ``pycsamt.airborne`` guideline-compliance pass –
AirborneEMDatasetnow inheritsCoreObjectinstead ofMTBase(it performs no EM arithmetic itself); the registry definitions andAirborneQCIssuenow inheritPyCSAMTObject, matching the equivalent objects inpycsamt.emtf/pycsamt.metadata;ztemandafmagwere migrated onto the newvalidationmodule, removing several dozen lines of near-duplicate boundary-validation logic. Verified against the full existing test suite at every step; no behaviour change outside theAirborneEMDatasetbase class.
Docs & tooling#
Docs ``user_guide/site/`` updated for EMTF-XML – Site Containers, Site Metadata, Export And Reporting, and Site Selection now cover the
Site/SitesEMTF-XML backend, with real captured output against the bundled Gabbs Valley station. Also fixed a stale cross-reference in Metadata pointing at “the upcoming EMTF XML guide”, which now exists.Docs ``pycsamt.airborne`` docstrings – every public class and function across
base,registry,qc,io, and theztem/afmagadapter and metadata modules gained complete NumPy-style docstrings (Parameters, Returns, Raises, and Notes explaining non-obvious design choices).Build ``pyproject.toml`` gains ``norecursedirs`` excluding the local-only vendored EMTF-FCU checkout from pytest collection – without it, a plain
pytestrun breaks entirely on Windows for anyone who builds that reference oracle locally.Docs ``user_guide/models/occam1d.rst`` – new page covering configuration, native-file construction, single-station and batch inversion, and text/image result products, with real captured output and figures against the Gabbs Valley survey.
Docs ``user_guide/airborne/`` completed (index/overview/ data_model/site/registry_and_io/quality_control, 6 pages) – real captured output and figures against the committed synthetic ZTEM/ AFMAG/MobileMT sample surveys throughout, including a literature- grade three-panel diagnostic composite in The Airborne Site View that calls each technology’s own
pycsamt.emtoolsplotting function directly.Docs ``api/airborne.rst`` – new API reference page for
pycsamt.airborne, registered in API reference.Docs ``user_guide/data_loading.rst`` covers EMTF-XML and airborne loading – documents that
ensure_sitesnormalizes EMTF-XML the same way it normalizes EDI, with a symmetric write-back example, and introducesensure_asitesas airborne surveys’ separate loading boundary.
Compatibility#
This release changes default output for real surveys. Any call to
plot_confidence_profile, station_confidence_table, or
frequency_confidence_table on stations that carry lat/lon but no
east/north attributes – the normal case for ordinary EDI-backed
Site objects – previously plotted a distance axis stretched out by a
uniform 200 m-per-station default; it now plots the real inter-station
distance. This is a correction, not a behaviour change to preserve:
station names, order, and confidence values are unaffected, only the
x-axis distance scale. force_spacing and annotate_low_step are
both new, opt-in parameters with defaults that keep every other existing
call unaffected.
Site/Sites gain new capabilities without removing any existing
ones – every EDI-only call site keeps working exactly as before, and
Site.edi/Site.tf are additive. Any EDI parsed from a file with
genuinely missing periods (rare, but real) now correctly reports NaN
for those instead of a fabricated 0.0; code that assumed EDI-derived
Z/tipper arrays are always fully finite should be checked against this.
Sites.write() now writes real EDI content where it previously wrote
a one-line placeholder – any code that happened to tolerate the
placeholder output will see a real, larger file instead.
pycsamt.emtools.afmag and pycsamt.airborne.validation are both
entirely new, additive modules. The one base-class change in
pycsamt.airborne (AirborneEMDataset
now inherits CoreObject instead of
MTBase) removes access to MTBase’s
electromagnetic numeric methods on that one class; nothing in pycsamt
itself called them there, and this only matters to external code that
did isinstance(dataset, MTBase) or called an MTBase method
directly on an AirborneEMDataset instance.
pycsamt.models.occam1d is an entirely new, additive package with no
interaction with any existing inversion engine; it does not touch
pycsamt invert, occam2d, or
modem. Its Numba/joblib acceleration is part of
the existing, optional perf extra – without it, the package still
works, only slower.