Пример #1
0
def amin(
    a: PolyLike,
    axis: Union[None, int, Sequence[int]] = None,
    out: Optional[ndpoly] = None,
    **kwargs: Any,
) -> ndpoly:
    """
    Return the minimum of an array or minimum along an axis.

    Args:
        a:
            Input data.
        axis:
            Axis or axes along which to operate.  By default, flattened input
            is used. If this is a tuple of ints, the minimum is selected over
            multiple axes, instead of a single axis or all the axes as before.
        out:
            Alternative output array in which to place the result. Must be of
            the same shape and buffer length as the expected output.
        keepdims:
            If this is set to True, the axes which are reduced are left in the
            result as dimensions with size one. With this option, the result
            will broadcast correctly against the input array.

            If the default value is passed, then `keepdims` will not be passed
            through to the `amax` method of sub-classes of `ndarray`, however
            any non-default value will be.  If the sub-class' method does not
            implement `keepdims` any exceptions will be raised.
        initial:
            The minimum value of an output element. Must be present to allow
            computation on empty slice.
        where:
            Elements to compare for the maximum.

    Returns:
        Minimum of `a`. If `axis` is None, the result is a scalar value.
        If `axis` is given, the result is an array of dimension ``a.ndim-1``.

    Examples:
        >>> q0, q1 = numpoly.variable(2)
        >>> numpoly.amin([13, 7])
        polynomial(7)
        >>> numpoly.amin([1, q0, q0**2, q1])
        polynomial(1)
        >>> numpoly.amin([q0, q1, q0**2])
        polynomial(q0)
        >>> numpoly.amin([[3*q0**2, q0**2],
        ...               [2*q0**2, 4*q0**2]], axis=1)
        polynomial([q0**2, 2*q0**2])

    """
    del out
    poly = numpoly.aspolynomial(a)
    options = numpoly.get_options()
    proxy = numpoly.sortable_proxy(
        poly, graded=options["sort_graded"], reverse=options["sort_reverse"])
    indices = numpy.amin(proxy, axis=axis, **kwargs)
    out = poly[numpy.isin(proxy, indices)]
    out = out[numpy.argsort(indices.ravel())]
    return numpoly.reshape(out, indices.shape)
Пример #2
0
def global_variables(doctest_namespace, monkeypatch):
    """Ensure certain variables are available during tests."""
    doctest_namespace["numpy"] = numpy
    doctest_namespace["numpoly"] = numpoly

    environ = os.environ.copy()
    environ["NUMPOLY_DEBUG"] = True
    monkeypatch.setattr("os.environ", environ)

    with numpoly.global_options(**numpoly.get_options(defaults=True)):
        yield
Пример #3
0
def argmax(
    a: PolyLike,
    axis: Optional[int] = None,
    out: Optional[numpy.ndarray] = None,
) -> Any:
    """
    Return the indices of the maximum values along an axis.

    As polynomials are not inherently sortable, values are sorted using the
    highest `lexicographical` ordering. Between the values that have the same
    highest ordering, the elements are sorted using the coefficients. This also
    ensures that the method behaves as expected with ``numpy.ndarray``.

    Args:
        a:
            Input array.
        axis:
            By default, the index is into the flattened array, otherwise along
            the specified axis.
        out:
            If provided, the result will be inserted into this array. It should
            be of the appropriate shape and dtype.

    Returns:
        Array of indices into the array. It has the same shape as `a.shape`
        with the dimension along `axis` removed.

    Notes:
        In case of multiple occurrences of the maximum values, the
        indices corresponding to the first occurrence are returned.

    Examples:
        >>> q0, q1 = numpoly.variable(2)
        >>> numpoly.argmax([13, 7])
        0
        >>> numpoly.argmax([1, q0, q0**2, q1])
        2
        >>> numpoly.argmax([1, q0, q1])
        2
        >>> numpoly.argmax([[3*q0**2, q0**2],
        ...                 [2*q0**2, 4*q0**2]], axis=0)
        array([0, 1])

    """
    a = numpoly.aspolynomial(a)
    options = numpoly.get_options()
    proxy = numpoly.sortable_proxy(
        a, graded=options["sort_graded"], reverse=options["sort_reverse"])
    return numpy.argmax(proxy, axis=axis, out=out)
Пример #4
0
def _to_string(
    poly: ndpoly,
    precision: float,
    suppress_small: bool,
) -> List[str]:
    """Backend for to_string."""
    exponents = poly.exponents.copy()
    coefficients = poly.coefficients
    options = numpoly.get_options()
    output: List[str] = []
    indices = numpoly.glexsort(
        exponents.T,
        graded=options["display_graded"],
        reverse=options["display_reverse"],
    )
    if options["display_inverse"]:
        indices = indices[::-1]
    for idx in indices:

        if not coefficients[idx]:
            continue
        if suppress_small and abs(coefficients[idx]) < 10**-precision:
            continue

        if coefficients[idx] == 1 and any(exponents[idx]):
            out = ""
        elif coefficients[idx] == -1 and any(exponents[idx]):
            out = "-"
        else:
            out = str(coefficients[idx])

        exps_and_names = list(zip(exponents[idx], poly.names))
        for exponent, indeterminant in exps_and_names:
            if exponent:
                if out not in ("", "-"):
                    out += options["display_multiply"]
                out += indeterminant
            if exponent > 1:
                out += options["display_exponent"] + str(exponent)
        if output and float(coefficients[idx]) >= 0:
            out = "+" + out
        output.append(out)

    return output
Пример #5
0
    def __new__(
            cls,
            exponents: numpy.typing.ArrayLike = ((0,),),
            shape: Tuple[int, ...] = (),
            names: Union[None, str, Tuple[str, ...], "ndpoly"] = None,
            dtype: Optional[numpy.typing.DTypeLike] = None,
            allocation: Optional[int] = None,
            **kwargs: Any
    ) -> "ndpoly":
        """
        Class constructor.

        Args:
            exponents:
                The exponents in an integer array with shape ``(N, D)``, where
                ``N`` is the number of terms in the polynomial sum and ``D`` is
                the number of dimensions.
            shape:
                Shape of created array.
            names:
                The name of the indeterminant variables in the polynomial. If
                polynomial, inherent from it. Else, pass argument to
                `numpoly.symbols` to create the indeterminants names. If only
                one name is provided, but more than one is required,
                indeterminants will be extended with an integer index. If
                omitted, use ``numpoly.get_options()["default_varname"]``.
            dtype:
                Any object that can be interpreted as a numpy data type.
            allocation:
                The maximum number of polynomial exponents. If omitted, use
                length of exponents for allocation.
            kwargs:
                Extra arguments passed to `numpy.ndarray` constructor.

        """
        exponents = numpy.array(exponents, dtype=numpy.uint32)
        if numpy.prod(exponents.shape):
            keys = (exponents+cls.KEY_OFFSET).flatten()
            keys = keys.view(f"U{exponents.shape[-1]}")
            keys = numpy.array(keys, dtype=f"U{exponents.shape[-1]}")
        else:
            keys = numpy.full((1,), cls.KEY_OFFSET, dtype="uint32").view("U1")
        assert len(keys.shape) == 1

        dtype = int if dtype is None else dtype
        dtype_ = numpy.dtype([(key, dtype) for key in keys])

        obj = super(ndpoly, cls).__new__(
            cls, shape=shape, dtype=dtype_, **kwargs)

        if allocation is None:
            allocation = 2*len(keys)
        assert isinstance(allocation, int) and allocation >= len(keys), (
            "Not enough memory allocated; increase 'allocation'")
        if allocation > len(keys):
            allocation_ = numpy.arange(allocation-len(keys), len(keys))
            allocation_ = [str(s) for s in allocation_]
            keys = numpy.concatenate([keys, allocation_])
        obj.allocation = allocation

        if names is None:
            names = numpoly.get_options()["default_varname"]
            obj.names = numpoly.symbols(
                f"{names}:{exponents.shape[-1]}").names
        elif isinstance(names, str):
            obj.names = numpoly.symbols(names).names
        elif isinstance(names, ndpoly):
            obj.names = names.names
        else:
            obj.names = tuple(str(name) for name in names)
        for name in obj.names:
            assert re.search(numpoly.get_options()["varname_filter"], name), (
                "invalid polynomial name; "
                f"expected format: {numpoly.get_options()['varname_filter']}")

        obj._dtype = numpy.dtype(dtype)  # pylint: disable=protected-access
        obj.keys = keys
        return obj
Пример #6
0
def greater(
    x1: PolyLike,
    x2: PolyLike,
    out: Optional[numpy.ndarray] = None,
    **kwargs: Any,
) -> numpy.ndarray:
    """
    Return the truth value of (x1 > x2) element-wise.

    Args:
        x1, x2:
            Input arrays. If ``x1.shape != x2.shape``, they must be
            broadcastable to a common shape (which becomes the shape of the
            output).
        out:
            A location into which the result is stored. If provided, it must
            have a shape that the inputs broadcast to. If not provided or
            `None`, a freshly-allocated array is returned. A tuple (possible
            only as a keyword argument) must have length equal to the number of
            outputs.
        where:
            This condition is broadcast over the input. At locations where the
            condition is True, the `out` array will be set to the ufunc result.
            Elsewhere, the `out` array will retain its original value. Note
            that if an uninitialized `out` array is created via the default
            ``out=None``, locations within it where the condition is False will
            remain uninitialized.
        kwargs:
            Keyword args passed to numpy.ufunc.

    Returns:
        Output array, element-wise comparison of `x1` and `x2`. Typically of
        type bool, unless ``dtype=object`` is passed. This is a scalar if both
        `x1` and `x2` are scalars.

    Examples:
        >>> q0, q1 = numpoly.variable(2)
        >>> numpoly.greater(3, 4)
        False
        >>> numpoly.greater(4*q0, 3*q0)
        True
        >>> numpoly.greater(q0, q1)
        False
        >>> numpoly.greater(q0**2, q0)
        True
        >>> numpoly.greater([1, q0, q0**2, q0**3], q1)
        array([False, False,  True,  True])
        >>> numpoly.greater(q0+1, q0-1)
        True
        >>> numpoly.greater(q0, q0)
        False

    """
    x1, x2 = numpoly.align_polynomials(x1, x2)
    coefficients1 = x1.coefficients
    coefficients2 = x2.coefficients
    if out is None:
        out = numpy.greater(coefficients1[0], coefficients2[0], **kwargs)
    if not out.shape:
        return greater(x1.ravel(), x2.ravel(), out=out.ravel()).item()

    options = numpoly.get_options()
    for idx in numpoly.glexsort(x1.exponents.T,
                                graded=options["sort_graded"],
                                reverse=options["sort_reverse"]):

        indices = (coefficients1[idx] != 0) | (coefficients2[idx] != 0)
        indices &= coefficients1[idx] != coefficients2[idx]
        out[indices] = numpy.greater(coefficients1[idx], coefficients2[idx],
                                     **kwargs)[indices]
    return out
Пример #7
0
def set_dimensions(poly: PolyLike, dimensions: Optional[int] = None) -> ndpoly:
    """
    Adjust the dimensions of a polynomial.

    Args:
        poly:
            Input polynomial
        dimensions:
            The dimensions of the output polynomial. If omitted, increase
            polynomial with one dimension.

    Returns:
        Polynomials with no internal dimensions. Unless the new dim is smaller
        then `poly`'s dimensions, the polynomial should have the same content.

    Examples:
        >>> q0, q1 = numpoly.variable(2)
        >>> poly = q0*q1-q0**2
        >>> numpoly.set_dimensions(poly, 1)
        polynomial(-q0**2)
        >>> numpoly.set_dimensions(poly, 3)
        polynomial(q0*q1-q0**2)
        >>> numpoly.set_dimensions(poly).names
        ('q0', 'q1', 'q2')

    """
    poly = numpoly.aspolynomial(poly)
    if dimensions is None:
        dimensions = len(poly.names)+1
    diff = dimensions-len(poly.names)
    if diff > 0:
        padding = numpy.zeros((len(poly.exponents), diff), dtype="uint32")
        exponents = numpy.hstack([poly.exponents, padding])
        coefficients = poly.coefficients
        varname = numpoly.get_options()["default_varname"]
        names_ = list(poly.names)
        idx = 0
        while len(names_) < dimensions:
            if f"{varname}{idx}" not in names_:
                names_.append(f"{varname}{idx}")
            idx += 1

        indices = numpy.lexsort([names_])
        exponents = exponents[:, indices]
        names = tuple(names_[idx] for idx in indices)

    elif diff < 0:
        indices = True ^ numpy.any(poly.exponents[:, dimensions:], -1)
        exponents = poly.exponents[:, :dimensions]
        exponents = exponents[indices]
        coefficients = [
            coeff for coeff, idx in zip(poly.coefficients, indices) if idx]
        names = poly.names[:dimensions]

    else:
        return poly

    return numpoly.polynomial_from_attributes(
        exponents=exponents,
        coefficients=coefficients,
        names=names,
        dtype=poly.dtype,
        allocation=poly.allocation,
        retain_names=True,
    )
Пример #8
0
def postprocess_attributes(
        exponents: numpy.typing.ArrayLike,
        coefficients: Sequence[numpy.typing.ArrayLike],
        names: Union[None, str, Tuple[str, ...], ndpoly] = None,
        retain_coefficients: Optional[bool] = None,
        retain_names: Optional[bool] = None,
) -> Tuple[numpy.ndarray, List[numpy.ndarray], Optional[Tuple[str, ...]]]:
    """
    Clean up polynomial attributes.

    Args:
        exponents:
            The exponents in an integer array with shape ``(N, D)``, where
            ``N`` is the number of terms in the polynomial sum and ``D`` is
            the number of dimensions.
        coefficients:
            The polynomial coefficients. Must correspond to `exponents` by
            having the same length ``N``.
        names:
            The indeterminant names, either as string names or as
            simple polynomials. Must correspond to the exponents by having
            length ``D``.
        retain_coefficients:
            Do not remove redundant coefficients. If omitted use global
            defaults.
        retain_names:
            Do not remove redundant names. If omitted use global defaults.

    Returns:
        Same as inputs `exponents`, `coefficients` and `names`, but
        post-processed.

    """
    exponents = numpy.asarray(exponents)
    if exponents.ndim != 2:
        raise PolynomialConstructionError(
            f"expected exponents.ndim == 2; found {exponents.ndim}")

    coefficients_ = [numpy.asarray(coefficient)
                     for coefficient in coefficients]
    if coefficients_ and len(exponents) != len(coefficients_):
        raise PolynomialConstructionError(
            "expected len(exponents) == len(coefficients_); "
            f"found {len(exponents)} != {len(coefficients_)}")

    if retain_coefficients is None:
        retain_coefficients = numpoly.get_options()["retain_coefficients"]
    if not retain_coefficients and coefficients_:
        exponents, coefficients_ = remove_redundant_coefficients(
            exponents, coefficients_)

    if isinstance(names, numpoly.ndpoly):
        names = names.names
    if isinstance(names, str):
        if exponents.shape[1] > 1:
            names = tuple(f"{names}{idx}"
                          for idx in range(exponents.shape[1]))
        else:
            names = (names,)
    if names:
        if len(names) != exponents.shape[1]:
            raise PolynomialConstructionError(
                "Name length incompatible exponent length; "
                f"len({names}) != {exponents.shape[1]}")
        if sorted(set(names)) != sorted(names):
            raise PolynomialConstructionError(
                f"Duplicate indeterminant names: {names}")

    if retain_names is None:
        retain_names = numpoly.get_options()["retain_names"]
    if not retain_names:
        exponents, names = remove_redundant_names(exponents, names)

    exponents_, count = numpy.unique(exponents, return_counts=True, axis=0)
    if numpy.any(count > 1):
        raise PolynomialConstructionError(
            f"Duplicate exponent keys found: {exponents_[count > 1][0]}")

    return numpy.asarray(exponents), list(coefficients_), names
Пример #9
0
def minimum(
    x1: PolyLike,
    x2: PolyLike,
    out: Optional[ndpoly] = None,
    **kwargs: Any,
) -> ndpoly:
    """
    Element-wise minimum of array elements.

    Compare two arrays and returns a new array containing the element-wise
    minima. If one of the elements being compared is a NaN, then that
    element is returned. If both elements are NaNs then the first is
    returned. The latter distinction is important for complex NaNs, which
    are defined as at least one of the real or imaginary parts being a NaN.
    The net effect is that NaNs are propagated.

    Args:
        x1, x2 :
            The arrays holding the elements to be compared. If ``x1.shape !=
            x2.shape``, they must be broadcastable to a common shape (which
            becomes the shape of the output).
        out:
            A location into which the result is stored. If provided, it must
            have a shape that the inputs broadcast to. If not provided or
            `None`, a freshly-allocated array is returned. A tuple (possible
            only as a keyword argument) must have length equal to the number of
            outputs.
        where:
            This condition is broadcast over the input. At locations where the
            condition is True, the `out` array will be set to the ufunc result.
            Elsewhere, the `out` array will retain its original value. Note
            that if an uninitialized `out` array is created via the default
            ``out=None``, locations within it where the condition is False will
            remain uninitialized.
        kwargs:
            Keyword args passed to numpy.ufunc.

    Returns:
        The minimum of `x1` and `x2`, element-wise. This is a scalar if
        both `x1` and `x2` are scalars.

    Examples:
        >>> q0, q1 = numpoly.variable(2)
        >>> numpoly.minimum(3, 4)
        polynomial(3)
        >>> numpoly.minimum(4*q0, 3*q0)
        polynomial(3*q0)
        >>> numpoly.minimum(q0, q1)
        polynomial(q0)
        >>> numpoly.minimum(q0**2, q0)
        polynomial(q0)
        >>> numpoly.minimum([1, q0, q0**2, q0**3], q1)
        polynomial([1, q0, q1, q1])
        >>> numpoly.minimum(q0+1, q0-1)
        polynomial(q0-1)

    """
    del out
    x1, x2 = numpoly.align_polynomials(x1, x2)
    coefficients1 = x1.coefficients
    coefficients2 = x2.coefficients
    out_ = numpy.zeros(x1.shape, dtype=bool)

    options = numpoly.get_options()
    for idx in numpoly.glexsort(x1.exponents.T,
                                graded=options["sort_graded"],
                                reverse=options["sort_reverse"]):

        indices = (coefficients1[idx] != 0) | (coefficients2[idx] != 0)
        indices = coefficients1[idx] != coefficients2[idx]
        out_[indices] = (coefficients1[idx] < coefficients2[idx])[indices]
    return numpoly.where(out_, x1, x2)