def setdim(poly, dim=None): """ Adjust the dimensions of a polynomial. Output the results into ndpoly object Args: poly (chaospy.poly.ndpoly): Input polynomial dim (int): The dimensions of the output polynomial. If omitted, increase polynomial with one dimension. If the new dim is smaller then `poly`'s dimensions, variables with cut components are all cut. Examples: >>> q0, q1 = chaospy.variable(2) >>> poly = q0*q1-q0**2 >>> chaospy.setdim(poly, 1) polynomial(-q0**2) >>> chaospy.setdim(poly, 3) polynomial(q0*q1-q0**2) >>> chaospy.setdim(poly).names ('q0', 'q1', 'q2') """ poly = numpoly.polynomial(poly) indices = [int(name[1:]) for name in poly.names] dim = max(indices) + 2 if dim is None else dim poly = poly(**{("q%d" % index): 0 for index in indices if index >= dim}) _, poly = numpoly.align_indeterminants(numpoly.symbols("q:%d" % dim), poly) return poly
def compose_polynomial_array( arrays: Sequence[PolyLike], dtype: Optional[numpy.typing.DTypeLike] = None, allocation: Optional[int] = None, ) -> ndpoly: """ Compose polynomial from array of arrays of polynomials. Backend for `numpoly.polynomial` when input is undetermined. Args: arrays: Input to be converted to a `numpoly.ndpoly` polynomial type. dtype: Data type used for the polynomial coefficients. allocation: The maximum number of polynomial exponents. If omitted, use length of exponents for allocation. Return: Polynomial based on input `arrays`. """ oarrays = numpy.array(arrays, dtype=object) shape = oarrays.shape if not oarrays.size: return numpoly.ndpoly(shape=shape, dtype=dtype) if len(oarrays.shape) > 1: return numpoly.concatenate([ numpoly.expand_dims(numpoly.aspolynomial(array, dtype=dtype), axis=0) for array in arrays ], axis=0) oarrays = oarrays.flatten() indices = numpy.array( [isinstance(array, numpoly.ndpoly) for array in oarrays]) oarrays[indices] = numpoly.align_indeterminants(*oarrays[indices]) names = oarrays[indices][0] if numpy.any(indices) else None oarrays = oarrays.tolist() dtypes: List[numpy.typing.DTypeLike] = [] keys: Set[Tuple[int, ...]] = {(0, )} for array in oarrays: if isinstance(array, numpoly.ndpoly): dtypes.append(array.dtype) keys = keys.union({ tuple(int(k) for k in key) for key in array.exponents.tolist() }) elif isinstance(array, (numpy.generic, numpy.ndarray)): dtypes.append(array.dtype) else: dtypes.append(type(array)) if dtype is None: dtype = numpy.find_common_type(dtypes, []) length = max(1, max([len(key) for key in keys])) collection = {} for idx, array in enumerate(oarrays): if isinstance(array, numpoly.ndpoly): for key, value in zip(array.exponents, array.coefficients): key = tuple(key) + (0, ) * (length - len(key)) if key not in collection: collection[key] = numpy.zeros(len(oarrays), dtype=dtype) collection[key][idx] = value else: key = (0, ) * length if key not in collection: collection[key] = numpy.zeros(len(oarrays), dtype=dtype) collection[key][idx] = array exponents = sorted(collection) coefficients = numpy.array([collection[key] for key in exponents]) coefficients = coefficients.reshape(-1, *shape) return numpoly.ndpoly.from_attributes( exponents=exponents, coefficients=list(coefficients), names=names, allocation=allocation, )
def compose_polynomial_array( arrays, dtype=None, ): """ Compose polynomial from array of arrays of polynomials. Backend for `numpoly.polynomial` when input is undetermined. Args: arrays (Any): Input to be converted to a `numpoly.ndpoly` polynomial type. dtype (Optional[numpy.dtype]): Data type used for the polynomial coefficients. Return: (numpoly.ndpoly): Polynomial based on input `arrays`. """ arrays_ = numpy.array(arrays, dtype=object) shape = arrays_.shape if not arrays_.size: return numpoly.ndpoly(shape=shape, dtype=dtype) if len(arrays_.shape) > 1: return numpoly.concatenate([ numpoly.aspolynomial(array, dtype)[numpy.newaxis] for array in arrays ], axis=0) arrays = arrays_.flatten() indices = numpy.array( [isinstance(array, numpoly.ndpoly) for array in arrays]) arrays[indices] = numpoly.align_indeterminants(*arrays[indices]) names = arrays[indices][0] if numpy.any(indices) else None arrays = arrays.tolist() dtypes = [] keys = {(0, )} for array in arrays: if isinstance(array, numpoly.ndpoly): dtypes.append(array.dtype) keys = keys.union([tuple(key) for key in array.exponents.tolist()]) elif isinstance(array, (numpy.generic, numpy.ndarray)): dtypes.append(array.dtype) else: dtypes.append(type(array)) if dtype is None: dtype = numpy.find_common_type(dtypes, []) length = max([len(key) for key in keys]) collection = {} for idx, array in enumerate(arrays): if isinstance(array, numpoly.ndpoly): for key, value in zip(array.exponents, array.coefficients): key = tuple(key) + (0, ) * (length - len(key)) if key not in collection: collection[key] = numpy.zeros(len(arrays), dtype=dtype) collection[key][idx] = value else: key = (0, ) * length if key not in collection: collection[key] = numpy.zeros(len(arrays), dtype=dtype) collection[key][idx] = array exponents = sorted(collection) coefficients = numpy.array([collection[key] for key in exponents]) coefficients = coefficients.reshape(-1, *shape) exponents, coefficients, names = postprocess_attributes( exponents, coefficients, names) return numpoly.ndpoly.from_attributes( exponents=exponents, coefficients=coefficients, names=names, )
def multiply( x1: PolyLike, x2: PolyLike, out: Optional[ndpoly] = None, where: numpy.typing.ArrayLike = True, **kwargs: Any, ) -> ndpoly: """ Multiply arguments element-wise. Args: x1, x2: Input arrays to be multiplied. 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 product of `x1` and `x2`, element-wise. This is a scalar if both `x1` and `x2` are scalars. Examples: >>> poly = numpy.arange(9.0).reshape((3, 3)) >>> q0q1q2 = numpoly.variable(3) >>> numpoly.multiply(poly, q0q1q2) polynomial([[0.0, q1, 2.0*q2], [3.0*q0, 4.0*q1, 5.0*q2], [6.0*q0, 7.0*q1, 8.0*q2]]) """ x1, x2 = numpoly.align_indeterminants(x1, x2) dtype = numpy.find_common_type([x1.dtype, x2.dtype], []) shape = numpy.broadcast_shapes(x1.shape, x2.shape) where = numpy.asarray(where) exponents = numpy.unique(numpy.tile(x1.exponents, (len(x2.exponents), 1)) + numpy.repeat(x2.exponents, len(x1.exponents), 0), axis=0) out_ = numpoly.ndpoly( exponents=exponents, shape=shape, names=x1.indeterminants, dtype=dtype, ) if out is None else out seen = set() for expon1, coeff1 in zip(x1.exponents, x1.coefficients): for expon2, coeff2 in zip(x2.exponents, x2.coefficients): key = (expon1 + expon2 + x1.KEY_OFFSET).ravel() key = key.view(f"U{len(expon1)}").item() if key in seen: out_.values[key] += numpy.multiply(coeff1, coeff2, where=where, **kwargs) else: numpy.multiply(coeff1, coeff2, out=out_.values[key], where=where, **kwargs) seen.add(key) if out is None: out_ = numpoly.clean_attributes(out_) return out_
def call(poly, *args, **kwargs): """ Evaluate polynomial by inserting new values in to the indeterminants. Equaivalent to calling the polynomial or using the ``__call__`` method. Args: poly (numpoly.ndpoly): Polynomial to evaluate. args (int, float, numpy.ndarray, numpoly.ndpoly): Argument to evaluate indeterminants. Ordered positional by ``poly.indeterminants``. kwargs (int, float, numpy.ndarray, numpoly.ndpoly): Same as ``args``, but positioned by name. Returns: (numpoly.ndpoly): Evaluated polynomial. Examples: >>> x, y = numpoly.symbols("x y") >>> poly = numpoly.polynomial([[x, x-1], [y, y+x]]) >>> poly() polynomial([[x, -1+x], [y, x+y]]) >>> poly polynomial([[x, -1+x], [y, x+y]]) >>> poly(1, 0) array([[1, 0], [0, 1]]) >>> poly(1, y=[0, 1, 2]) array([[[1, 1, 1], [0, 0, 0]], <BLANKLINE> [[0, 1, 2], [1, 2, 3]]]) >>> poly(y) polynomial([[y, -1+y], [y, 2*y]]) >>> poly(y=x-1, x=2*y) polynomial([[2*y, -1+2*y], [-1+x, -1+2*y+x]]) """ # Make sure kwargs contains all args and nothing but indeterminants: for arg, indeterminant in zip(args, poly.names): if indeterminant in kwargs: raise TypeError( "multiple values for argument '%s'" % indeterminant) kwargs[indeterminant] = arg extra_args = [key for key in kwargs if key not in poly.names] if extra_args: raise TypeError("unexpected keyword argument '%s'" % extra_args[0]) if not kwargs: return poly.copy() # Saturate kwargs with values not given: indeterminants = poly.indeterminants for indeterminant in indeterminants: name = indeterminant.names[0] if name not in kwargs: kwargs[name] = indeterminant # There can only be one shape: ones = numpy.ones((), dtype=int) for value in kwargs.values(): ones = ones * numpy.ones(numpoly.polynomial(value).shape, dtype=int) # main loop: out = 0 for exponent, coefficient in zip(poly.exponents, poly.coefficients): term = ones for power, name in zip(exponent, poly.names): term = term*kwargs[name]**power shape = coefficient.shape+ones.shape out = out+numpoly.outer(coefficient, term).reshape(shape) if out.isconstant(): return out.tonumpy() out, _ = numpoly.align_indeterminants(out, indeterminants) return out
def call( poly: PolyLike, args: Sequence[Optional[PolyLike]] = (), kwargs: Dict[str, PolyLike] = None, ) -> Union[numpy.ndarray, ndpoly]: """ Evaluate polynomial by inserting new values in to the indeterminants. Equivalent to calling the polynomial or using the ``__call__`` method. Args: poly: Polynomial to evaluate. args: Argument to evaluate indeterminants. Ordered positional by ``poly.indeterminants``. None values indicate that a variable is not to be evaluated, creating a partial evaluation. kwargs: Same as ``args``, but positioned by name. Returns: Evaluated polynomial. If the resulting array does not contain any indeterminants, an array is returned instead of a polynomial. Examples: >>> q0, q1 = numpoly.variable(2) >>> poly = numpoly.polynomial([[q0, q0-1], [q1, q1+q0]]) >>> numpoly.call(poly) polynomial([[q0, q0-1], [q1, q1+q0]]) >>> poly polynomial([[q0, q0-1], [q1, q1+q0]]) >>> numpoly.call(poly, (1, 0)) array([[1, 0], [0, 1]]) >>> numpoly.call(poly, (1,), {"q1": [0, 1, 2]}) array([[[1, 1, 1], [0, 0, 0]], <BLANKLINE> [[0, 1, 2], [1, 2, 3]]]) >>> numpoly.call(poly, (q1,)) polynomial([[q1, q1-1], [q1, 2*q1]]) >>> numpoly.call(poly, kwargs={"q1": q0-1, "q0": 2*q1}) polynomial([[2*q1, 2*q1-1], [q0-1, 2*q1+q0-1]]) """ logger = logging.getLogger(__name__) poly = numpoly.aspolynomial(poly) kwargs = kwargs if kwargs else {} parameters = dict(zip(poly.names, poly.indeterminants)) if kwargs: parameters.update(kwargs) for arg, name in zip(args, poly.names): if name in kwargs: raise TypeError(f"multiple values for argument '{name}'") if arg is not None: parameters[name] = arg extra_args = [key for key in parameters if key not in poly.names] if extra_args: raise TypeError(f"unexpected keyword argument '{extra_args[0]}'") # There can only be one shape: ones = numpy.ones((), dtype=int) for value in parameters.values(): ones = ones*numpy.ones(numpoly.polynomial(value).shape, dtype=int) shape = poly.shape+ones.shape logger.debug("poly shape: %s", poly.shape) logger.debug("parameter common shape: %s", ones.shape) logger.debug("output shape: %s", shape) # main loop: out = numpy.zeros((), dtype=int) for exponent, coefficient in zip(poly.exponents, poly.coefficients): term = ones for power, name in zip(exponent, poly.names): term = term*parameters[name]**power if isinstance(term, numpoly.ndpoly): tmp = numpoly.outer(coefficient, term) else: tmp = numpy.outer(coefficient, term) out = out+tmp.reshape(shape) if isinstance(out, numpoly.ndpoly): if out.isconstant(): out = out.tonumpy() else: out, _ = numpoly.align_indeterminants(out, poly.indeterminants) return out
def copyto( dst: ndpoly, src: PolyLike, casting: str = "same_kind", where: numpy.typing.ArrayLike = True, ) -> None: """ Copy values from one array to another, broadcasting as necessary. Raises a TypeError if the `casting` rule is violated, and if `where` is provided, it selects which elements to copy. Args: dst: The array into which values are copied. src: The array from which values are copied. casting: Controls what kind of data casting may occur when copying. * 'no' means the data types should not be cast at all. * 'equiv' means only byte-order changes are allowed. * 'safe' means only casts which can preserve values are allowed. * 'same_kind' means only safe casts or casts within a kind, like float64 to float32, are allowed. * 'unsafe' means any data conversions may be done. where: A boolean array which is broadcasted to match the dimensions of `dst`, and selects elements to copy from `src` to `dst` wherever it contains the value True. Examples: >>> q0 = numpoly.variable() >>> poly1 = numpoly.polynomial([1, q0**2, q0]) >>> poly2 = numpoly.polynomial([2, q0, 3]) >>> numpoly.copyto(poly1, poly2, where=[True, False, True]) >>> poly1 polynomial([2, q0**2, 3]) >>> numpoly.copyto(poly1, poly2) >>> poly1 polynomial([2, q0, 3]) """ logger = logging.getLogger(__name__) src = numpoly.aspolynomial(src) assert isinstance(dst, numpy.ndarray) if not isinstance(dst, numpoly.ndpoly): if dst.dtype.names is None: if src.isconstant(): return numpy.copyto(dst, src.tonumpy(), casting=casting, where=where) raise ValueError(f"Could not convert src {src} to dst {dst}") if casting != "unsafe": raise ValueError( f"could not safely convert src {src} to dst {dst}") logger.warning("Copying ndpoly input into ndarray") logger.warning("You might need to cast `numpoly.polynomial(dst)`.") logger.warning("Indeterminant names might be lost.") dst_keys = dst.dtype.names dst_ = dst else: dst_keys = dst.keys src, _ = numpoly.align_indeterminants(src, dst) dst_ = dst.values missing_keys = set(src.keys).difference(dst_keys) if missing_keys: raise ValueError(f"memory layouts are incompatible: {missing_keys}") for key in dst_keys: if key in src.keys: numpy.copyto(dst_[key], src.values[key], casting=casting, where=where) else: numpy.copyto(dst_[key], numpy.array(False, dtype=dst_[key].dtype), casting=casting, where=where)