_interpolate.py 86 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862186318641865186618671868186918701871187218731874187518761877187818791880188118821883188418851886188718881889189018911892189318941895189618971898189919001901190219031904190519061907190819091910191119121913191419151916191719181919192019211922192319241925192619271928192919301931193219331934193519361937193819391940194119421943194419451946194719481949195019511952195319541955195619571958195919601961196219631964196519661967196819691970197119721973197419751976197719781979198019811982198319841985198619871988198919901991199219931994199519961997199819992000200120022003200420052006200720082009201020112012201320142015201620172018201920202021202220232024202520262027202820292030203120322033203420352036203720382039204020412042204320442045204620472048204920502051205220532054205520562057205820592060206120622063206420652066206720682069207020712072207320742075207620772078207920802081208220832084208520862087208820892090209120922093209420952096209720982099210021012102210321042105210621072108210921102111211221132114211521162117211821192120212121222123212421252126212721282129213021312132213321342135213621372138213921402141214221432144214521462147214821492150215121522153215421552156215721582159216021612162216321642165216621672168216921702171217221732174217521762177217821792180218121822183218421852186218721882189219021912192219321942195219621972198219922002201220222032204220522062207220822092210221122122213221422152216221722182219222022212222222322242225222622272228222922302231223222332234223522362237223822392240224122422243224422452246224722482249225022512252225322542255225622572258225922602261226222632264226522662267226822692270227122722273227422752276227722782279228022812282228322842285228622872288228922902291229222932294229522962297229822992300230123022303230423052306230723082309231023112312231323142315231623172318231923202321232223232324232523262327232823292330233123322333233423352336233723382339234023412342234323442345234623472348234923502351235223532354235523562357235823592360236123622363236423652366236723682369237023712372237323742375237623772378237923802381238223832384238523862387238823892390239123922393239423952396239723982399240024012402240324042405240624072408240924102411241224132414241524162417241824192420242124222423242424252426242724282429243024312432243324342435243624372438243924402441244224432444244524462447244824492450245124522453245424552456245724582459246024612462
  1. __all__ = ['interp1d', 'interp2d', 'lagrange', 'PPoly', 'BPoly', 'NdPPoly']
  2. import numpy as np
  3. from numpy import (array, transpose, searchsorted, atleast_1d, atleast_2d,
  4. ravel, poly1d, asarray, intp)
  5. import scipy.special as spec
  6. from scipy.special import comb
  7. from scipy._lib._util import prod
  8. from . import _fitpack_py
  9. from . import dfitpack
  10. from . import _fitpack
  11. from ._polyint import _Interpolator1D
  12. from . import _ppoly
  13. from ._fitpack2 import RectBivariateSpline
  14. from .interpnd import _ndim_coords_from_arrays
  15. from ._bsplines import make_interp_spline, BSpline
  16. def lagrange(x, w):
  17. r"""
  18. Return a Lagrange interpolating polynomial.
  19. Given two 1-D arrays `x` and `w,` returns the Lagrange interpolating
  20. polynomial through the points ``(x, w)``.
  21. Warning: This implementation is numerically unstable. Do not expect to
  22. be able to use more than about 20 points even if they are chosen optimally.
  23. Parameters
  24. ----------
  25. x : array_like
  26. `x` represents the x-coordinates of a set of datapoints.
  27. w : array_like
  28. `w` represents the y-coordinates of a set of datapoints, i.e., f(`x`).
  29. Returns
  30. -------
  31. lagrange : `numpy.poly1d` instance
  32. The Lagrange interpolating polynomial.
  33. Examples
  34. --------
  35. Interpolate :math:`f(x) = x^3` by 3 points.
  36. >>> import numpy as np
  37. >>> from scipy.interpolate import lagrange
  38. >>> x = np.array([0, 1, 2])
  39. >>> y = x**3
  40. >>> poly = lagrange(x, y)
  41. Since there are only 3 points, Lagrange polynomial has degree 2. Explicitly,
  42. it is given by
  43. .. math::
  44. \begin{aligned}
  45. L(x) &= 1\times \frac{x (x - 2)}{-1} + 8\times \frac{x (x-1)}{2} \\
  46. &= x (-2 + 3x)
  47. \end{aligned}
  48. >>> from numpy.polynomial.polynomial import Polynomial
  49. >>> Polynomial(poly.coef[::-1]).coef
  50. array([ 0., -2., 3.])
  51. >>> import matplotlib.pyplot as plt
  52. >>> x_new = np.arange(0, 2.1, 0.1)
  53. >>> plt.scatter(x, y, label='data')
  54. >>> plt.plot(x_new, Polynomial(poly.coef[::-1])(x_new), label='Polynomial')
  55. >>> plt.plot(x_new, 3*x_new**2 - 2*x_new + 0*x_new,
  56. ... label=r"$3 x^2 - 2 x$", linestyle='-.')
  57. >>> plt.legend()
  58. >>> plt.show()
  59. """
  60. M = len(x)
  61. p = poly1d(0.0)
  62. for j in range(M):
  63. pt = poly1d(w[j])
  64. for k in range(M):
  65. if k == j:
  66. continue
  67. fac = x[j]-x[k]
  68. pt *= poly1d([1.0, -x[k]])/fac
  69. p += pt
  70. return p
  71. # !! Need to find argument for keeping initialize. If it isn't
  72. # !! found, get rid of it!
  73. dep_mesg = """\
  74. `interp2d` is deprecated in SciPy 1.10 and will be removed in SciPy 1.12.0.
  75. For legacy code, nearly bug-for-bug compatible replacements are
  76. `RectBivariateSpline` on regular grids, and `bisplrep`/`bisplev` for
  77. scattered 2D data.
  78. In new code, for regular grids use `RegularGridInterpolator` instead.
  79. For scattered data, prefer `LinearNDInterpolator` or
  80. `CloughTocher2DInterpolator`.
  81. For more details see
  82. `https://gist.github.com/ev-br/8544371b40f414b7eaf3fe6217209bff`
  83. """
  84. class interp2d:
  85. """
  86. interp2d(x, y, z, kind='linear', copy=True, bounds_error=False,
  87. fill_value=None)
  88. .. deprecated:: 1.10.0
  89. `interp2d` is deprecated in SciPy 1.10 and will be removed in SciPy
  90. 1.12.0.
  91. For legacy code, nearly bug-for-bug compatible replacements are
  92. `RectBivariateSpline` on regular grids, and `bisplrep`/`bisplev` for
  93. scattered 2D data.
  94. In new code, for regular grids use `RegularGridInterpolator` instead.
  95. For scattered data, prefer `LinearNDInterpolator` or
  96. `CloughTocher2DInterpolator`.
  97. For more details see
  98. `https://gist.github.com/ev-br/8544371b40f414b7eaf3fe6217209bff`
  99. Interpolate over a 2-D grid.
  100. `x`, `y` and `z` are arrays of values used to approximate some function
  101. f: ``z = f(x, y)`` which returns a scalar value `z`. This class returns a
  102. function whose call method uses spline interpolation to find the value
  103. of new points.
  104. If `x` and `y` represent a regular grid, consider using
  105. `RectBivariateSpline`.
  106. If `z` is a vector value, consider using `interpn`.
  107. Note that calling `interp2d` with NaNs present in input values, or with
  108. decreasing values in `x` an `y` results in undefined behaviour.
  109. Methods
  110. -------
  111. __call__
  112. Parameters
  113. ----------
  114. x, y : array_like
  115. Arrays defining the data point coordinates.
  116. The data point coordinates need to be sorted by increasing order.
  117. If the points lie on a regular grid, `x` can specify the column
  118. coordinates and `y` the row coordinates, for example::
  119. >>> x = [0,1,2]; y = [0,3]; z = [[1,2,3], [4,5,6]]
  120. Otherwise, `x` and `y` must specify the full coordinates for each
  121. point, for example::
  122. >>> x = [0,1,2,0,1,2]; y = [0,0,0,3,3,3]; z = [1,4,2,5,3,6]
  123. If `x` and `y` are multidimensional, they are flattened before use.
  124. z : array_like
  125. The values of the function to interpolate at the data points. If
  126. `z` is a multidimensional array, it is flattened before use assuming
  127. Fortran-ordering (order='F'). The length of a flattened `z` array
  128. is either len(`x`)*len(`y`) if `x` and `y` specify the column and
  129. row coordinates or ``len(z) == len(x) == len(y)`` if `x` and `y`
  130. specify coordinates for each point.
  131. kind : {'linear', 'cubic', 'quintic'}, optional
  132. The kind of spline interpolation to use. Default is 'linear'.
  133. copy : bool, optional
  134. If True, the class makes internal copies of x, y and z.
  135. If False, references may be used. The default is to copy.
  136. bounds_error : bool, optional
  137. If True, when interpolated values are requested outside of the
  138. domain of the input data (x,y), a ValueError is raised.
  139. If False, then `fill_value` is used.
  140. fill_value : number, optional
  141. If provided, the value to use for points outside of the
  142. interpolation domain. If omitted (None), values outside
  143. the domain are extrapolated via nearest-neighbor extrapolation.
  144. See Also
  145. --------
  146. RectBivariateSpline :
  147. Much faster 2-D interpolation if your input data is on a grid
  148. bisplrep, bisplev :
  149. Spline interpolation based on FITPACK
  150. BivariateSpline : a more recent wrapper of the FITPACK routines
  151. interp1d : 1-D version of this function
  152. RegularGridInterpolator : interpolation on a regular or rectilinear grid
  153. in arbitrary dimensions.
  154. interpn : Multidimensional interpolation on regular grids (wraps
  155. `RegularGridInterpolator` and `RectBivariateSpline`).
  156. Notes
  157. -----
  158. The minimum number of data points required along the interpolation
  159. axis is ``(k+1)**2``, with k=1 for linear, k=3 for cubic and k=5 for
  160. quintic interpolation.
  161. The interpolator is constructed by `bisplrep`, with a smoothing factor
  162. of 0. If more control over smoothing is needed, `bisplrep` should be
  163. used directly.
  164. The coordinates of the data points to interpolate `xnew` and `ynew`
  165. have to be sorted by ascending order.
  166. `interp2d` is legacy and is not
  167. recommended for use in new code. New code should use
  168. `RegularGridInterpolator` instead.
  169. Examples
  170. --------
  171. Construct a 2-D grid and interpolate on it:
  172. >>> import numpy as np
  173. >>> from scipy import interpolate
  174. >>> x = np.arange(-5.01, 5.01, 0.25)
  175. >>> y = np.arange(-5.01, 5.01, 0.25)
  176. >>> xx, yy = np.meshgrid(x, y)
  177. >>> z = np.sin(xx**2+yy**2)
  178. >>> f = interpolate.interp2d(x, y, z, kind='cubic')
  179. Now use the obtained interpolation function and plot the result:
  180. >>> import matplotlib.pyplot as plt
  181. >>> xnew = np.arange(-5.01, 5.01, 1e-2)
  182. >>> ynew = np.arange(-5.01, 5.01, 1e-2)
  183. >>> znew = f(xnew, ynew)
  184. >>> plt.plot(x, z[0, :], 'ro-', xnew, znew[0, :], 'b-')
  185. >>> plt.show()
  186. """
  187. @np.deprecate(old_name='interp2d', message=dep_mesg)
  188. def __init__(self, x, y, z, kind='linear', copy=True, bounds_error=False,
  189. fill_value=None):
  190. x = ravel(x)
  191. y = ravel(y)
  192. z = asarray(z)
  193. rectangular_grid = (z.size == len(x) * len(y))
  194. if rectangular_grid:
  195. if z.ndim == 2:
  196. if z.shape != (len(y), len(x)):
  197. raise ValueError("When on a regular grid with x.size = m "
  198. "and y.size = n, if z.ndim == 2, then z "
  199. "must have shape (n, m)")
  200. if not np.all(x[1:] >= x[:-1]):
  201. j = np.argsort(x)
  202. x = x[j]
  203. z = z[:, j]
  204. if not np.all(y[1:] >= y[:-1]):
  205. j = np.argsort(y)
  206. y = y[j]
  207. z = z[j, :]
  208. z = ravel(z.T)
  209. else:
  210. z = ravel(z)
  211. if len(x) != len(y):
  212. raise ValueError(
  213. "x and y must have equal lengths for non rectangular grid")
  214. if len(z) != len(x):
  215. raise ValueError(
  216. "Invalid length for input z for non rectangular grid")
  217. interpolation_types = {'linear': 1, 'cubic': 3, 'quintic': 5}
  218. try:
  219. kx = ky = interpolation_types[kind]
  220. except KeyError as e:
  221. raise ValueError(
  222. f"Unsupported interpolation type {repr(kind)}, must be "
  223. f"either of {', '.join(map(repr, interpolation_types))}."
  224. ) from e
  225. if not rectangular_grid:
  226. # TODO: surfit is really not meant for interpolation!
  227. self.tck = _fitpack_py.bisplrep(x, y, z, kx=kx, ky=ky, s=0.0)
  228. else:
  229. nx, tx, ny, ty, c, fp, ier = dfitpack.regrid_smth(
  230. x, y, z, None, None, None, None,
  231. kx=kx, ky=ky, s=0.0)
  232. self.tck = (tx[:nx], ty[:ny], c[:(nx - kx - 1) * (ny - ky - 1)],
  233. kx, ky)
  234. self.bounds_error = bounds_error
  235. self.fill_value = fill_value
  236. self.x, self.y, self.z = [array(a, copy=copy) for a in (x, y, z)]
  237. self.x_min, self.x_max = np.amin(x), np.amax(x)
  238. self.y_min, self.y_max = np.amin(y), np.amax(y)
  239. @np.deprecate(old_name='interp2d', message=dep_mesg)
  240. def __call__(self, x, y, dx=0, dy=0, assume_sorted=False):
  241. """Interpolate the function.
  242. Parameters
  243. ----------
  244. x : 1-D array
  245. x-coordinates of the mesh on which to interpolate.
  246. y : 1-D array
  247. y-coordinates of the mesh on which to interpolate.
  248. dx : int >= 0, < kx
  249. Order of partial derivatives in x.
  250. dy : int >= 0, < ky
  251. Order of partial derivatives in y.
  252. assume_sorted : bool, optional
  253. If False, values of `x` and `y` can be in any order and they are
  254. sorted first.
  255. If True, `x` and `y` have to be arrays of monotonically
  256. increasing values.
  257. Returns
  258. -------
  259. z : 2-D array with shape (len(y), len(x))
  260. The interpolated values.
  261. """
  262. x = atleast_1d(x)
  263. y = atleast_1d(y)
  264. if x.ndim != 1 or y.ndim != 1:
  265. raise ValueError("x and y should both be 1-D arrays")
  266. if not assume_sorted:
  267. x = np.sort(x, kind="mergesort")
  268. y = np.sort(y, kind="mergesort")
  269. if self.bounds_error or self.fill_value is not None:
  270. out_of_bounds_x = (x < self.x_min) | (x > self.x_max)
  271. out_of_bounds_y = (y < self.y_min) | (y > self.y_max)
  272. any_out_of_bounds_x = np.any(out_of_bounds_x)
  273. any_out_of_bounds_y = np.any(out_of_bounds_y)
  274. if self.bounds_error and (any_out_of_bounds_x or any_out_of_bounds_y):
  275. raise ValueError("Values out of range; x must be in %r, y in %r"
  276. % ((self.x_min, self.x_max),
  277. (self.y_min, self.y_max)))
  278. z = _fitpack_py.bisplev(x, y, self.tck, dx, dy)
  279. z = atleast_2d(z)
  280. z = transpose(z)
  281. if self.fill_value is not None:
  282. if any_out_of_bounds_x:
  283. z[:, out_of_bounds_x] = self.fill_value
  284. if any_out_of_bounds_y:
  285. z[out_of_bounds_y, :] = self.fill_value
  286. if len(z) == 1:
  287. z = z[0]
  288. return array(z)
  289. def _check_broadcast_up_to(arr_from, shape_to, name):
  290. """Helper to check that arr_from broadcasts up to shape_to"""
  291. shape_from = arr_from.shape
  292. if len(shape_to) >= len(shape_from):
  293. for t, f in zip(shape_to[::-1], shape_from[::-1]):
  294. if f != 1 and f != t:
  295. break
  296. else: # all checks pass, do the upcasting that we need later
  297. if arr_from.size != 1 and arr_from.shape != shape_to:
  298. arr_from = np.ones(shape_to, arr_from.dtype) * arr_from
  299. return arr_from.ravel()
  300. # at least one check failed
  301. raise ValueError('%s argument must be able to broadcast up '
  302. 'to shape %s but had shape %s'
  303. % (name, shape_to, shape_from))
  304. def _do_extrapolate(fill_value):
  305. """Helper to check if fill_value == "extrapolate" without warnings"""
  306. return (isinstance(fill_value, str) and
  307. fill_value == 'extrapolate')
  308. class interp1d(_Interpolator1D):
  309. """
  310. Interpolate a 1-D function.
  311. `x` and `y` are arrays of values used to approximate some function f:
  312. ``y = f(x)``. This class returns a function whose call method uses
  313. interpolation to find the value of new points.
  314. Parameters
  315. ----------
  316. x : (N,) array_like
  317. A 1-D array of real values.
  318. y : (...,N,...) array_like
  319. A N-D array of real values. The length of `y` along the interpolation
  320. axis must be equal to the length of `x`.
  321. kind : str or int, optional
  322. Specifies the kind of interpolation as a string or as an integer
  323. specifying the order of the spline interpolator to use.
  324. The string has to be one of 'linear', 'nearest', 'nearest-up', 'zero',
  325. 'slinear', 'quadratic', 'cubic', 'previous', or 'next'. 'zero',
  326. 'slinear', 'quadratic' and 'cubic' refer to a spline interpolation of
  327. zeroth, first, second or third order; 'previous' and 'next' simply
  328. return the previous or next value of the point; 'nearest-up' and
  329. 'nearest' differ when interpolating half-integers (e.g. 0.5, 1.5)
  330. in that 'nearest-up' rounds up and 'nearest' rounds down. Default
  331. is 'linear'.
  332. axis : int, optional
  333. Specifies the axis of `y` along which to interpolate.
  334. Interpolation defaults to the last axis of `y`.
  335. copy : bool, optional
  336. If True, the class makes internal copies of x and y.
  337. If False, references to `x` and `y` are used. The default is to copy.
  338. bounds_error : bool, optional
  339. If True, a ValueError is raised any time interpolation is attempted on
  340. a value outside of the range of x (where extrapolation is
  341. necessary). If False, out of bounds values are assigned `fill_value`.
  342. By default, an error is raised unless ``fill_value="extrapolate"``.
  343. fill_value : array-like or (array-like, array_like) or "extrapolate", optional
  344. - if a ndarray (or float), this value will be used to fill in for
  345. requested points outside of the data range. If not provided, then
  346. the default is NaN. The array-like must broadcast properly to the
  347. dimensions of the non-interpolation axes.
  348. - If a two-element tuple, then the first element is used as a
  349. fill value for ``x_new < x[0]`` and the second element is used for
  350. ``x_new > x[-1]``. Anything that is not a 2-element tuple (e.g.,
  351. list or ndarray, regardless of shape) is taken to be a single
  352. array-like argument meant to be used for both bounds as
  353. ``below, above = fill_value, fill_value``. Using a two-element tuple
  354. or ndarray requires ``bounds_error=False``.
  355. .. versionadded:: 0.17.0
  356. - If "extrapolate", then points outside the data range will be
  357. extrapolated.
  358. .. versionadded:: 0.17.0
  359. assume_sorted : bool, optional
  360. If False, values of `x` can be in any order and they are sorted first.
  361. If True, `x` has to be an array of monotonically increasing values.
  362. Attributes
  363. ----------
  364. fill_value
  365. Methods
  366. -------
  367. __call__
  368. See Also
  369. --------
  370. splrep, splev
  371. Spline interpolation/smoothing based on FITPACK.
  372. UnivariateSpline : An object-oriented wrapper of the FITPACK routines.
  373. interp2d : 2-D interpolation
  374. Notes
  375. -----
  376. Calling `interp1d` with NaNs present in input values results in
  377. undefined behaviour.
  378. Input values `x` and `y` must be convertible to `float` values like
  379. `int` or `float`.
  380. If the values in `x` are not unique, the resulting behavior is
  381. undefined and specific to the choice of `kind`, i.e., changing
  382. `kind` will change the behavior for duplicates.
  383. Examples
  384. --------
  385. >>> import numpy as np
  386. >>> import matplotlib.pyplot as plt
  387. >>> from scipy import interpolate
  388. >>> x = np.arange(0, 10)
  389. >>> y = np.exp(-x/3.0)
  390. >>> f = interpolate.interp1d(x, y)
  391. >>> xnew = np.arange(0, 9, 0.1)
  392. >>> ynew = f(xnew) # use interpolation function returned by `interp1d`
  393. >>> plt.plot(x, y, 'o', xnew, ynew, '-')
  394. >>> plt.show()
  395. """
  396. def __init__(self, x, y, kind='linear', axis=-1,
  397. copy=True, bounds_error=None, fill_value=np.nan,
  398. assume_sorted=False):
  399. """ Initialize a 1-D linear interpolation class."""
  400. _Interpolator1D.__init__(self, x, y, axis=axis)
  401. self.bounds_error = bounds_error # used by fill_value setter
  402. self.copy = copy
  403. if kind in ['zero', 'slinear', 'quadratic', 'cubic']:
  404. order = {'zero': 0, 'slinear': 1,
  405. 'quadratic': 2, 'cubic': 3}[kind]
  406. kind = 'spline'
  407. elif isinstance(kind, int):
  408. order = kind
  409. kind = 'spline'
  410. elif kind not in ('linear', 'nearest', 'nearest-up', 'previous',
  411. 'next'):
  412. raise NotImplementedError("%s is unsupported: Use fitpack "
  413. "routines for other types." % kind)
  414. x = array(x, copy=self.copy)
  415. y = array(y, copy=self.copy)
  416. if not assume_sorted:
  417. ind = np.argsort(x, kind="mergesort")
  418. x = x[ind]
  419. y = np.take(y, ind, axis=axis)
  420. if x.ndim != 1:
  421. raise ValueError("the x array must have exactly one dimension.")
  422. if y.ndim == 0:
  423. raise ValueError("the y array must have at least one dimension.")
  424. # Force-cast y to a floating-point type, if it's not yet one
  425. if not issubclass(y.dtype.type, np.inexact):
  426. y = y.astype(np.float_)
  427. # Backward compatibility
  428. self.axis = axis % y.ndim
  429. # Interpolation goes internally along the first axis
  430. self.y = y
  431. self._y = self._reshape_yi(self.y)
  432. self.x = x
  433. del y, x # clean up namespace to prevent misuse; use attributes
  434. self._kind = kind
  435. # Adjust to interpolation kind; store reference to *unbound*
  436. # interpolation methods, in order to avoid circular references to self
  437. # stored in the bound instance methods, and therefore delayed garbage
  438. # collection. See: https://docs.python.org/reference/datamodel.html
  439. if kind in ('linear', 'nearest', 'nearest-up', 'previous', 'next'):
  440. # Make a "view" of the y array that is rotated to the interpolation
  441. # axis.
  442. minval = 1
  443. if kind == 'nearest':
  444. # Do division before addition to prevent possible integer
  445. # overflow
  446. self._side = 'left'
  447. self.x_bds = self.x / 2.0
  448. self.x_bds = self.x_bds[1:] + self.x_bds[:-1]
  449. self._call = self.__class__._call_nearest
  450. elif kind == 'nearest-up':
  451. # Do division before addition to prevent possible integer
  452. # overflow
  453. self._side = 'right'
  454. self.x_bds = self.x / 2.0
  455. self.x_bds = self.x_bds[1:] + self.x_bds[:-1]
  456. self._call = self.__class__._call_nearest
  457. elif kind == 'previous':
  458. # Side for np.searchsorted and index for clipping
  459. self._side = 'left'
  460. self._ind = 0
  461. # Move x by one floating point value to the left
  462. self._x_shift = np.nextafter(self.x, -np.inf)
  463. self._call = self.__class__._call_previousnext
  464. if _do_extrapolate(fill_value):
  465. self._check_and_update_bounds_error_for_extrapolation()
  466. # assume y is sorted by x ascending order here.
  467. fill_value = (np.nan, np.take(self.y, -1, axis))
  468. elif kind == 'next':
  469. self._side = 'right'
  470. self._ind = 1
  471. # Move x by one floating point value to the right
  472. self._x_shift = np.nextafter(self.x, np.inf)
  473. self._call = self.__class__._call_previousnext
  474. if _do_extrapolate(fill_value):
  475. self._check_and_update_bounds_error_for_extrapolation()
  476. # assume y is sorted by x ascending order here.
  477. fill_value = (np.take(self.y, 0, axis), np.nan)
  478. else:
  479. # Check if we can delegate to numpy.interp (2x-10x faster).
  480. np_types = (np.float_, np.int_)
  481. cond = self.x.dtype in np_types and self.y.dtype in np_types
  482. cond = cond and self.y.ndim == 1
  483. cond = cond and not _do_extrapolate(fill_value)
  484. if cond:
  485. self._call = self.__class__._call_linear_np
  486. else:
  487. self._call = self.__class__._call_linear
  488. else:
  489. minval = order + 1
  490. rewrite_nan = False
  491. xx, yy = self.x, self._y
  492. if order > 1:
  493. # Quadratic or cubic spline. If input contains even a single
  494. # nan, then the output is all nans. We cannot just feed data
  495. # with nans to make_interp_spline because it calls LAPACK.
  496. # So, we make up a bogus x and y with no nans and use it
  497. # to get the correct shape of the output, which we then fill
  498. # with nans.
  499. # For slinear or zero order spline, we just pass nans through.
  500. mask = np.isnan(self.x)
  501. if mask.any():
  502. sx = self.x[~mask]
  503. if sx.size == 0:
  504. raise ValueError("`x` array is all-nan")
  505. xx = np.linspace(np.nanmin(self.x),
  506. np.nanmax(self.x),
  507. len(self.x))
  508. rewrite_nan = True
  509. if np.isnan(self._y).any():
  510. yy = np.ones_like(self._y)
  511. rewrite_nan = True
  512. self._spline = make_interp_spline(xx, yy, k=order,
  513. check_finite=False)
  514. if rewrite_nan:
  515. self._call = self.__class__._call_nan_spline
  516. else:
  517. self._call = self.__class__._call_spline
  518. if len(self.x) < minval:
  519. raise ValueError("x and y arrays must have at "
  520. "least %d entries" % minval)
  521. self.fill_value = fill_value # calls the setter, can modify bounds_err
  522. @property
  523. def fill_value(self):
  524. """The fill value."""
  525. # backwards compat: mimic a public attribute
  526. return self._fill_value_orig
  527. @fill_value.setter
  528. def fill_value(self, fill_value):
  529. # extrapolation only works for nearest neighbor and linear methods
  530. if _do_extrapolate(fill_value):
  531. self._check_and_update_bounds_error_for_extrapolation()
  532. self._extrapolate = True
  533. else:
  534. broadcast_shape = (self.y.shape[:self.axis] +
  535. self.y.shape[self.axis + 1:])
  536. if len(broadcast_shape) == 0:
  537. broadcast_shape = (1,)
  538. # it's either a pair (_below_range, _above_range) or a single value
  539. # for both above and below range
  540. if isinstance(fill_value, tuple) and len(fill_value) == 2:
  541. below_above = [np.asarray(fill_value[0]),
  542. np.asarray(fill_value[1])]
  543. names = ('fill_value (below)', 'fill_value (above)')
  544. for ii in range(2):
  545. below_above[ii] = _check_broadcast_up_to(
  546. below_above[ii], broadcast_shape, names[ii])
  547. else:
  548. fill_value = np.asarray(fill_value)
  549. below_above = [_check_broadcast_up_to(
  550. fill_value, broadcast_shape, 'fill_value')] * 2
  551. self._fill_value_below, self._fill_value_above = below_above
  552. self._extrapolate = False
  553. if self.bounds_error is None:
  554. self.bounds_error = True
  555. # backwards compat: fill_value was a public attr; make it writeable
  556. self._fill_value_orig = fill_value
  557. def _check_and_update_bounds_error_for_extrapolation(self):
  558. if self.bounds_error:
  559. raise ValueError("Cannot extrapolate and raise "
  560. "at the same time.")
  561. self.bounds_error = False
  562. def _call_linear_np(self, x_new):
  563. # Note that out-of-bounds values are taken care of in self._evaluate
  564. return np.interp(x_new, self.x, self.y)
  565. def _call_linear(self, x_new):
  566. # 2. Find where in the original data, the values to interpolate
  567. # would be inserted.
  568. # Note: If x_new[n] == x[m], then m is returned by searchsorted.
  569. x_new_indices = searchsorted(self.x, x_new)
  570. # 3. Clip x_new_indices so that they are within the range of
  571. # self.x indices and at least 1. Removes mis-interpolation
  572. # of x_new[n] = x[0]
  573. x_new_indices = x_new_indices.clip(1, len(self.x)-1).astype(int)
  574. # 4. Calculate the slope of regions that each x_new value falls in.
  575. lo = x_new_indices - 1
  576. hi = x_new_indices
  577. x_lo = self.x[lo]
  578. x_hi = self.x[hi]
  579. y_lo = self._y[lo]
  580. y_hi = self._y[hi]
  581. # Note that the following two expressions rely on the specifics of the
  582. # broadcasting semantics.
  583. slope = (y_hi - y_lo) / (x_hi - x_lo)[:, None]
  584. # 5. Calculate the actual value for each entry in x_new.
  585. y_new = slope*(x_new - x_lo)[:, None] + y_lo
  586. return y_new
  587. def _call_nearest(self, x_new):
  588. """ Find nearest neighbor interpolated y_new = f(x_new)."""
  589. # 2. Find where in the averaged data the values to interpolate
  590. # would be inserted.
  591. # Note: use side='left' (right) to searchsorted() to define the
  592. # halfway point to be nearest to the left (right) neighbor
  593. x_new_indices = searchsorted(self.x_bds, x_new, side=self._side)
  594. # 3. Clip x_new_indices so that they are within the range of x indices.
  595. x_new_indices = x_new_indices.clip(0, len(self.x)-1).astype(intp)
  596. # 4. Calculate the actual value for each entry in x_new.
  597. y_new = self._y[x_new_indices]
  598. return y_new
  599. def _call_previousnext(self, x_new):
  600. """Use previous/next neighbor of x_new, y_new = f(x_new)."""
  601. # 1. Get index of left/right value
  602. x_new_indices = searchsorted(self._x_shift, x_new, side=self._side)
  603. # 2. Clip x_new_indices so that they are within the range of x indices.
  604. x_new_indices = x_new_indices.clip(1-self._ind,
  605. len(self.x)-self._ind).astype(intp)
  606. # 3. Calculate the actual value for each entry in x_new.
  607. y_new = self._y[x_new_indices+self._ind-1]
  608. return y_new
  609. def _call_spline(self, x_new):
  610. return self._spline(x_new)
  611. def _call_nan_spline(self, x_new):
  612. out = self._spline(x_new)
  613. out[...] = np.nan
  614. return out
  615. def _evaluate(self, x_new):
  616. # 1. Handle values in x_new that are outside of x. Throw error,
  617. # or return a list of mask array indicating the outofbounds values.
  618. # The behavior is set by the bounds_error variable.
  619. x_new = asarray(x_new)
  620. y_new = self._call(self, x_new)
  621. if not self._extrapolate:
  622. below_bounds, above_bounds = self._check_bounds(x_new)
  623. if len(y_new) > 0:
  624. # Note fill_value must be broadcast up to the proper size
  625. # and flattened to work here
  626. y_new[below_bounds] = self._fill_value_below
  627. y_new[above_bounds] = self._fill_value_above
  628. return y_new
  629. def _check_bounds(self, x_new):
  630. """Check the inputs for being in the bounds of the interpolated data.
  631. Parameters
  632. ----------
  633. x_new : array
  634. Returns
  635. -------
  636. out_of_bounds : bool array
  637. The mask on x_new of values that are out of the bounds.
  638. """
  639. # If self.bounds_error is True, we raise an error if any x_new values
  640. # fall outside the range of x. Otherwise, we return an array indicating
  641. # which values are outside the boundary region.
  642. below_bounds = x_new < self.x[0]
  643. above_bounds = x_new > self.x[-1]
  644. if self.bounds_error and below_bounds.any():
  645. below_bounds_value = x_new[np.argmax(below_bounds)]
  646. raise ValueError("A value ({}) in x_new is below "
  647. "the interpolation range's minimum value ({})."
  648. .format(below_bounds_value, self.x[0]))
  649. if self.bounds_error and above_bounds.any():
  650. above_bounds_value = x_new[np.argmax(above_bounds)]
  651. raise ValueError("A value ({}) in x_new is above "
  652. "the interpolation range's maximum value ({})."
  653. .format(above_bounds_value, self.x[-1]))
  654. # !! Should we emit a warning if some values are out of bounds?
  655. # !! matlab does not.
  656. return below_bounds, above_bounds
  657. class _PPolyBase:
  658. """Base class for piecewise polynomials."""
  659. __slots__ = ('c', 'x', 'extrapolate', 'axis')
  660. def __init__(self, c, x, extrapolate=None, axis=0):
  661. self.c = np.asarray(c)
  662. self.x = np.ascontiguousarray(x, dtype=np.float64)
  663. if extrapolate is None:
  664. extrapolate = True
  665. elif extrapolate != 'periodic':
  666. extrapolate = bool(extrapolate)
  667. self.extrapolate = extrapolate
  668. if self.c.ndim < 2:
  669. raise ValueError("Coefficients array must be at least "
  670. "2-dimensional.")
  671. if not (0 <= axis < self.c.ndim - 1):
  672. raise ValueError("axis=%s must be between 0 and %s" %
  673. (axis, self.c.ndim-1))
  674. self.axis = axis
  675. if axis != 0:
  676. # move the interpolation axis to be the first one in self.c
  677. # More specifically, the target shape for self.c is (k, m, ...),
  678. # and axis !=0 means that we have c.shape (..., k, m, ...)
  679. # ^
  680. # axis
  681. # So we roll two of them.
  682. self.c = np.moveaxis(self.c, axis+1, 0)
  683. self.c = np.moveaxis(self.c, axis+1, 0)
  684. if self.x.ndim != 1:
  685. raise ValueError("x must be 1-dimensional")
  686. if self.x.size < 2:
  687. raise ValueError("at least 2 breakpoints are needed")
  688. if self.c.ndim < 2:
  689. raise ValueError("c must have at least 2 dimensions")
  690. if self.c.shape[0] == 0:
  691. raise ValueError("polynomial must be at least of order 0")
  692. if self.c.shape[1] != self.x.size-1:
  693. raise ValueError("number of coefficients != len(x)-1")
  694. dx = np.diff(self.x)
  695. if not (np.all(dx >= 0) or np.all(dx <= 0)):
  696. raise ValueError("`x` must be strictly increasing or decreasing.")
  697. dtype = self._get_dtype(self.c.dtype)
  698. self.c = np.ascontiguousarray(self.c, dtype=dtype)
  699. def _get_dtype(self, dtype):
  700. if np.issubdtype(dtype, np.complexfloating) \
  701. or np.issubdtype(self.c.dtype, np.complexfloating):
  702. return np.complex_
  703. else:
  704. return np.float_
  705. @classmethod
  706. def construct_fast(cls, c, x, extrapolate=None, axis=0):
  707. """
  708. Construct the piecewise polynomial without making checks.
  709. Takes the same parameters as the constructor. Input arguments
  710. ``c`` and ``x`` must be arrays of the correct shape and type. The
  711. ``c`` array can only be of dtypes float and complex, and ``x``
  712. array must have dtype float.
  713. """
  714. self = object.__new__(cls)
  715. self.c = c
  716. self.x = x
  717. self.axis = axis
  718. if extrapolate is None:
  719. extrapolate = True
  720. self.extrapolate = extrapolate
  721. return self
  722. def _ensure_c_contiguous(self):
  723. """
  724. c and x may be modified by the user. The Cython code expects
  725. that they are C contiguous.
  726. """
  727. if not self.x.flags.c_contiguous:
  728. self.x = self.x.copy()
  729. if not self.c.flags.c_contiguous:
  730. self.c = self.c.copy()
  731. def extend(self, c, x):
  732. """
  733. Add additional breakpoints and coefficients to the polynomial.
  734. Parameters
  735. ----------
  736. c : ndarray, size (k, m, ...)
  737. Additional coefficients for polynomials in intervals. Note that
  738. the first additional interval will be formed using one of the
  739. ``self.x`` end points.
  740. x : ndarray, size (m,)
  741. Additional breakpoints. Must be sorted in the same order as
  742. ``self.x`` and either to the right or to the left of the current
  743. breakpoints.
  744. """
  745. c = np.asarray(c)
  746. x = np.asarray(x)
  747. if c.ndim < 2:
  748. raise ValueError("invalid dimensions for c")
  749. if x.ndim != 1:
  750. raise ValueError("invalid dimensions for x")
  751. if x.shape[0] != c.shape[1]:
  752. raise ValueError("Shapes of x {} and c {} are incompatible"
  753. .format(x.shape, c.shape))
  754. if c.shape[2:] != self.c.shape[2:] or c.ndim != self.c.ndim:
  755. raise ValueError("Shapes of c {} and self.c {} are incompatible"
  756. .format(c.shape, self.c.shape))
  757. if c.size == 0:
  758. return
  759. dx = np.diff(x)
  760. if not (np.all(dx >= 0) or np.all(dx <= 0)):
  761. raise ValueError("`x` is not sorted.")
  762. if self.x[-1] >= self.x[0]:
  763. if not x[-1] >= x[0]:
  764. raise ValueError("`x` is in the different order "
  765. "than `self.x`.")
  766. if x[0] >= self.x[-1]:
  767. action = 'append'
  768. elif x[-1] <= self.x[0]:
  769. action = 'prepend'
  770. else:
  771. raise ValueError("`x` is neither on the left or on the right "
  772. "from `self.x`.")
  773. else:
  774. if not x[-1] <= x[0]:
  775. raise ValueError("`x` is in the different order "
  776. "than `self.x`.")
  777. if x[0] <= self.x[-1]:
  778. action = 'append'
  779. elif x[-1] >= self.x[0]:
  780. action = 'prepend'
  781. else:
  782. raise ValueError("`x` is neither on the left or on the right "
  783. "from `self.x`.")
  784. dtype = self._get_dtype(c.dtype)
  785. k2 = max(c.shape[0], self.c.shape[0])
  786. c2 = np.zeros((k2, self.c.shape[1] + c.shape[1]) + self.c.shape[2:],
  787. dtype=dtype)
  788. if action == 'append':
  789. c2[k2-self.c.shape[0]:, :self.c.shape[1]] = self.c
  790. c2[k2-c.shape[0]:, self.c.shape[1]:] = c
  791. self.x = np.r_[self.x, x]
  792. elif action == 'prepend':
  793. c2[k2-self.c.shape[0]:, :c.shape[1]] = c
  794. c2[k2-c.shape[0]:, c.shape[1]:] = self.c
  795. self.x = np.r_[x, self.x]
  796. self.c = c2
  797. def __call__(self, x, nu=0, extrapolate=None):
  798. """
  799. Evaluate the piecewise polynomial or its derivative.
  800. Parameters
  801. ----------
  802. x : array_like
  803. Points to evaluate the interpolant at.
  804. nu : int, optional
  805. Order of derivative to evaluate. Must be non-negative.
  806. extrapolate : {bool, 'periodic', None}, optional
  807. If bool, determines whether to extrapolate to out-of-bounds points
  808. based on first and last intervals, or to return NaNs.
  809. If 'periodic', periodic extrapolation is used.
  810. If None (default), use `self.extrapolate`.
  811. Returns
  812. -------
  813. y : array_like
  814. Interpolated values. Shape is determined by replacing
  815. the interpolation axis in the original array with the shape of x.
  816. Notes
  817. -----
  818. Derivatives are evaluated piecewise for each polynomial
  819. segment, even if the polynomial is not differentiable at the
  820. breakpoints. The polynomial intervals are considered half-open,
  821. ``[a, b)``, except for the last interval which is closed
  822. ``[a, b]``.
  823. """
  824. if extrapolate is None:
  825. extrapolate = self.extrapolate
  826. x = np.asarray(x)
  827. x_shape, x_ndim = x.shape, x.ndim
  828. x = np.ascontiguousarray(x.ravel(), dtype=np.float_)
  829. # With periodic extrapolation we map x to the segment
  830. # [self.x[0], self.x[-1]].
  831. if extrapolate == 'periodic':
  832. x = self.x[0] + (x - self.x[0]) % (self.x[-1] - self.x[0])
  833. extrapolate = False
  834. out = np.empty((len(x), prod(self.c.shape[2:])), dtype=self.c.dtype)
  835. self._ensure_c_contiguous()
  836. self._evaluate(x, nu, extrapolate, out)
  837. out = out.reshape(x_shape + self.c.shape[2:])
  838. if self.axis != 0:
  839. # transpose to move the calculated values to the interpolation axis
  840. l = list(range(out.ndim))
  841. l = l[x_ndim:x_ndim+self.axis] + l[:x_ndim] + l[x_ndim+self.axis:]
  842. out = out.transpose(l)
  843. return out
  844. class PPoly(_PPolyBase):
  845. """
  846. Piecewise polynomial in terms of coefficients and breakpoints
  847. The polynomial between ``x[i]`` and ``x[i + 1]`` is written in the
  848. local power basis::
  849. S = sum(c[m, i] * (xp - x[i])**(k-m) for m in range(k+1))
  850. where ``k`` is the degree of the polynomial.
  851. Parameters
  852. ----------
  853. c : ndarray, shape (k, m, ...)
  854. Polynomial coefficients, order `k` and `m` intervals.
  855. x : ndarray, shape (m+1,)
  856. Polynomial breakpoints. Must be sorted in either increasing or
  857. decreasing order.
  858. extrapolate : bool or 'periodic', optional
  859. If bool, determines whether to extrapolate to out-of-bounds points
  860. based on first and last intervals, or to return NaNs. If 'periodic',
  861. periodic extrapolation is used. Default is True.
  862. axis : int, optional
  863. Interpolation axis. Default is zero.
  864. Attributes
  865. ----------
  866. x : ndarray
  867. Breakpoints.
  868. c : ndarray
  869. Coefficients of the polynomials. They are reshaped
  870. to a 3-D array with the last dimension representing
  871. the trailing dimensions of the original coefficient array.
  872. axis : int
  873. Interpolation axis.
  874. Methods
  875. -------
  876. __call__
  877. derivative
  878. antiderivative
  879. integrate
  880. solve
  881. roots
  882. extend
  883. from_spline
  884. from_bernstein_basis
  885. construct_fast
  886. See also
  887. --------
  888. BPoly : piecewise polynomials in the Bernstein basis
  889. Notes
  890. -----
  891. High-order polynomials in the power basis can be numerically
  892. unstable. Precision problems can start to appear for orders
  893. larger than 20-30.
  894. """
  895. def _evaluate(self, x, nu, extrapolate, out):
  896. _ppoly.evaluate(self.c.reshape(self.c.shape[0], self.c.shape[1], -1),
  897. self.x, x, nu, bool(extrapolate), out)
  898. def derivative(self, nu=1):
  899. """
  900. Construct a new piecewise polynomial representing the derivative.
  901. Parameters
  902. ----------
  903. nu : int, optional
  904. Order of derivative to evaluate. Default is 1, i.e., compute the
  905. first derivative. If negative, the antiderivative is returned.
  906. Returns
  907. -------
  908. pp : PPoly
  909. Piecewise polynomial of order k2 = k - n representing the derivative
  910. of this polynomial.
  911. Notes
  912. -----
  913. Derivatives are evaluated piecewise for each polynomial
  914. segment, even if the polynomial is not differentiable at the
  915. breakpoints. The polynomial intervals are considered half-open,
  916. ``[a, b)``, except for the last interval which is closed
  917. ``[a, b]``.
  918. """
  919. if nu < 0:
  920. return self.antiderivative(-nu)
  921. # reduce order
  922. if nu == 0:
  923. c2 = self.c.copy()
  924. else:
  925. c2 = self.c[:-nu, :].copy()
  926. if c2.shape[0] == 0:
  927. # derivative of order 0 is zero
  928. c2 = np.zeros((1,) + c2.shape[1:], dtype=c2.dtype)
  929. # multiply by the correct rising factorials
  930. factor = spec.poch(np.arange(c2.shape[0], 0, -1), nu)
  931. c2 *= factor[(slice(None),) + (None,)*(c2.ndim-1)]
  932. # construct a compatible polynomial
  933. return self.construct_fast(c2, self.x, self.extrapolate, self.axis)
  934. def antiderivative(self, nu=1):
  935. """
  936. Construct a new piecewise polynomial representing the antiderivative.
  937. Antiderivative is also the indefinite integral of the function,
  938. and derivative is its inverse operation.
  939. Parameters
  940. ----------
  941. nu : int, optional
  942. Order of antiderivative to evaluate. Default is 1, i.e., compute
  943. the first integral. If negative, the derivative is returned.
  944. Returns
  945. -------
  946. pp : PPoly
  947. Piecewise polynomial of order k2 = k + n representing
  948. the antiderivative of this polynomial.
  949. Notes
  950. -----
  951. The antiderivative returned by this function is continuous and
  952. continuously differentiable to order n-1, up to floating point
  953. rounding error.
  954. If antiderivative is computed and ``self.extrapolate='periodic'``,
  955. it will be set to False for the returned instance. This is done because
  956. the antiderivative is no longer periodic and its correct evaluation
  957. outside of the initially given x interval is difficult.
  958. """
  959. if nu <= 0:
  960. return self.derivative(-nu)
  961. c = np.zeros((self.c.shape[0] + nu, self.c.shape[1]) + self.c.shape[2:],
  962. dtype=self.c.dtype)
  963. c[:-nu] = self.c
  964. # divide by the correct rising factorials
  965. factor = spec.poch(np.arange(self.c.shape[0], 0, -1), nu)
  966. c[:-nu] /= factor[(slice(None),) + (None,)*(c.ndim-1)]
  967. # fix continuity of added degrees of freedom
  968. self._ensure_c_contiguous()
  969. _ppoly.fix_continuity(c.reshape(c.shape[0], c.shape[1], -1),
  970. self.x, nu - 1)
  971. if self.extrapolate == 'periodic':
  972. extrapolate = False
  973. else:
  974. extrapolate = self.extrapolate
  975. # construct a compatible polynomial
  976. return self.construct_fast(c, self.x, extrapolate, self.axis)
  977. def integrate(self, a, b, extrapolate=None):
  978. """
  979. Compute a definite integral over a piecewise polynomial.
  980. Parameters
  981. ----------
  982. a : float
  983. Lower integration bound
  984. b : float
  985. Upper integration bound
  986. extrapolate : {bool, 'periodic', None}, optional
  987. If bool, determines whether to extrapolate to out-of-bounds points
  988. based on first and last intervals, or to return NaNs.
  989. If 'periodic', periodic extrapolation is used.
  990. If None (default), use `self.extrapolate`.
  991. Returns
  992. -------
  993. ig : array_like
  994. Definite integral of the piecewise polynomial over [a, b]
  995. """
  996. if extrapolate is None:
  997. extrapolate = self.extrapolate
  998. # Swap integration bounds if needed
  999. sign = 1
  1000. if b < a:
  1001. a, b = b, a
  1002. sign = -1
  1003. range_int = np.empty((prod(self.c.shape[2:]),), dtype=self.c.dtype)
  1004. self._ensure_c_contiguous()
  1005. # Compute the integral.
  1006. if extrapolate == 'periodic':
  1007. # Split the integral into the part over period (can be several
  1008. # of them) and the remaining part.
  1009. xs, xe = self.x[0], self.x[-1]
  1010. period = xe - xs
  1011. interval = b - a
  1012. n_periods, left = divmod(interval, period)
  1013. if n_periods > 0:
  1014. _ppoly.integrate(
  1015. self.c.reshape(self.c.shape[0], self.c.shape[1], -1),
  1016. self.x, xs, xe, False, out=range_int)
  1017. range_int *= n_periods
  1018. else:
  1019. range_int.fill(0)
  1020. # Map a to [xs, xe], b is always a + left.
  1021. a = xs + (a - xs) % period
  1022. b = a + left
  1023. # If b <= xe then we need to integrate over [a, b], otherwise
  1024. # over [a, xe] and from xs to what is remained.
  1025. remainder_int = np.empty_like(range_int)
  1026. if b <= xe:
  1027. _ppoly.integrate(
  1028. self.c.reshape(self.c.shape[0], self.c.shape[1], -1),
  1029. self.x, a, b, False, out=remainder_int)
  1030. range_int += remainder_int
  1031. else:
  1032. _ppoly.integrate(
  1033. self.c.reshape(self.c.shape[0], self.c.shape[1], -1),
  1034. self.x, a, xe, False, out=remainder_int)
  1035. range_int += remainder_int
  1036. _ppoly.integrate(
  1037. self.c.reshape(self.c.shape[0], self.c.shape[1], -1),
  1038. self.x, xs, xs + left + a - xe, False, out=remainder_int)
  1039. range_int += remainder_int
  1040. else:
  1041. _ppoly.integrate(
  1042. self.c.reshape(self.c.shape[0], self.c.shape[1], -1),
  1043. self.x, a, b, bool(extrapolate), out=range_int)
  1044. # Return
  1045. range_int *= sign
  1046. return range_int.reshape(self.c.shape[2:])
  1047. def solve(self, y=0., discontinuity=True, extrapolate=None):
  1048. """
  1049. Find real solutions of the equation ``pp(x) == y``.
  1050. Parameters
  1051. ----------
  1052. y : float, optional
  1053. Right-hand side. Default is zero.
  1054. discontinuity : bool, optional
  1055. Whether to report sign changes across discontinuities at
  1056. breakpoints as roots.
  1057. extrapolate : {bool, 'periodic', None}, optional
  1058. If bool, determines whether to return roots from the polynomial
  1059. extrapolated based on first and last intervals, 'periodic' works
  1060. the same as False. If None (default), use `self.extrapolate`.
  1061. Returns
  1062. -------
  1063. roots : ndarray
  1064. Roots of the polynomial(s).
  1065. If the PPoly object describes multiple polynomials, the
  1066. return value is an object array whose each element is an
  1067. ndarray containing the roots.
  1068. Notes
  1069. -----
  1070. This routine works only on real-valued polynomials.
  1071. If the piecewise polynomial contains sections that are
  1072. identically zero, the root list will contain the start point
  1073. of the corresponding interval, followed by a ``nan`` value.
  1074. If the polynomial is discontinuous across a breakpoint, and
  1075. there is a sign change across the breakpoint, this is reported
  1076. if the `discont` parameter is True.
  1077. Examples
  1078. --------
  1079. Finding roots of ``[x**2 - 1, (x - 1)**2]`` defined on intervals
  1080. ``[-2, 1], [1, 2]``:
  1081. >>> import numpy as np
  1082. >>> from scipy.interpolate import PPoly
  1083. >>> pp = PPoly(np.array([[1, -4, 3], [1, 0, 0]]).T, [-2, 1, 2])
  1084. >>> pp.solve()
  1085. array([-1., 1.])
  1086. """
  1087. if extrapolate is None:
  1088. extrapolate = self.extrapolate
  1089. self._ensure_c_contiguous()
  1090. if np.issubdtype(self.c.dtype, np.complexfloating):
  1091. raise ValueError("Root finding is only for "
  1092. "real-valued polynomials")
  1093. y = float(y)
  1094. r = _ppoly.real_roots(self.c.reshape(self.c.shape[0], self.c.shape[1], -1),
  1095. self.x, y, bool(discontinuity),
  1096. bool(extrapolate))
  1097. if self.c.ndim == 2:
  1098. return r[0]
  1099. else:
  1100. r2 = np.empty(prod(self.c.shape[2:]), dtype=object)
  1101. # this for-loop is equivalent to ``r2[...] = r``, but that's broken
  1102. # in NumPy 1.6.0
  1103. for ii, root in enumerate(r):
  1104. r2[ii] = root
  1105. return r2.reshape(self.c.shape[2:])
  1106. def roots(self, discontinuity=True, extrapolate=None):
  1107. """
  1108. Find real roots of the piecewise polynomial.
  1109. Parameters
  1110. ----------
  1111. discontinuity : bool, optional
  1112. Whether to report sign changes across discontinuities at
  1113. breakpoints as roots.
  1114. extrapolate : {bool, 'periodic', None}, optional
  1115. If bool, determines whether to return roots from the polynomial
  1116. extrapolated based on first and last intervals, 'periodic' works
  1117. the same as False. If None (default), use `self.extrapolate`.
  1118. Returns
  1119. -------
  1120. roots : ndarray
  1121. Roots of the polynomial(s).
  1122. If the PPoly object describes multiple polynomials, the
  1123. return value is an object array whose each element is an
  1124. ndarray containing the roots.
  1125. See Also
  1126. --------
  1127. PPoly.solve
  1128. """
  1129. return self.solve(0, discontinuity, extrapolate)
  1130. @classmethod
  1131. def from_spline(cls, tck, extrapolate=None):
  1132. """
  1133. Construct a piecewise polynomial from a spline
  1134. Parameters
  1135. ----------
  1136. tck
  1137. A spline, as returned by `splrep` or a BSpline object.
  1138. extrapolate : bool or 'periodic', optional
  1139. If bool, determines whether to extrapolate to out-of-bounds points
  1140. based on first and last intervals, or to return NaNs.
  1141. If 'periodic', periodic extrapolation is used. Default is True.
  1142. Examples
  1143. --------
  1144. Construct an interpolating spline and convert it to a `PPoly` instance
  1145. >>> import numpy as np
  1146. >>> from scipy.interpolate import splrep, PPoly
  1147. >>> x = np.linspace(0, 1, 11)
  1148. >>> y = np.sin(2*np.pi*x)
  1149. >>> tck = splrep(x, y, s=0)
  1150. >>> p = PPoly.from_spline(tck)
  1151. >>> isinstance(p, PPoly)
  1152. True
  1153. Note that this function only supports 1D splines out of the box.
  1154. If the ``tck`` object represents a parametric spline (e.g. constructed
  1155. by `splprep` or a `BSpline` with ``c.ndim > 1``), you will need to loop
  1156. over the dimensions manually.
  1157. >>> from scipy.interpolate import splprep, splev
  1158. >>> t = np.linspace(0, 1, 11)
  1159. >>> x = np.sin(2*np.pi*t)
  1160. >>> y = np.cos(2*np.pi*t)
  1161. >>> (t, c, k), u = splprep([x, y], s=0)
  1162. Note that ``c`` is a list of two arrays of length 11.
  1163. >>> unew = np.arange(0, 1.01, 0.01)
  1164. >>> out = splev(unew, (t, c, k))
  1165. To convert this spline to the power basis, we convert each
  1166. component of the list of b-spline coefficients, ``c``, into the
  1167. corresponding cubic polynomial.
  1168. >>> polys = [PPoly.from_spline((t, cj, k)) for cj in c]
  1169. >>> polys[0].c.shape
  1170. (4, 14)
  1171. Note that the coefficients of the polynomials `polys` are in the
  1172. power basis and their dimensions reflect just that: here 4 is the order
  1173. (degree+1), and 14 is the number of intervals---which is nothing but
  1174. the length of the knot array of the original `tck` minus one.
  1175. Optionally, we can stack the components into a single `PPoly` along
  1176. the third dimension:
  1177. >>> cc = np.dstack([p.c for p in polys]) # has shape = (4, 14, 2)
  1178. >>> poly = PPoly(cc, polys[0].x)
  1179. >>> np.allclose(poly(unew).T, # note the transpose to match `splev`
  1180. ... out, atol=1e-15)
  1181. True
  1182. """
  1183. if isinstance(tck, BSpline):
  1184. t, c, k = tck.tck
  1185. if extrapolate is None:
  1186. extrapolate = tck.extrapolate
  1187. else:
  1188. t, c, k = tck
  1189. cvals = np.empty((k + 1, len(t)-1), dtype=c.dtype)
  1190. for m in range(k, -1, -1):
  1191. y = _fitpack_py.splev(t[:-1], tck, der=m)
  1192. cvals[k - m, :] = y/spec.gamma(m+1)
  1193. return cls.construct_fast(cvals, t, extrapolate)
  1194. @classmethod
  1195. def from_bernstein_basis(cls, bp, extrapolate=None):
  1196. """
  1197. Construct a piecewise polynomial in the power basis
  1198. from a polynomial in Bernstein basis.
  1199. Parameters
  1200. ----------
  1201. bp : BPoly
  1202. A Bernstein basis polynomial, as created by BPoly
  1203. extrapolate : bool or 'periodic', optional
  1204. If bool, determines whether to extrapolate to out-of-bounds points
  1205. based on first and last intervals, or to return NaNs.
  1206. If 'periodic', periodic extrapolation is used. Default is True.
  1207. """
  1208. if not isinstance(bp, BPoly):
  1209. raise TypeError(".from_bernstein_basis only accepts BPoly instances. "
  1210. "Got %s instead." % type(bp))
  1211. dx = np.diff(bp.x)
  1212. k = bp.c.shape[0] - 1 # polynomial order
  1213. rest = (None,)*(bp.c.ndim-2)
  1214. c = np.zeros_like(bp.c)
  1215. for a in range(k+1):
  1216. factor = (-1)**a * comb(k, a) * bp.c[a]
  1217. for s in range(a, k+1):
  1218. val = comb(k-a, s-a) * (-1)**s
  1219. c[k-s] += factor * val / dx[(slice(None),)+rest]**s
  1220. if extrapolate is None:
  1221. extrapolate = bp.extrapolate
  1222. return cls.construct_fast(c, bp.x, extrapolate, bp.axis)
  1223. class BPoly(_PPolyBase):
  1224. """Piecewise polynomial in terms of coefficients and breakpoints.
  1225. The polynomial between ``x[i]`` and ``x[i + 1]`` is written in the
  1226. Bernstein polynomial basis::
  1227. S = sum(c[a, i] * b(a, k; x) for a in range(k+1)),
  1228. where ``k`` is the degree of the polynomial, and::
  1229. b(a, k; x) = binom(k, a) * t**a * (1 - t)**(k - a),
  1230. with ``t = (x - x[i]) / (x[i+1] - x[i])`` and ``binom`` is the binomial
  1231. coefficient.
  1232. Parameters
  1233. ----------
  1234. c : ndarray, shape (k, m, ...)
  1235. Polynomial coefficients, order `k` and `m` intervals
  1236. x : ndarray, shape (m+1,)
  1237. Polynomial breakpoints. Must be sorted in either increasing or
  1238. decreasing order.
  1239. extrapolate : bool, optional
  1240. If bool, determines whether to extrapolate to out-of-bounds points
  1241. based on first and last intervals, or to return NaNs. If 'periodic',
  1242. periodic extrapolation is used. Default is True.
  1243. axis : int, optional
  1244. Interpolation axis. Default is zero.
  1245. Attributes
  1246. ----------
  1247. x : ndarray
  1248. Breakpoints.
  1249. c : ndarray
  1250. Coefficients of the polynomials. They are reshaped
  1251. to a 3-D array with the last dimension representing
  1252. the trailing dimensions of the original coefficient array.
  1253. axis : int
  1254. Interpolation axis.
  1255. Methods
  1256. -------
  1257. __call__
  1258. extend
  1259. derivative
  1260. antiderivative
  1261. integrate
  1262. construct_fast
  1263. from_power_basis
  1264. from_derivatives
  1265. See also
  1266. --------
  1267. PPoly : piecewise polynomials in the power basis
  1268. Notes
  1269. -----
  1270. Properties of Bernstein polynomials are well documented in the literature,
  1271. see for example [1]_ [2]_ [3]_.
  1272. References
  1273. ----------
  1274. .. [1] https://en.wikipedia.org/wiki/Bernstein_polynomial
  1275. .. [2] Kenneth I. Joy, Bernstein polynomials,
  1276. http://www.idav.ucdavis.edu/education/CAGDNotes/Bernstein-Polynomials.pdf
  1277. .. [3] E. H. Doha, A. H. Bhrawy, and M. A. Saker, Boundary Value Problems,
  1278. vol 2011, article ID 829546, :doi:`10.1155/2011/829543`.
  1279. Examples
  1280. --------
  1281. >>> from scipy.interpolate import BPoly
  1282. >>> x = [0, 1]
  1283. >>> c = [[1], [2], [3]]
  1284. >>> bp = BPoly(c, x)
  1285. This creates a 2nd order polynomial
  1286. .. math::
  1287. B(x) = 1 \\times b_{0, 2}(x) + 2 \\times b_{1, 2}(x) + 3 \\times b_{2, 2}(x) \\\\
  1288. = 1 \\times (1-x)^2 + 2 \\times 2 x (1 - x) + 3 \\times x^2
  1289. """
  1290. def _evaluate(self, x, nu, extrapolate, out):
  1291. _ppoly.evaluate_bernstein(
  1292. self.c.reshape(self.c.shape[0], self.c.shape[1], -1),
  1293. self.x, x, nu, bool(extrapolate), out)
  1294. def derivative(self, nu=1):
  1295. """
  1296. Construct a new piecewise polynomial representing the derivative.
  1297. Parameters
  1298. ----------
  1299. nu : int, optional
  1300. Order of derivative to evaluate. Default is 1, i.e., compute the
  1301. first derivative. If negative, the antiderivative is returned.
  1302. Returns
  1303. -------
  1304. bp : BPoly
  1305. Piecewise polynomial of order k - nu representing the derivative of
  1306. this polynomial.
  1307. """
  1308. if nu < 0:
  1309. return self.antiderivative(-nu)
  1310. if nu > 1:
  1311. bp = self
  1312. for k in range(nu):
  1313. bp = bp.derivative()
  1314. return bp
  1315. # reduce order
  1316. if nu == 0:
  1317. c2 = self.c.copy()
  1318. else:
  1319. # For a polynomial
  1320. # B(x) = \sum_{a=0}^{k} c_a b_{a, k}(x),
  1321. # we use the fact that
  1322. # b'_{a, k} = k ( b_{a-1, k-1} - b_{a, k-1} ),
  1323. # which leads to
  1324. # B'(x) = \sum_{a=0}^{k-1} (c_{a+1} - c_a) b_{a, k-1}
  1325. #
  1326. # finally, for an interval [y, y + dy] with dy != 1,
  1327. # we need to correct for an extra power of dy
  1328. rest = (None,)*(self.c.ndim-2)
  1329. k = self.c.shape[0] - 1
  1330. dx = np.diff(self.x)[(None, slice(None))+rest]
  1331. c2 = k * np.diff(self.c, axis=0) / dx
  1332. if c2.shape[0] == 0:
  1333. # derivative of order 0 is zero
  1334. c2 = np.zeros((1,) + c2.shape[1:], dtype=c2.dtype)
  1335. # construct a compatible polynomial
  1336. return self.construct_fast(c2, self.x, self.extrapolate, self.axis)
  1337. def antiderivative(self, nu=1):
  1338. """
  1339. Construct a new piecewise polynomial representing the antiderivative.
  1340. Parameters
  1341. ----------
  1342. nu : int, optional
  1343. Order of antiderivative to evaluate. Default is 1, i.e., compute
  1344. the first integral. If negative, the derivative is returned.
  1345. Returns
  1346. -------
  1347. bp : BPoly
  1348. Piecewise polynomial of order k + nu representing the
  1349. antiderivative of this polynomial.
  1350. Notes
  1351. -----
  1352. If antiderivative is computed and ``self.extrapolate='periodic'``,
  1353. it will be set to False for the returned instance. This is done because
  1354. the antiderivative is no longer periodic and its correct evaluation
  1355. outside of the initially given x interval is difficult.
  1356. """
  1357. if nu <= 0:
  1358. return self.derivative(-nu)
  1359. if nu > 1:
  1360. bp = self
  1361. for k in range(nu):
  1362. bp = bp.antiderivative()
  1363. return bp
  1364. # Construct the indefinite integrals on individual intervals
  1365. c, x = self.c, self.x
  1366. k = c.shape[0]
  1367. c2 = np.zeros((k+1,) + c.shape[1:], dtype=c.dtype)
  1368. c2[1:, ...] = np.cumsum(c, axis=0) / k
  1369. delta = x[1:] - x[:-1]
  1370. c2 *= delta[(None, slice(None)) + (None,)*(c.ndim-2)]
  1371. # Now fix continuity: on the very first interval, take the integration
  1372. # constant to be zero; on an interval [x_j, x_{j+1}) with j>0,
  1373. # the integration constant is then equal to the jump of the `bp` at x_j.
  1374. # The latter is given by the coefficient of B_{n+1, n+1}
  1375. # *on the previous interval* (other B. polynomials are zero at the
  1376. # breakpoint). Finally, use the fact that BPs form a partition of unity.
  1377. c2[:,1:] += np.cumsum(c2[k, :], axis=0)[:-1]
  1378. if self.extrapolate == 'periodic':
  1379. extrapolate = False
  1380. else:
  1381. extrapolate = self.extrapolate
  1382. return self.construct_fast(c2, x, extrapolate, axis=self.axis)
  1383. def integrate(self, a, b, extrapolate=None):
  1384. """
  1385. Compute a definite integral over a piecewise polynomial.
  1386. Parameters
  1387. ----------
  1388. a : float
  1389. Lower integration bound
  1390. b : float
  1391. Upper integration bound
  1392. extrapolate : {bool, 'periodic', None}, optional
  1393. Whether to extrapolate to out-of-bounds points based on first
  1394. and last intervals, or to return NaNs. If 'periodic', periodic
  1395. extrapolation is used. If None (default), use `self.extrapolate`.
  1396. Returns
  1397. -------
  1398. array_like
  1399. Definite integral of the piecewise polynomial over [a, b]
  1400. """
  1401. # XXX: can probably use instead the fact that
  1402. # \int_0^{1} B_{j, n}(x) \dx = 1/(n+1)
  1403. ib = self.antiderivative()
  1404. if extrapolate is None:
  1405. extrapolate = self.extrapolate
  1406. # ib.extrapolate shouldn't be 'periodic', it is converted to
  1407. # False for 'periodic. in antiderivative() call.
  1408. if extrapolate != 'periodic':
  1409. ib.extrapolate = extrapolate
  1410. if extrapolate == 'periodic':
  1411. # Split the integral into the part over period (can be several
  1412. # of them) and the remaining part.
  1413. # For simplicity and clarity convert to a <= b case.
  1414. if a <= b:
  1415. sign = 1
  1416. else:
  1417. a, b = b, a
  1418. sign = -1
  1419. xs, xe = self.x[0], self.x[-1]
  1420. period = xe - xs
  1421. interval = b - a
  1422. n_periods, left = divmod(interval, period)
  1423. res = n_periods * (ib(xe) - ib(xs))
  1424. # Map a and b to [xs, xe].
  1425. a = xs + (a - xs) % period
  1426. b = a + left
  1427. # If b <= xe then we need to integrate over [a, b], otherwise
  1428. # over [a, xe] and from xs to what is remained.
  1429. if b <= xe:
  1430. res += ib(b) - ib(a)
  1431. else:
  1432. res += ib(xe) - ib(a) + ib(xs + left + a - xe) - ib(xs)
  1433. return sign * res
  1434. else:
  1435. return ib(b) - ib(a)
  1436. def extend(self, c, x):
  1437. k = max(self.c.shape[0], c.shape[0])
  1438. self.c = self._raise_degree(self.c, k - self.c.shape[0])
  1439. c = self._raise_degree(c, k - c.shape[0])
  1440. return _PPolyBase.extend(self, c, x)
  1441. extend.__doc__ = _PPolyBase.extend.__doc__
  1442. @classmethod
  1443. def from_power_basis(cls, pp, extrapolate=None):
  1444. """
  1445. Construct a piecewise polynomial in Bernstein basis
  1446. from a power basis polynomial.
  1447. Parameters
  1448. ----------
  1449. pp : PPoly
  1450. A piecewise polynomial in the power basis
  1451. extrapolate : bool or 'periodic', optional
  1452. If bool, determines whether to extrapolate to out-of-bounds points
  1453. based on first and last intervals, or to return NaNs.
  1454. If 'periodic', periodic extrapolation is used. Default is True.
  1455. """
  1456. if not isinstance(pp, PPoly):
  1457. raise TypeError(".from_power_basis only accepts PPoly instances. "
  1458. "Got %s instead." % type(pp))
  1459. dx = np.diff(pp.x)
  1460. k = pp.c.shape[0] - 1 # polynomial order
  1461. rest = (None,)*(pp.c.ndim-2)
  1462. c = np.zeros_like(pp.c)
  1463. for a in range(k+1):
  1464. factor = pp.c[a] / comb(k, k-a) * dx[(slice(None),)+rest]**(k-a)
  1465. for j in range(k-a, k+1):
  1466. c[j] += factor * comb(j, k-a)
  1467. if extrapolate is None:
  1468. extrapolate = pp.extrapolate
  1469. return cls.construct_fast(c, pp.x, extrapolate, pp.axis)
  1470. @classmethod
  1471. def from_derivatives(cls, xi, yi, orders=None, extrapolate=None):
  1472. """Construct a piecewise polynomial in the Bernstein basis,
  1473. compatible with the specified values and derivatives at breakpoints.
  1474. Parameters
  1475. ----------
  1476. xi : array_like
  1477. sorted 1-D array of x-coordinates
  1478. yi : array_like or list of array_likes
  1479. ``yi[i][j]`` is the ``j``th derivative known at ``xi[i]``
  1480. orders : None or int or array_like of ints. Default: None.
  1481. Specifies the degree of local polynomials. If not None, some
  1482. derivatives are ignored.
  1483. extrapolate : bool or 'periodic', optional
  1484. If bool, determines whether to extrapolate to out-of-bounds points
  1485. based on first and last intervals, or to return NaNs.
  1486. If 'periodic', periodic extrapolation is used. Default is True.
  1487. Notes
  1488. -----
  1489. If ``k`` derivatives are specified at a breakpoint ``x``, the
  1490. constructed polynomial is exactly ``k`` times continuously
  1491. differentiable at ``x``, unless the ``order`` is provided explicitly.
  1492. In the latter case, the smoothness of the polynomial at
  1493. the breakpoint is controlled by the ``order``.
  1494. Deduces the number of derivatives to match at each end
  1495. from ``order`` and the number of derivatives available. If
  1496. possible it uses the same number of derivatives from
  1497. each end; if the number is odd it tries to take the
  1498. extra one from y2. In any case if not enough derivatives
  1499. are available at one end or another it draws enough to
  1500. make up the total from the other end.
  1501. If the order is too high and not enough derivatives are available,
  1502. an exception is raised.
  1503. Examples
  1504. --------
  1505. >>> from scipy.interpolate import BPoly
  1506. >>> BPoly.from_derivatives([0, 1], [[1, 2], [3, 4]])
  1507. Creates a polynomial `f(x)` of degree 3, defined on `[0, 1]`
  1508. such that `f(0) = 1, df/dx(0) = 2, f(1) = 3, df/dx(1) = 4`
  1509. >>> BPoly.from_derivatives([0, 1, 2], [[0, 1], [0], [2]])
  1510. Creates a piecewise polynomial `f(x)`, such that
  1511. `f(0) = f(1) = 0`, `f(2) = 2`, and `df/dx(0) = 1`.
  1512. Based on the number of derivatives provided, the order of the
  1513. local polynomials is 2 on `[0, 1]` and 1 on `[1, 2]`.
  1514. Notice that no restriction is imposed on the derivatives at
  1515. ``x = 1`` and ``x = 2``.
  1516. Indeed, the explicit form of the polynomial is::
  1517. f(x) = | x * (1 - x), 0 <= x < 1
  1518. | 2 * (x - 1), 1 <= x <= 2
  1519. So that f'(1-0) = -1 and f'(1+0) = 2
  1520. """
  1521. xi = np.asarray(xi)
  1522. if len(xi) != len(yi):
  1523. raise ValueError("xi and yi need to have the same length")
  1524. if np.any(xi[1:] - xi[:1] <= 0):
  1525. raise ValueError("x coordinates are not in increasing order")
  1526. # number of intervals
  1527. m = len(xi) - 1
  1528. # global poly order is k-1, local orders are <=k and can vary
  1529. try:
  1530. k = max(len(yi[i]) + len(yi[i+1]) for i in range(m))
  1531. except TypeError as e:
  1532. raise ValueError(
  1533. "Using a 1-D array for y? Please .reshape(-1, 1)."
  1534. ) from e
  1535. if orders is None:
  1536. orders = [None] * m
  1537. else:
  1538. if isinstance(orders, (int, np.integer)):
  1539. orders = [orders] * m
  1540. k = max(k, max(orders))
  1541. if any(o <= 0 for o in orders):
  1542. raise ValueError("Orders must be positive.")
  1543. c = []
  1544. for i in range(m):
  1545. y1, y2 = yi[i], yi[i+1]
  1546. if orders[i] is None:
  1547. n1, n2 = len(y1), len(y2)
  1548. else:
  1549. n = orders[i]+1
  1550. n1 = min(n//2, len(y1))
  1551. n2 = min(n - n1, len(y2))
  1552. n1 = min(n - n2, len(y2))
  1553. if n1+n2 != n:
  1554. mesg = ("Point %g has %d derivatives, point %g"
  1555. " has %d derivatives, but order %d requested" % (
  1556. xi[i], len(y1), xi[i+1], len(y2), orders[i]))
  1557. raise ValueError(mesg)
  1558. if not (n1 <= len(y1) and n2 <= len(y2)):
  1559. raise ValueError("`order` input incompatible with"
  1560. " length y1 or y2.")
  1561. b = BPoly._construct_from_derivatives(xi[i], xi[i+1],
  1562. y1[:n1], y2[:n2])
  1563. if len(b) < k:
  1564. b = BPoly._raise_degree(b, k - len(b))
  1565. c.append(b)
  1566. c = np.asarray(c)
  1567. return cls(c.swapaxes(0, 1), xi, extrapolate)
  1568. @staticmethod
  1569. def _construct_from_derivatives(xa, xb, ya, yb):
  1570. r"""Compute the coefficients of a polynomial in the Bernstein basis
  1571. given the values and derivatives at the edges.
  1572. Return the coefficients of a polynomial in the Bernstein basis
  1573. defined on ``[xa, xb]`` and having the values and derivatives at the
  1574. endpoints `xa` and `xb` as specified by `ya`` and `yb`.
  1575. The polynomial constructed is of the minimal possible degree, i.e.,
  1576. if the lengths of `ya` and `yb` are `na` and `nb`, the degree
  1577. of the polynomial is ``na + nb - 1``.
  1578. Parameters
  1579. ----------
  1580. xa : float
  1581. Left-hand end point of the interval
  1582. xb : float
  1583. Right-hand end point of the interval
  1584. ya : array_like
  1585. Derivatives at `xa`. `ya[0]` is the value of the function, and
  1586. `ya[i]` for ``i > 0`` is the value of the ``i``th derivative.
  1587. yb : array_like
  1588. Derivatives at `xb`.
  1589. Returns
  1590. -------
  1591. array
  1592. coefficient array of a polynomial having specified derivatives
  1593. Notes
  1594. -----
  1595. This uses several facts from life of Bernstein basis functions.
  1596. First of all,
  1597. .. math:: b'_{a, n} = n (b_{a-1, n-1} - b_{a, n-1})
  1598. If B(x) is a linear combination of the form
  1599. .. math:: B(x) = \sum_{a=0}^{n} c_a b_{a, n},
  1600. then :math: B'(x) = n \sum_{a=0}^{n-1} (c_{a+1} - c_{a}) b_{a, n-1}.
  1601. Iterating the latter one, one finds for the q-th derivative
  1602. .. math:: B^{q}(x) = n!/(n-q)! \sum_{a=0}^{n-q} Q_a b_{a, n-q},
  1603. with
  1604. .. math:: Q_a = \sum_{j=0}^{q} (-)^{j+q} comb(q, j) c_{j+a}
  1605. This way, only `a=0` contributes to :math: `B^{q}(x = xa)`, and
  1606. `c_q` are found one by one by iterating `q = 0, ..., na`.
  1607. At ``x = xb`` it's the same with ``a = n - q``.
  1608. """
  1609. ya, yb = np.asarray(ya), np.asarray(yb)
  1610. if ya.shape[1:] != yb.shape[1:]:
  1611. raise ValueError('Shapes of ya {} and yb {} are incompatible'
  1612. .format(ya.shape, yb.shape))
  1613. dta, dtb = ya.dtype, yb.dtype
  1614. if (np.issubdtype(dta, np.complexfloating) or
  1615. np.issubdtype(dtb, np.complexfloating)):
  1616. dt = np.complex_
  1617. else:
  1618. dt = np.float_
  1619. na, nb = len(ya), len(yb)
  1620. n = na + nb
  1621. c = np.empty((na+nb,) + ya.shape[1:], dtype=dt)
  1622. # compute coefficients of a polynomial degree na+nb-1
  1623. # walk left-to-right
  1624. for q in range(0, na):
  1625. c[q] = ya[q] / spec.poch(n - q, q) * (xb - xa)**q
  1626. for j in range(0, q):
  1627. c[q] -= (-1)**(j+q) * comb(q, j) * c[j]
  1628. # now walk right-to-left
  1629. for q in range(0, nb):
  1630. c[-q-1] = yb[q] / spec.poch(n - q, q) * (-1)**q * (xb - xa)**q
  1631. for j in range(0, q):
  1632. c[-q-1] -= (-1)**(j+1) * comb(q, j+1) * c[-q+j]
  1633. return c
  1634. @staticmethod
  1635. def _raise_degree(c, d):
  1636. r"""Raise a degree of a polynomial in the Bernstein basis.
  1637. Given the coefficients of a polynomial degree `k`, return (the
  1638. coefficients of) the equivalent polynomial of degree `k+d`.
  1639. Parameters
  1640. ----------
  1641. c : array_like
  1642. coefficient array, 1-D
  1643. d : integer
  1644. Returns
  1645. -------
  1646. array
  1647. coefficient array, 1-D array of length `c.shape[0] + d`
  1648. Notes
  1649. -----
  1650. This uses the fact that a Bernstein polynomial `b_{a, k}` can be
  1651. identically represented as a linear combination of polynomials of
  1652. a higher degree `k+d`:
  1653. .. math:: b_{a, k} = comb(k, a) \sum_{j=0}^{d} b_{a+j, k+d} \
  1654. comb(d, j) / comb(k+d, a+j)
  1655. """
  1656. if d == 0:
  1657. return c
  1658. k = c.shape[0] - 1
  1659. out = np.zeros((c.shape[0] + d,) + c.shape[1:], dtype=c.dtype)
  1660. for a in range(c.shape[0]):
  1661. f = c[a] * comb(k, a)
  1662. for j in range(d+1):
  1663. out[a+j] += f * comb(d, j) / comb(k+d, a+j)
  1664. return out
  1665. class NdPPoly:
  1666. """
  1667. Piecewise tensor product polynomial
  1668. The value at point ``xp = (x', y', z', ...)`` is evaluated by first
  1669. computing the interval indices `i` such that::
  1670. x[0][i[0]] <= x' < x[0][i[0]+1]
  1671. x[1][i[1]] <= y' < x[1][i[1]+1]
  1672. ...
  1673. and then computing::
  1674. S = sum(c[k0-m0-1,...,kn-mn-1,i[0],...,i[n]]
  1675. * (xp[0] - x[0][i[0]])**m0
  1676. * ...
  1677. * (xp[n] - x[n][i[n]])**mn
  1678. for m0 in range(k[0]+1)
  1679. ...
  1680. for mn in range(k[n]+1))
  1681. where ``k[j]`` is the degree of the polynomial in dimension j. This
  1682. representation is the piecewise multivariate power basis.
  1683. Parameters
  1684. ----------
  1685. c : ndarray, shape (k0, ..., kn, m0, ..., mn, ...)
  1686. Polynomial coefficients, with polynomial order `kj` and
  1687. `mj+1` intervals for each dimension `j`.
  1688. x : ndim-tuple of ndarrays, shapes (mj+1,)
  1689. Polynomial breakpoints for each dimension. These must be
  1690. sorted in increasing order.
  1691. extrapolate : bool, optional
  1692. Whether to extrapolate to out-of-bounds points based on first
  1693. and last intervals, or to return NaNs. Default: True.
  1694. Attributes
  1695. ----------
  1696. x : tuple of ndarrays
  1697. Breakpoints.
  1698. c : ndarray
  1699. Coefficients of the polynomials.
  1700. Methods
  1701. -------
  1702. __call__
  1703. derivative
  1704. antiderivative
  1705. integrate
  1706. integrate_1d
  1707. construct_fast
  1708. See also
  1709. --------
  1710. PPoly : piecewise polynomials in 1D
  1711. Notes
  1712. -----
  1713. High-order polynomials in the power basis can be numerically
  1714. unstable.
  1715. """
  1716. def __init__(self, c, x, extrapolate=None):
  1717. self.x = tuple(np.ascontiguousarray(v, dtype=np.float64) for v in x)
  1718. self.c = np.asarray(c)
  1719. if extrapolate is None:
  1720. extrapolate = True
  1721. self.extrapolate = bool(extrapolate)
  1722. ndim = len(self.x)
  1723. if any(v.ndim != 1 for v in self.x):
  1724. raise ValueError("x arrays must all be 1-dimensional")
  1725. if any(v.size < 2 for v in self.x):
  1726. raise ValueError("x arrays must all contain at least 2 points")
  1727. if c.ndim < 2*ndim:
  1728. raise ValueError("c must have at least 2*len(x) dimensions")
  1729. if any(np.any(v[1:] - v[:-1] < 0) for v in self.x):
  1730. raise ValueError("x-coordinates are not in increasing order")
  1731. if any(a != b.size - 1 for a, b in zip(c.shape[ndim:2*ndim], self.x)):
  1732. raise ValueError("x and c do not agree on the number of intervals")
  1733. dtype = self._get_dtype(self.c.dtype)
  1734. self.c = np.ascontiguousarray(self.c, dtype=dtype)
  1735. @classmethod
  1736. def construct_fast(cls, c, x, extrapolate=None):
  1737. """
  1738. Construct the piecewise polynomial without making checks.
  1739. Takes the same parameters as the constructor. Input arguments
  1740. ``c`` and ``x`` must be arrays of the correct shape and type. The
  1741. ``c`` array can only be of dtypes float and complex, and ``x``
  1742. array must have dtype float.
  1743. """
  1744. self = object.__new__(cls)
  1745. self.c = c
  1746. self.x = x
  1747. if extrapolate is None:
  1748. extrapolate = True
  1749. self.extrapolate = extrapolate
  1750. return self
  1751. def _get_dtype(self, dtype):
  1752. if np.issubdtype(dtype, np.complexfloating) \
  1753. or np.issubdtype(self.c.dtype, np.complexfloating):
  1754. return np.complex_
  1755. else:
  1756. return np.float_
  1757. def _ensure_c_contiguous(self):
  1758. if not self.c.flags.c_contiguous:
  1759. self.c = self.c.copy()
  1760. if not isinstance(self.x, tuple):
  1761. self.x = tuple(self.x)
  1762. def __call__(self, x, nu=None, extrapolate=None):
  1763. """
  1764. Evaluate the piecewise polynomial or its derivative
  1765. Parameters
  1766. ----------
  1767. x : array-like
  1768. Points to evaluate the interpolant at.
  1769. nu : tuple, optional
  1770. Orders of derivatives to evaluate. Each must be non-negative.
  1771. extrapolate : bool, optional
  1772. Whether to extrapolate to out-of-bounds points based on first
  1773. and last intervals, or to return NaNs.
  1774. Returns
  1775. -------
  1776. y : array-like
  1777. Interpolated values. Shape is determined by replacing
  1778. the interpolation axis in the original array with the shape of x.
  1779. Notes
  1780. -----
  1781. Derivatives are evaluated piecewise for each polynomial
  1782. segment, even if the polynomial is not differentiable at the
  1783. breakpoints. The polynomial intervals are considered half-open,
  1784. ``[a, b)``, except for the last interval which is closed
  1785. ``[a, b]``.
  1786. """
  1787. if extrapolate is None:
  1788. extrapolate = self.extrapolate
  1789. else:
  1790. extrapolate = bool(extrapolate)
  1791. ndim = len(self.x)
  1792. x = _ndim_coords_from_arrays(x)
  1793. x_shape = x.shape
  1794. x = np.ascontiguousarray(x.reshape(-1, x.shape[-1]), dtype=np.float_)
  1795. if nu is None:
  1796. nu = np.zeros((ndim,), dtype=np.intc)
  1797. else:
  1798. nu = np.asarray(nu, dtype=np.intc)
  1799. if nu.ndim != 1 or nu.shape[0] != ndim:
  1800. raise ValueError("invalid number of derivative orders nu")
  1801. dim1 = prod(self.c.shape[:ndim])
  1802. dim2 = prod(self.c.shape[ndim:2*ndim])
  1803. dim3 = prod(self.c.shape[2*ndim:])
  1804. ks = np.array(self.c.shape[:ndim], dtype=np.intc)
  1805. out = np.empty((x.shape[0], dim3), dtype=self.c.dtype)
  1806. self._ensure_c_contiguous()
  1807. _ppoly.evaluate_nd(self.c.reshape(dim1, dim2, dim3),
  1808. self.x,
  1809. ks,
  1810. x,
  1811. nu,
  1812. bool(extrapolate),
  1813. out)
  1814. return out.reshape(x_shape[:-1] + self.c.shape[2*ndim:])
  1815. def _derivative_inplace(self, nu, axis):
  1816. """
  1817. Compute 1-D derivative along a selected dimension in-place
  1818. May result to non-contiguous c array.
  1819. """
  1820. if nu < 0:
  1821. return self._antiderivative_inplace(-nu, axis)
  1822. ndim = len(self.x)
  1823. axis = axis % ndim
  1824. # reduce order
  1825. if nu == 0:
  1826. # noop
  1827. return
  1828. else:
  1829. sl = [slice(None)]*ndim
  1830. sl[axis] = slice(None, -nu, None)
  1831. c2 = self.c[tuple(sl)]
  1832. if c2.shape[axis] == 0:
  1833. # derivative of order 0 is zero
  1834. shp = list(c2.shape)
  1835. shp[axis] = 1
  1836. c2 = np.zeros(shp, dtype=c2.dtype)
  1837. # multiply by the correct rising factorials
  1838. factor = spec.poch(np.arange(c2.shape[axis], 0, -1), nu)
  1839. sl = [None]*c2.ndim
  1840. sl[axis] = slice(None)
  1841. c2 *= factor[tuple(sl)]
  1842. self.c = c2
  1843. def _antiderivative_inplace(self, nu, axis):
  1844. """
  1845. Compute 1-D antiderivative along a selected dimension
  1846. May result to non-contiguous c array.
  1847. """
  1848. if nu <= 0:
  1849. return self._derivative_inplace(-nu, axis)
  1850. ndim = len(self.x)
  1851. axis = axis % ndim
  1852. perm = list(range(ndim))
  1853. perm[0], perm[axis] = perm[axis], perm[0]
  1854. perm = perm + list(range(ndim, self.c.ndim))
  1855. c = self.c.transpose(perm)
  1856. c2 = np.zeros((c.shape[0] + nu,) + c.shape[1:],
  1857. dtype=c.dtype)
  1858. c2[:-nu] = c
  1859. # divide by the correct rising factorials
  1860. factor = spec.poch(np.arange(c.shape[0], 0, -1), nu)
  1861. c2[:-nu] /= factor[(slice(None),) + (None,)*(c.ndim-1)]
  1862. # fix continuity of added degrees of freedom
  1863. perm2 = list(range(c2.ndim))
  1864. perm2[1], perm2[ndim+axis] = perm2[ndim+axis], perm2[1]
  1865. c2 = c2.transpose(perm2)
  1866. c2 = c2.copy()
  1867. _ppoly.fix_continuity(c2.reshape(c2.shape[0], c2.shape[1], -1),
  1868. self.x[axis], nu-1)
  1869. c2 = c2.transpose(perm2)
  1870. c2 = c2.transpose(perm)
  1871. # Done
  1872. self.c = c2
  1873. def derivative(self, nu):
  1874. """
  1875. Construct a new piecewise polynomial representing the derivative.
  1876. Parameters
  1877. ----------
  1878. nu : ndim-tuple of int
  1879. Order of derivatives to evaluate for each dimension.
  1880. If negative, the antiderivative is returned.
  1881. Returns
  1882. -------
  1883. pp : NdPPoly
  1884. Piecewise polynomial of orders (k[0] - nu[0], ..., k[n] - nu[n])
  1885. representing the derivative of this polynomial.
  1886. Notes
  1887. -----
  1888. Derivatives are evaluated piecewise for each polynomial
  1889. segment, even if the polynomial is not differentiable at the
  1890. breakpoints. The polynomial intervals in each dimension are
  1891. considered half-open, ``[a, b)``, except for the last interval
  1892. which is closed ``[a, b]``.
  1893. """
  1894. p = self.construct_fast(self.c.copy(), self.x, self.extrapolate)
  1895. for axis, n in enumerate(nu):
  1896. p._derivative_inplace(n, axis)
  1897. p._ensure_c_contiguous()
  1898. return p
  1899. def antiderivative(self, nu):
  1900. """
  1901. Construct a new piecewise polynomial representing the antiderivative.
  1902. Antiderivative is also the indefinite integral of the function,
  1903. and derivative is its inverse operation.
  1904. Parameters
  1905. ----------
  1906. nu : ndim-tuple of int
  1907. Order of derivatives to evaluate for each dimension.
  1908. If negative, the derivative is returned.
  1909. Returns
  1910. -------
  1911. pp : PPoly
  1912. Piecewise polynomial of order k2 = k + n representing
  1913. the antiderivative of this polynomial.
  1914. Notes
  1915. -----
  1916. The antiderivative returned by this function is continuous and
  1917. continuously differentiable to order n-1, up to floating point
  1918. rounding error.
  1919. """
  1920. p = self.construct_fast(self.c.copy(), self.x, self.extrapolate)
  1921. for axis, n in enumerate(nu):
  1922. p._antiderivative_inplace(n, axis)
  1923. p._ensure_c_contiguous()
  1924. return p
  1925. def integrate_1d(self, a, b, axis, extrapolate=None):
  1926. r"""
  1927. Compute NdPPoly representation for one dimensional definite integral
  1928. The result is a piecewise polynomial representing the integral:
  1929. .. math::
  1930. p(y, z, ...) = \int_a^b dx\, p(x, y, z, ...)
  1931. where the dimension integrated over is specified with the
  1932. `axis` parameter.
  1933. Parameters
  1934. ----------
  1935. a, b : float
  1936. Lower and upper bound for integration.
  1937. axis : int
  1938. Dimension over which to compute the 1-D integrals
  1939. extrapolate : bool, optional
  1940. Whether to extrapolate to out-of-bounds points based on first
  1941. and last intervals, or to return NaNs.
  1942. Returns
  1943. -------
  1944. ig : NdPPoly or array-like
  1945. Definite integral of the piecewise polynomial over [a, b].
  1946. If the polynomial was 1D, an array is returned,
  1947. otherwise, an NdPPoly object.
  1948. """
  1949. if extrapolate is None:
  1950. extrapolate = self.extrapolate
  1951. else:
  1952. extrapolate = bool(extrapolate)
  1953. ndim = len(self.x)
  1954. axis = int(axis) % ndim
  1955. # reuse 1-D integration routines
  1956. c = self.c
  1957. swap = list(range(c.ndim))
  1958. swap.insert(0, swap[axis])
  1959. del swap[axis + 1]
  1960. swap.insert(1, swap[ndim + axis])
  1961. del swap[ndim + axis + 1]
  1962. c = c.transpose(swap)
  1963. p = PPoly.construct_fast(c.reshape(c.shape[0], c.shape[1], -1),
  1964. self.x[axis],
  1965. extrapolate=extrapolate)
  1966. out = p.integrate(a, b, extrapolate=extrapolate)
  1967. # Construct result
  1968. if ndim == 1:
  1969. return out.reshape(c.shape[2:])
  1970. else:
  1971. c = out.reshape(c.shape[2:])
  1972. x = self.x[:axis] + self.x[axis+1:]
  1973. return self.construct_fast(c, x, extrapolate=extrapolate)
  1974. def integrate(self, ranges, extrapolate=None):
  1975. """
  1976. Compute a definite integral over a piecewise polynomial.
  1977. Parameters
  1978. ----------
  1979. ranges : ndim-tuple of 2-tuples float
  1980. Sequence of lower and upper bounds for each dimension,
  1981. ``[(a[0], b[0]), ..., (a[ndim-1], b[ndim-1])]``
  1982. extrapolate : bool, optional
  1983. Whether to extrapolate to out-of-bounds points based on first
  1984. and last intervals, or to return NaNs.
  1985. Returns
  1986. -------
  1987. ig : array_like
  1988. Definite integral of the piecewise polynomial over
  1989. [a[0], b[0]] x ... x [a[ndim-1], b[ndim-1]]
  1990. """
  1991. ndim = len(self.x)
  1992. if extrapolate is None:
  1993. extrapolate = self.extrapolate
  1994. else:
  1995. extrapolate = bool(extrapolate)
  1996. if not hasattr(ranges, '__len__') or len(ranges) != ndim:
  1997. raise ValueError("Range not a sequence of correct length")
  1998. self._ensure_c_contiguous()
  1999. # Reuse 1D integration routine
  2000. c = self.c
  2001. for n, (a, b) in enumerate(ranges):
  2002. swap = list(range(c.ndim))
  2003. swap.insert(1, swap[ndim - n])
  2004. del swap[ndim - n + 1]
  2005. c = c.transpose(swap)
  2006. p = PPoly.construct_fast(c, self.x[n], extrapolate=extrapolate)
  2007. out = p.integrate(a, b, extrapolate=extrapolate)
  2008. c = out.reshape(c.shape[2:])
  2009. return c