interpolate.py 101 KB

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