polyutils.py 22 KB

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