1"""

2 Utility classes and functions for the polynomial modules.

3

4 This module provides: error and warning objects; a polynomial base class;

5 and some routines used in both the `polynomial` and `chebyshev` modules.

6

7 Warning objects

8---------------

9

10.. autosummary::

11 :toctree: generated/

12

13 RankWarning raised in least-squares fit for rank-deficient matrix.

14

15 Functions

16---------

17

18.. autosummary::

19 :toctree: generated/

20

21 as_series convert list of array_likes into 1-D arrays of common type.

22 trimseq remove trailing zeros.

23 trimcoef remove small trailing coefficients.

24 getdomain return the domain appropriate for a given set of abscissae.

25 mapdomain maps points between domains.

26 mapparms parameters of the linear map between domains.

27

28"""

29 import operator

30 import functools

31 import warnings

32

33 import numpy as np

34

35 from numpy.core.multiarray import dragon4_positional, dragon4_scientific

36 from numpy.core.umath import absolute

37

38__all__ = [

39 'RankWarning', 'as_series', 'trimseq',

40 'trimcoef', 'getdomain', 'mapdomain', 'mapparms',

41 'format_float']

42

43#

44# Warnings and Exceptions

45#

46

47 class RankWarning(UserWarning):

48 """Issued by chebfit when the design matrix is rank deficient."""

49 pass

50

51#

52# Helper functions to convert inputs to 1-D arrays

53#

54 def trimseq(seq):

55 """Remove small Poly series coefficients.

56

57 Parameters

58 ----------

59 seq : sequence

60 Sequence of Poly series coefficients. This routine fails for

61 empty sequences.

62

63 Returns

64 -------

65 series : sequence

66 Subsequence with trailing zeros removed. If the resulting sequence

67 would be empty, return the first element. The returned sequence may

68 or may not be a view.

69

70 Notes

71 -----

72 Do not lose the type info if the sequence contains unknown objects.

73

74 """

75 if len(seq) == 0:

76 return seq

77 else:

78 for i in range(len(seq) - 1, -1, -1):

79 if seq[i] != 0:

80 break

81 return seq[:i+1]

82

83

84 def as_series(alist, trim=True):

85 """

86 Return argument as a list of 1-d arrays.

87

88 The returned list contains array(s) of dtype double, complex double, or

89 object. A 1-d argument of shape ``(N,)`` is parsed into ``N`` arrays of

90 size one; a 2-d argument of shape ``(M,N)`` is parsed into ``M`` arrays

91 of size ``N`` (i.e., is "parsed by row"); and a higher dimensional array

92 raises a Value Error if it is not first reshaped into either a 1-d or 2-d

93 array.

94

95 Parameters

96 ----------

97 alist : array_like

98 A 1- or 2-d array_like

99 trim : boolean, optional

100 When True, trailing zeros are removed from the inputs.

101 When False, the inputs are passed through intact.

102

103 Returns

104 -------

105 [a1, a2,...] : list of 1-D arrays

106 A copy of the input data as a list of 1-d arrays.

107

108 Raises

109 ------

110 ValueError

111 Raised when `as_series` cannot convert its input to 1-d arrays, or at

112 least one of the resulting arrays is empty.

113

114 Examples

115 --------

116 >>> from numpy.polynomial import polyutils as pu

117 >>> a = np.arange(4)

118 >>> pu.as_series(a)

119 [array([0.]), array([1.]), array([2.]), array([3.])]

120 >>> b = np.arange(6).reshape((2,3))

121 >>> pu.as_series(b)

122 [array([0., 1., 2.]), array([3., 4., 5.])]

123

124 >>> pu.as_series((1, np.arange(3), np.arange(2, dtype=np.float16)))

125 [array([1.]), array([0., 1., 2.]), array([0., 1.])]

126

127 >>> pu.as_series([2, [1.1, 0.]])

128 [array([2.]), array([1.1])]

129

130 >>> pu.as_series([2, [1.1, 0.]], trim=False)

131 [array([2.]), array([1.1, 0. ])]

132

133 """

134 arrays = [np.array(a, ndmin=1, copy=False) for a in alist]

135 if min([a.size for a in arrays]) == 0:

136 raise ValueError("Coefficient array is empty")

137 if any(a.ndim != 1 for a in arrays):

138 raise ValueError("Coefficient array is not 1-d")

139 if trim:

140 arrays = [trimseq(a) for a in arrays]

141

142 if any(a.dtype == np.dtype(object) for a in arrays):

143 ret = []

144 for a in arrays:

145 if a.dtype != np.dtype(object):

146 tmp = np.empty(len(a), dtype=np.dtype(object))

147 tmp[:] = a[:]

148 ret.append(tmp)

149 else:

150 ret.append(a.copy())

151 else:

152 try:

153 dtype = np.common_type(*arrays)

154 except Exception as e:

155 raise ValueError("Coefficient arrays have no common type") from e

156 ret = [np.array(a, copy=True, dtype=dtype) for a in arrays]

157 return ret

158

159

160 def trimcoef(c, tol=0):

161 """

162 Remove "small" "trailing" coefficients from a polynomial.

163

164 "Small" means "small in absolute value" and is controlled by the

165 parameter `tol`; "trailing" means highest order coefficient(s), e.g., in

166 ``[0, 1, 1, 0, 0]`` (which represents ``0 + x + x**2 + 0*x**3 + 0*x**4``)

167 both the 3-rd and 4-th order coefficients would be "trimmed."

168

169 Parameters

170 ----------

171 c : array_like

172 1-d array of coefficients, ordered from lowest order to highest.

173 tol : number, optional

174 Trailing (i.e., highest order) elements with absolute value less

175 than or equal to `tol` (default value is zero) are removed.

176

177 Returns

178 -------

179 trimmed : ndarray

180 1-d array with trailing zeros removed. If the resulting series

181 would be empty, a series containing a single zero is returned.

182

183 Raises

184 ------

185 ValueError

186 If `tol` < 0

187

188 See Also

189 --------

190 trimseq

191

192 Examples

193 --------

194 >>> from numpy.polynomial import polyutils as pu

195 >>> pu.trimcoef((0,0,3,0,5,0,0))

196 array([0., 0., 3., 0., 5.])

197 >>> pu.trimcoef((0,0,1e-3,0,1e-5,0,0),1e-3) # item == tol is trimmed

198 array([0.])

199 >>> i = complex(0,1) # works for complex

200 >>> pu.trimcoef((3e-4,1e-3*(1-i),5e-4,2e-5*(1+i)), 1e-3)

201 array([0.0003+0.j , 0.001 -0.001j])

202

203 """

204 if tol < 0:

205 raise ValueError("tol must be non-negative")

206

207 [c] = as_series([c])

208 [ind] = np.nonzero(np.abs(c) > tol)

209 if len(ind) == 0:

210 return c[:1]*0

211 else:

212 return c[:ind[-1] + 1].copy()

213

214 def getdomain(x):

215 """

216 Return a domain suitable for given abscissae.

217

218 Find a domain suitable for a polynomial or Chebyshev series

219 defined at the values supplied.

220

221 Parameters

222 ----------

223 x : array_like

224 1-d array of abscissae whose domain will be determined.

225

226 Returns

227 -------

228 domain : ndarray

229 1-d array containing two values. If the inputs are complex, then

230 the two returned points are the lower left and upper right corners

231 of the smallest rectangle (aligned with the axes) in the complex

232 plane containing the points `x`. If the inputs are real, then the

233 two points are the ends of the smallest interval containing the

234 points `x`.

235

236 See Also

237 --------

238 mapparms, mapdomain

239

240 Examples

241 --------

242 >>> from numpy.polynomial import polyutils as pu

243 >>> points = np.arange(4)**2 - 5; points

244 array([-5, -4, -1, 4])

245 >>> pu.getdomain(points)

246 array([-5., 4.])

247 >>> c = np.exp(complex(0,1)*np.pi*np.arange(12)/6) # unit circle

248 >>> pu.getdomain(c)

249 array([-1.-1.j, 1.+1.j])

250

251 """

252 [x] = as_series([x], trim=False)

253 if x.dtype.char in np.typecodes['Complex']:

254 rmin, rmax = x.real.min(), x.real.max()

255 imin, imax = x.imag.min(), x.imag.max()

256 return np.array((complex(rmin, imin), complex(rmax, imax)))

257 else:

258 return np.array((x.min(), x.max()))

259

260 def mapparms(old, new):

261 """

262 Linear map parameters between domains.

263

264 Return the parameters of the linear map ``offset + scale*x`` that maps

265 `old` to `new` such that ``old[i] -> new[i]``, ``i = 0, 1``.

266

267 Parameters

268 ----------

269 old, new : array_like

270 Domains. Each domain must (successfully) convert to a 1-d array

271 containing precisely two values.

272

273 Returns

274 -------

275 offset, scale : scalars

276 The map ``L(x) = offset + scale*x`` maps the first domain to the

277 second.

278

279 See Also

280 --------

281 getdomain, mapdomain

282

283 Notes

284 -----

285 Also works for complex numbers, and thus can be used to calculate the

286 parameters required to map any line in the complex plane to any other

287 line therein.

288

289 Examples

290 --------

291 >>> from numpy.polynomial import polyutils as pu

292 >>> pu.mapparms((-1,1),(-1,1))

293 (0.0, 1.0)

294 >>> pu.mapparms((1,-1),(-1,1))

295 (-0.0, -1.0)

296 >>> i = complex(0,1)

297 >>> pu.mapparms((-i,-1),(1,i))

298 ((1+1j), (1-0j))

299

300 """

301 oldlen = old[1] - old[0]

302 newlen = new[1] - new[0]

303 off = (old[1]*new[0] - old[0]*new[1])/oldlen

304 scl = newlen/oldlen

305 return off, scl

306

307 def mapdomain(x, old, new):

308 """

309 Apply linear map to input points.

310

311 The linear map ``offset + scale*x`` that maps the domain `old` to

312 the domain `new` is applied to the points `x`.

313

314 Parameters

315 ----------

316 x : array_like

317 Points to be mapped. If `x` is a subtype of ndarray the subtype

318 will be preserved.

319 old, new : array_like

320 The two domains that determine the map. Each must (successfully)

321 convert to 1-d arrays containing precisely two values.

322

323 Returns

324 -------

325 x_out : ndarray

326 Array of points of the same shape as `x`, after application of the

327 linear map between the two domains.

328

329 See Also

330 --------

331 getdomain, mapparms

332

333 Notes

334 -----

335 Effectively, this implements:

336

337 .. math::

338 x\\_out = new[0] + m(x - old[0])

339

340 where

341

342 .. math::

343 m = \\frac{new[1]-new[0]}{old[1]-old[0]}

344

345 Examples

346 --------

347 >>> from numpy.polynomial import polyutils as pu

348 >>> old_domain = (-1,1)

349 >>> new_domain = (0,2*np.pi)

350 >>> x = np.linspace(-1,1,6); x

351 array([-1. , -0.6, -0.2, 0.2, 0.6, 1. ])

352 >>> x_out = pu.mapdomain(x, old_domain, new_domain); x_out

353 array([ 0. , 1.25663706, 2.51327412, 3.76991118, 5.02654825, # may vary

354 6.28318531])

355 >>> x - pu.mapdomain(x_out, new_domain, old_domain)

356 array([0., 0., 0., 0., 0., 0.])

357

358 Also works for complex numbers (and thus can be used to map any line in

359 the complex plane to any other line therein).

360

361 >>> i = complex(0,1)

362 >>> old = (-1 - i, 1 + i)

363 >>> new = (-1 + i, 1 - i)

364 >>> z = np.linspace(old[0], old[1], 6); z

365 array([-1. -1.j , -0.6-0.6j, -0.2-0.2j, 0.2+0.2j, 0.6+0.6j, 1. +1.j ])

366 >>> new_z = pu.mapdomain(z, old, new); new_z

367 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

368

369 """

370 x = np.asanyarray(x)

371 off, scl = mapparms(old, new)

372 return off + scl*x

373

374

375 def _nth_slice(i, ndim):

376 sl = [np.newaxis] * ndim

377 sl[i] = slice(None)

378 return tuple(sl)

379

380

381 def _vander_nd(vander_fs, points, degrees):

382 r"""

383 A generalization of the Vandermonde matrix for N dimensions

384

385 The result is built by combining the results of 1d Vandermonde matrices,

386

387 .. math::

388 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]}

389

390 where

391

392 .. math::

393 N &= \texttt{len(points)} = \texttt{len(degrees)} = \texttt{len(vander\_fs)} \\

394 M &= \texttt{points[k].ndim} \\

395 V_k &= \texttt{vander\_fs[k]} \\

396 x_k &= \texttt{points[k]} \\

397 0 \le j_k &\le \texttt{degrees[k]}

398

399 Expanding the one-dimensional :math:`V_k` functions gives:

400

401 .. math::

402 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])}

403

404 where :math:`B_{k,m}` is the m'th basis of the polynomial construction used along

405 dimension :math:`k`. For a regular polynomial, :math:`B_{k, m}(x) = P_m(x) = x^m`.

406

407 Parameters

408 ----------

409 vander_fs : Sequence[function(array_like, int) -> ndarray]

410 The 1d vander function to use for each axis, such as ``polyvander``

411 points : Sequence[array_like]

412 Arrays of point coordinates, all of the same shape. The dtypes

413 will be converted to either float64 or complex128 depending on

414 whether any of the elements are complex. Scalars are converted to

415 1-D arrays.

416 This must be the same length as `vander_fs`.

417 degrees : Sequence[int]

418 The maximum degree (inclusive) to use for each axis.

419 This must be the same length as `vander_fs`.

420

421 Returns

422 -------

423 vander_nd : ndarray

424 An array of shape ``points[0].shape + tuple(d + 1 for d in degrees)``.

425 """

426 n_dims = len(vander_fs)

427 if n_dims != len(points):

428 raise ValueError(

429 f"Expected {n_dims} dimensions of sample points, got {len(points)}")

430 if n_dims != len(degrees):

431 raise ValueError(

432 f"Expected {n_dims} dimensions of degrees, got {len(degrees)}")

433 if n_dims == 0:

434 raise ValueError("Unable to guess a dtype or shape when no points are given")

435

436 # convert to the same shape and type

437 points = tuple(np.array(tuple(points), copy=False) + 0.0)

438

439 # produce the vandermonde matrix for each dimension, placing the last

440 # axis of each in an independent trailing axis of the output

441 vander_arrays = (

442 vander_fs[i](points[i], degrees[i])[(...,) + _nth_slice(i, n_dims)]

443 for i in range(n_dims)

444 )

445

446 # we checked this wasn't empty already, so no `initial` needed

447 return functools.reduce(operator.mul, vander_arrays)

448

449

450 def _vander_nd_flat(vander_fs, points, degrees):

451 """

452 Like `_vander_nd`, but flattens the last ``len(degrees)`` axes into a single axis

453

454 Used to implement the public ``<type>vander<n>d`` functions.

455 """

456 v = _vander_nd(vander_fs, points, degrees)

457 return v.reshape(v.shape[:-len(degrees)] + (-1,))

458

459

460 def _fromroots(line_f, mul_f, roots):

461 """

462 Helper function used to implement the ``<type>fromroots`` functions.

463

464 Parameters

465 ----------

466 line_f : function(float, float) -> ndarray

467 The ``<type>line`` function, such as ``polyline``

468 mul_f : function(array_like, array_like) -> ndarray

469 The ``<type>mul`` function, such as ``polymul``

470 roots

471 See the ``<type>fromroots`` functions for more detail

472 """

473 if len(roots) == 0:

474 return np.ones(1)

475 else:

476 [roots] = as_series([roots], trim=False)

477 roots.sort()

478 p = [line_f(-r, 1) for r in roots]

479 n = len(p)

480 while n > 1:

481 m, r = divmod(n, 2)

482 tmp = [mul_f(p[i], p[i+m]) for i in range(m)]

483 if r:

484 tmp[0] = mul_f(tmp[0], p[-1])

485 p = tmp

486 n = m

487 return p[0]

488

489

490 def _valnd(val_f, c, *args):

491 """

492 Helper function used to implement the ``<type>val<n>d`` functions.

493

494 Parameters

495 ----------

496 val_f : function(array_like, array_like, tensor: bool) -> array_like

497 The ``<type>val`` function, such as ``polyval``

498 c, args

499 See the ``<type>val<n>d`` functions for more detail

500 """

501 args = [np.asanyarray(a) for a in args]

502 shape0 = args[0].shape

503 if not all((a.shape == shape0 for a in args[1:])):

504 if len(args) == 3:

505 raise ValueError('x, y, z are incompatible')

506 elif len(args) == 2:

507 raise ValueError('x, y are incompatible')

508 else:

509 raise ValueError('ordinates are incompatible')

510 it = iter(args)

511 x0 = next(it)

512

513 # use tensor on only the first

514 c = val_f(x0, c)

515 for xi in it:

516 c = val_f(xi, c, tensor=False)

517 return c

518

519

520 def _gridnd(val_f, c, *args):

521 """

522 Helper function used to implement the ``<type>grid<n>d`` functions.

523

524 Parameters

525 ----------

526 val_f : function(array_like, array_like, tensor: bool) -> array_like

527 The ``<type>val`` function, such as ``polyval``

528 c, args

529 See the ``<type>grid<n>d`` functions for more detail

530 """

531 for xi in args:

532 c = val_f(xi, c)

533 return c

534

535

536 def _div(mul_f, c1, c2):

537 """

538 Helper function used to implement the ``<type>div`` functions.

539

540 Implementation uses repeated subtraction of c2 multiplied by the nth basis.

541 For some polynomial types, a more efficient approach may be possible.

542

543 Parameters

544 ----------

545 mul_f : function(array_like, array_like) -> array_like

546 The ``<type>mul`` function, such as ``polymul``

547 c1, c2

548 See the ``<type>div`` functions for more detail

549 """

550 # c1, c2 are trimmed copies

551 [c1, c2] = as_series([c1, c2])

552 if c2[-1] == 0:

553 raise ZeroDivisionError()

554

555 lc1 = len(c1)

556 lc2 = len(c2)

557 if lc1 < lc2:

558 return c1[:1]*0, c1

559 elif lc2 == 1:

560 return c1/c2[-1], c1[:1]*0

561 else:

562 quo = np.empty(lc1 - lc2 + 1, dtype=c1.dtype)

563 rem = c1

564 for i in range(lc1 - lc2, - 1, -1):

565 p = mul_f([0]*i + [1], c2)

566 q = rem[-1]/p[-1]

567 rem = rem[:-1] - q*p[:-1]

568 quo[i] = q

569 return quo, trimseq(rem)

570

571

572 def _add(c1, c2):

573 """ Helper function used to implement the ``<type>add`` functions. """

574 # c1, c2 are trimmed copies

575 [c1, c2] = as_series([c1, c2])

576 if len(c1) > len(c2):

577 c1[:c2.size] += c2

578 ret = c1

579 else:

580 c2[:c1.size] += c1

581 ret = c2

582 return trimseq(ret)

583

584

585 def _sub(c1, c2):

586 """ Helper function used to implement the ``<type>sub`` functions. """

587 # c1, c2 are trimmed copies

588 [c1, c2] = as_series([c1, c2])

589 if len(c1) > len(c2):

590 c1[:c2.size] -= c2

591 ret = c1

592 else:

593 c2 = -c2

594 c2[:c1.size] += c1

595 ret = c2

596 return trimseq(ret)

597

598

599 def _fit(vander_f, x, y, deg, rcond=None, full=False, w=None):

600 """

601 Helper function used to implement the ``<type>fit`` functions.

602

603 Parameters

604 ----------

605 vander_f : function(array_like, int) -> ndarray

606 The 1d vander function, such as ``polyvander``

607 c1, c2

608 See the ``<type>fit`` functions for more detail

609 """

610 x = np.asarray(x) + 0.0

611 y = np.asarray(y) + 0.0

612 deg = np.asarray(deg)

613

614 # check arguments.

615 if deg.ndim > 1 or deg.dtype.kind not in 'iu' or deg.size == 0:

616 raise TypeError("deg must be an int or non-empty 1-D array of int")

617 if deg.min() < 0:

618 raise ValueError("expected deg >= 0")

619 if x.ndim != 1:

620 raise TypeError("expected 1D vector for x")

621 if x.size == 0:

622 raise TypeError("expected non-empty vector for x")

623 if y.ndim < 1 or y.ndim > 2:

624 raise TypeError("expected 1D or 2D array for y")

625 if len(x) != len(y):

626 raise TypeError("expected x and y to have same length")

627

628 if deg.ndim == 0:

629 lmax = deg

630 order = lmax + 1

631 van = vander_f(x, lmax)

632 else:

633 deg = np.sort(deg)

634 lmax = deg[-1]

635 order = len(deg)

636 van = vander_f(x, lmax)[:, deg]

637

638 # set up the least squares matrices in transposed form

639 lhs = van.T

640 rhs = y.T

641 if w is not None:

642 w = np.asarray(w) + 0.0

643 if w.ndim != 1:

644 raise TypeError("expected 1D vector for w")

645 if len(x) != len(w):

646 raise TypeError("expected x and w to have same length")

647 # apply weights. Don't use inplace operations as they

648 # can cause problems with NA.

649 lhs = lhs * w

650 rhs = rhs * w

651

652 # set rcond

653 if rcond is None:

654 rcond = len(x)*np.finfo(x.dtype).eps

655

656 # Determine the norms of the design matrix columns.

657 if issubclass(lhs.dtype.type, np.complexfloating):

658 scl = np.sqrt((np.square(lhs.real) + np.square(lhs.imag)).sum(1))

659 else:

660 scl = np.sqrt(np.square(lhs).sum(1))

661 scl[scl == 0] = 1

662

663 # Solve the least squares problem.

664 c, resids, rank, s = np.linalg.lstsq(lhs.T/scl, rhs.T, rcond)

665 c = (c.T/scl).T

666

667 # Expand c to include non-fitted coefficients which are set to zero

668 if deg.ndim > 0:

669 if c.ndim == 2:

670 cc = np.zeros((lmax+1, c.shape[1]), dtype=c.dtype)

671 else:

672 cc = np.zeros(lmax+1, dtype=c.dtype)

673 cc[deg] = c

674 c = cc

675

676 # warn on rank reduction

677 if rank != order and not full:

678 msg = "The fit may be poorly conditioned"

679 warnings.warn(msg, RankWarning, stacklevel=2)

680

681 if full:

682 return c, [resids, rank, s, rcond]

683 else:

684 return c

685

686

687 def _pow(mul_f, c, pow, maxpower):

688 """

689 Helper function used to implement the ``<type>pow`` functions.

690

691 Parameters

692 ----------

693 mul_f : function(array_like, array_like) -> ndarray

694 The ``<type>mul`` function, such as ``polymul``

695 c : array_like

696 1-D array of array of series coefficients

697 pow, maxpower

698 See the ``<type>pow`` functions for more detail

699 """

700 # c is a trimmed copy

701 [c] = as_series([c])

702 power = int(pow)

703 if power != pow or power < 0:

704 raise ValueError("Power must be a non-negative integer.")

705 elif maxpower is not None and power > maxpower:

706 raise ValueError("Power is too large")

707 elif power == 0:

708 return np.array([1], dtype=c.dtype)

709 elif power == 1:

710 return c

711 else:

712 # This can be made more efficient by using powers of two

713 # in the usual way.

714 prd = c

715 for i in range(2, power + 1):

716 prd = mul_f(prd, c)

717 return prd

718

719

720 def _deprecate_as_int(x, desc):

721 """

722 Like `operator.index`, but emits a deprecation warning when passed a float

723

724 Parameters

725 ----------

726 x : int-like, or float with integral value

727 Value to interpret as an integer

728 desc : str

729 description to include in any error message

730

731 Raises

732 ------

733 TypeError : if x is a non-integral float or non-numeric

734 DeprecationWarning : if x is an integral float

735 """

736 try:

737 return operator.index(x)

738 except TypeError as e:

739 # Numpy 1.17.0, 2019-03-11

740 try:

741 ix = int(x)

742 except TypeError:

743 pass

744 else:

745 if ix == x:

746 warnings.warn(

747 f"In future, this will raise TypeError, as {desc} will "

748 "need to be an integer not just an integral float.",

749 DeprecationWarning,

750 stacklevel=3

751 )

752 return ix

753

754 raise TypeError(f"{desc} must be an integer") from e

755

756

757 def format_float(x, parens=False):

758 if not np.issubdtype(type(x), np.floating):

759 return str(x)

760

761 opts = np.get_printoptions()

762

763 if np.isnan(x):

764 return opts['nanstr']

765 elif np.isinf(x):

766 return opts['infstr']

767

768 exp_format = False

769 if x != 0:

770 a = absolute(x)

771 if a >= 1.e8 or a < 10**min(0, -(opts['precision']-1)//2):

772 exp_format = True

773

774 trim, unique = '0', True

775 if opts['floatmode'] == 'fixed':

776 trim, unique = 'k', False

777

778 if exp_format:

779 s = dragon4_scientific(x, precision=opts['precision'],

780 unique=unique, trim=trim,

781 sign=opts['sign'] == '+')

782 if parens:

783 s = '(' + s + ')'

784 else:

785 s = dragon4_positional(x, precision=opts['precision'],

786 fractional=True,

787 unique=unique, trim=trim,

788 sign=opts['sign'] == '+')

789 return s