# Transfer-Function Dataset Accessor (ds.tf)

## Overview

MTpy-v2 stores every MT transfer function as an xarray.Dataset whose data variables (transfer_function, transfer_function_error, transfer_function_model_error) use labelled output, input, and period dimensions. The `tf` accessor is registered on every such dataset so that all derived quantities — impedance, phase tensor, tipper, and correction routines — are reachable through a single, discoverable entry point:

`python ds.tf.<property_or_method> `

The accessor is the single source of truth for transfer-function math. Both the MT object and the MTData collection delegate their core operations (rotate, interpolate, remove_static_shift, remove_distortion) to it.

## Architecture

``` xr.Dataset (period × output × input) │ └── .tf ← TFDatasetAccessor (registered via @xr.register_dataset_accessor)

│ ├── Data access │ z(), z_error(), z_model_error() │ tipper(), tipper_error(), tipper_model_error() │ ├── Derived Z quantities (properties, all lazy) │ resistivity, phase │ resistivity_error, phase_error │ resistivity_model_error, phase_model_error │ Per-component aliases: res_xx / res_xy / res_yx / res_yy │ phase_xx / phase_xy / phase_yx / phase_yy │ Determinant variants: det, res_det, phase_det (+ error counterparts) │ ├── Tipper quantities │ t_zx, t_zy (+ error variants) │ tipper_amplitude, tipper_phase, tipper_mag_real/imag, tipper_angle_real/imag │ ├── Phase tensor quantities │ pt_xx / pt_xy / pt_yx / pt_yy (+ error counterparts) │ pt_phimax / pt_phimin, pt_azimuth, pt_skew │ pt_ellipticity, pt_eccentricity, pt_trace, pt_det │ pt_alpha, pt_beta (+ all error counterparts) │ phase_tensor → PhaseTensor object │ invariants → dict of standard PT invariants │ ├── Object converters │ to_z() → Z │ to_tipper() → Tipper │ to_pt() → PhaseTensor │ ├── Builders (write back into the dataset) │ with_z(z_obj) → updates transfer_function* variables │ with_tipper(t_obj) → updates tipper channel block │ with_res_phase(…) → constructs Z from ρ/φ, then writes │ set_resistivity_phase(…) → alias │ set_amp_phase(…) → Tipper setter alias │ set_mag_direction(…) → Tipper setter alias │ ├── Operations (return new Dataset or modify in-place) │ rotate(alpha, coordinate_reference_frame=’ned’, inplace=False) │ interpolate(new_periods, method=’slinear’, inplace=False) │ └── Correction / analysis helpers

remove_ss(ss_x, ss_y, as_dataset=False) remove_distortion(distortion_tensor, as_dataset=False) estimate_dimensionality() estimate_distortion() estimate_depth_of_investigation()

```

### Where the math lives

All numerical formulas are collected in mtpy/core/transfer_function/tf_helpers.py. The legacy Z, Tipper, and PhaseTensor classes import from the same module, so there is exactly one implementation of every formula shared across the entire stack.

## Integration with MT and MTData

### MT

The MT object stores its transfer function as self._transfer_function (an xr.Dataset). The accessor is therefore always available as:

`python mt._transfer_function.tf `

The following MT methods delegate entirely to the accessor:

MT method | Accessor call |

|---|—| | mt.rotate(angle) | self._transfer_function.tf.rotate(angle, inplace=True) | | mt.interpolate(periods) | self._transfer_function.tf.interpolate(periods) | | mt.remove_static_shift(sx, sy) | self._transfer_function.tf.remove_ss(sx, sy, as_dataset=True) | | mt.remove_distortion(D) | self._transfer_function.tf.remove_distortion(D, as_dataset=True) |

### MTData

MTData is a tree-backed collection of MT stations stored as xr.Dataset nodes inside an xr.DataTree. The two per-station static helpers that were previously large self-contained routines now delegate to the accessor:

MTData helper | Accessor call |

|---|—| | _rotate_station_dataset(ds, angle, crf) | ds.load().tf.rotate(angle, coordinate_reference_frame=crf) | | _interpolate_station_dataset(ds, periods) | ds.tf.interpolate(periods) |

Bulk methods rotate(), interpolate(), rotate_dask(), interpolate_dask(), and interpolate_lazy() all flow through these helpers and therefore implicitly use the accessor.

## Usage Examples

### Accessing derived quantities

```python from mtpy import MT

mt = MT(“path/to/station.edi”) mt.read()

ds = mt._transfer_function # xr.Dataset

# Impedance tensor (complex, shape n_periods × 2 × 2) z_array = ds.tf.z()

# Apparent resistivity and phase (shape n_periods × 2 × 2) rho = ds.tf.resistivity phi = ds.tf.phase

# Off-diagonal components only rho_xy = ds.tf.res_xy # shape (n_periods,) phi_yx = ds.tf.phase_yx

# Determinant resistivity rho_det = ds.tf.res_det

# Phase tensor azimuth azimuth = ds.tf.pt_azimuth ```

### Converting to legacy objects

`python z_obj     = ds.tf.to_z()       # Z instance t_obj     = ds.tf.to_tipper()  # Tipper instance pt_obj    = ds.tf.to_pt()      # PhaseTensor instance `

### Rotating a dataset

```python # Returns a new dataset rotated 30 degrees clockwise (NED convention) rotated_ds = ds.tf.rotate(30)

# In-place rotation ds.tf.rotate(30, inplace=True) ```

### Interpolating to a new period grid

```python import numpy as np

new_periods = np.logspace(-3, 3, 50) interp_ds = ds.tf.interpolate(new_periods)

# The resulting dataset has len(new_periods) entries along the period axis print(interp_ds.tf.z().shape) # (50, 2, 2) ```

### Removing static shift and distortion

```python # Remove static shift: sx applied to Ex row, sy to Ey row corrected_ds = ds.tf.remove_ss(sx=0.8, sy=1.2, as_dataset=True)

# Remove a known distortion tensor import numpy as np D = np.array([[1.1, 0.05], [0.02, 0.95]]) undistorted_ds = ds.tf.remove_distortion(D, as_dataset=True) ```

### Updating impedance values (builder pattern)

```python from mtpy.core.transfer_function.z import Z

# Create or modify a Z object then write it back z_obj = ds.tf.to_z() z_obj.z[:, 0, 0] = 0.0 # zero out Zxx ds.tf.with_z(z_obj, inplace=True) ```

### Builder methods (with_*) end-to-end

```python import numpy as np

# Start from an existing station dataset ds = mt._transfer_function

# 1) Build/update impedance directly with with_z z_obj = ds.tf.to_z() z_obj.z[:, 0, 0] = np.nan # clear Zxx z_obj.z[:, 1, 1] = np.nan # clear Zyy ds1 = ds.tf.with_z(z_obj, inplace=False)

# 2) Build/update tipper directly with with_tipper t_obj = ds1.tf.to_tipper() t_obj.tipper[:, 0, 0] *= 0.95 t_obj.tipper[:, 0, 1] *= 1.05 ds2 = ds1.tf.with_tipper(t_obj, inplace=False)

# 3) Construct impedance from resistivity/phase with with_res_phase rho = ds2.tf.resistivity phi = ds2.tf.phase

# Example transformation: increase off-diagonal rho by 10% rho_mod = rho.copy() rho_mod[:, 0, 1] *= 1.10 rho_mod[:, 1, 0] *= 1.10

ds3 = ds2.tf.with_res_phase(

resistivity=rho_mod, phase=phi, period=ds2.period.values, inplace=False,

)

# Optional aliases (equivalent builder entry points) ds4 = ds3.tf.set_resistivity_phase(

resistivity=ds3.tf.resistivity, phase=ds3.tf.phase, period=ds3.period.values, inplace=False,

)

### Tipper builder aliases

```python # Set tipper from amplitude/phase amp = ds.tf.tipper_amplitude phs = ds.tf.tipper_phase ds_amp = ds.tf.set_amp_phase(amp, phs, inplace=False)

# Set tipper from magnitude/direction pairs mag_real = ds_amp.tf.tipper_mag_real ang_real = ds_amp.tf.tipper_angle_real mag_imag = ds_amp.tf.tipper_mag_imag ang_imag = ds_amp.tf.tipper_angle_imag ds_mag = ds_amp.tf.set_mag_direction(

mag_real, ang_real, mag_imag, ang_imag, inplace=False,

)

### Working via MT

The MT API is the recommended entry point for single-station workflows. All the above operations are available directly:

```python from mtpy import MT

mt = MT(“station.edi”) mt.read()

mt.rotate(30) # rotates in place mt.interpolate(np.logspace(-2, 2, 40)) # returns a new MT mt.remove_static_shift(ss_x=0.8, ss_y=1.2) mt.remove_distortion() ```

### Working via MTData

For multi-station workflows use MTData, which applies the same accessor operations to every station in the collection:

```python from mtpy.core import MTData

md = MTData() md.add_stations(mt_list)

# Rotate all stations by 15 degrees md.rotate(15)

# Interpolate all stations to a shared period grid periods = np.logspace(-3, 3, 60) md.interpolate(periods)

# Access the underlying xarray dataset for one station station_ds = md.get_station(“surveyA/st01”) print(station_ds.tf.res_xy) ```

## Testing

The accessor is covered by tests/core/transfer_function/test_tf_accessor.py (26+ test methods) and tests/core/test_mt.py::TestMTAccessorIntegration (14 tests verifying that MT methods correctly round-trip through the accessor).