test_fitpack.py 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534
  1. import itertools
  2. import os
  3. import numpy as np
  4. from scipy._lib._array_api import (
  5. xp_assert_equal, xp_assert_close, assert_almost_equal, assert_array_almost_equal
  6. )
  7. from pytest import raises as assert_raises
  8. import pytest
  9. from scipy._lib._testutils import check_free_memory
  10. from scipy.interpolate import RectBivariateSpline
  11. from scipy.interpolate import make_splrep
  12. from scipy.interpolate._fitpack_py import (splrep, splev, bisplrep, bisplev,
  13. sproot, splprep, splint, spalde, splder, splantider, insert, dblint)
  14. from scipy.interpolate._dfitpack import regrid_smth
  15. from scipy.interpolate._fitpack2 import dfitpack_int
  16. def data_file(basename):
  17. return os.path.join(os.path.abspath(os.path.dirname(__file__)),
  18. 'data', basename)
  19. def norm2(x):
  20. return np.sqrt(np.dot(x.T, x))
  21. def f1(x, d=0):
  22. """Derivatives of sin->cos->-sin->-cos."""
  23. if d % 4 == 0:
  24. return np.sin(x)
  25. if d % 4 == 1:
  26. return np.cos(x)
  27. if d % 4 == 2:
  28. return -np.sin(x)
  29. if d % 4 == 3:
  30. return -np.cos(x)
  31. def makepairs(x, y):
  32. """Helper function to create an array of pairs of x and y."""
  33. xy = np.array(list(itertools.product(np.asarray(x), np.asarray(y))))
  34. return xy.T
  35. class TestSmokeTests:
  36. """
  37. Smoke tests (with a few asserts) for fitpack routines -- mostly
  38. check that they are runnable
  39. """
  40. def check_1(self, per=0, s=0, a=0, b=2*np.pi, at_nodes=False,
  41. xb=None, xe=None):
  42. if xb is None:
  43. xb = a
  44. if xe is None:
  45. xe = b
  46. N = 20
  47. # nodes and middle points of the nodes
  48. x = np.linspace(a, b, N + 1)
  49. x1 = a + (b - a) * np.arange(1, N, dtype=float) / float(N - 1)
  50. v = f1(x)
  51. def err_est(k, d):
  52. # Assume f has all derivatives < 1
  53. h = 1.0 / N
  54. tol = 5 * h**(.75*(k-d))
  55. if s > 0:
  56. tol += 1e5*s
  57. return tol
  58. for k in range(1, 6):
  59. tck = splrep(x, v, s=s, per=per, k=k, xe=xe)
  60. tt = tck[0][k:-k] if at_nodes else x1
  61. for d in range(k+1):
  62. tol = err_est(k, d)
  63. err = norm2(f1(tt, d) - splev(tt, tck, d)) / norm2(f1(tt, d))
  64. assert err < tol
  65. # smoke test make_splrep
  66. if not per:
  67. spl = make_splrep(x, v, k=k, s=s, xb=xb, xe=xe)
  68. if len(spl.t) == len(tck[0]):
  69. xp_assert_close(spl.t, tck[0], atol=1e-15)
  70. xp_assert_close(spl.c, tck[1][:spl.c.size], atol=1e-13)
  71. else:
  72. assert k == 5 # knot length differ in some k=5 cases
  73. else:
  74. if np.allclose(v[0], v[-1], atol=1e-15):
  75. spl = make_splrep(x, v, k=k, s=s, xb=xb, xe=xe, bc_type='periodic')
  76. if k != 1: # knots for k == 1 in some cases
  77. xp_assert_close(spl.t, tck[0], atol=1e-15)
  78. xp_assert_close(spl.c, tck[1][:spl.c.size], atol=1e-13)
  79. else:
  80. with assert_raises(ValueError):
  81. spl = make_splrep(x, v, k=k, s=s,
  82. xb=xb, xe=xe, bc_type='periodic')
  83. def check_2(self, per=0, N=20, ia=0, ib=2*np.pi):
  84. a, b, dx = 0, 2*np.pi, 0.2*np.pi
  85. x = np.linspace(a, b, N+1) # nodes
  86. v = np.sin(x)
  87. def err_est(k, d):
  88. # Assume f has all derivatives < 1
  89. h = 1.0 / N
  90. tol = 5 * h**(.75*(k-d))
  91. return tol
  92. nk = []
  93. for k in range(1, 6):
  94. tck = splrep(x, v, s=0, per=per, k=k, xe=b)
  95. nk.append([splint(ia, ib, tck), spalde(dx, tck)])
  96. k = 1
  97. for r in nk:
  98. d = 0
  99. for dr in r[1]:
  100. tol = err_est(k, d)
  101. xp_assert_close(dr, f1(dx, d), atol=0, rtol=tol)
  102. d = d+1
  103. k = k+1
  104. def test_smoke_splrep_splev(self):
  105. self.check_1(s=1e-6)
  106. self.check_1(b=1.5*np.pi)
  107. def test_smoke_splrep_splev_periodic(self):
  108. self.check_1(b=1.5*np.pi, xe=2*np.pi, per=1, s=1e-1)
  109. self.check_1(b=2*np.pi, per=1, s=1e-1)
  110. @pytest.mark.parametrize('per', [0, 1])
  111. @pytest.mark.parametrize('at_nodes', [True, False])
  112. def test_smoke_splrep_splev_2(self, per, at_nodes):
  113. self.check_1(per=per, at_nodes=at_nodes)
  114. @pytest.mark.parametrize('N', [20, 50])
  115. @pytest.mark.parametrize('per', [0, 1])
  116. def test_smoke_splint_spalde(self, N, per):
  117. self.check_2(per=per, N=N)
  118. @pytest.mark.parametrize('N', [20, 50])
  119. @pytest.mark.parametrize('per', [0, 1])
  120. def test_smoke_splint_spalde_iaib(self, N, per):
  121. self.check_2(ia=0.2*np.pi, ib=np.pi, N=N, per=per)
  122. def test_smoke_sproot(self):
  123. # sproot is only implemented for k=3
  124. a, b = 0.1, 15
  125. x = np.linspace(a, b, 20)
  126. v = np.sin(x)
  127. for k in [1, 2, 4, 5]:
  128. tck = splrep(x, v, s=0, per=0, k=k, xe=b)
  129. with assert_raises(ValueError):
  130. sproot(tck)
  131. k = 3
  132. tck = splrep(x, v, s=0, k=3)
  133. roots = sproot(tck)
  134. xp_assert_close(splev(roots, tck), np.zeros(len(roots)), atol=1e-10, rtol=1e-10)
  135. xp_assert_close(roots, np.pi * np.array([1, 2, 3, 4]), rtol=1e-3)
  136. @pytest.mark.parametrize('N', [20, 50])
  137. @pytest.mark.parametrize('k', [1, 2, 3, 4, 5])
  138. def test_smoke_splprep_splrep_splev(self, N, k):
  139. a, b, dx = 0, 2.*np.pi, 0.2*np.pi
  140. x = np.linspace(a, b, N+1) # nodes
  141. v = np.sin(x)
  142. tckp, u = splprep([x, v], s=0, per=0, k=k, nest=-1)
  143. uv = splev(dx, tckp)
  144. err1 = abs(uv[1] - np.sin(uv[0]))
  145. assert err1 < 1e-2
  146. tck = splrep(x, v, s=0, per=0, k=k)
  147. err2 = abs(splev(uv[0], tck) - np.sin(uv[0]))
  148. assert err2 < 1e-2
  149. # Derivatives of parametric cubic spline at u (first function)
  150. if k == 3:
  151. tckp, u = splprep([x, v], s=0, per=0, k=k, nest=-1)
  152. for d in range(1, k+1):
  153. uv = splev(dx, tckp, d)
  154. def test_smoke_bisplrep_bisplev(self):
  155. xb, xe = 0, 2.*np.pi
  156. yb, ye = 0, 2.*np.pi
  157. kx, ky = 3, 3
  158. Nx, Ny = 20, 20
  159. def f2(x, y):
  160. return np.sin(x+y)
  161. x = np.linspace(xb, xe, Nx + 1)
  162. y = np.linspace(yb, ye, Ny + 1)
  163. xy = makepairs(x, y)
  164. tck = bisplrep(xy[0], xy[1], f2(xy[0], xy[1]), s=0, kx=kx, ky=ky)
  165. tt = [tck[0][kx:-kx], tck[1][ky:-ky]]
  166. t2 = makepairs(tt[0], tt[1])
  167. v1 = bisplev(tt[0], tt[1], tck)
  168. v2 = f2(t2[0], t2[1])
  169. v2 = v2.reshape(len(tt[0]), len(tt[1]))
  170. assert norm2(np.ravel(v1 - v2)) < 1e-2
  171. class TestSplev:
  172. def test_1d_shape(self):
  173. x = [1,2,3,4,5]
  174. y = [4,5,6,7,8]
  175. tck = splrep(x, y)
  176. z = splev([1], tck)
  177. assert z.shape == (1,)
  178. z = splev(1, tck)
  179. assert z.shape == ()
  180. def test_2d_shape(self):
  181. x = [1, 2, 3, 4, 5]
  182. y = [4, 5, 6, 7, 8]
  183. tck = splrep(x, y)
  184. t = np.array([[1.0, 1.5, 2.0, 2.5],
  185. [3.0, 3.5, 4.0, 4.5]])
  186. z = splev(t, tck)
  187. z0 = splev(t[0], tck)
  188. z1 = splev(t[1], tck)
  189. xp_assert_equal(z, np.vstack((z0, z1)))
  190. def test_extrapolation_modes(self):
  191. # test extrapolation modes
  192. # * if ext=0, return the extrapolated value.
  193. # * if ext=1, return 0
  194. # * if ext=2, raise a ValueError
  195. # * if ext=3, return the boundary value.
  196. x = [1,2,3]
  197. y = [0,2,4]
  198. tck = splrep(x, y, k=1)
  199. rstl = [[-2, 6], [0, 0], None, [0, 4]]
  200. for ext in (0, 1, 3):
  201. assert_array_almost_equal(splev([0, 4], tck, ext=ext), rstl[ext])
  202. assert_raises(ValueError, splev, [0, 4], tck, ext=2)
  203. class TestSplder:
  204. def setup_method(self):
  205. # non-uniform grid, just to make it sure
  206. x = np.linspace(0, 1, 100)**3
  207. y = np.sin(20 * x)
  208. self.spl = splrep(x, y)
  209. # double check that knots are non-uniform
  210. assert np.ptp(np.diff(self.spl[0])) > 0
  211. def test_inverse(self):
  212. # Check that antiderivative + derivative is identity.
  213. for n in range(5):
  214. spl2 = splantider(self.spl, n)
  215. spl3 = splder(spl2, n)
  216. xp_assert_close(self.spl[0], spl3[0])
  217. xp_assert_close(self.spl[1], spl3[1])
  218. assert self.spl[2] == spl3[2]
  219. def test_splder_vs_splev(self):
  220. # Check derivative vs. FITPACK
  221. for n in range(3+1):
  222. # Also extrapolation!
  223. xx = np.linspace(-1, 2, 2000)
  224. if n == 3:
  225. # ... except that FITPACK extrapolates strangely for
  226. # order 0, so let's not check that.
  227. xx = xx[(xx >= 0) & (xx <= 1)]
  228. dy = splev(xx, self.spl, n)
  229. spl2 = splder(self.spl, n)
  230. dy2 = splev(xx, spl2)
  231. if n == 1:
  232. xp_assert_close(dy, dy2, rtol=2e-6)
  233. else:
  234. xp_assert_close(dy, dy2)
  235. def test_splantider_vs_splint(self):
  236. # Check antiderivative vs. FITPACK
  237. spl2 = splantider(self.spl)
  238. # no extrapolation, splint assumes function is zero outside
  239. # range
  240. xx = np.linspace(0, 1, 20)
  241. for x1 in xx:
  242. for x2 in xx:
  243. y1 = splint(x1, x2, self.spl)
  244. y2 = splev(x2, spl2) - splev(x1, spl2)
  245. xp_assert_close(np.asarray(y1), np.asarray(y2))
  246. def test_order0_diff(self):
  247. assert_raises(ValueError, splder, self.spl, 4)
  248. def test_kink(self):
  249. # Should refuse to differentiate splines with kinks
  250. spl2 = insert(0.5, self.spl, m=2)
  251. splder(spl2, 2) # Should work
  252. assert_raises(ValueError, splder, spl2, 3)
  253. spl2 = insert(0.5, self.spl, m=3)
  254. splder(spl2, 1) # Should work
  255. assert_raises(ValueError, splder, spl2, 2)
  256. spl2 = insert(0.5, self.spl, m=4)
  257. assert_raises(ValueError, splder, spl2, 1)
  258. def test_multidim(self):
  259. # c can have trailing dims
  260. for n in range(3):
  261. t, c, k = self.spl
  262. c2 = np.c_[c, c, c]
  263. c2 = np.dstack((c2, c2))
  264. spl2 = splantider((t, c2, k), n)
  265. spl3 = splder(spl2, n)
  266. xp_assert_close(t, spl3[0])
  267. xp_assert_close(c2, spl3[1])
  268. assert k == spl3[2]
  269. class TestSplint:
  270. def test_len_c(self):
  271. n, k = 7, 3
  272. x = np.arange(n)
  273. y = x**3
  274. t, c, k = splrep(x, y, s=0)
  275. # note that len(c) == len(t) == 11 (== len(x) + 2*(k-1))
  276. assert len(t) == len(c) == n + 2*(k-1)
  277. # integrate directly: $\int_0^6 x^3 dx = 6^4 / 4$
  278. res = splint(0, 6, (t, c, k))
  279. expected = 6**4 / 4
  280. assert abs(res - expected) < 1e-13
  281. # check that the coefficients past len(t) - k - 1 are ignored
  282. c0 = c.copy()
  283. c0[len(t) - k - 1:] = np.nan
  284. res0 = splint(0, 6, (t, c0, k))
  285. assert abs(res0 - expected) < 1e-13
  286. # however, all other coefficients *are* used
  287. c0[6] = np.nan
  288. assert np.isnan(splint(0, 6, (t, c0, k)))
  289. # check that the coefficient array can have length `len(t) - k - 1`
  290. c1 = c[:len(t) - k - 1]
  291. res1 = splint(0, 6, (t, c1, k))
  292. assert (res1 - expected) < 1e-13
  293. # however shorter c arrays raise. The error from f2py is a
  294. # `dftipack.error`, which is an Exception but not ValueError etc.
  295. with assert_raises(Exception, match=r">=n-k-1"):
  296. splint(0, 1, (np.ones(10), np.ones(5), 3))
  297. class TestBisplrep:
  298. def test_overflow(self):
  299. from numpy.lib.stride_tricks import as_strided
  300. if dfitpack_int.itemsize == 8:
  301. size = 1500000**2
  302. else:
  303. size = 400**2
  304. # Don't allocate a real array, as it's very big, but rely
  305. # on that it's not referenced
  306. x = as_strided(np.zeros(()), shape=(size,))
  307. assert_raises(OverflowError, bisplrep, x, x, x, w=x,
  308. xb=0, xe=1, yb=0, ye=1, s=0)
  309. def test_regression_1310(self):
  310. # Regression test for gh-1310
  311. with np.load(data_file('bug-1310.npz')) as loaded_data:
  312. data = loaded_data['data']
  313. # Shouldn't crash -- the input data triggers work array sizes
  314. # that caused previously some data to not be aligned on
  315. # sizeof(double) boundaries in memory, which made the Fortran
  316. # code to crash when compiled with -O3
  317. bisplrep(data[:,0], data[:,1], data[:,2], kx=3, ky=3, s=0,
  318. full_output=True)
  319. @pytest.mark.skipif(dfitpack_int != np.int64, reason="needs ilp64 fitpack")
  320. def test_ilp64_bisplrep(self):
  321. check_free_memory(28000) # VM size, doesn't actually use the pages
  322. x = np.linspace(0, 1, 400)
  323. y = np.linspace(0, 1, 400)
  324. x, y = np.meshgrid(x, y)
  325. z = np.zeros_like(x)
  326. tck = bisplrep(x, y, z, kx=3, ky=3, s=0)
  327. xp_assert_close(bisplev(0.5, 0.5, tck), 0.0)
  328. def test_dblint():
  329. # Basic test to see it runs and gives the correct result on a trivial
  330. # problem. Note that `dblint` is not exposed in the interpolate namespace.
  331. x = np.linspace(0, 1)
  332. y = np.linspace(0, 1)
  333. xx, yy = np.meshgrid(x, y)
  334. rect = RectBivariateSpline(x, y, 4 * xx * yy)
  335. tck = list(rect.tck)
  336. tck.extend(rect.degrees)
  337. assert abs(dblint(0, 1, 0, 1, tck) - 1) < 1e-10
  338. assert abs(dblint(0, 0.5, 0, 1, tck) - 0.25) < 1e-10
  339. assert abs(dblint(0.5, 1, 0, 1, tck) - 0.75) < 1e-10
  340. assert abs(dblint(-100, 100, -100, 100, tck) - 1) < 1e-10
  341. def test_splev_der_k():
  342. # regression test for gh-2188: splev(x, tck, der=k) gives garbage or crashes
  343. # for x outside of knot range
  344. # test case from gh-2188
  345. tck = (np.array([0., 0., 2.5, 2.5]),
  346. np.array([-1.56679978, 2.43995873, 0., 0.]),
  347. 1)
  348. t, c, k = tck
  349. x = np.array([-3, 0, 2.5, 3])
  350. # an explicit form of the linear spline
  351. xp_assert_close(splev(x, tck), c[0] + (c[1] - c[0]) * x/t[2])
  352. xp_assert_close(splev(x, tck, 1),
  353. np.ones_like(x) * (c[1] - c[0]) / t[2]
  354. )
  355. # now check a random spline vs splder
  356. np.random.seed(1234)
  357. x = np.sort(np.random.random(30))
  358. y = np.random.random(30)
  359. t, c, k = splrep(x, y)
  360. x = [t[0] - 1., t[-1] + 1.]
  361. tck2 = splder((t, c, k), k)
  362. xp_assert_close(splev(x, (t, c, k), k), splev(x, tck2))
  363. def test_splprep_segfault():
  364. # regression test for gh-3847: splprep segfaults if knots are specified
  365. # for task=-1
  366. t = np.arange(0, 1.1, 0.1)
  367. x = np.sin(2*np.pi*t)
  368. y = np.cos(2*np.pi*t)
  369. tck, u = splprep([x, y], s=0)
  370. np.arange(0, 1.01, 0.01)
  371. uknots = tck[0] # using the knots from the previous fitting
  372. tck, u = splprep([x, y], task=-1, t=uknots) # here is the crash
  373. @pytest.mark.skipif(dfitpack_int == np.int64,
  374. reason='Will crash (see gh-23396), test only meant for 32-bit overflow')
  375. def test_bisplev_integer_overflow():
  376. np.random.seed(1)
  377. x = np.linspace(0, 1, 11)
  378. y = x
  379. z = np.random.randn(11, 11).ravel()
  380. kx = 1
  381. ky = 1
  382. nx, tx, ny, ty, c, fp, ier = regrid_smth(
  383. x, y, z, None, None, None, None, kx=kx, ky=ky, s=0.0)
  384. tck = (tx[:nx], ty[:ny], c[:(nx - kx - 1) * (ny - ky - 1)], kx, ky)
  385. xp = np.zeros([2621440])
  386. yp = np.zeros([2621440])
  387. assert_raises((RuntimeError, MemoryError), bisplev, xp, yp, tck)
  388. @pytest.mark.xslow
  389. def test_gh_1766():
  390. # this should fail gracefully instead of segfaulting (int overflow)
  391. size = 22
  392. kx, ky = 3, 3
  393. def f2(x, y):
  394. return np.sin(x+y)
  395. x = np.linspace(0, 10, size)
  396. y = np.linspace(50, 700, size)
  397. xy = makepairs(x, y)
  398. tck = bisplrep(xy[0], xy[1], f2(xy[0], xy[1]), s=0, kx=kx, ky=ky)
  399. # the size value here can either segfault
  400. # or produce a MemoryError on main
  401. tx_ty_size = 500000
  402. tck[0] = np.arange(tx_ty_size)
  403. tck[1] = np.arange(tx_ty_size) * 4
  404. tt_0 = np.arange(50)
  405. tt_1 = np.arange(50) * 3
  406. with pytest.raises(MemoryError):
  407. bisplev(tt_0, tt_1, tck, 1, 1)
  408. def test_spalde_scalar_input():
  409. # Ticket #629
  410. x = np.linspace(0, 10)
  411. y = x**3
  412. tck = splrep(x, y, k=3, t=[5])
  413. res = spalde(np.float64(1), tck)
  414. des = np.array([1., 3., 6., 6.])
  415. assert_almost_equal(res, des)
  416. def test_spalde_nc():
  417. # regression test for https://github.com/scipy/scipy/issues/19002
  418. # here len(t) = 29 and len(c) = 25 (== len(t) - k - 1)
  419. x = np.asarray([-10., -9., -8., -7., -6., -5., -4., -3., -2.5, -2., -1.5,
  420. -1., -0.5, 0., 0.5, 1., 1.5, 2., 2.5, 3., 4., 5., 6.],
  421. dtype="float")
  422. t = [-10.0, -10.0, -10.0, -10.0, -9.0, -8.0, -7.0, -6.0, -5.0, -4.0, -3.0,
  423. -2.5, -2.0, -1.5, -1.0, -0.5, 0.0, 0.5, 1.0, 1.5, 2.0, 2.5, 3.0, 4.0,
  424. 5.0, 6.0, 6.0, 6.0, 6.0]
  425. c = np.asarray([1., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.,
  426. 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.])
  427. k = 3
  428. res = spalde(x, (t, c, k))
  429. res = np.vstack(res)
  430. res_splev = np.asarray([splev(x, (t, c, k), nu) for nu in range(4)])
  431. xp_assert_close(res, res_splev.T, atol=1e-15)