From ee59433850a7d421696b508dc39d1b93fad55b2e Mon Sep 17 00:00:00 2001 From: Olivier Cots Date: Sun, 5 Jul 2026 22:38:30 +0200 Subject: [PATCH] feat: ControlledVectorField and ComposedVectorField (state-space control composition) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit State-space analogues of PseudoHamiltonian / ComposedHamiltonian, for the OpenLoop / ClosedLoop control-flow path. - `Data.ControlledVectorField{F,TD,VD} <: AbstractControlledVectorField{TD,VD}` — a controlled vector field `fc(t,x,u,v)` with an explicit control argument, out-of-place (no mutability trait), `dynamics_trait = StateDynamics`. Natural + uniform (t,x,u,v) call signatures. - `Data.ComposedVectorField{TD,VD,CVF,L} <: AbstractVectorField{TD,VD,OutOfPlace}` — functor `g(t,x,v) = fc(t,x,u(...),v)` composing a controlled vector field with an OpenLoop or ClosedLoop control law. The control is read via the law's feedback- dispatched uniform call (open-loop `u(t,v)`, closed-loop `u(t,x,v)`); composed traits are the join of the two inputs, so fc and law may carry different TD/VD. Rejects DynClosedLoop (that is the Hamiltonian path). Getters `controlled_vector_field`, `control_law`. Reuses `_join_td` / `_join_vd` from the composed-Hamiltonian module. Tests cover all call arities, cross-trait joins, contract (AbstractVectorField / OutOfPlace / StateDynamics), type stability, and the DynClosedLoop rejection. Full suite: 4315/4315. Co-Authored-By: Claude Opus 4.8 --- src/Data/Data.jl | 7 + src/Data/abstract_controlled_vector_field.jl | 93 ++++++++++ src/Data/composed_vector_field.jl | 161 ++++++++++++++++++ src/Data/controlled_vector_field.jl | 156 +++++++++++++++++ test/suite/data/test_composed_vector_field.jl | 105 ++++++++++++ .../data/test_controlled_vector_field.jl | 82 +++++++++ 6 files changed, 604 insertions(+) create mode 100644 src/Data/abstract_controlled_vector_field.jl create mode 100644 src/Data/composed_vector_field.jl create mode 100644 src/Data/controlled_vector_field.jl create mode 100644 test/suite/data/test_composed_vector_field.jl create mode 100644 test/suite/data/test_controlled_vector_field.jl diff --git a/src/Data/Data.jl b/src/Data/Data.jl index bba4e3d2..9444302e 100644 --- a/src/Data/Data.jl +++ b/src/Data/Data.jl @@ -28,6 +28,9 @@ include(joinpath(@__DIR__, "control_law.jl")) include(joinpath(@__DIR__, "abstract_pseudo_hamiltonian.jl")) include(joinpath(@__DIR__, "pseudo_hamiltonian.jl")) include(joinpath(@__DIR__, "composed_hamiltonian.jl")) +include(joinpath(@__DIR__, "abstract_controlled_vector_field.jl")) +include(joinpath(@__DIR__, "controlled_vector_field.jl")) +include(joinpath(@__DIR__, "composed_vector_field.jl")) include(joinpath(@__DIR__, "abstract_hamiltonian_vector_field.jl")) include(joinpath(@__DIR__, "hamiltonian_vector_field.jl")) @@ -51,5 +54,9 @@ export PseudoHamiltonian export ComposedHamiltonian export pseudo_hamiltonian export control_law +export AbstractControlledVectorField +export ControlledVectorField +export ComposedVectorField +export controlled_vector_field end # module Data diff --git a/src/Data/abstract_controlled_vector_field.jl b/src/Data/abstract_controlled_vector_field.jl new file mode 100644 index 00000000..e988aaa9 --- /dev/null +++ b/src/Data/abstract_controlled_vector_field.jl @@ -0,0 +1,93 @@ +# ============================================================================= +# AbstractControlledVectorField — abstract supertype for controlled vector fields +# ============================================================================= + +""" +$(TYPEDEF) + +Abstract supertype for **controlled** vector-field functions together with their +time-dependence and variable-dependence traits. + +A controlled vector field is a function `fc(t, x, u[, v])` returning the state +derivative, with an **explicit control argument** `u`. It is the state-space analogue +of [`CTBase.Data.AbstractPseudoHamiltonian`](@ref): where a pseudo-Hamiltonian carries +the control alongside the costate, a controlled vector field carries the control +alongside the state. It is always **out-of-place** (returns the derivative), so unlike +[`CTBase.Data.AbstractVectorField`](@ref) it has no mutability trait. + +Composing a controlled vector field with an open-loop or closed-loop control law +eliminates the control and yields a plain [`CTBase.Data.ComposedVectorField`](@ref). + +# Type Parameters +- `TD <: TimeDependence`: `Autonomous` or `NonAutonomous`. +- `VD <: VariableDependence`: `Fixed` or `NonFixed`. + +# Notes +- All controlled vector fields support both natural and uniform call signatures. +- The uniform signature `(t, x, u, v)` is used internally by composition. +- The dynamics trait is always `StateDynamics`. + +See also: [`CTBase.Data.ControlledVectorField`](@ref), [`CTBase.Data.ComposedVectorField`](@ref), +[`CTBase.Data.AbstractPseudoHamiltonian`](@ref). +""" +abstract type AbstractControlledVectorField{ + TD<:Traits.TimeDependence,VD<:Traits.VariableDependence +} end + +# ============================================================================= +# Trait accessors +# ============================================================================= + +""" +$(TYPEDSIGNATURES) + +Indicate that all `AbstractControlledVectorField` types support time-dependence queries. + +See also: [`CTBase.Traits.time_dependence`](@ref). +""" +Traits.has_time_dependence_trait(::AbstractControlledVectorField) = true + +""" +$(TYPEDSIGNATURES) + +Indicate that all `AbstractControlledVectorField` types support variable-dependence queries. + +See also: [`CTBase.Traits.variable_dependence`](@ref). +""" +Traits.has_variable_dependence_trait(::AbstractControlledVectorField) = true + +""" +$(TYPEDSIGNATURES) + +Return the time-dependence trait of a controlled vector field. + +See also: [`CTBase.Traits.time_dependence`](@ref). +""" +function Traits.time_dependence( + ::AbstractControlledVectorField{TD,<:Traits.VariableDependence} +) where {TD<:Traits.TimeDependence} + return TD +end + +""" +$(TYPEDSIGNATURES) + +Return the variable-dependence trait of a controlled vector field. + +See also: [`CTBase.Traits.variable_dependence`](@ref). +""" +function Traits.variable_dependence( + ::AbstractControlledVectorField{<:Traits.TimeDependence,VD} +) where {VD<:Traits.VariableDependence} + return VD +end + +""" +$(TYPEDSIGNATURES) + +Return the dynamics trait of an `AbstractControlledVectorField`, namely +[`CTBase.Traits.StateDynamics`](@ref). + +See also: [`CTBase.Traits.dynamics_trait`](@ref). +""" +Traits.dynamics_trait(::AbstractControlledVectorField) = Traits.StateDynamics diff --git a/src/Data/composed_vector_field.jl b/src/Data/composed_vector_field.jl new file mode 100644 index 00000000..23a96a4e --- /dev/null +++ b/src/Data/composed_vector_field.jl @@ -0,0 +1,161 @@ +# ============================================================================= +# ComposedVectorField — vector field obtained by composing a controlled vector +# field with an open-loop or closed-loop control law +# ============================================================================= + +""" +$(TYPEDEF) + +Vector field obtained by eliminating the control from a +[`CTBase.Data.ControlledVectorField`](@ref) with an **open-loop** or **closed-loop** +[`CTBase.Data.ControlLaw`](@ref): + +```math +g(t, x, v) = fc(t, x, u(...), v), +``` + +where the control is `u(t, v)` for an open-loop law and `u(t, x, v)` for a closed-loop +law. It is the state-space analogue of [`CTBase.Data.ComposedHamiltonian`](@ref). + +It is a **functor** (not a closure), out-of-place, and subtypes +[`CTBase.Data.AbstractVectorField`](@ref) with `OutOfPlace` mutability — so it *is* a +vector field usable anywhere one is expected. + +# Type Parameters +- `TD <: TimeDependence`, `VD <: VariableDependence`: the **join** of the controlled + vector field's and the control law's dependences. +- `CVF <: ControlledVectorField`: concrete controlled vector-field type. +- `L <: ControlLaw`: concrete control-law type (must carry `OpenLoopFeedback` or + `ClosedLoopFeedback`). + +# Fields +- `fc::CVF`: the controlled vector field `fc(t, x, u, v)`. +- `law::L`: the open-loop or closed-loop control law. + +See also: [`CTBase.Data.ControlledVectorField`](@ref), [`CTBase.Data.ControlLaw`](@ref), +[`CTBase.Data.OpenLoop`](@ref), [`CTBase.Data.ClosedLoop`](@ref), +[`CTBase.Data.ComposedHamiltonian`](@ref). +""" +struct ComposedVectorField{ + TD<:Traits.TimeDependence, + VD<:Traits.VariableDependence, + CVF<:ControlledVectorField, + L<:ControlLaw{<:Function,<:Union{Traits.OpenLoopFeedback,Traits.ClosedLoopFeedback}}, +} <: AbstractVectorField{TD,VD,Traits.OutOfPlace} + fc::CVF + law::L +end + +# ============================================================================= +# Constructor — composed traits are the join of the two inputs +# ============================================================================= + +""" +$(TYPEDSIGNATURES) + +Construct a [`CTBase.Data.ComposedVectorField`](@ref) from a controlled vector field and +an open-loop or closed-loop control law, computing the composed time/variable +dependences as the join of the two inputs. + +See also: [`CTBase.Data.ComposedVectorField`](@ref). +""" +function ComposedVectorField( + fc::ControlledVectorField, + law::ControlLaw{<:Function,<:Union{Traits.OpenLoopFeedback,Traits.ClosedLoopFeedback}}, +) + TD = _join_td(Traits.time_dependence(fc), Traits.time_dependence(law)) + VD = _join_vd(Traits.variable_dependence(fc), Traits.variable_dependence(law)) + return ComposedVectorField{TD,VD,typeof(fc),typeof(law)}(fc, law) +end + +# ============================================================================= +# Getters +# ============================================================================= + +""" +$(TYPEDSIGNATURES) + +Return the underlying controlled vector field of a [`CTBase.Data.ComposedVectorField`](@ref). + +See also: [`CTBase.Data.ComposedVectorField`](@ref), [`CTBase.Data.control_law`](@ref). +""" +controlled_vector_field(g::ComposedVectorField) = g.fc + +""" +$(TYPEDSIGNATURES) + +Return the control law of a [`CTBase.Data.ComposedVectorField`](@ref). + +See also: [`CTBase.Data.ComposedVectorField`](@ref), [`CTBase.Data.controlled_vector_field`](@ref). +""" +control_law(g::ComposedVectorField) = g.law + +# ============================================================================= +# Core computation and call signatures +# ============================================================================= + +# Evaluate the control from the law — the uniform call arity depends on the feedback: +# open-loop u(t, v) ignores the state, closed-loop u(t, x, v) uses it. +_law_control(law::ControlLaw{<:Function,Traits.OpenLoopFeedback}, t, x, v) = law(t, v) +_law_control(law::ControlLaw{<:Function,Traits.ClosedLoopFeedback}, t, x, v) = law(t, x, v) + +""" +$(TYPEDSIGNATURES) + +Core computation `g(t, x, v) = fc(t, x, u(...), v)` using the uniform call signatures of +the controlled vector field and the control law (open-loop `u(t,v)` or closed-loop +`u(t,x,v)`). + +See also: [`CTBase.Data.ComposedVectorField`](@ref). +""" +function _composed_vf(g::ComposedVectorField, t, x, v) + u = _law_control(g.law, t, x, v) + return g.fc(t, x, u, v) # ControlledVectorField uniform call (t, x, u, v) +end + +# Natural call signatures (OutOfPlace) — one per composed (TD, VD) +function (g::ComposedVectorField{Traits.Autonomous,Traits.Fixed})(x) + _composed_vf(g, 0.0, x, nothing) +end +function (g::ComposedVectorField{Traits.NonAutonomous,Traits.Fixed})(t, x) + _composed_vf(g, t, x, nothing) +end +function (g::ComposedVectorField{Traits.Autonomous,Traits.NonFixed})(x, v) + _composed_vf(g, 0.0, x, v) +end +function (g::ComposedVectorField{Traits.NonAutonomous,Traits.NonFixed})(t, x, v) + return _composed_vf(g, t, x, v) +end + +# Uniform (t, x, v) call — used by VectorFieldSystem. +# (NonAutonomous, NonFixed) is already covered by the natural signature above. +(g::ComposedVectorField{Traits.Autonomous,Traits.Fixed})(t, x, v) = _composed_vf(g, t, x, v) +function (g::ComposedVectorField{Traits.NonAutonomous,Traits.Fixed})(t, x, v) + _composed_vf(g, t, x, v) +end +function (g::ComposedVectorField{Traits.Autonomous,Traits.NonFixed})(t, x, v) + _composed_vf(g, t, x, v) +end + +# ============================================================================= +# Base.show +# ============================================================================= + +""" +$(TYPEDSIGNATURES) + +Display a compact representation of a [`CTBase.Data.ComposedVectorField`](@ref). + +See also: [`CTBase.Data.ComposedVectorField`](@ref). +""" +function Base.show(io::IO, ::ComposedVectorField{TD,VD}) where {TD,VD} + natural = _natural_sig_vf(TD, VD, Traits.OutOfPlace) + uniform = _uniform_sig_vf(Traits.OutOfPlace) + println(io, "ComposedVectorField: $(_td_label(TD)), $(_vd_label(VD)), out-of-place") + println(io, " natural call: ", natural) + return print(io, " uniform call: ", uniform) +end + +function Base.show(io::IO, ::MIME"text/plain", g::ComposedVectorField) + return show(io, g) +end diff --git a/src/Data/controlled_vector_field.jl b/src/Data/controlled_vector_field.jl new file mode 100644 index 00000000..a2facef9 --- /dev/null +++ b/src/Data/controlled_vector_field.jl @@ -0,0 +1,156 @@ +# ============================================================================= +# Concrete ControlledVectorField type +# ============================================================================= + +""" +$(TYPEDEF) + +Parametric container for a **controlled** vector-field function together with its +time-dependence and variable-dependence traits. + +The function returns the state derivative `fc(t, x, u[, v])` with an explicit control +argument `u`, out-of-place. It is the state-space analogue of +[`CTBase.Data.PseudoHamiltonian`](@ref), and composing it with an open-loop or +closed-loop control law gives a [`CTBase.Data.ComposedVectorField`](@ref). + +# Type Parameters +- `F`: concrete type of the wrapped function. +- `TD <: TimeDependence`: `Autonomous` or `NonAutonomous`. +- `VD <: VariableDependence`: `Fixed` or `NonFixed`. + +# Fields +- `f::F`: the controlled vector-field function. + +# Construction +```julia +ControlledVectorField(f; is_autonomous = true, is_variable = false) # default: fc(x, u) +ControlledVectorField((t, x, u) -> ...; is_autonomous = false) # fc(t, x, u) +ControlledVectorField((x, u, v) -> ...; is_variable = true) # fc(x, u, v) +``` + +# Call Signatures + +Callable via its **natural** signature (matching the traits) and via a **uniform** +signature `(t, x, u, v)` that ignores unused arguments. + +| `(TD, VD)` | natural | uniform | +|---|---|---| +| `(Autonomous, Fixed)` | `fc(x, u)` | `fc(t, x, u, v)` | +| `(NonAutonomous, Fixed)` | `fc(t, x, u)` | `fc(t, x, u, v)` | +| `(Autonomous, NonFixed)` | `fc(x, u, v)` | `fc(t, x, u, v)` | +| `(NonAutonomous, NonFixed)` | `fc(t, x, u, v)` | `fc(t, x, u, v)` | + +See also: [`CTBase.Data.AbstractControlledVectorField`](@ref), +[`CTBase.Data.ComposedVectorField`](@ref), [`CTBase.Data.PseudoHamiltonian`](@ref). +""" +struct ControlledVectorField{F<:Function,TD,VD} <: AbstractControlledVectorField{TD,VD} + f::F +end + +# ============================================================================= +# Keyword constructor +# ============================================================================= + +""" +$(TYPEDSIGNATURES) + +Construct a `ControlledVectorField` with trait flags. + +# Arguments +- `f::Function`: The controlled vector-field function. +- `is_autonomous::Bool`: If true, autonomous (default: `__is_autonomous()`). +- `is_variable::Bool`: If true, depends on the variable (default: `__is_variable()`). + +See also: [`CTBase.Data.ControlledVectorField`](@ref). +""" +function ControlledVectorField( + f; is_autonomous::Bool=__is_autonomous(), is_variable::Bool=__is_variable() +) + TD = is_autonomous ? Traits.Autonomous : Traits.NonAutonomous + VD = is_variable ? Traits.NonFixed : Traits.Fixed + return ControlledVectorField{typeof(f),TD,VD}(f) +end + +# ============================================================================= +# Typed constructor +# ============================================================================= + +""" +$(TYPEDSIGNATURES) + +Typed constructor for `ControlledVectorField` with explicit trait types. + +See also: [`CTBase.Data.ControlledVectorField`](@ref). +""" +function ControlledVectorField( + f, ::Type{TD}, ::Type{VD} +) where {TD<:Traits.TimeDependence,VD<:Traits.VariableDependence} + return ControlledVectorField{typeof(f),TD,VD}(f) +end + +# ============================================================================= +# Natural call signatures — one per trait combination +# ============================================================================= + +(fc::ControlledVectorField{<:Function,Traits.Autonomous,Traits.Fixed})(x, u) = fc.f(x, u) +function (fc::ControlledVectorField{<:Function,Traits.NonAutonomous,Traits.Fixed})(t, x, u) + return fc.f(t, x, u) +end +function (fc::ControlledVectorField{<:Function,Traits.Autonomous,Traits.NonFixed})(x, u, v) + return fc.f(x, u, v) +end +function (fc::ControlledVectorField{<:Function,Traits.NonAutonomous,Traits.NonFixed})( + t, x, u, v +) + return fc.f(t, x, u, v) +end + +# ============================================================================= +# Uniform (t, x, u, v) call — used by composition. +# (NonAutonomous, NonFixed) is already covered by the natural signature above. +# ============================================================================= + +function (fc::ControlledVectorField{<:Function,Traits.Autonomous,Traits.Fixed})(t, x, u, v) + fc.f(x, u) +end +function (fc::ControlledVectorField{<:Function,Traits.NonAutonomous,Traits.Fixed})( + t, x, u, v +) + return fc.f(t, x, u) +end +function (fc::ControlledVectorField{<:Function,Traits.Autonomous,Traits.NonFixed})( + t, x, u, v +) + return fc.f(x, u, v) +end +# NonAutonomous, NonFixed — already covered by natural 4-arg + +# ============================================================================= +# Base.show +# ============================================================================= + +""" +$(TYPEDSIGNATURES) + +Display a compact representation of a `ControlledVectorField`. + +See also: [`CTBase.Data.ControlledVectorField`](@ref). +""" +function Base.show(io::IO, ::ControlledVectorField{F,TD,VD}) where {F,TD,VD} + natural = if TD === Traits.Autonomous && VD === Traits.Fixed + "fc(x, u)" + elseif TD === Traits.NonAutonomous && VD === Traits.Fixed + "fc(t, x, u)" + elseif TD === Traits.Autonomous && VD === Traits.NonFixed + "fc(x, u, v)" + else + "fc(t, x, u, v)" + end + println(io, "ControlledVectorField: $(_td_label(TD)), $(_vd_label(VD))") + println(io, " natural call: ", natural) + return print(io, " uniform call: fc(t, x, u, v)") +end + +function Base.show(io::IO, ::MIME"text/plain", fc::ControlledVectorField) + return show(io, fc) +end diff --git a/test/suite/data/test_composed_vector_field.jl b/test/suite/data/test_composed_vector_field.jl new file mode 100644 index 00000000..ffb710d8 --- /dev/null +++ b/test/suite/data/test_composed_vector_field.jl @@ -0,0 +1,105 @@ +module TestComposedVectorField + +using Test: Test +import CTBase.Data: Data +import CTBase.Traits: Traits + +const VERBOSE = isdefined(Main, :TestData) ? Main.TestData.VERBOSE : true +const SHOWTIMING = isdefined(Main, :TestData) ? Main.TestData.SHOWTIMING : true + +function test_composed_vector_field() + Test.@testset "ComposedVectorField Tests" verbose=VERBOSE showtiming=SHOWTIMING begin + + # ── composition value: ClosedLoop and OpenLoop ─────────────────────── + + Test.@testset "Unit: ClosedLoop composition g(x) = fc(x, u(x))" begin + fc = Data.ControlledVectorField((x, u) -> -x + u) # Autonomous, Fixed + g = Data.ComposedVectorField(fc, Data.ClosedLoop(x -> -x)) + # g(x) = fc(x, -x) = -x - x = -2x + Test.@test g(3.0) ≈ -6.0 # natural (x) + Test.@test g(0.0, 3.0, nothing) ≈ -6.0 # uniform (t,x,v) + end + + Test.@testset "Unit: OpenLoop composition g(x) = fc(x, u())" begin + fc = Data.ControlledVectorField((x, u) -> -x + u) + g = Data.ComposedVectorField(fc, Data.OpenLoop(() -> 2.0)) + # g(x) = fc(x, 2) = -x + 2 + Test.@test g(3.0) ≈ -1.0 + end + + Test.@testset "Unit: getters" begin + fc = Data.ControlledVectorField((x, u) -> -x + u) + law = Data.ClosedLoop(x -> -x) + g = Data.ComposedVectorField(fc, law) + Test.@test Data.controlled_vector_field(g) === fc + Test.@test Data.control_law(g) === law + end + + Test.@testset "Unit: Base.show" begin + g = Data.ComposedVectorField( + Data.ControlledVectorField((x, u) -> -x + u), Data.ClosedLoop(x -> -x) + ) + Test.@test occursin("ComposedVectorField", sprint(show, g)) + end + + # ── trait joins ────────────────────────────────────────────────────── + + Test.@testset "Unit: trait join — both autonomous/fixed" begin + g = Data.ComposedVectorField( + Data.ControlledVectorField((x, u) -> -x + u), Data.ClosedLoop(x -> -x) + ) + Test.@test Traits.time_dependence(g) === Traits.Autonomous + Test.@test Traits.variable_dependence(g) === Traits.Fixed + end + + Test.@testset "Unit: trait join — autonomous fc + time-varying law ⇒ NonAutonomous" begin + fc = Data.ControlledVectorField((x, u) -> -x + u) + law = Data.ClosedLoop((t, x) -> -x * t; is_autonomous=false) + g = Data.ComposedVectorField(fc, law) + Test.@test Traits.time_dependence(g) === Traits.NonAutonomous + Test.@test Traits.variable_dependence(g) === Traits.Fixed + end + + Test.@testset "Unit: trait join — variable law ⇒ NonFixed" begin + fc = Data.ControlledVectorField((x, u) -> -x + u) + law = Data.ClosedLoop((x, v) -> -x + v; is_variable=true) # ClosedLoop u(x,v) + g = Data.ComposedVectorField(fc, law) + Test.@test Traits.variable_dependence(g) === Traits.NonFixed + # g(x,v) = fc(x, -x+v) = -x + (-x+v) = -2x + v + Test.@test g(3.0, 5.0) ≈ -2 * 3.0 + 5.0 + end + + # ── contract: is an out-of-place AbstractVectorField ───────────────── + + Test.@testset "Contract: AbstractVectorField, OutOfPlace, StateDynamics" begin + g = Data.ComposedVectorField( + Data.ControlledVectorField((x, u) -> -x + u), Data.ClosedLoop(x -> -x) + ) + Test.@test g isa Data.AbstractVectorField + Test.@test Traits.mutability(g) === Traits.OutOfPlace + Test.@test Traits.dynamics_trait(g) === Traits.StateDynamics + end + + # ── type stability ─────────────────────────────────────────────────── + + Test.@testset "Unit: call is type-stable" begin + g = Data.ComposedVectorField( + Data.ControlledVectorField((x, u) -> -x + u), Data.ClosedLoop(x -> -x) + ) + Test.@test_nowarn Test.@inferred g(3.0) + end + + # ── error: DynClosedLoop rejected (that is the Hamiltonian path) ────── + + Test.@testset "Error: DynClosedLoop law is rejected" begin + fc = Data.ControlledVectorField((x, u) -> -x + u) + Test.@test_throws MethodError Data.ComposedVectorField( + fc, Data.DynClosedLoop((x, p) -> -p) + ) + end + end +end + +end # module + +test_composed_vector_field() = TestComposedVectorField.test_composed_vector_field() diff --git a/test/suite/data/test_controlled_vector_field.jl b/test/suite/data/test_controlled_vector_field.jl new file mode 100644 index 00000000..cb2a3eb6 --- /dev/null +++ b/test/suite/data/test_controlled_vector_field.jl @@ -0,0 +1,82 @@ +module TestControlledVectorField + +using Test: Test +import CTBase.Data: Data +import CTBase.Traits: Traits + +const VERBOSE = isdefined(Main, :TestData) ? Main.TestData.VERBOSE : true +const SHOWTIMING = isdefined(Main, :TestData) ? Main.TestData.SHOWTIMING : true + +function test_controlled_vector_field() + Test.@testset "ControlledVectorField Tests" verbose=VERBOSE showtiming=SHOWTIMING begin + + # ── construction + traits, all (TD, VD) combinations ───────────────── + + Test.@testset "Unit: construction and traits" begin + fc = Data.ControlledVectorField((x, u) -> -x + u) + Test.@test fc isa Data.ControlledVectorField + Test.@test fc isa Data.AbstractControlledVectorField + Test.@test Traits.time_dependence(fc) === Traits.Autonomous + Test.@test Traits.variable_dependence(fc) === Traits.Fixed + Test.@test Traits.dynamics_trait(fc) === Traits.StateDynamics + + fc2 = Data.ControlledVectorField( + (t, x, u, v) -> t * x + u + v; is_autonomous=false, is_variable=true + ) + Test.@test Traits.time_dependence(fc2) === Traits.NonAutonomous + Test.@test Traits.variable_dependence(fc2) === Traits.NonFixed + end + + # ── natural + uniform call signatures ──────────────────────────────── + + Test.@testset "Unit: natural and uniform calls" begin + # Autonomous/Fixed: fc(x,u), uniform fc(t,x,u,v) + fc = Data.ControlledVectorField((x, u) -> -x + u) + Test.@test fc(3.0, 1.0) ≈ -2.0 + Test.@test fc(0.0, 3.0, 1.0, nothing) ≈ -2.0 + + # NonAutonomous/Fixed: fc(t,x,u) + fct = Data.ControlledVectorField((t, x, u) -> t * x + u; is_autonomous=false) + Test.@test fct(2.0, 3.0, 1.0) ≈ 7.0 + Test.@test fct(2.0, 3.0, 1.0, nothing) ≈ 7.0 # uniform + + # Autonomous/NonFixed: fc(x,u,v) + fcv = Data.ControlledVectorField((x, u, v) -> v * x + u; is_variable=true) + Test.@test fcv(3.0, 1.0, 2.0) ≈ 7.0 + Test.@test fcv(0.0, 3.0, 1.0, 2.0) ≈ 7.0 # uniform + + # NonAutonomous/NonFixed: fc(t,x,u,v) (natural == uniform) + fctv = Data.ControlledVectorField( + (t, x, u, v) -> t * v * x + u; is_autonomous=false, is_variable=true + ) + Test.@test fctv(2.0, 3.0, 1.0, 4.0) ≈ 2 * 4 * 3 + 1 + end + + # ── vector state/control ───────────────────────────────────────────── + + Test.@testset "Unit: vector state and control" begin + fc = Data.ControlledVectorField((x, u) -> [x[2], u[1]]) + Test.@test fc([1.0, 2.0], [3.0]) ≈ [2.0, 3.0] + end + + # ── Base.show ──────────────────────────────────────────────────────── + + Test.@testset "Unit: Base.show" begin + fc = Data.ControlledVectorField((x, u) -> -x + u) + str = sprint(show, fc) + Test.@test occursin("ControlledVectorField", str) + Test.@test occursin("fc(x, u)", str) + end + + # ── type stability of the call ─────────────────────────────────────── + + Test.@testset "Unit: call is type-stable" begin + fc = Data.ControlledVectorField((x, u) -> -x + u) + Test.@test_nowarn Test.@inferred fc(3.0, 1.0) + end + end +end + +end # module + +test_controlled_vector_field() = TestControlledVectorField.test_controlled_vector_field()