diff --git a/CHANGELOG.md b/CHANGELOG.md index fc594b786..4129f2aed 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,10 @@ 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. + ### 🐛 Bug Fixes - **`init` and `initial_guess` together raise "Conflicting aliases" in descriptive mode too** ([#964](https://github.com/control-toolbox/OptimalControl.jl/issues/964)). Explicit mode already reported the conflict; descriptive mode, the common case, routed the leftover `init` to the strategies and rejected it as an unknown option, with unrelated suggestions. Both modes now run the same check before any routing. diff --git a/docs/src/solve/initial-guess.md b/docs/src/solve/initial-guess.md index 83700d8dc..7c002ace6 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