Exemple #1
0
def inner(a: PolyLike, b: PolyLike) -> ndpoly:
    """
    Inner product of two arrays.

    Ordinary inner product of vectors for 1-D arrays (without complex
    conjugation), in higher dimensions a sum product over the last axes.

    """
    a, b = numpoly.align_exponents(a, b)
    return numpoly.sum(numpoly.multiply(a, b), axis=-1)
Exemple #2
0
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
Exemple #3
0
def stack(
    arrays: Sequence[PolyLike],
    axis: int = 0,
    out: Optional[ndpoly] = None,
) -> ndpoly:
    """
    Join a sequence of arrays along a new axis.

    The ``axis`` parameter specifies the index of the new axis in the
    dimensions of the result. For example, if ``axis=0`` it will be the first
    dimension and if ``axis=-1`` it will be the last dimension.

    Args:
        arrays:
            Each array must have the same shape.
        axis:
            The axis in the result array along which the input arrays are
            stacked.
        out:
            If provided, the destination to place the result. The shape must be
            correct, matching that of what stack would have returned if no out
            argument were specified.

    Returns:
        The stacked array has one more dimension than the input arrays.

    Examples:
        >>> poly = numpoly.variable(3)
        >>> const = numpoly.polynomial([1, 2, 3])
        >>> numpoly.stack([poly, const])
        polynomial([[q0, q1, q2],
                    [1, 2, 3]])
        >>> numpoly.stack([poly, const], axis=-1)
        polynomial([[q0, 1],
                    [q1, 2],
                    [q2, 3]])

    """
    arrays = numpoly.align_exponents(*arrays)
    if out is None:
        coefficients = [numpy.stack(
            [array.values[key] for array in arrays], axis=axis)
                        for key in arrays[0].keys]
        out = numpoly.polynomial_from_attributes(
            exponents=arrays[0].exponents,
            coefficients=coefficients,
            names=arrays[0].names,
            dtype=coefficients[0].dtype,
        )
    else:
        for key in out.keys:
            if key in arrays[0].keys:
                numpy.stack([array.values[key] for array in arrays],
                            out=out.values[key], axis=axis)
    return out
Exemple #4
0
def concatenate(
    arrays: Sequence[PolyLike],
    axis: int = 0,
    out: Optional[ndpoly] = None,
) -> ndpoly:
    """
    Join a sequence of arrays along an existing axis.

    Args:
        arrays:
            The arrays must have the same shape, except in the dimension
            corresponding to `axis` (the first, by default).
        axis:
            The axis along which the arrays will be joined.  If axis is None,
            arrays are flattened before use.  Default is 0.
        out:
            If provided, the destination to place the result. The shape must be
            correct, matching that of what concatenate would have returned if
            no out argument were specified.

    Returns:
        The concatenated array.

    Examples:
        >>> const = numpy.array([[1, 2], [3, 4]])
        >>> poly = numpoly.variable(2).reshape(1, 2)
        >>> numpoly.concatenate((const, poly), axis=0)
        polynomial([[1, 2],
                    [3, 4],
                    [q0, q1]])
        >>> numpoly.concatenate((const, poly.T), axis=1)
        polynomial([[1, 2, q0],
                    [3, 4, q1]])
        >>> numpoly.concatenate((const, poly), axis=None)
        polynomial([1, 2, 3, 4, q0, q1])

    """
    arrays = numpoly.align_exponents(*arrays)
    if out is None:
        coefficients = [numpy.concatenate(
            [array.values[key] for array in arrays], axis=axis)
                        for key in arrays[0].keys]
        out = numpoly.polynomial_from_attributes(
            exponents=arrays[0].exponents,
            coefficients=coefficients,
            names=arrays[0].names,
            dtype=coefficients[0].dtype,
        )
    else:
        for key in out.keys:
            if key in arrays[0].keys:
                numpy.concatenate([array.values[key] for array in arrays],
                                  out=out.values[key], axis=axis)
    return out
Exemple #5
0
def dstack(tup: Sequence[PolyLike]) -> ndpoly:
    """
    Stack arrays in sequence depth wise (along third axis).

    This is equivalent to concatenation along the third axis after 2-D arrays
    of shape `(M,N)` have been reshaped to `(M,N,1)` and 1-D arrays of shape
    `(N,)` have been reshaped to `(1,N,1)`. Rebuilds arrays divided by
    `dsplit`.

    This function makes most sense for arrays with up to 3 dimensions. For
    instance, for pixel-data with a height (first axis), width (second axis),
    and r/g/b channels (third axis). The functions `concatenate`, `stack` and
    `block` provide more general stacking and concatenation operations.

    Args:
        tup:
            The arrays must have the same shape along all but the third axis.
            1-D or 2-D arrays must have the same shape.

    Returns:
        The array formed by stacking the given arrays, will be at least 3-D.

    Examples:
        >>> poly1 = numpoly.variable(3)
        >>> const1 = numpoly.polynomial([1, 2, 3])
        >>> numpoly.dstack([poly1, const1])
        polynomial([[[q0, 1],
                     [q1, 2],
                     [q2, 3]]])
        >>> const2 = numpoly.polynomial([[1], [2], [3]])
        >>> poly2 = poly1.reshape(3, 1)
        >>> numpoly.dstack([const2, poly2])
        polynomial([[[1, q0]],
        <BLANKLINE>
                    [[2, q1]],
        <BLANKLINE>
                    [[3, q2]]])

    """
    arrays = numpoly.align_exponents(*tup)
    coefficients = [numpy.dstack([array.values[key] for array in arrays])
                    for key in arrays[0].keys]
    return numpoly.polynomial_from_attributes(
        exponents=arrays[0].exponents,
        coefficients=coefficients,
        names=arrays[0].names,
        dtype=coefficients[0].dtype,
    )
Exemple #6
0
def roots(poly: PolyLike) -> numpy.ndarray:
    """
    Return the roots of a polynomial.

    Assumes the polynomial has a single dimension.

    Args:
        poly:
            Polynomial to take roots of, or if constant, the coefficients of
            said polynomial. This to be compatible with :func:`numpy.roots`.

    Returns:
        An array containing the roots of the polynomial.

    Raises:
        ValueError:
            When `poly` cannot be converted to a rank-1 polynomial.

    Notes:
        The algorithm relies on computing the eigenvalues of the companion
        matrix [1]_.

    .. [1] R. A. Horn & C. R. Johnson, *Matrix Analysis*.  Cambridge, UK:
        Cambridge University Press, 1999, pp. 146-7.

    Examples:
        >>> q0 = numpoly.variable()
        >>> poly = 3.2*q0**2+2*q0+1
        >>> numpoly.roots(poly)
        array([-0.3125+0.46351241j, -0.3125-0.46351241j])
        >>> numpoly.roots([3.2, 2, 1])
        array([-0.3125+0.46351241j, -0.3125-0.46351241j])

    """
    # backwards compatibility
    poly = numpoly.aspolynomial(poly)
    if poly.isconstant():
        return numpy.roots(poly.tonumpy())
    # only rank-1
    if len(poly.names) > 1:
        raise ValueError("polynomial is not of rank 1.")
    # align exponents to include all coefficients
    filled_basis = poly.indeterminants**numpy.arange(
        numpoly.lead_exponent(poly), dtype=int)
    _, poly = numpoly.align_exponents(filled_basis, poly)
    # pass coefficients to numpy
    return numpy.roots(poly.coefficients[::-1])
Exemple #7
0
def hstack(tup: Sequence[PolyLike]) -> ndpoly:
    """
    Stack arrays in sequence horizontally (column wise).

    This is equivalent to concatenation along the second axis, except for 1-D
    arrays where it concatenates along the first axis. Rebuilds arrays divided
    by `hsplit`.

    This function makes most sense for arrays with up to 3 dimensions. For
    instance, for pixel-data with a height (first axis), width (second axis),
    and r/g/b channels (third axis). The functions `concatenate`, `stack` and
    `block` provide more general stacking and concatenation operations.

    Args:
        tup:
            The arrays must have the same shape along all but the second axis,
            except 1-D arrays which can be any length.

    Returns:
        The array formed by stacking the given arrays.

    Examples:
        >>> poly1 = numpoly.variable(3)
        >>> const1 = numpoly.polynomial([1, 2, 3])
        >>> numpoly.hstack([poly1, const1])
        polynomial([q0, q1, q2, 1, 2, 3])
        >>> const2 = numpoly.polynomial([[1], [2], [3]])
        >>> poly2 = poly1.reshape(3, 1)
        >>> numpoly.hstack([const2, poly2])
        polynomial([[1, q0],
                    [2, q1],
                    [3, q2]])

    """
    arrays = numpoly.align_exponents(*tup)
    coefficients = [
        numpy.hstack([array.values[key] for array in arrays])
        for key in arrays[0].keys
    ]
    return numpoly.polynomial_from_attributes(
        exponents=arrays[0].exponents,
        coefficients=coefficients,
        names=arrays[0].names,
        dtype=coefficients[0].dtype,
    )
Exemple #8
0
def dstack(tup):
    """
    Stack arrays in sequence depth wise (along third axis).

    This is equivalent to concatenation along the third axis after 2-D arrays
    of shape `(M,N)` have been reshaped to `(M,N,1)` and 1-D arrays of shape
    `(N,)` have been reshaped to `(1,N,1)`. Rebuilds arrays divided by
    `dsplit`.

    This function makes most sense for arrays with up to 3 dimensions. For
    instance, for pixel-data with a height (first axis), width (second axis),
    and r/g/b channels (third axis). The functions `concatenate`, `stack` and
    `block` provide more general stacking and concatenation operations.

    Args:
        tup (Sequence[numpoly.ndpoly]):
            The arrays must have the same shape along all but the third axis.
            1-D or 2-D arrays must have the same shape.

    Returns:
        (numpoly.ndpoly):
            The array formed by stacking the given arrays, will be at least
            3-D.

    Examples:
        >>> a = numpoly.symbols("x y z")
        >>> b = numpoly.polynomial([1, 2, 3])
        >>> numpoly.dstack([a, b])
        polynomial([[[x, 1],
                     [y, 2],
                     [z, 3]]])
        >>> c = numpoly.polynomial([[1], [2], [3]])
        >>> d = a.reshape(3, 1)
        >>> numpoly.dstack([c, d])
        polynomial([[[1, x]],
        <BLANKLINE>
                    [[2, y]],
        <BLANKLINE>
                    [[3, z]]])

    """
    arrays = numpoly.align_exponents(*tup)
    arrays = numpoly.align_dtype(*arrays)
    result = numpy.dstack([array.values for array in arrays])
    return numpoly.aspolynomial(result, names=arrays[0].indeterminants)
Exemple #9
0
def outer(
    a: PolyLike,
    b: PolyLike,
    out: Optional[ndpoly] = None,
) -> ndpoly:
    """
    Compute the outer product of two vectors.

    Given two vectors, ``a = [a0, a1, ..., aM]`` and
    ``b = [b0, b1, ..., bN]``, the outer product is::

        [[a0*b0  a0*b1 ... a0*bN ]
         [a1*b0    .
         [ ...          .
         [aM*b0            aM*bN ]]

    Args:
        a:
            First input vector. Input is flattened if not already
            1-dimensional.
        b:
            Second input vector. Input is flattened if not already
            1-dimensional.
        out:
            A location where the result is stored.

    Returns:
        ``out[i, j] = a[i] * b[j]``

    Examples:
        >>> poly = numpoly.variable(3)
        >>> const = numpy.arange(5)
        >>> numpoly.outer(poly, const)
        polynomial([[0, q0, 2*q0, 3*q0, 4*q0],
                    [0, q1, 2*q1, 3*q1, 4*q1],
                    [0, q2, 2*q2, 3*q2, 4*q2]])

    """
    a, b = numpoly.align_exponents(a, b)
    a = a.ravel()[:, numpy.newaxis]
    b = b.ravel()[numpy.newaxis, :]
    return numpoly.multiply(a, b, out=out)
Exemple #10
0
def stack(arrays, axis=0, out=None):
    """
    Join a sequence of arrays along a new axis.

    The ``axis`` parameter specifies the index of the new axis in the
    dimensions of the result. For example, if ``axis=0`` it will be the first
    dimension and if ``axis=-1`` it will be the last dimension.

    Args:
        arrays (Sequence[numpoly.ndpoly]):
            Each array must have the same shape.
        axis (Optional[int]):
            The axis in the result array along which the input arrays are
            stacked.
        out (Optional[numpy.ndarray]):
            If provided, the destination to place the result. The shape must be
            correct, matching that of what stack would have returned if no out
            argument were specified.

    Returns:
        (numpoly.ndpoly):
            The stacked array has one more dimension than the input arrays.

    Examples:
        >>> a = numpoly.symbols("x y z")
        >>> b = numpoly.polynomial([1, 2, 3])
        >>> numpoly.stack([a, b])
        polynomial([[x, y, z],
                    [1, 2, 3]])
        >>> numpoly.stack([a, b], axis=-1)
        polynomial([[x, 1],
                    [y, 2],
                    [z, 3]])

    """
    arrays = numpoly.align_exponents(*arrays)
    arrays = numpoly.align_dtype(*arrays)
    result = numpy.stack(
        [array.values for array in arrays], axis=axis, out=out)
    return numpoly.aspolynomial(result, names=arrays[0].indeterminants)
Exemple #11
0
def concatenate(arrays, axis=0, out=None):
    """
    Join a sequence of arrays along an existing axis.

    Args:
        arrays (Iterable[numpoly.ndpoly]):
            The arrays must have the same shape, except in the dimension
            corresponding to `axis` (the first, by default).
        axis (Optional[int]):
            The axis along which the arrays will be joined.  If axis is None,
            arrays are flattened before use.  Default is 0.
        out (Optional[numpy.ndarray]):
            If provided, the destination to place the result. The shape must be
            correct, matching that of what concatenate would have returned if
            no out argument were specified.

    Returns:
        (numpoly.ndpoly):
            The concatenated array.

    Examples:
        >>> a = numpy.array([[1, 2], [3, 4]])
        >>> b = numpoly.symbols("x y").reshape(1, 2)
        >>> numpoly.concatenate((a, b), axis=0)
        polynomial([[1, 2],
                    [3, 4],
                    [x, y]])
        >>> numpoly.concatenate((a, b.T), axis=1)
        polynomial([[1, 2, x],
                    [3, 4, y]])
        >>> numpoly.concatenate((a, b), axis=None)
        polynomial([1, 2, 3, 4, x, y])

    """
    arrays = numpoly.align_exponents(*arrays)
    arrays = numpoly.align_dtype(*arrays)
    result = numpy.concatenate(
        [array.values for array in arrays], axis=axis, out=out)
    return numpoly.aspolynomial(result, names=arrays[0].indeterminants)
Exemple #12
0
def hstack(tup):
    """
    Stack arrays in sequence horizontally (column wise).

    This is equivalent to concatenation along the second axis, except for 1-D
    arrays where it concatenates along the first axis. Rebuilds arrays divided
    by `hsplit`.

    This function makes most sense for arrays with up to 3 dimensions. For
    instance, for pixel-data with a height (first axis), width (second axis),
    and r/g/b channels (third axis). The functions `concatenate`, `stack` and
    `block` provide more general stacking and concatenation operations.

    Args:
        tup (Sequence[numpoly.ndpoly]):
            The arrays must have the same shape along all but the second axis,
            except 1-D arrays which can be any length.

    Returns:
        (numpoly.ndpoly):
            The array formed by stacking the given arrays.

    Examples:
        >>> a = numpoly.symbols("x y z")
        >>> b = numpoly.polynomial([1, 2, 3])
        >>> numpoly.hstack([a, b])
        polynomial([x, y, z, 1, 2, 3])
        >>> c = numpoly.polynomial([[1], [2], [3]])
        >>> d = a.reshape(3, 1)
        >>> numpoly.hstack([c, d])
        polynomial([[1, x],
                    [2, y],
                    [3, z]])

    """
    arrays = numpoly.align_exponents(*tup)
    arrays = numpoly.align_dtype(*arrays)
    result = numpy.hstack([array.values for array in arrays])
    return numpoly.aspolynomial(result, names=arrays[0].indeterminants)
Exemple #13
0
def outer(a, b, out=None):
    """
    Compute the outer product of two vectors.

    Given two vectors, ``a = [a0, a1, ..., aM]`` and
    ``b = [b0, b1, ..., bN]``, the outer product is::

        [[a0*b0  a0*b1 ... a0*bN ]
         [a1*b0    .
         [ ...          .
         [aM*b0            aM*bN ]]

    Args:
        a (numpoly.ndpoly):
            First input vector. Input is flattened if not already
            1-dimensional.
        b (numpoly.ndpoly):
            Second input vector. Input is flattened if not already
            1-dimensional.
        out (numpy.ndarray):
            A location where the result is stored.

    Returns:
        (numpoly.ndpoly):
            ``out[i, j] = a[i] * b[j]``

    Examples:
        >>> numpoly.outer(numpoly.symbols("x y z"), numpy.arange(5))
        polynomial([[0, x, 2*x, 3*x, 4*x],
                    [0, y, 2*y, 3*y, 4*y],
                    [0, z, 2*z, 3*z, 4*z]])

    """
    a, b = numpoly.align_exponents(a, b)
    a = a.flatten()[:, numpy.newaxis]
    b = b.flatten()[numpy.newaxis, :]
    return numpoly.multiply(a, b, out=out)
Exemple #14
0
def not_equal(
    x1: PolyLike,
    x2: PolyLike,
    out: Optional[numpy.ndarray] = None,
    where: numpy.typing.ArrayLike = True,
    **kwargs: Any,
) -> numpy.ndarray:
    """
    Return (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.

    Examples:
        >>> q0, q1 = numpoly.variable(2)
        >>> numpoly.not_equal([q0, q0], [q0, q1])
        array([False,  True])
        >>> numpoly.not_equal([q0, q0], [[q0, q1], [q1, q0]])
        array([[False,  True],
               [ True, False]])

    """
    x1, x2 = numpoly.align_exponents(x1, x2)
    if not x1.flags["OWNDATA"]:
        x1 = numpoly.polynomial(x1)
    if not x2.flags["OWNDATA"]:
        x2 = numpoly.polynomial(x2)
    # x1, x2 = numpoly.align_polynomials(x1, x2)
    where = numpy.asarray(where)
    for key in x1.keys:
        tmp = numpy.not_equal(x1.values[key],
                              x2.values[key],
                              where=where,
                              **kwargs)
        if out is None:
            out = tmp
        else:
            out |= tmp
    return numpy.asarray(out)
Exemple #15
0
def diff(
    a: PolyLike,
    n: int = 1,
    axis: int = -1,
    prepend: Optional[PolyLike] = None,
    append: Optional[PolyLike] = None,
) -> ndpoly:
    """
    Calculate the n-th discrete difference along the given axis.

    The first difference is given by ``out[i] = a[i+1] - a[i]`` along the given
    axis, higher differences are calculated by using `diff` recursively.

    Args:
        a:
            Input array
        n:
            The number of times values are "differenced". If zero, the input is
            returned as-is.
        axis:
            The axis along which the difference is taken, default is the last
            axis.
        prepend, append:
            Values to prepend or append to `a` along axis prior to
            performing the difference. Scalar values are expanded to
            arrays with length 1 in the direction of axis and the shape
            of the input array in along all other axes.  Otherwise the
            dimension and shape must match `a` except along axis.

    Returns:
        The n-th differences. The shape of the output is the same as `a` except
        along `axis` where the dimension is smaller by `n`. The type of the
        output is the same as the type of the difference between any two
        elements of `a`. This is the same as the type of `a` in most cases.

    Examples:
        >>> q0, q1 = numpoly.variable(2)
        >>> poly = numpoly.polynomial([1, q0, q1, q0**2, q1-1])
        >>> numpoly.diff(poly)
        polynomial([q0-1, q1-q0, q0**2-q1, -q0**2+q1-1])
        >>> numpoly.diff(poly, n=2)
        polynomial([q1-2*q0+1, q0**2-2*q1+q0, -2*q0**2+2*q1-1])
        >>> poly = numpoly.polynomial([[q0, 1], [2, q1]])
        >>> numpoly.diff(poly)
        polynomial([[-q0+1],
                    [q1-2]])
        >>> numpoly.diff(poly, prepend=7, append=q1)
        polynomial([[q0-7, -q0+1, q1-1],
                    [-5, q1-2, 0]])

    """
    if append is not None:
        if prepend is not None:
            a, append, prepend = numpoly.align_exponents(a, append, prepend)
        else:
            a, append = numpoly.align_exponents(a, append)
    elif prepend is not None:
        a, prepend = numpoly.align_exponents(a, prepend)
    else:
        a = numpoly.aspolynomial(a)

    out = None
    for key in a.keys:

        kwargs = {}
        if append is not None:
            kwargs["append"] = append.values[key]
        if prepend is not None:
            kwargs["prepend"] = prepend.values[key]
        tmp = numpy.diff(a.values[key], n=n, axis=axis, **kwargs)

        if out is None:
            out = numpoly.ndpoly(
                exponents=a.exponents,
                shape=tmp.shape,
                names=a.indeterminants,
                dtype=tmp.dtype,
            )
        out.values[key] = tmp
    assert out is not None
    return numpoly.clean_attributes(out)
Exemple #16
0
def E_cond(poly, freeze, dist, **kws):
    """
    Conditional expected value operator.

    1st order statistics of a polynomial on a given probability space
    conditioned on some of the variables.

    Args:
        poly (numpoly.ndpoly):
            Polynomial to find conditional expected value on.
        freeze (numpy.ndarray):
            Boolean values defining the conditional variables. True values
            implies that the value is conditioned on, e.g. frozen during the
            expected value calculation.
        dist (Distribution) :
            The distributions of the input used in ``poly``.

    Returns:
        (numpoly.ndpoly) :
            Same as ``poly``, but with the variables not tagged in ``frozen``
            integrated away.

    Examples:
        >>> q0, q1 = chaospy.variable(2)
        >>> poly = chaospy.polynomial([1, q0, q1, 10*q0*q1-1])
        >>> poly
        polynomial([1, q0, q1, 10*q0*q1-1])
        >>> dist = chaospy.J(chaospy.Gamma(1, 1), chaospy.Normal(0, 2))
        >>> chaospy.E_cond(poly, [1, 0], dist)
        polynomial([1.0, q0, 0.0, -1.0])
        >>> chaospy.E_cond(poly, [0, 1], dist)
        polynomial([1.0, 1.0, q1, 10.0*q1-1.0])
        >>> chaospy.E_cond(poly, [1, 1], dist)
        polynomial([1, q0, q1, 10*q0*q1-1])
        >>> chaospy.E_cond(poly, [0, 0], dist)
        polynomial([1.0, 1.0, 0.0, -1.0])

    """
    poly = numpoly.set_dimensions(poly, len(dist))
    if not poly.isconstant:
        return poly.tonumpy()
    assert not dist.stochastic_dependent, dist

    freeze = numpoly.polynomial(freeze)
    if freeze.isconstant():
        freeze = freeze.tonumpy().astype(bool)
    else:
        poly, freeze = numpoly.align_exponents(poly, freeze)
        freeze = numpy.isin(poly.keys, freeze.keys)

    # decompose into frozen and unfrozen part
    poly = numpoly.decompose(poly)
    unfrozen = poly(**{("q%d" % idx): 1
                       for idx, keep in enumerate(freeze) if keep})
    frozen = poly(**{("q%d" % idx): 1
                     for idx, keep in enumerate(freeze) if not keep})

    # if no unfrozen, poly will return numpy.ndarray instead of numpoly.ndpoly
    if not isinstance(unfrozen, numpoly.ndpoly):
        return numpoly.sum(frozen, 0)

    # Remove frozen coefficients, such that poly == sum(frozen*unfrozen) holds
    for key in unfrozen.keys:
        unfrozen[key] = unfrozen[key] != 0

    return numpoly.sum(frozen * expected.E(unfrozen, dist), 0)