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)
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
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)
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
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
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
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, )
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
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)