From 81c61c5288184a2e97234235fb442c82b2501795 Mon Sep 17 00:00:00 2001 From: Olivier Cots Date: Sun, 28 Jun 2026 22:52:24 +0200 Subject: [PATCH 1/2] refactor: co-locate each strategy's contract with its abstract type Behavior-preserving reorganization so every strategy family keeps its contract stub next to its abstract type, mirroring the existing DOCP/contract.jl precedent. - Modelers: new contract.jl holds the canonical build_model / build_solution NotImplemented stubs, narrowed to ::AbstractNLPModeler (the documented TYPEDSIGNATURES stubs live with the modeler). Optimization keeps bare generic declarations (building.jl) so the binding exists and the re-export is valid. - Solvers: split common_solve_api.jl into contract.jl (mid-level solve stub + __display) and orchestration.jl (high-level composition). - DOCP: merge types.jl + accessors.jl into discretized_model.jl; rename building.jl to conveniences.jl. - docs/api_reference.jl: resync the per-submodule source-file lists (also fixes pre-existing staleness: add built_model.jl, solver_info.jl under Solvers, drop removed contract_impl/builders/common_solve_api). - test: FakeModeler now subtypes AbstractNLPModeler so an unimplemented (problem, modeler) pair still yields NotImplemented (not MethodError). Only semantic change: the modeler stub narrows from an untyped 3rd arg to ::AbstractNLPModeler. CTSolvers suites + Aqua green; CTDirect needs no source change (same CTSolvers.* functions, only method locations moved). Co-Authored-By: Claude Opus 4.8 --- docs/api_reference.jl | 18 ++-- docs/src/guides/implementing_a_modeler.md | 22 +++-- src/DOCP/DOCP.jl | 5 +- src/DOCP/accessors.jl | 23 ----- src/DOCP/{building.jl => conveniences.jl} | 6 +- src/DOCP/{types.jl => discretized_model.jl} | 26 +++++- src/Modelers/Modelers.jl | 1 + src/Modelers/contract.jl | 95 +++++++++++++++++++ src/Optimization/Optimization.jl | 16 ++-- src/Optimization/building.jl | 87 +++--------------- src/Solvers/Solvers.jl | 3 +- src/Solvers/common_solve_api.jl | 97 -------------------- src/Solvers/contract.jl | 51 ++++++++++ src/Solvers/orchestration.jl | 52 +++++++++++ test/suite/optimization/test_optimization.jl | 6 +- 15 files changed, 280 insertions(+), 228 deletions(-) delete mode 100644 src/DOCP/accessors.jl rename src/DOCP/{building.jl => conveniences.jl} (92%) rename src/DOCP/{types.jl => discretized_model.jl} (67%) create mode 100644 src/Modelers/contract.jl delete mode 100644 src/Solvers/common_solve_api.jl create mode 100644 src/Solvers/contract.jl create mode 100644 src/Solvers/orchestration.jl diff --git a/docs/api_reference.jl b/docs/api_reference.jl index 6cad1a63..ac6b4a32 100644 --- a/docs/api_reference.jl +++ b/docs/api_reference.jl @@ -46,10 +46,10 @@ function generate_api_reference(src_dir::String, ext_dir::String) primary_modules=[ CTSolvers.DOCP => src( joinpath("DOCP", "DOCP.jl"), - joinpath("DOCP", "accessors.jl"), - joinpath("DOCP", "building.jl"), - joinpath("DOCP", "contract_impl.jl"), - joinpath("DOCP", "types.jl"), + joinpath("DOCP", "abstract_discretizer.jl"), + joinpath("DOCP", "discretized_model.jl"), + joinpath("DOCP", "contract.jl"), + joinpath("DOCP", "conveniences.jl"), ), ], exclude=EXCLUDE_SYMBOLS, @@ -69,6 +69,7 @@ function generate_api_reference(src_dir::String, ext_dir::String) CTSolvers.Modelers => src( joinpath("Modelers", "Modelers.jl"), joinpath("Modelers", "abstract_modeler.jl"), + joinpath("Modelers", "contract.jl"), joinpath("Modelers", "adnlp.jl"), joinpath("Modelers", "exa.jl"), joinpath("Modelers", "validation.jl"), @@ -91,10 +92,7 @@ function generate_api_reference(src_dir::String, ext_dir::String) CTSolvers.Optimization => src( joinpath("Optimization", "Optimization.jl"), joinpath("Optimization", "abstract_types.jl"), - joinpath("Optimization", "builders.jl"), - joinpath("Optimization", "building.jl"), - joinpath("Optimization", "contract.jl"), - joinpath("Optimization", "solver_info.jl"), + joinpath("Optimization", "built_model.jl"), ), ], exclude=EXCLUDE_SYMBOLS, @@ -114,7 +112,9 @@ function generate_api_reference(src_dir::String, ext_dir::String) CTSolvers.Solvers => src( joinpath("Solvers", "Solvers.jl"), joinpath("Solvers", "abstract_solver.jl"), - joinpath("Solvers", "common_solve_api.jl"), + joinpath("Solvers", "contract.jl"), + joinpath("Solvers", "orchestration.jl"), + joinpath("Solvers", "solver_info.jl"), joinpath("Solvers", "ipopt.jl"), joinpath("Solvers", "knitro.jl"), joinpath("Solvers", "madncl.jl"), diff --git a/docs/src/guides/implementing_a_modeler.md b/docs/src/guides/implementing_a_modeler.md index 6c3baca8..9d135919 100644 --- a/docs/src/guides/implementing_a_modeler.md +++ b/docs/src/guides/implementing_a_modeler.md @@ -182,17 +182,27 @@ end ## Integration with build_model / build_solution -The `Optimization` module provides two generic functions that delegate to the modeler's callables: +The `Optimization` module *owns* two generic functions, `build_model` and +`build_solution`. Their canonical `NotImplemented` contract stubs — the modeler +contract — live in the `Modelers` module, typed on `AbstractNLPModeler`; concrete +methods are provided by the package supplying the problem (e.g. CTDirect), +dispatched on the concrete `(problem, modeler)` pair. `build_model` returns an +`Optimization.BuiltModel` (the NLP plus an immutable build-time cache), and +`build_solution` dispatches on that bundle: ```julia -# In src/Optimization/building.jl +# In src/Modelers/contract.jl -function build_model(prob, initial_guess, modeler) - return modeler(prob, initial_guess) +function Optimization.build_model( + prob::Optimization.AbstractOptimizationProblem, initial_guess, modeler::AbstractNLPModeler +) + throw(Exceptions.NotImplemented(...)) # implemented per (problem, modeler) downstream end -function build_solution(prob, model_solution, modeler) - return modeler(prob, model_solution) +function Optimization.build_solution( + built::Optimization.BuiltModel, model_solution, modeler::AbstractNLPModeler +) + throw(Exceptions.NotImplemented(...)) end ``` diff --git a/src/DOCP/DOCP.jl b/src/DOCP/DOCP.jl index e2d29752..51b4965d 100644 --- a/src/DOCP/DOCP.jl +++ b/src/DOCP/DOCP.jl @@ -31,10 +31,9 @@ using ..CTSolvers.Modelers # Submodules include(joinpath(@__DIR__, "abstract_discretizer.jl")) -include(joinpath(@__DIR__, "types.jl")) +include(joinpath(@__DIR__, "discretized_model.jl")) include(joinpath(@__DIR__, "contract.jl")) -include(joinpath(@__DIR__, "accessors.jl")) -include(joinpath(@__DIR__, "building.jl")) +include(joinpath(@__DIR__, "conveniences.jl")) # Public API export AbstractDiscretizer diff --git a/src/DOCP/accessors.jl b/src/DOCP/accessors.jl deleted file mode 100644 index 2dac47f3..00000000 --- a/src/DOCP/accessors.jl +++ /dev/null @@ -1,23 +0,0 @@ -# DOCP accessors -# -# Accessor functions for `DiscretizedModel`. - -""" -$(TYPEDSIGNATURES) - -Extract the original optimal control problem from a discretized problem. - -# Arguments -- `docp::DiscretizedModel`: The discretized optimal control problem - -# Returns -- The original optimal control problem - -# Example -```julia -ocp = ocp_model(docp) -``` - -See also: `DiscretizedModel` -""" -ocp_model(docp::DiscretizedModel) = docp.ocp diff --git a/src/DOCP/building.jl b/src/DOCP/conveniences.jl similarity index 92% rename from src/DOCP/building.jl rename to src/DOCP/conveniences.jl index 04bccc8e..c64b2bb7 100644 --- a/src/DOCP/building.jl +++ b/src/DOCP/conveniences.jl @@ -1,7 +1,7 @@ -# DOCP building API +# DOCP conveniences # -# Convenience wrappers for building NLP models and OCP solutions from a -# `DiscretizedModel`. +# Convenience wrappers (`nlp_model`, `ocp_solution`) over the modeler contract, +# specialized for `DiscretizedModel` / its `BuiltModel`. """ $(TYPEDSIGNATURES) diff --git a/src/DOCP/types.jl b/src/DOCP/discretized_model.jl similarity index 67% rename from src/DOCP/types.jl rename to src/DOCP/discretized_model.jl index d12e9b1c..82fbedca 100644 --- a/src/DOCP/types.jl +++ b/src/DOCP/discretized_model.jl @@ -1,7 +1,7 @@ -# DOCP types +# DiscretizedModel # -# Defines the `DiscretizedModel` type: a thin pairing of an optimal control -# problem with the discretizer that produced it, plus an optional backend cache. +# The `DiscretizedModel` type — a thin pairing of an optimal control problem with the +# discretizer that produced it, plus a backend cache — and its accessors. """ $(TYPEDEF) @@ -36,3 +36,23 @@ struct DiscretizedModel{ discretizer::TD cache::TC end + +""" +$(TYPEDSIGNATURES) + +Extract the original optimal control problem from a discretized problem. + +# Arguments +- `docp::DiscretizedModel`: The discretized optimal control problem + +# Returns +- The original optimal control problem + +# Example +```julia +ocp = ocp_model(docp) +``` + +See also: `DiscretizedModel` +""" +ocp_model(docp::DiscretizedModel) = docp.ocp diff --git a/src/Modelers/Modelers.jl b/src/Modelers/Modelers.jl index ee6e916e..6b0b019f 100644 --- a/src/Modelers/Modelers.jl +++ b/src/Modelers/Modelers.jl @@ -31,6 +31,7 @@ using ..Optimization # Submodules include(joinpath(@__DIR__, "abstract_modeler.jl")) +include(joinpath(@__DIR__, "contract.jl")) include(joinpath(@__DIR__, "validation.jl")) include(joinpath(@__DIR__, "adnlp.jl")) include(joinpath(@__DIR__, "exa.jl")) diff --git a/src/Modelers/contract.jl b/src/Modelers/contract.jl new file mode 100644 index 00000000..3a4ce495 --- /dev/null +++ b/src/Modelers/contract.jl @@ -0,0 +1,95 @@ +# Modeler contract +# +# Canonical contract for modeler strategies: the generic `Optimization.build_model` +# / `Optimization.build_solution` methods, dispatched on `AbstractNLPModeler`. These +# are `NotImplemented` stubs; the package providing the concrete problem implements +# them by multiple dispatch on the concrete `(problem, modeler)` pair (e.g. CTDirect +# for `(DiscretizedModel, ADNLP/Exa)`). Defining these methods here also creates the +# `Optimization.build_model` / `Optimization.build_solution` generic functions. + +""" +$(TYPEDSIGNATURES) + +Build a [`Optimization.BuiltModel`](@ref) from an optimization problem using the +specified modeler. + +This is the modeler contract: the modeler converts the problem into a backend NLP +model. The returned [`Optimization.BuiltModel`](@ref) carries the backend NLP model +together with any immutable build-time auxiliary needed later by +[`Optimization.build_solution`](@ref). + +# Contract +Must be implemented in the package providing the concrete problem, dispatching on the +concrete `(problem, modeler)` pair, e.g. +`Optimization.build_model(prob::DiscretizedModel, init, ::ADNLP)` in CTDirect. This +generic stub throws [`CTBase.Exceptions.NotImplemented`](@extref). + +# Arguments +- `prob::Optimization.AbstractOptimizationProblem`: The optimization problem. +- `initial_guess`: Initial guess for the NLP solver. +- `modeler::AbstractNLPModeler`: The modeler strategy (e.g. `ADNLP`, `Exa`). + +# Returns +- A [`Optimization.BuiltModel`](@ref) bundling the backend NLP model and its build-time cache. + +# Throws +- [`CTBase.Exceptions.NotImplemented`](@extref): when no method exists for this + `(problem, modeler)` pair. + +See also: `Optimization.build_solution`, `Optimization.BuiltModel`. +""" +function Optimization.build_model( + prob::Optimization.AbstractOptimizationProblem, initial_guess, modeler::AbstractNLPModeler +) + throw( + Exceptions.NotImplemented( + "Model building not implemented"; + required_method="Optimization.build_model(prob::$(typeof(prob)), initial_guess, modeler::$(typeof(modeler)))", + suggestion="Implement build_model for this (problem, modeler) pair in the package providing the problem", + context="Modelers.build_model - required method implementation", + ), + ) +end + +""" +$(TYPEDSIGNATURES) + +Build a problem-level solution from NLP execution statistics using the specified +modeler. + +This is the modeler contract: it reconstructs the solution from the +[`Optimization.BuiltModel`](@ref) returned by [`Optimization.build_model`](@ref) +(which carries the problem and the immutable build-time cache) and the solver output. + +# Contract +Must be implemented in the package providing the concrete problem, dispatching on the +concrete `(built, modeler)` pair, e.g. +`Optimization.build_solution(built::BuiltModel{<:DiscretizedModel}, stats, ::ADNLP)` +in CTDirect. This generic stub throws [`CTBase.Exceptions.NotImplemented`](@extref). + +# Arguments +- `built::Optimization.BuiltModel`: The bundle returned by `build_model`. +- `model_solution`: NLP solver output (execution statistics). +- `modeler::AbstractNLPModeler`: The modeler strategy used for building. + +# Returns +- A solution object appropriate for the problem type. + +# Throws +- [`CTBase.Exceptions.NotImplemented`](@extref): when no method exists for this + `(problem, modeler)` pair. + +See also: `Optimization.build_model`, `Optimization.BuiltModel`. +""" +function Optimization.build_solution( + built::Optimization.BuiltModel, model_solution, modeler::AbstractNLPModeler +) + throw( + Exceptions.NotImplemented( + "Solution building not implemented"; + required_method="Optimization.build_solution(built::BuiltModel{$(typeof(built.problem))}, model_solution, modeler::$(typeof(modeler)))", + suggestion="Implement build_solution for this (problem, modeler) pair in the package providing the problem", + context="Modelers.build_solution - required method implementation", + ), + ) +end diff --git a/src/Optimization/Optimization.jl b/src/Optimization/Optimization.jl index 8870d9c1..e694985e 100644 --- a/src/Optimization/Optimization.jl +++ b/src/Optimization/Optimization.jl @@ -7,10 +7,12 @@ Optimization module. This module defines the abstract optimization problem interface -(`AbstractOptimizationProblem`) and the backend-agnostic model/solution building -contract (`build_model` / `build_solution`). Concrete problem types (e.g. -`DiscretizedModel`) and the packages providing them implement these by multiple -dispatch on `(problem, modeler)`. +(`AbstractOptimizationProblem`) and the value types of the model/solution building +contract (`BuiltModel`, `NoCache`). The generic `build_model` / `build_solution` +functions are *owned* here (and re-exported) but their canonical `NotImplemented` +stubs — the modeler contract — live in `Modelers` (`Modelers/contract.jl`), typed on +`AbstractNLPModeler`; concrete methods live in the package providing the problem +(e.g. CTDirect), dispatched on `(problem, modeler)`. Solver-side utilities (e.g. `extract_solver_infos`) live in `Solvers`. """ @@ -18,13 +20,12 @@ module Optimization # Imports import CTBase.Core -import CTBase.Exceptions -import DocStringExtensions: TYPEDEF, TYPEDSIGNATURES -using SolverCore: SolverCore +import DocStringExtensions: TYPEDEF # Submodules include(joinpath(@__DIR__, "abstract_types.jl")) include(joinpath(@__DIR__, "built_model.jl")) +# Declares the generic `build_model` / `build_solution`; contract stubs live in Modelers. include(joinpath(@__DIR__, "building.jl")) # Public API - Abstract types @@ -34,6 +35,7 @@ export AbstractOptimizationProblem export BuiltModel, NoCache # Public API - Model building functions +# (declared in `building.jl`; contract stubs in `Modelers/contract.jl`) export build_model, build_solution end # module Optimization diff --git a/src/Optimization/building.jl b/src/Optimization/building.jl index 2c210ec7..1f97b8fc 100644 --- a/src/Optimization/building.jl +++ b/src/Optimization/building.jl @@ -1,77 +1,14 @@ -# Optimization building API +# Generic build functions (declarations) # -# General API for building NLP models and solutions from optimization problems. - -""" -$(TYPEDSIGNATURES) - -Build a [`BuiltModel`](@ref) from an optimization problem using the specified modeler. - -This is a general function that works with any `AbstractOptimizationProblem`. -The modeler handles the conversion to the specific NLP backend. The returned -[`BuiltModel`](@ref) carries the backend NLP model together with any immutable -build-time auxiliary needed later by [`build_solution`](@ref). - -# Arguments -- `prob::AbstractOptimizationProblem`: The optimization problem -- `initial_guess`: Initial guess for the NLP solver -- `modeler`: The modeler strategy (e.g., Modelers.ADNLP, Modelers.Exa) - -# Returns -- A [`BuiltModel`](@ref) bundling the backend NLP model and its build-time cache - -# Example -```julia -modeler = Modelers.ADNLP(show_time=false) -built = build_model(prob, initial_guess, modeler) -``` - -See also: `build_solution`, `BuiltModel` -""" -function build_model(prob::AbstractOptimizationProblem, initial_guess, modeler) - throw( - Exceptions.NotImplemented( - "Model building not implemented"; - required_method="build_model(prob::$(typeof(prob)), initial_guess, modeler::$(typeof(modeler)))", - suggestion="Implement build_model for this (problem, modeler) pair in the package providing the problem", - context="Optimization.build_model - required method implementation", - ), - ) -end - -""" -$(TYPEDSIGNATURES) - -Build a solution from NLP execution statistics using the specified modeler. - -Dispatches on the [`BuiltModel`](@ref) returned by [`build_model`](@ref): it -carries both the problem (for problem-level data) and the immutable build-time -cache (e.g. an ExaModels getter) needed to reconstruct the solution. - -# Arguments -- `built::BuiltModel`: The built model bundle returned by `build_model` -- `model_solution`: NLP solver output (execution statistics) -- `modeler`: The modeler strategy used for building - -# Returns -- A solution object appropriate for the problem type - -# Example -```julia -built = build_model(prob, initial_guess, modeler) -nlp_stats = solve(built.nlp, solver) -sol = build_solution(built, nlp_stats, modeler) -``` +# `Optimization` owns and re-exports the generic functions `build_model` and +# `build_solution`. They are *declared* here (empty generics) so the binding exists +# and the export is valid; their canonical `NotImplemented` contract stubs — the +# modeler contract, with full docstrings — live in `Modelers/contract.jl`, and +# concrete methods live in the package providing the problem (e.g. CTDirect). +# +# This file is intentionally excluded from the API reference: it carries no +# documentable content; `build_model` / `build_solution` are documented via their +# contract methods on the Modelers page. -See also: `build_model`, `BuiltModel` -""" -function build_solution(built::BuiltModel, model_solution, modeler) - throw( - Exceptions.NotImplemented( - "Solution building not implemented"; - required_method="build_solution(built::BuiltModel{$(typeof(built.problem))}, model_solution, modeler::$(typeof(modeler)))", - suggestion="Implement build_solution for this (problem, modeler) pair in the package providing the problem", - context="Optimization.build_solution - required method implementation", - ), - ) -end +function build_model end +function build_solution end diff --git a/src/Solvers/Solvers.jl b/src/Solvers/Solvers.jl index 9c9482a4..593235dc 100644 --- a/src/Solvers/Solvers.jl +++ b/src/Solvers/Solvers.jl @@ -64,7 +64,8 @@ include(joinpath(@__DIR__, "madncl.jl")) include(joinpath(@__DIR__, "madnlpsuite.jl")) include(joinpath(@__DIR__, "knitro.jl")) include(joinpath(@__DIR__, "uno.jl")) -include(joinpath(@__DIR__, "common_solve_api.jl")) +include(joinpath(@__DIR__, "contract.jl")) +include(joinpath(@__DIR__, "orchestration.jl")) include(joinpath(@__DIR__, "solver_info.jl")) # Public API - abstract and concrete types diff --git a/src/Solvers/common_solve_api.jl b/src/Solvers/common_solve_api.jl deleted file mode 100644 index b6833bb8..00000000 --- a/src/Solvers/common_solve_api.jl +++ /dev/null @@ -1,97 +0,0 @@ -""" -CommonSolve API implementation for optimization solvers. - -Provides unified solve interface for optimization problems at multiple levels: -1. High-level: OptimizationProblem → Solution -2. Mid-level: NLP → ExecutionStats -3. Low-level: Flexible solve with any compatible types -""" - -# Default display setting -""" -$(TYPEDSIGNATURES) - -Internal helper to define default display behavior. -""" -__display() = true - -""" -$(TYPEDSIGNATURES) - -High-level solve: Build NLP model, solve it, and build solution. - -# Arguments -- `problem::Optimization.AbstractOptimizationProblem`: The optimization problem -- `initial_guess`: Initial guess for the solution -- `modeler::Modelers.AbstractNLPModeler`: Modeler to build NLP -- `solver::AbstractNLPSolver`: Solver to use -- `display::Bool`: Whether to show solver output (default: true) - -# Returns -- Solution object from the optimization problem - -# Example -```julia -# Conceptual usage pattern -# problem = ... -# x0 = ... -# modeler = Modelers.ADNLP() -# solver = Solvers.Ipopt(max_iter=1000) -# solution = solve(problem, x0, modeler, solver, display=true) -``` - -See also: `Optimization.build_model`, `Optimization.build_solution` -""" -function CommonSolve.solve( - problem::Optimization.AbstractOptimizationProblem, - initial_guess, - modeler::Modelers.AbstractNLPModeler, - solver::AbstractNLPSolver; - display::Bool=__display(), -) - # Build NLP model (bundled with its immutable build-time cache) - built = Optimization.build_model(problem, initial_guess, modeler) - - # Solve NLP - nlp_solution = CommonSolve.solve(built.nlp, solver; display=display) - - # Build OCP solution - solution = Optimization.build_solution(built, nlp_solution, modeler) - - return solution -end - -""" -$(TYPEDSIGNATURES) - -Mid-level solve: Solve an NLP problem directly. - -# Contract -Concrete solvers implement this method, typically in a backend extension, -dispatching on both the problem type and the solver type, e.g. -`CommonSolve.solve(nlp::NLPModels.AbstractNLPModel, solver::Ipopt; display)` in -the `CTSolversIpopt` extension. This generic stub throws `NotImplemented`. -`NLPModels` is a weak dep — the typed method lives in each solver extension. - -# Arguments -- `nlp`: The NLP problem to solve (type depends on backend) -- `solver::AbstractNLPSolver`: Solver to use -- `display::Bool`: Whether to show solver output (default: true) - -# Returns -- `SolverCore.AbstractExecutionStats`: Solver execution statistics - -See also: `AbstractNLPSolver` -""" -function CommonSolve.solve( - nlp, solver::AbstractNLPSolver; display::Bool=__display() -) - throw( - Exceptions.NotImplemented( - "Solve not implemented for this solver"; - required_method="CommonSolve.solve(nlp, solver::$(typeof(solver)); display)", - suggestion="Load the backend extension providing $(typeof(solver)) (e.g. `using NLPModelsIpopt`)", - context="Solvers.solve - required method implementation", - ), - ) -end diff --git a/src/Solvers/contract.jl b/src/Solvers/contract.jl new file mode 100644 index 00000000..680fe128 --- /dev/null +++ b/src/Solvers/contract.jl @@ -0,0 +1,51 @@ +# Solver contract +# +# Canonical contract for solver strategies: the mid-level `CommonSolve.solve(nlp, +# solver)` method, dispatched on `AbstractNLPSolver`. This is a `NotImplemented` +# stub; the typed method (on the external NLP format) lives in each backend +# extension (e.g. `CTSolversIpopt`). Also defines `__display`, the default +# display setting shared by the contract stub and the orchestrator. + +""" +$(TYPEDSIGNATURES) + +Internal helper defining the default solver-display behavior. +""" +__display() = true + +""" +$(TYPEDSIGNATURES) + +Mid-level solve: solve an NLP problem directly with a solver strategy. + +# Contract +Concrete solvers implement this method, typically in a backend extension, +dispatching on both the problem type and the solver type, e.g. +`CommonSolve.solve(nlp::NLPModels.AbstractNLPModel, solver::Ipopt; display)` in +the `CTSolversIpopt` extension. This generic stub throws `NotImplemented`. +`NLPModels` is a weak dep — the typed method lives in each solver extension. + +# Arguments +- `nlp`: The NLP problem to solve (type depends on backend). +- `solver::AbstractNLPSolver`: Solver to use. +- `display::Bool`: Whether to show solver output (default: `true`). + +# Returns +- `SolverCore.AbstractExecutionStats`: Solver execution statistics. + +# Throws +- [`CTBase.Exceptions.NotImplemented`](@extref): until a backend extension provides + the typed method. + +See also: `AbstractNLPSolver`. +""" +function CommonSolve.solve(nlp, solver::AbstractNLPSolver; display::Bool=__display()) + throw( + Exceptions.NotImplemented( + "Solve not implemented for this solver"; + required_method="CommonSolve.solve(nlp, solver::$(typeof(solver)); display)", + suggestion="Load the backend extension providing $(typeof(solver)) (e.g. `using NLPModelsIpopt`)", + context="Solvers.solve - required method implementation", + ), + ) +end diff --git a/src/Solvers/orchestration.jl b/src/Solvers/orchestration.jl new file mode 100644 index 00000000..28f91f4c --- /dev/null +++ b/src/Solvers/orchestration.jl @@ -0,0 +1,52 @@ +# Solver orchestration +# +# High-level composition: discretized problem → NLP model (via the modeler +# contract) → solver stats (via the solver contract) → problem-level solution. +# The contracts themselves live in `Modelers/contract.jl` and `Solvers/contract.jl`; +# this file only composes them (`__display` is defined alongside the solver contract). + +""" +$(TYPEDSIGNATURES) + +High-level solve: Build NLP model, solve it, and build solution. + +# Arguments +- `problem::Optimization.AbstractOptimizationProblem`: The optimization problem +- `initial_guess`: Initial guess for the solution +- `modeler::Modelers.AbstractNLPModeler`: Modeler to build NLP +- `solver::AbstractNLPSolver`: Solver to use +- `display::Bool`: Whether to show solver output (default: true) + +# Returns +- Solution object from the optimization problem + +# Example +```julia +# Conceptual usage pattern +# problem = ... +# x0 = ... +# modeler = Modelers.ADNLP() +# solver = Solvers.Ipopt(max_iter=1000) +# solution = solve(problem, x0, modeler, solver, display=true) +``` + +See also: `Optimization.build_model`, `Optimization.build_solution` +""" +function CommonSolve.solve( + problem::Optimization.AbstractOptimizationProblem, + initial_guess, + modeler::Modelers.AbstractNLPModeler, + solver::AbstractNLPSolver; + display::Bool=__display(), +) + # Build NLP model (bundled with its immutable build-time cache) + built = Optimization.build_model(problem, initial_guess, modeler) + + # Solve NLP + nlp_solution = CommonSolve.solve(built.nlp, solver; display=display) + + # Build OCP solution + solution = Optimization.build_solution(built, nlp_solution, modeler) + + return solution +end diff --git a/test/suite/optimization/test_optimization.jl b/test/suite/optimization/test_optimization.jl index 0d7f9657..7bf144cb 100644 --- a/test/suite/optimization/test_optimization.jl +++ b/test/suite/optimization/test_optimization.jl @@ -4,6 +4,7 @@ using Test: Test import CTBase.Exceptions using CTSolvers: CTSolvers import CTSolvers.Optimization +import CTSolvers.Modelers import CTSolvers.Solvers using NLPModels: NLPModels using SolverCore: SolverCore @@ -31,8 +32,11 @@ struct MinimalProblem <: Optimization.AbstractOptimizationProblem end """ Fake modeler for testing building functions; carries a backend selector. + +Subtypes `AbstractNLPModeler` so that an unimplemented `(problem, modeler)` pair +falls through to the canonical `NotImplemented` contract stub in `Modelers`. """ -struct FakeModeler +struct FakeModeler <: Modelers.AbstractNLPModeler backend::Symbol end From cec6cdad2a55242004a5f7e8793fbdf7d604a801 Mon Sep 17 00:00:00 2001 From: Olivier Cots Date: Sun, 28 Jun 2026 22:59:11 +0200 Subject: [PATCH 2/2] docs: bump to 0.4.24-beta with CHANGELOG entry and compat updates MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Version 0.4.24-beta - CHANGELOG: contract co-location refactor entry - Project.toml: CTBase 0.25→0.26, CTModels 0.13→0.14 - docs/Project.toml: CTBase 0.21→0.26 - docs/api_reference.jl: remove stale Core section (file does not exist) Co-Authored-By: Claude Opus 4.8 --- CHANGELOG.md | 18 ++++++++++++++++++ Project.toml | 6 +++--- docs/Project.toml | 2 +- docs/api_reference.jl | 14 -------------- 4 files changed, 22 insertions(+), 18 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9e30a1f6..a7a9e57a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,24 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 --- +## [0.4.24-beta] - 2026-06-28 + +### Changed + +- **Contract co-location** — each strategy family now has its `NotImplemented` + contract stub adjacent to its abstract type, mirroring the existing `DOCP/contract.jl` + precedent: + - `Modelers/contract.jl` (new): `build_model` / `build_solution` stubs, now typed + `::AbstractNLPModeler` (previously untyped third argument). + - `Solvers/contract.jl` (new): mid-level `CommonSolve.solve(nlp, ::AbstractNLPSolver)` + stub + `__display` helper; high-level orchestrator moved to `Solvers/orchestration.jl`. + - `DOCP/discretized_model.jl` (new): merger of `types.jl` + `accessors.jl`; + `DOCP/conveniences.jl` (rename of `building.jl`). +- **Compat bumps** — `CTBase = "0.26"`, `CTModels = "0.14"` in `Project.toml` and + `docs/Project.toml`. + +--- + ## [0.4.23-beta] - 2026-06-27 ### Changed diff --git a/Project.toml b/Project.toml index d05c90df..9dd66de4 100644 --- a/Project.toml +++ b/Project.toml @@ -1,6 +1,6 @@ name = "CTSolvers" uuid = "d3e8d392-8e4b-4d9b-8e92-d7d4e3650ef6" -version = "0.4.23-beta" +version = "0.4.24-beta" authors = ["Olivier Cots "] [deps] @@ -42,8 +42,8 @@ CTSolversZygote = "Zygote" ADNLPModels = "0.8" Aqua = "0.8" BenchmarkTools = "1" -CTBase = "0.25" -CTModels = "0.13" +CTBase = "0.26" +CTModels = "0.14" CUDA = "5, 6" CommonSolve = "0.2" DocStringExtensions = "0.9" diff --git a/docs/Project.toml b/docs/Project.toml index a3b1980b..3b814ebf 100644 --- a/docs/Project.toml +++ b/docs/Project.toml @@ -5,7 +5,7 @@ DocumenterMermaid = "a078cd44-4d9c-4618-b545-3ab9d77f9177" MarkdownAST = "d0879d2d-cac2-40c8-9cee-1863dc0c7391" [compat] -CTBase = "0.21" +CTBase = "0.26" Documenter = "1" DocumenterMermaid = "0.2" MarkdownAST = "0.1" diff --git a/docs/api_reference.jl b/docs/api_reference.jl index ac6b4a32..464179dd 100644 --- a/docs/api_reference.jl +++ b/docs/api_reference.jl @@ -24,20 +24,6 @@ function generate_api_reference(src_dir::String, ext_dir::String) pages = [ - # ─────────────────────────────────────────────────────────────────── - # Core - # ─────────────────────────────────────────────────────────────────── - CTBase.automatic_reference_documentation(; - subdirectory="api", - primary_modules=[CTSolvers.Core => src(joinpath("Core", "Core.jl"))], - exclude=EXCLUDE_SYMBOLS, - public=true, - private=true, - title="Core", - title_in_menu="Core", - filename="core", - ), - # ─────────────────────────────────────────────────────────────────── # DOCP # ───────────────────────────────────────────────────────────────────