Skip to content

[Doc] Examples for the sign convention of duals and costate (min/max, writing of constraints, direct vs indirect) #1016

Open

Description

@ocots

Follow-up of #1014 (the convention) and companion of #1015 (the page that states it). This issue lists the examples to add to the documentation, so that a user can see that the signs do not depend on the solver, on the sense of the criterion, or on the way a constraint is written. Each example should also become a test: run in a docs page, it fails loudly if a convention moves.

The implementation is in three draft PRs:

The convention, as the user should read it (for #1015)

  • The dual of an inequality constraint is nonnegative, whatever the constraint and the sense of the criterion. g(x) ≤ 0 and -g(x) ≥ 0, or x(t) ≥ 0.1 and 0.1 - x(t) ≤ 0, are the same constraint and have the same dual.
  • An equality keeps its sign: c = 0 and -c = 0 have opposite duals.
  • min J and max -J are the same problem: they have the same duals and the same costate p. Only the objective changes sign.
  • Indirect method: the pseudo-Hamiltonian H = p·f + p⁰ℓ + μ·g (g ≥ 0, μ ≥ 0) is always maximised, with p⁰ = -1 for a minimisation and +1 for a maximisation. dual(sol, ocp, :label) of a flow solution is the μ given to the flow (0 off the boundary arc).
  • A constraint with two finite bounds, lb ≤ c ≤ ub, has a signed dual: +μ when the upper side is active, -μ when the lower side is.

Examples to add

  1. The signs do not change with min/max. Solve ẋ = u, x(0) = -1, x(t) + u(t) ≤ 0, 0 ≤ u(t):

    • once as ∫ -u(t) dt → min, once as ∫ u(t) dt → max;
    • with Ipopt and with MadNLP.

    Show that costate(sol) and dual(sol, ocp, :mixed) are the same in the four cases. The closed form is p(t) = e^{t-1} - 1 and dual = e^{t-1}.

  2. The signs do not change with the way a constraint is written. The same problem with x(t) + u(t) ≤ 0 written as -x(t) - u(t) ≥ 0: the same dual.

  3. Direct and indirect give the same duals. Rebuild the solution of example 1 with Flow(ocp, (x, p) -> -x; constraint=:mixed, multiplier=(x, p) -> p + 1), then compare dual(sol_direct, ocp, :mixed) and dual(sol_flow, ocp, :mixed) on one plot. They agree up to the discretisation error.

  4. A boundary arc in a multi-phase indirect solution. The problem of Flow(ocp, law; constraint=:label, …) uses the raw constraint function, ignoring its bound and its side CTFlows.jl#440 (x ≥ 0.1, three arcs). plot(sol) shows the dual, 0 then 2x = 0.2 on the boundary arc then 0, with its jumps at the junctions and the phase labels. Compare with a direct solve of the same problem.

  5. Why the label matters. In a flow, a constraint given as a function has no label: its dual cannot be placed in the solution, the flow warns, and dual throws. Show the labelled form, constraint=:label or :label => :upper.

Possible tests (OptimalControl test suite, the cross-package level): examples 1 to 3, with Ipopt and MadNLP (and Uno), as @test on the closed form.

Still open (not for the user page)

🤖 Generated with Claude Code

Activity

  1. ocots commented on Oct 23, 2026

    @ocots
    MemberAuthor

    Two additions, so that they are not forgotten:

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

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions