Skip to content

[Paper] Port to OptimalControl v2 and new problem interface #283

Closed
1 of 1 issue completed
Closed

Description

@ocots

Agents: read the guide for agents in #310 first (order, difficulty, how to claim an issue, working method). The specification is in this description and in the comments below.

Goal

Port OptimalControlProblems.jl to OptimalControl v2 and change its interface: a problem function returns the continuous problem and its initial guess, instead of a problem already discretized with the trapezoidal rule. The scheme, grid, modeler, solver and CPU/GPU choice are then made in solve, which is what the benchmark paper needs.

What we know

  • origin/main is 0.5.0 and depends on OptimalControl 1, CTDirect 0.17, CTModels 0.6, CTBase 0.16, ADNLPModels 0.8, ExaModels 0.9. OptimalControl is at 2.2.1-beta (in ct-registry).
  • The 22 files in ext/OptimalControlModels/ (and the 22 in ext/OptimalControlModels_s/) end with direct_transcription(ocp, description...; lagrange_to_mayer=false, init=init, grid_size=…, disc_method=:trapeze, …). In v2, direct_transcription, build_OCP_solution and set_initial_guess are gone (see BREAKING.md in OptimalControl: discretize, ocp_solution, @init).
  • The initial guess is a NamedTuple (state=…, control=…, variable=…), still accepted by v2.
  • src/OptimalControlProblems.jl generates the stubs of every problem with eval; the real methods live in the OptimalControlModels and JuMPModels extensions.
  • If problems no longer discretize, CTDirect, ADNLPModels, ExaModels and SolverCore are no longer needed as direct dependencies (this also answers the stale CT* versions reported in [Doc] review of examples published on the website #229).

Proposal (to be discussed)

pb = beam(OptimalControlBackend(); parameters=(tf=2,))   # continuous problem
sol = solve(pb.ocp, :exa, :madnlp, :gpu; init=pb.init, grid_size=2000, scheme=:midpoint)
model = beam(JuMPBackend(); grid_size=500, scheme=:midpoint)  # JuMP stays discretized

Open questions

  • Returned object: NamedTuple (name, ocp, init) or a small struct that can also carry metadata (see the metadata theme)?
  • Does the package keep a dependency on CTModels (JuMP accessors extend its functions), or does everything go through the OptimalControl extension?
  • Keep an nlp_model-like convenience? (Proposal: no, that is OptimalControl's job.)
  • merge / parameters_data use CTBase.UnauthorizedCall: check it still exists in CTBase 0.30.

Also in scope

Update compat; rewrite test/ for the v2 API; rewrite the tutorials tutorial-get.md and tutorial-solve.md; fix the docstring problems (#172, #173) and the website review (#229); publish 0.6.0-beta in ct-registry.

Related issues

#257 (roadmap), #136 (Exa and problems, solved by v2), #174 (description vs symbol; _s as standard), #151 (initial guess documentation), #229, #172, #173.

How to work on this theme

This is a theme issue: it describes the work to do and what is known so far, it is not meant to be closed by a single PR.

  1. Whoever takes it first defines the work precisely and splits it into sub-issues (they can live in any repository of the organization).
  2. Work proceeds through the sub-issues; tick the checklist below as they are closed.
  3. Close the theme when everything is done or explicitly dropped.

Context: tracking issue control-toolbox/CTBenchmarks.jl#200; planning notes: objectives and directions and updating OptimalControlProblems and CTBenchmarks (in control-toolbox/benchmark-paper).

Sub-issues

  • to be created when the theme is taken

Activity

  1. ocots commented on Nov 4, 2026

    @ocots
    MemberAuthor

    Decisions taken in #310 that concern this theme:

    • Returned object: a struct (name, ocp, init, metadata) holding the continuous (mathematical) problem. This package no longer discretizes for OptimalControl (direct_transcription no longer exists in v2). The user discretizes through solve, in descriptive or explicit mode, or through discretize:

      pb  = beam(OptimalControlBackend())
      sol = solve(pb.ocp, :collocation, :exa, :madnlp; init=pb.init, grid_size=500)  # descriptive mode
      sol = solve(pb.ocp; init=pb.init,                                               # explicit mode
                  discretizer=OptimalControl.Collocation(grid_size=500, scheme=:midpoint),
                  modeler=OptimalControl.Exa(), solver=OptimalControl.MadNLP())

      No convenience function for discretizing in this package: modeler and solver options belong to the solve call, not to the retrieval of the problem.

    • Branch: no long-lived branch. The port goes to main through PRs, with the version set to 0.6.0-beta (published in ct-registry) and use_ct_registry: true in the workflows. The per-modeling lists ([Dev] Per-modeling lists of problems, discovered automatically, with a status per modeling #303) land on main first, on the 0.5 API.

  2. ocots commented on Nov 4, 2026

    @ocots
    MemberAuthor

    Specification

    Detailed in #310 (step 2). This comment is the reference for whoever implements the port; it supersedes the "Proposal" and "Open questions" of the description. Depends on #313, #312, #303 and #304 (test layout per environment already in place).

    Facts found in the v2 stack

    Local versions: OptimalControl 2.2.1-beta, CTBase 0.30.13, CTModels 0.21.1, CTDirect 1.1.6-beta, CTSolvers 0.7.0, CTParser 0.8.17-beta.

    1. CTBase.UnauthorizedCall no longer exists (OptimalControl BREAKING.md). merge and parameters_data use it: replace by CTBase.PreconditionError or CTBase.IncorrectArgument, and update test_parameters.
    2. CTModels.matrix2vec and CTModels.ctinterpolate no longer exist: the JuMP accessors (state, control, costate in ext/JuMPModels.jl) must be rewritten.
    3. objective and variable are imported from ExaModels (src/OptimalControlProblems.jl:15) and exported, and the JuMP extension extends ExaModels.objective. In v2, extend the CTModels functions that OptimalControl reexports, so that using OptimalControl, OptimalControlProblems gives no name conflict and state(jump_model) and state(sol) are the same function.
    4. The v2 API needed exists and is reexported by OptimalControl: discretize(ocp, Collocation(grid_size=…, scheme=…)), nlp_model(dm, init, modeler), ocp_solution, modelers ADNLP / Exa, @init.
    5. The tutorials (docs/src/tutorial-get.md, tutorial-solve.md) call direct_transcription: the docs break with the port.

    Decisions

    • Returned object (OptimalControl and OptimalControlScalarized modelings): a struct Problem (OptimalControlProblems.Problem) with fields name::Symbol, ocp (continuous problem, the @def model), init (NamedTuple (state=…, control=…, variable=…), shared initial guess comes with [Metadata] Computed features, reference objective, shared initial guess #306), metadata; a show method.
      • Access through an API, as in the other ct packages, documented: e.g. name(pb), ocp(pb), initial_guess(pb), metadata(pb) (exact names to settle against existing exports and the conventions). Direct field access stays possible but is not the documented way.
    • No discretization in this package for OptimalControl: the 2 × 22 problem files no longer end with direct_transcription(…); they return the Problem. Discretization, modeler, solver, CPU/GPU are chosen by the user in solve (descriptive or explicit mode) or through discretize. No convenience function for that here.
    • JuMP stays discretized, trapezoid only (other schemes: [Paper] JuMP models for Euler, trapeze and midpoint, and NLP equivalence tests #284). Its accessors are ported to CTModels 0.21.
    • JuMP solution as a CTModels.Solution (first step towards JuMP support in OptimalControl):
      • CTModels.build_solution(ocp, T_state, T_control, T_costate, T_path, X, U, v, P; objective, iterations, successful, status, message, …) takes the continuous ocp first. So a full Solution can be built from a solved JuMP model when the problem is also available in OptimalControl, using its ocp; then plot, state(sol), control(sol), costate(sol), objective(sol), iterations(sol) and the comparisons work as for OptimalControl.
      • Proposal: a method that takes the solved JuMP model and the Problem, returning a CTModels.Solution (name to settle; must not be type piracy: one argument type must be ours or CTModels'). Use the four-grid form: grids differ per scheme (number of controls, see [Paper] JuMP models for Euler, trapeze and midpoint, and NLP equivalence tests #284); set control_interpolation accordingly.
      • For problems available in JuMP only, keep the per-component accessors (time_grid, state, control, costate, variable, objective, iterations), with a small linear interpolation written here.
      • Costate: follow the sign and time conventions of OptimalControl (see [General] Costate #170, CTDirect#639, CTDirect#640); document what is done and what is not yet comparable.
    • Dependencies: remove CTDirect, ADNLPModels, ExaModels, SolverCore. Keep CTBase, CTModels (for the shared accessors and build_solution), DocStringExtensions, OrderedCollections. Compat: OptimalControl 2.2, CTModels 0.21, CTBase 0.30, following the REL rules for betas (pre-release compat bounds).
    • Version: Project.toml at 0.6.0-beta, with CHANGELOG.md and BREAKING.md entries ([Chore] Release and CI infrastructure: CHANGELOG, BREAKING, release workflows, label-gated CI #312). No release for now: CTBenchmarks is not worked on yet.

    Tests

    The layout of #304 is in place; port the content to v2:

    • configurations in test/common/configs.jl in explicit mode, e.g. Collocation(grid_size, scheme=:trapeze) + ADNLP() + Ipopt(…), Exa() + MadNLP(…), Exa{GPU}() + MadNLP{GPU}(…);
    • GPU tests in the OptimalControl and OptimalControlScalarized environments (Exa on GPU), and the GPU jobs of CI.yml announced in [Tests] Test layout per environment and consistency tests #304 (run ci kkt-runner, run ci occidata-runner, ci-gpu.yml on main); GPU needs MadNLPGPU, CUDA and CUDSS loaded;
    • tests of the Problem API and of the JuMP → Solution construction (compare with the OptimalControl solution on trapezoid);
    • the comparison tests use discretize + nlp_model on the OptimalControl side and the JuMP model on the other.

    Documentation

    Only what keeps the docs building: port the tutorials' code to v2. The full rewrite is #307; the move to VitePress is #311.

    Closes

    #136 (Exa and GPU through solve). The "description" part of #174 disappears (the _s part stays in #174).

    Done when

    • Every OptimalControl and scalarized problem returns a Problem; no direct_transcription left.
    • Problem API documented; show prints name, dimensions and status.
    • JuMP accessors on CTModels 0.21; a solved JuMP model gives a CTModels.Solution when the problem exists in OptimalControl, and plot(sol) works on it.
    • No UnauthorizedCall, no import from ExaModels; using OptimalControl, OptimalControlProblems gives no ambiguity warning.
    • CTDirect, ADNLPModels, ExaModels, SolverCore removed from [deps]; version 0.6.0-beta; changelog entries.
    • Tests pass on CPU (three OSes) and GPU (kkt or occidata); docs build.
  3. ocots commented on Nov 4, 2026

    @ocots
    MemberAuthor

    Update to the specification above, about the JuMP solution:

    • A CTModels.Solution stores the continuous model (model(sol)), used by plots, warm start, duals and show. Proposed upstream: [Feature] build_solution from model information, without a full continuous Model CTModels.jl#515, build_solution from model information without a full Model.
    • In this issue: build the Solution from the ocp of the Problem when the problem exists in OptimalControl (works today); keep the per-component accessors for JuMP-only problems.
    • Later, once CTModels.jl#515 is done: JuMP models provide their own model information (from :state_components, :time_grid, …), so JuMP-only problems get a Solution too.
  4. ocots commented on Nov 4, 2026

    @ocots
    MemberAuthor

    Clarification on the JuMP solution, in stages; the goal is only to have what is needed to show and plot a JuMP result like an OptimalControl solution:

    1. Nothing available: per-component accessors only (time_grid, state, control, costate, variable, objective, iterations), no Solution.
    2. Once [Feature] build_solution from model information, without a full continuous Model CTModels.jl#515 exists: build a Solution from the information stored in the JuMP model (components, time grid, …).
    3. When the problem exists in OptimalControl: build the Solution from its ocp (possible today; this issue does it).

    No dynamics on the JuMP side for this: flows need a continuous model, and the long-term direction is to obtain JuMP models from an OptimalControl ocp, not the reverse.

  5. ocots commented on Nov 4, 2026

    @ocots
    MemberAuthor

    Documentation of OptimalControl v2: read the dev site, https://control-toolbox.org/OptimalControl.jl/dev/ (or docs/src/ on main of OptimalControl.jl); the stable site is not up to date. In particular, explicit mode: https://control-toolbox.org/OptimalControl.jl/dev/solve/explicit-mode.

  6. ocots commented on Nov 4, 2026

    @ocots
    MemberAuthor

    Taken by an agent; draft PR to follow.

  7. ocots commented on Nov 4, 2026

    @ocots
    MemberAuthor

    Progress and deviations from the specification.

    Draft PR: #331 (part 1). The theme is split in three sub-issues: #328 (port: Problem type, continuous problems, CPU tests; this PR), #329 (CTModels.Solution from a solved JuMP model), #330 (GPU tests).

    Deviations from the specification, argued in #331:

    • CTModels 0.19, not 0.21. OptimalControl 2.2.1-beta (ct-registry) requires CTModels = "0.19", so 0.21 cannot be resolved with it. Compat follows OptimalControl.
    • metadata(:beam) becomes problem_metadata(:beam). OptimalControl reexports CTBase.Strategies.metadata; a method on Symbol would be type piracy. metadata(pb) on a Problem extends it.
    • Problem API: model(pb), initial_guess(pb), metadata(pb). model extends CTModels.Solutions.model (already exported by OptimalControl, model(sol) returns the problem of a solution): no new exported name. No name(pb) (it repeats what the user called, and conflicts with JuMP.name) and no ocp(pb) (on Julia 1.10, ocp = @def … fails once an imported ocp has been used).
    • Problem has a modeling field beyond the four of the specification, so that show can print the status, which depends on the modeling.

    Still to do in #331: the tests of the modeling environments, after #324 (which rewrites them), and the CI of the PR.

    Deferred: the costate and dual signs of JuMP versus OptimalControl, in #326 (waits for the work on multipliers, discussion #68).

  8. ocots commented on Nov 5, 2026

    @ocots
    MemberAuthor

    From #304 (PRs #319, #324) — what the port to v2 inherits in the tests:

    • Remove the bound ExaModels = "0.9 - 0.9.4" of test/suite/OptimalControl/Project.toml: it works around a conflict of the v1 stack on Julia 1.13 (CTParser 0.8.2 redefines a zero method of ExaModels ≥ 0.9.5; precompilation fails). The v2 stack has no such conflict.
    • Configurations: test/common/configs.jl (Main.TestConfigs.CONFIGS) is the single matrix read by every test; extend it with the v2 modelers/solvers rather than writing options in test files. Solvers are one module each in test/common/solve_*.jl (an environment only includes what it loads).
    • Runner: test/common/runner.jl (LocalTestRunner) is provisional; replace it by CTBase.run_tests (CTBase ≥ 0.30) when CTBase is bumped. Keep select_environments semantics (tested in core).
    • Statuses: every test loops with Main.TestStatus.for_each_problem(f, test, backends...); :valid fails, :draft is reported (test/reports/). New v2-specific failures go through statuses, not @test_broken.
    • Deferred to this issue by [Tests] Test layout per environment and consistency tests #304: the GPU jobs of CI.yml (run ci kkt-runner, run ci occidata-runner, one GPU run on main via ci-gpu.yml) and the environment contract TST6 (GPU must be functional on a GPU runner), once Exa GPU tests exist.
    • test_modelings loads REPL: since Julia 1.13 Base.Docs.doc has no method without it.
  9. added a commit that references this issue on Nov 5, 2026
  10. ocots commented on Nov 5, 2026

    @ocots
    MemberAuthor

    Progress of the theme (2026-11-05).

    This issue stays open until #329, #330 and the release are done, or explicitly dropped.

  11. ocots commented on Nov 5, 2026

    @ocots
    MemberAuthor

    Closing the theme

    The port to OptimalControl v2 and the new problem interface are done.

    Delivered

    Not done here, on purpose

    Tracked elsewhere, no longer part of this theme

    Closed as completed.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

paperBenchmark paper

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions