"""
Zero-offset convolutional seismic modeling
Give a depth model and obtain a seismic zero-offset convolutional gather. You
can give the wavelet, if you already have, or use one of the existing, from
which we advise ricker wavelet (rickerwave function).
* :func:`~fatiando.seismic.conv.convolutional_model`: given the reflectivity
  series and wavelet, it returns the convolutional seismic gather.
* :func:`~fatiando.seismic.conv.reflectivity`: calculates the reflectivity
  series from the velocity model (and density model if present).
* :func:`~fatiando.seismic.conv.depth_2_time`: convert depth property model to
  the model in time.
* :func:`~fatiando.seismic.conv.rickerwave`: calculates a ricker wavelet.
References
----------
Yilmaz, Oz,
Ch.2 Deconvolution. In: YILMAZ, Oz. Seismic Data Analysis: Processing,
Inversion, and Interpretation of Seismic Data. Tulsa: Seg, 2001. Cap. 2.
p. 159-270. Available at: <http://dx.doi.org/10.1190/1.9781560801580.ch2>
"""
from __future__ import division
import numpy as np
from scipy import interpolate  # linear interpolation of velocity/density
[docs]def convolutional_model(rc, f, wavelet, dt):
    """
    Calculate convolutional seismogram for a geological model
    Calculate the synthetic convolutional seismogram of a geological model, Vp
    is mandatory while density is optional. The given model in a matrix form is
    considered a mesh of square cells.
    .. warning::
        Since the relative difference between the model is the important, being
        consistent with the units chosen for the parameters is the only
        requirement, whatever the units.
    Parameters:
    * rc : 2D-array
        Reflectivity values in the time domain.
    * f : float
        Dominant frequency of the ricker wavelet.
    * wavelet : float
        The function to consider as source in the seismic modelling.
    * dt: float
        Sample time of the ricker wavelet and of the resulting seismogram, in
        general a value of 2.e-3 is used.
    Returns:
    * synth_l : 2D-array
        Resulting seismogram.
    """
    w = wavelet(f, dt)
    # convolution
    synth_l = np.zeros(np.shape(rc))
    for j in range(0, rc.shape[1]):
        if np.shape(rc)[0] >= len(w):
            synth_l[:, j] = np.convolve(rc[:, j], w, mode='same')
        else:
            aux = np.floor(len(w)/2.)
            synth_l[:, j] = np.convolve(rc[:, j], w, mode='full')[aux:-aux]
    return synth_l 
[docs]def reflectivity(model_t, rho):
    """
    Calculate reflectivity series in the time domain, so it is necessary to use
    the function depth_2_time first if the model is in depth domain. Note this
    this function can also be used to one dimensional array.
    Parameters:
    * model_t : 2D-array
        Velocity values in time domain.
    * rho : 2D-array (optional)
        Density values for all the model, in time domain.
    Returns:
    * rc : 2D-array
        Calculated reflectivity series for all the model given.
    """
    err_message = "Velocity and density matrix must have the same dimension."
    assert model_t.shape == rho.shape, err_message
    rc = np.zeros(np.shape(model_t))
    rc[1:, :] = ((model_t[1:]*rho[1:]-model_t[:-1]*rho[:-1]) /
                 (model_t[1:]*rho[1:]+model_t[:-1]*rho[:-1]))
    return rc 
[docs]def depth_2_time(vel, model, dt, dz):
    """
    Convert depth property model to time model.
    Parameters:
    * vel : 2D-array
        Velocity values in the depth domain.
    * model : 2D-array
        Model values in the depth domain.
    * dt: float
        Sample time of the ricker wavelet and of the resulting seismogram, in
        general a value of 2.e-3 is used.
    * dz : float
        Length of square grid cells.
    * rho : 2D-array (optional)
        Density values for all the model, in depth domain.
    Returns:
    * model_t : 2D-array
        Property model in time domain.
    """
    err_message = "Velocity and model matrix must have the same dimension."
    assert vel.shape == model.shape, err_message
    # downsampled time rate to make a better interpolation
    n_samples, n_traces = [vel.shape[0], vel.shape[1]]
    dt_dwn = dt/10.
    if dt_dwn > dz/np.max(vel):
        dt_dwn = (dz/np.max(vel))/10.
    TWT = np.zeros((n_samples, n_traces))
    TWT[0, :] = 2*dz/vel[0, :]
    for j in range(1, n_samples):
        TWT[j, :] = TWT[j-1]+2*dz/vel[j, :]
    TMAX = max(TWT[-1, :])
    TMIN = min(TWT[0, :])
    TWT_rs = np.zeros(np.ceil(TMAX/dt_dwn))
    for j in range(1, len(TWT_rs)):
        TWT_rs[j] = TWT_rs[j-1]+dt_dwn
    resmpl = int(dt/dt_dwn)
    model_t = _resampling(model, TMAX, TWT, TWT_rs, dt, dt_dwn, n_traces)
    return model_t 
def _resampling(model, TMAX, TWT, TWT_rs, dt, dt_dwn, n_traces):
    """
    Resamples the input data to adjust it after time conversion with the chosen
    time sample rate, dt.
    Returns:
    * vel_l : 2D-array
        Resampled input data.
    """
    vel = np.ones((np.ceil(TMAX/dt_dwn), n_traces))
    for j in range(0, n_traces):
        kk = np.ceil(TWT[0, j]/dt_dwn)
        lim = np.ceil(TWT[-1, j]/dt_dwn)-1
    # necessary do before resampling to have values in all points of time model
        tck = interpolate.interp1d(TWT[:, j], model[:, j])
        vel[kk:lim, j] = tck(TWT_rs[kk:lim])
    # the model is extended in time because of depth time conversion
        vel[lim:, j] = vel[lim-1, j]
    # because of time conversion, the values between 0 e kk need to be filed
        vel[0:kk, j] = model[0, j]
    # resampling from dt_dwn to dt
    vel_l = np.zeros((np.ceil(TMAX/dt), n_traces))
    resmpl = int(dt/dt_dwn)
    vel_l[0, :] = vel[0, :]
    for j in range(0, n_traces):
        for jj in range(1, int(np.ceil(TMAX/dt))):
            vel_l[jj, j] = vel[resmpl*jj, j]
    return vel_l
[docs]def rickerwave(f, dt):
    r"""
    Given a frequency and time sampling rate, outputs ricker function. The
    length of the function varies according to f and dt, in order for the
    ricker function starts and ends as zero. It is also considered that the
    functions is causal, what means it starts at time zero. To satisfy sampling
    and stability:
    .. math::
        f << \frac{1}{2 dt}.
    Here, we consider this as:
    .. math::
        f < 0.2 \frac{1}{2 dt}.
    Parameters:
    * f : float
        dominant frequency value in Hz
    * dt : float
        time sampling rate in seconds (usually it is in the order of ms)
    Returns:
    * ricker : float
        Ricker function for the given parameters.
    """
    assert f < 0.2*(1./(2.*dt)), "Frequency too high for the dt chosen."
    nw = 2.2/f/dt
    nw = 2*np.floor(nw/2)+1
    nc = np.floor(nw/2)
    ricker = np.zeros(nw)
    k = np.arange(1, nw+1)
    alpha = (nc-k+1)*f*dt*np.pi
    beta = alpha**2
    ricker = (1.-beta*2)*np.exp(-beta)
    return ricker