Skip to content

feat: Add Uno solver integration with comprehensive tests#103

Merged
ocots merged 4 commits intomainfrom
uno
Mar 17, 2026
Merged

feat: Add Uno solver integration with comprehensive tests#103
ocots merged 4 commits intomainfrom
uno

Conversation

@ocots
Copy link
Copy Markdown
Member

@ocots ocots commented Mar 16, 2026

@jbcaillau @PierreMartinon @joseph-gergaud Uno solver

  • Add Uno solver with CPU-only support and ADNLP modeler compatibility
  • Implement complete metadata with validation for all Uno options
  • Add comprehensive test suite covering:
    • Type hierarchy and contracts (test_solver_types.jl)
    • Extension stubs and error handling (test_extension_stubs.jl)
    • Parameter validation (test_uno_parameter_validation.jl)
    • Type stability (test_type_stability.jl)
    • Module exports and interface compliance (test_solvers.jl)
    • Integration with validation modes (test_comprehensive_validation.jl)
    • Real strategy mode tests (test_real_strategies_mode.jl)
    • Route-to functionality (test_route_to_comprehensive.jl)
    • Full extension tests with problem solving (test_uno_extension.jl)

Key features:

  • Uno supports presets (ipopt, filtersqp) and full option set
  • CPU execution only (GPU parameter rejected at compile time)
  • ADNLP modeler compatibility (no Exa support)
  • Comprehensive option validation wi
  • Add Uno solver with CPU-only support and ADNLP modeler compatibility AP- Implement complete metadata with validation for all Uno options
  • Arc- Add comprehensive test suite covering:
    • Type hierarchy and l * Type hierarchy and contracts (test_oS * Extension stub

- Add Uno solver with CPU-only support and ADNLP modeler compatibility
- Implement complete metadata with validation for all Uno options
- Add comprehensive test suite covering:
  * Type hierarchy and contracts (test_solver_types.jl)
  * Extension stubs and error handling (test_extension_stubs.jl)
  * Parameter validation (test_uno_parameter_validation.jl)
  * Type stability (test_type_stability.jl)
  * Module exports and interface compliance (test_solvers.jl)
  * Integration with validation modes (test_comprehensive_validation.jl)
  * Real strategy mode tests (test_real_strategies_mode.jl)
  * Route-to functionality (test_route_to_comprehensive.jl)
  * Full extension tests with problem solving (test_uno_extension.jl)

Key features:
- Uno supports presets (ipopt, filtersqp) and full option set
- CPU execution only (GPU parameter rejected at compile time)
- ADNLP modeler compatibility (no Exa support)
- Comprehensive option validation wi
- Add Uno solver with CPU-only support and ADNLP modeler compatibility AP- Implement complete metadata with validation for all Uno options
- Arc- Add comprehensive test suite covering:
  * Type hierarchy and l   * Type hierarchy and contracts (test_oS  * Extension stub
ocots added 3 commits March 16, 2026 23:07
…sion

- Add UnoSolver to test targets in Project.toml
- Import NLPModels in CTSolversUno extension to fix precompilation error

This fixes the missing dependency error when running Uno tests.
- Use raw UnoSolver constants for direct solver calls (solver(nlp))
- Use Symbol() wrapper for CommonSolve.solve calls (goes through extract_solver_infos)
- Fix all status comparisons in test_uno_extension.jl

This fixes 8 test failures, reducing from 53 pass/8 fail/11 error to 61 pass/0 fail/11 error.
- Add SolverCore import and status mapping function in ext/CTSolversUno.jl
- Map Uno statuses to SolverCore symbols (UNO_FEASIBLE_KKT_POINT → :first_order, etc.)
- Convert UnoSolver.Statistics to GenericExecutionStats in solve_with_uno
- Update all tests to use GenericExecutionStats fields (.status, .solution, .objective)
- Skip Initial Guess tests (Uno performs 1 iteration even with max_iterations=0)
- Fix integration tests: add UnoSolver import and use correct canonical option names
- All tests passing: 192 passed, 2 intentionally skipped
@ocots ocots merged commit 72ed9d9 into main Mar 17, 2026
7 checks passed
@ocots ocots deleted the uno branch March 17, 2026 10:25
@ocots
Copy link
Copy Markdown
Member Author

ocots commented Mar 17, 2026

@amontoison @cvanaret Does Uno work with ExaModels? Or just ADNLPModels?

@amontoison
Copy link
Copy Markdown

amontoison commented Mar 17, 2026

@ocots Uno works with any AbstractNLPModel from.NLPModels.jl.
ExaModel, CUTEstModel, ADNLPModel, AMPLModel, ...

Comment on lines +350 to +426
function _uno_status_to_solvercore(optimization_status::Cint, solution_status::Cint)::Symbol
if optimization_status == UnoSolver.UNO_ITERATION_LIMIT
return :max_iter
elseif optimization_status == UnoSolver.UNO_TIME_LIMIT
return :max_time
elseif optimization_status == UnoSolver.UNO_EVALUATION_ERROR
return :exception
elseif optimization_status == UnoSolver.UNO_ALGORITHMIC_ERROR
return :exception
else # UNO_SUCCESS
if solution_status == UnoSolver.UNO_FEASIBLE_KKT_POINT
return :first_order
elseif solution_status == UnoSolver.UNO_FEASIBLE_FJ_POINT
return :acceptable
elseif solution_status == UnoSolver.UNO_INFEASIBLE_STATIONARY_POINT
return :infeasible
elseif solution_status == UnoSolver.UNO_FEASIBLE_SMALL_STEP
return :small_step
elseif solution_status == UnoSolver.UNO_INFEASIBLE_SMALL_STEP
return :small_step
else # UNO_UNBOUNDED
return :unbounded
end
end
end

"""
$(TYPEDSIGNATURES)

Convert UnoSolver.Statistics to SolverCore.GenericExecutionStats.

This conversion allows Uno to integrate seamlessly with the CTSolvers pipeline
which expects SolverCore.AbstractExecutionStats.

# Arguments
- `nlp::NLPModels.AbstractNLPModel`: The NLP model (needed for GenericExecutionStats constructor)
- `uno_stats::UnoSolver.Statistics`: Uno solver execution statistics

# Returns
- `SolverCore.GenericExecutionStats`: Converted statistics compatible with CTSolvers

# Field Mapping
- `status` ← mapped from `optimization_status` and `solution_status`
- `solution` ← `primal_solution`
- `objective` ← `solution_objective`
- `dual_feas` ← `solution_stationarity`
- `primal_feas` ← `solution_primal_feasibility`
- `multipliers` ← `constraint_dual_solution`
- `multipliers_L` ← `lower_bound_dual_solution`
- `multipliers_U` ← `upper_bound_dual_solution`
- `iter` ← `number_iterations`
- `elapsed_time` ← `cpu_time`
"""
function _uno_to_generic_stats(nlp::NLPModels.AbstractNLPModel, uno_stats::UnoSolver.Statistics)::SolverCore.GenericExecutionStats
# Map Uno status to SolverCore status
status = _uno_status_to_solvercore(
uno_stats.optimization_status,
uno_stats.solution_status
)

# Create GenericExecutionStats with all fields marked as reliable
stats = SolverCore.GenericExecutionStats(
nlp;
status=status,
solution=uno_stats.primal_solution,
objective=uno_stats.solution_objective,
dual_feas=uno_stats.solution_stationarity,
primal_feas=uno_stats.solution_primal_feasibility,
multipliers=uno_stats.constraint_dual_solution,
multipliers_L=uno_stats.lower_bound_dual_solution,
multipliers_U=uno_stats.upper_bound_dual_solution,
iter=Int(uno_stats.number_iterations),
elapsed_time=uno_stats.cpu_time
)

return stats
end
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@amontoison @cvanaret I have started to add Uno solver. It is easier for me to deal with SolverCore.AbstractExecutionStats hence I have made this conversion system. Do you think it could be placed directly in UnoSolver.jl?

ps: I am not sure it is correct.

Copy link
Copy Markdown

@amontoison amontoison Mar 17, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, I think we should add a option to decide what is the type of the returned statistics (UnoSolver.Statistics or SolverCore.AbstractExecutionStats).

Copy link
Copy Markdown

@amontoison amontoison Mar 17, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@ocots Can you open a PR with this converter in UnoSolver.jl ?
I can't open new PR now until I have approvals for working on it.

I am fine with adding SolverCore.jl as a dependency.
I would like to use a GenericExecutionStats instead of a Statistics on the long run but we need more attributes in GenericExecutionStats before.

@cvanaret will maybe open a PR in SolverCore.jl :)

Comment on lines +290 to +302
Test.@testset "Initial Guess - max_iterations=0" begin
Test.@testset "Rosenbrock" verbose=VERBOSE showtiming=SHOWTIMING begin
Test.@testset "Modelers.ADNLP" verbose=VERBOSE showtiming=SHOWTIMING begin
Test.@test_skip "Uno performs 1 iteration even with max_iterations=0"
end
end

Test.@testset "Elec" verbose=VERBOSE showtiming=SHOWTIMING begin
Test.@testset "Modelers.ADNLP" verbose=VERBOSE showtiming=SHOWTIMING begin
Test.@test_skip "Uno performs 1 iteration even with max_iterations=0"
end
end
end
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@amontoison @cvanaret I wanted to add some tests with 0 iterations, but it seems that when max_iterations=0, Uno performs 1 iteration.

For us, it would be useful to allow 0 iterations in order to build an optimal control solution directly from an initial guess. Indeed, the input (the initial guess) and the output (the optimal control solution) are not of the same type.

Copy link
Copy Markdown

@cvanaret cvanaret Mar 17, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Indeed, this special case is not detected. I can patch it real quick.
cvanaret/Uno#597

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It would be great!

Soon we will make a new release of OC with Uno and relaunch all our benchmarks 🤞

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's not going to be that quick because I have to do Uno and Uno_jll releases first 😅

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is fixed in UnoSolver.jl v0.2.8

Copy link
Copy Markdown

@cvanaret cvanaret Mar 18, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@ocots apparently you deleted your comment. Your function _uno_status_to_solvercore is missing the test solution_status == UNO_NOT_OPTIMAL. But I did forget to set the status to UNO_ITERATION_LIMIT 😆

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@cvanaret I tried it, and it works—thanks!

However, even though the number of iterations is effectively 0, the status is not UNO_ITERATION_LIMIT but UNO STATUS: optimization_status = UNO_SUCCESS, solution_status = UNO_NOT_OPTIMAL. Is that expected?

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in cvanaret/Uno#610

@ocots
Copy link
Copy Markdown
Member Author

ocots commented Mar 17, 2026

@ocots Uno works with any AbstractNLPModel from.NLPModels.jl. ExaModel, CUTEstModel, ADNLPModel, AMPLModel, ...

Tested with ADNLP and Exa: https://github.com/control-toolbox/OptimalControl.jl/blob/4176281a4709af90454ee8595ce4dc3c5036ed7c/src/helpers/methods.jl#L42-L60

Some tests from https://github.com/control-toolbox/OptimalControl.jl/blob/4176281a4709af90454ee8595ce4dc3c5036ed7c/test/suite/solve/test_canonical.jl#L52-L152 (not benchmarks since no "compilation" call):

Capture d’écran 2026-03-17 à 14 02 14

@cvanaret
Copy link
Copy Markdown

Nice! Is that with Uno-ipopt or Uno-filtersqp? Based on feedback here, I would recommend using the filtersqp preset. It nicely exploits negative curvature and is way more robust than the ipopt preset.

@ocots
Copy link
Copy Markdown
Member Author

ocots commented Mar 17, 2026

Nice! Is that with Uno-ipopt or Uno-filtersqp? Based on feedback here, I would recommend using the filtersqp preset. It nicely exploits negative curvature and is way more robust than the ipopt preset.

It is with ipopt since we are more familiar with it. I plan to do some benchmarking to choose the default.

@ocots ocots mentioned this pull request Apr 1, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants