| import os |
| import operator |
| import itertools |
| import math |
| import threading |
|
|
| import numpy as np |
| from numpy.testing import suppress_warnings |
| from scipy._lib._array_api import xp_assert_equal, xp_assert_close |
| from pytest import raises as assert_raises |
| import pytest |
|
|
| from scipy.interpolate import ( |
| BSpline, BPoly, PPoly, make_interp_spline, make_lsq_spline, |
| splev, splrep, splprep, splder, splantider, sproot, splint, insert, |
| CubicSpline, NdBSpline, make_smoothing_spline, RegularGridInterpolator, |
| ) |
| import scipy.linalg as sl |
| import scipy.sparse.linalg as ssl |
|
|
| from scipy.interpolate._bsplines import (_not_a_knot, _augknt, |
| _woodbury_algorithm, _periodic_knots, |
| _make_interp_per_full_matr) |
|
|
| from scipy.interpolate import generate_knots, make_splrep, make_splprep |
|
|
| import scipy.interpolate._fitpack_impl as _impl |
| from scipy._lib._util import AxisError |
| from scipy._lib._testutils import _run_concurrent_barrier |
|
|
| |
| from scipy.interpolate._ndbspline import make_ndbspl |
|
|
| from scipy.interpolate import _dfitpack as dfitpack |
| from scipy.interpolate import _bsplines as _b |
| from scipy.interpolate import _dierckx |
|
|
|
|
| class TestBSpline: |
|
|
| def test_ctor(self): |
| |
| assert_raises((TypeError, ValueError), BSpline, |
| **dict(t=[1, 1.j], c=[1.], k=0)) |
| with np.errstate(invalid='ignore'): |
| assert_raises(ValueError, BSpline, **dict(t=[1, np.nan], c=[1.], k=0)) |
| assert_raises(ValueError, BSpline, **dict(t=[1, np.inf], c=[1.], k=0)) |
| assert_raises(ValueError, BSpline, **dict(t=[1, -1], c=[1.], k=0)) |
| assert_raises(ValueError, BSpline, **dict(t=[[1], [1]], c=[1.], k=0)) |
|
|
| |
| assert_raises(ValueError, BSpline, **dict(t=[0, 1, 2], c=[1], k=0)) |
| assert_raises(ValueError, BSpline, |
| **dict(t=[0, 1, 2, 3, 4], c=[1., 1.], k=2)) |
|
|
| |
| assert_raises(TypeError, BSpline, |
| **dict(t=[0., 0., 1., 2., 3., 4.], c=[1., 1., 1.], k="cubic")) |
| assert_raises(TypeError, BSpline, |
| **dict(t=[0., 0., 1., 2., 3., 4.], c=[1., 1., 1.], k=2.5)) |
|
|
| |
| assert_raises(ValueError, BSpline, |
| **dict(t=[0., 0, 1, 1, 2, 3], c=[1., 1, 1], k=2)) |
|
|
| |
| n, k = 11, 3 |
| t = np.arange(n+k+1, dtype=np.float64) |
| c = np.random.random(n) |
| b = BSpline(t, c, k) |
|
|
| xp_assert_close(t, b.t) |
| xp_assert_close(c, b.c) |
| assert k == b.k |
|
|
| def test_tck(self): |
| b = _make_random_spline() |
| tck = b.tck |
|
|
| xp_assert_close(b.t, tck[0], atol=1e-15, rtol=1e-15) |
| xp_assert_close(b.c, tck[1], atol=1e-15, rtol=1e-15) |
| assert b.k == tck[2] |
|
|
| |
| with pytest.raises(AttributeError): |
| b.tck = 'foo' |
|
|
| def test_degree_0(self): |
| xx = np.linspace(0, 1, 10) |
|
|
| b = BSpline(t=[0, 1], c=[3.], k=0) |
| xp_assert_close(b(xx), np.ones_like(xx) * 3.0) |
|
|
| b = BSpline(t=[0, 0.35, 1], c=[3, 4], k=0) |
| xp_assert_close(b(xx), np.where(xx < 0.35, 3.0, 4.0)) |
|
|
| def test_degree_1(self): |
| t = [0, 1, 2, 3, 4] |
| c = [1, 2, 3] |
| k = 1 |
| b = BSpline(t, c, k) |
|
|
| x = np.linspace(1, 3, 50) |
| xp_assert_close(c[0]*B_012(x) + c[1]*B_012(x-1) + c[2]*B_012(x-2), |
| b(x), atol=1e-14) |
| xp_assert_close(splev(x, (t, c, k)), b(x), atol=1e-14) |
|
|
| def test_bernstein(self): |
| |
| k = 3 |
| t = np.asarray([0]*(k+1) + [1]*(k+1)) |
| c = np.asarray([1., 2., 3., 4.]) |
| bp = BPoly(c.reshape(-1, 1), [0, 1]) |
| bspl = BSpline(t, c, k) |
|
|
| xx = np.linspace(-1., 2., 10) |
| xp_assert_close(bp(xx, extrapolate=True), |
| bspl(xx, extrapolate=True), atol=1e-14) |
| xp_assert_close(splev(xx, (t, c, k)), |
| bspl(xx), atol=1e-14) |
|
|
| def test_rndm_naive_eval(self): |
| |
| |
| b = _make_random_spline() |
| t, c, k = b.tck |
| xx = np.linspace(t[k], t[-k-1], 50) |
| y_b = b(xx) |
|
|
| y_n = [_naive_eval(x, t, c, k) for x in xx] |
| xp_assert_close(y_b, y_n, atol=1e-14) |
|
|
| y_n2 = [_naive_eval_2(x, t, c, k) for x in xx] |
| xp_assert_close(y_b, y_n2, atol=1e-14) |
|
|
| def test_rndm_splev(self): |
| b = _make_random_spline() |
| t, c, k = b.tck |
| xx = np.linspace(t[k], t[-k-1], 50) |
| xp_assert_close(b(xx), splev(xx, (t, c, k)), atol=1e-14) |
|
|
| def test_rndm_splrep(self): |
| rng = np.random.RandomState(1234) |
| x = np.sort(rng.random(20)) |
| y = rng.random(20) |
|
|
| tck = splrep(x, y) |
| b = BSpline(*tck) |
|
|
| t, k = b.t, b.k |
| xx = np.linspace(t[k], t[-k-1], 80) |
| xp_assert_close(b(xx), splev(xx, tck), atol=1e-14) |
|
|
| def test_rndm_unity(self): |
| b = _make_random_spline() |
| b.c = np.ones_like(b.c) |
| xx = np.linspace(b.t[b.k], b.t[-b.k-1], 100) |
| xp_assert_close(b(xx), np.ones_like(xx)) |
|
|
| def test_vectorization(self): |
| rng = np.random.RandomState(1234) |
| n, k = 22, 3 |
| t = np.sort(rng.random(n)) |
| c = rng.random(size=(n, 6, 7)) |
| b = BSpline(t, c, k) |
| tm, tp = t[k], t[-k-1] |
| xx = tm + (tp - tm) * rng.random((3, 4, 5)) |
| assert b(xx).shape == (3, 4, 5, 6, 7) |
|
|
| def test_len_c(self): |
| |
| |
| rng = np.random.RandomState(1234) |
| n, k = 33, 3 |
| t = np.sort(rng.random(n+k+1)) |
| c = rng.random(n) |
|
|
| |
| c_pad = np.r_[c, rng.random(k+1)] |
|
|
| b, b_pad = BSpline(t, c, k), BSpline(t, c_pad, k) |
|
|
| dt = t[-1] - t[0] |
| xx = np.linspace(t[0] - dt, t[-1] + dt, 50) |
| xp_assert_close(b(xx), b_pad(xx), atol=1e-14) |
| xp_assert_close(b(xx), splev(xx, (t, c, k)), atol=1e-14) |
| xp_assert_close(b(xx), splev(xx, (t, c_pad, k)), atol=1e-14) |
|
|
| def test_endpoints(self, num_parallel_threads): |
| |
| b = _make_random_spline() |
| t, _, k = b.tck |
| tm, tp = t[k], t[-k-1] |
| |
| for extrap in (True, False): |
| xp_assert_close(b([tm, tp], extrap), |
| b([tm + 1e-10, tp - 1e-10], extrap), atol=1e-9, rtol=1e-7) |
|
|
| def test_continuity(self, num_parallel_threads): |
| |
| b = _make_random_spline() |
| t, _, k = b.tck |
| xp_assert_close(b(t[k+1:-k-1] - 1e-10), b(t[k+1:-k-1] + 1e-10), |
| atol=1e-9) |
|
|
| def test_extrap(self): |
| b = _make_random_spline() |
| t, c, k = b.tck |
| dt = t[-1] - t[0] |
| xx = np.linspace(t[k] - dt, t[-k-1] + dt, 50) |
| mask = (t[k] < xx) & (xx < t[-k-1]) |
|
|
| |
| xp_assert_close(b(xx[mask], extrapolate=True), |
| b(xx[mask], extrapolate=False)) |
|
|
| |
| xp_assert_close(b(xx, extrapolate=True), |
| splev(xx, (t, c, k), ext=0)) |
|
|
| def test_default_extrap(self): |
| |
| b = _make_random_spline() |
| t, _, k = b.tck |
| xx = [t[0] - 1, t[-1] + 1] |
| yy = b(xx) |
| assert not np.all(np.isnan(yy)) |
|
|
| def test_periodic_extrap(self): |
| rng = np.random.RandomState(1234) |
| t = np.sort(rng.random(8)) |
| c = rng.random(4) |
| k = 3 |
| b = BSpline(t, c, k, extrapolate='periodic') |
| n = t.size - (k + 1) |
|
|
| dt = t[-1] - t[0] |
| xx = np.linspace(t[k] - dt, t[n] + dt, 50) |
| xy = t[k] + (xx - t[k]) % (t[n] - t[k]) |
| xp_assert_close(b(xx), splev(xy, (t, c, k))) |
|
|
| |
| xx = [-1, 0, 0.5, 1] |
| xy = t[k] + (xx - t[k]) % (t[n] - t[k]) |
| xp_assert_equal(b(xx, extrapolate='periodic'), b(xy, extrapolate=True)) |
|
|
| def test_ppoly(self): |
| b = _make_random_spline() |
| t, c, k = b.tck |
| pp = PPoly.from_spline((t, c, k)) |
|
|
| xx = np.linspace(t[k], t[-k], 100) |
| xp_assert_close(b(xx), pp(xx), atol=1e-14, rtol=1e-14) |
|
|
| def test_derivative_rndm(self): |
| b = _make_random_spline() |
| t, c, k = b.tck |
| xx = np.linspace(t[0], t[-1], 50) |
| xx = np.r_[xx, t] |
|
|
| for der in range(1, k+1): |
| yd = splev(xx, (t, c, k), der=der) |
| xp_assert_close(yd, b(xx, nu=der), atol=1e-14) |
|
|
| |
| xp_assert_close(b(xx, nu=k+1), np.zeros_like(xx), atol=1e-14) |
|
|
| def test_derivative_jumps(self): |
| |
| |
| |
| k = 2 |
| t = [-1, -1, 0, 1, 1, 3, 4, 6, 6, 6, 7, 7] |
| rng = np.random.RandomState(1234) |
| c = np.r_[0, 0, rng.random(5), 0, 0] |
| b = BSpline(t, c, k) |
|
|
| |
| x = np.asarray([1, 3, 4, 6]) |
| xp_assert_close(b(x[x != 6] - 1e-10), |
| b(x[x != 6] + 1e-10)) |
| assert not np.allclose(b(6.-1e-10), b(6+1e-10)) |
|
|
| |
| x0 = np.asarray([3, 4]) |
| xp_assert_close(b(x0 - 1e-10, nu=1), |
| b(x0 + 1e-10, nu=1)) |
| x1 = np.asarray([1, 6]) |
| assert not np.allclose(b(x1 - 1e-10, nu=1), b(x1 + 1e-10, nu=1)) |
|
|
| |
| assert not np.allclose(b(x - 1e-10, nu=2), b(x + 1e-10, nu=2)) |
|
|
| def test_basis_element_quadratic(self): |
| xx = np.linspace(-1, 4, 20) |
| b = BSpline.basis_element(t=[0, 1, 2, 3]) |
| xp_assert_close(b(xx), |
| splev(xx, (b.t, b.c, b.k)), atol=1e-14) |
| xp_assert_close(b(xx), |
| B_0123(xx), atol=1e-14) |
|
|
| b = BSpline.basis_element(t=[0, 1, 1, 2]) |
| xx = np.linspace(0, 2, 10) |
| xp_assert_close(b(xx), |
| np.where(xx < 1, xx*xx, (2.-xx)**2), atol=1e-14) |
|
|
| def test_basis_element_rndm(self): |
| b = _make_random_spline() |
| t, c, k = b.tck |
| xx = np.linspace(t[k], t[-k-1], 20) |
| xp_assert_close(b(xx), _sum_basis_elements(xx, t, c, k), atol=1e-14) |
|
|
| def test_cmplx(self): |
| b = _make_random_spline() |
| t, c, k = b.tck |
| cc = c * (1. + 3.j) |
|
|
| b = BSpline(t, cc, k) |
| b_re = BSpline(t, b.c.real, k) |
| b_im = BSpline(t, b.c.imag, k) |
|
|
| xx = np.linspace(t[k], t[-k-1], 20) |
| xp_assert_close(b(xx).real, b_re(xx), atol=1e-14) |
| xp_assert_close(b(xx).imag, b_im(xx), atol=1e-14) |
|
|
| def test_nan(self): |
| |
| b = BSpline.basis_element([0, 1, 1, 2]) |
| assert np.isnan(b(np.nan)) |
|
|
| def test_derivative_method(self): |
| b = _make_random_spline(k=5) |
| t, c, k = b.tck |
| b0 = BSpline(t, c, k) |
| xx = np.linspace(t[k], t[-k-1], 20) |
| for j in range(1, k): |
| b = b.derivative() |
| xp_assert_close(b0(xx, j), b(xx), atol=1e-12, rtol=1e-12) |
|
|
| def test_antiderivative_method(self): |
| b = _make_random_spline() |
| t, c, k = b.tck |
| xx = np.linspace(t[k], t[-k-1], 20) |
| xp_assert_close(b.antiderivative().derivative()(xx), |
| b(xx), atol=1e-14, rtol=1e-14) |
|
|
| |
| c = np.c_[c, c, c] |
| c = np.dstack((c, c)) |
| b = BSpline(t, c, k) |
| xp_assert_close(b.antiderivative().derivative()(xx), |
| b(xx), atol=1e-14, rtol=1e-14) |
|
|
| def test_integral(self): |
| b = BSpline.basis_element([0, 1, 2]) |
| xp_assert_close(b.integrate(0, 1), np.asarray(0.5)) |
| xp_assert_close(b.integrate(1, 0), np.asarray(-1 * 0.5)) |
| xp_assert_close(b.integrate(1, 0), np.asarray(-0.5)) |
|
|
| |
| xp_assert_close(b.integrate(-1, 1), np.asarray(0.0)) |
| xp_assert_close(b.integrate(-1, 1, extrapolate=True), np.asarray(0.0)) |
| xp_assert_close(b.integrate(-1, 1, extrapolate=False), np.asarray(0.5)) |
| xp_assert_close(b.integrate(1, -1, extrapolate=False), np.asarray(-1 * 0.5)) |
|
|
| |
| xp_assert_close(b.integrate(1, -1, extrapolate=False), |
| np.asarray(_impl.splint(1, -1, b.tck))) |
|
|
| |
| b.extrapolate = 'periodic' |
| i = b.antiderivative() |
| period_int = np.asarray(i(2) - i(0)) |
|
|
| xp_assert_close(b.integrate(0, 2), period_int) |
| xp_assert_close(b.integrate(2, 0), np.asarray(-1 * period_int)) |
| xp_assert_close(b.integrate(-9, -7), period_int) |
| xp_assert_close(b.integrate(-8, -4), np.asarray(2 * period_int)) |
|
|
| xp_assert_close(b.integrate(0.5, 1.5), |
| np.asarray(i(1.5) - i(0.5))) |
| xp_assert_close(b.integrate(1.5, 3), |
| np.asarray(i(1) - i(0) + i(2) - i(1.5))) |
| xp_assert_close(b.integrate(1.5 + 12, 3 + 12), |
| np.asarray(i(1) - i(0) + i(2) - i(1.5))) |
| xp_assert_close(b.integrate(1.5, 3 + 12), |
| np.asarray(i(1) - i(0) + i(2) - i(1.5) + 6 * period_int)) |
|
|
| xp_assert_close(b.integrate(0, -1), np.asarray(i(0) - i(1))) |
| xp_assert_close(b.integrate(-9, -10), np.asarray(i(0) - i(1))) |
| xp_assert_close(b.integrate(0, -9), |
| np.asarray(i(1) - i(2) - 4 * period_int)) |
|
|
| def test_integrate_ppoly(self): |
| |
| x = [0, 1, 2, 3, 4] |
| b = make_interp_spline(x, x) |
| b.extrapolate = 'periodic' |
| p = PPoly.from_spline(b) |
|
|
| for x0, x1 in [(-5, 0.5), (0.5, 5), (-4, 13)]: |
| xp_assert_close(b.integrate(x0, x1), |
| p.integrate(x0, x1)) |
|
|
| def test_integrate_0D_always(self): |
| |
| b = BSpline.basis_element([0, 1, 2]) |
| for extrapolate in (True, False): |
| res = b.integrate(0, 1, extrapolate=extrapolate) |
| assert isinstance(res, np.ndarray) |
| assert res.ndim == 0 |
|
|
| def test_subclassing(self): |
| |
| class B(BSpline): |
| pass |
|
|
| b = B.basis_element([0, 1, 2, 2]) |
| assert b.__class__ == B |
| assert b.derivative().__class__ == B |
| assert b.antiderivative().__class__ == B |
|
|
| @pytest.mark.parametrize('axis', range(-4, 4)) |
| def test_axis(self, axis): |
| n, k = 22, 3 |
| t = np.linspace(0, 1, n + k + 1) |
| sh = [6, 7, 8] |
| |
| |
| pos_axis = axis % 4 |
| sh.insert(pos_axis, n) |
| sh = tuple(sh) |
| rng = np.random.RandomState(1234) |
| c = rng.random(size=sh) |
| b = BSpline(t, c, k, axis=axis) |
| assert b.c.shape == (sh[pos_axis],) + sh[:pos_axis] + sh[pos_axis+1:] |
|
|
| xp = rng.random((3, 4, 5)) |
| assert b(xp).shape == sh[:pos_axis] + xp.shape + sh[pos_axis+1:] |
|
|
| |
| for ax in [-c.ndim - 1, c.ndim]: |
| assert_raises(AxisError, BSpline, |
| **dict(t=t, c=c, k=k, axis=ax)) |
|
|
| |
| for b1 in [BSpline(t, c, k, axis=axis).derivative(), |
| BSpline(t, c, k, axis=axis).derivative(2), |
| BSpline(t, c, k, axis=axis).antiderivative(), |
| BSpline(t, c, k, axis=axis).antiderivative(2)]: |
| assert b1.axis == b.axis |
|
|
| def test_neg_axis(self): |
| k = 2 |
| t = [0, 1, 2, 3, 4, 5, 6] |
| c = np.array([[-1, 2, 0, -1], [2, 0, -3, 1]]) |
|
|
| spl = BSpline(t, c, k, axis=-1) |
| spl0 = BSpline(t, c[0], k) |
| spl1 = BSpline(t, c[1], k) |
| xp_assert_equal(spl(2.5), [spl0(2.5), spl1(2.5)]) |
|
|
| @pytest.mark.thread_unsafe |
| def test_design_matrix_bc_types(self): |
| ''' |
| Splines with different boundary conditions are built on different |
| types of vectors of knots. As far as design matrix depends only on |
| vector of knots, `k` and `x` it is useful to make tests for different |
| boundary conditions (and as following different vectors of knots). |
| ''' |
| def run_design_matrix_tests(n, k, bc_type): |
| ''' |
| To avoid repetition of code the following function is provided. |
| ''' |
| rng = np.random.RandomState(1234) |
| x = np.sort(rng.random_sample(n) * 40 - 20) |
| y = rng.random_sample(n) * 40 - 20 |
| if bc_type == "periodic": |
| y[0] = y[-1] |
|
|
| bspl = make_interp_spline(x, y, k=k, bc_type=bc_type) |
|
|
| c = np.eye(len(bspl.t) - k - 1) |
| des_matr_def = BSpline(bspl.t, c, k)(x) |
| des_matr_csr = BSpline.design_matrix(x, |
| bspl.t, |
| k).toarray() |
| xp_assert_close(des_matr_csr @ bspl.c, y, atol=1e-14) |
| xp_assert_close(des_matr_def, des_matr_csr, atol=1e-14) |
|
|
| |
| n = 11 |
| k = 3 |
| for bc in ["clamped", "natural"]: |
| run_design_matrix_tests(n, k, bc) |
|
|
| |
| for k in range(3, 8, 2): |
| run_design_matrix_tests(n, k, "not-a-knot") |
|
|
| |
| n = 5 |
| for k in range(2, 7): |
| run_design_matrix_tests(n, k, "periodic") |
|
|
| @pytest.mark.parametrize('extrapolate', [False, True, 'periodic']) |
| @pytest.mark.parametrize('degree', range(5)) |
| def test_design_matrix_same_as_BSpline_call(self, extrapolate, degree): |
| """Test that design_matrix(x) is equivalent to BSpline(..)(x).""" |
| rng = np.random.RandomState(1234) |
| x = rng.random_sample(10 * (degree + 1)) |
| xmin, xmax = np.amin(x), np.amax(x) |
| k = degree |
| t = np.r_[np.linspace(xmin - 2, xmin - 1, degree), |
| np.linspace(xmin, xmax, 2 * (degree + 1)), |
| np.linspace(xmax + 1, xmax + 2, degree)] |
| c = np.eye(len(t) - k - 1) |
| bspline = BSpline(t, c, k, extrapolate) |
| xp_assert_close( |
| bspline(x), BSpline.design_matrix(x, t, k, extrapolate).toarray() |
| ) |
|
|
| |
| x = np.array([xmin - 10, xmin - 1, xmax + 1.5, xmax + 10]) |
| if not extrapolate: |
| with pytest.raises(ValueError): |
| BSpline.design_matrix(x, t, k, extrapolate) |
| else: |
| xp_assert_close( |
| bspline(x), |
| BSpline.design_matrix(x, t, k, extrapolate).toarray() |
| ) |
|
|
| def test_design_matrix_x_shapes(self): |
| |
| rng = np.random.RandomState(1234) |
| n = 10 |
| k = 3 |
| x = np.sort(rng.random_sample(n) * 40 - 20) |
| y = rng.random_sample(n) * 40 - 20 |
|
|
| bspl = make_interp_spline(x, y, k=k) |
| for i in range(1, 4): |
| xc = x[:i] |
| yc = y[:i] |
| des_matr_csr = BSpline.design_matrix(xc, |
| bspl.t, |
| k).toarray() |
| xp_assert_close(des_matr_csr @ bspl.c, yc, atol=1e-14) |
|
|
| def test_design_matrix_t_shapes(self): |
| |
| t = [1., 1., 1., 2., 3., 4., 4., 4.] |
| des_matr = BSpline.design_matrix(2., t, 3).toarray() |
| xp_assert_close(des_matr, |
| [[0.25, 0.58333333, 0.16666667, 0.]], |
| atol=1e-14) |
|
|
| def test_design_matrix_asserts(self): |
| rng = np.random.RandomState(1234) |
| n = 10 |
| k = 3 |
| x = np.sort(rng.random_sample(n) * 40 - 20) |
| y = rng.random_sample(n) * 40 - 20 |
| bspl = make_interp_spline(x, y, k=k) |
| |
| |
| with assert_raises(ValueError): |
| BSpline.design_matrix(x, bspl.t[::-1], k) |
| k = 2 |
| t = [0., 1., 2., 3., 4., 5.] |
| x = [1., 2., 3., 4.] |
| |
| with assert_raises(ValueError): |
| BSpline.design_matrix(x, t, k) |
|
|
| @pytest.mark.parametrize('bc_type', ['natural', 'clamped', |
| 'periodic', 'not-a-knot']) |
| def test_from_power_basis(self, bc_type): |
| rng = np.random.RandomState(1234) |
| x = np.sort(rng.random(20)) |
| y = rng.random(20) |
| if bc_type == 'periodic': |
| y[-1] = y[0] |
| cb = CubicSpline(x, y, bc_type=bc_type) |
| bspl = BSpline.from_power_basis(cb, bc_type=bc_type) |
| xx = np.linspace(0, 1, 20) |
| xp_assert_close(cb(xx), bspl(xx), atol=1e-15) |
| bspl_new = make_interp_spline(x, y, bc_type=bc_type) |
| xp_assert_close(bspl.c, bspl_new.c, atol=1e-15) |
|
|
| @pytest.mark.parametrize('bc_type', ['natural', 'clamped', |
| 'periodic', 'not-a-knot']) |
| def test_from_power_basis_complex(self, bc_type): |
| rng = np.random.RandomState(1234) |
| x = np.sort(rng.random(20)) |
| y = rng.random(20) + rng.random(20) * 1j |
| if bc_type == 'periodic': |
| y[-1] = y[0] |
| cb = CubicSpline(x, y, bc_type=bc_type) |
| bspl = BSpline.from_power_basis(cb, bc_type=bc_type) |
| bspl_new_real = make_interp_spline(x, y.real, bc_type=bc_type) |
| bspl_new_imag = make_interp_spline(x, y.imag, bc_type=bc_type) |
| xp_assert_close(bspl.c, bspl_new_real.c + 1j * bspl_new_imag.c, atol=1e-15) |
|
|
| def test_from_power_basis_exmp(self): |
| ''' |
| For x = [0, 1, 2, 3, 4] and y = [1, 1, 1, 1, 1] |
| the coefficients of Cubic Spline in the power basis: |
| |
| $[[0, 0, 0, 0, 0],\\$ |
| $[0, 0, 0, 0, 0],\\$ |
| $[0, 0, 0, 0, 0],\\$ |
| $[1, 1, 1, 1, 1]]$ |
| |
| It could be shown explicitly that coefficients of the interpolating |
| function in B-spline basis are c = [1, 1, 1, 1, 1, 1, 1] |
| ''' |
| x = np.array([0, 1, 2, 3, 4]) |
| y = np.array([1, 1, 1, 1, 1]) |
| bspl = BSpline.from_power_basis(CubicSpline(x, y, bc_type='natural'), |
| bc_type='natural') |
| xp_assert_close(bspl.c, [1.0, 1, 1, 1, 1, 1, 1], atol=1e-15) |
|
|
| def test_read_only(self): |
| |
| t = np.array([0, 1]) |
| c = np.array([3.0]) |
| t.setflags(write=False) |
| c.setflags(write=False) |
|
|
| xx = np.linspace(0, 1, 10) |
| xx.setflags(write=False) |
|
|
| b = BSpline(t=t, c=c, k=0) |
| xp_assert_close(b(xx), np.ones_like(xx) * 3.0) |
|
|
| @pytest.mark.thread_unsafe |
| def test_concurrency(self): |
| |
| b = _make_random_spline() |
|
|
| def worker_fn(_, b): |
| t, _, k = b.tck |
| xx = np.linspace(t[k], t[-k-1], 10000) |
| b(xx) |
|
|
| _run_concurrent_barrier(10, worker_fn, b) |
|
|
|
|
| def test_memmap(self, tmpdir): |
| |
| |
| |
| |
| |
| b = _make_random_spline() |
| xx = np.linspace(0, 1, 10) |
|
|
| expected = b(xx) |
|
|
| tid = threading.get_native_id() |
| t_mm = np.memmap(str(tmpdir.join(f't{tid}.dat')), mode='w+', |
| dtype=b.t.dtype, shape=b.t.shape) |
| t_mm[:] = b.t |
| c_mm = np.memmap(str(tmpdir.join(f'c{tid}.dat')), mode='w+', |
| dtype=b.c.dtype, shape=b.c.shape) |
| c_mm[:] = b.c |
| b.t = t_mm |
| b.c = c_mm |
|
|
| xp_assert_close(b(xx), expected) |
|
|
| class TestInsert: |
|
|
| @pytest.mark.parametrize('xval', [0.0, 1.0, 2.5, 4, 6.5, 7.0]) |
| def test_insert(self, xval): |
| |
| x = np.arange(8) |
| y = np.sin(x)**3 |
| spl = make_interp_spline(x, y, k=3) |
|
|
| spl_1f = insert(xval, spl) |
| spl_1 = spl.insert_knot(xval) |
|
|
| xp_assert_close(spl_1.t, spl_1f.t, atol=1e-15) |
| xp_assert_close(spl_1.c, spl_1f.c[:-spl.k-1], atol=1e-15) |
|
|
| |
| xx = x if xval != x[-1] else x[:-1] |
| xx = np.r_[xx, 0.5*(x[1:] + x[:-1])] |
| xp_assert_close(spl(xx), spl_1(xx), atol=1e-15) |
|
|
| |
| y1 = np.cos(x)**3 |
| spl_y1 = make_interp_spline(x, y1, k=3) |
| spl_yy = make_interp_spline(x, np.c_[y, y1], k=3) |
| spl_yy1 = spl_yy.insert_knot(xval) |
|
|
| xp_assert_close(spl_yy1.t, spl_1.t, atol=1e-15) |
| xp_assert_close(spl_yy1.c, np.c_[spl.insert_knot(xval).c, |
| spl_y1.insert_knot(xval).c], atol=1e-15) |
|
|
| xx = x if xval != x[-1] else x[:-1] |
| xx = np.r_[xx, 0.5*(x[1:] + x[:-1])] |
| xp_assert_close(spl_yy(xx), spl_yy1(xx), atol=1e-15) |
|
|
|
|
| @pytest.mark.parametrize( |
| 'xval, m', [(0.0, 2), (1.0, 3), (1.5, 5), (4, 2), (7.0, 2)] |
| ) |
| def test_insert_multi(self, xval, m): |
| x = np.arange(8) |
| y = np.sin(x)**3 |
| spl = make_interp_spline(x, y, k=3) |
|
|
| spl_1f = insert(xval, spl, m=m) |
| spl_1 = spl.insert_knot(xval, m) |
|
|
| xp_assert_close(spl_1.t, spl_1f.t, atol=1e-15) |
| xp_assert_close(spl_1.c, spl_1f.c[:-spl.k-1], atol=1e-15) |
|
|
| xx = x if xval != x[-1] else x[:-1] |
| xx = np.r_[xx, 0.5*(x[1:] + x[:-1])] |
| xp_assert_close(spl(xx), spl_1(xx), atol=1e-15) |
|
|
| def test_insert_random(self): |
| rng = np.random.default_rng(12345) |
| n, k = 11, 3 |
|
|
| t = np.sort(rng.uniform(size=n+k+1)) |
| c = rng.uniform(size=(n, 3, 2)) |
| spl = BSpline(t, c, k) |
|
|
| xv = rng.uniform(low=t[k+1], high=t[-k-1]) |
| spl_1 = spl.insert_knot(xv) |
|
|
| xx = rng.uniform(low=t[k+1], high=t[-k-1], size=33) |
| xp_assert_close(spl(xx), spl_1(xx), atol=1e-15) |
|
|
| @pytest.mark.parametrize('xv', [0, 0.1, 2.0, 4.0, 4.5, |
| 5.5, 6.0, 6.1, 7.0] |
| ) |
| def test_insert_periodic(self, xv): |
| x = np.arange(8) |
| y = np.sin(x)**3 |
| tck = splrep(x, y, k=3) |
| spl = BSpline(*tck, extrapolate="periodic") |
|
|
| spl_1 = spl.insert_knot(xv) |
| tf, cf, k = insert(xv, spl.tck, per=True) |
|
|
| xp_assert_close(spl_1.t, tf, atol=1e-15) |
| xp_assert_close(spl_1.c[:-k-1], cf[:-k-1], atol=1e-15) |
|
|
| xx = np.random.default_rng(1234).uniform(low=0, high=7, size=41) |
| xp_assert_close(spl_1(xx), splev(xx, (tf, cf, k)), atol=1e-15) |
|
|
| @pytest.mark.parametrize('extrapolate', [None, 'periodic']) |
| def test_complex(self, extrapolate): |
| x = np.arange(8)*2*np.pi |
| y_re, y_im = np.sin(x), np.cos(x) |
|
|
| spl = make_interp_spline(x, y_re + 1j*y_im, k=3) |
| spl.extrapolate = extrapolate |
|
|
| spl_re = make_interp_spline(x, y_re, k=3) |
| spl_re.extrapolate = extrapolate |
|
|
| spl_im = make_interp_spline(x, y_im, k=3) |
| spl_im.extrapolate = extrapolate |
|
|
| xv = 3.5 |
| spl_1 = spl.insert_knot(xv) |
| spl_1re = spl_re.insert_knot(xv) |
| spl_1im = spl_im.insert_knot(xv) |
|
|
| xp_assert_close(spl_1.t, spl_1re.t, atol=1e-15) |
| xp_assert_close(spl_1.t, spl_1im.t, atol=1e-15) |
| xp_assert_close(spl_1.c, spl_1re.c + 1j*spl_1im.c, atol=1e-15) |
|
|
| def test_insert_periodic_too_few_internal_knots(self): |
| |
| |
| |
| |
| |
| |
| |
| xv = 3.5 |
| k = 3 |
| t = np.array([0]*(k+1) + [2, 3, 4, 5] + [7]*(k+1)) |
| c = np.ones(len(t) - k - 1) |
| spl = BSpline(t, c, k, extrapolate="periodic") |
|
|
| with assert_raises(ValueError): |
| insert(xv, (t, c, k), per=True) |
|
|
| with assert_raises(ValueError): |
| spl.insert_knot(xv) |
|
|
| def test_insert_no_extrap(self): |
| k = 3 |
| t = np.array([0]*(k+1) + [2, 3, 4, 5] + [7]*(k+1)) |
| c = np.ones(len(t) - k - 1) |
| spl = BSpline(t, c, k) |
|
|
| with assert_raises(ValueError): |
| spl.insert_knot(-1) |
|
|
| with assert_raises(ValueError): |
| spl.insert_knot(8) |
|
|
| with assert_raises(ValueError): |
| spl.insert_knot(3, m=0) |
|
|
|
|
| def test_knots_multiplicity(): |
| |
| |
|
|
| def check_splev(b, j, der=0, atol=1e-14, rtol=1e-14): |
| |
| t, c, k = b.tck |
| x = np.unique(t) |
| x = np.r_[t[0]-0.1, 0.5*(x[1:] + x[:1]), t[-1]+0.1] |
| xp_assert_close(splev(x, (t, c, k), der), b(x, der), |
| atol=atol, rtol=rtol, err_msg=f'der = {der} k = {b.k}') |
|
|
| |
| |
| for k in [1, 2, 3, 4, 5]: |
| b = _make_random_spline(k=k) |
| for j, b1 in enumerate(_make_multiples(b)): |
| check_splev(b1, j) |
| for der in range(1, k+1): |
| check_splev(b1, j, der, 1e-12, 1e-12) |
|
|
|
|
| |
| def _naive_B(x, k, i, t): |
| """ |
| Naive way to compute B-spline basis functions. Useful only for testing! |
| computes B(x; t[i],..., t[i+k+1]) |
| """ |
| if k == 0: |
| return 1.0 if t[i] <= x < t[i+1] else 0.0 |
| if t[i+k] == t[i]: |
| c1 = 0.0 |
| else: |
| c1 = (x - t[i])/(t[i+k] - t[i]) * _naive_B(x, k-1, i, t) |
| if t[i+k+1] == t[i+1]: |
| c2 = 0.0 |
| else: |
| c2 = (t[i+k+1] - x)/(t[i+k+1] - t[i+1]) * _naive_B(x, k-1, i+1, t) |
| return (c1 + c2) |
|
|
|
|
| |
| def _naive_eval(x, t, c, k): |
| """ |
| Naive B-spline evaluation. Useful only for testing! |
| """ |
| if x == t[k]: |
| i = k |
| else: |
| i = np.searchsorted(t, x) - 1 |
| assert t[i] <= x <= t[i+1] |
| assert i >= k and i < len(t) - k |
| return sum(c[i-j] * _naive_B(x, k, i-j, t) for j in range(0, k+1)) |
|
|
|
|
| def _naive_eval_2(x, t, c, k): |
| """Naive B-spline evaluation, another way.""" |
| n = len(t) - (k+1) |
| assert n >= k+1 |
| assert len(c) >= n |
| assert t[k] <= x <= t[n] |
| return sum(c[i] * _naive_B(x, k, i, t) for i in range(n)) |
|
|
|
|
| def _sum_basis_elements(x, t, c, k): |
| n = len(t) - (k+1) |
| assert n >= k+1 |
| assert len(c) >= n |
| s = 0. |
| for i in range(n): |
| b = BSpline.basis_element(t[i:i+k+2], extrapolate=False)(x) |
| s += c[i] * np.nan_to_num(b) |
| return s |
|
|
|
|
| def B_012(x): |
| """ A linear B-spline function B(x | 0, 1, 2).""" |
| x = np.atleast_1d(x) |
| return np.piecewise(x, [(x < 0) | (x > 2), |
| (x >= 0) & (x < 1), |
| (x >= 1) & (x <= 2)], |
| [lambda x: 0., lambda x: x, lambda x: 2.-x]) |
|
|
|
|
| def B_0123(x, der=0): |
| """A quadratic B-spline function B(x | 0, 1, 2, 3).""" |
| x = np.atleast_1d(x) |
| conds = [x < 1, (x > 1) & (x < 2), x > 2] |
| if der == 0: |
| funcs = [lambda x: x*x/2., |
| lambda x: 3./4 - (x-3./2)**2, |
| lambda x: (3.-x)**2 / 2] |
| elif der == 2: |
| funcs = [lambda x: 1., |
| lambda x: -2., |
| lambda x: 1.] |
| else: |
| raise ValueError(f'never be here: der={der}') |
| pieces = np.piecewise(x, conds, funcs) |
| return pieces |
|
|
|
|
| def _make_random_spline(n=35, k=3): |
| rng = np.random.RandomState(123) |
| t = np.sort(rng.random(n+k+1)) |
| c = rng.random(n) |
| return BSpline.construct_fast(t, c, k) |
|
|
|
|
| def _make_multiples(b): |
| """Increase knot multiplicity.""" |
| c, k = b.c, b.k |
|
|
| t1 = b.t.copy() |
| t1[17:19] = t1[17] |
| t1[22] = t1[21] |
| yield BSpline(t1, c, k) |
|
|
| t1 = b.t.copy() |
| t1[:k+1] = t1[0] |
| yield BSpline(t1, c, k) |
|
|
| t1 = b.t.copy() |
| t1[-k-1:] = t1[-1] |
| yield BSpline(t1, c, k) |
|
|
|
|
| class TestInterop: |
| |
| |
| |
| def setup_method(self): |
| xx = np.linspace(0, 4.*np.pi, 41) |
| yy = np.cos(xx) |
| b = make_interp_spline(xx, yy) |
| self.tck = (b.t, b.c, b.k) |
| self.xx, self.yy, self.b = xx, yy, b |
|
|
| self.xnew = np.linspace(0, 4.*np.pi, 21) |
|
|
| c2 = np.c_[b.c, b.c, b.c] |
| self.c2 = np.dstack((c2, c2)) |
| self.b2 = BSpline(b.t, self.c2, b.k) |
|
|
| def test_splev(self): |
| xnew, b, b2 = self.xnew, self.b, self.b2 |
|
|
| |
| |
| xp_assert_close(splev(xnew, b), |
| b(xnew), atol=1e-15, rtol=1e-15) |
| xp_assert_close(splev(xnew, b.tck), |
| b(xnew), atol=1e-15, rtol=1e-15) |
| xp_assert_close(np.asarray([splev(x, b) for x in xnew]), |
| b(xnew), atol=1e-15, rtol=1e-15) |
|
|
| |
| |
| with assert_raises(ValueError, match="Calling splev.. with BSpline"): |
| splev(xnew, b2) |
|
|
| |
| |
| |
| |
| sh = tuple(range(1, b2.c.ndim)) + (0,) |
| cc = b2.c.transpose(sh) |
| tck = (b2.t, cc, b2.k) |
| xp_assert_close(np.asarray(splev(xnew, tck)), |
| b2(xnew).transpose(sh), atol=1e-15, rtol=1e-15) |
|
|
| def test_splrep(self): |
| x, y = self.xx, self.yy |
| |
| tck = splrep(x, y) |
| t, c, k = _impl.splrep(x, y) |
| xp_assert_close(tck[0], t, atol=1e-15) |
| xp_assert_close(tck[1], c, atol=1e-15) |
| assert tck[2] == k |
|
|
| |
| tck_f, _, _, _ = splrep(x, y, full_output=True) |
| xp_assert_close(tck_f[0], t, atol=1e-15) |
| xp_assert_close(tck_f[1], c, atol=1e-15) |
| assert tck_f[2] == k |
|
|
| |
| |
| yy = splev(x, tck) |
| xp_assert_close(y, yy, atol=1e-15) |
|
|
| |
| b = BSpline(*tck) |
| xp_assert_close(y, b(x), atol=1e-15) |
|
|
| def test_splrep_errors(self): |
| |
| |
| x, y = self.xx, self.yy |
| y2 = np.c_[y, y] |
| with assert_raises(ValueError): |
| splrep(x, y2) |
| with assert_raises(ValueError): |
| _impl.splrep(x, y2) |
|
|
| |
| with assert_raises(TypeError, match="m > k must hold"): |
| splrep(x[:3], y[:3]) |
| with assert_raises(TypeError, match="m > k must hold"): |
| _impl.splrep(x[:3], y[:3]) |
|
|
| def test_splprep(self): |
| x = np.arange(15, dtype=np.float64).reshape((3, 5)) |
| b, u = splprep(x) |
| tck, u1 = _impl.splprep(x) |
|
|
| |
| xp_assert_close(u, u1, atol=1e-15) |
| xp_assert_close(np.asarray(splev(u, b)), x, atol=1e-15) |
| xp_assert_close(np.asarray(splev(u, tck)), x, atol=1e-15) |
|
|
| |
| (b_f, u_f), _, _, _ = splprep(x, s=0, full_output=True) |
| xp_assert_close(u, u_f, atol=1e-15) |
| xp_assert_close(np.asarray(splev(u_f, b_f)), x, atol=1e-15) |
|
|
| def test_splprep_errors(self): |
| |
| x = np.arange(3*4*5).reshape((3, 4, 5)) |
| with assert_raises(ValueError, match="too many values to unpack"): |
| splprep(x) |
| with assert_raises(ValueError, match="too many values to unpack"): |
| _impl.splprep(x) |
|
|
| |
| x = np.linspace(0, 40, num=3) |
| with assert_raises(TypeError, match="m > k must hold"): |
| splprep([x]) |
| with assert_raises(TypeError, match="m > k must hold"): |
| _impl.splprep([x]) |
|
|
| |
| |
| x = [-50.49072266, -50.49072266, -54.49072266, -54.49072266] |
| with assert_raises(ValueError, match="Invalid inputs"): |
| splprep([x]) |
| with assert_raises(ValueError, match="Invalid inputs"): |
| _impl.splprep([x]) |
|
|
| |
| x = [1, 3, 2, 4] |
| u = [0, 0.3, 0.2, 1] |
| with assert_raises(ValueError, match="Invalid inputs"): |
| splprep(*[[x], None, u]) |
|
|
| def test_sproot(self): |
| b, b2 = self.b, self.b2 |
| roots = np.array([0.5, 1.5, 2.5, 3.5])*np.pi |
| |
| xp_assert_close(sproot(b), roots, atol=1e-7, rtol=1e-7) |
| xp_assert_close(sproot((b.t, b.c, b.k)), roots, atol=1e-7, rtol=1e-7) |
|
|
| |
| with assert_raises(ValueError, match="Calling sproot.. with BSpline"): |
| sproot(b2, mest=50) |
|
|
| |
| c2r = b2.c.transpose(1, 2, 0) |
| rr = np.asarray(sproot((b2.t, c2r, b2.k), mest=50)) |
| assert rr.shape == (3, 2, 4) |
| xp_assert_close(rr - roots, np.zeros_like(rr), atol=1e-12) |
|
|
| def test_splint(self): |
| |
| b, b2 = self.b, self.b2 |
|
|
| xp_assert_close(splint(0, 1, b), |
| splint(0, 1, b.tck), atol=1e-14, check_0d=False) |
| xp_assert_close(splint(0, 1, b), |
| b.integrate(0, 1), atol=1e-14, check_0d=False) |
|
|
| |
| with assert_raises(ValueError, match="Calling splint.. with BSpline"): |
| splint(0, 1, b2) |
|
|
| |
| c2r = b2.c.transpose(1, 2, 0) |
| integr = np.asarray(splint(0, 1, (b2.t, c2r, b2.k))) |
| assert integr.shape == (3, 2) |
| xp_assert_close(integr, |
| splint(0, 1, b), atol=1e-14, check_shape=False) |
|
|
| def test_splder(self): |
| for b in [self.b, self.b2]: |
| |
| ct = len(b.t) - len(b.c) |
| b_c = b.c.copy() |
| if ct > 0: |
| b_c = np.r_[b_c, np.zeros((ct,) + b_c.shape[1:])] |
|
|
| for n in [1, 2, 3]: |
| bd = splder(b) |
| tck_d = _impl.splder((b.t.copy(), b_c, b.k)) |
| xp_assert_close(bd.t, tck_d[0], atol=1e-15) |
| xp_assert_close(bd.c, tck_d[1], atol=1e-15) |
| assert bd.k == tck_d[2] |
| assert isinstance(bd, BSpline) |
| assert isinstance(tck_d, tuple) |
|
|
| def test_splantider(self): |
| for b in [self.b, self.b2]: |
| |
| ct = len(b.t) - len(b.c) |
| b_c = b.c.copy() |
| if ct > 0: |
| b_c = np.r_[b_c, np.zeros((ct,) + b_c.shape[1:])] |
|
|
| for n in [1, 2, 3]: |
| bd = splantider(b) |
| tck_d = _impl.splantider((b.t.copy(), b_c, b.k)) |
| xp_assert_close(bd.t, tck_d[0], atol=1e-15) |
| xp_assert_close(bd.c, tck_d[1], atol=1e-15) |
| assert bd.k == tck_d[2] |
| assert isinstance(bd, BSpline) |
| assert isinstance(tck_d, tuple) |
|
|
| def test_insert(self): |
| b, b2, xx = self.b, self.b2, self.xx |
|
|
| j = b.t.size // 2 |
| tn = 0.5*(b.t[j] + b.t[j+1]) |
|
|
| bn, tck_n = insert(tn, b), insert(tn, (b.t, b.c, b.k)) |
| xp_assert_close(splev(xx, bn), |
| splev(xx, tck_n), atol=1e-15) |
| assert isinstance(bn, BSpline) |
| assert isinstance(tck_n, tuple) |
|
|
| |
| |
| sh = tuple(range(b2.c.ndim)) |
| c_ = b2.c.transpose(sh[1:] + (0,)) |
| tck_n2 = insert(tn, (b2.t, c_, b2.k)) |
|
|
| bn2 = insert(tn, b2) |
|
|
| |
| xp_assert_close(np.asarray(splev(xx, tck_n2)).transpose(2, 0, 1), |
| bn2(xx), atol=1e-15) |
| assert isinstance(bn2, BSpline) |
| assert isinstance(tck_n2, tuple) |
|
|
|
|
| class TestInterp: |
| |
| |
| |
| xx = np.linspace(0., 2.*np.pi) |
| yy = np.sin(xx) |
|
|
| def test_non_int_order(self): |
| with assert_raises(TypeError): |
| make_interp_spline(self.xx, self.yy, k=2.5) |
|
|
| def test_order_0(self): |
| b = make_interp_spline(self.xx, self.yy, k=0) |
| xp_assert_close(b(self.xx), self.yy, atol=1e-14, rtol=1e-14) |
| b = make_interp_spline(self.xx, self.yy, k=0, axis=-1) |
| xp_assert_close(b(self.xx), self.yy, atol=1e-14, rtol=1e-14) |
|
|
| def test_linear(self): |
| b = make_interp_spline(self.xx, self.yy, k=1) |
| xp_assert_close(b(self.xx), self.yy, atol=1e-14, rtol=1e-14) |
| b = make_interp_spline(self.xx, self.yy, k=1, axis=-1) |
| xp_assert_close(b(self.xx), self.yy, atol=1e-14, rtol=1e-14) |
|
|
| @pytest.mark.parametrize('k', [0, 1, 2, 3]) |
| def test_incompatible_x_y(self, k): |
| x = [0, 1, 2, 3, 4, 5] |
| y = [0, 1, 2, 3, 4, 5, 6, 7] |
| with assert_raises(ValueError, match="Shapes of x"): |
| make_interp_spline(x, y, k=k) |
|
|
| @pytest.mark.parametrize('k', [0, 1, 2, 3]) |
| def test_broken_x(self, k): |
| x = [0, 1, 1, 2, 3, 4] |
| y = [0, 1, 2, 3, 4, 5] |
| with assert_raises(ValueError, match="x to not have duplicates"): |
| make_interp_spline(x, y, k=k) |
|
|
| x = [0, 2, 1, 3, 4, 5] |
| with assert_raises(ValueError, match="Expect x to be a 1D strictly"): |
| make_interp_spline(x, y, k=k) |
|
|
| x = [0, 1, 2, 3, 4, 5] |
| x = np.asarray(x).reshape((1, -1)) |
| with assert_raises(ValueError, match="Expect x to be a 1D strictly"): |
| make_interp_spline(x, y, k=k) |
|
|
| def test_not_a_knot(self): |
| for k in [2, 3, 4, 5, 6, 7]: |
| b = make_interp_spline(self.xx, self.yy, k) |
| xp_assert_close(b(self.xx), self.yy, atol=1e-14, rtol=1e-14) |
|
|
| def test_periodic(self): |
| |
| b = make_interp_spline(self.xx, self.yy, k=5, bc_type='periodic') |
| xp_assert_close(b(self.xx), self.yy, atol=1e-14, rtol=1e-14) |
| |
| |
| for i in range(1, 5): |
| xp_assert_close(b(self.xx[0], nu=i), b(self.xx[-1], nu=i), atol=1e-11) |
| |
| b = make_interp_spline(self.xx, self.yy, k=5, bc_type='periodic', axis=-1) |
| xp_assert_close(b(self.xx), self.yy, atol=1e-14, rtol=1e-14) |
| for i in range(1, 5): |
| xp_assert_close(b(self.xx[0], nu=i), b(self.xx[-1], nu=i), atol=1e-11) |
|
|
| @pytest.mark.parametrize('k', [2, 3, 4, 5, 6, 7]) |
| def test_periodic_random(self, k): |
| |
| n = 5 |
| rng = np.random.RandomState(1234) |
| x = np.sort(rng.random_sample(n) * 10) |
| y = rng.random_sample(n) * 100 |
| y[0] = y[-1] |
| b = make_interp_spline(x, y, k=k, bc_type='periodic') |
| xp_assert_close(b(x), y, atol=1e-14) |
|
|
| def test_periodic_axis(self): |
| n = self.xx.shape[0] |
| rng = np.random.RandomState(1234) |
| x = rng.random_sample(n) * 2 * np.pi |
| x = np.sort(x) |
| x[0] = 0. |
| x[-1] = 2 * np.pi |
| y = np.zeros((2, n)) |
| y[0] = np.sin(x) |
| y[1] = np.cos(x) |
| b = make_interp_spline(x, y, k=5, bc_type='periodic', axis=1) |
| for i in range(n): |
| xp_assert_close(b(x[i]), y[:, i], atol=1e-14) |
| xp_assert_close(b(x[0]), b(x[-1]), atol=1e-14) |
|
|
| def test_periodic_points_exception(self): |
| |
| rng = np.random.RandomState(1234) |
| k = 5 |
| n = 8 |
| x = np.sort(rng.random_sample(n)) |
| y = rng.random_sample(n) |
| y[0] = y[-1] - 1 |
| with assert_raises(ValueError): |
| make_interp_spline(x, y, k=k, bc_type='periodic') |
|
|
| def test_periodic_knots_exception(self): |
| |
| rng = np.random.RandomState(1234) |
| k = 3 |
| n = 7 |
| x = np.sort(rng.random_sample(n)) |
| y = rng.random_sample(n) |
| t = np.zeros(n + 2 * k) |
| with assert_raises(ValueError): |
| make_interp_spline(x, y, k, t, 'periodic') |
|
|
| @pytest.mark.parametrize('k', [2, 3, 4, 5]) |
| def test_periodic_splev(self, k): |
| |
| b = make_interp_spline(self.xx, self.yy, k=k, bc_type='periodic') |
| tck = splrep(self.xx, self.yy, per=True, k=k) |
| spl = splev(self.xx, tck) |
| xp_assert_close(spl, b(self.xx), atol=1e-14) |
|
|
| |
| for i in range(1, k): |
| spl = splev(self.xx, tck, der=i) |
| xp_assert_close(spl, b(self.xx, nu=i), atol=1e-10) |
|
|
| def test_periodic_cubic(self): |
| |
| b = make_interp_spline(self.xx, self.yy, k=3, bc_type='periodic') |
| cub = CubicSpline(self.xx, self.yy, bc_type='periodic') |
| xp_assert_close(b(self.xx), cub(self.xx), atol=1e-14) |
|
|
| |
| rng = np.random.RandomState(1234) |
| n = 3 |
| x = np.sort(rng.random_sample(n) * 10) |
| y = rng.random_sample(n) * 100 |
| y[0] = y[-1] |
| b = make_interp_spline(x, y, k=3, bc_type='periodic') |
| cub = CubicSpline(x, y, bc_type='periodic') |
| xp_assert_close(b(x), cub(x), atol=1e-14) |
|
|
| def test_periodic_full_matrix(self): |
| |
| |
| k = 3 |
| b = make_interp_spline(self.xx, self.yy, k=k, bc_type='periodic') |
| t = _periodic_knots(self.xx, k) |
| c = _make_interp_per_full_matr(self.xx, self.yy, t, k) |
| b1 = np.vectorize(lambda x: _naive_eval(x, t, c, k)) |
| xp_assert_close(b(self.xx), b1(self.xx), atol=1e-14) |
|
|
| def test_quadratic_deriv(self): |
| der = [(1, 8.)] |
|
|
| |
| b = make_interp_spline(self.xx, self.yy, k=2, bc_type=(None, der)) |
| xp_assert_close(b(self.xx), self.yy, atol=1e-14, rtol=1e-14) |
| xp_assert_close( |
| b(self.xx[-1], 1), der[0][1], atol=1e-14, rtol=1e-14, check_0d=False |
| ) |
|
|
| |
| b = make_interp_spline(self.xx, self.yy, k=2, bc_type=(der, None)) |
| xp_assert_close(b(self.xx), self.yy, atol=1e-14, rtol=1e-14) |
| xp_assert_close( |
| b(self.xx[0], 1), der[0][1], atol=1e-14, rtol=1e-14, check_0d=False |
| ) |
|
|
| def test_cubic_deriv(self): |
| k = 3 |
|
|
| |
| der_l, der_r = [(1, 3.)], [(1, 4.)] |
| b = make_interp_spline(self.xx, self.yy, k, bc_type=(der_l, der_r)) |
| xp_assert_close(b(self.xx), self.yy, atol=1e-14, rtol=1e-14) |
| xp_assert_close(np.asarray([b(self.xx[0], 1), b(self.xx[-1], 1)]), |
| np.asarray([der_l[0][1], der_r[0][1]]), atol=1e-14, rtol=1e-14) |
|
|
| |
| der_l, der_r = [(2, 0)], [(2, 0)] |
| b = make_interp_spline(self.xx, self.yy, k, bc_type=(der_l, der_r)) |
| xp_assert_close(b(self.xx), self.yy, atol=1e-14, rtol=1e-14) |
|
|
| def test_quintic_derivs(self): |
| k, n = 5, 7 |
| x = np.arange(n).astype(np.float64) |
| y = np.sin(x) |
| der_l = [(1, -12.), (2, 1)] |
| der_r = [(1, 8.), (2, 3.)] |
| b = make_interp_spline(x, y, k=k, bc_type=(der_l, der_r)) |
| xp_assert_close(b(x), y, atol=1e-14, rtol=1e-14) |
| xp_assert_close(np.asarray([b(x[0], 1), b(x[0], 2)]), |
| np.asarray([val for (nu, val) in der_l])) |
| xp_assert_close(np.asarray([b(x[-1], 1), b(x[-1], 2)]), |
| np.asarray([val for (nu, val) in der_r])) |
|
|
| @pytest.mark.xfail(reason='unstable') |
| def test_cubic_deriv_unstable(self): |
| |
| |
| |
| |
| |
| |
| k = 3 |
| t = _augknt(self.xx, k) |
|
|
| der_l = [(1, 3.), (2, 4.)] |
| b = make_interp_spline(self.xx, self.yy, k, t, bc_type=(der_l, None)) |
| xp_assert_close(b(self.xx), self.yy, atol=1e-14, rtol=1e-14) |
|
|
| def test_knots_not_data_sites(self): |
| |
| |
| |
| k = 2 |
| t = np.r_[(self.xx[0],)*(k+1), |
| (self.xx[1:] + self.xx[:-1]) / 2., |
| (self.xx[-1],)*(k+1)] |
| b = make_interp_spline(self.xx, self.yy, k, t, |
| bc_type=([(2, 0)], [(2, 0)])) |
|
|
| xp_assert_close(b(self.xx), self.yy, atol=1e-14, rtol=1e-14) |
| xp_assert_close(b(self.xx[0], 2), np.asarray(0.0), atol=1e-14) |
| xp_assert_close(b(self.xx[-1], 2), np.asarray(0.0), atol=1e-14) |
|
|
| def test_minimum_points_and_deriv(self): |
| |
| |
| k = 3 |
| x = [0., 1.] |
| y = [0., 1.] |
| b = make_interp_spline(x, y, k, bc_type=([(1, 0.)], [(1, 3.)])) |
|
|
| xx = np.linspace(0., 1.) |
| yy = xx**3 |
| xp_assert_close(b(xx), yy, atol=1e-14, rtol=1e-14) |
|
|
| def test_deriv_spec(self): |
| |
| |
| x = y = [1.0, 2, 3, 4, 5, 6] |
|
|
| with assert_raises(ValueError): |
| make_interp_spline(x, y, bc_type=([(1, 0.)], None)) |
|
|
| with assert_raises(ValueError): |
| make_interp_spline(x, y, bc_type=(1, 0.)) |
|
|
| with assert_raises(ValueError): |
| make_interp_spline(x, y, bc_type=[(1, 0.)]) |
|
|
| with assert_raises(ValueError): |
| make_interp_spline(x, y, bc_type=42) |
|
|
| |
| |
| l, r = (1, 0.0), (1, 0.0) |
| with assert_raises(ValueError): |
| make_interp_spline(x, y, bc_type=(l, r)) |
|
|
| def test_deriv_order_too_large(self): |
| x = np.arange(7) |
| y = x**2 |
| l, r = [(6, 0)], [(1, 0)] |
| with assert_raises(ValueError, match="Bad boundary conditions at 0."): |
| |
| make_interp_spline(x, y, bc_type=(l, r)) |
|
|
| l, r = [(1, 0)], [(-6, 0)] |
| with assert_raises(ValueError, match="Bad boundary conditions at 6."): |
| |
| make_interp_spline(x, y, bc_type=(l, r)) |
|
|
| def test_complex(self): |
| k = 3 |
| xx = self.xx |
| yy = self.yy + 1.j*self.yy |
|
|
| |
| der_l, der_r = [(1, 3.j)], [(1, 4.+2.j)] |
| b = make_interp_spline(xx, yy, k, bc_type=(der_l, der_r)) |
| xp_assert_close(b(xx), yy, atol=1e-14, rtol=1e-14) |
| xp_assert_close( |
| b(xx[0], 1), der_l[0][1], atol=1e-14, rtol=1e-14, check_0d=False |
| ) |
| xp_assert_close( |
| b(xx[-1], 1), der_r[0][1], atol=1e-14, rtol=1e-14, check_0d=False |
| ) |
|
|
| |
| for k in (0, 1): |
| b = make_interp_spline(xx, yy, k=k) |
| xp_assert_close(b(xx), yy, atol=1e-14, rtol=1e-14) |
|
|
| def test_int_xy(self): |
| x = np.arange(10).astype(int) |
| y = np.arange(10).astype(int) |
|
|
| |
| |
| for k in (0, 1, 2, 3): |
| b = make_interp_spline(x, y, k=k) |
| b(x) |
|
|
| def test_sliced_input(self): |
| |
| xx = np.linspace(-1, 1, 100) |
|
|
| x = xx[::5] |
| y = xx[::5] |
|
|
| for k in (0, 1, 2, 3): |
| make_interp_spline(x, y, k=k) |
|
|
| def test_check_finite(self): |
| |
| x = np.arange(10).astype(float) |
| y = x**2 |
|
|
| for z in [np.nan, np.inf, -np.inf]: |
| y[-1] = z |
| assert_raises(ValueError, make_interp_spline, x, y) |
|
|
| @pytest.mark.parametrize('k', [1, 2, 3, 5]) |
| def test_list_input(self, k): |
| |
| x = list(range(10)) |
| y = [a**2 for a in x] |
| make_interp_spline(x, y, k=k) |
|
|
| def test_multiple_rhs(self): |
| yy = np.c_[np.sin(self.xx), np.cos(self.xx)] |
| der_l = [(1, [1., 2.])] |
| der_r = [(1, [3., 4.])] |
|
|
| b = make_interp_spline(self.xx, yy, k=3, bc_type=(der_l, der_r)) |
| xp_assert_close(b(self.xx), yy, atol=1e-14, rtol=1e-14) |
| xp_assert_close(b(self.xx[0], 1), der_l[0][1], atol=1e-14, rtol=1e-14) |
| xp_assert_close(b(self.xx[-1], 1), der_r[0][1], atol=1e-14, rtol=1e-14) |
|
|
| def test_shapes(self): |
| rng = np.random.RandomState(1234) |
| k, n = 3, 22 |
| x = np.sort(rng.random(size=n)) |
| y = rng.random(size=(n, 5, 6, 7)) |
|
|
| b = make_interp_spline(x, y, k) |
| assert b.c.shape == (n, 5, 6, 7) |
|
|
| |
| d_l = [(1, rng.random((5, 6, 7)))] |
| d_r = [(1, rng.random((5, 6, 7)))] |
| b = make_interp_spline(x, y, k, bc_type=(d_l, d_r)) |
| assert b.c.shape == (n + k - 1, 5, 6, 7) |
|
|
| def test_string_aliases(self): |
| yy = np.sin(self.xx) |
|
|
| |
| b1 = make_interp_spline(self.xx, yy, k=3, bc_type='natural') |
| b2 = make_interp_spline(self.xx, yy, k=3, bc_type=([(2, 0)], [(2, 0)])) |
| xp_assert_close(b1.c, b2.c, atol=1e-15) |
|
|
| |
| b1 = make_interp_spline(self.xx, yy, k=3, |
| bc_type=('natural', 'clamped')) |
| b2 = make_interp_spline(self.xx, yy, k=3, |
| bc_type=([(2, 0)], [(1, 0)])) |
| xp_assert_close(b1.c, b2.c, atol=1e-15) |
|
|
| |
| b1 = make_interp_spline(self.xx, yy, k=2, bc_type=(None, 'clamped')) |
| b2 = make_interp_spline(self.xx, yy, k=2, bc_type=(None, [(1, 0.0)])) |
| xp_assert_close(b1.c, b2.c, atol=1e-15) |
|
|
| |
| b1 = make_interp_spline(self.xx, yy, k=3, bc_type='not-a-knot') |
| b2 = make_interp_spline(self.xx, yy, k=3, bc_type=None) |
| xp_assert_close(b1.c, b2.c, atol=1e-15) |
|
|
| |
| with assert_raises(ValueError): |
| make_interp_spline(self.xx, yy, k=3, bc_type='typo') |
|
|
| |
| yy = np.c_[np.sin(self.xx), np.cos(self.xx)] |
| der_l = [(1, [0., 0.])] |
| der_r = [(2, [0., 0.])] |
| b2 = make_interp_spline(self.xx, yy, k=3, bc_type=(der_l, der_r)) |
| b1 = make_interp_spline(self.xx, yy, k=3, |
| bc_type=('clamped', 'natural')) |
| xp_assert_close(b1.c, b2.c, atol=1e-15) |
|
|
| |
| rng = np.random.RandomState(1234) |
| k, n = 3, 22 |
| x = np.sort(rng.random(size=n)) |
| y = rng.random(size=(n, 5, 6, 7)) |
|
|
| |
| d_l = [(1, np.zeros((5, 6, 7)))] |
| d_r = [(1, np.zeros((5, 6, 7)))] |
| b1 = make_interp_spline(x, y, k, bc_type=(d_l, d_r)) |
| b2 = make_interp_spline(x, y, k, bc_type='clamped') |
| xp_assert_close(b1.c, b2.c, atol=1e-15) |
|
|
| def test_full_matrix(self): |
| rng = np.random.RandomState(1234) |
| k, n = 3, 7 |
| x = np.sort(rng.random(size=n)) |
| y = rng.random(size=n) |
| t = _not_a_knot(x, k) |
|
|
| b = make_interp_spline(x, y, k, t) |
| cf = make_interp_full_matr(x, y, t, k) |
| xp_assert_close(b.c, cf, atol=1e-14, rtol=1e-14) |
|
|
| def test_woodbury(self): |
| ''' |
| Random elements in diagonal matrix with blocks in the |
| left lower and right upper corners checking the |
| implementation of Woodbury algorithm. |
| ''' |
| rng = np.random.RandomState(1234) |
| n = 201 |
| for k in range(3, 32, 2): |
| offset = int((k - 1) / 2) |
| a = np.diagflat(rng.random((1, n))) |
| for i in range(1, offset + 1): |
| a[:-i, i:] += np.diagflat(rng.random((1, n - i))) |
| a[i:, :-i] += np.diagflat(rng.random((1, n - i))) |
| ur = rng.random((offset, offset)) |
| a[:offset, -offset:] = ur |
| ll = rng.random((offset, offset)) |
| a[-offset:, :offset] = ll |
| d = np.zeros((k, n)) |
| for i, j in enumerate(range(offset, -offset - 1, -1)): |
| if j < 0: |
| d[i, :j] = np.diagonal(a, offset=j) |
| else: |
| d[i, j:] = np.diagonal(a, offset=j) |
| b = rng.random(n) |
| xp_assert_close(_woodbury_algorithm(d, ur, ll, b, k), |
| np.linalg.solve(a, b), atol=1e-14) |
|
|
|
|
| def make_interp_full_matr(x, y, t, k): |
| """Assemble an spline order k with knots t to interpolate |
| y(x) using full matrices. |
| Not-a-knot BC only. |
| |
| This routine is here for testing only (even though it's functional). |
| """ |
| assert x.size == y.size |
| assert t.size == x.size + k + 1 |
| n = x.size |
|
|
| A = np.zeros((n, n), dtype=np.float64) |
|
|
| for j in range(n): |
| xval = x[j] |
| if xval == t[k]: |
| left = k |
| else: |
| left = np.searchsorted(t, xval) - 1 |
|
|
| |
| bb = _dierckx.evaluate_all_bspl(t, k, xval, left) |
| A[j, left-k:left+1] = bb |
|
|
| c = sl.solve(A, y) |
| return c |
|
|
|
|
| def make_lsq_full_matrix(x, y, t, k=3): |
| """Make the least-square spline, full matrices.""" |
| x, y, t = map(np.asarray, (x, y, t)) |
| m = x.size |
| n = t.size - k - 1 |
|
|
| A = np.zeros((m, n), dtype=np.float64) |
|
|
| for j in range(m): |
| xval = x[j] |
| |
| if xval == t[k]: |
| left = k |
| else: |
| left = np.searchsorted(t, xval) - 1 |
|
|
| |
| bb = _dierckx.evaluate_all_bspl(t, k, xval, left) |
| A[j, left-k:left+1] = bb |
|
|
| |
| B = np.dot(A.T, A) |
| Y = np.dot(A.T, y) |
| c = sl.solve(B, Y) |
|
|
| return c, (A, Y) |
|
|
|
|
| parametrize_lsq_methods = pytest.mark.parametrize("method", ["norm-eq", "qr"]) |
|
|
| class TestLSQ: |
| |
| |
| |
| rng = np.random.RandomState(1234) |
| n, k = 13, 3 |
| x = np.sort(rng.random(n)) |
| y = rng.random(n) |
| t = _augknt(np.linspace(x[0], x[-1], 7), k) |
|
|
| @parametrize_lsq_methods |
| def test_lstsq(self, method): |
| |
| x, y, t, k = self.x, self.y, self.t, self.k |
|
|
| c0, AY = make_lsq_full_matrix(x, y, t, k) |
| b = make_lsq_spline(x, y, t, k, method=method) |
|
|
| xp_assert_close(b.c, c0) |
| assert b.c.shape == (t.size - k - 1,) |
|
|
| |
| aa, yy = AY |
| c1, _, _, _ = np.linalg.lstsq(aa, y, rcond=-1) |
| xp_assert_close(b.c, c1) |
|
|
| @parametrize_lsq_methods |
| def test_weights(self, method): |
| |
| x, y, t, k = self.x, self.y, self.t, self.k |
| w = np.ones_like(x) |
|
|
| b = make_lsq_spline(x, y, t, k, method=method) |
| b_w = make_lsq_spline(x, y, t, k, w=w, method=method) |
|
|
| xp_assert_close(b.t, b_w.t, atol=1e-14) |
| xp_assert_close(b.c, b_w.c, atol=1e-14) |
| assert b.k == b_w.k |
|
|
| def test_weights_same(self): |
| |
| x, y, t, k = self.x, self.y, self.t, self.k |
| w = np.random.default_rng(1234).uniform(size=x.shape[0]) |
|
|
| b_ne = make_lsq_spline(x, y, t, k, w=w, method="norm-eq") |
| b_qr = make_lsq_spline(x, y, t, k, w=w, method="qr") |
| b_no_w = make_lsq_spline(x, y, t, k, method="qr") |
|
|
| xp_assert_close(b_ne.c, b_qr.c, atol=1e-14) |
| assert not np.allclose(b_no_w.c, b_qr.c, atol=1e-14) |
|
|
| @parametrize_lsq_methods |
| def test_multiple_rhs(self, method): |
| x, t, k, n = self.x, self.t, self.k, self.n |
| rng = np.random.RandomState(1234) |
| y = rng.random(size=(n, 5, 6, 7)) |
| b = make_lsq_spline(x, y, t, k, method=method) |
| assert b.c.shape == (t.size-k-1, 5, 6, 7) |
|
|
| @parametrize_lsq_methods |
| def test_multiple_rhs_2(self, method): |
| x, t, k, n = self.x, self.t, self.k, self.n |
| nrhs = 3 |
| rng = np.random.RandomState(1234) |
| y = rng.random(size=(n, nrhs)) |
| b = make_lsq_spline(x, y, t, k, method=method) |
|
|
| bb = [make_lsq_spline(x, y[:, i], t, k, method=method) |
| for i in range(nrhs)] |
| coefs = np.vstack([bb[i].c for i in range(nrhs)]).T |
|
|
| xp_assert_close(coefs, b.c, atol=1e-15) |
|
|
| def test_multiple_rhs_3(self): |
| x, t, k, n = self.x, self.t, self.k, self.n |
| nrhs = 3 |
| y = np.random.random(size=(n, nrhs)) |
| b_qr = make_lsq_spline(x, y, t, k, method="qr") |
| b_neq = make_lsq_spline(x, y, t, k, method="norm-eq") |
| xp_assert_close(b_qr.c, b_neq.c, atol=1e-15) |
|
|
| @parametrize_lsq_methods |
| def test_complex(self, method): |
| |
| x, t, k = self.x, self.t, self.k |
| yc = self.y * (1. + 2.j) |
|
|
| b = make_lsq_spline(x, yc, t, k, method=method) |
| b_re = make_lsq_spline(x, yc.real, t, k, method=method) |
| b_im = make_lsq_spline(x, yc.imag, t, k, method=method) |
|
|
| xp_assert_close(b(x), b_re(x) + 1.j*b_im(x), atol=1e-15, rtol=1e-15) |
|
|
| def test_complex_2(self): |
| |
|
|
| x, t, k = self.x, self.t, self.k |
| yc = self.y * (1. + 2.j) |
| yc = np.stack((yc, yc), axis=1) |
|
|
| b = make_lsq_spline(x, yc, t, k) |
| b_re = make_lsq_spline(x, yc.real, t, k) |
| b_im = make_lsq_spline(x, yc.imag, t, k) |
|
|
| xp_assert_close(b(x), b_re(x) + 1.j*b_im(x), atol=1e-15, rtol=1e-15) |
|
|
| |
| yc = np.stack((yc, yc), axis=1) |
|
|
| b = make_lsq_spline(x, yc, t, k) |
| b_re = make_lsq_spline(x, yc.real, t, k) |
| b_im = make_lsq_spline(x, yc.imag, t, k) |
|
|
| xp_assert_close(b(x), b_re(x) + 1.j*b_im(x), atol=1e-15, rtol=1e-15) |
|
|
| @parametrize_lsq_methods |
| def test_int_xy(self, method): |
| x = np.arange(10).astype(int) |
| y = np.arange(10).astype(int) |
| t = _augknt(x, k=1) |
| |
| make_lsq_spline(x, y, t, k=1, method=method) |
|
|
| @parametrize_lsq_methods |
| def test_f32_xy(self, method): |
| x = np.arange(10, dtype=np.float32) |
| y = np.arange(10, dtype=np.float32) |
| t = _augknt(x, k=1) |
| spl_f32 = make_lsq_spline(x, y, t, k=1, method=method) |
| spl_f64 = make_lsq_spline( |
| x.astype(float), y.astype(float), t.astype(float), k=1, method=method |
| ) |
|
|
| x2 = (x[1:] + x[:-1]) / 2.0 |
| xp_assert_close(spl_f32(x2), spl_f64(x2), atol=1e-15) |
|
|
| @parametrize_lsq_methods |
| def test_sliced_input(self, method): |
| |
| xx = np.linspace(-1, 1, 100) |
|
|
| x = xx[::3] |
| y = xx[::3] |
| t = _augknt(x, 1) |
| make_lsq_spline(x, y, t, k=1, method=method) |
|
|
| @parametrize_lsq_methods |
| def test_checkfinite(self, method): |
| |
| x = np.arange(12).astype(float) |
| y = x**2 |
| t = _augknt(x, 3) |
|
|
| for z in [np.nan, np.inf, -np.inf]: |
| y[-1] = z |
| assert_raises(ValueError, make_lsq_spline, x, y, t, method=method) |
|
|
| @parametrize_lsq_methods |
| def test_read_only(self, method): |
| |
| x, y, t = self.x, self.y, self.t |
| x.setflags(write=False) |
| y.setflags(write=False) |
| t.setflags(write=False) |
| make_lsq_spline(x=x, y=y, t=t, method=method) |
|
|
| @pytest.mark.parametrize('k', list(range(1, 7))) |
| def test_qr_vs_norm_eq(self, k): |
| |
| x, y = self.x, self.y |
| t = _augknt(np.linspace(x[0], x[-1], 7), k) |
| spl_norm_eq = make_lsq_spline(x, y, t, k=k, method='norm-eq') |
| spl_qr = make_lsq_spline(x, y, t, k=k, method='qr') |
|
|
| xx = (x[1:] + x[:-1]) / 2.0 |
| xp_assert_close(spl_norm_eq(xx), spl_qr(xx), atol=1e-15) |
|
|
| def test_duplicates(self): |
| |
| x = np.repeat(self.x, 2) |
| y = np.repeat(self.y, 2) |
| spl_1 = make_lsq_spline(self.x, self.y, self.t, k=3, method='qr') |
| spl_2 = make_lsq_spline(x, y, self.t, k=3, method='qr') |
|
|
| xx = (x[1:] + x[:-1]) / 2.0 |
| xp_assert_close(spl_1(xx), spl_2(xx), atol=1e-15) |
|
|
|
|
| class PackedMatrix: |
| """A simplified CSR format for when non-zeros in each row are consecutive. |
| |
| Assuming that each row of an `(m, nc)` matrix 1) only has `nz` non-zeros, and |
| 2) these non-zeros are consecutive, we only store an `(m, nz)` matrix of |
| non-zeros and a 1D array of row offsets. This way, a row `i` of the original |
| matrix A is ``A[i, offset[i]: offset[i] + nz]``. |
| |
| """ |
| def __init__(self, a, offset, nc): |
| self.a = a |
| self.offset = offset |
| self.nc = nc |
|
|
| assert a.ndim == 2 |
| assert offset.ndim == 1 |
| assert a.shape[0] == offset.shape[0] |
|
|
| @property |
| def shape(self): |
| return self.a.shape[0], self.nc |
|
|
| def todense(self): |
| out = np.zeros(self.shape) |
| nelem = self.a.shape[1] |
| for i in range(out.shape[0]): |
| nel = min(self.nc - self.offset[i], nelem) |
| out[i, self.offset[i]:self.offset[i] + nel] = self.a[i, :nel] |
| return out |
|
|
|
|
| def _qr_reduce_py(a_p, y, startrow=1): |
| """This is a python counterpart of the `_qr_reduce` routine, |
| declared in interpolate/src/__fitpack.h |
| """ |
| from scipy.linalg.lapack import dlartg |
|
|
| |
| a = a_p.a |
| offset = a_p.offset |
| nc = a_p.nc |
|
|
| m, nz = a.shape |
|
|
| assert y.shape[0] == m |
| R = a.copy() |
| y1 = y.copy() |
|
|
| for i in range(startrow, m): |
| oi = offset[i] |
| for j in range(oi, nc): |
| |
| if j >= min(i, nc): |
| break |
|
|
| |
| c, s, r = dlartg(R[j, 0], R[i, 0]) |
|
|
| |
| R[j, 0] = r |
| for l in range(1, nz): |
| R[j, l], R[i, l-1] = fprota(c, s, R[j, l], R[i, l]) |
| R[i, -1] = 0.0 |
|
|
| |
| for l in range(y1.shape[1]): |
| y1[j, l], y1[i, l] = fprota(c, s, y1[j, l], y1[i, l]) |
|
|
| |
| offs = list(range(R.shape[0])) |
| R_p = PackedMatrix(R, np.array(offs, dtype=np.int64), nc) |
|
|
| return R_p, y1 |
|
|
|
|
| def fprota(c, s, a, b): |
| """Givens rotate [a, b]. |
| |
| [aa] = [ c s] @ [a] |
| [bb] [-s c] [b] |
| |
| """ |
| aa = c*a + s*b |
| bb = -s*a + c*b |
| return aa, bb |
|
|
|
|
| def fpback(R_p, y): |
| """Backsubsitution solve upper triangular banded `R @ c = y.` |
| |
| `R` is in the "packed" format: `R[i, :]` is `a[i, i:i+k+1]` |
| """ |
| R = R_p.a |
| _, nz = R.shape |
| nc = R_p.nc |
| assert y.shape[0] == R.shape[0] |
|
|
| c = np.zeros_like(y[:nc]) |
| c[nc-1, ...] = y[nc-1] / R[nc-1, 0] |
| for i in range(nc-2, -1, -1): |
| nel = min(nz, nc-i) |
| |
| summ = (R[i, 1:nel, None] * c[i+1:i+nel, ...]).sum(axis=0) |
| c[i, ...] = ( y[i] - summ ) / R[i, 0] |
| return c |
|
|
|
|
| class TestGivensQR: |
| |
| |
| def _get_xyt(self, n): |
| k = 3 |
| x = np.arange(n, dtype=float) |
| y = x**3 + 1/(1+x) |
| t = _not_a_knot(x, k) |
| return x, y, t, k |
|
|
| def test_vs_full(self): |
| n = 10 |
| x, y, t, k = self._get_xyt(n) |
|
|
| |
| a_csr = BSpline.design_matrix(x, t, k) |
|
|
| |
| q, r = sl.qr(a_csr.todense()) |
| qTy = q.T @ y |
|
|
| |
| |
| m, nc = a_csr.shape |
| assert nc == t.shape[0] - k - 1 |
|
|
| offset = a_csr.indices[::(k+1)] |
| offset = np.ascontiguousarray(offset, dtype=np.int64) |
| A = a_csr.data.reshape(m, k+1) |
|
|
| R = PackedMatrix(A, offset, nc) |
| y_ = y[:, None] |
| _dierckx.qr_reduce(A, offset, nc, y_) |
|
|
| |
| xp_assert_close(np.minimum(R.todense() + r, |
| R.todense() - r), np.zeros_like(r), atol=1e-15) |
| xp_assert_close(np.minimum(abs(qTy - y_[:, 0]), |
| abs(qTy + y_[:, 0])), np.zeros_like(qTy), atol=2e-13) |
|
|
| |
| c_full = sl.solve(r, qTy) |
| c_banded = _dierckx.fpback(R.a, R.nc, y_) |
| xp_assert_close(c_full, c_banded[:, 0], atol=5e-13) |
|
|
| def test_py_vs_compiled(self): |
| |
| n = 10 |
| x, y, t, k = self._get_xyt(n) |
|
|
| |
| a_csr = BSpline.design_matrix(x, t, k) |
| m, nc = a_csr.shape |
| assert nc == t.shape[0] - k - 1 |
|
|
| offset = a_csr.indices[::(k+1)] |
| offset = np.ascontiguousarray(offset, dtype=np.int64) |
| A = a_csr.data.reshape(m, k+1) |
|
|
| R = PackedMatrix(A, offset, nc) |
| y_ = y[:, None] |
|
|
| RR, yy = _qr_reduce_py(R, y_) |
| _dierckx.qr_reduce(A, offset, nc , y_) |
|
|
| xp_assert_close(RR.a, R.a, atol=1e-15) |
| xp_assert_equal(RR.offset, R.offset, check_dtype=False) |
| assert RR.nc == R.nc |
| xp_assert_close(yy, y_, atol=1e-15) |
|
|
| |
|
|
| def test_data_matrix(self): |
| n = 10 |
| x, y, t, k = self._get_xyt(n) |
| w = np.arange(1, n+1, dtype=float) |
|
|
| A, offset, nc = _dierckx.data_matrix(x, t, k, w) |
|
|
| m = x.shape[0] |
| a_csr = BSpline.design_matrix(x, t, k) |
| a_w = (a_csr * w[:, None]).tocsr() |
| A_ = a_w.data.reshape((m, k+1)) |
| offset_ = a_w.indices[::(k+1)].astype(np.int64) |
|
|
| xp_assert_close(A, A_, atol=1e-15) |
| xp_assert_equal(offset, offset_) |
| assert nc == t.shape[0] - k - 1 |
|
|
| def test_fpback(self): |
| n = 10 |
| x, y, t, k = self._get_xyt(n) |
| y = np.c_[y, y**2] |
| A, offset, nc = _dierckx.data_matrix(x, t, k, np.ones_like(x)) |
| R = PackedMatrix(A, offset, nc) |
| _dierckx.qr_reduce(A, offset, nc, y) |
|
|
| c = fpback(R, y) |
| cc = _dierckx.fpback(A, nc, y) |
|
|
| xp_assert_close(cc, c, atol=1e-14) |
|
|
|
|
| def data_file(basename): |
| return os.path.join(os.path.abspath(os.path.dirname(__file__)), |
| 'data', basename) |
|
|
|
|
| class TestSmoothingSpline: |
| |
| |
| |
| def test_invalid_input(self): |
| rng = np.random.RandomState(1234) |
| n = 100 |
| x = np.sort(rng.random_sample(n) * 4 - 2) |
| y = x**2 * np.sin(4 * x) + x**3 + rng.normal(0., 1.5, n) |
|
|
| |
| with assert_raises(ValueError): |
| make_smoothing_spline(x, y[1:]) |
| with assert_raises(ValueError): |
| make_smoothing_spline(x[1:], y) |
| with assert_raises(ValueError): |
| make_smoothing_spline(x.reshape(1, n), y) |
|
|
| |
| with assert_raises(ValueError): |
| make_smoothing_spline(x[::-1], y) |
|
|
| x_dupl = np.copy(x) |
| x_dupl[0] = x_dupl[1] |
|
|
| with assert_raises(ValueError): |
| make_smoothing_spline(x_dupl, y) |
|
|
| |
| x = np.arange(4) |
| y = np.ones(4) |
| exception_message = "``x`` and ``y`` length must be at least 5" |
| with pytest.raises(ValueError, match=exception_message): |
| make_smoothing_spline(x, y) |
|
|
| def test_compare_with_GCVSPL(self): |
| """ |
| Data is generated in the following way: |
| >>> np.random.seed(1234) |
| >>> n = 100 |
| >>> x = np.sort(np.random.random_sample(n) * 4 - 2) |
| >>> y = np.sin(x) + np.random.normal(scale=.5, size=n) |
| >>> np.savetxt('x.csv', x) |
| >>> np.savetxt('y.csv', y) |
| |
| We obtain the result of performing the GCV smoothing splines |
| package (by Woltring, gcvspl) on the sample data points |
| using its version for Octave (https://github.com/srkuberski/gcvspl). |
| In order to use this implementation, one should clone the repository |
| and open the folder in Octave. |
| In Octave, we load up ``x`` and ``y`` (generated from Python code |
| above): |
| |
| >>> x = csvread('x.csv'); |
| >>> y = csvread('y.csv'); |
| |
| Then, in order to access the implementation, we compile gcvspl files in |
| Octave: |
| |
| >>> mex gcvsplmex.c gcvspl.c |
| >>> mex spldermex.c gcvspl.c |
| |
| The first function computes the vector of unknowns from the dataset |
| (x, y) while the second one evaluates the spline in certain points |
| with known vector of coefficients. |
| |
| >>> c = gcvsplmex( x, y, 2 ); |
| >>> y0 = spldermex( x, c, 2, x, 0 ); |
| |
| If we want to compare the results of the gcvspl code, we can save |
| ``y0`` in csv file: |
| |
| >>> csvwrite('y0.csv', y0); |
| |
| """ |
| |
| with np.load(data_file('gcvspl.npz')) as data: |
| |
| x = data['x'] |
| y = data['y'] |
|
|
| y_GCVSPL = data['y_GCVSPL'] |
| y_compr = make_smoothing_spline(x, y)(x) |
|
|
| |
| |
| |
| |
| |
| xp_assert_close(y_compr, y_GCVSPL, atol=1e-4, rtol=1e-4, check_dtype=False) |
|
|
| def test_non_regularized_case(self): |
| """ |
| In case the regularization parameter is 0, the resulting spline |
| is an interpolation spline with natural boundary conditions. |
| """ |
| |
| rng = np.random.RandomState(1234) |
| n = 100 |
| x = np.sort(rng.random_sample(n) * 4 - 2) |
| y = x**2 * np.sin(4 * x) + x**3 + rng.normal(0., 1.5, n) |
|
|
| spline_GCV = make_smoothing_spline(x, y, lam=0.) |
| spline_interp = make_interp_spline(x, y, 3, bc_type='natural') |
|
|
| grid = np.linspace(x[0], x[-1], 2 * n) |
| xp_assert_close(spline_GCV(grid), |
| spline_interp(grid), |
| atol=1e-15) |
|
|
| @pytest.mark.fail_slow(2) |
| def test_weighted_smoothing_spline(self): |
| |
| rng = np.random.RandomState(1234) |
| n = 100 |
| x = np.sort(rng.random_sample(n) * 4 - 2) |
| y = x**2 * np.sin(4 * x) + x**3 + rng.normal(0., 1.5, n) |
|
|
| spl = make_smoothing_spline(x, y) |
|
|
| |
| |
| for ind in rng.choice(range(100), size=10): |
| w = np.ones(n) |
| w[ind] = 30. |
| spl_w = make_smoothing_spline(x, y, w) |
| |
| |
| orig = abs(spl(x[ind]) - y[ind]) |
| weighted = abs(spl_w(x[ind]) - y[ind]) |
|
|
| if orig < weighted: |
| raise ValueError(f'Spline with weights should be closer to the' |
| f' points than the original one: {orig:.4} < ' |
| f'{weighted:.4}') |
|
|
|
|
| |
| |
| def bspline2(xy, t, c, k): |
| """A naive 2D tensort product spline evaluation.""" |
| x, y = xy |
| tx, ty = t |
| nx = len(tx) - k - 1 |
| assert (nx >= k+1) |
| ny = len(ty) - k - 1 |
| assert (ny >= k+1) |
| res = sum(c[ix, iy] * B(x, k, ix, tx) * B(y, k, iy, ty) |
| for ix in range(nx) for iy in range(ny)) |
| return np.asarray(res) |
|
|
|
|
| def B(x, k, i, t): |
| if k == 0: |
| return 1.0 if t[i] <= x < t[i+1] else 0.0 |
| if t[i+k] == t[i]: |
| c1 = 0.0 |
| else: |
| c1 = (x - t[i])/(t[i+k] - t[i]) * B(x, k-1, i, t) |
| if t[i+k+1] == t[i+1]: |
| c2 = 0.0 |
| else: |
| c2 = (t[i+k+1] - x)/(t[i+k+1] - t[i+1]) * B(x, k-1, i+1, t) |
| return c1 + c2 |
|
|
|
|
| def bspline(x, t, c, k): |
| n = len(t) - k - 1 |
| assert (n >= k+1) and (len(c) >= n) |
| return sum(c[i] * B(x, k, i, t) for i in range(n)) |
|
|
|
|
| class NdBSpline0: |
| def __init__(self, t, c, k=3): |
| """Tensor product spline object. |
| |
| c[i1, i2, ..., id] * B(x1, i1) * B(x2, i2) * ... * B(xd, id) |
| |
| Parameters |
| ---------- |
| c : ndarray, shape (n1, n2, ..., nd, ...) |
| b-spline coefficients |
| t : tuple of 1D ndarrays |
| knot vectors in directions 1, 2, ... d |
| ``len(t[i]) == n[i] + k + 1`` |
| k : int or length-d tuple of integers |
| spline degrees. |
| """ |
| ndim = len(t) |
| assert ndim <= len(c.shape) |
|
|
| try: |
| len(k) |
| except TypeError: |
| |
| k = (k,)*ndim |
|
|
| self.k = tuple(operator.index(ki) for ki in k) |
| self.t = tuple(np.asarray(ti, dtype=float) for ti in t) |
| self.c = c |
|
|
| def __call__(self, x): |
| ndim = len(self.t) |
| |
| assert len(x) == ndim |
|
|
| |
| i = ['none', ]*ndim |
| for d in range(ndim): |
| td, xd = self.t[d], x[d] |
| k = self.k[d] |
|
|
| |
| if xd == td[k]: |
| i[d] = k |
| else: |
| i[d] = np.searchsorted(td, xd) - 1 |
| assert td[i[d]] <= xd <= td[i[d]+1] |
| assert i[d] >= k and i[d] < len(td) - k |
| i = tuple(i) |
|
|
| |
| |
| |
| result = 0 |
| iters = [range(i[d] - self.k[d], i[d] + 1) for d in range(ndim)] |
| for idx in itertools.product(*iters): |
| term = self.c[idx] * np.prod([B(x[d], self.k[d], idx[d], self.t[d]) |
| for d in range(ndim)]) |
| result += term |
| return np.asarray(result) |
|
|
|
|
| class TestNdBSpline: |
|
|
| def test_1D(self): |
| |
| rng = np.random.default_rng(12345) |
| n, k = 11, 3 |
| n_tr = 7 |
| t = np.sort(rng.uniform(size=n + k + 1)) |
| c = rng.uniform(size=(n, n_tr)) |
|
|
| b = BSpline(t, c, k) |
| nb = NdBSpline((t,), c, k) |
|
|
| xi = rng.uniform(size=21) |
| |
| xp_assert_close(nb(xi[:, None]), |
| b(xi), atol=1e-14) |
| assert nb(xi[:, None]).shape == (xi.shape[0], c.shape[1]) |
|
|
| def make_2d_case(self): |
| |
| x = np.arange(6) |
| y = x**3 |
| spl = make_interp_spline(x, y, k=3) |
|
|
| y_1 = x**3 + 2*x |
| spl_1 = make_interp_spline(x, y_1, k=3) |
|
|
| t2 = (spl.t, spl_1.t) |
| c2 = spl.c[:, None] * spl_1.c[None, :] |
|
|
| return t2, c2, 3 |
|
|
| def make_2d_mixed(self): |
| |
| x = np.arange(6) |
| y = x**3 |
| spl = make_interp_spline(x, y, k=3) |
|
|
| x = np.arange(5) + 1.5 |
| y_1 = x**2 + 2*x |
| spl_1 = make_interp_spline(x, y_1, k=2) |
|
|
| t2 = (spl.t, spl_1.t) |
| c2 = spl.c[:, None] * spl_1.c[None, :] |
|
|
| return t2, c2, spl.k, spl_1.k |
|
|
| def test_2D_separable(self): |
| xi = [(1.5, 2.5), (2.5, 1), (0.5, 1.5)] |
| t2, c2, k = self.make_2d_case() |
| target = [x**3 * (y**3 + 2*y) for (x, y) in xi] |
|
|
| |
| xp_assert_close(np.asarray([bspline2(xy, t2, c2, k) for xy in xi]), |
| np.asarray(target), |
| check_shape=False, |
| atol=1e-14) |
|
|
| |
| bspl2 = NdBSpline(t2, c2, k=3) |
| assert bspl2(xi).shape == (len(xi), ) |
| xp_assert_close(bspl2(xi), |
| target, atol=1e-14) |
|
|
| |
| rng = np.random.default_rng(12345) |
| xi = rng.uniform(size=(4, 3, 2)) * 5 |
| result = bspl2(xi) |
| assert result.shape == (4, 3) |
|
|
| |
| x, y = xi.reshape((-1, 2)).T |
| xp_assert_close(result.ravel(), |
| x**3 * (y**3 + 2*y), atol=1e-14) |
|
|
| def test_2D_separable_2(self): |
| |
| ndim = 2 |
| xi = [(1.5, 2.5), (2.5, 1), (0.5, 1.5)] |
| target = [x**3 * (y**3 + 2*y) for (x, y) in xi] |
|
|
| t2, c2, k = self.make_2d_case() |
| c2_4 = np.dstack((c2, c2, c2, c2)) |
|
|
| xy = (1.5, 2.5) |
| bspl2_4 = NdBSpline(t2, c2_4, k=3) |
| result = bspl2_4(xy) |
| val_single = NdBSpline(t2, c2, k)(xy) |
| assert result.shape == (4,) |
| xp_assert_close(result, |
| [val_single, ]*4, atol=1e-14) |
|
|
| |
| |
| assert bspl2_4(xi).shape == np.shape(xi)[:-1] + bspl2_4.c.shape[ndim:] |
| xp_assert_close(bspl2_4(xi), np.asarray(target)[:, None], |
| check_shape=False, |
| atol=5e-14) |
|
|
| |
| c2_22 = c2_4.reshape((6, 6, 2, 2)) |
| bspl2_22 = NdBSpline(t2, c2_22, k=3) |
|
|
| result = bspl2_22(xy) |
| assert result.shape == (2, 2) |
| xp_assert_close(result, |
| [[val_single, val_single], |
| [val_single, val_single]], atol=1e-14) |
|
|
| |
| |
| assert (bspl2_22(xi).shape == |
| np.shape(xi)[:-1] + bspl2_22.c.shape[ndim:]) |
| xp_assert_close(bspl2_22(xi), np.asarray(target)[:, None, None], |
| check_shape=False, |
| atol=5e-14) |
|
|
|
|
| def test_2D_separable_2_complex(self): |
| |
| xi = [(1.5, 2.5), (2.5, 1), (0.5, 1.5)] |
| target = [x**3 * (y**3 + 2*y) for (x, y) in xi] |
|
|
| target = [t + 2j*t for t in target] |
|
|
| t2, c2, k = self.make_2d_case() |
| c2 = c2 * (1 + 2j) |
| c2_4 = np.dstack((c2, c2, c2, c2)) |
|
|
| xy = (1.5, 2.5) |
| bspl2_4 = NdBSpline(t2, c2_4, k=3) |
| result = bspl2_4(xy) |
| val_single = NdBSpline(t2, c2, k)(xy) |
| assert result.shape == (4,) |
| xp_assert_close(result, |
| [val_single, ]*4, atol=1e-14) |
|
|
| def test_2D_random(self): |
| rng = np.random.default_rng(12345) |
| k = 3 |
| tx = np.r_[0, 0, 0, 0, np.sort(rng.uniform(size=7)) * 3, 3, 3, 3, 3] |
| ty = np.r_[0, 0, 0, 0, np.sort(rng.uniform(size=8)) * 4, 4, 4, 4, 4] |
| c = rng.uniform(size=(tx.size-k-1, ty.size-k-1)) |
|
|
| spl = NdBSpline((tx, ty), c, k=k) |
|
|
| xi = (1., 1.) |
| xp_assert_close(spl(xi), |
| bspline2(xi, (tx, ty), c, k), atol=1e-14) |
|
|
| xi = np.c_[[1, 1.5, 2], |
| [1.1, 1.6, 2.1]] |
| xp_assert_close(spl(xi), |
| [bspline2(xy, (tx, ty), c, k) for xy in xi], |
| atol=1e-14) |
|
|
| def test_2D_mixed(self): |
| t2, c2, kx, ky = self.make_2d_mixed() |
| xi = [(1.4, 4.5), (2.5, 2.4), (4.5, 3.5)] |
| target = [x**3 * (y**2 + 2*y) for (x, y) in xi] |
| bspl2 = NdBSpline(t2, c2, k=(kx, ky)) |
| assert bspl2(xi).shape == (len(xi), ) |
| xp_assert_close(bspl2(xi), |
| target, atol=1e-14) |
|
|
| def test_2D_derivative(self): |
| t2, c2, kx, ky = self.make_2d_mixed() |
| xi = [(1.4, 4.5), (2.5, 2.4), (4.5, 3.5)] |
| bspl2 = NdBSpline(t2, c2, k=(kx, ky)) |
|
|
| der = bspl2(xi, nu=(1, 0)) |
| xp_assert_close(der, |
| [3*x**2 * (y**2 + 2*y) for x, y in xi], atol=1e-14) |
|
|
| der = bspl2(xi, nu=(1, 1)) |
| xp_assert_close(der, |
| [3*x**2 * (2*y + 2) for x, y in xi], atol=1e-14) |
|
|
| der = bspl2(xi, nu=(0, 0)) |
| xp_assert_close(der, |
| [x**3 * (y**2 + 2*y) for x, y in xi], atol=1e-14) |
|
|
| with assert_raises(ValueError): |
| |
| der = bspl2(xi, nu=(-1, 0)) |
|
|
| with assert_raises(ValueError): |
| |
| der = bspl2(xi, nu=(-1, 0, 1)) |
|
|
| def test_2D_mixed_random(self): |
| rng = np.random.default_rng(12345) |
| kx, ky = 2, 3 |
| tx = np.r_[0, 0, 0, 0, np.sort(rng.uniform(size=7)) * 3, 3, 3, 3, 3] |
| ty = np.r_[0, 0, 0, 0, np.sort(rng.uniform(size=8)) * 4, 4, 4, 4, 4] |
| c = rng.uniform(size=(tx.size - kx - 1, ty.size - ky - 1)) |
|
|
| xi = np.c_[[1, 1.5, 2], |
| [1.1, 1.6, 2.1]] |
|
|
| bspl2 = NdBSpline((tx, ty), c, k=(kx, ky)) |
| bspl2_0 = NdBSpline0((tx, ty), c, k=(kx, ky)) |
|
|
| xp_assert_close(bspl2(xi), |
| [bspl2_0(xp) for xp in xi], atol=1e-14) |
|
|
| def test_tx_neq_ty(self): |
| |
| x = np.arange(6) |
| y = np.arange(7) + 1.5 |
|
|
| spl_x = make_interp_spline(x, x**3, k=3) |
| spl_y = make_interp_spline(y, y**2 + 2*y, k=3) |
| cc = spl_x.c[:, None] * spl_y.c[None, :] |
| bspl = NdBSpline((spl_x.t, spl_y.t), cc, (spl_x.k, spl_y.k)) |
|
|
| values = (x**3)[:, None] * (y**2 + 2*y)[None, :] |
| rgi = RegularGridInterpolator((x, y), values) |
|
|
| xi = [(a, b) for a, b in itertools.product(x, y)] |
| bxi = bspl(xi) |
|
|
| assert not np.isnan(bxi).any() |
| xp_assert_close(bxi, rgi(xi), atol=1e-14) |
| xp_assert_close(bxi.reshape(values.shape), values, atol=1e-14) |
|
|
| def make_3d_case(self): |
| |
| x = np.arange(6) |
| y = x**3 |
| spl = make_interp_spline(x, y, k=3) |
|
|
| y_1 = x**3 + 2*x |
| spl_1 = make_interp_spline(x, y_1, k=3) |
|
|
| y_2 = x**3 + 3*x + 1 |
| spl_2 = make_interp_spline(x, y_2, k=3) |
|
|
| t2 = (spl.t, spl_1.t, spl_2.t) |
| c2 = (spl.c[:, None, None] * |
| spl_1.c[None, :, None] * |
| spl_2.c[None, None, :]) |
|
|
| return t2, c2, 3 |
|
|
| def test_3D_separable(self): |
| rng = np.random.default_rng(12345) |
| x, y, z = rng.uniform(size=(3, 11)) * 5 |
| target = x**3 * (y**3 + 2*y) * (z**3 + 3*z + 1) |
|
|
| t3, c3, k = self.make_3d_case() |
| bspl3 = NdBSpline(t3, c3, k=3) |
|
|
| xi = [_ for _ in zip(x, y, z)] |
| result = bspl3(xi) |
| assert result.shape == (11,) |
| xp_assert_close(result, target, atol=1e-14) |
|
|
| def test_3D_derivative(self): |
| t3, c3, k = self.make_3d_case() |
| bspl3 = NdBSpline(t3, c3, k=3) |
| rng = np.random.default_rng(12345) |
| x, y, z = rng.uniform(size=(3, 11)) * 5 |
| xi = [_ for _ in zip(x, y, z)] |
|
|
| xp_assert_close(bspl3(xi, nu=(1, 0, 0)), |
| 3*x**2 * (y**3 + 2*y) * (z**3 + 3*z + 1), atol=1e-14) |
|
|
| xp_assert_close(bspl3(xi, nu=(2, 0, 0)), |
| 6*x * (y**3 + 2*y) * (z**3 + 3*z + 1), atol=1e-14) |
|
|
| xp_assert_close(bspl3(xi, nu=(2, 1, 0)), |
| 6*x * (3*y**2 + 2) * (z**3 + 3*z + 1), atol=1e-14) |
|
|
| xp_assert_close(bspl3(xi, nu=(2, 1, 3)), |
| 6*x * (3*y**2 + 2) * (6), atol=1e-14) |
|
|
| xp_assert_close(bspl3(xi, nu=(2, 1, 4)), |
| np.zeros(len(xi)), atol=1e-14) |
|
|
| def test_3D_random(self): |
| rng = np.random.default_rng(12345) |
| k = 3 |
| tx = np.r_[0, 0, 0, 0, np.sort(rng.uniform(size=7)) * 3, 3, 3, 3, 3] |
| ty = np.r_[0, 0, 0, 0, np.sort(rng.uniform(size=8)) * 4, 4, 4, 4, 4] |
| tz = np.r_[0, 0, 0, 0, np.sort(rng.uniform(size=8)) * 4, 4, 4, 4, 4] |
| c = rng.uniform(size=(tx.size-k-1, ty.size-k-1, tz.size-k-1)) |
|
|
| spl = NdBSpline((tx, ty, tz), c, k=k) |
| spl_0 = NdBSpline0((tx, ty, tz), c, k=k) |
|
|
| xi = (1., 1., 1) |
| xp_assert_close(spl(xi), spl_0(xi), atol=1e-14) |
|
|
| xi = np.c_[[1, 1.5, 2], |
| [1.1, 1.6, 2.1], |
| [0.9, 1.4, 1.9]] |
| xp_assert_close(spl(xi), [spl_0(xp) for xp in xi], atol=1e-14) |
|
|
| def test_3D_random_complex(self): |
| rng = np.random.default_rng(12345) |
| k = 3 |
| tx = np.r_[0, 0, 0, 0, np.sort(rng.uniform(size=7)) * 3, 3, 3, 3, 3] |
| ty = np.r_[0, 0, 0, 0, np.sort(rng.uniform(size=8)) * 4, 4, 4, 4, 4] |
| tz = np.r_[0, 0, 0, 0, np.sort(rng.uniform(size=8)) * 4, 4, 4, 4, 4] |
| c = (rng.uniform(size=(tx.size-k-1, ty.size-k-1, tz.size-k-1)) + |
| rng.uniform(size=(tx.size-k-1, ty.size-k-1, tz.size-k-1))*1j) |
|
|
| spl = NdBSpline((tx, ty, tz), c, k=k) |
| spl_re = NdBSpline((tx, ty, tz), c.real, k=k) |
| spl_im = NdBSpline((tx, ty, tz), c.imag, k=k) |
|
|
| xi = np.c_[[1, 1.5, 2], |
| [1.1, 1.6, 2.1], |
| [0.9, 1.4, 1.9]] |
| xp_assert_close(spl(xi), |
| spl_re(xi) + 1j*spl_im(xi), atol=1e-14) |
|
|
| @pytest.mark.parametrize('cls_extrap', [None, True]) |
| @pytest.mark.parametrize('call_extrap', [None, True]) |
| def test_extrapolate_3D_separable(self, cls_extrap, call_extrap): |
| |
| t3, c3, k = self.make_3d_case() |
| bspl3 = NdBSpline(t3, c3, k=3, extrapolate=cls_extrap) |
|
|
| |
| x, y, z = [-2, -1, 7], [-3, -0.5, 6.5], [-1, -1.5, 7.5] |
| x, y, z = map(np.asarray, (x, y, z)) |
| xi = [_ for _ in zip(x, y, z)] |
| target = x**3 * (y**3 + 2*y) * (z**3 + 3*z + 1) |
|
|
| result = bspl3(xi, extrapolate=call_extrap) |
| xp_assert_close(result, target, atol=1e-14) |
|
|
| @pytest.mark.parametrize('extrap', [(False, True), (True, None)]) |
| def test_extrapolate_3D_separable_2(self, extrap): |
| |
| |
| t3, c3, k = self.make_3d_case() |
| cls_extrap, call_extrap = extrap |
| bspl3 = NdBSpline(t3, c3, k=3, extrapolate=cls_extrap) |
|
|
| |
| x, y, z = [-2, -1, 7], [-3, -0.5, 6.5], [-1, -1.5, 7.5] |
| x, y, z = map(np.asarray, (x, y, z)) |
| xi = [_ for _ in zip(x, y, z)] |
| target = x**3 * (y**3 + 2*y) * (z**3 + 3*z + 1) |
|
|
| result = bspl3(xi, extrapolate=call_extrap) |
| xp_assert_close(result, target, atol=1e-14) |
|
|
| def test_extrapolate_false_3D_separable(self): |
| |
| t3, c3, k = self.make_3d_case() |
| bspl3 = NdBSpline(t3, c3, k=3) |
|
|
| |
| x, y, z = [-2, 1, 7], [-3, 0.5, 6.5], [-1, 1.5, 7.5] |
| x, y, z = map(np.asarray, (x, y, z)) |
| xi = [_ for _ in zip(x, y, z)] |
| target = x**3 * (y**3 + 2*y) * (z**3 + 3*z + 1) |
|
|
| result = bspl3(xi, extrapolate=False) |
| assert np.isnan(result[0]) |
| assert np.isnan(result[-1]) |
| xp_assert_close(result[1:-1], target[1:-1], atol=1e-14) |
|
|
| def test_x_nan_3D(self): |
| |
| t3, c3, k = self.make_3d_case() |
| bspl3 = NdBSpline(t3, c3, k=3) |
|
|
| |
| x = np.asarray([-2, 3, np.nan, 1, 2, 7, np.nan]) |
| y = np.asarray([-3, 3.5, 1, np.nan, 3, 6.5, 6.5]) |
| z = np.asarray([-1, 3.5, 2, 3, np.nan, 7.5, 7.5]) |
| xi = [_ for _ in zip(x, y, z)] |
| target = x**3 * (y**3 + 2*y) * (z**3 + 3*z + 1) |
| mask = np.isnan(x) | np.isnan(y) | np.isnan(z) |
| target[mask] = np.nan |
|
|
| result = bspl3(xi) |
| assert np.isnan(result[mask]).all() |
| xp_assert_close(result, target, atol=1e-14) |
|
|
| def test_non_c_contiguous(self): |
| |
| rng = np.random.default_rng(12345) |
| kx, ky = 3, 3 |
| tx = np.sort(rng.uniform(low=0, high=4, size=16)) |
| tx = np.r_[(tx[0],)*kx, tx, (tx[-1],)*kx] |
| ty = np.sort(rng.uniform(low=0, high=4, size=16)) |
| ty = np.r_[(ty[0],)*ky, ty, (ty[-1],)*ky] |
|
|
| assert not tx[::2].flags.c_contiguous |
| assert not ty[::2].flags.c_contiguous |
|
|
| c = rng.uniform(size=(tx.size//2 - kx - 1, ty.size//2 - ky - 1)) |
| c = c.T |
| assert not c.flags.c_contiguous |
|
|
| xi = np.c_[[1, 1.5, 2], |
| [1.1, 1.6, 2.1]] |
|
|
| bspl2 = NdBSpline((tx[::2], ty[::2]), c, k=(kx, ky)) |
| bspl2_0 = NdBSpline0((tx[::2], ty[::2]), c, k=(kx, ky)) |
|
|
| xp_assert_close(bspl2(xi), |
| [bspl2_0(xp) for xp in xi], atol=1e-14) |
|
|
| def test_readonly(self): |
| t3, c3, k = self.make_3d_case() |
| bspl3 = NdBSpline(t3, c3, k=3) |
|
|
| for i in range(3): |
| t3[i].flags.writeable = False |
| c3.flags.writeable = False |
|
|
| bspl3_ = NdBSpline(t3, c3, k=3) |
|
|
| assert bspl3((1, 2, 3)) == bspl3_((1, 2, 3)) |
|
|
| def test_design_matrix(self): |
| t3, c3, k = self.make_3d_case() |
|
|
| xi = np.asarray([[1, 2, 3], [4, 5, 6]]) |
| dm = NdBSpline(t3, c3, k).design_matrix(xi, t3, k) |
| dm1 = NdBSpline.design_matrix(xi, t3, [k, k, k]) |
| assert dm.shape[0] == xi.shape[0] |
| xp_assert_close(dm.todense(), dm1.todense(), atol=1e-16) |
|
|
| with assert_raises(ValueError): |
| NdBSpline.design_matrix([1, 2, 3], t3, [k]*3) |
|
|
| with assert_raises(ValueError, match="Data and knots*"): |
| NdBSpline.design_matrix([[1, 2]], t3, [k]*3) |
|
|
| @pytest.mark.thread_unsafe |
| def test_concurrency(self): |
| rng = np.random.default_rng(12345) |
| k = 3 |
| tx = np.r_[0, 0, 0, 0, np.sort(rng.uniform(size=7)) * 3, 3, 3, 3, 3] |
| ty = np.r_[0, 0, 0, 0, np.sort(rng.uniform(size=8)) * 4, 4, 4, 4, 4] |
| tz = np.r_[0, 0, 0, 0, np.sort(rng.uniform(size=8)) * 4, 4, 4, 4, 4] |
| c = rng.uniform(size=(tx.size-k-1, ty.size-k-1, tz.size-k-1)) |
|
|
| spl = NdBSpline((tx, ty, tz), c, k=k) |
|
|
| def worker_fn(_, spl): |
| xi = np.c_[[1, 1.5, 2], |
| [1.1, 1.6, 2.1], |
| [0.9, 1.4, 1.9]] |
| spl(xi) |
|
|
| _run_concurrent_barrier(10, worker_fn, spl) |
|
|
|
|
| class TestMakeND: |
| def test_2D_separable_simple(self): |
| x = np.arange(6) |
| y = np.arange(6) + 0.5 |
| values = x[:, None]**3 * (y**3 + 2*y)[None, :] |
| xi = [(a, b) for a, b in itertools.product(x, y)] |
|
|
| bspl = make_ndbspl((x, y), values, k=1) |
| xp_assert_close(bspl(xi), values.ravel(), atol=1e-15) |
|
|
| |
| spl_x = make_interp_spline(x, x**3, k=1) |
| spl_y = make_interp_spline(y, y**3 + 2*y, k=1) |
| cc = spl_x.c[:, None] * spl_y.c[None, :] |
| xp_assert_close(cc, bspl.c, atol=1e-11, rtol=0) |
|
|
| |
| from scipy.interpolate import RegularGridInterpolator as RGI |
| rgi = RGI((x, y), values, method='linear') |
| xp_assert_close(rgi(xi), bspl(xi), atol=1e-14) |
|
|
| def test_2D_separable_trailing_dims(self): |
| |
| x = np.arange(6) |
| y = np.arange(6) |
| xi = [(a, b) for a, b in itertools.product(x, y)] |
|
|
| |
| values = x[:, None]**3 * (y**3 + 2*y)[None, :] |
| values4 = np.dstack((values, values, values, values)) |
| bspl = make_ndbspl((x, y), values4, k=3, solver=ssl.spsolve) |
|
|
| result = bspl(xi) |
| target = np.dstack((values, values, values, values)).astype(float) |
| assert result.shape == (36, 4) |
| xp_assert_close(result.reshape(6, 6, 4), |
| target, atol=1e-14) |
|
|
| |
| values22 = values4.reshape((6, 6, 2, 2)) |
| bspl = make_ndbspl((x, y), values22, k=3, solver=ssl.spsolve) |
|
|
| result = bspl(xi) |
| assert result.shape == (36, 2, 2) |
| xp_assert_close(result.reshape(6, 6, 2, 2), |
| target.reshape((6, 6, 2, 2)), atol=1e-14) |
|
|
| @pytest.mark.parametrize('k', [(3, 3), (1, 1), (3, 1), (1, 3), (3, 5)]) |
| def test_2D_mixed(self, k): |
| |
| x = np.arange(6) |
| y = np.arange(7) + 1.5 |
| xi = [(a, b) for a, b in itertools.product(x, y)] |
|
|
| values = (x**3)[:, None] * (y**2 + 2*y)[None, :] |
| bspl = make_ndbspl((x, y), values, k=k, solver=ssl.spsolve) |
| xp_assert_close(bspl(xi), values.ravel(), atol=1e-15) |
|
|
| def _get_sample_2d_data(self): |
| |
| x = np.array([.5, 2., 3., 4., 5.5, 6.]) |
| y = np.array([.5, 2., 3., 4., 5.5, 6.]) |
| z = np.array( |
| [ |
| [1, 2, 1, 2, 1, 1], |
| [1, 2, 1, 2, 1, 1], |
| [1, 2, 3, 2, 1, 1], |
| [1, 2, 2, 2, 1, 1], |
| [1, 2, 1, 2, 1, 1], |
| [1, 2, 2, 2, 1, 1], |
| ] |
| ) |
| return x, y, z |
|
|
| def test_2D_vs_RGI_linear(self): |
| x, y, z = self._get_sample_2d_data() |
| bspl = make_ndbspl((x, y), z, k=1) |
| rgi = RegularGridInterpolator((x, y), z, method='linear') |
|
|
| xi = np.array([[1, 2.3, 5.3, 0.5, 3.3, 1.2, 3], |
| [1, 3.3, 1.2, 4.0, 5.0, 1.0, 3]]).T |
|
|
| xp_assert_close(bspl(xi), rgi(xi), atol=1e-14) |
|
|
| def test_2D_vs_RGI_cubic(self): |
| x, y, z = self._get_sample_2d_data() |
| bspl = make_ndbspl((x, y), z, k=3, solver=ssl.spsolve) |
| rgi = RegularGridInterpolator((x, y), z, method='cubic_legacy') |
|
|
| xi = np.array([[1, 2.3, 5.3, 0.5, 3.3, 1.2, 3], |
| [1, 3.3, 1.2, 4.0, 5.0, 1.0, 3]]).T |
|
|
| xp_assert_close(bspl(xi), rgi(xi), atol=1e-14) |
|
|
| @pytest.mark.parametrize('solver', [ssl.gmres, ssl.gcrotmk]) |
| def test_2D_vs_RGI_cubic_iterative(self, solver): |
| |
| |
| |
| |
| x, y, z = self._get_sample_2d_data() |
| bspl = make_ndbspl((x, y), z, k=3, solver=solver, rtol=1e-6) |
| rgi = RegularGridInterpolator((x, y), z, method='cubic_legacy') |
|
|
| xi = np.array([[1, 2.3, 5.3, 0.5, 3.3, 1.2, 3], |
| [1, 3.3, 1.2, 4.0, 5.0, 1.0, 3]]).T |
|
|
| xp_assert_close(bspl(xi), rgi(xi), atol=1e-14, rtol=1e-7) |
|
|
| def test_2D_vs_RGI_quintic(self): |
| x, y, z = self._get_sample_2d_data() |
| bspl = make_ndbspl((x, y), z, k=5, solver=ssl.spsolve) |
| rgi = RegularGridInterpolator((x, y), z, method='quintic_legacy') |
|
|
| xi = np.array([[1, 2.3, 5.3, 0.5, 3.3, 1.2, 3], |
| [1, 3.3, 1.2, 4.0, 5.0, 1.0, 3]]).T |
|
|
| xp_assert_close(bspl(xi), rgi(xi), atol=1e-14) |
|
|
| @pytest.mark.parametrize( |
| 'k, meth', [(1, 'linear'), (3, 'cubic_legacy'), (5, 'quintic_legacy')] |
| ) |
| def test_3D_random_vs_RGI(self, k, meth): |
| rndm = np.random.default_rng(123456) |
| x = np.cumsum(rndm.uniform(size=6)) |
| y = np.cumsum(rndm.uniform(size=7)) |
| z = np.cumsum(rndm.uniform(size=8)) |
| values = rndm.uniform(size=(6, 7, 8)) |
|
|
| bspl = make_ndbspl((x, y, z), values, k=k, solver=ssl.spsolve) |
| rgi = RegularGridInterpolator((x, y, z), values, method=meth) |
|
|
| xi = np.random.uniform(low=0.7, high=2.1, size=(11, 3)) |
| xp_assert_close(bspl(xi), rgi(xi), atol=1e-14) |
|
|
| def test_solver_err_not_converged(self): |
| x, y, z = self._get_sample_2d_data() |
| solver_args = {'maxiter': 1} |
| with assert_raises(ValueError, match='solver'): |
| make_ndbspl((x, y), z, k=3, **solver_args) |
|
|
| with assert_raises(ValueError, match='solver'): |
| make_ndbspl((x, y), np.dstack((z, z)), k=3, **solver_args) |
|
|
|
|
| class TestFpchec: |
| |
|
|
| def test_1D_x_t(self): |
| k = 1 |
| t = np.arange(12).reshape(2, 6) |
| x = np.arange(12) |
|
|
| with pytest.raises(ValueError, match="1D sequence"): |
| _b.fpcheck(x, t, k) |
|
|
| with pytest.raises(ValueError, match="1D sequence"): |
| _b.fpcheck(t, x, k) |
|
|
| def test_condition_1(self): |
| |
| k = 3 |
| n = 2*(k + 1) - 1 |
| m = n + 11 |
| t = np.arange(n) |
| x = np.arange(m) |
|
|
| assert dfitpack.fpchec(x, t, k) == 10 |
| with pytest.raises(ValueError, match="Need k+1*"): |
| _b.fpcheck(x, t, k) |
|
|
| n = 2*(k+1) + 1 |
| m = n - k - 2 |
| t = np.arange(n) |
| x = np.arange(m) |
|
|
| assert dfitpack.fpchec(x, t, k) == 10 |
| with pytest.raises(ValueError, match="Need k+1*"): |
| _b.fpcheck(x, t, k) |
|
|
| def test_condition_2(self): |
| |
| |
| k = 3 |
| t = [0]*(k+1) + [2] + [5]*(k+1) |
| x = [1, 2, 3, 4, 4.5] |
|
|
| assert dfitpack.fpchec(x, t, k) == 0 |
| assert _b.fpcheck(x, t, k) is None |
|
|
| tt = t.copy() |
| tt[-1] = tt[0] |
| assert dfitpack.fpchec(x, tt, k) == 20 |
| with pytest.raises(ValueError, match="Last k knots*"): |
| _b.fpcheck(x, tt, k) |
|
|
| tt = t.copy() |
| tt[0] = tt[-1] |
| assert dfitpack.fpchec(x, tt, k) == 20 |
| with pytest.raises(ValueError, match="First k knots*"): |
| _b.fpcheck(x, tt, k) |
|
|
| def test_condition_3(self): |
| |
| k = 3 |
| t = [0]*(k+1) + [2, 3] + [5]*(k+1) |
| x = [1, 2, 3, 3.5, 4, 4.5] |
| assert dfitpack.fpchec(x, t, k) == 0 |
| assert _b.fpcheck(x, t, k) is None |
|
|
| t = [0]*(k+1) + [2, 2] + [5]*(k+1) |
| assert dfitpack.fpchec(x, t, k) == 30 |
| with pytest.raises(ValueError, match="Internal knots*"): |
| _b.fpcheck(x, t, k) |
|
|
| def test_condition_4(self): |
| |
| |
| k = 3 |
| t = [0]*(k+1) + [5]*(k+1) |
| x = [1, 2, 3, 3.5, 4, 4.5] |
| assert dfitpack.fpchec(x, t, k) == 0 |
| assert _b.fpcheck(x, t, k) is None |
|
|
| xx = x.copy() |
| xx[0] = t[0] |
| assert dfitpack.fpchec(xx, t, k) == 0 |
| assert _b.fpcheck(x, t, k) is None |
|
|
| xx = x.copy() |
| xx[0] = t[0] - 1 |
| assert dfitpack.fpchec(xx, t, k) == 40 |
| with pytest.raises(ValueError, match="Out of bounds*"): |
| _b.fpcheck(xx, t, k) |
|
|
| xx = x.copy() |
| xx[-1] = t[-1] + 1 |
| assert dfitpack.fpchec(xx, t, k) == 40 |
| with pytest.raises(ValueError, match="Out of bounds*"): |
| _b.fpcheck(xx, t, k) |
|
|
| |
| |
| |
| |
| |
| def test_condition_5_x1xm(self): |
| |
| k = 1 |
| t = [0, 0, 1, 2, 2] |
| x = [1.1, 1.1, 1.1] |
| assert dfitpack.fpchec(x, t, k) == 50 |
| with pytest.raises(ValueError, match="Schoenberg-Whitney*"): |
| _b.fpcheck(x, t, k) |
|
|
| x = [0.5, 0.5, 0.5] |
| assert dfitpack.fpchec(x, t, k) == 50 |
| with pytest.raises(ValueError, match="Schoenberg-Whitney*"): |
| _b.fpcheck(x, t, k) |
|
|
| def test_condition_5_k1(self): |
| |
| k = 1 |
| t = [0, 0, 1, 1] |
| x = [0.5, 0.6] |
| assert dfitpack.fpchec(x, t, k) == 0 |
| assert _b.fpcheck(x, t, k) is None |
|
|
| def test_condition_5_1(self): |
| |
| k = 3 |
| t = [0]*(k+1) + [2] + [5]*(k+1) |
| x = [3]*5 |
| assert dfitpack.fpchec(x, t, k) == 50 |
| with pytest.raises(ValueError, match="Schoenberg-Whitney*"): |
| _b.fpcheck(x, t, k) |
|
|
| t = [0]*(k+1) + [2] + [5]*(k+1) |
| x = [1]*5 |
| assert dfitpack.fpchec(x, t, k) == 50 |
| with pytest.raises(ValueError, match="Schoenberg-Whitney*"): |
| _b.fpcheck(x, t, k) |
|
|
| def test_condition_5_2(self): |
| |
| k = 3 |
| t = [0]*(k+1) + [2, 3] + [5]*(k+1) |
| x = [1.1]*5 + [4] |
|
|
| assert dfitpack.fpchec(x, t, k) == 50 |
| with pytest.raises(ValueError, match="Schoenberg-Whitney*"): |
| _b.fpcheck(x, t, k) |
|
|
| |
| x = [1.1]*4 + [4, 4] |
| assert dfitpack.fpchec(x, t, k) == 0 |
| assert _b.fpcheck(x, t, k) is None |
|
|
| def test_condition_5_3(self): |
| |
| k = 1 |
| t = [0, 0, 2, 3, 4, 5, 6, 7, 7] |
| x = [1, 1, 1, 5.2, 5.2, 5.2, 6.5] |
|
|
| assert dfitpack.fpchec(x, t, k) == 50 |
| with pytest.raises(ValueError, match="Schoenberg-Whitney*"): |
| _b.fpcheck(x, t, k) |
|
|
|
|
| |
| |
| def _split(x, t, k, residuals): |
| """Split the knot interval into "runs". |
| """ |
| ix = np.searchsorted(x, t[k:-k]) |
| |
| fparts = [residuals[ix[i]:ix[i+1]].sum() for i in range(len(ix)-1)] |
| carries = residuals[ix[1:-1]] |
|
|
| for i in range(len(carries)): |
| carry = carries[i] / 2 |
| fparts[i] += carry |
| fparts[i+1] -= carry |
|
|
| fparts[-1] += residuals[-1] |
|
|
| xp_assert_close(sum(fparts), sum(residuals), atol=1e-15) |
|
|
| return fparts, ix |
|
|
|
|
| def _add_knot(x, t, k, residuals): |
| """Insert a new knot given reduals.""" |
| fparts, ix = _split(x, t, k, residuals) |
|
|
| |
| idx_max = -101 |
| fpart_max = -1e100 |
| for i in range(len(fparts)): |
| if ix[i+1] - ix[i] > 1 and fparts[i] > fpart_max: |
| idx_max = i |
| fpart_max = fparts[i] |
|
|
| if idx_max == -101: |
| raise ValueError("Internal error, please report it to SciPy developers.") |
|
|
| |
| idx_newknot = (ix[idx_max] + ix[idx_max+1] + 1) // 2 |
| new_knot = x[idx_newknot] |
| idx_t = np.searchsorted(t, new_knot) |
| t_new = np.r_[t[:idx_t], new_knot, t[idx_t:]] |
| return t_new |
|
|
|
|
| class TestGenerateKnots: |
| def test_split_add_knot(self): |
| |
| x = np.arange(8, dtype=float) |
| y = x**3 + 1./(1 + x) |
| k = 3 |
| t = np.array([0.]*(k+1) + [7.]*(k+1)) |
| spl = make_lsq_spline(x, y, k=k, t=t) |
| residuals = (spl(x) - y)**2 |
|
|
| from scipy.interpolate import _fitpack_repro as _fr |
| new_t = _fr.add_knot(x, t, k, residuals) |
| new_t_py = _add_knot(x, t, k, residuals) |
|
|
| xp_assert_close(new_t, new_t_py, atol=1e-15) |
|
|
| |
| spl2 = make_lsq_spline(x, y, k=k, t=new_t) |
| residuals2 = (spl2(x) - y)**2 |
|
|
| new_t2 = _fr.add_knot(x, new_t, k, residuals2) |
| new_t2_py = _add_knot(x, new_t, k, residuals2) |
|
|
| xp_assert_close(new_t2, new_t2_py, atol=1e-15) |
|
|
| @pytest.mark.parametrize('k', [1, 2, 3, 4, 5]) |
| def test_s0(self, k): |
| x = np.arange(8, dtype=np.float64) |
| y = np.sin(x*np.pi/8) |
| t = list(generate_knots(x, y, k=k, s=0))[-1] |
|
|
| tt = splrep(x, y, k=k, s=0)[0] |
| xp_assert_close(t, tt, atol=1e-15) |
|
|
| def test_s0_1(self): |
| |
| n = 10 |
| x = np.arange(n) |
| y = x**3 |
| knots = list(generate_knots(x, y, k=3, s=0)) |
| xp_assert_close(knots[-1], _not_a_knot(x, 3), atol=1e-15) |
|
|
| def test_s0_n20(self): |
| n = 20 |
| x = np.arange(n) |
| y = x**3 |
| knots = list(generate_knots(x, y, k=3, s=0)) |
| xp_assert_close(knots[-1], _not_a_knot(x, 3), atol=1e-15) |
|
|
| def test_s0_nest(self): |
| |
| x = np.arange(10) |
| y = x**3 |
| with assert_raises(ValueError): |
| list(generate_knots(x, y, k=3, s=0, nest=10)) |
|
|
| def test_s_switch(self): |
| |
| """ |
| To generate the `wanted` list below apply the following diff and rerun |
| the test. The stdout will contain successive iterations of the `t` |
| array. |
| |
| $ git diff scipy/interpolate/fitpack/fpcurf.f |
| diff --git a/scipy/interpolate/fitpack/fpcurf.f b/scipy/interpolate/fitpack/fpcurf.f |
| index 1afb1900f1..d817e51ad8 100644 |
| --- a/scipy/interpolate/fitpack/fpcurf.f |
| +++ b/scipy/interpolate/fitpack/fpcurf.f |
| @@ -216,6 +216,9 @@ c t(j+k) <= x(i) <= t(j+k+1) and store it in fpint(j),j=1,2,...nrint. |
| do 190 l=1,nplus |
| c add a new knot. |
| call fpknot(x,m,t,n,fpint,nrdata,nrint,nest,1) |
| + print*, l, nest, ': ', t |
| + print*, "n, nmax = ", n, nmax |
| + |
| c if n=nmax we locate the knots as for interpolation. |
| if(n.eq.nmax) go to 10 |
| c test whether we cannot further increase the number of knots. |
| """ |
| x = np.arange(8) |
| y = np.sin(x*np.pi/8) |
| k = 3 |
|
|
| knots = list(generate_knots(x, y, k=k, s=1e-7)) |
| wanted = [[0., 0., 0., 0., 7., 7., 7., 7.], |
| [0., 0., 0., 0., 4., 7., 7., 7., 7.], |
| [0., 0., 0., 0., 2., 4., 7., 7., 7., 7.], |
| [0., 0., 0., 0., 2., 4., 6., 7., 7., 7., 7.], |
| [0., 0., 0., 0., 2., 3., 4., 5., 7, 7., 7., 7.] |
| ] |
|
|
| assert len(knots) == len(wanted) |
| for t, tt in zip(knots, wanted): |
| xp_assert_close(t, tt, atol=1e-15) |
|
|
| |
| t, _, _ = splrep(x, y, k=k, s=1e-7) |
| xp_assert_close(knots[-1], t, atol=1e-15) |
|
|
| def test_list_input(self): |
| |
| x = list(range(8)) |
| gen = generate_knots(x, x, s=0.1, k=1) |
| next(gen) |
|
|
| def test_nest(self): |
| |
| x = np.arange(8) |
| y = np.sin(x*np.pi/8) |
| s = 1e-7 |
|
|
| knots = list(generate_knots(x, y, k=3, s=s, nest=10)) |
| xp_assert_close(knots[-1], |
| [0., 0., 0., 0., 2., 4., 7., 7., 7., 7.], atol=1e-15) |
|
|
| with assert_raises(ValueError): |
| |
| list(generate_knots(x, y, k=3, nest=4)) |
|
|
| def test_weights(self): |
| x = np.arange(8) |
| y = np.sin(x*np.pi/8) |
|
|
| with assert_raises(ValueError): |
| list(generate_knots(x, y, w=np.arange(11))) |
|
|
| with assert_raises(ValueError): |
| list(generate_knots(x, y, w=-np.ones(8))) |
|
|
| @pytest.mark.parametrize("npts", [30, 50, 100]) |
| @pytest.mark.parametrize("s", [0.1, 1e-2, 0]) |
| def test_vs_splrep(self, s, npts): |
| |
| |
| |
| |
| |
| |
| rndm = np.random.RandomState(12345) |
| x = 10*np.sort(rndm.uniform(size=npts)) |
| y = np.sin(x*np.pi/10) + np.exp(-(x-6)**2) |
|
|
| k = 3 |
| t = splrep(x, y, k=k, s=s)[0] |
| tt = list(generate_knots(x, y, k=k, s=s))[-1] |
|
|
| xp_assert_close(tt, t, atol=1e-15) |
|
|
| @pytest.mark.thread_unsafe |
| def test_s_too_small(self): |
| n = 14 |
| x = np.arange(n) |
| y = x**3 |
|
|
| |
| knots = list(generate_knots(x, y, k=3, s=1e-50)) |
|
|
| with suppress_warnings() as sup: |
| r = sup.record(RuntimeWarning) |
| tck = splrep(x, y, k=3, s=1e-50) |
| assert len(r) == 1 |
| xp_assert_equal(knots[-1], tck[0]) |
|
|
|
|
| def disc_naive(t, k): |
| """Straitforward way to compute the discontinuity matrix. For testing ONLY. |
| |
| This routine returns a dense matrix, while `_fitpack_repro.disc` returns |
| a packed one. |
| """ |
| n = t.shape[0] |
|
|
| delta = t[n - k - 1] - t[k] |
| nrint = n - 2*k - 1 |
|
|
| ti = t[k+1:n-k-1] |
| tii = np.repeat(ti, 2) |
| tii[::2] += 1e-10 |
| tii[1::2] -= 1e-10 |
| m = BSpline(t, np.eye(n - k - 1), k)(tii, nu=k) |
|
|
| matr = np.empty((nrint-1, m.shape[1]), dtype=float) |
| for i in range(0, m.shape[0], 2): |
| matr[i//2, :] = m[i, :] - m[i+1, :] |
|
|
| matr *= (delta/nrint)**k / math.factorial(k) |
| return matr |
|
|
|
|
| class F_dense: |
| """ The r.h.s. of ``f(p) = s``, an analog of _fitpack_repro.F |
| Uses full matrices, so is for tests only. |
| """ |
| def __init__(self, x, y, t, k, s, w=None): |
| self.x = x |
| self.y = y |
| self.t = t |
| self.k = k |
| self.w = np.ones_like(x, dtype=float) if w is None else w |
| assert self.w.ndim == 1 |
|
|
| |
| a_dense = BSpline(t, np.eye(t.shape[0] - k - 1), k)(x) |
| self.a_dense = a_dense * self.w[:, None] |
|
|
| from scipy.interpolate import _fitpack_repro as _fr |
| self.b_dense = PackedMatrix(*_fr.disc(t, k)).todense() |
|
|
| |
| assert y.ndim == 1 |
| yy = y * self.w |
| self.yy = np.r_[yy, np.zeros(self.b_dense.shape[0])] |
|
|
| self.s = s |
|
|
| def __call__(self, p): |
| ab = np.vstack((self.a_dense, self.b_dense / p)) |
|
|
| |
| from scipy.linalg import qr, solve |
| q, r = qr(ab, mode='economic') |
|
|
| qy = q.T @ self.yy |
|
|
| nc = r.shape[1] |
| c = solve(r[:nc, :nc], qy[:nc]) |
|
|
| spl = BSpline(self.t, c, self.k) |
| fp = np.sum(self.w**2 * (spl(self.x) - self.y)**2) |
|
|
| self.spl = spl |
|
|
| return fp - self.s |
|
|
|
|
| class TestMakeSplrep: |
| def test_input_errors(self): |
| x = np.linspace(0, 10, 11) |
| y = np.linspace(0, 10, 12) |
| with assert_raises(ValueError): |
| |
| make_splrep(x, y) |
|
|
| with assert_raises(ValueError): |
| |
| make_splrep(1, 2, s=0.1) |
|
|
| with assert_raises(ValueError): |
| |
| y = np.ones((x.size, 2, 2, 2)) |
| make_splrep(x, y, s=0.1) |
|
|
| w = np.ones(12) |
| with assert_raises(ValueError): |
| |
| make_splrep(x, x**3, w=w, s=0.1) |
|
|
| w = -np.ones(12) |
| with assert_raises(ValueError): |
| |
| make_splrep(x, x**3, w=w, s=0.1) |
|
|
| w = np.ones((x.shape[0], 2)) |
| with assert_raises(ValueError): |
| |
| make_splrep(x, x**3, w=w, s=0.1) |
|
|
| with assert_raises(ValueError): |
| |
| make_splrep(x[::-1], x**3, s=0.1) |
|
|
| with assert_raises(TypeError): |
| |
| make_splrep(x, x**3, k=2.5, s=0.1) |
|
|
| with assert_raises(ValueError): |
| |
| make_splrep(x, x**3, s=-1) |
|
|
| with assert_raises(ValueError): |
| |
| make_splrep(x, x**3, k=3, nest=2, s=0.1) |
|
|
| with assert_raises(ValueError): |
| |
| make_splrep(x, x**3, s=0, nest=11) |
|
|
| with assert_raises(ValueError): |
| |
| make_splrep(np.arange(8), np.arange(9), s=0.1) |
|
|
| def _get_xykt(self): |
| x = np.linspace(0, 5, 11) |
| y = np.sin(x*3.14 / 5)**2 |
| k = 3 |
| s = 1.7e-4 |
| tt = np.array([0]*(k+1) + [2.5, 4.0] + [5]*(k+1)) |
|
|
| return x, y, k, s, tt |
|
|
| def test_fitpack_F(self): |
| |
| from scipy.interpolate._fitpack_repro import F |
|
|
| x, y, k, s, t = self._get_xykt() |
| f = F(x, y[:, None], t, k, s) |
| f_d = F_dense(x, y, t, k, s) |
| for p in [1, 10, 100]: |
| xp_assert_close(f(p), f_d(p), atol=1e-15) |
|
|
| def test_fitpack_F_with_weights(self): |
| |
| from scipy.interpolate._fitpack_repro import F |
|
|
| x, y, k, s, t = self._get_xykt() |
| w = np.arange(x.shape[0], dtype=float) |
| fw = F(x, y[:, None], t, k, s, w=w) |
| fw_d = F_dense(x, y, t, k, s, w=w) |
|
|
| f_d = F_dense(x, y, t, k, s) |
|
|
| for p in [1, 10, 100]: |
| xp_assert_close(fw(p), fw_d(p), atol=1e-15) |
| assert not np.allclose(f_d(p), fw_d(p), atol=1e-15) |
|
|
| def test_disc_matrix(self): |
| |
| |
| import scipy.interpolate._fitpack_repro as _fr |
|
|
| rng = np.random.default_rng(12345) |
| t = np.r_[0, 0, 0, 0, np.sort(rng.uniform(size=7))*5, 5, 5, 5, 5] |
|
|
| n, k = len(t), 3 |
| D = PackedMatrix(*_fr.disc(t, k)).todense() |
| D_dense = disc_naive(t, k) |
| assert D.shape[0] == n - 2*k - 2 |
| xp_assert_close(D, D_dense, atol=1e-15) |
|
|
| def test_simple_vs_splrep(self): |
| x, y, k, s, tt = self._get_xykt() |
| tt = np.array([0]*(k+1) + [2.5, 4.0] + [5]*(k+1)) |
|
|
| t,c,k = splrep(x, y, k=k, s=s) |
| assert all(t == tt) |
|
|
| spl = make_splrep(x, y, k=k, s=s) |
| xp_assert_close(c[:spl.c.size], spl.c, atol=1e-15) |
|
|
| def test_with_knots(self): |
| x, y, k, s, _ = self._get_xykt() |
|
|
| t = list(generate_knots(x, y, k=k, s=s))[-1] |
|
|
| spl_auto = make_splrep(x, y, k=k, s=s) |
| spl_t = make_splrep(x, y, t=t, k=k, s=s) |
|
|
| xp_assert_close(spl_auto.t, spl_t.t, atol=1e-15) |
| xp_assert_close(spl_auto.c, spl_t.c, atol=1e-15) |
| assert spl_auto.k == spl_t.k |
|
|
| def test_no_internal_knots(self): |
| |
| n = 10 |
| x = np.arange(n) |
| y = x**3 |
| k = 3 |
| spl = make_splrep(x, y, k=k, s=1) |
| assert spl.t.shape[0] == 2*(k+1) |
|
|
| def test_default_s(self): |
| n = 10 |
| x = np.arange(n) |
| y = x**3 |
| spl = make_splrep(x, y, k=3) |
| spl_i = make_interp_spline(x, y, k=3) |
|
|
| xp_assert_close(spl.c, spl_i.c, atol=1e-15) |
|
|
| @pytest.mark.thread_unsafe |
| def test_s_too_small(self): |
| |
| n = 14 |
| x = np.arange(n) |
| y = x**3 |
|
|
| with suppress_warnings() as sup: |
| r = sup.record(RuntimeWarning) |
| tck = splrep(x, y, k=3, s=1e-50) |
| spl = make_splrep(x, y, k=3, s=1e-50) |
| assert len(r) == 2 |
| xp_assert_equal(spl.t, tck[0]) |
| xp_assert_close(np.r_[spl.c, [0]*(spl.k+1)], |
| tck[1], atol=5e-13) |
|
|
| def test_shape(self): |
| |
| n, k = 10, 3 |
| x = np.arange(n) |
| y = x**3 |
|
|
| spl = make_splrep(x, y, k=k) |
| spl_1 = make_splrep(x, y, k=k, s=1e-5) |
|
|
| assert spl.c.ndim == 1 |
| assert spl_1.c.ndim == 1 |
|
|
| |
| spl_2 = make_splrep(x, y + 1/(1+y), k=k, s=1e-5) |
| assert spl_2.c.ndim == 1 |
|
|
| def test_s0_vs_not(self): |
| |
| n, k = 10, 3 |
| x = np.arange(n) |
| y = x**3 |
|
|
| spl_0 = make_splrep(x, y, k=3, s=0) |
| spl_1 = make_splrep(x, y, k=3, s=1) |
|
|
| assert spl_0.c.ndim == 1 |
| assert spl_1.c.ndim == 1 |
|
|
| assert spl_0.t.shape[0] == n + k + 1 |
| assert spl_1.t.shape[0] == 2 * (k + 1) |
|
|
|
|
| class TestMakeSplprep: |
| def _get_xyk(self, m=10, k=3): |
| x = np.arange(m) * np.pi / m |
| y = [np.sin(x), np.cos(x)] |
| return x, y, k |
|
|
| @pytest.mark.parametrize('s', [0, 0.1, 1e-3, 1e-5]) |
| def test_simple_vs_splprep(self, s): |
| |
| |
| m, k = 10, 3 |
| x = np.arange(m) * np.pi / m |
| y = [np.sin(x), np.cos(x)] |
|
|
| |
| num_knots = {0: 14, 0.1: 8, 1e-3: 8 + 1, 1e-5: 8 + 2} |
|
|
| |
| (t, c, k), u_ = splprep(y, s=s) |
| spl, u = make_splprep(y, s=s) |
|
|
| |
| xp_assert_close(u, u_, atol=1e-15) |
|
|
| |
| xp_assert_close(spl.t, t, atol=1e-15) |
| assert len(t) == num_knots[s] |
|
|
| |
| cc = np.asarray(c).T |
| xp_assert_close(spl.c, cc, atol=1e-15) |
|
|
| |
| xp_assert_close(spl(u), |
| BSpline(t, c, k, axis=1)(u), atol=1e-15) |
|
|
| @pytest.mark.parametrize('s', [0, 0.1, 1e-3, 1e-5]) |
| def test_array_not_list(self, s): |
| |
| _, y, _ = self._get_xyk() |
| assert isinstance(y, list) |
| assert np.shape(y)[0] == 2 |
|
|
| |
| tck, u = splprep(y, s=s) |
| tck_a, u_a = splprep(np.asarray(y), s=s) |
| xp_assert_close(u, u_a, atol=s) |
| xp_assert_close(tck[0], tck_a[0], atol=1e-15) |
| assert len(tck[1]) == len(tck_a[1]) |
| for c1, c2 in zip(tck[1], tck_a[1]): |
| xp_assert_close(c1, c2, atol=1e-15) |
| assert tck[2] == tck_a[2] |
| assert np.shape(splev(u, tck)) == np.shape(y) |
|
|
| spl, u = make_splprep(y, s=s) |
| xp_assert_close(u, u_a, atol=1e-15) |
| xp_assert_close(spl.t, tck_a[0], atol=1e-15) |
| xp_assert_close(spl.c.T, tck_a[1], atol=1e-15) |
| assert spl.k == tck_a[2] |
| assert spl(u).shape == np.shape(y) |
|
|
| spl, u = make_splprep(np.asarray(y), s=s) |
| xp_assert_close(u, u_a, atol=1e-15) |
| xp_assert_close(spl.t, tck_a[0], atol=1e-15) |
| xp_assert_close(spl.c.T, tck_a[1], atol=1e-15) |
| assert spl.k == tck_a[2] |
| assert spl(u).shape == np.shape(y) |
|
|
| with assert_raises(ValueError): |
| make_splprep(np.asarray(y).T, s=s) |
|
|
| def test_default_s_is_zero(self): |
| x, y, k = self._get_xyk(m=10) |
|
|
| spl, u = make_splprep(y) |
| xp_assert_close(spl(u), y, atol=1e-15) |
|
|
| def test_s_zero_vs_near_zero(self): |
| |
| x, y, k = self._get_xyk(m=10) |
|
|
| spl_i, u_i = make_splprep(y, s=0) |
| spl_n, u_n = make_splprep(y, s=1e-15) |
|
|
| xp_assert_close(u_i, u_n, atol=1e-15) |
| xp_assert_close(spl_i(u_i), y, atol=1e-15) |
| xp_assert_close(spl_n(u_n), y, atol=1e-7) |
| assert spl_i.axis == spl_n.axis |
| assert spl_i.c.shape == spl_n.c.shape |
|
|
| def test_1D(self): |
| x = np.arange(8, dtype=float) |
| with assert_raises(ValueError): |
| splprep(x) |
|
|
| with assert_raises(ValueError): |
| make_splprep(x, s=0) |
|
|
| with assert_raises(ValueError): |
| make_splprep(x, s=0.1) |
|
|
| tck, u_ = splprep([x], s=1e-5) |
| spl, u = make_splprep([x], s=1e-5) |
|
|
| assert spl(u).shape == (1, 8) |
| xp_assert_close(spl(u), [x], atol=1e-15) |
|
|
|
|