From df770bca38e91f612ca4f55d656b9a5faddf5db5 Mon Sep 17 00:00:00 2001 From: Olivier Cots Date: Wed, 22 Apr 2026 21:46:22 +0200 Subject: [PATCH 1/2] Clarify :exa backend PreconditionError message - Update error message in get_build_examodel to explain that :exa modeler is unavailable for functional API (macro-free) models - Add regression test suite/test_build_examodel.jl - Suggest alternatives: ADNLP (:adnlp) or @def macro --- src/OCP/Building/model.jl | 10 +-- test/suite/ocp/test_build_examodel.jl | 98 +++++++++++++++++++++++++++ 2 files changed, 103 insertions(+), 5 deletions(-) create mode 100644 test/suite/ocp/test_build_examodel.jl diff --git a/src/OCP/Building/model.jl b/src/OCP/Building/model.jl index f14680e5..ac8a48ab 100644 --- a/src/OCP/Building/model.jl +++ b/src/OCP/Building/model.jl @@ -1387,7 +1387,7 @@ end """ $(TYPEDSIGNATURES) -Return an error (PreconditionError) since the model is not built with the :exa backend. +Fallback method: throws a `PreconditionError` explaining that the `:exa` modeler is not available because the `Model` was assembled through the functional (macro-free) API and therefore carries no Exa builder. """ function get_build_examodel( ::Model{ @@ -1405,10 +1405,10 @@ function get_build_examodel( ) 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", - context="dynamics accessor on unparsed model", + "The :exa modeler is not available for this model"; + reason="this Model was built with the functional (macro-free) API (PreModel + time!/state!/control!/variable!/dynamics!/objective!/constraint! + build), which does not generate the Exa builder required by the Exa (:exa) modeler", + suggestion="either choose another modeler, e.g. ADNLP (:adnlp), or define the optimal control problem with the @def macro so that the Exa builder is generated", + context="get_build_examodel called on a Model built without an Exa builder", ), ) end diff --git a/test/suite/ocp/test_build_examodel.jl b/test/suite/ocp/test_build_examodel.jl new file mode 100644 index 00000000..7e961dcd --- /dev/null +++ b/test/suite/ocp/test_build_examodel.jl @@ -0,0 +1,98 @@ +module TestBuildExamodel + +using Test: Test +import CTBase.Exceptions +using CTModels: CTModels + +const VERBOSE = isdefined(Main, :TestData) ? Main.TestData.VERBOSE : true +const SHOWTIMING = isdefined(Main, :TestData) ? Main.TestData.SHOWTIMING : true + +function test_build_examodel() + Test.@testset "Build Examodel Tests" verbose=VERBOSE showtiming=SHOWTIMING begin + + # ==================================================================== + # UNIT TESTS - Error on functional API model + # ==================================================================== + + Test.@testset "get_build_examodel error on functional API model" begin + # Build a minimal OCP using the functional (macro-free) API + ocp = CTModels.PreModel() + CTModels.time!(ocp; t0=0.0, tf=1.0) + CTModels.state!(ocp, 2) + CTModels.control!(ocp, 1) + + # Simple dynamics function + dynamics!(r, t, x, u, v) = (r[1] = x[2]; r[2] = u[1]) + CTModels.dynamics!(ocp, dynamics!) + + # Simple objective + CTModels.objective!(ocp, :min, mayer=(x0, xf) -> xf[1]^2) + + # Set time dependence (required before build) + CTModels.time_dependence!(ocp, autonomous=true) + + # Build without build_examodel (functional API) + model = CTModels.build(ocp) + + # Attempting to get build_examodel should throw PreconditionError + Test.@test_throws Exceptions.PreconditionError CTModels.get_build_examodel(model) + + # Verify the error message contains the key information + try + CTModels.get_build_examodel(model) + Test.@test false # Should not reach here + catch err + Test.@test err isa Exceptions.PreconditionError + Test.@test occursin(":exa modeler", err.msg) + Test.@test occursin("functional", err.reason) + Test.@test occursin("macro-free", err.reason) + Test.@test occursin(":adnlp", err.suggestion) + Test.@test occursin("@def", err.suggestion) + Test.@test occursin("Exa builder", err.context) + end + end + + # ==================================================================== + # INTEGRATION TESTS - Verify functional API workflow + # ==================================================================== + + Test.@testset "Functional API workflow integration" begin + # Build a complete OCP using functional API + ocp = CTModels.PreModel() + CTModels.time!(ocp; t0=0.0, tf=1.0) + CTModels.state!(ocp, 2) + CTModels.control!(ocp, 1) + CTModels.dynamics!(ocp, (r, t, x, u, v) -> (r[1] = x[2]; r[2] = u[1])) + CTModels.objective!(ocp, :min, mayer=(x0, xf) -> xf[1]^2) + + # Set time dependence (required before build) + CTModels.time_dependence!(ocp, autonomous=true) + + # Build without build_examodel + model = CTModels.build(ocp) + + # Verify model is built but has no Exa builder + Test.@test model isa CTModels.Model + Test.@test model.build_examodel === nothing + + # Verify get_build_examodel throws informative error + try + CTModels.get_build_examodel(model) + Test.@test false # Should not reach here + catch err + Test.@test err isa Exceptions.PreconditionError + Test.@test occursin(":exa modeler", err.msg) + Test.@test occursin("functional", err.reason) + Test.@test occursin("macro-free", err.reason) + Test.@test occursin(":adnlp", err.suggestion) + Test.@test occursin("@def", err.suggestion) + Test.@test occursin("Exa builder", err.context) + end + end + end +end + +end # module + +# CRITICAL: Redefine in outer scope for TestRunner +test_build_examodel() = TestBuildExamodel.test_build_examodel() From 34a6e3f5dd9bc1bbd5190de32c1d50ed09c385ad Mon Sep 17 00:00:00 2001 From: Olivier Cots Date: Wed, 22 Apr 2026 21:50:09 +0200 Subject: [PATCH 2/2] Update CHANGELOG for version 0.10.1 - Add entry for clarified :exa backend error message - Add missing entry for version 0.10.0 release --- CHANGELOG.md | 21 +++++++++++++++++++++ Project.toml | 2 +- 2 files changed, 22 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7ec080cc..3e7ad38c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,27 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [0.10.1] - 2026-04-22 + +### 🚀 Enhancements + +#### Clarified :exa Backend Error Message + +- **Improved error message**: `get_build_examodel` now clearly explains that the Exa (:exa) modeler is unavailable for functional API (macro-free) models +- **Actionable suggestions**: Error message now suggests using ADNLP (:adnlp) or the @def macro from CTParser.jl +- **Better context**: Error reason explains the root cause (functional API does not generate Exa builder) +- **Regression test**: Added `test_build_examodel.jl` to verify error message content + +### 🐛 Bug Fixes + +- Fixed misleading error message that incorrectly suggested "dynamics" instead of "Exa modeler" + +## [0.10.0] - 2026-04-20 + +### 📦 Release + +- Initial stable release version (no breaking changes from 0.9.15-beta) + ## [0.9.15-beta] - 2026-04-18 ### 🚀 Enhancements diff --git a/Project.toml b/Project.toml index 601afe94..18c38451 100644 --- a/Project.toml +++ b/Project.toml @@ -1,6 +1,6 @@ name = "CTModels" uuid = "34c4fa32-2049-4079-8329-de33c2a22e2d" -version = "0.10.0" +version = "0.10.1" authors = ["Olivier Cots "] [deps]