You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
[Bug] Reverse over forward AD issues with ADNLP #481
@ocots@PierreMartinon Same kind of error with AD as in #384 tough here the problem seems to be directly related to using a constant in the computation: this fails (Cannot determine ordering of Dual tags ForwardDiff.Tag)
using OptimalControl
using NLPModelsIpopt
o =@defbegin
v ∈ R, variable
t ∈ [0, 1], time
x ∈ R², state
u ∈ R, control
-1≤ v ≤1x(0) - [-1, v] == [0, 0] # OK: the boundary contraint may involve the variablex(1) == [0, 0]
ẋ(t) == [x₂(t), u(t)]
∫( 0.5u(t)^2 ) → min
end
s =solve(o)
this also fails (same error)
o =@defbegin
v ∈ R, variable
t ∈ [0, 1], time
x ∈ R², state
u ∈ R, control
-1≤ v ≤1x(0) - [-one(v), v] == [0, 0] # OK: the boundary contraint may involve the variablex(1) == [0, 0]
ẋ(t) == [x₂(t), u(t)]
∫( 0.5u(t)^2 ) → min
end
while this is OK
o =@defbegin
v ∈ R, variable
t ∈ [0, 1], time
x ∈ R², state
u ∈ R, control
-1≤ v ≤1x(0) - [-1+0v, v] == [0, 0] # OK: the boundary contraint may involve the variablex(1) == [0, 0]
ẋ(t) == [x₂(t), u(t)]
∫( 0.5u(t)^2 ) → min
end
@PierreMartinon I thought that using one as above (second version; see also zero) solved the problem, but this is not the case. any clue?
Hum it is the forward over reverse mode for the Hessian that fails. If you don't use backend=:optimized, it should work because it's forward over forward by default.
I should investigate but maybe better to spend time on the support of DI.jl in ADNLPModels.jl. Guillaume is maybe doing something smarter than us with the tape.
@PierreMartinon@ocots@amontoison defaulting adnlp_backend=:default indeed works. as pointed by @ocots 6 times slower on that example (o2 is equivalent but raises no bug as not single constant is used, check -1 + 0 * v instead of merely -1 😬).
@jbcaillau Do you still have the error with option adnlp_backend=:default added to the solve call ?
It works (but as you said it is slower so cannot be the default for us).
julia> o1 =@defbegin
v ∈ R, variable
t ∈ [0, 1], time
x ∈ R², state
u ∈ R, control
-1≤ v ≤1x(0) - [-1, v] == [0, 0] # OK: the boundary contraint may involve the variablex(1) == [0, 0]
ẋ(t) == [x₂(t), u(t)]
∫( 0.5u(t)^2 ) → min
end
julia>@btimesolve(o1; adnlp_backend=:default) # no more exception, indeed159.537 ms (502305 allocations:237.32 MiB)
julia> o2 =@defbegin
v ∈ R, variable
t ∈ [0, 1], time
x ∈ R², state
u ∈ R, control
-1≤ v ≤1x(0) - [-1+0* v, v] == [0, 0] # OK: the boundary contraint may involve the variablex(1) == [0, 0]
ẋ(t) == [x₂(t), u(t)]
∫( 0.5u(t)^2 ) → min
end
julia>@btimesolve(o2)
27.333 ms (113391 allocations:15.55 MiB) # and same objective reached up to
@PierreMartinon@amontoison any of you has tried to use ADNLP on a problem with a constraint function involving a vectorial function having one constant component to replicate the issue?
what about integrating these tests into those of OC.jl / CTDirect.jl?
on top of that, would be nice to test more intensively backend=:enzyme to potentially use it as the default (instead of :optimized, currently, that also does forward over reverse but with ReverseDiff.jl) 👀
using OptimalControl
using NLPModelsIpopt
using Enzyme
backend =:enzyme# vs. :default that does forward over reverse with ReverseDiff and fails# use case no. 1
x0 = [ 0.1. ]
A = [ 0.1.-1.0. ]
B = [ 0.1. ]
Q = [ 1.0.0.1. ]
R =1.
tf =3.
o =@defbegin
t ∈ [0, tf], time
x ∈ R², state
u ∈ R, control
x(0) == x0
ẋ(t) == A *x(t) + B *u(t)
0.5∫( x(t)'* Q *x(t) +u(t)'* R *u(t) ) → min
end
sol =solve(o; backend=backend)
# use case no. 2
o =@defbegin
v ∈ R, variable
t ∈ [0, 1], time
x ∈ R², state
u ∈ R, control
-1≤ v ≤1x(0) - [-1, v] == [0, 0] # OK: the boundary contraint may involve the variablex(1) == [0, 0]
ẋ(t) == [x₂(t), u(t)]
∫( 0.5u(t)^2 ) → min
end
sol =solve(o; backend=backend)
# use case no. 3
t0 =0.
vf =20.
x0 = [0., 0., 0.1, 0.1]
p = [1., 1., 1., 1., 2., 1.5]
# Functionse(x) = p[1] + x[3] + x[4]
g(x) = p[2]
f₁₁(x) = p[3]
f₁₂(x) =- p[4]*x[3]
f₂₁(x) = p[5]
f₂₂(x) =- p[6]*x[4]
# Vector fieldsFf(x) = [e(x), g(x), f₁₁(x), f₂₁(x)]
Fb(x) = [e(x), -g(x), f₁₂(x), f₂₂(x)]
F0(x) =Ff(x) +Fb(x)
F1(x) =Ff(x) -Fb(x)
o =@defbegin
tf ∈ R, variable
t ∈ [t0, tf], time
x = (e, v, r1, r2) ∈ R⁴, state
u ∈ R, control
-1≤u(t) ≤1x(t0) == x0
v(tf) == vf
ẋ(t) ==F0(x(t)) +u(t)*F1(x(t))
e(tf) → min
end
sol =solve(o; backend=backend)
using OptimalControl
using NLPModelsIpopt
backend =:optimized# errors while it is now ok with :enzyme (see above)# use case no. 2
o =@defbegin
v ∈ R, variable
t ∈ [0, 1], time
x ∈ R², state
u ∈ R, control
-1≤ v ≤1x(0) - [-1, v] == [0, 0] # FTR: x(0) - [-1 + 0v, v] == [0, 0] worksx(1) == [0, 0]
ẋ(t) == [x₂(t), u(t)]
∫( 0.5u(t)^2 ) → min
end
sol =solve(o; backend=backend)
raises the error documented at the beginning of the issue:
▫ OptimalControl v1.2.1-beta solving with: collocation → adnlp → ipopt
📦 Configuration:
├─ Discretizer: collocation (no user options)
├─ Modeler: adnlp (backend = optimized)
└─ Solver: ipopt (no user options)
This is Ipopt version 3.14.19, running with linear solver MUMPS 5.8.2.
Number of nonzeros in equality constraint Jacobian...: 1755
Number of nonzeros in inequality constraint Jacobian.: 0
Number of nonzeros in Lagrangian Hessian.............: 250
Total number of variables............................: 753
variables with only lower bounds:0
variables with lower and upper bounds:1
variables with only upper bounds:0
Total number of equality constraints.................: 504
Total number of inequality constraints...............: 0
inequality constraints with only lower bounds:0
inequality constraints with lower and upper bounds:0
inequality constraints with only upper bounds:0
ERROR: Cannot determine ordering of Dual tags ForwardDiff.Tag{ReverseDiff.var"...
everything comes from the generated function below:
julia>@macroexpand o =@defbegin
v ∈ R, variable
t ∈ [0, 1], time
x ∈ R², state
u ∈ R, control
-1≤ v ≤1x(0) - [-1, v] == [0, 0] # OK: the boundary contraint may involve the variablex(1) == [0, 0]
ẋ(t) == [x₂(t), u(t)]
∫( 0.5u(t)^2 ) → min
end
[...]
#= REPL[33]:7 =#begin#= /net/home/c/caillau/.julia/packages/CTParser/AmHLr/src/onepass.jl:186 =#try#= /net/home/c/caillau/.julia/packages/CTParser/AmHLr/src/onepass.jl:187 =#begin#= /net/home/c/caillau/.julia/packages/CTParser/AmHLr/src/onepass.jl:653 =#functionvar"fun##108335"(var"r##108338", var"x0##108336", var"xf##108337", var"v##108329")
#= /net/home/c/caillau/.julia/packages/CTParser/AmHLr/src/onepass.jl:653 =##= /net/home/c/caillau/.julia/packages/CTParser/AmHLr/src/onepass.jl:654 =#var"r##108338"[:] .=var"x0##108336"- [-1, (Base.maybeview)(var"v##108329", 1)]
#= /net/home/c/caillau/.julia/packages/CTParser/AmHLr/src/onepass.jl:655 =#returnnothingend#= /net/home/c/caillau/.julia/packages/CTParser/AmHLr/src/onepass.jl:657 =#
CTModels.constraint!(var"p_ocp##108320", :boundary; f =var"fun##108335", lb = [0, 0], ub = [0, 0], label =Symbol("label##108333"))
endcatch#= /net/home/c/caillau/.julia/packages/CTParser/AmHLr/src/onepass.jl:189 =#println("Line ", 6, ": ", "x(0) - [-1, v] == [0, 0]")
#= /net/home/c/caillau/.julia/packages/CTParser/AmHLr/src/onepass.jl:190 =#rethrow()
endend
the function that is accepted replaces x(0) - [-1, v] == [0, 0] with x(0) - [-1 + 0v, v] == [0, 0] so, in the generated code, the line
ADNLPModels 0.8.14 fixed the boundary-constraint cases above: x(0) - [-1, v] == [0, 0] and x(0) - [-one(v), v] == [0, 0] now solve with :adnlp. Another case of the same error remains, with ADNLPModels 0.8.14 as with 0.8.13: a dynamics component that is a constant.
using OptimalControl, NLPModelsIpopt
ocp =@defbegin
t ∈ [0, 1], time
x ∈ R³, state
u ∈ R, control
x(0) == [-1, 0, 2]
x₁(1) ==0x₂(1) ==0ẋ(t) == [x₂(t), u(t), 0]
0.5∫(u(t)^2) → min
endsolve(ocp) # ERROR: Cannot determine ordering of Dual tags ForwardDiff.Tag{ReverseDiff…}solve(ocp, :exa) # works, J = 6
Third component of ẋ(t)
:adnlp
0
✗ Dual tags error
0.0
✗
zero(u(t))
✗
1
✗
0 * u(t)
✓ J = 6
So any component that does not depend on the inputs fails, whatever its value, and 0 * u(t) is the workaround (it was already the workaround of CTDirect#190, dtf = 0, closed in January 2025). The same happens when the constant state then appears in the cost or in the dynamics (ẋ = [x₂ u, 0]). This is the usual way to turn a free final time into a state (ṫ_f = 0), so users hit it.
The tags in the error (ForwardDiff duals inside a ReverseDiff tape, through ADNLPModels' ψ) point to the second-order AD backend, as in the original report; I have not narrowed it down further. Versions: OptimalControl 2.2.1-beta, CTDirect 1, ADNLPModels 0.8.14 (also 0.8.13), Julia 1.12.6.
Thank you @amontoison for 0.8.15! I checked it: with ADNLPModels 0.8.15, the last case of this issue now solves with the default :adnlp modeler, a constant component of the dynamics:
ocp =@defbegin
t ∈ [0, 1], time
x ∈ R³, state
u ∈ R, control
x(0) == [-1, 0, 2]
x₁(1) ==0x₂(1) ==0ẋ(t) == [x₂(t), u(t), 0]
0.5∫(u(t)^2) → min
endsolve(ocp) # J = 6.000096, same as with `0 * u(t)` and with :exa
"Cannot determine ordering of Dual tags" is gone. A constant 1.0 component works too. The warning on the Abstract syntax page of the documentation is removed in #971, and the probe that tracked this bug now checks that it stays fixed. Thanks again for the quick fixes, 0.8.14 and now 0.8.15.
Activity
jbcaillau commentedon Apr 27, 2025
@amontoison @gdalle input welcome 🤞🏽
amontoison commentedon Apr 27, 2025
@jbcaillau If you can provide the full error, it will help.
jbcaillau commentedon Apr 28, 2025
@amontoison full log attached (copy paste of the first cell above)
amontoison commentedon Apr 28, 2025
Hum it is the forward over reverse mode for the Hessian that fails. If you don't use
backend=:optimized, it should work because it's forward over forward by default.I should investigate but maybe better to spend time on the support of DI.jl in ADNLPModels.jl. Guillaume is maybe doing something smarter than us with the tape.
PierreMartinon commentedon May 2, 2025
@jbcaillau Do you still have the error with option
adnlp_backend=:defaultadded to the solve call ?ocots commentedon May 2, 2025
It works (but as you said it is slower so cannot be the default for us).
ocots commentedon May 2, 2025
Should be explained on the doc.
jbcaillau commentedon May 3, 2025
@PierreMartinon @ocots @amontoison defaulting
adnlp_backend=:defaultindeed works. as pointed by @ocots 6 times slower on that example (o2is equivalent but raises no bug as not single constant is used, check-1 + 0 * vinstead of merely-1😬).jbcaillau commentedon May 3, 2025
@ocots adding a Know issues section (pointing to issues) at the end of the "define a problem" tutorial #480
jbcaillau commentedon May 3, 2025
@PierreMartinon @amontoison any of you has tried to use ADNLP on a problem with a constraint function involving a vectorial function having one constant component to replicate the issue?
amontoison commentedon May 25, 2025
@jbcaillau
I was cleaning my emails and checked again this issue. Why do you use some
Vector{Real}?It is not a concrete type for Julia.
ERROR: Cannot determine ordering of Dual tags ForwardDiff.Tag{ReverseDiff.var"#130#133"{typeof(-), ForwardDiff.Dual{ForwardDiff.Tag{ADNLPModels.var"#ψ#72"{CTDirect.var"#14#16"{CTDirect.DOCP{CTDirect.Trapeze, Model{CTModels.TimesModel{CTModels.FixedTimeModel{Int64}, CTModels.FixedTimeModel{Int64}}, CTModels.StateModel, CTModels.ControlModel, CTModels.VariableModel, var"##248#3", CTModels.LagrangeObjectiveModel{var"##252#4"}, CTModels.ConstraintsModel{Tuple{Vector{Real}, CTModels.var"#path_cons_nl!#59"{Int64, Vector{Int64}, Tuple{}}, Vector{Real}}, Tuple{Vector{Real}, CTModels.var"#boundary_cons_nl!#61"{Int64, Vector{Int64}, Tuple{var"##242#2", var"##237#1"}}, Vector{Real}}, Tuple{Vector{Real}, Vector{Int64}, Vector{Real}}, Tuple{Vector{Real}, Vector{Int64}, Vector{Real}}, Tuple{Vector{Real}, Vector{Int64}, Vector{Real}}, Dict{Symbol, Tuple{Symbol, Union{Function, OrdinalRange{<:Int64}}, AbstractVector{<:Real}, AbstractVector{<:Real}}}}}}}}, Float64}, Float64, 1}}, ForwardDiff.Dual{ForwardDiff.Tag{ADNLPModels.var"#ψ#72"{CTDirect.var"#14#16"{CTDirect.DOCP{CTDirect.Trapeze, Model{CTModels.TimesModel{CTModels.FixedTimeModel{Int64}, CTModels.FixedTimeModel{Int64}}, CTModels.StateModel, CTModels.ControlModel, CTModels.VariableModel, var"##248#3", CTModels.LagrangeObjectiveModel{var"##252#4"}, CTModels.ConstraintsModel{Tuple{Vector{Real}, CTModels.var"#path_cons_nl!#59"{Int64, Vector{Int64}, Tuple{}}, Vector{Real}}, Tuple{Vector{Real}, CTModels.var"#boundary_cons_nl!#61"{Int64, Vector{Int64}, Tuple{var"##242#2", var"##237#1"}}, Vector{Real}}, Tuple{Vector{Real}, Vector{Int64}, Vector{Real}}, Tuple{Vector{Real}, Vector{Int64}, Vector{Real}}, Tuple{Vector{Real}, Vector{Int64}, Vector{Real}}, Dict{Symbol, Tuple{Symbol, Union{Function, OrdinalRange{<:Int64}}, AbstractVector{<:Real}, AbstractVector{<:Real}}}}}}}}, Float64}, Float64, 1}} and ForwardDiff.Tag{ADNLPModels.var"#ψ#72"{CTDirect.var"#14#16"{CTDirect.DOCP{CTDirect.Trapeze, Model{CTModels.TimesModel{CTModels.FixedTimeModel{Int64}, CTModels.FixedTimeModel{Int64}}, CTModels.StateModel, CTModels.ControlModel, CTModels.VariableModel, var"##248#3", CTModels.LagrangeObjectiveModel{var"##252#4"}, CTModels.ConstraintsModel{Tuple{Vector{Real}, CTModels.var"#path_cons_nl!#59"{Int64, Vector{Int64}, Tuple{}}, Vector{Real}}, Tuple{Vector{Real}, CTModels.var"#boundary_cons_nl!#61"{Int64, Vector{Int64}, Tuple{var"##242#2", var"##237#1"}}, Vector{Real}}, Tuple{Vector{Real}, Vector{Int64}, Vector{Real}}, Tuple{Vector{Real}, Vector{Int64}, Vector{Real}}, Tuple{Vector{Real}, Vector{Int64}, Vector{Real}}, Dict{Symbol, Tuple{Symbol, Union{Function, OrdinalRange{<:Int64}}, AbstractVector{<:Real}, AbstractVector{<:Real}}}}}}}}, Float64}jbcaillau commentedon May 25, 2025
i think everything is eventually set to
Float64and type stable. @ocots and @PierreMartinon do you confirm?ocots commentedon May 25, 2025
Where do you that we use
Vector{Real}?18 remaining items
jbcaillau commentedon Feb 25, 2026
@PierreMartinon @ocots the fix above is now merged into
mainat ADNLModels (see JuliaSmoothOptimizers/ADNLPModels.jl#378). the three problems below solve with it andbackend=:enzyme:backend=:enzymeto potentially use it as the default (instead of:optimized, currently, that also does forward over reverse but with ReverseDiff.jl) 👀jbcaillau commentedon Feb 27, 2026
backend=:optimizedfor @amontoisonbuild_nlp!https://github.com/control-toolbox/CTDirect.jl/blob/main/ext/CTDirectExtADNLP.jlamontoison commentedon Feb 27, 2026
@jbcaillau https://github.com/JuliaSmoothOptimizers/ADNLPModels.jl/blob/main/src/predefined_backend.jl#L61-L66
jbcaillau commentedon Feb 27, 2026
@amontoison
raises the error documented at the beginning of the issue:
everything comes from the generated function below:
the function that is accepted replaces
x(0) - [-1, v] == [0, 0]withx(0) - [-1 + 0v, v] == [0, 0]so, in the generated code, the lineis replaced with
jbcaillau commentedon Feb 27, 2026
jbcaillau commentedon Apr 27, 2026
cc @abavoil
jbcaillau commentedon Apr 27, 2026
@PierreMartinon can you please confirm to @abavoil that the option to use the sparsity structure (that you computed) is
:manual, as shown below (excerpt from https://control-toolbox.org/OptimalControl.jl/stable/manual-solve.html)?ocots commentedon Apr 27, 2026
Yep see https://control-toolbox.org/Tutorials.jl/stable/tutorial-discretisation.html#Large-problems-and-AD-backend
docs(abstract-syntax): structure section, ASCII table, labels, verifi…
docs(probes): check the claims the docs build cannot execute
ocots commentedon Oct 9, 2026
ADNLPModels 0.8.14 fixed the boundary-constraint cases above:
x(0) - [-1, v] == [0, 0]andx(0) - [-one(v), v] == [0, 0]now solve with:adnlp. Another case of the same error remains, with ADNLPModels 0.8.14 as with 0.8.13: a dynamics component that is a constant.ẋ(t):adnlp00.0zero(u(t))10 * u(t)So any component that does not depend on the inputs fails, whatever its value, and
0 * u(t)is the workaround (it was already the workaround of CTDirect#190,dtf = 0, closed in January 2025). The same happens when the constant state then appears in the cost or in the dynamics (ẋ = [x₂ u, 0]). This is the usual way to turn a free final time into a state (ṫ_f = 0), so users hit it.The tags in the error (ForwardDiff duals inside a ReverseDiff tape, through ADNLPModels'
ψ) point to the second-order AD backend, as in the original report; I have not narrowed it down further. Versions: OptimalControl 2.2.1-beta, CTDirect 1, ADNLPModels 0.8.14 (also 0.8.13), Julia 1.12.6.amontoison commentedon Oct 10, 2026
@jbcaillau @ocots @PierreMartinon New release
0.8.15ofADNLPModels.jl.It should fix the last issue. 🤞
ocots commentedon Oct 10, 2026
Thank you @amontoison for 0.8.15! I checked it: with ADNLPModels 0.8.15, the last case of this issue now solves with the default
:adnlpmodeler, a constant component of the dynamics:"Cannot determine ordering of Dual tags" is gone. A constant
1.0component works too. The warning on the Abstract syntax page of the documentation is removed in #971, and the probe that tracked this bug now checks that it stays fixed. Thanks again for the quick fixes, 0.8.14 and now 0.8.15.docs(changelog): constant component of the dynamics, with the example (…