Skip to content

Commit ca254e2

Browse files
committed
refactor(solve): Option B - rename _solve dispatch to solve_explicit/solve_descriptive
- Replace _solve(::ExplicitMode, ...) with solve_explicit(...) - Replace _solve(::DescriptiveMode, ...) with solve_descriptive(...) - dispatch.jl: explicit if/else + component extraction by type via _extract_kwarg - mode.jl: restored cleanly, mode_detection.jl: types removed - Tests: fix outer-scope names (test_dispatch, test_mode), update all call sites - 76/76 tests passing
1 parent b6b2d8f commit ca254e2

File tree

11 files changed

+255
-99
lines changed

11 files changed

+255
-99
lines changed

.reports/kanban_orchestration/TODO/05_commonsolve_solve.md renamed to .reports/kanban_orchestration/DOING/05_commonsolve_solve.md

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -309,5 +309,6 @@ When `solve_descriptive` is implemented, this test will be updated.
309309

310310
## Status Tracking
311311

312-
**Current Status**: TODO
313-
**Created**: 2026-02-18
312+
**Current Status**: DOING
313+
**Started**: 2026-02-19
314+
**Developer**: Cascade

src/OptimalControl.jl

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ module OptimalControl
1010
using DocStringExtensions
1111
using Reexport
1212
using CTBase
13+
using CommonSolve
1314

1415
# Imports
1516
include(joinpath(@__DIR__, "imports", "ctbase.jl"))
@@ -32,6 +33,7 @@ include(joinpath(@__DIR__, "helpers", "component_completion.jl"))
3233
# solve
3334
include(joinpath(@__DIR__, "solve", "mode.jl"))
3435
include(joinpath(@__DIR__, "solve", "mode_detection.jl"))
36+
include(joinpath(@__DIR__, "solve", "dispatch.jl"))
3537
include(joinpath(@__DIR__, "solve", "canonical.jl"))
3638
include(joinpath(@__DIR__, "solve", "explicit.jl"))
3739
include(joinpath(@__DIR__, "solve", "descriptive.jl"))

src/solve/canonical.jl

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -24,17 +24,17 @@ function CommonSolve.solve(
2424
display::Bool # Explicit value (no default)
2525
)::CTModels.AbstractSolution
2626

27-
# 1. Discretize the optimal control problem
28-
discrete_problem = CTDirect.discretize(ocp, discretizer)
29-
30-
# 2. Display configuration (compact, user options only)
27+
# 1. Display configuration (compact, user options only)
3128
if display
3229
OptimalControl.display_ocp_configuration(
3330
discretizer, modeler, solver;
3431
display=true, show_options=true, show_sources=false
3532
)
3633
end
3734

35+
# 2. Discretize the optimal control problem
36+
discrete_problem = CTDirect.discretize(ocp, discretizer)
37+
3838
# 3. Solve the discretized optimal control problem
3939
return CommonSolve.solve(
4040
discrete_problem, initial_guess, modeler, solver; display=display

src/solve/descriptive.jl

Lines changed: 4 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,12 @@
11
"""
22
$(TYPEDSIGNATURES)
33
4-
Stub for descriptive mode resolution.
4+
Stub for descriptive mode resolution (Layer 2).
55
6-
Raises [`CTBase.NotImplemented`](@ref) until `solve_descriptive` is implemented.
6+
Raises [`CTBase.NotImplemented`](@ref) until descriptive mode is implemented.
77
This stub allows testing the orchestration layer (mode detection, dispatch routing)
88
before the descriptive mode handler exists.
99
10-
The `description` vararg will be forwarded to `solve_descriptive` when implemented.
11-
1210
# Arguments
1311
- `ocp`: The optimal control problem to solve
1412
- `description`: Symbolic description tokens (e.g., `:collocation`, `:adnlp`, `:ipopt`)
@@ -21,16 +19,14 @@ The `description` vararg will be forwarded to `solve_descriptive` when implement
2119
- `CTBase.NotImplemented`: Always — descriptive mode is not yet implemented
2220
2321
# See Also
24-
- [`DescriptiveMode`](@ref): The dispatch sentinel type
2522
- [`CommonSolve.solve`](@ref): The entry point that dispatches here
2623
"""
27-
function _solve(
28-
::DescriptiveMode,
24+
function solve_descriptive(
2925
ocp::CTModels.AbstractModel,
3026
description::Symbol...;
3127
initial_guess::CTModels.AbstractInitialGuess,
3228
display::Bool,
33-
registry::CTSolvers.Strategies.StrategyRegistry,
29+
registry::CTSolvers.StrategyRegistry,
3430
kwargs...
3531
)::CTModels.AbstractSolution
3632

src/solve/dispatch.jl

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
using CTBase
2+
3+
"""
4+
$(TYPEDSIGNATURES)
5+
6+
Main entry point for optimal control problem resolution.
7+
8+
This function orchestrates the complete solve workflow by:
9+
1. Detecting the resolution mode (explicit vs descriptive) from arguments
10+
2. Normalizing the initial guess
11+
3. Creating the strategy registry
12+
4. Dispatching to the appropriate `_solve` method based on the detected mode
13+
14+
# Arguments
15+
- `ocp`: The optimal control problem to solve
16+
- `description`: Symbolic description tokens (e.g., `:collocation`, `:adnlp`, `:ipopt`)
17+
- `initial_guess`: Initial guess or `nothing` for automatic generation
18+
- `display`: Whether to display configuration information
19+
- `kwargs...`: Additional keyword arguments (explicit components or strategy options)
20+
21+
# Returns
22+
- `CTModels.AbstractSolution`: Solution to the optimal control problem
23+
24+
# Examples
25+
```julia
26+
# Descriptive mode (symbolic description)
27+
solution = solve(ocp, :collocation, :adnlp, :ipopt)
28+
29+
# Explicit mode (typed components)
30+
solution = solve(ocp; discretizer=CTDirect.Collocation(),
31+
modeler=CTSolvers.ADNLP(), solver=CTSolvers.Ipopt())
32+
```
33+
34+
# See Also
35+
- [`_explicit_or_descriptive`](@ref): Mode detection and validation
36+
- [`ExplicitMode`](@ref), [`DescriptiveMode`](@ref): Dispatch sentinel types
37+
- [`_solve`](@ref): Mode-specific resolution methods
38+
"""
39+
function CommonSolve.solve(
40+
ocp::CTModels.AbstractModel,
41+
description::Symbol...;
42+
initial_guess=nothing,
43+
display::Bool=__display(),
44+
kwargs...
45+
)::CTModels.AbstractSolution
46+
47+
# 1. Detect mode and validate (raises on conflict)
48+
mode = _explicit_or_descriptive(description, kwargs)
49+
50+
# 2. Normalize initial guess ONCE at the top level
51+
normalized_init = CTModels.build_initial_guess(ocp, initial_guess)
52+
53+
# 3. Get registry for component completion
54+
registry = get_strategy_registry()
55+
56+
# 4. Dispatch — asymmetric signatures:
57+
# ExplicitMode: extract typed components by type from kwargs (default nothing)
58+
# DescriptiveMode: description forwarded as vararg positional arguments
59+
if mode isa ExplicitMode
60+
discretizer = _extract_kwarg(kwargs, CTDirect.AbstractDiscretizer)
61+
modeler = _extract_kwarg(kwargs, CTSolvers.AbstractNLPModeler)
62+
solver = _extract_kwarg(kwargs, CTSolvers.AbstractNLPSolver)
63+
return solve_explicit(
64+
ocp;
65+
initial_guess=normalized_init,
66+
display=display,
67+
registry=registry,
68+
discretizer=discretizer,
69+
modeler=modeler,
70+
solver=solver
71+
)
72+
else
73+
return solve_descriptive(
74+
ocp, description...;
75+
initial_guess=normalized_init,
76+
display=display,
77+
registry=registry,
78+
kwargs...
79+
)
80+
end
81+
end

src/solve/explicit.jl

Lines changed: 6 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,10 @@
11
"""
22
$(TYPEDSIGNATURES)
33
4-
Resolve an OCP in explicit mode.
4+
Resolve an OCP in explicit mode (Layer 2).
55
6-
Receives typed components (`discretizer`, `modeler`, `solver`) as named keyword arguments
7-
(pre-validated by [`_explicit_or_descriptive`](@ref)), then completes missing components
8-
via the registry before calling Layer 3.
6+
Receives typed components (`discretizer`, `modeler`, `solver`) as named keyword arguments,
7+
then completes missing components via the registry before calling Layer 3.
98
109
# Arguments
1110
- `ocp`: The optimal control problem to solve
@@ -22,18 +21,16 @@ via the registry before calling Layer 3.
2221
# See Also
2322
- [`_has_complete_components`](@ref): Checks if all three components are provided
2423
- [`_complete_components`](@ref): Completes missing components via registry
25-
- [`ExplicitMode`](@ref): The dispatch sentinel type
26-
- [`_explicit_or_descriptive`](@ref): Validates and routes to this method
24+
- [`_explicit_or_descriptive`](@ref): Mode detection that routes here
2725
"""
28-
function _solve(
29-
::ExplicitMode,
26+
function solve_explicit(
3027
ocp::CTModels.AbstractModel;
3128
initial_guess::CTModels.AbstractInitialGuess,
3229
discretizer::Union{CTDirect.AbstractDiscretizer, Nothing},
3330
modeler::Union{CTSolvers.AbstractNLPModeler, Nothing},
3431
solver::Union{CTSolvers.AbstractNLPSolver, Nothing},
3532
display::Bool,
36-
registry::CTSolvers.Strategies.StrategyRegistry
33+
registry::CTSolvers.StrategyRegistry
3734
)::CTModels.AbstractSolution
3835

3936
# Resolve components: use provided ones or complete via registry

src/solve/mode.jl

Lines changed: 6 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,43 +1,39 @@
11
"""
22
Abstract supertype for solve mode sentinel types.
33
4-
Concrete subtypes are used for multiple dispatch on `_solve` to route
5-
resolution to the appropriate mode handler without `if/else` branching.
4+
Concrete subtypes are used to route resolution to the appropriate mode handler
5+
without `if/else` branching in mode detection.
66
77
# Subtypes
88
- [`ExplicitMode`](@ref): User provided explicit components (discretizer, modeler, solver)
99
- [`DescriptiveMode`](@ref): User provided symbolic description (e.g., `:collocation, :adnlp, :ipopt`)
1010
1111
# See Also
1212
- [`_explicit_or_descriptive`](@ref): Returns the appropriate mode instance
13-
- [`_solve`](@ref): Dispatches on mode
1413
"""
1514
abstract type SolveMode end
1615

1716
"""
1817
Sentinel type indicating that the user provided explicit resolution components.
1918
20-
An instance `ExplicitMode()` is passed to `_solve` when at least one of
21-
`discretizer`, `modeler`, or `solver` is present in `kwargs` with the
19+
An instance `ExplicitMode()` is returned by [`_explicit_or_descriptive`](@ref) when at
20+
least one of `discretizer`, `modeler`, or `solver` is present in `kwargs` with the
2221
correct abstract type.
2322
2423
# See Also
2524
- [`DescriptiveMode`](@ref): The alternative mode
2625
- [`_explicit_or_descriptive`](@ref): Mode detection logic
27-
- [`_solve(::ExplicitMode, ...)`](@ref): Handler for this mode
2826
"""
2927
struct ExplicitMode <: SolveMode end
3028

3129
"""
3230
Sentinel type indicating that the user provided a symbolic description.
3331
34-
An instance `DescriptiveMode()` is passed to `_solve` when no explicit
35-
components are found in `kwargs`. The symbolic description (e.g.,
36-
`:collocation, :adnlp, :ipopt`) is forwarded via `kwargs`.
32+
An instance `DescriptiveMode()` is returned by [`_explicit_or_descriptive`](@ref) when
33+
no explicit components are found in `kwargs`.
3734
3835
# See Also
3936
- [`ExplicitMode`](@ref): The alternative mode
4037
- [`_explicit_or_descriptive`](@ref): Mode detection logic
41-
- [`_solve(::DescriptiveMode, ...)`](@ref): Handler for this mode
4238
"""
4339
struct DescriptiveMode <: SolveMode end

test/suite/solve/test_dispatch.jl

Lines changed: 9 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -49,12 +49,11 @@ function test_solve_dispatch()
4949
registry = OptimalControl.get_strategy_registry()
5050

5151
# ====================================================================
52-
# CONTRACT TESTS - ExplicitMode: complete components (mock Layer 3)
52+
# CONTRACT TESTS - solve_explicit: complete components (mock Layer 3)
5353
# ====================================================================
5454

55-
Test.@testset "ExplicitMode - all three components" begin
56-
result = OptimalControl._solve(
57-
OptimalControl.ExplicitMode(),
55+
Test.@testset "solve_explicit - all three components" begin
56+
result = OptimalControl.solve_explicit(
5857
ocp;
5958
initial_guess=init,
6059
display=false,
@@ -65,13 +64,12 @@ function test_solve_dispatch()
6564
end
6665

6766
# ====================================================================
68-
# CONTRACT TESTS - DescriptiveMode: stub raises NotImplemented
67+
# CONTRACT TESTS - solve_descriptive: stub raises NotImplemented
6968
# ====================================================================
7069

71-
Test.@testset "DescriptiveMode - raises NotImplemented" begin
70+
Test.@testset "solve_descriptive - raises NotImplemented" begin
7271
Test.@test_throws CTBase.NotImplemented begin
73-
OptimalControl._solve(
74-
OptimalControl.DescriptiveMode(),
72+
OptimalControl.solve_descriptive(
7573
ocp, :collocation, :adnlp, :ipopt;
7674
initial_guess=init,
7775
display=false,
@@ -80,65 +78,20 @@ function test_solve_dispatch()
8078
end
8179
end
8280

83-
Test.@testset "DescriptiveMode - empty description raises NotImplemented" begin
81+
Test.@testset "solve_descriptive - empty description raises NotImplemented" begin
8482
Test.@test_throws CTBase.NotImplemented begin
85-
OptimalControl._solve(
86-
OptimalControl.DescriptiveMode(),
83+
OptimalControl.solve_descriptive(
8784
ocp;
8885
initial_guess=init,
8986
display=false,
9087
registry=registry
9188
)
9289
end
9390
end
94-
95-
# ====================================================================
96-
# CONTRACT TESTS - Dispatch correctness
97-
# ====================================================================
98-
99-
Test.@testset "Dispatch correctness - ExplicitMode route" begin
100-
# Verify that ExplicitMode actually routes to the ExplicitMode method
101-
function _dispatch_route(mode::OptimalControl.ExplicitMode)
102-
return :explicit
103-
end
104-
function _dispatch_route(mode::OptimalControl.DescriptiveMode)
105-
return :descriptive
106-
end
107-
108-
Test.@test _dispatch_route(OptimalControl.ExplicitMode()) == :explicit
109-
end
110-
111-
Test.@testset "Dispatch correctness - DescriptiveMode route" begin
112-
# Verify that DescriptiveMode actually routes to the DescriptiveMode method
113-
function _dispatch_route(mode::OptimalControl.ExplicitMode)
114-
return :explicit
115-
end
116-
function _dispatch_route(mode::OptimalControl.DescriptiveMode)
117-
return :descriptive
118-
end
119-
120-
Test.@test _dispatch_route(OptimalControl.DescriptiveMode()) == :descriptive
121-
end
122-
123-
# ====================================================================
124-
# INTEGRATION TESTS - End-to-end dispatch
125-
# ====================================================================
126-
127-
Test.@testset "Integration - complete explicit workflow" begin
128-
result = OptimalControl._solve(
129-
OptimalControl.ExplicitMode(),
130-
ocp;
131-
initial_guess=init,
132-
display=false,
133-
registry=registry,
134-
discretizer=disc, modeler=mod, solver=sol
135-
)
136-
Test.@test result isa MockSolution
137-
end
13891
end
13992
end
14093

14194
end # module
14295

14396
# CRITICAL: Redefine in outer scope for TestRunner
144-
test_solve_dispatch() = TestSolveDispatch.test_solve_dispatch()
97+
test_dispatch() = TestSolveDispatch.test_solve_dispatch()

0 commit comments

Comments
 (0)