pycsamt.tdem.transform#

TEM time-domain → frequency-domain transforms.

Three loop configurations are fully supported:

Central / coincident loop (offset = 0)

Receiver at the centre of the transmitter loop. Uses the Ward & Hohmann (1988) late-time formula directly:

\[\rho_a(t) = \left( \frac{M\,\mu_0^{5/2}} {10\sqrt{\pi}\,|\partial B_z/\partial t|\,t^{5/2}} \right)^{2/3}\]
In-loop / large-loop (0 < offset < inner_radius)

Receiver is inside the transmitter loop but not at the centre. A Biot-Savart static-field correction factor \(\eta = H_z^{\rm static}(r_x, r_y)\,/\,H_z^{\rm static}(0,0)\) is applied to the effective transmitter moment:

\[\rho_a(t) = \left( \frac{M\,\eta\,\mu_0^{5/2}} {10\sqrt{\pi}\,|\partial B_z/\partial t|\,t^{5/2}} \right)^{2/3}\]

The Biot-Savart integral is evaluated analytically for rectangular / square loops and numerically for circular loops.

Offset / separated loop (offset inner_radius)

Transmitter and receiver are separated by horizontal distance d. Uses the Ward & Hohmann (1988) offset-dipole formula:

\[\rho_a(t) = \left( \frac{M\,\mu_0^{5/2}} {20\sqrt{\pi}\,|\partial B_z/\partial t|\,d^3\,t^{5/2}} \right)^{2/3}\]
LateTimeTransform

Fast, approximate method — standard industry approach.

FourierTransform

Rigorous Fourier cosine transform with Kramers-Kronig reconstruction. Valid at all time gates; first-order waveform deconvolution for all waveform types (Fitterman & Stewart 1986).

TEMtoEDI

High-level dispatcher → EDICollection.

Classes

FourierTransform([n_freq, freq_min, ...])

Rigorous TDEM → MT impedance via numerical Fourier cosine transform and Kramers-Kronig reconstruction (Meju 1996; Christensen 1990).

LateTimeTransform([freq_convention, ...])

Convert TEM soundings to frequency-domain apparent impedance using the late-time apparent-resistivity approximation.

TEMtoEDI([method, freq_convention, ...])

Convert one or more TEM soundings to a EDICollection.

class pycsamt.tdem.transform.LateTimeTransform(freq_convention='skin_depth', phase_mode='homogeneous', drop_nan=True, loop_geometry_correction=True, in_loop_n_iter=3, waveform_correction=True, verbose=0, logger=None)[source]#

Bases: PyCSAMTObject

Convert TEM soundings to frequency-domain apparent impedance using the late-time apparent-resistivity approximation.

Three loop configurations are handled automatically based on the offset and loop_dims attributes of each TEMSounding:

  • central / coincident loop (offset == 0): standard Ward & Hohmann (1988) formula.

  • in-loop off-centre receiver (0 < offset < inner_radius): Biot-Savart geometric correction applied to the effective moment.

  • separated / offset loop (offset inner_radius): offset dipole formula with \(d^3\) denominator.

Parameters:
  • freq_convention (str) – Time-to-pseudo-frequency mapping: "skin_depth" (default) or "diffusion".

  • phase_mode (str) – MT phase estimate: "homogeneous" (default, 45°) or "weidelt" (dispersion relation).

  • drop_nan (bool) – Remove gates with undefined ρ_a. Default True.

  • loop_geometry_correction (bool) – Apply the Biot-Savart in-loop correction when the receiver is off-centre. Set False to use the central-loop formula for all configurations (legacy behaviour). Default True.

  • in_loop_n_iter (int) – Number of iterations for the time-dependent geometry correction (see _rho_a_in_loop()). 0 uses the static Biot-Savart approximation only. Default 3.

  • waveform_correction (bool) – Apply first-order waveform deconvolution when sounding.waveform is set (Fitterman & Stewart 1986). Supports RampWaveform (analytic), HalfSineWaveform, and CustomWaveform (both numerical). SquareWaveform is a no-op (ideal step-off). Default True.

  • verbose (int)

  • logger (object | None)

Examples

Central-loop sounding:

>>> import numpy as np
>>> from pycsamt.tdem import TEMSounding
>>> from pycsamt.tdem.transform import LateTimeTransform
>>> t = np.logspace(-5, -2, 30)
>>> M = 8.0 * 100.0 ** 2
>>> MU0_loc = 4e-7 * np.pi
>>> dBdt = M * MU0_loc**2.5 / (10*np.sqrt(np.pi)*100.**1.5*t**2.5)
>>> snd = TEMSounding(t, dBdt, current=8.0, tx_area=100.**2)
>>> tr = LateTimeTransform()
>>> result = tr.transform(snd)
>>> result["freq"].shape
(30,)

Offset-loop sounding:

>>> snd_off = TEMSounding(t, dBdt, current=8.0, tx_area=100.**2,
...                       offset=500.0, loop_dims=(100.0,))
>>> result_off = tr.transform(snd_off)
transform(sounding)[source]#

Transform one TEMSounding to frequency-domain arrays.

Parameters:

sounding (TEMSounding)

Returns:

freq, Z, Z_err, rho_a, phase_xy, station_name, x, y, elevation, loop_config.

Return type:

dict with keys

transform_many(soundings)[source]#

Transform a list of soundings. Returns a list of result dicts.

Parameters:

soundings (Sequence[TEMSounding])

Return type:

list[dict]

class pycsamt.tdem.transform.FourierTransform(n_freq=50, freq_min=None, freq_max=None, n_aux=200, n_interp=400, waveform_correction=True, drop_nan=True, verbose=0, logger=None)[source]#

Bases: PyCSAMTObject

Rigorous TDEM → MT impedance via numerical Fourier cosine transform and Kramers-Kronig reconstruction (Meju 1996; Christensen 1990).

The 1-D step-off forward relation gives:

\[\frac{\partial B_z}{\partial t}(t) = \frac{2M}{\pi} \int_0^\infty \omega\,\mathrm{Im}[K(\omega)]\,\cos(\omega t)\,\mathrm{d}\omega\]

Inverting by the half-range cosine transform:

\[\mathrm{Im}[K(\omega)] = \frac{1}{M\omega} \int_0^\infty \frac{\partial B_z}{\partial t}(t) \cos(\omega t)\,\mathrm{d}t\]

The real part \(\mathrm{Re}[K(\omega)]\) is recovered via the Kramers-Kronig relation, then:

\[Z(\omega) = i\omega\mu_0 K(\omega),\qquad \rho_a(\omega) = \omega\mu_0\,|K(\omega)|^2\]
Parameters:
  • n_freq (int) – Number of output frequency points. Default 50.

  • freq_min (float or None) – Minimum output frequency [Hz] (None → auto from time gates).

  • freq_max (float or None) – Maximum output frequency [Hz] (None → auto from time gates).

  • n_aux (int) – Auxiliary frequency points for the K-K integral (≥ 4 × n_freq). Default 200.

  • n_interp (int) – Interpolation grid for the cosine transform (0 = disabled). Default 400.

  • waveform_correction (bool) – Apply first-order waveform deconvolution (Fitterman & Stewart 1986) when sounding.waveform is set. Supports all waveform types via _apply_waveform_correction(). Default True.

  • drop_nan (bool) – Remove output frequencies with undefined ρ_a. Default True.

  • verbose (int)

  • logger (object | None)

Examples

>>> import numpy as np
>>> from pycsamt.tdem import TEMSounding
>>> from pycsamt.tdem.transform import FourierTransform
>>> t = np.logspace(-5, -2, 40)
>>> MU0_v = 4e-7 * np.pi; rho0 = 100.0; M = 8.0 * 1e4
>>> dBdt = M * MU0_v**2.5 / (10*np.sqrt(np.pi)*rho0**1.5*t**2.5)
>>> snd = TEMSounding(t, dBdt, current=8.0, tx_area=1e4)
>>> res = FourierTransform(n_freq=30).transform(snd)
>>> np.isfinite(res["rho_a"]).all()
True

References

transform(sounding)[source]#

Transform one TEMSounding to frequency-domain arrays.

Returns the same dict structure as LateTimeTransform.transform() plus key "method": "fourier".

Parameters:

sounding (TEMSounding)

Return type:

dict

transform_many(soundings)[source]#

Transform a list of soundings. Returns a list of result dicts.

Parameters:

soundings (Sequence[TEMSounding])

Return type:

list[dict]

class pycsamt.tdem.transform.TEMtoEDI(method='late_time', freq_convention='skin_depth', phase_mode='homogeneous', loop_geometry_correction=True, out_dir='edi_out/tem', verbose=0, logger=None)[source]#

Bases: PyCSAMTObject

Convert one or more TEM soundings to a EDICollection.

Wraps either LateTimeTransform (default) or FourierTransform and writes one synthetic EDI file per sounding into out_dir.

All three loop configurations (central, in-loop, offset) are handled automatically via LateTimeTransform.

Parameters:
  • method (str) – "late_time" (default) or "fourier".

  • freq_convention (str) – "skin_depth" (default) or "diffusion".

  • phase_mode (str) – "homogeneous" (default) or "weidelt".

  • loop_geometry_correction (bool) – Forward to LateTimeTransform. Default True.

  • out_dir (str or Path) – EDI output directory. Default "edi_out/tem".

  • verbose (int) – Verbosity. Default 0.

  • logger (object | None)

Examples

Central-loop:

>>> from pycsamt.tdem import TEMSounding, TEMtoEDI
>>> import numpy as np
>>> t = np.logspace(-5, -2, 25)
>>> dBdt = 5e-5 * t ** (-5.0 / 2.0)
>>> snd = TEMSounding(t, dBdt, current=8.0, tx_area=1e4,
...                   station_name="S01")
>>> conv = TEMtoEDI(method="late_time")
>>> coll = conv.transform(snd)

Offset (separated) loop — just set offset:

>>> snd_off = TEMSounding(t, dBdt, current=8.0, tx_area=1e4,
...                       offset=500.0, loop_dims=(100.0,),
...                       station_name="S01")
>>> coll_off = conv.transform(snd_off)
transform(sounding)[source]#
Parameters:

sounding (TEMSounding)

transform_many(soundings)[source]#
Parameters:

soundings (Sequence[TEMSounding])

save(sounding_or_soundings, path=None)[source]#

Transform and write EDI files. Returns list of written paths.

pycsamt.tdem.transform.build_adjacency(coords, radius, *, self_loops=True, normalise=True)#

Build a symmetric adjacency matrix from 2-D station coordinates.

Parameters:
  • coords (ndarray, shape (n_stations, 2)) – Station (x, y) positions in any consistent unit (metres, degrees).

  • radius (float) – Maximum inter-station distance for an edge to exist. Uses the same unit as coords.

  • self_loops (bool) – If True (default), add \(\tilde{A} = A + I\).

  • normalise (bool) – If True (default), apply symmetric normalisation \(\tilde{D}^{-1/2}\tilde{A}\tilde{D}^{-1/2}\).

Returns:

A – Adjacency matrix, optionally normalised.

Return type:

ndarray, shape (n_stations, n_stations), float32