| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976 |
- import warnings
- import io
- import numpy as np
- from scipy._lib._array_api import (
- xp_assert_equal, xp_assert_close, assert_array_almost_equal, assert_almost_equal,
- make_xp_test_case
- )
- from pytest import raises as assert_raises
- import pytest
- from scipy.interpolate import (
- KroghInterpolator, krogh_interpolate,
- BarycentricInterpolator, barycentric_interpolate,
- approximate_taylor_polynomial, CubicHermiteSpline, pchip,
- PchipInterpolator, pchip_interpolate, Akima1DInterpolator, CubicSpline,
- make_interp_spline)
- from scipy._lib._testutils import _run_concurrent_barrier
- skip_xp_backends = pytest.mark.skip_xp_backends
- xfail_xp_backends = pytest.mark.xfail_xp_backends
- def check_shape(interpolator_cls, x_shape, y_shape, deriv_shape=None, axis=0,
- extra_args=None):
- if extra_args is None:
- extra_args = {}
- rng = np.random.RandomState(1234)
- x = [-1, 0, 1, 2, 3, 4]
- s = list(range(1, len(y_shape)+1))
- s.insert(axis % (len(y_shape)+1), 0)
- y = rng.rand(*((6,) + y_shape)).transpose(s)
- xi = np.zeros(x_shape)
- if interpolator_cls is CubicHermiteSpline:
- dydx = rng.rand(*((6,) + y_shape)).transpose(s)
- yi = interpolator_cls(x, y, dydx, axis=axis, **extra_args)(xi)
- else:
- yi = interpolator_cls(x, y, axis=axis, **extra_args)(xi)
- target_shape = ((deriv_shape or ()) + y.shape[:axis]
- + x_shape + y.shape[axis:][1:])
- assert yi.shape == target_shape
- # check it works also with lists
- if x_shape and y.size > 0:
- if interpolator_cls is CubicHermiteSpline:
- interpolator_cls(list(x), list(y), list(dydx), axis=axis,
- **extra_args)(list(xi))
- else:
- interpolator_cls(list(x), list(y), axis=axis,
- **extra_args)(list(xi))
- # check also values
- if xi.size > 0 and deriv_shape is None:
- bs_shape = y.shape[:axis] + (1,)*len(x_shape) + y.shape[axis:][1:]
- yv = y[((slice(None,),)*(axis % y.ndim)) + (1,)]
- yv = yv.reshape(bs_shape)
- yi, y = np.broadcast_arrays(yi, yv)
- xp_assert_close(yi, y)
- SHAPES = [(), (0,), (1,), (6, 2, 5)]
- def test_shapes():
- def spl_interp(x, y, axis):
- return make_interp_spline(x, y, axis=axis)
- for ip in [KroghInterpolator, BarycentricInterpolator, CubicHermiteSpline,
- pchip, Akima1DInterpolator, CubicSpline, spl_interp]:
- for s1 in SHAPES:
- for s2 in SHAPES:
- for axis in range(-len(s2), len(s2)):
- if ip != CubicSpline:
- check_shape(ip, s1, s2, None, axis)
- else:
- for bc in ['natural', 'clamped']:
- extra = {'bc_type': bc}
- check_shape(ip, s1, s2, None, axis, extra)
- def test_derivs_shapes():
- for ip in [KroghInterpolator, BarycentricInterpolator]:
- def interpolator_derivs(x, y, axis=0):
- return ip(x, y, axis).derivatives
- for s1 in SHAPES:
- for s2 in SHAPES:
- for axis in range(-len(s2), len(s2)):
- check_shape(interpolator_derivs, s1, s2, (6,), axis)
- def test_deriv_shapes():
- def krogh_deriv(x, y, axis=0):
- return KroghInterpolator(x, y, axis).derivative
- def bary_deriv(x, y, axis=0):
- return BarycentricInterpolator(x, y, axis).derivative
- def pchip_deriv(x, y, axis=0):
- return pchip(x, y, axis).derivative()
- def pchip_deriv2(x, y, axis=0):
- return pchip(x, y, axis).derivative(2)
- def pchip_antideriv(x, y, axis=0):
- return pchip(x, y, axis).antiderivative()
- def pchip_antideriv2(x, y, axis=0):
- return pchip(x, y, axis).antiderivative(2)
- def pchip_deriv_inplace(x, y, axis=0):
- class P(PchipInterpolator):
- def __call__(self, x):
- return PchipInterpolator.__call__(self, x, 1)
- pass
- return P(x, y, axis)
- def akima_deriv(x, y, axis=0):
- return Akima1DInterpolator(x, y, axis).derivative()
- def akima_antideriv(x, y, axis=0):
- return Akima1DInterpolator(x, y, axis).antiderivative()
- def cspline_deriv(x, y, axis=0):
- return CubicSpline(x, y, axis).derivative()
- def cspline_antideriv(x, y, axis=0):
- return CubicSpline(x, y, axis).antiderivative()
- def bspl_deriv(x, y, axis=0):
- return make_interp_spline(x, y, axis=axis).derivative()
- def bspl_antideriv(x, y, axis=0):
- return make_interp_spline(x, y, axis=axis).antiderivative()
- for ip in [krogh_deriv, bary_deriv, pchip_deriv, pchip_deriv2, pchip_deriv_inplace,
- pchip_antideriv, pchip_antideriv2, akima_deriv, akima_antideriv,
- cspline_deriv, cspline_antideriv, bspl_deriv, bspl_antideriv]:
- for s1 in SHAPES:
- for s2 in SHAPES:
- for axis in range(-len(s2), len(s2)):
- check_shape(ip, s1, s2, (), axis)
- def test_complex():
- x = [1, 2, 3, 4]
- y = [1, 2, 1j, 3]
- for ip in [KroghInterpolator, BarycentricInterpolator, CubicSpline]:
- p = ip(x, y)
- xp_assert_close(p(x), np.asarray(y))
- dydx = [0, -1j, 2, 3j]
- p = CubicHermiteSpline(x, y, dydx)
- xp_assert_close(p(x), np.asarray(y))
- xp_assert_close(p(x, 1), np.asarray(dydx))
- class TestKrogh:
- def setup_method(self):
- self.true_poly = np.polynomial.Polynomial([-4, 5, 1, 3, -2])
- self.test_xs = np.linspace(-1,1,100)
- self.xs = np.linspace(-1,1,5)
- self.ys = self.true_poly(self.xs)
- def test_lagrange(self):
- P = KroghInterpolator(self.xs,self.ys)
- assert_almost_equal(self.true_poly(self.test_xs),P(self.test_xs))
- def test_scalar(self):
- P = KroghInterpolator(self.xs,self.ys)
- assert_almost_equal(self.true_poly(7), P(7), check_0d=False)
- assert_almost_equal(self.true_poly(np.array(7)), P(np.array(7)), check_0d=False)
- def test_derivatives(self):
- P = KroghInterpolator(self.xs,self.ys)
- D = P.derivatives(self.test_xs)
- for i in range(D.shape[0]):
- assert_almost_equal(self.true_poly.deriv(i)(self.test_xs),
- D[i])
- def test_low_derivatives(self):
- P = KroghInterpolator(self.xs,self.ys)
- D = P.derivatives(self.test_xs,len(self.xs)+2)
- for i in range(D.shape[0]):
- assert_almost_equal(self.true_poly.deriv(i)(self.test_xs),
- D[i])
- def test_derivative(self):
- P = KroghInterpolator(self.xs,self.ys)
- m = 10
- r = P.derivatives(self.test_xs,m)
- for i in range(m):
- assert_almost_equal(P.derivative(self.test_xs,i),r[i])
- def test_high_derivative(self):
- P = KroghInterpolator(self.xs,self.ys)
- for i in range(len(self.xs), 2*len(self.xs)):
- assert_almost_equal(P.derivative(self.test_xs,i),
- np.zeros(len(self.test_xs)))
- def test_ndim_derivatives(self):
- poly1 = self.true_poly
- poly2 = np.polynomial.Polynomial([-2, 5, 3, -1])
- poly3 = np.polynomial.Polynomial([12, -3, 4, -5, 6])
- ys = np.stack((poly1(self.xs), poly2(self.xs), poly3(self.xs)), axis=-1)
- P = KroghInterpolator(self.xs, ys, axis=0)
- D = P.derivatives(self.test_xs)
- for i in range(D.shape[0]):
- xp_assert_close(D[i],
- np.stack((poly1.deriv(i)(self.test_xs),
- poly2.deriv(i)(self.test_xs),
- poly3.deriv(i)(self.test_xs)),
- axis=-1))
- def test_ndim_derivative(self):
- poly1 = self.true_poly
- poly2 = np.polynomial.Polynomial([-2, 5, 3, -1])
- poly3 = np.polynomial.Polynomial([12, -3, 4, -5, 6])
- ys = np.stack((poly1(self.xs), poly2(self.xs), poly3(self.xs)), axis=-1)
- P = KroghInterpolator(self.xs, ys, axis=0)
- for i in range(P.n):
- xp_assert_close(P.derivative(self.test_xs, i),
- np.stack((poly1.deriv(i)(self.test_xs),
- poly2.deriv(i)(self.test_xs),
- poly3.deriv(i)(self.test_xs)),
- axis=-1))
- def test_hermite(self):
- P = KroghInterpolator(self.xs,self.ys)
- assert_almost_equal(self.true_poly(self.test_xs),P(self.test_xs))
- def test_vector(self):
- xs = [0, 1, 2]
- ys = np.array([[0,1],[1,0],[2,1]])
- P = KroghInterpolator(xs,ys)
- Pi = [KroghInterpolator(xs,ys[:,i]) for i in range(ys.shape[1])]
- test_xs = np.linspace(-1,3,100)
- assert_almost_equal(P(test_xs),
- np.asarray([p(test_xs) for p in Pi]).T)
- assert_almost_equal(P.derivatives(test_xs),
- np.transpose(np.asarray([p.derivatives(test_xs) for p in Pi]),
- (1,2,0)))
- def test_empty(self):
- P = KroghInterpolator(self.xs,self.ys)
- xp_assert_equal(P([]), np.asarray([]))
- def test_shapes_scalarvalue(self):
- P = KroghInterpolator(self.xs,self.ys)
- assert np.shape(P(0)) == ()
- assert np.shape(P(np.array(0))) == ()
- assert np.shape(P([0])) == (1,)
- assert np.shape(P([0,1])) == (2,)
- def test_shapes_scalarvalue_derivative(self):
- P = KroghInterpolator(self.xs,self.ys)
- n = P.n
- assert np.shape(P.derivatives(0)) == (n,)
- assert np.shape(P.derivatives(np.array(0))) == (n,)
- assert np.shape(P.derivatives([0])) == (n, 1)
- assert np.shape(P.derivatives([0, 1])) == (n, 2)
- def test_shapes_vectorvalue(self):
- P = KroghInterpolator(self.xs,np.outer(self.ys,np.arange(3)))
- assert np.shape(P(0)) == (3,)
- assert np.shape(P([0])) == (1, 3)
- assert np.shape(P([0, 1])) == (2, 3)
- def test_shapes_1d_vectorvalue(self):
- P = KroghInterpolator(self.xs,np.outer(self.ys,[1]))
- assert np.shape(P(0)) == (1,)
- assert np.shape(P([0])) == (1, 1)
- assert np.shape(P([0,1])) == (2, 1)
- def test_shapes_vectorvalue_derivative(self):
- P = KroghInterpolator(self.xs,np.outer(self.ys,np.arange(3)))
- n = P.n
- assert np.shape(P.derivatives(0)) == (n, 3)
- assert np.shape(P.derivatives([0])) == (n, 1, 3)
- assert np.shape(P.derivatives([0,1])) == (n, 2, 3)
- def test_wrapper(self):
- P = KroghInterpolator(self.xs, self.ys)
- ki = krogh_interpolate
- assert_almost_equal(P(self.test_xs), ki(self.xs, self.ys, self.test_xs))
- assert_almost_equal(P.derivative(self.test_xs, 2),
- ki(self.xs, self.ys, self.test_xs, der=2))
- assert_almost_equal(P.derivatives(self.test_xs, 2),
- ki(self.xs, self.ys, self.test_xs, der=[0, 1]))
- def test_int_inputs(self):
- # Check input args are cast correctly to floats, gh-3669
- x = [0, 234, 468, 702, 936, 1170, 1404, 2340, 3744, 6084, 8424,
- 13104, 60000]
- offset_cdf = np.array([-0.95, -0.86114777, -0.8147762, -0.64072425,
- -0.48002351, -0.34925329, -0.26503107,
- -0.13148093, -0.12988833, -0.12979296,
- -0.12973574, -0.08582937, 0.05])
- f = KroghInterpolator(x, offset_cdf)
- xp_assert_close(abs((f(x) - offset_cdf) / f.derivative(x, 1)),
- np.zeros_like(offset_cdf), atol=1e-10)
- def test_derivatives_complex(self):
- # regression test for gh-7381: krogh.derivatives(0) fails complex y
- x, y = np.array([-1, -1, 0, 1, 1]), np.array([1, 1.0j, 0, -1, 1.0j])
- func = KroghInterpolator(x, y)
- cmplx = func.derivatives(0)
- cmplx2 = (KroghInterpolator(x, y.real).derivatives(0) +
- 1j*KroghInterpolator(x, y.imag).derivatives(0))
- xp_assert_close(cmplx, cmplx2, atol=1e-15)
- def test_high_degree_warning(self):
- with pytest.warns(UserWarning, match="40 degrees provided,"):
- KroghInterpolator(np.arange(40), np.ones(40))
- def test_concurrency(self):
- P = KroghInterpolator(self.xs, self.ys)
- def worker_fn(_, interp):
- interp(self.xs)
- _run_concurrent_barrier(10, worker_fn, P)
- class TestTaylor:
- def test_exponential(self):
- degree = 5
- p = approximate_taylor_polynomial(np.exp, 0, degree, 1, 15)
- for i in range(degree+1):
- assert_almost_equal(p(0),1)
- p = p.deriv()
- assert_almost_equal(p(0),0)
- class TestBarycentric:
- def setup_method(self):
- self.true_poly = np.polynomial.Polynomial([-4, 5, 1, 3, -2])
- self.test_xs = np.linspace(-1, 1, 100)
- self.xs = np.linspace(-1, 1, 5)
- self.ys = self.true_poly(self.xs)
- def test_lagrange(self):
- # Ensure backwards compatible post SPEC7
- P = BarycentricInterpolator(self.xs, self.ys, random_state=1)
- xp_assert_close(P(self.test_xs), self.true_poly(self.test_xs))
- def test_scalar(self):
- P = BarycentricInterpolator(self.xs, self.ys, rng=1)
- xp_assert_close(P(7), self.true_poly(7), check_0d=False)
- xp_assert_close(P(np.array(7)), self.true_poly(np.array(7)), check_0d=False)
- def test_derivatives(self):
- P = BarycentricInterpolator(self.xs, self.ys)
- D = P.derivatives(self.test_xs)
- for i in range(D.shape[0]):
- xp_assert_close(self.true_poly.deriv(i)(self.test_xs), D[i])
- def test_low_derivatives(self):
- P = BarycentricInterpolator(self.xs, self.ys)
- D = P.derivatives(self.test_xs, len(self.xs)+2)
- for i in range(D.shape[0]):
- xp_assert_close(self.true_poly.deriv(i)(self.test_xs),
- D[i],
- atol=1e-12)
- def test_derivative(self):
- P = BarycentricInterpolator(self.xs, self.ys)
- m = 10
- r = P.derivatives(self.test_xs, m)
- for i in range(m):
- xp_assert_close(P.derivative(self.test_xs, i), r[i])
- def test_high_derivative(self):
- P = BarycentricInterpolator(self.xs, self.ys)
- for i in range(len(self.xs), 5*len(self.xs)):
- xp_assert_close(P.derivative(self.test_xs, i),
- np.zeros(len(self.test_xs)))
- def test_ndim_derivatives(self):
- poly1 = self.true_poly
- poly2 = np.polynomial.Polynomial([-2, 5, 3, -1])
- poly3 = np.polynomial.Polynomial([12, -3, 4, -5, 6])
- ys = np.stack((poly1(self.xs), poly2(self.xs), poly3(self.xs)), axis=-1)
- P = BarycentricInterpolator(self.xs, ys, axis=0)
- D = P.derivatives(self.test_xs)
- for i in range(D.shape[0]):
- xp_assert_close(D[i],
- np.stack((poly1.deriv(i)(self.test_xs),
- poly2.deriv(i)(self.test_xs),
- poly3.deriv(i)(self.test_xs)),
- axis=-1),
- atol=1e-12)
- def test_ndim_derivative(self):
- poly1 = self.true_poly
- poly2 = np.polynomial.Polynomial([-2, 5, 3, -1])
- poly3 = np.polynomial.Polynomial([12, -3, 4, -5, 6])
- ys = np.stack((poly1(self.xs), poly2(self.xs), poly3(self.xs)), axis=-1)
- P = BarycentricInterpolator(self.xs, ys, axis=0)
- for i in range(P.n):
- xp_assert_close(P.derivative(self.test_xs, i),
- np.stack((poly1.deriv(i)(self.test_xs),
- poly2.deriv(i)(self.test_xs),
- poly3.deriv(i)(self.test_xs)),
- axis=-1),
- atol=1e-12)
- def test_delayed(self):
- P = BarycentricInterpolator(self.xs)
- P.set_yi(self.ys)
- assert_almost_equal(self.true_poly(self.test_xs), P(self.test_xs))
- def test_append(self):
- P = BarycentricInterpolator(self.xs[:3], self.ys[:3])
- P.add_xi(self.xs[3:], self.ys[3:])
- assert_almost_equal(self.true_poly(self.test_xs), P(self.test_xs))
- def test_vector(self):
- xs = [0, 1, 2]
- ys = np.array([[0, 1], [1, 0], [2, 1]])
- BI = BarycentricInterpolator
- P = BI(xs, ys)
- Pi = [BI(xs, ys[:, i]) for i in range(ys.shape[1])]
- test_xs = np.linspace(-1, 3, 100)
- assert_almost_equal(P(test_xs),
- np.asarray([p(test_xs) for p in Pi]).T)
- def test_shapes_scalarvalue(self):
- P = BarycentricInterpolator(self.xs, self.ys)
- assert np.shape(P(0)) == ()
- assert np.shape(P(np.array(0))) == ()
- assert np.shape(P([0])) == (1,)
- assert np.shape(P([0, 1])) == (2,)
- def test_shapes_scalarvalue_derivative(self):
- P = BarycentricInterpolator(self.xs,self.ys)
- n = P.n
- assert np.shape(P.derivatives(0)) == (n,)
- assert np.shape(P.derivatives(np.array(0))) == (n,)
- assert np.shape(P.derivatives([0])) == (n,1)
- assert np.shape(P.derivatives([0,1])) == (n,2)
- def test_shapes_vectorvalue(self):
- P = BarycentricInterpolator(self.xs, np.outer(self.ys, np.arange(3)))
- assert np.shape(P(0)) == (3,)
- assert np.shape(P([0])) == (1, 3)
- assert np.shape(P([0, 1])) == (2, 3)
- def test_shapes_1d_vectorvalue(self):
- P = BarycentricInterpolator(self.xs, np.outer(self.ys, [1]))
- assert np.shape(P(0)) == (1,)
- assert np.shape(P([0])) == (1, 1)
- assert np.shape(P([0, 1])) == (2, 1)
- def test_shapes_vectorvalue_derivative(self):
- P = BarycentricInterpolator(self.xs,np.outer(self.ys,np.arange(3)))
- n = P.n
- assert np.shape(P.derivatives(0)) == (n, 3)
- assert np.shape(P.derivatives([0])) == (n, 1, 3)
- assert np.shape(P.derivatives([0, 1])) == (n, 2, 3)
- def test_wrapper(self):
- P = BarycentricInterpolator(self.xs, self.ys, rng=1)
- bi = barycentric_interpolate
- xp_assert_close(P(self.test_xs), bi(self.xs, self.ys, self.test_xs, rng=1))
- xp_assert_close(P.derivative(self.test_xs, 2),
- bi(self.xs, self.ys, self.test_xs, der=2, rng=1))
- xp_assert_close(P.derivatives(self.test_xs, 2),
- bi(self.xs, self.ys, self.test_xs, der=[0, 1], rng=1))
- def test_int_input(self):
- x = 1000 * np.arange(1, 11) # np.prod(x[-1] - x[:-1]) overflows
- y = np.arange(1, 11)
- value = barycentric_interpolate(x, y, 1000 * 9.5)
- assert_almost_equal(value, np.asarray(9.5))
- def test_large_chebyshev(self):
- # The weights for Chebyshev points of the second kind have analytically
- # solvable weights. Naive calculation of barycentric weights will fail
- # for large N because of numerical underflow and overflow. We test
- # correctness for large N against analytical Chebyshev weights.
- # Without capacity scaling or permutation, n=800 fails,
- # With just capacity scaling, n=1097 fails
- # With both capacity scaling and random permutation, n=30000 succeeds
- n = 1100
- j = np.arange(n + 1).astype(np.float64)
- x = np.cos(j * np.pi / n)
- # See page 506 of Berrut and Trefethen 2004 for this formula
- w = (-1) ** j
- w[0] *= 0.5
- w[-1] *= 0.5
- P = BarycentricInterpolator(x)
- # It's okay to have a constant scaling factor in the weights because it
- # cancels out in the evaluation of the polynomial.
- factor = P.wi[0]
- assert_almost_equal(P.wi / (2 * factor), w)
- def test_warning(self):
- # Test if the divide-by-zero warning is properly ignored when computing
- # interpolated values equals to interpolation points
- P = BarycentricInterpolator([0, 1], [1, 2])
- with np.errstate(divide='raise'):
- yi = P(P.xi)
- # Check if the interpolated values match the input values
- # at the nodes
- assert_almost_equal(yi, P.yi.ravel())
- def test_repeated_node(self):
- # check that a repeated node raises a ValueError
- # (computing the weights requires division by xi[i] - xi[j])
- xis = np.array([0.1, 0.5, 0.9, 0.5])
- ys = np.array([1, 2, 3, 4])
- with pytest.raises(ValueError,
- match="Interpolation points xi must be distinct."):
- BarycentricInterpolator(xis, ys)
- def test_concurrency(self):
- P = BarycentricInterpolator(self.xs, self.ys)
- def worker_fn(_, interp):
- interp(self.xs)
- _run_concurrent_barrier(10, worker_fn, P)
- class TestPCHIP:
- def _make_random(self, npts=20):
- rng = np.random.RandomState(1234)
- xi = np.sort(rng.random(npts))
- yi = rng.random(npts)
- return pchip(xi, yi), xi, yi
- def test_overshoot(self):
- # PCHIP should not overshoot
- p, xi, yi = self._make_random()
- for i in range(len(xi)-1):
- x1, x2 = xi[i], xi[i+1]
- y1, y2 = yi[i], yi[i+1]
- if y1 > y2:
- y1, y2 = y2, y1
- xp = np.linspace(x1, x2, 10)
- yp = p(xp)
- assert ((y1 <= yp + 1e-15) & (yp <= y2 + 1e-15)).all()
- def test_monotone(self):
- # PCHIP should preserve monotonicty
- p, xi, yi = self._make_random()
- for i in range(len(xi)-1):
- x1, x2 = xi[i], xi[i+1]
- y1, y2 = yi[i], yi[i+1]
- xp = np.linspace(x1, x2, 10)
- yp = p(xp)
- assert ((y2-y1) * (yp[1:] - yp[:1]) > 0).all()
- def test_cast(self):
- # regression test for integer input data, see gh-3453
- data = np.array([[0, 4, 12, 27, 47, 60, 79, 87, 99, 100],
- [-33, -33, -19, -2, 12, 26, 38, 45, 53, 55]])
- xx = np.arange(100)
- curve = pchip(data[0], data[1])(xx)
- data1 = data * 1.0
- curve1 = pchip(data1[0], data1[1])(xx)
- xp_assert_close(curve, curve1, atol=1e-14, rtol=1e-14)
- def test_nag(self):
- # Example from NAG C implementation,
- # http://nag.com/numeric/cl/nagdoc_cl25/html/e01/e01bec.html
- # suggested in gh-5326 as a smoke test for the way the derivatives
- # are computed (see also gh-3453)
- dataStr = '''
- 7.99 0.00000E+0
- 8.09 0.27643E-4
- 8.19 0.43750E-1
- 8.70 0.16918E+0
- 9.20 0.46943E+0
- 10.00 0.94374E+0
- 12.00 0.99864E+0
- 15.00 0.99992E+0
- 20.00 0.99999E+0
- '''
- data = np.loadtxt(io.StringIO(dataStr))
- pch = pchip(data[:,0], data[:,1])
- resultStr = '''
- 7.9900 0.0000
- 9.1910 0.4640
- 10.3920 0.9645
- 11.5930 0.9965
- 12.7940 0.9992
- 13.9950 0.9998
- 15.1960 0.9999
- 16.3970 1.0000
- 17.5980 1.0000
- 18.7990 1.0000
- 20.0000 1.0000
- '''
- result = np.loadtxt(io.StringIO(resultStr))
- xp_assert_close(result[:,1], pch(result[:,0]), rtol=0., atol=5e-5)
- def test_endslopes(self):
- # this is a smoke test for gh-3453: PCHIP interpolator should not
- # set edge slopes to zero if the data do not suggest zero edge derivatives
- x = np.array([0.0, 0.1, 0.25, 0.35])
- y1 = np.array([279.35, 0.5e3, 1.0e3, 2.5e3])
- y2 = np.array([279.35, 2.5e3, 1.50e3, 1.0e3])
- for pp in (pchip(x, y1), pchip(x, y2)):
- for t in (x[0], x[-1]):
- assert pp(t, 1) != 0
- def test_all_zeros(self):
- x = np.arange(10)
- y = np.zeros_like(x)
- # this should work and not generate any warnings
- with warnings.catch_warnings():
- warnings.filterwarnings('error')
- pch = pchip(x, y)
- xx = np.linspace(0, 9, 101)
- assert all(pch(xx) == 0.)
- def test_two_points(self):
- # regression test for gh-6222: pchip([0, 1], [0, 1]) fails because
- # it tries to use a three-point scheme to estimate edge derivatives,
- # while there are only two points available.
- # Instead, it should construct a linear interpolator.
- x = np.linspace(0, 1, 11)
- p = pchip([0, 1], [0, 2])
- xp_assert_close(p(x), 2*x, atol=1e-15)
- def test_pchip_interpolate(self):
- assert_array_almost_equal(
- pchip_interpolate([1, 2, 3], [4, 5, 6], [0.5], der=1),
- np.asarray([1.]))
- assert_array_almost_equal(
- pchip_interpolate([1, 2, 3], [4, 5, 6], [0.5], der=0),
- np.asarray([3.5]))
- assert_array_almost_equal(
- np.asarray(pchip_interpolate([1, 2, 3], [4, 5, 6], [0.5], der=[0, 1])),
- np.asarray([[3.5], [1]]))
- def test_roots(self):
- # regression test for gh-6357: .roots method should work
- p = pchip([0, 1], [-1, 1])
- r = p.roots()
- xp_assert_close(r, np.asarray([0.5]))
- @make_xp_test_case(CubicSpline)
- class TestCubicSpline:
- @staticmethod
- def check_correctness(S, bc_start='not-a-knot', bc_end='not-a-knot',
- tol=1e-14):
- """Check that spline coefficients satisfy the continuity and boundary
- conditions."""
- x = S.x
- c = S.c
- dx = np.diff(x)
- dx = dx.reshape([dx.shape[0]] + [1] * (c.ndim - 2))
- dxi = dx[:-1]
- # Check C2 continuity.
- xp_assert_close(c[3, 1:], c[0, :-1] * dxi**3 + c[1, :-1] * dxi**2 +
- c[2, :-1] * dxi + c[3, :-1], rtol=tol, atol=tol)
- xp_assert_close(c[2, 1:], 3 * c[0, :-1] * dxi**2 +
- 2 * c[1, :-1] * dxi + c[2, :-1], rtol=tol, atol=tol)
- xp_assert_close(c[1, 1:], 3 * c[0, :-1] * dxi + c[1, :-1],
- rtol=tol, atol=tol)
- # Check that we found a parabola, the third derivative is 0.
- if x.size == 3 and bc_start == 'not-a-knot' and bc_end == 'not-a-knot':
- xp_assert_close(c[0], np.zeros_like(c[0]), rtol=tol, atol=tol)
- return
- # Check periodic boundary conditions.
- if bc_start == 'periodic':
- xp_assert_close(S(x[0], 0), S(x[-1], 0), rtol=tol, atol=tol)
- xp_assert_close(S(x[0], 1), S(x[-1], 1), rtol=tol, atol=tol)
- xp_assert_close(S(x[0], 2), S(x[-1], 2), rtol=tol, atol=tol)
- return
- # Check other boundary conditions.
- if bc_start == 'not-a-knot':
- if x.size == 2:
- slope = (S(x[1]) - S(x[0])) / dx[0]
- slope = np.asarray(slope)
- xp_assert_close(S(x[0], 1), slope, rtol=tol, atol=tol)
- else:
- xp_assert_close(c[0, 0], c[0, 1], rtol=tol, atol=tol)
- elif bc_start == 'clamped':
- xp_assert_close(
- S(x[0], 1), np.zeros_like(S(x[0], 1)), rtol=tol, atol=tol)
- elif bc_start == 'natural':
- xp_assert_close(
- S(x[0], 2), np.zeros_like(S(x[0], 2)), rtol=tol, atol=tol)
- else:
- order, value = bc_start
- xp_assert_close(S(x[0], order), np.asarray(value), rtol=tol, atol=tol)
- if bc_end == 'not-a-knot':
- if x.size == 2:
- slope = (S(x[1]) - S(x[0])) / dx[0]
- slope = np.asarray(slope)
- xp_assert_close(S(x[1], 1), slope, rtol=tol, atol=tol)
- else:
- xp_assert_close(c[0, -1], c[0, -2], rtol=tol, atol=tol)
- elif bc_end == 'clamped':
- xp_assert_close(S(x[-1], 1), np.zeros_like(S(x[-1], 1)),
- rtol=tol, atol=tol)
- elif bc_end == 'natural':
- xp_assert_close(S(x[-1], 2), np.zeros_like(S(x[-1], 2)),
- rtol=2*tol, atol=2*tol)
- else:
- order, value = bc_end
- xp_assert_close(S(x[-1], order), np.asarray(value), rtol=tol, atol=tol)
- def check_all_bc(self, x, y, axis):
- deriv_shape = list(y.shape)
- del deriv_shape[axis]
- first_deriv = np.empty(deriv_shape)
- first_deriv.fill(2)
- second_deriv = np.empty(deriv_shape)
- second_deriv.fill(-1)
- bc_all = [
- 'not-a-knot',
- 'natural',
- 'clamped',
- (1, first_deriv),
- (2, second_deriv)
- ]
- for bc in bc_all[:3]:
- S = CubicSpline(x, y, axis=axis, bc_type=bc)
- self.check_correctness(S, bc, bc)
- for bc_start in bc_all:
- for bc_end in bc_all:
- S = CubicSpline(x, y, axis=axis, bc_type=(bc_start, bc_end))
- self.check_correctness(S, bc_start, bc_end, tol=2e-14)
- def test_general(self):
- x = np.array([-1, 0, 0.5, 2, 4, 4.5, 5.5, 9])
- y = np.array([0, -0.5, 2, 3, 2.5, 1, 1, 0.5])
- for n in [2, 3, x.size]:
- self.check_all_bc(x[:n], y[:n], 0)
- Y = np.empty((2, n, 2))
- Y[0, :, 0] = y[:n]
- Y[0, :, 1] = y[:n] - 1
- Y[1, :, 0] = y[:n] + 2
- Y[1, :, 1] = y[:n] + 3
- self.check_all_bc(x[:n], Y, 1)
- def test_periodic(self):
- for n in [2, 3, 5]:
- x = np.linspace(0, 2 * np.pi, n)
- y = np.cos(x)
- S = CubicSpline(x, y, bc_type='periodic')
- self.check_correctness(S, 'periodic', 'periodic')
- Y = np.empty((2, n, 2))
- Y[0, :, 0] = y
- Y[0, :, 1] = y + 2
- Y[1, :, 0] = y - 1
- Y[1, :, 1] = y + 5
- S = CubicSpline(x, Y, axis=1, bc_type='periodic')
- self.check_correctness(S, 'periodic', 'periodic')
- def test_periodic_eval(self, xp):
- x = xp.linspace(0, 2 * xp.pi, 10, dtype=xp.float64)
- y = xp.cos(x)
- S = CubicSpline(x, y, bc_type='periodic')
- assert_almost_equal(S(1), S(1 + 2 * xp.pi), decimal=15)
- S = CubicSpline(x, y)
- assert_almost_equal(S(x), xp.cos(x), decimal=15)
- def test_second_derivative_continuity_gh_11758(self):
- # gh-11758: C2 continuity fail
- x = np.array([0.9, 1.3, 1.9, 2.1, 2.6, 3.0, 3.9, 4.4, 4.7, 5.0, 6.0,
- 7.0, 8.0, 9.2, 10.5, 11.3, 11.6, 12.0, 12.6, 13.0, 13.3])
- y = np.array([1.3, 1.5, 1.85, 2.1, 2.6, 2.7, 2.4, 2.15, 2.05, 2.1,
- 2.25, 2.3, 2.25, 1.95, 1.4, 0.9, 0.7, 0.6, 0.5, 0.4, 1.3])
- S = CubicSpline(x, y, bc_type='periodic', extrapolate='periodic')
- self.check_correctness(S, 'periodic', 'periodic')
- def test_three_points(self):
- # gh-11758: Fails computing a_m2_m1
- # In this case, s (first derivatives) could be found manually by solving
- # system of 2 linear equations. Due to solution of this system,
- # s[i] = (h1m2 + h2m1) / (h1 + h2), where h1 = x[1] - x[0], h2 = x[2] - x[1],
- # m1 = (y[1] - y[0]) / h1, m2 = (y[2] - y[1]) / h2
- x = np.array([1.0, 2.75, 3.0])
- y = np.array([1.0, 15.0, 1.0])
- S = CubicSpline(x, y, bc_type='periodic')
- self.check_correctness(S, 'periodic', 'periodic')
- xp_assert_close(S.derivative(1)(x), np.array([-48.0, -48.0, -48.0]))
- def test_periodic_three_points_multidim(self):
- # make sure one multidimensional interpolator does the same as multiple
- # one-dimensional interpolators
- x = np.array([0.0, 1.0, 3.0])
- y = np.array([[0.0, 1.0], [1.0, 0.0], [0.0, 1.0]])
- S = CubicSpline(x, y, bc_type="periodic")
- self.check_correctness(S, 'periodic', 'periodic')
- S0 = CubicSpline(x, y[:, 0], bc_type="periodic")
- S1 = CubicSpline(x, y[:, 1], bc_type="periodic")
- q = np.linspace(0, 2, 5)
- xp_assert_close(S(q)[:, 0], S0(q))
- xp_assert_close(S(q)[:, 1], S1(q))
- def test_dtypes(self):
- x = np.array([0, 1, 2, 3], dtype=int)
- y = np.array([-5, 2, 3, 1], dtype=int)
- S = CubicSpline(x, y)
- self.check_correctness(S)
- y = np.array([-1+1j, 0.0, 1-1j, 0.5-1.5j])
- S = CubicSpline(x, y)
- self.check_correctness(S)
- S = CubicSpline(x, x ** 3, bc_type=("natural", (1, 2j)))
- self.check_correctness(S, "natural", (1, 2j))
- y = np.array([-5, 2, 3, 1])
- S = CubicSpline(x, y, bc_type=[(1, 2 + 0.5j), (2, 0.5 - 1j)])
- self.check_correctness(S, (1, 2 + 0.5j), (2, 0.5 - 1j))
- def test_small_dx(self):
- rng = np.random.RandomState(0)
- x = np.sort(rng.uniform(size=100))
- y = 1e4 + rng.uniform(size=100)
- S = CubicSpline(x, y)
- self.check_correctness(S, tol=1e-13)
- def test_incorrect_inputs(self):
- x = np.array([1, 2, 3, 4])
- y = np.array([1, 2, 3, 4])
- xc = np.array([1 + 1j, 2, 3, 4])
- xn = np.array([np.nan, 2, 3, 4])
- xo = np.array([2, 1, 3, 4])
- yn = np.array([np.nan, 2, 3, 4])
- y3 = [1, 2, 3]
- x1 = [1]
- y1 = [1]
- assert_raises(ValueError, CubicSpline, xc, y)
- assert_raises(ValueError, CubicSpline, xn, y)
- assert_raises(ValueError, CubicSpline, x, yn)
- assert_raises(ValueError, CubicSpline, xo, y)
- assert_raises(ValueError, CubicSpline, x, y3)
- assert_raises(ValueError, CubicSpline, x[:, np.newaxis], y)
- assert_raises(ValueError, CubicSpline, x1, y1)
- wrong_bc = [('periodic', 'clamped'),
- ((2, 0), (3, 10)),
- ((1, 0), ),
- (0., 0.),
- 'not-a-typo']
- for bc_type in wrong_bc:
- assert_raises(ValueError, CubicSpline, x, y, 0, bc_type, True)
- # Shapes mismatch when giving arbitrary derivative values:
- Y = np.c_[y, y]
- bc1 = ('clamped', (1, 0))
- bc2 = ('clamped', (1, [0, 0, 0]))
- bc3 = ('clamped', (1, [[0, 0]]))
- assert_raises(ValueError, CubicSpline, x, Y, 0, bc1, True)
- assert_raises(ValueError, CubicSpline, x, Y, 0, bc2, True)
- assert_raises(ValueError, CubicSpline, x, Y, 0, bc3, True)
- # periodic condition, y[-1] must be equal to y[0]:
- assert_raises(ValueError, CubicSpline, x, y, 0, 'periodic', True)
- @make_xp_test_case(CubicHermiteSpline)
- def test_CubicHermiteSpline_correctness(xp):
- x = xp.asarray([0, 2, 7])
- y = xp.asarray([-1, 2, 3])
- dydx = xp.asarray([0, 3, 7])
- s = CubicHermiteSpline(x, y, dydx)
- xp_assert_close(s(x), y, check_shape=False, check_dtype=False, rtol=1e-15)
- xp_assert_close(s(x, 1), dydx, check_shape=False, check_dtype=False, rtol=1e-15)
- def test_CubicHermiteSpline_error_handling():
- x = [1, 2, 3]
- y = [0, 3, 5]
- dydx = [1, -1, 2, 3]
- assert_raises(ValueError, CubicHermiteSpline, x, y, dydx)
- dydx_with_nan = [1, 0, np.nan]
- assert_raises(ValueError, CubicHermiteSpline, x, y, dydx_with_nan)
- def test_roots_extrapolate_gh_11185():
- x = np.array([0.001, 0.002])
- y = np.array([1.66066935e-06, 1.10410807e-06])
- dy = np.array([-1.60061854, -1.600619])
- p = CubicHermiteSpline(x, y, dy)
- # roots(extrapolate=True) for a polynomial with a single interval
- # should return all three real roots
- r = p.roots(extrapolate=True)
- assert p.c.shape[1] == 1
- assert r.size == 3
- class TestZeroSizeArrays:
- # regression tests for gh-17241 : CubicSpline et al must not segfault
- # when y.size == 0
- # The two methods below are _almost_ the same, but not quite:
- # one is for objects which have the `bc_type` argument (CubicSpline)
- # and the other one is for those which do not (Pchip, Akima1D)
- @pytest.mark.parametrize('y', [np.zeros((10, 0, 5)),
- np.zeros((10, 5, 0))])
- @pytest.mark.parametrize('bc_type',
- ['not-a-knot', 'periodic', 'natural', 'clamped'])
- @pytest.mark.parametrize('axis', [0, 1, 2])
- @pytest.mark.parametrize('cls', [make_interp_spline, CubicSpline])
- def test_zero_size(self, cls, y, bc_type, axis):
- x = np.arange(10)
- xval = np.arange(3)
- obj = cls(x, y, bc_type=bc_type)
- assert obj(xval).size == 0
- assert obj(xval).shape == xval.shape + y.shape[1:]
- # Also check with an explicit non-default axis
- yt = np.moveaxis(y, 0, axis) # (10, 0, 5) --> (0, 10, 5) if axis=1 etc
- obj = cls(x, yt, bc_type=bc_type, axis=axis)
- sh = yt.shape[:axis] + (xval.size, ) + yt.shape[axis+1:]
- assert obj(xval).size == 0
- assert obj(xval).shape == sh
- @pytest.mark.parametrize('y', [np.zeros((10, 0, 5)),
- np.zeros((10, 5, 0))])
- @pytest.mark.parametrize('axis', [0, 1, 2])
- @pytest.mark.parametrize('cls', [PchipInterpolator, Akima1DInterpolator])
- def test_zero_size_2(self, cls, y, axis):
- x = np.arange(10)
- xval = np.arange(3)
- obj = cls(x, y)
- assert obj(xval).size == 0
- assert obj(xval).shape == xval.shape + y.shape[1:]
- # Also check with an explicit non-default axis
- yt = np.moveaxis(y, 0, axis) # (10, 0, 5) --> (0, 10, 5) if axis=1 etc
- obj = cls(x, yt, axis=axis)
- sh = yt.shape[:axis] + (xval.size, ) + yt.shape[axis+1:]
- assert obj(xval).size == 0
- assert obj(xval).shape == sh
|