Skip to content

Commit

Permalink
Removing all usage of 1D arrays.
Browse files Browse the repository at this point in the history
1D arrays were mostly created via `.flatten()`, but
lots of code (and tests and docs) relied on those
outputs, so there was a large cascade of changes
needed.
  • Loading branch information
dhermes committed Dec 30, 2016
1 parent 90e22b8 commit b5e5b32
Show file tree
Hide file tree
Showing 14 changed files with 106 additions and 95 deletions.
22 changes: 11 additions & 11 deletions bezier/_curve_helpers.py
Expand Up @@ -284,13 +284,13 @@ def evaluate_hodograph(nodes, degree, s):
is to be evaluated.
Returns:
numpy.ndarray: The point on the Hodograph curve (as a one
dimensional NumPy array).
numpy.ndarray: The point on the Hodograph curve (as a two
dimensional NumPy array with a single row).
"""
first_deriv = nodes[1:, :] - nodes[:-1, :]
# NOTE: Taking the derivative drops the degree by 1.
return degree * evaluate_multi(
first_deriv, degree - 1, np.array([s])).flatten()
first_deriv, degree - 1, np.array([s]))


def get_curvature(nodes, degree, tangent_vec, s):
Expand Down Expand Up @@ -329,7 +329,7 @@ def hodograph(nodes, s):
>>> s = 0.5
>>> tangent_vec = hodograph(nodes, s)
>>> tangent_vec
array([-1., 0.])
array([[-1., 0.]])
>>> curvature = get_curvature(nodes, 4, tangent_vec, s)
>>> curvature
-12.0
Expand Down Expand Up @@ -360,10 +360,8 @@ def hodograph(nodes, s):
second_deriv = first_deriv[1:, :] - first_deriv[:-1, :]
concavity = degree * (degree - 1) * evaluate_multi(
second_deriv, degree - 2, np.array([s]))
# NOTE: This assumes ``tangent_vec`` was ``flatten()``-ed, but
# we intentionally don't flatten ``concavity``.
curvature = _helpers.cross_product(
np.array([tangent_vec]), concavity)

curvature = _helpers.cross_product(tangent_vec, concavity)
curvature /= np.linalg.norm(tangent_vec, ord=2)**3
return curvature

Expand Down Expand Up @@ -529,9 +527,12 @@ def newton_refine(curve, point, s):
float: The updated value :math:`s + \Delta s`.
"""
nodes = curve._nodes # pylint: disable=protected-access
pt_delta = point.flatten() - curve.evaluate(s)
pt_delta = point - curve.evaluate(s)
derivative = evaluate_hodograph(nodes, curve.degree, s)
delta_s = np.dot(pt_delta, derivative) / np.dot(derivative, derivative)
# Each array is 1x2 (i.e. a row vector).
delta_s = pt_delta.dot(derivative.T) / derivative.dot(derivative.T)
# Unpack 1x1 array into a scalar (and assert size).
(delta_s,), = delta_s
return s + delta_s


Expand All @@ -556,7 +557,6 @@ def locate_point(curve, point):
Optional[float]: The parameter value (:math:`s`) corresponding
to ``point`` or :data:`None` if the point is not on the ``curve``.
"""
point = point.flatten()
candidates = [curve]
for _ in six.moves.xrange(_MAX_LOCATE_SUBDIVISIONS + 1):
next_candidates = []
Expand Down
12 changes: 6 additions & 6 deletions bezier/_intersection_helpers.py
Expand Up @@ -546,10 +546,10 @@ def newton_refine(s, curve1, t, curve2):
nodes2, curve2.degree, t)

# Solve the system (via the transposes, since, as above, the roles
# of columns and rows are reversed). Note that since ``func_val``
# is a 1D object, then ``delta_s``, ``delta_t`` can be unpacked
# without worry of them being vectors.
delta_s, delta_t = np.linalg.solve(jac_mat.T, func_val.T)
# of columns and rows are reversed).
result = np.linalg.solve(jac_mat.T, func_val.T)
# Convert to row-vector and unpack (also makes assertion on shape).
(delta_s, delta_t), = result.T
return s + delta_s, t + delta_t


Expand Down Expand Up @@ -1010,9 +1010,9 @@ def _tangent_bbox_intersection(left, right, intersections):
right_nodes = right._nodes
# pylint: enable=protected-access
for i, s in ((0, 0.0), (-1, 1.0)):
node_left = left_nodes[i, :]
node_left = left_nodes[[i], :]
for j, t in ((0, 0.0), (-1, 1.0)):
node_right = right_nodes[j, :]
node_right = right_nodes[[j], :]
if _helpers.vector_close(node_left, node_right):
orig_s = (1 - s) * left.start + s * left.end
orig_t = (1 - t) * right.start + t * right.end
Expand Down
56 changes: 26 additions & 30 deletions bezier/_surface_helpers.py
Expand Up @@ -763,7 +763,7 @@ def newton_refine(surface, x_val, y_val, s, t):
... ]))
>>> surface.is_valid
True
>>> x_val, y_val = surface.evaluate_cartesian(0.25, 0.5)
>>> (x_val, y_val), = surface.evaluate_cartesian(0.25, 0.5)
>>> x_val, y_val
(1.25, 1.25)
>>> s, t = 0.5, 0.25
Expand Down Expand Up @@ -792,7 +792,7 @@ def newton_refine(surface, x_val, y_val, s, t):
Returns:
Tuple[float, float]: The refined :math:`s` and :math:`t` values.
"""
surf_x, surf_y = surface.evaluate_cartesian(s, t)
(surf_x, surf_y), = surface.evaluate_cartesian(s, t)
if surf_x == x_val and surf_y == y_val:
# No refinement is needed.
return s, t
Expand Down Expand Up @@ -920,13 +920,13 @@ def curvature(curve, s):
... ]))
>>> s, t = 0.25, 0.5
>>> curve1.evaluate(s) == curve2.evaluate(t)
array([ True, True], dtype=bool)
array([[ True, True]], dtype=bool)
>>> tangent1 = hodograph(curve1, s)
>>> tangent1
array([ 1.25, 0.75])
array([[ 1.25, 0.75]])
>>> tangent2 = hodograph(curve2, t)
>>> tangent2
array([ 2. , 0.5])
array([[ 2. , 0.5]])
>>> intersection = Intersection(curve1, s, curve2, t)
>>> classify_intersection(intersection)
<IntersectionClassification.first: 'first'>
Expand Down Expand Up @@ -979,7 +979,7 @@ def curvature(curve, s):
... ]))
>>> s, t = 0.5, 0.5
>>> curve1.evaluate(s) == curve2.evaluate(t)
array([ True, True], dtype=bool)
array([[ True, True]], dtype=bool)
>>> intersection = Intersection(curve1, s, curve2, t)
>>> classify_intersection(intersection)
<IntersectionClassification.tangent_second: 'tangent_second'>
Expand Down Expand Up @@ -1013,7 +1013,7 @@ def curvature(curve, s):
... ]))
>>> s, t = 0.5, 0.5
>>> curve1.evaluate(s) == curve2.evaluate(t)
array([ True, True], dtype=bool)
array([[ True, True]], dtype=bool)
>>> intersection = Intersection(curve1, s, curve2, t)
>>> classify_intersection(intersection)
<IntersectionClassification.tangent_first: 'tangent_first'>
Expand Down Expand Up @@ -1045,7 +1045,7 @@ def curvature(curve, s):
... ]))
>>> s, t = 0.5, 0.5
>>> curve1.evaluate(s) == curve2.evaluate(t)
array([ True, True], dtype=bool)
array([[ True, True]], dtype=bool)
>>> intersection = Intersection(curve1, s, curve2, t)
>>> classify_intersection(intersection)
<IntersectionClassification.opposed: 'opposed'>
Expand Down Expand Up @@ -1078,7 +1078,7 @@ def curvature(curve, s):
... ]))
>>> s, t = 0.5, 0.5
>>> curve1.evaluate(s) == curve2.evaluate(t)
array([ True, True], dtype=bool)
array([[ True, True]], dtype=bool)
>>> intersection = Intersection(curve1, s, curve2, t)
>>> classify_intersection(intersection)
Traceback (most recent call last):
Expand Down Expand Up @@ -1112,11 +1112,11 @@ def curvature(curve, s):
... ]))
>>> s, t = 0.5, 0.5
>>> curve1.evaluate(s) == curve2.evaluate(t)
array([ True, True], dtype=bool)
array([[ True, True]], dtype=bool)
>>> hodograph(curve1, s)
array([-0.5, 0. ])
array([[-0.5, 0. ]])
>>> hodograph(curve2, t)
array([-1., 0.])
array([[-1., 0.]])
>>> curvature(curve1, s)
-2.0
>>> curvature(curve2, t)
Expand Down Expand Up @@ -1153,7 +1153,7 @@ def curvature(curve, s):
... ]))
>>> s, t = 1.0, 0.375
>>> curve1a.evaluate(s) == curve2.evaluate(t)
array([ True, True], dtype=bool)
array([[ True, True]], dtype=bool)
>>> intersection = Intersection(curve1a, s, curve2, t)
>>> classify_intersection(intersection)
Traceback (most recent call last):
Expand All @@ -1167,7 +1167,7 @@ def curvature(curve, s):
... [0.0, 2.5 ],
... ]))
>>> curve1b.evaluate(0.0) == curve2.evaluate(t)
array([ True, True], dtype=bool)
array([[ True, True]], dtype=bool)
>>> intersection = Intersection(curve1b, 0.0, curve2, t)
>>> classify_intersection(intersection)
<IntersectionClassification.first: 'first'>
Expand Down Expand Up @@ -1207,7 +1207,7 @@ def curvature(curve, s):
>>> curve2, _, _ = surface2.edges
>>> s, t = 0.5, 0.0
>>> curve1.evaluate(s) == curve2.evaluate(t)
array([ True, True], dtype=bool)
array([[ True, True]], dtype=bool)
>>> intersection = Intersection(curve1, s, curve2, t)
>>> classify_intersection(intersection)
<IntersectionClassification.ignored_corner: 'ignored_corner'>
Expand Down Expand Up @@ -1259,8 +1259,7 @@ def curvature(curve, s):

# Take the cross product of tangent vectors to determine which one
# is more "to the left".
cross_prod = _helpers.cross_product(
np.array([tangent1]), np.array([tangent2]))
cross_prod = _helpers.cross_product(tangent1, tangent2)
if cross_prod < 0:
return IntersectionClassification.first
elif cross_prod > 0:
Expand Down Expand Up @@ -1291,7 +1290,10 @@ def _classify_tangent_intersection(intersection, tangent1, tangent2):
NotImplementedError: If the curves are tangent at the intersection
and have the same curvature.
"""
dot_prod = tangent1.dot(tangent2)
# Each array is 1x2 (i.e. a row vector).
dot_prod = tangent1.dot(tangent2.T)
# Unpack 1x1 array into a scalar (and assert size).
(dot_prod,), = dot_prod
# NOTE: When computing curvatures we assume that we don't have lines
# here, because lines that are tangent at an intersection are
# parallel and we don't handle that case.
Expand Down Expand Up @@ -1374,8 +1376,7 @@ def _ignored_edge_corner(edge_tangent, corner_tangent, corner_previous_edge):
Returns:
bool: Indicates if the corner intersection should be ignored.
"""
cross_prod = _helpers.cross_product(
np.array([edge_tangent]), np.array([corner_tangent]))
cross_prod = _helpers.cross_product(edge_tangent, corner_tangent)
# A negative cross product indicates that ``edge_tangent`` is
# "to the left" of ``corner_tangent`` (due to right-hand rule).
if cross_prod > 0.0:
Expand All @@ -1387,8 +1388,7 @@ def _ignored_edge_corner(edge_tangent, corner_tangent, corner_previous_edge):
corner_previous_edge.degree, 1.0)
# Change the direction of the "in" tangent so that it points "out".
alt_corner_tangent *= -1.0
cross_prod = _helpers.cross_product(
np.array([edge_tangent]), np.array([alt_corner_tangent]))
cross_prod = _helpers.cross_product(edge_tangent, alt_corner_tangent)
return cross_prod <= 0.0


Expand Down Expand Up @@ -1419,17 +1419,15 @@ def _ignored_double_corner(intersection, tangent_s, tangent_t):
prev_edge.degree, 1.0)

# First check if ``tangent_t`` is interior to the ``s`` surface.
cross_prod1 = _helpers.cross_product(
np.array([tangent_s]), np.array([tangent_t]))
cross_prod1 = _helpers.cross_product(tangent_s, tangent_t)
# A positive cross product indicates that ``tangent_t`` is
# interior to ``tangent_s``. Similar for ``alt_tangent_s``.
# If ``tangent_t`` is interior to both, then the surfaces
# do more than just "kiss" at the corner, so the corner should
# not be ignored.
if cross_prod1 >= 0.0:
# Only compute ``cross_prod2`` if we need to.
cross_prod2 = _helpers.cross_product(
np.array([alt_tangent_s]), np.array([tangent_t]))
cross_prod2 = _helpers.cross_product(alt_tangent_s, tangent_t)
if cross_prod2 >= 0.0:
return False

Expand All @@ -1442,12 +1440,10 @@ def _ignored_double_corner(intersection, tangent_s, tangent_t):
# Change the direction of the "in" tangent so that it points "out".
alt_tangent_t *= -1.0

cross_prod3 = _helpers.cross_product(
np.array([tangent_s]), np.array([alt_tangent_t]))
cross_prod3 = _helpers.cross_product(tangent_s, alt_tangent_t)
if cross_prod3 >= 0.0:
# Only compute ``cross_prod4`` if we need to.
cross_prod4 = _helpers.cross_product(
np.array([alt_tangent_s]), np.array([alt_tangent_t]))
cross_prod4 = _helpers.cross_product(alt_tangent_s, alt_tangent_t)
if cross_prod4 >= 0.0:
return False

Expand Down
13 changes: 6 additions & 7 deletions bezier/curve.py
Expand Up @@ -221,15 +221,15 @@ def root(self):
>>> right.root is curve
True
>>> right.evaluate(0.0) == curve.evaluate(0.5)
array([ True, True], dtype=bool)
array([[ True, True]], dtype=bool)
>>>
>>> mid_left, _ = right.subdivide()
>>> mid_left
<Curve (degree=2, dimension=2, start=0.5, end=0.75)>
>>> mid_left.root is curve
True
>>> mid_left.evaluate(1.0) == curve.evaluate(0.75)
array([ True, True], dtype=bool)
array([[ True, True]], dtype=bool)
"""
return self._root

Expand Down Expand Up @@ -306,7 +306,7 @@ def evaluate(self, s):
... ])
>>> curve = bezier.Curve(nodes)
>>> curve.evaluate(0.75)
array([ 0.796875, 0.46875 ])
array([[ 0.796875, 0.46875 ]])
.. testcleanup:: curve-eval
Expand All @@ -317,11 +317,10 @@ def evaluate(self, s):
s (float): Parameter along the curve.
Returns:
numpy.ndarray: The point on the curve (as a one dimensional
NumPy array).
numpy.ndarray: The point on the curve (as a two dimensional
NumPy array with a single row).
"""
result = self.evaluate_multi(np.array([s]))
return result.flatten()
return self.evaluate_multi(np.array([s]))

def evaluate_multi(self, s_vals):
r"""Evaluate :math:`B(s)` for multiple points along the curve.
Expand Down
10 changes: 5 additions & 5 deletions bezier/surface.py
Expand Up @@ -430,7 +430,7 @@ def evaluate_barycentric(self, lambda1, lambda2, lambda3):
>>> surface = bezier.Surface(nodes)
>>> point = surface.evaluate_barycentric(0.125, 0.125, 0.75)
>>> point
array([ 0.265625 , 0.73046875])
array([[ 0.265625 , 0.73046875]])
.. testcleanup:: surface-barycentric
Expand Down Expand Up @@ -462,8 +462,8 @@ def evaluate_barycentric(self, lambda1, lambda2, lambda3):
lambda3 (float): Parameter along the reference triangle.
Returns:
numpy.ndarray: The point on the surface (as a one dimensional
NumPy array).
numpy.ndarray: The point on the surface (as a two dimensional
NumPy array with a single row).
Raises:
ValueError: If the weights are not valid barycentric
Expand Down Expand Up @@ -513,9 +513,9 @@ def evaluate_barycentric(self, lambda1, lambda2, lambda3):
for reduced_deg in six.moves.xrange(self.degree, 0, -1):
result = _surface_helpers.de_casteljau_one_round(
result, reduced_deg, lambda1, lambda2, lambda3)
return result.flatten()
return result

return weights.dot(self._nodes).flatten() # pylint: disable=no-member
return weights.dot(self._nodes) # pylint: disable=no-member

def evaluate_cartesian(self, s, t):
r"""Compute a point on the surface.
Expand Down

0 comments on commit b5e5b32

Please sign in to comment.