Repository navigation
[Doc] Examples for the sign convention of duals and costate (min/max, writing of constraints, direct vs indirect) #1016
Description
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:
- feat(solvers): one multiplier convention for every solver CTSolvers.jl#289: one multiplier convention for every NLP solver.
min fandmax -fhave the same multipliers; a constraint with a single bound has a nonnegative multiplier; equalities keep their sign. - feat(flows): constraint duals in the Solution of Flow(ocp, law; constraint=:label) CTFlows.jl#494: the solution of
Flow(ocp, law; constraint=:label, multiplier=μ)carriesdual(sol, ocp, :label), in the same convention. - fix(solutions): dual() on missing duals + limits of the path duals at switching times CTModels.jl#483:
dualon a solution that holds the duals of some constraints only, and the jumps of the duals in the plots of a multi-phase solution.
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) ≤ 0and-g(x) ≥ 0, orx(t) ≥ 0.1and0.1 - x(t) ≤ 0, are the same constraint and have the same dual. - An equality keeps its sign:
c = 0and-c = 0have opposite duals. min Jandmax -Jare the same problem: they have the same duals and the same costatep. Only the objective changes sign.- Indirect method: the pseudo-Hamiltonian
H = p·f + p⁰ℓ + μ·g(g ≥ 0,μ ≥ 0) is always maximised, withp⁰ = -1for a minimisation and+1for a maximisation.dual(sol, ocp, :label)of a flow solution is theμgiven to the flow (0off 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
-
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)anddual(sol, ocp, :mixed)are the same in the four cases. The closed form isp(t) = e^{t-1} - 1anddual = e^{t-1}. - once as
-
The signs do not change with the way a constraint is written. The same problem with
x(t) + u(t) ≤ 0written as-x(t) - u(t) ≥ 0: the same dual. -
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 comparedual(sol_direct, ocp, :mixed)anddual(sol_flow, ocp, :mixed)on one plot. They agree up to the discretisation error. -
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,0then2x = 0.2on the boundary arc then0, with its jumps at the junctions and the phase labels. Compare with a direct solve of the same problem. -
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
dualthrows. Show the labelled form,constraint=:labelor: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)
- Box constraints. The dual of a box (
0 ≤ u(t) ≤ 1declared as a box) has the opposite sign to the same constraint written as a path constraint, and it is not divided by the step (Box multipliers are not divided by the time step, and their sign is the opposite of the path multipliers CTDirect.jl#639). The page should say so until it is fixed, or wait for it. - Boundary and box duals of a flow solution are not rebuilt (
dualthrows aPreconditionError). - Two-sided constraints: the signed value is kept. Is that what the page should present, or a pair (lower, upper)?
🤖 Generated with Claude Code
Activity
ocots commentedon Oct 23, 2026
Two additions, so that they are not forgotten:
SGN1–SGN6(rules/signs.md). The page of Documentation: sign conventions (min/max, p⁰, constraints, duals) and how they appear in a solution #1015 and these examples should cite it. The packages do too: CTSolvers, CTModels, CTFlows (and CTDirect, to do).dual(sol, ocp, :label)andcostate(sol)between a direct solve and the solution ofFlow(ocp, law; constraint=:label, multiplier=μ), on a problem with a closed form (the mixed constraint of example 1:dual = e^{t-1},p = e^{t-1} − 1). Run it with Ipopt and MadNLP, asminand asmax. This is the checkSGN5asks for; it can only live in OptimalControl, which loads both CTDirect and CTFlows. A failure tells that one of the two sides moved.