Team Ai
Datasetpublic

codekingpro/portable-devtools

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