test_polyint.py 37 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976
  1. import warnings
  2. import io
  3. import numpy as np
  4. from scipy._lib._array_api import (
  5. xp_assert_equal, xp_assert_close, assert_array_almost_equal, assert_almost_equal,
  6. make_xp_test_case
  7. )
  8. from pytest import raises as assert_raises
  9. import pytest
  10. from scipy.interpolate import (
  11. KroghInterpolator, krogh_interpolate,
  12. BarycentricInterpolator, barycentric_interpolate,
  13. approximate_taylor_polynomial, CubicHermiteSpline, pchip,
  14. PchipInterpolator, pchip_interpolate, Akima1DInterpolator, CubicSpline,
  15. make_interp_spline)
  16. from scipy._lib._testutils import _run_concurrent_barrier
  17. skip_xp_backends = pytest.mark.skip_xp_backends
  18. xfail_xp_backends = pytest.mark.xfail_xp_backends
  19. def check_shape(interpolator_cls, x_shape, y_shape, deriv_shape=None, axis=0,
  20. extra_args=None):
  21. if extra_args is None:
  22. extra_args = {}
  23. rng = np.random.RandomState(1234)
  24. x = [-1, 0, 1, 2, 3, 4]
  25. s = list(range(1, len(y_shape)+1))
  26. s.insert(axis % (len(y_shape)+1), 0)
  27. y = rng.rand(*((6,) + y_shape)).transpose(s)
  28. xi = np.zeros(x_shape)
  29. if interpolator_cls is CubicHermiteSpline:
  30. dydx = rng.rand(*((6,) + y_shape)).transpose(s)
  31. yi = interpolator_cls(x, y, dydx, axis=axis, **extra_args)(xi)
  32. else:
  33. yi = interpolator_cls(x, y, axis=axis, **extra_args)(xi)
  34. target_shape = ((deriv_shape or ()) + y.shape[:axis]
  35. + x_shape + y.shape[axis:][1:])
  36. assert yi.shape == target_shape
  37. # check it works also with lists
  38. if x_shape and y.size > 0:
  39. if interpolator_cls is CubicHermiteSpline:
  40. interpolator_cls(list(x), list(y), list(dydx), axis=axis,
  41. **extra_args)(list(xi))
  42. else:
  43. interpolator_cls(list(x), list(y), axis=axis,
  44. **extra_args)(list(xi))
  45. # check also values
  46. if xi.size > 0 and deriv_shape is None:
  47. bs_shape = y.shape[:axis] + (1,)*len(x_shape) + y.shape[axis:][1:]
  48. yv = y[((slice(None,),)*(axis % y.ndim)) + (1,)]
  49. yv = yv.reshape(bs_shape)
  50. yi, y = np.broadcast_arrays(yi, yv)
  51. xp_assert_close(yi, y)
  52. SHAPES = [(), (0,), (1,), (6, 2, 5)]
  53. def test_shapes():
  54. def spl_interp(x, y, axis):
  55. return make_interp_spline(x, y, axis=axis)
  56. for ip in [KroghInterpolator, BarycentricInterpolator, CubicHermiteSpline,
  57. pchip, Akima1DInterpolator, CubicSpline, spl_interp]:
  58. for s1 in SHAPES:
  59. for s2 in SHAPES:
  60. for axis in range(-len(s2), len(s2)):
  61. if ip != CubicSpline:
  62. check_shape(ip, s1, s2, None, axis)
  63. else:
  64. for bc in ['natural', 'clamped']:
  65. extra = {'bc_type': bc}
  66. check_shape(ip, s1, s2, None, axis, extra)
  67. def test_derivs_shapes():
  68. for ip in [KroghInterpolator, BarycentricInterpolator]:
  69. def interpolator_derivs(x, y, axis=0):
  70. return ip(x, y, axis).derivatives
  71. for s1 in SHAPES:
  72. for s2 in SHAPES:
  73. for axis in range(-len(s2), len(s2)):
  74. check_shape(interpolator_derivs, s1, s2, (6,), axis)
  75. def test_deriv_shapes():
  76. def krogh_deriv(x, y, axis=0):
  77. return KroghInterpolator(x, y, axis).derivative
  78. def bary_deriv(x, y, axis=0):
  79. return BarycentricInterpolator(x, y, axis).derivative
  80. def pchip_deriv(x, y, axis=0):
  81. return pchip(x, y, axis).derivative()
  82. def pchip_deriv2(x, y, axis=0):
  83. return pchip(x, y, axis).derivative(2)
  84. def pchip_antideriv(x, y, axis=0):
  85. return pchip(x, y, axis).antiderivative()
  86. def pchip_antideriv2(x, y, axis=0):
  87. return pchip(x, y, axis).antiderivative(2)
  88. def pchip_deriv_inplace(x, y, axis=0):
  89. class P(PchipInterpolator):
  90. def __call__(self, x):
  91. return PchipInterpolator.__call__(self, x, 1)
  92. pass
  93. return P(x, y, axis)
  94. def akima_deriv(x, y, axis=0):
  95. return Akima1DInterpolator(x, y, axis).derivative()
  96. def akima_antideriv(x, y, axis=0):
  97. return Akima1DInterpolator(x, y, axis).antiderivative()
  98. def cspline_deriv(x, y, axis=0):
  99. return CubicSpline(x, y, axis).derivative()
  100. def cspline_antideriv(x, y, axis=0):
  101. return CubicSpline(x, y, axis).antiderivative()
  102. def bspl_deriv(x, y, axis=0):
  103. return make_interp_spline(x, y, axis=axis).derivative()
  104. def bspl_antideriv(x, y, axis=0):
  105. return make_interp_spline(x, y, axis=axis).antiderivative()
  106. for ip in [krogh_deriv, bary_deriv, pchip_deriv, pchip_deriv2, pchip_deriv_inplace,
  107. pchip_antideriv, pchip_antideriv2, akima_deriv, akima_antideriv,
  108. cspline_deriv, cspline_antideriv, bspl_deriv, bspl_antideriv]:
  109. for s1 in SHAPES:
  110. for s2 in SHAPES:
  111. for axis in range(-len(s2), len(s2)):
  112. check_shape(ip, s1, s2, (), axis)
  113. def test_complex():
  114. x = [1, 2, 3, 4]
  115. y = [1, 2, 1j, 3]
  116. for ip in [KroghInterpolator, BarycentricInterpolator, CubicSpline]:
  117. p = ip(x, y)
  118. xp_assert_close(p(x), np.asarray(y))
  119. dydx = [0, -1j, 2, 3j]
  120. p = CubicHermiteSpline(x, y, dydx)
  121. xp_assert_close(p(x), np.asarray(y))
  122. xp_assert_close(p(x, 1), np.asarray(dydx))
  123. class TestKrogh:
  124. def setup_method(self):
  125. self.true_poly = np.polynomial.Polynomial([-4, 5, 1, 3, -2])
  126. self.test_xs = np.linspace(-1,1,100)
  127. self.xs = np.linspace(-1,1,5)
  128. self.ys = self.true_poly(self.xs)
  129. def test_lagrange(self):
  130. P = KroghInterpolator(self.xs,self.ys)
  131. assert_almost_equal(self.true_poly(self.test_xs),P(self.test_xs))
  132. def test_scalar(self):
  133. P = KroghInterpolator(self.xs,self.ys)
  134. assert_almost_equal(self.true_poly(7), P(7), check_0d=False)
  135. assert_almost_equal(self.true_poly(np.array(7)), P(np.array(7)), check_0d=False)
  136. def test_derivatives(self):
  137. P = KroghInterpolator(self.xs,self.ys)
  138. D = P.derivatives(self.test_xs)
  139. for i in range(D.shape[0]):
  140. assert_almost_equal(self.true_poly.deriv(i)(self.test_xs),
  141. D[i])
  142. def test_low_derivatives(self):
  143. P = KroghInterpolator(self.xs,self.ys)
  144. D = P.derivatives(self.test_xs,len(self.xs)+2)
  145. for i in range(D.shape[0]):
  146. assert_almost_equal(self.true_poly.deriv(i)(self.test_xs),
  147. D[i])
  148. def test_derivative(self):
  149. P = KroghInterpolator(self.xs,self.ys)
  150. m = 10
  151. r = P.derivatives(self.test_xs,m)
  152. for i in range(m):
  153. assert_almost_equal(P.derivative(self.test_xs,i),r[i])
  154. def test_high_derivative(self):
  155. P = KroghInterpolator(self.xs,self.ys)
  156. for i in range(len(self.xs), 2*len(self.xs)):
  157. assert_almost_equal(P.derivative(self.test_xs,i),
  158. np.zeros(len(self.test_xs)))
  159. def test_ndim_derivatives(self):
  160. poly1 = self.true_poly
  161. poly2 = np.polynomial.Polynomial([-2, 5, 3, -1])
  162. poly3 = np.polynomial.Polynomial([12, -3, 4, -5, 6])
  163. ys = np.stack((poly1(self.xs), poly2(self.xs), poly3(self.xs)), axis=-1)
  164. P = KroghInterpolator(self.xs, ys, axis=0)
  165. D = P.derivatives(self.test_xs)
  166. for i in range(D.shape[0]):
  167. xp_assert_close(D[i],
  168. np.stack((poly1.deriv(i)(self.test_xs),
  169. poly2.deriv(i)(self.test_xs),
  170. poly3.deriv(i)(self.test_xs)),
  171. axis=-1))
  172. def test_ndim_derivative(self):
  173. poly1 = self.true_poly
  174. poly2 = np.polynomial.Polynomial([-2, 5, 3, -1])
  175. poly3 = np.polynomial.Polynomial([12, -3, 4, -5, 6])
  176. ys = np.stack((poly1(self.xs), poly2(self.xs), poly3(self.xs)), axis=-1)
  177. P = KroghInterpolator(self.xs, ys, axis=0)
  178. for i in range(P.n):
  179. xp_assert_close(P.derivative(self.test_xs, i),
  180. np.stack((poly1.deriv(i)(self.test_xs),
  181. poly2.deriv(i)(self.test_xs),
  182. poly3.deriv(i)(self.test_xs)),
  183. axis=-1))
  184. def test_hermite(self):
  185. P = KroghInterpolator(self.xs,self.ys)
  186. assert_almost_equal(self.true_poly(self.test_xs),P(self.test_xs))
  187. def test_vector(self):
  188. xs = [0, 1, 2]
  189. ys = np.array([[0,1],[1,0],[2,1]])
  190. P = KroghInterpolator(xs,ys)
  191. Pi = [KroghInterpolator(xs,ys[:,i]) for i in range(ys.shape[1])]
  192. test_xs = np.linspace(-1,3,100)
  193. assert_almost_equal(P(test_xs),
  194. np.asarray([p(test_xs) for p in Pi]).T)
  195. assert_almost_equal(P.derivatives(test_xs),
  196. np.transpose(np.asarray([p.derivatives(test_xs) for p in Pi]),
  197. (1,2,0)))
  198. def test_empty(self):
  199. P = KroghInterpolator(self.xs,self.ys)
  200. xp_assert_equal(P([]), np.asarray([]))
  201. def test_shapes_scalarvalue(self):
  202. P = KroghInterpolator(self.xs,self.ys)
  203. assert np.shape(P(0)) == ()
  204. assert np.shape(P(np.array(0))) == ()
  205. assert np.shape(P([0])) == (1,)
  206. assert np.shape(P([0,1])) == (2,)
  207. def test_shapes_scalarvalue_derivative(self):
  208. P = KroghInterpolator(self.xs,self.ys)
  209. n = P.n
  210. assert np.shape(P.derivatives(0)) == (n,)
  211. assert np.shape(P.derivatives(np.array(0))) == (n,)
  212. assert np.shape(P.derivatives([0])) == (n, 1)
  213. assert np.shape(P.derivatives([0, 1])) == (n, 2)
  214. def test_shapes_vectorvalue(self):
  215. P = KroghInterpolator(self.xs,np.outer(self.ys,np.arange(3)))
  216. assert np.shape(P(0)) == (3,)
  217. assert np.shape(P([0])) == (1, 3)
  218. assert np.shape(P([0, 1])) == (2, 3)
  219. def test_shapes_1d_vectorvalue(self):
  220. P = KroghInterpolator(self.xs,np.outer(self.ys,[1]))
  221. assert np.shape(P(0)) == (1,)
  222. assert np.shape(P([0])) == (1, 1)
  223. assert np.shape(P([0,1])) == (2, 1)
  224. def test_shapes_vectorvalue_derivative(self):
  225. P = KroghInterpolator(self.xs,np.outer(self.ys,np.arange(3)))
  226. n = P.n
  227. assert np.shape(P.derivatives(0)) == (n, 3)
  228. assert np.shape(P.derivatives([0])) == (n, 1, 3)
  229. assert np.shape(P.derivatives([0,1])) == (n, 2, 3)
  230. def test_wrapper(self):
  231. P = KroghInterpolator(self.xs, self.ys)
  232. ki = krogh_interpolate
  233. assert_almost_equal(P(self.test_xs), ki(self.xs, self.ys, self.test_xs))
  234. assert_almost_equal(P.derivative(self.test_xs, 2),
  235. ki(self.xs, self.ys, self.test_xs, der=2))
  236. assert_almost_equal(P.derivatives(self.test_xs, 2),
  237. ki(self.xs, self.ys, self.test_xs, der=[0, 1]))
  238. def test_int_inputs(self):
  239. # Check input args are cast correctly to floats, gh-3669
  240. x = [0, 234, 468, 702, 936, 1170, 1404, 2340, 3744, 6084, 8424,
  241. 13104, 60000]
  242. offset_cdf = np.array([-0.95, -0.86114777, -0.8147762, -0.64072425,
  243. -0.48002351, -0.34925329, -0.26503107,
  244. -0.13148093, -0.12988833, -0.12979296,
  245. -0.12973574, -0.08582937, 0.05])
  246. f = KroghInterpolator(x, offset_cdf)
  247. xp_assert_close(abs((f(x) - offset_cdf) / f.derivative(x, 1)),
  248. np.zeros_like(offset_cdf), atol=1e-10)
  249. def test_derivatives_complex(self):
  250. # regression test for gh-7381: krogh.derivatives(0) fails complex y
  251. x, y = np.array([-1, -1, 0, 1, 1]), np.array([1, 1.0j, 0, -1, 1.0j])
  252. func = KroghInterpolator(x, y)
  253. cmplx = func.derivatives(0)
  254. cmplx2 = (KroghInterpolator(x, y.real).derivatives(0) +
  255. 1j*KroghInterpolator(x, y.imag).derivatives(0))
  256. xp_assert_close(cmplx, cmplx2, atol=1e-15)
  257. def test_high_degree_warning(self):
  258. with pytest.warns(UserWarning, match="40 degrees provided,"):
  259. KroghInterpolator(np.arange(40), np.ones(40))
  260. def test_concurrency(self):
  261. P = KroghInterpolator(self.xs, self.ys)
  262. def worker_fn(_, interp):
  263. interp(self.xs)
  264. _run_concurrent_barrier(10, worker_fn, P)
  265. class TestTaylor:
  266. def test_exponential(self):
  267. degree = 5
  268. p = approximate_taylor_polynomial(np.exp, 0, degree, 1, 15)
  269. for i in range(degree+1):
  270. assert_almost_equal(p(0),1)
  271. p = p.deriv()
  272. assert_almost_equal(p(0),0)
  273. class TestBarycentric:
  274. def setup_method(self):
  275. self.true_poly = np.polynomial.Polynomial([-4, 5, 1, 3, -2])
  276. self.test_xs = np.linspace(-1, 1, 100)
  277. self.xs = np.linspace(-1, 1, 5)
  278. self.ys = self.true_poly(self.xs)
  279. def test_lagrange(self):
  280. # Ensure backwards compatible post SPEC7
  281. P = BarycentricInterpolator(self.xs, self.ys, random_state=1)
  282. xp_assert_close(P(self.test_xs), self.true_poly(self.test_xs))
  283. def test_scalar(self):
  284. P = BarycentricInterpolator(self.xs, self.ys, rng=1)
  285. xp_assert_close(P(7), self.true_poly(7), check_0d=False)
  286. xp_assert_close(P(np.array(7)), self.true_poly(np.array(7)), check_0d=False)
  287. def test_derivatives(self):
  288. P = BarycentricInterpolator(self.xs, self.ys)
  289. D = P.derivatives(self.test_xs)
  290. for i in range(D.shape[0]):
  291. xp_assert_close(self.true_poly.deriv(i)(self.test_xs), D[i])
  292. def test_low_derivatives(self):
  293. P = BarycentricInterpolator(self.xs, self.ys)
  294. D = P.derivatives(self.test_xs, len(self.xs)+2)
  295. for i in range(D.shape[0]):
  296. xp_assert_close(self.true_poly.deriv(i)(self.test_xs),
  297. D[i],
  298. atol=1e-12)
  299. def test_derivative(self):
  300. P = BarycentricInterpolator(self.xs, self.ys)
  301. m = 10
  302. r = P.derivatives(self.test_xs, m)
  303. for i in range(m):
  304. xp_assert_close(P.derivative(self.test_xs, i), r[i])
  305. def test_high_derivative(self):
  306. P = BarycentricInterpolator(self.xs, self.ys)
  307. for i in range(len(self.xs), 5*len(self.xs)):
  308. xp_assert_close(P.derivative(self.test_xs, i),
  309. np.zeros(len(self.test_xs)))
  310. def test_ndim_derivatives(self):
  311. poly1 = self.true_poly
  312. poly2 = np.polynomial.Polynomial([-2, 5, 3, -1])
  313. poly3 = np.polynomial.Polynomial([12, -3, 4, -5, 6])
  314. ys = np.stack((poly1(self.xs), poly2(self.xs), poly3(self.xs)), axis=-1)
  315. P = BarycentricInterpolator(self.xs, ys, axis=0)
  316. D = P.derivatives(self.test_xs)
  317. for i in range(D.shape[0]):
  318. xp_assert_close(D[i],
  319. np.stack((poly1.deriv(i)(self.test_xs),
  320. poly2.deriv(i)(self.test_xs),
  321. poly3.deriv(i)(self.test_xs)),
  322. axis=-1),
  323. atol=1e-12)
  324. def test_ndim_derivative(self):
  325. poly1 = self.true_poly
  326. poly2 = np.polynomial.Polynomial([-2, 5, 3, -1])
  327. poly3 = np.polynomial.Polynomial([12, -3, 4, -5, 6])
  328. ys = np.stack((poly1(self.xs), poly2(self.xs), poly3(self.xs)), axis=-1)
  329. P = BarycentricInterpolator(self.xs, ys, axis=0)
  330. for i in range(P.n):
  331. xp_assert_close(P.derivative(self.test_xs, i),
  332. np.stack((poly1.deriv(i)(self.test_xs),
  333. poly2.deriv(i)(self.test_xs),
  334. poly3.deriv(i)(self.test_xs)),
  335. axis=-1),
  336. atol=1e-12)
  337. def test_delayed(self):
  338. P = BarycentricInterpolator(self.xs)
  339. P.set_yi(self.ys)
  340. assert_almost_equal(self.true_poly(self.test_xs), P(self.test_xs))
  341. def test_append(self):
  342. P = BarycentricInterpolator(self.xs[:3], self.ys[:3])
  343. P.add_xi(self.xs[3:], self.ys[3:])
  344. assert_almost_equal(self.true_poly(self.test_xs), P(self.test_xs))
  345. def test_vector(self):
  346. xs = [0, 1, 2]
  347. ys = np.array([[0, 1], [1, 0], [2, 1]])
  348. BI = BarycentricInterpolator
  349. P = BI(xs, ys)
  350. Pi = [BI(xs, ys[:, i]) for i in range(ys.shape[1])]
  351. test_xs = np.linspace(-1, 3, 100)
  352. assert_almost_equal(P(test_xs),
  353. np.asarray([p(test_xs) for p in Pi]).T)
  354. def test_shapes_scalarvalue(self):
  355. P = BarycentricInterpolator(self.xs, self.ys)
  356. assert np.shape(P(0)) == ()
  357. assert np.shape(P(np.array(0))) == ()
  358. assert np.shape(P([0])) == (1,)
  359. assert np.shape(P([0, 1])) == (2,)
  360. def test_shapes_scalarvalue_derivative(self):
  361. P = BarycentricInterpolator(self.xs,self.ys)
  362. n = P.n
  363. assert np.shape(P.derivatives(0)) == (n,)
  364. assert np.shape(P.derivatives(np.array(0))) == (n,)
  365. assert np.shape(P.derivatives([0])) == (n,1)
  366. assert np.shape(P.derivatives([0,1])) == (n,2)
  367. def test_shapes_vectorvalue(self):
  368. P = BarycentricInterpolator(self.xs, np.outer(self.ys, np.arange(3)))
  369. assert np.shape(P(0)) == (3,)
  370. assert np.shape(P([0])) == (1, 3)
  371. assert np.shape(P([0, 1])) == (2, 3)
  372. def test_shapes_1d_vectorvalue(self):
  373. P = BarycentricInterpolator(self.xs, np.outer(self.ys, [1]))
  374. assert np.shape(P(0)) == (1,)
  375. assert np.shape(P([0])) == (1, 1)
  376. assert np.shape(P([0, 1])) == (2, 1)
  377. def test_shapes_vectorvalue_derivative(self):
  378. P = BarycentricInterpolator(self.xs,np.outer(self.ys,np.arange(3)))
  379. n = P.n
  380. assert np.shape(P.derivatives(0)) == (n, 3)
  381. assert np.shape(P.derivatives([0])) == (n, 1, 3)
  382. assert np.shape(P.derivatives([0, 1])) == (n, 2, 3)
  383. def test_wrapper(self):
  384. P = BarycentricInterpolator(self.xs, self.ys, rng=1)
  385. bi = barycentric_interpolate
  386. xp_assert_close(P(self.test_xs), bi(self.xs, self.ys, self.test_xs, rng=1))
  387. xp_assert_close(P.derivative(self.test_xs, 2),
  388. bi(self.xs, self.ys, self.test_xs, der=2, rng=1))
  389. xp_assert_close(P.derivatives(self.test_xs, 2),
  390. bi(self.xs, self.ys, self.test_xs, der=[0, 1], rng=1))
  391. def test_int_input(self):
  392. x = 1000 * np.arange(1, 11) # np.prod(x[-1] - x[:-1]) overflows
  393. y = np.arange(1, 11)
  394. value = barycentric_interpolate(x, y, 1000 * 9.5)
  395. assert_almost_equal(value, np.asarray(9.5))
  396. def test_large_chebyshev(self):
  397. # The weights for Chebyshev points of the second kind have analytically
  398. # solvable weights. Naive calculation of barycentric weights will fail
  399. # for large N because of numerical underflow and overflow. We test
  400. # correctness for large N against analytical Chebyshev weights.
  401. # Without capacity scaling or permutation, n=800 fails,
  402. # With just capacity scaling, n=1097 fails
  403. # With both capacity scaling and random permutation, n=30000 succeeds
  404. n = 1100
  405. j = np.arange(n + 1).astype(np.float64)
  406. x = np.cos(j * np.pi / n)
  407. # See page 506 of Berrut and Trefethen 2004 for this formula
  408. w = (-1) ** j
  409. w[0] *= 0.5
  410. w[-1] *= 0.5
  411. P = BarycentricInterpolator(x)
  412. # It's okay to have a constant scaling factor in the weights because it
  413. # cancels out in the evaluation of the polynomial.
  414. factor = P.wi[0]
  415. assert_almost_equal(P.wi / (2 * factor), w)
  416. def test_warning(self):
  417. # Test if the divide-by-zero warning is properly ignored when computing
  418. # interpolated values equals to interpolation points
  419. P = BarycentricInterpolator([0, 1], [1, 2])
  420. with np.errstate(divide='raise'):
  421. yi = P(P.xi)
  422. # Check if the interpolated values match the input values
  423. # at the nodes
  424. assert_almost_equal(yi, P.yi.ravel())
  425. def test_repeated_node(self):
  426. # check that a repeated node raises a ValueError
  427. # (computing the weights requires division by xi[i] - xi[j])
  428. xis = np.array([0.1, 0.5, 0.9, 0.5])
  429. ys = np.array([1, 2, 3, 4])
  430. with pytest.raises(ValueError,
  431. match="Interpolation points xi must be distinct."):
  432. BarycentricInterpolator(xis, ys)
  433. def test_concurrency(self):
  434. P = BarycentricInterpolator(self.xs, self.ys)
  435. def worker_fn(_, interp):
  436. interp(self.xs)
  437. _run_concurrent_barrier(10, worker_fn, P)
  438. class TestPCHIP:
  439. def _make_random(self, npts=20):
  440. rng = np.random.RandomState(1234)
  441. xi = np.sort(rng.random(npts))
  442. yi = rng.random(npts)
  443. return pchip(xi, yi), xi, yi
  444. def test_overshoot(self):
  445. # PCHIP should not overshoot
  446. p, xi, yi = self._make_random()
  447. for i in range(len(xi)-1):
  448. x1, x2 = xi[i], xi[i+1]
  449. y1, y2 = yi[i], yi[i+1]
  450. if y1 > y2:
  451. y1, y2 = y2, y1
  452. xp = np.linspace(x1, x2, 10)
  453. yp = p(xp)
  454. assert ((y1 <= yp + 1e-15) & (yp <= y2 + 1e-15)).all()
  455. def test_monotone(self):
  456. # PCHIP should preserve monotonicty
  457. p, xi, yi = self._make_random()
  458. for i in range(len(xi)-1):
  459. x1, x2 = xi[i], xi[i+1]
  460. y1, y2 = yi[i], yi[i+1]
  461. xp = np.linspace(x1, x2, 10)
  462. yp = p(xp)
  463. assert ((y2-y1) * (yp[1:] - yp[:1]) > 0).all()
  464. def test_cast(self):
  465. # regression test for integer input data, see gh-3453
  466. data = np.array([[0, 4, 12, 27, 47, 60, 79, 87, 99, 100],
  467. [-33, -33, -19, -2, 12, 26, 38, 45, 53, 55]])
  468. xx = np.arange(100)
  469. curve = pchip(data[0], data[1])(xx)
  470. data1 = data * 1.0
  471. curve1 = pchip(data1[0], data1[1])(xx)
  472. xp_assert_close(curve, curve1, atol=1e-14, rtol=1e-14)
  473. def test_nag(self):
  474. # Example from NAG C implementation,
  475. # http://nag.com/numeric/cl/nagdoc_cl25/html/e01/e01bec.html
  476. # suggested in gh-5326 as a smoke test for the way the derivatives
  477. # are computed (see also gh-3453)
  478. dataStr = '''
  479. 7.99 0.00000E+0
  480. 8.09 0.27643E-4
  481. 8.19 0.43750E-1
  482. 8.70 0.16918E+0
  483. 9.20 0.46943E+0
  484. 10.00 0.94374E+0
  485. 12.00 0.99864E+0
  486. 15.00 0.99992E+0
  487. 20.00 0.99999E+0
  488. '''
  489. data = np.loadtxt(io.StringIO(dataStr))
  490. pch = pchip(data[:,0], data[:,1])
  491. resultStr = '''
  492. 7.9900 0.0000
  493. 9.1910 0.4640
  494. 10.3920 0.9645
  495. 11.5930 0.9965
  496. 12.7940 0.9992
  497. 13.9950 0.9998
  498. 15.1960 0.9999
  499. 16.3970 1.0000
  500. 17.5980 1.0000
  501. 18.7990 1.0000
  502. 20.0000 1.0000
  503. '''
  504. result = np.loadtxt(io.StringIO(resultStr))
  505. xp_assert_close(result[:,1], pch(result[:,0]), rtol=0., atol=5e-5)
  506. def test_endslopes(self):
  507. # this is a smoke test for gh-3453: PCHIP interpolator should not
  508. # set edge slopes to zero if the data do not suggest zero edge derivatives
  509. x = np.array([0.0, 0.1, 0.25, 0.35])
  510. y1 = np.array([279.35, 0.5e3, 1.0e3, 2.5e3])
  511. y2 = np.array([279.35, 2.5e3, 1.50e3, 1.0e3])
  512. for pp in (pchip(x, y1), pchip(x, y2)):
  513. for t in (x[0], x[-1]):
  514. assert pp(t, 1) != 0
  515. def test_all_zeros(self):
  516. x = np.arange(10)
  517. y = np.zeros_like(x)
  518. # this should work and not generate any warnings
  519. with warnings.catch_warnings():
  520. warnings.filterwarnings('error')
  521. pch = pchip(x, y)
  522. xx = np.linspace(0, 9, 101)
  523. assert all(pch(xx) == 0.)
  524. def test_two_points(self):
  525. # regression test for gh-6222: pchip([0, 1], [0, 1]) fails because
  526. # it tries to use a three-point scheme to estimate edge derivatives,
  527. # while there are only two points available.
  528. # Instead, it should construct a linear interpolator.
  529. x = np.linspace(0, 1, 11)
  530. p = pchip([0, 1], [0, 2])
  531. xp_assert_close(p(x), 2*x, atol=1e-15)
  532. def test_pchip_interpolate(self):
  533. assert_array_almost_equal(
  534. pchip_interpolate([1, 2, 3], [4, 5, 6], [0.5], der=1),
  535. np.asarray([1.]))
  536. assert_array_almost_equal(
  537. pchip_interpolate([1, 2, 3], [4, 5, 6], [0.5], der=0),
  538. np.asarray([3.5]))
  539. assert_array_almost_equal(
  540. np.asarray(pchip_interpolate([1, 2, 3], [4, 5, 6], [0.5], der=[0, 1])),
  541. np.asarray([[3.5], [1]]))
  542. def test_roots(self):
  543. # regression test for gh-6357: .roots method should work
  544. p = pchip([0, 1], [-1, 1])
  545. r = p.roots()
  546. xp_assert_close(r, np.asarray([0.5]))
  547. @make_xp_test_case(CubicSpline)
  548. class TestCubicSpline:
  549. @staticmethod
  550. def check_correctness(S, bc_start='not-a-knot', bc_end='not-a-knot',
  551. tol=1e-14):
  552. """Check that spline coefficients satisfy the continuity and boundary
  553. conditions."""
  554. x = S.x
  555. c = S.c
  556. dx = np.diff(x)
  557. dx = dx.reshape([dx.shape[0]] + [1] * (c.ndim - 2))
  558. dxi = dx[:-1]
  559. # Check C2 continuity.
  560. xp_assert_close(c[3, 1:], c[0, :-1] * dxi**3 + c[1, :-1] * dxi**2 +
  561. c[2, :-1] * dxi + c[3, :-1], rtol=tol, atol=tol)
  562. xp_assert_close(c[2, 1:], 3 * c[0, :-1] * dxi**2 +
  563. 2 * c[1, :-1] * dxi + c[2, :-1], rtol=tol, atol=tol)
  564. xp_assert_close(c[1, 1:], 3 * c[0, :-1] * dxi + c[1, :-1],
  565. rtol=tol, atol=tol)
  566. # Check that we found a parabola, the third derivative is 0.
  567. if x.size == 3 and bc_start == 'not-a-knot' and bc_end == 'not-a-knot':
  568. xp_assert_close(c[0], np.zeros_like(c[0]), rtol=tol, atol=tol)
  569. return
  570. # Check periodic boundary conditions.
  571. if bc_start == 'periodic':
  572. xp_assert_close(S(x[0], 0), S(x[-1], 0), rtol=tol, atol=tol)
  573. xp_assert_close(S(x[0], 1), S(x[-1], 1), rtol=tol, atol=tol)
  574. xp_assert_close(S(x[0], 2), S(x[-1], 2), rtol=tol, atol=tol)
  575. return
  576. # Check other boundary conditions.
  577. if bc_start == 'not-a-knot':
  578. if x.size == 2:
  579. slope = (S(x[1]) - S(x[0])) / dx[0]
  580. slope = np.asarray(slope)
  581. xp_assert_close(S(x[0], 1), slope, rtol=tol, atol=tol)
  582. else:
  583. xp_assert_close(c[0, 0], c[0, 1], rtol=tol, atol=tol)
  584. elif bc_start == 'clamped':
  585. xp_assert_close(
  586. S(x[0], 1), np.zeros_like(S(x[0], 1)), rtol=tol, atol=tol)
  587. elif bc_start == 'natural':
  588. xp_assert_close(
  589. S(x[0], 2), np.zeros_like(S(x[0], 2)), rtol=tol, atol=tol)
  590. else:
  591. order, value = bc_start
  592. xp_assert_close(S(x[0], order), np.asarray(value), rtol=tol, atol=tol)
  593. if bc_end == 'not-a-knot':
  594. if x.size == 2:
  595. slope = (S(x[1]) - S(x[0])) / dx[0]
  596. slope = np.asarray(slope)
  597. xp_assert_close(S(x[1], 1), slope, rtol=tol, atol=tol)
  598. else:
  599. xp_assert_close(c[0, -1], c[0, -2], rtol=tol, atol=tol)
  600. elif bc_end == 'clamped':
  601. xp_assert_close(S(x[-1], 1), np.zeros_like(S(x[-1], 1)),
  602. rtol=tol, atol=tol)
  603. elif bc_end == 'natural':
  604. xp_assert_close(S(x[-1], 2), np.zeros_like(S(x[-1], 2)),
  605. rtol=2*tol, atol=2*tol)
  606. else:
  607. order, value = bc_end
  608. xp_assert_close(S(x[-1], order), np.asarray(value), rtol=tol, atol=tol)
  609. def check_all_bc(self, x, y, axis):
  610. deriv_shape = list(y.shape)
  611. del deriv_shape[axis]
  612. first_deriv = np.empty(deriv_shape)
  613. first_deriv.fill(2)
  614. second_deriv = np.empty(deriv_shape)
  615. second_deriv.fill(-1)
  616. bc_all = [
  617. 'not-a-knot',
  618. 'natural',
  619. 'clamped',
  620. (1, first_deriv),
  621. (2, second_deriv)
  622. ]
  623. for bc in bc_all[:3]:
  624. S = CubicSpline(x, y, axis=axis, bc_type=bc)
  625. self.check_correctness(S, bc, bc)
  626. for bc_start in bc_all:
  627. for bc_end in bc_all:
  628. S = CubicSpline(x, y, axis=axis, bc_type=(bc_start, bc_end))
  629. self.check_correctness(S, bc_start, bc_end, tol=2e-14)
  630. def test_general(self):
  631. x = np.array([-1, 0, 0.5, 2, 4, 4.5, 5.5, 9])
  632. y = np.array([0, -0.5, 2, 3, 2.5, 1, 1, 0.5])
  633. for n in [2, 3, x.size]:
  634. self.check_all_bc(x[:n], y[:n], 0)
  635. Y = np.empty((2, n, 2))
  636. Y[0, :, 0] = y[:n]
  637. Y[0, :, 1] = y[:n] - 1
  638. Y[1, :, 0] = y[:n] + 2
  639. Y[1, :, 1] = y[:n] + 3
  640. self.check_all_bc(x[:n], Y, 1)
  641. def test_periodic(self):
  642. for n in [2, 3, 5]:
  643. x = np.linspace(0, 2 * np.pi, n)
  644. y = np.cos(x)
  645. S = CubicSpline(x, y, bc_type='periodic')
  646. self.check_correctness(S, 'periodic', 'periodic')
  647. Y = np.empty((2, n, 2))
  648. Y[0, :, 0] = y
  649. Y[0, :, 1] = y + 2
  650. Y[1, :, 0] = y - 1
  651. Y[1, :, 1] = y + 5
  652. S = CubicSpline(x, Y, axis=1, bc_type='periodic')
  653. self.check_correctness(S, 'periodic', 'periodic')
  654. def test_periodic_eval(self, xp):
  655. x = xp.linspace(0, 2 * xp.pi, 10, dtype=xp.float64)
  656. y = xp.cos(x)
  657. S = CubicSpline(x, y, bc_type='periodic')
  658. assert_almost_equal(S(1), S(1 + 2 * xp.pi), decimal=15)
  659. S = CubicSpline(x, y)
  660. assert_almost_equal(S(x), xp.cos(x), decimal=15)
  661. def test_second_derivative_continuity_gh_11758(self):
  662. # gh-11758: C2 continuity fail
  663. 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,
  664. 7.0, 8.0, 9.2, 10.5, 11.3, 11.6, 12.0, 12.6, 13.0, 13.3])
  665. y = np.array([1.3, 1.5, 1.85, 2.1, 2.6, 2.7, 2.4, 2.15, 2.05, 2.1,
  666. 2.25, 2.3, 2.25, 1.95, 1.4, 0.9, 0.7, 0.6, 0.5, 0.4, 1.3])
  667. S = CubicSpline(x, y, bc_type='periodic', extrapolate='periodic')
  668. self.check_correctness(S, 'periodic', 'periodic')
  669. def test_three_points(self):
  670. # gh-11758: Fails computing a_m2_m1
  671. # In this case, s (first derivatives) could be found manually by solving
  672. # system of 2 linear equations. Due to solution of this system,
  673. # s[i] = (h1m2 + h2m1) / (h1 + h2), where h1 = x[1] - x[0], h2 = x[2] - x[1],
  674. # m1 = (y[1] - y[0]) / h1, m2 = (y[2] - y[1]) / h2
  675. x = np.array([1.0, 2.75, 3.0])
  676. y = np.array([1.0, 15.0, 1.0])
  677. S = CubicSpline(x, y, bc_type='periodic')
  678. self.check_correctness(S, 'periodic', 'periodic')
  679. xp_assert_close(S.derivative(1)(x), np.array([-48.0, -48.0, -48.0]))
  680. def test_periodic_three_points_multidim(self):
  681. # make sure one multidimensional interpolator does the same as multiple
  682. # one-dimensional interpolators
  683. x = np.array([0.0, 1.0, 3.0])
  684. y = np.array([[0.0, 1.0], [1.0, 0.0], [0.0, 1.0]])
  685. S = CubicSpline(x, y, bc_type="periodic")
  686. self.check_correctness(S, 'periodic', 'periodic')
  687. S0 = CubicSpline(x, y[:, 0], bc_type="periodic")
  688. S1 = CubicSpline(x, y[:, 1], bc_type="periodic")
  689. q = np.linspace(0, 2, 5)
  690. xp_assert_close(S(q)[:, 0], S0(q))
  691. xp_assert_close(S(q)[:, 1], S1(q))
  692. def test_dtypes(self):
  693. x = np.array([0, 1, 2, 3], dtype=int)
  694. y = np.array([-5, 2, 3, 1], dtype=int)
  695. S = CubicSpline(x, y)
  696. self.check_correctness(S)
  697. y = np.array([-1+1j, 0.0, 1-1j, 0.5-1.5j])
  698. S = CubicSpline(x, y)
  699. self.check_correctness(S)
  700. S = CubicSpline(x, x ** 3, bc_type=("natural", (1, 2j)))
  701. self.check_correctness(S, "natural", (1, 2j))
  702. y = np.array([-5, 2, 3, 1])
  703. S = CubicSpline(x, y, bc_type=[(1, 2 + 0.5j), (2, 0.5 - 1j)])
  704. self.check_correctness(S, (1, 2 + 0.5j), (2, 0.5 - 1j))
  705. def test_small_dx(self):
  706. rng = np.random.RandomState(0)
  707. x = np.sort(rng.uniform(size=100))
  708. y = 1e4 + rng.uniform(size=100)
  709. S = CubicSpline(x, y)
  710. self.check_correctness(S, tol=1e-13)
  711. def test_incorrect_inputs(self):
  712. x = np.array([1, 2, 3, 4])
  713. y = np.array([1, 2, 3, 4])
  714. xc = np.array([1 + 1j, 2, 3, 4])
  715. xn = np.array([np.nan, 2, 3, 4])
  716. xo = np.array([2, 1, 3, 4])
  717. yn = np.array([np.nan, 2, 3, 4])
  718. y3 = [1, 2, 3]
  719. x1 = [1]
  720. y1 = [1]
  721. assert_raises(ValueError, CubicSpline, xc, y)
  722. assert_raises(ValueError, CubicSpline, xn, y)
  723. assert_raises(ValueError, CubicSpline, x, yn)
  724. assert_raises(ValueError, CubicSpline, xo, y)
  725. assert_raises(ValueError, CubicSpline, x, y3)
  726. assert_raises(ValueError, CubicSpline, x[:, np.newaxis], y)
  727. assert_raises(ValueError, CubicSpline, x1, y1)
  728. wrong_bc = [('periodic', 'clamped'),
  729. ((2, 0), (3, 10)),
  730. ((1, 0), ),
  731. (0., 0.),
  732. 'not-a-typo']
  733. for bc_type in wrong_bc:
  734. assert_raises(ValueError, CubicSpline, x, y, 0, bc_type, True)
  735. # Shapes mismatch when giving arbitrary derivative values:
  736. Y = np.c_[y, y]
  737. bc1 = ('clamped', (1, 0))
  738. bc2 = ('clamped', (1, [0, 0, 0]))
  739. bc3 = ('clamped', (1, [[0, 0]]))
  740. assert_raises(ValueError, CubicSpline, x, Y, 0, bc1, True)
  741. assert_raises(ValueError, CubicSpline, x, Y, 0, bc2, True)
  742. assert_raises(ValueError, CubicSpline, x, Y, 0, bc3, True)
  743. # periodic condition, y[-1] must be equal to y[0]:
  744. assert_raises(ValueError, CubicSpline, x, y, 0, 'periodic', True)
  745. @make_xp_test_case(CubicHermiteSpline)
  746. def test_CubicHermiteSpline_correctness(xp):
  747. x = xp.asarray([0, 2, 7])
  748. y = xp.asarray([-1, 2, 3])
  749. dydx = xp.asarray([0, 3, 7])
  750. s = CubicHermiteSpline(x, y, dydx)
  751. xp_assert_close(s(x), y, check_shape=False, check_dtype=False, rtol=1e-15)
  752. xp_assert_close(s(x, 1), dydx, check_shape=False, check_dtype=False, rtol=1e-15)
  753. def test_CubicHermiteSpline_error_handling():
  754. x = [1, 2, 3]
  755. y = [0, 3, 5]
  756. dydx = [1, -1, 2, 3]
  757. assert_raises(ValueError, CubicHermiteSpline, x, y, dydx)
  758. dydx_with_nan = [1, 0, np.nan]
  759. assert_raises(ValueError, CubicHermiteSpline, x, y, dydx_with_nan)
  760. def test_roots_extrapolate_gh_11185():
  761. x = np.array([0.001, 0.002])
  762. y = np.array([1.66066935e-06, 1.10410807e-06])
  763. dy = np.array([-1.60061854, -1.600619])
  764. p = CubicHermiteSpline(x, y, dy)
  765. # roots(extrapolate=True) for a polynomial with a single interval
  766. # should return all three real roots
  767. r = p.roots(extrapolate=True)
  768. assert p.c.shape[1] == 1
  769. assert r.size == 3
  770. class TestZeroSizeArrays:
  771. # regression tests for gh-17241 : CubicSpline et al must not segfault
  772. # when y.size == 0
  773. # The two methods below are _almost_ the same, but not quite:
  774. # one is for objects which have the `bc_type` argument (CubicSpline)
  775. # and the other one is for those which do not (Pchip, Akima1D)
  776. @pytest.mark.parametrize('y', [np.zeros((10, 0, 5)),
  777. np.zeros((10, 5, 0))])
  778. @pytest.mark.parametrize('bc_type',
  779. ['not-a-knot', 'periodic', 'natural', 'clamped'])
  780. @pytest.mark.parametrize('axis', [0, 1, 2])
  781. @pytest.mark.parametrize('cls', [make_interp_spline, CubicSpline])
  782. def test_zero_size(self, cls, y, bc_type, axis):
  783. x = np.arange(10)
  784. xval = np.arange(3)
  785. obj = cls(x, y, bc_type=bc_type)
  786. assert obj(xval).size == 0
  787. assert obj(xval).shape == xval.shape + y.shape[1:]
  788. # Also check with an explicit non-default axis
  789. yt = np.moveaxis(y, 0, axis) # (10, 0, 5) --> (0, 10, 5) if axis=1 etc
  790. obj = cls(x, yt, bc_type=bc_type, axis=axis)
  791. sh = yt.shape[:axis] + (xval.size, ) + yt.shape[axis+1:]
  792. assert obj(xval).size == 0
  793. assert obj(xval).shape == sh
  794. @pytest.mark.parametrize('y', [np.zeros((10, 0, 5)),
  795. np.zeros((10, 5, 0))])
  796. @pytest.mark.parametrize('axis', [0, 1, 2])
  797. @pytest.mark.parametrize('cls', [PchipInterpolator, Akima1DInterpolator])
  798. def test_zero_size_2(self, cls, y, axis):
  799. x = np.arange(10)
  800. xval = np.arange(3)
  801. obj = cls(x, y)
  802. assert obj(xval).size == 0
  803. assert obj(xval).shape == xval.shape + y.shape[1:]
  804. # Also check with an explicit non-default axis
  805. yt = np.moveaxis(y, 0, axis) # (10, 0, 5) --> (0, 10, 5) if axis=1 etc
  806. obj = cls(x, yt, axis=axis)
  807. sh = yt.shape[:axis] + (xval.size, ) + yt.shape[axis+1:]
  808. assert obj(xval).size == 0
  809. assert obj(xval).shape == sh