polyint.py 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666
  1. from __future__ import division, print_function, absolute_import
  2. import warnings
  3. import numpy as np
  4. from scipy.special import factorial
  5. from scipy._lib.six import xrange
  6. from scipy._lib._util import _asarray_validated
  7. __all__ = ["KroghInterpolator", "krogh_interpolate", "BarycentricInterpolator",
  8. "barycentric_interpolate", "approximate_taylor_polynomial"]
  9. def _isscalar(x):
  10. """Check whether x is if a scalar type, or 0-dim"""
  11. return np.isscalar(x) or hasattr(x, 'shape') and x.shape == ()
  12. class _Interpolator1D(object):
  13. """
  14. Common features in univariate interpolation
  15. Deal with input data type and interpolation axis rolling. The
  16. actual interpolator can assume the y-data is of shape (n, r) where
  17. `n` is the number of x-points, and `r` the number of variables,
  18. and use self.dtype as the y-data type.
  19. Attributes
  20. ----------
  21. _y_axis
  22. Axis along which the interpolation goes in the original array
  23. _y_extra_shape
  24. Additional trailing shape of the input arrays, excluding
  25. the interpolation axis.
  26. dtype
  27. Dtype of the y-data arrays. Can be set via set_dtype, which
  28. forces it to be float or complex.
  29. Methods
  30. -------
  31. __call__
  32. _prepare_x
  33. _finish_y
  34. _reshape_yi
  35. _set_yi
  36. _set_dtype
  37. _evaluate
  38. """
  39. __slots__ = ('_y_axis', '_y_extra_shape', 'dtype')
  40. def __init__(self, xi=None, yi=None, axis=None):
  41. self._y_axis = axis
  42. self._y_extra_shape = None
  43. self.dtype = None
  44. if yi is not None:
  45. self._set_yi(yi, xi=xi, axis=axis)
  46. def __call__(self, x):
  47. """
  48. Evaluate the interpolant
  49. Parameters
  50. ----------
  51. x : array_like
  52. Points to evaluate the interpolant at.
  53. Returns
  54. -------
  55. y : array_like
  56. Interpolated values. Shape is determined by replacing
  57. the interpolation axis in the original array with the shape of x.
  58. """
  59. x, x_shape = self._prepare_x(x)
  60. y = self._evaluate(x)
  61. return self._finish_y(y, x_shape)
  62. def _evaluate(self, x):
  63. """
  64. Actually evaluate the value of the interpolator.
  65. """
  66. raise NotImplementedError()
  67. def _prepare_x(self, x):
  68. """Reshape input x array to 1-D"""
  69. x = _asarray_validated(x, check_finite=False, as_inexact=True)
  70. x_shape = x.shape
  71. return x.ravel(), x_shape
  72. def _finish_y(self, y, x_shape):
  73. """Reshape interpolated y back to n-d array similar to initial y"""
  74. y = y.reshape(x_shape + self._y_extra_shape)
  75. if self._y_axis != 0 and x_shape != ():
  76. nx = len(x_shape)
  77. ny = len(self._y_extra_shape)
  78. s = (list(range(nx, nx + self._y_axis))
  79. + list(range(nx)) + list(range(nx+self._y_axis, nx+ny)))
  80. y = y.transpose(s)
  81. return y
  82. def _reshape_yi(self, yi, check=False):
  83. yi = np.rollaxis(np.asarray(yi), self._y_axis)
  84. if check and yi.shape[1:] != self._y_extra_shape:
  85. ok_shape = "%r + (N,) + %r" % (self._y_extra_shape[-self._y_axis:],
  86. self._y_extra_shape[:-self._y_axis])
  87. raise ValueError("Data must be of shape %s" % ok_shape)
  88. return yi.reshape((yi.shape[0], -1))
  89. def _set_yi(self, yi, xi=None, axis=None):
  90. if axis is None:
  91. axis = self._y_axis
  92. if axis is None:
  93. raise ValueError("no interpolation axis specified")
  94. yi = np.asarray(yi)
  95. shape = yi.shape
  96. if shape == ():
  97. shape = (1,)
  98. if xi is not None and shape[axis] != len(xi):
  99. raise ValueError("x and y arrays must be equal in length along "
  100. "interpolation axis.")
  101. self._y_axis = (axis % yi.ndim)
  102. self._y_extra_shape = yi.shape[:self._y_axis]+yi.shape[self._y_axis+1:]
  103. self.dtype = None
  104. self._set_dtype(yi.dtype)
  105. def _set_dtype(self, dtype, union=False):
  106. if np.issubdtype(dtype, np.complexfloating) \
  107. or np.issubdtype(self.dtype, np.complexfloating):
  108. self.dtype = np.complex_
  109. else:
  110. if not union or self.dtype != np.complex_:
  111. self.dtype = np.float_
  112. class _Interpolator1DWithDerivatives(_Interpolator1D):
  113. def derivatives(self, x, der=None):
  114. """
  115. Evaluate many derivatives of the polynomial at the point x
  116. Produce an array of all derivative values at the point x.
  117. Parameters
  118. ----------
  119. x : array_like
  120. Point or points at which to evaluate the derivatives
  121. der : int or None, optional
  122. How many derivatives to extract; None for all potentially
  123. nonzero derivatives (that is a number equal to the number
  124. of points). This number includes the function value as 0th
  125. derivative.
  126. Returns
  127. -------
  128. d : ndarray
  129. Array with derivatives; d[j] contains the j-th derivative.
  130. Shape of d[j] is determined by replacing the interpolation
  131. axis in the original array with the shape of x.
  132. Examples
  133. --------
  134. >>> from scipy.interpolate import KroghInterpolator
  135. >>> KroghInterpolator([0,0,0],[1,2,3]).derivatives(0)
  136. array([1.0,2.0,3.0])
  137. >>> KroghInterpolator([0,0,0],[1,2,3]).derivatives([0,0])
  138. array([[1.0,1.0],
  139. [2.0,2.0],
  140. [3.0,3.0]])
  141. """
  142. x, x_shape = self._prepare_x(x)
  143. y = self._evaluate_derivatives(x, der)
  144. y = y.reshape((y.shape[0],) + x_shape + self._y_extra_shape)
  145. if self._y_axis != 0 and x_shape != ():
  146. nx = len(x_shape)
  147. ny = len(self._y_extra_shape)
  148. s = ([0] + list(range(nx+1, nx + self._y_axis+1))
  149. + list(range(1,nx+1)) +
  150. list(range(nx+1+self._y_axis, nx+ny+1)))
  151. y = y.transpose(s)
  152. return y
  153. def derivative(self, x, der=1):
  154. """
  155. Evaluate one derivative of the polynomial at the point x
  156. Parameters
  157. ----------
  158. x : array_like
  159. Point or points at which to evaluate the derivatives
  160. der : integer, optional
  161. Which derivative to extract. This number includes the
  162. function value as 0th derivative.
  163. Returns
  164. -------
  165. d : ndarray
  166. Derivative interpolated at the x-points. Shape of d is
  167. determined by replacing the interpolation axis in the
  168. original array with the shape of x.
  169. Notes
  170. -----
  171. This is computed by evaluating all derivatives up to the desired
  172. one (using self.derivatives()) and then discarding the rest.
  173. """
  174. x, x_shape = self._prepare_x(x)
  175. y = self._evaluate_derivatives(x, der+1)
  176. return self._finish_y(y[der], x_shape)
  177. class KroghInterpolator(_Interpolator1DWithDerivatives):
  178. """
  179. Interpolating polynomial for a set of points.
  180. The polynomial passes through all the pairs (xi,yi). One may
  181. additionally specify a number of derivatives at each point xi;
  182. this is done by repeating the value xi and specifying the
  183. derivatives as successive yi values.
  184. Allows evaluation of the polynomial and all its derivatives.
  185. For reasons of numerical stability, this function does not compute
  186. the coefficients of the polynomial, although they can be obtained
  187. by evaluating all the derivatives.
  188. Parameters
  189. ----------
  190. xi : array_like, length N
  191. Known x-coordinates. Must be sorted in increasing order.
  192. yi : array_like
  193. Known y-coordinates. When an xi occurs two or more times in
  194. a row, the corresponding yi's represent derivative values.
  195. axis : int, optional
  196. Axis in the yi array corresponding to the x-coordinate values.
  197. Notes
  198. -----
  199. Be aware that the algorithms implemented here are not necessarily
  200. the most numerically stable known. Moreover, even in a world of
  201. exact computation, unless the x coordinates are chosen very
  202. carefully - Chebyshev zeros (e.g. cos(i*pi/n)) are a good choice -
  203. polynomial interpolation itself is a very ill-conditioned process
  204. due to the Runge phenomenon. In general, even with well-chosen
  205. x values, degrees higher than about thirty cause problems with
  206. numerical instability in this code.
  207. Based on [1]_.
  208. References
  209. ----------
  210. .. [1] Krogh, "Efficient Algorithms for Polynomial Interpolation
  211. and Numerical Differentiation", 1970.
  212. Examples
  213. --------
  214. To produce a polynomial that is zero at 0 and 1 and has
  215. derivative 2 at 0, call
  216. >>> from scipy.interpolate import KroghInterpolator
  217. >>> KroghInterpolator([0,0,1],[0,2,0])
  218. This constructs the quadratic 2*X**2-2*X. The derivative condition
  219. is indicated by the repeated zero in the xi array; the corresponding
  220. yi values are 0, the function value, and 2, the derivative value.
  221. For another example, given xi, yi, and a derivative ypi for each
  222. point, appropriate arrays can be constructed as:
  223. >>> xi = np.linspace(0, 1, 5)
  224. >>> yi, ypi = np.random.rand(2, 5)
  225. >>> xi_k, yi_k = np.repeat(xi, 2), np.ravel(np.dstack((yi,ypi)))
  226. >>> KroghInterpolator(xi_k, yi_k)
  227. To produce a vector-valued polynomial, supply a higher-dimensional
  228. array for yi:
  229. >>> KroghInterpolator([0,1],[[2,3],[4,5]])
  230. This constructs a linear polynomial giving (2,3) at 0 and (4,5) at 1.
  231. """
  232. def __init__(self, xi, yi, axis=0):
  233. _Interpolator1DWithDerivatives.__init__(self, xi, yi, axis)
  234. self.xi = np.asarray(xi)
  235. self.yi = self._reshape_yi(yi)
  236. self.n, self.r = self.yi.shape
  237. c = np.zeros((self.n+1, self.r), dtype=self.dtype)
  238. c[0] = self.yi[0]
  239. Vk = np.zeros((self.n, self.r), dtype=self.dtype)
  240. for k in xrange(1,self.n):
  241. s = 0
  242. while s <= k and xi[k-s] == xi[k]:
  243. s += 1
  244. s -= 1
  245. Vk[0] = self.yi[k]/float(factorial(s))
  246. for i in xrange(k-s):
  247. if xi[i] == xi[k]:
  248. raise ValueError("Elements if `xi` can't be equal.")
  249. if s == 0:
  250. Vk[i+1] = (c[i]-Vk[i])/(xi[i]-xi[k])
  251. else:
  252. Vk[i+1] = (Vk[i+1]-Vk[i])/(xi[i]-xi[k])
  253. c[k] = Vk[k-s]
  254. self.c = c
  255. def _evaluate(self, x):
  256. pi = 1
  257. p = np.zeros((len(x), self.r), dtype=self.dtype)
  258. p += self.c[0,np.newaxis,:]
  259. for k in range(1, self.n):
  260. w = x - self.xi[k-1]
  261. pi = w*pi
  262. p += pi[:,np.newaxis] * self.c[k]
  263. return p
  264. def _evaluate_derivatives(self, x, der=None):
  265. n = self.n
  266. r = self.r
  267. if der is None:
  268. der = self.n
  269. pi = np.zeros((n, len(x)))
  270. w = np.zeros((n, len(x)))
  271. pi[0] = 1
  272. p = np.zeros((len(x), self.r), dtype=self.dtype)
  273. p += self.c[0, np.newaxis, :]
  274. for k in xrange(1, n):
  275. w[k-1] = x - self.xi[k-1]
  276. pi[k] = w[k-1] * pi[k-1]
  277. p += pi[k, :, np.newaxis] * self.c[k]
  278. cn = np.zeros((max(der, n+1), len(x), r), dtype=self.dtype)
  279. cn[:n+1, :, :] += self.c[:n+1, np.newaxis, :]
  280. cn[0] = p
  281. for k in xrange(1, n):
  282. for i in xrange(1, n-k+1):
  283. pi[i] = w[k+i-1]*pi[i-1] + pi[i]
  284. cn[k] = cn[k] + pi[i, :, np.newaxis]*cn[k+i]
  285. cn[k] *= factorial(k)
  286. cn[n, :, :] = 0
  287. return cn[:der]
  288. def krogh_interpolate(xi, yi, x, der=0, axis=0):
  289. """
  290. Convenience function for polynomial interpolation.
  291. See `KroghInterpolator` for more details.
  292. Parameters
  293. ----------
  294. xi : array_like
  295. Known x-coordinates.
  296. yi : array_like
  297. Known y-coordinates, of shape ``(xi.size, R)``. Interpreted as
  298. vectors of length R, or scalars if R=1.
  299. x : array_like
  300. Point or points at which to evaluate the derivatives.
  301. der : int or list, optional
  302. How many derivatives to extract; None for all potentially
  303. nonzero derivatives (that is a number equal to the number
  304. of points), or a list of derivatives to extract. This number
  305. includes the function value as 0th derivative.
  306. axis : int, optional
  307. Axis in the yi array corresponding to the x-coordinate values.
  308. Returns
  309. -------
  310. d : ndarray
  311. If the interpolator's values are R-dimensional then the
  312. returned array will be the number of derivatives by N by R.
  313. If `x` is a scalar, the middle dimension will be dropped; if
  314. the `yi` are scalars then the last dimension will be dropped.
  315. See Also
  316. --------
  317. KroghInterpolator
  318. Notes
  319. -----
  320. Construction of the interpolating polynomial is a relatively expensive
  321. process. If you want to evaluate it repeatedly consider using the class
  322. KroghInterpolator (which is what this function uses).
  323. """
  324. P = KroghInterpolator(xi, yi, axis=axis)
  325. if der == 0:
  326. return P(x)
  327. elif _isscalar(der):
  328. return P.derivative(x,der=der)
  329. else:
  330. return P.derivatives(x,der=np.amax(der)+1)[der]
  331. def approximate_taylor_polynomial(f,x,degree,scale,order=None):
  332. """
  333. Estimate the Taylor polynomial of f at x by polynomial fitting.
  334. Parameters
  335. ----------
  336. f : callable
  337. The function whose Taylor polynomial is sought. Should accept
  338. a vector of `x` values.
  339. x : scalar
  340. The point at which the polynomial is to be evaluated.
  341. degree : int
  342. The degree of the Taylor polynomial
  343. scale : scalar
  344. The width of the interval to use to evaluate the Taylor polynomial.
  345. Function values spread over a range this wide are used to fit the
  346. polynomial. Must be chosen carefully.
  347. order : int or None, optional
  348. The order of the polynomial to be used in the fitting; `f` will be
  349. evaluated ``order+1`` times. If None, use `degree`.
  350. Returns
  351. -------
  352. p : poly1d instance
  353. The Taylor polynomial (translated to the origin, so that
  354. for example p(0)=f(x)).
  355. Notes
  356. -----
  357. The appropriate choice of "scale" is a trade-off; too large and the
  358. function differs from its Taylor polynomial too much to get a good
  359. answer, too small and round-off errors overwhelm the higher-order terms.
  360. The algorithm used becomes numerically unstable around order 30 even
  361. under ideal circumstances.
  362. Choosing order somewhat larger than degree may improve the higher-order
  363. terms.
  364. """
  365. if order is None:
  366. order = degree
  367. n = order+1
  368. # Choose n points that cluster near the endpoints of the interval in
  369. # a way that avoids the Runge phenomenon. Ensure, by including the
  370. # endpoint or not as appropriate, that one point always falls at x
  371. # exactly.
  372. xs = scale*np.cos(np.linspace(0,np.pi,n,endpoint=n % 1)) + x
  373. P = KroghInterpolator(xs, f(xs))
  374. d = P.derivatives(x,der=degree+1)
  375. return np.poly1d((d/factorial(np.arange(degree+1)))[::-1])
  376. class BarycentricInterpolator(_Interpolator1D):
  377. """The interpolating polynomial for a set of points
  378. Constructs a polynomial that passes through a given set of points.
  379. Allows evaluation of the polynomial, efficient changing of the y
  380. values to be interpolated, and updating by adding more x values.
  381. For reasons of numerical stability, this function does not compute
  382. the coefficients of the polynomial.
  383. The values yi need to be provided before the function is
  384. evaluated, but none of the preprocessing depends on them, so rapid
  385. updates are possible.
  386. Parameters
  387. ----------
  388. xi : array_like
  389. 1-d array of x coordinates of the points the polynomial
  390. should pass through
  391. yi : array_like, optional
  392. The y coordinates of the points the polynomial should pass through.
  393. If None, the y values will be supplied later via the `set_y` method.
  394. axis : int, optional
  395. Axis in the yi array corresponding to the x-coordinate values.
  396. Notes
  397. -----
  398. This class uses a "barycentric interpolation" method that treats
  399. the problem as a special case of rational function interpolation.
  400. This algorithm is quite stable, numerically, but even in a world of
  401. exact computation, unless the x coordinates are chosen very
  402. carefully - Chebyshev zeros (e.g. cos(i*pi/n)) are a good choice -
  403. polynomial interpolation itself is a very ill-conditioned process
  404. due to the Runge phenomenon.
  405. Based on Berrut and Trefethen 2004, "Barycentric Lagrange Interpolation".
  406. """
  407. def __init__(self, xi, yi=None, axis=0):
  408. _Interpolator1D.__init__(self, xi, yi, axis)
  409. self.xi = np.asarray(xi)
  410. self.set_yi(yi)
  411. self.n = len(self.xi)
  412. self.wi = np.zeros(self.n)
  413. self.wi[0] = 1
  414. for j in xrange(1,self.n):
  415. self.wi[:j] *= (self.xi[j]-self.xi[:j])
  416. self.wi[j] = np.multiply.reduce(self.xi[:j]-self.xi[j])
  417. self.wi **= -1
  418. def set_yi(self, yi, axis=None):
  419. """
  420. Update the y values to be interpolated
  421. The barycentric interpolation algorithm requires the calculation
  422. of weights, but these depend only on the xi. The yi can be changed
  423. at any time.
  424. Parameters
  425. ----------
  426. yi : array_like
  427. The y coordinates of the points the polynomial should pass through.
  428. If None, the y values will be supplied later.
  429. axis : int, optional
  430. Axis in the yi array corresponding to the x-coordinate values.
  431. """
  432. if yi is None:
  433. self.yi = None
  434. return
  435. self._set_yi(yi, xi=self.xi, axis=axis)
  436. self.yi = self._reshape_yi(yi)
  437. self.n, self.r = self.yi.shape
  438. def add_xi(self, xi, yi=None):
  439. """
  440. Add more x values to the set to be interpolated
  441. The barycentric interpolation algorithm allows easy updating by
  442. adding more points for the polynomial to pass through.
  443. Parameters
  444. ----------
  445. xi : array_like
  446. The x coordinates of the points that the polynomial should pass
  447. through.
  448. yi : array_like, optional
  449. The y coordinates of the points the polynomial should pass through.
  450. Should have shape ``(xi.size, R)``; if R > 1 then the polynomial is
  451. vector-valued.
  452. If `yi` is not given, the y values will be supplied later. `yi` should
  453. be given if and only if the interpolator has y values specified.
  454. """
  455. if yi is not None:
  456. if self.yi is None:
  457. raise ValueError("No previous yi value to update!")
  458. yi = self._reshape_yi(yi, check=True)
  459. self.yi = np.vstack((self.yi,yi))
  460. else:
  461. if self.yi is not None:
  462. raise ValueError("No update to yi provided!")
  463. old_n = self.n
  464. self.xi = np.concatenate((self.xi,xi))
  465. self.n = len(self.xi)
  466. self.wi **= -1
  467. old_wi = self.wi
  468. self.wi = np.zeros(self.n)
  469. self.wi[:old_n] = old_wi
  470. for j in xrange(old_n,self.n):
  471. self.wi[:j] *= (self.xi[j]-self.xi[:j])
  472. self.wi[j] = np.multiply.reduce(self.xi[:j]-self.xi[j])
  473. self.wi **= -1
  474. def __call__(self, x):
  475. """Evaluate the interpolating polynomial at the points x
  476. Parameters
  477. ----------
  478. x : array_like
  479. Points to evaluate the interpolant at.
  480. Returns
  481. -------
  482. y : array_like
  483. Interpolated values. Shape is determined by replacing
  484. the interpolation axis in the original array with the shape of x.
  485. Notes
  486. -----
  487. Currently the code computes an outer product between x and the
  488. weights, that is, it constructs an intermediate array of size
  489. N by len(x), where N is the degree of the polynomial.
  490. """
  491. return _Interpolator1D.__call__(self, x)
  492. def _evaluate(self, x):
  493. if x.size == 0:
  494. p = np.zeros((0, self.r), dtype=self.dtype)
  495. else:
  496. c = x[...,np.newaxis]-self.xi
  497. z = c == 0
  498. c[z] = 1
  499. c = self.wi/c
  500. p = np.dot(c,self.yi)/np.sum(c,axis=-1)[...,np.newaxis]
  501. # Now fix where x==some xi
  502. r = np.nonzero(z)
  503. if len(r) == 1: # evaluation at a scalar
  504. if len(r[0]) > 0: # equals one of the points
  505. p = self.yi[r[0][0]]
  506. else:
  507. p[r[:-1]] = self.yi[r[-1]]
  508. return p
  509. def barycentric_interpolate(xi, yi, x, axis=0):
  510. """
  511. Convenience function for polynomial interpolation.
  512. Constructs a polynomial that passes through a given set of points,
  513. then evaluates the polynomial. For reasons of numerical stability,
  514. this function does not compute the coefficients of the polynomial.
  515. This function uses a "barycentric interpolation" method that treats
  516. the problem as a special case of rational function interpolation.
  517. This algorithm is quite stable, numerically, but even in a world of
  518. exact computation, unless the `x` coordinates are chosen very
  519. carefully - Chebyshev zeros (e.g. cos(i*pi/n)) are a good choice -
  520. polynomial interpolation itself is a very ill-conditioned process
  521. due to the Runge phenomenon.
  522. Parameters
  523. ----------
  524. xi : array_like
  525. 1-d array of x coordinates of the points the polynomial should
  526. pass through
  527. yi : array_like
  528. The y coordinates of the points the polynomial should pass through.
  529. x : scalar or array_like
  530. Points to evaluate the interpolator at.
  531. axis : int, optional
  532. Axis in the yi array corresponding to the x-coordinate values.
  533. Returns
  534. -------
  535. y : scalar or array_like
  536. Interpolated values. Shape is determined by replacing
  537. the interpolation axis in the original array with the shape of x.
  538. See Also
  539. --------
  540. BarycentricInterpolator
  541. Notes
  542. -----
  543. Construction of the interpolation weights is a relatively slow process.
  544. If you want to call this many times with the same xi (but possibly
  545. varying yi or x) you should use the class `BarycentricInterpolator`.
  546. This is what this function uses internally.
  547. """
  548. return BarycentricInterpolator(xi, yi, axis=axis)(x)