From 5cca2303b453ed13ab6e1a11f7a0b3077e6e3fa9 Mon Sep 17 00:00:00 2001 From: Olivier Cots Date: Tue, 13 Oct 2026 15:42:37 +0200 Subject: [PATCH 1/2] docs(initial-guess): check a guess with build_initial_guess; test that the other Init helpers stay unexported (#965) Co-Authored-By: Claude Opus 5.5 --- docs/src/solve/initial-guess.md | 20 +++++++++++++++++++- test/suite/reexport/test_ctmodels.jl | 18 ++++++++++++++++++ 2 files changed, 37 insertions(+), 1 deletion(-) diff --git a/docs/src/solve/initial-guess.md b/docs/src/solve/initial-guess.md index 5d5c9e1cb..bb8228468 100644 --- a/docs/src/solve/initial-guess.md +++ b/docs/src/solve/initial-guess.md @@ -364,7 +364,7 @@ sol = solve(ocp2; init=(state=s -> [-1 + s, 0.0], control=0.1, variable=2.0), di println(iterations(sol), " iterations") ``` -## Inspect a guess +## Inspect or check a guess Whatever you pass as `init`, `solve` turns it into an initial guess with [`build_initial_guess`](@ref)`(ocp, init)`. You can call it yourself, and read the result back @@ -381,6 +381,24 @@ state(ig)(0.5), control(ig)(0.5), variable(ig) The same holds for the output of `@init` itself, which is an initial guess already. +`build_initial_guess` also checks the guess against the problem: a component of the wrong +dimension raises an `IncorrectArgument`. To check a guess before solving, build it: + +```@example main +err = try + build_initial_guess(ocp2, (state=[-1.0, 0.0, 0.0],)) # ocp2 has a 2-D state + nothing +catch e + e +end +err isa IncorrectArgument +``` + +```@example main +@assert err isa IncorrectArgument # hide +nothing # hide +``` + ## Costate and multipliers There is currently no way to seed the costate or the multipliers — only state, control, and diff --git a/test/suite/reexport/test_ctmodels.jl b/test/suite/reexport/test_ctmodels.jl index baea935f5..abfabd71b 100644 --- a/test/suite/reexport/test_ctmodels.jl +++ b/test/suite/reexport/test_ctmodels.jl @@ -161,6 +161,24 @@ function test_ctmodels() end Test.@test reexports(OptimalControl, :build_initial_guess, CTModels.Init) Test.@test isdefined(CurrentModule, :build_initial_guess) + + # Deliberately not exported (#965): `build_initial_guess` already builds and + # validates a guess, `@init` and named tuples cover construction, and + # `state`/`control`/`variable` cover inspection. + for f in ( + :initial_guess, + :pre_initial_guess, + :validate_initial_guess, + :initial_state, + :initial_control, + :initial_variable, + :PreInitialGuess, + ) + Test.@testset "$f not exported" begin + Test.@test !is_exported(OptimalControl, f) + Test.@test !isdefined(CurrentModule, f) + end + end end Test.@testset "Serialization" begin From 793a719baaf092a2c72677bb960d318d576ded90 Mon Sep 17 00:00:00 2001 From: Olivier Cots Date: Tue, 13 Oct 2026 15:42:45 +0200 Subject: [PATCH 2/2] docs(changelog): entry for #965 Co-Authored-By: Claude Opus 5.5 --- CHANGELOG.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index d62669594..c27bc54a7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,12 @@ Versions follow [Semantic Versioning](https://semver.org/spec/v2.0.0.html). --- +## [Unreleased] + +### 📚 Documentation + +- **Initial guess: check a guess before solving with `build_initial_guess`** ([#965](https://github.com/control-toolbox/OptimalControl.jl/issues/965)). It builds and validates the guess against the problem. The other initial-guess helpers of CTModels (`initial_guess`, `pre_initial_guess`, `validate_initial_guess`, `initial_state`, `initial_control`, `initial_variable`, `PreInitialGuess`) stay unexported, and a test now records that choice. + ## [2.2.1-beta] — 2026-09-02 Documentation-infrastructure follow-up to the 2.2.0-beta site rewrite. No change to OptimalControl's API or runtime behaviour; nothing to migrate — see [BREAKING.md](BREAKING.md).