# Licensed under a 3-clause BSD style license - see LICENSE.rst import math import numpy as np import pytest from astropy.convolution.convolve import convolve, convolve_fft, convolve_models from astropy.modeling import models, fitting from astropy.utils.misc import NumpyRNGContext from astropy.utils.compat.optional_deps import HAS_SCIPY # noqa from numpy.testing import assert_allclose, assert_almost_equal class TestConvolve1DModels: @pytest.mark.parametrize('mode', ['convolve_fft', 'convolve']) @pytest.mark.skipif('not HAS_SCIPY') def test_is_consistency_with_astropy_convolution(self, mode): kernel = models.Gaussian1D(1, 0, 1) model = models.Gaussian1D(1, 0, 1) model_conv = convolve_models(model, kernel, mode=mode) x = np.arange(-5, 6) ans = eval(f"{mode}(model(x), kernel(x))") assert_allclose(ans, model_conv(x), atol=1e-5) @pytest.mark.parametrize('mode', ['convolve_fft', 'convolve']) @pytest.mark.skipif('not HAS_SCIPY') def test_against_scipy(self, mode): from scipy.signal import fftconvolve kernel = models.Gaussian1D(1, 0, 1) model = models.Gaussian1D(1, 0, 1) model_conv = convolve_models(model, kernel, mode=mode) x = np.arange(-5, 6) ans = fftconvolve(kernel(x), model(x), mode='same') assert_allclose(ans, model_conv(x) * kernel(x).sum(), atol=1e-5) @pytest.mark.parametrize('mode', ['convolve_fft', 'convolve']) @pytest.mark.skipif('not HAS_SCIPY') def test_against_scipy_with_additional_keywords(self, mode): from scipy.signal import fftconvolve kernel = models.Gaussian1D(1, 0, 1) model = models.Gaussian1D(1, 0, 1) model_conv = convolve_models(model, kernel, mode=mode, normalize_kernel=False) x = np.arange(-5, 6) ans = fftconvolve(kernel(x), model(x), mode='same') assert_allclose(ans, model_conv(x), atol=1e-5) @pytest.mark.parametrize('mode', ['convolve_fft', 'convolve']) def test_sum_of_gaussians(self, mode): """ Test that convolving N(a, b) with N(c, d) gives N(a + c, b + d), where N(., .) stands for Gaussian probability density function, in which a and c are their means and b and d are their variances. """ kernel = models.Gaussian1D(1 / math.sqrt(2 * np.pi), 1, 1) model = models.Gaussian1D(1 / math.sqrt(2 * np.pi), 3, 1) model_conv = convolve_models(model, kernel, mode=mode, normalize_kernel=False) ans = models.Gaussian1D(1 / (2 * math.sqrt(np.pi)), 4, np.sqrt(2)) x = np.arange(-5, 6) assert_allclose(ans(x), model_conv(x), atol=1e-3) @pytest.mark.parametrize('mode', ['convolve_fft', 'convolve']) def test_convolve_box_models(self, mode): kernel = models.Box1D() model = models.Box1D() model_conv = convolve_models(model, kernel, mode=mode) x = np.linspace(-1, 1, 99) ans = (x + 1) * (x < 0) + (-x + 1) * (x >= 0) assert_allclose(ans, model_conv(x), atol=1e-3) @pytest.mark.parametrize('mode', ['convolve_fft', 'convolve']) @pytest.mark.skipif('not HAS_SCIPY') def test_fitting_convolve_models(self, mode): """ test that a convolve model can be fitted """ b1 = models.Box1D() g1 = models.Gaussian1D() x = np.linspace(-5, 5, 99) fake_model = models.Gaussian1D(amplitude=10) with NumpyRNGContext(123): fake_data = fake_model(x) + np.random.normal(size=len(x)) init_model = convolve_models(b1, g1, mode=mode, normalize_kernel=False) fitter = fitting.LevMarLSQFitter() fitted_model = fitter(init_model, x, fake_data) me = np.mean(fitted_model(x) - fake_data) assert_almost_equal(me, 0.0, decimal=2)