test_interpnd.py 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388
  1. from __future__ import division, print_function, absolute_import
  2. import os
  3. import numpy as np
  4. from numpy.testing import assert_equal, assert_allclose, assert_almost_equal
  5. from pytest import raises as assert_raises
  6. import pytest
  7. from scipy._lib._numpy_compat import suppress_warnings
  8. import scipy.interpolate.interpnd as interpnd
  9. import scipy.spatial.qhull as qhull
  10. import pickle
  11. def data_file(basename):
  12. return os.path.join(os.path.abspath(os.path.dirname(__file__)),
  13. 'data', basename)
  14. class TestLinearNDInterpolation(object):
  15. def test_smoketest(self):
  16. # Test at single points
  17. x = np.array([(0,0), (-0.5,-0.5), (-0.5,0.5), (0.5, 0.5), (0.25, 0.3)],
  18. dtype=np.double)
  19. y = np.arange(x.shape[0], dtype=np.double)
  20. yi = interpnd.LinearNDInterpolator(x, y)(x)
  21. assert_almost_equal(y, yi)
  22. def test_smoketest_alternate(self):
  23. # Test at single points, alternate calling convention
  24. x = np.array([(0,0), (-0.5,-0.5), (-0.5,0.5), (0.5, 0.5), (0.25, 0.3)],
  25. dtype=np.double)
  26. y = np.arange(x.shape[0], dtype=np.double)
  27. yi = interpnd.LinearNDInterpolator((x[:,0], x[:,1]), y)(x[:,0], x[:,1])
  28. assert_almost_equal(y, yi)
  29. def test_complex_smoketest(self):
  30. # Test at single points
  31. x = np.array([(0,0), (-0.5,-0.5), (-0.5,0.5), (0.5, 0.5), (0.25, 0.3)],
  32. dtype=np.double)
  33. y = np.arange(x.shape[0], dtype=np.double)
  34. y = y - 3j*y
  35. yi = interpnd.LinearNDInterpolator(x, y)(x)
  36. assert_almost_equal(y, yi)
  37. def test_tri_input(self):
  38. # Test at single points
  39. x = np.array([(0,0), (-0.5,-0.5), (-0.5,0.5), (0.5, 0.5), (0.25, 0.3)],
  40. dtype=np.double)
  41. y = np.arange(x.shape[0], dtype=np.double)
  42. y = y - 3j*y
  43. tri = qhull.Delaunay(x)
  44. yi = interpnd.LinearNDInterpolator(tri, y)(x)
  45. assert_almost_equal(y, yi)
  46. def test_square(self):
  47. # Test barycentric interpolation on a square against a manual
  48. # implementation
  49. points = np.array([(0,0), (0,1), (1,1), (1,0)], dtype=np.double)
  50. values = np.array([1., 2., -3., 5.], dtype=np.double)
  51. # NB: assume triangles (0, 1, 3) and (1, 2, 3)
  52. #
  53. # 1----2
  54. # | \ |
  55. # | \ |
  56. # 0----3
  57. def ip(x, y):
  58. t1 = (x + y <= 1)
  59. t2 = ~t1
  60. x1 = x[t1]
  61. y1 = y[t1]
  62. x2 = x[t2]
  63. y2 = y[t2]
  64. z = 0*x
  65. z[t1] = (values[0]*(1 - x1 - y1)
  66. + values[1]*y1
  67. + values[3]*x1)
  68. z[t2] = (values[2]*(x2 + y2 - 1)
  69. + values[1]*(1 - x2)
  70. + values[3]*(1 - y2))
  71. return z
  72. xx, yy = np.broadcast_arrays(np.linspace(0, 1, 14)[:,None],
  73. np.linspace(0, 1, 14)[None,:])
  74. xx = xx.ravel()
  75. yy = yy.ravel()
  76. xi = np.array([xx, yy]).T.copy()
  77. zi = interpnd.LinearNDInterpolator(points, values)(xi)
  78. assert_almost_equal(zi, ip(xx, yy))
  79. def test_smoketest_rescale(self):
  80. # Test at single points
  81. x = np.array([(0, 0), (-5, -5), (-5, 5), (5, 5), (2.5, 3)],
  82. dtype=np.double)
  83. y = np.arange(x.shape[0], dtype=np.double)
  84. yi = interpnd.LinearNDInterpolator(x, y, rescale=True)(x)
  85. assert_almost_equal(y, yi)
  86. def test_square_rescale(self):
  87. # Test barycentric interpolation on a rectangle with rescaling
  88. # agaings the same implementation without rescaling
  89. points = np.array([(0,0), (0,100), (10,100), (10,0)], dtype=np.double)
  90. values = np.array([1., 2., -3., 5.], dtype=np.double)
  91. xx, yy = np.broadcast_arrays(np.linspace(0, 10, 14)[:,None],
  92. np.linspace(0, 100, 14)[None,:])
  93. xx = xx.ravel()
  94. yy = yy.ravel()
  95. xi = np.array([xx, yy]).T.copy()
  96. zi = interpnd.LinearNDInterpolator(points, values)(xi)
  97. zi_rescaled = interpnd.LinearNDInterpolator(points, values,
  98. rescale=True)(xi)
  99. assert_almost_equal(zi, zi_rescaled)
  100. def test_tripoints_input_rescale(self):
  101. # Test at single points
  102. x = np.array([(0,0), (-5,-5), (-5,5), (5, 5), (2.5, 3)],
  103. dtype=np.double)
  104. y = np.arange(x.shape[0], dtype=np.double)
  105. y = y - 3j*y
  106. tri = qhull.Delaunay(x)
  107. yi = interpnd.LinearNDInterpolator(tri.points, y)(x)
  108. yi_rescale = interpnd.LinearNDInterpolator(tri.points, y,
  109. rescale=True)(x)
  110. assert_almost_equal(yi, yi_rescale)
  111. def test_tri_input_rescale(self):
  112. # Test at single points
  113. x = np.array([(0,0), (-5,-5), (-5,5), (5, 5), (2.5, 3)],
  114. dtype=np.double)
  115. y = np.arange(x.shape[0], dtype=np.double)
  116. y = y - 3j*y
  117. tri = qhull.Delaunay(x)
  118. match = ("Rescaling is not supported when passing a "
  119. "Delaunay triangulation as ``points``.")
  120. with pytest.raises(ValueError, match=match):
  121. interpnd.LinearNDInterpolator(tri, y, rescale=True)(x)
  122. def test_pickle(self):
  123. # Test at single points
  124. np.random.seed(1234)
  125. x = np.random.rand(30, 2)
  126. y = np.random.rand(30) + 1j*np.random.rand(30)
  127. ip = interpnd.LinearNDInterpolator(x, y)
  128. ip2 = pickle.loads(pickle.dumps(ip))
  129. assert_almost_equal(ip(0.5, 0.5), ip2(0.5, 0.5))
  130. class TestEstimateGradients2DGlobal(object):
  131. def test_smoketest(self):
  132. x = np.array([(0, 0), (0, 2),
  133. (1, 0), (1, 2), (0.25, 0.75), (0.6, 0.8)], dtype=float)
  134. tri = qhull.Delaunay(x)
  135. # Should be exact for linear functions, independent of triangulation
  136. funcs = [
  137. (lambda x, y: 0*x + 1, (0, 0)),
  138. (lambda x, y: 0 + x, (1, 0)),
  139. (lambda x, y: -2 + y, (0, 1)),
  140. (lambda x, y: 3 + 3*x + 14.15*y, (3, 14.15))
  141. ]
  142. for j, (func, grad) in enumerate(funcs):
  143. z = func(x[:,0], x[:,1])
  144. dz = interpnd.estimate_gradients_2d_global(tri, z, tol=1e-6)
  145. assert_equal(dz.shape, (6, 2))
  146. assert_allclose(dz, np.array(grad)[None,:] + 0*dz,
  147. rtol=1e-5, atol=1e-5, err_msg="item %d" % j)
  148. def test_regression_2359(self):
  149. # Check regression --- for certain point sets, gradient
  150. # estimation could end up in an infinite loop
  151. points = np.load(data_file('estimate_gradients_hang.npy'))
  152. values = np.random.rand(points.shape[0])
  153. tri = qhull.Delaunay(points)
  154. # This should not hang
  155. with suppress_warnings() as sup:
  156. sup.filter(interpnd.GradientEstimationWarning,
  157. "Gradient estimation did not converge")
  158. interpnd.estimate_gradients_2d_global(tri, values, maxiter=1)
  159. class TestCloughTocher2DInterpolator(object):
  160. def _check_accuracy(self, func, x=None, tol=1e-6, alternate=False, rescale=False, **kw):
  161. np.random.seed(1234)
  162. if x is None:
  163. x = np.array([(0, 0), (0, 1),
  164. (1, 0), (1, 1), (0.25, 0.75), (0.6, 0.8),
  165. (0.5, 0.2)],
  166. dtype=float)
  167. if not alternate:
  168. ip = interpnd.CloughTocher2DInterpolator(x, func(x[:,0], x[:,1]),
  169. tol=1e-6, rescale=rescale)
  170. else:
  171. ip = interpnd.CloughTocher2DInterpolator((x[:,0], x[:,1]),
  172. func(x[:,0], x[:,1]),
  173. tol=1e-6, rescale=rescale)
  174. p = np.random.rand(50, 2)
  175. if not alternate:
  176. a = ip(p)
  177. else:
  178. a = ip(p[:,0], p[:,1])
  179. b = func(p[:,0], p[:,1])
  180. try:
  181. assert_allclose(a, b, **kw)
  182. except AssertionError:
  183. print(abs(a - b))
  184. print(ip.grad)
  185. raise
  186. def test_linear_smoketest(self):
  187. # Should be exact for linear functions, independent of triangulation
  188. funcs = [
  189. lambda x, y: 0*x + 1,
  190. lambda x, y: 0 + x,
  191. lambda x, y: -2 + y,
  192. lambda x, y: 3 + 3*x + 14.15*y,
  193. ]
  194. for j, func in enumerate(funcs):
  195. self._check_accuracy(func, tol=1e-13, atol=1e-7, rtol=1e-7,
  196. err_msg="Function %d" % j)
  197. self._check_accuracy(func, tol=1e-13, atol=1e-7, rtol=1e-7,
  198. alternate=True,
  199. err_msg="Function (alternate) %d" % j)
  200. # check rescaling
  201. self._check_accuracy(func, tol=1e-13, atol=1e-7, rtol=1e-7,
  202. err_msg="Function (rescaled) %d" % j, rescale=True)
  203. self._check_accuracy(func, tol=1e-13, atol=1e-7, rtol=1e-7,
  204. alternate=True, rescale=True,
  205. err_msg="Function (alternate, rescaled) %d" % j)
  206. def test_quadratic_smoketest(self):
  207. # Should be reasonably accurate for quadratic functions
  208. funcs = [
  209. lambda x, y: x**2,
  210. lambda x, y: y**2,
  211. lambda x, y: x**2 - y**2,
  212. lambda x, y: x*y,
  213. ]
  214. for j, func in enumerate(funcs):
  215. self._check_accuracy(func, tol=1e-9, atol=0.22, rtol=0,
  216. err_msg="Function %d" % j)
  217. self._check_accuracy(func, tol=1e-9, atol=0.22, rtol=0,
  218. err_msg="Function %d" % j, rescale=True)
  219. def test_tri_input(self):
  220. # Test at single points
  221. x = np.array([(0,0), (-0.5,-0.5), (-0.5,0.5), (0.5, 0.5), (0.25, 0.3)],
  222. dtype=np.double)
  223. y = np.arange(x.shape[0], dtype=np.double)
  224. y = y - 3j*y
  225. tri = qhull.Delaunay(x)
  226. yi = interpnd.CloughTocher2DInterpolator(tri, y)(x)
  227. assert_almost_equal(y, yi)
  228. def test_tri_input_rescale(self):
  229. # Test at single points
  230. x = np.array([(0,0), (-5,-5), (-5,5), (5, 5), (2.5, 3)],
  231. dtype=np.double)
  232. y = np.arange(x.shape[0], dtype=np.double)
  233. y = y - 3j*y
  234. tri = qhull.Delaunay(x)
  235. match = ("Rescaling is not supported when passing a "
  236. "Delaunay triangulation as ``points``.")
  237. with pytest.raises(ValueError, match=match):
  238. interpnd.CloughTocher2DInterpolator(tri, y, rescale=True)(x)
  239. def test_tripoints_input_rescale(self):
  240. # Test at single points
  241. x = np.array([(0,0), (-5,-5), (-5,5), (5, 5), (2.5, 3)],
  242. dtype=np.double)
  243. y = np.arange(x.shape[0], dtype=np.double)
  244. y = y - 3j*y
  245. tri = qhull.Delaunay(x)
  246. yi = interpnd.CloughTocher2DInterpolator(tri.points, y)(x)
  247. yi_rescale = interpnd.CloughTocher2DInterpolator(tri.points, y, rescale=True)(x)
  248. assert_almost_equal(yi, yi_rescale)
  249. def test_dense(self):
  250. # Should be more accurate for dense meshes
  251. funcs = [
  252. lambda x, y: x**2,
  253. lambda x, y: y**2,
  254. lambda x, y: x**2 - y**2,
  255. lambda x, y: x*y,
  256. lambda x, y: np.cos(2*np.pi*x)*np.sin(2*np.pi*y)
  257. ]
  258. np.random.seed(4321) # use a different seed than the check!
  259. grid = np.r_[np.array([(0,0), (0,1), (1,0), (1,1)], dtype=float),
  260. np.random.rand(30*30, 2)]
  261. for j, func in enumerate(funcs):
  262. self._check_accuracy(func, x=grid, tol=1e-9, atol=5e-3, rtol=1e-2,
  263. err_msg="Function %d" % j)
  264. self._check_accuracy(func, x=grid, tol=1e-9, atol=5e-3, rtol=1e-2,
  265. err_msg="Function %d" % j, rescale=True)
  266. def test_wrong_ndim(self):
  267. x = np.random.randn(30, 3)
  268. y = np.random.randn(30)
  269. assert_raises(ValueError, interpnd.CloughTocher2DInterpolator, x, y)
  270. def test_pickle(self):
  271. # Test at single points
  272. np.random.seed(1234)
  273. x = np.random.rand(30, 2)
  274. y = np.random.rand(30) + 1j*np.random.rand(30)
  275. ip = interpnd.CloughTocher2DInterpolator(x, y)
  276. ip2 = pickle.loads(pickle.dumps(ip))
  277. assert_almost_equal(ip(0.5, 0.5), ip2(0.5, 0.5))
  278. def test_boundary_tri_symmetry(self):
  279. # Interpolation at neighbourless triangles should retain
  280. # symmetry with mirroring the triangle.
  281. # Equilateral triangle
  282. points = np.array([(0, 0), (1, 0), (0.5, np.sqrt(3)/2)])
  283. values = np.array([1, 0, 0])
  284. ip = interpnd.CloughTocher2DInterpolator(points, values)
  285. # Set gradient to zero at vertices
  286. ip.grad[...] = 0
  287. # Interpolation should be symmetric vs. bisector
  288. alpha = 0.3
  289. p1 = np.array([0.5 * np.cos(alpha), 0.5 * np.sin(alpha)])
  290. p2 = np.array([0.5 * np.cos(np.pi/3 - alpha), 0.5 * np.sin(np.pi/3 - alpha)])
  291. v1 = ip(p1)
  292. v2 = ip(p2)
  293. assert_allclose(v1, v2)
  294. # ... and affine invariant
  295. np.random.seed(1)
  296. A = np.random.randn(2, 2)
  297. b = np.random.randn(2)
  298. points = A.dot(points.T).T + b[None,:]
  299. p1 = A.dot(p1) + b
  300. p2 = A.dot(p2) + b
  301. ip = interpnd.CloughTocher2DInterpolator(points, values)
  302. ip.grad[...] = 0
  303. w1 = ip(p1)
  304. w2 = ip(p2)
  305. assert_allclose(w1, v1)
  306. assert_allclose(w2, v2)