polyutils.py 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789
  1. """
  2. Utility classes and functions for the polynomial modules.
  3. This module provides: error and warning objects; a polynomial base class;
  4. and some routines used in both the `polynomial` and `chebyshev` modules.
  5. Warning objects
  6. ---------------
  7. .. autosummary::
  8. :toctree: generated/
  9. RankWarning raised in least-squares fit for rank-deficient matrix.
  10. Functions
  11. ---------
  12. .. autosummary::
  13. :toctree: generated/
  14. as_series convert list of array_likes into 1-D arrays of common type.
  15. trimseq remove trailing zeros.
  16. trimcoef remove small trailing coefficients.
  17. getdomain return the domain appropriate for a given set of abscissae.
  18. mapdomain maps points between domains.
  19. mapparms parameters of the linear map between domains.
  20. """
  21. import operator
  22. import functools
  23. import warnings
  24. import numpy as np
  25. from numpy.core.multiarray import dragon4_positional, dragon4_scientific
  26. from numpy.core.umath import absolute
  27. __all__ = [
  28. 'RankWarning', 'as_series', 'trimseq',
  29. 'trimcoef', 'getdomain', 'mapdomain', 'mapparms',
  30. 'format_float']
  31. #
  32. # Warnings and Exceptions
  33. #
  34. class RankWarning(UserWarning):
  35. """Issued by chebfit when the design matrix is rank deficient."""
  36. pass
  37. #
  38. # Helper functions to convert inputs to 1-D arrays
  39. #
  40. def trimseq(seq):
  41. """Remove small Poly series coefficients.
  42. Parameters
  43. ----------
  44. seq : sequence
  45. Sequence of Poly series coefficients. This routine fails for
  46. empty sequences.
  47. Returns
  48. -------
  49. series : sequence
  50. Subsequence with trailing zeros removed. If the resulting sequence
  51. would be empty, return the first element. The returned sequence may
  52. or may not be a view.
  53. Notes
  54. -----
  55. Do not lose the type info if the sequence contains unknown objects.
  56. """
  57. if len(seq) == 0:
  58. return seq
  59. else:
  60. for i in range(len(seq) - 1, -1, -1):
  61. if seq[i] != 0:
  62. break
  63. return seq[:i+1]
  64. def as_series(alist, trim=True):
  65. """
  66. Return argument as a list of 1-d arrays.
  67. The returned list contains array(s) of dtype double, complex double, or
  68. object. A 1-d argument of shape ``(N,)`` is parsed into ``N`` arrays of
  69. size one; a 2-d argument of shape ``(M,N)`` is parsed into ``M`` arrays
  70. of size ``N`` (i.e., is "parsed by row"); and a higher dimensional array
  71. raises a Value Error if it is not first reshaped into either a 1-d or 2-d
  72. array.
  73. Parameters
  74. ----------
  75. alist : array_like
  76. A 1- or 2-d array_like
  77. trim : boolean, optional
  78. When True, trailing zeros are removed from the inputs.
  79. When False, the inputs are passed through intact.
  80. Returns
  81. -------
  82. [a1, a2,...] : list of 1-D arrays
  83. A copy of the input data as a list of 1-d arrays.
  84. Raises
  85. ------
  86. ValueError
  87. Raised when `as_series` cannot convert its input to 1-d arrays, or at
  88. least one of the resulting arrays is empty.
  89. Examples
  90. --------
  91. >>> from numpy.polynomial import polyutils as pu
  92. >>> a = np.arange(4)
  93. >>> pu.as_series(a)
  94. [array([0.]), array([1.]), array([2.]), array([3.])]
  95. >>> b = np.arange(6).reshape((2,3))
  96. >>> pu.as_series(b)
  97. [array([0., 1., 2.]), array([3., 4., 5.])]
  98. >>> pu.as_series((1, np.arange(3), np.arange(2, dtype=np.float16)))
  99. [array([1.]), array([0., 1., 2.]), array([0., 1.])]
  100. >>> pu.as_series([2, [1.1, 0.]])
  101. [array([2.]), array([1.1])]
  102. >>> pu.as_series([2, [1.1, 0.]], trim=False)
  103. [array([2.]), array([1.1, 0. ])]
  104. """
  105. arrays = [np.array(a, ndmin=1, copy=False) for a in alist]
  106. if min([a.size for a in arrays]) == 0:
  107. raise ValueError("Coefficient array is empty")
  108. if any(a.ndim != 1 for a in arrays):
  109. raise ValueError("Coefficient array is not 1-d")
  110. if trim:
  111. arrays = [trimseq(a) for a in arrays]
  112. if any(a.dtype == np.dtype(object) for a in arrays):
  113. ret = []
  114. for a in arrays:
  115. if a.dtype != np.dtype(object):
  116. tmp = np.empty(len(a), dtype=np.dtype(object))
  117. tmp[:] = a[:]
  118. ret.append(tmp)
  119. else:
  120. ret.append(a.copy())
  121. else:
  122. try:
  123. dtype = np.common_type(*arrays)
  124. except Exception as e:
  125. raise ValueError("Coefficient arrays have no common type") from e
  126. ret = [np.array(a, copy=True, dtype=dtype) for a in arrays]
  127. return ret
  128. def trimcoef(c, tol=0):
  129. """
  130. Remove "small" "trailing" coefficients from a polynomial.
  131. "Small" means "small in absolute value" and is controlled by the
  132. parameter `tol`; "trailing" means highest order coefficient(s), e.g., in
  133. ``[0, 1, 1, 0, 0]`` (which represents ``0 + x + x**2 + 0*x**3 + 0*x**4``)
  134. both the 3-rd and 4-th order coefficients would be "trimmed."
  135. Parameters
  136. ----------
  137. c : array_like
  138. 1-d array of coefficients, ordered from lowest order to highest.
  139. tol : number, optional
  140. Trailing (i.e., highest order) elements with absolute value less
  141. than or equal to `tol` (default value is zero) are removed.
  142. Returns
  143. -------
  144. trimmed : ndarray
  145. 1-d array with trailing zeros removed. If the resulting series
  146. would be empty, a series containing a single zero is returned.
  147. Raises
  148. ------
  149. ValueError
  150. If `tol` < 0
  151. See Also
  152. --------
  153. trimseq
  154. Examples
  155. --------
  156. >>> from numpy.polynomial import polyutils as pu
  157. >>> pu.trimcoef((0,0,3,0,5,0,0))
  158. array([0., 0., 3., 0., 5.])
  159. >>> pu.trimcoef((0,0,1e-3,0,1e-5,0,0),1e-3) # item == tol is trimmed
  160. array([0.])
  161. >>> i = complex(0,1) # works for complex
  162. >>> pu.trimcoef((3e-4,1e-3*(1-i),5e-4,2e-5*(1+i)), 1e-3)
  163. array([0.0003+0.j , 0.001 -0.001j])
  164. """
  165. if tol < 0:
  166. raise ValueError("tol must be non-negative")
  167. [c] = as_series([c])
  168. [ind] = np.nonzero(np.abs(c) > tol)
  169. if len(ind) == 0:
  170. return c[:1]*0
  171. else:
  172. return c[:ind[-1] + 1].copy()
  173. def getdomain(x):
  174. """
  175. Return a domain suitable for given abscissae.
  176. Find a domain suitable for a polynomial or Chebyshev series
  177. defined at the values supplied.
  178. Parameters
  179. ----------
  180. x : array_like
  181. 1-d array of abscissae whose domain will be determined.
  182. Returns
  183. -------
  184. domain : ndarray
  185. 1-d array containing two values. If the inputs are complex, then
  186. the two returned points are the lower left and upper right corners
  187. of the smallest rectangle (aligned with the axes) in the complex
  188. plane containing the points `x`. If the inputs are real, then the
  189. two points are the ends of the smallest interval containing the
  190. points `x`.
  191. See Also
  192. --------
  193. mapparms, mapdomain
  194. Examples
  195. --------
  196. >>> from numpy.polynomial import polyutils as pu
  197. >>> points = np.arange(4)**2 - 5; points
  198. array([-5, -4, -1, 4])
  199. >>> pu.getdomain(points)
  200. array([-5., 4.])
  201. >>> c = np.exp(complex(0,1)*np.pi*np.arange(12)/6) # unit circle
  202. >>> pu.getdomain(c)
  203. array([-1.-1.j, 1.+1.j])
  204. """
  205. [x] = as_series([x], trim=False)
  206. if x.dtype.char in np.typecodes['Complex']:
  207. rmin, rmax = x.real.min(), x.real.max()
  208. imin, imax = x.imag.min(), x.imag.max()
  209. return np.array((complex(rmin, imin), complex(rmax, imax)))
  210. else:
  211. return np.array((x.min(), x.max()))
  212. def mapparms(old, new):
  213. """
  214. Linear map parameters between domains.
  215. Return the parameters of the linear map ``offset + scale*x`` that maps
  216. `old` to `new` such that ``old[i] -> new[i]``, ``i = 0, 1``.
  217. Parameters
  218. ----------
  219. old, new : array_like
  220. Domains. Each domain must (successfully) convert to a 1-d array
  221. containing precisely two values.
  222. Returns
  223. -------
  224. offset, scale : scalars
  225. The map ``L(x) = offset + scale*x`` maps the first domain to the
  226. second.
  227. See Also
  228. --------
  229. getdomain, mapdomain
  230. Notes
  231. -----
  232. Also works for complex numbers, and thus can be used to calculate the
  233. parameters required to map any line in the complex plane to any other
  234. line therein.
  235. Examples
  236. --------
  237. >>> from numpy.polynomial import polyutils as pu
  238. >>> pu.mapparms((-1,1),(-1,1))
  239. (0.0, 1.0)
  240. >>> pu.mapparms((1,-1),(-1,1))
  241. (-0.0, -1.0)
  242. >>> i = complex(0,1)
  243. >>> pu.mapparms((-i,-1),(1,i))
  244. ((1+1j), (1-0j))
  245. """
  246. oldlen = old[1] - old[0]
  247. newlen = new[1] - new[0]
  248. off = (old[1]*new[0] - old[0]*new[1])/oldlen
  249. scl = newlen/oldlen
  250. return off, scl
  251. def mapdomain(x, old, new):
  252. """
  253. Apply linear map to input points.
  254. The linear map ``offset + scale*x`` that maps the domain `old` to
  255. the domain `new` is applied to the points `x`.
  256. Parameters
  257. ----------
  258. x : array_like
  259. Points to be mapped. If `x` is a subtype of ndarray the subtype
  260. will be preserved.
  261. old, new : array_like
  262. The two domains that determine the map. Each must (successfully)
  263. convert to 1-d arrays containing precisely two values.
  264. Returns
  265. -------
  266. x_out : ndarray
  267. Array of points of the same shape as `x`, after application of the
  268. linear map between the two domains.
  269. See Also
  270. --------
  271. getdomain, mapparms
  272. Notes
  273. -----
  274. Effectively, this implements:
  275. .. math::
  276. x\\_out = new[0] + m(x - old[0])
  277. where
  278. .. math::
  279. m = \\frac{new[1]-new[0]}{old[1]-old[0]}
  280. Examples
  281. --------
  282. >>> from numpy.polynomial import polyutils as pu
  283. >>> old_domain = (-1,1)
  284. >>> new_domain = (0,2*np.pi)
  285. >>> x = np.linspace(-1,1,6); x
  286. array([-1. , -0.6, -0.2, 0.2, 0.6, 1. ])
  287. >>> x_out = pu.mapdomain(x, old_domain, new_domain); x_out
  288. array([ 0. , 1.25663706, 2.51327412, 3.76991118, 5.02654825, # may vary
  289. 6.28318531])
  290. >>> x - pu.mapdomain(x_out, new_domain, old_domain)
  291. array([0., 0., 0., 0., 0., 0.])
  292. Also works for complex numbers (and thus can be used to map any line in
  293. the complex plane to any other line therein).
  294. >>> i = complex(0,1)
  295. >>> old = (-1 - i, 1 + i)
  296. >>> new = (-1 + i, 1 - i)
  297. >>> z = np.linspace(old[0], old[1], 6); z
  298. array([-1. -1.j , -0.6-0.6j, -0.2-0.2j, 0.2+0.2j, 0.6+0.6j, 1. +1.j ])
  299. >>> new_z = pu.mapdomain(z, old, new); new_z
  300. array([-1.0+1.j , -0.6+0.6j, -0.2+0.2j, 0.2-0.2j, 0.6-0.6j, 1.0-1.j ]) # may vary
  301. """
  302. x = np.asanyarray(x)
  303. off, scl = mapparms(old, new)
  304. return off + scl*x
  305. def _nth_slice(i, ndim):
  306. sl = [np.newaxis] * ndim
  307. sl[i] = slice(None)
  308. return tuple(sl)
  309. def _vander_nd(vander_fs, points, degrees):
  310. r"""
  311. A generalization of the Vandermonde matrix for N dimensions
  312. The result is built by combining the results of 1d Vandermonde matrices,
  313. .. math::
  314. W[i_0, \ldots, i_M, j_0, \ldots, j_N] = \prod_{k=0}^N{V_k(x_k)[i_0, \ldots, i_M, j_k]}
  315. where
  316. .. math::
  317. N &= \texttt{len(points)} = \texttt{len(degrees)} = \texttt{len(vander\_fs)} \\
  318. M &= \texttt{points[k].ndim} \\
  319. V_k &= \texttt{vander\_fs[k]} \\
  320. x_k &= \texttt{points[k]} \\
  321. 0 \le j_k &\le \texttt{degrees[k]}
  322. Expanding the one-dimensional :math:`V_k` functions gives:
  323. .. math::
  324. W[i_0, \ldots, i_M, j_0, \ldots, j_N] = \prod_{k=0}^N{B_{k, j_k}(x_k[i_0, \ldots, i_M])}
  325. where :math:`B_{k,m}` is the m'th basis of the polynomial construction used along
  326. dimension :math:`k`. For a regular polynomial, :math:`B_{k, m}(x) = P_m(x) = x^m`.
  327. Parameters
  328. ----------
  329. vander_fs : Sequence[function(array_like, int) -> ndarray]
  330. The 1d vander function to use for each axis, such as ``polyvander``
  331. points : Sequence[array_like]
  332. Arrays of point coordinates, all of the same shape. The dtypes
  333. will be converted to either float64 or complex128 depending on
  334. whether any of the elements are complex. Scalars are converted to
  335. 1-D arrays.
  336. This must be the same length as `vander_fs`.
  337. degrees : Sequence[int]
  338. The maximum degree (inclusive) to use for each axis.
  339. This must be the same length as `vander_fs`.
  340. Returns
  341. -------
  342. vander_nd : ndarray
  343. An array of shape ``points[0].shape + tuple(d + 1 for d in degrees)``.
  344. """
  345. n_dims = len(vander_fs)
  346. if n_dims != len(points):
  347. raise ValueError(
  348. f"Expected {n_dims} dimensions of sample points, got {len(points)}")
  349. if n_dims != len(degrees):
  350. raise ValueError(
  351. f"Expected {n_dims} dimensions of degrees, got {len(degrees)}")
  352. if n_dims == 0:
  353. raise ValueError("Unable to guess a dtype or shape when no points are given")
  354. # convert to the same shape and type
  355. points = tuple(np.array(tuple(points), copy=False) + 0.0)
  356. # produce the vandermonde matrix for each dimension, placing the last
  357. # axis of each in an independent trailing axis of the output
  358. vander_arrays = (
  359. vander_fs[i](points[i], degrees[i])[(...,) + _nth_slice(i, n_dims)]
  360. for i in range(n_dims)
  361. )
  362. # we checked this wasn't empty already, so no `initial` needed
  363. return functools.reduce(operator.mul, vander_arrays)
  364. def _vander_nd_flat(vander_fs, points, degrees):
  365. """
  366. Like `_vander_nd`, but flattens the last ``len(degrees)`` axes into a single axis
  367. Used to implement the public ``<type>vander<n>d`` functions.
  368. """
  369. v = _vander_nd(vander_fs, points, degrees)
  370. return v.reshape(v.shape[:-len(degrees)] + (-1,))
  371. def _fromroots(line_f, mul_f, roots):
  372. """
  373. Helper function used to implement the ``<type>fromroots`` functions.
  374. Parameters
  375. ----------
  376. line_f : function(float, float) -> ndarray
  377. The ``<type>line`` function, such as ``polyline``
  378. mul_f : function(array_like, array_like) -> ndarray
  379. The ``<type>mul`` function, such as ``polymul``
  380. roots
  381. See the ``<type>fromroots`` functions for more detail
  382. """
  383. if len(roots) == 0:
  384. return np.ones(1)
  385. else:
  386. [roots] = as_series([roots], trim=False)
  387. roots.sort()
  388. p = [line_f(-r, 1) for r in roots]
  389. n = len(p)
  390. while n > 1:
  391. m, r = divmod(n, 2)
  392. tmp = [mul_f(p[i], p[i+m]) for i in range(m)]
  393. if r:
  394. tmp[0] = mul_f(tmp[0], p[-1])
  395. p = tmp
  396. n = m
  397. return p[0]
  398. def _valnd(val_f, c, *args):
  399. """
  400. Helper function used to implement the ``<type>val<n>d`` functions.
  401. Parameters
  402. ----------
  403. val_f : function(array_like, array_like, tensor: bool) -> array_like
  404. The ``<type>val`` function, such as ``polyval``
  405. c, args
  406. See the ``<type>val<n>d`` functions for more detail
  407. """
  408. args = [np.asanyarray(a) for a in args]
  409. shape0 = args[0].shape
  410. if not all((a.shape == shape0 for a in args[1:])):
  411. if len(args) == 3:
  412. raise ValueError('x, y, z are incompatible')
  413. elif len(args) == 2:
  414. raise ValueError('x, y are incompatible')
  415. else:
  416. raise ValueError('ordinates are incompatible')
  417. it = iter(args)
  418. x0 = next(it)
  419. # use tensor on only the first
  420. c = val_f(x0, c)
  421. for xi in it:
  422. c = val_f(xi, c, tensor=False)
  423. return c
  424. def _gridnd(val_f, c, *args):
  425. """
  426. Helper function used to implement the ``<type>grid<n>d`` functions.
  427. Parameters
  428. ----------
  429. val_f : function(array_like, array_like, tensor: bool) -> array_like
  430. The ``<type>val`` function, such as ``polyval``
  431. c, args
  432. See the ``<type>grid<n>d`` functions for more detail
  433. """
  434. for xi in args:
  435. c = val_f(xi, c)
  436. return c
  437. def _div(mul_f, c1, c2):
  438. """
  439. Helper function used to implement the ``<type>div`` functions.
  440. Implementation uses repeated subtraction of c2 multiplied by the nth basis.
  441. For some polynomial types, a more efficient approach may be possible.
  442. Parameters
  443. ----------
  444. mul_f : function(array_like, array_like) -> array_like
  445. The ``<type>mul`` function, such as ``polymul``
  446. c1, c2
  447. See the ``<type>div`` functions for more detail
  448. """
  449. # c1, c2 are trimmed copies
  450. [c1, c2] = as_series([c1, c2])
  451. if c2[-1] == 0:
  452. raise ZeroDivisionError()
  453. lc1 = len(c1)
  454. lc2 = len(c2)
  455. if lc1 < lc2:
  456. return c1[:1]*0, c1
  457. elif lc2 == 1:
  458. return c1/c2[-1], c1[:1]*0
  459. else:
  460. quo = np.empty(lc1 - lc2 + 1, dtype=c1.dtype)
  461. rem = c1
  462. for i in range(lc1 - lc2, - 1, -1):
  463. p = mul_f([0]*i + [1], c2)
  464. q = rem[-1]/p[-1]
  465. rem = rem[:-1] - q*p[:-1]
  466. quo[i] = q
  467. return quo, trimseq(rem)
  468. def _add(c1, c2):
  469. """ Helper function used to implement the ``<type>add`` functions. """
  470. # c1, c2 are trimmed copies
  471. [c1, c2] = as_series([c1, c2])
  472. if len(c1) > len(c2):
  473. c1[:c2.size] += c2
  474. ret = c1
  475. else:
  476. c2[:c1.size] += c1
  477. ret = c2
  478. return trimseq(ret)
  479. def _sub(c1, c2):
  480. """ Helper function used to implement the ``<type>sub`` functions. """
  481. # c1, c2 are trimmed copies
  482. [c1, c2] = as_series([c1, c2])
  483. if len(c1) > len(c2):
  484. c1[:c2.size] -= c2
  485. ret = c1
  486. else:
  487. c2 = -c2
  488. c2[:c1.size] += c1
  489. ret = c2
  490. return trimseq(ret)
  491. def _fit(vander_f, x, y, deg, rcond=None, full=False, w=None):
  492. """
  493. Helper function used to implement the ``<type>fit`` functions.
  494. Parameters
  495. ----------
  496. vander_f : function(array_like, int) -> ndarray
  497. The 1d vander function, such as ``polyvander``
  498. c1, c2
  499. See the ``<type>fit`` functions for more detail
  500. """
  501. x = np.asarray(x) + 0.0
  502. y = np.asarray(y) + 0.0
  503. deg = np.asarray(deg)
  504. # check arguments.
  505. if deg.ndim > 1 or deg.dtype.kind not in 'iu' or deg.size == 0:
  506. raise TypeError("deg must be an int or non-empty 1-D array of int")
  507. if deg.min() < 0:
  508. raise ValueError("expected deg >= 0")
  509. if x.ndim != 1:
  510. raise TypeError("expected 1D vector for x")
  511. if x.size == 0:
  512. raise TypeError("expected non-empty vector for x")
  513. if y.ndim < 1 or y.ndim > 2:
  514. raise TypeError("expected 1D or 2D array for y")
  515. if len(x) != len(y):
  516. raise TypeError("expected x and y to have same length")
  517. if deg.ndim == 0:
  518. lmax = deg
  519. order = lmax + 1
  520. van = vander_f(x, lmax)
  521. else:
  522. deg = np.sort(deg)
  523. lmax = deg[-1]
  524. order = len(deg)
  525. van = vander_f(x, lmax)[:, deg]
  526. # set up the least squares matrices in transposed form
  527. lhs = van.T
  528. rhs = y.T
  529. if w is not None:
  530. w = np.asarray(w) + 0.0
  531. if w.ndim != 1:
  532. raise TypeError("expected 1D vector for w")
  533. if len(x) != len(w):
  534. raise TypeError("expected x and w to have same length")
  535. # apply weights. Don't use inplace operations as they
  536. # can cause problems with NA.
  537. lhs = lhs * w
  538. rhs = rhs * w
  539. # set rcond
  540. if rcond is None:
  541. rcond = len(x)*np.finfo(x.dtype).eps
  542. # Determine the norms of the design matrix columns.
  543. if issubclass(lhs.dtype.type, np.complexfloating):
  544. scl = np.sqrt((np.square(lhs.real) + np.square(lhs.imag)).sum(1))
  545. else:
  546. scl = np.sqrt(np.square(lhs).sum(1))
  547. scl[scl == 0] = 1
  548. # Solve the least squares problem.
  549. c, resids, rank, s = np.linalg.lstsq(lhs.T/scl, rhs.T, rcond)
  550. c = (c.T/scl).T
  551. # Expand c to include non-fitted coefficients which are set to zero
  552. if deg.ndim > 0:
  553. if c.ndim == 2:
  554. cc = np.zeros((lmax+1, c.shape[1]), dtype=c.dtype)
  555. else:
  556. cc = np.zeros(lmax+1, dtype=c.dtype)
  557. cc[deg] = c
  558. c = cc
  559. # warn on rank reduction
  560. if rank != order and not full:
  561. msg = "The fit may be poorly conditioned"
  562. warnings.warn(msg, RankWarning, stacklevel=2)
  563. if full:
  564. return c, [resids, rank, s, rcond]
  565. else:
  566. return c
  567. def _pow(mul_f, c, pow, maxpower):
  568. """
  569. Helper function used to implement the ``<type>pow`` functions.
  570. Parameters
  571. ----------
  572. mul_f : function(array_like, array_like) -> ndarray
  573. The ``<type>mul`` function, such as ``polymul``
  574. c : array_like
  575. 1-D array of array of series coefficients
  576. pow, maxpower
  577. See the ``<type>pow`` functions for more detail
  578. """
  579. # c is a trimmed copy
  580. [c] = as_series([c])
  581. power = int(pow)
  582. if power != pow or power < 0:
  583. raise ValueError("Power must be a non-negative integer.")
  584. elif maxpower is not None and power > maxpower:
  585. raise ValueError("Power is too large")
  586. elif power == 0:
  587. return np.array([1], dtype=c.dtype)
  588. elif power == 1:
  589. return c
  590. else:
  591. # This can be made more efficient by using powers of two
  592. # in the usual way.
  593. prd = c
  594. for i in range(2, power + 1):
  595. prd = mul_f(prd, c)
  596. return prd
  597. def _deprecate_as_int(x, desc):
  598. """
  599. Like `operator.index`, but emits a deprecation warning when passed a float
  600. Parameters
  601. ----------
  602. x : int-like, or float with integral value
  603. Value to interpret as an integer
  604. desc : str
  605. description to include in any error message
  606. Raises
  607. ------
  608. TypeError : if x is a non-integral float or non-numeric
  609. DeprecationWarning : if x is an integral float
  610. """
  611. try:
  612. return operator.index(x)
  613. except TypeError as e:
  614. # Numpy 1.17.0, 2019-03-11
  615. try:
  616. ix = int(x)
  617. except TypeError:
  618. pass
  619. else:
  620. if ix == x:
  621. warnings.warn(
  622. f"In future, this will raise TypeError, as {desc} will "
  623. "need to be an integer not just an integral float.",
  624. DeprecationWarning,
  625. stacklevel=3
  626. )
  627. return ix
  628. raise TypeError(f"{desc} must be an integer") from e
  629. def format_float(x, parens=False):
  630. if not np.issubdtype(type(x), np.floating):
  631. return str(x)
  632. opts = np.get_printoptions()
  633. if np.isnan(x):
  634. return opts['nanstr']
  635. elif np.isinf(x):
  636. return opts['infstr']
  637. exp_format = False
  638. if x != 0:
  639. a = absolute(x)
  640. if a >= 1.e8 or a < 10**min(0, -(opts['precision']-1)//2):
  641. exp_format = True
  642. trim, unique = '0', True
  643. if opts['floatmode'] == 'fixed':
  644. trim, unique = 'k', False
  645. if exp_format:
  646. s = dragon4_scientific(x, precision=opts['precision'],
  647. unique=unique, trim=trim,
  648. sign=opts['sign'] == '+')
  649. if parens:
  650. s = '(' + s + ')'
  651. else:
  652. s = dragon4_positional(x, precision=opts['precision'],
  653. fractional=True,
  654. unique=unique, trim=trim,
  655. sign=opts['sign'] == '+')
  656. return s