Ejemplo n.º 1
0
def maybe_reduce(nodes):
    r"""Reduce nodes in a curve if they are degree-elevated.

    .. note::

        This is a helper for :func:`.full_reduce`. Hence there is no
        corresponding Fortran speedup.

    We check if the nodes are degree-elevated by projecting onto the
    space of degree-elevated curves of the same degree, then comparing
    to the projection. We form the projection by taking the corresponding
    (right) elevation matrix :math:`E` (from one degree lower) and forming
    :math:`E^T \left(E E^T\right)^{-1} E`.

    Args:
        nodes (numpy.ndarray): The nodes in the curve.

    Returns:
        Tuple[bool, numpy.ndarray]: Pair of values. The first indicates
        if the ``nodes`` were reduced. The second is the resulting nodes,
        either the reduced ones or the original passed in.

    Raises:
        .UnsupportedDegree: If the curve is degree 5 or higher.
    """
    _, num_nodes = nodes.shape
    if num_nodes < 2:
        return False, nodes

    elif num_nodes == 2:
        projection = _PROJECTION0
        denom = _PROJ_DENOM0
    elif num_nodes == 3:
        projection = _PROJECTION1
        denom = _PROJ_DENOM1
    elif num_nodes == 4:
        projection = _PROJECTION2
        denom = _PROJ_DENOM2
    elif num_nodes == 5:
        projection = _PROJECTION3
        denom = _PROJ_DENOM3
    else:
        raise _py_helpers.UnsupportedDegree(
            num_nodes - 1, supported=(0, 1, 2, 3, 4)
        )

    projected = _py_helpers.matrix_product(nodes, projection) / denom
    relative_err = projection_error(nodes, projected)
    if relative_err < _REDUCE_THRESHOLD:
        return True, reduce_pseudo_inverse(nodes)

    else:
        return False, nodes
Ejemplo n.º 2
0
def poly_to_power_basis(bezier_coeffs):
    """Convert a B |eacute| zier curve to polynomial in power basis.

    .. note::

       This assumes, but does not verify, that the "B |eacute| zier
       degree" matches the true degree of the curve. Callers can
       guarantee this by calling :func:`.full_reduce`.

    Args:
        bezier_coeffs (numpy.ndarray): A 1D array of coefficients in
            the Bernstein basis.

    Returns:
        numpy.ndarray: 1D array of coefficients in monomial basis.

    Raises:
        UnsupportedDegree: If the degree of the curve is not among
            0, 1, 2 or 3.
    """
    (num_coeffs, ) = bezier_coeffs.shape
    if num_coeffs == 1:
        return bezier_coeffs

    elif num_coeffs == 2:
        # C0 (1 - s) + C1 s = C0 + (C1 - C0) s
        coeff0, coeff1 = bezier_coeffs
        return np.asfortranarray([coeff0, coeff1 - coeff0])

    elif num_coeffs == 3:
        #   C0 (1 - s)^2 + C1 2 (1 - s) s + C2 s^2
        # = C0 + 2(C1 - C0) s + (C2 - 2 C1 + C0) s^2
        coeff0, coeff1, coeff2 = bezier_coeffs
        return np.asfortranarray(
            [coeff0, 2.0 * (coeff1 - coeff0), coeff2 - 2.0 * coeff1 + coeff0])

    elif num_coeffs == 4:
        #   C0 (1 - s)^3 + C1 3 (1 - s)^2 + C2 3 (1 - s) s^2 + C3 s^3
        # = C0 + 3(C1 - C0) s + 3(C2 - 2 C1 + C0) s^2 +
        #   (C3 - 3 C2 + 3 C1 - C0) s^3
        coeff0, coeff1, coeff2, coeff3 = bezier_coeffs
        return np.asfortranarray([
            coeff0,
            3.0 * (coeff1 - coeff0),
            3.0 * (coeff2 - 2.0 * coeff1 + coeff0),
            coeff3 - 3.0 * coeff2 + 3.0 * coeff1 - coeff0,
        ])

    else:
        raise _py_helpers.UnsupportedDegree(num_coeffs - 1,
                                            supported=(0, 1, 2, 3))
Ejemplo n.º 3
0
def reduce_pseudo_inverse(nodes):
    """Performs degree-reduction for a B |eacute| zier curve.

    Does so by using the pseudo-inverse of the degree elevation
    operator (which is overdetermined).

    .. note::

       There is also a Fortran implementation of this function, which
       will be used if it can be built.

    Args:
        nodes (numpy.ndarray): The nodes in the curve.

    Returns:
        numpy.ndarray: The reduced nodes.

    Raises:
        .UnsupportedDegree: If the degree is not 1, 2, 3 or 4.
    """
    _, num_nodes = np.shape(nodes)
    if num_nodes == 2:
        reduction = _REDUCTION0
        denom = _REDUCTION_DENOM0
    elif num_nodes == 3:
        reduction = _REDUCTION1
        denom = _REDUCTION_DENOM1
    elif num_nodes == 4:
        reduction = _REDUCTION2
        denom = _REDUCTION_DENOM2
    elif num_nodes == 5:
        reduction = _REDUCTION3
        denom = _REDUCTION_DENOM3
    else:
        raise _py_helpers.UnsupportedDegree(
            num_nodes - 1, supported=(1, 2, 3, 4)
        )

    result = _py_helpers.matrix_product(nodes, reduction)
    result /= denom
    return result
Ejemplo n.º 4
0
    def _compute_valid(self):
        r"""Determines if the current triangle is "valid".

        Does this by checking if the Jacobian of the map from the
        reference triangle is everywhere positive.

        Returns:
            bool: Flag indicating if the current triangle is valid.

        Raises:
            NotImplementedError: If the triangle is in a dimension other
                than :math:`\mathbf{R}^2`.
            .UnsupportedDegree: If the degree is not 1, 2 or 3.
        """
        if self._dimension != 2:
            raise NotImplementedError("Validity check only implemented in R^2")

        poly_sign = None
        if self._degree == 1:
            # In the linear case, we are only invalid if the points
            # are collinear.
            first_deriv = self._nodes[:, 1:] - self._nodes[:, :-1]
            # pylint: disable=assignment-from-no-return
            poly_sign = _SIGN(np.linalg.det(first_deriv))
            # pylint: enable=assignment-from-no-return
        elif self._degree == 2:
            bernstein = _py_triangle_helpers.quadratic_jacobian_polynomial(
                self._nodes
            )
            poly_sign = _py_triangle_helpers.polynomial_sign(bernstein, 2)
        elif self._degree == 3:
            bernstein = _py_triangle_helpers.cubic_jacobian_polynomial(
                self._nodes
            )
            poly_sign = _py_triangle_helpers.polynomial_sign(bernstein, 4)
        else:
            raise _py_helpers.UnsupportedDegree(
                self._degree, supported=(1, 2, 3)
            )

        return poly_sign == 1
Ejemplo n.º 5
0
def evaluate(nodes, x_val, y_val):
    r"""Evaluate the implicitized bivariate polynomial containing the curve.

    Assumes the `algebraic curve`_ containing :math:`B(s, t)` is given by
    :math:`f(x, y) = 0`. This function evaluates :math:`f(x, y)`.

    .. note::

       This assumes, but doesn't check, that ``nodes`` has 2 rows.

    .. note::

       This assumes, but doesn't check, that ``nodes`` is not degree-elevated.
       If it were degree-elevated, then the Sylvester matrix will always
       have zero determinant.

    Args:
        nodes (numpy.ndarray): ``2 x N`` array of nodes in a curve.
        x_val (float): ``x``-coordinate for evaluation.
        y_val (float): ``y``-coordinate for evaluation.

    Returns:
        float: The computed value of :math:`f(x, y)`.

    Raises:
        ValueError: If the curve is a point.
        UnsupportedDegree: If the degree is not 1, 2 or 3.
    """
    _, num_nodes = nodes.shape
    if num_nodes == 1:
        raise ValueError("A point cannot be implicitized")

    if num_nodes == 2:
        # x(s) - x = (x0 - x) (1 - s) + (x1 - x) s
        # y(s) - y = (y0 - y) (1 - s) + (y1 - y) s
        # Modified Sylvester: [x0 - x, x1 - x]
        #                     [y0 - y, y1 - y]
        return (nodes[0, 0] - x_val) * (nodes[1, 1] - y_val) - (
            nodes[0, 1] - x_val) * (nodes[1, 0] - y_val)

    if num_nodes == 3:
        # x(s) - x = (x0 - x) (1 - s)^2 + 2 (x1 - x) s(1 - s) + (x2 - x) s^2
        # y(s) - y = (y0 - y) (1 - s)^2 + 2 (y1 - y) s(1 - s) + (y2 - y) s^2
        # Modified Sylvester: [x0 - x, 2(x1 - x),    x2 - x,      0] = A|B|C|0
        #                     [     0,    x0 - x, 2(x1 - x), x2 - x]   0|A|B|C
        #                     [y0 - y, 2(y1 - y),    y2 - y,      0]   D|E|F|0
        #                     [     0,    y0 - y, 2(y1 - y), y2 - y]   0|D|E|F
        val_a, val_b, val_c = nodes[0, :] - x_val
        val_b *= 2
        val_d, val_e, val_f = nodes[1, :] - y_val
        val_e *= 2
        #     [A, B, C]         [E, F, 0]
        # det [E, F, 0] = - det [A, B, C] = -E (BF - CE) + F(AF - CD)
        #     [D, E, F]         [D, E, F]
        sub1 = val_b * val_f - val_c * val_e
        sub2 = val_a * val_f - val_c * val_d
        sub_det_a = -val_e * sub1 + val_f * sub2
        #     [B, C, 0]
        # det [A, B, C] = B (BF - CE) - C (AF - CD)
        #     [D, E, F]
        sub_det_d = val_b * sub1 - val_c * sub2
        return val_a * sub_det_a + val_d * sub_det_d

    if num_nodes == 4:
        return _evaluate3(nodes, x_val, y_val)

    raise _py_helpers.UnsupportedDegree(num_nodes - 1, supported=(1, 2, 3))