polyutils.py 22 KB

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