def locate_point(curve, point): r"""Locate a point on a curve. Does so by recursively subdividing the curve and rejecting sub-curves with bounding boxes that don't contain the point. After the sub-curves are sufficiently small, uses Newton's method to zoom in on the parameter value. .. note:: This assumes, but does not check, that ``point`` is ``1xD``, where ``D`` is the dimension that ``curve`` is in. Args: curve (.Curve): A B |eacute| zier curve. point (numpy.ndarray): The point to locate. Returns: Optional[float]: The parameter value (:math:`s`) corresponding to ``point`` or :data:`None` if the point is not on the ``curve``. Raises: ValueError: If the standard deviation of the remaining start / end parameters among the subdivided intervals exceeds a given threshold (e.g. :math:`2^{-20}`). """ candidates = [curve] for _ in six.moves.xrange(_MAX_LOCATE_SUBDIVISIONS + 1): next_candidates = [] for candidate in candidates: nodes = candidate._nodes # pylint: disable=protected-access if _helpers.contains_nd(nodes, point): next_candidates.extend(candidate.subdivide()) candidates = next_candidates if not candidates: return None # pylint: disable=protected-access params = [ (candidate._start, candidate._end) for candidate in candidates] # pylint: enable=protected-access if np.std(params) > _LOCATE_STD_CAP: raise ValueError( 'Parameters not close enough to one another', params) s_approx = np.mean(params) return newton_refine(curve, point, s_approx)
def update_locate_candidates(candidate, next_candidates, x_val, y_val, degree): """Update list of candidate surfaces during geometric search for a point. .. note:: This is used **only** as a helper for :func:`locate_point`. Checks if the point ``(x_val, y_val)`` is contained in the ``candidate`` surface. If not, this function does nothing. If the point is contaned, the four subdivided surfaces from ``candidate`` are added to ``next_candidates``. Args: candidate (Tuple[float, float, float, numpy.ndarray]): A 4-tuple describing a surface and its centroid / width. Contains * Three times centroid ``x``-value * Three times centroid ``y``-value * "Width" of parameter space for the surface * Control points for the surface next_candidates (list): List of "candidate" sub-surfaces that may contain the point being located. x_val (float): The ``x``-coordinate being located. y_val (float): The ``y``-coordinate being located. degree (int): The degree of the surface. """ centroid_x, centroid_y, width, candidate_nodes = candidate point = np.asfortranarray([x_val, y_val]) if not _helpers.contains_nd(candidate_nodes, point): return nodes_a, nodes_b, nodes_c, nodes_d = _surface_helpers.subdivide_nodes( candidate_nodes, degree) half_width = 0.5 * width next_candidates.extend(( ( centroid_x - half_width, centroid_y - half_width, half_width, nodes_a, ), (centroid_x, centroid_y, -half_width, nodes_b), (centroid_x + width, centroid_y - half_width, half_width, nodes_c), (centroid_x - half_width, centroid_y + width, half_width, nodes_d), ))
def _locate_point(nodes, point): r"""Locate a point on a curve. Does so by recursively subdividing the curve and rejecting sub-curves with bounding boxes that don't contain the point. After the sub-curves are sufficiently small, uses Newton's method to zoom in on the parameter value. .. note:: This assumes, but does not check, that ``point`` is ``D x 1``, where ``D`` is the dimension that ``curve`` is in. .. 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. point (numpy.ndarray): The point to locate. Returns: Optional[float]: The parameter value (:math:`s`) corresponding to ``point`` or :data:`None` if the point is not on the ``curve``. Raises: ValueError: If the standard deviation of the remaining start / end parameters among the subdivided intervals exceeds a given threshold (e.g. :math:`2^{-20}`). """ candidates = [(0.0, 1.0, nodes)] for _ in six.moves.xrange(_MAX_LOCATE_SUBDIVISIONS + 1): next_candidates = [] for start, end, candidate in candidates: if _helpers.contains_nd(candidate, point.ravel(order="F")): midpoint = 0.5 * (start + end) left, right = subdivide_nodes(candidate) next_candidates.extend( ((start, midpoint, left), (midpoint, end, right)) ) candidates = next_candidates if not candidates: return None params = [(start, end) for start, end, _ in candidates] if np.std(params) > _LOCATE_STD_CAP: raise ValueError("Parameters not close enough to one another", params) s_approx = np.mean(params) s_approx = newton_refine(nodes, point, s_approx) # NOTE: Since ``np.mean(params)`` must be in ``[0, 1]`` it's # "safe" to push the Newton-refined value back into the unit # interval. if s_approx < 0.0: return 0.0 elif s_approx > 1.0: return 1.0 else: return s_approx
def _call_function_under_test(nodes, point): from bezier import _helpers return _helpers.contains_nd(nodes, point)