Source code for pygimli.physics.SIP.models

#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""Spectral induced polarization (SIP) relaxations models."""

from math import pi
import numpy as np
import pygimli as pg


[docs] def ColeColeRho(f, rho, m, tau, c, a=1): pg.deprecated("Please use modelColeColeRho instead of ColeColeRho.") return modelColeColeRho(f, rho, m, tau, c, a)
[docs] def modelColeColeRho(f, rho, m, tau, c, a=1): r"""Frequency-domain Cole-Cole impedance model after Pelton et al. (1978). Frequency-domain Cole-Cole impedance model after Pelton et al. (1978) :cite:`PeltonWarHal1978` .. math:: Z(\omega) & = \rho_0\left[1 - m \left(1 - \frac{1}{1+(\text{i}\omega\tau)^c}\right)\right] \\ \quad \text{with}\quad m & = \frac{1}{1+\frac{\rho_0}{\rho_1}} \quad \text{and}\quad \omega =2\pi f * :math:`Z(\omega)` - Complex impedance per 1A current injection * :math:`f` - Frequency * :math:`\rho_0` -- Background resistivity states the unblocked pore path * :math:`\rho_1` -- Resistance of the solution in the blocked pore passages * :math:`m` -- Chargeability after Seigel (1959) :cite:`Seigel1959` as being the ratio of voltage immediately after, to the voltage immediately before cessation of an infinitely long charging current. * :math:`\tau` -- 'Time constant' relaxation time [s] for 1/e decay * :math:`c` - Rate of charge accumulation. Cole-Cole exponent typically [0.1 .. 0.6] Examples -------- >>> import numpy as np >>> import pygimli as pg >>> from pygimli.physics.SIP import modelColeColeRho >>> f = np.logspace(-2, 5, 100) >>> m = np.linspace(0.1, 0.9, 5) >>> tau = 0.01 >>> fImMin = 1/(tau*2*np.pi) >>> fig, axs = pg.plt.subplots(1, 2) >>> ax1 = axs[0] >>> ax2 = axs[0].twinx() >>> ax3 = axs[1] >>> ax4 = axs[1].twinx() >>> for i in range(len(m)): ... Z = modelColeColeRho(f, rho=1, m=m[i], tau=tau, c=0.5) ... _= ax1.loglog(f, np.abs(Z), color='black') ... _= ax2.loglog(f, -np.angle(Z)*1000, color='b') ... _= ax3.loglog(f, Z.real, color='g') ... _= ax4.semilogx(f, Z.imag, color='r') ... _= ax4.plot([fImMin, fImMin], [-0.2, 0.], color='r') >>> _ = ax4.text(fImMin, -0.1, r"$f($min($Z''$))=$\frac{1}{2*\pi\tau}$", color='r') >>> _ = ax4.text(0.1, -0.17, r"$f($min[$Z''$])=$\frac{1}{2\pi\tau}$", color='r') >>> _ = ax1.set_ylabel('Amplitude $|Z(f)|$', color='black') >>> _ = ax1.set_xlabel('Frequency $f$ [Hz]') >>> _ = ax1.set_ylim(1e-2, 1) >>> _ = ax2.set_ylabel(r'- Phase $\varphi$ [mrad]', color='b') >>> _ = ax2.set_ylim(1, 1e3) >>> _ = ax3.set_ylabel('re $Z(f)$', color='g') >>> _ = ax4.set_ylabel('im $Z(f)$', color='r') >>> _ = ax3.set_xlabel('Frequency $f$ [Hz]') >>> _ = ax3.set_ylim(1e-2, 1) >>> _ = ax4.set_ylim(-0.2, 0) >>> pg.plt.show() """ z = (1. - m * (1. - relaxationTerm(f, tau, c, a))) * rho if np.isnan(z).any(): print(f, 'rho', rho, 'm', m, 'tau', tau, 'c', c) pg.critical(z) return z
[docs] def ColeColeRhoDouble(f, rho, m1, t1, c1, m2, t2, c2): pg.deprecated("Use modelColeColeRhoDouble instead of ColeColeRhoDouble.") return modelColeColeRhoDouble(f, rho, m1, t1, c1, m2, t2, c2)
[docs] def modelColeColeRhoDouble(f, rho, m1, t1, c1, m2, t2, c2, a=1, mult=False): """Frequency-domain double Cole-Cole resistivity (impedance) model. Frequency-domain Double Cole-Cole impedance model returns the sum of two Cole-Cole Models with a common amplitude. Z = rho * (Z1(Cole-Cole) + Z2(Cole-Cole)) """ Z1 = modelColeColeRho(f, rho=1, m=m1, tau=t1, c=c1, a=a) Z2 = modelColeColeRho(f, rho=1, m=m2, tau=t2, c=c2, a=a) if mult: return rho * Z1 * Z2 else: return rho * (Z1 + Z2 - 1)
[docs] def ColeColeSigma(f, sigma, m, tau, c, a=1): pg.deprecated("Please use modelColeColeSigma instead of ColeColeSigma.") return modelColeColeSigma(f, sigma, m, tau, c, a)
[docs] def modelColeColeSigma(f, sigma, m, tau, c, a=1): """Complex-valued conductivity (admittance) Cole-Cole model.""" return (1. + m / (1-m) * (1. - relaxationTerm(f, tau, c, a))) * sigma
def modelColeColeSigmaTauRho(f, sigma, m, tau, c, a=1): """Complex-valued conductivity (admittance) Cole-Cole model.""" return (1. + m / (1-m) * (1. - relaxationTerm(f, tau, c, a)*(1-m))) * sigma
[docs] def modelColeColeSigmaDouble(f, sigma, m1, t1, c1, m2, t2, c2, a=1, tauRho=True): """Complex-valued double added conductivity (admittance) model.""" if tauRho: R1 = 1. - relaxationTerm(f, tau=t1, c=c1, a=a, p=1-m1) R2 = 1. - relaxationTerm(f, tau=t2, c=c2, a=a, p=1-m2) return sigma * (1. + m1 / (1 - m1) * R1 + m2 / (1 - m2) * R2) else: A1 = modelColeColeSigma(f, sigma=1, m=m1, tau=t1, c=c1, a=a) A2 = modelColeColeSigma(f, sigma=1, m=m2, tau=t2, c=c2, a=a) return (A1 + A2 - 1.) * sigma
[docs] def tauRhoToTauSigma(tRho, m, c): r"""Convert :math:`\tau_{\rho}` to :math:`\tau_{\sigma}` Cole-Cole model. .. math:: \tau_{\sigma} = \tau_{\rho}/(1-m)^{\frac{1}{c}} Examples -------- >>> import numpy as np >>> import pygimli as pg >>> from pygimli.physics.SIP import modelColeColeRho, modelColeColeSigma >>> from pygimli.physics.SIP import tauRhoToTauSigma >>> tr = 1. >>> Z = modelColeColeRho(1e5, rho=10.0, m=0.5, tau=tr, c=0.5) >>> ts = tauRhoToTauSigma(tr, m=0.5, c=0.5) >>> S = modelColeColeSigma(1e5, sigma=0.1, m=0.5, tau=ts, c=0.5) >>> abs(1.0/S - Z) < 1e-12 True >>> float(np.angle(1.0/S / Z)) < 1e-12 True """ return tRho * (1-m) ** (1/c)
def relaxationTerm(f, tau, c=1., a=1., p=1.): r"""Auxiliary function for Debye type relaxation term of the basic type. .. math:: A(f,\tau,c) = \frac{1}{1+(\jmath 2\pi f \tau)^c} or the more generalized term .. math:: A(f,\tau,c,a,p) = \frac{1}{(1+(\jmath 2\pi f \tau)^c p)^a} With c=1 and a one yields the Cole-Davidson model. With p=(1-m) one can account for the difference of sigma and rho tau. """ return 1. / ((f * 2. * pi * tau * 1j)**c * p + 1.)**a def DebyeRelaxation(f, tau, m): pg.deprecated("Please use relaxationDebye instead of DebyeRelaxation.") return relaxationDebye(f, tau, m) def relaxationDebye(f, tau, m): """Complex-valued single Debye relaxation term with chargeability.""" return 1. - (1. - relaxationTerm(f, tau)) * m def WarbugRelaxation(f, tau, m): pg.deprecated("Please use relaxationWarbug instead of WarbugRelaxation.") return relaxationWarbug(f, tau, m) def relaxationWarbug(f, tau, m): """Complex-valued single Debye relaxation term with chargeability.""" return 1. - (1. - relaxationTerm(f, tau, c=0.5)) * m
[docs] def ColeColeEpsilon(f, e0, eInf, tau, alpha): pg.deprecated("Use modelColeColeEpsilon instead of ColeColeEpsilon.") return modelColeColeEpsilon(f, e0, eInf, tau, alpha)
[docs] def modelColeColeEpsilon(f, e0, eInf, tau, alpha): """Original complex-valued permittivity formulation (Cole&Cole, 1941).""" return (e0 - eInf) * relaxationTerm(f, tau, c=1./alpha) + eInf
[docs] def ColeCole(f, R, m, tau, c, a=1): pg.deprecated("Please use modelColeColeRho instead of ColeCole.") return modelColeColeRho(f, R, m, tau, c, a)
[docs] def ColeDavidson(f, R, m, tau, a=1): pg.deprecated("Please use modelColeDavidson instead of ColeDavidson.") return modelColeDavidson(f, R, m, tau, a)
[docs] def modelColeDavidson(f, R, m, tau, a=1): """For backward compatibility.""" return ColeCole(f, R, m, tau, c=1, a=a)
[docs] class ColeColePhi(pg.core.ModellingBase): r"""Cole-Cole model with EM term after Pelton et al. (1978). Modelling operator for the Frequency Domain :py:mod:`Cole-Cole <pygimli.physics.SIP.modelColeColeRho>` impedance model using :py:mod:`pygimli.physics.SIP.modelColeColeRho` after Pelton et al. (1978) :cite:`PeltonWarHal1978` * :math:`\textbf{m} =\{ m, \tau, c\}` Modelling parameter for the Cole-Cole model with :math:`\rho_0 = 1` * :math:`\textbf{d} =\{\varphi_i(f_i)\}` Modelling response for all given frequencies as negative phase angles :math:`\varphi(f) = -tan^{-1}\frac{\text{Im}\,Z(f)}{\text{Re}\,Z(f)}` and :math:`Z(f, \rho_0=1, m, \tau, c) =` Cole-Cole impedance. """
[docs] def __init__(self, f, verbose=False): """Setup class by specifying the frequency.""" super(ColeColePhi, self).__init__(self, verbose) self.f_ = f self.setMesh(pg.meshtools.createMesh1D(1, 3))
[docs] def response(self, par): """Phase angle of the model.""" spec = modelColeColeRho(self.f_, 1.0, par[0], par[1], par[2]) return -np.angle(spec)
class DoubleColeCole(pg.Modelling): r"""Complex double Cole-Cole model with EM term after Pelton et al. (1978). Modelling operator for the Frequency Domain :py:mod:`Cole-Cole <pygimli.physics.SIP.modelColeColeRho>` impedance model using :py:mod:`pygimli.physics.SIP.modelColeColeRho` after Pelton et al. (1978) :cite:`PeltonWarHal1978` * :math:`\textbf{m} =\{ m_1, \tau_1, c_1, m_2, \tau_2, c_2\}` Modelling parameter for the Cole-Cole model with :math:`\rho_0 = 1` * :math:`\textbf{d} =\{\varphi_i(f_i)\}` Modelling Response for all given frequencies as negative phase angles :math:`\varphi(f) = \varphi_1(Z_1(f))+\varphi_2(Z_2(f)) = -tan^{-1}\frac{\text{Im}\,(Z_1Z_2)}{\text{Re}\,(Z_1Z_2)}` and :math:`Z_1(f, \rho_0=1, m_1, \tau_1, c_1)` and :math:`Z_2(f, \rho_0=1, m_2, \tau_2, c_2)` ColeCole impedances. """ def __init__(self, f, rho=True, tauRho=True, mult=False, aphi=True, verbose=False): """Setup class by specifying the frequency.""" super().__init__(verbose=verbose) self.f_ = f # save frequencies self.setMesh(pg.meshtools.createMesh1D(1, 7)) # 4 single parameters self.rho = rho self.tauRho = tauRho self.mult = mult self.aphi = aphi def response(self, par): """Amplitude/phase or real/imag angle of the model.""" if self.rho: out = modelColeColeRhoDouble(self.f_, *par, a=1, mult=self.mult) else: out = modelColeColeSigmaDouble(self.f_, *par, a=1, tauRho=self.tauRho) if self.aphi: return np.hstack((np.abs(out), np.abs(np.angle(out)))) else: return np.hstack((out.real, np.abs(out.imag)))
[docs] class DoubleColeColePhi(pg.core.ModellingBase): r"""Double Cole-Cole model after Pelton et al. (1978). Modelling operator for the Frequency Domain - phase only :py:mod:`Cole-Cole <pygimli.physics.SIP.modelColeColeRho>` impedance model using :py:mod:`pygimli.physics.SIP.modelColeColeRho` after Pelton et al. (1978) :cite:`PeltonWarHal1978` * :math:`\textbf{m} =\{ m_1, \tau_1, c_1, m_2, \tau_2, c_2\}` Modelling parameter for the Cole-Cole model with :math:`\rho_0 = 1` * :math:`\textbf{d} =\{\varphi_i(f_i)\}` Modelling Response for all given frequencies as negative phase angles :math:`\varphi(f) = \varphi_1(Z_1(f))+\varphi_2(Z_2(f)) = -tan^{-1}\frac{\text{Im}\,(Z_1Z_2)}{\text{Re}\,(Z_1Z_2)}` and :math:`Z_1(f, \rho_0=1, m_1, \tau_1, c_1)` and :math:`Z_2(f, \rho_0=1, m_2, \tau_2, c_2)` ColeCole impedances. """
[docs] def __init__(self, f, verbose=False): """Setup class by specifying the frequency.""" super(DoubleColeColePhi, self).__init__(verbose) self.f_ = f # save frequencies self.setMesh(pg.meshtools.createMesh1D(1, 6)) # 4 single parameters
[docs] def response(self, par): """Phase angle of the model.""" spec1 = modelColeColeRho(self.f_, 1.0, par[0], par[1], par[2]) spec2 = modelColeColeRho(self.f_, 1.0, par[3], par[4], par[5]) return -np.angle(spec1) - np.angle(spec2)
[docs] class ColeColeAbs(pg.core.ModellingBase): """Cole-Cole model with EM term after Pelton et al. (1978)."""
[docs] def __init__(self, f, verbose=False): super().__init__(verbose) self.f_ = f # save frequencies self.setMesh(pg.meshtools.createMesh1D(1, 4)) # 3 single parameters
[docs] def response(self, par): """Amplitude of the model.""" spec = modelColeColeRho(self.f_, par[0], par[1], par[2], par[3]) return np.abs(spec)
[docs] class ColeColeComplex(pg.core.ModellingBase): """Cole-Cole model with EM term after Pelton et al. (1978)."""
[docs] def __init__(self, f, verbose=False): super(ColeColeComplex, self).__init__(verbose) self.f_ = f # save frequencies self.setMesh(pg.meshtools.createMesh1D(1, 4)) # 4 single parameters
[docs] def response(self, par): """Phase angle of the model.""" spec = modelColeColeRho(self.f_, *par) return pg.cat(np.abs(spec), -np.angle(spec))
[docs] class ColeColeComplexSigma(pg.core.ModellingBase): """Cole-Cole model with EM term after Pelton et al. (1978)."""
[docs] def __init__(self, f, verbose=False): super(ColeColeComplexSigma, self).__init__(verbose) self.f_ = f # save frequencies self.setMesh(pg.meshtools.createMesh1D(1, 4)) # 4 single parameters
[docs] def response(self, par): """Phase angle of the model.""" spec = modelColeColeSigma(self.f_, *par) return pg.cat(np.real(spec), np.imag(spec))
[docs] class PeltonPhiEM(pg.core.ModellingBase): """Cole-Cole model with EM term after Pelton et al. (1978)."""
[docs] def __init__(self, f, verbose=False): super(PeltonPhiEM, self).__init__(verbose) self.f_ = f # save frequencies self.setMesh(pg.meshtools.createMesh1D(1, 4)) # 4 single parameters
[docs] def response(self, par): """Phase angle of the model.""" spec = modelColeColeRho(self.f_, 1.0, par[0], par[1], par[2]) * \ relaxationTerm(self.f_, par[3]) # pure EM has c=1 return -np.angle(spec)
[docs] class DebyePhi(pg.Modelling): """Debye decomposition (smooth Debye relaxations) phase only."""
[docs] def __init__(self, fvec, tvec, verbose=False): # save reference in class """Init with frequency and tau vector.""" super(DebyePhi, self).__init__(verbose=verbose) self.f_ = fvec self.nf_ = len(fvec) self.t_ = tvec self.mesh = pg.meshtools.createMesh1D(len(tvec)) # standard 1d mesh self.setMesh(self.mesh)
[docs] def response(self, par): """amplitude/phase spectra as function of spectral chargeabilities.""" y = np.ones(self.nf_, dtype=np.complex) # 1 - for (tau, mk) in zip(self.t_, par): y -= (1. - relaxationTerm(self.f_, tau)) * mk return -np.angle(y)
[docs] class DebyeComplex(pg.Modelling): """Debye decomposition (smooth Debye relaxations) of complex data."""
[docs] def __init__(self, fvec, tvec, verbose=False): # save reference in class """Init with frequency and tau vector.""" self.f = fvec self.nf = len(fvec) self.t = tvec self.nt = len(tvec) mesh = pg.meshtools.createMesh1D(len(tvec)) # standard 1d mesh super(DebyeComplex, self).__init__(mesh=mesh, verbose=verbose) T, W = np.meshgrid(tvec, fvec * 2. * pi) WT = W*T self.A = WT**2 / (WT**2 + 1) self.B = WT / (WT**2+1) self.J = pg.Matrix() self.J.resize(len(fvec)*2, len(tvec)) for i in range(self.nf): wt = fvec[i] * 2.0 * pi * tvec wt2 = wt**2 self.J[i] = wt2 / (wt2 + 1.0) self.J[i+self.nf] = wt / (wt2 + 1.0) self.setJacobian(self.J)
[docs] def response(self, par): """amplitude/phase spectra as function of spectral chargeabilities.""" return self.J * par
[docs] def createJacobian(self, par): """Linear jacobian after Nordsiek&Weller (2008).""" pass
if __name__ == "__main__": pass