def test_201_values(self): print('Check Key 201 filter values against empymod') from empymod.filters import key_201_2012 dlf201 = key_201_2012() self.assertTrue(np.all(chs.Wab201[:, 0] == dlf201.base)) self.assertTrue(np.all(chs.Wab201[:, 1] == dlf201.j0)) self.assertTrue(np.all(chs.Wab201[:, 2] == dlf201.j1))
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 = 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
# We only check that the summed return values in the functions in tmtemod agree # with the corresponding functions from empymod. Nothing more. The examples are # based on the examples in empymod/tests/create_data. # Simple model, three frequencies, 6 layers freq = np.array([0.003, 2.5, 1e6]) res = np.array([3, .3, 10, 4, 3, 1]) aniso = np.array([1, .5, 3, 1, 2, 1]) eperm = np.array([80, 100, 3, 8, 1, 1]) mperm = np.array([.5, 100, 30, 1, 30, 1]) etaH = 1 / res + np.outer(2j * np.pi * freq, eperm * epsilon_0) etaV = 1 / (res * aniso * aniso) + np.outer(2j * np.pi * freq, eperm * epsilon_0) zeta = np.outer(2j * np.pi * freq, mperm * mu_0) filt = filters.key_201_2012() lambd = filt.base / np.array([0.001, 1, 100, 10000])[:, None] depth = np.array([-np.infty, 0, 150, 300, 500, 600, 800]) def test_dipole(): for lay in [0, 1, 5]: # Src/rec in first, second, and last layer for f in freq: # One freq at a time src = [0, 0, depth[lay + 1] - 50] # Offset depending on frequency if f < 1: rec = [10000, 0, depth[lay + 1] - 10] elif f > 10: rec = [2, 0, depth[lay + 1] - 10] else: