Rationalize JuMP and comparison test suites: separate generic transcription unit tests from per-problem modeling checks
Les mainteneurs répondent en général sous 1 jour
@ocots y travaille déjà.
Depuis le 8/11/2026.
Évaluation
Cette issue n'a pas encore été évaluée.
Description
Context and Motivation
Since #368, #369, and #370, JuMP models in ext/JuMPModels/ are no longer transcribed problem by problem:
- Each problem file (
ext/JuMPModels/<problem>.jl) only declares aProblemDatastruct (components, bounds, times, dynamics, Lagrange/Mayer costs, path and boundary constraints) and delegates model construction tobuild. - The transcription itself (
ext/JuMPModels/schemes/:data.jl,tableaux.jl,build.jl) and the solution accessors / builder (ext/JuMPModels.jl:state,control,costate,variable,time_grid,solution) are completely generic.
In #401, test/suite/comparison/test_equivalence.jl was updated to reflect this separation: it checks :trapeze on every problem (to verify that each problem's modeling matches OptimalControl), and checks all schemes on a small subset of problems selected by their features (every_scheme).
However, the rest of test/suite/JuMP/ and test/suite/comparison/ still loops over the Cartesian product all problems × all schemes (× all orderings), running hundreds of redundant JuMP model builds, CTDirect discretizations, and max_iter=0 Ipopt/MadNLP solves that test the generic transcription code over and over instead of testing problem-specific modeling.
Goals
- Unit-test the generic JuMP transcription once (
test/suite/JuMP/):- Test
ButcherTableautraits, scheme equations, convergence orders, variable/constraint orderings (:interleaved,:blocks), the model dictionary contract (:scheme,:ordering,:time_grid,:constraints, container lengths),_start!initial guess loading,JuMP.Modelargument forwarding, and error paths (IncorrectArgument,NotImplemented) on small toyProblemDatadefinitions.
- Test
- Test each problem's modeling once per problem (
test/suite/JuMP/andtest/suite/comparison/):- Verify that each problem's
ProblemData, metadata parameters, and default initial guess are well-formed and consistent on a single default build (without looping over schemes/orderings or launching a solver just to read the initial time grid). - Keep
test_smoke.jl(SMOKE_GRID,SMOKE_MAX_ITER) on every problem so every problem is shown to build and run a few solver iterations. - Verify NLP equivalence with
OptimalControl(CTDirect) on every problem using a single configuration (scheme=:trapeze,ordering=:interleaved), plus component name agreement (state_components,control_components,variable_components).
- Verify that each problem's
- Test transcription & solution integration with
OptimalControl.jlon a representative subset of problems (test/suite/comparison/):- Use feature-based selection (covering cost types, fixed/free times, scalar vs. vector state/control/variable per
DIM1, box/path/boundary constraints) to test all4 schemes × 2 orderingsagainstCTDirectand to testOCP.solution(model, pb)and its interpolations (:linearvs.:constant).
- Use feature-based selection (covering cost types, fixed/free times, scalar vs. vector state/control/variable per
- Preserve
cron/androbustness/behavior while sharing common test runners (test/common/):- Keep
test/cron/andtest/robustness/solving all problems on their default grids and emitting the exact report tables and.tsv/.tomlartifacts expected bytools/cron/report.py. - Factor out duplicated test code across
OptimalControlandOptimalControlScalarized(test_problem.jl,test_smoke.jl,test_convergence.jl) into shared helpers intest/common/, while preservingtools/ci/scope.pydependency detection (Repository.includers).
- Keep
Current State and Redundancies (for $P = 22$ problems)
1. test/suite/JuMP/
test_contract.jl:- Current: Builds $P \times 4 \text{ schemes} \times 2 \text{ orderings} = 176$ JuMP models, runs Ipopt with
max_iter=0whenevertfis free to evaluatemodel[:time_grid](), and runs $2P = 44$ error checks (scheme=:gauss_legendre_2,ordering=:random). - Redundancy: Keys like
:scheme,:ordering,:constraints, container sizes ($N+1$, $N$, $M$), and unknown scheme/ordering errors are determined solely byschemes/build.jl, not by<problem>.jl.
- Current: Builds $P \times 4 \text{ schemes} \times 2 \text{ orderings} = 176$ JuMP models, runs Ipopt with
test_kwargs.jl:- Current: Builds $3P = 66$ JuMP models to verify that
Ipopt.Optimizerandadd_bridges=trueare forwarded toJuMP.Modeland that passinginit = 2 * guessdoublesJuMP.start_value. - Redundancy: Keyword forwarding and
_start!are generic functions inschemes/build.jl. Only checking that the problem's default metadatainitpopulates all start values is problem-specific.
- Current: Builds $3P = 66$ JuMP models to verify that
test_tableaux.jl,test_build.jl,test_signs.jl,test_draft.jl,test_methods.jl,test_smoke.jl:- Already targeted and non-redundant.
2. test/suite/comparison/
test_equivalence.jl&test/common/equivalence.jl:- Current: Tests
:trapezeon every problem and all 4 schemes onselected = every_scheme(both, features). However,Equivalence.test_jumpstill loops over bothORDERINGS = (:interleaved, :blocks)for every problem, even though:blocksonly permutes variable/constraint creation order inschemes/build.jl.
- Current: Tests
test_start.jl:- Current: Loops over $P \times 4 \text{ schemes} = 88$ pairs, building the JuMP model and calling
OptimalControl.discretize+OptimalControl.nlp_model(..., ADNLP())to compareJuMP.start_valuewithnlp.meta.x0. - Redundancy:
Equivalence.compareandconsistent_nlpsintest_equivalence.jlalready comparemeta.x0(:x0) on every problem for:trapezeand on the representative subset for all 4 schemes.
- Current: Loops over $P \times 4 \text{ schemes} = 88$ pairs, building the JuMP model and calling
test_init.jl(test/common/comparison.jl):- Current: Runs Ipopt/MadNLP with
max_iter=0onSMALL_GRID = 50for every problem acrossJuMP,OptimalControl, andOptimalControlScalarized($3P = 66$ solves) to comparenvar,ncon, component names, time grid, initial trajectories, and initial objective. - Redundancy:
test_equivalence.jlalready checksnvar,ncon, bounds,x0, objective, constraints, gradient, Jacobian, and Hessian atx0to1e-10without invoking any solver. Only the component names (state_components,control_components,variable_components) are specific totest_init.jl, and they can be compared directly on the models without solving.
- Current: Runs Ipopt/MadNLP with
test_solution.jl:- Current: Loops over $P \times 4 \text{ schemes} = 88$ JuMP models on
SMALL_GRID = 50, running Ipopt withmax_iter=0on each to check thatOCP.solution(model, pb)andTestStatus.trajectorymatch the JuMP accessors, plus a full solve onbeamfor the 4 schemes. - Redundancy:
OCP.solution(model, pb)and the JuMP accessors inext/JuMPModels.jlare completely generic. Testing them on a representative subset of problems (covering scalar vs. vector state/control and $0, 1, >1$ variables) across the 4 schemes is sufficient.
- Current: Loops over $P \times 4 \text{ schemes} = 88$ JuMP models on
3. Duplicated files across OptimalControl and OptimalControlScalarized
test/suite/OptimalControl/test_problem.jlandtest/suite/OptimalControlScalarized/test_problem.jlare identical except for the backend constant.test/suite/OptimalControl/test_smoke.jlandtest/suite/OptimalControlScalarized/test_smoke.jlare identical except for the backend constant.test/cron/OptimalControl/test_convergence.jlandtest/cron/OptimalControlScalarized/test_convergence.jlare identical except for the backend constant.
Proposed Solutions
1. Move generic contract and _start! checks to test/suite/JuMP/test_build.jl
- In
test_build.jl:- Check the full model dictionary contract (
:scheme,:ordering,:time_grid,:constraints, component keys and container lengths $N+1$, $M$, $N$, $1$) across all4 schemes × 2 orderingsonFAKE, plus theIncorrectArgumentthrows on unknownschemeandordering. - Unit-test
_start!(constant, vector, time-dependent function, custominitoverride, free final time) andJuMP.Modelargument/keyword forwarding (Ipopt.Optimizer,add_bridges).
- Check the full model dictionary contract (
- Simplify
test_contract.jl(or mergetest_kwargs.jlinto it):- Build one model per problem (
grid_size=2, default:trapezeand:interleaved). - Check problem-specific modeling invariants: non-empty component names,
length(costate_components) == length(state_components), all variables have a finiteJuMP.start_value, and the initial time grid evaluated at the start values is sorted withT[1] < T[end](no Ipoptmax_iter=0call needed).
- Build one model per problem (
2. Streamline test/suite/comparison/
- Shared representative problem selector:
- Extract the feature-based representative problem selection (
every_schemeintest_equivalence.jl, extended if needed to ensure scalar/vector state, control, and variable dimensions0, 1, >1are covered) into a shared helper intest/common/.
- Extract the feature-based representative problem selection (
test_equivalence.jl&test/common/equivalence.jl:- On every problem: test
(scheme=:trapeze, ordering=:interleaved)againstCTDirect(ADNLPandExa), which directly checks problem modeling equivalence (includingmeta.x0). - On the representative subset: test all
4 schemes × 2 orderings.
- On every problem: test
test_features.jl:- In addition to comparing
features(vector) == features(scalarized), check that component names (state_components,control_components,variable_components) and dimensions agree betweenJuMP,OptimalControl, andOptimalControlScalarizedfor every problem (no solver call).
- In addition to comparing
test_start.jlandtest_init.jl:- Remove
test_start.jl(sincemeta.x0equivalence is already checked bytest_equivalence.jlon every problem for:trapezeand on the representative subset for all 4 schemes, and_start!is unit-tested intest_build.jl). - Remove
test_init.jl(since NLP evaluation atx0is checked to1e-10bytest_equivalence.jland component names are checked bytest_features.jl). KeepComparison.within/Comparison.within_L2(used bytest_solution.jl).
- Remove
test_solution.jl:- Run
check_solution(problem, scheme)across the 4 schemes on the representative subset of problems (plusbeamsolved againstOptimalControl), instead of all $P \times 4$ pairs.
- Run
3. Factorize shared OptimalControl / OptimalControlScalarized runners
- Extract shared helpers in
test/common/fortest_problem,test_smoke, andtest_convergencesoOptimalControlandOptimalControlScalarizedcall a single implementation parameterized by the backend, without altering report names,TestStatus.result!payloads, ortools/ci/scope.pyscoping rules.
- Langage dominant
- Julia
- Étoiles
- 13
- Forks
- 1
- Merge moyen
- 3 h 36 min
- PR mergées (30 j)
- 57
Préparer son environnement
- Aucun Dockerfile ni fichier Docker Compose
- Aucun modèle de pull request
- Lire le guide de contribution
Par où commencer
- Lisez l'issue en entier, puis le guide de contribution du projet.
- Signalez en commentaire que vous la prenez — cela évite que deux personnes fassent le même travail.
- Forkez le dépôt et travaillez sur une branche.
- Ouvrez une pull request qui référence le numéro de l'issue.
Autres issues de control-toolbox/OptimalControlProblems.jl
-
Share the GPU smoke test between OptimalControl and OptimalControlScalarizedPeut-être pris @ocots l’a pris aujourd’hui. Ouverte
control-toolbox/OptimalControlProblems.jl#445 · 1 personne assignée ·
Les mainteneurs répondent en général sous 1 jour
-
[Cron/Doc] ModelingToolkit convergence cron, trajectories, and documentation (#308)Peut-être pris @ocots l’a pris il y a 2 jours. Ouvertedocumentation feature
control-toolbox/OptimalControlProblems.jl#439 · 1 personne assignée ·
Les mainteneurs répondent en général sous 1 jour
-
cron-convergence
Difficulté 4/5 3-5 jours Accessibilité débutants 32/100
control-toolbox/OptimalControlProblems.jl#436 · 3 commentaires ·
Les mainteneurs répondent en général sous 1 jour
-
[Cron] cron-failureOuvertecron-failure
Difficulté 4/5 3-5 jours Accessibilité débutants 35/100
control-toolbox/OptimalControlProblems.jl#435 · 5 commentaires ·
Les mainteneurs répondent en général sous 1 jour
-
[Dev] Expose the duals of the JuMP path, boundary and box constraints, aligned with signs.mdPeut-être pris @ocots l’a pris il y a 2 jours. Ouverte
control-toolbox/OptimalControlProblems.jl#430 · 1 personne assignée ·
Les mainteneurs répondent en général sous 1 jour
Toutes les issues de control-toolbox/OptimalControlProblems.jl
Issues similaires
-
documentation priority: high
Difficulté 2/5 1-3 heures Accessibilité débutants 78/100
Les mainteneurs répondent en général sous 1 jour
-
Difficulté 1/5 Moins d'une heure Accessibilité débutants 90/100
epiforecasts/BVDOutbreakSize#1260 ·
Les mainteneurs répondent en général sous 1 jour
-
Difficulté 2/5 1-3 heures Accessibilité débutants 76/100
TuringLang/Turing.jl#2917 ·
Les mainteneurs répondent en général sous 1 jour
-
Difficulté 1/5 Moins d'une heure Accessibilité débutants 88/100
-
Validate UserDemand return_factorPeut-être pris Une pull request liée à cette issue est ouverte ou déjà fusionnée. Ouverte
Difficulté 2/5 1-3 heures Accessibilité débutants 65/100
Les mainteneurs répondent en général sous 2 jours