Minimizing the Acceleration of Bézier Curves on the Sphere

Ronny Bergmann 2023-06-06

using Manifolds, Manopt, ManoptExamples

Introduction

Bézier Curves can be generalized to manifolds by generalizing the de Casteljau algorithm 📖 to work with geodesics instead of straight lines. An implementation in just a few lines as we demonstrated in [ABBR23] as

function bezier(M::AbstractManifold, t, pts::NTuple)
    p = bezier(M, t, pts[1:(end - 1)])
    q = bezier(M, t, pts[2:end])
    return shortest_geodesic(M, p, q, t)
end
function bezier(M::AbstractManifold, t, pts::NTuple{2})
    return shortest_geodesic(M, pts[1], pts[2], t)
end

which is also available within this package as de_Casteljau using a simple BezierSegment struct to make it easier to also discuss the case where we compose a set of segments into a composite Bézier course.

In the following we will need the following packages and functions. They are documented in the section on Bezier Curves and were derived in [BG18] based on [PN07].

using ManoptExamples:
    artificial_S2_composite_Bezier_curve,
    BezierSegment,
    de_Casteljau,
    get_Bezier_degrees,
    get_Bezier_inner_points,
    get_Bezier_junctions,
    get_Bezier_junction_tangent_vectors,
    get_Bezier_points,
    get_Bezier_segments,
    grad_L2_acceleration_Bezier,
    L2_acceleration_Bezier

This notebook reproduces the example form Section 5.2 in [BG18].

The following image illustrates how the de-Casteljau algorithm works for one segment.

A Bezier segment and illustration of the de-Casteljau algorithm

Approximating data by a curve with minimal accelartion

We first load our example data

M = Sphere(2)
B = artificial_S2_composite_Bezier_curve()
data_points = get_Bezier_junctions(M, B)

Which is the following cure, which clearly starts and ends slower than its speed in the middle, which can be seen by the increasing length of the gangent vectors in the middle.

The original curve

We continue to recude the points, since we “know” sme points due to the $C^1$ property: the second to last control point of the first segment $b_{0,2}$, the joint junction point connecting both segments $b_{0,3}=b_{1,0}$ and the second control point $b_{1,1}$ of the second segment have to line in the tangent space of the joint junction point. Hence we only have to store one of the control points.

We can use this reduced form as the variable to optimize and the one from the data as our initial point.

pB = get_Bezier_points(M, B, :differentiable)
N = PowerManifold(M, NestedPowerRepresentation(), length(pB))
PowerManifold(Sphere(2, ℝ), NestedPowerRepresentation(), 8)

And we further define the acceleration of the curve as our cost function, where we discretize the acceleration at a certain set of points and set the $λ=10$

curve_samples = [range(0, 3; length=101)...] # sample curve for the gradient
λ = 10.0
function f(M, pB)
    return L2_acceleration_Bezier(
        M.manifold, pB, get_Bezier_degrees(M.manifold, B), curve_samples, λ, data_points
    )
end
function grad_f(M, pB)
    return grad_L2_acceleration_Bezier(
        M.manifold, pB, get_Bezier_degrees(M.manifold, B), curve_samples, λ, data_points
    )
end
grad_f (generic function with 1 method)

Then we can optimize

x0 = pB
pB_opt = gradient_descent(
    N,
    f,
    grad_f,
    x0;
    stepsize=ArmijoLinesearch(N;
        initial_stepsize=1.0,
        retraction_method=ExponentialRetraction(),
        contraction_factor=0.5,
        sufficient_decrease=0.001,
    ),
    stopping_criterion=StopWhenChangeLess(1e-5) |
                       StopWhenGradientNormLess(1e-7) |
                       StopAfterIteration(300),
    debug=[
        :Iteration,
        " | ",
        :Cost,
        " | ",
        DebugGradientNorm(),
        " | ",
        DebugStepsize(),
        " | ",
        :Change,
        "\n",
        25,
        :Stop,
    ],
);
Initial  | f(x): 10.647244 |  |  | 
# 25     | f(x): 2.667564 | |grad f(p)|:0.890845571434862 | s:0.01326670131422904 | Last Change: 0.763281
# 50     | f(x): 2.650064 | |grad f(p)|:0.05536989605165708 | s:0.05306680525691616 | Last Change: 0.081780
# 75     | f(x): 2.649707 | |grad f(p)|:0.02135638585837997 | s:0.01326670131422904 | Last Change: 0.011590
# 100    | f(x): 2.649700 | |grad f(p)|:0.0012887575647752057 | s:0.05306680525691616 | Last Change: 0.001745
At iteration 109 the algorithm performed a step with a change (2.9063044690733034e-7) less than 1.0e-5.

And we can again look at the result

The result looks as

The resulting curve

where all control points are evenly spaced and we hence have less acceleration as the final cost compared to the initial one indicates. Note that the cost is not zero, since we always have a tradeoff between approximating the initial junctinons (data points) and minimizing the acceleration.

[ABBR23]
S. D. Axen, M. Baran, R. Bergmann and K. Rzecki. Manifolds.jl: An Extensible Julia Framework for Data Analysis on Manifolds. ACM Transactions on Mathematical Software (2023), arXiv:2021.08777.
[BG18]
R. Bergmann and P.-Y. Gousenbourger. A variational model for data fitting on manifolds by minimizing the acceleration of a Bézier curve. Frontiers in Applied Mathematics and Statistics 4 (2018), arXiv:1807.10090.
[PN07]
T. Popiel and L. Noakes. Bézier curves and $C^2$ interpolation in Riemannian manifolds. Journal of Approximation Theory 148, 111–127 (2007).