Source code for numpoly.construct.clean

"""Clean up polynomial attributes."""
from __future__ import annotations
from typing import List, Optional, Sequence, Tuple, Union

import numpy
import numpy.typing

import numpoly
from ..baseclass import ndpoly


class PolynomialConstructionError(ValueError):
    """Error related to construction of polynomial."""


[docs]def clean_attributes( poly: ndpoly, retain_coefficients: Optional[bool] = None, retain_names: Optional[bool] = None, ) -> ndpoly: """ Clean up polynomial attributes that does not affect representation. Some operations results in polynomial with structures that are redundant. This includes extra unused indeterminants and coefficients consisting of only zeros. Args: poly: Polynomial to clean up. retain_coefficients: Do not remove redundant coefficients. If omitted use global defaults. retain_names: Do not remove redundant names. If omitted use global defaults. Return: Same as `poly`, but with attributes cleaned up. Example: >>> q0, _ = numpoly.align_polynomials(*numpoly.variable(2)) >>> q0.indeterminants polynomial([q0, q1]) >>> q0.exponents array([[0, 1], [1, 0]], dtype=uint32) >>> q0 = numpoly.clean_attributes( ... q0, retain_coefficients=False, retain_names=False) >>> q0.indeterminants polynomial([q0]) >>> q0.exponents array([[1]], dtype=uint32) """ return numpoly.ndpoly.from_attributes( exponents=poly.exponents, coefficients=poly.coefficients, names=poly.names, dtype=poly.dtype, retain_coefficients=retain_coefficients, retain_names=retain_names, )
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. Return: 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
[docs]def remove_redundant_coefficients( exponents: numpy.typing.ArrayLike, coefficients: Sequence[numpy.typing.ArrayLike], ) -> Tuple[numpy.ndarray, List[numpy.ndarray]]: """ Remove coefficients if they are redundant to the polynomial representation. Will always keep at least one coefficient. 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``. Return: Same as inputs, but with redundant exponent rows and coefficients removed. This corresponds to ``N`` being lowered. Example: >>> exponents, coefficients = remove_redundant_coefficients( ... [[0, 0], [0, 1]], [1, 2]) >>> exponents array([[0, 0], [0, 1]]) >>> coefficients [array(1), array(2)] >>> remove_redundant_coefficients( ... [[0, 0], [0, 1]], [1, 0]) (array([[0, 0]]), [array(1)]) >>> remove_redundant_coefficients([[0]], [[]]) (array([[0]]), [array([], dtype=float64)]) """ exponents_ = numpy.asarray(exponents) coefficients_ = [numpy.asarray(coefficient) for coefficient in coefficients] assert len(exponents_) == len(coefficients_), (exponents, coefficients) elements = list( zip( *[ (exponent, coefficient) for exponent, coefficient in zip(exponents_, coefficients_) if numpy.any(coefficient) or not numpy.any(exponent) ] ) ) if not elements: exponents_ = numpy.zeros((1, exponents_.shape[-1]), dtype="uint32") coefficients_ = [numpy.zeros_like(coefficients_[0])] else: exponents_ = numpy.asarray(elements[0], dtype=int) coefficients_ = list(elements[1]) return exponents_, coefficients_
[docs]def remove_redundant_names( exponents: numpy.typing.ArrayLike, names: Optional[Sequence[str]], ) -> Tuple[numpy.ndarray, Optional[Tuple[str, ...]]]: """ Remove names if they are redundant to the polynomial representation. Will always keep at least one dimension. 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. names: The indeterminant names, either as string names or as simple polynomials. Must correspond to the exponents by having length ``D``. Return: Same as inputs, but with redundant exponent columns and names removed. This corresponds to ``D`` being lowered. Example: >>> exponents, names = remove_redundant_names( ... [[0, 0], [1, 1]], ["q0", "q1"]) >>> exponents array([[0, 0], [1, 1]]) >>> names ('q0', 'q1') >>> exponents, names = remove_redundant_names( ... [[0, 0], [0, 1]], ["q0", "q1"]) >>> exponents array([[0], [1]]) >>> names ('q1',) >>> exponents, names = remove_redundant_names( ... [[0, 0]], ["q0", "q1"]) >>> exponents array([[0]]) >>> names ('q0',) """ exponents_ = numpy.asarray(exponents) indices = numpy.any(exponents_ != 0, 0) if not numpy.any(indices): indices[0] = True exponents_ = exponents_[:, indices] if names is None: return exponents_, names names_ = tuple(numpy.array(names)[indices].tolist()) return exponents_, names_