Source code for mtpy.core.transfer_function.tipper

# -*- coding: utf-8 -*-
"""
Created on Fri Oct  7 23:25:57 2022

@author: jpeacock
"""

# =============================================================================
# Imports
# =============================================================================
from __future__ import annotations

import cmath
import copy
import math

import numpy as np

import mtpy.utils.calculator as MTcc

from .base import TFBase

# =============================================================================


[docs] class Tipper(TFBase): """ Tipper class. Errors are given as standard deviations (sqrt(VAR)). Parameters ---------- tipper : np.ndarray, optional Tipper array in the shape of [Tx, Ty] (nf, 1, 2), by default None tipper_error : np.ndarray, optional Array of estimated tipper errors in the shape of [Tx, Ty] (nf, 1, 2), by default None frequency : np.ndarray, optional Array of frequencies corresponding to the tipper elements (nf), by default None tipper_model_error : np.ndarray, optional Array of model errors in the shape of [Tx, Ty] (nf, 1, 2), by default None """ def __init__( self, tipper: np.ndarray | None = None, tipper_error: np.ndarray | None = None, frequency: np.ndarray | None = None, tipper_model_error: np.ndarray | None = None, ) -> None: """Initialize tipper object.""" super().__init__( tf=tipper, tf_error=tipper_error, tf_model_error=tipper_model_error, frequency=frequency, _name="tipper", _expected_shape=(1, 2), inputs=["x", "y"], outputs=["z"], ) # --- tipper ---- @property def tipper(self) -> np.ndarray | None: """Tipper array.""" if self._has_tf(): return self._dataset.transfer_function.values @tipper.setter def tipper(self, tipper: np.ndarray | None) -> None: """ Set tipper array. Parameters ---------- tipper : np.ndarray or None Tipper array in the shape of [Tx, Ty] (nf, 1, 2) """ old_shape = None if self._has_tf(): old_shape = self._dataset.transfer_function.shape elif self._has_frequency(): old_shape = ( self.frequency.size, self._expected_shape[0], self._expected_shape[1], ) tipper = self._validate_array_input(tipper, "complex", old_shape) if tipper is None: return if self._is_empty(): self._dataset = self._initialize(tf=tipper) else: self._dataset["transfer_function"].loc[self.comps] = tipper # ----tipper error--------------- @property def tipper_error(self) -> np.ndarray | None: """Tipper error.""" if self._has_tf_error(): return self._dataset.transfer_function_error.values @tipper_error.setter def tipper_error(self, tipper_error: np.ndarray | None) -> None: """ Set tipper error array. Parameters ---------- tipper_error : np.ndarray or None Array of estimated tipper errors in the shape of [Tx, Ty] (nf, 1, 2) """ old_shape = None if not self._has_tf_error(): old_shape = self._dataset.transfer_function_error.shape elif self._has_frequency(): old_shape = ( self.frequency.size, self._expected_shape[0], self._expected_shape[1], ) tipper_error = self._validate_array_input(tipper_error, "float", old_shape) if tipper_error is None: return if self._is_empty(): self._dataset = self._initialize(tf_error=tipper_error) else: self._dataset["transfer_function_error"].loc[self.comps] = tipper_error # ----tipper model error--------------------------------------------------------- @property def tipper_model_error(self) -> np.ndarray | None: """Tipper model error.""" if self._has_tf_model_error(): return self._dataset.transfer_function_model_error.values @tipper_model_error.setter def tipper_model_error(self, tipper_model_error: np.ndarray | None) -> None: """ Set tipper model error array. Parameters ---------- tipper_model_error : np.ndarray or None Array of estimated tipper model errors in the shape of [Tx, Ty] (nf, 1, 2) """ old_shape = None if not self._has_tf_error(): old_shape = self._dataset.transfer_function_error.shape elif self._has_frequency(): old_shape = ( self.frequency.size, self._expected_shape[0], self._expected_shape[1], ) tipper_model_error = self._validate_array_input( tipper_model_error, "float", old_shape ) if tipper_model_error is None: return if self._is_empty(): self._dataset = self._initialize(tf_error=tipper_model_error) else: self._dataset["transfer_function_model_error"].loc[ self.comps ] = tipper_model_error # ----amplitude and phase def _compute_amp_phase_error( self, error: np.ndarray | None ) -> tuple[np.ndarray | None, np.ndarray | None]: """ Compute amplitude and phase errors. Parameters ---------- error : np.ndarray or None Error array Returns ------- tuple of np.ndarray or None Amplitude error and phase error arrays """ amplitude_error = None phase_error = None if error is not None: amplitude_error = np.zeros(self.tipper_error.shape) phase_error = np.zeros(self.tipper_error.shape) for idx_f in range(len(self.tipper)): for jj in range(2): if type(self.tipper) == np.ma.core.MaskedArray: if self.tipper.mask[idx_f, 0, jj]: continue r_error, phi_error = MTcc.propagate_error_rect2polar( np.real(self.tipper[idx_f, 0, jj]), error[idx_f, 0, jj], np.imag(self.tipper[idx_f, 0, jj]), error[idx_f, 0, jj], ) amplitude_error[idx_f, 0, jj] = r_error phase_error[idx_f, 0, jj] = phi_error return amplitude_error, phase_error
[docs] def set_amp_phase(self, r: np.ndarray, phi: np.ndarray) -> None: """ Set values for amplitude (r) and phase (phi). Parameters ---------- r : np.ndarray Amplitude array phi : np.ndarray Phase array in degrees Notes ----- Updates the attributes tipper and tipper_error. """ if self.tipper is not None: tipper_new = copy.copy(self.tipper) if self.tipper.shape != r.shape: self.logger.error( 'Error - shape of "r" array does not match shape of ' + "tipper array: %s ; %s" % (str(r.shape), str(self.tipper.shape)) ) return if self.tipper.shape != phi.shape: self.logger.error( 'Error - shape of "phi" array does not match shape of ' + "tipper array: %s ; %s" % (str(phi.shape), str(self.tipper.shape)) ) return else: tipper_new = np.zeros(r.shape, "complex") if r.shape != phi.shape: self.logger.error( 'Error - shape of "phi" array does not match shape ' + 'of "r" array: %s ; %s' % (str(phi.shape), str(r.shape)) ) return # assert real array: if np.linalg.norm(np.imag(r)) != 0: self.logger.error('Error - array "r" is not real valued !') return if np.linalg.norm(np.imag(phi)) != 0: self.logger.error('Error - array "phi" is not real valued !') return for idx_f in range(len(r)): for jj in range(2): tipper_new[idx_f, 0, jj] = cmath.rect( r[idx_f, 0, jj], math.radians(phi[idx_f, 0, jj]), ) self.tipper = tipper_new
# --------------------------------- # properties @property def amplitude(self) -> np.ndarray | None: """Amplitude of tipper.""" if self._has_tf(): return np.abs(self.tipper) @property def phase(self) -> np.ndarray | None: """Phase of tipper in degrees.""" if self._has_tf(): return np.rad2deg(np.angle(self.tipper)) @property def amplitude_error(self) -> np.ndarray | None: """Amplitude error.""" if self._has_tf_error(): return self._compute_amp_phase_error(self.tipper_error)[0] @property def phase_error(self) -> np.ndarray | None: """Phase error in degrees.""" if self._has_tf_error(): return self._compute_amp_phase_error(self.tipper_error)[1] @property def amplitude_model_error(self) -> np.ndarray | None: """Amplitude model error.""" if self._has_tf_model_error(): return self._compute_amp_phase_error(self.tipper_model_error)[0] @property def phase_model_error(self) -> np.ndarray | None: """Phase model error in degrees.""" if self._has_tf_model_error(): return self._compute_amp_phase_error(self.tipper_model_error)[1] # ----magnitude and direction----------------------------------------------
[docs] def set_mag_direction( self, mag_real: np.ndarray, ang_real: np.ndarray, mag_imag: np.ndarray, ang_imag: np.ndarray, ) -> None: """ Compute tipper from magnitude and direction of real and imaginary components. Parameters ---------- mag_real : np.ndarray Magnitude of real component ang_real : np.ndarray Angle of real component mag_imag : np.ndarray Magnitude of imaginary component ang_imag : np.ndarray Angle of imaginary component Notes ----- Updates tipper. No error propagation yet. """ self.tipper[:, 0, 0].real = np.sqrt( (mag_real**2 * np.arctan(ang_real) ** 2) / (1 - np.arctan(ang_real) ** 2) ) self.tipper[:, 0, 1].real = np.sqrt( mag_real**2 / (1 - np.arctan(ang_real) ** 2) ) self.tipper[:, 0, 0].imag = np.sqrt( (mag_imag**2 * np.arctan(ang_imag) ** 2) / (1 - np.arctan(ang_imag) ** 2) ) self.tipper[:, 0, 1].imag = np.sqrt( mag_imag**2 / (1 - np.arctan(ang_imag) ** 2) ) # for consistency recalculate mag and angle self.compute_mag_direction() self.compute_amp_phase()
@property def mag_real(self) -> np.ndarray | None: """Magnitude of real component.""" if self._has_tf(): return np.sqrt( self.tipper[:, 0, 0].real ** 2 + self.tipper[:, 0, 1].real ** 2 ) @property def mag_imag(self) -> np.ndarray | None: """Magnitude of imaginary component.""" if self._has_tf(): return np.sqrt( self.tipper[:, 0, 0].imag ** 2 + self.tipper[:, 0, 1].imag ** 2 ) @property def angle_real(self) -> np.ndarray | None: """Angle of real component in degrees.""" if self._has_tf(): return np.rad2deg( np.arctan2(self.tipper[:, 0, 1].real, self.tipper[:, 0, 0].real) ) @property def angle_imag(self) -> np.ndarray | None: """Angle of imaginary component in degrees.""" if self._has_tf(): return np.rad2deg( np.arctan2(self.tipper[:, 0, 1].imag, self.tipper[:, 0, 0].imag) ) @property def mag_error(self) -> np.ndarray | None: """Magnitude error.""" if self._has_tf_error(): return np.sqrt( self.tipper_error[:, 0, 0] ** 2 + self.tipper_error[:, 0, 1] ** 2 ) @property def angle_error(self) -> np.ndarray | None: """Angle error in degrees.""" if self._has_tf_error(): return np.abs( np.rad2deg( np.arctan(self.tipper_error[:, 0, 0] / self.tipper_error[:, 0, 1]) ) - 45 ) @property def mag_model_error(self) -> np.ndarray | None: """Magnitude model error.""" if self._has_tf_model_error(): return np.sqrt( self.tipper_model_error[:, 0, 0] ** 2 + self.tipper_model_error[:, 0, 1] ** 2 ) @property def angle_model_error(self) -> np.ndarray | None: """Angle model error in degrees.""" if self._has_tf_model_error(): return np.abs( np.rad2deg( np.arctan( self.tipper_model_error[:, 0, 0] / self.tipper_model_error[:, 0, 1] ) ) - 45 )