|
2 | 2 |
|
3 | 3 | **Layer**: 2 (Mode-Specific Logic - Descriptive Mode) |
4 | 4 |
|
5 | | -## R0 - High-Level Description |
| 5 | +## R0 — Rôle et flux général |
6 | 6 |
|
7 | | -`solve_descriptive` solves an optimal control problem using symbolic method descriptions (e.g., `:collocation`, `:adnlp`, `:ipopt`). It: |
| 7 | +`solve_descriptive` est le point d'entrée du mode **descriptif** : l'utilisateur passe des symboles (`:collocation`, `:adnlp`, `:ipopt`) et des options à plat (kwargs), et la fonction doit : |
8 | 8 |
|
9 | | -1. Completes partial descriptions using the available methods registry |
10 | | -2. Builds components from the complete description |
11 | | -3. Handles component options and routing |
| 9 | +1. **Compléter** la description partielle en un triplet complet `(discretizer_id, modeler_id, solver_id)` |
| 10 | +2. **Router** les kwargs vers les bonnes stratégies via `CTSolvers.Orchestration.route_all_options` |
| 11 | +3. **Construire** les trois stratégies concrètes avec leurs options routées |
| 12 | +4. **Appeler** la couche canonique (Layer 3) avec les composants complets |
12 | 13 |
|
13 | | -## R1 - Signature and Delegation |
| 14 | +### Contexte d'appel (Layer 1 → Layer 2) |
| 15 | + |
| 16 | +`dispatch.jl` appelle `solve_descriptive` après avoir : |
| 17 | + |
| 18 | +- Normalisé `initial_guess` (via `CTModels.build_initial_guess`) |
| 19 | +- Créé ou extrait le `registry` (`CTSolvers.StrategyRegistry`) |
| 20 | +- Filtré les composants explicites (mode descriptif = aucun composant explicite dans kwargs) |
14 | 21 |
|
15 | 22 | ```julia |
16 | | -# ============================================================================ |
17 | | -# LAYER 2: Descriptive Mode - NO defaults (all values explicit from Layer 1) |
18 | | -# ============================================================================ |
| 23 | +# dispatch.jl (Layer 1 → Layer 2, mode descriptif) |
| 24 | +return solve_descriptive( |
| 25 | + ocp, description...; |
| 26 | + initial_guess = normalized_init, |
| 27 | + display = display, |
| 28 | + registry = registry, |
| 29 | + kwargs... # options à plat pour les stratégies |
| 30 | +) |
| 31 | +``` |
| 32 | + |
| 33 | +Les `kwargs` reçus par `solve_descriptive` sont **exclusivement** des options de stratégies |
| 34 | +(plus éventuellement des `RoutedOption` via `route_to`). Il n'y a **pas** de composants |
| 35 | +explicites (`discretizer=`, `modeler=`, `solver=`) car ceux-ci auraient déclenché |
| 36 | +`ExplicitMode` dans `_explicit_or_descriptive`. |
| 37 | + |
| 38 | +--- |
| 39 | + |
| 40 | +## R1 — Signature et corps de haut niveau |
19 | 41 |
|
| 42 | +```julia |
20 | 43 | function solve_descriptive( |
21 | | - ocp::AbstractModel, |
22 | | - initial_guess::AbstractInitialGuess; # Already normalized by Layer 1 |
23 | | - description::Symbol..., # Symbolic description (may be partial) |
24 | | - discretizer::Union{AbstractDiscretizer, Nothing}, # Optional override |
25 | | - modeler::Union{AbstractNLPModeler, Nothing}, # Optional override |
26 | | - solver::Union{AbstractNLPSolver, Nothing}, # Optional override |
27 | | - display::Bool, # NO default |
28 | | - kwargs... # Component options |
29 | | -)::AbstractSolution |
30 | | - |
31 | | - # 1. Complete the description using available methods registry |
32 | | - complete_description = CTBase.complete( |
33 | | - description...; |
34 | | - descriptions=available_methods() |
35 | | - ) |
36 | | - |
37 | | - # 2. Validate and route component options |
38 | | - _validate_component_options(complete_description, kwargs) |
39 | | - |
40 | | - # 3. Build components from complete description |
41 | | - # Use provided components as overrides if present |
42 | | - components = _build_components_from_description( |
43 | | - complete_description; |
44 | | - discretizer_override=discretizer, |
45 | | - modeler_override=modeler, |
46 | | - solver_override=solver, |
47 | | - kwargs... |
48 | | - ) |
49 | | - |
50 | | - # 4. Call canonical solve with complete components |
| 44 | + ocp::CTModels.AbstractModel, |
| 45 | + description::Symbol...; |
| 46 | + initial_guess::CTModels.AbstractInitialGuess, # normalisé par Layer 1 |
| 47 | + display::Bool, # sans défaut |
| 48 | + registry::CTSolvers.StrategyRegistry, |
| 49 | + kwargs... # options stratégies (plat + route_to) |
| 50 | +)::CTModels.AbstractSolution |
| 51 | + |
| 52 | + # 1. Compléter la description partielle → triplet complet |
| 53 | + complete_description = _complete_description(description) |
| 54 | + |
| 55 | + # 2. Router toutes les options vers les familles de stratégies |
| 56 | + routed = _route_descriptive_options(complete_description, registry, kwargs) |
| 57 | + |
| 58 | + # 3. Construire les trois stratégies avec leurs options routées |
| 59 | + components = _build_components_from_routed(complete_description, registry, routed) |
| 60 | + |
| 61 | + # 4. Appel canonique (Layer 3) |
51 | 62 | return CommonSolve.solve( |
52 | 63 | ocp, initial_guess, |
53 | 64 | components.discretizer, |
54 | 65 | components.modeler, |
55 | 66 | components.solver; |
56 | | - display=display |
| 67 | + display = display |
| 68 | + ) |
| 69 | +end |
| 70 | +``` |
| 71 | + |
| 72 | +**Invariants** : |
| 73 | + |
| 74 | +- Aucun défaut dans cette fonction (tout vient de Layer 1) |
| 75 | +- `_complete_description` réutilise le helper existant dans `helpers/strategy_builders.jl` |
| 76 | +- `_route_descriptive_options` encapsule l'appel à `CTSolvers.Orchestration.route_all_options` |
| 77 | +- `_build_components_from_routed` construit les stratégies via `CTSolvers.Strategies.build_strategy_from_method` |
| 78 | + |
| 79 | +--- |
| 80 | + |
| 81 | +## R2 — Fonctions helpers à implémenter |
| 82 | + |
| 83 | +### R2.1 — `_route_descriptive_options` (nouveau helper) |
| 84 | + |
| 85 | +**Rôle** : Encapsule `CTSolvers.Orchestration.route_all_options` avec les familles et |
| 86 | +`action_defs` propres à OptimalControl. |
| 87 | + |
| 88 | +```julia |
| 89 | +function _route_descriptive_options( |
| 90 | + complete_description::Tuple{Symbol, Symbol, Symbol}, |
| 91 | + registry::CTSolvers.Strategies.StrategyRegistry, |
| 92 | + kwargs |
| 93 | +) |
| 94 | + families = _descriptive_families() |
| 95 | + action_defs = _descriptive_action_defs() |
| 96 | + return CTSolvers.Orchestration.route_all_options( |
| 97 | + complete_description, |
| 98 | + families, |
| 99 | + action_defs, |
| 100 | + NamedTuple(kwargs), |
| 101 | + registry; |
| 102 | + source_mode = :description, |
| 103 | + mode = :strict |
| 104 | + ) |
| 105 | +end |
| 106 | +``` |
| 107 | + |
| 108 | +**Fichier** : `src/helpers/descriptive_routing.jl` |
| 109 | + |
| 110 | +### R2.2 — `_descriptive_families` (nouveau helper, pur) |
| 111 | + |
| 112 | +**Rôle** : Retourne le `NamedTuple` des familles abstraites pour le routage. |
| 113 | + |
| 114 | +```julia |
| 115 | +function _descriptive_families() |
| 116 | + return ( |
| 117 | + discretizer = CTDirect.AbstractDiscretizer, |
| 118 | + modeler = CTSolvers.AbstractNLPModeler, |
| 119 | + solver = CTSolvers.AbstractNLPSolver, |
| 120 | + ) |
| 121 | +end |
| 122 | +``` |
| 123 | + |
| 124 | +**Fichier** : `src/helpers/descriptive_routing.jl` |
| 125 | + |
| 126 | +### R2.3 — `_descriptive_action_defs` (nouveau helper, pur) |
| 127 | + |
| 128 | +**Rôle** : Retourne les `OptionDefinition` pour les options d'action (niveau `solve`), |
| 129 | +c'est-à-dire les options qui ne sont **pas** des options de stratégies. |
| 130 | + |
| 131 | +> **Note** : `display` et `initial_guess` sont gérés par Layer 1 et ne parviennent |
| 132 | +> **pas** dans les `kwargs` de `solve_descriptive`. Les `action_defs` sont donc vides |
| 133 | +> pour l'instant. Ce helper existe pour extensibilité future. |
| 134 | +
|
| 135 | +```julia |
| 136 | +function _descriptive_action_defs() |
| 137 | + return CTSolvers.Options.OptionDefinition[] |
| 138 | +end |
| 139 | +``` |
| 140 | + |
| 141 | +**Fichier** : `src/helpers/descriptive_routing.jl` |
| 142 | + |
| 143 | +### R2.4 — `_build_components_from_routed` (nouveau helper) |
| 144 | + |
| 145 | +**Rôle** : Construit les trois stratégies concrètes à partir du résultat de routage. |
| 146 | + |
| 147 | +```julia |
| 148 | +function _build_components_from_routed( |
| 149 | + complete_description::Tuple{Symbol, Symbol, Symbol}, |
| 150 | + registry::CTSolvers.Strategies.StrategyRegistry, |
| 151 | + routed::NamedTuple |
| 152 | +) |
| 153 | + discretizer = CTSolvers.Strategies.build_strategy_from_method( |
| 154 | + complete_description, |
| 155 | + CTDirect.AbstractDiscretizer, |
| 156 | + registry; |
| 157 | + routed.strategies.discretizer... |
| 158 | + ) |
| 159 | + modeler = CTSolvers.Strategies.build_strategy_from_method( |
| 160 | + complete_description, |
| 161 | + CTSolvers.AbstractNLPModeler, |
| 162 | + registry; |
| 163 | + routed.strategies.modeler... |
| 164 | + ) |
| 165 | + solver = CTSolvers.Strategies.build_strategy_from_method( |
| 166 | + complete_description, |
| 167 | + CTSolvers.AbstractNLPSolver, |
| 168 | + registry; |
| 169 | + routed.strategies.solver... |
57 | 170 | ) |
| 171 | + return (discretizer=discretizer, modeler=modeler, solver=solver) |
58 | 172 | end |
59 | 173 | ``` |
60 | 174 |
|
61 | | -### Functions Called (R2 candidates) |
| 175 | +**Fichier** : `src/helpers/descriptive_routing.jl` |
| 176 | + |
| 177 | +--- |
| 178 | + |
| 179 | +## R3 — Gestion du mode strict vs permissive |
| 180 | + |
| 181 | +### Principe |
| 182 | + |
| 183 | +`route_all_options` accepte un paramètre `mode::Symbol` (`:strict` ou `:permissive`) : |
| 184 | + |
| 185 | +- **`:strict`** (défaut) : toute option inconnue lève une `IncorrectArgument` |
| 186 | +- **`:permissive`** : une option inconnue *avec* `route_to` est acceptée avec un warning |
| 187 | + |
| 188 | +### Règle adoptée pour `solve_descriptive` |
| 189 | + |
| 190 | +**Toujours `:strict`** dans un premier temps. La raison : |
| 191 | + |
| 192 | +- En mode descriptif, l'utilisateur passe des options à plat. Si une option est inconnue, |
| 193 | + c'est presque toujours une faute de frappe → erreur claire préférable. |
| 194 | +- Le mode `:permissive` est utile pour des backends expérimentaux qui ajoutent des options |
| 195 | + non déclarées dans les métadonnées. Ce cas peut être géré plus tard. |
| 196 | + |
| 197 | +### Extension future (mode permissive) |
| 198 | + |
| 199 | +Si l'utilisateur veut passer une option non déclarée à une stratégie spécifique, il peut |
| 200 | +utiliser `route_to` avec un mode permissif. Pour l'activer, il suffirait d'exposer un |
| 201 | +paramètre `validation_mode::Symbol=:strict` dans `solve_descriptive` et de le passer à |
| 202 | +`_route_descriptive_options`. Cette extension est **YAGNI** pour l'instant. |
| 203 | + |
| 204 | +--- |
| 205 | + |
| 206 | +## R4 — Plan d'action |
| 207 | + |
| 208 | +### Étape 1 : Nouveau fichier `src/helpers/descriptive_routing.jl` |
| 209 | + |
| 210 | +Implémenter les 3 helpers purs (R2.2, R2.3, R2.4) et le helper principal (R2.1). |
| 211 | +Ces fonctions sont **directement testables** sans mock complexe. |
| 212 | + |
| 213 | +Ajouter l'include dans `OptimalControl.jl` : |
| 214 | + |
| 215 | +```julia |
| 216 | +include(joinpath(@__DIR__, "helpers", "descriptive_routing.jl")) |
| 217 | +``` |
| 218 | + |
| 219 | +### Étape 2 : Implémenter `solve_descriptive` dans `src/solve/descriptive.jl` |
| 220 | + |
| 221 | +Remplacer le stub `NotImplemented` par l'implémentation réelle (R1). |
| 222 | + |
| 223 | +### Étape 3 : Tests unitaires — `test/suite/solve/test_descriptive_routing.jl` |
| 224 | + |
| 225 | +Tests des helpers purs (sans mock OCP, sans vrai solver) : |
| 226 | + |
| 227 | +- `_descriptive_families` : structure correcte |
| 228 | +- `_descriptive_action_defs` : liste vide |
| 229 | +- `_route_descriptive_options` : auto-routing, disambiguation, erreurs |
| 230 | +- `_build_components_from_routed` : construction avec options routées |
| 231 | + |
| 232 | +Utiliser des mocks de stratégies (pattern des tests CTSolvers) pour éviter les dépendances |
| 233 | +sur les vrais backends. |
| 234 | + |
| 235 | +### Étape 4 : Tests d'intégration — mise à jour de `test/suite/solve/test_orchestration.jl` |
| 236 | + |
| 237 | +Remplacer le test `"solve_descriptive raises NotImplemented"` par des tests réels : |
| 238 | + |
| 239 | +- Descriptive mode complet : `solve(ocp, :collocation, :adnlp, :ipopt; display=false)` |
| 240 | +- Descriptive mode partiel : `solve(ocp, :collocation; display=false)` |
| 241 | +- Descriptive mode vide : `solve(ocp; display=false)` |
| 242 | +- Options routées : `solve(ocp, :collocation, :adnlp, :ipopt; grid_size=10, display=false)` |
| 243 | +- Disambiguation : `solve(ocp, ...; backend=route_to(adnlp=:sparse), display=false)` |
| 244 | +- Erreur option inconnue : `@test_throws IncorrectArgument solve(ocp, ...; bad_opt=1)` |
| 245 | +- Erreur option ambiguë : `@test_throws IncorrectArgument solve(ocp, ...; backend=:sparse)` |
| 246 | + |
| 247 | +--- |
| 248 | + |
| 249 | +## R5 — Fichiers à créer / modifier |
| 250 | + |
| 251 | +| Fichier | Action | |
| 252 | +| --- | --- | |
| 253 | +| `src/helpers/descriptive_routing.jl` | **Créer** — helpers R2.1–R2.4 | |
| 254 | +| `src/solve/descriptive.jl` | **Modifier** — remplacer stub par implémentation R1 | |
| 255 | +| `src/OptimalControl.jl` | **Modifier** — ajouter include de `descriptive_routing.jl` | |
| 256 | +| `test/suite/solve/test_descriptive_routing.jl` | **Créer** — tests unitaires helpers | |
| 257 | +| `test/suite/solve/test_orchestration.jl` | **Modifier** — remplacer test stub, ajouter intégration | |
| 258 | +| `test/runtests.jl` | **Modifier** — enregistrer `test_descriptive_routing` | |
| 259 | + |
| 260 | +--- |
| 261 | + |
| 262 | +## R6 — Points d'attention |
62 | 263 |
|
63 | | -- `CTBase.complete(description...; descriptions=available_methods())` - Complete partial description |
64 | | -- `_validate_component_options(complete_description, kwargs)` - Validate option routing |
65 | | -- `_build_components_from_description(...)` - Build components from symbols |
66 | | -- `CommonSolve.solve(ocp, initial_guess, discretizer, modeler, solver; display)` - Canonical solve (Layer 3) |
| 264 | +1. **`NamedTuple(kwargs)`** : `kwargs` dans `solve_descriptive` est un `Base.Pairs`. |
| 265 | + Il faut le convertir en `NamedTuple` pour `route_all_options`. Utiliser `(; kwargs...)`. |
67 | 266 |
|
68 | | -### Responsibilities |
| 267 | +2. **Splatting des options routées** : `routed.strategies.discretizer` est un `NamedTuple`. |
| 268 | + Le splatting `routed.strategies.discretizer...` passe les options comme kwargs à |
| 269 | + `build_strategy_from_method`. |
69 | 270 |
|
70 | | -1. **Description completion**: Transform partial symbolic description to complete triplet |
71 | | -2. **Option routing**: Route kwargs to appropriate components (discretizer/modeler/solver) |
72 | | -3. **Component construction**: Build concrete components from symbolic description |
73 | | -4. **Override handling**: Allow explicit components to override description-based ones |
74 | | -5. **Canonical solve invocation**: Call Layer 3 with complete components |
| 271 | +3. **`_complete_description` existe déjà** dans `helpers/strategy_builders.jl` — la |
| 272 | + réutiliser directement. |
75 | 273 |
|
76 | | -### Key Design Decisions |
| 274 | +4. **`CTSolvers.Orchestration`** est importé via `src/imports/ctsolvers.jl` mais |
| 275 | + `route_all_options` n'est pas encore importé. Il faudra vérifier si un import |
| 276 | + supplémentaire est nécessaire ou si l'accès qualifié `CTSolvers.Orchestration.route_all_options` |
| 277 | + suffit. |
77 | 278 |
|
78 | | -- **No defaults**: All parameters are explicit (passed from Layer 1) |
79 | | -- **Registry-based**: Uses `available_methods()` registry for completion |
80 | | -- **Flexible description**: Accepts empty, partial, or complete symbolic descriptions |
81 | | -- **Component overrides**: Explicit components take precedence over description |
82 | | -- **Option validation**: Ensures kwargs are routed to correct components |
| 279 | +5. **`CTSolvers.Options.OptionDefinition`** est importé comme `OptionDefinition` dans |
| 280 | + `ctsolvers.jl` — utiliser ce nom court dans les helpers. |
0 commit comments