diff --git a/BREAKING.md b/BREAKING.md index 4e8a4ed4..bd78ae57 100644 --- a/BREAKING.md +++ b/BREAKING.md @@ -3,6 +3,15 @@ This document outlines all breaking changes introduced in CTBase v0.18.0-beta compared to v0.17.4. Use this guide to migrate your code and understand the impact of these changes. +## Non-breaking note (0.28.5-beta) + +- **`Data`: new `PseudoHamiltonianVectorField` / `AbstractPseudoHamiltonianVectorField` + data type.** Purely additive: two new exported symbols in `CTBase.Data` + (`AbstractPseudoHamiltonianVectorField`, `PseudoHamiltonianVectorField`), + no changes to any existing type, signature, or exported symbol. **No + breaking change**: existing code is entirely unaffected. No migration + required. + ## Non-breaking note (0.28.4-beta) - **`Strategies`: Tip line in `show` now displays the parameterized type diff --git a/CHANGELOG.md b/CHANGELOG.md index c2807574..d19fa147 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,59 @@ All notable changes to CTBase 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.28.5-beta] - 2026-07-24 + +### ✨ New Features + +#### **PseudoHamiltonianVectorField** + +- **New `PseudoHamiltonianVectorField` data type** in `CTBase.Data` for + pseudo-Hamiltonian dynamics that have already been differentiated by + hand, with an explicit control argument: + - **Definition**: `h̃vf(t, x, p, u, v) = (ẋ, ṗ)` — the vector-field + analogue of `PseudoHamiltonian`, the same way `HamiltonianVectorField` + is the vector-field analogue of `Hamiltonian` + - **Abstract supertype**: `AbstractPseudoHamiltonianVectorField{TD, VD, + MD} <: AbstractVectorField{TD, VD, MD}` — a **sibling** of + `AbstractHamiltonianVectorField`, not a subtype, mirroring the existing + `AbstractPseudoHamiltonian`/`AbstractHamiltonian` sibling relationship; + `dynamics_trait = HamiltonianDynamics` + - **Both in-place and out-of-place**, mirroring `HamiltonianVectorField` + exactly: mutability is auto-detected from the wrapped function's + arity, or set explicitly via `is_inplace` + - **Variable-costate support** (`dpv`) from the start via the + `variable_costate` keyword, mirroring `HamiltonianVectorField`'s + `variable_costate` kwarg + - **Natural + uniform call signatures**: one natural arity per `(TD, VD, + MD)` combination (one more than `HamiltonianVectorField`'s, for the + extra `u`) and uniform `(t, x, p, u, v)` / in-place `(dx, dp, t, x, p, + u, v)` calls + - **No `hamiltonian(sys)` accessor** on the downstream system side + (CTFlows.jl) — deliberate, matching `HamiltonianVectorFieldSystem`'s + existing gap, since no scalar `H̃` is ever built + +### 🧪 Testing + +- Added `test/suite/data/test_abstract_pseudo_hamiltonian_vector_field.jl`: + abstract-type definition, sibling (not subtype) check against + `AbstractHamiltonianVectorField`, trait accessors, dynamics trait, + Liskov substitution, exports (17 tests) +- Added `test/suite/data/test_pseudo_hamiltonian_vector_field.jl`: + construction (all `TD`/`VD`/`MD` combinations), natural/uniform + signatures, trait accessors, typed constructor, subtyping, `Base.show`, + explicit `is_inplace`, ambiguous-arity `PreconditionError`, + `variable_costate`/`dpv` behavior (92 tests) +- Full suite green: **4882/4882** + +### 📚 Documentation + +- Added the two new `Data/` source files to `docs/api_reference.jl` + +### ✅ Compatibility + +- **No breaking changes**: purely additive. Existing code unaffected. See + [BREAKING.md](BREAKING.md). + ## [0.28.4-beta] - 2026-07-23 ### 🐛 Bug Fixes diff --git a/Project.toml b/Project.toml index 2bf56604..4d85fab6 100644 --- a/Project.toml +++ b/Project.toml @@ -1,6 +1,6 @@ name = "CTBase" uuid = "54762871-cc72-4466-b8e8-f6c8b58076cd" -version = "0.28.4-beta" +version = "0.28.5-beta" authors = ["Olivier Cots ", "Jean-Baptiste Caillau "] [deps] diff --git a/docs/api_reference.jl b/docs/api_reference.jl index 2c5d2ac9..353bb467 100644 --- a/docs/api_reference.jl +++ b/docs/api_reference.jl @@ -57,6 +57,8 @@ function generate_api_reference(src_dir::String) joinpath("Data", "hamiltonian.jl"), joinpath("Data", "abstract_hamiltonian_vector_field.jl"), joinpath("Data", "hamiltonian_vector_field.jl"), + joinpath("Data", "abstract_pseudo_hamiltonian_vector_field.jl"), + joinpath("Data", "pseudo_hamiltonian_vector_field.jl"), joinpath("Data", "abstract_control_law.jl"), joinpath("Data", "control_law.jl"), joinpath("Data", "abstract_path_constraint.jl"), diff --git a/src/Data/Data.jl b/src/Data/Data.jl index d792b5cd..55df6c35 100644 --- a/src/Data/Data.jl +++ b/src/Data/Data.jl @@ -37,6 +37,8 @@ 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")) +include(joinpath(@__DIR__, "abstract_pseudo_hamiltonian_vector_field.jl")) +include(joinpath(@__DIR__, "pseudo_hamiltonian_vector_field.jl")) # ============================================================================== # Module exports @@ -62,6 +64,8 @@ export AbstractMultiplier export Multiplier export AbstractPseudoHamiltonian export PseudoHamiltonian +export AbstractPseudoHamiltonianVectorField +export PseudoHamiltonianVectorField export ComposedHamiltonian export pseudo_hamiltonian export control_law diff --git a/src/Data/abstract_pseudo_hamiltonian_vector_field.jl b/src/Data/abstract_pseudo_hamiltonian_vector_field.jl new file mode 100644 index 00000000..77ce548b --- /dev/null +++ b/src/Data/abstract_pseudo_hamiltonian_vector_field.jl @@ -0,0 +1,55 @@ +# ============================================================================= +# AbstractPseudoHamiltonianVectorField — Abstract type for pseudo-Hamiltonian +# vector fields +# ============================================================================= + +""" +$(TYPEDEF) + +Abstract supertype for pseudo-Hamiltonian vector fields. + +A pseudo-Hamiltonian vector field represents the already-differentiated dynamics +`(ẋ, ṗ) = (dx, dp)(t, x, p, u[, v])` of a pseudo-Hamiltonian system, with an explicit +control argument `u` — the vector-field analogue of +[`CTBase.Data.AbstractPseudoHamiltonian`](@ref), exactly as +[`CTBase.Data.AbstractHamiltonianVectorField`](@ref) is the vector-field analogue of +[`CTBase.Data.AbstractHamiltonian`](@ref). It extends `AbstractVectorField` with the +additional structure required for Hamiltonian mechanics. + +`AbstractPseudoHamiltonianVectorField` is a **sibling** of +[`CTBase.Data.AbstractHamiltonianVectorField`](@ref), not a subtype of it — mirroring how +[`CTBase.Data.AbstractPseudoHamiltonian`](@ref) is a sibling of, not a subtype of, +[`CTBase.Data.AbstractHamiltonian`](@ref): a pseudo-Hamiltonian vector field's natural +call signature carries an extra control argument `u` that a plain Hamiltonian vector +field does not have. + +# Type Parameters +- `TD <: TimeDependence`: Time dependence trait (Autonomous or NonAutonomous) +- `VD <: VariableDependence`: Variable dependence trait (Fixed or NonFixed) +- `MD <: AbstractMutabilityTrait`: Mutability trait (InPlace or OutOfPlace) + +# Interface Requirements + +All subtypes must implement: +- Call signature: `(t, x, p, u)` or `(x, p, u)` depending on time dependence, plus `v` + for non-fixed problems. +- Returns the combined state-costate derivative `(dx, dp)`. + +See also: [`CTBase.Data.AbstractVectorField`](@ref), [`CTBase.Data.PseudoHamiltonianVectorField`](@ref), +[`CTBase.Data.AbstractHamiltonianVectorField`](@ref), [`CTBase.Data.AbstractPseudoHamiltonian`](@ref). +""" +abstract type AbstractPseudoHamiltonianVectorField{ + TD<:Traits.TimeDependence, + VD<:Traits.VariableDependence, + MD<:Traits.AbstractMutabilityTrait, +} <: AbstractVectorField{TD,VD,MD} end + +""" +$(TYPEDSIGNATURES) + +Return the dynamics trait of an `AbstractPseudoHamiltonianVectorField`, namely +[`CTBase.Traits.HamiltonianDynamics`](@ref). + +See also: [`CTBase.Traits.dynamics_trait`](@ref), [`CTBase.Data.AbstractPseudoHamiltonianVectorField`](@ref). +""" +Traits.dynamics_trait(::AbstractPseudoHamiltonianVectorField) = Traits.HamiltonianDynamics diff --git a/src/Data/helpers.jl b/src/Data/helpers.jl index 8e456692..bbd0aef3 100644 --- a/src/Data/helpers.jl +++ b/src/Data/helpers.jl @@ -216,6 +216,57 @@ function _uniform_sig_hvf(::Type{Traits.InPlace}) return "f(dx, dp, t, x, p, v)" end +# ============================================================================= +# PseudoHamiltonianVectorField-specific signature helpers +# ============================================================================= + +""" + _natural_sig_phvf(::Type{TD}, ::Type{VD}, ::Type{Traits.OutOfPlace}) where {TD, VD} -> String + _natural_sig_phvf(::Type{TD}, ::Type{VD}, ::Type{Traits.InPlace}) where {TD, VD} -> String + +Return the natural call signature for a PseudoHamiltonianVectorField based on its traits. + +See also: [`CTBase.Data._uniform_sig_phvf`](@ref), [`CTBase.Data._natural_sig_hvf`](@ref). +""" +function _natural_sig_phvf( + ::Type{TD}, ::Type{VD}, ::Type{Traits.OutOfPlace} +) where {TD<:Traits.TimeDependence,VD<:Traits.VariableDependence} + args = String[] + TD === Traits.NonAutonomous && push!(args, "t") + push!(args, "x") + push!(args, "p") + push!(args, "u") + VD === Traits.NonFixed && push!(args, "v") + return "f(" * join(args, ", ") * ")" +end + +function _natural_sig_phvf( + ::Type{TD}, ::Type{VD}, ::Type{Traits.InPlace} +) where {TD<:Traits.TimeDependence,VD<:Traits.VariableDependence} + args = ["dx", "dp"] + TD === Traits.NonAutonomous && push!(args, "t") + push!(args, "x") + push!(args, "p") + push!(args, "u") + VD === Traits.NonFixed && push!(args, "v") + return "f(" * join(args, ", ") * ")" +end + +""" + _uniform_sig_phvf(::Type{Traits.OutOfPlace}) -> String + _uniform_sig_phvf(::Type{Traits.InPlace}) -> String + +Return the uniform call signature for a PseudoHamiltonianVectorField. + +See also: [`CTBase.Data._natural_sig_phvf`](@ref), [`CTBase.Data._uniform_sig_hvf`](@ref). +""" +function _uniform_sig_phvf(::Type{Traits.OutOfPlace}) + return "f(t, x, p, u, v)" +end +function _uniform_sig_phvf(::Type{Traits.InPlace}) + return "f(dx, dp, t, x, p, u, v)" +end + # ============================================================================= # Hamiltonian-specific signature helpers # ============================================================================= diff --git a/src/Data/pseudo_hamiltonian_vector_field.jl b/src/Data/pseudo_hamiltonian_vector_field.jl new file mode 100644 index 00000000..c3c7e697 --- /dev/null +++ b/src/Data/pseudo_hamiltonian_vector_field.jl @@ -0,0 +1,473 @@ +# ============================================================================= +# Concrete PseudoHamiltonianVectorField type +# ============================================================================= + +""" +$(TYPEDEF) + +Parametric container for a pseudo-Hamiltonian vector field function together with its +time-dependence, variable-dependence, and mutability traits. + +The function returns a tuple `(dx, dp)` representing the already-differentiated +derivatives of state `x` and costate `p`, with an explicit control argument `u` — the +vector-field analogue of [`CTBase.Data.PseudoHamiltonian`](@ref), just as +[`CTBase.Data.HamiltonianVectorField`](@ref) is the vector-field analogue of +[`CTBase.Data.Hamiltonian`](@ref). + +# Type Parameters +- `F`: concrete type of the wrapped function. +- `TD <: TimeDependence`: `Autonomous` or `NonAutonomous`. +- `VD <: VariableDependence`: `Fixed` or `NonFixed`. +- `MD <: AbstractMutabilityTrait`: `InPlace` or `OutOfPlace`. + +# Fields +- `f::F`: the pseudo-Hamiltonian vector field function. + +# Construction + +Use the keyword constructor: + +```julia +PseudoHamiltonianVectorField(f; is_autonomous = true, is_variable = false) # default: f(x, p, u) +PseudoHamiltonianVectorField((t, x, p, u) -> ...; is_autonomous = false) # f(t, x, p, u) +PseudoHamiltonianVectorField((x, p, u, v) -> ...; is_variable = true) # f(x, p, u, v) +PseudoHamiltonianVectorField((t, x, p, u, v) -> ...; is_autonomous = false, is_variable = true) +``` + +The mutability trait (InPlace/OutOfPlace) is auto-detected from the function signature. + +# Call Signatures + +Every `PseudoHamiltonianVectorField` is callable via its **natural** signature (matching +the traits), and via a **uniform** signature `(t, x, p, u, v)` that ignores the +unused arguments. + +For InPlace pseudo-Hamiltonian vector fields, the natural signature includes the +derivative buffers as the first two arguments (e.g., `(dx, dp, x, p, u)` for +Autonomous/Fixed). + +See also: [`CTBase.Data.AbstractPseudoHamiltonianVectorField`](@ref), +[`CTBase.Data.PseudoHamiltonian`](@ref), [`CTBase.Data.HamiltonianVectorField`](@ref), +[`CTBase.Traits.TimeDependence`](@ref), [`CTBase.Traits.VariableDependence`](@ref). +""" +struct PseudoHamiltonianVectorField{ + F<:Function, + TD<:Traits.TimeDependence, + VD<:Traits.VariableDependence, + MD<:Traits.AbstractMutabilityTrait, +} <: AbstractPseudoHamiltonianVectorField{TD,VD,MD} + f::F +end + +# ============================================================================= +# Internal helpers for mutability detection +# ============================================================================= + +""" + _oop_arity_phvf(::Type{Traits.Autonomous}, ::Type{Traits.Fixed}) -> Int + +Return the out-of-place arity for Autonomous/Fixed pseudo-Hamiltonian vector fields +(3: x, p, u). +""" +_oop_arity_phvf(::Type{Traits.Autonomous}, ::Type{Traits.Fixed}) = 3 + +""" + _oop_arity_phvf(::Type{Traits.NonAutonomous}, ::Type{Traits.Fixed}) -> Int + +Return the out-of-place arity for NonAutonomous/Fixed pseudo-Hamiltonian vector fields +(4: t, x, p, u). +""" +_oop_arity_phvf(::Type{Traits.NonAutonomous}, ::Type{Traits.Fixed}) = 4 + +""" + _oop_arity_phvf(::Type{Traits.Autonomous}, ::Type{Traits.NonFixed}) -> Int + +Return the out-of-place arity for Autonomous/NonFixed pseudo-Hamiltonian vector fields +(4: x, p, u, v). +""" +_oop_arity_phvf(::Type{Traits.Autonomous}, ::Type{Traits.NonFixed}) = 4 + +""" + _oop_arity_phvf(::Type{Traits.NonAutonomous}, ::Type{Traits.NonFixed}) -> Int + +Return the out-of-place arity for NonAutonomous/NonFixed pseudo-Hamiltonian vector +fields (5: t, x, p, u, v). +""" +_oop_arity_phvf(::Type{Traits.NonAutonomous}, ::Type{Traits.NonFixed}) = 5 + +""" + _detect_mutability_phvf(f::Function, TD, VD) -> Type{<:AbstractMutabilityTrait} + +Detect the mutability trait from the pseudo-Hamiltonian vector field function +signature. + +Compares the function arity to the expected out-of-place arity and in-place arity +(arity + 2 for `PseudoHamiltonianVectorField`, which has two output buffers). Returns +`InPlace` or `OutOfPlace` accordingly. + +If the function has multiple methods, throws a `PreconditionError` indicating that +auto-detection is ambiguous and the user should specify `is_inplace` explicitly. + +# Arguments +- `f::Function`: The pseudo-Hamiltonian vector-field function. +- `TD`: Time dependence trait type. +- `VD`: Variable dependence trait type. + +# Returns +- `Type{InPlace}` or `Type{OutOfPlace}`. + +# Throws +- `Exceptions.PreconditionError`: If the function has multiple methods, making + automatic arity detection ambiguous. +- `Exceptions.IncorrectArgument`: If the arity is invalid (does not match expected + out-of-place or in-place arity). + +# Notes +- This function is called automatically by the `PseudoHamiltonianVectorField` + constructor when `is_inplace` is `nothing`. +- Users can bypass auto-detection by specifying `is_inplace=true` or `is_inplace=false` + explicitly in the constructor. + +See also: [`CTBase.Data.PseudoHamiltonianVectorField`](@ref), [`CTBase.Traits.InPlace`](@ref), +[`CTBase.Traits.OutOfPlace`](@ref). +""" +function _detect_mutability_phvf(f::Function, TD, VD) + method_count = length(methods(f)) + if method_count > 1 + throw( + Exceptions.PreconditionError( + "Cannot auto-detect mutability: function has multiple methods"; + reason="The function has $method_count methods, making automatic arity detection ambiguous", + suggestion="Specify `is_inplace=true` or `is_inplace=false` explicitly in the constructor", + context="PseudoHamiltonianVectorField mutability detection", + ), + ) + end + + arity = first(methods(f)).nargs - 1 + oop_arity = _oop_arity_phvf(TD, VD) + ip_arity = oop_arity + 2 # PseudoHamiltonianVectorField has two output buffers (dx, dp) + + if arity == oop_arity + return Traits.OutOfPlace + elseif arity == ip_arity + return Traits.InPlace + else + throw( + Exceptions.IncorrectArgument( + "Invalid function arity: expected $oop_arity (out-of-place) or $ip_arity (in-place), got $arity"; + suggestion="Ensure your function signature matches the expected pattern for the given traits.", + context="PseudoHamiltonianVectorField mutability detection", + ), + ) + end +end + +""" +$(TYPEDSIGNATURES) + +Construct a `PseudoHamiltonianVectorField` with trait flags. + +# Arguments +- `f::Function`: The pseudo-Hamiltonian vector field function returning `(dx, dp)` + (or `(dx, dp, dpv)` when called with `variable_costate=true`). +- `is_autonomous::Bool`: If true, system is autonomous (default: `__is_autonomous()`). +- `is_variable::Bool`: If true, system depends on variable parameters (default: + `__is_variable()`). +- `is_inplace::Union{Bool, Nothing}`: If true, function is in-place; if false, + function is out-of-place; if `nothing`, mutability is auto-detected from function + signature (default: `__is_inplace()`). + +# Returns +- `PseudoHamiltonianVectorField`: A `PseudoHamiltonianVectorField` with appropriate + traits. + +# Example +```julia-repl +julia> using CTBase.Data + +julia> h̃vf = PseudoHamiltonianVectorField((x, p, u) -> (u, -p)) +PseudoHamiltonianVectorField: autonomous, fixed (no variable), out-of-place + natural call: f(x, p, u) + uniform call: f(t, x, p, u, v) +``` + +# Notes +- If `is_inplace` is `nothing` (default), the mutability is auto-detected from the + function signature by checking the number of arguments. +- If the function has multiple methods, auto-detection will fail with a + `PreconditionError`. In this case, specify `is_inplace` explicitly. + +See also: [`CTBase.Data.PseudoHamiltonianVectorField`](@ref), [`CTBase.Traits.Autonomous`](@ref), +[`CTBase.Traits.NonAutonomous`](@ref), [`CTBase.Traits.Fixed`](@ref), [`CTBase.Traits.NonFixed`](@ref), +[`CTBase.Traits.InPlace`](@ref), [`CTBase.Traits.OutOfPlace`](@ref). +""" +function PseudoHamiltonianVectorField( + f; + is_autonomous::Bool=__is_autonomous(), + is_variable::Bool=__is_variable(), + is_inplace::Union{Bool,Nothing}=__is_inplace(), +) + TD = is_autonomous ? Traits.Autonomous : Traits.NonAutonomous + VD = is_variable ? Traits.NonFixed : Traits.Fixed + MD = if is_inplace === nothing + _detect_mutability_phvf(f, TD, VD) + else + is_inplace ? Traits.InPlace : Traits.OutOfPlace + end + return PseudoHamiltonianVectorField{typeof(f),TD,VD,MD}(f) +end + +# ============================================================================= +# Typed constructor — calls struct inner constructor directly +# ============================================================================= + +""" +$(TYPEDSIGNATURES) + +Typed constructor for `PseudoHamiltonianVectorField` with explicit trait types. + +# Arguments +- `f`: The pseudo-Hamiltonian vector field function. +- `::Type{TD}`: The time-dependence trait type. +- `::Type{VD}`: The variable-dependence trait type. +- `::Type{MD}`: The mutability trait type. + +# Returns +- `PseudoHamiltonianVectorField`: A pseudo-Hamiltonian vector field with the specified + traits. + +See also: [`CTBase.Data.PseudoHamiltonianVectorField`](@ref). +""" +function PseudoHamiltonianVectorField( + f, ::Type{TD}, ::Type{VD}, ::Type{MD} +) where { + TD<:Traits.TimeDependence, + VD<:Traits.VariableDependence, + MD<:Traits.AbstractMutabilityTrait, +} + return PseudoHamiltonianVectorField{typeof(f),TD,VD,MD}(f) +end + +# ============================================================================= +# Natural call signatures - one per trait combination +# ============================================================================= + +# OutOfPlace signatures (natural call, one per TD/VD combination) +function (H::PseudoHamiltonianVectorField{ + <:Function,Traits.Autonomous,Traits.Fixed,Traits.OutOfPlace +})( + x, p, u +) + return H.f(x, p, u) +end +function (H::PseudoHamiltonianVectorField{ + <:Function,Traits.NonAutonomous,Traits.Fixed,Traits.OutOfPlace +})( + t, x, p, u +) + return H.f(t, x, p, u) +end +function (H::PseudoHamiltonianVectorField{ + <:Function,Traits.Autonomous,Traits.NonFixed,Traits.OutOfPlace +})( + x, p, u, v; variable_costate::Bool=false +) + variable_costate || return H.f(x, p, u, v) + hasmethod( + H.f, Tuple{typeof(x),typeof(p),typeof(u),typeof(v)}, (:variable_costate,) + ) || throw( + Exceptions.PreconditionError( + "variable_costate=true is not supported by this PseudoHamiltonianVectorField's inner function"; + suggestion="Provide an inner function accepting a `variable_costate::Bool` keyword and returning `(dx, dp, dpv)` when true", + context="PseudoHamiltonianVectorField Autonomous/NonFixed call", + ), + ) + return H.f(x, p, u, v; variable_costate=true) +end + +function (H::PseudoHamiltonianVectorField{ + <:Function,Traits.NonAutonomous,Traits.NonFixed,Traits.OutOfPlace +})( + t, x, p, u, v; variable_costate::Bool=false +) + variable_costate || return H.f(t, x, p, u, v) + hasmethod( + H.f, + Tuple{typeof(t),typeof(x),typeof(p),typeof(u),typeof(v)}, + (:variable_costate,), + ) || throw( + Exceptions.PreconditionError( + "variable_costate=true is not supported by this PseudoHamiltonianVectorField's inner function"; + suggestion="Provide an inner function accepting a `variable_costate::Bool` keyword and returning `(dx, dp, dpv)` when true", + context="PseudoHamiltonianVectorField NonAutonomous/NonFixed call", + ), + ) + return H.f(t, x, p, u, v; variable_costate=true) +end + +# InPlace signatures (natural call, one per TD/VD combination) +function (H::PseudoHamiltonianVectorField{ + <:Function,Traits.Autonomous,Traits.Fixed,Traits.InPlace +})( + dx, dp, x, p, u +) + return H.f(dx, dp, x, p, u) +end +function (H::PseudoHamiltonianVectorField{ + <:Function,Traits.NonAutonomous,Traits.Fixed,Traits.InPlace +})( + dx, dp, t, x, p, u +) + return H.f(dx, dp, t, x, p, u) +end +function (H::PseudoHamiltonianVectorField{ + <:Function,Traits.Autonomous,Traits.NonFixed,Traits.InPlace +})( + dx, dp, x, p, u, v; dpv=nothing, variable_costate::Bool=false +) + variable_costate || return H.f(dx, dp, x, p, u, v) + hasmethod( + H.f, + Tuple{typeof(dx),typeof(dp),typeof(x),typeof(p),typeof(u),typeof(v)}, + (:variable_costate,), + ) || throw( + Exceptions.PreconditionError( + "variable_costate=true is not supported by this PseudoHamiltonianVectorField's inner function"; + suggestion="Provide an inner function accepting a `variable_costate::Bool` keyword and filling `dpv` when true", + context="PseudoHamiltonianVectorField IP Autonomous/NonFixed call", + ), + ) + return H.f(dx, dp, x, p, u, v; dpv=dpv, variable_costate=true) +end + +function (H::PseudoHamiltonianVectorField{ + <:Function,Traits.NonAutonomous,Traits.NonFixed,Traits.InPlace +})( + dx, dp, t, x, p, u, v; dpv=nothing, variable_costate::Bool=false +) + variable_costate || return H.f(dx, dp, t, x, p, u, v) + hasmethod( + H.f, + Tuple{typeof(dx),typeof(dp),typeof(t),typeof(x),typeof(p),typeof(u),typeof(v)}, + (:variable_costate,), + ) || throw( + Exceptions.PreconditionError( + "variable_costate=true is not supported by this PseudoHamiltonianVectorField's inner function"; + suggestion="Provide an inner function accepting a `variable_costate::Bool` keyword and filling `dpv` when true", + context="PseudoHamiltonianVectorField IP NonAutonomous/NonFixed call", + ), + ) + return H.f(dx, dp, t, x, p, u, v; dpv=dpv, variable_costate=true) +end + +# ============================================================================= +# Uniform (t, x, p, u, v) call - used by PseudoHamiltonianVectorFieldSystem.rhs +# Every combination forwards to its natural call, ignoring unused args. +# (NonAutonomous, NonFixed) is already covered by the natural signature above. +# ============================================================================= + +# OutOfPlace uniform signatures +function (H::PseudoHamiltonianVectorField{ + <:Function,Traits.Autonomous,Traits.Fixed,Traits.OutOfPlace +})( + t, x, p, u, v; variable_costate::Bool=false +) + return H.f(x, p, u) +end +function (H::PseudoHamiltonianVectorField{ + <:Function,Traits.NonAutonomous,Traits.Fixed,Traits.OutOfPlace +})( + t, x, p, u, v; variable_costate::Bool=false +) + return H.f(t, x, p, u) +end +function (H::PseudoHamiltonianVectorField{ + <:Function,Traits.Autonomous,Traits.NonFixed,Traits.OutOfPlace +})( + t, x, p, u, v; variable_costate::Bool=false +) + return H(x, p, u, v; variable_costate=variable_costate) +end # delegate to natural signature + +# InPlace uniform signatures +function (H::PseudoHamiltonianVectorField{ + <:Function,Traits.Autonomous,Traits.Fixed,Traits.InPlace +})( + dx, dp, t, x, p, u, v; variable_costate::Bool=false, dpv=nothing +) + return H.f(dx, dp, x, p, u) +end +function (H::PseudoHamiltonianVectorField{ + <:Function,Traits.NonAutonomous,Traits.Fixed,Traits.InPlace +})( + dx, dp, t, x, p, u, v; variable_costate::Bool=false, dpv=nothing +) + return H.f(dx, dp, t, x, p, u) +end +function (H::PseudoHamiltonianVectorField{ + <:Function,Traits.Autonomous,Traits.NonFixed,Traits.InPlace +})( + dx, dp, t, x, p, u, v; variable_costate::Bool=false, dpv=nothing +) + return H(dx, dp, x, p, u, v; dpv=dpv, variable_costate=variable_costate) +end # delegate to natural signature + +# ============================================================================= +# Base.show +# ============================================================================= + +""" +$(TYPEDSIGNATURES) + +Display a compact representation of a `PseudoHamiltonianVectorField`. + +Shows the type name, time dependence, variable dependence, mutability, and the +natural/uniform call signatures. + +# Arguments +- `io::IO`: The IO stream to write to. +- `h̃vf::PseudoHamiltonianVectorField`: The pseudo-Hamiltonian vector field to display. + +See also: [`CTBase.Data.PseudoHamiltonianVectorField`](@ref). +""" +function Base.show( + io::IO, ::PseudoHamiltonianVectorField{F,TD,VD,MD} +) where { + F<:Function, + TD<:Traits.TimeDependence, + VD<:Traits.VariableDependence, + MD<:Traits.AbstractMutabilityTrait, +} + header = "PseudoHamiltonianVectorField: $(_td_label(TD)), $(_vd_label(VD)), $(_md_label(MD))" + natural = _natural_sig_phvf(TD, VD, MD) + uniform = _uniform_sig_phvf(MD) + println(io, header) + println(io, " natural call: ", natural) + return print(io, " uniform call: ", uniform) +end + +""" +$(TYPEDSIGNATURES) + +Display a `PseudoHamiltonianVectorField` in the REPL with text/plain MIME type. + +Delegates to the compact `show` method. + +# Arguments +- `io::IO`: The IO stream to write to. +- `::MIME"text/plain"`: The MIME type for REPL display. +- `h̃vf::PseudoHamiltonianVectorField`: The pseudo-Hamiltonian vector field to display. + +See also: [`CTBase.Data.PseudoHamiltonianVectorField`](@ref). +""" +function Base.show( + io::IO, ::MIME"text/plain", hvf::PseudoHamiltonianVectorField{F,TD,VD,MD} +) where { + F<:Function, + TD<:Traits.TimeDependence, + VD<:Traits.VariableDependence, + MD<:Traits.AbstractMutabilityTrait, +} + return show(io, hvf) +end diff --git a/test/suite/data/test_abstract_pseudo_hamiltonian_vector_field.jl b/test/suite/data/test_abstract_pseudo_hamiltonian_vector_field.jl new file mode 100644 index 00000000..ce3fa8fc --- /dev/null +++ b/test/suite/data/test_abstract_pseudo_hamiltonian_vector_field.jl @@ -0,0 +1,136 @@ +module TestAbstractPseudoHamiltonianVectorField + +using Test: Test +import CTBase.Data +import CTBase.Traits + +const VERBOSE = isdefined(Main, :TestData) ? Main.TestData.VERBOSE : true +const SHOWTIMING = isdefined(Main, :TestData) ? Main.TestData.SHOWTIMING : true + +# ============================================================================== +# Fake type for contract testing (defined at module top-level per testing-creation.md) +# ============================================================================== + +struct FakePseudoHamiltonianVectorField{TD,VD,MD} <: + Data.AbstractPseudoHamiltonianVectorField{TD,VD,MD} end + +# ============================================================================== +# Test function +# ============================================================================== + +function test_abstract_pseudo_hamiltonian_vector_field() + Test.@testset "AbstractPseudoHamiltonianVectorField Tests" verbose=VERBOSE showtiming=SHOWTIMING begin + + # ==================================================================== + # UNIT TESTS - Abstract Type Definition + # ==================================================================== + + Test.@testset "Abstract Type Definition" begin + Test.@testset "AbstractPseudoHamiltonianVectorField exists" begin + Test.@test isdefined(Data, :AbstractPseudoHamiltonianVectorField) + end + + Test.@testset "AbstractPseudoHamiltonianVectorField is abstract" begin + Test.@test isabstracttype(Data.AbstractPseudoHamiltonianVectorField) + end + + Test.@testset "Subtypes AbstractVectorField" begin + Test.@test Data.AbstractPseudoHamiltonianVectorField <: + Data.AbstractVectorField + end + + Test.@testset "Not a subtype of AbstractHamiltonianVectorField" begin + # Sibling hierarchy, mirroring AbstractPseudoHamiltonian vs AbstractHamiltonian + Test.@test !( + Data.AbstractPseudoHamiltonianVectorField <: + Data.AbstractHamiltonianVectorField + ) + end + + Test.@testset "FakePseudoHamiltonianVectorField subtypes AbstractPseudoHamiltonianVectorField" begin + fake = FakePseudoHamiltonianVectorField{ + Traits.Autonomous,Traits.Fixed,Traits.OutOfPlace + }() + Test.@test fake isa Data.AbstractPseudoHamiltonianVectorField + Test.@test fake isa Data.AbstractVectorField + end + end + + # ==================================================================== + # UNIT TESTS - Trait Accessors on Abstract Type + # ==================================================================== + + Test.@testset "Trait Accessors on Abstract Type" begin + Test.@testset "has_*_trait return true (inherited from AbstractVectorField)" begin + fake = FakePseudoHamiltonianVectorField{ + Traits.Autonomous,Traits.Fixed,Traits.OutOfPlace + }() + Test.@test Traits.has_time_dependence_trait(fake) === true + Test.@test Traits.has_variable_dependence_trait(fake) === true + Test.@test Traits.has_mutability_trait(fake) === true + end + + Test.@testset "trait values are read from type parameters" begin + fake = FakePseudoHamiltonianVectorField{ + Traits.NonAutonomous,Traits.NonFixed,Traits.InPlace + }() + Test.@test Traits.time_dependence(fake) === Traits.NonAutonomous + Test.@test Traits.variable_dependence(fake) === Traits.NonFixed + Test.@test Traits.mutability(fake) === Traits.InPlace + end + end + + # ==================================================================== + # UNIT TESTS - Dynamics Trait + # ==================================================================== + + Test.@testset "Dynamics Trait" begin + Test.@testset "abstract type returns HamiltonianDynamics" begin + fake = FakePseudoHamiltonianVectorField{ + Traits.Autonomous,Traits.Fixed,Traits.OutOfPlace + }() + Test.@test Traits.dynamics_trait(fake) === Traits.HamiltonianDynamics + end + + Test.@testset "concrete PseudoHamiltonianVectorField returns HamiltonianDynamics" begin + h̃vf = Data.PseudoHamiltonianVectorField( + (x, p, u) -> (p .* u, -x); is_autonomous=true, is_variable=false + ) + Test.@test Traits.dynamics_trait(h̃vf) === Traits.HamiltonianDynamics + end + end + + # ==================================================================== + # UNIT TESTS - Liskov Substitution + # ==================================================================== + + Test.@testset "Liskov Substitution" begin + Test.@testset "PseudoHamiltonianVectorField is an AbstractPseudoHamiltonianVectorField" begin + h̃vf = Data.PseudoHamiltonianVectorField( + (x, p, u) -> (p .* u, -x); is_autonomous=true, is_variable=false + ) + Test.@test h̃vf isa Data.AbstractPseudoHamiltonianVectorField + end + + Test.@testset "PseudoHamiltonianVectorField type subtypes the abstract type" begin + Test.@test Data.PseudoHamiltonianVectorField <: + Data.AbstractPseudoHamiltonianVectorField + end + end + + # ==================================================================== + # UNIT TESTS - Exports Verification + # ==================================================================== + + Test.@testset "Exports Verification" begin + Test.@test isdefined(Data, :AbstractPseudoHamiltonianVectorField) + end + end +end + +end # module + +# CRITICAL: Redefine in outer scope for TestRunner +function test_abstract_pseudo_hamiltonian_vector_field() + return TestAbstractPseudoHamiltonianVectorField.test_abstract_pseudo_hamiltonian_vector_field() +end diff --git a/test/suite/data/test_data_module.jl b/test/suite/data/test_data_module.jl index b8f92e42..f92289e1 100644 --- a/test/suite/data/test_data_module.jl +++ b/test/suite/data/test_data_module.jl @@ -39,6 +39,7 @@ const EXPORTED_ABSTRACT_TYPES = ( :AbstractHamiltonian, :AbstractControlLaw, :AbstractPseudoHamiltonian, + :AbstractPseudoHamiltonianVectorField, :AbstractPathConstraint, :AbstractMultiplier, ) @@ -52,6 +53,7 @@ const EXPORTED_CONCRETE_TYPES = ( :ClosedLoop, :DynClosedLoop, :PseudoHamiltonian, + :PseudoHamiltonianVectorField, :PathConstraint, :StateConstraint, :ControlConstraint, @@ -64,12 +66,14 @@ const PRIVATE_SYMBOLS = ( :__is_inplace, :__is_variable, :_detect_mutability_hvf, + :_detect_mutability_phvf, :_detect_mutability_vf, :_md_label, :_natural_sig_h, :_natural_sig_hvf, :_natural_sig_vf, :_oop_arity_hvf, + :_oop_arity_phvf, :_oop_arity_vf, :_td_label, :_uniform_sig_h, @@ -81,6 +85,8 @@ const PRIVATE_SYMBOLS = ( :_uniform_sig_cl, :_natural_sig_ph, :_uniform_sig_ph, + :_natural_sig_phvf, + :_uniform_sig_phvf, :_kind_label, :_natural_sig_pc, :_natural_args_pc, @@ -173,12 +179,15 @@ function test_data_module() Test.@test isabstracttype(Data.AbstractVectorField) Test.@test isabstracttype(Data.AbstractHamiltonianVectorField) Test.@test isabstracttype(Data.AbstractHamiltonian) + Test.@test isabstracttype(Data.AbstractPseudoHamiltonianVectorField) end Test.@testset "Concrete types inherit from abstract types" begin Test.@test Data.VectorField <: Data.AbstractVectorField Test.@test Data.HamiltonianVectorField <: Data.AbstractHamiltonianVectorField + Test.@test Data.PseudoHamiltonianVectorField <: + Data.AbstractPseudoHamiltonianVectorField # Note: Hamiltonian is parametric (Hamiltonian{F, TD, VD} <: AbstractHamiltonian{TD, VD}) # Test via instance instead of type h = Data.Hamiltonian((x, p) -> 0.0) diff --git a/test/suite/data/test_pseudo_hamiltonian_vector_field.jl b/test/suite/data/test_pseudo_hamiltonian_vector_field.jl new file mode 100644 index 00000000..93f925e4 --- /dev/null +++ b/test/suite/data/test_pseudo_hamiltonian_vector_field.jl @@ -0,0 +1,534 @@ +module TestPseudoHamiltonianVectorField + +using Test: Test +import CTBase.Data: Data +import CTBase.Traits: Traits +import CTBase.Exceptions + +const VERBOSE = isdefined(Main, :TestData) ? Main.TestData.VERBOSE : true +const SHOWTIMING = isdefined(Main, :TestData) ? Main.TestData.SHOWTIMING : true + +# TOP-LEVEL: Fake function with multiple methods for testing +_multi_method_phvf(x::Int, p, u) = (p .* u, -x) +_multi_method_phvf(x::Float64, p, u) = (p .* u, -x) +_multi_method_phvf(x::AbstractVector, p, u) = (p .* u, -x) + +# TOP-LEVEL: Fake function with invalid arity for testing error branches +_bad_arity_phvf(x) = (x, -x) + +function test_pseudo_hamiltonian_vector_field() + Test.@testset "Pseudo-Hamiltonian Vector Field Tests" verbose=VERBOSE showtiming=SHOWTIMING begin + + # ==================================================================== + # UNIT TESTS - Construction + # ==================================================================== + + Test.@testset "Construction" begin + # Autonomous, Fixed + phvf_autonomous_fixed = Data.PseudoHamiltonianVectorField( + (x, p, u) -> (x .+ u, -p); is_autonomous=true, is_variable=false + ) + Test.@test phvf_autonomous_fixed isa Data.PseudoHamiltonianVectorField + Test.@test Traits.time_dependence(phvf_autonomous_fixed) == Traits.Autonomous + Test.@test Traits.variable_dependence(phvf_autonomous_fixed) == Traits.Fixed + + # NonAutonomous, Fixed + phvf_nonautonomous_fixed = Data.PseudoHamiltonianVectorField( + (t, x, p, u) -> (t .* x .+ u, -p); is_autonomous=false, is_variable=false + ) + Test.@test phvf_nonautonomous_fixed isa Data.PseudoHamiltonianVectorField + Test.@test Traits.time_dependence(phvf_nonautonomous_fixed) == + Traits.NonAutonomous + Test.@test Traits.variable_dependence(phvf_nonautonomous_fixed) == Traits.Fixed + + # Autonomous, NonFixed + phvf_autonomous_nonfixed = Data.PseudoHamiltonianVectorField( + (x, p, u, v) -> (x .* v .+ u, -p); is_autonomous=true, is_variable=true + ) + Test.@test phvf_autonomous_nonfixed isa Data.PseudoHamiltonianVectorField + Test.@test Traits.time_dependence(phvf_autonomous_nonfixed) == + Traits.Autonomous + Test.@test Traits.variable_dependence(phvf_autonomous_nonfixed) == + Traits.NonFixed + + # NonAutonomous, NonFixed + phvf_nonautonomous_nonfixed = Data.PseudoHamiltonianVectorField( + (t, x, p, u, v) -> (t .* x .* v .+ u, -p); + is_autonomous=false, + is_variable=true, + ) + Test.@test phvf_nonautonomous_nonfixed isa Data.PseudoHamiltonianVectorField + Test.@test Traits.time_dependence(phvf_nonautonomous_nonfixed) == + Traits.NonAutonomous + Test.@test Traits.variable_dependence(phvf_nonautonomous_nonfixed) == + Traits.NonFixed + end + + # ==================================================================== + # UNIT TESTS - Natural signatures + # ==================================================================== + + Test.@testset "Natural Signatures" begin + # (x, p, u) for Autonomous, Fixed + phvf1 = Data.PseudoHamiltonianVectorField( + (x, p, u) -> (x .+ u, -p); is_autonomous=true, is_variable=false + ) + dx, dp = phvf1([1.0, 2.0], [3.0, 4.0], [0.5, 0.5]) + Test.@test dx == [1.5, 2.5] + Test.@test dp == [-3.0, -4.0] + + # (t, x, p, u) for NonAutonomous, Fixed + phvf2 = Data.PseudoHamiltonianVectorField( + (t, x, p, u) -> (t .* x .+ u, -p); is_autonomous=false, is_variable=false + ) + dx, dp = phvf2(2.0, [1.0, 2.0], [3.0, 4.0], [0.5, 0.5]) + Test.@test dx == [2.5, 4.5] + Test.@test dp == [-3.0, -4.0] + + # (x, p, u, v) for Autonomous, NonFixed + phvf3 = Data.PseudoHamiltonianVectorField( + (x, p, u, v) -> (x .* v .+ u, -p); is_autonomous=true, is_variable=true + ) + dx, dp = phvf3([1.0, 2.0], [3.0, 4.0], [0.5, 0.5], 2.0) + Test.@test dx == [2.5, 4.5] + Test.@test dp == [-3.0, -4.0] + + # (t, x, p, u, v) for NonAutonomous, NonFixed + phvf4 = Data.PseudoHamiltonianVectorField( + (t, x, p, u, v) -> (t .* x .* v .+ u, -p); + is_autonomous=false, + is_variable=true, + ) + dx, dp = phvf4(2.0, [1.0, 2.0], [3.0, 4.0], [0.5, 0.5], 2.0) + Test.@test dx == [4.5, 8.5] + Test.@test dp == [-3.0, -4.0] + end + + # ==================================================================== + # UNIT TESTS - Uniform signature + # ==================================================================== + + Test.@testset "Uniform Signature" begin + phvf_autonomous_fixed = Data.PseudoHamiltonianVectorField( + (x, p, u) -> (x .+ u, -p); is_autonomous=true, is_variable=false + ) + dx, dp = phvf_autonomous_fixed(0.0, [1.0, 2.0], [3.0, 4.0], [0.5, 0.5], nothing) + Test.@test dx == [1.5, 2.5] + Test.@test dp == [-3.0, -4.0] + + phvf_nonautonomous_fixed = Data.PseudoHamiltonianVectorField( + (t, x, p, u) -> (t .* x .+ u, -p); is_autonomous=false, is_variable=false + ) + dx, dp = phvf_nonautonomous_fixed( + 2.0, [1.0, 2.0], [3.0, 4.0], [0.5, 0.5], nothing + ) + Test.@test dx == [2.5, 4.5] + Test.@test dp == [-3.0, -4.0] + + phvf_autonomous_nonfixed = Data.PseudoHamiltonianVectorField( + (x, p, u, v) -> (x .* v .+ u, -p); is_autonomous=true, is_variable=true + ) + dx, dp = phvf_autonomous_nonfixed(0.0, [1.0, 2.0], [3.0, 4.0], [0.5, 0.5], 2.0) + Test.@test dx == [2.5, 4.5] + Test.@test dp == [-3.0, -4.0] + + # Test variable_costate kwarg for NonFixed + dx, dp = phvf_autonomous_nonfixed( + 0.0, [1.0, 2.0], [3.0, 4.0], [0.5, 0.5], 2.0; variable_costate=false + ) + Test.@test dx == [2.5, 4.5] + Test.@test dp == [-3.0, -4.0] + end + + # ==================================================================== + # UNIT TESTS - Trait accessors + # ==================================================================== + + Test.@testset "Trait Accessors" begin + phvf = Data.PseudoHamiltonianVectorField( + (x, p, u) -> (x .+ u, -p); is_autonomous=true, is_variable=false + ) + Test.@test Traits.has_time_dependence_trait(phvf) == true + Test.@test Traits.has_variable_dependence_trait(phvf) == true + Test.@test Traits.time_dependence(phvf) == Traits.Autonomous + Test.@test Traits.variable_dependence(phvf) == Traits.Fixed + end + + # ==================================================================== + # UNIT TESTS - Typed constructor (trait types passed positionally) + # ==================================================================== + + Test.@testset "Typed constructor" begin + phvf = Data.PseudoHamiltonianVectorField( + (x, p, u) -> (x .+ u, -p), + Traits.Autonomous, + Traits.Fixed, + Traits.OutOfPlace, + ) + Test.@test phvf isa Data.PseudoHamiltonianVectorField + Test.@test Traits.time_dependence(phvf) === Traits.Autonomous + Test.@test Traits.variable_dependence(phvf) === Traits.Fixed + Test.@test Traits.mutability(phvf) === Traits.OutOfPlace + dx, dp = phvf([1.0, 2.0], [3.0, 4.0], [0.5, 0.5]) + Test.@test dx == [1.5, 2.5] + Test.@test dp == [-3.0, -4.0] + end + + # ==================================================================== + # UNIT TESTS - Subtyping + # ==================================================================== + + Test.@testset "Subtyping" begin + Test.@testset "PseudoHamiltonianVectorField is an AbstractVectorField" begin + phvf = Data.PseudoHamiltonianVectorField( + (x, p, u) -> (x .+ u, -p); is_autonomous=true, is_variable=false + ) + Test.@test phvf isa Data.AbstractVectorField + end + end + + # ==================================================================== + # UNIT TESTS - Base.show + # ==================================================================== + + Test.@testset "Base.show" begin + phvf = Data.PseudoHamiltonianVectorField( + (x, p, u) -> (x .+ u, -p); is_autonomous=true, is_variable=false + ) + # Just check that show doesn't throw + Test.@test_nowarn sprint(show, phvf) + end + + # ==================================================================== + # UNIT TESTS - Explicit is_inplace parameter + # ==================================================================== + + Test.@testset "Explicit is_inplace parameter" begin + Test.@testset "is_inplace=true creates InPlace PseudoHamiltonianVectorField" begin + # Define an out-of-place function but force InPlace + f(x, p, u) = (x .+ u, -p) + phvf = Data.PseudoHamiltonianVectorField(f; is_inplace=true) + Test.@test Traits.mutability(phvf) === Traits.InPlace + end + + Test.@testset "is_inplace=false creates OutOfPlace PseudoHamiltonianVectorField" begin + # Define an in-place function but force OutOfPlace + f(x, p, u) = (x .+ u, -p) + phvf = Data.PseudoHamiltonianVectorField(f; is_inplace=false) + Test.@test Traits.mutability(phvf) === Traits.OutOfPlace + end + end + + # ==================================================================== + # UNIT TESTS - PreconditionError for multiple methods + # ==================================================================== + + Test.@testset "PreconditionError for multiple methods" begin + Test.@testset "Throws PreconditionError when is_inplace is not specified" begin + Test.@test_throws Exceptions.PreconditionError Data.PseudoHamiltonianVectorField( + _multi_method_phvf + ) + end + + Test.@testset "No error when is_inplace is explicitly specified" begin + phvf = Data.PseudoHamiltonianVectorField( + _multi_method_phvf; is_inplace=false + ) + Test.@test Traits.mutability(phvf) === Traits.OutOfPlace + end + end + + # ==================================================================== + # UNIT TESTS - Internal Helpers + # ==================================================================== + + Test.@testset "Internal Helpers" begin + Test.@testset "_oop_arity_phvf" begin + Test.@test Data._oop_arity_phvf(Traits.Autonomous, Traits.Fixed) == 3 + Test.@test Data._oop_arity_phvf(Traits.NonAutonomous, Traits.Fixed) == 4 + Test.@test Data._oop_arity_phvf(Traits.Autonomous, Traits.NonFixed) == 4 + Test.@test Data._oop_arity_phvf(Traits.NonAutonomous, Traits.NonFixed) == 5 + end + + Test.@testset "_natural_sig_phvf helpers" begin + Test.@test Data._natural_sig_phvf( + Traits.Autonomous, Traits.Fixed, Traits.OutOfPlace + ) == "f(x, p, u)" + Test.@test Data._natural_sig_phvf( + Traits.NonAutonomous, Traits.Fixed, Traits.OutOfPlace + ) == "f(t, x, p, u)" + Test.@test Data._natural_sig_phvf( + Traits.Autonomous, Traits.Fixed, Traits.InPlace + ) == "f(dx, dp, x, p, u)" + Test.@test Data._uniform_sig_phvf(Traits.OutOfPlace) == "f(t, x, p, u, v)" + Test.@test Data._uniform_sig_phvf(Traits.InPlace) == + "f(dx, dp, t, x, p, u, v)" + end + + Test.@testset "_detect_mutability_phvf invalid arity" begin + Test.@test_throws Exceptions.IncorrectArgument Data._detect_mutability_phvf( + _bad_arity_phvf, Traits.Autonomous, Traits.Fixed + ) + end + end + + # ==================================================================== + # UNIT TESTS - InPlace Call Signatures + # ==================================================================== + + Test.@testset "InPlace Call Signatures" begin + Test.@testset "Autonomous Fixed - (dx, dp, x, p, u)" begin + f(dx, dp, x, p, u) = (dx.=(-x .+ u); dp.=(-p)) + phvf = Data.PseudoHamiltonianVectorField( + f; is_autonomous=true, is_variable=false, is_inplace=true + ) + + Test.@testset "scalar" begin + dx = [0.0] + dp = [0.0] + phvf(dx, dp, 3.0, 1.0, 0.5) + Test.@test dx[1] == -2.5 + Test.@test dp[1] == -1.0 + end + + Test.@testset "vector" begin + dx = [0.0, 0.0] + dp = [0.0, 0.0] + phvf(dx, dp, [1.0, 2.0], [0.5, 1.0], [0.1, 0.1]) + Test.@test dx == [-0.9, -1.9] + Test.@test dp == [-0.5, -1.0] + end + end + + Test.@testset "NonAutonomous Fixed - (dx, dp, t, x, p, u)" begin + f(dx, dp, t, x, p, u) = (dx.=t .* x .+ u; dp.=t .* p) + phvf = Data.PseudoHamiltonianVectorField( + f; is_autonomous=false, is_variable=false, is_inplace=true + ) + + Test.@testset "scalar" begin + dx = [0.0] + dp = [0.0] + phvf(dx, dp, 2.0, 3.0, 1.0, 0.5) + Test.@test dx[1] == 6.5 + Test.@test dp[1] == 2.0 + end + + Test.@testset "vector" begin + dx = [0.0, 0.0] + dp = [0.0, 0.0] + phvf(dx, dp, 2.0, [1.0, 2.0], [0.5, 1.0], [0.1, 0.1]) + Test.@test dx == [2.1, 4.1] + Test.@test dp == [1.0, 2.0] + end + end + + Test.@testset "Autonomous NonFixed - (dx, dp, x, p, u, v)" begin + f(dx, dp, x, p, u, v) = (dx.=x .+ v .+ u; dp.=p .+ v) + phvf = Data.PseudoHamiltonianVectorField( + f; is_autonomous=true, is_variable=true, is_inplace=true + ) + + Test.@testset "scalar" begin + dx = [0.0] + dp = [0.0] + phvf(dx, dp, 3.0, 1.0, 0.5, 0.5) + Test.@test dx[1] == 4.0 + Test.@test dp[1] == 1.5 + end + + Test.@testset "vector" begin + dx = [0.0, 0.0] + dp = [0.0, 0.0] + phvf(dx, dp, [1.0, 2.0], [0.5, 1.0], [0.1, 0.1], 0.5) + Test.@test dx == [1.6, 2.6] + Test.@test dp == [1.0, 1.5] + end + end + + Test.@testset "NonAutonomous NonFixed - (dx, dp, t, x, p, u, v)" begin + f(dx, dp, t, x, p, u, v) = (dx.=t .* x .+ v .+ u; dp.=t .* p .+ v) + phvf = Data.PseudoHamiltonianVectorField( + f; is_autonomous=false, is_variable=true, is_inplace=true + ) + + Test.@testset "scalar" begin + dx = [0.0] + dp = [0.0] + phvf(dx, dp, 2.0, 3.0, 1.0, 0.5, 0.5) + Test.@test dx[1] == 7.0 + Test.@test dp[1] == 2.5 + end + + Test.@testset "vector" begin + dx = [0.0, 0.0] + dp = [0.0, 0.0] + phvf(dx, dp, 2.0, [1.0, 2.0], [0.5, 1.0], [0.1, 0.1], 0.5) + Test.@test dx == [2.6, 4.6] + Test.@test dp == [1.5, 2.5] + end + end + end + + # ==================================================================== + # UNIT TESTS - Uniform InPlace Call Signature + # ==================================================================== + + Test.@testset "Uniform InPlace Signature" begin + Test.@testset "Autonomous Fixed InPlace uniform" begin + f(dx, dp, x, p, u) = (dx.=(-x .+ u); dp.=(-p)) + phvf = Data.PseudoHamiltonianVectorField( + f; is_autonomous=true, is_variable=false, is_inplace=true + ) + + dx = [0.0, 0.0] + dp = [0.0, 0.0] + phvf(dx, dp, 0.0, [1.0, 2.0], [0.5, 1.0], [0.1, 0.1], 0.0) + Test.@test dx == [-0.9, -1.9] + Test.@test dp == [-0.5, -1.0] + end + + Test.@testset "NonAutonomous Fixed InPlace uniform" begin + f(dx, dp, t, x, p, u) = (dx.=t .* x .+ u; dp.=t .* p) + phvf = Data.PseudoHamiltonianVectorField( + f; is_autonomous=false, is_variable=false, is_inplace=true + ) + + dx = [0.0, 0.0] + dp = [0.0, 0.0] + phvf(dx, dp, 2.0, [1.0, 2.0], [0.5, 1.0], [0.1, 0.1], 0.0) + Test.@test dx == [2.1, 4.1] + Test.@test dp == [1.0, 2.0] + end + + Test.@testset "Autonomous NonFixed InPlace uniform" begin + f(dx, dp, x, p, u, v) = (dx.=x .+ v .+ u; dp.=p .+ v) + phvf = Data.PseudoHamiltonianVectorField( + f; is_autonomous=true, is_variable=true, is_inplace=true + ) + + dx = [0.0, 0.0] + dp = [0.0, 0.0] + phvf(dx, dp, 0.0, [1.0, 2.0], [0.5, 1.0], [0.1, 0.1], 0.5) + Test.@test dx == [1.6, 2.6] + Test.@test dp == [1.0, 1.5] + + # Test variable_costate kwarg + dx = [0.0, 0.0] + dp = [0.0, 0.0] + phvf(dx, dp, 0.0, [1.0, 2.0], [0.5, 1.0], [0.1, 0.1], 0.5; variable_costate=false) + Test.@test dx == [1.6, 2.6] + Test.@test dp == [1.0, 1.5] + end + end + + Test.@testset "Show Methods" begin + phvf = Data.PseudoHamiltonianVectorField( + (x, p, u) -> (x .+ u, -p); is_autonomous=true, is_variable=false + ) + + Test.@testset "Base.show (compact)" begin + io = IOBuffer() + show(io, phvf) + str = String(take!(io)) + Test.@test occursin("PseudoHamiltonianVectorField", str) + Test.@test occursin("autonomous", str) + Test.@test occursin("fixed (no variable)", str) + Test.@test occursin("out-of-place", str) + Test.@test occursin("natural call", str) + Test.@test occursin("uniform call", str) + end + + Test.@testset "Base.show (text/plain)" begin + io = IOBuffer() + show(io, MIME("text/plain"), phvf) + str = String(take!(io)) + Test.@test occursin("PseudoHamiltonianVectorField", str) + Test.@test occursin("autonomous", str) + Test.@test occursin("fixed (no variable)", str) + end + end + + # ==================================================================== + # UNIT TESTS - variable_costate kwarg on user-built PHVF + # ==================================================================== + + Test.@testset "variable_costate kwarg on user-built PHVF" begin + Test.@testset "OOP NonFixed: variable_costate=true on user-built PHVF throws PreconditionError" begin + f = (x, p, u, v) -> (p, -x) # no variable_costate kwarg + phvf = Data.PseudoHamiltonianVectorField( + f; is_autonomous=true, is_variable=true + ) + x = [1.0]; + p = [0.5]; + u = [0.2]; + v = [0.1] + Test.@test_throws Exception phvf(x, p, u, v; variable_costate=true) + end + + Test.@testset "IP NonFixed: variable_costate=true on user-built PHVF throws PreconditionError" begin + f = (dx, dp, x, p, u, v) -> (dx.=p; dp.=(.-x); nothing) + phvf = Data.PseudoHamiltonianVectorField( + f; is_autonomous=true, is_variable=true, is_inplace=true + ) + x = [1.0]; + p = [0.5]; + u = [0.2]; + v = [0.1] + dx = similar(x); + dp = similar(p) + Test.@test_throws Exception phvf(dx, dp, x, p, u, v; variable_costate=true) + end + + Test.@testset "OOP NonAutonomous NonFixed: variable_costate=true on user-built PHVF throws PreconditionError" begin + f = (t, x, p, u, v) -> (p, -x) # no variable_costate kwarg + phvf = Data.PseudoHamiltonianVectorField( + f; is_autonomous=false, is_variable=true + ) + t = 0.5; + x = [1.0]; + p = [0.5]; + u = [0.2]; + v = [0.1] + Test.@test_throws Exception phvf(t, x, p, u, v; variable_costate=true) + end + + Test.@testset "IP NonAutonomous NonFixed: variable_costate=true on user-built PHVF throws PreconditionError" begin + f = (dx, dp, t, x, p, u, v) -> (dx.=p; dp.=(.-x); nothing) + phvf = Data.PseudoHamiltonianVectorField( + f; is_autonomous=false, is_variable=true, is_inplace=true + ) + t = 0.5; + x = [1.0]; + p = [0.5]; + u = [0.2]; + v = [0.1] + dx = similar(x); + dp = similar(p) + Test.@test_throws Exception phvf( + dx, dp, t, x, p, u, v; variable_costate=true + ) + end + + Test.@testset "OOP NonFixed: variable_costate=false (default) on user-built PHVF works" begin + f = (x, p, u, v) -> (p, -x) + phvf = Data.PseudoHamiltonianVectorField( + f; is_autonomous=true, is_variable=true + ) + x = [1.0]; + p = [0.5]; + u = [0.2]; + v = [0.1] + result = phvf(x, p, u, v) + Test.@test result == (p, -x) + end + end + end +end + +end # module + +# CRITICAL: Redefine in outer scope for TestRunner +function test_pseudo_hamiltonian_vector_field() + return TestPseudoHamiltonianVectorField.test_pseudo_hamiltonian_vector_field() +end diff --git a/test/suite/meta/test_data_subtyping.jl b/test/suite/meta/test_data_subtyping.jl index 9901533e..a1a39327 100644 --- a/test/suite/meta/test_data_subtyping.jl +++ b/test/suite/meta/test_data_subtyping.jl @@ -41,6 +41,9 @@ function test_data_subtyping() Test.@test Data.HamiltonianVectorField <: Data.AbstractHamiltonianVectorField Test.@test Data.ComposedVectorField <: Data.AbstractVectorField Test.@test Data.ComposedHamiltonian <: Data.AbstractHamiltonian + Test.@test Data.PseudoHamiltonianVectorField <: + Data.AbstractPseudoHamiltonianVectorField + Test.@test Data.AbstractPseudoHamiltonianVectorField <: Data.AbstractVectorField end end end