diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml index 90d81ede..4547d7ef 100644 --- a/.github/workflows/CI.yml +++ b/.github/workflows/CI.yml @@ -10,3 +10,8 @@ on: jobs: call: uses: control-toolbox/CTActions/.github/workflows/ci.yml@main + with: + runs_on: '["ubuntu-latest","macos-latest"]' + use_ct_registry: true + secrets: + SSH_KEY: ${{ secrets.SSH_KEY }} diff --git a/Project.toml b/Project.toml index 0eaf1c7f..123074cc 100644 --- a/Project.toml +++ b/Project.toml @@ -1,34 +1,27 @@ name = "CTModels" uuid = "34c4fa32-2049-4079-8329-de33c2a22e2d" -version = "0.7.1-beta" +version = "0.8.0-beta" authors = ["Olivier Cots "] [deps] -ADNLPModels = "54578032-b7ea-4c30-94aa-7cbd1cce6c9a" CTBase = "54762871-cc72-4466-b8e8-f6c8b58076cd" DocStringExtensions = "ffbed154-4ef7-542d-bbb7-c09d3a79fcae" -ExaModels = "1037b233-b668-4ce9-9b63-f9f681f55dd2" Interpolations = "a98d9a8b-a2ab-59e6-89dd-64a1c18fca59" -KernelAbstractions = "63c18a36-062a-441e-b654-da1e3ab1ce7c" LinearAlgebra = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e" MLStyle = "d8e11817-5142-5d16-987a-aa16d5891078" MacroTools = "1914dd2f-81c6-5fcd-8719-6d5c9610ff09" -NLPModels = "a4795742-8479-5a88-8948-cc11e1c8c1a6" OrderedCollections = "bac558e1-5e72-5ebc-8fee-abe8a469f55d" Parameters = "d96e819e-fc66-5662-9728-84c9c7592b0a" RecipesBase = "3cdcf5f2-1ef4-517c-9805-6587b60abb01" -SolverCore = "ff4d7338-4cf1-434d-91df-b86cb86fb843" [weakdeps] JLD2 = "033835bb-8acc-5ee8-8aae-3f567f8a3819" JSON3 = "0f8b85d8-7281-11e9-16c2-39a750bddbf1" -MadNLP = "2621e9c9-9eb4-46b1-8089-e8c72242dfb6" Plots = "91a5bcdd-55d7-5caf-9e0b-520d859cae80" [extensions] CTModelsJLD = "JLD2" CTModelsJSON = "JSON3" -CTModelsMadNLP = "MadNLP" CTModelsPlots = "Plots" [extras] @@ -41,32 +34,25 @@ test = [ "Aqua", "JLD2", "JSON3", - "MadNLP", "Plots", "Random", "Test" ] [compat] -ADNLPModels = "0.8" Aqua = "0.8" -CTBase = "0.16, 0.17" +CTBase = "0.18" DocStringExtensions = "0.9" -ExaModels = "0.9" Interpolations = "0.16" JLD2 = "0.6" JSON3 = "1" -KernelAbstractions = "0.9" LinearAlgebra = "1" -MadNLP = "0.8" MLStyle = "0.4" MacroTools = "0.5" -NLPModels = "0.21" OrderedCollections = "1" Parameters = "0.12" Plots = "1" Random = "1" RecipesBase = "1" -SolverCore = "0.3" Test = "1" -julia = "1.10" +julia = "1.10" \ No newline at end of file diff --git a/docs/Project.toml b/docs/Project.toml index 32df2b39..c924b836 100644 --- a/docs/Project.toml +++ b/docs/Project.toml @@ -7,7 +7,7 @@ MarkdownAST = "d0879d2d-cac2-40c8-9cee-1863dc0c7391" Plots = "91a5bcdd-55d7-5caf-9e0b-520d859cae80" [compat] -CTBase = "0.17" +CTBase = "0.18" Documenter = "1" JLD2 = "0.6" JSON3 = "1" diff --git a/docs/api_reference.jl b/docs/api_reference.jl index a063ef94..f1ee6d34 100644 --- a/docs/api_reference.jl +++ b/docs/api_reference.jl @@ -18,7 +18,7 @@ function generate_api_reference(src_dir::String, ext_dir::String) src(files...) = [abspath(joinpath(src_dir, f)) for f in files] ext(files...) = [abspath(joinpath(ext_dir, f)) for f in files] - # Symbols to exclude from documentation (auto-generated by @with_kw, etc.) + # Symbols to exclude from documentation EXCLUDE_SYMBOLS = Symbol[ :include, :eval, @@ -26,429 +26,258 @@ function generate_api_reference(src_dir::String, ext_dir::String) Symbol("@pack_PreModel!"), Symbol("@unpack_PreModel"), :is_empty, + :time_ns, ] + CTModelsPlots = Base.get_extension(CTModels, :CTModelsPlots) + CTModelsJSON = Base.get_extension(CTModels, :CTModelsJSON) + CTModelsJLD = Base.get_extension(CTModels, :CTModelsJLD) + pages = [ # ─────────────────────────────────────────────────────────────────── - # Main module - # ─────────────────────────────────────────────────────────────────── - CTBase.automatic_reference_documentation(; - subdirectory=".", - primary_modules=[CTModels => src("CTModels.jl")], - exclude=EXCLUDE_SYMBOLS, - public=false, - private=true, - title="CTModels", - title_in_menu="CTModels", - filename="ctmodels", - ), - # ─────────────────────────────────────────────────────────────────── - # Core: OCP Types + # Utils # ─────────────────────────────────────────────────────────────────── CTBase.automatic_reference_documentation(; subdirectory=".", primary_modules=[ - CTModels => src( - "ocp/types/components.jl", - "ocp/types/model.jl", - "ocp/types/solution.jl", + CTModels.Utils => src( + joinpath("Utils", "Utils.jl"), + joinpath("Utils", "macros.jl"), + joinpath("Utils", "interpolation.jl"), + joinpath("Utils", "matrix_utils.jl"), + joinpath("Utils", "function_utils.jl"), ), ], exclude=EXCLUDE_SYMBOLS, - public=false, + public=true, private=true, - title="OCP Types", - title_in_menu="OCP Types", - filename="ocp_types", + title="Utils", + title_in_menu="Utils", + filename="api_utils", ), # ─────────────────────────────────────────────────────────────────── - # Base Types & Export/Import + # OCP - Types # ─────────────────────────────────────────────────────────────────── CTBase.automatic_reference_documentation(; subdirectory=".", primary_modules=[ - CTModels => src( - "types/aliases.jl", - "types/export_import.jl", - "types/export_import_functions.jl", - ), - ], - exclude=EXCLUDE_SYMBOLS, - public=false, - private=true, - title="Base Types & Export/Import", - title_in_menu="Base Types & Export/Import", - filename="base_types_export_import", - ), - # ─────────────────────────────────────────────────────────────────── - # Options Module - Public API - # ─────────────────────────────────────────────────────────────────── - CTBase.automatic_reference_documentation(; - subdirectory="options", - primary_modules=[ - CTModels => src( - "Options/Options.jl", - "Options/option_value.jl", - "Options/option_definition.jl", - "Options/extraction.jl", + CTModels.OCP => src( + joinpath("OCP", "aliases.jl"), + joinpath("OCP", "Types", "components.jl"), + joinpath("OCP", "Types", "model.jl"), + joinpath("OCP", "Types", "solution.jl"), ), ], + external_modules_to_document=[CTModels], exclude=EXCLUDE_SYMBOLS, public=true, - private=false, - title="Options - Public API", - title_in_menu="Options (Public)", - filename="options_public", - ), - # ─────────────────────────────────────────────────────────────────── - # Options Module - Internal API - # ─────────────────────────────────────────────────────────────────── - CTBase.automatic_reference_documentation(; - subdirectory="options", - primary_modules=[ - CTModels => src( - "Options/Options.jl", - "Options/option_value.jl", - "Options/option_definition.jl", - "Options/extraction.jl", - ), - ], - exclude=EXCLUDE_SYMBOLS, - public=false, private=true, - title="Options - Internal API", - title_in_menu="Options (Internal)", - filename="options_internal", + title="OCP - Types", + title_in_menu="OCP Types", + filename="api_ocp_types", ), # ─────────────────────────────────────────────────────────────────── - # Strategies Module - Contract (Public) + # OCP - Components # ─────────────────────────────────────────────────────────────────── CTBase.automatic_reference_documentation(; - subdirectory="strategies", + subdirectory=".", primary_modules=[ - CTModels => src( - "Strategies/Strategies.jl", - "Strategies/contract/abstract_strategy.jl", - "Strategies/contract/metadata.jl", - "Strategies/contract/strategy_options.jl", + CTModels.OCP => src( + joinpath("OCP", "Components", "state.jl"), + joinpath("OCP", "Components", "control.jl"), + joinpath("OCP", "Components", "variable.jl"), + joinpath("OCP", "Components", "times.jl"), + joinpath("OCP", "Components", "dynamics.jl"), + joinpath("OCP", "Components", "objective.jl"), + joinpath("OCP", "Components", "constraints.jl"), ), ], + external_modules_to_document=[CTModels], exclude=EXCLUDE_SYMBOLS, public=true, - private=false, - title="Strategies - Contract (Public)", - title_in_menu="Strategies Contract (Public)", - filename="strategies_contract_public", - ), - # ─────────────────────────────────────────────────────────────────── - # Strategies Module - Contract (Internal) - # ─────────────────────────────────────────────────────────────────── - CTBase.automatic_reference_documentation(; - subdirectory="strategies", - primary_modules=[ - CTModels => src( - "Strategies/Strategies.jl", - "Strategies/contract/abstract_strategy.jl", - "Strategies/contract/metadata.jl", - "Strategies/contract/strategy_options.jl", - ), - ], - exclude=EXCLUDE_SYMBOLS, - public=false, private=true, - title="Strategies - Contract (Internal)", - title_in_menu="Strategies Contract (Internal)", - filename="strategies_contract_internal", + title="OCP - Components", + title_in_menu="OCP Components", + filename="api_ocp_components", ), # ─────────────────────────────────────────────────────────────────── - # Strategies Module - API (Public) + # OCP - Building # ─────────────────────────────────────────────────────────────────── CTBase.automatic_reference_documentation(; - subdirectory="strategies", + subdirectory=".", primary_modules=[ - CTModels => src( - "Strategies/api/builders.jl", - "Strategies/api/configuration.jl", - "Strategies/api/introspection.jl", - "Strategies/api/registry.jl", - "Strategies/api/utilities.jl", - "Strategies/api/validation.jl", + CTModels.OCP => src( + joinpath("OCP", "Building", "model.jl"), + joinpath("OCP", "Building", "solution.jl"), + joinpath("OCP", "Building", "interpolation_helpers.jl"), + joinpath("OCP", "Building", "discretization_utils.jl"), + joinpath("OCP", "Building", "dual_model.jl"), + joinpath("OCP", "Building", "definition.jl"), ), ], + external_modules_to_document=[CTModels], exclude=EXCLUDE_SYMBOLS, public=true, - private=false, - title="Strategies - API (Public)", - title_in_menu="Strategies API (Public)", - filename="strategies_api_public", - ), - # ─────────────────────────────────────────────────────────────────── - # Strategies Module - API (Internal) - # ─────────────────────────────────────────────────────────────────── - CTBase.automatic_reference_documentation(; - subdirectory="strategies", - primary_modules=[ - CTModels => src( - "Strategies/api/builders.jl", - "Strategies/api/configuration.jl", - "Strategies/api/introspection.jl", - "Strategies/api/registry.jl", - "Strategies/api/utilities.jl", - "Strategies/api/validation.jl", - ), - ], - exclude=EXCLUDE_SYMBOLS, - public=false, private=true, - title="Strategies - API (Internal)", - title_in_menu="Strategies API (Internal)", - filename="strategies_api_internal", + title="OCP - Building", + title_in_menu="OCP Building", + filename="api_ocp_building", ), # ─────────────────────────────────────────────────────────────────── - # Orchestration Module - Public API + # OCP - Core & Validation # ─────────────────────────────────────────────────────────────────── CTBase.automatic_reference_documentation(; - subdirectory="orchestration", + subdirectory=".", primary_modules=[ - CTModels => src( - "Orchestration/Orchestration.jl", - "Orchestration/routing.jl", - "Orchestration/disambiguation.jl", - "Orchestration/method_builders.jl", + CTModels.OCP => src( + joinpath("OCP", "Core", "defaults.jl"), + joinpath("OCP", "Core", "time_dependence.jl"), + joinpath("OCP", "Validation", "name_validation.jl"), ), ], + external_modules_to_document=[CTModels], exclude=EXCLUDE_SYMBOLS, public=true, - private=false, - title="Orchestration - Public API", - title_in_menu="Orchestration (Public)", - filename="orchestration_public", - ), - # ─────────────────────────────────────────────────────────────────── - # Orchestration Module - Internal API - # ─────────────────────────────────────────────────────────────────── - CTBase.automatic_reference_documentation(; - subdirectory="orchestration", - primary_modules=[ - CTModels => src( - "Orchestration/Orchestration.jl", - "Orchestration/routing.jl", - "Orchestration/disambiguation.jl", - "Orchestration/method_builders.jl", - ), - ], - exclude=EXCLUDE_SYMBOLS, - public=false, private=true, - title="Orchestration - Internal API", - title_in_menu="Orchestration (Internal)", - filename="orchestration_internal", + title="OCP - Core & Validation", + title_in_menu="OCP Core", + filename="api_ocp_core", ), # ─────────────────────────────────────────────────────────────────── - # Defaults & Utils + # Display # ─────────────────────────────────────────────────────────────────── CTBase.automatic_reference_documentation(; subdirectory=".", primary_modules=[ - CTModels => src( - "ocp/defaults.jl", - "utils/interpolation.jl", - "utils/matrix_utils.jl", - "utils/function_utils.jl", - "utils/macros.jl", + CTModels.Display => src( + joinpath("Display", "Display.jl"), + joinpath("Display", "print.jl"), + ), + CTModelsPlots => ext( + "CTModelsPlots.jl", + "plot.jl", + "plot_utils.jl", + "plot_default.jl", ), ], + external_modules_to_document=[Plots], exclude=EXCLUDE_SYMBOLS, - public=false, - private=true, - title="Defaults & Utils", - title_in_menu="Defaults & Utils", - filename="defaults_utils", - ), - # ─────────────────────────────────────────────────────────────────── - # OCP: Model (model, definition, time_dependence) - # ─────────────────────────────────────────────────────────────────── - CTBase.automatic_reference_documentation(; - subdirectory=".", - primary_modules=[ - CTModels => - src("ocp/model.jl", "ocp/definition.jl", "ocp/time_dependence.jl"), - ], - exclude=EXCLUDE_SYMBOLS, - public=false, - private=true, - title="Model", - title_in_menu="Model", - filename="model", - ), - # ─────────────────────────────────────────────────────────────────── - # OCP: Times - # ─────────────────────────────────────────────────────────────────── - CTBase.automatic_reference_documentation(; - subdirectory=".", - primary_modules=[CTModels => src("ocp/times.jl")], - exclude=EXCLUDE_SYMBOLS, - public=false, + public=true, private=true, - title="Times", - title_in_menu="Times", - filename="times", + title="Display, Plots", + title_in_menu="Display, Plots", + filename="api_display", ), # ─────────────────────────────────────────────────────────────────── - # OCP: State, Control, Variable + # Serialization # ─────────────────────────────────────────────────────────────────── CTBase.automatic_reference_documentation(; subdirectory=".", primary_modules=[ - CTModels => src("ocp/state.jl", "ocp/control.jl", "ocp/variable.jl") + CTModels.Serialization => src( + joinpath("Serialization", "Serialization.jl"), + joinpath("Serialization", "export_import.jl"), + joinpath("Serialization", "types.jl"), + ), + CTModelsJSON => ext("CTModelsJSON.jl"), + CTModelsJLD => ext("CTModelsJLD.jl"), ], exclude=EXCLUDE_SYMBOLS, - public=false, - private=true, - title="State, Control & Variable", - title_in_menu="State, Control & Variable", - filename="state_control_variable", - ), - # ─────────────────────────────────────────────────────────────────── - # OCP: Dynamics & Objective - # ─────────────────────────────────────────────────────────────────── - CTBase.automatic_reference_documentation(; - subdirectory=".", - primary_modules=[CTModels => src("ocp/dynamics.jl", "ocp/objective.jl")], - exclude=EXCLUDE_SYMBOLS, - public=false, - private=true, - title="Dynamics & Objective", - title_in_menu="Dynamics & Objective", - filename="dynamics_objective", - ), - # ─────────────────────────────────────────────────────────────────── - # OCP: Constraints - # ─────────────────────────────────────────────────────────────────── - CTBase.automatic_reference_documentation(; - subdirectory=".", - primary_modules=[CTModels => src("ocp/constraints.jl")], - exclude=EXCLUDE_SYMBOLS, - public=false, - private=true, - title="Constraints", - title_in_menu="Constraints", - filename="constraints", - ), - # ─────────────────────────────────────────────────────────────────── - # OCP: Solution & Dual - # ─────────────────────────────────────────────────────────────────── - CTBase.automatic_reference_documentation(; - subdirectory=".", - primary_modules=[CTModels => src("ocp/solution.jl", "ocp/dual_model.jl")], - exclude=EXCLUDE_SYMBOLS, - public=false, - private=true, - title="Solution & Dual", - title_in_menu="Solution & Dual", - filename="solution_dual", - ), - # ─────────────────────────────────────────────────────────────────── - # OCP: Print - # ─────────────────────────────────────────────────────────────────── - CTBase.automatic_reference_documentation(; - subdirectory=".", - primary_modules=[CTModels => src("ocp/print.jl")], - exclude=EXCLUDE_SYMBOLS, - public=false, - private=true, - title="Print", - title_in_menu="Print", - filename="print", - ), - # ─────────────────────────────────────────────────────────────────── - # Initial Guess - # ─────────────────────────────────────────────────────────────────── - CTBase.automatic_reference_documentation(; - subdirectory=".", - primary_modules=[CTModels => src("init/initial_guess.jl")], - exclude=EXCLUDE_SYMBOLS, - public=false, + public=true, private=true, - title="Initial Guess", - title_in_menu="Initial Guess", - filename="initial_guess", + title="Serialization, JSON & JLD2", + title_in_menu="Serialization, JSON & JLD2", + filename="api_serialization", ), # ─────────────────────────────────────────────────────────────────── - # NLP Backends + # InitialGuess # ─────────────────────────────────────────────────────────────────── CTBase.automatic_reference_documentation(; subdirectory=".", primary_modules=[ - CTModels => src( - "nlp/nlp_backends.jl", - "nlp/options_schema.jl", - "nlp/problem_core.jl", - "nlp/discretized_ocp.jl", - "nlp/model_api.jl", + CTModels.InitialGuess => src( + joinpath("InitialGuess", "InitialGuess.jl"), + joinpath("InitialGuess", "types.jl"), + joinpath("InitialGuess", "api.jl"), + joinpath("InitialGuess", "builders.jl"), + joinpath("InitialGuess", "state.jl"), + joinpath("InitialGuess", "control.jl"), + joinpath("InitialGuess", "variable.jl"), + joinpath("InitialGuess", "validation.jl"), + joinpath("InitialGuess", "utils.jl"), ), ], exclude=EXCLUDE_SYMBOLS, - public=false, + public=true, private=true, - title="NLP Backends", - title_in_menu="NLP Backends", - filename="nlp", + title="InitialGuess", + title_in_menu="InitialGuess", + filename="api_initial_guess", ), ] # ─────────────────────────────────────────────────────────────────── - # Extension: Plot + # Extensions (conditional) # ─────────────────────────────────────────────────────────────────── - CTModelsPlots = Base.get_extension(CTModels, :CTModelsPlots) - if !isnothing(CTModelsPlots) - push!( - pages, - CTBase.automatic_reference_documentation(; - subdirectory=".", - primary_modules=[ - CTModelsPlots => ext( - "CTModelsPlots.jl", - "plot.jl", - "plot_default.jl", - "plot_utils.jl", - ), - ], - external_modules_to_document=[Plots], - exclude=EXCLUDE_SYMBOLS, - public=false, - private=true, - title="Plot Extension", - title_in_menu="Plot", - filename="plot", - ), - ) - end + + # # CTModelsPlots extension + # CTModelsPlots = Base.get_extension(CTModels, :CTModelsPlots) + # if !isnothing(CTModelsPlots) + # push!( + # pages, + # CTBase.automatic_reference_documentation(; + # subdirectory=".", + # primary_modules=[ + # CTModelsPlots => ext("plot.jl", "plot_utils.jl", "plot_default.jl") + # ], + # external_modules_to_document=[CTModels], + # exclude=EXCLUDE_SYMBOLS, + # public=true, + # private=true, + # title="CTModelsPlots", + # title_in_menu="Plots Extension", + # filename="api_plots_extension", + # ), + # ) + # end - # ─────────────────────────────────────────────────────────────────── - # Extension: JLD & JSON (combined) - # ─────────────────────────────────────────────────────────────────── - CTModelsJSON = Base.get_extension(CTModels, :CTModelsJSON) - CTModelsJLD = Base.get_extension(CTModels, :CTModelsJLD) - if !isnothing(CTModelsJSON) && !isnothing(CTModelsJLD) - push!( - pages, - CTBase.automatic_reference_documentation(; - subdirectory=".", - primary_modules=[ - CTModelsJSON => ext("CTModelsJSON.jl"), - CTModelsJLD => ext("CTModelsJLD.jl"), - ], - external_modules_to_document=[CTModels], - exclude=EXCLUDE_SYMBOLS, - public=false, - private=true, - title="JLD & JSON Extension", - title_in_menu="JLD & JSON", - filename="import_export", - ), - ) - end + # # CTModelsJSON extension + # CTModelsJSON = Base.get_extension(CTModels, :CTModelsJSON) + # if !isnothing(CTModelsJSON) + # push!( + # pages, + # CTBase.automatic_reference_documentation(; + # subdirectory=".", + # primary_modules=[CTModelsJSON => ext("CTModelsJSON.jl")], + # external_modules_to_document=[CTModels], + # exclude=EXCLUDE_SYMBOLS, + # public=true, + # private=true, + # title="CTModelsJSON", + # title_in_menu="JSON Extension", + # filename="api_json_extension", + # ), + # ) + # end + + # # CTModelsJLD extension + # CTModelsJLD = Base.get_extension(CTModels, :CTModelsJLD) + # if !isnothing(CTModelsJLD) + # push!( + # pages, + # CTBase.automatic_reference_documentation(; + # subdirectory=".", + # primary_modules=[CTModelsJLD => ext("CTModelsJLD.jl")], + # external_modules_to_document=[CTModels], + # exclude=EXCLUDE_SYMBOLS, + # public=true, + # private=true, + # title="CTModelsJLD", + # title_in_menu="JLD2 Extension", + # filename="api_jld_extension", + # ), + # ) + # end return pages end @@ -464,17 +293,38 @@ function with_api_reference(f::Function, src_dir::String, ext_dir::String) f(pages) finally # Clean up generated files - docs_src = abspath(joinpath(@__DIR__, "src")) + # The pages are Pairs: "Title" => "filename.md" (relative to build? no, relative to src) + # automatic_reference_documentation returns "filename" which is relative to docs/src if subdirectory="." - for p in pages - filename = last(p) - fname = endswith(filename, ".md") ? filename : filename * ".md" - full_path = joinpath(docs_src, fname) + # We need to reconstruct the full path to delete them. + # Assuming they are in docs/src (which is where makedocs runs from?) + # Wait, makedocs options say subdirectory=".". + # Typically automatic_reference_documentation writes to joinpath(@__DIR__, "src", subdirectory, filename.md) ?? + # I need to check where automatic_reference_documentation writes. + # Assuming we are running from docs/ (where make.jl is). - if isfile(full_path) - rm(full_path) - println("Removed temporary API doc: $full_path") + # Let's assume the files are in `docs/src`. + docs_src = abspath(joinpath(@__DIR__, "src")) + + function cleanup_pages(pages) + for p in pages + content = last(p) + if content isa AbstractString + # file path + filename = content + fname = endswith(filename, ".md") ? filename : filename * ".md" + full_path = joinpath(docs_src, fname) + if isfile(full_path) + rm(full_path) + println("Removed temporary API doc: $full_path") + end + elseif content isa Vector + # nested pages + cleanup_pages(content) + end end end + + cleanup_pages(pages) end -end +end \ No newline at end of file diff --git a/docs/make.jl b/docs/make.jl index 44ef76c3..5d964c7f 100644 --- a/docs/make.jl +++ b/docs/make.jl @@ -1,6 +1,6 @@ using Documenter using CTModels -using CTBase # For automatic_reference_documentation +using CTBase using Plots using JSON3 using JLD2 @@ -20,12 +20,10 @@ const CTModelsJSON = Base.get_extension(CTModels, :CTModelsJSON) const CTModelsJLD = Base.get_extension(CTModels, :CTModelsJLD) const DocumenterReference = Base.get_extension(CTBase, :DocumenterReference) -# Reset DocumenterReference configuration for proper local/remote link generation if !isnothing(DocumenterReference) DocumenterReference.reset_config!() end -# to add docstrings from external packages Modules = [Plots, CTModelsPlots, CTModelsJSON, CTModelsJLD] for Module in Modules isnothing(DocMeta.getdocmeta(Module, :DocTestSetup)) && @@ -48,61 +46,20 @@ include("api_reference.jl") with_api_reference(src_dir, ext_dir) do api_pages makedocs(; draft=draft, - remotes=nothing, # Disable remote links. Needed for DocumenterReference - warnonly=true, + remotes=nothing, + warnonly=[:cross_references], sitename="CTModels.jl", format=Documenter.HTML(; repolink="https://" * repo_url, prettyurls=false, - #size_threshold_ignore=["api.md", "dev.md"], - #size_threshold=300_000, # 300 KiB threshold assets=[ asset("https://control-toolbox.org/assets/css/documentation.css"), asset("https://control-toolbox.org/assets/js/documentation.js"), ], ), - checkdocs=:none, pages=[ "Introduction" => "index.md", - "User Guide" => [ - "Defining Problems" => "interfaces/optimization_problems.md", - "Building Solutions" => "interfaces/ocp_solution_builders.md", - ], - "Developer Guide" => [ - "Tutorials" => [ - "Creating a Strategy" => "tutorials/creating_a_strategy.md", - "Creating a Strategy Family" => "tutorials/creating_a_strategy_family.md", - ], - "Interfaces" => [ - "Strategies" => "interfaces/strategies.md", - "Strategy Families" => "interfaces/strategy_families.md", - "Orchestration & Routing" => "interfaces/orchestration.md", - "Optimization Modelers" => "interfaces/optimization_modelers.md", - ], - "Examples" => [ - "Simple Strategy" => "examples/simple_strategy.md", - "Strategy with Options" => "examples/strategy_with_options.md", - "Strategy Family" => "examples/strategy_family.md", - "Option Routing" => "examples/routing_example.md", - "Integration Example" => "examples/integration_example.md", - "Migration Example" => "examples/migration_example.md", - ], - ], - "API Reference" => [ - "Public API" => [ - "Options" => "options/options_public.md", - "Strategies (Contract)" => "strategies/strategies_contract_public.md", - "Strategies (API)" => "strategies/strategies_api_public.md", - "Orchestration" => "orchestration/orchestration_public.md", - ], - "Internal API" => [ - "Options (Internal)" => "options/options_internal.md", - "Strategies Contract (Internal)" => "strategies/strategies_contract_internal.md", - "Strategies API (Internal)" => "strategies/strategies_api_internal.md", - "Orchestration (Internal)" => "orchestration/orchestration_internal.md", - ], - "Core & OCP" => api_pages, - ], + "API Reference" => api_pages, ], ) end diff --git a/docs/src/index.md b/docs/src/index.md index f3474f8d..d5fa161c 100644 --- a/docs/src/index.md +++ b/docs/src/index.md @@ -9,8 +9,14 @@ It provides the **mathematical model layer** for optimal control problems: - **types and building blocks** for states, controls, variables, time grids, and constraints; - an `AbstractModel`/`Model` and `AbstractSolution`/`Solution` hierarchy for optimal control problems; -- tools to build **initial guesses**, connect to **NLP backends**, and interpret their solutions; -- optional extensions for **exporting solutions** (JSON/JLD) and **plotting**. +- tools to build **initial guesses** for optimization; +- optional extensions for **exporting/importing solutions** (JSON/JLD) and **plotting**. + +!!! info "CTModels vs CTSolvers" + + **CTModels** focuses on **defining** optimal control problems and representing their solutions. + For **solving** these problems (discretization, NLP backends, optimization strategies), + see [CTSolvers.jl](https://github.com/control-toolbox/CTSolvers.jl). !!! note @@ -64,25 +70,11 @@ At a high level, CTModels is responsible for: `AbstractSolution` / `Solution` store state, control, dual variables, and solver information. - **Managing time grids and dimensions** through convenient type aliases. - **Structuring constraints** (path, boundary, box constraints on state, control, and variables). -- **Connecting to NLP backends** (ADNLPModels, ExaModels, etc.) via modelers and builders. -- **Strategy architecture** (NEW): - - **Options**: Generic option handling with aliases and validation - - **Strategies**: Configurable components (modelers, solvers, discretizers) - **Providing utilities** for initial guesses, export/import, and plotting of solutions. Most of the public API is organized in a way that closely mirrors the mathematical objects you manipulate when formulating an optimal control problem. -## Strategy Architecture - -CTModels provides a modern, type-stable architecture for configurable components: - -- **Options Module**: Low-level option extraction, validation, and alias resolution. -- **Strategies Module**: Strategy contract, metadata, registry, and builders. - -This architecture replaces the legacy `AbstractOCPTool` interface with a cleaner, -more maintainable design. See the **Developer Guide → Interfaces → Strategies** section for details. - ## Time grids and basic aliases CTModels defines a few central type aliases that appear throughout the API: @@ -121,37 +113,30 @@ These objects are the main bridge between the mathematical problem and the NLP b ## Initial guesses Good initial guesses are crucial for challenging optimal control problems. -CTModels provides a small layer to organize them: +CTModels provides a layer to organize them: - `pre_initial_guess` builds an `OptimalControlPreInit` object from raw user data (functions, vectors, or constants for state, control, and variables). - `initial_guess` turns this into an `OptimalControlInitialGuess`, checking consistency - with the chosen `AbstractOptimalControlProblem`. - -The corresponding API is implemented in `src/init/initial_guess.jl` and is documented -in the *Initial Guess* section of the API reference. - -## NLP backends and modelers - -CTModels does **not** solve the NLP itself. Instead, it connects to external NLP -backends via modelers and builders defined in `src/nlp/`: + with the chosen `AbstractModel`. +- `build_initial_guess` constructs initial guess objects from various input formats. +- `validate_initial_guess` ensures consistency with the problem dimensions. -- `ADNLPModeler` (based on `ADNLPModels.jl`), -- `ExaModeler` (based on `ExaModels.jl`), -- additional builder types and helper functions. +The corresponding API is documented in the *InitialGuess* section of the API reference. -These modelers: +## Solving optimal control problems -- expose options through the generic `AbstractOCPTool` interface from CTBase - (see the *Interfaces → OCP Tools* page), -- build backend-specific NLP models from an `AbstractOptimizationProblem`, -- optionally map NLP solutions back to `CTModels.Solution` objects. +CTModels defines the **problem structure** but does **not** solve it. +For solving optimal control problems, use [CTSolvers.jl](https://github.com/control-toolbox/CTSolvers.jl), +which provides: -The *Interfaces* section of the documentation contains detailed guides for: +- **Discretization strategies** (direct collocation, multiple shooting, etc.) +- **NLP backends** (ADNLPModels, ExaModels, etc.) +- **Optimization modelers** to connect problems to solvers +- **Strategy architecture** for configurable components -- implementing new **optimization problems**, -- implementing new **optimization modelers**, and -- implementing new **OCP solution builders**. +CTModels provides the `AbstractModel` type alias `AbstractOptimalControlProblem` +for compatibility with CTSolvers. ## Extensions: JSON, JLD, and plotting @@ -183,56 +168,37 @@ throw a descriptive `CTBase.ExtensionError`. ## How this documentation is organized -The documentation is split into two main parts: - -- **Interfaces** - - *OCP Tools*: how to implement new configurable tools (backends, discretizers, solvers). - - *Optimization Problems*: how to define `AbstractOptimizationProblem` types. - - *Optimization Modelers*: how to map optimization problems to specific NLP backends. - - *Solution Builders*: how to turn NLP execution statistics into `CTModels.Solution` objects. - -- **API Reference** - - *Types*: core types for models, solutions, and internal structures. - - *Model / Times / Dynamics / Objective / Constraints*: detailed API for building OCP models. - - *Solution & Dual*: how solutions and dual variables are represented. - - *Initial Guess*: utilities to build and validate initial guesses. - - *NLP Backends*: ADNLPModels/ExaModels-based backends and related options. - - *Extensions*: Plot, JSON, and JLD extensions. - -You can start by reading the **Interfaces** pages to understand the high-level -design, then use the **API Reference** to look up the details of particular -functions and types. - -## I am X, I want to do Y → read… - -### User Guide - -- **I want to formulate a new optimal control / optimization problem** - Read **User Guide → Optimization Problems**, then **API Reference → Model / Times / Dynamics / Objective / Constraints** - for details about fields and conventions. -- **I want to build good initial guesses for my problems** - Read **User Guide → Solution Builders** for the overall philosophy, then **API Reference → Initial Guess** - for the `pre_initial_guess` and `initial_guess` functions. -- **I want to save / reload solutions (for example for numerical experiments)** - Read **API Reference → Extensions (JSON & JLD)** and the pages associated with the `CTModelsJSON` and `CTModelsJLD` modules. -- **I want to plot solution trajectories nicely** - Read **API Reference → Extensions (Plot Extension)**, and look at the examples using `Plots.plot(sol)` and `Plots.plot!(sol)`. -- **I use OptimalControl.jl and I just want to understand what CTModels does in the background** - Read this introduction page, then skim through the **User Guide** section to see how - problems, modelers, and builders fit together. - -### Developer Guide - -- **I want to create a new strategy (modeler, solver, discretizer)** - Read **Developer Guide → Tutorials → Creating a Strategy**, then **Developer Guide → Interfaces → Strategies** - for the complete contract specification. -- **I want to create a family of related strategies** - Read **Developer Guide → Tutorials → Creating a Strategy Family**, then **Developer Guide → Interfaces → Strategy Families** - for registry integration and best practices. -- **I want to migrate from AbstractOCPTool to AbstractStrategy** - Read **Developer Guide → Interfaces → Strategies → Migration Guide** for step-by-step instructions. -- **I want to connect a new NLP backend or tweak an existing backend** - Read **Developer Guide → Interfaces → Optimization Modelers** (updated) and the **API Reference → NLP Backends** section. -- **I want to contribute to the core of CTModels (types, constraints, dual variables, etc.)** - Start with **API Reference → Types**, then **Solution & Dual** and **Constraints** to understand the internal structures - before modifying or adding new fields. +The documentation consists of: + +- **Introduction** (this page): Overview of CTModels and its role in the control-toolbox ecosystem. + +- **API Reference**: Complete documentation of all modules and functions: + - *CTModels*: Main module and exports + - *Utils*: Utilities (interpolation, macros, matrix operations) + - *OCP*: Optimal Control Problem types, components, building, and validation + - *Display*: Text display and printing + - *Serialization*: Export/import functionality + - *InitialGuess*: Initial guess management + - *Extensions*: Plots, JSON, and JLD2 extensions + +Use the **API Reference** to look up the details of particular functions and types. + +## Quick start guide + +- **I want to define an optimal control problem** + See **API Reference → OCP Components** for `state!`, `control!`, `dynamics!`, `objective!`, `constraint!`, etc. + +- **I want to build initial guesses** + See **API Reference → InitialGuess** for `pre_initial_guess`, `initial_guess`, and `build_initial_guess`. + +- **I want to save/load solutions** + See **API Reference → Serialization** and the JSON/JLD2 extension pages for `export_ocp_solution` and `import_ocp_solution`. + +- **I want to plot solution trajectories** + See **API Reference → Plots Extension** for `plot(sol)` and `plot!(sol)` with `Plots.jl`. + +- **I want to solve an optimal control problem** + Use [CTSolvers.jl](https://github.com/control-toolbox/CTSolvers.jl) which provides discretization, NLP backends, and optimization strategies. + +- **I use OptimalControl.jl** + CTModels provides the underlying types and building blocks. OptimalControl.jl offers a higher-level interface. diff --git a/ext/CTModelsPlots.jl b/ext/CTModelsPlots.jl index 174cd6dd..30881ffe 100644 --- a/ext/CTModelsPlots.jl +++ b/ext/CTModelsPlots.jl @@ -6,6 +6,7 @@ using MLStyle: MLStyle # using CTBase +using CTBase: Exceptions using CTModels using LinearAlgebra using Plots # redefine plot, plot! @@ -16,4 +17,6 @@ include("plot_utils.jl") include("plot_default.jl") include("plot.jl") +export plot, plot! + end diff --git a/ext/plot.jl b/ext/plot.jl index b27623f0..27e9df6e 100644 --- a/ext/plot.jl +++ b/ext/plot.jl @@ -84,8 +84,12 @@ function __plot_time!( :normalize => t_label == "" ? "" : t_label * " (normalized)" :normalise => t_label == "" ? "" : t_label * " (normalised)" _ => throw( - CTModels.Exceptions.IncorrectArgument( - "Internal error, no such choice for time: $time. Use :default, :normalize or :normalise", + Exceptions.IncorrectArgument( + "Invalid time choice"; + got="time=$time", + expected=":default, :normalize or :normalise", + suggestion="Use one of the supported time options", + context="plot time parameter" ), ) end @@ -305,8 +309,12 @@ function __initial_plot( end end _ => throw( - CTModels.Exceptions.IncorrectArgument( - "No such choice for control. Use :components, :norm or :all" + Exceptions.IncorrectArgument( + "Invalid control choice"; + got="control=$control", + expected=":components, :norm or :all", + suggestion="Use one of the supported control options", + context="plot control parameter" ), ) end @@ -345,7 +353,7 @@ function __initial_plot( l = m + 1 end _ => throw( - CTModels.Exceptions.IncorrectArgument( + Exceptions.IncorrectArgument( "No such choice for control. Use :components, :norm or :all" ), ) @@ -430,7 +438,13 @@ function __initial_plot( end else - throw(CTModels.Exceptions.IncorrectArgument("No such choice for layout. Use :group or :split")) + throw(Exceptions.IncorrectArgument( + "Invalid layout choice"; + got="layout=$layout", + expected=":group or :split", + suggestion="Use one of the supported layout options", + context="plot layout parameter" + )) end end @@ -653,7 +667,7 @@ function __plot!( icur += 1 end _ => throw( - CTModels.Exceptions.IncorrectArgument( + Exceptions.IncorrectArgument( "No such choice for control. Use :components, :norm or :all" ), ) @@ -848,7 +862,7 @@ function __plot!( icur += 1 end _ => throw( - CTModels.Exceptions.IncorrectArgument( + Exceptions.IncorrectArgument( "No such choice for control. Use :components, :norm or :all" ), ) @@ -978,7 +992,13 @@ function __plot!( end end else - throw(CTModels.Exceptions.IncorrectArgument("No such choice for layout. Use :group or :split")) + throw(Exceptions.IncorrectArgument( + "Invalid layout choice"; + got="layout=$layout", + expected=":group or :split", + suggestion="Use one of the supported layout options", + context="plot layout parameter" + )) end # end layout # plot vertical lines at the initial and final times if model is not nothing @@ -1419,7 +1439,11 @@ function __get_data_plot( # if the time grid is empty then throw an error if CTModels.is_empty_time_grid(sol) == true - throw(CTModels.Exceptions.IncorrectArgument("The time grid is empty")) + throw(Exceptions.IncorrectArgument( + "The time grid is empty"; + suggestion="Provide a solution with non-empty time grid", + context="plot validation" + )) end vv, ii = MLStyle.@match xx begin diff --git a/ext/plot_default.jl b/ext/plot_default.jl index c248064d..657feaf6 100644 --- a/ext/plot_default.jl +++ b/ext/plot_default.jl @@ -143,7 +143,7 @@ function __size_plot( :norm => 1 :all => m + 1 _ => throw( - CTModels.Exceptions.IncorrectArgument( + Exceptions.IncorrectArgument( "Invalid control choice", got="control=$control", expected=":components, :norm or :all", diff --git a/migration_to_ctsolvers/docs/api_reference.jl b/migration_to_ctsolvers/docs/api_reference.jl new file mode 100644 index 00000000..a063ef94 --- /dev/null +++ b/migration_to_ctsolvers/docs/api_reference.jl @@ -0,0 +1,480 @@ +# ============================================================================== +# CTModels API Reference Generator +# ============================================================================== +# +# This module provides functions to generate API reference documentation +# for CTModels.jl, following the pattern established in CTBase.jl. +# +# ============================================================================== + +""" + generate_api_reference(src_dir::String, ext_dir::String) + +Generate the API reference documentation for CTModels. +Returns the list of pages. +""" +function generate_api_reference(src_dir::String, ext_dir::String) + # Helper to build absolute paths + src(files...) = [abspath(joinpath(src_dir, f)) for f in files] + ext(files...) = [abspath(joinpath(ext_dir, f)) for f in files] + + # Symbols to exclude from documentation (auto-generated by @with_kw, etc.) + EXCLUDE_SYMBOLS = Symbol[ + :include, + :eval, + Symbol("@pack_PreModel"), + Symbol("@pack_PreModel!"), + Symbol("@unpack_PreModel"), + :is_empty, + ] + + pages = [ + # ─────────────────────────────────────────────────────────────────── + # Main module + # ─────────────────────────────────────────────────────────────────── + CTBase.automatic_reference_documentation(; + subdirectory=".", + primary_modules=[CTModels => src("CTModels.jl")], + exclude=EXCLUDE_SYMBOLS, + public=false, + private=true, + title="CTModels", + title_in_menu="CTModels", + filename="ctmodels", + ), + # ─────────────────────────────────────────────────────────────────── + # Core: OCP Types + # ─────────────────────────────────────────────────────────────────── + CTBase.automatic_reference_documentation(; + subdirectory=".", + primary_modules=[ + CTModels => src( + "ocp/types/components.jl", + "ocp/types/model.jl", + "ocp/types/solution.jl", + ), + ], + exclude=EXCLUDE_SYMBOLS, + public=false, + private=true, + title="OCP Types", + title_in_menu="OCP Types", + filename="ocp_types", + ), + # ─────────────────────────────────────────────────────────────────── + # Base Types & Export/Import + # ─────────────────────────────────────────────────────────────────── + CTBase.automatic_reference_documentation(; + subdirectory=".", + primary_modules=[ + CTModels => src( + "types/aliases.jl", + "types/export_import.jl", + "types/export_import_functions.jl", + ), + ], + exclude=EXCLUDE_SYMBOLS, + public=false, + private=true, + title="Base Types & Export/Import", + title_in_menu="Base Types & Export/Import", + filename="base_types_export_import", + ), + # ─────────────────────────────────────────────────────────────────── + # Options Module - Public API + # ─────────────────────────────────────────────────────────────────── + CTBase.automatic_reference_documentation(; + subdirectory="options", + primary_modules=[ + CTModels => src( + "Options/Options.jl", + "Options/option_value.jl", + "Options/option_definition.jl", + "Options/extraction.jl", + ), + ], + exclude=EXCLUDE_SYMBOLS, + public=true, + private=false, + title="Options - Public API", + title_in_menu="Options (Public)", + filename="options_public", + ), + # ─────────────────────────────────────────────────────────────────── + # Options Module - Internal API + # ─────────────────────────────────────────────────────────────────── + CTBase.automatic_reference_documentation(; + subdirectory="options", + primary_modules=[ + CTModels => src( + "Options/Options.jl", + "Options/option_value.jl", + "Options/option_definition.jl", + "Options/extraction.jl", + ), + ], + exclude=EXCLUDE_SYMBOLS, + public=false, + private=true, + title="Options - Internal API", + title_in_menu="Options (Internal)", + filename="options_internal", + ), + # ─────────────────────────────────────────────────────────────────── + # Strategies Module - Contract (Public) + # ─────────────────────────────────────────────────────────────────── + CTBase.automatic_reference_documentation(; + subdirectory="strategies", + primary_modules=[ + CTModels => src( + "Strategies/Strategies.jl", + "Strategies/contract/abstract_strategy.jl", + "Strategies/contract/metadata.jl", + "Strategies/contract/strategy_options.jl", + ), + ], + exclude=EXCLUDE_SYMBOLS, + public=true, + private=false, + title="Strategies - Contract (Public)", + title_in_menu="Strategies Contract (Public)", + filename="strategies_contract_public", + ), + # ─────────────────────────────────────────────────────────────────── + # Strategies Module - Contract (Internal) + # ─────────────────────────────────────────────────────────────────── + CTBase.automatic_reference_documentation(; + subdirectory="strategies", + primary_modules=[ + CTModels => src( + "Strategies/Strategies.jl", + "Strategies/contract/abstract_strategy.jl", + "Strategies/contract/metadata.jl", + "Strategies/contract/strategy_options.jl", + ), + ], + exclude=EXCLUDE_SYMBOLS, + public=false, + private=true, + title="Strategies - Contract (Internal)", + title_in_menu="Strategies Contract (Internal)", + filename="strategies_contract_internal", + ), + # ─────────────────────────────────────────────────────────────────── + # Strategies Module - API (Public) + # ─────────────────────────────────────────────────────────────────── + CTBase.automatic_reference_documentation(; + subdirectory="strategies", + primary_modules=[ + CTModels => src( + "Strategies/api/builders.jl", + "Strategies/api/configuration.jl", + "Strategies/api/introspection.jl", + "Strategies/api/registry.jl", + "Strategies/api/utilities.jl", + "Strategies/api/validation.jl", + ), + ], + exclude=EXCLUDE_SYMBOLS, + public=true, + private=false, + title="Strategies - API (Public)", + title_in_menu="Strategies API (Public)", + filename="strategies_api_public", + ), + # ─────────────────────────────────────────────────────────────────── + # Strategies Module - API (Internal) + # ─────────────────────────────────────────────────────────────────── + CTBase.automatic_reference_documentation(; + subdirectory="strategies", + primary_modules=[ + CTModels => src( + "Strategies/api/builders.jl", + "Strategies/api/configuration.jl", + "Strategies/api/introspection.jl", + "Strategies/api/registry.jl", + "Strategies/api/utilities.jl", + "Strategies/api/validation.jl", + ), + ], + exclude=EXCLUDE_SYMBOLS, + public=false, + private=true, + title="Strategies - API (Internal)", + title_in_menu="Strategies API (Internal)", + filename="strategies_api_internal", + ), + # ─────────────────────────────────────────────────────────────────── + # Orchestration Module - Public API + # ─────────────────────────────────────────────────────────────────── + CTBase.automatic_reference_documentation(; + subdirectory="orchestration", + primary_modules=[ + CTModels => src( + "Orchestration/Orchestration.jl", + "Orchestration/routing.jl", + "Orchestration/disambiguation.jl", + "Orchestration/method_builders.jl", + ), + ], + exclude=EXCLUDE_SYMBOLS, + public=true, + private=false, + title="Orchestration - Public API", + title_in_menu="Orchestration (Public)", + filename="orchestration_public", + ), + # ─────────────────────────────────────────────────────────────────── + # Orchestration Module - Internal API + # ─────────────────────────────────────────────────────────────────── + CTBase.automatic_reference_documentation(; + subdirectory="orchestration", + primary_modules=[ + CTModels => src( + "Orchestration/Orchestration.jl", + "Orchestration/routing.jl", + "Orchestration/disambiguation.jl", + "Orchestration/method_builders.jl", + ), + ], + exclude=EXCLUDE_SYMBOLS, + public=false, + private=true, + title="Orchestration - Internal API", + title_in_menu="Orchestration (Internal)", + filename="orchestration_internal", + ), + # ─────────────────────────────────────────────────────────────────── + # Defaults & Utils + # ─────────────────────────────────────────────────────────────────── + CTBase.automatic_reference_documentation(; + subdirectory=".", + primary_modules=[ + CTModels => src( + "ocp/defaults.jl", + "utils/interpolation.jl", + "utils/matrix_utils.jl", + "utils/function_utils.jl", + "utils/macros.jl", + ), + ], + exclude=EXCLUDE_SYMBOLS, + public=false, + private=true, + title="Defaults & Utils", + title_in_menu="Defaults & Utils", + filename="defaults_utils", + ), + # ─────────────────────────────────────────────────────────────────── + # OCP: Model (model, definition, time_dependence) + # ─────────────────────────────────────────────────────────────────── + CTBase.automatic_reference_documentation(; + subdirectory=".", + primary_modules=[ + CTModels => + src("ocp/model.jl", "ocp/definition.jl", "ocp/time_dependence.jl"), + ], + exclude=EXCLUDE_SYMBOLS, + public=false, + private=true, + title="Model", + title_in_menu="Model", + filename="model", + ), + # ─────────────────────────────────────────────────────────────────── + # OCP: Times + # ─────────────────────────────────────────────────────────────────── + CTBase.automatic_reference_documentation(; + subdirectory=".", + primary_modules=[CTModels => src("ocp/times.jl")], + exclude=EXCLUDE_SYMBOLS, + public=false, + private=true, + title="Times", + title_in_menu="Times", + filename="times", + ), + # ─────────────────────────────────────────────────────────────────── + # OCP: State, Control, Variable + # ─────────────────────────────────────────────────────────────────── + CTBase.automatic_reference_documentation(; + subdirectory=".", + primary_modules=[ + CTModels => src("ocp/state.jl", "ocp/control.jl", "ocp/variable.jl") + ], + exclude=EXCLUDE_SYMBOLS, + public=false, + private=true, + title="State, Control & Variable", + title_in_menu="State, Control & Variable", + filename="state_control_variable", + ), + # ─────────────────────────────────────────────────────────────────── + # OCP: Dynamics & Objective + # ─────────────────────────────────────────────────────────────────── + CTBase.automatic_reference_documentation(; + subdirectory=".", + primary_modules=[CTModels => src("ocp/dynamics.jl", "ocp/objective.jl")], + exclude=EXCLUDE_SYMBOLS, + public=false, + private=true, + title="Dynamics & Objective", + title_in_menu="Dynamics & Objective", + filename="dynamics_objective", + ), + # ─────────────────────────────────────────────────────────────────── + # OCP: Constraints + # ─────────────────────────────────────────────────────────────────── + CTBase.automatic_reference_documentation(; + subdirectory=".", + primary_modules=[CTModels => src("ocp/constraints.jl")], + exclude=EXCLUDE_SYMBOLS, + public=false, + private=true, + title="Constraints", + title_in_menu="Constraints", + filename="constraints", + ), + # ─────────────────────────────────────────────────────────────────── + # OCP: Solution & Dual + # ─────────────────────────────────────────────────────────────────── + CTBase.automatic_reference_documentation(; + subdirectory=".", + primary_modules=[CTModels => src("ocp/solution.jl", "ocp/dual_model.jl")], + exclude=EXCLUDE_SYMBOLS, + public=false, + private=true, + title="Solution & Dual", + title_in_menu="Solution & Dual", + filename="solution_dual", + ), + # ─────────────────────────────────────────────────────────────────── + # OCP: Print + # ─────────────────────────────────────────────────────────────────── + CTBase.automatic_reference_documentation(; + subdirectory=".", + primary_modules=[CTModels => src("ocp/print.jl")], + exclude=EXCLUDE_SYMBOLS, + public=false, + private=true, + title="Print", + title_in_menu="Print", + filename="print", + ), + # ─────────────────────────────────────────────────────────────────── + # Initial Guess + # ─────────────────────────────────────────────────────────────────── + CTBase.automatic_reference_documentation(; + subdirectory=".", + primary_modules=[CTModels => src("init/initial_guess.jl")], + exclude=EXCLUDE_SYMBOLS, + public=false, + private=true, + title="Initial Guess", + title_in_menu="Initial Guess", + filename="initial_guess", + ), + # ─────────────────────────────────────────────────────────────────── + # NLP Backends + # ─────────────────────────────────────────────────────────────────── + CTBase.automatic_reference_documentation(; + subdirectory=".", + primary_modules=[ + CTModels => src( + "nlp/nlp_backends.jl", + "nlp/options_schema.jl", + "nlp/problem_core.jl", + "nlp/discretized_ocp.jl", + "nlp/model_api.jl", + ), + ], + exclude=EXCLUDE_SYMBOLS, + public=false, + private=true, + title="NLP Backends", + title_in_menu="NLP Backends", + filename="nlp", + ), + ] + + # ─────────────────────────────────────────────────────────────────── + # Extension: Plot + # ─────────────────────────────────────────────────────────────────── + CTModelsPlots = Base.get_extension(CTModels, :CTModelsPlots) + if !isnothing(CTModelsPlots) + push!( + pages, + CTBase.automatic_reference_documentation(; + subdirectory=".", + primary_modules=[ + CTModelsPlots => ext( + "CTModelsPlots.jl", + "plot.jl", + "plot_default.jl", + "plot_utils.jl", + ), + ], + external_modules_to_document=[Plots], + exclude=EXCLUDE_SYMBOLS, + public=false, + private=true, + title="Plot Extension", + title_in_menu="Plot", + filename="plot", + ), + ) + end + + # ─────────────────────────────────────────────────────────────────── + # Extension: JLD & JSON (combined) + # ─────────────────────────────────────────────────────────────────── + CTModelsJSON = Base.get_extension(CTModels, :CTModelsJSON) + CTModelsJLD = Base.get_extension(CTModels, :CTModelsJLD) + if !isnothing(CTModelsJSON) && !isnothing(CTModelsJLD) + push!( + pages, + CTBase.automatic_reference_documentation(; + subdirectory=".", + primary_modules=[ + CTModelsJSON => ext("CTModelsJSON.jl"), + CTModelsJLD => ext("CTModelsJLD.jl"), + ], + external_modules_to_document=[CTModels], + exclude=EXCLUDE_SYMBOLS, + public=false, + private=true, + title="JLD & JSON Extension", + title_in_menu="JLD & JSON", + filename="import_export", + ), + ) + end + + return pages +end + +""" + with_api_reference(f::Function, src_dir::String, ext_dir::String) + +Generates the API reference, executes `f(pages)`, and cleans up generated files. +""" +function with_api_reference(f::Function, src_dir::String, ext_dir::String) + pages = generate_api_reference(src_dir, ext_dir) + try + f(pages) + finally + # Clean up generated files + docs_src = abspath(joinpath(@__DIR__, "src")) + + for p in pages + filename = last(p) + fname = endswith(filename, ".md") ? filename : filename * ".md" + full_path = joinpath(docs_src, fname) + + if isfile(full_path) + rm(full_path) + println("Removed temporary API doc: $full_path") + end + end + end +end diff --git a/migration_to_ctsolvers/docs/make.jl b/migration_to_ctsolvers/docs/make.jl new file mode 100644 index 00000000..44ef76c3 --- /dev/null +++ b/migration_to_ctsolvers/docs/make.jl @@ -0,0 +1,111 @@ +using Documenter +using CTModels +using CTBase # For automatic_reference_documentation +using Plots +using JSON3 +using JLD2 +using Markdown +using MarkdownAST: MarkdownAST + +# ═══════════════════════════════════════════════════════════════════════════════ +# Configuration +# ═══════════════════════════════════════════════════════════════════════════════ +draft = false # Draft mode: if true, @example blocks in markdown are not executed + +# ═══════════════════════════════════════════════════════════════════════════════ +# Load extensions +# ═══════════════════════════════════════════════════════════════════════════════ +const CTModelsPlots = Base.get_extension(CTModels, :CTModelsPlots) +const CTModelsJSON = Base.get_extension(CTModels, :CTModelsJSON) +const CTModelsJLD = Base.get_extension(CTModels, :CTModelsJLD) +const DocumenterReference = Base.get_extension(CTBase, :DocumenterReference) + +# Reset DocumenterReference configuration for proper local/remote link generation +if !isnothing(DocumenterReference) + DocumenterReference.reset_config!() +end + +# to add docstrings from external packages +Modules = [Plots, CTModelsPlots, CTModelsJSON, CTModelsJLD] +for Module in Modules + isnothing(DocMeta.getdocmeta(Module, :DocTestSetup)) && + DocMeta.setdocmeta!(Module, :DocTestSetup, :(using $Module); recursive=true) +end + +# ═══════════════════════════════════════════════════════════════════════════════ +# Paths +# ═══════════════════════════════════════════════════════════════════════════════ +repo_url = "github.com/control-toolbox/CTModels.jl" +src_dir = abspath(joinpath(@__DIR__, "..", "src")) +ext_dir = abspath(joinpath(@__DIR__, "..", "ext")) + +# Include the API reference manager +include("api_reference.jl") + +# ═══════════════════════════════════════════════════════════════════════════════ +# Build documentation +# ═══════════════════════════════════════════════════════════════════════════════ +with_api_reference(src_dir, ext_dir) do api_pages + makedocs(; + draft=draft, + remotes=nothing, # Disable remote links. Needed for DocumenterReference + warnonly=true, + sitename="CTModels.jl", + format=Documenter.HTML(; + repolink="https://" * repo_url, + prettyurls=false, + #size_threshold_ignore=["api.md", "dev.md"], + #size_threshold=300_000, # 300 KiB threshold + assets=[ + asset("https://control-toolbox.org/assets/css/documentation.css"), + asset("https://control-toolbox.org/assets/js/documentation.js"), + ], + ), + checkdocs=:none, + pages=[ + "Introduction" => "index.md", + "User Guide" => [ + "Defining Problems" => "interfaces/optimization_problems.md", + "Building Solutions" => "interfaces/ocp_solution_builders.md", + ], + "Developer Guide" => [ + "Tutorials" => [ + "Creating a Strategy" => "tutorials/creating_a_strategy.md", + "Creating a Strategy Family" => "tutorials/creating_a_strategy_family.md", + ], + "Interfaces" => [ + "Strategies" => "interfaces/strategies.md", + "Strategy Families" => "interfaces/strategy_families.md", + "Orchestration & Routing" => "interfaces/orchestration.md", + "Optimization Modelers" => "interfaces/optimization_modelers.md", + ], + "Examples" => [ + "Simple Strategy" => "examples/simple_strategy.md", + "Strategy with Options" => "examples/strategy_with_options.md", + "Strategy Family" => "examples/strategy_family.md", + "Option Routing" => "examples/routing_example.md", + "Integration Example" => "examples/integration_example.md", + "Migration Example" => "examples/migration_example.md", + ], + ], + "API Reference" => [ + "Public API" => [ + "Options" => "options/options_public.md", + "Strategies (Contract)" => "strategies/strategies_contract_public.md", + "Strategies (API)" => "strategies/strategies_api_public.md", + "Orchestration" => "orchestration/orchestration_public.md", + ], + "Internal API" => [ + "Options (Internal)" => "options/options_internal.md", + "Strategies Contract (Internal)" => "strategies/strategies_contract_internal.md", + "Strategies API (Internal)" => "strategies/strategies_api_internal.md", + "Orchestration (Internal)" => "orchestration/orchestration_internal.md", + ], + "Core & OCP" => api_pages, + ], + ], + ) +end + +# ═══════════════════════════════════════════════════════════════════════════════ +deploydocs(; repo=repo_url * ".git", devbranch="main") diff --git a/docs/src/examples/integration_example.md b/migration_to_ctsolvers/docs/src/examples/integration_example.md similarity index 100% rename from docs/src/examples/integration_example.md rename to migration_to_ctsolvers/docs/src/examples/integration_example.md diff --git a/docs/src/examples/migration_example.md b/migration_to_ctsolvers/docs/src/examples/migration_example.md similarity index 100% rename from docs/src/examples/migration_example.md rename to migration_to_ctsolvers/docs/src/examples/migration_example.md diff --git a/docs/src/examples/routing_example.md b/migration_to_ctsolvers/docs/src/examples/routing_example.md similarity index 100% rename from docs/src/examples/routing_example.md rename to migration_to_ctsolvers/docs/src/examples/routing_example.md diff --git a/docs/src/examples/simple_strategy.md b/migration_to_ctsolvers/docs/src/examples/simple_strategy.md similarity index 100% rename from docs/src/examples/simple_strategy.md rename to migration_to_ctsolvers/docs/src/examples/simple_strategy.md diff --git a/docs/src/examples/strategy_family.md b/migration_to_ctsolvers/docs/src/examples/strategy_family.md similarity index 100% rename from docs/src/examples/strategy_family.md rename to migration_to_ctsolvers/docs/src/examples/strategy_family.md diff --git a/docs/src/examples/strategy_with_options.md b/migration_to_ctsolvers/docs/src/examples/strategy_with_options.md similarity index 100% rename from docs/src/examples/strategy_with_options.md rename to migration_to_ctsolvers/docs/src/examples/strategy_with_options.md diff --git a/migration_to_ctsolvers/docs/src/index.md b/migration_to_ctsolvers/docs/src/index.md new file mode 100644 index 00000000..f3474f8d --- /dev/null +++ b/migration_to_ctsolvers/docs/src/index.md @@ -0,0 +1,238 @@ +# CTModels.jl + +```@meta +CurrentModule = CTModels +``` + +The `CTModels.jl` package is part of the [control-toolbox ecosystem](https://github.com/control-toolbox). +It provides the **mathematical model layer** for optimal control problems: + +- **types and building blocks** for states, controls, variables, time grids, and constraints; +- an `AbstractModel`/`Model` and `AbstractSolution`/`Solution` hierarchy for optimal control problems; +- tools to build **initial guesses**, connect to **NLP backends**, and interpret their solutions; +- optional extensions for **exporting solutions** (JSON/JLD) and **plotting**. + +!!! note + + The root package is [OptimalControl.jl](https://github.com/control-toolbox/OptimalControl.jl) which aims + to provide tools to model and solve optimal control problems with ordinary differential equations + by direct and indirect methods, both on CPU and GPU. + +!!! warning + + In some examples in the documentation, private methods are shown without the module prefix. + This is done for the sake of clarity and readability. + + ```julia-repl + julia> using CTModels + julia> x = 1 + julia> private_fun(x) # throws an error + ``` + + This should instead be written as: + + ```julia-repl + julia> using CTModels + julia> x = 1 + julia> CTModels.private_fun(x) + ``` + + If the method is re-exported by another package, + + ```julia + module OptimalControl + import CTModels: private_fun + export private_fun + end + ``` + + then there is no need to prefix it with the original module name: + + ```julia-repl + julia> using OptimalControl + julia> x = 1 + julia> private_fun(x) + ``` + +## What CTModels provides + +At a high level, CTModels is responsible for: + +- **Defining optimal control problems**: + `AbstractModel` / `Model` store dynamics, objective, constraints, time structure, and metadata. +- **Representing numerical solutions**: + `AbstractSolution` / `Solution` store state, control, dual variables, and solver information. +- **Managing time grids and dimensions** through convenient type aliases. +- **Structuring constraints** (path, boundary, box constraints on state, control, and variables). +- **Connecting to NLP backends** (ADNLPModels, ExaModels, etc.) via modelers and builders. +- **Strategy architecture** (NEW): + - **Options**: Generic option handling with aliases and validation + - **Strategies**: Configurable components (modelers, solvers, discretizers) +- **Providing utilities** for initial guesses, export/import, and plotting of solutions. + +Most of the public API is organized in a way that closely mirrors the mathematical +objects you manipulate when formulating an optimal control problem. + +## Strategy Architecture + +CTModels provides a modern, type-stable architecture for configurable components: + +- **Options Module**: Low-level option extraction, validation, and alias resolution. +- **Strategies Module**: Strategy contract, metadata, registry, and builders. + +This architecture replaces the legacy `AbstractOCPTool` interface with a cleaner, +more maintainable design. See the **Developer Guide → Interfaces → Strategies** section for details. + +## Time grids and basic aliases + +CTModels defines a few central type aliases that appear throughout the API: + +- `Dimension`: integer dimensions used for state, control, and variables. +- `ctNumber` and `ctVector`: real numbers and vectors of reals. +- `Time`, `Times`, `TimesDisc`: continuous time, time vectors, and discrete time grids. + +These aliases make type signatures more readable while remaining flexible enough +to accept a variety of numeric types. + +## Models, solutions, and constraints + +The core **optimal control model** is expressed via: + +- `AbstractModel` / `Model`: store the structure of the OCP + (dynamics, objective, constraints, time dependence, etc.). +- `ConstraintsModel`: a structured representation of all constraints + (path constraints, boundary constraints, and box constraints on state, control, and variables). + +In practice you typically: + +1. Specify **time dependence** and **time models** (fixed or free final time, etc.). +2. Describe **state, control, and variable spaces**. +3. Provide **dynamics** and **objective** functions. +4. Add **constraints**, either programmatically or via a `ConstraintsDictType` dictionary. + +The numerical **solution** of an OCP is represented by: + +- `AbstractSolution` / `Solution`: contain time grids, state and control trajectories, + path and boundary dual variables, solver status, and diagnostics. +- `DualModel` and related types: organize dual variables associated with constraints. + +These objects are the main bridge between the mathematical problem and the NLP backends. + +## Initial guesses + +Good initial guesses are crucial for challenging optimal control problems. +CTModels provides a small layer to organize them: + +- `pre_initial_guess` builds an `OptimalControlPreInit` object from raw user data + (functions, vectors, or constants for state, control, and variables). +- `initial_guess` turns this into an `OptimalControlInitialGuess`, checking consistency + with the chosen `AbstractOptimalControlProblem`. + +The corresponding API is implemented in `src/init/initial_guess.jl` and is documented +in the *Initial Guess* section of the API reference. + +## NLP backends and modelers + +CTModels does **not** solve the NLP itself. Instead, it connects to external NLP +backends via modelers and builders defined in `src/nlp/`: + +- `ADNLPModeler` (based on `ADNLPModels.jl`), +- `ExaModeler` (based on `ExaModels.jl`), +- additional builder types and helper functions. + +These modelers: + +- expose options through the generic `AbstractOCPTool` interface from CTBase + (see the *Interfaces → OCP Tools* page), +- build backend-specific NLP models from an `AbstractOptimizationProblem`, +- optionally map NLP solutions back to `CTModels.Solution` objects. + +The *Interfaces* section of the documentation contains detailed guides for: + +- implementing new **optimization problems**, +- implementing new **optimization modelers**, and +- implementing new **OCP solution builders**. + +## Extensions: JSON, JLD, and plotting + +Several optional extensions live in the `ext/` directory and are loaded on demand +by the corresponding packages: + +- **CTModelsJSON.jl** (requires `JSON3.jl`): + helpers to serialize/deserialize the `infos::Dict{Symbol,Any}` carried by solutions, + and methods for + `export_ocp_solution(CTModels.JSON3Tag(), ::Solution)` / + `import_ocp_solution(CTModels.JSON3Tag(), ::Model)`. + +- **CTModelsJLD.jl** (requires `JLD2.jl`): + methods to export and import a `Solution` as a `.jld2` file using + `export_ocp_solution(CTModels.JLD2Tag(), ::Solution)` and + `import_ocp_solution(CTModels.JLD2Tag(), ::Model)`. + +- **CTModelsPlots.jl** (requires `Plots.jl`): + plot recipes and helpers that make + `Plots.plot(sol::CTModels.Solution, ...)` + and + `Plots.plot!(sol::CTModels.Solution, ...)` + display the trajectories of state, control, costate, constraints, and dual + variables in a consistent, configurable way. + +If the corresponding extension package is not loaded, the public wrappers +`export_ocp_solution`, `import_ocp_solution`, and the generic `RecipesBase.plot` +throw a descriptive `CTBase.ExtensionError`. + +## How this documentation is organized + +The documentation is split into two main parts: + +- **Interfaces** + - *OCP Tools*: how to implement new configurable tools (backends, discretizers, solvers). + - *Optimization Problems*: how to define `AbstractOptimizationProblem` types. + - *Optimization Modelers*: how to map optimization problems to specific NLP backends. + - *Solution Builders*: how to turn NLP execution statistics into `CTModels.Solution` objects. + +- **API Reference** + - *Types*: core types for models, solutions, and internal structures. + - *Model / Times / Dynamics / Objective / Constraints*: detailed API for building OCP models. + - *Solution & Dual*: how solutions and dual variables are represented. + - *Initial Guess*: utilities to build and validate initial guesses. + - *NLP Backends*: ADNLPModels/ExaModels-based backends and related options. + - *Extensions*: Plot, JSON, and JLD extensions. + +You can start by reading the **Interfaces** pages to understand the high-level +design, then use the **API Reference** to look up the details of particular +functions and types. + +## I am X, I want to do Y → read… + +### User Guide + +- **I want to formulate a new optimal control / optimization problem** + Read **User Guide → Optimization Problems**, then **API Reference → Model / Times / Dynamics / Objective / Constraints** + for details about fields and conventions. +- **I want to build good initial guesses for my problems** + Read **User Guide → Solution Builders** for the overall philosophy, then **API Reference → Initial Guess** + for the `pre_initial_guess` and `initial_guess` functions. +- **I want to save / reload solutions (for example for numerical experiments)** + Read **API Reference → Extensions (JSON & JLD)** and the pages associated with the `CTModelsJSON` and `CTModelsJLD` modules. +- **I want to plot solution trajectories nicely** + Read **API Reference → Extensions (Plot Extension)**, and look at the examples using `Plots.plot(sol)` and `Plots.plot!(sol)`. +- **I use OptimalControl.jl and I just want to understand what CTModels does in the background** + Read this introduction page, then skim through the **User Guide** section to see how + problems, modelers, and builders fit together. + +### Developer Guide + +- **I want to create a new strategy (modeler, solver, discretizer)** + Read **Developer Guide → Tutorials → Creating a Strategy**, then **Developer Guide → Interfaces → Strategies** + for the complete contract specification. +- **I want to create a family of related strategies** + Read **Developer Guide → Tutorials → Creating a Strategy Family**, then **Developer Guide → Interfaces → Strategy Families** + for registry integration and best practices. +- **I want to migrate from AbstractOCPTool to AbstractStrategy** + Read **Developer Guide → Interfaces → Strategies → Migration Guide** for step-by-step instructions. +- **I want to connect a new NLP backend or tweak an existing backend** + Read **Developer Guide → Interfaces → Optimization Modelers** (updated) and the **API Reference → NLP Backends** section. +- **I want to contribute to the core of CTModels (types, constraints, dual variables, etc.)** + Start with **API Reference → Types**, then **Solution & Dual** and **Constraints** to understand the internal structures + before modifying or adding new fields. diff --git a/docs/src/interfaces/ocp_solution_builders.md b/migration_to_ctsolvers/docs/src/interfaces/ocp_solution_builders.md similarity index 100% rename from docs/src/interfaces/ocp_solution_builders.md rename to migration_to_ctsolvers/docs/src/interfaces/ocp_solution_builders.md diff --git a/docs/src/interfaces/optimization_modelers.md b/migration_to_ctsolvers/docs/src/interfaces/optimization_modelers.md similarity index 100% rename from docs/src/interfaces/optimization_modelers.md rename to migration_to_ctsolvers/docs/src/interfaces/optimization_modelers.md diff --git a/docs/src/interfaces/optimization_problems.md b/migration_to_ctsolvers/docs/src/interfaces/optimization_problems.md similarity index 100% rename from docs/src/interfaces/optimization_problems.md rename to migration_to_ctsolvers/docs/src/interfaces/optimization_problems.md diff --git a/docs/src/interfaces/orchestration.md b/migration_to_ctsolvers/docs/src/interfaces/orchestration.md similarity index 100% rename from docs/src/interfaces/orchestration.md rename to migration_to_ctsolvers/docs/src/interfaces/orchestration.md diff --git a/docs/src/interfaces/strategies.md b/migration_to_ctsolvers/docs/src/interfaces/strategies.md similarity index 100% rename from docs/src/interfaces/strategies.md rename to migration_to_ctsolvers/docs/src/interfaces/strategies.md diff --git a/docs/src/interfaces/strategy_families.md b/migration_to_ctsolvers/docs/src/interfaces/strategy_families.md similarity index 100% rename from docs/src/interfaces/strategy_families.md rename to migration_to_ctsolvers/docs/src/interfaces/strategy_families.md diff --git a/docs/src/options/private.md b/migration_to_ctsolvers/docs/src/options/private.md similarity index 100% rename from docs/src/options/private.md rename to migration_to_ctsolvers/docs/src/options/private.md diff --git a/docs/src/options/public.md b/migration_to_ctsolvers/docs/src/options/public.md similarity index 100% rename from docs/src/options/public.md rename to migration_to_ctsolvers/docs/src/options/public.md diff --git a/docs/src/strategies/api/private.md b/migration_to_ctsolvers/docs/src/strategies/api/private.md similarity index 100% rename from docs/src/strategies/api/private.md rename to migration_to_ctsolvers/docs/src/strategies/api/private.md diff --git a/docs/src/strategies/api/public.md b/migration_to_ctsolvers/docs/src/strategies/api/public.md similarity index 100% rename from docs/src/strategies/api/public.md rename to migration_to_ctsolvers/docs/src/strategies/api/public.md diff --git a/docs/src/strategies/contract/private.md b/migration_to_ctsolvers/docs/src/strategies/contract/private.md similarity index 100% rename from docs/src/strategies/contract/private.md rename to migration_to_ctsolvers/docs/src/strategies/contract/private.md diff --git a/docs/src/strategies/contract/public.md b/migration_to_ctsolvers/docs/src/strategies/contract/public.md similarity index 100% rename from docs/src/strategies/contract/public.md rename to migration_to_ctsolvers/docs/src/strategies/contract/public.md diff --git a/docs/src/tutorials/creating_a_strategy.md b/migration_to_ctsolvers/docs/src/tutorials/creating_a_strategy.md similarity index 100% rename from docs/src/tutorials/creating_a_strategy.md rename to migration_to_ctsolvers/docs/src/tutorials/creating_a_strategy.md diff --git a/docs/src/tutorials/creating_a_strategy_family.md b/migration_to_ctsolvers/docs/src/tutorials/creating_a_strategy_family.md similarity index 100% rename from docs/src/tutorials/creating_a_strategy_family.md rename to migration_to_ctsolvers/docs/src/tutorials/creating_a_strategy_family.md diff --git a/ext/CTModelsMadNLP.jl b/migration_to_ctsolvers/ext/CTModelsMadNLP.jl similarity index 100% rename from ext/CTModelsMadNLP.jl rename to migration_to_ctsolvers/ext/CTModelsMadNLP.jl diff --git a/migration_to_ctsolvers/src/CTModels.jl b/migration_to_ctsolvers/src/CTModels.jl new file mode 100644 index 00000000..48057869 --- /dev/null +++ b/migration_to_ctsolvers/src/CTModels.jl @@ -0,0 +1,132 @@ +""" + CTModels + +Control Toolbox Models (CTModels) - A Julia package for optimal control problems. + +This module provides a comprehensive framework for defining, building, and solving +optimal control problems with a modular architecture that separates concerns and +facilitates extensibility. + +# Architecture Overview + +CTModels is organized into specialized modules, each with clear responsibilities: + +## Core Modules + +- **OCP**: Optimal Control Problem core + - Types: `Model`, `PreModel`, `Solution`, `AbstractModel`, `AbstractSolution` + - Components: state, control, dynamics, objective, constraints + - Builders: model construction and solution building + - Type aliases: `Dimension`, `ctNumber`, `Time`, `Times`, `TimesDisc`, `ConstraintsDictType` + +- **Utils**: General utilities + - Interpolation: `ctinterpolate` + - Matrix operations: `matrix2vec` + - Macros: `@ensure` for validation + +- **Display**: Formatting and visualization + - Text display via `Base.show` extensions + - Plotting stubs via `RecipesBase.plot` + +- **Serialization**: Import/export functionality + - `export_ocp_solution`, `import_ocp_solution` + - Format tags: `JLD2Tag`, `JSON3Tag` + +- **InitialGuess**: Initial guess management + - `initial_guess`, `build_initial_guess`, `validate_initial_guess` + - Types: `OptimalControlInitialGuess`, `OptimalControlPreInit` + +## Supporting Modules + +- **Options**: Configuration and options management +- **Strategies**: Strategy patterns for optimization +- **Orchestration**: High-level orchestration and coordination +- **Optimization**: General optimization types and builders +- **Modelers**: Modeler implementations (ADNLPModeler, ExaModeler) +- **DOCP**: Discretized Optimal Control Problem types + +# Loading Order + +Modules are loaded in dependency order to ensure all types and functions are available +when needed: + +1. **Foundational types** → **Utils** → **OCP** → **Display/Serialization/InitialGuess** +2. **Supporting modules** → **Optimization** → **Modelers** → **DOCP** + +# Public API + +All exported functions and types are accessible via `CTModels.function_name()`. +The modular architecture ensures that: + +- Types are defined where they belong +- Dependencies are explicit and minimal +- Extensions can target specific modules +- The public API remains stable and clean +""" +module CTModels + +# ============================================================================ # +# FOUNDATIONAL TYPES AND UTILITIES +# ============================================================================ # + +# Utils module - must load before OCP (uses @ensure macro) +include(joinpath(@__DIR__, "Utils", "Utils.jl")) +using .Utils +import .Utils: @ensure + +# ============================================================================ # +# CONFIGURATION AND STRATEGY MODULES +# ============================================================================ # + +# Configuration and strategy modules (no dependencies) +include(joinpath(@__DIR__, "Options", "Options.jl")) +using .Options + +include(joinpath(@__DIR__, "Strategies", "Strategies.jl")) +using .Strategies + +include(joinpath(@__DIR__, "Orchestration", "Orchestration.jl")) +using .Orchestration + +# Optimization framework (general types) +include(joinpath(@__DIR__, "Optimization", "Optimization.jl")) +using .Optimization + +# Modeler implementations (depend on Optimization) +include(joinpath(@__DIR__, "Modelers", "Modelers.jl")) +using .Modelers + +# OCP module - core optimal control problem functionality +# Contains type aliases, types, components, builders, and compatibility aliases +include(joinpath(@__DIR__, "OCP", "OCP.jl")) +using .OCP + +# Discretized OCP types (depend on OCP and Modelers) +include(joinpath(@__DIR__, "DOCP", "DOCP.jl")) +using .DOCP + +# ============================================================================ # +# IMPLEMENTATION MODULES +# ============================================================================ # + +# Display and visualization +include(joinpath(@__DIR__, "Display", "Display.jl")) +using .Display + +# Import and export plot and plot! from RecipesBase for public API +import RecipesBase: RecipesBase, plot, plot! +export plot, plot! + +# Serialization (import/export) +include(joinpath(@__DIR__, "Serialization", "Serialization.jl")) +using .Serialization + +# Initial guess management +include(joinpath(@__DIR__, "InitialGuess", "InitialGuess.jl")) +using .InitialGuess + +# ============================================================================ # +# END OF MODULE +# ============================================================================ # + +end diff --git a/src/DOCP/DOCP.jl b/migration_to_ctsolvers/src/DOCP/DOCP.jl similarity index 97% rename from src/DOCP/DOCP.jl rename to migration_to_ctsolvers/src/DOCP/DOCP.jl index 6dd8fefb..2aec7767 100644 --- a/src/DOCP/DOCP.jl +++ b/migration_to_ctsolvers/src/DOCP/DOCP.jl @@ -11,7 +11,6 @@ module DOCP using DocStringExtensions using NLPModels using SolverCore -using ..CTModels.Exceptions using ..CTModels.Optimization: AbstractOptimizationProblem using ..CTModels.Optimization: AbstractBuilder, AbstractModelBuilder, AbstractSolutionBuilder using ..CTModels.Optimization: AbstractOCPSolutionBuilder diff --git a/src/DOCP/accessors.jl b/migration_to_ctsolvers/src/DOCP/accessors.jl similarity index 100% rename from src/DOCP/accessors.jl rename to migration_to_ctsolvers/src/DOCP/accessors.jl diff --git a/src/DOCP/building.jl b/migration_to_ctsolvers/src/DOCP/building.jl similarity index 100% rename from src/DOCP/building.jl rename to migration_to_ctsolvers/src/DOCP/building.jl diff --git a/src/DOCP/contract_impl.jl b/migration_to_ctsolvers/src/DOCP/contract_impl.jl similarity index 100% rename from src/DOCP/contract_impl.jl rename to migration_to_ctsolvers/src/DOCP/contract_impl.jl diff --git a/src/DOCP/types.jl b/migration_to_ctsolvers/src/DOCP/types.jl similarity index 100% rename from src/DOCP/types.jl rename to migration_to_ctsolvers/src/DOCP/types.jl diff --git a/src/Modelers/Modelers.jl b/migration_to_ctsolvers/src/Modelers/Modelers.jl similarity index 94% rename from src/Modelers/Modelers.jl rename to migration_to_ctsolvers/src/Modelers/Modelers.jl index a9ed1f94..eba434bf 100644 --- a/src/Modelers/Modelers.jl +++ b/migration_to_ctsolvers/src/Modelers/Modelers.jl @@ -8,7 +8,7 @@ module Modelers -using CTBase: CTBase +using CTBase: CTBase, Exceptions using DocStringExtensions using SolverCore using ADNLPModels @@ -16,7 +16,6 @@ using ExaModels using KernelAbstractions using ..CTModels.Options using ..CTModels.Strategies -using ..CTModels.Exceptions using ..CTModels.Optimization: AbstractOptimizationProblem, get_adnlp_model_builder, get_exa_model_builder, get_adnlp_solution_builder, get_exa_solution_builder diff --git a/src/Modelers/abstract_modeler.jl b/migration_to_ctsolvers/src/Modelers/abstract_modeler.jl similarity index 92% rename from src/Modelers/abstract_modeler.jl rename to migration_to_ctsolvers/src/Modelers/abstract_modeler.jl index e5f38f49..cabd026e 100644 --- a/src/Modelers/abstract_modeler.jl +++ b/migration_to_ctsolvers/src/Modelers/abstract_modeler.jl @@ -65,7 +65,7 @@ function (modeler::AbstractOptimizationModeler)( ) throw(Exceptions.NotImplemented( "Model building not implemented", - type_info="Model building not implemented for $(typeof(modeler))", + required_method="(modeler::$(typeof(modeler)))(prob::AbstractOptimizationProblem, initial_guess)", suggestion="Implement the callable method for $(typeof(modeler)) to build NLP models", context="AbstractOptimizationModeler - required method implementation" )) @@ -94,7 +94,7 @@ function (modeler::AbstractOptimizationModeler)( ) throw(Exceptions.NotImplemented( "Solution building not implemented", - type_info="Solution building not implemented for $(typeof(modeler))", + required_method="(modeler::$(typeof(modeler)))(prob::AbstractOptimizationProblem, nlp_solution::SolverCore.AbstractExecutionStats)", suggestion="Implement the callable method for $(typeof(modeler)) to build solution objects", context="AbstractOptimizationModeler - required method implementation" )) diff --git a/src/Modelers/adnlp_modeler.jl b/migration_to_ctsolvers/src/Modelers/adnlp_modeler.jl similarity index 100% rename from src/Modelers/adnlp_modeler.jl rename to migration_to_ctsolvers/src/Modelers/adnlp_modeler.jl diff --git a/src/Modelers/exa_modeler.jl b/migration_to_ctsolvers/src/Modelers/exa_modeler.jl similarity index 100% rename from src/Modelers/exa_modeler.jl rename to migration_to_ctsolvers/src/Modelers/exa_modeler.jl diff --git a/src/Modelers/validation.jl b/migration_to_ctsolvers/src/Modelers/validation.jl similarity index 99% rename from src/Modelers/validation.jl rename to migration_to_ctsolvers/src/Modelers/validation.jl index a0ac18fa..3ae2447f 100644 --- a/src/Modelers/validation.jl +++ b/migration_to_ctsolvers/src/Modelers/validation.jl @@ -292,7 +292,7 @@ ERROR: IncorrectArgument: Backend override must be a Type or nothing """ function validate_backend_override(backend) if backend !== nothing && !isa(backend, Type) - throw(IncorrectArgument( + throw(Exceptions.IncorrectArgument( "Backend override must be a Type or nothing", got=string(typeof(backend)), expected="Type or nothing", diff --git a/src/Optimization/Optimization.jl b/migration_to_ctsolvers/src/Optimization/Optimization.jl similarity index 95% rename from src/Optimization/Optimization.jl rename to migration_to_ctsolvers/src/Optimization/Optimization.jl index aef058f1..54d14c69 100644 --- a/src/Optimization/Optimization.jl +++ b/migration_to_ctsolvers/src/Optimization/Optimization.jl @@ -8,11 +8,10 @@ module Optimization -using CTBase: CTBase +using CTBase: CTBase, Exceptions using DocStringExtensions using NLPModels using SolverCore -using ..CTModels.Exceptions # Include submodules include(joinpath(@__DIR__, "abstract_types.jl")) diff --git a/src/Optimization/abstract_types.jl b/migration_to_ctsolvers/src/Optimization/abstract_types.jl similarity index 100% rename from src/Optimization/abstract_types.jl rename to migration_to_ctsolvers/src/Optimization/abstract_types.jl diff --git a/src/Optimization/builders.jl b/migration_to_ctsolvers/src/Optimization/builders.jl similarity index 100% rename from src/Optimization/builders.jl rename to migration_to_ctsolvers/src/Optimization/builders.jl diff --git a/src/Optimization/building.jl b/migration_to_ctsolvers/src/Optimization/building.jl similarity index 100% rename from src/Optimization/building.jl rename to migration_to_ctsolvers/src/Optimization/building.jl diff --git a/src/Optimization/contract.jl b/migration_to_ctsolvers/src/Optimization/contract.jl similarity index 93% rename from src/Optimization/contract.jl rename to migration_to_ctsolvers/src/Optimization/contract.jl index 1c114450..fe8fbc97 100644 --- a/src/Optimization/contract.jl +++ b/migration_to_ctsolvers/src/Optimization/contract.jl @@ -37,7 +37,7 @@ ADNLPModel(...) function get_adnlp_model_builder(prob::AbstractOptimizationProblem) throw(Exceptions.NotImplemented( "ADNLP model builder not implemented", - type_info="get_adnlp_model_builder not implemented for $(typeof(prob))", + required_method="get_adnlp_model_builder(prob::$(typeof(prob)))", suggestion="Implement get_adnlp_model_builder for $(typeof(prob)) to support ADNLPModels backend", context="AbstractOptimizationProblem.get_adnlp_model_builder - required method implementation" )) @@ -74,7 +74,7 @@ ExaModel{Float64}(...) function get_exa_model_builder(prob::AbstractOptimizationProblem) throw(Exceptions.NotImplemented( "ExaModel builder not implemented", - type_info="get_exa_model_builder not implemented for $(typeof(prob))", + required_method="get_exa_model_builder(prob::$(typeof(prob)))", suggestion="Implement get_exa_model_builder for $(typeof(prob)) to support ExaModels backend", context="AbstractOptimizationProblem.get_exa_model_builder - required method implementation" )) @@ -111,7 +111,7 @@ OptimalControlSolution(...) function get_adnlp_solution_builder(prob::AbstractOptimizationProblem) throw(Exceptions.NotImplemented( "ADNLP solution builder not implemented", - type_info="get_adnlp_solution_builder not implemented for $(typeof(prob))", + required_method="get_adnlp_solution_builder(prob::$(typeof(prob)))", suggestion="Implement get_adnlp_solution_builder for $(typeof(prob)) to support ADNLPModels backend", context="AbstractOptimizationProblem.get_adnlp_solution_builder - required method implementation" )) @@ -148,7 +148,7 @@ OptimalControlSolution(...) function get_exa_solution_builder(prob::AbstractOptimizationProblem) throw(Exceptions.NotImplemented( "ExaSolution builder not implemented", - type_info="get_exa_solution_builder not implemented for $(typeof(prob))", + required_method="get_exa_solution_builder(prob::$(typeof(prob)))", suggestion="Implement get_exa_solution_builder for $(typeof(prob)) to support ExaModels backend", context="AbstractOptimizationProblem.get_exa_solution_builder - required method implementation" )) diff --git a/src/Optimization/solver_info.jl b/migration_to_ctsolvers/src/Optimization/solver_info.jl similarity index 100% rename from src/Optimization/solver_info.jl rename to migration_to_ctsolvers/src/Optimization/solver_info.jl diff --git a/src/Options/Options.jl b/migration_to_ctsolvers/src/Options/Options.jl similarity index 97% rename from src/Options/Options.jl rename to migration_to_ctsolvers/src/Options/Options.jl index c46cb1d4..1f2b1a40 100644 --- a/src/Options/Options.jl +++ b/migration_to_ctsolvers/src/Options/Options.jl @@ -13,7 +13,7 @@ CTModels modules, making it reusable across the ecosystem. module Options using DocStringExtensions -using ..CTModels.Exceptions +using CTBase: CTBase, Exceptions # ============================================================================== # Include submodules diff --git a/src/Options/extraction.jl b/migration_to_ctsolvers/src/Options/extraction.jl similarity index 100% rename from src/Options/extraction.jl rename to migration_to_ctsolvers/src/Options/extraction.jl diff --git a/src/Options/not_provided.jl b/migration_to_ctsolvers/src/Options/not_provided.jl similarity index 100% rename from src/Options/not_provided.jl rename to migration_to_ctsolvers/src/Options/not_provided.jl diff --git a/src/Options/option_definition.jl b/migration_to_ctsolvers/src/Options/option_definition.jl similarity index 100% rename from src/Options/option_definition.jl rename to migration_to_ctsolvers/src/Options/option_definition.jl diff --git a/src/Options/option_value.jl b/migration_to_ctsolvers/src/Options/option_value.jl similarity index 100% rename from src/Options/option_value.jl rename to migration_to_ctsolvers/src/Options/option_value.jl diff --git a/src/Orchestration/Orchestration.jl b/migration_to_ctsolvers/src/Orchestration/Orchestration.jl similarity index 98% rename from src/Orchestration/Orchestration.jl rename to migration_to_ctsolvers/src/Orchestration/Orchestration.jl index 28bdf1f2..6e8cd8e6 100644 --- a/src/Orchestration/Orchestration.jl +++ b/migration_to_ctsolvers/src/Orchestration/Orchestration.jl @@ -21,7 +21,7 @@ Design guidelines follow `reference/16_development_standards_reference.md`: module Orchestration using DocStringExtensions -using ..CTModels.Exceptions +using CTBase: CTBase, Exceptions using ..Options using ..Strategies diff --git a/src/Orchestration/disambiguation.jl b/migration_to_ctsolvers/src/Orchestration/disambiguation.jl similarity index 100% rename from src/Orchestration/disambiguation.jl rename to migration_to_ctsolvers/src/Orchestration/disambiguation.jl diff --git a/src/Orchestration/method_builders.jl b/migration_to_ctsolvers/src/Orchestration/method_builders.jl similarity index 100% rename from src/Orchestration/method_builders.jl rename to migration_to_ctsolvers/src/Orchestration/method_builders.jl diff --git a/src/Orchestration/routing.jl b/migration_to_ctsolvers/src/Orchestration/routing.jl similarity index 100% rename from src/Orchestration/routing.jl rename to migration_to_ctsolvers/src/Orchestration/routing.jl diff --git a/migration_to_ctsolvers/src/Project.toml b/migration_to_ctsolvers/src/Project.toml new file mode 100644 index 00000000..bb99abb8 --- /dev/null +++ b/migration_to_ctsolvers/src/Project.toml @@ -0,0 +1,72 @@ + +name = "CTModels" +uuid = "34c4fa32-2049-4079-8329-de33c2a22e2d" +version = "0.8.0-beta" +authors = ["Olivier Cots "] + +[deps] +ADNLPModels = "54578032-b7ea-4c30-94aa-7cbd1cce6c9a" +CTBase = "54762871-cc72-4466-b8e8-f6c8b58076cd" +DocStringExtensions = "ffbed154-4ef7-542d-bbb7-c09d3a79fcae" +ExaModels = "1037b233-b668-4ce9-9b63-f9f681f55dd2" +Interpolations = "a98d9a8b-a2ab-59e6-89dd-64a1c18fca59" +KernelAbstractions = "63c18a36-062a-441e-b654-da1e3ab1ce7c" +LinearAlgebra = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e" +MLStyle = "d8e11817-5142-5d16-987a-aa16d5891078" +MacroTools = "1914dd2f-81c6-5fcd-8719-6d5c9610ff09" +NLPModels = "a4795742-8479-5a88-8948-cc11e1c8c1a6" +OrderedCollections = "bac558e1-5e72-5ebc-8fee-abe8a469f55d" +Parameters = "d96e819e-fc66-5662-9728-84c9c7592b0a" +RecipesBase = "3cdcf5f2-1ef4-517c-9805-6587b60abb01" +SolverCore = "ff4d7338-4cf1-434d-91df-b86cb86fb843" + +[weakdeps] +JLD2 = "033835bb-8acc-5ee8-8aae-3f567f8a3819" +JSON3 = "0f8b85d8-7281-11e9-16c2-39a750bddbf1" +Plots = "91a5bcdd-55d7-5caf-9e0b-520d859cae80" + +[extensions] +CTModelsJLD = "JLD2" +CTModelsJSON = "JSON3" +CTModelsMadNLP = "MadNLP" +CTModelsPlots = "Plots" + +[extras] +Aqua = "4c88cf16-eb10-579e-8560-4a9242c79595" +Random = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c" +Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40" + +[targets] +test = [ + "Aqua", + "JLD2", + "JSON3", + "MadNLP", + "Plots", + "Random", + "Test" +] + +[compat] +ADNLPModels = "0.8" +Aqua = "0.8" +CTBase = "0.18" +DocStringExtensions = "0.9" +ExaModels = "0.9" +Interpolations = "0.16" +JLD2 = "0.6" +JSON3 = "1" +KernelAbstractions = "0.9" +LinearAlgebra = "1" +MadNLP = "0.8" +MLStyle = "0.4" +MacroTools = "0.5" +NLPModels = "0.21" +OrderedCollections = "1" +Parameters = "0.12" +Plots = "1" +Random = "1" +RecipesBase = "1" +SolverCore = "0.3" +Test = "1" +julia = "1.10" diff --git a/src/Strategies/Strategies.jl b/migration_to_ctsolvers/src/Strategies/Strategies.jl similarity index 97% rename from src/Strategies/Strategies.jl rename to migration_to_ctsolvers/src/Strategies/Strategies.jl index 5040a2b1..aff1e699 100644 --- a/src/Strategies/Strategies.jl +++ b/migration_to_ctsolvers/src/Strategies/Strategies.jl @@ -12,10 +12,9 @@ but provides higher-level strategy management capabilities. """ module Strategies -using CTBase: CTBase +using CTBase: CTBase, Exceptions using DocStringExtensions using ..CTModels.Options -using ..CTModels.Exceptions # ============================================================================== # Include submodules diff --git a/src/Strategies/api/builders.jl b/migration_to_ctsolvers/src/Strategies/api/builders.jl similarity index 100% rename from src/Strategies/api/builders.jl rename to migration_to_ctsolvers/src/Strategies/api/builders.jl diff --git a/src/Strategies/api/configuration.jl b/migration_to_ctsolvers/src/Strategies/api/configuration.jl similarity index 100% rename from src/Strategies/api/configuration.jl rename to migration_to_ctsolvers/src/Strategies/api/configuration.jl diff --git a/src/Strategies/api/introspection.jl b/migration_to_ctsolvers/src/Strategies/api/introspection.jl similarity index 100% rename from src/Strategies/api/introspection.jl rename to migration_to_ctsolvers/src/Strategies/api/introspection.jl diff --git a/src/Strategies/api/registry.jl b/migration_to_ctsolvers/src/Strategies/api/registry.jl similarity index 100% rename from src/Strategies/api/registry.jl rename to migration_to_ctsolvers/src/Strategies/api/registry.jl diff --git a/src/Strategies/api/utilities.jl b/migration_to_ctsolvers/src/Strategies/api/utilities.jl similarity index 100% rename from src/Strategies/api/utilities.jl rename to migration_to_ctsolvers/src/Strategies/api/utilities.jl diff --git a/src/Strategies/api/validation.jl b/migration_to_ctsolvers/src/Strategies/api/validation.jl similarity index 97% rename from src/Strategies/api/validation.jl rename to migration_to_ctsolvers/src/Strategies/api/validation.jl index 0bf507fe..6ff17ac6 100644 --- a/src/Strategies/api/validation.jl +++ b/migration_to_ctsolvers/src/Strategies/api/validation.jl @@ -89,7 +89,7 @@ function validate_strategy_contract(strategy_type::Type{T}) where {T<:AbstractSt if e isa MethodError throw(Exceptions.NotImplemented( "Strategy ID method not implemented", - type_info="$T", + required_method="id(::Type{<:$T})", context="validate_strategy_contract - checking id() method availability", suggestion="Implement id(::Type{<:$T}) returning a Symbol for your strategy" )) @@ -114,7 +114,7 @@ function validate_strategy_contract(strategy_type::Type{T}) where {T<:AbstractSt if e isa MethodError throw(Exceptions.NotImplemented( "Strategy metadata method not implemented", - type_info="$T", + required_method="metadata(::Type{<:$T})", context="validate_strategy_contract - checking metadata() method availability", suggestion="Implement metadata(::Type{<:$T}) returning a StrategyMetadata for your strategy" )) @@ -140,7 +140,7 @@ function validate_strategy_contract(strategy_type::Type{T}) where {T<:AbstractSt if e isa MethodError throw(Exceptions.NotImplemented( "Strategy options builder not available", - type_info="$T", + required_method="build_strategy_options(::Type{<:$T})", context="validate_strategy_contract - checking build_strategy_options() method availability", suggestion="Ensure build_strategy_options() is available for strategy type $T (usually provided by Options API)" )) @@ -156,7 +156,7 @@ function validate_strategy_contract(strategy_type::Type{T}) where {T<:AbstractSt if e isa MethodError throw(Exceptions.NotImplemented( "Default constructor not implemented", - type_info="$T", + required_method="$T(; kwargs...)", context="validate_strategy_contract - checking default constructor availability", suggestion="Implement default constructor $T(; kwargs...) that uses build_strategy_options" )) @@ -182,7 +182,7 @@ function validate_strategy_contract(strategy_type::Type{T}) where {T<:AbstractSt if e isa MethodError throw(Exceptions.NotImplemented( "Instance options method not implemented", - type_info="$T", + required_method="options(instance::$T)", context="validate_strategy_contract - checking options() method availability", suggestion="Implement options(instance::T) returning the StrategyOptions for your strategy" )) diff --git a/src/Strategies/contract/abstract_strategy.jl b/migration_to_ctsolvers/src/Strategies/contract/abstract_strategy.jl similarity index 97% rename from src/Strategies/contract/abstract_strategy.jl rename to migration_to_ctsolvers/src/Strategies/contract/abstract_strategy.jl index 38a5fd23..7eab2b87 100644 --- a/src/Strategies/contract/abstract_strategy.jl +++ b/migration_to_ctsolvers/src/Strategies/contract/abstract_strategy.jl @@ -170,7 +170,7 @@ the `id` method to provide its unique identifier. function id(::Type{T}) where {T<:AbstractStrategy} throw(Exceptions.NotImplemented( "Strategy ID method not implemented", - type_info="id(::Type{<:$T}) must be implemented", + required_method="id(::Type{<:$T})", suggestion="Implement id(::Type{<:$T}) to return a unique Symbol identifier", context="AbstractStrategy.id - required method implementation" )) @@ -192,7 +192,7 @@ a `Dict` of `OptionDefinition` objects. function metadata(::Type{T}) where {T<:AbstractStrategy} throw(Exceptions.NotImplemented( "Strategy metadata method not implemented", - type_info="metadata(::Type{<:$T}) must be implemented", + required_method="metadata(::Type{<:$T})", suggestion="Implement metadata(::Type{<:$T}) to return StrategyMetadata with OptionDefinitions", context="AbstractStrategy.metadata - required method implementation" )) @@ -229,7 +229,7 @@ function options(strategy::T) where {T<:AbstractStrategy} # Fallback: require custom implementation for complex internal structures throw(Exceptions.NotImplemented( "Strategy options method not implemented", - type_info="Strategy $T must either have an options field or implement options(::$T)", + required_method="options(strategy::$T)", suggestion="Add options::StrategyOptions field to strategy type or implement custom options() method", context="AbstractStrategy.options - required method implementation" )) diff --git a/src/Strategies/contract/metadata.jl b/migration_to_ctsolvers/src/Strategies/contract/metadata.jl similarity index 100% rename from src/Strategies/contract/metadata.jl rename to migration_to_ctsolvers/src/Strategies/contract/metadata.jl diff --git a/src/Strategies/contract/strategy_options.jl b/migration_to_ctsolvers/src/Strategies/contract/strategy_options.jl similarity index 100% rename from src/Strategies/contract/strategy_options.jl rename to migration_to_ctsolvers/src/Strategies/contract/strategy_options.jl diff --git a/migration_to_ctsolvers/test/problems/TestProblems.jl b/migration_to_ctsolvers/test/problems/TestProblems.jl new file mode 100644 index 00000000..c193ffe0 --- /dev/null +++ b/migration_to_ctsolvers/test/problems/TestProblems.jl @@ -0,0 +1,29 @@ +module TestProblems + using CTModels + using SolverCore + using ADNLPModels + using ExaModels + + include("problems_definition.jl") + include("solution_example.jl") + include("rosenbrock.jl") + include("max1minusx2.jl") + include("elec.jl") + include("beam.jl") + include("solution_example_dual.jl") + +# From problems_definition.jl +export OptimizationProblem, DummyProblem + +# From solution_example.jl +export solution_example + +# From rosenbrock.jl +export Rosenbrock, rosenbrock_objective, rosenbrock_constraint + +# From beam.jl +export Beam + +# From solution_example_dual.jl +export solution_example_dual +end diff --git a/migration_to_ctsolvers/test/problems/beam.jl b/migration_to_ctsolvers/test/problems/beam.jl new file mode 100644 index 00000000..9957b8e1 --- /dev/null +++ b/migration_to_ctsolvers/test/problems/beam.jl @@ -0,0 +1,68 @@ +# Beam optimal control problem definition used by tests and examples. +# +# Returns a NamedTuple with fields: +# - ocp :: the CTParser-defined optimal control problem +# - obj :: reference optimal objective value (Ipopt / MadNLP, Collocation) +# - name :: a short problem name +# - init :: NamedTuple of components for CTSolvers.initial_guess +function Beam() + pre_ocp = CTModels.PreModel() + + CTModels.variable!(pre_ocp, 0) + + CTModels.time!(pre_ocp; t0=0.0, tf=1.0) + + CTModels.state!(pre_ocp, 2) + + CTModels.control!(pre_ocp, 1) + + dynamics!(r, t, x, u, v) = begin + r[1] = x[2] + r[2] = u[1] + return nothing + end + CTModels.dynamics!(pre_ocp, dynamics!) + + lagrange(t, x, u, v) = u[1]^2 + CTModels.objective!(pre_ocp, :min; lagrange=lagrange) + + f_boundary(r, x0, xf, v) = begin + r[1] = x0[1] - 0.0 + r[2] = x0[2] - 1.0 + r[3] = xf[1] - 0.0 + r[4] = xf[2] + 1.0 + return nothing + end + CTModels.constraint!( + pre_ocp, :boundary; f=f_boundary, lb=zeros(4), ub=zeros(4), label=:beam_boundary + ) + + CTModels.constraint!(pre_ocp, :state; rg=1:1, lb=[0.0], ub=[0.1], label=:beam_state_x1) + CTModels.constraint!( + pre_ocp, :control; rg=1:1, lb=[-10.0], ub=[10.0], label=:beam_control_u + ) + + definition = quote + t ∈ [0, 1], time + x ∈ R², state + u ∈ R, control + + x(0) == [0, 1] + x(1) == [0, -1] + 0 ≤ x₁(t) ≤ 0.1 + -10 ≤ u(t) ≤ 10 + + ẋ(t) == [x₂(t), u(t)] + + ∫(u(t)^2) → min + end + CTModels.definition!(pre_ocp, definition) + + CTModels.time_dependence!(pre_ocp; autonomous=true) + + ocp = CTModels.build(pre_ocp) + + init = (state=[0.05, 0.1], control=0.1) + + return (ocp=ocp, obj=8.898598, name="beam", init=init) +end diff --git a/test/problems/elec.jl b/migration_to_ctsolvers/test/problems/elec.jl similarity index 100% rename from test/problems/elec.jl rename to migration_to_ctsolvers/test/problems/elec.jl diff --git a/test/problems/max1minusx2.jl b/migration_to_ctsolvers/test/problems/max1minusx2.jl similarity index 100% rename from test/problems/max1minusx2.jl rename to migration_to_ctsolvers/test/problems/max1minusx2.jl diff --git a/test/problems/problems_definition.jl b/migration_to_ctsolvers/test/problems/problems_definition.jl similarity index 100% rename from test/problems/problems_definition.jl rename to migration_to_ctsolvers/test/problems/problems_definition.jl diff --git a/test/problems/rosenbrock.jl b/migration_to_ctsolvers/test/problems/rosenbrock.jl similarity index 100% rename from test/problems/rosenbrock.jl rename to migration_to_ctsolvers/test/problems/rosenbrock.jl diff --git a/migration_to_ctsolvers/test/problems/solution_example.jl b/migration_to_ctsolvers/test/problems/solution_example.jl new file mode 100644 index 00000000..4e4dbd90 --- /dev/null +++ b/migration_to_ctsolvers/test/problems/solution_example.jl @@ -0,0 +1,182 @@ +function solution_example(; fun=false) + + # create a pre-model + pre_ocp = CTModels.PreModel() + + # set times + CTModels.time!(pre_ocp; t0=0.0, tf=1.0) + + # set state + CTModels.state!(pre_ocp, 2) + + # set control + CTModels.control!(pre_ocp, 1) + + # set control + CTModels.variable!(pre_ocp, 2) + + # set dynamics + dynamics!(r, t, x, u, v) = r .= [x[1], u[1]] + CTModels.dynamics!(pre_ocp, dynamics!) # does not correspond to the solution + + # set objective + mayer(x0, xf, v) = x0[1] + xf[1] + lagrange(t, x, u, v) = 0.5 * u[1]^2 + CTModels.objective!(pre_ocp, :min; mayer=mayer, lagrange=lagrange) # does not correspond to the solution + + # set some constraints + f_path(r, t, x, u, v) = r .= x .+ u .+ v .+ t + f_boundary(r, x0, xf, v) = r .= x0 .+ v .* (xf .- x0) + f_variable(r, t, v) = r .= v .+ t + CTModels.constraint!(pre_ocp, :path; f=f_path, lb=[0, 1], ub=[1, 2], label=:path) + CTModels.constraint!( + pre_ocp, :boundary; f=f_boundary, lb=[0, 1], ub=[1, 2], label=:boundary + ) + CTModels.constraint!(pre_ocp, :state; rg=1:2, lb=[0, 1], ub=[1, 2], label=:state_rg) + CTModels.constraint!(pre_ocp, :control; rg=1:1, lb=[0], ub=[1], label=:control_rg) + CTModels.constraint!( + pre_ocp, :variable; rg=1:2, lb=[0, 1], ub=[1, 2], label=:variable_rg + ) + + # set definition + definition = quote + t ∈ [0, 1], time + x ∈ R², state + u ∈ R, control + x(0) == [-1, 0] + x(1) == [0, 0] + ẋ(t) == [x₂(t), u(t)] + ∫(0.5u(t)^2) → min + end + CTModels.definition!(pre_ocp, definition) # does not correspond to the solution + + CTModels.time_dependence!(pre_ocp; autonomous=false) + + pre_ocp_returned = deepcopy(pre_ocp) + + # build model + ocp = CTModels.build(pre_ocp) + + # create a solution + + # times: T Vector{Float64} + t0 = 0.0 + tf = 1.0 + N = 201 + T = range(t0, tf; length=N) + # convert T to a vector of Float64 + T = Vector{Float64}(T) + + # state: X Matrix{Float64} + x0 = [-1.0, 0.0] + xf = [0.0, 0.0] + a = x0[1] + b = x0[2] + C = [ + -(tf - t0)^3/6.0 (tf - t0)^2/2.0 + -(tf - t0)^2/2.0 (tf-t0) + ] + D = [-a - b * (tf - t0), -b] + xf + p0 = C \ D + α = p0[1] + β = p0[2] + function x(t) + return [ + a + b * (t - t0) + β * (t - t0)^2 / 2.0 - α * (t - t0)^3 / 6.0, + b + β * (t - t0) - α * (t - t0)^2 / 2.0, + ] + end + X = fun ? x : vcat([x(t)' for t in T]...) + + # costate: P Matrix{Float64} + P = zeros(N, 2) + function p(t) + return [α, -α * (t - t0) + β] + end + P = fun ? p : vcat([p(t)' for t in T[1:(end - 1)]]...) + + # control: U Matrix{Float64} + U = zeros(N, 1) + function u(t) + return [p(t)[2]] + end + U = fun ? u : vcat([u(t)' for t in T]...) + + # variable: v Vector{Float64} + v = [1.0, 1.0] #Float64[] + + # objective: Float64 + objective = 0.5 * (α^2 * (tf - t0)^3 / 3 + β^2 * (tf - t0) - α * β * (tf - t0)^2) + + # Iterations: Int + iterations = 0 + + # Constraints violation: Float64 + constraints_violation = 0.0 + + # Message: String + message = "Solve_Succeeded" + + # Stopping: Symbol + status = :Solve_Succeeded + + # Success: Bool + successful = true + + # Path constraints: Matrix{Float64} + path_constraints = nothing + + # Path constraints dual: Matrix{Float64} + path_constraints_dual = nothing + + # Boundary constraints: Vector{Float64} + boundary_constraints = nothing + + # Boundary constraints dual: Vector{Float64} + boundary_constraints_dual = nothing + + # State constraints lower bound dual: Matrix{Float64} + state_constraints_lb_dual = nothing + + # State constraints upper bound dual: Matrix{Float64} + state_constraints_ub_dual = nothing + + # Control constraints lower bound dual: Matrix{Float64} + control_constraints_lb_dual = nothing + + # Control constraints upper bound dual: Matrix{Float64} + control_constraints_ub_dual = nothing + + # Variable constraints lower bound dual: Vector{Float64} + variable_constraints_lb_dual = nothing + + # Variable constraints upper bound dual: Vector{Float64} + variable_constraints_ub_dual = nothing + + # solution + sol = CTModels.build_solution( + ocp, + T, + X, + U, + v, + P; + objective=objective, + iterations=iterations, + constraints_violation=constraints_violation, + message=message, + status=status, + successful=successful, + path_constraints_dual=path_constraints_dual, + boundary_constraints_dual=boundary_constraints_dual, + state_constraints_lb_dual=state_constraints_lb_dual, + state_constraints_ub_dual=state_constraints_ub_dual, + control_constraints_lb_dual=control_constraints_lb_dual, + control_constraints_ub_dual=control_constraints_ub_dual, + variable_constraints_lb_dual=variable_constraints_lb_dual, + variable_constraints_ub_dual=variable_constraints_ub_dual, + ) + + # return + return ocp, sol, pre_ocp_returned +end diff --git a/migration_to_ctsolvers/test/problems/solution_example_dual.jl b/migration_to_ctsolvers/test/problems/solution_example_dual.jl new file mode 100644 index 00000000..6d4601e5 --- /dev/null +++ b/migration_to_ctsolvers/test/problems/solution_example_dual.jl @@ -0,0 +1,115 @@ +function solution_example_dual() + t0 = 0 + tf = 1 + x0 = -1 + + # the model (explicit CTModels.PreModel construction) + function OCP(t0, tf, x0) + pre_ocp = CTModels.PreModel() + + # No variables + CTModels.variable!(pre_ocp, 0) + + # Time, state, control + CTModels.time!(pre_ocp; t0=t0, tf=tf) + CTModels.state!(pre_ocp, 1) + CTModels.control!(pre_ocp, 1) + + # Dynamics: ẋ(t) == u(t) + dynamics!(r, t, x, u, v) = begin + r[1] = u[1] + return nothing + end + CTModels.dynamics!(pre_ocp, dynamics!) + + # Objective: ∫(-u(t)) → min + lagrange(t, x, u, v) = -u[1] + CTModels.objective!(pre_ocp, :min; lagrange=lagrange) + + # Boundary constraint: x(t0) == x0 (label: initial_con) + f_initial(r, x0_state, xf, v) = begin + r[1] = x0_state[1] - x0 + return nothing + end + CTModels.constraint!( + pre_ocp, :boundary; f=f_initial, lb=[0.0], ub=[0.0], label=:initial_con + ) + + # Control box constraint: 0 ≤ u(t) ≤ +Inf (label: u_con) + CTModels.constraint!(pre_ocp, :control; rg=1:1, lb=[0.0], ub=[Inf], label=:u_con) + + # Path constraint: -Inf ≤ x(t) + u(t) ≤ 0 + f_path1(r, t, x, u, v) = begin + r[1] = x[1] + u[1] + return nothing + end + CTModels.constraint!(pre_ocp, :path; f=f_path1, lb=[-Inf], ub=[0.0]) + + # Path constraint: [-3, 1] ≤ [x(t)+1, u(t)+1] ≤ [1, 2.5] (label: 2) + f_path2(r, t, x, u, v) = begin + r[1] = x[1] + 1 + r[2] = u[1] + 1 + return nothing + end + CTModels.constraint!( + pre_ocp, :path; f=f_path2, lb=[-3.0, 1.0], ub=[1.0, 2.5], label=:con2 + ) + + # Keep a DSL-style definition expression for printing only + definition = quote + t ∈ [t0, tf], time + x ∈ R, state + u ∈ R, control + x(t0) == x0, (initial_con) + 0 ≤ u(t) ≤ +Inf, (u_con) + -Inf ≤ x(t) + u(t) ≤ 0 + [-3, 1] ≤ [x(t) + 1, u(t) + 1] ≤ [1, 2.5], (2) + ẋ(t) == u(t) + ∫(-u(t)) → min + end + CTModels.definition!(pre_ocp, definition) + + # Non-autonomous (matches the original DSL semantics) + CTModels.time_dependence!(pre_ocp; autonomous=false) + + ocp = CTModels.build(pre_ocp) + return ocp + end + + # the solution + function SOL(ocp, t0, tf) + x(t) = -exp(-t) + p(t) = exp(t-1) - 1 + u(t) = -x(t) + objective = exp(-1) - 1 + v = Float64[] + + # + path_constraints_dual(t) = [-(p(t)+1), 0, t] + + # + times = range(t0, tf, 201) + sol = CTModels.build_solution( + ocp, + Vector{Float64}(times), + x, + u, + v, + p; + objective=objective, + iterations=-1, + constraints_violation=0.0, + message="", + status=:optimal, + successful=true, + path_constraints_dual=path_constraints_dual, + ) + + return sol + end + + ocp = OCP(t0, tf, x0) + sol = SOL(ocp, t0, tf) + + return ocp, sol +end diff --git a/test/suite/docp/test_docp.jl b/migration_to_ctsolvers/test/suite/docp/test_docp.jl similarity index 100% rename from test/suite/docp/test_docp.jl rename to migration_to_ctsolvers/test/suite/docp/test_docp.jl diff --git a/test/suite/extensions/test_madnlp.jl b/migration_to_ctsolvers/test/suite/extensions/test_madnlp.jl similarity index 100% rename from test/suite/extensions/test_madnlp.jl rename to migration_to_ctsolvers/test/suite/extensions/test_madnlp.jl diff --git a/test/suite/integration/test_end_to_end.jl b/migration_to_ctsolvers/test/suite/integration/test_end_to_end.jl similarity index 100% rename from test/suite/integration/test_end_to_end.jl rename to migration_to_ctsolvers/test/suite/integration/test_end_to_end.jl diff --git a/test/suite/modelers/test_enhanced_options.jl b/migration_to_ctsolvers/test/suite/modelers/test_enhanced_options.jl similarity index 95% rename from test/suite/modelers/test_enhanced_options.jl rename to migration_to_ctsolvers/test/suite/modelers/test_enhanced_options.jl index 1ef69c53..6975b7c8 100644 --- a/test/suite/modelers/test_enhanced_options.jl +++ b/migration_to_ctsolvers/test/suite/modelers/test_enhanced_options.jl @@ -9,13 +9,13 @@ module TestEnhancedOptions using Test +using CTBase: CTBase, Exceptions using CTModels const VERBOSE = isdefined(Main, :TestOptions) ? Main.TestOptions.VERBOSE : true const SHOWTIMING = isdefined(Main, :TestOptions) ? Main.TestOptions.SHOWTIMING : true # Import the specific types we need using CTModels.Modelers: ADNLPModeler, ExaModeler -using CTModels.Exceptions using KernelAbstractions: CPU using CTModels.Strategies: options @@ -211,10 +211,10 @@ function test_enhanced_options() @testset "Backend Override Type Validation" begin # Invalid types should throw enriched exceptions (redirect stderr to hide error logs) redirect_stderr(devnull) do - @test_throws CTModels.Exceptions.IncorrectArgument ADNLPModeler(gradient_backend="invalid") - @test_throws CTModels.Exceptions.IncorrectArgument ADNLPModeler(hprod_backend=123) - @test_throws CTModels.Exceptions.IncorrectArgument ADNLPModeler(jprod_backend=:invalid) - @test_throws CTModels.Exceptions.IncorrectArgument ADNLPModeler(ghjvprod_backend="invalid") + @test_throws Exceptions.IncorrectArgument ADNLPModeler(gradient_backend="invalid") + @test_throws Exceptions.IncorrectArgument ADNLPModeler(hprod_backend=123) + @test_throws Exceptions.IncorrectArgument ADNLPModeler(jprod_backend=:invalid) + @test_throws Exceptions.IncorrectArgument ADNLPModeler(ghjvprod_backend="invalid") end end diff --git a/test/suite/modelers/test_modelers.jl b/migration_to_ctsolvers/test/suite/modelers/test_modelers.jl similarity index 100% rename from test/suite/modelers/test_modelers.jl rename to migration_to_ctsolvers/test/suite/modelers/test_modelers.jl diff --git a/test/suite/optimization/test_error_cases.jl b/migration_to_ctsolvers/test/suite/optimization/test_error_cases.jl similarity index 93% rename from test/suite/optimization/test_error_cases.jl rename to migration_to_ctsolvers/test/suite/optimization/test_error_cases.jl index 56217d9e..0e21e21c 100644 --- a/test/suite/optimization/test_error_cases.jl +++ b/migration_to_ctsolvers/test/suite/optimization/test_error_cases.jl @@ -1,8 +1,8 @@ module TestOptimizationErrorCases using Test +using CTBase: CTBase, Exceptions using CTModels -using CTBase using NLPModels using SolverCore using ADNLPModels @@ -81,19 +81,19 @@ function test_error_cases() prob = MinimalProblemForErrors() @testset "get_adnlp_model_builder - NotImplemented" begin - @test_throws CTModels.Exceptions.NotImplemented get_adnlp_model_builder(prob) + @test_throws Exceptions.NotImplemented get_adnlp_model_builder(prob) end @testset "get_exa_model_builder - NotImplemented" begin - @test_throws CTModels.Exceptions.NotImplemented get_exa_model_builder(prob) + @test_throws Exceptions.NotImplemented get_exa_model_builder(prob) end @testset "get_adnlp_solution_builder - NotImplemented" begin - @test_throws CTModels.Exceptions.NotImplemented get_adnlp_solution_builder(prob) + @test_throws Exceptions.NotImplemented get_adnlp_solution_builder(prob) end @testset "get_exa_solution_builder - NotImplemented" begin - @test_throws CTModels.Exceptions.NotImplemented get_exa_solution_builder(prob) + @test_throws Exceptions.NotImplemented get_exa_solution_builder(prob) end end @@ -115,9 +115,9 @@ function test_error_cases() end @testset "Non-implemented builders throw NotImplemented" begin - @test_throws CTModels.Exceptions.NotImplemented get_exa_model_builder(prob) - @test_throws CTModels.Exceptions.NotImplemented get_adnlp_solution_builder(prob) - @test_throws CTModels.Exceptions.NotImplemented get_exa_solution_builder(prob) + @test_throws Exceptions.NotImplemented get_exa_model_builder(prob) + @test_throws Exceptions.NotImplemented get_adnlp_solution_builder(prob) + @test_throws Exceptions.NotImplemented get_exa_solution_builder(prob) end end diff --git a/test/suite/optimization/test_optimization.jl b/migration_to_ctsolvers/test/suite/optimization/test_optimization.jl similarity index 97% rename from test/suite/optimization/test_optimization.jl rename to migration_to_ctsolvers/test/suite/optimization/test_optimization.jl index 3b68bce4..beaa917e 100644 --- a/test/suite/optimization/test_optimization.jl +++ b/migration_to_ctsolvers/test/suite/optimization/test_optimization.jl @@ -1,8 +1,8 @@ module TestOptimization using Test +using CTBase: CTBase, Exceptions using CTModels -using CTBase using NLPModels using SolverCore using ADNLPModels @@ -115,10 +115,10 @@ function test_optimization() @testset "Contract interface - NotImplemented errors" begin prob = MinimalProblem() - @test_throws CTModels.Exceptions.NotImplemented get_adnlp_model_builder(prob) - @test_throws CTModels.Exceptions.NotImplemented get_exa_model_builder(prob) - @test_throws CTModels.Exceptions.NotImplemented get_adnlp_solution_builder(prob) - @test_throws CTModels.Exceptions.NotImplemented get_exa_solution_builder(prob) + @test_throws Exceptions.NotImplemented get_adnlp_model_builder(prob) + @test_throws Exceptions.NotImplemented get_exa_model_builder(prob) + @test_throws Exceptions.NotImplemented get_adnlp_solution_builder(prob) + @test_throws Exceptions.NotImplemented get_exa_solution_builder(prob) end end diff --git a/test/suite/optimization/test_real_problems.jl b/migration_to_ctsolvers/test/suite/optimization/test_real_problems.jl similarity index 100% rename from test/suite/optimization/test_real_problems.jl rename to migration_to_ctsolvers/test/suite/optimization/test_real_problems.jl diff --git a/test/suite/options/test_extraction_api.jl b/migration_to_ctsolvers/test/suite/options/test_extraction_api.jl similarity index 100% rename from test/suite/options/test_extraction_api.jl rename to migration_to_ctsolvers/test/suite/options/test_extraction_api.jl diff --git a/test/suite/options/test_not_provided.jl b/migration_to_ctsolvers/test/suite/options/test_not_provided.jl similarity index 100% rename from test/suite/options/test_not_provided.jl rename to migration_to_ctsolvers/test/suite/options/test_not_provided.jl diff --git a/test/suite/options/test_option_definition.jl b/migration_to_ctsolvers/test/suite/options/test_option_definition.jl similarity index 98% rename from test/suite/options/test_option_definition.jl rename to migration_to_ctsolvers/test/suite/options/test_option_definition.jl index 5ece8ee6..c4a50057 100644 --- a/test/suite/options/test_option_definition.jl +++ b/migration_to_ctsolvers/test/suite/options/test_option_definition.jl @@ -1,8 +1,8 @@ module TestOptionsOptionDefinition using Test +using CTBase: CTBase, Exceptions using CTModels -using CTBase using CTModels.Options const VERBOSE = isdefined(Main, :TestOptions) ? Main.TestOptions.VERBOSE : true const SHOWTIMING = isdefined(Main, :TestOptions) ? Main.TestOptions.SHOWTIMING : true @@ -85,7 +85,7 @@ function test_option_definition() ) # Invalid default value type - Test.@test_throws CTModels.Exceptions.IncorrectArgument Options.OptionDefinition( + Test.@test_throws Exceptions.IncorrectArgument Options.OptionDefinition( name = :test, type = Int, default = "not an int", diff --git a/test/suite/options/test_options_value.jl b/migration_to_ctsolvers/test/suite/options/test_options_value.jl similarity index 89% rename from test/suite/options/test_options_value.jl rename to migration_to_ctsolvers/test/suite/options/test_options_value.jl index 2c929f34..f00363a8 100644 --- a/test/suite/options/test_options_value.jl +++ b/migration_to_ctsolvers/test/suite/options/test_options_value.jl @@ -1,8 +1,8 @@ module TestOptionsValue using Test +using CTBase: CTBase, Exceptions using CTModels -using CTBase using CTModels.Options const VERBOSE = isdefined(Main, :TestOptions) ? Main.TestOptions.VERBOSE : true const SHOWTIMING = isdefined(Main, :TestOptions) ? Main.TestOptions.SHOWTIMING : true @@ -36,9 +36,9 @@ function test_options_value() # Test OptionValue validation Test.@testset "OptionValue validation" begin # Test invalid sources - Test.@test_throws CTModels.Exceptions.IncorrectArgument Options.OptionValue(42, :invalid) - Test.@test_throws CTModels.Exceptions.IncorrectArgument Options.OptionValue(42, :wrong) - Test.@test_throws CTModels.Exceptions.IncorrectArgument Options.OptionValue(42, :DEFAULT) # case sensitive + Test.@test_throws Exceptions.IncorrectArgument Options.OptionValue(42, :invalid) + Test.@test_throws Exceptions.IncorrectArgument Options.OptionValue(42, :wrong) + Test.@test_throws Exceptions.IncorrectArgument Options.OptionValue(42, :DEFAULT) # case sensitive end # Test OptionValue display diff --git a/test/suite/orchestration/test_disambiguation.jl b/migration_to_ctsolvers/test/suite/orchestration/test_disambiguation.jl similarity index 97% rename from test/suite/orchestration/test_disambiguation.jl rename to migration_to_ctsolvers/test/suite/orchestration/test_disambiguation.jl index c3980b6d..53e2bd24 100644 --- a/test/suite/orchestration/test_disambiguation.jl +++ b/migration_to_ctsolvers/test/suite/orchestration/test_disambiguation.jl @@ -1,11 +1,11 @@ module TestOrchestrationDisambiguation using Test +using CTBase: CTBase, Exceptions using CTModels using CTModels.Orchestration using CTModels.Strategies using CTModels.Options -using CTBase const VERBOSE = isdefined(Main, :TestOptions) ? Main.TestOptions.VERBOSE : true const SHOWTIMING = isdefined(Main, :TestOptions) ? Main.TestOptions.SHOWTIMING : true @@ -97,13 +97,13 @@ function test_disambiguation() Test.@test result[2] == (:cpu, :ipopt) # Invalid strategy ID in single disambiguation - Test.@test_throws CTModels.Exceptions.IncorrectArgument Orchestration.extract_strategy_ids( + Test.@test_throws Exceptions.IncorrectArgument Orchestration.extract_strategy_ids( (:sparse, :unknown), TEST_METHOD ) # Invalid strategy ID in multi disambiguation - Test.@test_throws CTModels.Exceptions.IncorrectArgument Orchestration.extract_strategy_ids( + Test.@test_throws Exceptions.IncorrectArgument Orchestration.extract_strategy_ids( ((:sparse, :adnlp), (:cpu, :unknown)), TEST_METHOD ) diff --git a/test/suite/orchestration/test_method_builders.jl b/migration_to_ctsolvers/test/suite/orchestration/test_method_builders.jl similarity index 100% rename from test/suite/orchestration/test_method_builders.jl rename to migration_to_ctsolvers/test/suite/orchestration/test_method_builders.jl diff --git a/test/suite/orchestration/test_routing.jl b/migration_to_ctsolvers/test/suite/orchestration/test_routing.jl similarity index 96% rename from test/suite/orchestration/test_routing.jl rename to migration_to_ctsolvers/test/suite/orchestration/test_routing.jl index c5754cfe..dc5b6ee3 100644 --- a/test/suite/orchestration/test_routing.jl +++ b/migration_to_ctsolvers/test/suite/orchestration/test_routing.jl @@ -1,11 +1,11 @@ module TestOrchestrationRouting using Test +using CTBase: CTBase, Exceptions using CTModels using CTModels.Orchestration using CTModels.Strategies using CTModels.Options -using CTBase const VERBOSE = isdefined(Main, :TestOptions) ? Main.TestOptions.VERBOSE : true const SHOWTIMING = isdefined(Main, :TestOptions) ? Main.TestOptions.SHOWTIMING : true @@ -183,7 +183,7 @@ function test_routing() Test.@testset "Error on unknown option" begin kwargs = (unknown_option = 123,) - Test.@test_throws CTModels.Exceptions.IncorrectArgument Orchestration.route_all_options( + Test.@test_throws Exceptions.IncorrectArgument Orchestration.route_all_options( ROUTING_METHOD, ROUTING_FAMILIES, ROUTING_ACTION_DEFS, @@ -199,7 +199,7 @@ function test_routing() Test.@testset "Error on ambiguous option" begin kwargs = (backend = :sparse,) # No disambiguation - Test.@test_throws CTModels.Exceptions.IncorrectArgument Orchestration.route_all_options( + Test.@test_throws Exceptions.IncorrectArgument Orchestration.route_all_options( ROUTING_METHOD, ROUTING_FAMILIES, ROUTING_ACTION_DEFS, @@ -216,7 +216,7 @@ function test_routing() # Try to route max_iter to modeler (wrong family) kwargs = (max_iter = (1000, :adnlp),) - Test.@test_throws CTModels.Exceptions.IncorrectArgument Orchestration.route_all_options( + Test.@test_throws Exceptions.IncorrectArgument Orchestration.route_all_options( ROUTING_METHOD, ROUTING_FAMILIES, ROUTING_ACTION_DEFS, diff --git a/test/suite/strategies/test_abstract_strategy.jl b/migration_to_ctsolvers/test/suite/strategies/test_abstract_strategy.jl similarity index 95% rename from test/suite/strategies/test_abstract_strategy.jl rename to migration_to_ctsolvers/test/suite/strategies/test_abstract_strategy.jl index fa39cac8..bbbe3832 100644 --- a/test/suite/strategies/test_abstract_strategy.jl +++ b/migration_to_ctsolvers/test/suite/strategies/test_abstract_strategy.jl @@ -1,10 +1,10 @@ module TestStrategiesAbstractStrategy using Test +using CTBase: CTBase, Exceptions using CTModels using CTModels.Strategies using CTModels.Options -using CTBase const VERBOSE = isdefined(Main, :TestOptions) ? Main.TestOptions.VERBOSE : true const SHOWTIMING = isdefined(Main, :TestOptions) ? Main.TestOptions.SHOWTIMING : true @@ -113,12 +113,12 @@ function test_abstract_strategy() Test.@testset "Error handling" begin # Test NotImplemented errors for unimplemented methods - Test.@test_throws CTModels.Exceptions.NotImplemented CTModels.Strategies.id(UnimplementedStrategy) - Test.@test_throws CTModels.Exceptions.NotImplemented CTModels.Strategies.metadata(UnimplementedStrategy) + Test.@test_throws Exceptions.NotImplemented CTModels.Strategies.id(UnimplementedStrategy) + Test.@test_throws Exceptions.NotImplemented CTModels.Strategies.metadata(UnimplementedStrategy) # Test options error for strategy without options field incomplete_strategy = IncompleteStrategy() - Test.@test_throws CTModels.Exceptions.NotImplemented CTModels.Strategies.options(incomplete_strategy) + Test.@test_throws Exceptions.NotImplemented CTModels.Strategies.options(incomplete_strategy) end end diff --git a/test/suite/strategies/test_builders.jl b/migration_to_ctsolvers/test/suite/strategies/test_builders.jl similarity index 96% rename from test/suite/strategies/test_builders.jl rename to migration_to_ctsolvers/test/suite/strategies/test_builders.jl index 2a370572..32e966bf 100644 --- a/test/suite/strategies/test_builders.jl +++ b/migration_to_ctsolvers/test/suite/strategies/test_builders.jl @@ -1,10 +1,11 @@ module TestStrategiesBuilders using Test +using CTBase: CTBase, Exceptions using CTModels using CTModels.Strategies using CTModels.Options -using CTBase + const VERBOSE = isdefined(Main, :TestOptions) ? Main.TestOptions.VERBOSE : true const SHOWTIMING = isdefined(Main, :TestOptions) ? Main.TestOptions.SHOWTIMING : true @@ -161,7 +162,7 @@ function test_builders() Test.@test CTModels.Strategies.option_value(modeler_b, :precision) == 32 # Test error on unknown ID - Test.@test_throws CTModels.Exceptions.IncorrectArgument CTModels.Strategies.build_strategy(:unknown, AbstractTestModeler, registry) + Test.@test_throws Exceptions.IncorrectArgument CTModels.Strategies.build_strategy(:unknown, AbstractTestModeler, registry) end # ==================================================================== @@ -185,13 +186,13 @@ function test_builders() # Error: No ID for family method_no_modeler = (:solver_x, :solver_y) - Test.@test_throws CTModels.Exceptions.IncorrectArgument CTModels.Strategies.extract_id_from_method( + Test.@test_throws Exceptions.IncorrectArgument CTModels.Strategies.extract_id_from_method( method_no_modeler, AbstractTestModeler, registry ) # Error: Multiple IDs for same family method_duplicate = (:modeler_a, :modeler_b, :solver_x) - Test.@test_throws CTModels.Exceptions.IncorrectArgument CTModels.Strategies.extract_id_from_method( + Test.@test_throws Exceptions.IncorrectArgument CTModels.Strategies.extract_id_from_method( method_duplicate, AbstractTestModeler, registry ) end diff --git a/test/suite/strategies/test_configuration.jl b/migration_to_ctsolvers/test/suite/strategies/test_configuration.jl similarity index 100% rename from test/suite/strategies/test_configuration.jl rename to migration_to_ctsolvers/test/suite/strategies/test_configuration.jl diff --git a/test/suite/strategies/test_introspection.jl b/migration_to_ctsolvers/test/suite/strategies/test_introspection.jl similarity index 100% rename from test/suite/strategies/test_introspection.jl rename to migration_to_ctsolvers/test/suite/strategies/test_introspection.jl diff --git a/test/suite/strategies/test_metadata.jl b/migration_to_ctsolvers/test/suite/strategies/test_metadata.jl similarity index 98% rename from test/suite/strategies/test_metadata.jl rename to migration_to_ctsolvers/test/suite/strategies/test_metadata.jl index 36d3fb6e..02109f5c 100644 --- a/test/suite/strategies/test_metadata.jl +++ b/migration_to_ctsolvers/test/suite/strategies/test_metadata.jl @@ -1,10 +1,11 @@ module TestStrategiesMetadata using Test +using CTBase: CTBase, Exceptions using CTModels using CTModels.Strategies using CTModels.Options -using CTBase + const VERBOSE = isdefined(Main, :TestOptions) ? Main.TestOptions.VERBOSE : true const SHOWTIMING = isdefined(Main, :TestOptions) ? Main.TestOptions.SHOWTIMING : true @@ -72,7 +73,7 @@ function test_metadata() # ======================================================================== Test.@testset "Duplicate detection" begin - Test.@test_throws CTModels.Exceptions.IncorrectArgument CTModels.Strategies.StrategyMetadata( + Test.@test_throws Exceptions.IncorrectArgument CTModels.Strategies.StrategyMetadata( CTModels.Options.OptionDefinition( name = :max_iter, type = Int, diff --git a/test/suite/strategies/test_registry.jl b/migration_to_ctsolvers/test/suite/strategies/test_registry.jl similarity index 94% rename from test/suite/strategies/test_registry.jl rename to migration_to_ctsolvers/test/suite/strategies/test_registry.jl index 92b9470e..23c25226 100644 --- a/test/suite/strategies/test_registry.jl +++ b/migration_to_ctsolvers/test/suite/strategies/test_registry.jl @@ -1,10 +1,11 @@ module TestStrategiesRegistry using Test +using CTBase: CTBase, Exceptions using CTModels using CTModels.Strategies using CTModels.Options -using CTBase + const VERBOSE = isdefined(Main, :TestOptions) ? Main.TestOptions.VERBOSE : true const SHOWTIMING = isdefined(Main, :TestOptions) ? Main.TestOptions.SHOWTIMING : true @@ -99,20 +100,20 @@ function test_registry() Test.@testset "create_registry - validation: duplicate IDs" begin # Create a duplicate ID by reusing TestStrategyA - Test.@test_throws CTModels.Exceptions.IncorrectArgument CTModels.Strategies.create_registry( + Test.@test_throws Exceptions.IncorrectArgument CTModels.Strategies.create_registry( AbstractTestFamily => (TestStrategyA, TestStrategyA) ) end Test.@testset "create_registry - validation: wrong type hierarchy" begin # WrongTypeStrategy is not a subtype of AbstractTestFamily - Test.@test_throws CTModels.Exceptions.IncorrectArgument CTModels.Strategies.create_registry( + Test.@test_throws Exceptions.IncorrectArgument CTModels.Strategies.create_registry( AbstractTestFamily => (TestStrategyA, WrongTypeStrategy) ) end Test.@testset "create_registry - validation: duplicate family" begin - Test.@test_throws CTModels.Exceptions.IncorrectArgument CTModels.Strategies.create_registry( + Test.@test_throws Exceptions.IncorrectArgument CTModels.Strategies.create_registry( AbstractTestFamily => (TestStrategyA,), AbstractTestFamily => (TestStrategyB,) ) @@ -148,7 +149,7 @@ function test_registry() registry = CTModels.Strategies.create_registry( AbstractTestFamily => (TestStrategyA,) ) - Test.@test_throws CTModels.Exceptions.IncorrectArgument CTModels.Strategies.strategy_ids( + Test.@test_throws Exceptions.IncorrectArgument CTModels.Strategies.strategy_ids( AbstractOtherFamily, registry ) end @@ -169,7 +170,7 @@ function test_registry() registry = CTModels.Strategies.create_registry( AbstractTestFamily => (TestStrategyA,) ) - Test.@test_throws CTModels.Exceptions.IncorrectArgument CTModels.Strategies.type_from_id( + Test.@test_throws Exceptions.IncorrectArgument CTModels.Strategies.type_from_id( :nonexistent, AbstractTestFamily, registry ) end @@ -178,7 +179,7 @@ function test_registry() registry = CTModels.Strategies.create_registry( AbstractTestFamily => (TestStrategyA,) ) - Test.@test_throws CTModels.Exceptions.IncorrectArgument CTModels.Strategies.type_from_id( + Test.@test_throws Exceptions.IncorrectArgument CTModels.Strategies.type_from_id( :strategy_a, AbstractOtherFamily, registry ) end diff --git a/test/suite/strategies/test_strategy_options.jl b/migration_to_ctsolvers/test/suite/strategies/test_strategy_options.jl similarity index 97% rename from test/suite/strategies/test_strategy_options.jl rename to migration_to_ctsolvers/test/suite/strategies/test_strategy_options.jl index 1502ecd8..226f6686 100644 --- a/test/suite/strategies/test_strategy_options.jl +++ b/migration_to_ctsolvers/test/suite/strategies/test_strategy_options.jl @@ -1,10 +1,11 @@ module TestStrategiesStrategyOptions using Test +using CTBase: CTBase, Exceptions using CTModels using CTModels.Strategies using CTModels.Options -using CTBase + const VERBOSE = isdefined(Main, :TestOptions) ? Main.TestOptions.VERBOSE : true const SHOWTIMING = isdefined(Main, :TestOptions) ? Main.TestOptions.SHOWTIMING : true @@ -39,7 +40,7 @@ function test_strategy_options() Test.@testset "Validation - OptionValue required" begin # Should error if not OptionValue - Test.@test_throws CTModels.Exceptions.IncorrectArgument CTModels.Strategies.StrategyOptions( + Test.@test_throws Exceptions.IncorrectArgument CTModels.Strategies.StrategyOptions( max_iter = 200 # Not an OptionValue ) end @@ -54,7 +55,7 @@ function test_strategy_options() end # Invalid source throws in OptionValue constructor - Test.@test_throws CTModels.Exceptions.IncorrectArgument CTModels.Options.OptionValue(200, :invalid) + Test.@test_throws Exceptions.IncorrectArgument CTModels.Options.OptionValue(200, :invalid) end Test.@testset "Value access" begin diff --git a/test/suite/strategies/test_utilities.jl b/migration_to_ctsolvers/test/suite/strategies/test_utilities.jl similarity index 100% rename from test/suite/strategies/test_utilities.jl rename to migration_to_ctsolvers/test/suite/strategies/test_utilities.jl diff --git a/test/suite/strategies/test_validation.jl b/migration_to_ctsolvers/test/suite/strategies/test_validation.jl similarity index 91% rename from test/suite/strategies/test_validation.jl rename to migration_to_ctsolvers/test/suite/strategies/test_validation.jl index c4adb98f..ed20d5cb 100644 --- a/test/suite/strategies/test_validation.jl +++ b/migration_to_ctsolvers/test/suite/strategies/test_validation.jl @@ -1,10 +1,11 @@ module TestStrategiesValidation using Test +using CTBase: CTBase, Exceptions using CTModels using CTModels.Strategies using CTModels.Options: OptionDefinition -using CTModels.Exceptions + const VERBOSE = isdefined(Main, :TestOptions) ? Main.TestOptions.VERBOSE : true const SHOWTIMING = isdefined(Main, :TestOptions) ? Main.TestOptions.SHOWTIMING : true @@ -369,16 +370,16 @@ function test_validation() Test.@testset "Invalid strategies - Missing methods" begin # Missing id method - Test.@test_throws CTModels.Exceptions.NotImplemented CTModels.Strategies.validate_strategy_contract(MissingIdStrategy) + Test.@test_throws Exceptions.NotImplemented CTModels.Strategies.validate_strategy_contract(MissingIdStrategy) # Missing metadata method - Test.@test_throws CTModels.Exceptions.NotImplemented CTModels.Strategies.validate_strategy_contract(MissingMetadataStrategy) + Test.@test_throws Exceptions.NotImplemented CTModels.Strategies.validate_strategy_contract(MissingMetadataStrategy) # Missing constructor - Test.@test_throws CTModels.Exceptions.NotImplemented CTModels.Strategies.validate_strategy_contract(MissingConstructorStrategy) + Test.@test_throws Exceptions.NotImplemented CTModels.Strategies.validate_strategy_contract(MissingConstructorStrategy) # Missing options method - Test.@test_throws CTModels.Exceptions.NotImplemented CTModels.Strategies.validate_strategy_contract(MissingOptionsStrategy) + Test.@test_throws Exceptions.NotImplemented CTModels.Strategies.validate_strategy_contract(MissingOptionsStrategy) end # ==================================================================== @@ -387,13 +388,13 @@ function test_validation() Test.@testset "Invalid strategies - Wrong return types" begin # Wrong id return type (String instead of Symbol) - Test.@test_throws CTModels.Exceptions.IncorrectArgument CTModels.Strategies.validate_strategy_contract(WrongIdTypeStrategy) + Test.@test_throws Exceptions.IncorrectArgument CTModels.Strategies.validate_strategy_contract(WrongIdTypeStrategy) # Wrong metadata return type (String instead of StrategyMetadata) - Test.@test_throws CTModels.Exceptions.IncorrectArgument CTModels.Strategies.validate_strategy_contract(WrongMetadataTypeStrategy) + Test.@test_throws Exceptions.IncorrectArgument CTModels.Strategies.validate_strategy_contract(WrongMetadataTypeStrategy) # Wrong options return type (String instead of StrategyOptions) - Test.@test_throws CTModels.Exceptions.IncorrectArgument CTModels.Strategies.validate_strategy_contract(WrongOptionsTypeStrategy) + Test.@test_throws Exceptions.IncorrectArgument CTModels.Strategies.validate_strategy_contract(WrongOptionsTypeStrategy) end # ==================================================================== @@ -406,7 +407,7 @@ function test_validation() CTModels.Strategies.validate_strategy_contract(WrongIdTypeStrategy) Test.@test false # Should not reach here catch e - Test.@test e isa CTModels.Exceptions.IncorrectArgument + Test.@test e isa Exceptions.IncorrectArgument Test.@test occursin("Invalid strategy ID type", string(e)) Test.@test occursin("WrongIdTypeStrategy", string(e)) end @@ -415,7 +416,7 @@ function test_validation() CTModels.Strategies.validate_strategy_contract(MissingIdStrategy) Test.@test false # Should not reach here catch e - Test.@test e isa CTModels.Exceptions.NotImplemented + Test.@test e isa Exceptions.NotImplemented Test.@test occursin("Strategy ID method not implemented", string(e)) Test.@test occursin("MissingIdStrategy", string(e)) end @@ -433,7 +434,7 @@ function test_validation() CTModels.Strategies.validate_strategy_contract(MissingIdStrategy) Test.@test false # Should not reach here catch e - Test.@test e isa CTModels.Exceptions.NotImplemented + Test.@test e isa Exceptions.NotImplemented Test.@test occursin("Strategy ID method not implemented", string(e)) end @@ -443,7 +444,7 @@ function test_validation() CTModels.Strategies.validate_strategy_contract(WrongIdTypeStrategy) Test.@test false # Should not reach here catch e - Test.@test e isa CTModels.Exceptions.IncorrectArgument + Test.@test e isa Exceptions.IncorrectArgument Test.@test occursin("Invalid strategy ID type", string(e)) end end @@ -493,26 +494,26 @@ function test_validation() Test.@testset "Metadata-Options consistency" begin # Strategy with mismatched options (missing key) # Should fail with missing options error - Test.@test_throws CTModels.Exceptions.IncorrectArgument CTModels.Strategies.validate_strategy_contract(MissingKeyStrategy) + Test.@test_throws Exceptions.IncorrectArgument CTModels.Strategies.validate_strategy_contract(MissingKeyStrategy) try CTModels.Strategies.validate_strategy_contract(MissingKeyStrategy) Test.@test false catch e - Test.@test e isa CTModels.Exceptions.IncorrectArgument + Test.@test e isa Exceptions.IncorrectArgument Test.@test occursin("missing options", string(e)) Test.@test occursin("param2", string(e)) end # Strategy with extra options # Should fail with unexpected options error - Test.@test_throws CTModels.Exceptions.IncorrectArgument CTModels.Strategies.validate_strategy_contract(ExtraKeyStrategy) + Test.@test_throws Exceptions.IncorrectArgument CTModels.Strategies.validate_strategy_contract(ExtraKeyStrategy) try CTModels.Strategies.validate_strategy_contract(ExtraKeyStrategy) Test.@test false catch e - Test.@test e isa CTModels.Exceptions.IncorrectArgument + Test.@test e isa Exceptions.IncorrectArgument Test.@test occursin("unexpected options", string(e)) Test.@test occursin("extra", string(e)) end @@ -525,13 +526,13 @@ function test_validation() Test.@testset "Constructor behavior" begin # Strategy that ignores kwargs # Should fail because constructor doesn't use kwargs - Test.@test_throws CTModels.Exceptions.IncorrectArgument CTModels.Strategies.validate_strategy_contract(IgnoresKwargsStrategy) + Test.@test_throws Exceptions.IncorrectArgument CTModels.Strategies.validate_strategy_contract(IgnoresKwargsStrategy) try CTModels.Strategies.validate_strategy_contract(IgnoresKwargsStrategy) Test.@test false catch e - Test.@test e isa CTModels.Exceptions.IncorrectArgument + Test.@test e isa Exceptions.IncorrectArgument Test.@test occursin("Constructor does not use keyword arguments properly", string(e)) Test.@test occursin("build_strategy_options", string(e)) end diff --git a/src/CTModels.jl b/src/CTModels.jl index 27eede79..0cb1b0dd 100644 --- a/src/CTModels.jl +++ b/src/CTModels.jl @@ -65,55 +65,16 @@ The modular architecture ensures that: """ module CTModels -# ============================================================================ # -# FOUNDATIONAL TYPES AND UTILITIES -# ============================================================================ # - -# Exceptions module - enhanced error handling system (must load first) -include(joinpath(@__DIR__, "Exceptions", "Exceptions.jl")) -using .Exceptions -import .Exceptions: set_show_full_stacktrace!, get_show_full_stacktrace - # Utils module - must load before OCP (uses @ensure macro) include(joinpath(@__DIR__, "Utils", "Utils.jl")) using .Utils import .Utils: @ensure -# ============================================================================ # -# CONFIGURATION AND STRATEGY MODULES -# ============================================================================ # - -# Configuration and strategy modules (no dependencies) -include(joinpath(@__DIR__, "Options", "Options.jl")) -using .Options - -include(joinpath(@__DIR__, "Strategies", "Strategies.jl")) -using .Strategies - -include(joinpath(@__DIR__, "Orchestration", "Orchestration.jl")) -using .Orchestration - -# Optimization framework (general types) -include(joinpath(@__DIR__, "Optimization", "Optimization.jl")) -using .Optimization - -# Modeler implementations (depend on Optimization) -include(joinpath(@__DIR__, "Modelers", "Modelers.jl")) -using .Modelers - # OCP module - core optimal control problem functionality # Contains type aliases, types, components, builders, and compatibility aliases include(joinpath(@__DIR__, "OCP", "OCP.jl")) using .OCP -# Discretized OCP types (depend on OCP and Modelers) -include(joinpath(@__DIR__, "DOCP", "DOCP.jl")) -using .DOCP - -# ============================================================================ # -# IMPLEMENTATION MODULES -# ============================================================================ # - # Display and visualization include(joinpath(@__DIR__, "Display", "Display.jl")) using .Display @@ -130,8 +91,4 @@ using .Serialization include(joinpath(@__DIR__, "InitialGuess", "InitialGuess.jl")) using .InitialGuess -# ============================================================================ # -# END OF MODULE -# ============================================================================ # - end diff --git a/src/Display/Display.jl b/src/Display/Display.jl index bbf24712..7df693d1 100644 --- a/src/Display/Display.jl +++ b/src/Display/Display.jl @@ -25,13 +25,12 @@ See also: [`CTModels`](@ref) """ module Display -using CTBase: CTBase +using CTBase: CTBase, Exceptions using DocStringExtensions using MLStyle: MLStyle using Base: Base using RecipesBase: RecipesBase using MacroTools: MacroTools -using ..Exceptions # Import types from parent module (will be available after CTModels loads this) # These are forward declarations - actual types defined in OCP module diff --git a/src/Display/print.jl b/src/Display/print.jl index 95477137..5777769a 100644 --- a/src/Display/print.jl +++ b/src/Display/print.jl @@ -329,7 +329,7 @@ end """ $(TYPEDSIGNATURES) -Default show method for a [`Model`](@ref CTModels.Model). +Default show method for a [`Model`](@ref CTModels.OCP.Model). Prints only the type name. """ diff --git a/src/Exceptions/Exceptions.jl b/src/Exceptions/Exceptions.jl deleted file mode 100644 index 3b3fd392..00000000 --- a/src/Exceptions/Exceptions.jl +++ /dev/null @@ -1,81 +0,0 @@ -""" - Exceptions - -Enhanced exception system for CTModels with user-friendly error messages. - -This module provides enriched exceptions compatible with CTBase but with additional -fields for better error reporting, suggestions, and context. - -# Main Features - -1. **Enriched Exceptions**: `IncorrectArgument`, `UnauthorizedCall`, etc. with optional fields -2. **User-Friendly Display**: Clear, formatted error messages with emojis and sections -3. **Stacktrace Control**: Toggle between full Julia stacktraces and clean user display -4. **CTBase Compatibility**: Can convert to CTBase exceptions for future migration - -# Usage - -```julia -using CTModels - -# Throw enriched exceptions -throw(CTModels.Exceptions.IncorrectArgument( - "Invalid criterion", - got=":invalid", - expected=":min or :max", - suggestion="Use objective!(ocp, :min, ...) or objective!(ocp, :max, ...)" -)) - -# Control stacktrace display -CTModels.set_show_full_stacktrace!(true) # Show full Julia stacktraces -CTModels.set_show_full_stacktrace!(false) # User-friendly display (default) -``` - -# Organization - -The Exceptions module is organized into thematic files: - -- **config.jl**: Global configuration for stacktrace display -- **types.jl**: Exception type definitions -- **display.jl**: Custom display functions for user-friendly error messages -- **conversion.jl**: Compatibility layer with CTBase exceptions - -# Public API - -## Exported Types -- `CTModelsException`: Abstract base type -- `IncorrectArgument`: Invalid argument exception -- `UnauthorizedCall`: Unauthorized call exception -- `NotImplemented`: Unimplemented interface exception -- `ParsingError`: Parsing error exception - -## Exported Functions -- `set_show_full_stacktrace!`: Control stacktrace display -- `get_show_full_stacktrace`: Get current stacktrace setting -- `to_ctbase`: Convert to CTBase exceptions - -See also: [`CTModels`](@ref) -""" -module Exceptions - -using CTBase - -# Configuration -include("config.jl") - -# Type definitions -include("types.jl") - -# Display functions -include("display.jl") - -# CTBase compatibility -include("conversion.jl") - -# Export public API -export CTModelsException -export IncorrectArgument, UnauthorizedCall, NotImplemented, ParsingError -export set_show_full_stacktrace!, get_show_full_stacktrace -export to_ctbase - -end # module diff --git a/src/Exceptions/config.jl b/src/Exceptions/config.jl deleted file mode 100644 index 40f17d1a..00000000 --- a/src/Exceptions/config.jl +++ /dev/null @@ -1,51 +0,0 @@ -# Configuration for exception display behavior - -""" - SHOW_FULL_STACKTRACE - -Module-level configuration to control stacktrace display. -Set to `true` to show full Julia stacktraces, `false` for user-friendly display only. - -Default: `false` (user-friendly display) - -# Example -```julia -CTModels.set_show_full_stacktrace!(true) # Show full stacktraces -CTModels.set_show_full_stacktrace!(false) # User-friendly display only -``` -""" -const SHOW_FULL_STACKTRACE = Ref{Bool}(true) - -""" - set_show_full_stacktrace!(value::Bool) - -Configure whether to display full Julia stacktraces in error messages. - -# Arguments -- `value::Bool`: `true` to show full stacktraces, `false` for user-friendly display - -# Example -```julia -# Enable full stacktraces for debugging -CTModels.set_show_full_stacktrace!(true) - -# Disable for cleaner user experience (default) -CTModels.set_show_full_stacktrace!(false) -``` -""" -function set_show_full_stacktrace!(value::Bool) - SHOW_FULL_STACKTRACE[] = value - return nothing -end - -""" - get_show_full_stacktrace() - -Get current stacktrace display configuration. - -# Returns -- `Bool`: Current setting for full stacktrace display -""" -function get_show_full_stacktrace() - return SHOW_FULL_STACKTRACE[] -end diff --git a/src/Exceptions/conversion.jl b/src/Exceptions/conversion.jl deleted file mode 100644 index 2bf09bf4..00000000 --- a/src/Exceptions/conversion.jl +++ /dev/null @@ -1,101 +0,0 @@ -# Compatibility layer with CTBase exceptions - -""" - to_ctbase(e::IncorrectArgument) - -Convert CTModels.IncorrectArgument to CTBase.IncorrectArgument. -Useful for migration to CTBase. - -# Arguments -- `e::IncorrectArgument`: CTModels exception - -# Returns -- `CTBase.IncorrectArgument`: Compatible CTBase exception -""" -function to_ctbase(e::IncorrectArgument) - # Build a complete message with all context - full_msg = e.msg - if !isnothing(e.got) - full_msg *= " (got: $(e.got))" - end - if !isnothing(e.expected) - full_msg *= " (expected: $(e.expected))" - end - if !isnothing(e.suggestion) - full_msg *= ". Suggestion: $(e.suggestion)" - end - - return CTBase.IncorrectArgument(full_msg) -end - -""" - to_ctbase(e::UnauthorizedCall) - -Convert CTModels.UnauthorizedCall to CTBase.UnauthorizedCall. - -# Arguments -- `e::UnauthorizedCall`: CTModels exception - -# Returns -- `CTBase.UnauthorizedCall`: Compatible CTBase exception -""" -function to_ctbase(e::UnauthorizedCall) - full_msg = e.msg - if !isnothing(e.reason) - full_msg *= " (reason: $(e.reason))" - end - if !isnothing(e.suggestion) - full_msg *= ". Suggestion: $(e.suggestion)" - end - - return CTBase.UnauthorizedCall(full_msg) -end - -""" - to_ctbase(e::NotImplemented) - -Convert CTModels.NotImplemented to CTBase.NotImplemented. - -# Arguments -- `e::NotImplemented`: CTModels exception - -# Returns -- `CTBase.NotImplemented`: Compatible CTBase exception -""" -function to_ctbase(e::NotImplemented) - full_msg = e.msg - if !isnothing(e.type_info) - full_msg *= " (type: $(e.type_info))" - end - if !isnothing(e.context) - full_msg *= " (context: $(e.context))" - end - if !isnothing(e.suggestion) - full_msg *= ". Suggestion: $(e.suggestion)" - end - - return CTBase.NotImplemented(full_msg) -end - -""" - to_ctbase(e::ParsingError) - -Convert CTModels.ParsingError to CTBase.NotImplemented. - -# Arguments -- `e::ParsingError`: CTModels exception - -# Returns -- `CTBase.NotImplemented`: Compatible CTBase exception -""" -function to_ctbase(e::ParsingError) - full_msg = e.msg - if !isnothing(e.location) - full_msg *= " (at: $(e.location))" - end - if !isnothing(e.suggestion) - full_msg *= ". Suggestion: $(e.suggestion)" - end - - return CTBase.NotImplemented(full_msg) -end diff --git a/src/Exceptions/display.jl b/src/Exceptions/display.jl deleted file mode 100644 index 107dad57..00000000 --- a/src/Exceptions/display.jl +++ /dev/null @@ -1,210 +0,0 @@ -# Custom display functions for user-friendly error messages - -""" - extract_user_frames(st::Vector) - -Extract stacktrace frames that are relevant to user code. -Filters out Julia stdlib and CTModels internal frames. - -# Arguments -- `st::Vector`: Stacktrace from `stacktrace(catch_backtrace())` - -# Returns -- `Vector`: Filtered stacktrace frames -""" -function extract_user_frames(st::Vector) - user_frames = filter(st) do frame - file_str = string(frame.file) - # Keep frames that are NOT from Julia stdlib or CTModels internals - return !contains(file_str, ".julia/") && - !contains(file_str, "juliaup/") && - !contains(file_str, "/macros.jl") && - !contains(file_str, "/exception") && - !contains(file_str, "Base.jl") && - !contains(file_str, "boot.jl") - end - return user_frames -end - -""" - format_user_friendly_error(io::IO, e::CTModelsException) - -Display an error in a user-friendly format with clear sections and user code location. - -# Arguments -- `io::IO`: Output stream -- `e::CTModelsException`: The exception to display -""" -function format_user_friendly_error(io::IO, e::CTModelsException) - println(io, "\n" * "━"^70) - printstyled(io, "❌ ERROR in CTModels\n"; color=:red, bold=true) - println(io, "━"^70) - - # Main problem - println(io, "\n📋 Problem:") - println(io, " ", e.msg) - - # Type-specific details - if e isa IncorrectArgument - if !isnothing(e.got) - println(io, "\n🔍 Details:") - println(io, " Got: ", e.got) - if !isnothing(e.expected) - println(io, " Expected: ", e.expected) - end - end - - if !isnothing(e.context) - println(io, "\n📂 Context:") - println(io, " ", e.context) - end - - if !isnothing(e.suggestion) - println(io, "\n💡 Suggestion:") - println(io, " ", e.suggestion) - end - - elseif e isa UnauthorizedCall - if !isnothing(e.reason) - println(io, "\n❓ Reason:") - println(io, " ", e.reason) - end - - if !isnothing(e.context) - println(io, "\n📂 Context:") - println(io, " ", e.context) - end - - if !isnothing(e.suggestion) - println(io, "\n💡 Suggestion:") - println(io, " ", e.suggestion) - end - - elseif e isa NotImplemented - if !isnothing(e.type_info) - println(io, "\n🔧 Type:") - println(io, " ", e.type_info) - end - - if !isnothing(e.context) - println(io, "\n📂 Context:") - println(io, " ", e.context) - end - - if !isnothing(e.suggestion) - println(io, "\n💡 Suggestion:") - println(io, " ", e.suggestion) - end - - elseif e isa ParsingError - if !isnothing(e.location) - println(io, "\n📍 Location:") - println(io, " ", e.location) - end - - if !isnothing(e.suggestion) - println(io, "\n💡 Suggestion:") - println(io, " ", e.suggestion) - end - end - - # Add user code location - user_frames = extract_user_frames(stacktrace(catch_backtrace())) - if !isempty(user_frames) - println(io, "\n📍 In your code:") - # Show up to 3 most relevant user frames - for (i, frame) in enumerate(user_frames[1:min(3, length(user_frames))]) - file_name = basename(string(frame.file)) - line_info = frame.line - func_name = frame.func - - if i == 1 - # The most recent frame (where error occurred) - println(io, " $func_name at $file_name:$line_info") - else - # Previous frames (call stack) - println(io, " called from $func_name at $file_name:$line_info") - end - end - end - - # Stacktrace info - if !SHOW_FULL_STACKTRACE[] - println(io, "\n💬 Note:") - println(io, " For full Julia stacktrace, run:") - printstyled(io, " CTModels.set_show_full_stacktrace!(true)\n"; color=:cyan) - end - - println(io, "━"^70 * "\n") -end - -""" - Base.showerror(io::IO, e::IncorrectArgument) - -Custom error display for IncorrectArgument. -Shows user-friendly format if SHOW_FULL_STACKTRACE is false. -""" -function Base.showerror(io::IO, e::IncorrectArgument) - if SHOW_FULL_STACKTRACE[] - # Standard Julia error display - printstyled(io, "IncorrectArgument"; color=:red, bold=true) - print(io, ": ", e.msg) - if !isnothing(e.got) - print(io, " (got: ", e.got, ")") - end - if !isnothing(e.expected) - print(io, " (expected: ", e.expected, ")") - end - else - # User-friendly display - format_user_friendly_error(io, e) - end -end - -""" - Base.showerror(io::IO, e::UnauthorizedCall) - -Custom error display for UnauthorizedCall. -""" -function Base.showerror(io::IO, e::UnauthorizedCall) - if SHOW_FULL_STACKTRACE[] - printstyled(io, "UnauthorizedCall"; color=:red, bold=true) - print(io, ": ", e.msg) - if !isnothing(e.reason) - print(io, " (reason: ", e.reason, ")") - end - else - format_user_friendly_error(io, e) - end -end - -""" - Base.showerror(io::IO, e::NotImplemented) - -Custom error display for NotImplemented. -""" -function Base.showerror(io::IO, e::NotImplemented) - if SHOW_FULL_STACKTRACE[] - printstyled(io, "NotImplemented"; color=:red, bold=true) - print(io, ": ", e.msg) - else - format_user_friendly_error(io, e) - end -end - -""" - Base.showerror(io::IO, e::ParsingError) - -Custom error display for ParsingError. -""" -function Base.showerror(io::IO, e::ParsingError) - if SHOW_FULL_STACKTRACE[] - printstyled(io, "ParsingError"; color=:red, bold=true) - print(io, ": ", e.msg) - if !isnothing(e.location) - print(io, " (at: ", e.location, ")") - end - else - format_user_friendly_error(io, e) - end -end diff --git a/src/Exceptions/types.jl b/src/Exceptions/types.jl deleted file mode 100644 index dfe949dc..00000000 --- a/src/Exceptions/types.jl +++ /dev/null @@ -1,199 +0,0 @@ -# Exception type definitions for CTModels -# Based on CTBase.jl but with enriched error handling - -""" - CTModelsException - -Abstract supertype for all CTModels exceptions. -Compatible with CTBase.CTException for future migration. - -All exceptions inherit from this type to allow uniform error handling. -""" -abstract type CTModelsException <: Exception end - -""" - IncorrectArgument <: CTModelsException - -Exception thrown when an individual argument is invalid or violates a precondition. - -This is an enhanced version of `CTBase.IncorrectArgument` with additional fields -for better error reporting and user guidance. - -# Fields -- `msg::String`: Main error message describing the problem -- `got::Union{String, Nothing}`: What value was received (optional) -- `expected::Union{String, Nothing}`: What value was expected (optional) -- `suggestion::Union{String, Nothing}`: How to fix the problem (optional) -- `context::Union{String, Nothing}`: Where the error occurred (optional) - -# Examples -```julia -# Simple message -throw(IncorrectArgument("Invalid criterion")) - -# With details -throw(IncorrectArgument( - "Invalid criterion", - got=":invalid", - expected=":min or :max", - suggestion="Use objective!(ocp, :min, ...) or objective!(ocp, :max, ...)" -)) - -# With full context -throw(IncorrectArgument( - "Dimension mismatch", - got="vector of length 3", - expected="vector of length 2", - suggestion="Provide a vector matching the state dimension", - context="initial_guess for state" -)) -``` - -# See Also -- [`UnauthorizedCall`](@ref): For state-related or context-related errors -- [`set_show_full_stacktrace!`](@ref): Control stacktrace display -""" -struct IncorrectArgument <: CTModelsException - msg::String - got::Union{String,Nothing} - expected::Union{String,Nothing} - suggestion::Union{String,Nothing} - context::Union{String,Nothing} - - # Constructor for enriched exceptions - IncorrectArgument( - msg::String; - got::Union{String,Nothing}=nothing, - expected::Union{String,Nothing}=nothing, - suggestion::Union{String,Nothing}=nothing, - context::Union{String,Nothing}=nothing, - ) = new(msg, got, expected, suggestion, context) -end - -""" - UnauthorizedCall <: CTModelsException - -Exception thrown when a function call is not allowed in the current state. - -Enhanced version with additional context for better error reporting. - -# Fields -- `msg::String`: Main error message -- `reason::Union{String, Nothing}`: Why the call is unauthorized (optional) -- `suggestion::Union{String, Nothing}`: How to fix the problem (optional) -- `context::Union{String, Nothing}`: Where the error occurred (optional) - -# Examples -```julia -# Simple message -throw(UnauthorizedCall("State already set")) - -# With details -throw(UnauthorizedCall( - "Cannot call state! twice", - reason="state has already been defined for this OCP", - suggestion="Create a new OCP instance or use a different component name" -)) -``` - -# See Also -- [`IncorrectArgument`](@ref): For input validation errors -""" -struct UnauthorizedCall <: CTModelsException - msg::String - reason::Union{String,Nothing} - suggestion::Union{String,Nothing} - context::Union{String,Nothing} - - UnauthorizedCall( - msg::String; - reason::Union{String,Nothing}=nothing, - suggestion::Union{String,Nothing}=nothing, - context::Union{String,Nothing}=nothing, - ) = new(msg, reason, suggestion, context) -end - -""" - NotImplemented <: CTModelsException - -Exception for unimplemented interface methods. - -Enhanced version with additional context for better error reporting. - -# Fields -- `msg::String`: Description of what is not implemented -- `type_info::Union{String, Nothing}`: Type information (optional) -- `suggestion::Union{String, Nothing}`: How to fix the problem (optional) -- `context::Union{String, Nothing}`: Where the error occurred (optional) - -# Examples -```julia -# Simple message -throw(NotImplemented("run! not implemented for MyAlgorithm")) - -# With full context -throw(NotImplemented( - "Method solve! not implemented", - type_info="MyStrategy", - context="solve call", - suggestion="Import the relevant package (e.g. CTDirect) or implement solve!(::MyStrategy, ...)" -)) -``` - -# See Also -- [`IncorrectArgument`](@ref): For input validation errors -- [`UnauthorizedCall`](@ref): For state-related or context-related errors -""" -struct NotImplemented <: CTModelsException - msg::String - type_info::Union{String,Nothing} - suggestion::Union{String,Nothing} - context::Union{String,Nothing} - - NotImplemented( - msg::String; - type_info::Union{String,Nothing}=nothing, - suggestion::Union{String,Nothing}=nothing, - context::Union{String,Nothing}=nothing, - ) = new(msg, type_info, suggestion, context) -end - -""" - ParsingError <: CTModelsException - -Exception for parsing errors in DSLs or structured input. - -Enhanced version with additional context for better error reporting. - -# Fields -- `msg::String`: Description of the parsing error -- `location::Union{String, Nothing}`: Where in the input the error occurred (optional) -- `suggestion::Union{String, Nothing}`: How to fix the problem (optional) - -# Examples -```julia -# Simple message -throw(ParsingError("Unexpected token 'end'", location="line 42")) - -# with suggestion -throw(ParsingError( - "Unexpected token 'end'", - location="line 42, column 15", - suggestion="Check syntax balance or remove extra 'end'" -)) -``` - -# See Also -- [`IncorrectArgument`](@ref): For input validation errors -""" -struct ParsingError <: CTModelsException - msg::String - location::Union{String,Nothing} - suggestion::Union{String,Nothing} - - ParsingError( - msg::String; - location::Union{String,Nothing}=nothing, - suggestion::Union{String,Nothing}=nothing, - ) = new(msg, location, suggestion) -end diff --git a/src/InitialGuess/InitialGuess.jl b/src/InitialGuess/InitialGuess.jl index 9cd6f8a1..8f72b348 100644 --- a/src/InitialGuess/InitialGuess.jl +++ b/src/InitialGuess/InitialGuess.jl @@ -24,7 +24,7 @@ See also: [`CTModels`](@ref) module InitialGuess using DocStringExtensions -using CTBase +using CTBase: CTBase, Exceptions # Import types and aliases from OCP module import ..OCP: AbstractModel, AbstractSolution @@ -42,9 +42,6 @@ import ..OCP: has_free_initial_time, has_free_final_time # Import utilities from Utils module import ..Utils: ctinterpolate, matrix2vec -# Import exceptions -import ..Exceptions - # Load types first include("types.jl") diff --git a/src/OCP/Building/interpolation_helpers.jl b/src/OCP/Building/interpolation_helpers.jl index 528832ad..86f893e5 100644 --- a/src/OCP/Building/interpolation_helpers.jl +++ b/src/OCP/Building/interpolation_helpers.jl @@ -5,9 +5,6 @@ # 3. Special case handling (constant costate) via parameters # 4. Always apply deepcopy+scalar wrapping (single responsibility) -using ..Exceptions: IncorrectArgument -using ..Utils: @ensure - """ _interpolate_from_data(data, T, dim, type_param; allow_nothing=false, constant_if_two_points=false, expected_dim=nothing) @@ -45,7 +42,7 @@ function _interpolate_from_data( # Validation: nothing handling if isnothing(data) if !allow_nothing - throw(IncorrectArgument( + throw(Exceptions.IncorrectArgument( "Data cannot be nothing", got="nothing", expected="Matrix{Float64} or Function", @@ -65,7 +62,7 @@ function _interpolate_from_data( # Dimension validation if expected_dim provided if !isnothing(expected_dim) && !isnothing(dim) actual_dim = size(data, 2) - @ensure actual_dim >= dim IncorrectArgument( + @ensure actual_dim >= dim Exceptions.IncorrectArgument( "Matrix dimension mismatch", got="$actual_dim columns", expected="at least $dim columns", diff --git a/src/OCP/Building/model.jl b/src/OCP/Building/model.jl index 53a14461..13b738aa 100644 --- a/src/OCP/Building/model.jl +++ b/src/OCP/Building/model.jl @@ -145,7 +145,7 @@ function build(constraints::ConstraintsDictType)::ConstraintsModel ) else throw( - Exceptions.UnauthorizedCall( + Exceptions.IncorrectArgument( "Unknown constraint type", got="constraint type $type for label $label", expected="one of :state, :control, :variable, :boundary, :path", @@ -277,49 +277,49 @@ julia> model = build(pre_ocp) ``` """ function build(pre_ocp::PreModel; build_examodel=nothing)::Model - @ensure __is_times_set(pre_ocp) Exceptions.UnauthorizedCall( + @ensure __is_times_set(pre_ocp) Exceptions.PreconditionError( "Times must be set before building model", reason="time horizon has not been defined yet", suggestion="Call times!(pre_ocp, t0, tf) or times!(pre_ocp, N) before building", context="build function - times validation" ) - @ensure __is_state_set(pre_ocp) Exceptions.UnauthorizedCall( + @ensure __is_state_set(pre_ocp) Exceptions.PreconditionError( "State must be set before building model", reason="state has not been defined yet", suggestion="Call state!(pre_ocp, dimension) before building", context="build function - state validation" ) - @ensure __is_control_set(pre_ocp) Exceptions.UnauthorizedCall( + @ensure __is_control_set(pre_ocp) Exceptions.PreconditionError( "Control must be set before building model", reason="control has not been defined yet", suggestion="Call control!(pre_ocp, dimension) before building", context="build function - control validation" ) - @ensure __is_dynamics_set(pre_ocp) Exceptions.UnauthorizedCall( + @ensure __is_dynamics_set(pre_ocp) Exceptions.PreconditionError( "Dynamics must be set before building model", reason="dynamics have not been defined yet", suggestion="Call dynamics!(pre_ocp, f) or partial_dynamics! before building", context="build function - dynamics validation" ) - @ensure __is_dynamics_complete(pre_ocp) Exceptions.UnauthorizedCall( + @ensure __is_dynamics_complete(pre_ocp) Exceptions.PreconditionError( "Dynamics must be complete before building model", reason="not all state components are covered by dynamics", suggestion="Complete dynamics definition with partial_dynamics! or use full dynamics!", context="build function - dynamics completeness validation" ) - @ensure __is_objective_set(pre_ocp) Exceptions.UnauthorizedCall( + @ensure __is_objective_set(pre_ocp) Exceptions.PreconditionError( "Objective must be set before building model", reason="objective has not been defined yet", suggestion="Call objective!(pre_ocp, ...) before building", context="build function - objective validation" ) - @ensure __is_definition_set(pre_ocp) Exceptions.UnauthorizedCall( + @ensure __is_definition_set(pre_ocp) Exceptions.PreconditionError( "Definition must be set before building model", reason="definition has not been set yet", suggestion="Call definition!(pre_ocp) before building", context="build function - definition validation" ) - @ensure __is_autonomous_set(pre_ocp) Exceptions.UnauthorizedCall( + @ensure __is_autonomous_set(pre_ocp) Exceptions.PreconditionError( "Time dependence must be set before building model", reason="autonomous status has not been defined yet", suggestion="Call time_dependence!(pre_ocp, autonomous=true/false) before building", @@ -357,6 +357,43 @@ function build(pre_ocp::PreModel; build_examodel=nothing)::Model return model end +""" +$(TYPEDSIGNATURES) + +Build a complete optimal control problem model from a pre-model. + +This function is an alias for `build(pre_ocp; build_examodel=build_examodel)` and constructs +a fully validated `Model` from a `PreModel` by extracting and organizing all components +(times, state, control, variable, dynamics, objective, constraints). + +# Arguments +- `pre_ocp::PreModel`: The pre-model containing all problem components +- `build_examodel=nothing`: Optional ExaModel builder function for GPU acceleration + +# Returns +- `Model`: A complete, validated optimal control problem model + +# Throws +- `Exceptions.PreconditionError`: If time dependence has not been set via `time_dependence!` + +# Example +```julia +using CTModels + +# Create and configure a pre-model +pre_ocp = PreModel() +time_dependence!(pre_ocp, autonomous=true) +state!(pre_ocp, 2) +control!(pre_ocp, 1) +dynamics!(pre_ocp, (x, u) -> [x[2], u[1]]) +objective!(pre_ocp, :mayer, (x0, xf) -> xf[1]^2) + +# Build the model +ocp = build_model(pre_ocp) +``` + +See also: [`build`](@ref), [`PreModel`](@ref), [`Model`](@ref), [`time_dependence!`](@ref) +""" function build_model(pre_ocp::PreModel; build_examodel=nothing)::Model return build(pre_ocp; build_examodel=build_examodel) end @@ -594,7 +631,7 @@ $(TYPEDSIGNATURES) Throw an error for unsupported initial time access. """ function initial_time(ocp::AbstractModel) - throw(CTModels.Exceptions.UnauthorizedCall( + throw(Exceptions.PreconditionError( "Cannot get initial time with this function", reason="This model type does not support direct initial time access", suggestion="Use initial_time(ocp) on a Model with FixedTimeModel or use initial_time(ocp, variable) for variable initial time", @@ -608,7 +645,7 @@ $(TYPEDSIGNATURES) Throw an error for unsupported initial time access with variable. """ function initial_time(ocp::AbstractModel, variable::AbstractVector) - throw(CTModels.Exceptions.UnauthorizedCall( + throw(Exceptions.PreconditionError( "Cannot get initial time with this function", reason="This model type does not support initial time access with variable", suggestion="Ensure the model has variable initial time configured, or use initial_time(ocp) for fixed initial time", @@ -715,7 +752,7 @@ $(TYPEDSIGNATURES) Throw an error for unsupported final time access. """ function final_time(ocp::AbstractModel) - throw(CTModels.Exceptions.UnauthorizedCall( + throw(Exceptions.PreconditionError( "Cannot get final time with this function", reason="This model type does not support direct final time access", suggestion="Use final_time(ocp) on a Model with FixedTimeModel or use final_time(ocp, variable) for variable final time", @@ -729,7 +766,7 @@ $(TYPEDSIGNATURES) Throw an error for unsupported final time access with variable. """ function final_time(ocp::AbstractModel, variable::AbstractVector) - throw(CTModels.Exceptions.UnauthorizedCall( + throw(Exceptions.PreconditionError( "Cannot get final time with this function", reason="This model type does not support final time access with variable", suggestion="Ensure the model has variable final time configured, or use final_time(ocp) for fixed final time", @@ -867,7 +904,7 @@ $(TYPEDSIGNATURES) Throw an error when accessing Mayer cost on a model without one. """ function mayer(ocp::AbstractModel) - throw(CTModels.Exceptions.UnauthorizedCall( + throw(Exceptions.PreconditionError( "Cannot access Mayer cost", reason="This OCP has no Mayer objective defined", suggestion="Define a Mayer objective using objective!(ocp, :min/:max, mayer=...) before accessing it", @@ -933,7 +970,7 @@ $(TYPEDSIGNATURES) Throw an error when accessing Lagrange cost on a model without one. """ function lagrange(ocp::AbstractModel) - throw(CTModels.Exceptions.UnauthorizedCall( + throw(Exceptions.PreconditionError( "Cannot access Lagrange cost", reason="This OCP has no Lagrange objective defined", suggestion="Define a Lagrange objective using objective!(ocp, :min/:max, lagrange=...) before accessing it", @@ -1039,7 +1076,7 @@ end """ $(TYPEDSIGNATURES) -Return an error (UnauthorizedCall) since the model is not built with the :exa backend. +Return an error (PreconditionError) since the model is not built with the :exa backend. """ function get_build_examodel( ::Model{ @@ -1054,7 +1091,7 @@ function get_build_examodel( <:Nothing, }, ) - throw(CTModels.Exceptions.UnauthorizedCall( + throw(Exceptions.PreconditionError( "Cannot access dynamics", reason="Model must be parsed with :exa backend first", suggestion="Parse the OCP with backend=:exa before accessing dynamics", diff --git a/src/OCP/Building/solution.jl b/src/OCP/Building/solution.jl index 99fd92fc..a31d3c37 100644 --- a/src/OCP/Building/solution.jl +++ b/src/OCP/Building/solution.jl @@ -39,7 +39,6 @@ constraint declarations. If multiple constraints are declared on the same compon and `x₂(t) ≤ 2.0`), only the last bound value is retained, and a warning is emitted during model construction. """ - function build_solution( ocp::Model, T::Vector{Float64}, diff --git a/src/OCP/Components/constraints.jl b/src/OCP/Components/constraints.jl index c1446c4b..8b634d36 100644 --- a/src/OCP/Components/constraints.jl +++ b/src/OCP/Components/constraints.jl @@ -65,7 +65,7 @@ function __constraint!( # checks: the constraint must not be set before @ensure( !(label ∈ keys(ocp_constraints)), - Exceptions.UnauthorizedCall( + Exceptions.PreconditionError( "Constraint already exists", reason="constraint with label '$(label)' is already defined", suggestion="Use a different label or remove the existing constraint first", @@ -76,7 +76,7 @@ function __constraint!( # checks: lb and ub cannot be both nothing @ensure( !(isnothing(lb) && isnothing(ub)), - Exceptions.UnauthorizedCall( + Exceptions.PreconditionError( "Both bounds cannot be nothing", reason="constraint requires at least one bound (lower or upper)", suggestion="Provide lb (lower bound), ub (upper bound), or both", @@ -269,12 +269,12 @@ julia> constraint!(ocp, :control, rg=1:2, lb=[0.0], ub=[1.0], label=:control_con # Throws -- `Exceptions.UnauthorizedCall`: If state has not been set -- `Exceptions.UnauthorizedCall`: If control has not been set -- `Exceptions.UnauthorizedCall`: If times has not been set -- `Exceptions.UnauthorizedCall`: If variable has not been set (when type=:variable) -- `Exceptions.UnauthorizedCall`: If constraint with same label already exists -- `Exceptions.UnauthorizedCall`: If both lb and ub are nothing +- `Exceptions.PreconditionError`: If state has not been set +- `Exceptions.PreconditionError`: If control has not been set +- `Exceptions.PreconditionError`: If times has not been set +- `Exceptions.PreconditionError`: If variable has not been set (when type=:variable) +- `Exceptions.PreconditionError`: If constraint with same label already exists +- `Exceptions.PreconditionError`: If both lb and ub are nothing - `Exceptions.IncorrectArgument`: If lb and ub have different lengths - `Exceptions.IncorrectArgument`: If lb > ub element-wise - `Exceptions.IncorrectArgument`: If dimensions don't match expected sizes @@ -291,19 +291,19 @@ function constraint!( ) # checks: times, state and control must be set before adding constraints - @ensure __is_state_set(ocp) Exceptions.UnauthorizedCall( + @ensure __is_state_set(ocp) Exceptions.PreconditionError( "State must be set before adding constraints", reason="state has not been defined yet", suggestion="Call state!(ocp, dimension) before adding constraints", context="constraint! function - state validation" ) - @ensure __is_control_set(ocp) Exceptions.UnauthorizedCall( + @ensure __is_control_set(ocp) Exceptions.PreconditionError( "Control must be set before adding constraints", reason="control has not been defined yet", suggestion="Call control!(ocp, dimension) before adding constraints", context="constraint! function - control validation" ) - @ensure __is_times_set(ocp) Exceptions.UnauthorizedCall( + @ensure __is_times_set(ocp) Exceptions.PreconditionError( "Times must be set before adding constraints", reason="time horizon has not been defined yet", suggestion="Call times!(ocp, t0, tf) or times!(ocp, N) before adding constraints", @@ -311,7 +311,7 @@ function constraint!( ) # checks: variable must be set if using type=:variable - @ensure (type != :variable || __is_variable_set(ocp)) Exceptions.UnauthorizedCall( + @ensure (type != :variable || __is_variable_set(ocp)) Exceptions.PreconditionError( "Variable must be set for type=:variable constraints", reason="OCP has no variable defined but constraint type requires it", suggestion="Call variable!(ocp, dimension) before adding variable constraints, or use a different constraint type", diff --git a/src/OCP/Components/control.jl b/src/OCP/Components/control.jl index 6c25ba50..3f5343b8 100644 --- a/src/OCP/Components/control.jl +++ b/src/OCP/Components/control.jl @@ -41,7 +41,7 @@ julia> control_components(ocp) # Throws -- `Exceptions.UnauthorizedCall`: If control has already been set +- `Exceptions.PreconditionError`: If control has already been set - `Exceptions.IncorrectArgument`: If m ≤ 0 - `Exceptions.IncorrectArgument`: If number of component names ≠ m - `Exceptions.IncorrectArgument`: If name is empty @@ -59,7 +59,7 @@ function control!( )::Nothing where {T1<:Union{String,Symbol},T2<:Union{String,Symbol}} # checks using @ensure - @ensure !__is_control_set(ocp) Exceptions.UnauthorizedCall( + @ensure !__is_control_set(ocp) Exceptions.PreconditionError( "Control already set", reason="control has already been defined for this OCP", suggestion="Create a new OCP instance or use the existing control definition", diff --git a/src/OCP/Components/dynamics.jl b/src/OCP/Components/dynamics.jl index a11403f4..dab3ddc3 100644 --- a/src/OCP/Components/dynamics.jl +++ b/src/OCP/Components/dynamics.jl @@ -17,28 +17,28 @@ if any of the required fields (`state`, `control`, `times`) are not yet set, or dynamics have already been set. # Errors -Throws `Exceptions.UnauthorizedCall` if called out of order or in an invalid state. +Throws `Exceptions.PreconditionError` if called out of order or in an invalid state. """ function dynamics!(ocp::PreModel, f::Function)::Nothing - @ensure __is_state_set(ocp) Exceptions.UnauthorizedCall( + @ensure __is_state_set(ocp) Exceptions.PreconditionError( "State must be set before defining dynamics", reason="state has not been defined yet", suggestion="Call state!(ocp, dimension) before dynamics!", context="dynamics! function - state validation" ) - @ensure __is_control_set(ocp) Exceptions.UnauthorizedCall( + @ensure __is_control_set(ocp) Exceptions.PreconditionError( "Control must be set before defining dynamics", reason="control has not been defined yet", suggestion="Call control!(ocp, dimension) before dynamics!", context="dynamics! function - control validation" ) - @ensure __is_times_set(ocp) Exceptions.UnauthorizedCall( + @ensure __is_times_set(ocp) Exceptions.PreconditionError( "Times must be set before defining dynamics", reason="time horizon has not been defined yet", suggestion="Call times!(ocp, t0, tf) or times!(ocp, N) before dynamics!", context="dynamics! function - times validation" ) - @ensure !__is_dynamics_set(ocp) Exceptions.UnauthorizedCall( + @ensure !__is_dynamics_set(ocp) Exceptions.PreconditionError( "Dynamics already set", reason="dynamics have already been defined for this OCP", suggestion="Create a new OCP instance or use partial_dynamics! for additional dynamics", @@ -73,7 +73,7 @@ that the specified indices are not already covered and that the system is in a v configuration for adding partial dynamics. # Errors -Throws `Exceptions.UnauthorizedCall` if: +Throws `Exceptions.PreconditionError` if: - The state, control, or times are not yet set. - The dynamics are already defined completely. - Any index in `rg` overlaps with an existing dynamics range. @@ -85,25 +85,25 @@ julia> dynamics!(ocp, 3:3, (out, t, x, u, v) -> out .= x[3] * v[1]) ``` """ function dynamics!(ocp::PreModel, rg::AbstractRange{<:Int}, f::Function)::Nothing - @ensure __is_state_set(ocp) Exceptions.UnauthorizedCall( + @ensure __is_state_set(ocp) Exceptions.PreconditionError( "State must be set before defining partial dynamics", reason="state has not been defined yet", suggestion="Call state!(ocp, dimension) before partial dynamics!", context="partial_dynamics! function - state validation" ) - @ensure __is_control_set(ocp) Exceptions.UnauthorizedCall( + @ensure __is_control_set(ocp) Exceptions.PreconditionError( "Control must be set before defining partial dynamics", reason="control has not been defined yet", suggestion="Call control!(ocp, dimension) before partial dynamics!", context="partial_dynamics! function - control validation" ) - @ensure __is_times_set(ocp) Exceptions.UnauthorizedCall( + @ensure __is_times_set(ocp) Exceptions.PreconditionError( "Times must be set before defining partial dynamics", reason="time horizon has not been defined yet", suggestion="Call times!(ocp, t0, tf) or times!(ocp, N) before partial dynamics!", context="partial_dynamics! function - times validation" ) - @ensure !__is_dynamics_complete(ocp) Exceptions.UnauthorizedCall( + @ensure !__is_dynamics_complete(ocp) Exceptions.PreconditionError( "Complete dynamics already set", reason="dynamics have already been completely defined for this OCP", suggestion="Use partial_dynamics! before setting complete dynamics, or create a new OCP instance", @@ -130,7 +130,7 @@ function dynamics!(ocp::PreModel, rg::AbstractRange{<:Int}, f::Function)::Nothin ocp.dynamics = Vector{Tuple{UnitRange{Int},Function}}() elseif ocp.dynamics isa Function throw( - Exceptions.UnauthorizedCall( + Exceptions.PreconditionError( "Cannot add partial dynamics to complete dynamics", reason="dynamics already defined as a single function", suggestion="Use partial_dynamics! calls instead of dynamics! function, or create a new OCP instance", @@ -144,7 +144,7 @@ function dynamics!(ocp::PreModel, rg::AbstractRange{<:Int}, f::Function)::Nothin for i in rg if i in existing_range throw( - Exceptions.UnauthorizedCall( + Exceptions.PreconditionError( "Dynamics range overlap", reason="index $i in range already has assigned dynamics", suggestion="Use a non-overlapping range or remove existing dynamics first", diff --git a/src/OCP/Components/objective.jl b/src/OCP/Components/objective.jl index 6071148c..b963c8da 100644 --- a/src/OCP/Components/objective.jl +++ b/src/OCP/Components/objective.jl @@ -30,10 +30,10 @@ julia> objective!(ocp, :min, mayer=mayer, lagrange=lagrange) # Throws -- `Exceptions.UnauthorizedCall`: If state has not been set -- `Exceptions.UnauthorizedCall`: If control has not been set -- `Exceptions.UnauthorizedCall`: If times has not been set -- `Exceptions.UnauthorizedCall`: If objective has already been set +- `Exceptions.PreconditionError`: If state has not been set +- `Exceptions.PreconditionError`: If control has not been set +- `Exceptions.PreconditionError`: If times has not been set +- `Exceptions.PreconditionError`: If objective has already been set - `Exceptions.IncorrectArgument`: If criterion is not :min, :max, :MIN, or :MAX - `Exceptions.IncorrectArgument`: If neither mayer nor lagrange function is provided """ @@ -45,19 +45,19 @@ function objective!( )::Nothing # checks: times, state, and control must be set before the objective - @ensure __is_state_set(ocp) Exceptions.UnauthorizedCall( + @ensure __is_state_set(ocp) Exceptions.PreconditionError( "State must be set before objective", reason="state has not been defined yet", suggestion="Call state!(ocp, dimension) before objective!(ocp, ...)", context="objective! function - state validation" ) - @ensure __is_control_set(ocp) Exceptions.UnauthorizedCall( + @ensure __is_control_set(ocp) Exceptions.PreconditionError( "Control must be set before objective", reason="control has not been defined yet", suggestion="Call control!(ocp, dimension) before objective!(ocp, ...)", context="objective! function - control validation" ) - @ensure __is_times_set(ocp) Exceptions.UnauthorizedCall( + @ensure __is_times_set(ocp) Exceptions.PreconditionError( "Times must be set before objective", reason="time horizon has not been defined yet", suggestion="Call time!(ocp, t0, tf) before objective!(ocp, ...)", @@ -65,7 +65,7 @@ function objective!( ) # checks: the objective must not already be set - @ensure !__is_objective_set(ocp) Exceptions.UnauthorizedCall( + @ensure !__is_objective_set(ocp) Exceptions.PreconditionError( "Objective already set", reason="objective has already been defined for this OCP", suggestion="Create a new OCP instance or use the existing objective definition", diff --git a/src/OCP/Components/state.jl b/src/OCP/Components/state.jl index 44f8e5c8..ee1a7308 100644 --- a/src/OCP/Components/state.jl +++ b/src/OCP/Components/state.jl @@ -55,7 +55,7 @@ julia> state_components(ocp) # Throws -- `Exceptions.UnauthorizedCall`: If state has already been set +- `Exceptions.PreconditionError`: If state has already been set - `Exceptions.IncorrectArgument`: If n ≤ 0 - `Exceptions.IncorrectArgument`: If number of component names ≠ n - `Exceptions.IncorrectArgument`: If name is empty @@ -73,7 +73,7 @@ function state!( )::Nothing where {T1<:Union{String,Symbol},T2<:Union{String,Symbol}} # checks - @ensure !__is_state_set(ocp) Exceptions.UnauthorizedCall( + @ensure !__is_state_set(ocp) Exceptions.PreconditionError( "State already set", reason="state has already been defined for this OCP", suggestion="Create a new OCP instance or use the existing state definition", diff --git a/src/OCP/Components/times.jl b/src/OCP/Components/times.jl index f567a7be..698e3de0 100644 --- a/src/OCP/Components/times.jl +++ b/src/OCP/Components/times.jl @@ -30,8 +30,8 @@ julia> time!(ocp, t0=0, tf=1, time_name=:s ) # time_name is a Symbol # Throws -- `Exceptions.UnauthorizedCall`: If time has already been set -- `Exceptions.UnauthorizedCall`: If variable must be set before (when t0 or tf is free) +- `Exceptions.PreconditionError`: If time has already been set +- `Exceptions.PreconditionError`: If variable must be set before (when t0 or tf is free) - `Exceptions.IncorrectArgument`: If ind0 or indf is out of bounds - `Exceptions.IncorrectArgument`: If both t0 and ind0 are provided - `Exceptions.IncorrectArgument`: If neither t0 nor ind0 is provided @@ -49,14 +49,14 @@ function time!( indf::Union{Int,Nothing}=nothing, time_name::Union{String,Symbol}=__time_name(), )::Nothing - @ensure !__is_times_set(ocp) Exceptions.UnauthorizedCall( + @ensure !__is_times_set(ocp) Exceptions.PreconditionError( "Time already set", reason="time has already been defined for this OCP", suggestion="Create a new OCP instance or use the existing time definition", context="time! function - duplicate definition check" ) - @ensure __is_variable_set(ocp) || (isnothing(ind0) && isnothing(indf)) Exceptions.UnauthorizedCall( + @ensure __is_variable_set(ocp) || (isnothing(ind0) && isnothing(indf)) Exceptions.PreconditionError( "Variable must be set for free time", reason="variable is required when t0 or tf is free (ind0/indf provided)", suggestion="Call variable!(ocp, dimension) before time! with free time parameters, or use fixed times (t0, tf)", diff --git a/src/OCP/Components/variable.jl b/src/OCP/Components/variable.jl index 62dc7321..7b3a727a 100644 --- a/src/OCP/Components/variable.jl +++ b/src/OCP/Components/variable.jl @@ -22,9 +22,9 @@ julia> variable!(ocp, 2, "v", ["v₁", "v₂"]) # Throws -- `Exceptions.UnauthorizedCall`: If variable has already been set -- `Exceptions.UnauthorizedCall`: If objective has already been set -- `Exceptions.UnauthorizedCall`: If dynamics has already been set +- `Exceptions.PreconditionError`: If variable has already been set +- `Exceptions.PreconditionError`: If objective has already been set +- `Exceptions.PreconditionError`: If dynamics has already been set - `Exceptions.IncorrectArgument`: If number of component names ≠ q (when q > 0) - `Exceptions.IncorrectArgument`: If name is empty (when q > 0) - `Exceptions.IncorrectArgument`: If any component name is empty (when q > 0) @@ -39,7 +39,7 @@ function variable!( name::T1=__variable_name(q), components_names::Vector{T2}=__variable_components(q, string(name)), )::Nothing where {T1<:Union{String,Symbol},T2<:Union{String,Symbol}} - @ensure !__is_variable_set(ocp) Exceptions.UnauthorizedCall( + @ensure !__is_variable_set(ocp) Exceptions.PreconditionError( "Variable already set", reason="variable has already been defined for this OCP", suggestion="Create a new OCP instance or use the existing variable definition", @@ -54,14 +54,14 @@ function variable!( context="variable!(ocp, q=$q, components_names=[...]) - validating names count" ) - @ensure !__is_objective_set(ocp) Exceptions.UnauthorizedCall( + @ensure !__is_objective_set(ocp) Exceptions.PreconditionError( "Variable must be set before objective", reason="objective has already been defined but variable is not set yet", suggestion="Call variable!(ocp, dimension) before objective!(ocp, ...)", context="variable! function - objective ordering check" ) - @ensure !__is_dynamics_set(ocp) Exceptions.UnauthorizedCall( + @ensure !__is_dynamics_set(ocp) Exceptions.PreconditionError( "Variable must be set before dynamics", reason="dynamics have already been defined but variable is not set yet", suggestion="Call variable!(ocp, dimension) before dynamics!(ocp, ...)", diff --git a/src/OCP/Core/time_dependence.jl b/src/OCP/Core/time_dependence.jl index 548fa14c..2fed515c 100644 --- a/src/OCP/Core/time_dependence.jl +++ b/src/OCP/Core/time_dependence.jl @@ -15,7 +15,7 @@ This function sets the `autonomous` field of the model to indicate whether the s explicitly depend on time. It can only be called once. # Errors -Throws `Exceptions.UnauthorizedCall` if the time dependence has already been set. +Throws `Exceptions.PreconditionError` if the time dependence has already been set. # Example ```julia-repl @@ -24,7 +24,7 @@ julia> time_dependence!(ocp; autonomous=true) ``` """ function time_dependence!(ocp::PreModel; autonomous::Bool)::Nothing - @ensure !__is_autonomous_set(ocp) Exceptions.UnauthorizedCall( + @ensure !__is_autonomous_set(ocp) Exceptions.PreconditionError( "Time dependence already set", reason="time dependence has already been defined for this OCP", suggestion="Create a new OCP instance or use the existing time dependence definition", diff --git a/src/OCP/OCP.jl b/src/OCP/OCP.jl index 1af05eaa..7938ea12 100644 --- a/src/OCP/OCP.jl +++ b/src/OCP/OCP.jl @@ -29,7 +29,7 @@ See also: [`CTModels`](@ref) module OCP using DocStringExtensions -using CTBase +using CTBase: CTBase, Exceptions using MLStyle: MLStyle using MacroTools using Parameters @@ -42,13 +42,6 @@ include("aliases.jl") # Import macro from Utils module import ..Utils: @ensure -# Import Exceptions module for error handling -import ..Exceptions -using ..CTModels.Exceptions - -# Import build_solution from Optimization to overload it -import ..Optimization: build_solution, build_model - # Import matrix2vec, ctinterpolate and to_out_of_place from Utils for solution building import ..Utils: matrix2vec, ctinterpolate, to_out_of_place diff --git a/src/OCP/Types/model.jl b/src/OCP/Types/model.jl index 92e32ac4..8ab7391a 100644 --- a/src/OCP/Types/model.jl +++ b/src/OCP/Types/model.jl @@ -6,10 +6,10 @@ $(TYPEDEF) Abstract base type for optimal control problem models. -Subtypes represent either a fully built immutable model ([`Model`](@ref CTModels.Model)) or a +Subtypes represent either a fully built immutable model ([`Model`](@ref CTModels.OCP.Model)) or a mutable model under construction ([`PreModel`](@ref)). -See also: [`Model`](@ref CTModels.Model), [`PreModel`](@ref). +See also: [`Model`](@ref CTModels.OCP.Model), [`PreModel`](@ref). """ abstract type AbstractModel end @@ -102,49 +102,49 @@ end """ $(TYPEDSIGNATURES) -Return `true` since times are always set in a built [`Model`](@ref CTModels.Model). +Return `true` since times are always set in a built [`Model`](@ref CTModels.OCP.Model). """ __is_times_set(ocp::Model)::Bool = true """ $(TYPEDSIGNATURES) -Return `true` since state is always set in a built [`Model`](@ref CTModels.Model). +Return `true` since state is always set in a built [`Model`](@ref CTModels.OCP.Model). """ __is_state_set(ocp::Model)::Bool = true """ $(TYPEDSIGNATURES) -Return `true` since control is always set in a built [`Model`](@ref CTModels.Model). +Return `true` since control is always set in a built [`Model`](@ref CTModels.OCP.Model). """ __is_control_set(ocp::Model)::Bool = true """ $(TYPEDSIGNATURES) -Return `true` since variable is always set in a built [`Model`](@ref CTModels.Model). +Return `true` since variable is always set in a built [`Model`](@ref CTModels.OCP.Model). """ __is_variable_set(ocp::Model)::Bool = true """ $(TYPEDSIGNATURES) -Return `true` since dynamics is always set in a built [`Model`](@ref CTModels.Model). +Return `true` since dynamics is always set in a built [`Model`](@ref CTModels.OCP.Model). """ __is_dynamics_set(ocp::Model)::Bool = true """ $(TYPEDSIGNATURES) -Return `true` since objective is always set in a built [`Model`](@ref CTModels.Model). +Return `true` since objective is always set in a built [`Model`](@ref CTModels.OCP.Model). """ __is_objective_set(ocp::Model)::Bool = true """ $(TYPEDSIGNATURES) -Return `true` since definition is always set in a built [`Model`](@ref CTModels.Model). +Return `true` since definition is always set in a built [`Model`](@ref CTModels.OCP.Model). """ __is_definition_set(ocp::Model)::Bool = true @@ -154,7 +154,7 @@ $(TYPEDEF) Mutable optimal control problem model under construction. A `PreModel` is used to incrementally define an optimal control problem before -building it into an immutable [`Model`](@ref CTModels.Model). Fields can be set in any order +building it into an immutable [`Model`](@ref CTModels.OCP.Model). Fields can be set in any order and the model is validated before building. # Fields @@ -266,10 +266,10 @@ $(TYPEDSIGNATURES) Return the state dimension of the [`PreModel`](@ref). -Throws `Exceptions.UnauthorizedCall` if state has not been set. +Throws `Exceptions.PreconditionError` if state has not been set. """ function state_dimension(ocp::PreModel)::Dimension - @ensure(__is_state_set(ocp), Exceptions.UnauthorizedCall( + @ensure(__is_state_set(ocp), Exceptions.PreconditionError( "State must be set before accessing dimension", reason="state has not been defined yet", suggestion="Call state!(ocp, dimension) before accessing state_dimension", @@ -291,7 +291,7 @@ function __is_dynamics_complete(ocp::PreModel)::Bool elseif ocp.dynamics isa Function return true else # ocp.dynamics isa Vector{<:Tuple{<:AbstractRange{<:Int},<:Function}} - @ensure(__is_state_set(ocp), Exceptions.UnauthorizedCall( + @ensure(__is_state_set(ocp), Exceptions.PreconditionError( "State must be set before checking dynamics completeness", reason="state has not been defined yet", suggestion="Call state!(ocp, dimension) before defining dynamics", @@ -305,7 +305,7 @@ function __is_dynamics_complete(ocp::PreModel)::Bool covered[i] = true else throw( - Exceptions.UnauthorizedCall( + Exceptions.PreconditionError( "Dynamics index out of bounds", got="dynamics index $i for state of size $n", expected="indices in range 1:$n", diff --git a/src/OCP/aliases.jl b/src/OCP/aliases.jl index c42f7c8e..e6adeccb 100644 --- a/src/OCP/aliases.jl +++ b/src/OCP/aliases.jl @@ -26,7 +26,7 @@ Type alias for a time. julia> const Time = ctNumber ``` -See also: [`ctNumber`](@ref), [`Times`](@ref CTModels.Times), [`TimesDisc`](@ref). +See also: [`ctNumber`](@ref), [`Times`](@ref CTModels.OCP.Times), [`TimesDisc`](@ref). """ const Time = ctNumber @@ -59,7 +59,7 @@ Type alias for a grid of times. This is used to define a discretization of time julia> const TimesDisc = Union{Times, StepRangeLen} ``` -See also: [`Time`](@ref), [`Times`](@ref CTModels.Times). +See also: [`Time`](@ref), [`Times`](@ref CTModels.OCP.Times). """ const TimesDisc = Union{Times,StepRangeLen} @@ -70,7 +70,7 @@ Type alias for a dictionary of constraints. This is used to store constraints be julia> const TimesDisc = Union{Times, StepRangeLen} ``` -See also: [`ConstraintsModel`](@ref), [`PreModel`](@ref) and [`Model`](@ref CTModels.Model). +See also: [`ConstraintsModel`](@ref), [`PreModel`](@ref) and [`Model`](@ref CTModels.OCP.Model). """ const ConstraintsDictType = OrderedDict{ Symbol,Tuple{Symbol,Union{Function,OrdinalRange{<:Int}},ctVector,ctVector} diff --git a/src/Serialization/Serialization.jl b/src/Serialization/Serialization.jl index 4f051771..c4b8e4cc 100644 --- a/src/Serialization/Serialization.jl +++ b/src/Serialization/Serialization.jl @@ -30,7 +30,7 @@ See also: [`CTModels`](@ref), [`export_ocp_solution`](@ref), [`import_ocp_soluti module Serialization using DocStringExtensions -using ..CTModels.Exceptions +using CTBase: CTBase, Exceptions # Import types from parent module import ..AbstractModel, ..AbstractSolution, ..Solution diff --git a/src/Serialization/export_import.jl b/src/Serialization/export_import.jl index c793f5bd..d1df1975 100644 --- a/src/Serialization/export_import.jl +++ b/src/Serialization/export_import.jl @@ -3,19 +3,19 @@ # ----------------------------- # to be extended by extensions function export_ocp_solution(::JLD2Tag, ::AbstractSolution; filename::String) - throw(CTModels.Exceptions.IncorrectArgument(:JLD2)) + throw(Exceptions.ExtensionError(:JLD2; message="to export solutions to JLD2 format")) end function import_ocp_solution(::JLD2Tag, ::AbstractModel; filename::String) - throw(CTModels.Exceptions.IncorrectArgument(:JLD2)) + throw(Exceptions.ExtensionError(:JLD2; message="to import solutions from JLD2 format")) end function export_ocp_solution(::JSON3Tag, ::AbstractSolution; filename::String) - throw(CTModels.Exceptions.IncorrectArgument(:JSON)) + throw(Exceptions.ExtensionError(:JSON3; message="to export solutions to JSON format")) end function import_ocp_solution(::JSON3Tag, ::AbstractModel; filename::String) - throw(CTModels.Exceptions.IncorrectArgument(:JSON)) + throw(Exceptions.ExtensionError(:JSON3; message="to import solutions from JSON format")) end """ diff --git a/test/problems/TestProblems.jl b/test/problems/TestProblems.jl index c193ffe0..2aa8b8e9 100644 --- a/test/problems/TestProblems.jl +++ b/test/problems/TestProblems.jl @@ -1,29 +1,16 @@ module TestProblems using CTModels - using SolverCore - using ADNLPModels - using ExaModels - include("problems_definition.jl") include("solution_example.jl") - include("rosenbrock.jl") - include("max1minusx2.jl") - include("elec.jl") include("beam.jl") include("solution_example_dual.jl") -# From problems_definition.jl -export OptimizationProblem, DummyProblem + # From solution_example.jl + export solution_example -# From solution_example.jl -export solution_example + # From beam.jl + export Beam -# From rosenbrock.jl -export Rosenbrock, rosenbrock_objective, rosenbrock_constraint - -# From beam.jl -export Beam - -# From solution_example_dual.jl -export solution_example_dual + # From solution_example_dual.jl + export solution_example_dual end diff --git a/test/runtests.jl b/test/runtests.jl index e42ef881..636a1bb2 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -8,14 +8,8 @@ # Test dependencies using Test -using Aqua using CTBase using CTModels -using ADNLPModels -using SolverCore -using NLPModels -using ExaModels -using MadNLP # Trigger CTModelsMadNLP extension # Trigger loading of optional extensions const TestRunner = Base.get_extension(CTBase, :TestRunner) diff --git a/test/suite/exceptions/test_config.jl b/test/suite/exceptions/test_config.jl deleted file mode 100644 index 167994a2..00000000 --- a/test/suite/exceptions/test_config.jl +++ /dev/null @@ -1,59 +0,0 @@ -module TestExceptionConfig - -using Test -using CTModels -using CTModels.Exceptions -const VERBOSE = isdefined(Main, :TestOptions) ? Main.TestOptions.VERBOSE : true -const SHOWTIMING = isdefined(Main, :TestOptions) ? Main.TestOptions.SHOWTIMING : true - -""" -Tests for exception configuration (config.jl) -""" -function test_exception_config() - @testset "Exception Configuration" verbose = VERBOSE showtiming = SHOWTIMING begin - - @testset "Stacktrace Control - Default Value" begin - # Test default value is false (user-friendly display) - @test CTModels.get_show_full_stacktrace() == true - end - - @testset "Stacktrace Control - Set to True" begin - # Test setting to true (full Julia stacktraces) - CTModels.set_show_full_stacktrace!(true) - @test CTModels.get_show_full_stacktrace() == true - end - - @testset "Stacktrace Control - Set to False" begin - # Test setting back to false - CTModels.set_show_full_stacktrace!(false) - @test CTModels.get_show_full_stacktrace() == false - end - - @testset "Stacktrace Control - Multiple Toggles" begin - # Test multiple toggles work correctly - original = CTModels.get_show_full_stacktrace() - - CTModels.set_show_full_stacktrace!(true) - @test CTModels.get_show_full_stacktrace() == true - - CTModels.set_show_full_stacktrace!(false) - @test CTModels.get_show_full_stacktrace() == false - - CTModels.set_show_full_stacktrace!(true) - @test CTModels.get_show_full_stacktrace() == true - - # Restore original state - CTModels.set_show_full_stacktrace!(original) - end - - @testset "Stacktrace Control - Return Value" begin - # Test that set_show_full_stacktrace! returns nothing - result = CTModels.set_show_full_stacktrace!(false) - @test isnothing(result) - end - end -end - -end # module - -test_config() = TestExceptionConfig.test_exception_config() diff --git a/test/suite/exceptions/test_conversion.jl b/test/suite/exceptions/test_conversion.jl deleted file mode 100644 index a0ee0b6d..00000000 --- a/test/suite/exceptions/test_conversion.jl +++ /dev/null @@ -1,202 +0,0 @@ -module TestExceptionConversion - -using Test -using CTModels.Exceptions -using CTBase -const VERBOSE = isdefined(Main, :TestOptions) ? Main.TestOptions.VERBOSE : true -const SHOWTIMING = isdefined(Main, :TestOptions) ? Main.TestOptions.SHOWTIMING : true - -""" -Tests for CTBase compatibility layer (conversion.jl) -""" -function test_exception_conversion() - @testset "CTBase Conversion" verbose = VERBOSE showtiming = SHOWTIMING begin - - @testset "IncorrectArgument - Simple Conversion" begin - e = IncorrectArgument("Invalid input") - ctbase_e = to_ctbase(e) - - @test ctbase_e isa CTBase.IncorrectArgument - @test contains(ctbase_e.var, "Invalid input") - end - - @testset "IncorrectArgument - Full Conversion" begin - e = IncorrectArgument( - "Invalid input", - got="x", - expected="y", - suggestion="Use y instead" - ) - - ctbase_e = to_ctbase(e) - - @test ctbase_e isa CTBase.IncorrectArgument - @test contains(ctbase_e.var, "Invalid input") - @test contains(ctbase_e.var, "got: x") - @test contains(ctbase_e.var, "expected: y") - @test contains(ctbase_e.var, "Suggestion: Use y instead") - end - - @testset "IncorrectArgument - Partial Fields" begin - # Only got field - e1 = IncorrectArgument("Error", got="value") - ctbase_e1 = to_ctbase(e1) - @test contains(ctbase_e1.var, "Error") - @test contains(ctbase_e1.var, "got: value") - - # Only expected field - e2 = IncorrectArgument("Error", expected="expected_value") - ctbase_e2 = to_ctbase(e2) - @test contains(ctbase_e2.var, "Error") - @test contains(ctbase_e2.var, "expected: expected_value") - - # Only suggestion field - e3 = IncorrectArgument("Error", suggestion="Fix it") - ctbase_e3 = to_ctbase(e3) - @test contains(ctbase_e3.var, "Error") - @test contains(ctbase_e3.var, "Suggestion: Fix it") - end - - @testset "UnauthorizedCall - Simple Conversion" begin - e = UnauthorizedCall("Cannot call") - ctbase_e = to_ctbase(e) - - @test ctbase_e isa CTBase.UnauthorizedCall - @test contains(ctbase_e.var, "Cannot call") - end - - @testset "UnauthorizedCall - Full Conversion" begin - e = UnauthorizedCall( - "Cannot call", - reason="already called", - suggestion="Create new instance" - ) - - ctbase_e = to_ctbase(e) - - @test ctbase_e isa CTBase.UnauthorizedCall - @test contains(ctbase_e.var, "Cannot call") - @test contains(ctbase_e.var, "reason: already called") - @test contains(ctbase_e.var, "Suggestion: Create new instance") - end - - @testset "UnauthorizedCall - Partial Fields" begin - # Only reason field - e1 = UnauthorizedCall("Error", reason="test reason") - ctbase_e1 = to_ctbase(e1) - @test contains(ctbase_e1.var, "Error") - @test contains(ctbase_e1.var, "reason: test reason") - - # Only suggestion field - e2 = UnauthorizedCall("Error", suggestion="Fix it") - ctbase_e2 = to_ctbase(e2) - @test contains(ctbase_e2.var, "Error") - @test contains(ctbase_e2.var, "Suggestion: Fix it") - end - - @testset "NotImplemented - Simple Conversion" begin - e = NotImplemented("run! not implemented") - ctbase_e = to_ctbase(e) - - @test ctbase_e isa CTBase.NotImplemented - @test contains(ctbase_e.var, "run! not implemented") - end - - @testset "NotImplemented - Full Conversion" begin - e = NotImplemented( - "Method solve! not implemented", - type_info="MyStrategy", - context="solve call", - suggestion="Import the relevant package (e.g. CTDirect) or implement solve!(::MyStrategy, ...)" - ) - - ctbase_e = to_ctbase(e) - - @test ctbase_e isa CTBase.NotImplemented - @test contains(ctbase_e.var, "Method solve! not implemented") - @test contains(ctbase_e.var, "type: MyStrategy") - @test contains(ctbase_e.var, "context: solve call") - @test contains(ctbase_e.var, "Suggestion: Import the relevant package") - end - - @testset "NotImplemented - Partial Fields" begin - # Only type_info field - e1 = NotImplemented("Error", type_info="MyType") - ctbase_e1 = to_ctbase(e1) - @test contains(ctbase_e1.var, "Error") - @test contains(ctbase_e1.var, "type: MyType") - - # Only context field - e2 = NotImplemented("Error", context="test context") - ctbase_e2 = to_ctbase(e2) - @test contains(ctbase_e2.var, "Error") - @test contains(ctbase_e2.var, "context: test context") - - # Only suggestion field - e3 = NotImplemented("Error", suggestion="Fix it") - ctbase_e3 = to_ctbase(e3) - @test contains(ctbase_e3.var, "Error") - @test contains(ctbase_e3.var, "Suggestion: Fix it") - end - - @testset "ParsingError - Simple Conversion" begin - e = ParsingError("Unexpected token") - ctbase_e = to_ctbase(e) - - @test ctbase_e isa CTBase.NotImplemented - @test contains(ctbase_e.var, "Unexpected token") - end - - @testset "ParsingError - Full Conversion" begin - e = ParsingError( - "Unexpected token 'end'", - location="line 42, column 15", - suggestion="Check syntax balance or remove extra 'end'" - ) - - ctbase_e = to_ctbase(e) - - @test ctbase_e isa CTBase.NotImplemented - @test contains(ctbase_e.var, "Unexpected token 'end'") - @test contains(ctbase_e.var, "at: line 42, column 15") - @test contains(ctbase_e.var, "Suggestion: Check syntax balance") - end - - @testset "ParsingError - Partial Fields" begin - # Only location field - e1 = ParsingError("Error", location="line 10") - ctbase_e1 = to_ctbase(e1) - @test contains(ctbase_e1.var, "Error") - @test contains(ctbase_e1.var, "at: line 10") - - # Only suggestion field - e2 = ParsingError("Error", suggestion="Fix syntax") - ctbase_e2 = to_ctbase(e2) - @test contains(ctbase_e2.var, "Error") - @test contains(ctbase_e2.var, "Suggestion: Fix syntax") - end - - @testset "Conversion - Preserves Information" begin - # Test that all information is preserved in conversion - e = IncorrectArgument( - "Complex error", - got="actual_value", - expected="expected_value", - suggestion="Do this instead" - ) - - ctbase_e = to_ctbase(e) - msg = ctbase_e.var - - # All parts should be in the message - @test contains(msg, "Complex error") - @test contains(msg, "actual_value") - @test contains(msg, "expected_value") - @test contains(msg, "Do this instead") - end - end -end - -end # module - -test_conversion() = TestExceptionConversion.test_exception_conversion() diff --git a/test/suite/exceptions/test_display.jl b/test/suite/exceptions/test_display.jl deleted file mode 100644 index dd1988af..00000000 --- a/test/suite/exceptions/test_display.jl +++ /dev/null @@ -1,180 +0,0 @@ -module TestExceptionDisplay - -using Test -using CTModels -using CTModels.Exceptions -const VERBOSE = isdefined(Main, :TestOptions) ? Main.TestOptions.VERBOSE : true -const SHOWTIMING = isdefined(Main, :TestOptions) ? Main.TestOptions.SHOWTIMING : true - -""" -Tests for exception display functions (display.jl) -""" -function test_exception_display() - @testset "Exception Display" verbose = VERBOSE showtiming = SHOWTIMING begin - - @testset "IncorrectArgument - User-Friendly Display" begin - io = IOBuffer() - e = IncorrectArgument( - "Test error", - got="value1", - expected="value2", - suggestion="Fix it like this", - context="test function" - ) - - # User-friendly display (default) - CTModels.set_show_full_stacktrace!(false) - @test_nowarn showerror(io, e) - output = String(take!(io)) - - # Check for key sections in user-friendly display - @test contains(output, "ERROR in CTModels") - @test contains(output, "Problem:") - @test contains(output, "Test error") - @test contains(output, "Details:") - @test contains(output, "Got:") - @test contains(output, "value1") - @test contains(output, "Expected:") - @test contains(output, "value2") - @test contains(output, "Context:") - @test contains(output, "test function") - @test contains(output, "Suggestion:") - @test contains(output, "Fix it like this") - end - - @testset "IncorrectArgument - Full Stacktrace Display" begin - io = IOBuffer() - e = IncorrectArgument( - "Test error", - got="value1", - expected="value2" - ) - - # Full stacktrace display - CTModels.set_show_full_stacktrace!(true) - @test_nowarn showerror(io, e) - output = String(take!(io)) - - # Check for standard Julia error format - @test contains(output, "IncorrectArgument") - @test contains(output, "Test error") - @test contains(output, "got: value1") - @test contains(output, "expected: value2") - - # Reset to default - CTModels.set_show_full_stacktrace!(false) - end - - @testset "IncorrectArgument - Minimal Display" begin - io = IOBuffer() - e = IncorrectArgument("Simple error") - - CTModels.set_show_full_stacktrace!(false) - @test_nowarn showerror(io, e) - output = String(take!(io)) - - @test contains(output, "Simple error") - @test contains(output, "Problem:") - end - - @testset "UnauthorizedCall - User-Friendly Display" begin - io = IOBuffer() - e = UnauthorizedCall( - "Cannot call function", - reason="already called", - suggestion="Create new instance", - context="state! function" - ) - - CTModels.set_show_full_stacktrace!(false) - @test_nowarn showerror(io, e) - output = String(take!(io)) - - @test contains(output, "ERROR in CTModels") - @test contains(output, "Cannot call function") - @test contains(output, "Reason:") - @test contains(output, "already called") - @test contains(output, "Suggestion:") - @test contains(output, "Create new instance") - end - - @testset "UnauthorizedCall - Full Stacktrace Display" begin - io = IOBuffer() - e = UnauthorizedCall("Test", reason="test reason") - - CTModels.set_show_full_stacktrace!(true) - @test_nowarn showerror(io, e) - output = String(take!(io)) - - @test contains(output, "UnauthorizedCall") - @test contains(output, "Test") - @test contains(output, "reason: test reason") - - CTModels.set_show_full_stacktrace!(false) - end - - @testset "NotImplemented - Display" begin - io = IOBuffer() - e = NotImplemented("Feature not implemented", type_info="MyType") - - # User-friendly - CTModels.set_show_full_stacktrace!(false) - @test_nowarn showerror(io, e) - output = String(take!(io)) - @test contains(output, "Feature not implemented") - @test contains(output, "Type:") - @test contains(output, "MyType") - - # Full stacktrace - CTModels.set_show_full_stacktrace!(true) - @test_nowarn showerror(io, e) - output = String(take!(io)) - @test contains(output, "NotImplemented") - - CTModels.set_show_full_stacktrace!(false) - end - - @testset "ParsingError - Display" begin - io = IOBuffer() - e = ParsingError("Syntax error", location="line 42") - - # User-friendly - CTModels.set_show_full_stacktrace!(false) - @test_nowarn showerror(io, e) - output = String(take!(io)) - @test contains(output, "Syntax error") - @test contains(output, "Location:") - @test contains(output, "line 42") - - # Full stacktrace - CTModels.set_show_full_stacktrace!(true) - @test_nowarn showerror(io, e) - output = String(take!(io)) - @test contains(output, "ParsingError") - @test contains(output, "at: line 42") - - CTModels.set_show_full_stacktrace!(false) - end - - @testset "Display - No Crash on Edge Cases" begin - io = IOBuffer() - - # Empty optional fields - e1 = IncorrectArgument("Error") - @test_nowarn showerror(io, e1) - - e2 = UnauthorizedCall("Error") - @test_nowarn showerror(io, e2) - - e3 = NotImplemented("Error") - @test_nowarn showerror(io, e3) - - e4 = ParsingError("Error") - @test_nowarn showerror(io, e4) - end - end -end - -end # module - -test_display() = TestExceptionDisplay.test_exception_display() diff --git a/test/suite/exceptions/test_ocp_integration.jl b/test/suite/exceptions/test_ocp_integration.jl index cb2a05d2..5937bdc7 100644 --- a/test/suite/exceptions/test_ocp_integration.jl +++ b/test/suite/exceptions/test_ocp_integration.jl @@ -2,7 +2,7 @@ module TestExceptionOCPIntegration using Test using CTModels -using CTModels.Exceptions +using CTBase: CTBase, Exceptions const VERBOSE = isdefined(Main, :TestOptions) ? Main.TestOptions.VERBOSE : true const SHOWTIMING = isdefined(Main, :TestOptions) ? Main.TestOptions.SHOWTIMING : true @@ -28,7 +28,7 @@ function test_ocp_exception_integration() ocp = OCP() state!(ocp, 2) - @test_throws Exceptions.UnauthorizedCall begin + @test_throws Exceptions.PreconditionError begin state!(ocp, 3) end @@ -36,7 +36,7 @@ function test_ocp_exception_integration() try state!(ocp, 3) catch e - @test e isa Exceptions.UnauthorizedCall + @test e isa Exceptions.PreconditionError @test e.msg == "State already set" @test !isnothing(e.reason) @test !isnothing(e.suggestion) @@ -53,7 +53,7 @@ function test_ocp_exception_integration() state!(ocp, 2) control!(ocp, 1) - @test_throws Exceptions.UnauthorizedCall begin + @test_throws Exceptions.PreconditionError begin control!(ocp, 2) end @@ -61,7 +61,7 @@ function test_ocp_exception_integration() try control!(ocp, 2) catch e - @test e isa Exceptions.UnauthorizedCall + @test e isa Exceptions.PreconditionError @test e.msg == "Control already set" @test !isnothing(e.reason) @test !isnothing(e.suggestion) @@ -82,7 +82,7 @@ function test_ocp_exception_integration() # Set objective first (should fail) objective!(ocp, :min, mayer=(x0, xf, v) -> x0[1]) - @test_throws Exceptions.UnauthorizedCall begin + @test_throws Exceptions.PreconditionError begin variable!(ocp, 1) end @@ -90,7 +90,7 @@ function test_ocp_exception_integration() try variable!(ocp, 1) catch e - @test e isa Exceptions.UnauthorizedCall + @test e isa Exceptions.PreconditionError @test e.msg == "Variable must be set before objective" @test !isnothing(e.reason) @test !isnothing(e.suggestion) @@ -107,7 +107,7 @@ function test_ocp_exception_integration() state!(ocp, 2) times!(ocp, t0=0, tf=1) - @test_throws Exceptions.UnauthorizedCall begin + @test_throws Exceptions.PreconditionError begin times!(ocp, t0=1, tf=2) end @@ -115,7 +115,7 @@ function test_ocp_exception_integration() try times!(ocp, t0=1, tf=2) catch e - @test e isa Exceptions.UnauthorizedCall + @test e isa Exceptions.PreconditionError @test e.msg == "Time already set" @test !isnothing(e.reason) @test !isnothing(e.suggestion) @@ -130,7 +130,7 @@ function test_ocp_exception_integration() # Test objective without prerequisites ocp = OCP() - @test_throws Exceptions.UnauthorizedCall begin + @test_throws Exceptions.PreconditionError begin objective!(ocp, :min, mayer=(x0, xf, v) -> x0[1]) end @@ -138,7 +138,7 @@ function test_ocp_exception_integration() try objective!(ocp, :min, mayer=(x0, xf, v) -> x0[1]) catch e - @test e isa Exceptions.UnauthorizedCall + @test e isa Exceptions.PreconditionError @test e.msg == "State must be set before objective" @test !isnothing(e.reason) @test !isnothing(e.suggestion) @@ -153,7 +153,7 @@ function test_ocp_exception_integration() try objective!(ocp, :min, mayer=(x0, xf, v) -> x0[1]) catch e - @test e isa Exceptions.UnauthorizedCall + @test e isa Exceptions.PreconditionError @test e.msg == "Control must be set before objective" @test occursin("control has not been defined yet", e.reason) @test occursin("Call control!(ocp, dimension) before objective!", e.suggestion) @@ -165,7 +165,7 @@ function test_ocp_exception_integration() # Test dynamics without prerequisites ocp = OCP() - @test_throws Exceptions.UnauthorizedCall begin + @test_throws Exceptions.PreconditionError begin dynamics!(ocp, (out, t, x, u, v) -> out .= x) end @@ -173,7 +173,7 @@ function test_ocp_exception_integration() try dynamics!(ocp, (out, t, x, u, v) -> out .= x) catch e - @test e isa Exceptions.UnauthorizedCall + @test e isa Exceptions.PreconditionError @test e.msg == "State must be set before defining dynamics" @test !isnothing(e.reason) @test !isnothing(e.suggestion) @@ -190,7 +190,7 @@ function test_ocp_exception_integration() times!(ocp2, t0=0, tf=1) dynamics!(ocp2, (out, t, x, u, v) -> out .= x) - @test_throws Exceptions.UnauthorizedCall begin + @test_throws Exceptions.PreconditionError begin dynamics!(ocp2, (out, t, x, u, v) -> out .= 2*x) end @@ -198,7 +198,7 @@ function test_ocp_exception_integration() try dynamics!(ocp2, (out, t, x, u, v) -> out .= 2*x) catch e - @test e isa Exceptions.UnauthorizedCall + @test e isa Exceptions.PreconditionError @test e.msg == "Dynamics already set" @test occursin("dynamics have already been defined", e.reason) @test occursin("Create a new OCP instance", e.suggestion) @@ -210,7 +210,7 @@ function test_ocp_exception_integration() # Test constraint without prerequisites ocp = OCP() - @test_throws Exceptions.UnauthorizedCall begin + @test_throws Exceptions.PreconditionError begin constraint!(ocp, :state, lb=[0], ub=[1]) end @@ -218,7 +218,7 @@ function test_ocp_exception_integration() try constraint!(ocp, :state, lb=[0], ub=[1]) catch e - @test e isa Exceptions.UnauthorizedCall + @test e isa Exceptions.PreconditionError @test e.msg == "State must be set before adding constraints" @test !isnothing(e.reason) @test !isnothing(e.suggestion) @@ -235,7 +235,7 @@ function test_ocp_exception_integration() times!(ocp2, t0=0, tf=1) constraint!(ocp2, :state, lb=[0, 0], ub=[1, 1], label=:test) - @test_throws Exceptions.UnauthorizedCall begin + @test_throws Exceptions.PreconditionError begin constraint!(ocp2, :state, lb=[0, 0], ub=[2, 2], label=:test) end @@ -243,7 +243,7 @@ function test_ocp_exception_integration() try constraint!(ocp2, :state, lb=[0, 0], ub=[2, 2], label=:test) catch e - @test e isa Exceptions.UnauthorizedCall + @test e isa Exceptions.PreconditionError @test e.msg == "Constraint already exists" @test occursin("constraint with label", e.reason) @test occursin("Use a different label", e.suggestion) diff --git a/test/suite/exceptions/test_types.jl b/test/suite/exceptions/test_types.jl deleted file mode 100644 index e8f2fbef..00000000 --- a/test/suite/exceptions/test_types.jl +++ /dev/null @@ -1,155 +0,0 @@ -module TestExceptionTypes - -using Test -using CTModels.Exceptions -const VERBOSE = isdefined(Main, :TestOptions) ? Main.TestOptions.VERBOSE : true -const SHOWTIMING = isdefined(Main, :TestOptions) ? Main.TestOptions.SHOWTIMING : true - -""" -Tests for exception type definitions (types.jl) -""" -function test_exception_types() - @testset "Exception Types" verbose = VERBOSE showtiming = SHOWTIMING begin - - @testset "CTModelsException Hierarchy" begin - # Test that all exceptions inherit from CTModelsException - @test IncorrectArgument("test") isa CTModelsException - @test UnauthorizedCall("test") isa CTModelsException - @test NotImplemented("test") isa CTModelsException - @test ParsingError("test") isa CTModelsException - - # Test that they are also standard Exceptions - @test IncorrectArgument("test") isa Exception - @test UnauthorizedCall("test") isa Exception - @test NotImplemented("test") isa Exception - @test ParsingError("test") isa Exception - end - - @testset "IncorrectArgument - Construction" begin - # Simple message only - e = IncorrectArgument("Invalid input") - @test e.msg == "Invalid input" - @test isnothing(e.got) - @test isnothing(e.expected) - @test isnothing(e.suggestion) - @test isnothing(e.context) - - # With got and expected - e = IncorrectArgument("Invalid value", got="x", expected="y") - @test e.msg == "Invalid value" - @test e.got == "x" - @test e.expected == "y" - @test isnothing(e.suggestion) - @test isnothing(e.context) - - # With all fields - e = IncorrectArgument( - "Invalid criterion", - got=":invalid", - expected=":min or :max", - suggestion="Use objective!(ocp, :min, ...)", - context="objective! function" - ) - @test e.msg == "Invalid criterion" - @test e.got == ":invalid" - @test e.expected == ":min or :max" - @test e.suggestion == "Use objective!(ocp, :min, ...)" - @test e.context == "objective! function" - - # Test that it can be thrown - @test_throws IncorrectArgument throw(IncorrectArgument("Test error")) - end - - @testset "UnauthorizedCall - Construction" begin - # Simple message only - e = UnauthorizedCall("State already set") - @test e.msg == "State already set" - @test isnothing(e.reason) - @test isnothing(e.suggestion) - @test isnothing(e.context) - - # With reason - e = UnauthorizedCall("Cannot call", reason="already called") - @test e.msg == "Cannot call" - @test e.reason == "already called" - @test isnothing(e.suggestion) - - # With all fields - e = UnauthorizedCall( - "Cannot call state! twice", - reason="state has already been defined for this OCP", - suggestion="Create a new OCP instance", - context="state! function" - ) - @test e.msg == "Cannot call state! twice" - @test e.reason == "state has already been defined for this OCP" - @test e.suggestion == "Create a new OCP instance" - @test e.context == "state! function" - - # Test that it can be thrown - @test_throws UnauthorizedCall throw(UnauthorizedCall("Test error")) - end - - @testset "NotImplemented - Construction" begin - # Simple message only - e = NotImplemented("run! not implemented") - @test e.msg == "run! not implemented" - @test isnothing(e.type_info) - @test isnothing(e.suggestion) - @test isnothing(e.context) - - # With type info - e = NotImplemented("run! not implemented", type_info="MyAlgorithm") - @test e.msg == "run! not implemented" - @test e.type_info == "MyAlgorithm" - @test isnothing(e.suggestion) - @test isnothing(e.context) - - # With all fields (NEW) - e = NotImplemented( - "Method solve! not implemented", - type_info="MyStrategy", - context="solve call", - suggestion="Import the relevant package (e.g. CTDirect) or implement solve!(::MyStrategy, ...)" - ) - @test e.msg == "Method solve! not implemented" - @test e.type_info == "MyStrategy" - @test e.context == "solve call" - @test e.suggestion == "Import the relevant package (e.g. CTDirect) or implement solve!(::MyStrategy, ...)" - - # Test that it can be thrown - @test_throws NotImplemented throw(NotImplemented("Test")) - end - - @testset "ParsingError - Construction" begin - # Simple message only - e = ParsingError("Unexpected token") - @test e.msg == "Unexpected token" - @test isnothing(e.location) - @test isnothing(e.suggestion) - - # With location - e = ParsingError("Unexpected token", location="line 42") - @test e.msg == "Unexpected token" - @test e.location == "line 42" - @test isnothing(e.suggestion) - - # With all fields (NEW) - e = ParsingError( - "Unexpected token 'end'", - location="line 42, column 15", - suggestion="Check syntax balance or remove extra 'end'" - ) - @test e.msg == "Unexpected token 'end'" - @test e.location == "line 42, column 15" - @test e.suggestion == "Check syntax balance or remove extra 'end'" - - # Test that it can be thrown - @test_throws ParsingError throw(ParsingError("Test")) - end - end -end - -end # module - -test_types() = TestExceptionTypes.test_exception_types() diff --git a/test/suite/extensions/test_plot.jl b/test/suite/extensions/test_plot.jl index b2359b3f..c96c4b1e 100644 --- a/test/suite/extensions/test_plot.jl +++ b/test/suite/extensions/test_plot.jl @@ -1,7 +1,7 @@ module TestPlot using Test -using CTBase +using CTBase: CTBase, Exceptions using CTModels using Main.TestProblems using Plots @@ -231,7 +231,7 @@ function test_plot() Test.@test sz_full == (600, 140 * 5) # 2 (state) + 1 (control) + 2 (path) # Invalid control keyword should throw - Test.@test_throws CTModels.Exceptions.IncorrectArgument plots_ext.__size_plot( + Test.@test_throws Exceptions.IncorrectArgument plots_ext.__size_plot( fake_state, CTModels.model(fake_state), :wrong_choice, @@ -343,7 +343,7 @@ function test_plot() Test.@test plot(sol; time=:default) isa Plots.Plot Test.@test plot(sol; time=:normalize) isa Plots.Plot Test.@test plot(sol; time=:normalise) isa Plots.Plot - Test.@test_throws CTModels.Exceptions.IncorrectArgument plot(sol; time=:wrong_choice) + Test.@test_throws Exceptions.IncorrectArgument plot(sol; time=:wrong_choice) end Test.@testset "plot(sol) – layout and control options" begin @@ -351,7 +351,7 @@ function test_plot() Test.@test plot(sol; layout=:group, control=:components) isa Plots.Plot Test.@test plot(sol; layout=:group, control=:norm) isa Plots.Plot Test.@test plot(sol; layout=:group, control=:all) isa Plots.Plot - Test.@test_throws CTModels.Exceptions.IncorrectArgument plot( + Test.@test_throws Exceptions.IncorrectArgument plot( sol; layout=:group, control=:wrong_choice ) @@ -359,14 +359,14 @@ function test_plot() Test.@test plot(sol; layout=:split, control=:components) isa Plots.Plot Test.@test plot(sol; layout=:split, control=:norm) isa Plots.Plot Test.@test plot(sol; layout=:split, control=:all) isa Plots.Plot - Test.@test_throws CTModels.Exceptions.IncorrectArgument plot( + Test.@test_throws Exceptions.IncorrectArgument plot( sol; layout=:split, control=:wrong_choice ) # layout only Test.@test plot(sol; layout=:split) isa Plots.Plot Test.@test plot(sol; layout=:group) isa Plots.Plot - Test.@test_throws CTModels.Exceptions.IncorrectArgument plot(sol; layout=:wrong_choice) + Test.@test_throws Exceptions.IncorrectArgument plot(sol; layout=:wrong_choice) end Test.@testset "plot!(...) – reuse of plots and time keyword" begin @@ -375,21 +375,21 @@ function test_plot() Test.@test plot!(plt, sol; time=:default) isa Plots.Plot Test.@test plot!(plt, sol; time=:normalize) isa Plots.Plot Test.@test plot!(plt, sol; time=:normalise) isa Plots.Plot - Test.@test_throws CTModels.Exceptions.IncorrectArgument plot!(plt, sol; time=:wrong_choice) + Test.@test_throws Exceptions.IncorrectArgument plot!(plt, sol; time=:wrong_choice) # plot!(sol, ...) variants with implicit current plot plot(sol; time=:default) Test.@test plot!(sol; time=:default) isa Plots.Plot Test.@test plot!(sol; time=:normalize) isa Plots.Plot Test.@test plot!(sol; time=:normalise) isa Plots.Plot - Test.@test_throws CTModels.Exceptions.IncorrectArgument plot!(sol; time=:wrong_choice) + Test.@test_throws Exceptions.IncorrectArgument plot!(sol; time=:wrong_choice) # Start from an empty plot() plt2 = plot() Test.@test plot!(plt2, sol; time=:default) isa Plots.Plot Test.@test plot!(plt2, sol; time=:normalize) isa Plots.Plot Test.@test plot!(plt2, sol; time=:normalise) isa Plots.Plot - Test.@test_throws CTModels.Exceptions.IncorrectArgument plot!(plt2, sol; time=:wrong_choice) + Test.@test_throws Exceptions.IncorrectArgument plot!(plt2, sol; time=:wrong_choice) end Test.@testset "plot!(...) – layout and control options" begin @@ -404,7 +404,7 @@ function test_plot() plt = plot(sol; layout=:group, control=:all) Test.@test plot!(plt, sol; layout=:group, control=:all) isa Plots.Plot - Test.@test_throws CTModels.Exceptions.IncorrectArgument plot!( + Test.@test_throws Exceptions.IncorrectArgument plot!( plt, sol; layout=:group, control=:wrong_choice ) @@ -419,7 +419,7 @@ function test_plot() plt = plot(sol; layout=:split, control=:all) Test.@test plot!(plt, sol; layout=:split, control=:all) isa Plots.Plot - Test.@test_throws CTModels.Exceptions.IncorrectArgument plot!( + Test.@test_throws Exceptions.IncorrectArgument plot!( plt, sol; layout=:split, control=:wrong_choice ) @@ -429,7 +429,7 @@ function test_plot() plt = plot(sol; layout=:group) Test.@test plot!(plt, sol; layout=:group) isa Plots.Plot - Test.@test_throws CTModels.Exceptions.IncorrectArgument plot!(plt, sol; layout=:wrong_choice) + Test.@test_throws Exceptions.IncorrectArgument plot!(plt, sol; layout=:wrong_choice) end Test.@testset "display(sol) – side effect" begin @@ -447,26 +447,26 @@ function test_plot() Test.@test plot(sol_pc; time=:default) isa Plots.Plot Test.@test plot(sol_pc; time=:normalize) isa Plots.Plot Test.@test plot(sol_pc; time=:normalise) isa Plots.Plot - Test.@test_throws CTModels.Exceptions.IncorrectArgument plot(sol_pc; time=:wrong_choice) + Test.@test_throws Exceptions.IncorrectArgument plot(sol_pc; time=:wrong_choice) # layout/control Test.@test plot(sol_pc; layout=:group, control=:components) isa Plots.Plot Test.@test plot(sol_pc; layout=:group, control=:norm) isa Plots.Plot Test.@test plot(sol_pc; layout=:group, control=:all) isa Plots.Plot - Test.@test_throws CTModels.Exceptions.IncorrectArgument plot( + Test.@test_throws Exceptions.IncorrectArgument plot( sol_pc; layout=:group, control=:wrong_choice ) Test.@test plot(sol_pc; layout=:split, control=:components) isa Plots.Plot Test.@test plot(sol_pc; layout=:split, control=:norm) isa Plots.Plot Test.@test plot(sol_pc; layout=:split, control=:all) isa Plots.Plot - Test.@test_throws CTModels.Exceptions.IncorrectArgument plot( + Test.@test_throws Exceptions.IncorrectArgument plot( sol_pc; layout=:split, control=:wrong_choice ) Test.@test plot(sol_pc; layout=:split) isa Plots.Plot Test.@test plot(sol_pc; layout=:group) isa Plots.Plot - Test.@test_throws CTModels.Exceptions.IncorrectArgument plot(sol_pc; layout=:wrong_choice) + Test.@test_throws Exceptions.IncorrectArgument plot(sol_pc; layout=:wrong_choice) end Test.@testset "plot!(sol with path constraints) – layout and time" begin @@ -475,7 +475,7 @@ function test_plot() Test.@test plot!(plt, sol_pc; time=:default) isa Plots.Plot Test.@test plot!(plt, sol_pc; time=:normalize) isa Plots.Plot Test.@test plot!(plt, sol_pc; time=:normalise) isa Plots.Plot - Test.@test_throws CTModels.Exceptions.IncorrectArgument plot!(plt, sol_pc; time=:wrong_choice) + Test.@test_throws Exceptions.IncorrectArgument plot!(plt, sol_pc; time=:wrong_choice) # layout/control plt = plot(sol_pc; layout=:group, control=:components) @@ -488,7 +488,7 @@ function test_plot() plt = plot(sol_pc; layout=:group, control=:all) Test.@test plot!(plt, sol_pc; layout=:group, control=:all) isa Plots.Plot - Test.@test_throws CTModels.Exceptions.IncorrectArgument plot!( + Test.@test_throws Exceptions.IncorrectArgument plot!( plt, sol_pc; layout=:group, control=:wrong_choice ) @@ -502,7 +502,7 @@ function test_plot() plt = plot(sol_pc; layout=:split, control=:all) Test.@test plot!(plt, sol_pc; layout=:split, control=:all) isa Plots.Plot - Test.@test_throws CTModels.Exceptions.IncorrectArgument plot!( + Test.@test_throws Exceptions.IncorrectArgument plot!( plt, sol_pc; layout=:split, control=:wrong_choice ) @@ -511,7 +511,7 @@ function test_plot() plt = plot(sol_pc; layout=:group) Test.@test plot!(plt, sol_pc; layout=:group) isa Plots.Plot - Test.@test_throws CTModels.Exceptions.IncorrectArgument plot!(plt, sol_pc; layout=:wrong_choice) + Test.@test_throws Exceptions.IncorrectArgument plot!(plt, sol_pc; layout=:wrong_choice) end end end diff --git a/test/suite/initial_guess/test_initial_guess_api.jl b/test/suite/initial_guess/test_initial_guess_api.jl index a2b7da49..def42e25 100644 --- a/test/suite/initial_guess/test_initial_guess_api.jl +++ b/test/suite/initial_guess/test_initial_guess_api.jl @@ -1,8 +1,8 @@ module TestInitialGuessAPI using Test +using CTBase: CTBase, Exceptions using CTModels -using CTModels.Exceptions using Main.TestProblems const VERBOSE = isdefined(Main, :TestOptions) ? Main.TestOptions.VERBOSE : true const SHOWTIMING = isdefined(Main, :TestOptions) ? Main.TestOptions.SHOWTIMING : true @@ -182,13 +182,13 @@ function test_initial_guess_api() ocp = DummyOCP1DNoVar() # Unsupported type should throw - Test.@test_throws CTModels.Exceptions.IncorrectArgument CTModels.build_initial_guess( + Test.@test_throws Exceptions.IncorrectArgument CTModels.build_initial_guess( ocp, 42 ) - Test.@test_throws CTModels.Exceptions.IncorrectArgument CTModels.build_initial_guess( + Test.@test_throws Exceptions.IncorrectArgument CTModels.build_initial_guess( ocp, "invalid" ) - Test.@test_throws CTModels.Exceptions.IncorrectArgument CTModels.build_initial_guess( + Test.@test_throws Exceptions.IncorrectArgument CTModels.build_initial_guess( ocp, [1, 2, 3] ) end diff --git a/test/suite/initial_guess/test_initial_guess_builders.jl b/test/suite/initial_guess/test_initial_guess_builders.jl index 31770a7b..0f9e829a 100644 --- a/test/suite/initial_guess/test_initial_guess_builders.jl +++ b/test/suite/initial_guess/test_initial_guess_builders.jl @@ -1,8 +1,8 @@ module TestInitialGuessBuilders using Test +using CTBase: CTBase, Exceptions using CTModels -using CTModels.Exceptions const VERBOSE = isdefined(Main, :TestOptions) ? Main.TestOptions.VERBOSE : true const SHOWTIMING = isdefined(Main, :TestOptions) ? Main.TestOptions.SHOWTIMING : true diff --git a/test/suite/initial_guess/test_initial_guess_control.jl b/test/suite/initial_guess/test_initial_guess_control.jl index 111cd37e..bb158f00 100644 --- a/test/suite/initial_guess/test_initial_guess_control.jl +++ b/test/suite/initial_guess/test_initial_guess_control.jl @@ -1,8 +1,8 @@ module TestInitialGuessControl using Test +using CTBase: CTBase, Exceptions using CTModels -using CTModels.Exceptions const VERBOSE = isdefined(Main, :TestOptions) ? Main.TestOptions.VERBOSE : true const SHOWTIMING = isdefined(Main, :TestOptions) ? Main.TestOptions.SHOWTIMING : true @@ -40,7 +40,7 @@ function test_initial_guess_control() Test.@test result(0.0) == 0.5 ocp_2d = DummyOCP2D() - Test.@test_throws IncorrectArgument CTModels.initial_control(ocp_2d, 0.5) + Test.@test_throws Exceptions.IncorrectArgument CTModels.initial_control(ocp_2d, 0.5) end Test.@testset "initial_control with Vector" begin @@ -50,7 +50,7 @@ function test_initial_guess_control() Test.@test result isa Function Test.@test result(0.0) == [0.0] - Test.@test_throws IncorrectArgument CTModels.initial_control(ocp, [0.0, 1.0]) + Test.@test_throws Exceptions.IncorrectArgument CTModels.initial_control(ocp, [0.0, 1.0]) end Test.@testset "initial_control with Nothing" begin diff --git a/test/suite/initial_guess/test_initial_guess_integration.jl b/test/suite/initial_guess/test_initial_guess_integration.jl index 434d0d15..cf6675ee 100644 --- a/test/suite/initial_guess/test_initial_guess_integration.jl +++ b/test/suite/initial_guess/test_initial_guess_integration.jl @@ -1,8 +1,8 @@ module TestInitialGuessIntegration using Test +using CTBase: CTBase, Exceptions using CTModels -using CTModels.Exceptions using Main.TestProblems const VERBOSE = isdefined(Main, :TestOptions) ? Main.TestOptions.VERBOSE : true const SHOWTIMING = isdefined(Main, :TestOptions) ? Main.TestOptions.SHOWTIMING : true @@ -38,7 +38,7 @@ function test_initial_guess_integration() # Test with incorrect state dimension (should throw) bad_named = (state=[0.1, 0.2, 0.3], control=[0.1], variable=Float64[]) - Test.@test_throws CTModels.Exceptions.IncorrectArgument CTModels.build_initial_guess( + Test.@test_throws Exceptions.IncorrectArgument CTModels.build_initial_guess( ocp, bad_named ) end diff --git a/test/suite/initial_guess/test_initial_guess_state.jl b/test/suite/initial_guess/test_initial_guess_state.jl index da85505d..6d56e4c8 100644 --- a/test/suite/initial_guess/test_initial_guess_state.jl +++ b/test/suite/initial_guess/test_initial_guess_state.jl @@ -1,8 +1,8 @@ module TestInitialGuessState using Test +using CTBase: CTBase, Exceptions using CTModels -using CTModels.Exceptions const VERBOSE = isdefined(Main, :TestOptions) ? Main.TestOptions.VERBOSE : true const SHOWTIMING = isdefined(Main, :TestOptions) ? Main.TestOptions.SHOWTIMING : true @@ -40,7 +40,7 @@ function test_initial_guess_state() Test.@test result(0.0) == 0.5 ocp_2d = DummyOCP2D() - Test.@test_throws IncorrectArgument CTModels.initial_state(ocp_2d, 0.5) + Test.@test_throws Exceptions.IncorrectArgument CTModels.initial_state(ocp_2d, 0.5) end Test.@testset "initial_state with Vector" begin @@ -50,7 +50,7 @@ function test_initial_guess_state() Test.@test result isa Function Test.@test result(0.0) == [0.0, 1.0] - Test.@test_throws IncorrectArgument CTModels.initial_state(ocp, [0.0]) + Test.@test_throws Exceptions.IncorrectArgument CTModels.initial_state(ocp, [0.0]) end Test.@testset "initial_state with Nothing" begin diff --git a/test/suite/initial_guess/test_initial_guess_validation.jl b/test/suite/initial_guess/test_initial_guess_validation.jl index f308b8e2..f3fd2c81 100644 --- a/test/suite/initial_guess/test_initial_guess_validation.jl +++ b/test/suite/initial_guess/test_initial_guess_validation.jl @@ -1,8 +1,8 @@ module TestInitialGuessValidation using Test +using CTBase: CTBase, Exceptions using CTModels -using CTModels.Exceptions using Main.TestProblems const VERBOSE = isdefined(Main, :TestOptions) ? Main.TestOptions.VERBOSE : true const SHOWTIMING = isdefined(Main, :TestOptions) ? Main.TestOptions.SHOWTIMING : true @@ -111,7 +111,7 @@ function test_initial_guess_validation() ) # Should throw - Test.@test_throws CTModels.Exceptions.IncorrectArgument CTModels.validate_initial_guess( + Test.@test_throws Exceptions.IncorrectArgument CTModels.validate_initial_guess( ocp, init_bad ) end @@ -126,7 +126,7 @@ function test_initial_guess_validation() ) # Should throw - Test.@test_throws CTModels.Exceptions.IncorrectArgument CTModels.validate_initial_guess( + Test.@test_throws Exceptions.IncorrectArgument CTModels.validate_initial_guess( ocp, init_bad ) end @@ -140,7 +140,7 @@ function test_initial_guess_validation() ) # Should throw - Test.@test_throws CTModels.Exceptions.IncorrectArgument CTModels.validate_initial_guess( + Test.@test_throws Exceptions.IncorrectArgument CTModels.validate_initial_guess( ocp, init_bad ) end @@ -173,7 +173,7 @@ function test_initial_guess_validation() sol = DummySolution1DVar(ocp1, t -> 0.1, t -> -0.2, 0.5) # Try to use it for ocp2 (wrong dimensions) - Test.@test_throws CTModels.Exceptions.IncorrectArgument CTModels.build_initial_guess( + Test.@test_throws Exceptions.IncorrectArgument CTModels.build_initial_guess( ocp2, sol ) end @@ -198,7 +198,7 @@ function test_initial_guess_validation() ocp = DummyOCP1DNoVar() bad_unknown = (state=0.1, foo=1.0) - Test.@test_throws CTModels.Exceptions.IncorrectArgument CTModels.build_initial_guess( + Test.@test_throws Exceptions.IncorrectArgument CTModels.build_initial_guess( ocp, bad_unknown ) end @@ -207,7 +207,7 @@ function test_initial_guess_validation() ocp = DummyOCP1DNoVar() bad_time = (time=[0.0, 1.0], state=0.1) - Test.@test_throws CTModels.Exceptions.IncorrectArgument CTModels.build_initial_guess( + Test.@test_throws Exceptions.IncorrectArgument CTModels.build_initial_guess( ocp, bad_time ) end @@ -217,7 +217,7 @@ function test_initial_guess_validation() # Both block and component level bad_nt = (state=[0.0, 0.0], x1=1.0) - Test.@test_throws CTModels.Exceptions.IncorrectArgument CTModels.build_initial_guess( + Test.@test_throws Exceptions.IncorrectArgument CTModels.build_initial_guess( ocp, bad_nt ) end @@ -227,7 +227,7 @@ function test_initial_guess_validation() # Both block and component level bad_nt = (control=[0.0, 1.0], u1=1.0) - Test.@test_throws CTModels.Exceptions.IncorrectArgument CTModels.build_initial_guess( + Test.@test_throws Exceptions.IncorrectArgument CTModels.build_initial_guess( ocp, bad_nt ) end @@ -237,7 +237,7 @@ function test_initial_guess_validation() # Both block and component level bad_nt = (w=[1.0, 2.0], tf=1.0) - Test.@test_throws CTModels.Exceptions.IncorrectArgument CTModels.build_initial_guess( + Test.@test_throws Exceptions.IncorrectArgument CTModels.build_initial_guess( ocp, bad_nt ) end @@ -314,7 +314,7 @@ function test_initial_guess_validation() CTModels.initial_guess(ocp; state=0.1) Test.@test false # Should not reach here catch e - Test.@test e isa CTModels.Exceptions.IncorrectArgument + Test.@test e isa Exceptions.IncorrectArgument # Verify enriched fields exist Test.@test !isempty(e.got) Test.@test !isempty(e.expected) diff --git a/test/suite/initial_guess/test_initial_guess_variable.jl b/test/suite/initial_guess/test_initial_guess_variable.jl index 6a008dde..d4990771 100644 --- a/test/suite/initial_guess/test_initial_guess_variable.jl +++ b/test/suite/initial_guess/test_initial_guess_variable.jl @@ -1,8 +1,8 @@ module TestInitialGuessVariable using Test +using CTBase: CTBase, Exceptions using CTModels -using CTModels.Exceptions const VERBOSE = isdefined(Main, :TestOptions) ? Main.TestOptions.VERBOSE : true const SHOWTIMING = isdefined(Main, :TestOptions) ? Main.TestOptions.SHOWTIMING : true @@ -38,7 +38,7 @@ function test_initial_guess_variable() Test.@test result == 0.5 ocp_no_var = DummyOCPNoVar() - Test.@test_throws IncorrectArgument CTModels.initial_variable(ocp_no_var, 0.5) + Test.@test_throws Exceptions.IncorrectArgument CTModels.initial_variable(ocp_no_var, 0.5) end Test.@testset "initial_variable with Vector" begin @@ -47,7 +47,7 @@ function test_initial_guess_variable() result = CTModels.initial_variable(ocp, [0.0, 1.0]) Test.@test result == [0.0, 1.0] - Test.@test_throws IncorrectArgument CTModels.initial_variable(ocp, [0.0]) + Test.@test_throws Exceptions.IncorrectArgument CTModels.initial_variable(ocp, [0.0]) end Test.@testset "initial_variable with Nothing" begin diff --git a/test/suite/meta/test_CTModels.jl b/test/suite/meta/test_CTModels.jl index 0f63597b..b22e7d1f 100644 --- a/test/suite/meta/test_CTModels.jl +++ b/test/suite/meta/test_CTModels.jl @@ -1,8 +1,8 @@ module TestCTModelsTop using Test +using CTBase: CTBase, Exceptions using CTModels -using CTBase const VERBOSE = isdefined(Main, :TestOptions) ? Main.TestOptions.VERBOSE : true const SHOWTIMING = isdefined(Main, :TestOptions) ? Main.TestOptions.SHOWTIMING : true @@ -45,10 +45,10 @@ function test_CTModels() ocp = CTMDummyModelTop() # Unknown format should trigger an IncorrectArgument without touching extensions. - Test.@test_throws CTModels.Exceptions.IncorrectArgument CTModels.export_ocp_solution( + Test.@test_throws Exceptions.IncorrectArgument CTModels.export_ocp_solution( sol; format=:FOO ) - Test.@test_throws CTModels.Exceptions.IncorrectArgument CTModels.import_ocp_solution( + Test.@test_throws Exceptions.IncorrectArgument CTModels.import_ocp_solution( ocp; format=:FOO ) end diff --git a/test/suite/meta/test_exports.jl b/test/suite/meta/test_exports.jl deleted file mode 100644 index 9667f0c5..00000000 --- a/test/suite/meta/test_exports.jl +++ /dev/null @@ -1,103 +0,0 @@ -module TestMetaExports - -using Test -using CTModels -using CTModels.Options -using CTModels.Strategies -using CTModels.Orchestration - -# Default test options -const VERBOSE = isdefined(Main, :TestOptions) ? Main.TestOptions.VERBOSE : true -const SHOWTIMING = isdefined(Main, :TestOptions) ? Main.TestOptions.SHOWTIMING : true - -""" - test_exports() - -Verify that the expected methods and types are correctly exported by the modules. -This helps maintain an explicit public API. -""" -function test_exports() - # Test.@testset "Meta Exports" verbose=VERBOSE showtiming=SHOWTIMING begin - - # Test.@testset "Options Exports" begin - # # List of expected exports in Options - # # Note: We use Symbol because we test if they are exported from the module - # expected_options = [ - # :NotProvided, :NotProvidedType, - # :OptionValue, :OptionDefinition, - # :extract_option, :extract_options, :extract_raw_options - # ] - - # for sym in expected_options - # Test.@test isdefined(CTModels.Options, sym) - # # Check if it's exported - # Test.@test sym in names(CTModels.Options) - # end - # end - - # Test.@testset "Strategies Exports" begin - # # List of expected exports in Strategies - # expected_strategies = [ - # :AbstractStrategy, :StrategyRegistry, :StrategyMetadata, :StrategyOptions, :OptionDefinition, - # :id, :metadata, :options, - # :create_registry, :strategy_ids, :type_from_id, - # :option_names, :option_type, :option_description, :option_default, :option_defaults, - # :option_value, :option_source, - # :is_user, :is_default, :is_computed, - # :build_strategy, :build_strategy_from_method, - # :extract_id_from_method, :option_names_from_method, - # :build_strategy_options, :resolve_alias, - # :filter_options, :suggest_options, - # :validate_strategy_contract - # ] - - # for sym in expected_strategies - # Test.@test isdefined(CTModels.Strategies, sym) - # Test.@test sym in names(CTModels.Strategies) - # end - # end - - # Test.@testset "Orchestration Exports" begin - # expected_orchestration = [ - # :route_all_options, - # :extract_strategy_ids, :build_strategy_to_family_map, :build_option_ownership_map, - # :build_strategy_from_method, :option_names_from_method - # ] - - # for sym in expected_orchestration - # Test.@test isdefined(CTModels.Orchestration, sym) - # Test.@test sym in names(CTModels.Orchestration) - # end - # end - - # Test.@testset "Main Module Exports" begin - # # Optimization Problem and Builders - # expected_main = [ - # :AbstractOptimizationProblem, - # :AbstractBuilder, :AbstractModelBuilder, :AbstractSolutionBuilder, - # :AbstractOCPSolutionBuilder, - # :ADNLPModelBuilder, :ExaModelBuilder, - # :ADNLPSolutionBuilder, :ExaSolutionBuilder, - # :get_adnlp_model_builder, :get_exa_model_builder, - # :get_adnlp_solution_builder, :get_exa_solution_builder, - # :build_model, :build_solution, - # :extract_solver_infos - # ] - - # # Modelers - # append!(expected_main, [:AbstractOptimizationModeler, :ADNLPModeler, :ExaModeler]) - - # # DOCP - # append!(expected_main, [:DiscretizedOptimalControlProblem, :ocp_model, :nlp_model, :ocp_solution]) - - # for sym in expected_main - # Test.@test isdefined(CTModels, sym) - # end - # end - - # end -end - -end # module - -test_exports() = TestMetaExports.test_exports() diff --git a/test/suite/ocp/test_constraints.jl b/test/suite/ocp/test_constraints.jl index 312fabbd..edfec3f9 100644 --- a/test/suite/ocp/test_constraints.jl +++ b/test/suite/ocp/test_constraints.jl @@ -1,8 +1,8 @@ module TestOCPConstraints using Test +using CTBase: CTBase, Exceptions using CTModels -using CTBase const VERBOSE = isdefined(Main, :TestOptions) ? Main.TestOptions.VERBOSE : true const SHOWTIMING = isdefined(Main, :TestOptions) ? Main.TestOptions.SHOWTIMING : true @@ -33,103 +33,103 @@ function test_constraints() CTModels.time!(ocp; t0=0.0, tf=10.0) CTModels.control!(ocp, 1) CTModels.variable!(ocp, 1) - @test_throws CTModels.Exceptions.UnauthorizedCall CTModels.constraint!(ocp, :dummy) + @test_throws Exceptions.PreconditionError CTModels.constraint!(ocp, :dummy) # control not set ocp = CTModels.PreModel() CTModels.time!(ocp; t0=0.0, tf=10.0) CTModels.state!(ocp, 1) CTModels.variable!(ocp, 1) - @test_throws CTModels.Exceptions.UnauthorizedCall CTModels.constraint!(ocp, :dummy) + @test_throws Exceptions.PreconditionError CTModels.constraint!(ocp, :dummy) # times not set ocp = CTModels.PreModel() CTModels.state!(ocp, 1) CTModels.control!(ocp, 1) CTModels.variable!(ocp, 1) - @test_throws CTModels.Exceptions.UnauthorizedCall CTModels.constraint!(ocp, :dummy) + @test_throws Exceptions.PreconditionError CTModels.constraint!(ocp, :dummy) # variable not set and try to add a :variable constraint ocp = CTModels.PreModel() CTModels.time!(ocp; t0=0.0, tf=10.0) CTModels.state!(ocp, 1) CTModels.control!(ocp, 1) - @test_throws CTModels.Exceptions.UnauthorizedCall CTModels.constraint!(ocp, :variable) + @test_throws Exceptions.PreconditionError CTModels.constraint!(ocp, :variable) # lb and ub cannot be both nothing - @test_throws CTModels.Exceptions.UnauthorizedCall CTModels.constraint!(ocp_set, :state) + @test_throws Exceptions.PreconditionError CTModels.constraint!(ocp_set, :state) # twice the same label for two constraints CTModels.constraint!(ocp_set, :state; lb=[0, 1], label=:cons) - @test_throws CTModels.Exceptions.UnauthorizedCall CTModels.constraint!( + @test_throws Exceptions.PreconditionError CTModels.constraint!( ocp_set, :control, lb=[0, 1], label=:cons ) # lb and ub must have the same length - @test_throws CTModels.Exceptions.IncorrectArgument CTModels.constraint!( + @test_throws Exceptions.IncorrectArgument CTModels.constraint!( ocp_set, :state, lb=[0, 1], ub=[0, 1, 2] ) # x(1) == [0, 0, 1] must raise an error if x is of dimension 2 - @test_throws CTModels.Exceptions.IncorrectArgument CTModels.constraint!( + @test_throws Exceptions.IncorrectArgument CTModels.constraint!( ocp_set, :boundary, lb=[0, 0, 1], ub=[0, 1, 2], codim_f=2 ) # if no range nor function is provided, lb and ub must have the right length: # depending on state, control, or variable - @test_throws CTModels.Exceptions.IncorrectArgument CTModels.constraint!( + @test_throws Exceptions.IncorrectArgument CTModels.constraint!( ocp_set, :state, lb=[0, 1, 2] ) - @test_throws CTModels.Exceptions.IncorrectArgument CTModels.constraint!( + @test_throws Exceptions.IncorrectArgument CTModels.constraint!( ocp_set, :control, lb=[0, 1, 2] ) - @test_throws CTModels.Exceptions.IncorrectArgument CTModels.constraint!( + @test_throws Exceptions.IncorrectArgument CTModels.constraint!( ocp_set, :variable, lb=[0, 1, 2] ) - @test_throws CTModels.Exceptions.IncorrectArgument CTModels.constraint!( + @test_throws Exceptions.IncorrectArgument CTModels.constraint!( ocp_set, :state, ub=[0, 1, 2] ) - @test_throws CTModels.Exceptions.IncorrectArgument CTModels.constraint!( + @test_throws Exceptions.IncorrectArgument CTModels.constraint!( ocp_set, :control, ub=[0, 1, 2] ) - @test_throws CTModels.Exceptions.IncorrectArgument CTModels.constraint!( + @test_throws Exceptions.IncorrectArgument CTModels.constraint!( ocp_set, :variable, ub=[0, 1, 2] ) # if no range nor function is provided, the only possible constraints are # :state, :control, and :variable - @test_throws CTModels.Exceptions.IncorrectArgument CTModels.constraint!( + @test_throws Exceptions.IncorrectArgument CTModels.constraint!( ocp_set, :dummy, lb=[0], ub=[1] ) # if a range is provided, lb and ub must have the same length as the range - @test_throws CTModels.Exceptions.IncorrectArgument CTModels.constraint!( + @test_throws Exceptions.IncorrectArgument CTModels.constraint!( ocp_set, :state, rg=1:2, lb=[0], ub=[1] ) # if a range is provided, it must be consistent with the dimensions of the model - @test_throws CTModels.Exceptions.IncorrectArgument CTModels.constraint!( + @test_throws Exceptions.IncorrectArgument CTModels.constraint!( ocp_set, :state, rg=3:4, lb=[0, 1], ub=[1, 2] ) - @test_throws CTModels.Exceptions.IncorrectArgument CTModels.constraint!( + @test_throws Exceptions.IncorrectArgument CTModels.constraint!( ocp_set, :control, rg=2:3, lb=[0, 1], ub=[1, 2] ) - @test_throws CTModels.Exceptions.IncorrectArgument CTModels.constraint!( + @test_throws Exceptions.IncorrectArgument CTModels.constraint!( ocp_set, :variable, rg=2:3, lb=[0, 1], ub=[1, 2] ) # if a range is provided, the only possible constraints are :state, :control, and :variable - @test_throws CTModels.Exceptions.IncorrectArgument CTModels.constraint!( + @test_throws Exceptions.IncorrectArgument CTModels.constraint!( ocp_set, :dummy, rg=1:2, lb=[0, 1], ub=[1, 2] ) # if a function is provided, the only possible constraints are :path, :boundary and :variable - @test_throws CTModels.Exceptions.IncorrectArgument CTModels.constraint!( + @test_throws Exceptions.IncorrectArgument CTModels.constraint!( ocp_set, :dummy, f=(x, y) -> x + y, lb=[0, 1], ub=[1, 2] ) # we cannot provide a function and a range - @test_throws CTModels.Exceptions.IncorrectArgument CTModels.constraint!( + @test_throws Exceptions.IncorrectArgument CTModels.constraint!( ocp_set, :variable, f=(x, y) -> x + y, rg=1:2, lb=[0, 1], ub=[1, 2] ) @@ -230,29 +230,29 @@ function test_constraints() # NEW: lb ≤ ub validation tests @testset "constraints! - Bounds validation" begin # lb > ub for state constraints - @test_throws CTModels.Exceptions.IncorrectArgument CTModels.constraint!( + @test_throws Exceptions.IncorrectArgument CTModels.constraint!( ocp_set, :state, lb=[1.0, 2.0], ub=[0.5, 1.0], label=:invalid_state ) # lb > ub for control constraints - @test_throws CTModels.Exceptions.IncorrectArgument CTModels.constraint!( + @test_throws Exceptions.IncorrectArgument CTModels.constraint!( ocp_set, :control, lb=[2.0], ub=[1.0], label=:invalid_control ) # lb > ub for variable constraints - @test_throws CTModels.Exceptions.IncorrectArgument CTModels.constraint!( + @test_throws Exceptions.IncorrectArgument CTModels.constraint!( ocp_set, :variable, lb=[1.5], ub=[0.5], label=:invalid_variable ) # lb > ub for boundary constraints f_boundary(r, x0, xf, v) = r .= x0 .+ v - @test_throws CTModels.Exceptions.IncorrectArgument CTModels.constraint!( + @test_throws Exceptions.IncorrectArgument CTModels.constraint!( ocp_set, :boundary; f=f_boundary, lb=[1.0, 2.0], ub=[0.5, 1.0], label=:invalid_boundary ) # lb > ub for path constraints f_path(r, t, x, u, v) = r .= x .+ u .+ v - @test_throws CTModels.Exceptions.IncorrectArgument CTModels.constraint!( + @test_throws Exceptions.IncorrectArgument CTModels.constraint!( ocp_set, :path; f=f_path, lb=[2.0], ub=[1.0], label=:invalid_path ) diff --git a/test/suite/ocp/test_control.jl b/test/suite/ocp/test_control.jl index 95af0a03..e7aef24b 100644 --- a/test/suite/ocp/test_control.jl +++ b/test/suite/ocp/test_control.jl @@ -1,7 +1,7 @@ module TestOCPControl using Test -using CTBase +using CTBase: CTBase, Exceptions using CTModels const VERBOSE = isdefined(Main, :TestOptions) ? Main.TestOptions.VERBOSE : true const SHOWTIMING = isdefined(Main, :TestOptions) ? Main.TestOptions.SHOWTIMING : true @@ -19,7 +19,7 @@ function test_control() # control! ocp = CTModels.PreModel() - @test_throws CTModels.Exceptions.IncorrectArgument CTModels.control!(ocp, 0) + @test_throws Exceptions.IncorrectArgument CTModels.control!(ocp, 0) ocp = CTModels.PreModel() CTModels.control!(ocp, 1) @@ -59,25 +59,25 @@ function test_control() # set twice ocp = CTModels.PreModel() CTModels.control!(ocp, 1) - @test_throws CTModels.Exceptions.UnauthorizedCall CTModels.control!(ocp, 1) + @test_throws Exceptions.PreconditionError CTModels.control!(ocp, 1) # wrong number of components ocp = CTModels.PreModel() - @test_throws CTModels.Exceptions.IncorrectArgument CTModels.control!(ocp, 2, "v", ["a"]) + @test_throws Exceptions.IncorrectArgument CTModels.control!(ocp, 2, "v", ["a"]) # NEW: Internal name validation tests @testset "control! - Internal name validation" begin # Empty name ocp = CTModels.PreModel() - @test_throws CTModels.Exceptions.IncorrectArgument CTModels.control!(ocp, 1, "") + @test_throws Exceptions.IncorrectArgument CTModels.control!(ocp, 1, "") # Empty component name ocp = CTModels.PreModel() - @test_throws CTModels.Exceptions.IncorrectArgument CTModels.control!(ocp, 2, "u", ["", "v"]) + @test_throws Exceptions.IncorrectArgument CTModels.control!(ocp, 2, "u", ["", "v"]) # Name in components (multiple) - should fail ocp = CTModels.PreModel() - @test_throws CTModels.Exceptions.IncorrectArgument CTModels.control!(ocp, 2, "u", ["u", "v"]) + @test_throws Exceptions.IncorrectArgument CTModels.control!(ocp, 2, "u", ["u", "v"]) # Name == component (single) - should PASS (default behavior) ocp = CTModels.PreModel() @@ -85,7 +85,7 @@ function test_control() # Duplicate components ocp = CTModels.PreModel() - @test_throws CTModels.Exceptions.IncorrectArgument CTModels.control!(ocp, 2, "u", ["v", "v"]) + @test_throws Exceptions.IncorrectArgument CTModels.control!(ocp, 2, "u", ["v", "v"]) end # NEW: Inter-component conflicts tests @@ -93,37 +93,37 @@ function test_control() # control.name vs state.name ocp = CTModels.PreModel() CTModels.state!(ocp, 2, "x", ["x₁", "x₂"]) - @test_throws CTModels.Exceptions.IncorrectArgument CTModels.control!(ocp, 1, "x") # Conflict! + @test_throws Exceptions.IncorrectArgument CTModels.control!(ocp, 1, "x") # Conflict! # control.name vs state.component ocp = CTModels.PreModel() CTModels.state!(ocp, 2, "x", ["u", "v"]) - @test_throws CTModels.Exceptions.IncorrectArgument CTModels.control!(ocp, 1, "u") + @test_throws Exceptions.IncorrectArgument CTModels.control!(ocp, 1, "u") # control.component vs state.name ocp = CTModels.PreModel() CTModels.state!(ocp, 1, "x") - @test_throws CTModels.Exceptions.IncorrectArgument CTModels.control!(ocp, 2, "u", ["x", "v"]) + @test_throws Exceptions.IncorrectArgument CTModels.control!(ocp, 2, "u", ["x", "v"]) # control.name vs time_name ocp = CTModels.PreModel() CTModels.time!(ocp, t0=0, tf=1, time_name="t") - @test_throws CTModels.Exceptions.IncorrectArgument CTModels.control!(ocp, 1, "t") + @test_throws Exceptions.IncorrectArgument CTModels.control!(ocp, 1, "t") # control.component vs time_name ocp = CTModels.PreModel() CTModels.time!(ocp, t0=0, tf=1, time_name="t") - @test_throws CTModels.Exceptions.IncorrectArgument CTModels.control!(ocp, 2, "u", ["t", "v"]) + @test_throws Exceptions.IncorrectArgument CTModels.control!(ocp, 2, "u", ["t", "v"]) # control.name vs variable.name ocp = CTModels.PreModel() CTModels.variable!(ocp, 1, "v") - @test_throws CTModels.Exceptions.IncorrectArgument CTModels.control!(ocp, 1, "v") + @test_throws Exceptions.IncorrectArgument CTModels.control!(ocp, 1, "v") # control.component vs variable.name ocp = CTModels.PreModel() CTModels.variable!(ocp, 1, "v") - @test_throws CTModels.Exceptions.IncorrectArgument CTModels.control!(ocp, 2, "u", ["v", "w"]) + @test_throws Exceptions.IncorrectArgument CTModels.control!(ocp, 2, "u", ["v", "w"]) end # NEW: Type stability tests diff --git a/test/suite/ocp/test_dynamics.jl b/test/suite/ocp/test_dynamics.jl index b78984fb..f320318f 100644 --- a/test/suite/ocp/test_dynamics.jl +++ b/test/suite/ocp/test_dynamics.jl @@ -1,8 +1,8 @@ module TestOCPDynamics using Test +using CTBase: CTBase, Exceptions using CTModels -using CTBase const VERBOSE = isdefined(Main, :TestOptions) ? Main.TestOptions.VERBOSE : true const SHOWTIMING = isdefined(Main, :TestOptions) ? Main.TestOptions.SHOWTIMING : true @@ -139,19 +139,19 @@ function test_partial_dynamics() ###### ocp5 = deepcopy(ocp) CTModels.dynamics!(ocp5, 1:1, partial_dyn_1!) - @test_throws CTModels.Exceptions.UnauthorizedCall CTModels.dynamics!(ocp5, full_dynamics!) + @test_throws Exceptions.PreconditionError CTModels.dynamics!(ocp5, full_dynamics!) ocp6 = deepcopy(ocp) CTModels.dynamics!(ocp6, 1:2, (r, t, x, u, v)->(r[1]=0; r[2]=0)) - @test_throws CTModels.Exceptions.UnauthorizedCall CTModels.dynamics!(ocp6, full_dynamics!) + @test_throws Exceptions.PreconditionError CTModels.dynamics!(ocp6, full_dynamics!) ###### # 7. Error: add index out of range (< 1 or > n_states) ###### ocp7 = deepcopy(ocp) - @test_throws CTModels.Exceptions.IncorrectArgument CTModels.dynamics!(ocp7, 0:0, partial_dyn_1!) - @test_throws CTModels.Exceptions.IncorrectArgument CTModels.dynamics!(ocp7, -1:-1, partial_dyn_1!) - @test_throws CTModels.Exceptions.IncorrectArgument CTModels.dynamics!( + @test_throws Exceptions.IncorrectArgument CTModels.dynamics!(ocp7, 0:0, partial_dyn_1!) + @test_throws Exceptions.IncorrectArgument CTModels.dynamics!(ocp7, -1:-1, partial_dyn_1!) + @test_throws Exceptions.IncorrectArgument CTModels.dynamics!( ocp7, (n_states + 1):(n_states + 1), partial_dyn_1! ) @@ -159,7 +159,7 @@ function test_partial_dynamics() # 8. Error: add range with at least one index out of range ###### ocp8 = deepcopy(ocp) - @test_throws CTModels.Exceptions.IncorrectArgument CTModels.dynamics!( + @test_throws Exceptions.IncorrectArgument CTModels.dynamics!( ocp8, (n_states):(n_states + 1), partial_dyn_1! ) @@ -168,14 +168,14 @@ function test_partial_dynamics() ###### ocp9 = deepcopy(ocp) CTModels.dynamics!(ocp9, 2:2, partial_dyn_1!) - @test_throws CTModels.Exceptions.UnauthorizedCall CTModels.dynamics!(ocp9, 1:2, partial_dyn_1!) + @test_throws Exceptions.PreconditionError CTModels.dynamics!(ocp9, 1:2, partial_dyn_1!) ###### # 10. Error: add twice the same index in two different ranges ###### ocp10 = deepcopy(ocp) CTModels.dynamics!(ocp10, 1:2, (r, t, x, u, v) -> (r[1]=t; r[2]=u[1])) - @test_throws CTModels.Exceptions.UnauthorizedCall CTModels.dynamics!( + @test_throws Exceptions.PreconditionError CTModels.dynamics!( ocp10, 2:3, (r, t, x, u, v) -> (r[2]=0; r[3]=0) ) @@ -185,21 +185,21 @@ function test_partial_dynamics() ocp_missing = CTModels.PreModel() CTModels.time!(ocp_missing; t0=0.0, tf=10.0) CTModels.control!(ocp_missing, 1) - @test_throws CTModels.Exceptions.UnauthorizedCall CTModels.dynamics!( + @test_throws Exceptions.PreconditionError CTModels.dynamics!( ocp_missing, 1:1, partial_dyn_1! ) ocp_missing = CTModels.PreModel() CTModels.time!(ocp_missing; t0=0.0, tf=10.0) CTModels.state!(ocp_missing, 1) - @test_throws CTModels.Exceptions.UnauthorizedCall CTModels.dynamics!( + @test_throws Exceptions.PreconditionError CTModels.dynamics!( ocp_missing, 1:1, partial_dyn_1! ) ocp_missing = CTModels.PreModel() CTModels.state!(ocp_missing, 1) CTModels.control!(ocp_missing, 1) - @test_throws CTModels.Exceptions.UnauthorizedCall CTModels.dynamics!( + @test_throws Exceptions.PreconditionError CTModels.dynamics!( ocp_missing, 1:1, partial_dyn_1! ) @@ -209,7 +209,7 @@ function test_partial_dynamics() CTModels.state!(ocp_variable, 3) CTModels.control!(ocp_variable, 1) CTModels.dynamics!(ocp_variable, 1:3, full_dynamics!) - @test_throws CTModels.Exceptions.UnauthorizedCall CTModels.variable!(ocp_variable, 1) + @test_throws Exceptions.PreconditionError CTModels.variable!(ocp_variable, 1) end function test_full_dynamics() @@ -231,7 +231,7 @@ function test_full_dynamics() ###### # 2. Error: set full dynamics twice not allowed ###### - @test_throws CTModels.Exceptions.UnauthorizedCall CTModels.dynamics!(ocp, dynamics!) + @test_throws Exceptions.PreconditionError CTModels.dynamics!(ocp, dynamics!) ###### # 3. Error: state must be set before dynamics @@ -240,7 +240,7 @@ function test_full_dynamics() CTModels.time!(ocp2; t0=0.0, tf=10.0) CTModels.control!(ocp2, 1) CTModels.variable!(ocp2, 1) - @test_throws CTModels.Exceptions.UnauthorizedCall CTModels.dynamics!(ocp2, dynamics!) + @test_throws Exceptions.PreconditionError CTModels.dynamics!(ocp2, dynamics!) ###### # 4. Error: control must be set before dynamics @@ -249,7 +249,7 @@ function test_full_dynamics() CTModels.time!(ocp3; t0=0.0, tf=10.0) CTModels.state!(ocp3, 1) CTModels.variable!(ocp3, 1) - @test_throws CTModels.Exceptions.UnauthorizedCall CTModels.dynamics!(ocp3, dynamics!) + @test_throws Exceptions.PreconditionError CTModels.dynamics!(ocp3, dynamics!) ###### # 5. Error: time must be set before dynamics @@ -258,7 +258,7 @@ function test_full_dynamics() CTModels.state!(ocp4, 1) CTModels.control!(ocp4, 1) CTModels.variable!(ocp4, 1) - @test_throws CTModels.Exceptions.UnauthorizedCall CTModels.dynamics!(ocp4, dynamics!) + @test_throws Exceptions.PreconditionError CTModels.dynamics!(ocp4, dynamics!) ###### # 6. Error: variable must NOT be set after dynamics @@ -268,7 +268,7 @@ function test_full_dynamics() CTModels.state!(ocp5, 1) CTModels.control!(ocp5, 1) CTModels.dynamics!(ocp5, dynamics!) - @test_throws CTModels.Exceptions.UnauthorizedCall CTModels.variable!(ocp5, 1) + @test_throws Exceptions.PreconditionError CTModels.variable!(ocp5, 1) ###### # 7. Error: mixing full dynamics and partial dynamics not allowed @@ -281,7 +281,7 @@ function test_full_dynamics() CTModels.dynamics!(ocp6, dynamics!) # Attempt to add partial dynamics after full dynamics -> error - @test_throws CTModels.Exceptions.UnauthorizedCall CTModels.dynamics!( + @test_throws Exceptions.PreconditionError CTModels.dynamics!( ocp6, 1:1, (r, t, x, u, v)->(r[1]=0) ) @@ -292,7 +292,7 @@ function test_full_dynamics() CTModels.control!(ocp7, 1) CTModels.variable!(ocp7, 1) CTModels.dynamics!(ocp7, 1:1, (r, t, x, u, v)->(r[1]=0)) - @test_throws CTModels.Exceptions.UnauthorizedCall CTModels.dynamics!(ocp7, dynamics!) + @test_throws Exceptions.PreconditionError CTModels.dynamics!(ocp7, dynamics!) end function test_dynamics() diff --git a/test/suite/ocp/test_interpolation_helpers.jl b/test/suite/ocp/test_interpolation_helpers.jl index 7f9beec1..7afd0e24 100644 --- a/test/suite/ocp/test_interpolation_helpers.jl +++ b/test/suite/ocp/test_interpolation_helpers.jl @@ -1,9 +1,9 @@ module TestInterpolationHelpers using Test +using CTBase: CTBase, Exceptions using CTModels using CTModels.OCP: build_interpolated_function, _interpolate_from_data, _wrap_scalar_and_deepcopy -using CTModels.Exceptions: IncorrectArgument const VERBOSE = isdefined(Main, :TestOptions) ? Main.TestOptions.VERBOSE : true const SHOWTIMING = isdefined(Main, :TestOptions) ? Main.TestOptions.SHOWTIMING : true @@ -35,7 +35,7 @@ function test_interpolation_helpers() @test isnothing(result) # Test allow_nothing=false (should throw) - @test_throws IncorrectArgument _interpolate_from_data( + @test_throws Exceptions.IncorrectArgument _interpolate_from_data( nothing, T, 2, Nothing; allow_nothing=false ) end @@ -77,7 +77,7 @@ function test_interpolation_helpers() @test !isnothing(func) # Invalid: matrix has 2 columns, we expect 3 - @test_throws IncorrectArgument _interpolate_from_data( + @test_throws Exceptions.IncorrectArgument _interpolate_from_data( X_2d, T, 3, Matrix{Float64}; expected_dim=3 ) @@ -175,13 +175,13 @@ function test_interpolation_helpers() @testset "build_interpolated_function: error cases" begin # Nothing not allowed - @test_throws IncorrectArgument build_interpolated_function( + @test_throws Exceptions.IncorrectArgument build_interpolated_function( nothing, T, 2, Nothing; allow_nothing=false ) # Dimension mismatch - @test_throws IncorrectArgument build_interpolated_function( + @test_throws Exceptions.IncorrectArgument build_interpolated_function( X_2d, T, 3, Matrix{Float64}; expected_dim=3 ) diff --git a/test/suite/ocp/test_model.jl b/test/suite/ocp/test_model.jl index 9a56bd48..9a51dffd 100644 --- a/test/suite/ocp/test_model.jl +++ b/test/suite/ocp/test_model.jl @@ -1,8 +1,8 @@ module TestOCPModel using Test +using CTBase: CTBase, Exceptions using CTModels -using CTBase const VERBOSE = isdefined(Main, :TestOptions) ? Main.TestOptions.VERBOSE : true const SHOWTIMING = isdefined(Main, :TestOptions) ? Main.TestOptions.SHOWTIMING : true @@ -13,19 +13,19 @@ function test_model() pre_ocp = CTModels.PreModel() # exception: times must be set - @test_throws CTModels.Exceptions.UnauthorizedCall CTModels.build(pre_ocp) + @test_throws Exceptions.PreconditionError CTModels.build(pre_ocp) # set times CTModels.time!(pre_ocp; t0=0.0, tf=1.0) # exception: state must be set - @test_throws CTModels.Exceptions.UnauthorizedCall CTModels.build(pre_ocp) + @test_throws Exceptions.PreconditionError CTModels.build(pre_ocp) # set state CTModels.state!(pre_ocp, 2) # exception: control must be set - @test_throws CTModels.Exceptions.UnauthorizedCall CTModels.build(pre_ocp) + @test_throws Exceptions.PreconditionError CTModels.build(pre_ocp) # set control CTModels.control!(pre_ocp, 2) @@ -34,14 +34,14 @@ function test_model() CTModels.variable!(pre_ocp, 2) # exception: dynamics must be set - @test_throws CTModels.Exceptions.UnauthorizedCall CTModels.build(pre_ocp) + @test_throws Exceptions.PreconditionError CTModels.build(pre_ocp) # set dynamics dynamics!(r, t, x, u, v) = r .= t .+ x .+ u .+ v CTModels.dynamics!(pre_ocp, dynamics!) # exception: objective must be set - @test_throws CTModels.Exceptions.UnauthorizedCall CTModels.build(pre_ocp) + @test_throws Exceptions.PreconditionError CTModels.build(pre_ocp) # set objective mayer(x0, xf, v) = x0 .+ xf .+ v @@ -49,7 +49,7 @@ function test_model() CTModels.objective!(pre_ocp, :min; mayer=mayer, lagrange=lagrange) # exception: definition must be set - @test_throws CTModels.Exceptions.UnauthorizedCall CTModels.build(pre_ocp) + @test_throws Exceptions.PreconditionError CTModels.build(pre_ocp) # set definition definition = quote @@ -64,7 +64,7 @@ function test_model() CTModels.definition!(pre_ocp, definition) # exception: time dependence must be set - @test_throws CTModels.Exceptions.UnauthorizedCall CTModels.build(pre_ocp) + @test_throws Exceptions.PreconditionError CTModels.build(pre_ocp) # set time dependence CTModels.time_dependence!(pre_ocp; autonomous=false) diff --git a/test/suite/ocp/test_name_conflicts_integration.jl b/test/suite/ocp/test_name_conflicts_integration.jl index 3be6e45a..dd8c025b 100644 --- a/test/suite/ocp/test_name_conflicts_integration.jl +++ b/test/suite/ocp/test_name_conflicts_integration.jl @@ -1,8 +1,8 @@ module TestNameConflictsIntegrationSimple using Test +using CTBase: CTBase, Exceptions using CTModels -using CTBase const VERBOSE = isdefined(Main, :TestOptions) ? Main.TestOptions.VERBOSE : true const SHOWTIMING = isdefined(Main, :TestOptions) ? Main.TestOptions.SHOWTIMING : true @@ -13,17 +13,17 @@ function test_name_conflicts_integration() # Test state vs control conflict ocp = CTModels.PreModel() CTModels.state!(ocp, 1, "x") - @test_throws CTModels.Exceptions.IncorrectArgument CTModels.control!(ocp, 1, "x") + @test_throws Exceptions.IncorrectArgument CTModels.control!(ocp, 1, "x") # Test control vs variable conflict ocp2 = CTModels.PreModel() CTModels.control!(ocp2, 1, "u") - @test_throws CTModels.Exceptions.IncorrectArgument CTModels.variable!(ocp2, 1, "u") + @test_throws Exceptions.IncorrectArgument CTModels.variable!(ocp2, 1, "u") # Test state vs time conflict ocp3 = CTModels.PreModel() CTModels.state!(ocp3, 1, "x") - @test_throws CTModels.Exceptions.IncorrectArgument CTModels.time!(ocp3, t0=0, tf=1, time_name="x") + @test_throws Exceptions.IncorrectArgument CTModels.time!(ocp3, t0=0, tf=1, time_name="x") end @testset "Valid complete workflow" begin @@ -71,7 +71,7 @@ function test_name_conflicts_integration() CTModels.control!(ocp, 1, "u") CTModels.variable!(ocp, 1, "v") - @test_throws CTModels.Exceptions.IncorrectArgument CTModels.constraint!(ocp, :state, lb=[1, 2], ub=[0, 1]) + @test_throws Exceptions.IncorrectArgument CTModels.constraint!(ocp, :state, lb=[1, 2], ub=[0, 1]) @test_nowarn CTModels.constraint!(ocp, :state, lb=[0, 1], ub=[1, 2]) end @@ -114,7 +114,7 @@ function test_name_conflicts_integration() ocp2 = CTModels.PreModel() CTModels.time!(ocp2, t0=0, tf=1, time_name="t") CTModels.state!(ocp2, 1, "α") - @test_throws CTModels.Exceptions.IncorrectArgument CTModels.control!(ocp2, 1, "α") + @test_throws Exceptions.IncorrectArgument CTModels.control!(ocp2, 1, "α") end @testset "Edge cases with bounds" begin @@ -195,14 +195,14 @@ function test_name_conflicts_integration() # State component named "u" should conflict with control name "u" CTModels.state!(ocp, 3, "x", ["x₁", "u", "x₃"]) - @test_throws CTModels.Exceptions.IncorrectArgument CTModels.control!(ocp, 1, "u") + @test_throws Exceptions.IncorrectArgument CTModels.control!(ocp, 1, "u") # Test with fresh ocp: control component named "v" should conflict with variable name "v" ocp2 = CTModels.PreModel() CTModels.time!(ocp2, t0=0, tf=1, time_name="t") CTModels.state!(ocp2, 2, "x", ["x₁", "x₂"]) CTModels.control!(ocp2, 2, "w", ["w₁", "v"]) - @test_throws CTModels.Exceptions.IncorrectArgument CTModels.variable!(ocp2, 1, "v") + @test_throws Exceptions.IncorrectArgument CTModels.variable!(ocp2, 1, "v") end @testset "Empty variable edge cases" begin @@ -226,8 +226,8 @@ function test_name_conflicts_integration() @testset "Time bounds validation" begin # Test t0 < tf validation ocp = CTModels.PreModel() - @test_throws CTModels.Exceptions.IncorrectArgument CTModels.time!(ocp, t0=10, tf=5, time_name="t") - @test_throws CTModels.Exceptions.IncorrectArgument CTModels.time!(ocp, t0=5, tf=5, time_name="t") # Equal not allowed + @test_throws Exceptions.IncorrectArgument CTModels.time!(ocp, t0=10, tf=5, time_name="t") + @test_throws Exceptions.IncorrectArgument CTModels.time!(ocp, t0=5, tf=5, time_name="t") # Equal not allowed @test_nowarn CTModels.time!(ocp, t0=0, tf=10, time_name="t") # Valid end end diff --git a/test/suite/validation/test_name_validation.jl b/test/suite/ocp/test_name_validation.jl similarity index 77% rename from test/suite/validation/test_name_validation.jl rename to test/suite/ocp/test_name_validation.jl index 0564286d..f2b67e26 100644 --- a/test/suite/validation/test_name_validation.jl +++ b/test/suite/ocp/test_name_validation.jl @@ -1,7 +1,7 @@ module TestNameValidation using Test -using CTBase +using CTBase: CTBase, Exceptions using CTModels # Get test options if available, otherwise use defaults @@ -98,15 +98,15 @@ function test_name_validation() @testset "__validate_name_uniqueness" begin # Valid case - empty model ocp = CTModels.PreModel() - @test_throws CTModels.Exceptions.IncorrectArgument CTModels.OCP.__validate_name_uniqueness(ocp, "", ["x"], :state) + @test_throws Exceptions.IncorrectArgument CTModels.OCP.__validate_name_uniqueness(ocp, "", ["x"], :state) # Empty component ocp = CTModels.PreModel() - @test_throws CTModels.Exceptions.IncorrectArgument CTModels.OCP.__validate_name_uniqueness(ocp, "x", [""], :state) + @test_throws Exceptions.IncorrectArgument CTModels.OCP.__validate_name_uniqueness(ocp, "x", [""], :state) # Name in components (multiple components) - should fail ocp = CTModels.PreModel() - @test_throws CTModels.Exceptions.IncorrectArgument CTModels.OCP.__validate_name_uniqueness(ocp, "x", ["x", "y"], :state) + @test_throws Exceptions.IncorrectArgument CTModels.OCP.__validate_name_uniqueness(ocp, "x", ["x", "y"], :state) # Name == component (single component) - should PASS (default behavior) ocp = CTModels.PreModel() @@ -114,13 +114,13 @@ function test_name_validation() # Duplicate components ocp = CTModels.PreModel() - @test_throws CTModels.Exceptions.IncorrectArgument CTModels.OCP.__validate_name_uniqueness(ocp, "x", ["y", "y"], :state) + @test_throws Exceptions.IncorrectArgument CTModels.OCP.__validate_name_uniqueness(ocp, "x", ["y", "y"], :state) # Error: conflict with existing names ocp = CTModels.PreModel() CTModels.control!(ocp, 1, "u") - @test_throws CTModels.Exceptions.IncorrectArgument CTModels.OCP.__validate_name_uniqueness(ocp, "u", ["x₁"], :state) # name conflicts - @test_throws CTModels.Exceptions.IncorrectArgument CTModels.OCP.__validate_name_uniqueness(ocp, "x", ["u"], :state) # component conflicts + @test_throws Exceptions.IncorrectArgument CTModels.OCP.__validate_name_uniqueness(ocp, "u", ["x₁"], :state) # name conflicts + @test_throws Exceptions.IncorrectArgument CTModels.OCP.__validate_name_uniqueness(ocp, "x", ["u"], :state) # component conflicts # Complex scenario - all components set ocp = CTModels.PreModel() @@ -130,12 +130,12 @@ function test_name_validation() CTModels.variable!(ocp, 1, "v") # All these should throw - @test_throws CTModels.Exceptions.IncorrectArgument CTModels.OCP.__validate_name_uniqueness(ocp, "t", ["y₁"], :state) # conflicts with time - @test_throws CTModels.Exceptions.IncorrectArgument CTModels.OCP.__validate_name_uniqueness(ocp, "x", ["y₁"], :control) # conflicts with state - @test_throws CTModels.Exceptions.IncorrectArgument CTModels.OCP.__validate_name_uniqueness(ocp, "u", ["y₁"], :variable) # conflicts with control - @test_throws CTModels.Exceptions.IncorrectArgument CTModels.OCP.__validate_name_uniqueness(ocp, "v", ["y₁"], :state) # conflicts with variable - @test_throws CTModels.Exceptions.IncorrectArgument CTModels.OCP.__validate_name_uniqueness(ocp, "x", ["x₁"], :control) # conflicts with state component - @test_throws CTModels.Exceptions.IncorrectArgument CTModels.OCP.__validate_name_uniqueness(ocp, "x₁", ["y"], :control) # conflicts with state component + @test_throws Exceptions.IncorrectArgument CTModels.OCP.__validate_name_uniqueness(ocp, "t", ["y₁"], :state) # conflicts with time + @test_throws Exceptions.IncorrectArgument CTModels.OCP.__validate_name_uniqueness(ocp, "x", ["y₁"], :control) # conflicts with state + @test_throws Exceptions.IncorrectArgument CTModels.OCP.__validate_name_uniqueness(ocp, "u", ["y₁"], :variable) # conflicts with control + @test_throws Exceptions.IncorrectArgument CTModels.OCP.__validate_name_uniqueness(ocp, "v", ["y₁"], :state) # conflicts with variable + @test_throws Exceptions.IncorrectArgument CTModels.OCP.__validate_name_uniqueness(ocp, "x", ["x₁"], :control) # conflicts with state component + @test_throws Exceptions.IncorrectArgument CTModels.OCP.__validate_name_uniqueness(ocp, "x₁", ["y"], :control) # conflicts with state component # Valid case with exclude_component @test_nowarn CTModels.OCP.__validate_name_uniqueness(ocp, "x", ["y₁", "y₂"], :state) # exclude state, no conflicts diff --git a/test/suite/ocp/test_objective.jl b/test/suite/ocp/test_objective.jl index 1d8f4f03..666bc502 100644 --- a/test/suite/ocp/test_objective.jl +++ b/test/suite/ocp/test_objective.jl @@ -1,8 +1,8 @@ module TestOCPObjective using Test +using CTBase: CTBase, Exceptions using CTModels -using CTBase const VERBOSE = isdefined(Main, :TestOptions) ? Main.TestOptions.VERBOSE : true const SHOWTIMING = isdefined(Main, :TestOptions) ? Main.TestOptions.SHOWTIMING : true @@ -82,21 +82,21 @@ function test_objective() CTModels.time!(ocp; t0=0.0, tf=10.0) CTModels.control!(ocp, 1) CTModels.variable!(ocp, 1) - @test_throws CTModels.Exceptions.UnauthorizedCall CTModels.objective!(ocp, :min, mayer=mayer) + @test_throws Exceptions.PreconditionError CTModels.objective!(ocp, :min, mayer=mayer) # control not set ocp = CTModels.PreModel() CTModels.time!(ocp; t0=0.0, tf=10.0) CTModels.state!(ocp, 1) CTModels.variable!(ocp, 1) - @test_throws CTModels.Exceptions.UnauthorizedCall CTModels.objective!(ocp, :min, mayer=mayer) + @test_throws Exceptions.PreconditionError CTModels.objective!(ocp, :min, mayer=mayer) # times not set ocp = CTModels.PreModel() CTModels.state!(ocp, 1) CTModels.control!(ocp, 1) CTModels.variable!(ocp, 1) - @test_throws CTModels.Exceptions.UnauthorizedCall CTModels.objective!(ocp, :min, mayer=mayer) + @test_throws Exceptions.PreconditionError CTModels.objective!(ocp, :min, mayer=mayer) # objective already set ocp = CTModels.PreModel() @@ -105,7 +105,7 @@ function test_objective() CTModels.control!(ocp, 1) CTModels.variable!(ocp, 1) CTModels.objective!(ocp, :min; mayer=mayer) - @test_throws CTModels.Exceptions.UnauthorizedCall CTModels.objective!(ocp, :min, mayer=mayer) + @test_throws Exceptions.PreconditionError CTModels.objective!(ocp, :min, mayer=mayer) # variable set after the objective ocp = CTModels.PreModel() @@ -113,7 +113,7 @@ function test_objective() CTModels.state!(ocp, 1) CTModels.control!(ocp, 1) CTModels.objective!(ocp, :min; mayer=mayer) - @test_throws CTModels.Exceptions.UnauthorizedCall CTModels.variable!(ocp, 1) + @test_throws Exceptions.PreconditionError CTModels.variable!(ocp, 1) # no function given ocp = CTModels.PreModel() @@ -121,7 +121,7 @@ function test_objective() CTModels.state!(ocp, 1) CTModels.control!(ocp, 1) CTModels.variable!(ocp, 1) - @test_throws CTModels.Exceptions.IncorrectArgument CTModels.objective!(ocp, :min) + @test_throws Exceptions.IncorrectArgument CTModels.objective!(ocp, :min) # NEW: Criterion validation tests @testset "objective! - Criterion validation" begin @@ -131,9 +131,9 @@ function test_objective() CTModels.state!(ocp, 1) CTModels.control!(ocp, 1) CTModels.variable!(ocp, 1) - @test_throws CTModels.Exceptions.IncorrectArgument CTModels.objective!(ocp, :invalid, mayer=mayer) - @test_throws CTModels.Exceptions.IncorrectArgument CTModels.objective!(ocp, :optimize, mayer=mayer) - @test_throws CTModels.Exceptions.IncorrectArgument CTModels.objective!(ocp, :Minimize, mayer=mayer) # not in accepted list + @test_throws Exceptions.IncorrectArgument CTModels.objective!(ocp, :invalid, mayer=mayer) + @test_throws Exceptions.IncorrectArgument CTModels.objective!(ocp, :optimize, mayer=mayer) + @test_throws Exceptions.IncorrectArgument CTModels.objective!(ocp, :Minimize, mayer=mayer) # not in accepted list # Valid criteria (lowercase) ocp2 = CTModels.PreModel() diff --git a/test/suite/ocp/test_state.jl b/test/suite/ocp/test_state.jl index 71712653..d0d9bf02 100644 --- a/test/suite/ocp/test_state.jl +++ b/test/suite/ocp/test_state.jl @@ -1,7 +1,7 @@ module TestOCPState using Test -using CTBase +using CTBase: CTBase, Exceptions using CTModels const VERBOSE = isdefined(Main, :TestOptions) ? Main.TestOptions.VERBOSE : true const SHOWTIMING = isdefined(Main, :TestOptions) ? Main.TestOptions.SHOWTIMING : true @@ -19,7 +19,7 @@ function test_state() # state! ocp = CTModels.PreModel() - @test_throws CTModels.Exceptions.IncorrectArgument CTModels.state!(ocp, 0) + @test_throws Exceptions.IncorrectArgument CTModels.state!(ocp, 0) ocp = CTModels.PreModel() CTModels.state!(ocp, 1) @@ -60,25 +60,25 @@ function test_state() # set twice ocp = CTModels.PreModel() CTModels.state!(ocp, 1) - @test_throws CTModels.Exceptions.UnauthorizedCall CTModels.state!(ocp, 1) + @test_throws Exceptions.PreconditionError CTModels.state!(ocp, 1) # wrong number of components ocp = CTModels.PreModel() - @test_throws CTModels.Exceptions.IncorrectArgument CTModels.state!(ocp, 2, "y", ["u"]) + @test_throws Exceptions.IncorrectArgument CTModels.state!(ocp, 2, "y", ["u"]) # NEW: Internal name validation tests @testset "state! - Internal name validation" begin # Empty name ocp = CTModels.PreModel() - @test_throws CTModels.Exceptions.IncorrectArgument CTModels.state!(ocp, 1, "") + @test_throws Exceptions.IncorrectArgument CTModels.state!(ocp, 1, "") # Empty component name ocp = CTModels.PreModel() - @test_throws CTModels.Exceptions.IncorrectArgument CTModels.state!(ocp, 2, "x", ["", "y"]) + @test_throws Exceptions.IncorrectArgument CTModels.state!(ocp, 2, "x", ["", "y"]) # Name in components (multiple components) - should fail ocp = CTModels.PreModel() - @test_throws CTModels.Exceptions.IncorrectArgument CTModels.state!(ocp, 2, "x", ["x", "y"]) + @test_throws Exceptions.IncorrectArgument CTModels.state!(ocp, 2, "x", ["x", "y"]) # Name == component (single) - should PASS (default behavior) ocp = CTModels.PreModel() @@ -86,7 +86,7 @@ function test_state() # Duplicate components ocp = CTModels.PreModel() - @test_throws CTModels.Exceptions.IncorrectArgument CTModels.state!(ocp, 2, "x", ["y", "y"]) + @test_throws Exceptions.IncorrectArgument CTModels.state!(ocp, 2, "x", ["y", "y"]) end # NEW: Inter-component conflicts tests @@ -94,32 +94,32 @@ function test_state() # state.name vs control.name ocp = CTModels.PreModel() CTModels.control!(ocp, 1, "u") - @test_throws CTModels.Exceptions.IncorrectArgument CTModels.state!(ocp, 1, "u") # Conflict! + @test_throws Exceptions.IncorrectArgument CTModels.state!(ocp, 1, "u") # Conflict! # state.component vs control.name ocp = CTModels.PreModel() CTModels.control!(ocp, 1, "u") - @test_throws CTModels.Exceptions.IncorrectArgument CTModels.state!(ocp, 2, "x", ["u", "v"]) + @test_throws Exceptions.IncorrectArgument CTModels.state!(ocp, 2, "x", ["u", "v"]) # state.name vs time_name ocp = CTModels.PreModel() CTModels.time!(ocp, t0=0, tf=1, time_name="t") - @test_throws CTModels.Exceptions.IncorrectArgument CTModels.state!(ocp, 1, "t") + @test_throws Exceptions.IncorrectArgument CTModels.state!(ocp, 1, "t") # state.component vs time_name ocp = CTModels.PreModel() CTModels.time!(ocp, t0=0, tf=1, time_name="t") - @test_throws CTModels.Exceptions.IncorrectArgument CTModels.state!(ocp, 2, "x", ["t", "y"]) + @test_throws Exceptions.IncorrectArgument CTModels.state!(ocp, 2, "x", ["t", "y"]) # state.name vs variable.name ocp = CTModels.PreModel() CTModels.variable!(ocp, 1, "v") - @test_throws CTModels.Exceptions.IncorrectArgument CTModels.state!(ocp, 1, "v") + @test_throws Exceptions.IncorrectArgument CTModels.state!(ocp, 1, "v") # state.component vs variable.name ocp = CTModels.PreModel() CTModels.variable!(ocp, 1, "v") - @test_throws CTModels.Exceptions.IncorrectArgument CTModels.state!(ocp, 2, "x", ["v", "y"]) + @test_throws Exceptions.IncorrectArgument CTModels.state!(ocp, 2, "x", ["v", "y"]) end # NEW: Type stability tests diff --git a/test/suite/ocp/test_time_dependence.jl b/test/suite/ocp/test_time_dependence.jl index bf7c2509..9b86d50c 100644 --- a/test/suite/ocp/test_time_dependence.jl +++ b/test/suite/ocp/test_time_dependence.jl @@ -1,7 +1,7 @@ module TestOCPTimeDependence using Test -using CTBase +using CTBase: CTBase, Exceptions using CTModels const VERBOSE = isdefined(Main, :TestOptions) ? Main.TestOptions.VERBOSE : true const SHOWTIMING = isdefined(Main, :TestOptions) ? Main.TestOptions.SHOWTIMING : true @@ -25,7 +25,7 @@ function test_time_dependence() Test.@test CTModels.is_autonomous(ocp) === true # Second call must fail - Test.@test_throws CTModels.Exceptions.UnauthorizedCall CTModels.time_dependence!( + Test.@test_throws Exceptions.PreconditionError CTModels.time_dependence!( ocp; autonomous=false ) end diff --git a/test/suite/ocp/test_times.jl b/test/suite/ocp/test_times.jl index 6fe995f9..7704b5c6 100644 --- a/test/suite/ocp/test_times.jl +++ b/test/suite/ocp/test_times.jl @@ -1,8 +1,8 @@ module TestOCPTimes using Test +using CTBase: CTBase, Exceptions using CTModels -using CTBase const VERBOSE = isdefined(Main, :TestOptions) ? Main.TestOptions.VERBOSE : true const SHOWTIMING = isdefined(Main, :TestOptions) ? Main.TestOptions.SHOWTIMING : true @@ -29,7 +29,7 @@ function test_times() time = CTModels.FreeTimeModel(1, "s") @test CTModels.index(time) == 1 @test CTModels.name(time) == "s" - @test_throws CTModels.Exceptions.IncorrectArgument CTModels.time(time, Float64[]) + @test_throws Exceptions.IncorrectArgument CTModels.time(time, Float64[]) # some checks ocp = CTModels.PreModel() @@ -80,67 +80,67 @@ function test_times() # set twice ocp = CTModels.PreModel() CTModels.time!(ocp; t0=0.0, tf=10.0) - @test_throws CTModels.Exceptions.UnauthorizedCall CTModels.time!(ocp, t0=0.0, tf=10.0) + @test_throws Exceptions.PreconditionError CTModels.time!(ocp, t0=0.0, tf=10.0) # if ind0 or indf is provided, the variable must be set ocp = CTModels.PreModel() - @test_throws CTModels.Exceptions.UnauthorizedCall CTModels.time!(ocp, ind0=1, tf=10.0) - @test_throws CTModels.Exceptions.UnauthorizedCall CTModels.time!(ocp, t0=0.0, indf=1) - @test_throws CTModels.Exceptions.UnauthorizedCall CTModels.time!(ocp, ind0=1, indf=2) + @test_throws Exceptions.PreconditionError CTModels.time!(ocp, ind0=1, tf=10.0) + @test_throws Exceptions.PreconditionError CTModels.time!(ocp, t0=0.0, indf=1) + @test_throws Exceptions.PreconditionError CTModels.time!(ocp, ind0=1, indf=2) # index must satisfy 1 <= index <= q ocp = CTModels.PreModel() CTModels.variable!(ocp, 2) - @test_throws CTModels.Exceptions.IncorrectArgument CTModels.time!(ocp, ind0=0, tf=10.0) - @test_throws CTModels.Exceptions.IncorrectArgument CTModels.time!(ocp, ind0=3, tf=10.0) - @test_throws CTModels.Exceptions.IncorrectArgument CTModels.time!(ocp, t0=0.0, indf=0) - @test_throws CTModels.Exceptions.IncorrectArgument CTModels.time!(ocp, t0=0.0, indf=3) - @test_throws CTModels.Exceptions.IncorrectArgument CTModels.time!(ocp, ind0=0, indf=3) - @test_throws CTModels.Exceptions.IncorrectArgument CTModels.time!(ocp, ind0=3, indf=3) + @test_throws Exceptions.IncorrectArgument CTModels.time!(ocp, ind0=0, tf=10.0) + @test_throws Exceptions.IncorrectArgument CTModels.time!(ocp, ind0=3, tf=10.0) + @test_throws Exceptions.IncorrectArgument CTModels.time!(ocp, t0=0.0, indf=0) + @test_throws Exceptions.IncorrectArgument CTModels.time!(ocp, t0=0.0, indf=3) + @test_throws Exceptions.IncorrectArgument CTModels.time!(ocp, ind0=0, indf=3) + @test_throws Exceptions.IncorrectArgument CTModels.time!(ocp, ind0=3, indf=3) # consistency of function arguments ocp = CTModels.PreModel() CTModels.variable!(ocp, 2) - @test_throws CTModels.Exceptions.IncorrectArgument CTModels.time!(ocp, t0=0.0, ind0=1) - @test_throws CTModels.Exceptions.IncorrectArgument CTModels.time!(ocp, tf=10.0, indf=1) - @test_throws CTModels.Exceptions.IncorrectArgument CTModels.time!(ocp, t0=0.0, tf=10.0, indf=1) + @test_throws Exceptions.IncorrectArgument CTModels.time!(ocp, t0=0.0, ind0=1) + @test_throws Exceptions.IncorrectArgument CTModels.time!(ocp, tf=10.0, indf=1) + @test_throws Exceptions.IncorrectArgument CTModels.time!(ocp, t0=0.0, tf=10.0, indf=1) # NEW: Name validation tests Test.@testset "times: Name validation" verbose = VERBOSE showtiming = SHOWTIMING begin # Empty time_name ocp = CTModels.PreModel() - @test_throws CTModels.Exceptions.IncorrectArgument CTModels.time!(ocp, t0=0, tf=1, time_name="") + @test_throws Exceptions.IncorrectArgument CTModels.time!(ocp, t0=0, tf=1, time_name="") # time_name conflicts with state ocp = CTModels.PreModel() CTModels.state!(ocp, 1, "x") - @test_throws CTModels.Exceptions.IncorrectArgument CTModels.time!(ocp, t0=0, tf=1, time_name="x") + @test_throws Exceptions.IncorrectArgument CTModels.time!(ocp, t0=0, tf=1, time_name="x") # time_name conflicts with control ocp = CTModels.PreModel() CTModels.control!(ocp, 1, "u") - @test_throws CTModels.Exceptions.IncorrectArgument CTModels.time!(ocp, t0=0, tf=1, time_name="u") + @test_throws Exceptions.IncorrectArgument CTModels.time!(ocp, t0=0, tf=1, time_name="u") # time_name conflicts with variable ocp = CTModels.PreModel() CTModels.variable!(ocp, 1, "v") - @test_throws CTModels.Exceptions.IncorrectArgument CTModels.time!(ocp, t0=0, tf=1, time_name="v") + @test_throws Exceptions.IncorrectArgument CTModels.time!(ocp, t0=0, tf=1, time_name="v") # time_name conflicts with state component ocp = CTModels.PreModel() CTModels.state!(ocp, 2, "x", ["x₁", "x₂"]) - @test_throws CTModels.Exceptions.IncorrectArgument CTModels.time!(ocp, t0=0, tf=1, time_name="x₁") + @test_throws Exceptions.IncorrectArgument CTModels.time!(ocp, t0=0, tf=1, time_name="x₁") end # NEW: Temporal validation tests Test.@testset "times: Temporal validation" verbose = VERBOSE showtiming = SHOWTIMING begin # t0 > tf ocp = CTModels.PreModel() - @test_throws CTModels.Exceptions.IncorrectArgument CTModels.time!(ocp, t0=1.0, tf=0.0) + @test_throws Exceptions.IncorrectArgument CTModels.time!(ocp, t0=1.0, tf=0.0) # t0 = tf ocp = CTModels.PreModel() - @test_throws CTModels.Exceptions.IncorrectArgument CTModels.time!(ocp, t0=1.0, tf=1.0) + @test_throws Exceptions.IncorrectArgument CTModels.time!(ocp, t0=1.0, tf=1.0) # Valid: t0 < tf ocp = CTModels.PreModel() @@ -158,7 +158,7 @@ function test_times() @test CTModels.time(ft, v_ok) == 3.0 v_short = FakeTimeVector([1.0]) - @test_throws CTModels.Exceptions.IncorrectArgument CTModels.time(ft, v_short) + @test_throws Exceptions.IncorrectArgument CTModels.time(ft, v_short) end Test.@testset "times: TimesModel names and flags" verbose = VERBOSE showtiming = SHOWTIMING begin diff --git a/test/suite/ocp/test_variable.jl b/test/suite/ocp/test_variable.jl index ca084816..59251de1 100644 --- a/test/suite/ocp/test_variable.jl +++ b/test/suite/ocp/test_variable.jl @@ -1,7 +1,7 @@ module TestOCPVariable using Test -using CTBase +using CTBase: CTBase, Exceptions using CTModels const VERBOSE = isdefined(Main, :TestOptions) ? Main.TestOptions.VERBOSE : true const SHOWTIMING = isdefined(Main, :TestOptions) ? Main.TestOptions.SHOWTIMING : true @@ -63,25 +63,25 @@ function test_variable() # set twice ocp = CTModels.PreModel() CTModels.variable!(ocp, 1) - @test_throws CTModels.Exceptions.UnauthorizedCall CTModels.variable!(ocp, 1) + @test_throws Exceptions.PreconditionError CTModels.variable!(ocp, 1) # wrong number of components ocp = CTModels.PreModel() - @test_throws CTModels.Exceptions.IncorrectArgument CTModels.variable!(ocp, 2, "w", ["a"]) + @test_throws Exceptions.IncorrectArgument CTModels.variable!(ocp, 2, "w", ["a"]) # NEW: Internal name validation tests (only for q > 0) @testset "variable! - Internal name validation" begin # Empty name (q > 0) ocp = CTModels.PreModel() - @test_throws CTModels.Exceptions.IncorrectArgument CTModels.variable!(ocp, 1, "") + @test_throws Exceptions.IncorrectArgument CTModels.variable!(ocp, 1, "") # Empty component name (q > 0) ocp = CTModels.PreModel() - @test_throws CTModels.Exceptions.IncorrectArgument CTModels.variable!(ocp, 2, "v", ["", "w"]) + @test_throws Exceptions.IncorrectArgument CTModels.variable!(ocp, 2, "v", ["", "w"]) # Name in components (multiple) - should fail ocp = CTModels.PreModel() - @test_throws CTModels.Exceptions.IncorrectArgument CTModels.variable!(ocp, 2, "v", ["v", "w"]) + @test_throws Exceptions.IncorrectArgument CTModels.variable!(ocp, 2, "v", ["v", "w"]) # Name == component (single) - should PASS (default behavior) ocp = CTModels.PreModel() @@ -89,7 +89,7 @@ function test_variable() # Duplicate components (q > 0) ocp = CTModels.PreModel() - @test_throws CTModels.Exceptions.IncorrectArgument CTModels.variable!(ocp, 2, "v", ["w", "w"]) + @test_throws Exceptions.IncorrectArgument CTModels.variable!(ocp, 2, "v", ["w", "w"]) # Empty variable (q = 0) should not trigger name validation ocp = CTModels.PreModel() @@ -101,37 +101,37 @@ function test_variable() # variable.name vs state.name ocp = CTModels.PreModel() CTModels.state!(ocp, 2, "x", ["x₁", "x₂"]) - @test_throws CTModels.Exceptions.IncorrectArgument CTModels.variable!(ocp, 1, "x") # Conflict! + @test_throws Exceptions.IncorrectArgument CTModels.variable!(ocp, 1, "x") # Conflict! # variable.name vs state.component ocp = CTModels.PreModel() CTModels.state!(ocp, 2, "x", ["v", "w"]) - @test_throws CTModels.Exceptions.IncorrectArgument CTModels.variable!(ocp, 1, "v") + @test_throws Exceptions.IncorrectArgument CTModels.variable!(ocp, 1, "v") # variable.component vs state.name ocp = CTModels.PreModel() CTModels.state!(ocp, 1, "x") - @test_throws CTModels.Exceptions.IncorrectArgument CTModels.variable!(ocp, 2, "v", ["x", "w"]) + @test_throws Exceptions.IncorrectArgument CTModels.variable!(ocp, 2, "v", ["x", "w"]) # variable.name vs control.name ocp = CTModels.PreModel() CTModels.control!(ocp, 1, "u") - @test_throws CTModels.Exceptions.IncorrectArgument CTModels.variable!(ocp, 1, "u") + @test_throws Exceptions.IncorrectArgument CTModels.variable!(ocp, 1, "u") # variable.component vs control.name ocp = CTModels.PreModel() CTModels.control!(ocp, 1, "u") - @test_throws CTModels.Exceptions.IncorrectArgument CTModels.variable!(ocp, 2, "v", ["u", "w"]) + @test_throws Exceptions.IncorrectArgument CTModels.variable!(ocp, 2, "v", ["u", "w"]) # variable.name vs time_name ocp = CTModels.PreModel() CTModels.time!(ocp, t0=0, tf=1, time_name="t") - @test_throws CTModels.Exceptions.IncorrectArgument CTModels.variable!(ocp, 1, "t") + @test_throws Exceptions.IncorrectArgument CTModels.variable!(ocp, 1, "t") # variable.component vs time_name ocp = CTModels.PreModel() CTModels.time!(ocp, t0=0, tf=1, time_name="t") - @test_throws CTModels.Exceptions.IncorrectArgument CTModels.variable!(ocp, 2, "v", ["t", "w"]) + @test_throws Exceptions.IncorrectArgument CTModels.variable!(ocp, 2, "v", ["t", "w"]) # Empty variable (q = 0) should not trigger inter-component conflicts ocp = CTModels.PreModel() diff --git a/test/suite/serialization/test_ext_exceptions.jl b/test/suite/serialization/test_ext_exceptions.jl index 0755f216..4bd7f460 100644 --- a/test/suite/serialization/test_ext_exceptions.jl +++ b/test/suite/serialization/test_ext_exceptions.jl @@ -1,8 +1,8 @@ module TestExtExceptions using Test +using CTBase: CTBase, Exceptions using CTModels -using CTBase using Main.TestProblems const VERBOSE = isdefined(Main, :TestOptions) ? Main.TestOptions.VERBOSE : true const SHOWTIMING = isdefined(Main, :TestOptions) ? Main.TestOptions.SHOWTIMING : true @@ -12,8 +12,9 @@ const SHOWTIMING = isdefined(Main, :TestOptions) ? Main.TestOptions.SHOWTIMING : struct DummyJLD2Tag <: CTModels.AbstractTag end struct DummyJSON3Tag <: CTModels.AbstractTag end -# Dummy solution type for testing plot stub +# Dummy solution and model types for testing serialization stubs struct DummyAbstractSolution <: CTModels.AbstractSolution end +struct DummyAbstractModel <: CTModels.AbstractModel end function test_ext_exceptions() Test.@testset "Extension Exceptions" verbose = VERBOSE showtiming = SHOWTIMING begin @@ -23,14 +24,43 @@ function test_ext_exceptions() # Test IncorrectArgument for unknown format # ============================================================================ Test.@testset "IncorrectArgument for unknown format" begin - Test.@test_throws CTModels.Exceptions.IncorrectArgument CTModels.export_ocp_solution( + Test.@test_throws Exceptions.IncorrectArgument CTModels.export_ocp_solution( sol; format=:dummy ) - Test.@test_throws CTModels.Exceptions.IncorrectArgument CTModels.import_ocp_solution( + Test.@test_throws Exceptions.IncorrectArgument CTModels.import_ocp_solution( ocp; format=:dummy ) end + # ============================================================================ + # Test ExtensionError for real tags (JLD2Tag and JSON3Tag) with dummy types + # These stubs throw ExtensionError when extensions are not loaded + # We use dummy types to ensure we're testing the stubs, not extension overrides + # ============================================================================ + Test.@testset "ExtensionError for JLD2Tag export/import" begin + dummy_sol = DummyAbstractSolution() + dummy_ocp = DummyAbstractModel() + + Test.@test_throws Exceptions.ExtensionError CTModels.export_ocp_solution( + CTModels.JLD2Tag(), dummy_sol; filename="test" + ) + Test.@test_throws Exceptions.ExtensionError CTModels.import_ocp_solution( + CTModels.JLD2Tag(), dummy_ocp; filename="test" + ) + end + + Test.@testset "ExtensionError for JSON3Tag export/import" begin + dummy_sol = DummyAbstractSolution() + dummy_ocp = DummyAbstractModel() + + Test.@test_throws Exceptions.ExtensionError CTModels.export_ocp_solution( + CTModels.JSON3Tag(), dummy_sol; filename="test" + ) + Test.@test_throws Exceptions.ExtensionError CTModels.import_ocp_solution( + CTModels.JSON3Tag(), dummy_ocp; filename="test" + ) + end + # ============================================================================ # Test stub dispatch for export/import (using dummy tags) # The stubs for JLD2Tag and JSON3Tag are in CTModels.jl but become no-ops @@ -38,7 +68,7 @@ function test_ext_exceptions() # tag types that will call the stub fallback. # ============================================================================ Test.@testset "Stub dispatch for export_ocp_solution" begin - # Test that calling with our dummy tag triggers ExtensionError + # Test that calling with our dummy tag triggers MethodError # Note: The actual stubs are defined for JLD2Tag/JSON3Tag, # but method dispatch should fail for unknown tag types Test.@test_throws MethodError CTModels.export_ocp_solution( @@ -67,7 +97,7 @@ function test_ext_exceptions() Test.@testset "Plot method signature errors" begin # Test that calling plot with a dummy AbstractSolution subtype uses the stub # The stub should throw IncorrectArgument since Plots extension is not loaded - Test.@test_throws CTModels.Exceptions.IncorrectArgument CTModels.plot(DummyAbstractSolution()) + Test.@test_throws Exceptions.IncorrectArgument CTModels.plot(DummyAbstractSolution()) end # ============================================================================ diff --git a/test/suite/utils/test_macros.jl b/test/suite/utils/test_macros.jl index 2531422d..3d1888f8 100644 --- a/test/suite/utils/test_macros.jl +++ b/test/suite/utils/test_macros.jl @@ -74,7 +74,7 @@ function test_macros() catch e Test.@test e isa CTBase.IncorrectArgument # CTBase.IncorrectArgument stores the message in var field - Test.@test e.var == "x must be positive" + Test.@test e.msg == "x must be positive" end end