예제 #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 = _curve_helpers.evaluate_multi(self.nodes1, s_vals)
        b1_ds = _curve_helpers.evaluate_multi(self.first_deriv1, s_vals)
        t_vals = np.asfortranarray([t])
        b2_t = _curve_helpers.evaluate_multi(self.nodes2, t_vals)
        b2_dt = _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, :] = _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] = _helpers.cross_product(
                    _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] = _helpers.cross_product(
                    b1_ds[:, 0],
                    _curve_helpers.evaluate_multi(self.second_deriv2, t_vals)[
                        :, 0
                    ],
                )
            modified_lhs = _helpers.matrix_product(jacobian.T, jacobian)
            modified_rhs = _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 surface) 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 = _helpers.matrix_product(nodes, _LINEAR_SUBDIVIDE_LEFT)
        right_nodes = _helpers.matrix_product(nodes, _LINEAR_SUBDIVIDE_RIGHT)
    elif num_nodes == 3:
        left_nodes = _helpers.matrix_product(nodes, _QUADRATIC_SUBDIVIDE_LEFT)
        right_nodes = _helpers.matrix_product(
            nodes, _QUADRATIC_SUBDIVIDE_RIGHT
        )
    elif num_nodes == 4:
        left_nodes = _helpers.matrix_product(nodes, _CUBIC_SUBDIVIDE_LEFT)
        right_nodes = _helpers.matrix_product(nodes, _CUBIC_SUBDIVIDE_RIGHT)
    else:
        left_mat, right_mat = make_subdivision_matrices(num_nodes - 1)
        left_nodes = _helpers.matrix_product(nodes, left_mat)
        right_nodes = _helpers.matrix_product(nodes, right_mat)
    return left_nodes, right_nodes
예제 #3
0
def reduce_pseudo_inverse(nodes, degree):
    """Performs degree-reduction for a B |eacute| zier curve.

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

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

    Returns:
        numpy.ndarray: The reduced nodes.

    Raises:
        NotImplementedError: If the degree is not 1, 2, 3 or 4.
    """
    if degree == 1:
        reduction = _REDUCTION0
        denom = _REDUCTION_DENOM0
    elif degree == 2:
        reduction = _REDUCTION1
        denom = _REDUCTION_DENOM1
    elif degree == 3:
        reduction = _REDUCTION2
        denom = _REDUCTION_DENOM2
    elif degree == 4:
        reduction = _REDUCTION3
        denom = _REDUCTION_DENOM3
    else:
        raise NotImplementedError(degree)

    result = _helpers.matrix_product(reduction, nodes)
    result /= denom
    return result
예제 #4
0
    def _actually_inverse_helper(self, degree):
        from bezier import _curve_helpers
        from bezier 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 = _curve_helpers.elevate_nodes(id_mat)
        result = _helpers.matrix_product(elevation_mat, reduction_mat)
        return result, id_mat
예제 #5
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 _helpers.UnsupportedDegree(
            num_nodes - 1, supported=(0, 1, 2, 3, 4)
        )

    projected = _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
예제 #6
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 = _helpers.eye(num_companion)
    evaluated = coeffs[-1] * id_mat
    for index in six.moves.xrange(num_coeffs - 2, -1, -1):
        coeff = coeffs[index]
        evaluated = (_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)
예제 #7
0
def _maybe_reduce(nodes):
    r"""Reduce nodes in a curve if they are degree-elevated.

    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
    elevation matrix :math:`E` (from one degree lower) and forming
    :math:`E \left(E^T E\right)^{-1} E^T`.

    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:
        NotImplementedError: 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 NotImplementedError(num_nodes)

    projected = _helpers.matrix_product(projection, nodes) / denom
    relative_err = _projection_error(nodes, projected)
    if relative_err < _REDUCE_THRESHOLD:
        return True, reduce_pseudo_inverse(nodes, num_nodes - 1)
    else:
        return False, nodes
예제 #8
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 _helpers.UnsupportedDegree(num_nodes - 1, supported=(1, 2, 3, 4))

    result = _helpers.matrix_product(nodes, reduction)
    result /= denom
    return result
예제 #9
0
    def subdivide(self):
        r"""Split the curve :math:`B(s)` into a left and right half.

        Takes the interval :math:`\left[0, 1\right]` and splits the curve into
        :math:`B_1 = B\left(\left[0, \frac{1}{2}\right]\right)` and
        :math:`B_2 = B\left(\left[\frac{1}{2}, 1\right]\right)`. In
        order to do this, also reparameterizes the curve, hence the resulting
        left and right halves have new nodes.

        .. image:: ../images/curve_subdivide.png
           :align: center

        .. doctest:: curve-subdivide
           :options: +NORMALIZE_WHITESPACE

           >>> nodes = np.asfortranarray([
           ...     [0.0 , 0.0],
           ...     [1.25, 3.0],
           ...     [2.0 , 1.0],
           ... ])
           >>> curve = bezier.Curve(nodes, degree=2)
           >>> left, right = curve.subdivide()
           >>> left
           <Curve (degree=2, dimension=2, start=0, end=0.5)>
           >>> left.nodes
           array([[ 0.   , 0.   ],
                  [ 0.625, 1.5  ],
                  [ 1.125, 1.75 ]])
           >>> right
           <Curve (degree=2, dimension=2, start=0.5, end=1)>
           >>> right.nodes
           array([[ 1.125, 1.75 ],
                  [ 1.625, 2.   ],
                  [ 2.   , 1.   ]])

        .. testcleanup:: curve-subdivide

           import make_images
           make_images.curve_subdivide(curve, left, right)

        Returns:
            Tuple[Curve, Curve]: The left and right sub-curves.
        """
        if self._degree == 1:
            left_nodes = _helpers.matrix_product(_LINEAR_SUBDIVIDE_LEFT,
                                                 self._nodes)
            right_nodes = _helpers.matrix_product(_LINEAR_SUBDIVIDE_RIGHT,
                                                  self._nodes)
        elif self._degree == 2:
            left_nodes = _helpers.matrix_product(_QUADRATIC_SUBDIVIDE_LEFT,
                                                 self._nodes)
            right_nodes = _helpers.matrix_product(_QUADRATIC_SUBDIVIDE_RIGHT,
                                                  self._nodes)
        elif self._degree == 3:
            left_nodes = _helpers.matrix_product(_CUBIC_SUBDIVIDE_LEFT,
                                                 self._nodes)
            right_nodes = _helpers.matrix_product(_CUBIC_SUBDIVIDE_RIGHT,
                                                  self._nodes)
        else:
            left_mat, right_mat = _curve_helpers.make_subdivision_matrices(
                self._degree)
            left_nodes = _helpers.matrix_product(left_mat, self._nodes)
            right_nodes = _helpers.matrix_product(right_mat, self._nodes)

        midpoint = 0.5 * (self._start + self._end)
        left = Curve(left_nodes,
                     self._degree,
                     start=self._start,
                     end=midpoint,
                     root=self._root,
                     _copy=False)
        right = Curve(right_nodes,
                      self._degree,
                      start=midpoint,
                      end=self._end,
                      root=self._root,
                      _copy=False)
        return left, right
예제 #10
0
    def _call_function_under_test(mat1, mat2):
        from bezier import _helpers

        return _helpers.matrix_product(mat1, mat2)
예제 #11
0
    def subdivide(self):
        r"""Split the surface into four sub-surfaces.

        Does so by taking the unit triangle (i.e. the domain
        of the surface) and splitting it into four sub-triangles

        .. image:: ../images/surface_subdivide1.png
           :align: center

        Then the surface is re-parameterized via the map to / from the
        given sub-triangles and the unit triangle.

        For example, when a degree two surface is subdivided:

        .. image:: ../images/surface_subdivide2.png
           :align: center

        .. doctest:: surface-subdivide
           :options: +NORMALIZE_WHITESPACE

           >>> nodes = np.asfortranarray([
           ...     [-1.0 , 0.0 ],
           ...     [ 0.5 , 0.5 ],
           ...     [ 2.0 , 0.0 ],
           ...     [ 0.25, 1.75],
           ...     [ 2.0 , 3.0 ],
           ...     [ 0.0 , 4.0 ],
           ... ])
           >>> surface = bezier.Surface(nodes, degree=2)
           >>> _, sub_surface_b, _, _ = surface.subdivide()
           >>> sub_surface_b
           <Surface (degree=2, dimension=2, base=(0.5, 0.5), width=-0.5)>
           >>> sub_surface_b.nodes
           array([[ 1.5   , 2.5   ],
                  [ 0.6875, 2.3125],
                  [-0.125 , 1.875 ],
                  [ 1.1875, 1.3125],
                  [ 0.4375, 1.3125],
                  [ 0.5   , 0.25  ]])

        .. testcleanup:: surface-subdivide

           import make_images
           make_images.surface_subdivide1()
           make_images.surface_subdivide2(surface, sub_surface_b)

        Returns:
            Tuple[Surface, Surface, Surface, Surface]: The lower left, central,
            lower right and upper left sub-surfaces (in that order).
        """
        if self._degree == 1:
            nodes_a = _helpers.matrix_product(
                _surface_helpers.LINEAR_SUBDIVIDE_A, self._nodes)
            nodes_b = _helpers.matrix_product(
                _surface_helpers.LINEAR_SUBDIVIDE_B, self._nodes)
            nodes_c = _helpers.matrix_product(
                _surface_helpers.LINEAR_SUBDIVIDE_C, self._nodes)
            nodes_d = _helpers.matrix_product(
                _surface_helpers.LINEAR_SUBDIVIDE_D, self._nodes)
        elif self._degree == 2:
            nodes_a = _helpers.matrix_product(
                _surface_helpers.QUADRATIC_SUBDIVIDE_A, self._nodes)
            nodes_b = _helpers.matrix_product(
                _surface_helpers.QUADRATIC_SUBDIVIDE_B, self._nodes)
            nodes_c = _helpers.matrix_product(
                _surface_helpers.QUADRATIC_SUBDIVIDE_C, self._nodes)
            nodes_d = _helpers.matrix_product(
                _surface_helpers.QUADRATIC_SUBDIVIDE_D, self._nodes)
        elif self._degree == 3:
            nodes_a = _helpers.matrix_product(
                _surface_helpers.CUBIC_SUBDIVIDE_A, self._nodes)
            nodes_b = _helpers.matrix_product(
                _surface_helpers.CUBIC_SUBDIVIDE_B, self._nodes)
            nodes_c = _helpers.matrix_product(
                _surface_helpers.CUBIC_SUBDIVIDE_C, self._nodes)
            nodes_d = _helpers.matrix_product(
                _surface_helpers.CUBIC_SUBDIVIDE_D, self._nodes)
        elif self._degree == 4:
            nodes_a = _helpers.matrix_product(
                _surface_helpers.QUARTIC_SUBDIVIDE_A, self._nodes)
            nodes_b = _helpers.matrix_product(
                _surface_helpers.QUARTIC_SUBDIVIDE_B, self._nodes)
            nodes_c = _helpers.matrix_product(
                _surface_helpers.QUARTIC_SUBDIVIDE_C, self._nodes)
            nodes_d = _helpers.matrix_product(
                _surface_helpers.QUARTIC_SUBDIVIDE_D, self._nodes)
        else:
            nodes_a = _surface_helpers.specialize_surface(
                self._nodes, self._degree,
                (1.0, 0.0, 0.0), (0.5, 0.5, 0.0), (0.5, 0.0, 0.5))
            nodes_b = _surface_helpers.specialize_surface(
                self._nodes, self._degree,
                (0.0, 0.5, 0.5), (0.5, 0.0, 0.5), (0.5, 0.5, 0.0))
            nodes_c = _surface_helpers.specialize_surface(
                self._nodes, self._degree,
                (0.5, 0.5, 0.0), (0.0, 1.0, 0.0), (0.0, 0.5, 0.5))
            nodes_d = _surface_helpers.specialize_surface(
                self._nodes, self._degree,
                (0.5, 0.0, 0.5), (0.0, 0.5, 0.5), (0.0, 0.0, 1.0))

        half_width = 0.5 * self._width
        shifted_x = self._base_x + half_width
        shifted_y = self._base_y + half_width
        return (
            Surface(nodes_a, self._degree, base_x=self._base_x,
                    base_y=self._base_y, width=half_width, _copy=False),
            Surface(nodes_b, self._degree, base_x=shifted_x, base_y=shifted_y,
                    width=-half_width, _copy=False),
            Surface(nodes_c, self._degree, base_x=shifted_x,
                    base_y=self._base_y, width=half_width, _copy=False),
            Surface(nodes_d, self._degree, base_x=self._base_x,
                    base_y=shifted_y, width=half_width, _copy=False),
        )