From b856c6ff96007998c1311f4553fd0554a6645cdd Mon Sep 17 00:00:00 2001 From: Olivier Cots Date: Tue, 13 Oct 2026 15:42:37 +0200 Subject: [PATCH 1/2] feat!: stop exporting the module name CTLie, require CTLie 0.3 (#966) CTLie 0.3 made the @Lie expansion hygienic (CTLie#40), so the module no longer has to be in scope at the call site. Tests check that @Lie works after 'import OptimalControl' and 'using OptimalControl: @Lie' only. Co-Authored-By: Claude Opus 5.5 --- Project.toml | 2 +- docs/Project.toml | 2 +- docs/src/assets/Project.toml | 2 +- src/imports/ctlie.jl | 5 +-- test/suite/indirect/test_shooting_sweep.jl | 6 --- test/suite/reexport/test_ctlie.jl | 50 +++++++++++++++++++--- 6 files changed, 49 insertions(+), 18 deletions(-) diff --git a/Project.toml b/Project.toml index ad776d915..2dfb9e872 100644 --- a/Project.toml +++ b/Project.toml @@ -27,7 +27,7 @@ BenchmarkTools = "1" CTBase = "0.30" CTDirect = "1" CTFlows = "0.18" -CTLie = "0.2" +CTLie = "0.3" CTModels = "0.19" CTParser = "0.9" CTSolvers = "0.5" diff --git a/docs/Project.toml b/docs/Project.toml index 30d7aef81..ded6e56e2 100644 --- a/docs/Project.toml +++ b/docs/Project.toml @@ -40,7 +40,7 @@ ADNLPModels = "0.8" CTBase = "0.30" CTDirect = "1" CTFlows = "0.18" -CTLie = "0.2" +CTLie = "0.3" CTModels = "0.19" CTParser = "0.9" CTSolvers = "0.5" diff --git a/docs/src/assets/Project.toml b/docs/src/assets/Project.toml index 30d7aef81..ded6e56e2 100644 --- a/docs/src/assets/Project.toml +++ b/docs/src/assets/Project.toml @@ -40,7 +40,7 @@ ADNLPModels = "0.8" CTBase = "0.30" CTDirect = "1" CTFlows = "0.18" -CTLie = "0.2" +CTLie = "0.3" CTModels = "0.19" CTParser = "0.9" CTSolvers = "0.5" diff --git a/src/imports/ctlie.jl b/src/imports/ctlie.jl index e548bdf9f..b3c02b809 100644 --- a/src/imports/ctlie.jl +++ b/src/imports/ctlie.jl @@ -8,9 +8,8 @@ # CTBase's `CTBaseDifferentiationInterface` extension to be *armed*. # That is what `imports/ad.jl` is for. -# Generated code — the `@Lie` macro emits `CTLie.*` and `CTBase.Traits.*` -# prefixes in its expansion, so both modules must be in scope at the call site. -@reexport import CTLie: CTLie # for generated code (prefix) +# The module name `CTLie` is not exported: since CTLie 0.3 the expansion of `@Lie` holds the +# modules themselves, not their names, so it works from any caller (#966). # Differential geometry @reexport import CTLie: ad, Lift, Poisson, ∂ₜ, @Lie diff --git a/test/suite/indirect/test_shooting_sweep.jl b/test/suite/indirect/test_shooting_sweep.jl index ae70ef3dd..f44502fff 100644 --- a/test/suite/indirect/test_shooting_sweep.jl +++ b/test/suite/indirect/test_shooting_sweep.jl @@ -32,12 +32,6 @@ using NonlinearSolve: NonlinearProblem, SimpleNewtonRaphson, solve import LinearAlgebra: norm import OrdinaryDiffEqTsit5: OrdinaryDiffEqTsit5 # `Flow` needs an integrator -# `@Lie` (used by the Goddard shoot_builder) expands to bare `CTLie.*` / -# `CTBase.Traits.*` prefixes — both modules must be in scope here too, for the -# same reason `test/problems/goddard.jl` needs them via `TestProblems`' own -# `using OptimalControl`. -import CTLie: CTLie -import CTBase: CTBase include(joinpath(@__DIR__, "..", "..", "problems", "TestProblems.jl")) using .TestProblems diff --git a/test/suite/reexport/test_ctlie.jl b/test/suite/reexport/test_ctlie.jl index e251a476f..4625b3934 100644 --- a/test/suite/reexport/test_ctlie.jl +++ b/test/suite/reexport/test_ctlie.jl @@ -36,15 +36,53 @@ const CurrentModule = TestCtlie const LIE_X1 = VectorField(x -> [x[2], -x[1]]) const LIE_X2 = VectorField(x -> [x[1], x[2]]) +# `@Lie` called from modules that have neither `CTLie` nor `CTBase` in scope (#966). The +# expansion holds the modules themselves since CTLie 0.3, so it needs no name at the call site. +module LieImportOnly +import OptimalControl +X(x) = [x[2], -x[1]] +Y(x) = [x[1], x[2]] +Xt(t, x) = [t + x[2], -x[1]] +Yt(t, x) = [x[1], t * x[2]] +const H1 = OptimalControl.Hamiltonian((x, p) -> x[1] * p[1]) +const H2 = OptimalControl.Hamiltonian((x, p) -> x[2] * p[2]) +bracket() = OptimalControl.@Lie [X, Y] +bracket_nonautonomous() = OptimalControl.@Lie [Xt, Yt] is_autonomous = false +poisson() = OptimalControl.@Lie {H1, H2} +end + +module LieMacroOnly +using OptimalControl: @Lie +X(x) = [x[2], -x[1]] +Y(x) = [x[1], x[2]] +bracket() = @Lie [X, Y] +end + function test_ctlie() Test.@testset "CTLie reexports" verbose = VERBOSE showtiming = SHOWTIMING begin - Test.@testset "Generated Code Prefix" begin - # `@Lie` emits both `CTLie.*` and `CTBase.Traits.*` in its expansion, - # so both modules must reach the call site. + Test.@testset "Module name not exported" begin + # OptimalControl uses CTLie internally, but no longer exports its name (#966). Test.@test isdefined(OptimalControl, :CTLie) - Test.@test isdefined(CurrentModule, :CTLie) - Test.@test CTLie isa Module - Test.@test isdefined(CurrentModule, :CTBase) + Test.@test !is_exported(OptimalControl, :CTLie) + end + + Test.@testset "@Lie is hygienic" begin + Test.@testset "after import OptimalControl" begin + Test.@test !isdefined(LieImportOnly, :CTLie) + Test.@test !isdefined(LieImportOnly, :CTBase) + y = [1.0, 2.0] + Z = LieImportOnly.bracket() + Test.@test Z isa VectorField + Test.@test Z(y) ≈ ad(VectorField(LieImportOnly.X), VectorField(LieImportOnly.Y))(y) + Test.@test LieImportOnly.bracket_nonautonomous() isa VectorField + Test.@test LieImportOnly.poisson() isa Hamiltonian + end + + Test.@testset "after using OptimalControl: @Lie" begin + Test.@test !isdefined(LieMacroOnly, :CTLie) + Test.@test !isdefined(LieMacroOnly, :CTBase) + Test.@test LieMacroOnly.bracket()([1.0, 2.0]) ≈ LieImportOnly.bracket()([1.0, 2.0]) + end end Test.@testset "Differential geometry" begin From 57e22125d41665efdd77cb5aac25f5db64f2c545 Mon Sep 17 00:00:00 2001 From: Olivier Cots Date: Tue, 13 Oct 2026 15:42:37 +0200 Subject: [PATCH 2/2] docs: drop the section '`using OptimalControl` is needed' of the @Lie page; BREAKING + CHANGELOG Co-Authored-By: Claude Opus 5.5 --- BREAKING.md | 35 ++++++++++++++++++++++++++++++++++ CHANGELOG.md | 14 ++++++++++++++ docs/src/geometry/lie-macro.md | 20 ------------------- 3 files changed, 49 insertions(+), 20 deletions(-) diff --git a/BREAKING.md b/BREAKING.md index 359cfe3da..886f495dd 100644 --- a/BREAKING.md +++ b/BREAKING.md @@ -4,6 +4,7 @@ Chronological, newest first. Each `CHANGELOG.md` entry links back here for the f record; this file never duplicates a release's own compat table, only what a caller must change or be aware of. +- [Breaking changes (unreleased)](#breaking-changes-unreleased) - [Non-breaking notes (2.2.1-beta)](#non-breaking-notes-221-beta) - [Non-breaking notes (2.2.0-beta)](#non-breaking-notes-220-beta) - [Breaking changes: v2.0 → v2.1.0-beta](#breaking-changes-v20--v210-beta) @@ -11,6 +12,40 @@ change or be aware of. --- +## Breaking changes (unreleased) + +### The module name `CTLie` is no longer exported + +`using OptimalControl` used to bring the module `CTLie` into scope, only so that the code +generated by `@Lie` could name it. CTLie 0.3 made `@Lie` hygienic, so the export is gone +([#966](https://github.com/control-toolbox/OptimalControl.jl/issues/966)). The functions +and the macro (`ad`, `Lift`, `Poisson`, `∂ₜ`, `@Lie`, `dg_ad_backend`, `dg_ad_backend!`) are +still exported, and `@Lie` now works after `import OptimalControl` or +`using OptimalControl: @Lie` as well. + +**Migration:** only code that wrote `CTLie.…` after `using OptimalControl` is affected. +Load the module yourself, or qualify it through OptimalControl: + +```julia +# Before +using OptimalControl +CTLie.LiftedHamiltonianFunction + +# After +using OptimalControl +using CTLie: CTLie # CTLie in your environment +CTLie.LiftedHamiltonianFunction +# or, without adding CTLie to your environment +OptimalControl.CTLie.LiftedHamiltonianFunction +``` + +### CTLie 0.3: some guard errors are `IncorrectArgument` + +Through the CTLie bump: `ad` and `Lift` on a `HamiltonianVectorField`, and `ad` and `∂ₜ` on +an in-place vector field, now throw `IncorrectArgument` instead of `NotImplemented`. Only code +that catches these errors by type is affected; see the +[CTLie BREAKING notes](https://github.com/control-toolbox/CTLie.jl/blob/main/BREAKING.md). + ## Non-breaking notes (2.2.1-beta) Documentation infrastructure only. **Nothing to migrate** — OptimalControl's API and runtime diff --git a/CHANGELOG.md b/CHANGELOG.md index d62669594..0bf648198 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,20 @@ Versions follow [Semantic Versioning](https://semver.org/spec/v2.0.0.html). --- +## [Unreleased] + +### ⚠️ Breaking + +- **`using OptimalControl` no longer brings the module name `CTLie` into scope** ([#966](https://github.com/control-toolbox/OptimalControl.jl/issues/966)). It was exported only because the expansion of `@Lie` named `CTLie` and `CTBase` in the caller's module. Since CTLie 0.3 the expansion holds the modules themselves ([CTLie#40](https://github.com/control-toolbox/CTLie.jl/issues/40)), so `@Lie` now also works after `import OptimalControl` or `using OptimalControl: @Lie`. `ad`, `Lift`, `Poisson`, `∂ₜ`, `@Lie`, `dg_ad_backend` and `dg_ad_backend!` are still exported. See [BREAKING.md](BREAKING.md). + +### 📚 Documentation + +- The page "The `@Lie` macro" drops its section "`using OptimalControl` is needed". + +### 📦 Dependencies + +- **CTLie `0.3`** (was `0.2`), in the package and the docs environment. + ## [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). diff --git a/docs/src/geometry/lie-macro.md b/docs/src/geometry/lie-macro.md index 906032e8d..c986eb0b4 100644 --- a/docs/src/geometry/lie-macro.md +++ b/docs/src/geometry/lie-macro.md @@ -113,26 +113,6 @@ function instead of a Boolean, without an error. nothing # hide ``` -## `using OptimalControl` is needed - -The expansion of `@Lie` refers to the modules `CTLie` and `CTBase` by name, which -`using OptimalControl` brings into scope. With `import OptimalControl` or -`using OptimalControl: @Lie` only, the expanded code fails with an `UndefVarError` -([CTLie#40](https://github.com/control-toolbox/CTLie.jl/issues/40)). - -```@example main -m = Module() # hide -Core.eval(m, :(import OptimalControl)) # hide -err = try # hide - Core.eval(m, :((OptimalControl.@Lie [x -> [x[2], -x[1]], x -> x])([1.0, 2.0]))) # hide - nothing # hide -catch e # hide - e # hide -end # hide -@assert err isa UndefVarError # hide -nothing # hide -``` - ## See also - [Lie derivative and Lie bracket](@ref geometry-ad): `ad`, called by `@Lie [X, Y]`.