2.14.4.4. pycsamt.emtools.csumt#

Controlled-source ultra-audio MT depth and survey-planning tools.

The module converts apparent resistivity and transmitter frequency into Bostick depth estimates, evaluates vertical resolution and depth coverage, designs CSUMT frequency schedules, and plots depth sections for survey sites.

Module Attributes

BOSTICK_CONST

Bostick depth constant in metres:

F_MIN_CSUMT

9.6 kHz (zhang2025).

F_MAX_CSUMT

614.4 kHz (zhang2025).

Functions

bostick_depth(sites, *[, recursive, on_dup, ...])

Bostick depth estimate per station per frequency from measured data.

bostick_depth_from_rho(rho, freq)

Bostick depth estimate D(f) from apparent resistivity.

depth_coverage_table(sites, *[, recursive, ...])

Summary depth-coverage statistics per station.

frequency_for_depth(depth_m, rho)

Invert the Bostick formula: return the frequency (Hz) that maps to a given depth for a background resistivity rho.

frequency_schedule(target_depths, ...[, ...])

Design a CSUMT frequency schedule that samples a set of target depths.

plot_depth_coverage_ranking(sites, *[, ...])

Horizontal ranking of the deepest Bostick depth reached per station.

plot_depth_section(sites, *[, log_color, ...])

Pseudosection of Bostick depth across stations and periods/frequencies.

plot_frequency_schedule(target_depths, ...)

Visualize which requested target depths survive the CSUMT band filter applied by frequency_schedule().

vertical_resolution(sites, *[, ...])

Vertical resolution ΔD between adjacent frequencies per station.

vertical_resolution_pair(rho, f_lo, f_hi)

Vertical resolution ΔD between two adjacent frequencies.

pycsamt.emtools.csumt.bostick_depth_from_rho(rho, freq)[source]

Bostick depth estimate D(f) from apparent resistivity.

D(f) = 356 × √(ρ_a / f) [metres]

Parameters:
  • rho (float or array) – Apparent resistivity ρ_a in Ω·m. Broadcastable with freq.

  • freq (float or array) – Frequency in Hz.

Returns:

Depth in metres, same shape as broadcast of rho and freq.

Return type:

numpy.ndarray

References

Zhang et al. (2025), Eq. (1), Measurement.

pycsamt.emtools.csumt.vertical_resolution_pair(rho, f_lo, f_hi)[source]

Vertical resolution ΔD between two adjacent frequencies.

ΔD = 356 × √ρ_c × (1/√f_lo − 1/√f_hi) [metres; f_lo < f_hi]

Parameters:
  • rho (float) – Characteristic (apparent) resistivity ρ_c in Ω·m.

  • f_lo (float) – Lower frequency in Hz (deeper penetration).

  • f_hi (float) – Higher frequency in Hz (shallower penetration).

Returns:

Vertical resolution in metres. Positive when f_lo < f_hi.

Return type:

float

References

Zhang et al. (2025), Eq. (2), Measurement.

pycsamt.emtools.csumt.frequency_for_depth(depth_m, rho)[source]

Invert the Bostick formula: return the frequency (Hz) that maps to a given depth for a background resistivity rho.

f = ρ × (356 / D)² [Hz]

Parameters:
  • depth_m (float or array) – Target depth(s) in metres.

  • rho (float) – Background apparent resistivity in Ω·m.

Returns:

Frequency in Hz, same shape as depth_m.

Return type:

numpy.ndarray

pycsamt.emtools.csumt.frequency_schedule(target_depths, rho_estimate, *, f_min=9600.0, f_max=614400.0, min_resolution_m=None, fill_decades=False, per_decade=3, as_khz=False)[source]

Design a CSUMT frequency schedule that samples a set of target depths.

Each target depth is converted to a frequency via frequency_for_depth(), then clipped to [f_min, f_max]. Optionally, intermediate frequencies can be inserted to guarantee a minimum vertical resolution between consecutive depth levels.

Parameters:
  • target_depths (float or array) – Target depths in metres (deepest first or any order — sorted internally).

  • rho_estimate (float) – Background apparent resistivity ρ (Ω·m) used for the conversion.

  • f_min (float, default=9.6e3) – Minimum transmitter frequency in Hz (lower bound of CSUMT range).

  • f_max (float, default=614.4e3) – Maximum transmitter frequency in Hz (upper bound of CSUMT range).

  • min_resolution_m (float or None) – If given, insert additional frequencies between adjacent target depths whenever their vertical resolution would exceed this value.

  • fill_decades (bool, default=False) – If True, add per_decade log-spaced frequencies within each decade of the schedule to smooth coverage.

  • per_decade (int, default=3) – Number of extra frequencies to insert per decade when fill_decades is True.

  • as_khz (bool, default=False) – If True, return frequencies in kHz instead of Hz.

Returns:

Sorted frequencies in Hz (or kHz if as_khz is True).

Return type:

numpy.ndarray

References

Zhang et al. (2025), “Controlled source ultra-audio frequency magnetotellurics (CSUMT) transmitter”, Measurement.

pycsamt.emtools.csumt.plot_frequency_schedule(target_depths, rho_estimate, *, f_min=9600.0, f_max=614400.0, ax=None, figsize=(7.0, 5.0), title='Frequency schedule from target depths')[source]

Visualize which requested target depths survive the CSUMT band filter applied by frequency_schedule().

Each target depth is converted to its raw frequency with frequency_for_depth() and drawn as an open circle. Targets whose raw frequency falls inside [f_min, f_max] are additionally marked with a cross – exactly the filter frequency_schedule() applies before any min_resolution_m padding. This makes the schedule’s silent clipping of unreachable targets visible instead of only reporting a row count.

Parameters:
  • target_depths (float or array) – Target depths in metres, as passed to frequency_schedule().

  • rho_estimate (float) – Background apparent resistivity ρ (Ω·m).

  • f_min (float) – CSUMT transmitter band, default F_MIN_CSUMT / F_MAX_CSUMT.

  • f_max (float) – CSUMT transmitter band, default F_MIN_CSUMT / F_MAX_CSUMT.

  • ax (matplotlib.axes.Axes, optional)

  • figsize ((float, float), default=(7, 5))

  • title (str)

Return type:

matplotlib.axes.Axes

Examples

>>> from pycsamt.emtools.csumt import plot_frequency_schedule
>>> ax = plot_frequency_schedule([10.0, 20.0, 35.0, 50.0, 65.0], 300.0)
pycsamt.emtools.csumt.bostick_depth(sites, *, recursive=True, on_dup='replace', strict=False, verbose=0)[source]

Bostick depth estimate per station per frequency from measured data.

Uses the apparent resistivity derived from the off-diagonal impedance tensor components (geometric mean):

D(f) = 356 × √(ρ_a(f) / f) [metres]

Parameters:
  • sites (path, EDI-like, Sites, or iterable) – Any input accepted by ensure_sites().

  • recursive (bool) – Forwarded to ensure_sites().

  • on_dup (str) – Forwarded to ensure_sites().

  • strict (bool) – Forwarded to ensure_sites().

  • verbose (int) – Forwarded to ensure_sites().

Returns:

One row per (station, frequency) with columns: station, freq_hz, period_s, rho_a_ohmm, depth_m.

Return type:

pandas.DataFrame

References

Zhang et al. (2025), Eq. (1).

pycsamt.emtools.csumt.vertical_resolution(sites, *, rho_override=None, recursive=True, on_dup='replace', strict=False, verbose=0)[source]

Vertical resolution ΔD between adjacent frequencies per station.

For each consecutive pair (f_lo, f_hi) in the station’s frequency list (sorted ascending), computes:

ΔD = D(f_lo) − D(f_hi) [metres]

using the Bostick depths derived from the measured ρ_a. Alternatively, supply rho_override to use a fixed background resistivity with the analytical formula 356 × √ρ × (1/√f_lo 1/√f_hi).

Parameters:
  • sites (path, EDI-like, Sites, or iterable)

  • rho_override (float or None) – If given, use this constant resistivity for all ΔD calculations (analytical formula) instead of the per-frequency ρ_a.

  • recursive (bool) – Forwarded to ensure_sites().

  • on_dup (str) – Forwarded to ensure_sites().

  • strict (bool) – Forwarded to ensure_sites().

  • verbose (int) – Forwarded to ensure_sites().

Returns:

Columns: station, freq_lo_hz, freq_hi_hz, depth_lo_m, depth_hi_m, delta_depth_m, rho_a_ohmm.

Return type:

pandas.DataFrame

pycsamt.emtools.csumt.depth_coverage_table(sites, *, recursive=True, on_dup='replace', strict=False, verbose=0)[source]

Summary depth-coverage statistics per station.

Parameters:
  • sites (path, EDI-like, Sites, or iterable)

  • recursive (bool) – Forwarded to ensure_sites().

  • on_dup (str) – Forwarded to ensure_sites().

  • strict (bool) – Forwarded to ensure_sites().

  • verbose (int) – Forwarded to ensure_sites().

Returns:

One row per station with columns: station, n_freq, freq_min_hz, freq_max_hz, depth_min_m, depth_max_m, mean_resolution_m, median_resolution_m.

Return type:

pandas.DataFrame

pycsamt.emtools.csumt.plot_depth_section(sites, *, log_color=True, sort_by=None, cmap='viridis_r', figsize=(10.0, 5.0), period_axis=True, recursive=True, on_dup='replace', strict=False, verbose=0, ax=None)[source]

Pseudosection of Bostick depth across stations and periods/frequencies.

Each cell (station × period) is coloured by the Bostick depth D(f) = 356 √(ρ_a / f).

Parameters:
  • sites (path, EDI-like, Sites, or iterable)

  • log_color (bool, default=True) – Color by log10(depth) instead of depth.

  • sort_by ({"auto", "chainage", "input", "name", "lon", "lat"}, optional) – Station ordering along the x-axis. None inherits pycsamt.api.PYCSAMT_ORDERING.

  • cmap (str, default="viridis_r") – Matplotlib colormap name.

  • figsize ((float, float), default=(10, 5))

  • period_axis (bool, default=True) – If True y-axis is period (s); otherwise frequency (Hz).

  • recursive (bool) – Forwarded to ensure_sites().

  • on_dup (str) – Forwarded to ensure_sites().

  • strict (bool) – Forwarded to ensure_sites().

  • verbose (int) – Forwarded to ensure_sites().

  • ax (matplotlib.axes.Axes, optional)

Return type:

matplotlib.axes.Axes

pycsamt.emtools.csumt.plot_depth_coverage_ranking(sites, *, depth_unit='km', flag_coarse_resolution=True, color_fine='#2e6f9e', color_coarse='#c0392b', title='Per-station Bostick depth coverage', ax=None, figsize=None, recursive=True, on_dup='replace', strict=False, verbose=0)[source]

Horizontal ranking of the deepest Bostick depth reached per station.

Reduces depth_coverage_table() to one horizontal bar per station, sorted by depth_max_m descending – the fastest way to see which stations drive a line’s overall depth coverage. When flag_coarse_resolution is True, stations whose median_resolution_m is coarser than the survey-wide median are drawn in color_coarse rather than color_fine, so a station that only reaches deep with coarse vertical resolution is visually distinguished from one that reaches comparable depth with fine resolution.

Parameters:
  • sites (path, EDI-like, Sites, or iterable) – Any input accepted by ensure_sites().

  • depth_unit ({"km", "m"}, default="km") – Unit for the plotted depth axis.

  • flag_coarse_resolution (bool, default=True) – Colour bars by whether the station’s median vertical resolution exceeds the survey-wide median.

  • color_fine (str) – Bar colours for resolution at-or-finer than, and coarser than, the survey median.

  • color_coarse (str) – Bar colours for resolution at-or-finer than, and coarser than, the survey median.

  • title (str)

  • ax (matplotlib.axes.Axes, optional)

  • figsize ((float, float), optional) – Defaults to a height that grows with the station count.

  • recursive (bool) – Forwarded to ensure_sites().

  • on_dup (str) – Forwarded to ensure_sites().

  • strict (bool) – Forwarded to ensure_sites().

  • verbose (int) – Forwarded to ensure_sites().

Return type:

matplotlib.axes.Axes

Examples

>>> from pycsamt.emtools.csumt import plot_depth_coverage_ranking
>>> ax = plot_depth_coverage_ranking("data/AMT/WILLY_DATA/L18PLT")