Exemplo n.º 1
0
def test_printstartfinish(capsys):
    t0 = utils.printstartfinish(0)
    assert isinstance(t0, float)
    out, _ = capsys.readouterr()
    assert out == ""

    t0 = utils.printstartfinish(3)
    assert isinstance(t0, float)
    out, _ = capsys.readouterr()
    assert ":: empymod START  ::  v" in out

    utils.printstartfinish(0, t0)
    out, _ = capsys.readouterr()
    assert out == ""

    utils.printstartfinish(3, t0)
    out, _ = capsys.readouterr()
    assert out[:27] == "\n:: empymod END; runtime = "

    utils.printstartfinish(3, t0, 13)
    out, _ = capsys.readouterr()
    assert out[-19:] == "13 kernel call(s)\n\n"
Exemplo n.º 2
0
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
Exemplo n.º 3
0
def design(n, spacing, shift, fI, fC=False, r=None, r_def=(1, 1, 2), reim=None,
           cvar='amp', error=0.01, name=None, full_output=False, finish=False,
           save=True, path='filters', verb=2, plot=1):
    r"""Digital linear filter (DLF) design

    This routine can be used to design digital linear filters for the Hankel or
    Fourier transform, or for any linear transform ([Ghos70]_). For this
    included or provided theoretical transform pairs can be used.
    Alternatively, one can use the EM modeller empymod to use the responses to
    an arbitrary 1D model as numerical transform pair.

    This filter designing tool uses the direct matrix inversion method as
    described in [Kong07]_ and is based on scripts by [Key12]_. The tool is an
    add-on to the electromagnetic modeller empymod [Wert17]_. Fruitful
    discussions with Evert Slob and Kerry Key improved the add-on
    substantially.

    Example notebooks of its usage can be found in the documentation-gallery,
    https://empymod.readthedocs.io/en/stable/examples

    Parameters
    ----------
    n : int
        Filter length.

    spacing: float or tuple (start, stop, num)
        Spacing between filter points. If tuple, it corresponds to the input
        for np.linspace with endpoint=True.

    shift: float or tuple (start, stop, num)
        Shift of base from zero. If tuple, it corresponds to the input for
        np.linspace with endpoint=True.

    fI, fC : transform pairs
        Theoretical or numerical transform pair(s) for the inversion (I) and
        for the check of goodness (fC). fC is optional. If not provided, fI is
        used for both fI and fC.

    r : array, optional
        Right-hand side evaluation points for the check of goodness (fC).
        Defaults to r = np.logspace(0, 5, 1000), which are a lot of evaluation
        points, and depending on the transform pair way too long r's.

    r_def : tuple (add_left, add_right, factor), optional
        Definition of the right-hand side evaluation points r of the inversion.
        r is derived from the base values, default is (1, 1, 2).

        - rmin = log10(1/max(base)) - add_left
        - rmax = log10(1/min(base)) + add_right
        - r = logspace(rmin, rmax, factor*n)

    reim : np.real or np.imag, optional
        Which part of complex transform pairs is used for the inversion.
        Defaults to np.real.

    cvar : string {'amp', 'r'}, optional
        If 'amp', the inversion minimizes the amplitude. If 'r', the inversion
        maximizes the right-hand side evaluation point r. Defaults is 'amp'.

    error : float, optional
        Up to which relative error the transformation is considered good in the
        evaluation of the goodness. Default is 0.01 (1 %).

    name : str, optional
        Name of the filter. Defaults to dlf_+str(n).

    full_output : bool, optional
        If True, returns best filter and output from scipy.optimize.brute; else
        only filter. Default is False.

    finish : None, True, or callable, optional
        If callable, it is passed through to scipy.optimize.brute: minimization
        function to find minimize best result from brute-force approach.
        Default is None. You can simply provide True in order to use
        scipy.optimize.fmin_powell(). Set this to None if you are only
        interested in the actually provided spacing/shift-values.

    save : bool, optional
        If True, best filter is saved to plain text files in ./filters/. Can be
        loaded with fdesign.load_filter(name). If full, the inversion output is
        stored too. You can add '.gz' to `name`, which will then save the full
        inversion output in a compressed file instead of plain text.

    path : string, optional
        Absolute or relative path where output will be saved if `save=True`.
        Default is 'filters'.

    verb : {0, 1, 2}, optional
        Level of verbosity, default is 2:

        - 0: Print nothing.
        - 1: Print warnings.
        - 2: Print additional time, progress, and result

    plot : {0, 1, 2, 3}, optional
        Level of plot-verbosity, default is 1:

        - 0: Plot nothing.
        - 1: Plot brute-force result
        - 2: Plot additional theoretical transform pairs, and best inv.
        - 3: Plot additional inversion result
          (can result in lots of plots depending on spacing and shift)
          If you are using a notebook, use %matplotlib notebook to have
          all inversion results appear in the same plot.

    Returns
    -------
    filter : empymod.filter.DigitalFilter instance
        Best filter for the input parameters.
    full : tuple
        Output from scipy.optimize.brute with full_output=True. (Returned when
        `full_output` is True.)

    """

    # === 1.  LET'S START ============
    t0 = printstartfinish(verb)

    # Check plot with matplotlib (soft dependency)
    if plot > 0 and not plt:
        plot = 0
        if verb > 0:
            print(plt_msg)

    # Ensure fI, fC are lists
    def check_f(f):
        if hasattr(f, 'name'):  # put into list if single tp
            f = [f, ]
        else:  # ensure list (works for lists, tuples, arrays)
            f = list(f)
        return f

    if not fC:  # copy fI if fC not provided
        fC = dc(fI)
    fI = check_f(fI)
    if fI[0].name == 'j2':
        raise ValueError("j2 (jointly j0 and j1) is only implemented for "
                         "fC, not for fI!")
    fC = check_f(fC)

    # Check default input values
    if finish and not callable(finish):
        finish = fmin_powell
    if name is None:
        name = 'dlf_'+str(n)
    if r is None:
        r = np.logspace(0, 5, 1000)
    if reim not in [np.real, np.imag]:
        reim = np.real

    # Get spacing and shift slices, cast r
    ispacing = _ls2ar(spacing, 'spacing')
    ishift = _ls2ar(shift, 'shift')
    r = np.atleast_1d(r)

    # Initialize log-dict to keep track in brute-force minimization-function.
    log = {'cnt1': -1,   # Counter
           'cnt2': -1,   # %-counter;  v Total number of iterations v
           'totnr': np.arange(*ispacing).size*np.arange(*ishift).size,
           'time': t0,   # Timer
           'warn-r': 0}  # Warning for short r

    # === 2.  THEORETICAL MODEL rhs ============

    # Calculate rhs
    for i, f in enumerate(fC):
        fC[i].rhs = f.rhs(r)

    # Plot
    if plot > 1:
        _call_qc_transform_pairs(n, ispacing, ishift, fI, fC, r, r_def, reim)

    # === 3. RUN BRUTE FORCE OVER THE GRID ============
    full = brute(_get_min_val, (ispacing, ishift), full_output=True,
                 args=(n, fI, fC, r, r_def, error, reim, cvar, verb, plot,
                       log), finish=finish)

    # Add cvar-information to full: 0 for 'amp', 1 for 'r'
    if cvar == 'r':
        full += (1, )
    else:
        full += (0, )

    # Finish output from brute/fmin; depending if finish or not
    if verb > 1:
        print("")
        if callable(finish):
            print("")

    # Get best filter (full[0] contains spacing/shift of the best result).
    dlf = _calculate_filter(n, full[0][0], full[0][1], fI, r_def, reim, name)

    # If verbose, print result
    if verb > 1:
        print_result(dlf, full)

    # === 4.  FINISHED ============
    printstartfinish(verb, t0)

    # If plot, show result
    if plot > 0:
        print("* QC: Overview of brute-force inversion:")
        plot_result(dlf, full, False)
        if plot > 1:
            print("* QC: Inversion result of best filter (minimum amplitude):")
            _get_min_val(full[0], n, fI, fC, r, r_def, error, reim, cvar, 0,
                         plot+1, log)

    # Save if desired
    if save:
        if full_output:
            save_filter(name, dlf, full, path=path)
        else:
            save_filter(name, dlf, path=path)

    # Output, depending on full_output
    if full_output:
        return dlf, full
    else:
        return dlf