Source code for empymod.scripts.tmtemod

r"""
This add-on for empymod adjusts [HuTS15]_ for TM/TE-split. The development was
initiated by the development of
https://github.com/emsig/csem-ziolkowski-and-slob ([ZiSl19]_).

This is a stripped-down version of empymod with a lot of simplifications
but an important addition. The modeller empymod returns the total field,
hence not distinguishing between TM and TE mode, and even less between up- and
down-going fields. The reason behind this is simple: The derivation of
[HuTS15]_, on which empymod is based, returns the total field. In this
derivation each mode (TM and TE) contains non-physical contributions. The
non-physical contributions have opposite signs in TM and TE, so they cancel
each other out in the total field. However, in order to obtain the correct TM
and TE contributions one has to remove these non-physical parts.

This is what this routine does, but only for an x-directed electric source with
an x-directed electric receiver, and in the frequency domain (src and rec in
same layer). This version of :func:`empymod.dipole` returns the signal
separated into TM++, TM+-, TM-+, TM--, TE++, TE+-, TE-+, and TE-- as well as
the direct field TM and TE contributions. The first superscript denotes the
direction in which the field diffuses towards the receiver and the second
superscript denotes the direction in which the field diffuses away from the
source. For both the plus-sign indicates the field diffuses in the downward
direction and the minus-sign indicates the field diffuses in the upward
direction. It uses empymod wherever possible. See the corresponding functions
in empymod for more explanation and documentation regarding input parameters.
There are important limitations:

- `ab` == 11                   [=> x-directed el. source & el. receivers]
- `signal` == None             [=> only frequency domain]
- `xdirect` == False           [=> direct field calc. in wavenr-domain]
- `ht` == 'dlf'
- `htarg` == 'key_201_2012'
- Options `ft`, `ftarg`, and `loop` are not available.
- `lsrc` == `lrec`             [=> src & rec are assumed in same layer!]
- Model must have more than 1 layer
- Electric permittivity and magnetic permeability are isotropic.
- Only one frequency at once.


Theory
------

The derivation of [HuTS15]_, on which empymod is based, returns the total
field. Internally it also calculates TM and TE modes, and sums these up.
However, the separation into TM and TE mode introduces a singularity at
:math:`\kappa = 0`. It has no contribution in the space-frequency domain to the
total fields, but it introduces non-physical events in each mode with opposite
sign (so they cancel each other out in the total field). In order to obtain the
correct TM and TE contributions one has to remove these non-physical parts.

This routine removes the non-physical part. It is basically a heavily
simplified version of empymod with the following limitations outlined above.

It returns the signal separated into TM++, TM+-, TM-+, TM--, TE++, TE+-, TE-+,
and TE-- as well as the direct field TM and TE contributions. The first
superscript denotes the direction in which the field diffuses towards the
receiver and the second superscript denotes the direction in which the field
diffuses away from the source. For both the plus-sign indicates the field
diffuses in the downward direction and the minus-sign indicates the field
diffuses in the upward direction. The routine uses empymod wherever possible,
see the corresponding functions in empymod for more explanation and
documentation regarding input parameters.

Please note that the notation in [HuTS15]_ differs from the notation in
[ZiSl19]_. I specify therefore always, which notification applies, either
*Hun15* or *Zio19*.

We start with equation (105) in *Hun15*:

.. math::
    :label: hun1

    \hat{G}^{ee}_{xx}(\boldsymbol{x}, \boldsymbol{x'}, \omega) =
    \hat{G}^{ee;i}_{xx;s}(\boldsymbol{x}-\boldsymbol{x'}, \omega)
    + \frac{1}{8\pi}\int^\infty_{\kappa=0}
    \left(\frac{\Gamma_s \tilde{g}^{tm}_{hh;s}}{\eta_s} -
    \frac{\zeta_s \tilde{g}^{te}_{zz;s}}{\bar{\Gamma}_s}\right)
    J_0(\kappa r)\kappa d \kappa

.. math::
    :label: hun2

    - \frac{\cos(2\phi)}{8\pi}\int^\infty_{\kappa=0}
    \left(\frac{\Gamma_s \tilde{g}^{tm}_{hh;s}}{\eta_s} +
    \frac{\zeta_s \tilde{g}^{te}_{zz;s}}{\bar{\Gamma}_s}\right)
    J_2(\kappa r)\kappa d \kappa .

Ignoring the incident field, and using
:math:`J_2 = \frac{2}{\kappa r}J_1 - J_0` to avoid
:math:`J_2`-integrals, we get

.. math::
    :label: hun3

    \hat{G}^{ee}_{xx}(\boldsymbol{x}, \boldsymbol{x'}, \omega) =
    \frac{1}{8\pi}\int^\infty_{\kappa=0}
    \left(\frac{\Gamma_s \tilde{g}^{tm}_{hh;s}}{\eta_s}-
    \frac{\zeta_s \tilde{g}^{te}_{zz;s}}{\bar{\Gamma}_s}\right)
    J_0(\kappa r)\kappa\,{\mathrm{d}}\kappa

.. math::
    :label: hun4

    + \frac{\cos(2\phi)}{8\pi}\int^\infty_{\kappa=0}
    \left(\frac{\Gamma_s \tilde{g}^{tm}_{hh;s}}{\eta_s} +
    \frac{\zeta_s \tilde{g}^{te}_{zz;s}}{\bar{\Gamma}_s}\right)
    J_0(\kappa r)\kappa\,{\mathrm{d}}\kappa

.. math::
    :label: hun5

    - \frac{\cos(2\phi)}{4\pi r}\int^\infty_{\kappa=0}
    \left(\frac{\Gamma_s \tilde{g}^{tm}_{hh;s}}{\eta_s} +
    \frac{\zeta_s \tilde{g}^{te}_{zz;s}}{\bar{\Gamma}_s}\right)
    J_1(\kappa r)\,{\mathrm{d}}\kappa .

From this the TM- and TE-parts follow as

.. math::
    :label: hun6

    {\mathrm{TE}} = \frac{\cos(2\phi)-1}{8\pi}\int^\infty_{\kappa=0}
    \frac{\zeta_s \tilde{g}^{te}_{zz;s}}{\bar{\Gamma}_s}
    J_0(\kappa r)\kappa\,{\mathrm{d}}\kappa
    - \frac{\cos(2\phi)}{4\pi r}\int^\infty_{\kappa=0}
    \frac{\zeta_s \tilde{g}^{te}_{zz;s}}{\bar{\Gamma}_s}
    J_1(\kappa r)\,{\mathrm{d}}\kappa ,

.. math::
    :label: hun7

      {\mathrm{TM}} = \frac{\cos(2\phi)+1}{8\pi}\int^\infty_{\kappa=0}
    \frac{\Gamma_s \tilde{g}^{tm}_{hh;s}}{\eta_s}
    J_0(\kappa r)\kappa\,{\mathrm{d}}\kappa
    - \frac{\cos(2\phi)}{4\pi r}\int^\infty_{\kappa=0}
    \frac{\Gamma_s \tilde{g}^{tm}_{hh;s}}{\eta_s}
    J_1(\kappa r)\,{\mathrm{d}}\kappa .

Equations (108) and (109) in Hun15 yield the required parameters
:math:`\tilde{g}^{tm}_{hh;s}` and :math:`\tilde{g}^{te}_{zz;s}`,

.. math::
    :label: hun8

    \tilde{g}^{tm}_{hh;s} = P^{u-}_s W^u_s + P^{d-}_s W^d_s ,

.. math::
    :label: hun9

    \tilde{g}^{te}_{zz;s} = \bar{P}^{u+}_s \bar{W}^u_s +
                             \bar{P}^{d+}_s \bar{W}^d_s \ .

The parameters :math:`P^{u\pm}_s` and :math:`P^{d\pm}_s` are given in equations
(81) and (82), :math:`\bar{P}^{u\pm}_s` and :math:`\bar{P}^{d\pm}_s` in
equations (A-8) and (A-9); :math:`W^u_s` and :math:`W^d_s` in equation (74)
in Hun15. This yields

.. math::
    :label: hun10

    \tilde{g}^{te}_{zz;s} =
    \frac{\bar{R}_s^+}{\bar{M}_s}\left\{\exp[-\bar{\Gamma}_s(z_s-z+d^+)] +
    \bar{R}_s^-\exp[-\bar{\Gamma}_s(z_s-z+d_s+d^-)]\right\}

.. math::
    :label: hun11

    + \frac{\bar{R}_s^-}{\bar{M}_s}
    \left\{\exp[-\bar{\Gamma}_s(z-z_{s-1}+d^-)]+
    \bar{R}_s^+\exp[-\bar{\Gamma}_s(z-z_{s-1}+d_s+d^+)]\right\} ,

.. math::
    :label: hun12

    =\frac{\bar{R}_s^+}{\bar{M}_s}\left\{\exp[-\bar{\Gamma}_s(2z_s-z-z')]
    + \bar{R}_s^-\exp[-\bar{\Gamma}_s(z'-z+2d_s)]\right\}

.. math::
    :label: hun13

    + \frac{\bar{R}_s^-}{\bar{M}_s}
    \left\{\exp[-\bar{\Gamma}_s(z+z'-2z_{s-1})]+
    \bar{R}_s^+\exp[-\bar{\Gamma}_s(z-z'+2d_s)]\right\} ,


where :math:`d^\pm` is taken from the text below equation (67). There are four
terms in the right-hand side, two in the first line and two in the second line.
The first term in the first line is the integrand of TE+-, the second term in
the first line corresponds to TE++, the first term in the second line is TE-+,
and the second term in the second line is TE--.

If we look at TE+-, we have

.. math::
    :label: hun14

    \tilde{g}^{te+-}_{zz;s} =
    \frac{\bar{R}_s^+}{\bar{M}_s}\exp[-\bar{\Gamma}_s(2z_s-z-z')] \ ,


and therefore

.. math::
    :label: hun15

    {\mathrm{TE}}^{+-} = \frac{\cos(2\phi)-1}{8\pi}\int^\infty_{\kappa=0}
    \frac{\zeta_s \bar{R}_s^+}{\bar{\Gamma}_s\bar{M}_s}
    \exp[-\bar{\Gamma}_s(2z_s-z-z')]
    J_0(\kappa r)\kappa\,{\mathrm{d}}\kappa

.. math::
    :label: hun16

    - \frac{\cos(2\phi)}{4\pi r}\int^\infty_{\kappa=0}
    \frac{\zeta_s \bar{R}_s^+}{\bar{\Gamma}_s\bar{M}_s}
    \exp[-\bar{\Gamma}_s(2z_s-z-z')]
    J_1(\kappa r)\,{\mathrm{d}}\kappa .

We can compare this to equation (4.165) in Zio19, with :math:`\hat{I}^e_x=1`
and slightly re-arranging it to look more alike, we get

.. math::
    :label: hun17

    \hat{E}^{+-}_{xx;H} = \frac{y^2}{4\pi r^2}
    \int^\infty_{\kappa=0} \frac{\zeta_1}{\Gamma_1}
    \frac{R^-_{H;1}}{M_{H;1}}
    \exp(-\Gamma_1 h^{+-})J_0(\kappa r)\kappa d\kappa

.. math::
    :label: hun18

   + \frac{x^2-y^2}{4\pi r^3}
   \int^\infty_{\kappa=0} \frac{\zeta_1}{\Gamma_1}
   \left(\frac{R^-_{H;1}}{M_{H;1}} -
   \frac{R^-_{H;1}(\kappa=0)}{M_{H;1}(\kappa=0)}\right)
   \exp(-\Gamma_1 h^{+-})J_1(\kappa r) d\kappa


.. math::
    :label: hun19

    - \frac{\zeta_1 (x^2-y^2)}{4\pi\gamma_1 r^4}
    \frac{R^-_{H;1}(\kappa=0)}{M_{H;1}(\kappa=0)}
    \exp(-\gamma_1 R^{+-}) .

The notation in this equation follows Zio19.

The difference between the two previous equations is that the first one
contains non-physical contributions. These have opposite signs in TM+- and
TE+-, and therefore cancel each other out. But if we want to know the specific
contributions from TM and TE we have to remove them. The non-physical
contributions only affect the :math:`J_1`-integrals, and only for :math:`\kappa
= 0`.

The following lists for all 8 cases the term that has to be removed, in the
notation of Zio19 (for the notation as in Hun15 see the implementation in this
file):

.. math::
    :label: hun20

    TE^{++} = + \frac{\zeta_1 (x^2-y^2)}{4\pi\gamma_1 r^4}
    \frac{\exp(-\gamma_1 |h^-|) }{M_{H;1}(\kappa=0)} ,

.. math::
    :label: hun21

    TE^{-+} = - \frac{\zeta_1 (x^2-y^2)}{4\pi\gamma_1 r^4}
    \frac{R^+_{H;1}(\kappa=0)\exp(-\gamma_1 h^{-+}) }{M_{H;1}(\kappa=0)} ,

.. math::
    :label: hun22

    TE^{+-} = - \frac{\zeta_1 (x^2-y^2)}{4\pi\gamma_1 r^4}
    \frac{R^-_{H;1}(\kappa=0)\exp(-\gamma_1 h^{+-}) }{M_{H;1}(\kappa=0)} ,

.. math::
    :label: hun23

    TE^{--} = + \frac{\zeta_1 (x^2-y^2)}{4\pi\gamma_1 r^4}
    \frac{R^+_{H;1}(\kappa=0)R^-_{H;1}(\kappa=0)\exp(-\gamma_1 h^{--}) }
    {M_{H;1}(\kappa=0)} ,

.. math::
    :label: hun24

    TM^{++} = - \frac{\zeta_1 (x^2-y^2)}{4\pi\gamma_1 r^4}
    \frac{\exp(-\gamma_1 |h^-|) }{M_{V;1}(\kappa=0)} ,

.. math::
    :label: hun25

    TM^{-+} = - \frac{\zeta_1 (x^2-y^2)}{4\pi\gamma_1 r^4}
    \frac{R^+_{V;1}(\kappa=0)\exp(-\gamma_1 h^{-+}) }{M_{V;1}(\kappa=0)} ,

.. math::
    :label: hun26

    TM^{+-} = - \frac{\zeta_1 (x^2-y^2)}{4\pi\gamma_1 r^4}
    \frac{R^-_{V;1}(\kappa=0)\exp(-\gamma_1 h^{+-}) }{M_{V;1}(\kappa=0)} ,

.. math::
    :label: hun27

    TM^{--} = - \frac{\zeta_1 (x^2-y^2)}{4\pi\gamma_1 r^4}
    \frac{R^+_{V;1}(\kappa=0)R^-_{V;1}(\kappa=0)\exp(-\gamma_1 h^{--}) }
    {M_{V;1}(\kappa=0)} .



Note that in the first and fourth equations the correction terms have opposite
sign as those in the fifth and eighth equations because at :math:`\kappa=0` the
TM and TE mode correction terms are equal. Also note that in the second and
third equations the correction terms have the same sign as those in the sixth
and seventh equations because at :math:`\kappa=0` the TM and TE mode reflection
responses in those terms are equal but with opposite sign:
:math:`R^\pm_{V;1}(\kappa=0) = -R^\pm_{V;1}(\kappa=0)`.

Hun15 uses :math:`\phi`, whereas Zio19 uses :math:`x`, :math:`y`, for which we
can use

.. math::
    :label: hun28

    \cos(2\phi) = -\frac{x^2-y^2}{r^2} \ .


"""
# Copyright 2016 The emsig community.
#
# This file is part of empymod.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may not
# use this file except in compliance with the License.  You may obtain a copy
# of the License at
#
#     https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
# License for the specific language governing permissions and limitations under
# the License.

import numpy as np

from empymod.filters import Hankel
from empymod.kernel import reflections, angle_factor
from empymod.utils import (check_model, check_frequency, check_dipole, _strvar,
                           get_off_ang, get_layer_nr, printstartfinish)

__all__ = ['dipole']


def __dir__():
    return __all__


[docs] def dipole(src, rec, depth, res, freqtime, aniso=None, eperm=None, mperm=None, verb=2): r"""Return the electromagnetic field due to a dipole source. This is a modified version of :func:`empymod.model.dipole`. It returns the separated contributions of TM--, TM-+, TM+-, TM++, TMdirect, TE--, TE-+, TE+-, TE++, and TEdirect. Parameters ---------- src, rec : list of floats or arrays Source and receiver coordinates (m): [x, y, z]. The x- and y-coordinates can be arrays, z is a single value. The x- and y-coordinates must have the same dimension. Sources or receivers placed on a layer interface are considered in the upper layer. Sources and receivers must be in the same layer. depth : list Absolute layer interfaces z (m); #depth = #res - 1 (excluding +/- infinity). res : array_like Horizontal resistivities rho_h (Ohm.m); #res = #depth + 1. freqtime : float Frequency f (Hz). (The name `freqtime` is kept for consistency with :func:`empymod.model.dipole`. Only one frequency at once. aniso : array_like, optional Anisotropies lambda = sqrt(rho_v/rho_h) (-); #aniso = #res. Defaults to ones. eperm : array_like, optional Relative electric permittivities epsilon (-); #eperm = #res. Default is ones. mperm : array_like, optional Relative magnetic permeabilities mu (-); #mperm = #res. Default is ones. verb : {0, 1, 2, 3, 4}, optional Level of verbosity, default is 2: - 0: Print nothing. - 1: Print warnings. - 2: Print additional runtime and kernel calls - 3: Print additional start/stop, condensed parameter information. - 4: Print additional full parameter information Returns ------- TM, TE : list of ndarrays, (nfreq, nrec, nsrc) Frequency-domain EM field [V/m], separated into TM = [TM--, TM-+, TM+-, TM++, TMdirect] and TE = [TE--, TE-+, TE+-, TE++, TEdirect]. However, source and receiver are normalised. So the source strength is 1 A and its length is 1 m. Therefore the electric field could also be written as [V/(A.m2)]. The shape of EM is (nfreq, nrec, nsrc). However, single dimensions are removed. """ # === 1. LET'S START ============ t0 = printstartfinish(verb) # === 2. CHECK INPUT ============ # Check layer parameters model = check_model(depth, res, aniso, eperm, eperm, mperm, mperm, False, verb) depth, res, aniso, epermH, epermV, mpermH, mpermV, _ = model # Check frequency => get etaH, etaV, zetaH, and zetaV frequency = check_frequency(freqtime, res, aniso, epermH, epermV, mpermH, mpermV, verb) freq, etaH, etaV, zetaH, zetaV = frequency # Check src and rec src, nsrc = check_dipole(src, 'src', verb) rec, nrec = check_dipole(rec, 'rec', verb) # Get offsets off, ang = get_off_ang(src, rec, nsrc, nrec, verb) # Get layer number in which src and rec reside (lsrc/lrec) lsrc, zsrc = get_layer_nr(src, depth) lrec, zrec = get_layer_nr(rec, depth) # Check limitations of this routine compared to the standard `dipole` if lsrc != lrec: # src and rec in same layer raise ValueError("src and rec must be in the same layer; " f"<lsrc>/<lrec> provided: {lsrc}/{lrec}.") if depth.size < 2: # at least two layers raise ValueError("model must have more than one layer; " f"<depth> provided: {_strvar(depth[1:])}.") if freq.size > 1: # only 1 frequency raise ValueError("only one frequency permitted; " f"<freqtime> provided: {_strvar(freqtime)}.") # === 3. EM-FIELD CALCULATION ============ # This part is a simplification of: # - model.fem() # - transform.dlf() # - kernel.wavenumber() # DLF filter we use filt = Hankel().key_201_2012 # 3.1. COMPUTE REQUIRED LAMBDAS for given hankel-filter-base lambd = filt.base/off[:, None] # 3.2. CALL THE KERNEL PTM, PTE = greenfct(zsrc, zrec, lsrc, lrec, depth, etaH, etaV, zetaH, zetaV, lambd) # 3.3. CARRY OUT THE HANKEL TRANSFORM WITH DLF ang_fact = angle_factor(ang, 11, False, False) zm_ang_fact = (ang_fact[:, np.newaxis]-1)/2 zp_ang_fact = (ang_fact[:, np.newaxis]+1)/2 fact = 4*np.pi*off # TE [uu, ud, du, dd, df] for i, val in enumerate(PTE): PTE[i] = (ang_fact*np.dot(-val, filt.j1)/off + np.dot(zm_ang_fact*val*lambd, filt.j0))/fact # TM [uu, ud, du, dd, df] for i, val in enumerate(PTM): PTM[i] = (ang_fact*np.dot(-val, filt.j1)/off + np.dot(zp_ang_fact*val*lambd, filt.j0))/fact # 3.4. Remove non-physical contributions # (Note: The T*dd corrections differ slightly from the equations given in # the accompanying pdf, due to the way the direct field is accounted for # in the book.) # General parameters Gam = np.sqrt((zetaH*etaH)[:, None, :, None]) # Gam for lambd=0 iGam = Gam[:, :, lsrc, 0] lgam = np.sqrt(zetaH[:, lsrc]*etaH[:, lsrc]) ddepth = np.r_[depth, np.inf] ds = ddepth[lsrc+1] - ddepth[lsrc] def get_rp_rm(z_eta): r"""Return Rp, Rm.""" # Get Rp/Rm for lambd=0 Rp, Rm = reflections(depth, z_eta, Gam, lrec, lsrc) # Depending on model Rp/Rm have 3 or 4 dimensions. Last two are # wavenumbers and layers btw src and rec, which both are 1. if Rp.ndim == 4: Rp = np.squeeze(Rp, axis=3) if Rm.ndim == 4: Rm = np.squeeze(Rm, axis=3) Rp = np.squeeze(Rp, axis=2) Rm = np.squeeze(Rm, axis=2) # Calculate reverberation M and general factor npfct Ms = 1 - Rp*Rm*np.exp(-2*iGam*ds) npfct = ang_fact*zetaH[:, lsrc]/(fact*off*lgam*Ms) return Rp, Rm, npfct # TE modes TE[uu, ud, du, dd] Rp, Rm, npfct = get_rp_rm(zetaH) PTE[0] += npfct*Rp*Rm*np.exp(-lgam*(2*ds - zrec + zsrc)) PTE[1] += npfct*Rp*np.exp(-lgam*(2*ddepth[lrec+1] - zrec - zsrc)) PTE[2] += npfct*Rm*np.exp(-lgam*(zrec + zsrc)) PTE[3] += npfct*Rp*Rm*np.exp(-lgam*(2*ds + zrec - zsrc)) # TM modes TM[uu, ud, du, dd] Rp, Rm, npfct = get_rp_rm(etaH) PTM[0] -= npfct*Rp*Rm*np.exp(-lgam*(2*ds - zrec + zsrc)) PTM[1] += npfct*Rp*np.exp(-lgam*(2*ddepth[lrec+1] - zrec - zsrc)) PTM[2] += npfct*Rm*np.exp(-lgam*(zrec + zsrc)) PTM[3] -= npfct*Rp*Rm*np.exp(-lgam*(2*ds + zrec - zsrc)) # 3.5 Reshape for number of sources for i, val in enumerate(PTE): PTE[i] = np.squeeze(val.reshape((-1, nrec, nsrc), order='F')) for i, val in enumerate(PTM): PTM[i] = np.squeeze(val.reshape((-1, nrec, nsrc), order='F')) # === 4. FINISHED ============ printstartfinish(verb, t0) # return [TMuu, TMud, TMdu, TMdd, TMdf], [TEuu, TEud, TEdu, TEdd, TEdf] return PTM, PTE
def greenfct(zsrc, zrec, lsrc, lrec, depth, etaH, etaV, zetaH, zetaV, lambd): r"""Calculate Green's function for TM and TE. This is a modified version of empymod.kernel.greenfct(). See the original version for more information. """ # GTM/GTE have shape (frequency, offset, lambda). # gamTM/gamTE have shape (frequency, offset, layer, lambda): for TM in [True, False]: # Define eta/zeta depending if TM or TE if TM: e_zH, e_zV, z_eH = etaH, etaV, zetaH # TM: zetaV not used else: e_zH, e_zV, z_eH = zetaH, zetaV, etaH # TE: etaV not used # Uppercase gamma Gam = np.sqrt((e_zH/e_zV)[:, None, :, None] * (lambd*lambd)[None, :, None, :] + (z_eH*e_zH)[:, None, :, None]) # Gamma in receiver layer lrecGam = Gam[:, :, lrec, :] # Reflection (coming from below (Rp) and above (Rm) rec) Rp, Rm = reflections(depth, e_zH, Gam, lrec, lsrc) # Field propagators # (Up- (Wu) and downgoing (Wd), in rec layer); Eq 74 if lrec != depth.size-1: # No upgoing field prop. if rec in last ddepth = depth[lrec + 1] - zrec Wu = np.exp(-lrecGam*ddepth) else: Wu = np.full_like(lrecGam, 0+0j) if lrec != 0: # No downgoing field propagator if rec in first ddepth = zrec - depth[lrec] Wd = np.exp(-lrecGam*ddepth) else: Wd = np.full_like(lrecGam, 0+0j) # Field at rec level (coming from below (Pu) and above (Pd) rec) Puu, Pud, Pdu, Pdd = fields(depth, Rp, Rm, Gam, lrec, lsrc, zsrc, TM) # Store in corresponding variable PT* = [T*uu, T*ud, T*du, T*dd] df = np.exp(-lrecGam*abs(zsrc - zrec)) # direct field fTM = Gam[:, :, lrec, :]/etaH[:, None, lrec, None] fTE = zetaH[:, None, lsrc, None]/Gam[:, :, lsrc, :] if TM: PTM = [Puu*Wu*fTM, Pud*Wu*fTM, Pdu*Wd*fTM, Pdd*Wd*fTM, -df*fTM] else: PTE = [Puu*Wu*fTE, Pud*Wu*fTE, Pdu*Wd*fTE, Pdd*Wd*fTE, df*fTE] # Return Green's functions return PTM, PTE def fields(depth, Rp, Rm, Gam, lrec, lsrc, zsrc, TM): r"""Calculate Pu+, Pu-, Pd+, Pd-. This is a modified version of empymod.kernel.fields(). See the original version for more information. """ # Booleans if src in first or last layer; swapped if up=True first_layer = lsrc == 0 last_layer = lsrc == depth.size-1 # Depths; dp and dm are swapped if up=True if lsrc != depth.size-1: ds = depth[lsrc+1]-depth[lsrc] dp = depth[lsrc+1]-zsrc dm = zsrc-depth[lsrc] # Rm and Rp; swapped if up=True Rmp = Rm Rpm = Rp # Boolean if plus or minus has to be calculated if TM: plus = False else: plus = True # Sign-switches pm = 1 # + if plus=True, - if plus=False if not plus: pm = -1 # Calculate down- and up-going fields for up in [False, True]: # No upgoing field if rec is in last layer or below src if up and (lrec == depth.size-1 or lrec > lsrc): Puu = np.full_like(Gam[:, :, lsrc, :], 0+0j) Pud = np.full_like(Gam[:, :, lsrc, :], 0+0j) continue # No downgoing field if rec is in first layer or above src if not up and (lrec == 0 or lrec < lsrc): Pdu = np.full_like(Gam[:, :, lsrc, :], 0+0j) Pdd = np.full_like(Gam[:, :, lsrc, :], 0+0j) continue # Swaps if up=True if up: dp, dm = dm, dp Rmp, Rpm = Rpm, Rmp first_layer, last_layer = last_layer, first_layer # Calculate Pu+, Pu-, Pd+, Pd-; rec in src layer; Eqs 81/82, A-8/A-9 iGam = Gam[:, :, lsrc, :] if last_layer: # If src/rec are in top (up) or bottom (down) layer Pd = Rmp[:, :, 0, :]*np.exp(-iGam*dm) Pu = np.full_like(Gam[:, :, lsrc, :], 0+0j) else: # If src and rec are in any layer in between Ms = 1 - Rmp[:, :, 0, :]*Rpm[:, :, 0, :]*np.exp(-2*iGam*ds) Pd = Rmp[:, :, 0, :]/Ms*np.exp(-iGam*dm) Pu = Rmp[:, :, 0, :]/Ms*pm*Rpm[:, :, 0, :]*np.exp(-iGam*(ds+dp)) # Store P's if up: Puu = Pu Pud = Pd else: Pdu = Pd Pdd = Pu # Return fields (up- and downgoing) return Puu, Pud, Pdu, Pdd