bezier.hazmat.curve_helpers module

Pure Python implementations of helper methods for Bézier curves.

bezier.hazmat.curve_helpers.make_subdivision_matrices(degree)

Make the matrix used to subdivide a curve.

Note

This is a helper for subdivide_nodes(). It does not have a Fortran speedup because it is only used by a function which has a Fortran speedup.

Parameters

degree (int) – The degree of the curve.

Returns

The matrices used to convert the nodes into left and right nodes, respectively.

Return type

Tuple [ numpy.ndarray, numpy.ndarray ]

bezier.hazmat.curve_helpers.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.

Parameters

nodes (numpy.ndarray) – The nodes defining a Bézier curve.

Returns

The nodes for the two sub-curves.

Return type

Tuple [ numpy.ndarray, numpy.ndarray ]

bezier.hazmat.curve_helpers.evaluate_multi(nodes, s_vals)

Computes multiple points along a curve.

Does so via a modified Horner’s method for each value in s_vals rather than using the de Casteljau algorithm.

Note

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

Parameters
Returns

The evaluated points on the curve as a two dimensional NumPy array, with the columns corresponding to each s value and the rows to the dimension.

Return type

numpy.ndarray

bezier.hazmat.curve_helpers.evaluate_multi_barycentric(nodes, lambda1, lambda2)

Evaluates a Bézier type-function.

Of the form

\[B(\lambda_1, \lambda_2) = \sum_j \binom{n}{j} \lambda_1^{n - j} \lambda_2^j \cdot v_j\]

for some set of vectors \(v_j\) given by nodes.

Does so via a modified Horner’s method for each pair of values in lambda1 and lambda2, rather than using the de Casteljau algorithm.

Note

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

Parameters
  • nodes (numpy.ndarray) – The nodes defining a curve.

  • lambda1 (numpy.ndarray) – Parameters along the curve (as a 1D array).

  • lambda2 (numpy.ndarray) – Parameters along the curve (as a 1D array). Typically we have lambda1 + lambda2 == 1.

Returns

The evaluated points as a two dimensional NumPy array, with the columns corresponding to each pair of parameter values and the rows to the dimension.

Return type

numpy.ndarray

bezier.hazmat.curve_helpers.vec_size(nodes, s_val)

Compute \(\|B(s)\|_2\).

Note

This is a helper for compute_length() and does not have a Fortran speedup.

Intended to be used with functools.partial() to fill in the value of nodes and create a callable that only accepts s_val.

Parameters
  • nodes (numpy.ndarray) – The nodes defining a curve.

  • s_val (float) – Parameter to compute \(B(s)\).

Returns

The norm of \(B(s)\).

Return type

float

bezier.hazmat.curve_helpers.compute_length(nodes)

Approximately compute the length of a curve.

If degree is \(n\), then the Hodograph curve \(B'(s)\) is degree \(d = n - 1\). Using this curve, we approximate the integral:

\[\int_{B\left(\left[0, 1\right]\right)} 1 \, d\mathbf{x} = \int_0^1 \left\lVert B'(s) \right\rVert_2 \, ds\]

using QUADPACK (via SciPy).

Note

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

Parameters

nodes (numpy.ndarray) – The nodes defining a curve.

Returns

The length of the curve.

Return type

float

Raises

ValueError – If nodes has zero columns.

bezier.hazmat.curve_helpers.elevate_nodes(nodes)

Degree-elevate a Bézier curve.

Does this by converting the current nodes \(v_0, \ldots, v_n\) to new nodes \(w_0, \ldots, w_{n + 1}\) where

\[\begin{split}\begin{align*} w_0 &= v_0 \\ w_j &= \frac{j}{n + 1} v_{j - 1} + \frac{n + 1 - j}{n + 1} v_j \\ w_{n + 1} &= v_n \end{align*}\end{split}\]

Note

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

Parameters

nodes (numpy.ndarray) – The nodes defining a curve.

Returns

The nodes of the degree-elevated curve.

Return type

numpy.ndarray

bezier.hazmat.curve_helpers.de_casteljau_one_round(nodes, lambda1, lambda2)

Perform one round of de Casteljau’s algorithm.

Note

This is a helper for specialize_curve(). It does not have a Fortran speedup because it is only used by a function which has a Fortran speedup.

The weights are assumed to sum to one.

Parameters
  • nodes (numpy.ndarray) – Control points for a curve.

  • lambda1 (float) – First barycentric weight on interval.

  • lambda2 (float) – Second barycentric weight on interval.

Returns

The nodes for a “blended” curve one degree lower.

Return type

numpy.ndarray

bezier.hazmat.curve_helpers.specialize_curve(nodes, start, end)

Specialize a curve to a re-parameterization

Note

This assumes the curve is degree 1 or greater but doesn’t check.

Note

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

Parameters
  • nodes (numpy.ndarray) – Control points for a curve.

  • start (float) – The start point of the interval we are specializing to.

  • end (float) – The end point of the interval we are specializing to.

Returns

The control points for the specialized curve.

Return type

numpy.ndarray

bezier.hazmat.curve_helpers.evaluate_hodograph(s, nodes)

Evaluate the Hodograph curve at a point \(s\).

The Hodograph (first derivative) of a Bézier curve degree \(d = n - 1\) and is given by

\[B'(s) = n \sum_{j = 0}^{d} \binom{d}{j} s^j (1 - s)^{d - j} \cdot \Delta v_j\]

where each forward difference is given by \(\Delta v_j = v_{j + 1} - v_j\).

Note

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

Parameters
  • nodes (numpy.ndarray) – The nodes of a curve.

  • s (float) – A parameter along the curve at which the Hodograph is to be evaluated.

Returns

The point on the Hodograph curve (as a two dimensional NumPy array with a single row).

Return type

numpy.ndarray

bezier.hazmat.curve_helpers.get_curvature(nodes, tangent_vec, s)

Compute the signed curvature of a curve at \(s\).

Computed via

\[\frac{B'(s) \times B''(s)}{\left\lVert B'(s) \right\rVert_2^3}\]
../../_images/get_curvature1.png
>>> nodes = np.asfortranarray([
...     [1.0, 0.75,  0.5, 0.25, 0.0],
...     [0.0, 2.0 , -2.0, 2.0 , 0.0],
... ])
>>> s = 0.5
>>> tangent_vec = evaluate_hodograph(s, nodes)
>>> tangent_vec
array([[-1.],
       [ 0.]])
>>> curvature = get_curvature(nodes, tangent_vec, s)
>>> curvature
-12.0

Note

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

Parameters
  • nodes (numpy.ndarray) – The nodes of a curve.

  • tangent_vec (numpy.ndarray) – The already computed value of \(B'(s)\)

  • s (float) – The parameter value along the curve.

Returns

The signed curvature.

Return type

float

bezier.hazmat.curve_helpers.newton_refine(nodes, point, s)

Refine a solution to \(B(s) = p\) using Newton’s method.

Computes updates via

\[\mathbf{0} \approx \left(B\left(s_{\ast}\right) - p\right) + B'\left(s_{\ast}\right) \Delta s\]

For example, consider the curve

\[\begin{split}B(s) = \left[\begin{array}{c} 0 \\ 0 \end{array}\right] (1 - s)^2 + \left[\begin{array}{c} 1 \\ 2 \end{array}\right] 2 (1 - s) s + \left[\begin{array}{c} 3 \\ 1 \end{array}\right] s^2\end{split}\]

and the point \(B\left(\frac{1}{4}\right) = \frac{1}{16} \left[\begin{array}{c} 9 \\ 13 \end{array}\right]\).

Starting from the wrong point \(s = \frac{3}{4}\), we have

\[\begin{split}\begin{align*} p - B\left(\frac{1}{2}\right) &= -\frac{1}{2} \left[\begin{array}{c} 3 \\ 1 \end{array}\right] \\ B'\left(\frac{1}{2}\right) &= \frac{1}{2} \left[\begin{array}{c} 7 \\ -1 \end{array}\right] \\ \Longrightarrow \frac{1}{4} \left[\begin{array}{c c} 7 & -1 \end{array}\right] \left[\begin{array}{c} 7 \\ -1 \end{array}\right] \Delta s &= -\frac{1}{4} \left[\begin{array}{c c} 7 & -1 \end{array}\right] \left[\begin{array}{c} 3 \\ 1 \end{array}\right] \\ \Longrightarrow \Delta s &= -\frac{2}{5} \end{align*}\end{split}\]
../../_images/newton_refine_curve1.png
>>> nodes = np.asfortranarray([
...     [0.0, 1.0, 3.0],
...     [0.0, 2.0, 1.0],
... ])
>>> curve = bezier.Curve(nodes, degree=2)
>>> point = curve.evaluate(0.25)
>>> point
array([[0.5625],
       [0.8125]])
>>> s = 0.75
>>> new_s = newton_refine(nodes, point, s)
>>> 5 * (new_s - s)
-2.0

On curves that are not “valid” (i.e. \(B(s)\) is not injective with non-zero gradient), Newton’s method may break down and converge linearly:

../../_images/newton_refine_curve_cusp.png
>>> nodes = np.asfortranarray([
...     [ 6.0, -2.0, -2.0, 6.0],
...     [-3.0,  3.0, -3.0, 3.0],
... ])
>>> curve = bezier.Curve(nodes, degree=3)
>>> expected = 0.5
>>> point = curve.evaluate(expected)
>>> point
array([[0.],
       [0.]])
>>> s_vals = [0.625, None, None, None, None, None]
>>> np.log2(abs(expected - s_vals[0]))
-3.0
>>> s_vals[1] = newton_refine(nodes, point, s_vals[0])
>>> np.log2(abs(expected - s_vals[1]))
-3.983...
>>> s_vals[2] = newton_refine(nodes, point, s_vals[1])
>>> np.log2(abs(expected - s_vals[2]))
-4.979...
>>> s_vals[3] = newton_refine(nodes, point, s_vals[2])
>>> np.log2(abs(expected - s_vals[3]))
-5.978...
>>> s_vals[4] = newton_refine(nodes, point, s_vals[3])
>>> np.log2(abs(expected - s_vals[4]))
-6.978...
>>> s_vals[5] = newton_refine(nodes, point, s_vals[4])
>>> np.log2(abs(expected - s_vals[5]))
-7.978...

Due to round-off, the Newton process terminates with an error that is not close to machine precision \(\varepsilon\) when \(\Delta s = 0\).

>>> s_vals = [0.625]
>>> new_s = newton_refine(nodes, point, s_vals[-1])
>>> while new_s not in s_vals:
...     s_vals.append(new_s)
...     new_s = newton_refine(nodes, point, s_vals[-1])
...
>>> terminal_s = s_vals[-1]
>>> terminal_s == newton_refine(nodes, point, terminal_s)
True
>>> 2.0**(-31) <= abs(terminal_s - 0.5) <= 2.0**(-28)
True

Due to round-off near the cusp, the final error resembles \(\sqrt{\varepsilon}\) rather than machine precision as expected.

Note

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

Parameters
  • nodes (numpy.ndarray) – The nodes defining a Bézier curve.

  • point (numpy.ndarray) – A point on the curve.

  • s (float) – An “almost” solution to \(B(s) = p\).

Returns

The updated value \(s + \Delta s\).

Return type

float

bezier.hazmat.curve_helpers.locate_point(nodes, point)

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.

Parameters
Returns

The parameter value (\(s\)) corresponding to point or None if the point is not on the curve.

Return type

Optional [ float ]

Raises

ValueError – If the standard deviation of the remaining start / end parameters among the subdivided intervals exceeds a given threshold (e.g. \(2^{-20}\)).

bezier.hazmat.curve_helpers.reduce_pseudo_inverse(nodes)

Performs degree-reduction for a Bé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.

Parameters

nodes (numpy.ndarray) – The nodes in the curve.

Returns

The reduced nodes.

Return type

numpy.ndarray

Raises

UnsupportedDegree – If the degree is not 1, 2, 3 or 4.

bezier.hazmat.curve_helpers.projection_error(nodes, projected)

Compute the error between nodes and the projected nodes.

Note

This is a helper for maybe_reduce(), which is in turn a helper for full_reduce(). Hence there is no corresponding Fortran speedup.

For now, just compute the relative error in the Frobenius norm. But, we may wish to consider the error per row / point instead.

Parameters
  • nodes (numpy.ndarray) – Nodes in a curve.

  • projected (numpy.ndarray) – The nodes projected into the space of degree-elevated nodes.

Returns

The relative error.

Return type

float

bezier.hazmat.curve_helpers.maybe_reduce(nodes)

Reduce nodes in a curve if they are degree-elevated.

Note

This is a helper for 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 \(E\) (from one degree lower) and forming \(E^T \left(E E^T\right)^{-1} E\).

Parameters

nodes (numpy.ndarray) – The nodes in the curve.

Returns

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.

Return type

Tuple [ bool, numpy.ndarray ]

Raises

UnsupportedDegree – If the curve is degree 5 or higher.

bezier.hazmat.curve_helpers.full_reduce(nodes)

Apply degree reduction to nodes until it can no longer be reduced.

Note

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

Parameters

nodes (numpy.ndarray) – The nodes in the curve.

Returns

The fully degree-reduced nodes.

Return type

numpy.ndarray