OptimalControlBenchmarks

OptimalControlBenchmarks provides 31 ModelingToolkit optimal-control problems and a driver using Corleone's shared optimization bridge. Use it to prepare, solve, time, and plot problems with consistent control and shooting grids.

Solve an LQR problem

The linear quadratic regulator (LQR) benchmark tracks a target state while penalizing control effort. Start with single shooting and a piecewise-constant control on 20 intervals:

using OptimalControlBenchmarks, Ipopt

grids = BenchmarkGrids(;
    control_grid = 0.0:0.05:0.95,
    shooting_grid = Float64[],
    constraint_grid = 0.0:0.05:1.0,
)
catalog = load_benchmarks()
lqr = only(filter(problem -> nameof(problem) == :lqr, catalog))
model_kwargs = (; problem_kwargs = (; abstol = 1e-10, reltol = 1e-10))
solution = solve_with_corleone(
    lqr, Ipopt.Optimizer(), grids, false; model_kwargs, print_level = 0
)
(; objective = solution.objective, retcode = solution.retcode)
(objective = 15.77257368960536, retcode = SciMLBase.ReturnCode.Success)

Grids are normalized to [0, 1]. Each constructor scales independent copies to its physical time span, so the same grids can be reused across problems. Control points mark interval starts; a terminal endpoint is optional and is discarded when preparing the control schedule. Use shooting_grid = 0.0:0.1:1.0 for multiple shooting. Interior shooting points lift states and add continuity constraints; endpoints do not create additional segments.

Initial guesses and tolerances

Dense shooting grids may need better starting values. Use prepare_benchmark to obtain a model, initialize its shooting values with Corleone's forward_initialization(rng, model.layer, model.ps, model.st), then construct the optimization problem. Set integration tolerances through problem_kwargs to resolve the requested optimization accuracy.

Prepare and time problems

prepare_benchmark returns a CorleoneDynamicOptProblem. Use its parameters and state with Corleone's bounds, vectorization, initialization, and optimization APIs. When evaluating optimized parameters, reconstruct them with the chosen vectorizer and reuse model.st. Controls are parameter histories; plotting selects them through symbolic metadata.

To collect timing records, use the same catalog and grids:

results = run_all([lqr], Ipopt.Optimizer(), grids; model_kwargs, print_level = 0)

Each record contains name, time in seconds, objective, and retcode. Timing includes model construction, first-use compilation during the call, and optimization. Package import time is excluded and plotting is disabled by default. These are single-run wall-clock measurements, not warmed statistical benchmarks. Pass catalog instead of [lqr] to run all problems.

Interpret results before comparing timings

Inspect each solver's termination status. Successful model construction does not imply convergence or global optimality. Sampled path constraints do not establish feasibility between samples. Nonlinear problems may encounter invalid trial states; exceptions propagate from the driver.

solve_with_corleone accepts AD, vectorize, algorithm, and model_kwargs. Remaining keywords override the optimizer defaults (max_iter = 100, tol = 5e-6, and hessian_approximation = "limited-memory"); they must suit the chosen optimizer. Its fourth positional Boolean controls plotting.

Grids and registry

OptimalControlBenchmarks.BenchmarkGrids — Type
struct BenchmarkGrids

The grids used by an optimal-control benchmark constructor.

Fields

  • control_grid: Control interval starts; a final endpoint is optional and discarded during preparation.

  • shooting_grid: Shooting boundaries; an empty grid selects single shooting.

  • constraint_grid: Points at which path or terminal constraints are evaluated.

Usage contract

User grids contain sorted, unique normalized time points in [0, 1]. Constructors return independent grids scaled to their problem horizon. A benchmark constructor accepts a BenchmarkGrids value and returns the problem data consumed by run_all.

OptimalControlBenchmarks.load_benchmarks — Function
load_benchmarks()

Return the benchmark constructor functions in alphabetical order. Definitions are loaded once with the package; repeated calls do not redefine methods. Each constructor accepts BenchmarkGrids and returns a system, independent scaled grids, declared state/control counts, time span, and descriptive name.

OptimalControlBenchmarks.OptimalControlBenchmark — Type
struct OptimalControlBenchmark{F}

Description of one benchmark problem registered by load_benchmarks.

Fields

  • name: Stable identifier used in benchmark output.

  • description: Human-readable description of the problem.

  • make_problem: Callable accepting BenchmarkGrids and returning benchmark problem data.

This is a developer type. Benchmark packages should provide a constructor with the same callable contract as make_problem rather than depending on implementation details of the benchmark registry.

OptimalControlBenchmarks.scale_grids — Function
scale_grids(tspan, grids)

Return independent grids scaled from [0, 1] into tspan. The input grids and all their buffers remain unchanged, so one configuration can be reused across problems with different horizons.

OptimalControlBenchmarks.scale_grids! — Function
scale_grids!(tspan, grids)

Map normalized grids into tspan with t = t0 + (tf - t0) * τ, updating their existing buffers. Validate every grid before writing. Problem constructors use scale_grids to keep the caller's normalized configuration reusable.

Preparation, solving, and timing

OptimalControlBenchmarks.prepare_benchmark — Function
prepare_benchmark(benchmark, grids; algorithm, kwargs...)

Build a prepared Corleone model from a benchmark constructor and normalized grids. Control grids contain interval starts; a final endpoint is discarded. Shooting endpoints are accepted and only interior points create matching resets. Pass an empty shooting grid for single shooting. The benchmark's explicit time span is used even when a grid omits its final endpoint.

algorithm selects the differential-equation solver. Other keywords are forwarded to CorleoneDynamicOptProblem, including problem_kwargs, ensemblealg, and rng. The returned model retains its initialized parameters and state.

OptimalControlBenchmarks.solve_with_corleone — Function
solve_with_corleone(benchmark, optimizer, grids; ...)
solve_with_corleone(
    benchmark,
    optimizer,
    grids,
    plot_res;
    algorithm,
    AD,
    vectorize,
    model_kwargs,
    kwargs...
)

Prepare and solve a benchmark with Corleone's SciMLBase optimization bridge. plot_res=true displays the resulting states, controls, and quadratures. Reconstruct optimizer values with the selected vectorizer and reuse the prepared model's state.

AD defaults to AutoForwardDiff(). vectorize defaults to Corleone's loaded vectorizer. model_kwargs configure prepare_benchmark; remaining keywords configure the optimization solver and override the default iteration limit, tolerance, or limited-memory Hessian approximation. Return the optimization solution, including its actual solver status; convergence is not assumed.

OptimalControlBenchmarks.run_all — Function
run_all(benchmarks, optimizer, grids; plot_res, kwargs...)

Run each benchmark once and return records with name, elapsed time in seconds, objective, and retcode. Timing includes model construction, compilation, and optimization; it is not a warmed microbenchmark. Inspect solver statuses before comparing objective values. Exceptions propagate to the caller.

Plotting is disabled by default. Other keywords are forwarded to solve_with_corleone. The normalized input grids are reusable and remain unchanged throughout the run.

OptimalControlBenchmarks.plot_oc_problem — Function
plot_oc_problem(trajectory)

Plot states, controls, and quadratures using the trajectory's symbolic metadata. Controls are parameter histories, not appended state indices. The legacy three-argument form accepts dimension counts but derives selections from metadata.