Exemple #1
0
def test_FFT_random_real(par):
    shape = par["shape"]
    dtype, decimal = par["dtype_precision"]
    ifftshift_before = par["ifftshift_before"]

    x = np.random.randn(*shape).astype(dtype)

    # Select an axis to apply FFT on. It can be any integer
    # in [0,..., ndim-1] but also in [-ndim, ..., -1]
    axis = _choose_random_axes(x.ndim, n_choices=1)[0]

    FFTop = FFT(
        dims=x.shape,
        dir=axis,
        ifftshift_before=ifftshift_before,
        real=True,
        dtype=dtype,
    )
    x = x.ravel()
    y = FFTop * x

    # Ensure inverse and adjoint recover x
    xadj = FFTop.H * y  # adjoint is same as inverse for fft
    xinv = lsqr(FFTop, y, damp=0, iter_lim=10, show=0)[0]
    assert_array_almost_equal(x, xadj, decimal=decimal)
    assert_array_almost_equal(x, xinv, decimal=decimal)

    # Dot tests
    nr, nc = FFTop.shape
    assert dottest(FFTop, nr, nc, complexflag=0, tol=10**(-decimal))
    assert dottest(FFTop, nr, nc, complexflag=2, tol=10**(-decimal))
Exemple #2
0
def test_FFT_1dsignal(par):
    """Dot-test and inversion for FFT operator for 1d signal
    """
    dt = 0.005
    t = np.arange(par['nt']) * dt
    f0 = 10
    x = np.sin(2 * np.pi * f0 * t)
    nfft = par['nt'] if isinstance(par['nfft'], bool) else par['nfft']
    FFTop = FFT(dims=[par['nt']],
                nfft=nfft,
                sampling=dt,
                real=par['real'],
                engine=par['engine'])

    # FFT with real=True cannot pass dot-test neither be inverted correctly,
    # see FFT documentation for a detailed explanation. We thus test FFT.H*FFT
    if par['real']:
        FFTop = FFTop.H * FFTop
        assert dottest(FFTop, par['nt'], par['nt'], complexflag=0)
    else:
        assert dottest(FFTop, nfft, par['nt'], complexflag=2)
        assert dottest(FFTop, nfft, par['nt'], complexflag=3)

    y = FFTop * x
    xadj = FFTop.H * y  # adjoint is same as inverse for fft
    xinv = lsqr(FFTop, y, damp=1e-10, iter_lim=10, show=0)[0]

    assert_array_almost_equal(x, xadj, decimal=8)
    assert_array_almost_equal(x, xinv, decimal=8)
Exemple #3
0
def test_FFT_1dsignal(par):
    """Dot-test and inversion for FFT operator for 1d signal
    """
    decimal = 4 if np.real(np.ones(1, par['dtype'])).dtype == np.float32 else 8

    dt = 0.005
    t = np.arange(par['nt']) * dt
    f0 = 10
    x = np.sin(2 * np.pi * f0 * t)
    x = x.astype(par['dtype'])
    nfft = par['nt'] if par['nfft'] is None else par['nfft']
    
    FFTop = FFT(dims=[par['nt']], nfft=nfft, sampling=dt,
                real=par['real'], engine=par['engine'], dtype=par['dtype'])

    # FFT with real=True cannot pass dot-test neither be inverted correctly,
    # see FFT documentation for a detailed explanation. We thus test FFT.H*FFT
    if par['real']:
        FFTop = FFTop.H * FFTop
        assert dottest(FFTop, par['nt'], par['nt'],
                       complexflag=0, tol=10**(-decimal), verb=True)
    else:
        assert dottest(FFTop, nfft, par['nt'],
                       complexflag=2, tol=10**(-decimal), verb=True)
        assert dottest(FFTop, nfft, par['nt'],
                       complexflag=3, tol=10**(-decimal), verb=True)

    y = FFTop * x
    xadj = FFTop.H * y  # adjoint is same as inverse for fft
    xinv = lsqr(FFTop, y, damp=1e-10, iter_lim=10, show=0)[0]

    assert_array_almost_equal(x, xadj, decimal=decimal)
    assert_array_almost_equal(x, xinv, decimal=decimal)
Exemple #4
0
def test_unknown_engine(par):
    """Check error is raised if unknown engine is passed
    """
    with pytest.raises(NotImplementedError):
        _ = FFT(dims=[par['nt']],
                nfft=par['nfft'],
                sampling=0.005,
                real=par['real'],
                engine='foo')
Exemple #5
0
def test_unknown_engine(par):
    """Check error is raised if unknown engine is passed"""
    with pytest.raises(NotImplementedError):
        _ = FFT(
            dims=[par["nt"]],
            nfft=par["nfft"],
            sampling=0.005,
            real=par["real"],
            engine="foo",
        )
Exemple #6
0
def test_FFT_2dsignal(par):
    """Dot-test and inversion for fft operator for 2d signal (fft on single dimension)
    """
    dt = 0.005
    nt, nx = par['nt'], par['nx']
    t = np.arange(nt) * dt
    f0 = 10
    d = np.outer(np.sin(2 * np.pi * f0 * t), np.arange(nx) + 1)

    # 1st dimension
    FFTop = FFT(dims=(nt, nx), dir=0, nfft=par['nfft'], sampling=dt)
    assert dottest(FFTop,
                   par['nfft'] * par['nx'],
                   par['nt'] * par['nx'],
                   complexflag=2)

    D = FFTop * d.flatten()
    dadj = FFTop.H * D  # adjoint is same as inverse for fft
    dinv = lsqr(FFTop, D, damp=1e-10, iter_lim=10, show=0)[0]

    dadj = np.real(dadj.reshape(par['nt'], par['nx']))
    dinv = np.real(dinv.reshape(par['nt'], par['nx']))

    assert_array_almost_equal(d, dadj, decimal=8)
    assert_array_almost_equal(d, dinv, decimal=8)

    # 2nd dimension
    FFTop = FFT(dims=(nt, nx), dir=1, nfft=par['nfft'], sampling=dt)
    assert dottest(FFTop,
                   par['nt'] * par['nfft'],
                   par['nt'] * par['nx'],
                   complexflag=2)

    D = FFTop * d.flatten()
    dadj = FFTop.H * D  # adjoint is inverse for fft
    dinv = lsqr(FFTop, D, damp=1e-10, iter_lim=10, show=0)[0]

    dadj = np.real(dadj.reshape(par['nt'], par['nx']))
    dinv = np.real(dinv.reshape(par['nt'], par['nx']))

    assert_array_almost_equal(d, dadj, decimal=8)
    assert_array_almost_equal(d, dinv, decimal=8)
Exemple #7
0
def test_FFT_random_complex(par):
    shape = par["shape"]
    dtype, decimal = par["dtype_precision"]
    ifftshift_before = par["ifftshift_before"]
    fftshift_after = par["fftshift_after"]
    engine = par["engine"]

    x = np.random.randn(*shape).astype(dtype)
    if np.issubdtype(dtype, np.complexfloating):
        x += 1j * np.random.randn(*shape).astype(dtype)

    # Select an axis to apply FFT on. It can be any integer
    # in [0,..., ndim-1] but also in [-ndim, ..., -1]
    axis = _choose_random_axes(x.ndim, n_choices=1)[0]

    FFTop = FFT(
        dims=x.shape,
        dir=axis,
        ifftshift_before=ifftshift_before,
        fftshift_after=fftshift_after,
        dtype=dtype,
        engine=engine,
    )

    # Compute FFT of x independently
    y_true = np.fft.fft(x, axis=axis, norm="ortho")
    if fftshift_after:
        y_true = np.fft.fftshift(y_true, axes=axis)
    if ifftshift_before:
        y_true = np.swapaxes(y_true, axis, -1)
        x0 = -np.ceil(x.shape[axis] / 2)
        phase_correction = np.exp(2 * np.pi * 1j * FFTop.f * x0)
        y_true *= phase_correction
        y_true = np.swapaxes(y_true, -1, axis)
    y_true = y_true.ravel()

    # Compute FFT with FFTop and compare with y_true
    x = x.ravel()
    y = FFTop * x
    assert_array_almost_equal(y, y_true, decimal=decimal)

    # Ensure inverse and adjoint recover x
    xadj = FFTop.H * y  # adjoint is same as inverse for fft
    xinv = lsqr(FFTop, y, damp=0, iter_lim=10, show=0)[0]
    assert_array_almost_equal(x, xadj, decimal=decimal)
    assert_array_almost_equal(x, xinv, decimal=decimal)

    # Dot tests
    nr, nc = FFTop.shape
    assert dottest(FFTop, nr, nc, complexflag=0, tol=10**(-decimal))
    assert dottest(FFTop, nr, nc, complexflag=2, tol=10**(-decimal))
    if np.issubdtype(dtype, np.complexfloating):
        assert dottest(FFTop, nr, nc, complexflag=1, tol=10**(-decimal))
        assert dottest(FFTop, nr, nc, complexflag=3, tol=10**(-decimal))
Exemple #8
0
def test_FFT_1dsignal(par):
    """Dot-test and inversion for FFT operator for 1d signal
    """
    dt = 0.005
    t = np.arange(par['nt']) * dt
    f0 = 10
    x = np.sin(2 * np.pi * f0 * t)

    FFTop = FFT(dims=[par['nt']], nfft=par['nfft'], sampling=dt)
    assert dottest(FFTop, par['nfft'], par['nt'], complexflag=2)

    y = FFTop * x
    xadj = FFTop.H * y  # adjoint is same as inverse for fft
    xinv = lsqr(FFTop, y, damp=1e-10, iter_lim=10, show=0)[0]

    assert_array_almost_equal(x, xadj, decimal=8)
    assert_array_almost_equal(x, xinv, decimal=8)
Exemple #9
0
def test_FFT_small_real(par):
    dtype, decimal = par["dtype_precision"]
    norm = par["norm"]
    ifftshift_before = par["ifftshift_before"]
    engine = par["engine"]

    x = np.array([1, 0, -1, 1], dtype=dtype)

    FFTop = FFT(
        dims=x.shape,
        dir=0,
        norm=norm,
        real=True,
        ifftshift_before=ifftshift_before,
        dtype=dtype,
        engine=engine,
    )
    y = FFTop * x.ravel()

    if norm == "ortho":
        y_true = np.array([0.5, 1 + 0.5j, -0.5], dtype=FFTop.cdtype)
    elif norm == "none":
        y_true = np.array([1, 2 + 1j, -1], dtype=FFTop.cdtype)
    elif norm == "1/n":
        y_true = np.array([0.25, 0.5 + 0.25j, -0.25], dtype=FFTop.cdtype)

    y_true[1:-1] *= np.sqrt(2)  # Zero and Nyquist
    if ifftshift_before:
        # `ifftshift_before`` is useful when the time-axis is centered around zero as
        # it ensures the time axis to starts at zero:
        #     [-2, -1, 0, 1] ---ifftshift--> [0, 1, -2, -1]
        # This does not alter the amplitude of the FFT, but does alter the phase. To
        # match the results without ifftshift, we need to add a phase shift opposite to
        # the one introduced by FFT as given below. See "An FFT Primer for physicists",
        # by Thomas Kaiser.
        # https://www.iap.uni-jena.de/iapmedia/de/Lecture/Computational+Photonics/CoPho19_supp_FFT_primer.pdf
        x0 = -np.ceil(len(x) / 2)
        y_true *= np.exp(2 * np.pi * 1j * FFTop.f * x0)

    assert_array_almost_equal(y, y_true, decimal=decimal)
    assert dottest(FFTop, len(y), len(x), complexflag=0, tol=10**(-decimal))
    assert dottest(FFTop, len(y), len(x), complexflag=2, tol=10**(-decimal))

    x_inv = FFTop / y
    x_inv = x_inv.reshape(x.shape)
    assert_array_almost_equal(x_inv, x, decimal=decimal)
Exemple #10
0
def test_FFT_1dsignal(par):
    """Dot-test and comparison with pylops FFT operator for 1d signal
    """
    dt, f0 = 0.005, 10
    t = np.arange(par['nt']) * dt
    x = da.from_array(np.sin(2 * np.pi * f0 * t))
    nfft = par['nt'] if par['nfft'] is None else par['nfft']
    dFFTop = dFFT(dims=[par['nt']],
                  nfft=nfft,
                  sampling=dt,
                  real=par['real'],
                  chunks=(par['nt'], nfft),
                  dtype=par['dtype'])
    FFTop = FFT(dims=[par['nt']],
                nfft=nfft,
                sampling=dt,
                real=par['real'],
                dtype=par['dtype'])

    # FFT with real=True cannot pass dot-test neither be inverted correctly,
    # see FFT documentation for a detailed explanation. We thus test FFT.H*FFT
    if par['real']:
        dFFTop = dFFTop.H * dFFTop
        FFTop = FFTop.H * FFTop
        assert dottest(dFFTop,
                       par['nt'],
                       par['nt'],
                       chunks=(par['nt'], nfft),
                       complexflag=0)
    else:
        assert dottest(dFFTop,
                       nfft,
                       par['nt'],
                       chunks=(par['nt'], nfft),
                       complexflag=2)
        assert dottest(dFFTop,
                       nfft,
                       par['nt'],
                       chunks=(par['nt'], nfft),
                       complexflag=3)
    dy = dFFTop * x
    y = FFTop * x.compute()
    assert_array_almost_equal(dy, y, decimal=5)
Exemple #11
0
def test_FFT_small_complex(par):
    dtype, decimal = par["dtype_precision"]
    norm = par["norm"]
    ifftshift_before = par["ifftshift_before"]
    fftshift_after = par["fftshift_after"]

    x = np.array([1, 2 - 1j, -1j, -1 + 2j], dtype=dtype)

    FFTop = FFT(
        dims=x.shape,
        dir=0,
        norm=norm,
        ifftshift_before=ifftshift_before,
        fftshift_after=fftshift_after,
        dtype=dtype,
    )

    # Compute FFT of x independently
    if norm == "ortho":
        y_true = np.array([1, -1 - 1j, -1j, 2 + 2j], dtype=FFTop.cdtype)
    elif norm == "none":
        y_true = np.array([2, -2 - 2j, -2j, 4 + 4j], dtype=FFTop.cdtype)
    elif norm == "1/n":
        y_true = np.array([0.5, -0.5 - 0.5j, -0.5j, 1 + 1j],
                          dtype=FFTop.cdtype)

    if fftshift_after:
        y_true = np.fft.fftshift(y_true)
    if ifftshift_before:
        x0 = -np.ceil(x.shape[0] / 2)
        y_true *= np.exp(2 * np.pi * 1j * FFTop.f * x0)

    # Compute FFT with FFTop and compare with y_true
    y = FFTop * x.ravel()
    assert_array_almost_equal(y, y_true, decimal=decimal)
    assert dottest(FFTop, *FFTop.shape, complexflag=3, tol=10**(-decimal))

    x_inv = FFTop / y
    x_inv = x_inv.reshape(x.shape)
    assert_array_almost_equal(x_inv, x, decimal=decimal)
Exemple #12
0
def test_FFT_3dsignal(par):
    """Dot-test and inversion for fft operator for 3d signal
    (fft on single dimension)
    """
    dt = 0.005
    nt, nx, ny = par['nt'], par['nx'], par['ny']
    t = np.arange(nt) * dt
    f0 = 10
    d = np.outer(np.sin(2 * np.pi * f0 * t), np.arange(nx) + 1)
    d = np.tile(d[:, :, np.newaxis], [1, 1, ny])

    # 1st dimension
    nfft = par['nt'] if isinstance(par['nfft'], bool) else par['nfft']
    FFTop = FFT(dims=(nt, nx, ny),
                dir=0,
                nfft=nfft,
                sampling=dt,
                real=par['real'],
                engine=par['engine'])

    # FFT with real=True cannot pass dot-test neither be inverted correctly,
    # see FFT documentation for a detailed explanation. We thus test FFT.H*FFT
    if par['real']:
        FFTop = FFTop.H * FFTop
        assert dottest(FFTop, nt * nx * ny, nt * nx * ny, complexflag=0)
    else:
        assert dottest(FFTop, nfft * nx * ny, nt * nx * ny, complexflag=2)
        assert dottest(FFTop, nfft * nx * ny, nt * nx * ny, complexflag=3)

    D = FFTop * d.flatten()
    dadj = FFTop.H * D  # adjoint is same as inverse for fft
    dinv = lsqr(FFTop, D, damp=1e-10, iter_lim=10, show=0)[0]

    dadj = np.real(dadj.reshape(nt, nx, ny))
    dinv = np.real(dinv.reshape(nt, nx, ny))

    assert_array_almost_equal(d, dadj, decimal=8)
    assert_array_almost_equal(d, dinv, decimal=8)

    # 2nd dimension
    if isinstance(par['nfft'], bool):
        nfft = par['nx']
    FFTop = FFT(dims=(nt, nx, ny),
                dir=1,
                nfft=nfft,
                sampling=dt,
                real=par['real'],
                engine=par['engine'])

    # FFT with real=True cannot pass dot-test neither be inverted correctly,
    # see FFT documentation for a detailed explanation. We thus test FFT.H*FFT
    if par['real']:
        FFTop = FFTop.H * FFTop
        assert dottest(FFTop, nt * nx * ny, nt * nx * ny, complexflag=0)
    else:
        assert dottest(FFTop, nt * nfft * ny, nt * nx * ny, complexflag=2)
        assert dottest(FFTop, nt * nfft * ny, nt * nx * ny, complexflag=3)

    D = FFTop * d.flatten()
    dadj = FFTop.H * D  # adjoint is inverse for fft
    dinv = lsqr(FFTop, D, damp=1e-10, iter_lim=10, show=0)[0]

    dadj = np.real(dadj.reshape(nt, nx, ny))
    dinv = np.real(dinv.reshape(nt, nx, ny))

    assert_array_almost_equal(d, dadj, decimal=8)
    assert_array_almost_equal(d, dinv, decimal=8)

    # 3rd dimension
    if isinstance(par['nfft'], bool):
        nfft = par['ny']
    FFTop = FFT(dims=(nt, nx, ny),
                dir=2,
                nfft=nfft,
                sampling=dt,
                real=par['real'],
                engine=par['engine'])

    # FFT with real=True cannot pass dot-test neither be inverted correctly,
    # see FFT documentation for a detailed explanation. We thus test FFT.H*FFT
    if par['real']:
        FFTop = FFTop.H * FFTop
        assert dottest(FFTop, nt * nx * ny, nt * nx * ny, complexflag=0)
    else:
        assert dottest(FFTop, nt * nx * nfft, nt * nx * ny, complexflag=2)
        assert dottest(FFTop, nt * nx * nfft, nt * nx * ny, complexflag=3)

    D = FFTop * d.flatten()
    dadj = FFTop.H * D  # adjoint is inverse for fft
    dinv = lsqr(FFTop, D, damp=1e-10, iter_lim=10, show=0)[0]

    dadj = np.real(dadj.reshape(nt, nx, ny))
    dinv = np.real(dinv.reshape(nt, nx, ny))

    assert_array_almost_equal(d, dadj, decimal=8)
    assert_array_almost_equal(d, dinv, decimal=8)
Exemple #13
0
def test_FFT_2dsignal(par):
    """Dot-test and inversion for fft operator for 2d signal
    (fft on single dimension)
    """
    decimal = 4 if np.real(np.ones(1, par['dtype'])).dtype == np.float32 else 8

    dt = 0.005
    nt, nx = par['nt'], par['nx']
    t = np.arange(nt) * dt
    f0 = 10
    d = np.outer(np.sin(2 * np.pi * f0 * t), np.arange(nx) + 1)
    d = d.astype(par['dtype'])
    
    # 1st dimension
    nfft = par['nt'] if par['nfft'] is None else par['nfft']
    FFTop = FFT(dims=(nt, nx), dir=0, nfft=nfft, sampling=dt,
                real=par['real'], engine=par['engine'], dtype=par['dtype'])

    # FFT with real=True cannot pass dot-test neither be inverted correctly,
    # see FFT documentation for a detailed explanation. We thus test FFT.H*FFT
    if par['real']:
        FFTop = FFTop.H * FFTop
        assert dottest(FFTop, nt * nx, nt * nx,
                       complexflag=0, tol=10**(-decimal))
    else:
        assert dottest(FFTop, nfft * nx, nt * nx,
                       complexflag=2, tol=10**(-decimal))
        assert dottest(FFTop, nfft * nx, nt * nx,
                       complexflag=3, tol=10**(-decimal))

    D = FFTop * d.flatten()
    dadj = FFTop.H*D # adjoint is same as inverse for fft
    dinv = lsqr(FFTop, D, damp=1e-10, iter_lim=10, show=0)[0]

    dadj = np.real(dadj.reshape(nt, nx))
    dinv = np.real(dinv.reshape(nt, nx))

    assert_array_almost_equal(d, dadj, decimal=decimal)
    assert_array_almost_equal(d, dinv, decimal=decimal)

    # 2nd dimension
    nfft = par['nx'] if par['nfft'] is None else par['nfft']
    FFTop = FFT(dims=(nt, nx), dir=1, nfft=nfft, sampling=dt,
                real=par['real'], engine=par['engine'], dtype=par['dtype'])

    # FFT with real=True cannot pass dot-test neither be inverted correctly,
    # see FFT documentation for a detailed explanation. We thus test FFT.H*FFT
    if par['real']:
        FFTop = FFTop.H * FFTop
        assert dottest(FFTop, nt * nx, nt * nx,
                       complexflag=0, tol=10**(-decimal))
    else:
        assert dottest(FFTop, nt * nfft, nt * nx,
                       complexflag=2, tol=10**(-decimal))
        assert dottest(FFTop, nt * nfft, nt * nx,
                       complexflag=3, tol=10**(-decimal))

    D = FFTop * d.flatten()
    dadj = FFTop.H * D  # adjoint is inverse for fft
    dinv = lsqr(FFTop, D, damp=1e-10, iter_lim=10, show=0)[0]

    dadj = np.real(dadj.reshape(nt, nx))
    dinv = np.real(dinv.reshape(nt, nx))

    assert_array_almost_equal(d, dadj, decimal=decimal)
    assert_array_almost_equal(d, dinv, decimal=decimal)
Exemple #14
0
def test_FFT_2dsignal(par):
    """Dot-test and inversion for fft operator for 2d signal
    (fft on single dimension)
    """
    decimal = 3 if np.real(np.ones(1, par["dtype"])).dtype == np.float32 else 8

    dt = 0.005
    nt, nx = par["nt"], par["nx"]
    t = np.arange(nt) * dt
    f0 = 10
    d = np.outer(np.sin(2 * np.pi * f0 * t), np.arange(nx) + 1)
    d = d.astype(par["dtype"])

    # 1st dimension
    nfft = par["nt"] if par["nfft"] is None else par["nfft"]
    FFTop = FFT(
        dims=(nt, nx),
        dir=0,
        nfft=nfft,
        sampling=dt,
        real=par["real"],
        engine=par["engine"],
        dtype=par["dtype"],
    )

    if par["real"]:
        assert dottest(FFTop, (nfft // 2 + 1) * nx,
                       nt * nx,
                       complexflag=2,
                       tol=10**(-decimal))
    else:
        assert dottest(FFTop,
                       nfft * nx,
                       nt * nx,
                       complexflag=2,
                       tol=10**(-decimal))
        assert dottest(FFTop,
                       nfft * nx,
                       nt * nx,
                       complexflag=3,
                       tol=10**(-decimal))

    D = FFTop * d.ravel()
    dadj = FFTop.H * D  # adjoint is same as inverse for fft
    dinv = lsqr(FFTop, D, damp=1e-10, iter_lim=10, show=0)[0]

    dadj = np.real(dadj.reshape(nt, nx))
    dinv = np.real(dinv.reshape(nt, nx))

    # check all signal if nt>nfft and only up to nfft if nfft<nt
    imax = par["nt"] if par["nfft"] is None else min([par["nt"], par["nfft"]])
    assert_array_almost_equal(d[:imax], dadj[:imax], decimal=decimal)
    assert_array_almost_equal(d[:imax], dinv[:imax], decimal=decimal)

    if not par["real"]:
        FFTop_fftshift = FFT(
            dims=(nt, nx),
            dir=0,
            nfft=nfft,
            sampling=dt,
            real=par["real"],
            fftshift_after=True,
            engine=par["engine"],
            dtype=par["dtype"],
        )
        assert_array_almost_equal(FFTop_fftshift.f, np.fft.fftshift(FFTop.f))

        D_fftshift = FFTop_fftshift * d.flatten()
        D2 = np.fft.fftshift(D.reshape(nfft, nx), axes=0).flatten()
        assert_array_almost_equal(D_fftshift, D2)

        dadj = FFTop_fftshift.H * D_fftshift  # adjoint is same as inverse for fft
        dinv = lsqr(FFTop_fftshift,
                    D_fftshift,
                    damp=1e-10,
                    iter_lim=10,
                    show=0)[0]

        dadj = np.real(dadj.reshape(nt, nx))
        dinv = np.real(dinv.reshape(nt, nx))

        assert_array_almost_equal(d[:imax], dadj[:imax], decimal=decimal)
        assert_array_almost_equal(d[:imax], dinv[:imax], decimal=decimal)

    # 2nd dimension
    nfft = par["nx"] if par["nfft"] is None else par["nfft"]
    FFTop = FFT(
        dims=(nt, nx),
        dir=1,
        nfft=nfft,
        sampling=dt,
        real=par["real"],
        engine=par["engine"],
        dtype=par["dtype"],
    )

    if par["real"]:
        assert dottest(FFTop,
                       nt * (nfft // 2 + 1),
                       nt * nx,
                       complexflag=2,
                       tol=10**(-decimal))
    else:
        assert dottest(FFTop,
                       nt * nfft,
                       nt * nx,
                       complexflag=2,
                       tol=10**(-decimal))
        assert dottest(FFTop,
                       nt * nfft,
                       nt * nx,
                       complexflag=3,
                       tol=10**(-decimal))

    D = FFTop * d.ravel()
    dadj = FFTop.H * D  # adjoint is inverse for fft
    dinv = lsqr(FFTop, D, damp=1e-10, iter_lim=10, show=0)[0]

    dadj = np.real(dadj.reshape(nt, nx))
    dinv = np.real(dinv.reshape(nt, nx))

    # check all signal if nx>nfft and only up to nfft if nfft<nx
    imax = par["nx"] if par["nfft"] is None else min([par["nx"], par["nfft"]])
    assert_array_almost_equal(d[:, :imax], dadj[:, :imax], decimal=decimal)
    assert_array_almost_equal(d[:, :imax], dinv[:, :imax], decimal=decimal)

    if not par["real"]:
        FFTop_fftshift = FFT(
            dims=(nt, nx),
            dir=1,
            nfft=nfft,
            sampling=dt,
            real=par["real"],
            fftshift_after=True,
            engine=par["engine"],
            dtype=par["dtype"],
        )
        assert_array_almost_equal(FFTop_fftshift.f, np.fft.fftshift(FFTop.f))

        D_fftshift = FFTop_fftshift * d.flatten()
        D2 = np.fft.fftshift(D.reshape(nt, nfft), axes=1).flatten()
        assert_array_almost_equal(D_fftshift, D2)

        dadj = FFTop_fftshift.H * D_fftshift  # adjoint is same as inverse for fft
        dinv = lsqr(FFTop_fftshift,
                    D_fftshift,
                    damp=1e-10,
                    iter_lim=10,
                    show=0)[0]

        dadj = np.real(dadj.reshape(nt, nx))
        dinv = np.real(dinv.reshape(nt, nx))

        assert_array_almost_equal(d[:, :imax], dadj[:, :imax], decimal=decimal)
        assert_array_almost_equal(d[:, :imax], dinv[:, :imax], decimal=decimal)
Exemple #15
0
def test_FFT_1dsignal(par):
    """Dot-test and inversion for FFT operator for 1d signal"""
    decimal = 3 if np.real(np.ones(1, par["dtype"])).dtype == np.float32 else 8

    dt = 0.005
    t = np.arange(par["nt"]) * dt
    f0 = 10
    x = np.sin(2 * np.pi * f0 * t)
    x = x.astype(par["dtype"])
    nfft = par["nt"] if par["nfft"] is None else par["nfft"]

    FFTop = FFT(
        dims=[par["nt"]],
        nfft=nfft,
        sampling=dt,
        real=par["real"],
        engine=par["engine"],
        dtype=par["dtype"],
    )

    if par["real"]:
        assert dottest(FFTop,
                       nfft // 2 + 1,
                       par["nt"],
                       complexflag=2,
                       tol=10**(-decimal))
    else:
        assert dottest(FFTop,
                       nfft,
                       par["nt"],
                       complexflag=2,
                       tol=10**(-decimal))
        assert dottest(FFTop,
                       nfft,
                       par["nt"],
                       complexflag=3,
                       tol=10**(-decimal))

    y = FFTop * x
    xadj = FFTop.H * y  # adjoint is same as inverse for fft
    xinv = lsqr(FFTop, y, damp=1e-10, iter_lim=10, show=0)[0]

    # check all signal if nt>nfft and only up to nfft if nfft<nt
    imax = par["nt"] if par["nfft"] is None else min([par["nt"], par["nfft"]])
    assert_array_almost_equal(x[:imax], xadj[:imax], decimal=decimal)
    assert_array_almost_equal(x[:imax], xinv[:imax], decimal=decimal)

    if not par["real"]:
        FFTop_fftshift = FFT(
            dims=[par["nt"]],
            nfft=nfft,
            sampling=dt,
            real=par["real"],
            ifftshift_before=par["ifftshift_before"],
            fftshift_after=True,
            engine=par["engine"],
            dtype=par["dtype"],
        )
        assert_array_almost_equal(FFTop_fftshift.f, np.fft.fftshift(FFTop.f))

        y_fftshift = FFTop_fftshift * x
        assert_array_almost_equal(y_fftshift, np.fft.fftshift(y))

        xadj = FFTop_fftshift.H * y_fftshift  # adjoint is same as inverse for fft
        xinv = lsqr(FFTop_fftshift,
                    y_fftshift,
                    damp=1e-10,
                    iter_lim=10,
                    show=0)[0]
        assert_array_almost_equal(x[:imax], xadj[:imax], decimal=decimal)
        assert_array_almost_equal(x[:imax], xinv[:imax], decimal=decimal)
Exemple #16
0
def MDC(G,
        nt,
        nv,
        dt=1.,
        dr=1.,
        twosided=True,
        fast=None,
        dtype=None,
        fftengine='numpy',
        transpose=True,
        saveGt=True,
        conj=False):
    r"""Multi-dimensional convolution.

    Apply multi-dimensional convolution between two datasets. If
    ``transpose=True``, model and data should be provided after flattening
    2- or 3-dimensional arrays of size :math:`[n_r (\times n_{vs}) \times n_t]`
    and :math:`[n_s (\times n_{vs}) \times n_t]` (or :math:`2*n_t-1` for
    ``twosided=True``), respectively. If ``transpose=False``, model and data
    should be provided after flattening 2- or 3-dimensional arrays of size
    :math:`[n_t \times n_r (\times n_{vs})]` and
    :math:`[n_t \times n_s (\times n_{vs})]` (or :math:`2*n_t-1` for
    ``twosided=True``), respectively.

    .. warning:: A new implementation of MDC is provided in v1.5.0. This
      currently affects only the inner working of the operator and end-users
      can use the operator in the same way as they used to do with the previous
      one. Nevertheless, it is now reccomended to use the operator with
      ``transpose=False``, as this behaviour will become default in version
      v2.0.0 and the behaviour with ``transpose=True`` will be deprecated.

    Parameters
    ----------
    G : :obj:`numpy.ndarray`
        Multi-dimensional convolution kernel in frequency domain of size
        :math:`[\times n_s \times n_r \times n_{fmax}]` if ``transpose=True``
        or size :math:`[n_{fmax} \times n_s \times n_r]` if ``transpose=False``
    nt : :obj:`int`
        Number of samples along time axis
    nv : :obj:`int`
        Number of samples along virtual source axis
    dt : :obj:`float`, optional
        Sampling of time integration axis
    dr : :obj:`float`, optional
        Sampling of receiver integration axis
    twosided : :obj:`bool`, optional
        MDC operator has both negative and positive time (``True``) or
        only positive (``False``)
    fast : :obj:`bool`, optional
        *Deprecated*, will be removed in v2.0.0
    dtype : :obj:`str`, optional
        *Deprecated*, will be removed in v2.0.0
    fftengine : :obj:`str`, optional
        Engine used for fft computation (``numpy`` or ``fftw``)
    transpose : :obj:`bool`, optional
        Transpose ``G`` and inputs such that time/frequency is placed in first
        dimension. This allows back-compatibility with v1.4.0 and older but
        will be removed in v2.0.0 where time/frequency axis will be required
        to be in first dimension for efficiency reasons.
    saveGt : :obj:`bool`, optional
        Save ``G`` and ``G^H`` to speed up the computation of adjoint of
        :class:`pylops.signalprocessing.Fredholm1` (``True``) or create
        ``G^H`` on-the-fly (``False``) Note that ``saveGt=True`` will be
        faster but double the amount of required memory
    conj : :obj:`str`, optional
        Perform Fredholm integral computation with complex conjugate of ``G``

    See Also
    --------
    MDD : Multi-dimensional deconvolution

    Notes
    -----
    The so-called multi-dimensional convolution (MDC) is a chained
    operator [1]_. It is composed of a forward Fourier transform,
    a multi-dimensional integration, and an inverse Fourier transform:

    .. math::
        y(f, s, v) = \mathscr{F}^{-1} \Big( \int_S G(f, s, r)
        \mathscr{F}(x(f, r, v)) dr \Big)

    This operation can be discretized and performed by means of a
    linear operator

    .. math::
        \mathbf{D}= \mathbf{F}^H  \mathbf{G} \mathbf{F}

    where :math:`\mathbf{F}` is the Fourier transform applied along
    the time axis and :math:`\mathbf{G}` is the multi-dimensional
    convolution kernel.

    .. [1] Wapenaar, K., van der Neut, J., Ruigrok, E., Draganov, D., Hunziker,
       J., Slob, E., Thorbecke, J., and Snieder, R., "Seismic interferometry
       by crosscorrelation and by multi-dimensional deconvolution: a
       systematic comparison", Geophysical Journal International, vol. 185,
       pp. 1335-1364. 2011.

    """
    warnings.warn(
        'A new implementation of MDC is provided in v1.5.0. This '
        'currently affects only the inner working of the operator, '
        'end-users can continue using the operator in the same way. '
        'Nevertheless, it is now recommended to start using the '
        'operator with transpose=True, as this behaviour will '
        'become default in version v2.0.0 and the behaviour with '
        'transpose=False will be deprecated.', FutureWarning)

    if twosided and nt % 2 == 0:
        raise ValueError('nt must be odd number')

    # transpose G
    if transpose:
        G = np.transpose(G, axes=(2, 0, 1))

    # create Fredholm operator
    dtype = G[0, 0, 0].dtype
    fdtype = (G[0, 0, 0] + 1j * G[0, 0, 0]).dtype
    Frop = Fredholm1(dr * dt * np.sqrt(nt) * G,
                     nv,
                     saveGt=saveGt,
                     usematmul=False,
                     dtype=fdtype)
    if conj:
        Frop = Frop.conj()

    # create FFT operators
    nfmax, ns, nr = G.shape
    # ensure that nfmax is not bigger than allowed
    nfft = int(np.ceil((nt + 1) / 2))
    if nfmax > nfft:
        nfmax = nfft
        logging.warning('nfmax set equal to ceil[(nt+1)/2=%d]' % nfmax)

    Fop = FFT(dims=(nt, nr, nv),
              dir=0,
              real=True,
              fftshift=twosided,
              engine=fftengine,
              dtype=fdtype)
    F1op = FFT(dims=(nt, ns, nv),
               dir=0,
               real=True,
               fftshift=False,
               engine=fftengine,
               dtype=fdtype)

    # create Identity operator to extract only relevant frequencies
    Iop = Identity(N=nfmax * nr * nv,
                   M=nfft * nr * nv,
                   inplace=True,
                   dtype=dtype)
    I1op = Identity(N=nfmax * ns * nv,
                    M=nfft * ns * nv,
                    inplace=True,
                    dtype=dtype)
    F1opH = F1op.H
    I1opH = I1op.H

    # create transpose operator
    if transpose:
        dims = [nr, nt] if nv == 1 else [nr, nv, nt]
        axes = (1, 0) if nv == 1 else (2, 0, 1)
        Top = Transpose(dims, axes, dtype=dtype)

        dims = [nt, ns] if nv == 1 else [nt, ns, nv]
        axes = (1, 0) if nv == 1 else (1, 2, 0)
        TopH = Transpose(dims, axes, dtype=dtype)

    # create MDC operator
    MDCop = F1opH * I1opH * Frop * Iop * Fop
    if transpose:
        MDCop = TopH * MDCop * Top
    return MDCop
Exemple #17
0
def PhaseShift(vel, dz, nt, freq, kx, ky=None, dtype="float64"):
    r"""Phase shift operator

    Apply positive (forward) phase shift with constant velocity in
    forward mode, and negative (backward) phase shift with constant velocity in
    adjoint mode. Input model and data should be 2- or 3-dimensional arrays
    in time-space domain of size :math:`[n_t \times n_x \;(\times n_y)]`.

    Parameters
    ----------
    vel : :obj:`float`, optional
        Constant propagation velocity
    dz : :obj:`float`, optional
        Depth step
    nt : :obj:`int`, optional
        Number of time samples of model and data
    freq : :obj:`numpy.ndarray`
        Positive frequency axis
    kx : :obj:`int`, optional
        Horizontal wavenumber axis (centered around 0) of size
        :math:`[n_x \times 1]`.
    ky : :obj:`int`, optional
        Second horizontal wavenumber axis for 3d phase shift
        (centered around 0) of size :math:`[n_y \times 1]`.
    dtype : :obj:`str`, optional
        Type of elements in input array

    Returns
    -------
    Pop : :obj:`pylops.LinearOperator`
        Phase shift operator

    Notes
    -----
    The phase shift operator implements a one-way wave equation forward
    propagation in frequency-wavenumber domain by applying the following
    transformation to the input model:

    .. math::
        d(f, k_x, k_y) = m(f, k_x, k_y)
        e^{-j \Delta z \sqrt{\omega^2/v^2 - k_x^2 - k_y^2}}

    where :math:`v` is the constant propagation velocity and
    :math:`\Delta z` is the propagation depth. In adjoint mode, the data is
    propagated backward using the following transformation:

    .. math::
        m(f, k_x, k_y) = d(f, k_x, k_y)
        e^{j \Delta z \sqrt{\omega^2/v^2 - k_x^2 - k_y^2}}

    Effectively, the input model and data are assumed to be in time-space
    domain and forward Fourier transform is applied to both dimensions, leading
    to the following operator:

    .. math::
        \mathbf{d} = \mathbf{F}^H_t \mathbf{F}^H_x  \mathbf{P}
            \mathbf{F}_x \mathbf{F}_t \mathbf{m}

    where :math:`\mathbf{P}` perfoms the phase-shift as discussed above.

    """
    dtypefft = (np.ones(1, dtype=dtype) + 1j * np.ones(1, dtype=dtype)).dtype
    if ky is None:
        dims = (nt, kx.size)
        dimsfft = (freq.size, kx.size)
    else:
        dims = (nt, kx.size, ky.size)
        dimsfft = (freq.size, kx.size, ky.size)
    Fop = FFT(dims, dir=0, nfft=nt, real=True, dtype=dtype)
    Kxop = FFT(
        dimsfft, dir=1, nfft=kx.size, real=False, fftshift_after=True, dtype=dtypefft
    )
    if ky is not None:
        Kyop = FFT(
            dimsfft,
            dir=2,
            nfft=ky.size,
            real=False,
            fftshift_after=True,
            dtype=dtypefft,
        )
    Pop = _PhaseShift(vel, dz, freq, kx, ky, dtypefft)
    if ky is None:
        Pop = Fop.H * Kxop * Pop * Kxop.H * Fop
    else:
        Pop = Fop.H * Kxop * Kyop * Pop * Kyop.H * Kxop.H * Fop
    # Recasting of type is required to avoid FFT operators to cast to complex.
    # We know this is correct because forward and inverse FFTs are applied at
    # the beginning and end of this combined operator
    Pop.dtype = dtype
    return LinearOperator(Pop)
def test_FFT_2dsignal(par):
    """Dot-test and comparison with pylops FFT operator for 2d signal
    (fft on single dimension)
    """
    dt, f0 = 0.005, 10
    nt, nx = par['nt'], par['nx']
    t = np.arange(nt) * dt
    d = np.outer(np.sin(2 * np.pi * f0 * t), np.arange(nx) + 1)
    d = da.from_array(d)

    # 1st dimension
    nfft = par['nt'] if par['nfft'] is None else par['nfft']
    dFFTop = dFFT(dims=(nt, nx),
                  dir=0,
                  nfft=nfft,
                  sampling=dt,
                  real=par['real'],
                  chunks=((nt, nx), (nfft, nx)))
    FFTop = FFT(dims=(nt, nx), dir=0, nfft=nfft, sampling=dt)

    # FFT with real=True cannot pass dot-test neither be inverted correctly,
    # see FFT documentation for a detailed explanation. We thus test FFT.H*FFT
    if par['real']:
        dFFTop = dFFTop.H * dFFTop
        FFTop = FFTop.H * FFTop
        assert dottest(dFFTop,
                       nt * nx,
                       nt * nx,
                       chunks=(nt * nx, nt * nx),
                       complexflag=0)
    else:
        assert dottest(dFFTop,
                       nfft * nx,
                       nt * nx,
                       chunks=(nt * nx, nfft * nx),
                       complexflag=2)
        assert dottest(dFFTop,
                       nfft * nx,
                       nt * nx,
                       chunks=(nt * nx, nfft * nx),
                       complexflag=3)
    dy = dFFTop * d.ravel()
    y = FFTop * d.ravel().compute()
    assert_array_almost_equal(dy, y, decimal=5)

    # 2nd dimension
    nfft = par['nx'] if par['nfft'] is None else par['nfft']
    dFFTop = dFFT(dims=(nt, nx),
                  dir=1,
                  nfft=nfft,
                  sampling=dt,
                  real=par['real'],
                  chunks=((nt, nx), (nt, nfft)))
    FFTop = FFT(dims=(nt, nx), dir=1, nfft=nfft, sampling=dt)

    # FFT with real=True cannot pass dot-test neither be inverted correctly,
    # see FFT documentation for a detailed explanation. We thus test FFT.H*FFT
    if par['real']:
        dFFTop = dFFTop.H * dFFTop
        FFTop = FFTop.H * FFTop
        assert dottest(dFFTop,
                       nt * nx,
                       nt * nx,
                       chunks=(nt * nx, nt * nx),
                       complexflag=0)
    else:
        assert dottest(dFFTop,
                       nt * nfft,
                       nt * nx,
                       chunks=(nt * nx, nt * nfft),
                       complexflag=2)
        assert dottest(dFFTop,
                       nt * nfft,
                       nt * nx,
                       chunks=(nt * nx, nt * nfft),
                       complexflag=3)
    dy = dFFTop * d.ravel()
    y = FFTop * d.ravel().compute()
    assert_array_almost_equal(dy, y, decimal=5)