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 ``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 ``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 print("* ERROR :: src and rec must be in the same layer; " + "<lsrc>/<lrec> provided: " + str(lsrc) + "/" + str(lrec)) raise ValueError('src-z/rec-z') if depth.size < 2: # at least two layers print("* ERROR :: model must have more than one layer; " + "<depth> provided: " + _strvar(depth[1:])) raise ValueError('depth') if freq.size > 1: # only 1 frequency print("* ERROR :: only one frequency permitted; " + "<freqtime> provided: " + _strvar(freqtime)) raise ValueError('frequency') # === 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 factAng = angle_factor(ang, 11, False, False) zmfactAng = (factAng[:, np.newaxis] - 1) / 2 zpfactAng = (factAng[:, np.newaxis] + 1) / 2 fact = 4 * np.pi * off # TE [uu, ud, du, dd, df] for i, val in enumerate(PTE): PTE[i] = (factAng * np.dot(-val, filt.j1) / off + np.dot(zmfactAng * val * lambd, filt.j0)) / fact # TM [uu, ud, du, dd, df] for i, val in enumerate(PTM): PTM[i] = (factAng * np.dot(-val, filt.j1) / off + np.dot(zpfactAng * 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, False) # 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 = factAng * 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 test_strvar(): out = utils._strvar(np.arange(3) * np.pi) assert out == "0 3.14159 6.28319" out = utils._strvar(np.pi, '{:20.10e}') assert out == " 3.1415926536e+00"