def dsplit(ary, indices_or_sections): """ Split array into multiple sub-arrays along the 3rd axis (depth). Please refer to the `split` documentation. `dsplit` is equivalent to `split` with ``axis=2``, the array is always split along the third axis provided the array dimension is greater than or equal to 3. Examples: >>> poly = numpoly.monomial(8).reshape(2, 2, 2) >>> poly polynomial([[[1, q], [q**2, q**3]], <BLANKLINE> [[q**4, q**5], [q**6, q**7]]]) >>> part1, part2 = numpoly.dsplit(poly, 2) >>> part1 polynomial([[[1], [q**2]], <BLANKLINE> [[q**4], [q**6]]]) >>> part2 polynomial([[[q], [q**3]], <BLANKLINE> [[q**5], [q**7]]]) """ ary = numpoly.aspolynomial(ary) results = numpy.dsplit(ary.values, indices_or_sections=indices_or_sections) return [numpoly.aspolynomial(result, names=ary.indeterminants) for result in results]
def atleast_3d(*arys: PolyLike) -> Union[ndpoly, List[ndpoly]]: """ View inputs as arrays with at least three dimensions. Args: arys: One or more array-like sequences. Non-array inputs are converted to arrays. Arrays that already have three or more dimensions are preserved. Returns: An array, or list of arrays, each with ``a.ndim >= 3``. Copies are avoided where possible, and views with three or more dimensions are returned. For example, a 1-D array of shape ``(N,)`` becomes a view of shape ``(1, N, 1)``, and a 2-D array of shape ``(M, N)`` becomes a view of shape ``(M, N, 1)``. Examples: >>> numpoly.atleast_3d(numpoly.variable()) polynomial([[[q0]]]) >>> a, b = numpoly.atleast_3d(1, [2, 3]) >>> a polynomial([[[1]]]) >>> b polynomial([[[2], [3]]]) """ if len(arys) == 1: poly = numpoly.aspolynomial(arys[0]) array = numpy.atleast_3d(poly.values) return numpoly.aspolynomial(array, names=poly.indeterminants) return [atleast_3d(ary) for ary in arys]
def atleast_1d(*arys: PolyLike) -> Union[ndpoly, List[ndpoly]]: """ Convert inputs to arrays with at least one dimension. Scalar inputs are converted to 1-dimensional arrays, whilst higher-dimensional inputs are preserved. Args: arys: One or more input arrays. Returns: An array, or list of arrays, each with ``a.ndim >= 1``. Copies are made only if necessary. Examples: >>> numpoly.atleast_1d(numpoly.variable()) polynomial([q0]) >>> numpoly.atleast_1d(1, [2, 3]) [polynomial([1]), polynomial([2, 3])] """ if len(arys) == 1: poly = numpoly.aspolynomial(arys[0]) array = numpy.atleast_1d(poly.values) return numpoly.aspolynomial(array, names=poly.indeterminants) return [atleast_1d(ary) for ary in arys]
def atleast_2d(*arys: PolyLike) -> Union[ndpoly, List[ndpoly]]: """ View inputs as arrays with at least two dimensions. Args: arys: One or more array-like sequences. Non-array inputs are converted to arrays. Arrays that already have two or more dimensions are preserved. Returns: An array, or list of arrays, each with ``a.ndim >= 2``. Copies are avoided where possible, and views with two or more dimensions are returned. Examples: >>> numpoly.atleast_2d(numpoly.variable()) polynomial([[q0]]) >>> numpoly.atleast_2d(1, [2], [[3]]) [polynomial([[1]]), polynomial([[2]]), polynomial([[3]])] """ if len(arys) == 1: poly = numpoly.aspolynomial(arys[0]) array = numpy.atleast_2d(poly.values) return numpoly.aspolynomial(array, names=poly.indeterminants) return [atleast_2d(ary) for ary in arys]
def logical_and( x1: PolyLike, x2: PolyLike, out: Optional[numpy.ndarray] = None, where: Union[bool, Sequence[bool]] = True, **kwargs: Any, ) -> numpy.ndarray: """ Compute the truth value of x1 AND 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: Boolean result of the logical OR operation applied to the elements of `x1` and `x2`; the shape is determined by broadcasting. This is a scalar if both `x1` and `x2` are scalars. Examples: >>> q0, q1 = numpoly.variable(2) >>> numpoly.logical_and(q0, 0) False >>> numpoly.logical_and([q0, False], [q0, q1]) array([ True, False]) >>> const = numpy.arange(5) >>> numpoly.logical_and(const > 1, const < 4) array([False, False, True, True, False]) """ x1 = numpoly.aspolynomial(x1) x2 = numpoly.aspolynomial(x2) coefficients1 = numpy.any(numpy.asarray(x1.coefficients), 0) coefficients2 = numpy.any(numpy.asarray(x2.coefficients), 0) where_ = numpy.asarray(where) return numpy.logical_and(coefficients1, coefficients2, out=out, where=where_, **kwargs)
def derivative(poly: PolyLike, *diffvars: Union[ndpoly, str, int]) -> ndpoly: """ Polynomial differential operator. Args: poly: Polynomial to differentiate. diffvars: Singleton variables to take derivative off. Returns: Same as ``poly`` but differentiated with respect to ``diffvars``. Examples: >>> q0, q1 = numpoly.variable(2) >>> poly = numpoly.polynomial([1, q0, q0*q1**2+1]) >>> poly polynomial([1, q0, q0*q1**2+1]) >>> numpoly.derivative(poly, "q0") polynomial([0, 1, q1**2]) >>> numpoly.derivative(poly, 0, 1) polynomial([0, 0, 2*q1]) >>> numpoly.derivative(poly, q0, q0, q0) polynomial([0, 0, 0]) """ poly = poly_ref = numpoly.aspolynomial(poly) for diffvar in diffvars: if isinstance(diffvar, str): idx = poly.names.index(diffvar) elif isinstance(diffvar, int): idx = diffvar else: diffvar = numpoly.aspolynomial(diffvar) exponents, names = numpoly.remove_redundant_names( diffvar.exponents, diffvar.names) assert names is not None and len(names) == 1, "one at the time" assert numpy.all( exponents == 1), ("derivative variable assumes singletons") idx = poly.names.index(names[0]) exponents = poly.exponents coefficients = [ (exponent[idx] * coefficient.T).T for exponent, coefficient in zip(exponents, poly.coefficients) ] exponents[:, idx] -= 1 assert not numpy.any(exponents < 0) poly = numpoly.ndpoly.from_attributes( exponents=exponents, coefficients=coefficients, names=poly_ref.names, ) poly, poly_ref = numpoly.align_polynomials(poly, poly_ref) return poly
def ediff1d( ary: PolyLike, to_end: Optional[PolyLike] = None, to_begin: Optional[PolyLike] = None, ) -> ndpoly: """ Difference between consecutive elements of an array. Args: ary: If necessary, will be flattened before the differences are taken. to_end: Polynomial(s) to append at the end of the returned differences. to_begin: Polynomial(s) to prepend at the beginning of the returned differences. Returns: The differences. Loosely, this is ``ary.flat[1:] - ary.flat[:-1]``. Examples: >>> poly = numpoly.monomial(4) >>> poly polynomial([1, q0, q0**2, q0**3]) >>> numpoly.ediff1d(poly) polynomial([q0-1, q0**2-q0, q0**3-q0**2]) >>> q0, q1 = numpoly.variable(2) >>> numpoly.ediff1d(poly, to_begin=q0, to_end=[1, q1]) polynomial([q0, q0-1, q0**2-q0, q0**3-q0**2, 1, q1]) """ ary = numpoly.aspolynomial(ary).ravel() arys_ = [ary[1:] - ary[:-1]] if to_end is not None: arys_.append(numpoly.aspolynomial(to_end).ravel()) if to_begin is not None: arys_.insert(0, numpoly.aspolynomial(to_begin).ravel()) arys = tuple(numpoly.aspolynomial(ary) for ary in arys_) if len(arys) > 1: arys = numpoly.align_exponents(*arys) out = numpoly.ndpoly( exponents=arys[0].exponents, shape=(sum([ary.size for ary in arys]), ), names=arys[0].names, dtype=ary[0].dtype, ) idx = 0 for ary in arys: for key in ary.keys: out.values[key][idx:idx + ary.size] = ary.values[key] idx += ary.size return out
def diff(poly, *diffvars): """ Polynomial differential operator. Args: poly (numpoly.ndpoly): Polynomial to differentiate. diffvars (numpoly.ndpoly, str): Singleton variables to take derivative off. Returns: Same as ``poly`` but differentiated with respect to ``diffvars``. Examples: >>> x, y = numpoly.symbols("x y") >>> poly = numpoly.polynomial([1, x, x*y**2+1]) >>> poly polynomial([1, x, 1+x*y**2]) >>> numpoly.diff(poly, "x") polynomial([0, 1, y**2]) >>> numpoly.diff(poly, 0, 1) polynomial([0, 0, 2*y]) >>> numpoly.diff(poly, x, x, x) polynomial([0, 0, 0]) """ poly = poly_ref = numpoly.aspolynomial(poly) for diffvar in diffvars: if isinstance(diffvar, string_types): idx = poly.names.index(diffvar) elif isinstance(diffvar, int): idx = diffvar else: diffvar = numpoly.aspolynomial(diffvar) assert len(diffvar.names) == 1, "only one at the time" assert numpy.all(diffvar.exponents == 1), ( "derivative variable assumes singletons") idx = poly.names.index(diffvar.names[0]) exponents = poly.exponents coefficients = [ (exponent[idx] * coefficient.T).T for exponent, coefficient in zip(exponents, poly.coefficients) ] exponents[:, idx] -= 1 assert not numpy.any(exponents < 0) poly = numpoly.ndpoly.from_attributes( exponents=exponents, coefficients=coefficients, names=poly_ref.names, ) poly, poly_ref = numpoly.align_polynomials(poly, poly_ref) return poly
def matmul( x1: PolyLike, x2: PolyLike, out: Optional[ndpoly] = None, **kwargs: Any, ) -> ndpoly: """ Matrix product of two arrays. Args: x1, x2: Input arrays, scalars not allowed. out: A location into which the result is stored. If provided, it must have a shape that matches the signature `(n,k),(k,m)->(n,m)`. If not provided or `None`, a freshly-allocated array is returned. Returns: The matrix product of the inputs. This is a scalar only when both x1, x2 are 1-d vectors. Raises: ValueError: If the last dimension of `x1` is not the same size as the second-to-last dimension of `x2`. Examples: >>> poly = numpoly.variable(4).reshape(2, 2) >>> poly polynomial([[q0, q1], [q2, q3]]) >>> numpoly.matmul(poly, [[0, 1], [2, 3]]) polynomial([[2*q1, 3*q1+q0], [2*q3, 3*q3+q2]]) >>> numpoly.matmul(poly, [4, 5]) polynomial([[4*q1+4*q0, 5*q1+5*q0], [4*q3+4*q2, 5*q3+5*q2]]) >>> numpoly.matmul(*poly) polynomial([q1*q2+q0*q2, q1*q3+q0*q3]) """ x1 = numpoly.aspolynomial(x1) x2 = numpoly.aspolynomial(x2) if not x1.shape: raise ValueError(ERROR_MESSAGE % 0) if not x2.shape: raise ValueError(ERROR_MESSAGE % 1) x1 = numpoly.reshape(x1, x1.shape+(1,)) x2 = numpoly.reshape(x2, x2.shape[:-2]+(1,)+x2.shape[-2:]) x1, x2 = numpoly.broadcast_arrays(x1, x2) out_ = numpoly.multiply(x1, x2, out=out, **kwargs) return numpoly.sum(out_, axis=-2)
def simple_dispatch(numpy_func: Callable, inputs: Sequence[Any], out: Optional[Tuple[ndpoly, ...]] = None, **kwargs: Any) -> ndpoly: """ Dispatch function between numpy and numpoly. Assumes that evaluation can be performed on the coefficients alone and that there are no change to the polynomials. Args: numpy_func: The numpy function to evaluate `inputs` on. inputs: One or more input arrays. 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. kwargs: Keyword args passed to `numpy_func`. Returns: Polynomial, where the coefficients from `input` are passed to `numpy_func` to create the output coefficients. """ inputs = numpoly.align_polynomials(*inputs) keys = (inputs[0] if out is None else numpoly.aspolynomial(out[0])).keys tmp = numpy_func(*[poly.values[keys[0]] for poly in inputs], **kwargs) if out is None: out_ = numpoly.ndpoly( exponents=inputs[0].exponents, shape=tmp.shape, names=inputs[0].indeterminants, dtype=tmp.dtype, ) else: assert len(out) == 1 out_ = out[0] out_.values[keys[0]] = tmp for key in keys[1:]: out_.values[key] = numpy_func(*[poly.values[key] for poly in inputs], **kwargs) if out is None: out_ = numpoly.clean_attributes(out_) return numpoly.aspolynomial(out_)
def logical_or(x1, x2, out=None, where=True, **kwargs): """ Compute the truth value of x1 OR x2 element-wise. Args: x1, x2 (numpoly.ndpoly): Logical OR is applied to the elements of `x1` and `x2`. If ``x1.shape != x2.shape``, they must be broadcastable to a common shape (which becomes the shape of the output). out (Optional[numpy.ndarray]): 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 (Optional[numpy.ndarray]): 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: y (numpy.ndarray): Boolean result of the logical OR operation applied to the elements of `x1` and `x2`; the shape is determined by broadcasting. This is a scalar if both `x1` and `x2` are scalars. Examples: >>> numpoly.logical_or(True, False) True >>> numpoly.logical_or([True, False], [False, False]) array([ True, False]) >>> x = numpy.arange(5) >>> numpoly.logical_or(x < 1, x > 3) array([ True, False, False, False, True]) """ x1 = numpoly.aspolynomial(x1) x2 = numpoly.aspolynomial(x2) coefficients1 = numpy.any(x1.coefficients, 0) coefficients2 = numpy.any(x2.coefficients, 0) return numpy.logical_or(coefficients1, coefficients2, out=out, where=where, **kwargs)
def nonzero(x: PolyLike, **kwargs: Any) -> Tuple[numpy.ndarray, ...]: """ Return the indices of the elements that are non-zero. Args: x: Input array. Returns: Indices of elements that are non-zero. Examples: >>> q0, q1 = numpoly.variable(2) >>> poly = numpoly.polynomial([[3*q0, 0, 0], ... [0, 4*q1, 0], ... [5*q0+q1, 6*q0, 0]]) >>> poly polynomial([[3*q0, 0, 0], [0, 4*q1, 0], [q1+5*q0, 6*q0, 0]]) >>> numpoly.nonzero(poly) (array([0, 1, 2, 2]), array([0, 1, 0, 1])) >>> poly[numpoly.nonzero(poly)] polynomial([3*q0, 4*q1, q1+5*q0, 6*q0]) """ x = numpoly.aspolynomial(x) return numpy.nonzero(numpy.any(numpy.asarray(x.coefficients), axis=0))
def tonumpy(poly: PolyLike) -> numpy.ndarray: """ Cast polynomial to numpy.ndarray, if possible. Args: poly: polynomial to cast. Returns: Numpy array. Raises: numpoly.baseclass.FeatureNotSupported: Only constant polynomials can be cast to numpy.ndarray. Examples: >>> numpoly.tonumpy(numpoly.polynomial([1, 2])) array([1, 2]) """ poly = numpoly.aspolynomial(poly) if not poly.isconstant(): raise numpoly.FeatureNotSupported( "only constant polynomials can be converted to array.") idx = numpy.argwhere(numpy.all(poly.exponents == 0, -1)).item() if poly.size: return numpy.array(poly.coefficients[idx]) return numpy.array([])
def common_type(*arrays): """ Return a scalar type which is common to the input arrays. The return type will always be an inexact (i.e. floating point) scalar type, even if all the arrays are integer arrays. If one of the inputs is an integer array, the minimum precision type that is returned is a 64-bit floating point dtype. All input arrays except int64 and uint64 can be safely cast to the returned dtype without loss of information. Args: arrays (numpoly.ndpoly): Input arrays. Return: out (numpy.generic): Data type code. Examples: >>> numpoly.common_type( ... numpy.array(2, dtype=numpy.float32)).__name__ 'float32' >>> numpoly.common_type( ... numpoly.symbols("x")).__name__ 'float64' >>> numpoly.common_type( ... numpy.arange(3), 1j*numpoly.symbols("x"), 45).__name__ 'complex128' """ arrays = [numpoly.aspolynomial(array) for array in arrays] arrays = [array[array.keys[0]] for array in arrays] return numpy.common_type(*arrays)
def transpose(a, axes=None): """ Permute the dimensions of an array. Args: a (numpoly.ndpoly): Input array. axes (int, Sequence[int]): By default, reverse the dimensions, otherwise permute the axes according to the values given. Returns: (numpoly.ndpoly): `a` with its axes permuted. A view is returned whenever possible. Examples: >>> poly = numpoly.monomial(3, names=("x", "y")).reshape(2, 3) >>> poly polynomial([[1, y, x], [y**2, x*y, x**2]]) >>> numpoly.transpose(poly) polynomial([[1, y**2], [y, x*y], [x, x**2]]) """ a = numpoly.aspolynomial(a) out = numpy.transpose(a.values, axes=axes) out = numpoly.polynomial(out, names=a.indeterminants) return out
def isconstant(poly: PolyLike) -> bool: """ Check if a polynomial is constant or not. Args: poly: polynomial to check if is constant or not. Returns: True if polynomial has no indeterminants. Examples: >>> q0 = numpoly.variable() >>> numpoly.isconstant(numpoly.polynomial([q0])) False >>> numpoly.isconstant(numpoly.polynomial([1])) True """ poly = numpoly.aspolynomial(poly) for exponent, coefficient in zip(poly.exponents, poly.coefficients): if not numpy.any(exponent): continue if numpy.any(coefficient): return False return True
def transpose( a: PolyLike, axes: Union[None, Sequence[int], numpy.ndarray] = None, ) -> ndpoly: """ Permute the dimensions of an array. Args: a: Input array. axes: By default, reverse the dimensions, otherwise permute the axes according to the values given. Returns: `a` with its axes permuted. A view is returned whenever possible. Examples: >>> poly = numpoly.monomial([3, 3]).reshape(2, 3) >>> poly polynomial([[1, q0, q0**2], [q1, q0*q1, q1**2]]) >>> numpoly.transpose(poly) polynomial([[1, q1], [q0, q0*q1], [q0**2, q1**2]]) """ a = numpoly.aspolynomial(a) out = numpy.transpose(a.values, axes=axes) out = numpoly.polynomial(out, names=a.indeterminants) return out
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 expand_dims(a: PolyLike, axis: int) -> ndpoly: """ Expand the shape of an array. Insert a new axis that will appear at the `axis` position in the expanded array shape. Args: a: Input array. axis: Position in the expanded axes where the new axis is placed. Returns: View of `a` with the number of dimensions increased by one. Examples: >>> poly = numpoly.variable(2) >>> numpoly.expand_dims(poly, axis=0) polynomial([[q0, q1]]) >>> numpoly.expand_dims(poly, axis=1) polynomial([[q0], [q1]]) """ a = numpoly.aspolynomial(a) out = numpy.expand_dims(a.values, axis=axis) return numpoly.polynomial(out, names=a.indeterminants)
def apply_over_axes( func: Callable, a: PolyLike, axes: numpy.typing.ArrayLike, ) -> ndpoly: """ Apply a function repeatedly over multiple axes. `func` is called as `res = func(a, axis)`, where `axis` is the first element of `axes`. The result `res` of the function call must have either the same dimensions as `a` or one less dimension. If `res` has one less dimension than `a`, a dimension is inserted before `axis`. The call to `func` is then repeated for each axis in `axes`, with `res` as the first argument. Args: func: This function must take two arguments, `func(a, axis)`. a: Input array. axes: Axes over which `func` is applied; the elements must be integers. Returns: The output array. The number of dimensions is the same as `a`, but the shape can be different. This depends on whether `func` changes the shape of its output with respect to its input. Examples: >>> polynomial = numpy.arange(24).reshape(2, 4, 3) >>> polynomial = polynomial*numpoly.variable(3) >>> numpoly.apply_over_axes( ... func=numpoly.sum, a=polynomial, axes=1) polynomial([[[18*q0, 22*q1, 26*q2]], <BLANKLINE> [[66*q0, 70*q1, 74*q2]]]) >>> numpoly.apply_over_axes( ... func=numpoly.sum, a=polynomial, axes=[0, 2]) polynomial([[[16*q2+14*q1+12*q0], [22*q2+20*q1+18*q0], [28*q2+26*q1+24*q0], [34*q2+32*q1+30*q0]]]) """ @wraps(func) def wrapper_func(array, axis): """Wrap func function.""" # Align indeterminants in case slicing changed them array = numpoly.polynomial(array, names=a.indeterminants) array, _ = numpoly.align.align_indeterminants(array, a.indeterminants) # Evaluate function out = func(array, axis=axis) # Restore indeterminants in case func changed them. out, _ = numpoly.align.align_indeterminants(out, a.indeterminants) return out # Initiate wrapper a = numpoly.aspolynomial(a) out = numpy.apply_over_axes(wrapper_func, a=a.values, axes=axes) return out
def gradient(poly: PolyLike) -> ndpoly: """ Polynomial gradient operator. Args: poly: Polynomial to differentiate. If polynomial vector is provided, the Jacobi-matrix is returned instead. Returns: Same as ``poly`` but with an extra first dimensions, one for each variable in ``poly.indeterminants``, filled with gradient values. Examples: >>> q0 = numpoly.variable() >>> poly = 5*q0**5+4 >>> numpoly.gradient(poly) polynomial([25*q0**4]) >>> q0, q1 = numpoly.variable(2) >>> poly = 4*q0**3+2*q1**2+3 >>> numpoly.gradient(poly) polynomial([12*q0**2, 4*q1]) >>> poly = numpoly.polynomial([1, q0**3, q0*q1**2+1]) >>> numpoly.gradient(poly) polynomial([[0, 3*q0**2, q1**2], [0, 0, 2*q0*q1]]) """ poly = numpoly.aspolynomial(poly) polys = [ derivative(poly, diffvar)[numpy.newaxis] for diffvar in poly.names ] return numpoly.concatenate(polys, axis=0)
def align_dtype(*polys): """ Align polynomial by shape. Args: polys (numpoly.ndpoly): Polynomial to make adjustment to. Returns: (Tuple[numpoly.ndpoly, ...]): Same as ``polys``, but internal adjustments made to make them compatible for further operations. Examples: >>> x = numpoly.symbols("x") >>> x.dtype.name 'int64' >>> poly, _ = numpoly.align_dtype(x, 4.5) >>> poly.dtype.name 'float64' """ polys = [numpoly.aspolynomial(poly) for poly in polys] dtype = numpy.sum([numpy.array(True, dtype=poly.dtype) for poly in polys]).dtype polys = [ numpoly.ndpoly.from_attributes( exponents=poly.exponents, coefficients=poly.coefficients, names=poly.indeterminants, dtype=dtype, clean=False, ) for poly in polys ] return tuple(polys)
def common_type(*arrays: PolyLike) -> numpy.dtype: """ Return a scalar type which is common to the input arrays. The return type will always be an inexact (i.e. floating point) scalar type, even if all the arrays are integer arrays. If one of the inputs is an integer array, the minimum precision type that is returned is a 64-bit floating point dtype. All input arrays except int64 and uint64 can be safely cast to the returned dtype without loss of information. Args: arrays: Input arrays. Return: Data type code. Examples: >>> scalar = numpy.array(2, dtype=numpy.float32) >>> numpoly.common_type(scalar) is numpy.float32 True >>> q0 = numpoly.variable() >>> numpoly.common_type(q0) is numpy.float64 True >>> numpoly.common_type(q0, 1j) is numpy.complex128 True """ arrays_ = [numpoly.aspolynomial(array) for array in arrays] arrays_ = [array.values[array.keys[0]] for array in arrays_] return numpy.common_type(*arrays_)
def diag(y: PolyLike, k: int = 0) -> ndpoly: """ Extract a diagonal or construct a diagonal array. Args: v: If `v` is a 2-D array, return a copy of its `k`-th diagonal. If `v` is a 1-D array, return a 2-D array with `v` on the `k`-th diagonal. k: Diagonal in question. Use `k > 0` for diagonals above the main diagonal, and `k < 0` for diagonals below the main diagonal. Returns: The extracted diagonal or constructed diagonal array. Examples: >>> poly = numpoly.monomial(9).reshape(3, 3) >>> poly polynomial([[1, q0, q0**2], [q0**3, q0**4, q0**5], [q0**6, q0**7, q0**8]]) >>> numpoly.diag(poly) polynomial([1, q0**4, q0**8]) >>> numpoly.diag(poly, k=1) polynomial([q0, q0**5]) >>> numpoly.diag(poly, k=-1) polynomial([q0**3, q0**7]) """ y = numpoly.aspolynomial(y) out = numpy.diag(y.values, k=k) return numpoly.polynomial(out, names=y.names)
def to_sympy(poly: PolyLike) -> Any: """ Convert numpoly object to sympy object, or array of sympy objects. Args: poly: Polynomial object to convert to sympy. Returns: If scalar, a sympy expression object, or if array, numpy.array with expression object values. Examples: >>> q0, q1 = numpoly.variable(2) >>> poly = numpoly.polynomial([[1, q0**3], [q1-1, -3*q0]]) >>> sympy_poly = to_sympy(poly) >>> sympy_poly array([[1, q0**3], [q1 - 1, -3*q0]], dtype=object) >>> type(sympy_poly.item(-1)) <class 'sympy.core.mul.Mul'> """ poly = numpoly.aspolynomial(poly) if poly.shape: return numpy.array([to_sympy(poly_) for poly_ in poly]) from sympy import symbols # type: ignore locals_ = dict(zip(poly.names, symbols(poly.names))) polynomial = eval(str(poly), locals_, {}) # pylint: disable=eval-used return polynomial
def gradient(poly): """ Polynomial gradient operator. Args: poly (numpoly.ndpoly): Polynomial to differentiate. If polynomial vector is provided, the Jacobi-matrix is returned instead. Returns: Same as ``poly`` but with an extra first dimensions, one for each variable in ``poly.indeterminants``, filled with gradient values. Examples: >>> x, y = numpoly.symbols("x y") >>> poly = 5*x**5+4 >>> numpoly.gradient(poly) polynomial([25*x**4]) >>> poly = 4*x**3+2*y**2+3 >>> numpoly.gradient(poly) polynomial([12*x**2, 4*y]) >>> poly = numpoly.polynomial([1, x**3, x*y**2+1]) >>> numpoly.gradient(poly) polynomial([[0, 3*x**2, y**2], [0, 0, 2*x*y]]) """ poly = numpoly.aspolynomial(poly) polys = [diff(poly, diffvar)[numpy.newaxis] for diffvar in poly.names] return numpoly.concatenate(polys, axis=0)
def tonumpy(poly): """ Cast polynomial to numpy.ndarray, if possible. Args: poly (numpoly.ndpoly): polynomial to cast. Returns: (numpy.ndarray): Numpy array. Raises: ValueError: Only constant polynomials can be cast to numpy.ndarray. Examples: >>> numpoly.tonumpy(numpoly.polynomial([1, 2])) array([1, 2]) >>> numpoly.tonumpy(numpoly.symbols("x")) Traceback (most recent call last): ... ValueError: only constant polynomials can be converted to array. """ poly = numpoly.aspolynomial(poly) if not poly.isconstant(): raise ValueError( "only constant polynomials can be converted to array.") idx = numpy.argwhere(numpy.all(poly.exponents == 0, -1)).item() if poly.size: return numpy.array(poly.coefficients[idx]) return numpy.array([])
def reshape( a: PolyLike, newshape: Union[int, Sequence[int]], order: Order = "C", ) -> ndpoly: """ Give a new shape to an array without changing its data. Args: a: Array to be reshaped. newshape: The new shape should be compatible with the original shape. If an integer, then the result will be a 1-D array of that length. One shape dimension can be -1. In this case, the value is inferred from the length of the array and remaining dimensions. order: Read the elements of `a` using this index order, and place the elements into the reshaped array using this index order. 'C' means to read / write the elements using C-like index order, with the last axis index changing fastest, back to the first axis index changing slowest. 'F' means to read / write the elements using Fortran-like index order, with the first index changing fastest, and the last index changing slowest. Note that the 'C' and 'F' options take no account of the memory layout of the underlying array, and only refer to the order of indexing. 'A' means to read / write the elements in Fortran-like index order if `a` is Fortran *contiguous* in memory, C-like order otherwise. Returns: This will be a new view object if possible; otherwise, it will be a copy. Note there is no guarantee of the *memory layout* (C- or Fortran- contiguous) of the returned array. Examples: >>> numpoly.reshape([1, 2, 3, 4], (2, 2)) polynomial([[1, 2], [3, 4]]) >>> numpoly.reshape(numpoly.monomial(6), (3, 2)) polynomial([[1, q0], [q0**2, q0**3], [q0**4, q0**5]]) """ poly = numpoly.aspolynomial(a) array = numpy.reshape(poly.values, newshape=newshape, order=order) return numpoly.aspolynomial(array, names=poly.indeterminants)
def sortable_proxy( poly: PolyLike, graded: bool = False, reverse: bool = False, ) -> numpy.ndarray: """ Create a numerical proxy for a polynomial to allow compare. 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: poly: Polynomial to convert into something sortable. graded: Graded sorting, meaning the indices are always sorted by the index sum. E.g. ``q0**2*q1**2*q2**2`` has an exponent sum of 6, and will therefore be consider larger than both ``q0**3*q1*q2``, ``q0*q1**3*q2`` and ``q0*q1*z**3``. reverse: Reverses lexicographical sorting meaning that ``q0*q1**3`` is considered bigger than ``q0**3*q1``, instead of the opposite. Returns: Integer array where ``a > b`` is retained for the giving rule of ``ordering``. Examples: >>> q0, q1 = numpoly.variable(2) >>> poly = numpoly.polynomial( ... [q0**2, 2*q0, 3*q1, 4*q0, 5]) >>> numpoly.sortable_proxy(poly) array([3, 1, 4, 2, 0]) >>> numpoly.sortable_proxy(poly, reverse=True) array([4, 2, 1, 3, 0]) >>> numpoly.sortable_proxy([8, 4, 10, -100]) array([2, 1, 3, 0]) >>> numpoly.sortable_proxy([[8, 4], [10, -100]]) array([[2, 1], [3, 0]]) """ poly = numpoly.aspolynomial(poly) coefficients = poly.coefficients proxy = numpy.tile(-1, poly.shape) largest = numpoly.lead_exponent(poly, graded=graded, reverse=reverse) for idx in numpoly.glexsort( poly.exponents.T, graded=graded, reverse=reverse): indices = numpy.all(largest == poly.exponents[idx], axis=-1) values = numpy.argsort(coefficients[idx][indices]) proxy[indices] = numpy.argsort(values)+numpy.max(proxy)+1 proxy = numpy.argsort(numpy.argsort(proxy.ravel())).reshape(proxy.shape) return proxy
def repeat( a: PolyLike, repeats: numpy.typing.ArrayLike, axis: int = 0, ) -> ndpoly: """ Repeat elements of an array. Args: a: Input array. repeats: The number of repetitions for each element. `repeats` is broadcasted to fit the shape of the given axis. axis: The axis along which to repeat values. By default, use the flattened input array, and return a flat output array. Returns: Output array which has the same shape as `a`, except along the given axis. Examples: >>> q0 = numpoly.variable() >>> numpoly.repeat(q0, 4) polynomial([q0, q0, q0, q0]) >>> poly = numpoly.polynomial([[1, q0-1], [q0**2, q0]]) >>> numpoly.repeat(poly, 2) polynomial([[1, q0-1], [1, q0-1], [q0**2, q0], [q0**2, q0]]) >>> numpoly.repeat(poly, 3, axis=1) polynomial([[1, 1, 1, q0-1, q0-1, q0-1], [q0**2, q0**2, q0**2, q0, q0, q0]]) >>> numpoly.repeat(poly, [1, 2], axis=0) polynomial([[1, q0-1], [q0**2, q0], [q0**2, q0]]) """ a = numpoly.aspolynomial(a) repeats = numpy.asarray(repeats) result = numpy.repeat(a.values, repeats=repeats, axis=axis) return numpoly.aspolynomial(result, names=a.indeterminants)