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] Mixing explicit solve and option routing ignores the routing part #852
Describe the bug
What is showcased here does not seem to work on v2.0.4.
To Reproduce
using OptimalControl, NLPModelsIpopt
ocp =@defbegin
t ∈ [0, 1], time
x ∈ R², state
u ∈ R, control
x(0) == [-1, 0]
x(1) == [0, 0]
ẋ(t) == [x₂(t), u(t)]
∫(0.5u(t)^2) → min
end
disc = OptimalControl.Collocation()
## Backend option is ignored
s =solve(ocp; disc, backend=:generic)
## Code that should error
s =solve(ocp; disc, backend=:beep)
s =solve(ocp; disc, beep=:beep)
## Expected errors
s =solve(ocp; backend=:beep)
#=ERROR: Control Toolbox Error❌ Error: CTBase.Exceptions.IncorrectArgument, Invalid ADNLPModels backend=#
s =solve(ocp; beep=:beep)
#=ERROR: Control Toolbox Error❌ Error: CTBase.Exceptions.IncorrectArgument, Unknown option provided=#
Expected behavior
The to-be-routed options should not be discarded, they should be routed if valid, or result in an error otherwise.
Thanks @abavoil for reporting this and for providing such a clear reproducer.
There was indeed a bug here, but it was slightly different from backend being ignored intentionally. The actual bug was that solve silently accepted and discarded an option that it could not consume. An exception should have been raised instead.
There are two different ways to call solve:
Descriptive (implicit) mode: the method is selected with symbolic tokens, and strategy options can be passed to solve and routed to the appropriate strategy. For example:
Explicit mode: at least one typed strategy instance is passed to solve. In that mode, the strategy is already constructed, so its options must be provided when constructing the strategy. Options are not routed from the solve call.
disc automatically selects explicit mode. The problem is not that backend is lost; this syntax is not valid for explicit mode because backend is a strategy option and should have been provided when constructing the modeler.
The behavior has been fixed so that these invalid calls now raise informative IncorrectArgument exceptions instead of being silently ignored. The fix is included in PR #853. It will be included in a future version of OptimalControl.jl; however, we do not plan to publish a release solely for this fix. It will be included in the next regular release.
The reproducer can therefore be corrected as follows:
using OptimalControl, NLPModelsIpopt
ocp =@defbegin
t ∈ [0, 1], time
x ∈ R², state
u ∈ R, control
x(0) == [-1, 0]
x(1) == [0, 0]
ẋ(t) == [x₂(t), u(t)]
∫(0.5u(t)^2) → min
end
disc = OptimalControl.Collocation()
modeler = OptimalControl.ADNLP(backend=:generic)
# Correct explicit-mode call:
s =solve(ocp; discretizer=disc, modeler=modeler)
# Or use descriptive (implicit) mode, where strategy options are routed:
s =solve(ocp, :collocation, :adnlp, :ipopt; backend=:generic)
The following are independent invalid examples; each one is expected to raise an exception:
# Invalid in explicit mode: `backend` must be configured when constructing# the modeler. This now raises an informative IncorrectArgument exception.
s =solve(ocp; disc, backend=:generic)
# Invalid in explicit mode for the same reason.
s =solve(ocp; disc, backend=:beep)
# Invalid in explicit mode: `beep` is neither a solve action option nor an# option declared by any of the completed strategies.
s =solve(ocp; disc, beep=:beep)
For comparison, these calls remain descriptive (implicit) mode calls because no typed component is provided:
# Invalid backend value. The option is routed to ADNLP, which raises an# IncorrectArgument for the invalid backend.
s =solve(ocp; backend=:beep)
# Invalid option. No action or strategy accepts this option, so solve raises# an IncorrectArgument for an unknown option.
s =solve(ocp; beep=:beep)
The expected distinction is now:
solve(ocp; disc, backend=:generic)
→ IncorrectArgument: `backend` is a modeler/solver strategy option in
explicit mode; construct the strategy with `backend=...` and pass it to solve.
solve(ocp; disc, beep=:beep)
→ IncorrectArgument: unknown explicit-mode solve option `beep`; the error lists
the options accepted directly by the solve action.
solve(ocp; backend=:beep)
→ IncorrectArgument: invalid ADNLP backend.
solve(ocp; beep=:beep)
→ IncorrectArgument: unknown option provided in descriptive mode.
Thanks again for helping us identify this behavior and improve the error handling.
Activity
ocots commentedon Aug 13, 2026
Response to issue #852
Thanks @abavoil for reporting this and for providing such a clear reproducer.
There was indeed a bug here, but it was slightly different from
backendbeing ignored intentionally. The actual bug was thatsolvesilently accepted and discarded an option that it could not consume. An exception should have been raised instead.There are two different ways to call
solve:Descriptive (implicit) mode: the method is selected with symbolic tokens, and strategy options can be passed to
solveand routed to the appropriate strategy. For example:Explicit mode: at least one typed strategy instance is passed to
solve. In that mode, the strategy is already constructed, so its options must be provided when constructing the strategy. Options are not routed from thesolvecall.Therefore, in:
discautomatically selects explicit mode. The problem is not thatbackendis lost; this syntax is not valid for explicit mode becausebackendis a strategy option and should have been provided when constructing the modeler.The behavior has been fixed so that these invalid calls now raise informative
IncorrectArgumentexceptions instead of being silently ignored. The fix is included in PR #853. It will be included in a future version ofOptimalControl.jl; however, we do not plan to publish a release solely for this fix. It will be included in the next regular release.The reproducer can therefore be corrected as follows:
The following are independent invalid examples; each one is expected to raise an exception:
For comparison, these calls remain descriptive (implicit) mode calls because no typed component is provided:
The expected distinction is now:
Thanks again for helping us identify this behavior and improve the error handling.
abavoil commentedon Aug 14, 2026
Ah I read the doc too quicly, that part describes the behavior, but at least now it clearer that this doesn't work, thank you :)