コード例 #1
0
    def __call__(self, s, t):
        r"""This computes :math:`DG^T G` and :math:`DG^T DG`.

        If :math:`DG^T DG` is not full rank, this means either :math:`DG`
        was not full rank or that it was, but with a relatively high condition
        number. So, in the case that :math:`DG^T DG` is singular, the
        assumption is that the intersection has a multiplicity higher than two.

        Args:
            s (float): The parameter where we'll compute :math:`G(s, t)` and
                :math:`DG(s, t)`.
            t (float): The parameter where we'll compute :math:`G(s, t)` and
                :math:`DG(s, t)`.

        Returns:
            Tuple[Optional[numpy.ndarray], Optional[numpy.ndarray]]: Pair of

            * The LHS matrix ``DG^T DG``, a ``2 x 2`` array. If ``G == 0`` then
              this matrix won't be computed and :data:`None` will be returned.
            * The RHS vector ``DG^T G``, a ``2 x 1`` array.
        """
        s_vals = np.asfortranarray([s])
        b1_s = _py_curve_helpers.evaluate_multi(self.nodes1, s_vals)
        b1_ds = _py_curve_helpers.evaluate_multi(self.first_deriv1, s_vals)
        t_vals = np.asfortranarray([t])
        b2_t = _py_curve_helpers.evaluate_multi(self.nodes2, t_vals)
        b2_dt = _py_curve_helpers.evaluate_multi(self.first_deriv2, t_vals)
        func_val = np.empty((3, 1), order="F")
        func_val[:2, :] = b1_s - b2_t
        func_val[2, :] = _py_helpers.cross_product(b1_ds[:, 0], b2_dt[:, 0])
        if np.all(func_val == 0.0):
            return None, func_val[:2, :]

        else:
            jacobian = np.empty((3, 2), order="F")
            jacobian[:2, :1] = b1_ds
            jacobian[:2, 1:] = -b2_dt
            if self.second_deriv1.size == 0:
                jacobian[2, 0] = 0.0
            else:
                jacobian[2, 0] = _py_helpers.cross_product(
                    _py_curve_helpers.evaluate_multi(
                        self.second_deriv1, s_vals
                    )[:, 0],
                    b2_dt[:, 0],
                )
            if self.second_deriv2.size == 0:
                jacobian[2, 1] = 0.0
            else:
                jacobian[2, 1] = _py_helpers.cross_product(
                    b1_ds[:, 0],
                    _py_curve_helpers.evaluate_multi(
                        self.second_deriv2, t_vals
                    )[:, 0],
                )
            modified_lhs = _py_helpers.matrix_product(jacobian.T, jacobian)
            modified_rhs = _py_helpers.matrix_product(jacobian.T, func_val)
            return modified_lhs, modified_rhs
コード例 #2
0
def subdivide_nodes(nodes):
    """Subdivide a curve into two sub-curves.

    Does so by taking the unit interval (i.e. the domain of the curve) and
    splitting it into two sub-intervals by splitting down the middle.

    .. 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 defining a B |eacute| zier curve.

    Returns:
        Tuple[numpy.ndarray, numpy.ndarray]: The nodes for the two sub-curves.
    """
    _, num_nodes = np.shape(nodes)
    if num_nodes == 2:
        left_nodes = _py_helpers.matrix_product(nodes, _LINEAR_SUBDIVIDE_LEFT)
        right_nodes = _py_helpers.matrix_product(nodes,
                                                 _LINEAR_SUBDIVIDE_RIGHT)
    elif num_nodes == 3:
        left_nodes = _py_helpers.matrix_product(nodes,
                                                _QUADRATIC_SUBDIVIDE_LEFT)
        right_nodes = _py_helpers.matrix_product(nodes,
                                                 _QUADRATIC_SUBDIVIDE_RIGHT)
    elif num_nodes == 4:
        left_nodes = _py_helpers.matrix_product(nodes, _CUBIC_SUBDIVIDE_LEFT)
        right_nodes = _py_helpers.matrix_product(nodes, _CUBIC_SUBDIVIDE_RIGHT)
    else:
        left_mat, right_mat = make_subdivision_matrices(num_nodes - 1)
        left_nodes = _py_helpers.matrix_product(nodes, left_mat)
        right_nodes = _py_helpers.matrix_product(nodes, right_mat)
    return left_nodes, right_nodes
コード例 #3
0
    def _actually_inverse_helper(self, degree):
        from bezier import _py_curve_helpers
        from bezier.hazmat import helpers

        nodes = np.eye(degree + 2, order="F")
        reduction_mat = self._call_function_under_test(nodes)
        id_mat = np.eye(degree + 1, order="F")
        elevation_mat = _py_curve_helpers.elevate_nodes(id_mat)
        result = helpers.matrix_product(elevation_mat, reduction_mat)
        return result, id_mat
コード例 #4
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
コード例 #5
0
def _check_non_simple(coeffs):
    r"""Checks that a polynomial has no non-simple roots.

    Does so by computing the companion matrix :math:`A` of :math:`f'`
    and then evaluating the rank of :math:`B = f(A)`. If :math:`B` is not
    full rank, then :math:`f` and :math:`f'` have a shared factor.

    See: https://dx.doi.org/10.1016/0024-3795(70)90023-6

    .. note::

       This assumes that :math:`f \neq 0`.

    Args:
        coeffs (numpy.ndarray): ``d + 1``-array of coefficients in monomial /
            power basis.

    Raises:
        NotImplementedError: If the polynomial has non-simple roots.
    """
    coeffs = _strip_leading_zeros(coeffs)
    (num_coeffs, ) = coeffs.shape
    if num_coeffs < 3:
        return

    deriv_poly = polynomial.polyder(coeffs)
    companion = polynomial.polycompanion(deriv_poly)
    # NOTE: ``polycompanion()`` returns a C-contiguous array.
    companion = companion.T
    # Use Horner's method to evaluate f(companion)
    num_companion, _ = companion.shape
    id_mat = np.eye(num_companion, order="F")
    evaluated = coeffs[-1] * id_mat
    for index in range(num_coeffs - 2, -1, -1):
        coeff = coeffs[index]
        evaluated = (_py_helpers.matrix_product(evaluated, companion) +
                     coeff * id_mat)
    if num_companion == 1:
        # NOTE: This relies on the fact that coeffs is normalized.
        if np.abs(evaluated[0, 0]) > _NON_SIMPLE_THRESHOLD:
            rank = 1
        else:
            rank = 0
    else:
        rank = np.linalg.matrix_rank(evaluated)
    if rank < num_companion:
        raise NotImplementedError(_NON_SIMPLE_ERR, coeffs)
コード例 #6
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
コード例 #7
0
ファイル: test_helpers.py プロジェクト: jzhang73/bezier
    def _call_function_under_test(mat1, mat2):
        from bezier.hazmat import helpers

        return helpers.matrix_product(mat1, mat2)