From a306d212d83c04df08c73ba4e4eb2434b73b649f Mon Sep 17 00:00:00 2001 From: Olivier Cots Date: Thu, 25 Jun 2026 00:24:25 +0200 Subject: [PATCH 1/8] Add CTBase.Data submodule (moved from CTFlows) Moves the Data module (VectorField, Hamiltonian, HamiltonianVectorField and their abstract supertypes) from CTFlows into CTBase so it can be shared across the ecosystem. The three construction defaults (__is_autonomous, __is_variable, __is_inplace) are also moved here (Data/default.jl) so CTBase.Data is fully self-contained (depends only on CTBase.Traits and CTBase.Exceptions). Co-Authored-By: Claude Sonnet 4.6 --- Project.toml | 2 +- src/CTBase.jl | 4 + src/Data/Data.jl | 40 +++ src/Data/abstract_hamiltonian.jl | 100 ++++++ src/Data/abstract_hamiltonian_vector_field.jl | 49 +++ src/Data/abstract_vector_field.jl | 171 ++++++++++ src/Data/default.jl | 40 +++ src/Data/hamiltonian.jl | 202 +++++++++++ src/Data/hamiltonian_vector_field.jl | 319 ++++++++++++++++++ src/Data/helpers.jl | 258 ++++++++++++++ src/Data/vector_field.jl | 278 +++++++++++++++ 11 files changed, 1462 insertions(+), 1 deletion(-) create mode 100644 src/Data/Data.jl create mode 100644 src/Data/abstract_hamiltonian.jl create mode 100644 src/Data/abstract_hamiltonian_vector_field.jl create mode 100644 src/Data/abstract_vector_field.jl create mode 100644 src/Data/default.jl create mode 100644 src/Data/hamiltonian.jl create mode 100644 src/Data/hamiltonian_vector_field.jl create mode 100644 src/Data/helpers.jl create mode 100644 src/Data/vector_field.jl diff --git a/Project.toml b/Project.toml index ec82be17..6ce69e9c 100644 --- a/Project.toml +++ b/Project.toml @@ -1,6 +1,6 @@ name = "CTBase" uuid = "54762871-cc72-4466-b8e8-f6c8b58076cd" -version = "0.22.0-beta" +version = "0.23.0-beta" authors = ["Olivier Cots ", "Jean-Baptiste Caillau "] [deps] diff --git a/src/CTBase.jl b/src/CTBase.jl index 61e7e79a..8a31d0cd 100644 --- a/src/CTBase.jl +++ b/src/CTBase.jl @@ -36,6 +36,10 @@ using .Orchestration include(joinpath(@__DIR__, "Traits", "Traits.jl")) using .Traits +# Data module - vector fields and Hamiltonians with traits (moved from CTFlows) +include(joinpath(@__DIR__, "Data", "Data.jl")) +using .Data + # Unicode module - Unicode character utilities include(joinpath(@__DIR__, "Unicode", "Unicode.jl")) using .Unicode diff --git a/src/Data/Data.jl b/src/Data/Data.jl new file mode 100644 index 00000000..20c2ab5a --- /dev/null +++ b/src/Data/Data.jl @@ -0,0 +1,40 @@ +""" + Data + +Data structures including vector fields and Hamiltonian vector fields with traits. + +This module defines the `VectorField` and `HamiltonianVectorField` types which encapsulate +vector-field functions together with their time-dependence and variable-dependence traits. +""" +module Data + +# 1. External-package imports (qualified, pollution-free) +import DocStringExtensions: TYPEDEF, TYPEDSIGNATURES +import CTBase.Exceptions +import CTBase.Traits + +# ============================================================================== +# Include files +# ============================================================================== + +include(joinpath(@__DIR__, "default.jl")) +include(joinpath(@__DIR__, "helpers.jl")) +include(joinpath(@__DIR__, "abstract_vector_field.jl")) +include(joinpath(@__DIR__, "vector_field.jl")) +include(joinpath(@__DIR__, "abstract_hamiltonian.jl")) +include(joinpath(@__DIR__, "hamiltonian.jl")) +include(joinpath(@__DIR__, "abstract_hamiltonian_vector_field.jl")) +include(joinpath(@__DIR__, "hamiltonian_vector_field.jl")) + +# ============================================================================== +# Module exports +# ============================================================================== + +export AbstractVectorField +export VectorField +export HamiltonianVectorField +export AbstractHamiltonianVectorField +export AbstractHamiltonian +export Hamiltonian + +end # module Data diff --git a/src/Data/abstract_hamiltonian.jl b/src/Data/abstract_hamiltonian.jl new file mode 100644 index 00000000..1a29e357 --- /dev/null +++ b/src/Data/abstract_hamiltonian.jl @@ -0,0 +1,100 @@ +""" +$(TYPEDEF) + +Abstract supertype for scalar Hamiltonian functions together with their +time-dependence and variable-dependence traits. + +A Hamiltonian is a scalar function `H(t, x, p[, v]) → ℝ` from which a +Hamiltonian vector field can be derived via automatic differentiation. +Unlike vector fields, a Hamiltonian has no mutability trait (in-place vs +out-of-place) because a scalar return has no meaningful in-place form. + +# Type Parameters +- `TD <: TimeDependence`: `Autonomous` or `NonAutonomous`. +- `VD <: VariableDependence`: `Fixed` or `NonFixed`. + +# Notes +- All Hamiltonian types support both natural and uniform call signatures. +- The uniform signature `(t, x, p, v)` is used internally by systems. + +See also: [`CTBase.Data.Hamiltonian`](@ref), [`CTBase.Traits.TimeDependence`](@ref), [`CTBase.Traits.VariableDependence`](@ref). +""" +abstract type AbstractHamiltonian{ + TD <: Traits.TimeDependence, + VD <: Traits.VariableDependence +} end + +# ============================================================================= +# Trait accessors for AbstractHamiltonian +# ============================================================================= + +""" +$(TYPEDSIGNATURES) + +Indicates that all `AbstractHamiltonian` types support time-dependence queries. + +# Returns +- `true`: Always returns `true` for Hamiltonian types. + +See also: [`CTBase.Traits.time_dependence`](@ref), [`CTBase.Data.AbstractHamiltonian`](@ref). +""" +function Traits.has_time_dependence_trait(::AbstractHamiltonian) + return true +end + +""" +$(TYPEDSIGNATURES) + +Indicates that all `AbstractHamiltonian` types support variable-dependence queries. + +# Returns +- `true`: Always returns `true` for Hamiltonian types. + +See also: [`CTBase.Traits.variable_dependence`](@ref), [`CTBase.Data.AbstractHamiltonian`](@ref). +""" +function Traits.has_variable_dependence_trait(::AbstractHamiltonian) + return true +end + +""" +$(TYPEDSIGNATURES) + +Return the time-dependence trait of a Hamiltonian. + +# Arguments +- `h::AbstractHamiltonian`: The Hamiltonian object. + +# Returns +- `TD`: The time-dependence type (`Autonomous` or `NonAutonomous`). + +See also: [`CTBase.Traits.time_dependence`](@ref), [`CTBase.Traits.TimeDependence`](@ref). +""" +function Traits.time_dependence(::AbstractHamiltonian{TD, <:Traits.VariableDependence}) where {TD <: Traits.TimeDependence} + return TD +end + +""" +$(TYPEDSIGNATURES) + +Return the variable-dependence trait of a Hamiltonian. + +# Arguments +- `h::AbstractHamiltonian`: The Hamiltonian object. + +# Returns +- `VD`: The variable-dependence type (`Fixed` or `NonFixed`). + +See also: [`CTBase.Traits.variable_dependence`](@ref), [`CTBase.Traits.VariableDependence`](@ref). +""" +function Traits.variable_dependence(::AbstractHamiltonian{<:Traits.TimeDependence, VD}) where {VD <: Traits.VariableDependence} + return VD +end + +""" +$(TYPEDSIGNATURES) + +Return the dynamics trait of an `AbstractHamiltonian`, namely [`CTBase.Traits.HamiltonianDynamics`](@ref). + +See also: [`CTBase.Traits.dynamics_trait`](@ref), [`CTBase.Data.AbstractHamiltonian`](@ref). +""" +Traits.dynamics_trait(::AbstractHamiltonian) = Traits.HamiltonianDynamics diff --git a/src/Data/abstract_hamiltonian_vector_field.jl b/src/Data/abstract_hamiltonian_vector_field.jl new file mode 100644 index 00000000..83f54781 --- /dev/null +++ b/src/Data/abstract_hamiltonian_vector_field.jl @@ -0,0 +1,49 @@ +# ============================================================================= +# AbstractHamiltonianVectorField — Abstract type for Hamiltonian vector fields +# ============================================================================= + +""" +$(TYPEDEF) + +Abstract supertype for Hamiltonian vector fields. + +A Hamiltonian vector field represents the dynamics of a Hamiltonian system, +combining state and costate evolution according to Hamilton's equations. +It extends `AbstractVectorField` with the additional structure required +for Hamiltonian mechanics. + +# 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)` or `(x, p)` depending on time dependence +- Returns the combined state-costate derivative vector + +# Example +\`\`\`julia-repl +julia> using CTBase.Data + +julia> HamiltonianVectorField <: Data.AbstractHamiltonianVectorField +true +\`\`\` + +See also: [`CTBase.Data.AbstractVectorField`](@ref), [`CTBase.Data.HamiltonianVectorField`](@ref), [`CTBase.Data.Hamiltonian`](@ref). +""" +abstract type AbstractHamiltonianVectorField{ + TD <: Traits.TimeDependence, + VD <: Traits.VariableDependence, + MD <: Traits.AbstractMutabilityTrait +} <: AbstractVectorField{TD, VD, MD} end + +""" +$(TYPEDSIGNATURES) + +Return the dynamics trait of an `AbstractHamiltonianVectorField`, namely [`CTBase.Traits.HamiltonianDynamics`](@ref). + +See also: [`CTBase.Traits.dynamics_trait`](@ref), [`CTBase.Data.AbstractHamiltonianVectorField`](@ref). +""" +Traits.dynamics_trait(::AbstractHamiltonianVectorField) = Traits.HamiltonianDynamics diff --git a/src/Data/abstract_vector_field.jl b/src/Data/abstract_vector_field.jl new file mode 100644 index 00000000..a473ada1 --- /dev/null +++ b/src/Data/abstract_vector_field.jl @@ -0,0 +1,171 @@ +""" +$(TYPEDEF) + +Abstract type for all vector fields in the control-toolbox ecosystem. + +An `AbstractVectorField` represents a vector field function with time-dependence, +variable-dependence, and mutability traits encoded in the type parameters. + +# Contract + +All subtypes must have type parameters: +- `TD <: Traits.TimeDependence`: `Autonomous` or `NonAutonomous` +- `VD <: Traits.VariableDependence`: `Fixed` or `NonFixed` +- `MD <: Traits.AbstractMutabilityTrait`: `InPlace` or `OutOfPlace` + +Trait accessors are implemented at the abstract level and work for all subtypes. + +# Example + +\`\`\`julia +using CTBase.Data +using CTBase.Traits + +# Define a concrete vector field +struct MyVectorField{F, TD, VD, MD} <: AbstractVectorField{TD, VD, MD} + f::F +end + +# Trait accessors work automatically +vf = MyVectorField(x -> -x, Traits.Autonomous, Traits.Fixed, Traits.OutOfPlace) +Traits.time_dependence(vf) # Returns Autonomous +Traits.variable_dependence(vf) # Returns Fixed +Traits.mutability(vf) # Returns OutOfPlace +\`\`\` + +See also: [`CTBase.Data.VectorField`](@ref), [`CTBase.Data.HamiltonianVectorField`](@ref), [`CTBase.Traits.time_dependence`](@ref), [`CTBase.Traits.variable_dependence`](@ref), [`CTBase.Traits.mutability`](@ref). +""" +abstract type AbstractVectorField{TD<:Traits.TimeDependence, VD<:Traits.VariableDependence, MD<:Traits.AbstractMutabilityTrait} end + +# ============================================================================= +# Trait accessors for AbstractVectorField +# ============================================================================= + +""" +$(TYPEDSIGNATURES) + +Indicate that `AbstractVectorField` has the time-dependence trait. + +This implementation declares that all vector fields support time-dependence queries. +Concrete `AbstractVectorField` instances have their time dependence encoded in the type parameter `TD`. + +See also: [`CTBase.Traits.time_dependence`](@ref), [`CTBase.Data.AbstractVectorField`](@ref). +""" +function Traits.has_time_dependence_trait(::AbstractVectorField) + return true +end + +""" +$(TYPEDSIGNATURES) + +Indicate that `AbstractVectorField` has the variable-dependence trait. + +This implementation declares that all vector fields support variable-dependence queries. +Concrete `AbstractVectorField` instances have their variable dependence encoded in the type parameter `VD`. + +See also: [`CTBase.Traits.variable_dependence`](@ref), [`CTBase.Data.AbstractVectorField`](@ref). +""" +function Traits.has_variable_dependence_trait(::AbstractVectorField) + return true +end + +""" +$(TYPEDSIGNATURES) + +Indicate that `AbstractVectorField` has the mutability trait. + +This implementation declares that all vector fields support mutability queries. +Concrete `AbstractVectorField` instances have their mutability encoded in the type parameter `MD`. + +See also: [`CTBase.Traits.mutability`](@ref), [`CTBase.Data.AbstractVectorField`](@ref). +""" +function Traits.has_mutability_trait(::AbstractVectorField) + return true +end + +""" +$(TYPEDSIGNATURES) + +Extract the time dependence trait from an AbstractVectorField. + +# Returns +- `Type{<:TimeDependence}`: The time dependence trait type (Autonomous or NonAutonomous). + +# Example +\`\`\`julia +using CTBase.Data +using CTBase.Traits + +vf = Data.VectorField(x -> -x; is_autonomous=true) +Traits.time_dependence(vf) # Returns Autonomous + +hvf = Data.HamiltonianVectorField((t, x, p) -> (x, -p); is_autonomous=false) +Traits.time_dependence(hvf) # Returns NonAutonomous +\`\`\` + +See also: [`CTBase.Traits.has_time_dependence_trait`](@ref), `is_autonomous`. +""" +function Traits.time_dependence(vf::AbstractVectorField{TD, <:Traits.VariableDependence, <:Traits.AbstractMutabilityTrait}) where {TD <: Traits.TimeDependence} + return TD +end + +""" +$(TYPEDSIGNATURES) + +Extract the variable dependence trait from an AbstractVectorField. + +# Returns +- `Type{<:VariableDependence}`: The variable dependence trait type (Fixed or NonFixed). + +# Example +\`\`\`julia +using CTBase.Data +using CTBase.Traits + +vf = Data.VectorField(x -> -x; is_variable=false) +Traits.variable_dependence(vf) # Returns Fixed + +hvf = Data.HamiltonianVectorField((x, p, v) -> (x .* v, -p); is_variable=true) +Traits.variable_dependence(hvf) # Returns NonFixed +\`\`\` + +See also: [`CTBase.Traits.has_variable_dependence_trait`](@ref), `is_variable`. +""" +function Traits.variable_dependence(vf::AbstractVectorField{<:Traits.TimeDependence, VD, <:Traits.AbstractMutabilityTrait}) where {VD <: Traits.VariableDependence} + return VD +end + +""" +$(TYPEDSIGNATURES) + +Extract the mutability trait from an AbstractVectorField. + +# Returns +- `Type{<:AbstractMutabilityTrait}`: The mutability trait type (InPlace or OutOfPlace). + +# Example +\`\`\`julia +using CTBase.Data +using CTBase.Traits + +vf = Data.VectorField((dx, x) -> (dx .= -x; nothing)) +Traits.mutability(vf) # Returns InPlace + +vf2 = Data.VectorField(x -> -x) +Traits.mutability(vf2) # Returns OutOfPlace +\`\`\` + +See also: [`CTBase.Traits.has_mutability_trait`](@ref), [`CTBase.Traits.is_inplace`](@ref). +""" +function Traits.mutability(vf::AbstractVectorField{<:Traits.TimeDependence, <:Traits.VariableDependence, MD}) where {MD <: Traits.AbstractMutabilityTrait} + return MD +end + +""" +$(TYPEDSIGNATURES) + +Return the dynamics trait of an `AbstractVectorField`, namely [`CTBase.Traits.StateDynamics`](@ref). + +See also: [`CTBase.Traits.dynamics_trait`](@ref), [`CTBase.Data.AbstractVectorField`](@ref). +""" +Traits.dynamics_trait(::AbstractVectorField) = Traits.StateDynamics diff --git a/src/Data/default.jl b/src/Data/default.jl new file mode 100644 index 00000000..0280f785 --- /dev/null +++ b/src/Data/default.jl @@ -0,0 +1,40 @@ +# ============================================================================= +# Default values for trait-carrying object constructors (VectorField, +# Hamiltonian, HamiltonianVectorField). Moved from CTFlows.Common so that +# CTBase.Data is self-contained. +# ============================================================================= + +""" +$(TYPEDSIGNATURES) + +Default value for the autonomous flag in time-dependent object constructors. + +Returns `true` by default, meaning objects do not explicitly depend on time +unless specified otherwise. +""" +__is_autonomous()::Bool = true + +""" +$(TYPEDSIGNATURES) + +Default value for the variable flag in time-dependent object constructors. + +Returns `false` by default, meaning objects have fixed parameters unless +specified otherwise. +""" +__is_variable()::Bool = false + +""" +$(TYPEDSIGNATURES) + +Default value for the in-place flag in vector field constructors. + +Returns `nothing` by default, meaning mutability is auto-detected from the +function signature unless specified otherwise. + +# Returns +- `Nothing`: The default value for the `is_inplace` parameter. + +See also: [`CTBase.Data.VectorField`](@ref), [`CTBase.Data.HamiltonianVectorField`](@ref). +""" +__is_inplace() = nothing diff --git a/src/Data/hamiltonian.jl b/src/Data/hamiltonian.jl new file mode 100644 index 00000000..aec42c28 --- /dev/null +++ b/src/Data/hamiltonian.jl @@ -0,0 +1,202 @@ +# ============================================================================= +# Concrete Hamiltonian type +# ============================================================================= + +""" +$(TYPEDEF) + +Parametric container for a scalar Hamiltonian function together with its +time-dependence and variable-dependence traits. + +The function returns a scalar value `H(t, x, p[, v]) → ℝ` representing the +Hamiltonian of the system. + +# Type Parameters +- `F`: concrete type of the wrapped function. +- `TD <: TimeDependence`: `Autonomous` or `NonAutonomous`. +- `VD <: VariableDependence`: `Fixed` or `NonFixed`. + +# Fields +- `f::F`: the Hamiltonian function. + +# Construction + +Use the keyword constructor: + +```julia +Hamiltonian(f; is_autonomous = true, is_variable = false) # default: h(x, p) +Hamiltonian((t, x, p) -> ...; is_autonomous = false) # h(t, x, p) +Hamiltonian((x, p, v) -> ...; is_variable = true) # h(x, p, v) +Hamiltonian((t, x, p, v) -> ...; is_autonomous = false, is_variable = true) +``` + +# Call Signatures + +Every `Hamiltonian` is callable via its **natural** signature (matching the +traits), and via a **uniform** signature `(t, x, p, v)` that ignores the +unused arguments. + +For Autonomous/Fixed: natural `h(x, p)`, uniform `h(t, x, p, v)`. +For NonAutonomous/Fixed: natural `h(t, x, p)`, uniform `h(t, x, p, v)`. +For Autonomous/NonFixed: natural `h(x, p, v)`, uniform `h(t, x, p, v)`. +For NonAutonomous/NonFixed: natural `h(t, x, p, v)`, uniform `h(t, x, p, v)`. + +# Example +```julia-repl +julia> using CTBase.Data + +julia> h = Hamiltonian((x, p) -> dot(x, p)) # Uses defaults: is_autonomous=true, is_variable=false +Hamiltonian: autonomous, fixed (no variable) + natural call: h(x, p) + uniform call: h(t, x, p, v) + +julia> h = Hamiltonian((t, x, p) -> t * dot(x, p); is_autonomous=false) +Hamiltonian: non-autonomous, fixed (no variable) + natural call: h(t, x, p) + uniform call: h(t, x, p, v) +``` + +See also: [`CTBase.Data.AbstractHamiltonian`](@ref), [`CTBase.Traits.TimeDependence`](@ref), [`CTBase.Traits.VariableDependence`](@ref). +""" +struct Hamiltonian{F<:Function, TD, VD} <: AbstractHamiltonian{TD, VD} + f::F +end + +# ============================================================================= +# Constructor +# ============================================================================= + +""" +$(TYPEDSIGNATURES) + +Construct a `Hamiltonian` with trait flags. + +# Arguments +- `f::Function`: The Hamiltonian function returning a scalar value. +- `is_autonomous::Bool`: If true, system is autonomous (default: `__is_autonomous()`). +- `is_variable::Bool`: If true, system depends on variable parameters (default: `__is_variable()`). + +# Returns +- `Hamiltonian`: A Hamiltonian with appropriate traits. + +# Example +```julia-repl +julia> using CTBase.Data + +julia> h = Hamiltonian((x, p) -> dot(x, p)) # Uses defaults: is_autonomous=true, is_variable=false +Hamiltonian: autonomous, fixed (no variable) + natural call: h(x, p) + uniform call: h(t, x, p, v) + +julia> h = Hamiltonian((t, x, p) -> t * dot(x, p); is_autonomous=false) +Hamiltonian: non-autonomous, fixed (no variable) + natural call: h(t, x, p) + uniform call: h(t, x, p, v) + +julia> h = Hamiltonian((x, p, v) -> v * dot(x, p); is_variable=true) +Hamiltonian: autonomous, non-fixed (variable) + natural call: h(x, p, v) + uniform call: h(t, x, p, v) +``` + +# Notes +- The default values for `is_autonomous` and `is_variable` come from `__is_autonomous()` and `__is_variable()`. +- The function signature should match the specified traits (e.g., if `is_autonomous=true` and `is_variable=false`, the function should accept `(x, p)`). + +See also: [`CTBase.Data.Hamiltonian`](@ref), [`CTBase.Traits.Autonomous`](@ref), [`CTBase.Traits.NonAutonomous`](@ref), [`CTBase.Traits.Fixed`](@ref), [`CTBase.Traits.NonFixed`](@ref). +""" +function Hamiltonian(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 Hamiltonian{typeof(f), TD, VD}(f) +end + +# ============================================================================= +# Typed constructor — calls struct inner constructor directly +# ============================================================================= + +function Hamiltonian( + f, + ::Type{TD}, ::Type{VD}, +) where { + TD <: Traits.TimeDependence, + VD <: Traits.VariableDependence, +} + return Hamiltonian{typeof(f), TD, VD}(f) +end + +# ============================================================================= +# Natural call signatures - one per trait combination +# ============================================================================= + +(H::Hamiltonian{<:Function, Traits.Autonomous, Traits.Fixed})(x, p) = H.f(x, p) +(H::Hamiltonian{<:Function, Traits.NonAutonomous, Traits.Fixed})(t, x, p) = H.f(t, x, p) +(H::Hamiltonian{<:Function, Traits.Autonomous, Traits.NonFixed})(x, p, v) = H.f(x, p, v) +(H::Hamiltonian{<:Function, Traits.NonAutonomous, Traits.NonFixed})(t, x, p, v) = H.f(t, x, p, v) + +# ============================================================================= +# Uniform (t, x, p, v) call - used by future HamiltonianSystem.rhs +# Every combination forwards to its natural call, ignoring unused args. +# (NonAutonomous, NonFixed) is already covered by the natural signature above. +# ============================================================================= + +(H::Hamiltonian{<:Function, Traits.Autonomous, Traits.Fixed})(_, x, p, _) = H.f(x, p) +(H::Hamiltonian{<:Function, Traits.NonAutonomous, Traits.Fixed})(t, x, p, _) = H.f(t, x, p) +(H::Hamiltonian{<:Function, Traits.Autonomous, Traits.NonFixed})(_, x, p, v) = H.f(x, p, v) + +# ============================================================================= +# Base.show +# ============================================================================= + +""" +$(TYPEDSIGNATURES) + +Display a compact representation of a Hamiltonian showing its traits and call signatures. + +# Arguments +- `io::IO`: The IO stream. +- `h::Hamiltonian`: The Hamiltonian object. + +# Output +Displays three lines: +- Header with time and variable dependence traits +- Natural call signature +- Uniform call signature + +# Example +```julia-repl +julia> h = Hamiltonian((x, p) -> dot(x, p)) +Hamiltonian: autonomous, fixed (no variable) + natural call: h(x, p) + uniform call: h(t, x, p, v) +``` +""" +function Base.show(io::IO, ::Hamiltonian{F, TD, VD}) where {F, TD, VD} + header = "Hamiltonian: $(_td_label(TD)), $(_vd_label(VD))" + natural = _natural_sig_h(TD, VD) + uniform = _uniform_sig_h() + println(io, header) + println(io, " natural call: ", natural) + print(io, " uniform call: ", uniform) +end + +""" +$(TYPEDSIGNATURES) + +Display a Hamiltonian in the REPL with the same format as the compact `show`. + +This method is called automatically when displaying a Hamiltonian in the Julia REPL. + +# Arguments +- `io::IO`: The IO stream. +- `mime::MIME"text/plain"`: The MIME type. +- `h::Hamiltonian`: The Hamiltonian object. + +See also: [`CTBase.Data.Hamiltonian`](@ref). +""" +function Base.show(io::IO, ::MIME"text/plain", h::Hamiltonian{F, TD, VD}) where {F, TD, VD} + show(io, h) +end diff --git a/src/Data/hamiltonian_vector_field.jl b/src/Data/hamiltonian_vector_field.jl new file mode 100644 index 00000000..b767d5b3 --- /dev/null +++ b/src/Data/hamiltonian_vector_field.jl @@ -0,0 +1,319 @@ +""" +$(TYPEDEF) + +Parametric container for a Hamiltonian vector field function together with its +time-dependence, variable-dependence, and mutability traits. + +The function returns a tuple `(dx, dp)` representing the derivatives of state `x` +and costate `p` according to Hamiltonian dynamics. + +# 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 Hamiltonian vector field function. + +# Construction + +Use the keyword constructor: + +```julia +HamiltonianVectorField(f; is_autonomous = true, is_variable = false) # default: f(x, p) +HamiltonianVectorField((t, x, p) -> ...; is_autonomous = false) # f(t, x, p) +HamiltonianVectorField((x, p, v) -> ...; is_variable = true) # f(x, p, v) +HamiltonianVectorField((t, x, p, v) -> ...; is_autonomous = false, is_variable = true) +``` + +The mutability trait (InPlace/OutOfPlace) is auto-detected from the function signature. + +# Call Signatures + +Every `HamiltonianVectorField` is callable via its **natural** signature (matching the +traits), and via a **uniform** signature `(t, x, p, v)` that ignores the +unused arguments. + +For InPlace Hamiltonian vector fields, the natural signature includes the derivative +buffers as the first two arguments (e.g., `(dx, dp, x, p)` for Autonomous/Fixed). + +See also: [`CTBase.Data.AbstractVectorField`](@ref), [`CTBase.Traits.TimeDependence`](@ref), [`CTBase.Traits.VariableDependence`](@ref), [`CTBase.Traits.AbstractMutabilityTrait`](@ref). +""" +struct HamiltonianVectorField{F<:Function, TD<:Traits.TimeDependence, VD<:Traits.VariableDependence, MD<:Traits.AbstractMutabilityTrait} <: AbstractHamiltonianVectorField{TD, VD, MD} + f::F +end + +# ============================================================================= +# Internal helpers for mutability detection +# ============================================================================= + +""" + _oop_arity_hvf(::Type{Traits.Autonomous}, ::Type{Traits.Fixed}) -> Int + +Return the out-of-place arity for Autonomous/Fixed Hamiltonian vector fields (2: x, p). +""" +_oop_arity_hvf(::Type{Traits.Autonomous}, ::Type{Traits.Fixed}) = 2 + +""" + _oop_arity_hvf(::Type{Traits.NonAutonomous}, ::Type{Traits.Fixed}) -> Int + +Return the out-of-place arity for NonAutonomous/Fixed Hamiltonian vector fields (3: t, x, p). +""" +_oop_arity_hvf(::Type{Traits.NonAutonomous}, ::Type{Traits.Fixed}) = 3 + +""" + _oop_arity_hvf(::Type{Traits.Autonomous}, ::Type{Traits.NonFixed}) -> Int + +Return the out-of-place arity for Autonomous/NonFixed Hamiltonian vector fields (3: x, p, v). +""" +_oop_arity_hvf(::Type{Traits.Autonomous}, ::Type{Traits.NonFixed}) = 3 + +""" + _oop_arity_hvf(::Type{Traits.NonAutonomous}, ::Type{Traits.NonFixed}) -> Int + +Return the out-of-place arity for NonAutonomous/NonFixed Hamiltonian vector fields (4: t, x, p, v). +""" +_oop_arity_hvf(::Type{Traits.NonAutonomous}, ::Type{Traits.NonFixed}) = 4 + +""" + _detect_mutability_hvf(f::Function, TD, VD) -> Type{<:AbstractMutabilityTrait} + +Detect the mutability trait from the Hamiltonian vector field function signature. + +Compares the function arity to the expected out-of-place arity and in-place arity +(arity + 2 for HamiltonianVectorField, 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 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 `HamiltonianVectorField` constructor when `is_inplace` is `nothing`. +- Users can bypass auto-detection by specifying `is_inplace=true` or `is_inplace=false` explicitly in the constructor. +- HamiltonianVectorField has two output buffers (dx, dp), so the in-place arity is `oop_arity + 2`. + +See also: [`CTBase.Data.HamiltonianVectorField`](@ref), [`CTBase.Traits.InPlace`](@ref), [`CTBase.Traits.OutOfPlace`](@ref). +""" +function _detect_mutability_hvf(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 = "HamiltonianVectorField mutability detection", + )) + end + + arity = first(methods(f)).nargs - 1 + oop_arity = _oop_arity_hvf(TD, VD) + ip_arity = oop_arity + 2 # HamiltonianVectorField 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 = "HamiltonianVectorField mutability detection", + )) + end +end + +""" +$(TYPEDSIGNATURES) + +Construct a `HamiltonianVectorField` with trait flags. + +# Arguments +- `f::Function`: The Hamiltonian vector field function returning `(dx, dp)`. +- `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 +- `HamiltonianVectorField`: A HamiltonianVectorField with appropriate traits. + +# Example +```julia-repl +julia> using CTBase.Data + +julia> hvf = HamiltonianVectorField((x, p) -> (x, -p)) # Uses defaults: is_autonomous=true, is_variable=false +HamiltonianVectorField: autonomous, fixed (no variable), out-of-place + natural call: f(x, p) + uniform call: f(t, x, p, v) + +julia> hvf = HamiltonianVectorField((t, x, p) -> (t .* x, -p); is_autonomous=false) +HamiltonianVectorField: non-autonomous, fixed (no variable), out-of-place + natural call: f(t, x, p) + uniform call: f(t, x, p, v) + +julia> hvf = HamiltonianVectorField((x, p) -> (x, -p); is_inplace=true) # Explicit in-place +HamiltonianVectorField: autonomous, fixed (no variable), in-place + natural call: f(dx, dp, x, p) + uniform call: f(dx, dp, t, x, p, 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.HamiltonianVectorField`](@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 HamiltonianVectorField(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_hvf(f, TD, VD) + else + is_inplace ? Traits.InPlace : Traits.OutOfPlace + end + return HamiltonianVectorField{typeof(f), TD, VD, MD}(f) +end + +# ============================================================================= +# Typed constructor — calls struct inner constructor directly +# ============================================================================= + +function HamiltonianVectorField( + f, + ::Type{TD}, ::Type{VD}, ::Type{MD}, +) where { + TD <: Traits.TimeDependence, + VD <: Traits.VariableDependence, + MD <: Traits.AbstractMutabilityTrait, +} + return HamiltonianVectorField{typeof(f), TD, VD, MD}(f) +end + +# ============================================================================= +# Natural call signatures - one per trait combination +# ============================================================================= + +# OutOfPlace signatures (existing) +(H::HamiltonianVectorField{<:Function, Traits.Autonomous, Traits.Fixed, Traits.OutOfPlace})(x, p) = H.f(x, p) +(H::HamiltonianVectorField{<:Function, Traits.NonAutonomous, Traits.Fixed, Traits.OutOfPlace})(t, x, p) = H.f(t, x, p) +function (H::HamiltonianVectorField{<:Function, Traits.Autonomous, Traits.NonFixed, Traits.OutOfPlace})(x, p, v; variable_costate::Bool=false) + variable_costate || return H.f(x, p, v) + hasmethod(H.f, Tuple{typeof(x), typeof(p), typeof(v)}, (:variable_costate,)) || + throw(Exceptions.PreconditionError( + "variable_costate=true is not supported by this HamiltonianVectorField's inner function"; + suggestion = "Use hamiltonian_vector_field(h; ...) to obtain a HVF that supports variable_costate", + context = "HamiltonianVectorField Autonomous/NonFixed call", + )) + return H.f(x, p, v; variable_costate=true) +end + +function (H::HamiltonianVectorField{<:Function, Traits.NonAutonomous, Traits.NonFixed, Traits.OutOfPlace})(t, x, p, v; variable_costate::Bool=false) + variable_costate || return H.f(t, x, p, v) + hasmethod(H.f, Tuple{typeof(t), typeof(x), typeof(p), typeof(v)}, (:variable_costate,)) || + throw(Exceptions.PreconditionError( + "variable_costate=true is not supported by this HamiltonianVectorField's inner function"; + suggestion = "Use hamiltonian_vector_field(h; ...) to obtain a HVF that supports variable_costate", + context = "HamiltonianVectorField NonAutonomous/NonFixed call", + )) + return H.f(t, x, p, v; variable_costate=true) +end + +# InPlace signatures (new) +(H::HamiltonianVectorField{<:Function, Traits.Autonomous, Traits.Fixed, Traits.InPlace})(dx, dp, x, p) = H.f(dx, dp, x, p) +(H::HamiltonianVectorField{<:Function, Traits.NonAutonomous, Traits.Fixed, Traits.InPlace})(dx, dp, t, x, p) = H.f(dx, dp, t, x, p) +function (H::HamiltonianVectorField{<:Function, Traits.Autonomous, Traits.NonFixed, Traits.InPlace})(dx, dp, x, p, v; dpv=nothing, variable_costate::Bool=false) + variable_costate || return H.f(dx, dp, x, p, v) + hasmethod(H.f, Tuple{typeof(dx), typeof(dp), typeof(x), typeof(p), typeof(v)}, (:variable_costate,)) || + throw(Exceptions.PreconditionError( + "variable_costate=true is not supported by this HamiltonianVectorField's inner function"; + suggestion = "Use hamiltonian_vector_field(h; inplace=true) to obtain a HVF that supports variable_costate", + context = "HamiltonianVectorField IP Autonomous/NonFixed call", + )) + return H.f(dx, dp, x, p, v; dpv=dpv, variable_costate=true) +end + +function (H::HamiltonianVectorField{<:Function, Traits.NonAutonomous, Traits.NonFixed, Traits.InPlace})(dx, dp, t, x, p, v; dpv=nothing, variable_costate::Bool=false) + variable_costate || return H.f(dx, dp, t, x, p, v) + hasmethod(H.f, Tuple{typeof(dx), typeof(dp), typeof(t), typeof(x), typeof(p), typeof(v)}, (:variable_costate,)) || + throw(Exceptions.PreconditionError( + "variable_costate=true is not supported by this HamiltonianVectorField's inner function"; + suggestion = "Use hamiltonian_vector_field(h; inplace=true) to obtain a HVF that supports variable_costate", + context = "HamiltonianVectorField IP NonAutonomous/NonFixed call", + )) + return H.f(dx, dp, t, x, p, v; dpv=dpv, variable_costate=true) +end + +# ============================================================================= +# Uniform (t, x, p, v) call - used by HamiltonianVectorFieldSystem.rhs +# Every combination forwards to its natural call, ignoring unused args. +# (NonAutonomous, NonFixed) is already covered by the natural signature above. +# ============================================================================= + +# OutOfPlace uniform signatures +(H::HamiltonianVectorField{<:Function, Traits.Autonomous, Traits.Fixed, Traits.OutOfPlace})(t, x, p, v; variable_costate::Bool=false) = H.f(x, p) +(H::HamiltonianVectorField{<:Function, Traits.NonAutonomous, Traits.Fixed, Traits.OutOfPlace})(t, x, p, v; variable_costate::Bool=false) = H.f(t, x, p) +(H::HamiltonianVectorField{<:Function, Traits.Autonomous, Traits.NonFixed, Traits.OutOfPlace})(t, x, p, v; variable_costate::Bool=false) = H(x, p, v; variable_costate=variable_costate) # delegate to natural signature + +# InPlace uniform signatures +(H::HamiltonianVectorField{<:Function, Traits.Autonomous, Traits.Fixed, Traits.InPlace})(dx, dp, t, x, p, v; variable_costate::Bool=false, dpv=nothing) = H.f(dx, dp, x, p) +(H::HamiltonianVectorField{<:Function, Traits.NonAutonomous, Traits.Fixed, Traits.InPlace})(dx, dp, t, x, p, v; variable_costate::Bool=false, dpv=nothing) = H.f(dx, dp, t, x, p) +(H::HamiltonianVectorField{<:Function, Traits.Autonomous, Traits.NonFixed, Traits.InPlace})(dx, dp, t, x, p, v; variable_costate::Bool=false, dpv=nothing) = H(dx, dp, x, p, v; dpv=dpv, variable_costate=variable_costate) # delegate to natural signature + +# ============================================================================= +# Base.show +# ============================================================================= + +""" +$(TYPEDSIGNATURES) + +Display a compact representation of a HamiltonianVectorField. + +Shows the type name, time dependence, variable dependence, mutability, and function type. + +# Arguments +- `io::IO`: The IO stream to write to. +- `hvf::HamiltonianVectorField`: The HamiltonianVectorField to display. + +See also: [`CTBase.Data.HamiltonianVectorField`](@ref). +""" +function Base.show(io::IO, ::HamiltonianVectorField{F, TD, VD, MD}) where {F, TD, VD, MD} + header = "HamiltonianVectorField: $(_td_label(TD)), $(_vd_label(VD)), $(_md_label(MD))" + natural = _natural_sig_hvf(TD, VD, MD) + uniform = _uniform_sig_hvf(MD) + println(io, header) + println(io, " natural call: ", natural) + print(io, " uniform call: ", uniform) +end + +""" +$(TYPEDSIGNATURES) + +Display a HamiltonianVectorField 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. +- `hvf::HamiltonianVectorField`: The HamiltonianVectorField to display. + +See also: [`CTBase.Data.HamiltonianVectorField`](@ref). +""" +function Base.show(io::IO, ::MIME"text/plain", hvf::HamiltonianVectorField{F, TD, VD, MD}) where {F, TD, VD, MD} + show(io, hvf) +end diff --git a/src/Data/helpers.jl b/src/Data/helpers.jl new file mode 100644 index 00000000..64a2a6ac --- /dev/null +++ b/src/Data/helpers.jl @@ -0,0 +1,258 @@ +""" +Internal helper functions for display formatting. + +This module provides helper functions for generating user-friendly display +representations of VectorField and HamiltonianVectorField types. +""" + +# ============================================================================= +# Shared label helpers +# ============================================================================= + +""" + _td_label(::Type{Traits.Autonomous}) -> String + _td_label(::Type{Traits.NonAutonomous}) -> String + +Return a user-friendly label for time dependence traits. + +# Arguments +- `TD`: Type parameter for time dependence (`Autonomous` or `NonAutonomous`) + +# Returns +- `String`: User-friendly label ("autonomous" or "non-autonomous") + +See also: [`_vd_label`](@ref), [`_md_label`](@ref). +""" +function _td_label(::Type{Traits.Autonomous}) + return "autonomous" +end +function _td_label(::Type{Traits.NonAutonomous}) + return "non-autonomous" +end + +""" + _vd_label(::Type{Traits.Fixed}) -> String + _vd_label(::Type{Traits.NonFixed}) -> String + +Return a user-friendly label for variable dependence traits. + +# Arguments +- `VD`: Type parameter for variable dependence (`Fixed` or `NonFixed`) + +# Returns +- `String`: User-friendly label ("fixed (no variable)" or "variable") + +See also: [`_td_label`](@ref), [`_md_label`](@ref). +""" +function _vd_label(::Type{Traits.Fixed}) + return "fixed (no variable)" +end +function _vd_label(::Type{Traits.NonFixed}) + return "variable" +end + +""" + _md_label(::Type{OutOfPlace}) -> String + _md_label(::Type{InPlace}) -> String + +Return a user-friendly label for mutability traits. + +# Arguments +- `MD`: Type parameter for mutability (`OutOfPlace` or `InPlace`) + +# Returns +- `String`: User-friendly label ("out-of-place" or "in-place") + +See also: [`_td_label`](@ref), [`_vd_label`](@ref). +""" +function _md_label(::Type{Traits.OutOfPlace}) + return "out-of-place" +end +function _md_label(::Type{Traits.InPlace}) + return "in-place" +end + +# ============================================================================= +# VectorField-specific signature helpers +# ============================================================================= + +""" + _natural_sig_vf(::Type{TD}, ::Type{VD}, ::Type{Traits.OutOfPlace}) where {TD, VD} -> String + _natural_sig_vf(::Type{TD}, ::Type{VD}, ::Type{Traits.InPlace}) where {TD, VD} -> String + +Return the natural call signature for a VectorField based on its traits. + +# Arguments +- `TD`: Time dependence type (`Autonomous` or `NonAutonomous`) +- `VD`: Variable dependence type (`Fixed` or `NonFixed`) +- `MD`: Mutability type (`Traits.OutOfPlace` or `Traits.InPlace`) + +# Returns +- `String`: Natural call signature (e.g., "f(x)", "f(t, x)", "f(dx, x)") + +# Example +\`\`\`julia +_natural_sig_vf(Traits.Autonomous, Traits.Fixed, Traits.OutOfPlace) # Returns "f(x)" +_natural_sig_vf(Traits.NonAutonomous, Traits.Fixed, Traits.OutOfPlace) # Returns "f(t, x)" +_natural_sig_vf(Traits.Autonomous, Traits.Fixed, Traits.InPlace) # Returns "f(dx, x)" +\`\`\` + +See also: [`_uniform_sig_vf`](@ref). +""" +function _natural_sig_vf(::Type{TD}, ::Type{VD}, ::Type{Traits.OutOfPlace}) where {TD, VD} + args = String[] + TD === Traits.NonAutonomous && push!(args, "t") + push!(args, "x") + VD === Traits.NonFixed && push!(args, "v") + return "f(" * join(args, ", ") * ")" +end + +function _natural_sig_vf(::Type{TD}, ::Type{VD}, ::Type{Traits.InPlace}) where {TD, VD} + args = ["dx"] + TD === Traits.NonAutonomous && push!(args, "t") + push!(args, "x") + VD === Traits.NonFixed && push!(args, "v") + return "f(" * join(args, ", ") * ")" +end + +""" + _uniform_sig_vf(::Type{Traits.OutOfPlace}) -> String + _uniform_sig_vf(::Type{Traits.InPlace}) -> String + +Return the uniform call signature for a VectorField. + +The uniform signature always includes all arguments (t, x, v) regardless of traits, +and includes the derivative buffer (dx) for in-place variants. + +# Arguments +- `MD`: Mutability type (`Traits.OutOfPlace` or `Traits.InPlace`) + +# Returns +- `String`: Uniform call signature ("f(t, x, v)" or "f(dx, t, x, v)") + +See also: [`_natural_sig_vf`](@ref). +""" +function _uniform_sig_vf(::Type{Traits.OutOfPlace}) + return "f(t, x, v)" +end +function _uniform_sig_vf(::Type{Traits.InPlace}) + return "f(dx, t, x, v)" +end + +# ============================================================================= +# HamiltonianVectorField-specific signature helpers +# ============================================================================= + +""" + _natural_sig_hvf(::Type{TD}, ::Type{VD}, ::Type{Traits.OutOfPlace}) where {TD, VD} -> String + _natural_sig_hvf(::Type{TD}, ::Type{VD}, ::Type{Traits.InPlace}) where {TD, VD} -> String + +Return the natural call signature for a HamiltonianVectorField based on its traits. + +# Arguments +- `TD`: Time dependence type (`Autonomous` or `NonAutonomous`) +- `VD`: Variable dependence type (`Fixed` or `NonFixed`) +- `MD`: Mutability type (`Traits.OutOfPlace` or `Traits.InPlace`) + +# Returns +- `String`: Natural call signature (e.g., "f(x, p)", "f(t, x, p)", "f(dx, dp, x, p)") + +# Example +\`\`\`julia +_natural_sig_hvf(Traits.Autonomous, Traits.Fixed, Traits.OutOfPlace) # Returns "f(x, p)" +_natural_sig_hvf(Traits.NonAutonomous, Traits.Fixed, Traits.OutOfPlace) # Returns "f(t, x, p)" +_natural_sig_hvf(Traits.Autonomous, Traits.Fixed, Traits.InPlace) # Returns "f(dx, dp, x, p)" +\`\`\` + +See also: [`_uniform_sig_hvf`](@ref). +""" +function _natural_sig_hvf(::Type{TD}, ::Type{VD}, ::Type{Traits.OutOfPlace}) where {TD, VD} + args = String[] + TD === Traits.NonAutonomous && push!(args, "t") + push!(args, "x") + push!(args, "p") + VD === Traits.NonFixed && push!(args, "v") + return "f(" * join(args, ", ") * ")" +end + +function _natural_sig_hvf(::Type{TD}, ::Type{VD}, ::Type{Traits.InPlace}) where {TD, VD} + args = ["dx", "dp"] + TD === Traits.NonAutonomous && push!(args, "t") + push!(args, "x") + push!(args, "p") + VD === Traits.NonFixed && push!(args, "v") + return "f(" * join(args, ", ") * ")" +end + +""" + _uniform_sig_hvf(::Type{Traits.OutOfPlace}) -> String + _uniform_sig_hvf(::Type{Traits.InPlace}) -> String + +Return the uniform call signature for a HamiltonianVectorField. + +The uniform signature always includes all arguments (t, x, p, v) regardless of traits, +and includes the derivative buffers (dx, dp) for in-place variants. + +# Arguments +- `MD`: Mutability type (`Traits.OutOfPlace` or `Traits.InPlace`) + +# Returns +- `String`: Uniform call signature ("f(t, x, p, v)" or "f(dx, dp, t, x, p, v)") + +See also: [`_natural_sig_hvf`](@ref). +""" +function _uniform_sig_hvf(::Type{Traits.OutOfPlace}) + return "f(t, x, p, v)" +end +function _uniform_sig_hvf(::Type{Traits.InPlace}) + return "f(dx, dp, t, x, p, v)" +end + +# ============================================================================= +# Hamiltonian-specific signature helpers +# ============================================================================= + +""" + _natural_sig_h(::Type{TD}, ::Type{VD}) where {TD, VD} -> String + +Return the natural call signature for a Hamiltonian based on its traits. + +# Arguments +- `TD`: Time dependence type (`Autonomous` or `NonAutonomous`) +- `VD`: Variable dependence type (`Fixed` or `NonFixed`) + +# Returns +- `String`: Natural call signature (e.g., "h(x, p)", "h(t, x, p)") + +# Example +\`\`\`julia +_natural_sig_h(Autonomous, Fixed) # Returns "h(x, p)" +_natural_sig_h(NonAutonomous, Fixed) # Returns "h(t, x, p)" +\`\`\` + +See also: [`_uniform_sig_h`](@ref). +""" +function _natural_sig_h(::Type{TD}, ::Type{VD}) where {TD, VD} + args = String[] + TD === Traits.NonAutonomous && push!(args, "t") + push!(args, "x") + push!(args, "p") + VD === Traits.NonFixed && push!(args, "v") + return "h(" * join(args, ", ") * ")" +end + +""" + _uniform_sig_h() -> String + +Return the uniform call signature for a Hamiltonian. + +The uniform signature always includes all arguments (t, x, p, v) regardless of traits. + +# Returns +- `String`: Uniform call signature ("h(t, x, p, v)") + +See also: [`_natural_sig_h`](@ref). +""" +function _uniform_sig_h() + return "h(t, x, p, v)" +end diff --git a/src/Data/vector_field.jl b/src/Data/vector_field.jl new file mode 100644 index 00000000..799784c0 --- /dev/null +++ b/src/Data/vector_field.jl @@ -0,0 +1,278 @@ +""" +$(TYPEDEF) + +Parametric container for a vector-field function together with its +time-dependence, variable-dependence, and mutability traits. + +# 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 vector-field function. + +# Construction + +Use the keyword constructor: + +```julia +VectorField(f; is_autonomous = true, is_variable = false) # default: f(x) +VectorField((t, x) -> ...; is_autonomous = false) # f(t, x) +VectorField((x, v) -> ...; is_variable = true) # f(x, v) +VectorField((t, x, v) -> ...; is_autonomous = false, is_variable = true) +``` + +The mutability trait (InPlace/OutOfPlace) is auto-detected from the function signature. + +# Call Signatures + +Every `VectorField` is callable via its **natural** signature (matching the +traits), and via a **uniform** signature `(t, x, v)` that ignores the +unused arguments — this uniform form is used internally to build the right-hand +side of the ODE in a trait-agnostic way. + +For InPlace vector fields, the natural signature includes the derivative buffer +as the first argument (e.g., `(dx, x)` for Autonomous/Fixed). + +See also: [`CTBase.Data.AbstractVectorField`](@ref), [`CTBase.Traits.TimeDependence`](@ref), [`CTBase.Traits.VariableDependence`](@ref), [`CTBase.Traits.AbstractMutabilityTrait`](@ref). +""" +struct VectorField{F<:Function, TD<:Traits.TimeDependence, VD<:Traits.VariableDependence, MD<:Traits.AbstractMutabilityTrait} <: AbstractVectorField{TD, VD, MD} + f::F +end + +# ============================================================================= +# Internal helpers for mutability detection +# ============================================================================= + +""" + _oop_arity_vf(::Type{Traits.Autonomous}, ::Type{Traits.Fixed}) -> Int + +Return the out-of-place arity for Autonomous/Fixed vector fields (1: x). +""" +_oop_arity_vf(::Type{Traits.Autonomous}, ::Type{Traits.Fixed}) = 1 + +""" + _oop_arity_vf(::Type{Traits.NonAutonomous}, ::Type{Traits.Fixed}) -> Int + +Return the out-of-place arity for NonAutonomous/Fixed vector fields (2: t, x). +""" +_oop_arity_vf(::Type{Traits.NonAutonomous}, ::Type{Traits.Fixed}) = 2 + +""" + _oop_arity_vf(::Type{Traits.Autonomous}, ::Type{Traits.NonFixed}) -> Int + +Return the out-of-place arity for Autonomous/NonFixed vector fields (2: x, v). +""" +_oop_arity_vf(::Type{Traits.Autonomous}, ::Type{Traits.NonFixed}) = 2 + +""" + _oop_arity_vf(::Type{Traits.NonAutonomous}, ::Type{Traits.NonFixed}) -> Int + +Return the out-of-place arity for NonAutonomous/NonFixed vector fields (3: t, x, v). +""" +_oop_arity_vf(::Type{Traits.NonAutonomous}, ::Type{Traits.NonFixed}) = 3 + +""" + _detect_mutability_vf(f::Function, TD, VD) -> Type{<:AbstractMutabilityTrait} + +Detect the mutability trait from the function signature. + +Compares the function arity to the expected out-of-place arity and in-place arity +(arity + 1 for VectorField). 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 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 `VectorField` 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.VectorField`](@ref), [`CTBase.Traits.InPlace`](@ref), [`CTBase.Traits.OutOfPlace`](@ref). +""" +function _detect_mutability_vf(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 = "VectorField mutability detection", + )) + end + + arity = first(methods(f)).nargs - 1 + oop_arity = _oop_arity_vf(TD, VD) + ip_arity = oop_arity + 1 + + 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 = "VectorField mutability detection", + )) + end +end + +""" +$(TYPEDSIGNATURES) + +Construct a `VectorField` with trait flags. + +# Arguments +- `f::Function`: The vector-field function. +- `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 +- `VectorField`: A VectorField with appropriate traits. + +# Example +\`\`\`julia-repl +julia> using CTBase.Data + +julia> vf = VectorField(x -> -x) # Uses defaults: is_autonomous=true, is_variable=false +VectorField: autonomous, fixed (no variable), out-of-place + natural call: f(x) + uniform call: f(t, x, v) + +julia> vf = VectorField((t, x) -> t .* x; is_autonomous=false) +VectorField: non-autonomous, fixed (no variable), out-of-place + natural call: f(t, x) + uniform call: f(t, x, v) + +julia> vf = VectorField(x -> -x; is_inplace=true) # Explicit in-place +VectorField: autonomous, fixed (no variable), in-place + natural call: f(dx, x) + uniform call: f(dx, t, x, 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.VectorField`](@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 VectorField(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_vf(f, TD, VD) + else + is_inplace ? Traits.InPlace : Traits.OutOfPlace + end + return VectorField{typeof(f), TD, VD, MD}(f) +end + +# ============================================================================= +# Typed constructor — calls struct inner constructor directly +# ============================================================================= + +function VectorField( + f, + ::Type{TD}, ::Type{VD}, ::Type{MD}, +) where { + TD <: Traits.TimeDependence, + VD <: Traits.VariableDependence, + MD <: Traits.AbstractMutabilityTrait, +} + return VectorField{typeof(f), TD, VD, MD}(f) +end + +# ============================================================================= +# Natural call signatures - one per trait combination +# ============================================================================= + +# OutOfPlace signatures (existing) +(F::VectorField{<:Function, Traits.Autonomous, Traits.Fixed, Traits.OutOfPlace})(x) = F.f(x) +(F::VectorField{<:Function, Traits.NonAutonomous, Traits.Fixed, Traits.OutOfPlace})(t, x) = F.f(t, x) +(F::VectorField{<:Function, Traits.Autonomous, Traits.NonFixed, Traits.OutOfPlace})(x, v) = F.f(x, v) +(F::VectorField{<:Function, Traits.NonAutonomous, Traits.NonFixed, Traits.OutOfPlace})(t, x, v) = F.f(t, x, v) + +# InPlace signatures (new) +(F::VectorField{<:Function, Traits.Autonomous, Traits.Fixed, Traits.InPlace})(dx, x) = F.f(dx, x) +(F::VectorField{<:Function, Traits.NonAutonomous, Traits.Fixed, Traits.InPlace})(dx, t, x) = F.f(dx, t, x) +(F::VectorField{<:Function, Traits.Autonomous, Traits.NonFixed, Traits.InPlace})(dx, x, v) = F.f(dx, x, v) +(F::VectorField{<:Function, Traits.NonAutonomous, Traits.NonFixed, Traits.InPlace})(dx, t, x, v) = F.f(dx, t, x, v) + +# ============================================================================= +# Uniform (t, x, v) call - used by VectorFieldSystem.rhs +# Every combination forwards to its natural call, ignoring unused args. +# (NonAutonomous, NonFixed) is already covered by the natural signature above. +# ============================================================================= + +# OutOfPlace uniform signatures (existing) +(F::VectorField{<:Function, Traits.Autonomous, Traits.Fixed, Traits.OutOfPlace})(t, x, v) = F.f(x) +(F::VectorField{<:Function, Traits.NonAutonomous, Traits.Fixed, Traits.OutOfPlace})(t, x, v) = F.f(t, x) +(F::VectorField{<:Function, Traits.Autonomous, Traits.NonFixed, Traits.OutOfPlace})(t, x, v) = F.f(x, v) + +# InPlace uniform signatures (new) +(F::VectorField{<:Function, Traits.Autonomous, Traits.Fixed, Traits.InPlace})(dx, t, x, v) = F.f(dx, x) +(F::VectorField{<:Function, Traits.NonAutonomous, Traits.Fixed, Traits.InPlace})(dx, t, x, v) = F.f(dx, t, x) +(F::VectorField{<:Function, Traits.Autonomous, Traits.NonFixed, Traits.InPlace})(dx, t, x, v) = F.f(dx, x, v) + +# ============================================================================= +# Base.show +# ============================================================================= + +""" +$(TYPEDSIGNATURES) + +Display a compact representation of a VectorField. + +Shows the type name, time dependence, variable dependence, mutability, and function type. + +# Arguments +- `io::IO`: The IO stream to write to. +- `vf::VectorField`: The VectorField to display. + +See also: [`CTBase.Data.VectorField`](@ref). +""" +function Base.show(io::IO, vf::VectorField{F, TD, VD, MD}) where {F, TD, VD, MD} + header = "VectorField: $(_td_label(TD)), $(_vd_label(VD)), $(_md_label(MD))" + natural = _natural_sig_vf(TD, VD, MD) + uniform = _uniform_sig_vf(MD) + println(io, header) + println(io, " natural call: ", natural) + print(io, " uniform call: ", uniform) +end + +""" +$(TYPEDSIGNATURES) + +Display a VectorField 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. +- `vf::VectorField`: The VectorField to display. + +See also: [`CTBase.Data.VectorField`](@ref). +""" +function Base.show(io::IO, ::MIME"text/plain", vf::VectorField{F, TD, VD, MD}) where {F, TD, VD, MD} + show(io, vf) +end From 9a94744d89527cd9bc45508910fe05b91b195199 Mon Sep 17 00:00:00 2001 From: Olivier Cots Date: Thu, 25 Jun 2026 00:24:31 +0200 Subject: [PATCH 2/8] test: add CTBase.Data test suite Co-Authored-By: Claude Sonnet 4.6 --- test/suite/data/test_abstract_hamiltonian.jl | 158 ++++++ .../test_abstract_hamiltonian_vector_field.jl | 111 ++++ test/suite/data/test_abstract_vector_field.jl | 174 ++++++ test/suite/data/test_data_module.jl | 168 ++++++ test/suite/data/test_hamiltonian.jl | 171 ++++++ .../data/test_hamiltonian_vector_field.jl | 429 +++++++++++++++ test/suite/data/test_helpers.jl | 139 +++++ test/suite/data/test_vector_field.jl | 507 ++++++++++++++++++ 8 files changed, 1857 insertions(+) create mode 100644 test/suite/data/test_abstract_hamiltonian.jl create mode 100644 test/suite/data/test_abstract_hamiltonian_vector_field.jl create mode 100644 test/suite/data/test_abstract_vector_field.jl create mode 100644 test/suite/data/test_data_module.jl create mode 100644 test/suite/data/test_hamiltonian.jl create mode 100644 test/suite/data/test_hamiltonian_vector_field.jl create mode 100644 test/suite/data/test_helpers.jl create mode 100644 test/suite/data/test_vector_field.jl diff --git a/test/suite/data/test_abstract_hamiltonian.jl b/test/suite/data/test_abstract_hamiltonian.jl new file mode 100644 index 00000000..6e75dfc5 --- /dev/null +++ b/test/suite/data/test_abstract_hamiltonian.jl @@ -0,0 +1,158 @@ +module TestAbstractHamiltonian + +import 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 FakeHamiltonian{TD, VD} <: Data.AbstractHamiltonian{TD, VD} end + +# ============================================================================== +# Test function +# ============================================================================== + +function test_abstract_hamiltonian() + Test.@testset "AbstractHamiltonian Tests" verbose=VERBOSE showtiming=SHOWTIMING begin + + # ==================================================================== + # UNIT TESTS - Abstract Type Definition + # ==================================================================== + + Test.@testset "Abstract Type Definition" begin + Test.@testset "AbstractHamiltonian exists" begin + Test.@test isdefined(Data, :AbstractHamiltonian) + end + + Test.@testset "AbstractHamiltonian is exported" begin + Test.@test isdefined(Data, :AbstractHamiltonian) + end + + Test.@testset "FakeHamiltonian subtypes AbstractHamiltonian" begin + fake = FakeHamiltonian{Traits.Autonomous, Traits.Fixed}() + Test.@test fake isa Data.AbstractHamiltonian + end + end + + # ==================================================================== + # UNIT TESTS - Trait Accessors on Abstract Type + # ==================================================================== + + Test.@testset "Trait Accessors on Abstract Type" begin + Test.@testset "has_time_dependence_trait returns true" begin + fake = FakeHamiltonian{Traits.Autonomous, Traits.Fixed}() + Test.@test Traits.has_time_dependence_trait(fake) === true + Test.@test Base.invokelatest(Traits.has_time_dependence_trait, fake) === true + end + + Test.@testset "has_variable_dependence_trait returns true" begin + fake = FakeHamiltonian{Traits.Autonomous, Traits.Fixed}() + Test.@test Traits.has_variable_dependence_trait(fake) === true + Test.@test Base.invokelatest(Traits.has_variable_dependence_trait, fake) === true + end + + Test.@testset "time_dependence returns correct trait" begin + fake_aut = FakeHamiltonian{Traits.Autonomous, Traits.Fixed}() + fake_nonaut = FakeHamiltonian{Traits.NonAutonomous, Traits.Fixed}() + Test.@test Traits.time_dependence(fake_aut) === Traits.Autonomous + Test.@test Traits.time_dependence(fake_nonaut) === Traits.NonAutonomous + end + + Test.@testset "variable_dependence returns correct trait" begin + fake_fixed = FakeHamiltonian{Traits.Autonomous, Traits.Fixed}() + fake_nonfixed = FakeHamiltonian{Traits.Autonomous, Traits.NonFixed}() + Test.@test Traits.variable_dependence(fake_fixed) === Traits.Fixed + Test.@test Traits.variable_dependence(fake_nonfixed) === Traits.NonFixed + end + + Test.@testset "explicit dispatch on AbstractHamiltonian methods" begin + fake = FakeHamiltonian{Traits.NonAutonomous, Traits.NonFixed}() + + Test.@test invoke( + Traits.has_time_dependence_trait, + Tuple{Data.AbstractHamiltonian}, + fake, + ) === true + + Test.@test invoke( + Traits.has_variable_dependence_trait, + Tuple{Data.AbstractHamiltonian}, + fake, + ) === true + + Test.@test invoke( + Traits.time_dependence, + Tuple{Data.AbstractHamiltonian{Traits.NonAutonomous, Traits.NonFixed}}, + fake, + ) === Traits.NonAutonomous + + Test.@test invoke( + Traits.variable_dependence, + Tuple{Data.AbstractHamiltonian{Traits.NonAutonomous, Traits.NonFixed}}, + fake, + ) === Traits.NonFixed + end + end + + # ==================================================================== + # UNIT TESTS - Dynamics Trait + # ==================================================================== + + Test.@testset "Dynamics Trait" begin + Test.@testset "abstract type returns HamiltonianDynamics" begin + fake = FakeHamiltonian{Traits.Autonomous, Traits.Fixed}() + Test.@test Traits.dynamics_trait(fake) === Traits.HamiltonianDynamics + end + + Test.@testset "concrete Hamiltonian returns HamiltonianDynamics" begin + h = Data.Hamiltonian((x, p) -> x + p; is_autonomous=true, is_variable=false) + Test.@test Traits.dynamics_trait(h) === Traits.HamiltonianDynamics + end + end + + # ==================================================================== + # UNIT TESTS - Liskov Substitution + # ==================================================================== + + Test.@testset "Liskov Substitution" begin + Test.@testset "Hamiltonian is an AbstractHamiltonian" begin + h = Data.Hamiltonian((x, p) -> x + p; is_autonomous=true, is_variable=false) + Test.@test h isa Data.AbstractHamiltonian + end + end + + # ==================================================================== + # UNIT TESTS - Type Stability + # ==================================================================== + + Test.@testset "Type Stability" begin + Test.@testset "Trait accessors are type-stable" begin + fake = FakeHamiltonian{Traits.Autonomous, Traits.Fixed}() + Test.@test Test.@inferred(Traits.has_time_dependence_trait(fake)) === true + Test.@test Test.@inferred(Traits.has_variable_dependence_trait(fake)) === true + Test.@test Test.@inferred(Traits.time_dependence(fake)) === Traits.Autonomous + Test.@test Test.@inferred(Traits.variable_dependence(fake)) === Traits.Fixed + end + end + + # ==================================================================== + # UNIT TESTS - Exports Verification + # ==================================================================== + + Test.@testset "Exports Verification" begin + Test.@testset "Exported types" begin + Test.@test isdefined(Data, :AbstractHamiltonian) + end + end + end +end + +end # module + +# CRITICAL: Redefine in outer scope for TestRunner +test_abstract_hamiltonian() = TestAbstractHamiltonian.test_abstract_hamiltonian() diff --git a/test/suite/data/test_abstract_hamiltonian_vector_field.jl b/test/suite/data/test_abstract_hamiltonian_vector_field.jl new file mode 100644 index 00000000..d80a24b6 --- /dev/null +++ b/test/suite/data/test_abstract_hamiltonian_vector_field.jl @@ -0,0 +1,111 @@ +module TestAbstractHamiltonianVectorField + +import 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 FakeHamiltonianVectorField{TD, VD, MD} <: Data.AbstractHamiltonianVectorField{TD, VD, MD} end + +# ============================================================================== +# Test function +# ============================================================================== + +function test_abstract_hamiltonian_vector_field() + Test.@testset "AbstractHamiltonianVectorField Tests" verbose=VERBOSE showtiming=SHOWTIMING begin + + # ==================================================================== + # UNIT TESTS - Abstract Type Definition + # ==================================================================== + + Test.@testset "Abstract Type Definition" begin + Test.@testset "AbstractHamiltonianVectorField exists" begin + Test.@test isdefined(Data, :AbstractHamiltonianVectorField) + end + + Test.@testset "AbstractHamiltonianVectorField is abstract" begin + Test.@test isabstracttype(Data.AbstractHamiltonianVectorField) + end + + Test.@testset "Subtypes AbstractVectorField" begin + Test.@test Data.AbstractHamiltonianVectorField <: Data.AbstractVectorField + end + + Test.@testset "FakeHamiltonianVectorField subtypes AbstractHamiltonianVectorField" begin + fake = FakeHamiltonianVectorField{Traits.Autonomous, Traits.Fixed, Traits.OutOfPlace}() + Test.@test fake isa Data.AbstractHamiltonianVectorField + 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 = FakeHamiltonianVectorField{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 = FakeHamiltonianVectorField{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 = FakeHamiltonianVectorField{Traits.Autonomous, Traits.Fixed, Traits.OutOfPlace}() + Test.@test Traits.dynamics_trait(fake) === Traits.HamiltonianDynamics + end + + Test.@testset "concrete HamiltonianVectorField returns HamiltonianDynamics" begin + hvf = Data.HamiltonianVectorField((x, p) -> (x, -p); is_autonomous=true, is_variable=false) + Test.@test Traits.dynamics_trait(hvf) === Traits.HamiltonianDynamics + end + end + + # ==================================================================== + # UNIT TESTS - Liskov Substitution + # ==================================================================== + + Test.@testset "Liskov Substitution" begin + Test.@testset "HamiltonianVectorField is an AbstractHamiltonianVectorField" begin + hvf = Data.HamiltonianVectorField((x, p) -> (x, -p); is_autonomous=true, is_variable=false) + Test.@test hvf isa Data.AbstractHamiltonianVectorField + end + + Test.@testset "HamiltonianVectorField type subtypes the abstract type" begin + Test.@test Data.HamiltonianVectorField <: Data.AbstractHamiltonianVectorField + end + end + + # ==================================================================== + # UNIT TESTS - Exports Verification + # ==================================================================== + + Test.@testset "Exports Verification" begin + Test.@test isdefined(Data, :AbstractHamiltonianVectorField) + end + end +end + +end # module + +# CRITICAL: Redefine in outer scope for TestRunner +test_abstract_hamiltonian_vector_field() = TestAbstractHamiltonianVectorField.test_abstract_hamiltonian_vector_field() diff --git a/test/suite/data/test_abstract_vector_field.jl b/test/suite/data/test_abstract_vector_field.jl new file mode 100644 index 00000000..d715b595 --- /dev/null +++ b/test/suite/data/test_abstract_vector_field.jl @@ -0,0 +1,174 @@ +module TestAbstractVectorField + +import 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 FakeVectorField{TD, VD, MD} <: Data.AbstractVectorField{TD, VD, MD} end + +# ============================================================================== +# Test function +# ============================================================================== + +function test_abstract_vector_field() + Test.@testset "AbstractVectorField Tests" verbose=VERBOSE showtiming=SHOWTIMING begin + + # ==================================================================== + # UNIT TESTS - Abstract Type Definition + # ==================================================================== + + Test.@testset "Abstract Type Definition" begin + Test.@testset "AbstractVectorField exists" begin + Test.@test isdefined(Data, :AbstractVectorField) + end + + Test.@testset "AbstractVectorField is exported" begin + Test.@test isdefined(Data, :AbstractVectorField) + end + + Test.@testset "FakeVectorField subtypes AbstractVectorField" begin + fake = FakeVectorField{Traits.Autonomous, Traits.Fixed, Traits.OutOfPlace}() + 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_time_dependence_trait returns true" begin + fake = FakeVectorField{Traits.Autonomous, Traits.Fixed, Traits.OutOfPlace}() + Test.@test Traits.has_time_dependence_trait(fake) === true + Test.@test Base.invokelatest(Traits.has_time_dependence_trait, fake) === true + end + + Test.@testset "has_variable_dependence_trait returns true" begin + fake = FakeVectorField{Traits.Autonomous, Traits.Fixed, Traits.OutOfPlace}() + Test.@test Traits.has_variable_dependence_trait(fake) === true + Test.@test Base.invokelatest(Traits.has_variable_dependence_trait, fake) === true + end + + Test.@testset "has_mutability_trait returns true" begin + fake = FakeVectorField{Traits.Autonomous, Traits.Fixed, Traits.OutOfPlace}() + Test.@test Traits.has_mutability_trait(fake) === true + Test.@test Base.invokelatest(Traits.has_mutability_trait, fake) === true + end + + Test.@testset "time_dependence returns correct trait" begin + fake_aut = FakeVectorField{Traits.Autonomous, Traits.Fixed, Traits.OutOfPlace}() + fake_nonaut = FakeVectorField{Traits.NonAutonomous, Traits.Fixed, Traits.OutOfPlace}() + Test.@test Traits.time_dependence(fake_aut) === Traits.Autonomous + Test.@test Traits.time_dependence(fake_nonaut) === Traits.NonAutonomous + end + + Test.@testset "variable_dependence returns correct trait" begin + fake_fixed = FakeVectorField{Traits.Autonomous, Traits.Fixed, Traits.OutOfPlace}() + fake_nonfixed = FakeVectorField{Traits.Autonomous, Traits.NonFixed, Traits.OutOfPlace}() + Test.@test Traits.variable_dependence(fake_fixed) === Traits.Fixed + Test.@test Traits.variable_dependence(fake_nonfixed) === Traits.NonFixed + end + + Test.@testset "mutability returns correct trait" begin + fake_oop = FakeVectorField{Traits.Autonomous, Traits.Fixed, Traits.OutOfPlace}() + fake_ip = FakeVectorField{Traits.Autonomous, Traits.Fixed, Traits.InPlace}() + Test.@test Traits.mutability(fake_oop) === Traits.OutOfPlace + Test.@test Traits.mutability(fake_ip) === Traits.InPlace + end + + Test.@testset "explicit dispatch on AbstractVectorField methods" begin + fake = FakeVectorField{Traits.NonAutonomous, Traits.NonFixed, Traits.InPlace}() + + Test.@test invoke( + Traits.has_time_dependence_trait, + Tuple{Data.AbstractVectorField}, + fake, + ) === true + + Test.@test invoke( + Traits.has_variable_dependence_trait, + Tuple{Data.AbstractVectorField}, + fake, + ) === true + + Test.@test invoke( + Traits.has_mutability_trait, + Tuple{Data.AbstractVectorField}, + fake, + ) === true + + Test.@test invoke( + Traits.time_dependence, + Tuple{Data.AbstractVectorField{Traits.NonAutonomous, Traits.NonFixed, Traits.InPlace}}, + fake, + ) === Traits.NonAutonomous + + Test.@test invoke( + Traits.variable_dependence, + Tuple{Data.AbstractVectorField{Traits.NonAutonomous, Traits.NonFixed, Traits.InPlace}}, + fake, + ) === Traits.NonFixed + + Test.@test invoke( + Traits.mutability, + Tuple{Data.AbstractVectorField{Traits.NonAutonomous, Traits.NonFixed, Traits.InPlace}}, + fake, + ) === Traits.InPlace + end + end + + # ==================================================================== + # UNIT TESTS - Dynamics Trait + # ==================================================================== + + Test.@testset "Dynamics Trait" begin + Test.@testset "abstract type returns StateDynamics" begin + fake = FakeVectorField{Traits.Autonomous, Traits.Fixed, Traits.OutOfPlace}() + Test.@test Traits.dynamics_trait(fake) === Traits.StateDynamics + end + + Test.@testset "concrete VectorField returns StateDynamics" begin + vf = Data.VectorField(x -> -x; is_autonomous=true, is_variable=false) + Test.@test Traits.dynamics_trait(vf) === Traits.StateDynamics + end + end + + # ==================================================================== + # UNIT TESTS - Liskov Substitution + # ==================================================================== + + Test.@testset "Liskov Substitution" begin + Test.@testset "VectorField is an AbstractVectorField" begin + vf = Data.VectorField(x -> -x; is_autonomous=true, is_variable=false) + Test.@test vf isa Data.AbstractVectorField + end + + Test.@testset "HamiltonianVectorField is an AbstractVectorField" begin + hvf = Data.HamiltonianVectorField((x, p) -> (x, -p); is_autonomous=true, is_variable=false) + Test.@test hvf isa Data.AbstractVectorField + end + end + + # ==================================================================== + # UNIT TESTS - Exports Verification + # ==================================================================== + + Test.@testset "Exports Verification" begin + Test.@testset "Exported types" begin + Test.@test isdefined(Data, :AbstractVectorField) + end + end + end +end + +end # module + +# CRITICAL: Redefine in outer scope for TestRunner +test_abstract_vector_field() = TestAbstractVectorField.test_abstract_vector_field() diff --git a/test/suite/data/test_data_module.jl b/test/suite/data/test_data_module.jl new file mode 100644 index 00000000..38fb2853 --- /dev/null +++ b/test/suite/data/test_data_module.jl @@ -0,0 +1,168 @@ +""" +# ============================================================================ +# Data Module Exports Tests +# ============================================================================ +# This file tests the exports from the `Data` module. It verifies that +# the expected types are properly exported by `CTBase.Data` and readily +# accessible to the end user. +# +# Functionality tests are in separate files: +# - test_abstract_vector_field.jl for abstract vector field types +# - test_vector_field.jl for VectorField constructor and functionality +# - test_abstract_hamiltonian.jl for abstract Hamiltonian types +# - test_hamiltonian.jl for Hamiltonian constructor and functionality +# - test_abstract_hamiltonian_vector_field.jl for abstract HVF types +# - test_hamiltonian_vector_field.jl for HVF constructor and functionality +# - test_helpers.jl for helper functions +""" + +module TestDataModule + +import Test +import CTBase +import CTBase.Data +using CTBase.Data # For testing exported symbols + +const VERBOSE = isdefined(Main, :TestData) ? Main.TestData.VERBOSE : true +const SHOWTIMING = isdefined(Main, :TestData) ? Main.TestData.SHOWTIMING : true + +const CurrentModule = TestDataModule + +# ============================================================================ +# Hardcoded export lists +# ============================================================================ +# These lists define the expected public API of the Data module. + +const EXPORTED_ABSTRACT_TYPES = ( + :AbstractVectorField, + :AbstractHamiltonianVectorField, + :AbstractHamiltonian, +) + +const EXPORTED_CONCRETE_TYPES = ( + :VectorField, + :HamiltonianVectorField, + :Hamiltonian, +) + +const PRIVATE_SYMBOLS = ( + :__is_autonomous, + :__is_inplace, + :__is_variable, + :_detect_mutability_hvf, + :_detect_mutability_vf, + :_md_label, + :_natural_sig_h, + :_natural_sig_hvf, + :_natural_sig_vf, + :_oop_arity_hvf, + :_oop_arity_vf, + :_td_label, + :_uniform_sig_h, + :_uniform_sig_hvf, + :_uniform_sig_vf, + :_vd_label, +) + +# ============================================================================ +# Helper functions (generic for reuse in other modules) +# ============================================================================ + +""" + test_exported_symbols(module_ref::Module, symbols::Tuple, test_module::Module) + +Test that symbols are exported from a module and available via `using`. +""" +function test_exported_symbols(module_ref::Module, symbols::Tuple, test_module::Module) + for sym in symbols + Test.@testset "$(sym)" begin + Test.@test isdefined(module_ref, sym) + Test.@test isdefined(test_module, sym) + end + end +end + +""" + test_internal_symbols(module_ref::Module, symbols::Tuple, test_module::Module) + +Test that symbols are defined in a module but NOT exported (not available via `using`). +Generic helper for modules with private symbols. +""" +function test_internal_symbols(module_ref::Module, symbols::Tuple, test_module::Module) + for sym in symbols + Test.@testset "$(sym)" begin + Test.@test isdefined(module_ref, sym) + Test.@test !isdefined(test_module, sym) + end + end +end + +# ============================================================================ +# Test function +# ============================================================================ + +function test_data_module() + Test.@testset "Data Module Exports" verbose=VERBOSE showtiming=SHOWTIMING begin + + # ==================================================================== + # Module availability + # ==================================================================== + + Test.@testset "Module availability" begin + Test.@testset "Data module exists" begin + Test.@test isdefined(CTBase, :Data) + Test.@test CTBase.Data isa Module + end + end + + # ==================================================================== + # Exported abstract types verification + # ==================================================================== + + Test.@testset "Exported abstract types" begin + test_exported_symbols(Data, EXPORTED_ABSTRACT_TYPES, CurrentModule) + end + + # ==================================================================== + # Exported concrete types verification + # ==================================================================== + + Test.@testset "Exported concrete types" begin + test_exported_symbols(Data, EXPORTED_CONCRETE_TYPES, CurrentModule) + end + + # ==================================================================== + # Private symbols (not exported) verification + # ==================================================================== + + Test.@testset "Private symbols (not exported)" begin + test_internal_symbols(Data, PRIVATE_SYMBOLS, CurrentModule) + end + + # ==================================================================== + # Type hierarchy tests + # ==================================================================== + + Test.@testset "Type hierarchy" begin + Test.@testset "Abstract types are abstract" begin + Test.@test isabstracttype(Data.AbstractVectorField) + Test.@test isabstracttype(Data.AbstractHamiltonianVectorField) + Test.@test isabstracttype(Data.AbstractHamiltonian) + end + + Test.@testset "Concrete types inherit from abstract types" begin + Test.@test Data.VectorField <: Data.AbstractVectorField + Test.@test Data.HamiltonianVectorField <: Data.AbstractHamiltonianVectorField + # Note: Hamiltonian is parametric (Hamiltonian{F, TD, VD} <: AbstractHamiltonian{TD, VD}) + # Test via instance instead of type + h = Data.Hamiltonian((x, p) -> 0.0) + Test.@test h isa Data.AbstractHamiltonian + end + end + end +end + +end # module TestDataModule + +# CRITICAL: Redefine in outer scope for TestRunner +test_data_module() = TestDataModule.test_data_module() diff --git a/test/suite/data/test_hamiltonian.jl b/test/suite/data/test_hamiltonian.jl new file mode 100644 index 00000000..855b6c59 --- /dev/null +++ b/test/suite/data/test_hamiltonian.jl @@ -0,0 +1,171 @@ +module TestHamiltonian + +import Test +import CTBase.Data +import CTBase.Traits +import CTBase.Exceptions + +const VERBOSE = isdefined(Main, :TestData) ? Main.TestData.VERBOSE : true +const SHOWTIMING = isdefined(Main, :TestData) ? Main.TestData.SHOWTIMING : true + +function test_hamiltonian() + Test.@testset "Hamiltonian Tests" verbose=VERBOSE showtiming=SHOWTIMING begin + + # ==================================================================== + # UNIT TESTS - Construction with all trait combinations + # ==================================================================== + + Test.@testset "Unit: Construction with all trait combinations" begin + # Autonomous, Fixed + h_aut_fixed = Data.Hamiltonian((x, p) -> x + p; is_autonomous=true, is_variable=false) + Test.@test h_aut_fixed isa Data.Hamiltonian + Test.@test Traits.time_dependence(h_aut_fixed) == Traits.Autonomous + Test.@test Traits.variable_dependence(h_aut_fixed) == Traits.Fixed + + # NonAutonomous, Fixed + h_nonaut_fixed = Data.Hamiltonian((t, x, p) -> t + x + p; is_autonomous=false, is_variable=false) + Test.@test h_nonaut_fixed isa Data.Hamiltonian + Test.@test Traits.time_dependence(h_nonaut_fixed) == Traits.NonAutonomous + Test.@test Traits.variable_dependence(h_nonaut_fixed) == Traits.Fixed + + # Autonomous, NonFixed + h_aut_nonfixed = Data.Hamiltonian((x, p, v) -> x + p + v; is_autonomous=true, is_variable=true) + Test.@test h_aut_nonfixed isa Data.Hamiltonian + Test.@test Traits.time_dependence(h_aut_nonfixed) == Traits.Autonomous + Test.@test Traits.variable_dependence(h_aut_nonfixed) == Traits.NonFixed + + # NonAutonomous, NonFixed + h_nonaut_nonfixed = Data.Hamiltonian((t, x, p, v) -> t + x + p + v; is_autonomous=false, is_variable=true) + Test.@test h_nonaut_nonfixed isa Data.Hamiltonian + Test.@test Traits.time_dependence(h_nonaut_nonfixed) == Traits.NonAutonomous + Test.@test Traits.variable_dependence(h_nonaut_nonfixed) == Traits.NonFixed + end + + # ==================================================================== + # UNIT TESTS - Natural call signatures + # ==================================================================== + + Test.@testset "Unit: Natural call signatures" begin + # (x, p) for Autonomous, Fixed + h1 = Data.Hamiltonian((x, p) -> x .+ p; is_autonomous=true, is_variable=false) + result = h1([1.0, 2.0], [3.0, 4.0]) + Test.@test result == [4.0, 6.0] + + # (t, x, p) for NonAutonomous, Fixed + h2 = Data.Hamiltonian((t, x, p) -> x .+ p; is_autonomous=false, is_variable=false) + result = h2(2.0, [1.0, 2.0], [3.0, 4.0]) + Test.@test result == [4.0, 6.0] + + # (x, p, v) for Autonomous, NonFixed + h3 = Data.Hamiltonian((x, p, v) -> x .+ p .+ v; is_autonomous=true, is_variable=true) + result = h3([1.0, 2.0], [3.0, 4.0], 2.0) + Test.@test result == [6.0, 8.0] + + # (t, x, p, v) for NonAutonomous, NonFixed + h4 = Data.Hamiltonian((t, x, p, v) -> x .+ p .+ v; is_autonomous=false, is_variable=true) + result = h4(2.0, [1.0, 2.0], [3.0, 4.0], 2.0) + Test.@test result == [6.0, 8.0] + end + + # ==================================================================== + # UNIT TESTS - Uniform call signature + # ==================================================================== + + Test.@testset "Unit: Uniform call signature (t, x, p, v)" begin + # Autonomous Fixed - ignores t and v + h1 = Data.Hamiltonian((x, p) -> x .+ p; is_autonomous=true, is_variable=false) + result = h1(0.0, [1.0, 2.0], [3.0, 4.0], nothing) + Test.@test result == [4.0, 6.0] + + # NonAutonomous Fixed - ignores v + h2 = Data.Hamiltonian((t, x, p) -> x .+ p; is_autonomous=false, is_variable=false) + result = h2(2.0, [1.0, 2.0], [3.0, 4.0], nothing) + Test.@test result == [4.0, 6.0] + + # Autonomous NonFixed - ignores t + h3 = Data.Hamiltonian((x, p, v) -> x .+ p .+ v; is_autonomous=true, is_variable=true) + result = h3(0.0, [1.0, 2.0], [3.0, 4.0], 2.0) + Test.@test result == [6.0, 8.0] + + # NonAutonomous NonFixed - uses all + h4 = Data.Hamiltonian((t, x, p, v) -> x .+ p .+ v; is_autonomous=false, is_variable=true) + result = h4(2.0, [1.0, 2.0], [3.0, 4.0], 2.0) + Test.@test result == [6.0, 8.0] + end + + # ==================================================================== + # UNIT TESTS - Type stability + # ==================================================================== + + Test.@testset "Unit: Type stability" begin + # Uniform call type stability + h = Data.Hamiltonian((x, p) -> x .+ p; is_autonomous=true, is_variable=false) + Test.@test Test.@inferred(h(0.0, [1.0, 2.0], [3.0, 4.0], nothing)) == [4.0, 6.0] + end + + # ==================================================================== + # UNIT TESTS - Show Methods + # ==================================================================== + + Test.@testset "Show Methods" begin + h = Data.Hamiltonian((x, p) -> x .+ p; is_autonomous=true, is_variable=false) + + Test.@testset "Base.show (compact)" begin + io = IOBuffer() + show(io, h) + str = String(take!(io)) + Test.@test occursin("Hamiltonian", str) + Test.@test occursin("autonomous", str) + Test.@test occursin("fixed (no variable)", 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"), h) + str = String(take!(io)) + Test.@test occursin("Hamiltonian", str) + Test.@test occursin("autonomous", str) + Test.@test occursin("fixed (no variable)", str) + end + end + + # ==================================================================== + # UNIT TESTS - Typed constructor (trait types passed positionally) + # ==================================================================== + + Test.@testset "Typed constructor" begin + h = Data.Hamiltonian((x, p) -> x .+ p, Traits.Autonomous, Traits.Fixed) + Test.@test h isa Data.Hamiltonian + Test.@test Traits.time_dependence(h) === Traits.Autonomous + Test.@test Traits.variable_dependence(h) === Traits.Fixed + Test.@test h([1.0, 2.0], [3.0, 4.0]) == [4.0, 6.0] + end + + # ==================================================================== + # UNIT TESTS - Subtyping + # ==================================================================== + + Test.@testset "Subtyping" begin + Test.@testset "Hamiltonian is an AbstractHamiltonian" begin + h = Data.Hamiltonian((x, p) -> x .+ p; is_autonomous=true, is_variable=false) + Test.@test h isa Data.AbstractHamiltonian + end + end + + # ==================================================================== + # EXPORTS TESTS + # ==================================================================== + + Test.@testset "Exports" begin + Test.@testset "Hamiltonian is exported" begin + Test.@test isdefined(Data, :Hamiltonian) + end + end + end +end + +end # module TestHamiltonian + +test_hamiltonian() = TestHamiltonian.test_hamiltonian() diff --git a/test/suite/data/test_hamiltonian_vector_field.jl b/test/suite/data/test_hamiltonian_vector_field.jl new file mode 100644 index 00000000..e1e2b5f3 --- /dev/null +++ b/test/suite/data/test_hamiltonian_vector_field.jl @@ -0,0 +1,429 @@ +module TestHamiltonianVectorField + +import 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_hvf(x::Int, p) = (x, -p) +_multi_method_hvf(x::Float64, p) = (x, -p) +_multi_method_hvf(x::AbstractVector, p) = (x, -p) + +# TOP-LEVEL: Fake function with invalid arity for testing error branches +_bad_arity_hvf(x) = (x, -x) + +function test_hamiltonian_vector_field() + Test.@testset "Hamiltonian Vector Field Tests" verbose=VERBOSE showtiming=SHOWTIMING begin + + # ==================================================================== + # UNIT TESTS - Construction + # ==================================================================== + + Test.@testset "Construction" begin + # Autonomous, Fixed + hvf_autonomous_fixed = Data.HamiltonianVectorField((x, p) -> (x, -p); is_autonomous=true, is_variable=false) + Test.@test hvf_autonomous_fixed isa Data.HamiltonianVectorField + Test.@test Traits.time_dependence(hvf_autonomous_fixed) == Traits.Autonomous + Test.@test Traits.variable_dependence(hvf_autonomous_fixed) == Traits.Fixed + + # NonAutonomous, Fixed + hvf_nonautonomous_fixed = Data.HamiltonianVectorField((t, x, p) -> (x, -p); is_autonomous=false, is_variable=false) + Test.@test hvf_nonautonomous_fixed isa Data.HamiltonianVectorField + Test.@test Traits.time_dependence(hvf_nonautonomous_fixed) == Traits.NonAutonomous + Test.@test Traits.variable_dependence(hvf_nonautonomous_fixed) == Traits.Fixed + + # Autonomous, NonFixed + hvf_autonomous_nonfixed = Data.HamiltonianVectorField((x, p, v) -> (x .* v, -p); is_autonomous=true, is_variable=true) + Test.@test hvf_autonomous_nonfixed isa Data.HamiltonianVectorField + Test.@test Traits.time_dependence(hvf_autonomous_nonfixed) == Traits.Autonomous + Test.@test Traits.variable_dependence(hvf_autonomous_nonfixed) == Traits.NonFixed + + # NonAutonomous, NonFixed + hvf_nonautonomous_nonfixed = Data.HamiltonianVectorField((t, x, p, v) -> (x .* v, -p); is_autonomous=false, is_variable=true) + Test.@test hvf_nonautonomous_nonfixed isa Data.HamiltonianVectorField + Test.@test Traits.time_dependence(hvf_nonautonomous_nonfixed) == Traits.NonAutonomous + Test.@test Traits.variable_dependence(hvf_nonautonomous_nonfixed) == Traits.NonFixed + end + + # ==================================================================== + # UNIT TESTS - Natural signatures + # ==================================================================== + + Test.@testset "Natural Signatures" begin + # (x, p) for Autonomous, Fixed + hvf1 = Data.HamiltonianVectorField((x, p) -> (x, -p); is_autonomous=true, is_variable=false) + dx, dp = hvf1([1.0, 2.0], [3.0, 4.0]) + Test.@test dx == [1.0, 2.0] + Test.@test dp == [-3.0, -4.0] + + # (t, x, p) for NonAutonomous, Fixed + hvf2 = Data.HamiltonianVectorField((t, x, p) -> (t .* x, -p); is_autonomous=false, is_variable=false) + dx, dp = hvf2(2.0, [1.0, 2.0], [3.0, 4.0]) + Test.@test dx == [2.0, 4.0] + Test.@test dp == [-3.0, -4.0] + + # (x, p, v) for Autonomous, NonFixed + hvf3 = Data.HamiltonianVectorField((x, p, v) -> (x .* v, -p); is_autonomous=true, is_variable=true) + dx, dp = hvf3([1.0, 2.0], [3.0, 4.0], 2.0) + Test.@test dx == [2.0, 4.0] + Test.@test dp == [-3.0, -4.0] + + # (t, x, p, v) for NonAutonomous, NonFixed + hvf4 = Data.HamiltonianVectorField((t, x, p, v) -> (t .* x .* v, -p); is_autonomous=false, is_variable=true) + dx, dp = hvf4(2.0, [1.0, 2.0], [3.0, 4.0], 2.0) + Test.@test dx == [4.0, 8.0] + Test.@test dp == [-3.0, -4.0] + end + + # ==================================================================== + # UNIT TESTS - Uniform signature + # ==================================================================== + + Test.@testset "Uniform Signature" begin + # All combinations should work with (t, x, p, v) + hvf_autonomous_fixed = Data.HamiltonianVectorField((x, p) -> (x, -p); is_autonomous=true, is_variable=false) + dx, dp = hvf_autonomous_fixed(0.0, [1.0, 2.0], [3.0, 4.0], nothing) + Test.@test dx == [1.0, 2.0] + Test.@test dp == [-3.0, -4.0] + + hvf_nonautonomous_fixed = Data.HamiltonianVectorField((t, x, p) -> (t .* x, -p); is_autonomous=false, is_variable=false) + dx, dp = hvf_nonautonomous_fixed(2.0, [1.0, 2.0], [3.0, 4.0], nothing) + Test.@test dx == [2.0, 4.0] + Test.@test dp == [-3.0, -4.0] + + hvf_autonomous_nonfixed = Data.HamiltonianVectorField((x, p, v) -> (x .* v, -p); is_autonomous=true, is_variable=true) + dx, dp = hvf_autonomous_nonfixed(0.0, [1.0, 2.0], [3.0, 4.0], 2.0) + Test.@test dx == [2.0, 4.0] + Test.@test dp == [-3.0, -4.0] + + # Test variable_costate kwarg for NonFixed + dx, dp = hvf_autonomous_nonfixed(0.0, [1.0, 2.0], [3.0, 4.0], 2.0; variable_costate=false) + Test.@test dx == [2.0, 4.0] + Test.@test dp == [-3.0, -4.0] + end + + # ==================================================================== + # UNIT TESTS - Trait accessors + # ==================================================================== + + Test.@testset "Trait Accessors" begin + hvf = Data.HamiltonianVectorField((x, p) -> (x, -p); is_autonomous=true, is_variable=false) + Test.@test Traits.has_time_dependence_trait(hvf) == true + Test.@test Traits.has_variable_dependence_trait(hvf) == true + Test.@test Traits.time_dependence(hvf) == Traits.Autonomous + Test.@test Traits.variable_dependence(hvf) == Traits.Fixed + end + + # ==================================================================== + # UNIT TESTS - Typed constructor (trait types passed positionally) + # ==================================================================== + + Test.@testset "Typed constructor" begin + hvf = Data.HamiltonianVectorField( + (x, p) -> (x, -p), Traits.Autonomous, Traits.Fixed, Traits.OutOfPlace + ) + Test.@test hvf isa Data.HamiltonianVectorField + Test.@test Traits.time_dependence(hvf) === Traits.Autonomous + Test.@test Traits.variable_dependence(hvf) === Traits.Fixed + Test.@test Traits.mutability(hvf) === Traits.OutOfPlace + dx, dp = hvf([1.0, 2.0], [3.0, 4.0]) + Test.@test dx == [1.0, 2.0] + Test.@test dp == [-3.0, -4.0] + end + + # ==================================================================== + # UNIT TESTS - Subtyping + # ==================================================================== + + Test.@testset "Subtyping" begin + Test.@testset "HamiltonianVectorField is an AbstractVectorField" begin + hvf = Data.HamiltonianVectorField((x, p) -> (x, -p); is_autonomous=true, is_variable=false) + Test.@test hvf isa Data.AbstractVectorField + end + end + + # ==================================================================== + # UNIT TESTS - Base.show + # ==================================================================== + + Test.@testset "Base.show" begin + hvf = Data.HamiltonianVectorField((x, p) -> (x, -p); is_autonomous=true, is_variable=false) + # Just check that show doesn't throw + Test.@test_nowarn sprint(show, hvf) + end + + # ==================================================================== + # UNIT TESTS - Explicit is_inplace parameter + # ==================================================================== + + Test.@testset "Explicit is_inplace parameter" begin + Test.@testset "is_inplace=true creates InPlace HamiltonianVectorField" begin + # Define an out-of-place function but force InPlace + f(x, p) = (x, -p) + hvf = Data.HamiltonianVectorField(f; is_inplace=true) + Test.@test Traits.mutability(hvf) === Traits.InPlace + end + + Test.@testset "is_inplace=false creates OutOfPlace HamiltonianVectorField" begin + # Define an in-place function but force OutOfPlace + f(x, p) = (x, -p) + hvf = Data.HamiltonianVectorField(f; is_inplace=false) + Test.@test Traits.mutability(hvf) === 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.HamiltonianVectorField(_multi_method_hvf) + end + + Test.@testset "No error when is_inplace is explicitly specified" begin + hvf = Data.HamiltonianVectorField(_multi_method_hvf; is_inplace=false) + Test.@test Traits.mutability(hvf) === Traits.OutOfPlace + end + end + + # ==================================================================== + # UNIT TESTS - Internal Helpers + # ==================================================================== + + Test.@testset "Internal Helpers" begin + Test.@testset "_oop_arity_hvf" begin + Test.@test Data._oop_arity_hvf(Traits.Autonomous, Traits.Fixed) == 2 + Test.@test Data._oop_arity_hvf(Traits.NonAutonomous, Traits.Fixed) == 3 + Test.@test Data._oop_arity_hvf(Traits.Autonomous, Traits.NonFixed) == 3 + Test.@test Data._oop_arity_hvf(Traits.NonAutonomous, Traits.NonFixed) == 4 + end + + Test.@testset "_natural_sig_hvf helpers" begin + Test.@test Data._natural_sig_hvf(Traits.Autonomous, Traits.Fixed, Traits.OutOfPlace) == "f(x, p)" + Test.@test Data._natural_sig_hvf(Traits.NonAutonomous, Traits.Fixed, Traits.OutOfPlace) == "f(t, x, p)" + Test.@test Data._natural_sig_hvf(Traits.Autonomous, Traits.Fixed, Traits.InPlace) == "f(dx, dp, x, p)" + Test.@test Data._uniform_sig_hvf(Traits.OutOfPlace) == "f(t, x, p, v)" + Test.@test Data._uniform_sig_hvf(Traits.InPlace) == "f(dx, dp, t, x, p, v)" + end + + Test.@testset "_detect_mutability_hvf invalid arity" begin + Test.@test_throws Exceptions.IncorrectArgument Data._detect_mutability_hvf(_bad_arity_hvf, 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)" begin + f(dx, dp, x, p) = (dx .= -x; dp .= -p) + hvf = Data.HamiltonianVectorField(f; is_autonomous=true, is_variable=false, is_inplace=true) + + Test.@testset "scalar" begin + dx = [0.0] + dp = [0.0] + hvf(dx, dp, 3.0, 1.0) + Test.@test dx[1] == -3.0 + Test.@test dp[1] == -1.0 + end + + Test.@testset "vector" begin + dx = [0.0, 0.0] + dp = [0.0, 0.0] + hvf(dx, dp, [1.0, 2.0], [0.5, 1.0]) + Test.@test dx == [-1.0, -2.0] + Test.@test dp == [-0.5, -1.0] + end + end + + Test.@testset "NonAutonomous Fixed - (dx, dp, t, x, p)" begin + f(dx, dp, t, x, p) = (dx .= t .* x; dp .= t .* p) + hvf = Data.HamiltonianVectorField(f; is_autonomous=false, is_variable=false, is_inplace=true) + + Test.@testset "scalar" begin + dx = [0.0] + dp = [0.0] + hvf(dx, dp, 2.0, 3.0, 1.0) + Test.@test dx[1] == 6.0 + Test.@test dp[1] == 2.0 + end + + Test.@testset "vector" begin + dx = [0.0, 0.0] + dp = [0.0, 0.0] + hvf(dx, dp, 2.0, [1.0, 2.0], [0.5, 1.0]) + Test.@test dx == [2.0, 4.0] + Test.@test dp == [1.0, 2.0] + end + end + + Test.@testset "Autonomous NonFixed - (dx, dp, x, p, v)" begin + f(dx, dp, x, p, v) = (dx .= x .+ v; dp .= p .+ v) + hvf = Data.HamiltonianVectorField(f; is_autonomous=true, is_variable=true, is_inplace=true) + + Test.@testset "scalar" begin + dx = [0.0] + dp = [0.0] + hvf(dx, dp, 3.0, 1.0, 0.5) + Test.@test dx[1] == 3.5 + Test.@test dp[1] == 1.5 + end + + Test.@testset "vector" begin + dx = [0.0, 0.0] + dp = [0.0, 0.0] + hvf(dx, dp, [1.0, 2.0], [0.5, 1.0], 0.5) + Test.@test dx == [1.5, 2.5] + Test.@test dp == [1.0, 1.5] + end + end + + Test.@testset "NonAutonomous NonFixed - (dx, dp, t, x, p, v)" begin + f(dx, dp, t, x, p, v) = (dx .= t .* x .+ v; dp .= t .* p .+ v) + hvf = Data.HamiltonianVectorField(f; is_autonomous=false, is_variable=true, is_inplace=true) + + Test.@testset "scalar" begin + dx = [0.0] + dp = [0.0] + hvf(dx, dp, 2.0, 3.0, 1.0, 0.5) + Test.@test dx[1] == 6.5 + Test.@test dp[1] == 2.5 + end + + Test.@testset "vector" begin + dx = [0.0, 0.0] + dp = [0.0, 0.0] + hvf(dx, dp, 2.0, [1.0, 2.0], [0.5, 1.0], 0.5) + Test.@test dx == [2.5, 4.5] + 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) = (dx .= -x; dp .= -p) + hvf = Data.HamiltonianVectorField(f; is_autonomous=true, is_variable=false, is_inplace=true) + + dx = [0.0, 0.0] + dp = [0.0, 0.0] + hvf(dx, dp, 0.0, [1.0, 2.0], [0.5, 1.0], 0.0) + Test.@test dx == [-1.0, -2.0] + Test.@test dp == [-0.5, -1.0] + end + + Test.@testset "NonAutonomous Fixed InPlace uniform" begin + f(dx, dp, t, x, p) = (dx .= t .* x; dp .= t .* p) + hvf = Data.HamiltonianVectorField(f; is_autonomous=false, is_variable=false, is_inplace=true) + + dx = [0.0, 0.0] + dp = [0.0, 0.0] + hvf(dx, dp, 2.0, [1.0, 2.0], [0.5, 1.0], 0.0) + Test.@test dx == [2.0, 4.0] + Test.@test dp == [1.0, 2.0] + end + + Test.@testset "Autonomous NonFixed InPlace uniform" begin + f(dx, dp, x, p, v) = (dx .= x .+ v; dp .= p .+ v) + hvf = Data.HamiltonianVectorField(f; is_autonomous=true, is_variable=true, is_inplace=true) + + dx = [0.0, 0.0] + dp = [0.0, 0.0] + hvf(dx, dp, 0.0, [1.0, 2.0], [0.5, 1.0], 0.5) + Test.@test dx == [1.5, 2.5] + Test.@test dp == [1.0, 1.5] + + # Test variable_costate kwarg + dx = [0.0, 0.0] + dp = [0.0, 0.0] + hvf(dx, dp, 0.0, [1.0, 2.0], [0.5, 1.0], 0.5; variable_costate=false) + Test.@test dx == [1.5, 2.5] + Test.@test dp == [1.0, 1.5] + end + end + + Test.@testset "Show Methods" begin + hvf = Data.HamiltonianVectorField((x, p) -> (x, -p); is_autonomous=true, is_variable=false) + + Test.@testset "Base.show (compact)" begin + io = IOBuffer() + show(io, hvf) + str = String(take!(io)) + Test.@test occursin("HamiltonianVectorField", 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"), hvf) + str = String(take!(io)) + Test.@test occursin("HamiltonianVectorField", str) + Test.@test occursin("autonomous", str) + Test.@test occursin("fixed (no variable)", str) + end + end + + # ==================================================================== + # UNIT TESTS - variable_costate kwarg on user-built HVF + # ==================================================================== + + Test.@testset "variable_costate kwarg on user-built HVF" begin + Test.@testset "OOP NonFixed: variable_costate=true on user-built HVF throws PreconditionError" begin + f = (x, p, v) -> (p, -x) # no variable_costate kwarg + hvf = Data.HamiltonianVectorField(f; is_autonomous=true, is_variable=true) + x = [1.0]; p = [0.5]; v = [0.1] + Test.@test_throws Exception hvf(x, p, v; variable_costate=true) + end + + Test.@testset "IP NonFixed: variable_costate=true on user-built HVF throws PreconditionError" begin + f = (dx, dp, x, p, v) -> (dx .= p; dp .= .-x; nothing) + hvf = Data.HamiltonianVectorField(f; is_autonomous=true, is_variable=true, is_inplace=true) + x = [1.0]; p = [0.5]; v = [0.1] + dx = similar(x); dp = similar(p) + Test.@test_throws Exception hvf(dx, dp, x, p, v; variable_costate=true) + end + + Test.@testset "OOP NonAutonomous NonFixed: variable_costate=true on user-built HVF throws PreconditionError" begin + f = (t, x, p, v) -> (p, -x) # no variable_costate kwarg + hvf = Data.HamiltonianVectorField(f; is_autonomous=false, is_variable=true) + t = 0.5; x = [1.0]; p = [0.5]; v = [0.1] + Test.@test_throws Exception hvf(t, x, p, v; variable_costate=true) + end + + Test.@testset "IP NonAutonomous NonFixed: variable_costate=true on user-built HVF throws PreconditionError" begin + f = (dx, dp, t, x, p, v) -> (dx .= p; dp .= .-x; nothing) + hvf = Data.HamiltonianVectorField(f; is_autonomous=false, is_variable=true, is_inplace=true) + t = 0.5; x = [1.0]; p = [0.5]; v = [0.1] + dx = similar(x); dp = similar(p) + Test.@test_throws Exception hvf(dx, dp, t, x, p, v; variable_costate=true) + end + + Test.@testset "OOP NonFixed: variable_costate=false (default) on user-built HVF works" begin + f = (x, p, v) -> (p, -x) + hvf = Data.HamiltonianVectorField(f; is_autonomous=true, is_variable=true) + x = [1.0]; p = [0.5]; v = [0.1] + result = hvf(x, p, v) + Test.@test result == (p, -x) + end + end + end +end + +end # module + +# CRITICAL: Redefine in outer scope for TestRunner +test_hamiltonian_vector_field() = TestHamiltonianVectorField.test_hamiltonian_vector_field() diff --git a/test/suite/data/test_helpers.jl b/test/suite/data/test_helpers.jl new file mode 100644 index 00000000..ee69991e --- /dev/null +++ b/test/suite/data/test_helpers.jl @@ -0,0 +1,139 @@ +module TestHelpers + +import 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 + +# ============================================================================== +# Test function +# ============================================================================== + +function test_helpers() + Test.@testset "Helpers Tests" verbose=VERBOSE showtiming=SHOWTIMING begin + + # ==================================================================== + # UNIT TESTS - Label Helpers + # ==================================================================== + + Test.@testset "Label Helpers" begin + Test.@testset "_td_label" begin + Test.@test Data._td_label(Traits.Autonomous) == "autonomous" + Test.@test Data._td_label(Traits.NonAutonomous) == "non-autonomous" + end + + Test.@testset "_vd_label" begin + Test.@test Data._vd_label(Traits.Fixed) == "fixed (no variable)" + Test.@test Data._vd_label(Traits.NonFixed) == "variable" + end + + Test.@testset "_md_label" begin + Test.@test Data._md_label(Traits.OutOfPlace) == "out-of-place" + Test.@test Data._md_label(Traits.InPlace) == "in-place" + end + end + + # ==================================================================== + # UNIT TESTS - Hamiltonian Signature Helpers + # ==================================================================== + + Test.@testset "Hamiltonian Signature Helpers" begin + Test.@testset "_natural_sig_h" begin + Test.@test Data._natural_sig_h(Traits.Autonomous, Traits.Fixed) == "h(x, p)" + Test.@test Data._natural_sig_h(Traits.NonAutonomous, Traits.Fixed) == "h(t, x, p)" + Test.@test Data._natural_sig_h(Traits.Autonomous, Traits.NonFixed) == "h(x, p, v)" + Test.@test Data._natural_sig_h(Traits.NonAutonomous, Traits.NonFixed) == "h(t, x, p, v)" + end + + Test.@testset "_uniform_sig_h" begin + Test.@test Data._uniform_sig_h() == "h(t, x, p, v)" + end + end + + # ==================================================================== + # UNIT TESTS - VectorField Signature Helpers + # ==================================================================== + + Test.@testset "VectorField Signature Helpers" begin + Test.@testset "_natural_sig_vf - OutOfPlace" begin + Test.@test Data._natural_sig_vf(Traits.Autonomous, Traits.Fixed, Traits.OutOfPlace) == "f(x)" + Test.@test Data._natural_sig_vf(Traits.NonAutonomous, Traits.Fixed, Traits.OutOfPlace) == "f(t, x)" + Test.@test Data._natural_sig_vf(Traits.Autonomous, Traits.NonFixed, Traits.OutOfPlace) == "f(x, v)" + Test.@test Data._natural_sig_vf(Traits.NonAutonomous, Traits.NonFixed, Traits.OutOfPlace) == "f(t, x, v)" + end + + Test.@testset "_natural_sig_vf - InPlace" begin + Test.@test Data._natural_sig_vf(Traits.Autonomous, Traits.Fixed, Traits.InPlace) == "f(dx, x)" + Test.@test Data._natural_sig_vf(Traits.NonAutonomous, Traits.Fixed, Traits.InPlace) == "f(dx, t, x)" + Test.@test Data._natural_sig_vf(Traits.Autonomous, Traits.NonFixed, Traits.InPlace) == "f(dx, x, v)" + Test.@test Data._natural_sig_vf(Traits.NonAutonomous, Traits.NonFixed, Traits.InPlace) == "f(dx, t, x, v)" + end + + Test.@testset "_uniform_sig_vf" begin + Test.@test Data._uniform_sig_vf(Traits.OutOfPlace) == "f(t, x, v)" + Test.@test Data._uniform_sig_vf(Traits.InPlace) == "f(dx, t, x, v)" + end + end + + # ==================================================================== + # UNIT TESTS - HamiltonianVectorField Signature Helpers + # ==================================================================== + + Test.@testset "HamiltonianVectorField Signature Helpers" begin + Test.@testset "_natural_sig_hvf - OutOfPlace" begin + Test.@test Data._natural_sig_hvf(Traits.Autonomous, Traits.Fixed, Traits.OutOfPlace) == "f(x, p)" + Test.@test Data._natural_sig_hvf(Traits.NonAutonomous, Traits.Fixed, Traits.OutOfPlace) == "f(t, x, p)" + Test.@test Data._natural_sig_hvf(Traits.Autonomous, Traits.NonFixed, Traits.OutOfPlace) == "f(x, p, v)" + Test.@test Data._natural_sig_hvf(Traits.NonAutonomous, Traits.NonFixed, Traits.OutOfPlace) == "f(t, x, p, v)" + end + + Test.@testset "_natural_sig_hvf - InPlace" begin + Test.@test Data._natural_sig_hvf(Traits.Autonomous, Traits.Fixed, Traits.InPlace) == "f(dx, dp, x, p)" + Test.@test Data._natural_sig_hvf(Traits.NonAutonomous, Traits.Fixed, Traits.InPlace) == "f(dx, dp, t, x, p)" + Test.@test Data._natural_sig_hvf(Traits.Autonomous, Traits.NonFixed, Traits.InPlace) == "f(dx, dp, x, p, v)" + Test.@test Data._natural_sig_hvf(Traits.NonAutonomous, Traits.NonFixed, Traits.InPlace) == "f(dx, dp, t, x, p, v)" + end + + Test.@testset "_uniform_sig_hvf" begin + Test.@test Data._uniform_sig_hvf(Traits.OutOfPlace) == "f(t, x, p, v)" + Test.@test Data._uniform_sig_hvf(Traits.InPlace) == "f(dx, dp, t, x, p, v)" + end + + Test.@testset "Uniform signatures" begin + Test.@testset "VectorField uniform signatures" begin + Test.@test Data._uniform_sig_vf(Traits.OutOfPlace) == "f(t, x, v)" + Test.@test Data._uniform_sig_vf(Traits.InPlace) == "f(dx, t, x, v)" + end + + Test.@testset "HamiltonianVectorField uniform signatures" begin + Test.@test Data._uniform_sig_hvf(Traits.OutOfPlace) == "f(t, x, p, v)" + Test.@test Data._uniform_sig_hvf(Traits.InPlace) == "f(dx, dp, t, x, p, v)" + end + end + + Test.@testset "Direct label calls" begin + Test.@testset "Time dependence labels" begin + Test.@test Data._td_label(Traits.Autonomous) == "autonomous" + Test.@test Data._td_label(Traits.NonAutonomous) == "non-autonomous" + end + + Test.@testset "Variable dependence labels" begin + Test.@test Data._vd_label(Traits.Fixed) == "fixed (no variable)" + Test.@test Data._vd_label(Traits.NonFixed) == "variable" + end + + Test.@testset "Mutability labels" begin + Test.@test Data._md_label(Traits.OutOfPlace) == "out-of-place" + Test.@test Data._md_label(Traits.InPlace) == "in-place" + end + end + end + end +end + +end # module + +# CRITICAL: Redefine in outer scope for TestRunner +test_helpers() = TestHelpers.test_helpers() diff --git a/test/suite/data/test_vector_field.jl b/test/suite/data/test_vector_field.jl new file mode 100644 index 00000000..324505b7 --- /dev/null +++ b/test/suite/data/test_vector_field.jl @@ -0,0 +1,507 @@ +module TestVectorField + +import Test +import CTBase.Data +import CTBase.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_f(x::Int) = -x +_multi_method_f(x::Float64) = -x +_multi_method_f(x::AbstractVector) = -x + +# TOP-LEVEL: Fake function with invalid arity for testing error branches +_bad_arity_f(x, y, z) = x + y + z + +# ============================================================================== +# Test function +# ============================================================================== + +function test_vector_field() + Test.@testset "VectorField Tests" verbose=VERBOSE showtiming=SHOWTIMING begin + + # ==================================================================== + # UNIT TESTS - Abstract Types + # ==================================================================== + + Test.@testset "Abstract Types" begin + vf = Data.VectorField(x -> x; is_autonomous=true, is_variable=false) + Test.@test vf isa Data.VectorField + end + + # ==================================================================== + # UNIT TESTS - Construction + # ==================================================================== + + Test.@testset "Construction" begin + Test.@testset "keyword constructor with defaults" begin + vf = Data.VectorField(x -> x) + Test.@test vf isa Data.VectorField + Test.@test Traits.time_dependence(vf) === Traits.Autonomous + Test.@test Traits.variable_dependence(vf) === Traits.Fixed + end + + Test.@testset "keyword constructor with explicit flags" begin + vf_autonomous = Data.VectorField(x -> x; is_autonomous=true, is_variable=false) + Test.@test Traits.time_dependence(vf_autonomous) === Traits.Autonomous + Test.@test Traits.variable_dependence(vf_autonomous) === Traits.Fixed + + vf_nonautonomous = Data.VectorField((t, x) -> t .* x; is_autonomous=false, is_variable=false) + Test.@test Traits.time_dependence(vf_nonautonomous) === Traits.NonAutonomous + Test.@test Traits.variable_dependence(vf_nonautonomous) === Traits.Fixed + + vf_nonfixed = Data.VectorField((x, v) -> x .+ v; is_autonomous=true, is_variable=true) + Test.@test Traits.time_dependence(vf_nonfixed) === Traits.Autonomous + Test.@test Traits.variable_dependence(vf_nonfixed) === Traits.NonFixed + + vf_full = Data.VectorField((t, x, v) -> t .* x .+ v; is_autonomous=false, is_variable=true) + Test.@test Traits.time_dependence(vf_full) === Traits.NonAutonomous + Test.@test Traits.variable_dependence(vf_full) === Traits.NonFixed + end + end + + # ==================================================================== + # UNIT TESTS - Trait Methods + # ==================================================================== + + Test.@testset "Trait Methods" begin + vf_aut = Data.VectorField(x -> x; is_autonomous=true, is_variable=false) + vf_nonaut = Data.VectorField((t, x) -> t .* x; is_autonomous=false, is_variable=false) + vf_fixed = Data.VectorField(x -> x; is_autonomous=true, is_variable=false) + vf_nonfixed = Data.VectorField((x, v) -> x .+ v; is_autonomous=true, is_variable=true) + + Test.@testset "has_time_dependence_trait returns true" begin + Test.@test Traits.has_time_dependence_trait(vf_aut) === true + Test.@test Traits.has_time_dependence_trait(vf_nonaut) === true + end + + Test.@testset "has_variable_dependence_trait returns true" begin + Test.@test Traits.has_variable_dependence_trait(vf_fixed) === true + Test.@test Traits.has_variable_dependence_trait(vf_nonfixed) === true + end + + Test.@testset "time_dependence returns correct trait" begin + Test.@test Traits.time_dependence(vf_aut) === Traits.Autonomous + Test.@test Traits.time_dependence(vf_nonaut) === Traits.NonAutonomous + end + + Test.@testset "variable_dependence returns correct trait" begin + Test.@test Traits.variable_dependence(vf_fixed) === Traits.Fixed + Test.@test Traits.variable_dependence(vf_nonfixed) === Traits.NonFixed + end + end + + # ==================================================================== + # UNIT TESTS - Natural Call Signatures + # ==================================================================== + + Test.@testset "Natural Call Signatures" begin + Test.@testset "Autonomous Fixed - (x)" begin + vf = Data.VectorField(x -> -x; is_autonomous=true, is_variable=false) + + Test.@testset "scalar" begin + Test.@test vf(3.0) == -3.0 + end + + Test.@testset "vector" begin + Test.@test vf([1.0, 2.0]) == [-1.0, -2.0] + end + + Test.@testset "matrix" begin + x0 = [1.0 2.0; 3.0 4.0] + result = vf(x0) + Test.@test result == -x0 + end + end + + Test.@testset "NonAutonomous Fixed - (t, x)" begin + vf = Data.VectorField((t, x) -> t .* x; is_autonomous=false, is_variable=false) + + Test.@testset "scalar" begin + Test.@test vf(2.0, 3.0) == 6.0 + end + + Test.@testset "vector" begin + Test.@test vf(2.0, [1.0, 2.0]) == [2.0, 4.0] + end + + Test.@testset "matrix" begin + x0 = [1.0 2.0; 3.0 4.0] + result = vf(2.0, x0) + Test.@test result == 2 .* x0 + end + end + + Test.@testset "Autonomous NonFixed - (x, v)" begin + vf = Data.VectorField((x, v) -> x .+ v; is_autonomous=true, is_variable=true) + + Test.@testset "scalar" begin + Test.@test vf(3.0, 0.5) == 3.5 + end + + Test.@testset "vector" begin + Test.@test vf([1.0, 2.0], 0.5) == [1.5, 2.5] + end + + Test.@testset "matrix" begin + x0 = [1.0 2.0; 3.0 4.0] + result = vf(x0, 0.5) + Test.@test result == x0 .+ 0.5 + end + end + + Test.@testset "NonAutonomous NonFixed - (t, x, v)" begin + vf = Data.VectorField((t, x, v) -> t .* x .+ v; is_autonomous=false, is_variable=true) + + Test.@testset "scalar" begin + Test.@test vf(2.0, 3.0, 0.5) == 6.5 + end + + Test.@testset "vector" begin + Test.@test vf(2.0, [1.0, 2.0], 0.5) == [2.5, 4.5] + end + + Test.@testset "matrix" begin + x0 = [1.0 2.0; 3.0 4.0] + result = vf(2.0, x0, 0.5) + Test.@test result == 2 .* x0 .+ 0.5 + end + end + end + + # ==================================================================== + # UNIT TESTS - Uniform Call Signature (t, x, v) + # ==================================================================== + + Test.@testset "Uniform Call Signature" begin + Test.@testset "Autonomous Fixed ignores t and v" begin + vf = Data.VectorField(x -> -x; is_autonomous=true, is_variable=false) + + Test.@testset "scalar" begin + Test.@test vf(0.0, 3.0, 0.0) == -3.0 + end + + Test.@testset "vector" begin + Test.@test vf(0.0, [1.0, 2.0], 0.0) == [-1.0, -2.0] + end + + Test.@testset "matrix" begin + x0 = [1.0 2.0; 3.0 4.0] + result = vf(0.0, x0, 0.0) + Test.@test result == -x0 + end + end + + Test.@testset "NonAutonomous Fixed uses t, ignores v" begin + vf = Data.VectorField((t, x) -> t .* x; is_autonomous=false, is_variable=false) + + Test.@testset "scalar" begin + Test.@test vf(2.0, 3.0, 0.0) == 6.0 + end + + Test.@testset "vector" begin + Test.@test vf(2.0, [1.0, 2.0], 0.0) == [2.0, 4.0] + end + + Test.@testset "matrix" begin + x0 = [1.0 2.0; 3.0 4.0] + result = vf(2.0, x0, 0.0) + Test.@test result == 2 .* x0 + end + end + + Test.@testset "Autonomous NonFixed ignores t, uses v" begin + vf = Data.VectorField((x, v) -> x .+ v; is_autonomous=true, is_variable=true) + + Test.@testset "scalar" begin + Test.@test vf(0.0, 3.0, 0.5) == 3.5 + end + + Test.@testset "vector" begin + Test.@test vf(0.0, [1.0, 2.0], 0.5) == [1.5, 2.5] + end + + Test.@testset "matrix" begin + x0 = [1.0 2.0; 3.0 4.0] + result = vf(0.0, x0, 0.5) + Test.@test result == x0 .+ 0.5 + end + end + end + + # ==================================================================== + # UNIT TESTS - InPlace Call Signatures + # ==================================================================== + + Test.@testset "InPlace Call Signatures" begin + Test.@testset "Autonomous Fixed - (dx, x)" begin + f(dx, x) = dx .= -x + vf = Data.VectorField(f; is_autonomous=true, is_variable=false, is_inplace=true) + + Test.@testset "scalar" begin + dx = [0.0] + x = 3.0 + vf(dx, x) + Test.@test dx[1] == -3.0 + end + + Test.@testset "vector" begin + dx = [0.0, 0.0] + x = [1.0, 2.0] + vf(dx, x) + Test.@test dx == [-1.0, -2.0] + end + end + + Test.@testset "NonAutonomous Fixed - (dx, t, x)" begin + f(dx, t, x) = dx .= t .* x + vf = Data.VectorField(f; is_autonomous=false, is_variable=false, is_inplace=true) + + Test.@testset "scalar" begin + dx = [0.0] + vf(dx, 2.0, 3.0) + Test.@test dx[1] == 6.0 + end + + Test.@testset "vector" begin + dx = [0.0, 0.0] + vf(dx, 2.0, [1.0, 2.0]) + Test.@test dx == [2.0, 4.0] + end + end + + Test.@testset "Autonomous NonFixed - (dx, x, v)" begin + f(dx, x, v) = dx .= x .+ v + vf = Data.VectorField(f; is_autonomous=true, is_variable=true, is_inplace=true) + + Test.@testset "scalar" begin + dx = [0.0] + vf(dx, 3.0, 0.5) + Test.@test dx[1] == 3.5 + end + + Test.@testset "vector" begin + dx = [0.0, 0.0] + vf(dx, [1.0, 2.0], 0.5) + Test.@test dx == [1.5, 2.5] + end + end + + Test.@testset "NonAutonomous NonFixed - (dx, t, x, v)" begin + f(dx, t, x, v) = dx .= t .* x .+ v + vf = Data.VectorField(f; is_autonomous=false, is_variable=true, is_inplace=true) + + Test.@testset "scalar" begin + dx = [0.0] + vf(dx, 2.0, 3.0, 0.5) + Test.@test dx[1] == 6.5 + end + + Test.@testset "vector" begin + dx = [0.0, 0.0] + vf(dx, 2.0, [1.0, 2.0], 0.5) + Test.@test dx == [2.5, 4.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, x) = dx .= -x + vf = Data.VectorField(f; is_autonomous=true, is_variable=false, is_inplace=true) + + dx = [0.0, 0.0] + vf(dx, 0.0, [1.0, 2.0], 0.0) + Test.@test dx == [-1.0, -2.0] + end + + Test.@testset "NonAutonomous Fixed InPlace uniform" begin + f(dx, t, x) = dx .= t .* x + vf = Data.VectorField(f; is_autonomous=false, is_variable=false, is_inplace=true) + + dx = [0.0, 0.0] + vf(dx, 2.0, [1.0, 2.0], 0.0) + Test.@test dx == [2.0, 4.0] + end + + Test.@testset "Autonomous NonFixed InPlace uniform" begin + f(dx, x, v) = dx .= x .+ v + vf = Data.VectorField(f; is_autonomous=true, is_variable=true, is_inplace=true) + + dx = [0.0, 0.0] + vf(dx, 0.0, [1.0, 2.0], 0.5) + Test.@test dx == [1.5, 2.5] + end + + Test.@testset "NonAutonomous NonFixed InPlace uniform" begin + # natural signature (dx, t, x, v) coincides with the uniform one + f(dx, t, x, v) = dx .= t .* x .+ v + vf = Data.VectorField(f; is_autonomous=false, is_variable=true, is_inplace=true) + + dx = [0.0, 0.0] + vf(dx, 2.0, [1.0, 2.0], 0.5) + Test.@test dx == [2.5, 4.5] + end + end + + # ==================================================================== + # UNIT TESTS - Typed constructor (trait types passed positionally) + # ==================================================================== + + Test.@testset "Typed constructor" begin + vf = Data.VectorField(x -> -x, Traits.Autonomous, Traits.Fixed, Traits.OutOfPlace) + Test.@test vf isa Data.VectorField + Test.@test Traits.time_dependence(vf) === Traits.Autonomous + Test.@test Traits.variable_dependence(vf) === Traits.Fixed + Test.@test Traits.mutability(vf) === Traits.OutOfPlace + Test.@test vf(3.0) == -3.0 + + vf_ip = Data.VectorField( + (dx, x) -> (dx .= -x), Traits.Autonomous, Traits.Fixed, Traits.InPlace + ) + Test.@test Traits.mutability(vf_ip) === Traits.InPlace + end + + Test.@testset "Common Trait Predicates" begin + vf_aut = Data.VectorField(x -> x; is_autonomous=true, is_variable=false) + vf_nonaut = Data.VectorField((t, x) -> t .* x; is_autonomous=false, is_variable=false) + vf_fixed = Data.VectorField(x -> x; is_autonomous=true, is_variable=false) + vf_nonfixed = Data.VectorField((x, v) -> x .+ v; is_autonomous=true, is_variable=true) + + Test.@testset "is_autonomous / is_nonautonomous" begin + Test.@test Traits.is_autonomous(vf_aut) === true + Test.@test Traits.is_nonautonomous(vf_aut) === false + Test.@test Traits.is_autonomous(vf_nonaut) === false + Test.@test Traits.is_nonautonomous(vf_nonaut) === true + end + + Test.@testset "is_variable / is_nonvariable" begin + Test.@test Traits.is_variable(vf_fixed) === false + Test.@test Traits.is_nonvariable(vf_fixed) === true + Test.@test Traits.is_variable(vf_nonfixed) === true + Test.@test Traits.is_nonvariable(vf_nonfixed) === false + end + end + + # ==================================================================== + # UNIT TESTS - Show Methods + # ==================================================================== + + Test.@testset "Show Methods" begin + vf = Data.VectorField(x -> x; is_autonomous=true, is_variable=false) + + Test.@testset "Base.show (compact)" begin + io = IOBuffer() + show(io, vf) + str = String(take!(io)) + Test.@test occursin("VectorField", 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"), vf) + str = String(take!(io)) + Test.@test occursin("VectorField", str) + Test.@test occursin("autonomous", str) + Test.@test occursin("fixed (no variable)", str) + end + end + + # ==================================================================== + # UNIT TESTS - Subtyping + # ==================================================================== + + Test.@testset "Subtyping" begin + Test.@testset "VectorField is an AbstractVectorField" begin + vf = Data.VectorField(x -> -x; is_autonomous=true, is_variable=false) + Test.@test vf isa Data.AbstractVectorField + end + end + + # ==================================================================== + # UNIT TESTS - Exports Verification + # ==================================================================== + + Test.@testset "Exports Verification" begin + Test.@testset "Exported types" begin + Test.@test isdefined(Data, :VectorField) + end + end + + # ==================================================================== + # UNIT TESTS - Explicit is_inplace parameter + # ==================================================================== + + Test.@testset "Explicit is_inplace parameter" begin + Test.@testset "is_inplace=true creates InPlace VectorField" begin + # Define an out-of-place function but force InPlace + f(x) = -x + vf = Data.VectorField(f; is_inplace=true) + Test.@test Traits.mutability(vf) === Traits.InPlace + end + + Test.@testset "is_inplace=false creates OutOfPlace VectorField" begin + # Define an in-place function but force OutOfPlace + f(x) = -x + vf = Data.VectorField(f; is_inplace=false) + Test.@test Traits.mutability(vf) === 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.VectorField(_multi_method_f) + end + + Test.@testset "No error when is_inplace is explicitly specified" begin + vf = Data.VectorField(_multi_method_f; is_inplace=false) + Test.@test Traits.mutability(vf) === Traits.OutOfPlace + end + end + + # ==================================================================== + # UNIT TESTS - Internal Helpers + # ==================================================================== + + Test.@testset "Internal Helpers" begin + Test.@testset "_oop_arity_vf" begin + Test.@test Data._oop_arity_vf(Traits.Autonomous, Traits.Fixed) == 1 + Test.@test Data._oop_arity_vf(Traits.NonAutonomous, Traits.Fixed) == 2 + Test.@test Data._oop_arity_vf(Traits.Autonomous, Traits.NonFixed) == 2 + Test.@test Data._oop_arity_vf(Traits.NonAutonomous, Traits.NonFixed) == 3 + end + + Test.@testset "_natural_sig_vf helpers" begin + Test.@test Data._natural_sig_vf(Traits.Autonomous, Traits.Fixed, Traits.OutOfPlace) == "f(x)" + Test.@test Data._natural_sig_vf(Traits.NonAutonomous, Traits.Fixed, Traits.OutOfPlace) == "f(t, x)" + Test.@test Data._natural_sig_vf(Traits.Autonomous, Traits.Fixed, Traits.InPlace) == "f(dx, x)" + Test.@test Data._uniform_sig_vf(Traits.OutOfPlace) == "f(t, x, v)" + Test.@test Data._uniform_sig_vf(Traits.InPlace) == "f(dx, t, x, v)" + end + + Test.@testset "_detect_mutability_vf invalid arity" begin + Test.@test_throws Exceptions.IncorrectArgument Data._detect_mutability_vf(_bad_arity_f, Traits.Autonomous, Traits.Fixed) + end + end + end +end + +end # module + +test_vector_field() = TestVectorField.test_vector_field() From a870d5bd243e4e3a44dbd3423e1222772e50b87a Mon Sep 17 00:00:00 2001 From: Olivier Cots Date: Thu, 25 Jun 2026 00:24:33 +0200 Subject: [PATCH 3/8] docs: add Data guide and API reference Co-Authored-By: Claude Sonnet 4.6 --- docs/api_reference.jl | 22 ++-- docs/make.jl | 1 + docs/src/guide/data.md | 238 +++++++++++++++++++++++++++++++++++++++++ 3 files changed, 254 insertions(+), 7 deletions(-) create mode 100644 docs/src/guide/data.md diff --git a/docs/api_reference.jl b/docs/api_reference.jl index 8a4db600..b87cea63 100644 --- a/docs/api_reference.jl +++ b/docs/api_reference.jl @@ -24,6 +24,14 @@ function generate_api_reference(src_dir::String) joinpath("Core", "function_utils.jl"), joinpath("Core", "macros.jl"), joinpath("Core", "palette.jl"), joinpath("Core", "display.jl"), )), + (mod=CTBase.Data, title="Data", filename="data", files=src( + joinpath("Data", "Data.jl"), joinpath("Data", "default.jl"), + joinpath("Data", "helpers.jl"), + joinpath("Data", "abstract_vector_field.jl"), joinpath("Data", "vector_field.jl"), + joinpath("Data", "abstract_hamiltonian.jl"), joinpath("Data", "hamiltonian.jl"), + joinpath("Data", "abstract_hamiltonian_vector_field.jl"), + joinpath("Data", "hamiltonian_vector_field.jl"), + )), (mod=CTBase.Descriptions, title="Descriptions", filename="descriptions", files=src( joinpath("Descriptions", "Descriptions.jl"), joinpath("Descriptions", "types.jl"), joinpath("Descriptions", "similarity.jl"), joinpath("Descriptions", "display.jl"), @@ -42,13 +50,6 @@ function generate_api_reference(src_dir::String) joinpath("Interpolation", "Interpolation.jl"), joinpath("Interpolation", "types.jl"), joinpath("Interpolation", "ctinterpolate.jl"), joinpath("Interpolation", "display.jl"), )), - (mod=CTBase.Traits, title="Traits", filename="traits", files=src( - joinpath("Traits", "Traits.jl"), joinpath("Traits", "helpers.jl"), - joinpath("Traits", "abstract.jl"), joinpath("Traits", "mode.jl"), - joinpath("Traits", "dynamics.jl"), joinpath("Traits", "ad.jl"), - joinpath("Traits", "variable_costate.jl"), joinpath("Traits", "mutability.jl"), - joinpath("Traits", "time_dependence.jl"), joinpath("Traits", "variable_dependence.jl"), - )), (mod=CTBase.Options, title="Options", filename="options", files=src( joinpath("Options", "Options.jl"), joinpath("Options", "not_provided.jl"), joinpath("Options", "option_value.jl"), joinpath("Options", "option_definition.jl"), @@ -77,6 +78,13 @@ function generate_api_reference(src_dir::String) joinpath("Strategies", "api", "validation_helpers.jl"), joinpath("Strategies", "api", "disambiguation.jl"), )), + (mod=CTBase.Traits, title="Traits", filename="traits", files=src( + joinpath("Traits", "Traits.jl"), joinpath("Traits", "helpers.jl"), + joinpath("Traits", "abstract.jl"), joinpath("Traits", "mode.jl"), + joinpath("Traits", "dynamics.jl"), joinpath("Traits", "ad.jl"), + joinpath("Traits", "variable_costate.jl"), joinpath("Traits", "mutability.jl"), + joinpath("Traits", "time_dependence.jl"), joinpath("Traits", "variable_dependence.jl"), + )), (mod=CTBase.Unicode, title="Unicode", filename="unicode", files=src( joinpath("Unicode", "Unicode.jl"), joinpath("Unicode", "subscripts.jl"), joinpath("Unicode", "superscripts.jl"), diff --git a/docs/make.jl b/docs/make.jl index 9ef1b8d6..fcb2a18f 100644 --- a/docs/make.jl +++ b/docs/make.jl @@ -70,6 +70,7 @@ with_api_reference(src_dir) do api_pages pages=[ "Getting Started" => "getting-started.md", "Core Concepts" => [ + "Data" => joinpath("guide", "data.md"), "Descriptions" => joinpath("guide", "descriptions.md"), "Exceptions" => joinpath("guide", "exceptions.md"), "Traits" => joinpath("guide", "traits.md"), diff --git a/docs/src/guide/data.md b/docs/src/guide/data.md new file mode 100644 index 00000000..a8156d06 --- /dev/null +++ b/docs/src/guide/data.md @@ -0,0 +1,238 @@ +# Data: typed function wrappers + +```@meta +CurrentModule = CTBase +``` + +The [`CTBase.Data`](@ref CTBase.Data) submodule provides typed wrappers that carry +a Julia function together with its **trait metadata** (see the [Traits](traits.md) +guide). Each wrapper knows, at the type level, whether it depends on time, whether +it depends on an extra variable, and whether it is evaluated in-place. + +```@setup data +using CTBase +using CTBase.Data +using CTBase.Traits +using LinearAlgebra +``` + +## Overview + +| Type | Mathematical object | Natural signature (autonomous/fixed) | +|---|---|---| +| [`Data.VectorField`](@ref CTBase.Data.VectorField) | ``X : \mathcal{X} \to \mathbb{R}^n`` | `X(x)` | +| [`Data.Hamiltonian`](@ref CTBase.Data.Hamiltonian) | ``H : T^*\mathcal{X} \to \mathbb{R}`` | `H(x, p)` | +| [`Data.HamiltonianVectorField`](@ref CTBase.Data.HamiltonianVectorField) | ``\vec{H} : T^*\mathcal{X} \to \mathbb{R}^{2n}`` | `HVF(x, p)` | + +All three share the same trait axes — time dependence, variable dependence and +mutability. See [Traits](traits.md) for the full call-signature tables. + +--- + +## VectorField + +A `VectorField` wraps a Julia function representing ``\dot{x} = X(\cdots)``. + +### Construction + +```@example data +using CTBase.Data, CTBase.Traits + +# Autonomous, fixed (default): X(x) +vf1 = Data.VectorField(x -> -x) + +# Non-autonomous, fixed: X(t, x) +vf2 = Data.VectorField((t, x) -> t .* x; is_autonomous=false) + +# Autonomous, non-fixed: X(x, v) +vf3 = Data.VectorField((x, v) -> x .* v; is_variable=true) + +# Non-autonomous, non-fixed: X(t, x, v) +vf4 = Data.VectorField((t, x, v) -> t .* x .+ v; is_autonomous=false, is_variable=true) + +vf1 +``` + +The `show` output summarises the traits and both call signatures (natural and +uniform). + +### In-place construction + +Prefer out-of-place for clarity. Use in-place when avoiding allocations matters: + +```@example data +# In-place Autonomous/Fixed: f(dx, x) — mutability auto-detected +vf_ip = Data.VectorField((dx, x) -> (dx .= -x; nothing)) +Traits.mutability(vf_ip) +``` + +If the function has multiple methods, auto-detection fails; pass `is_inplace` +explicitly: + +```@example data +f_multi(x) = -x +f_multi(dx, x) = (dx .= -x; nothing) + +vf_explicit = Data.VectorField(f_multi; is_inplace=true) +``` + +### Calling + +```@example data +x0 = [1.0, 0.5] + +vf1(x0) # natural call +vf1(0.0, x0, nothing) # uniform call (ignores t and v) +vf2(0.5, x0) # non-autonomous natural call +``` + +--- + +## Hamiltonian + +A `Hamiltonian` wraps a scalar function ``H(x, p) \in \mathbb{R}``. + +```math +H : T^*\mathcal{X} \to \mathbb{R}, \quad (x, p) \mapsto H(x, p). +``` + +### Construction + +```@example data +# Autonomous, fixed (default): H(x, p) +h1 = Data.Hamiltonian((x, p) -> dot(p, x)) + +# Non-autonomous, fixed: H(t, x, p) +h2 = Data.Hamiltonian((t, x, p) -> t * dot(p, x); is_autonomous=false) + +# Autonomous, non-fixed: H(x, p, v) +h3 = Data.Hamiltonian((x, p, v) -> dot(p, x) * v[1]; is_variable=true) + +h1 +``` + +### Calling + +```@example data +x0, p0 = [1.0, 0.5], [0.3, 0.7] + +h1(x0, p0) # natural call +h1(0.0, x0, p0, nothing) # uniform call +h2(0.5, x0, p0) # non-autonomous +``` + +--- + +## HamiltonianVectorField + +A `HamiltonianVectorField` wraps the map +``(x, p) \mapsto (\dot{x}, \dot{p}) = (\partial_p H, -\partial_x H)`` +when the derivatives are provided **explicitly** (no automatic differentiation). + +```math +\vec{H}(x, p) = \bigl(\partial_p H(x,p),\; -\partial_x H(x,p)\bigr). +``` + +Use this when the Hamiltonian equations are known analytically. When only the +scalar Hamiltonian is available and the derivatives must be obtained by automatic +differentiation, use [`Data.Hamiltonian`](@ref CTBase.Data.Hamiltonian) instead. + +### Construction + +```@example data +# Harmonic oscillator H = (x²+p²)/2: ẋ = p, ṗ = -x +hvf = Data.HamiltonianVectorField((x, p) -> (p, -x)) + +# With time dependence +hvf_na = Data.HamiltonianVectorField((t, x, p) -> (p, -x .* t); is_autonomous=false) + +hvf +``` + +### Calling + +```@example data +dx, dp = hvf(x0, p0) # natural call: returns (ẋ, ṗ) +(dx, dp) +``` + +### The `variable_costate` keyword + +For **non-fixed** Hamiltonian vector fields, the call accepts an optional +`variable_costate` keyword (default `false`): + +```julia +hvf(x, p, v; variable_costate=true) # out-of-place, Autonomous/NonFixed +hvf(dx, dp, x, p, v; variable_costate=true) # in-place +``` + +When `true`, the field also propagates the derivative of the costate with respect +to the variable `v`. This is only supported by Hamiltonian vector fields whose +inner function implements that extra path — typically those **derived from a +[`Data.Hamiltonian`](@ref CTBase.Data.Hamiltonian)** by differentiation. For a +plain user-supplied function the default `variable_costate=false` is the only +valid value; passing `true` raises a +[`CTBase.Exceptions.PreconditionError`](@ref) (see the [Exceptions](exceptions.md) +guide). + +--- + +## Querying traits + +Because the trait metadata lives in the type, it can be recovered from any data +object through the [Traits](traits.md) accessors — with no runtime cost: + +```@example data +Traits.time_dependence(vf2) # NonAutonomous +``` + +```@example data +Traits.variable_dependence(vf3) # NonFixed +``` + +```@example data +Traits.mutability(vf_ip) # InPlace +``` + +The boolean predicates follow generically: + +```@example data +Traits.is_autonomous(vf1), Traits.is_variable(vf1), Traits.is_inplace(vf1) +``` + +Each data family also reports its dynamics trait via +[`Traits.dynamics_trait`](@ref CTBase.Traits.dynamics_trait): + +```@example data +Traits.dynamics_trait(vf1), Traits.dynamics_trait(h1), Traits.dynamics_trait(hvf) +``` + +A `VectorField` carries [`Traits.StateDynamics`](@ref CTBase.Traits.StateDynamics), +while both `Hamiltonian` and `HamiltonianVectorField` carry +[`Traits.HamiltonianDynamics`](@ref CTBase.Traits.HamiltonianDynamics). + +--- + +## Typed constructors + +A lower-level constructor takes the trait types directly (no keyword inference). +It is used internally by code that produces typed data objects programmatically: + +```@example data +vf_typed = Data.VectorField(x -> 2x, Traits.Autonomous, Traits.Fixed, Traits.OutOfPlace) +Traits.time_dependence(vf_typed) +``` + +The same positional form exists for [`Data.Hamiltonian`](@ref CTBase.Data.Hamiltonian) +(`Hamiltonian(f, TD, VD)`) and +[`Data.HamiltonianVectorField`](@ref CTBase.Data.HamiltonianVectorField) +(`HamiltonianVectorField(f, TD, VD, MD)`). + +--- + +## See also + +- [`Data.VectorField`](@ref CTBase.Data.VectorField), [`Data.Hamiltonian`](@ref CTBase.Data.Hamiltonian), [`Data.HamiltonianVectorField`](@ref CTBase.Data.HamiltonianVectorField) — concrete data types. +- [`Data.AbstractVectorField`](@ref CTBase.Data.AbstractVectorField), [`Data.AbstractHamiltonian`](@ref CTBase.Data.AbstractHamiltonian), [`Data.AbstractHamiltonianVectorField`](@ref CTBase.Data.AbstractHamiltonianVectorField) — abstract supertypes. +- [Traits](traits.md) — the three trait axes, the dynamics trait, and call-signature tables. +- [Exceptions](exceptions.md) — `PreconditionError` and `IncorrectArgument`, raised by the constructors and the `variable_costate` path. From 4e28db42609f44a2caa790306e3bbead5658c725 Mon Sep 17 00:00:00 2001 From: Olivier Cots Date: Thu, 25 Jun 2026 00:26:26 +0200 Subject: [PATCH 4/8] test: rename TestOptions to TestData in test runner Co-Authored-By: Claude Sonnet 4.6 --- test/runtests.jl | 4 ++-- test/suite/core/test_core_display.jl | 4 ++-- test/suite/core/test_core_types.jl | 4 ++-- test/suite/core/test_function_utils.jl | 4 ++-- test/suite/core/test_macros.jl | 4 ++-- test/suite/core/test_matrix_utils.jl | 4 ++-- test/suite/core/test_palette.jl | 4 ++-- test/suite/core/test_utils.jl | 4 ++-- test/suite/descriptions/test_catalog.jl | 4 ++-- test/suite/descriptions/test_complete.jl | 4 ++-- test/suite/descriptions/test_description_types.jl | 4 ++-- test/suite/descriptions/test_display_description.jl | 4 ++-- test/suite/descriptions/test_integration.jl | 4 ++-- test/suite/descriptions/test_remove.jl | 4 ++-- test/suite/descriptions/test_similarity.jl | 4 ++-- test/suite/exceptions/test_exception_display.jl | 4 ++-- test/suite/exceptions/test_exception_types.jl | 4 ++-- test/suite/exceptions/test_exceptions.jl | 4 ++-- test/suite/exceptions/test_extension_error.jl | 4 ++-- test/suite/extensions/test_coverage_helpers.jl | 4 ++-- test/suite/extensions/test_coverage_post_process.jl | 4 ++-- test/suite/extensions/test_documenter_reference.jl | 4 ++-- test/suite/extensions/test_documenter_reference_api.jl | 4 ++-- .../extensions/test_documenter_reference_config_helpers.jl | 4 ++-- .../extensions/test_documenter_reference_page_building.jl | 4 ++-- test/suite/extensions/test_documenter_reference_pipeline.jl | 4 ++-- .../extensions/test_documenter_reference_symbol_helpers.jl | 4 ++-- .../extensions/test_documenter_reference_type_formatting.jl | 4 ++-- test/suite/extensions/test_documenter_reference_types.jl | 4 ++-- test/suite/extensions/test_extension_stubs.jl | 4 ++-- test/suite/extensions/test_testrunner.jl | 4 ++-- test/suite/extensions/test_testrunner_arg_parsing.jl | 4 ++-- test/suite/extensions/test_testrunner_execution.jl | 4 ++-- test/suite/extensions/test_testrunner_progress.jl | 4 ++-- test/suite/extensions/test_testrunner_selection.jl | 4 ++-- test/suite/extensions/test_testrunner_types.jl | 4 ++-- test/suite/interpolation/test_interpolation.jl | 4 ++-- test/suite/meta/test_code_quality.jl | 4 ++-- test/suite/options/test_options.jl | 1 + test/suite/options/test_options_value.jl | 1 + test/suite/traits/test_abstract_traits.jl | 4 ++-- test/suite/traits/test_ad.jl | 4 ++-- test/suite/traits/test_dynamics.jl | 4 ++-- test/suite/traits/test_mode.jl | 4 ++-- test/suite/traits/test_mutability.jl | 4 ++-- test/suite/traits/test_time_dependence.jl | 4 ++-- test/suite/traits/test_traits_module.jl | 4 ++-- test/suite/traits/test_variable_costate.jl | 4 ++-- test/suite/traits/test_variable_dependence.jl | 4 ++-- test/suite/unicode/test_subscripts.jl | 4 ++-- test/suite/unicode/test_superscripts.jl | 4 ++-- 51 files changed, 100 insertions(+), 98 deletions(-) diff --git a/test/runtests.jl b/test/runtests.jl index 08623a07..43e0aa8d 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -9,11 +9,11 @@ using Test: Test using CTBase: CTBase # Controls nested testset output formatting (used by individual test files) -module TestOptions +module TestData const VERBOSE = true const SHOWTIMING = true end -using .TestOptions: VERBOSE, SHOWTIMING +using .TestData: VERBOSE, SHOWTIMING # Macro to check if an expression is type-stable and inferred correctly macro test_inferred(expr) diff --git a/test/suite/core/test_core_display.jl b/test/suite/core/test_core_display.jl index 407f60ec..d0d416ae 100644 --- a/test/suite/core/test_core_display.jl +++ b/test/suite/core/test_core_display.jl @@ -3,8 +3,8 @@ module TestCoreDisplay using Test: Test import CTBase.Core -const VERBOSE = isdefined(Main, :TestOptions) ? Main.TestOptions.VERBOSE : true -const SHOWTIMING = isdefined(Main, :TestOptions) ? Main.TestOptions.SHOWTIMING : true +const VERBOSE = isdefined(Main, :TestData) ? Main.TestData.VERBOSE : true +const SHOWTIMING = isdefined(Main, :TestData) ? Main.TestData.SHOWTIMING : true function test_core_display() io_plain = IOContext(devnull, :color => false) diff --git a/test/suite/core/test_core_types.jl b/test/suite/core/test_core_types.jl index 71e07554..7d79491f 100644 --- a/test/suite/core/test_core_types.jl +++ b/test/suite/core/test_core_types.jl @@ -3,8 +3,8 @@ module TestCoreTypes using Test: Test import CTBase.Core -const VERBOSE = isdefined(Main, :TestOptions) ? Main.TestOptions.VERBOSE : true -const SHOWTIMING = isdefined(Main, :TestOptions) ? Main.TestOptions.SHOWTIMING : true +const VERBOSE = isdefined(Main, :TestData) ? Main.TestData.VERBOSE : true +const SHOWTIMING = isdefined(Main, :TestData) ? Main.TestData.SHOWTIMING : true function test_types() Test.@testset verbose = VERBOSE showtiming = SHOWTIMING "Type aliases" begin diff --git a/test/suite/core/test_function_utils.jl b/test/suite/core/test_function_utils.jl index 8e953f91..002dcaca 100644 --- a/test/suite/core/test_function_utils.jl +++ b/test/suite/core/test_function_utils.jl @@ -3,8 +3,8 @@ module TestCoreFunctionUtils using Test: Test import CTBase.Core -const VERBOSE = isdefined(Main, :TestOptions) ? Main.TestOptions.VERBOSE : true -const SHOWTIMING = isdefined(Main, :TestOptions) ? Main.TestOptions.SHOWTIMING : true +const VERBOSE = isdefined(Main, :TestData) ? Main.TestData.VERBOSE : true +const SHOWTIMING = isdefined(Main, :TestData) ? Main.TestData.SHOWTIMING : true function test_function_utils() Test.@testset verbose = VERBOSE showtiming = SHOWTIMING "to_out_of_place" begin diff --git a/test/suite/core/test_macros.jl b/test/suite/core/test_macros.jl index 7a652432..79be71b8 100644 --- a/test/suite/core/test_macros.jl +++ b/test/suite/core/test_macros.jl @@ -4,8 +4,8 @@ using Test: Test import CTBase.Core import CTBase.Exceptions -const VERBOSE = isdefined(Main, :TestOptions) ? Main.TestOptions.VERBOSE : true -const SHOWTIMING = isdefined(Main, :TestOptions) ? Main.TestOptions.SHOWTIMING : true +const VERBOSE = isdefined(Main, :TestData) ? Main.TestData.VERBOSE : true +const SHOWTIMING = isdefined(Main, :TestData) ? Main.TestData.SHOWTIMING : true function test_macros() Test.@testset verbose = VERBOSE showtiming = SHOWTIMING "@ensure" begin diff --git a/test/suite/core/test_matrix_utils.jl b/test/suite/core/test_matrix_utils.jl index 9cc8b053..2a50f91e 100644 --- a/test/suite/core/test_matrix_utils.jl +++ b/test/suite/core/test_matrix_utils.jl @@ -3,8 +3,8 @@ module TestCoreMatrixUtils using Test: Test import CTBase.Core -const VERBOSE = isdefined(Main, :TestOptions) ? Main.TestOptions.VERBOSE : true -const SHOWTIMING = isdefined(Main, :TestOptions) ? Main.TestOptions.SHOWTIMING : true +const VERBOSE = isdefined(Main, :TestData) ? Main.TestData.VERBOSE : true +const SHOWTIMING = isdefined(Main, :TestData) ? Main.TestData.SHOWTIMING : true function test_matrix_utils() Test.@testset verbose = VERBOSE showtiming = SHOWTIMING "matrix2vec" begin diff --git a/test/suite/core/test_palette.jl b/test/suite/core/test_palette.jl index 44bcb4c2..b02ed451 100644 --- a/test/suite/core/test_palette.jl +++ b/test/suite/core/test_palette.jl @@ -3,8 +3,8 @@ module TestPalette using Test: Test import CTBase.Core -const VERBOSE = isdefined(Main, :TestOptions) ? Main.TestOptions.VERBOSE : true -const SHOWTIMING = isdefined(Main, :TestOptions) ? Main.TestOptions.SHOWTIMING : true +const VERBOSE = isdefined(Main, :TestData) ? Main.TestData.VERBOSE : true +const SHOWTIMING = isdefined(Main, :TestData) ? Main.TestData.SHOWTIMING : true function test_palette() io_plain = IOContext(devnull, :color => false) diff --git a/test/suite/core/test_utils.jl b/test/suite/core/test_utils.jl index a8e00aa5..b57e236c 100644 --- a/test/suite/core/test_utils.jl +++ b/test/suite/core/test_utils.jl @@ -3,8 +3,8 @@ module TestCoreUtils using Test: Test import CTBase.Core -const VERBOSE = isdefined(Main, :TestOptions) ? Main.TestOptions.VERBOSE : true -const SHOWTIMING = isdefined(Main, :TestOptions) ? Main.TestOptions.SHOWTIMING : true +const VERBOSE = isdefined(Main, :TestData) ? Main.TestData.VERBOSE : true +const SHOWTIMING = isdefined(Main, :TestData) ? Main.TestData.SHOWTIMING : true function test_utils() Test.@testset verbose = VERBOSE showtiming = SHOWTIMING "Default value of the display during resolution" begin diff --git a/test/suite/descriptions/test_catalog.jl b/test/suite/descriptions/test_catalog.jl index cb52b011..e0dc5cf2 100644 --- a/test/suite/descriptions/test_catalog.jl +++ b/test/suite/descriptions/test_catalog.jl @@ -4,8 +4,8 @@ using Test: Test import CTBase.Descriptions import CTBase.Exceptions -const VERBOSE = isdefined(Main, :TestOptions) ? Main.TestOptions.VERBOSE : true -const SHOWTIMING = isdefined(Main, :TestOptions) ? Main.TestOptions.SHOWTIMING : true +const VERBOSE = isdefined(Main, :TestData) ? Main.TestData.VERBOSE : true +const SHOWTIMING = isdefined(Main, :TestData) ? Main.TestData.SHOWTIMING : true function test_catalog() Test.@testset verbose = VERBOSE showtiming = SHOWTIMING "Catalog Operations" begin diff --git a/test/suite/descriptions/test_complete.jl b/test/suite/descriptions/test_complete.jl index f7118e5c..f52d3302 100644 --- a/test/suite/descriptions/test_complete.jl +++ b/test/suite/descriptions/test_complete.jl @@ -4,8 +4,8 @@ using Test: Test import CTBase.Descriptions import CTBase.Exceptions -const VERBOSE = isdefined(Main, :TestOptions) ? Main.TestOptions.VERBOSE : true -const SHOWTIMING = isdefined(Main, :TestOptions) ? Main.TestOptions.SHOWTIMING : true +const VERBOSE = isdefined(Main, :TestData) ? Main.TestData.VERBOSE : true +const SHOWTIMING = isdefined(Main, :TestData) ? Main.TestData.SHOWTIMING : true function test_complete() Test.@testset verbose = VERBOSE showtiming = SHOWTIMING "Complete Descriptions" begin diff --git a/test/suite/descriptions/test_description_types.jl b/test/suite/descriptions/test_description_types.jl index 2505823c..5cc0da7e 100644 --- a/test/suite/descriptions/test_description_types.jl +++ b/test/suite/descriptions/test_description_types.jl @@ -3,8 +3,8 @@ module TestDescriptionTypes using Test: Test import CTBase.Descriptions -const VERBOSE = isdefined(Main, :TestOptions) ? Main.TestOptions.VERBOSE : true -const SHOWTIMING = isdefined(Main, :TestOptions) ? Main.TestOptions.SHOWTIMING : true +const VERBOSE = isdefined(Main, :TestData) ? Main.TestData.VERBOSE : true +const SHOWTIMING = isdefined(Main, :TestData) ? Main.TestData.SHOWTIMING : true function test_description_types() Test.@testset verbose = VERBOSE showtiming = SHOWTIMING "Description Types" begin diff --git a/test/suite/descriptions/test_display_description.jl b/test/suite/descriptions/test_display_description.jl index be40573e..e58c13d3 100644 --- a/test/suite/descriptions/test_display_description.jl +++ b/test/suite/descriptions/test_display_description.jl @@ -3,8 +3,8 @@ module TestDisplayDescription using Test: Test import CTBase.Descriptions: Descriptions -const VERBOSE = isdefined(Main, :TestOptions) ? Main.TestOptions.VERBOSE : true -const SHOWTIMING = isdefined(Main, :TestOptions) ? Main.TestOptions.SHOWTIMING : true +const VERBOSE = isdefined(Main, :TestData) ? Main.TestData.VERBOSE : true +const SHOWTIMING = isdefined(Main, :TestData) ? Main.TestData.SHOWTIMING : true function test_display_description() Test.@testset verbose = VERBOSE showtiming = SHOWTIMING "Description Display" begin diff --git a/test/suite/descriptions/test_integration.jl b/test/suite/descriptions/test_integration.jl index 85dad7bf..6787c59d 100644 --- a/test/suite/descriptions/test_integration.jl +++ b/test/suite/descriptions/test_integration.jl @@ -6,8 +6,8 @@ import CTBase.DevTools: DevTools import CTBase.Exceptions: Exceptions import CTBase.Unicode: Unicode -const VERBOSE = isdefined(Main, :TestOptions) ? Main.TestOptions.VERBOSE : true -const SHOWTIMING = isdefined(Main, :TestOptions) ? Main.TestOptions.SHOWTIMING : true +const VERBOSE = isdefined(Main, :TestData) ? Main.TestData.VERBOSE : true +const SHOWTIMING = isdefined(Main, :TestData) ? Main.TestData.SHOWTIMING : true # TOP-LEVEL: Fake type for extension testing struct DummyDocRefTag <: DevTools.AbstractDocumenterReferenceTag end diff --git a/test/suite/descriptions/test_remove.jl b/test/suite/descriptions/test_remove.jl index 86b29a7e..f50d6fed 100644 --- a/test/suite/descriptions/test_remove.jl +++ b/test/suite/descriptions/test_remove.jl @@ -3,8 +3,8 @@ module TestRemove using Test: Test import CTBase.Descriptions -const VERBOSE = isdefined(Main, :TestOptions) ? Main.TestOptions.VERBOSE : true -const SHOWTIMING = isdefined(Main, :TestOptions) ? Main.TestOptions.SHOWTIMING : true +const VERBOSE = isdefined(Main, :TestData) ? Main.TestData.VERBOSE : true +const SHOWTIMING = isdefined(Main, :TestData) ? Main.TestData.SHOWTIMING : true function test_remove() Test.@testset verbose = VERBOSE showtiming = SHOWTIMING "Remove Symbols" begin diff --git a/test/suite/descriptions/test_similarity.jl b/test/suite/descriptions/test_similarity.jl index 83b2fde4..cb88055d 100644 --- a/test/suite/descriptions/test_similarity.jl +++ b/test/suite/descriptions/test_similarity.jl @@ -3,8 +3,8 @@ module TestSimilarity using Test: Test import CTBase.Descriptions -const VERBOSE = isdefined(Main, :TestOptions) ? Main.TestOptions.VERBOSE : true -const SHOWTIMING = isdefined(Main, :TestOptions) ? Main.TestOptions.SHOWTIMING : true +const VERBOSE = isdefined(Main, :TestData) ? Main.TestData.VERBOSE : true +const SHOWTIMING = isdefined(Main, :TestData) ? Main.TestData.SHOWTIMING : true function test_similarity() Test.@testset verbose = VERBOSE showtiming = SHOWTIMING "Similarity Utilities" begin diff --git a/test/suite/exceptions/test_exception_display.jl b/test/suite/exceptions/test_exception_display.jl index d5874dec..177f4427 100644 --- a/test/suite/exceptions/test_exception_display.jl +++ b/test/suite/exceptions/test_exception_display.jl @@ -3,8 +3,8 @@ module TestExceptionDisplay using Test: Test import CTBase.Exceptions -const VERBOSE = isdefined(Main, :TestOptions) ? Main.TestOptions.VERBOSE : true -const SHOWTIMING = isdefined(Main, :TestOptions) ? Main.TestOptions.SHOWTIMING : true +const VERBOSE = isdefined(Main, :TestData) ? Main.TestData.VERBOSE : true +const SHOWTIMING = isdefined(Main, :TestData) ? Main.TestData.SHOWTIMING : true """ Tests for exception display functions (display.jl) diff --git a/test/suite/exceptions/test_exception_types.jl b/test/suite/exceptions/test_exception_types.jl index 6046f557..134b2908 100644 --- a/test/suite/exceptions/test_exception_types.jl +++ b/test/suite/exceptions/test_exception_types.jl @@ -3,8 +3,8 @@ module TestExceptionTypes using Test: Test import CTBase.Exceptions -const VERBOSE = isdefined(Main, :TestOptions) ? Main.TestOptions.VERBOSE : true -const SHOWTIMING = isdefined(Main, :TestOptions) ? Main.TestOptions.SHOWTIMING : true +const VERBOSE = isdefined(Main, :TestData) ? Main.TestData.VERBOSE : true +const SHOWTIMING = isdefined(Main, :TestData) ? Main.TestData.SHOWTIMING : true """ Tests for exception type definitions (types.jl) diff --git a/test/suite/exceptions/test_exceptions.jl b/test/suite/exceptions/test_exceptions.jl index 852782c9..2bacec41 100644 --- a/test/suite/exceptions/test_exceptions.jl +++ b/test/suite/exceptions/test_exceptions.jl @@ -3,8 +3,8 @@ module TestExceptions using Test: Test import CTBase.Exceptions -const VERBOSE = isdefined(Main, :TestOptions) ? Main.TestOptions.VERBOSE : true -const SHOWTIMING = isdefined(Main, :TestOptions) ? Main.TestOptions.SHOWTIMING : true +const VERBOSE = isdefined(Main, :TestData) ? Main.TestData.VERBOSE : true +const SHOWTIMING = isdefined(Main, :TestData) ? Main.TestData.SHOWTIMING : true function test_exceptions() diff --git a/test/suite/exceptions/test_extension_error.jl b/test/suite/exceptions/test_extension_error.jl index 23b7fc01..9ededa88 100644 --- a/test/suite/exceptions/test_extension_error.jl +++ b/test/suite/exceptions/test_extension_error.jl @@ -3,8 +3,8 @@ module TestExtensionError using Test: Test import CTBase.Exceptions -const VERBOSE = isdefined(Main, :TestOptions) ? Main.TestOptions.VERBOSE : true -const SHOWTIMING = isdefined(Main, :TestOptions) ? Main.TestOptions.SHOWTIMING : true +const VERBOSE = isdefined(Main, :TestData) ? Main.TestData.VERBOSE : true +const SHOWTIMING = isdefined(Main, :TestData) ? Main.TestData.SHOWTIMING : true function test_extension_error() Test.@testset verbose = VERBOSE showtiming = SHOWTIMING "ExtensionError Contract Implementation" begin diff --git a/test/suite/extensions/test_coverage_helpers.jl b/test/suite/extensions/test_coverage_helpers.jl index 521992ca..3281863d 100644 --- a/test/suite/extensions/test_coverage_helpers.jl +++ b/test/suite/extensions/test_coverage_helpers.jl @@ -6,8 +6,8 @@ using Coverage: Coverage const CP = Base.get_extension(CTBase, :CoveragePostprocessing) -const VERBOSE = isdefined(Main, :TestOptions) ? Main.TestOptions.VERBOSE : true -const SHOWTIMING = isdefined(Main, :TestOptions) ? Main.TestOptions.SHOWTIMING : true +const VERBOSE = isdefined(Main, :TestData) ? Main.TestData.VERBOSE : true +const SHOWTIMING = isdefined(Main, :TestData) ? Main.TestData.SHOWTIMING : true function test_coverage_helpers() Test.@testset verbose = VERBOSE showtiming = SHOWTIMING "_reset_coverage_dir" begin diff --git a/test/suite/extensions/test_coverage_post_process.jl b/test/suite/extensions/test_coverage_post_process.jl index ab2e7f9d..e54ea943 100644 --- a/test/suite/extensions/test_coverage_post_process.jl +++ b/test/suite/extensions/test_coverage_post_process.jl @@ -6,8 +6,8 @@ using CTBase: CTBase import CTBase.DevTools import CTBase.Exceptions -const VERBOSE = isdefined(Main, :TestOptions) ? Main.TestOptions.VERBOSE : true -const SHOWTIMING = isdefined(Main, :TestOptions) ? Main.TestOptions.SHOWTIMING : true +const VERBOSE = isdefined(Main, :TestData) ? Main.TestData.VERBOSE : true +const SHOWTIMING = isdefined(Main, :TestData) ? Main.TestData.SHOWTIMING : true # TOP-LEVEL: Fake type for stub testing struct DummyCoverageTag <: DevTools.AbstractCoveragePostprocessingTag end diff --git a/test/suite/extensions/test_documenter_reference.jl b/test/suite/extensions/test_documenter_reference.jl index ddca97e1..ca8dfe94 100644 --- a/test/suite/extensions/test_documenter_reference.jl +++ b/test/suite/extensions/test_documenter_reference.jl @@ -5,8 +5,8 @@ using CTBase: CTBase import CTBase.DevTools: DevTools using Documenter: Documenter -const VERBOSE = isdefined(Main, :TestOptions) ? Main.TestOptions.VERBOSE : true -const SHOWTIMING = isdefined(Main, :TestOptions) ? Main.TestOptions.SHOWTIMING : true +const VERBOSE = isdefined(Main, :TestData) ? Main.TestData.VERBOSE : true +const SHOWTIMING = isdefined(Main, :TestData) ? Main.TestData.SHOWTIMING : true const DocumenterReference = Base.get_extension(CTBase, :DocumenterReference) const DR = DocumenterReference diff --git a/test/suite/extensions/test_documenter_reference_api.jl b/test/suite/extensions/test_documenter_reference_api.jl index bcd06752..e7ce8b6f 100644 --- a/test/suite/extensions/test_documenter_reference_api.jl +++ b/test/suite/extensions/test_documenter_reference_api.jl @@ -9,8 +9,8 @@ using Documenter: Documenter const DocumenterReference = Base.get_extension(CTBase, :DocumenterReference) const DR = DocumenterReference -const VERBOSE = isdefined(Main, :TestOptions) ? Main.TestOptions.VERBOSE : true -const SHOWTIMING = isdefined(Main, :TestOptions) ? Main.TestOptions.SHOWTIMING : true +const VERBOSE = isdefined(Main, :TestData) ? Main.TestData.VERBOSE : true +const SHOWTIMING = isdefined(Main, :TestData) ? Main.TestData.SHOWTIMING : true # Test module for API tests module DocumenterReferenceAPITestMod diff --git a/test/suite/extensions/test_documenter_reference_config_helpers.jl b/test/suite/extensions/test_documenter_reference_config_helpers.jl index 9f3d1d30..80c0959e 100644 --- a/test/suite/extensions/test_documenter_reference_config_helpers.jl +++ b/test/suite/extensions/test_documenter_reference_config_helpers.jl @@ -7,8 +7,8 @@ using Documenter: Documenter const DocumenterReference = Base.get_extension(CTBase, :DocumenterReference) const DR = DocumenterReference -const VERBOSE = isdefined(Main, :TestOptions) ? Main.TestOptions.VERBOSE : true -const SHOWTIMING = isdefined(Main, :TestOptions) ? Main.TestOptions.SHOWTIMING : true +const VERBOSE = isdefined(Main, :TestData) ? Main.TestData.VERBOSE : true +const SHOWTIMING = isdefined(Main, :TestData) ? Main.TestData.SHOWTIMING : true # Test module for config helpers module DocumenterReferenceConfigTestMod diff --git a/test/suite/extensions/test_documenter_reference_page_building.jl b/test/suite/extensions/test_documenter_reference_page_building.jl index a6445935..be8b1721 100644 --- a/test/suite/extensions/test_documenter_reference_page_building.jl +++ b/test/suite/extensions/test_documenter_reference_page_building.jl @@ -7,8 +7,8 @@ using Documenter: Documenter const DocumenterReference = Base.get_extension(CTBase, :DocumenterReference) const DR = DocumenterReference -const VERBOSE = isdefined(Main, :TestOptions) ? Main.TestOptions.VERBOSE : true -const SHOWTIMING = isdefined(Main, :TestOptions) ? Main.TestOptions.SHOWTIMING : true +const VERBOSE = isdefined(Main, :TestData) ? Main.TestData.VERBOSE : true +const SHOWTIMING = isdefined(Main, :TestData) ? Main.TestData.SHOWTIMING : true # Test module for page building module DocumenterReferencePageTestMod diff --git a/test/suite/extensions/test_documenter_reference_pipeline.jl b/test/suite/extensions/test_documenter_reference_pipeline.jl index eb8cb6b2..9c70fc89 100644 --- a/test/suite/extensions/test_documenter_reference_pipeline.jl +++ b/test/suite/extensions/test_documenter_reference_pipeline.jl @@ -8,8 +8,8 @@ using Documenter: Documenter const DocumenterReference = Base.get_extension(CTBase, :DocumenterReference) const DR = DocumenterReference -const VERBOSE = isdefined(Main, :TestOptions) ? Main.TestOptions.VERBOSE : true -const SHOWTIMING = isdefined(Main, :TestOptions) ? Main.TestOptions.SHOWTIMING : true +const VERBOSE = isdefined(Main, :TestData) ? Main.TestData.VERBOSE : true +const SHOWTIMING = isdefined(Main, :TestData) ? Main.TestData.SHOWTIMING : true # Test module for pipeline tests module DocumenterReferencePipelineTestMod diff --git a/test/suite/extensions/test_documenter_reference_symbol_helpers.jl b/test/suite/extensions/test_documenter_reference_symbol_helpers.jl index 7c450d6c..d5855a32 100644 --- a/test/suite/extensions/test_documenter_reference_symbol_helpers.jl +++ b/test/suite/extensions/test_documenter_reference_symbol_helpers.jl @@ -7,8 +7,8 @@ using Documenter: Documenter const DocumenterReference = Base.get_extension(CTBase, :DocumenterReference) const DR = DocumenterReference -const VERBOSE = isdefined(Main, :TestOptions) ? Main.TestOptions.VERBOSE : true -const SHOWTIMING = isdefined(Main, :TestOptions) ? Main.TestOptions.SHOWTIMING : true +const VERBOSE = isdefined(Main, :TestData) ? Main.TestData.VERBOSE : true +const SHOWTIMING = isdefined(Main, :TestData) ? Main.TestData.SHOWTIMING : true # Test module for symbol helpers module DocumenterReferenceSymbolTestMod diff --git a/test/suite/extensions/test_documenter_reference_type_formatting.jl b/test/suite/extensions/test_documenter_reference_type_formatting.jl index dd5fbe93..43e6a0ca 100644 --- a/test/suite/extensions/test_documenter_reference_type_formatting.jl +++ b/test/suite/extensions/test_documenter_reference_type_formatting.jl @@ -7,8 +7,8 @@ using Documenter: Documenter const DocumenterReference = Base.get_extension(CTBase, :DocumenterReference) const DR = DocumenterReference -const VERBOSE = isdefined(Main, :TestOptions) ? Main.TestOptions.VERBOSE : true -const SHOWTIMING = isdefined(Main, :TestOptions) ? Main.TestOptions.SHOWTIMING : true +const VERBOSE = isdefined(Main, :TestData) ? Main.TestData.VERBOSE : true +const SHOWTIMING = isdefined(Main, :TestData) ? Main.TestData.SHOWTIMING : true # Test module for type formatting module DRTypeFormatTestMod diff --git a/test/suite/extensions/test_documenter_reference_types.jl b/test/suite/extensions/test_documenter_reference_types.jl index 4d913a27..36bd847b 100644 --- a/test/suite/extensions/test_documenter_reference_types.jl +++ b/test/suite/extensions/test_documenter_reference_types.jl @@ -7,8 +7,8 @@ using Documenter: Documenter const DocumenterReference = Base.get_extension(CTBase, :DocumenterReference) const DR = DocumenterReference -const VERBOSE = isdefined(Main, :TestOptions) ? Main.TestOptions.VERBOSE : true -const SHOWTIMING = isdefined(Main, :TestOptions) ? Main.TestOptions.SHOWTIMING : true +const VERBOSE = isdefined(Main, :TestData) ? Main.TestData.VERBOSE : true +const SHOWTIMING = isdefined(Main, :TestData) ? Main.TestData.SHOWTIMING : true function test_documenter_reference_types() Test.@testset verbose = VERBOSE showtiming = SHOWTIMING "DocType enum" begin diff --git a/test/suite/extensions/test_extension_stubs.jl b/test/suite/extensions/test_extension_stubs.jl index 3b5636ee..eb18da85 100644 --- a/test/suite/extensions/test_extension_stubs.jl +++ b/test/suite/extensions/test_extension_stubs.jl @@ -4,8 +4,8 @@ using Test: Test import CTBase.DevTools import CTBase.Exceptions -const VERBOSE = isdefined(Main, :TestOptions) ? Main.TestOptions.VERBOSE : true -const SHOWTIMING = isdefined(Main, :TestOptions) ? Main.TestOptions.SHOWTIMING : true +const VERBOSE = isdefined(Main, :TestData) ? Main.TestData.VERBOSE : true +const SHOWTIMING = isdefined(Main, :TestData) ? Main.TestData.SHOWTIMING : true # Fake tag types for extension stub testing (testing-creation.md §6). # These subtype the abstract tags but are unknown to any extension, diff --git a/test/suite/extensions/test_testrunner.jl b/test/suite/extensions/test_testrunner.jl index aa3c160c..472412ee 100644 --- a/test/suite/extensions/test_testrunner.jl +++ b/test/suite/extensions/test_testrunner.jl @@ -7,8 +7,8 @@ import CTBase.Exceptions const TestRunner = Base.get_extension(CTBase, :TestRunner) -const VERBOSE = isdefined(Main, :TestOptions) ? Main.TestOptions.VERBOSE : true -const SHOWTIMING = isdefined(Main, :TestOptions) ? Main.TestOptions.SHOWTIMING : true +const VERBOSE = isdefined(Main, :TestData) ? Main.TestData.VERBOSE : true +const SHOWTIMING = isdefined(Main, :TestData) ? Main.TestData.SHOWTIMING : true struct DummyTestRunnerTag <: DevTools.AbstractTestRunnerTag end diff --git a/test/suite/extensions/test_testrunner_arg_parsing.jl b/test/suite/extensions/test_testrunner_arg_parsing.jl index bf168167..e1d5563e 100644 --- a/test/suite/extensions/test_testrunner_arg_parsing.jl +++ b/test/suite/extensions/test_testrunner_arg_parsing.jl @@ -6,8 +6,8 @@ import CTBase.Exceptions const TestRunner = Base.get_extension(CTBase, :TestRunner) -const VERBOSE = isdefined(Main, :TestOptions) ? Main.TestOptions.VERBOSE : true -const SHOWTIMING = isdefined(Main, :TestOptions) ? Main.TestOptions.SHOWTIMING : true +const VERBOSE = isdefined(Main, :TestData) ? Main.TestData.VERBOSE : true +const SHOWTIMING = isdefined(Main, :TestData) ? Main.TestData.SHOWTIMING : true function test_testrunner_arg_parsing() Test.@testset verbose = VERBOSE showtiming = SHOWTIMING "_parse_test_args" begin diff --git a/test/suite/extensions/test_testrunner_execution.jl b/test/suite/extensions/test_testrunner_execution.jl index 8be95545..d19b8419 100644 --- a/test/suite/extensions/test_testrunner_execution.jl +++ b/test/suite/extensions/test_testrunner_execution.jl @@ -6,8 +6,8 @@ import CTBase.Exceptions const TestRunner = Base.get_extension(CTBase, :TestRunner) -const VERBOSE = isdefined(Main, :TestOptions) ? Main.TestOptions.VERBOSE : true -const SHOWTIMING = isdefined(Main, :TestOptions) ? Main.TestOptions.SHOWTIMING : true +const VERBOSE = isdefined(Main, :TestData) ? Main.TestData.VERBOSE : true +const SHOWTIMING = isdefined(Main, :TestData) ? Main.TestData.SHOWTIMING : true function test_testrunner_execution() Test.@testset verbose = VERBOSE showtiming = SHOWTIMING "_run_single_test" begin diff --git a/test/suite/extensions/test_testrunner_progress.jl b/test/suite/extensions/test_testrunner_progress.jl index 81dcee0e..9a3a0336 100644 --- a/test/suite/extensions/test_testrunner_progress.jl +++ b/test/suite/extensions/test_testrunner_progress.jl @@ -5,8 +5,8 @@ using CTBase: CTBase const TestRunner = Base.get_extension(CTBase, :TestRunner) -const VERBOSE = isdefined(Main, :TestOptions) ? Main.TestOptions.VERBOSE : true -const SHOWTIMING = isdefined(Main, :TestOptions) ? Main.TestOptions.SHOWTIMING : true +const VERBOSE = isdefined(Main, :TestData) ? Main.TestData.VERBOSE : true +const SHOWTIMING = isdefined(Main, :TestData) ? Main.TestData.SHOWTIMING : true function test_testrunner_progress() Test.@testset verbose = VERBOSE showtiming = SHOWTIMING "Progress display" begin diff --git a/test/suite/extensions/test_testrunner_selection.jl b/test/suite/extensions/test_testrunner_selection.jl index a0709138..9ef7f1fb 100644 --- a/test/suite/extensions/test_testrunner_selection.jl +++ b/test/suite/extensions/test_testrunner_selection.jl @@ -5,8 +5,8 @@ using CTBase: CTBase const TestRunner = Base.get_extension(CTBase, :TestRunner) -const VERBOSE = isdefined(Main, :TestOptions) ? Main.TestOptions.VERBOSE : true -const SHOWTIMING = isdefined(Main, :TestOptions) ? Main.TestOptions.SHOWTIMING : true +const VERBOSE = isdefined(Main, :TestData) ? Main.TestData.VERBOSE : true +const SHOWTIMING = isdefined(Main, :TestData) ? Main.TestData.SHOWTIMING : true function test_testrunner_selection() Test.@testset verbose = VERBOSE showtiming = SHOWTIMING "_select_tests" begin diff --git a/test/suite/extensions/test_testrunner_types.jl b/test/suite/extensions/test_testrunner_types.jl index 3bb36422..962aec56 100644 --- a/test/suite/extensions/test_testrunner_types.jl +++ b/test/suite/extensions/test_testrunner_types.jl @@ -6,8 +6,8 @@ import CTBase.DevTools const TestRunner = Base.get_extension(CTBase, :TestRunner) -const VERBOSE = isdefined(Main, :TestOptions) ? Main.TestOptions.VERBOSE : true -const SHOWTIMING = isdefined(Main, :TestOptions) ? Main.TestOptions.SHOWTIMING : true +const VERBOSE = isdefined(Main, :TestData) ? Main.TestData.VERBOSE : true +const SHOWTIMING = isdefined(Main, :TestData) ? Main.TestData.SHOWTIMING : true function test_testrunner_types() Test.@testset verbose = VERBOSE showtiming = SHOWTIMING "TestRunInfo" begin diff --git a/test/suite/interpolation/test_interpolation.jl b/test/suite/interpolation/test_interpolation.jl index 7c466b3e..c81c3581 100644 --- a/test/suite/interpolation/test_interpolation.jl +++ b/test/suite/interpolation/test_interpolation.jl @@ -3,8 +3,8 @@ module TestInterpolation using Test: Test import CTBase.Interpolation -const VERBOSE = isdefined(Main, :TestOptions) ? Main.TestOptions.VERBOSE : true -const SHOWTIMING = isdefined(Main, :TestOptions) ? Main.TestOptions.SHOWTIMING : true +const VERBOSE = isdefined(Main, :TestData) ? Main.TestData.VERBOSE : true +const SHOWTIMING = isdefined(Main, :TestData) ? Main.TestData.SHOWTIMING : true function test_interpolation() Test.@testset verbose = VERBOSE showtiming = SHOWTIMING "Interpolation" begin diff --git a/test/suite/meta/test_code_quality.jl b/test/suite/meta/test_code_quality.jl index ed4d7c6c..ffd80d67 100644 --- a/test/suite/meta/test_code_quality.jl +++ b/test/suite/meta/test_code_quality.jl @@ -4,8 +4,8 @@ using Test: Test using Aqua: Aqua using CTBase: CTBase -const VERBOSE = isdefined(Main, :TestOptions) ? Main.TestOptions.VERBOSE : true -const SHOWTIMING = isdefined(Main, :TestOptions) ? Main.TestOptions.SHOWTIMING : true +const VERBOSE = isdefined(Main, :TestData) ? Main.TestData.VERBOSE : true +const SHOWTIMING = isdefined(Main, :TestData) ? Main.TestData.SHOWTIMING : true function test_code_quality() Test.@testset verbose = VERBOSE showtiming = SHOWTIMING "Code quality" begin diff --git a/test/suite/options/test_options.jl b/test/suite/options/test_options.jl index ae4696be..ecf071ba 100644 --- a/test/suite/options/test_options.jl +++ b/test/suite/options/test_options.jl @@ -8,6 +8,7 @@ using CTBase.Options # For testing exported symbols const VERBOSE = isdefined(Main, :TestData) ? Main.TestData.VERBOSE : true const SHOWTIMING = isdefined(Main, :TestData) ? Main.TestData.SHOWTIMING : true + const CurrentModule = TestOptions """ diff --git a/test/suite/options/test_options_value.jl b/test/suite/options/test_options_value.jl index 15e53253..12f05d68 100644 --- a/test/suite/options/test_options_value.jl +++ b/test/suite/options/test_options_value.jl @@ -4,6 +4,7 @@ using Test: Test import CTBase.Exceptions using CTBase: CTBase import CTBase.Options + const VERBOSE = isdefined(Main, :TestData) ? Main.TestData.VERBOSE : true const SHOWTIMING = isdefined(Main, :TestData) ? Main.TestData.SHOWTIMING : true diff --git a/test/suite/traits/test_abstract_traits.jl b/test/suite/traits/test_abstract_traits.jl index aba1d0d1..796f747f 100644 --- a/test/suite/traits/test_abstract_traits.jl +++ b/test/suite/traits/test_abstract_traits.jl @@ -3,8 +3,8 @@ module TestAbstractTraits import Test import CTBase.Traits -const VERBOSE = isdefined(Main, :TestOptions) ? Main.TestOptions.VERBOSE : true -const SHOWTIMING = isdefined(Main, :TestOptions) ? Main.TestOptions.SHOWTIMING : true +const VERBOSE = isdefined(Main, :TestData) ? Main.TestData.VERBOSE : true +const SHOWTIMING = isdefined(Main, :TestData) ? Main.TestData.SHOWTIMING : true function test_abstract_traits() Test.@testset "Abstract Traits Tests" verbose=VERBOSE showtiming=SHOWTIMING begin diff --git a/test/suite/traits/test_ad.jl b/test/suite/traits/test_ad.jl index 39fdc4a4..7aa2b998 100644 --- a/test/suite/traits/test_ad.jl +++ b/test/suite/traits/test_ad.jl @@ -3,8 +3,8 @@ module TestAD import Test import CTBase.Traits -const VERBOSE = isdefined(Main, :TestOptions) ? Main.TestOptions.VERBOSE : true -const SHOWTIMING = isdefined(Main, :TestOptions) ? Main.TestOptions.SHOWTIMING : true +const VERBOSE = isdefined(Main, :TestData) ? Main.TestData.VERBOSE : true +const SHOWTIMING = isdefined(Main, :TestData) ? Main.TestData.SHOWTIMING : true function test_ad() Test.@testset "AD Trait Tests" verbose=VERBOSE showtiming=SHOWTIMING begin diff --git a/test/suite/traits/test_dynamics.jl b/test/suite/traits/test_dynamics.jl index c2e463d4..3968303e 100644 --- a/test/suite/traits/test_dynamics.jl +++ b/test/suite/traits/test_dynamics.jl @@ -3,8 +3,8 @@ module TestDynamics import Test import CTBase.Traits -const VERBOSE = isdefined(Main, :TestOptions) ? Main.TestOptions.VERBOSE : true -const SHOWTIMING = isdefined(Main, :TestOptions) ? Main.TestOptions.SHOWTIMING : true +const VERBOSE = isdefined(Main, :TestData) ? Main.TestData.VERBOSE : true +const SHOWTIMING = isdefined(Main, :TestData) ? Main.TestData.SHOWTIMING : true function test_dynamics() Test.@testset "Dynamics Trait Tests" verbose=VERBOSE showtiming=SHOWTIMING begin diff --git a/test/suite/traits/test_mode.jl b/test/suite/traits/test_mode.jl index d8d7ce1d..dd179ae8 100644 --- a/test/suite/traits/test_mode.jl +++ b/test/suite/traits/test_mode.jl @@ -3,8 +3,8 @@ module TestMode import Test import CTBase.Traits -const VERBOSE = isdefined(Main, :TestOptions) ? Main.TestOptions.VERBOSE : true -const SHOWTIMING = isdefined(Main, :TestOptions) ? Main.TestOptions.SHOWTIMING : true +const VERBOSE = isdefined(Main, :TestData) ? Main.TestData.VERBOSE : true +const SHOWTIMING = isdefined(Main, :TestData) ? Main.TestData.SHOWTIMING : true function test_mode() Test.@testset "Mode Trait Tests" verbose=VERBOSE showtiming=SHOWTIMING begin diff --git a/test/suite/traits/test_mutability.jl b/test/suite/traits/test_mutability.jl index 3c9350b1..fcaeacc2 100644 --- a/test/suite/traits/test_mutability.jl +++ b/test/suite/traits/test_mutability.jl @@ -4,8 +4,8 @@ import Test import CTBase.Exceptions import CTBase.Traits -const VERBOSE = isdefined(Main, :TestOptions) ? Main.TestOptions.VERBOSE : true -const SHOWTIMING = isdefined(Main, :TestOptions) ? Main.TestOptions.SHOWTIMING : true +const VERBOSE = isdefined(Main, :TestData) ? Main.TestData.VERBOSE : true +const SHOWTIMING = isdefined(Main, :TestData) ? Main.TestData.SHOWTIMING : true # ============================================================================== # Fake types for contract testing diff --git a/test/suite/traits/test_time_dependence.jl b/test/suite/traits/test_time_dependence.jl index 801b09a5..389f4598 100644 --- a/test/suite/traits/test_time_dependence.jl +++ b/test/suite/traits/test_time_dependence.jl @@ -4,8 +4,8 @@ import Test import CTBase.Exceptions import CTBase.Traits -const VERBOSE = isdefined(Main, :TestOptions) ? Main.TestOptions.VERBOSE : true -const SHOWTIMING = isdefined(Main, :TestOptions) ? Main.TestOptions.SHOWTIMING : true +const VERBOSE = isdefined(Main, :TestData) ? Main.TestData.VERBOSE : true +const SHOWTIMING = isdefined(Main, :TestData) ? Main.TestData.SHOWTIMING : true # ============================================================================== # Fake types for contract testing diff --git a/test/suite/traits/test_traits_module.jl b/test/suite/traits/test_traits_module.jl index 13098529..db9a95f1 100644 --- a/test/suite/traits/test_traits_module.jl +++ b/test/suite/traits/test_traits_module.jl @@ -24,8 +24,8 @@ import CTBase import CTBase.Traits using CTBase.Traits # For testing exported symbols -const VERBOSE = isdefined(Main, :TestOptions) ? Main.TestOptions.VERBOSE : true -const SHOWTIMING = isdefined(Main, :TestOptions) ? Main.TestOptions.SHOWTIMING : true +const VERBOSE = isdefined(Main, :TestData) ? Main.TestData.VERBOSE : true +const SHOWTIMING = isdefined(Main, :TestData) ? Main.TestData.SHOWTIMING : true const CurrentModule = TestTraitsModule diff --git a/test/suite/traits/test_variable_costate.jl b/test/suite/traits/test_variable_costate.jl index c85a0a0e..0b6f476e 100644 --- a/test/suite/traits/test_variable_costate.jl +++ b/test/suite/traits/test_variable_costate.jl @@ -3,8 +3,8 @@ module TestVariableCostate import Test import CTBase.Traits -const VERBOSE = isdefined(Main, :TestOptions) ? Main.TestOptions.VERBOSE : true -const SHOWTIMING = isdefined(Main, :TestOptions) ? Main.TestOptions.SHOWTIMING : true +const VERBOSE = isdefined(Main, :TestData) ? Main.TestData.VERBOSE : true +const SHOWTIMING = isdefined(Main, :TestData) ? Main.TestData.SHOWTIMING : true function test_variable_costate() Test.@testset "Variable Costate Trait Tests" verbose=VERBOSE showtiming=SHOWTIMING begin diff --git a/test/suite/traits/test_variable_dependence.jl b/test/suite/traits/test_variable_dependence.jl index c9aff8d8..28920524 100644 --- a/test/suite/traits/test_variable_dependence.jl +++ b/test/suite/traits/test_variable_dependence.jl @@ -4,8 +4,8 @@ import Test import CTBase.Exceptions import CTBase.Traits -const VERBOSE = isdefined(Main, :TestOptions) ? Main.TestOptions.VERBOSE : true -const SHOWTIMING = isdefined(Main, :TestOptions) ? Main.TestOptions.SHOWTIMING : true +const VERBOSE = isdefined(Main, :TestData) ? Main.TestData.VERBOSE : true +const SHOWTIMING = isdefined(Main, :TestData) ? Main.TestData.SHOWTIMING : true # ============================================================================== # Fake types for contract testing diff --git a/test/suite/unicode/test_subscripts.jl b/test/suite/unicode/test_subscripts.jl index 11e4d271..aabb8977 100644 --- a/test/suite/unicode/test_subscripts.jl +++ b/test/suite/unicode/test_subscripts.jl @@ -4,8 +4,8 @@ using Test: Test import CTBase.Exceptions: Exceptions import CTBase.Unicode: Unicode -const VERBOSE = isdefined(Main, :TestOptions) ? Main.TestOptions.VERBOSE : true -const SHOWTIMING = isdefined(Main, :TestOptions) ? Main.TestOptions.SHOWTIMING : true +const VERBOSE = isdefined(Main, :TestData) ? Main.TestData.VERBOSE : true +const SHOWTIMING = isdefined(Main, :TestData) ? Main.TestData.SHOWTIMING : true function test_subscripts() Test.@testset verbose = VERBOSE showtiming = SHOWTIMING "ctindice (single subscript char)" begin diff --git a/test/suite/unicode/test_superscripts.jl b/test/suite/unicode/test_superscripts.jl index 85e45f69..3d059ff1 100644 --- a/test/suite/unicode/test_superscripts.jl +++ b/test/suite/unicode/test_superscripts.jl @@ -4,8 +4,8 @@ using Test: Test import CTBase.Exceptions: Exceptions import CTBase.Unicode: Unicode -const VERBOSE = isdefined(Main, :TestOptions) ? Main.TestOptions.VERBOSE : true -const SHOWTIMING = isdefined(Main, :TestOptions) ? Main.TestOptions.SHOWTIMING : true +const VERBOSE = isdefined(Main, :TestData) ? Main.TestData.VERBOSE : true +const SHOWTIMING = isdefined(Main, :TestData) ? Main.TestData.SHOWTIMING : true function test_superscripts() Test.@testset verbose = VERBOSE showtiming = SHOWTIMING "ctupperscript (single superscript char)" begin From f23d0fc196bb84f09443d7d4ae7984596e3316e1 Mon Sep 17 00:00:00 2001 From: Olivier Cots Date: Thu, 25 Jun 2026 09:25:40 +0200 Subject: [PATCH 5/8] docs: update getting-started and index for Data module Co-Authored-By: Claude Sonnet 4.6 --- docs/src/getting-started.md | 27 ++++++++++++++++++++++++--- docs/src/index.md | 4 +++- 2 files changed, 27 insertions(+), 4 deletions(-) diff --git a/docs/src/getting-started.md b/docs/src/getting-started.md index 029e4536..86eb2a2f 100644 --- a/docs/src/getting-started.md +++ b/docs/src/getting-started.md @@ -1,9 +1,9 @@ +# Getting Started + ```@meta CurrentModule = CTBase ``` -# Getting Started - ## Installation CTBase.jl is typically installed as a dependency of another package in the ecosystem @@ -31,7 +31,7 @@ Three things to keep in mind: CTBase.Exceptions.NotImplemented ``` 2. **Submodule-first API.** The public API lives in named submodules - (`Core`, `Descriptions`, `Exceptions`, `Traits`, `DevTools`, `Unicode`, …). + (`Core`, `Descriptions`, `Exceptions`, `Traits`, `Data`, `DevTools`, `Unicode`, …). You can bring a submodule's exports into scope explicitly: ```julia using CTBase.Exceptions # brings IncorrectArgument, NotImplemented, … into scope @@ -106,6 +106,26 @@ end # hide For more, see the **[Exceptions guide](guide/exceptions.md)**. +### Working with Data + +The [`CTBase.Data`](@ref) submodule wraps a Julia function together with its +**trait metadata** (time, variable, and mutability dependence). The wrapper picks +the right call path at compile time, and the traits can be recovered from the type. + +```@repl walkthrough +# Autonomous, fixed, out-of-place vector field: X(x) +vf = CTBase.Data.VectorField(x -> -x) + +CTBase.Traits.time_dependence(vf) # Autonomous +CTBase.Traits.mutability(vf) # OutOfPlace (auto-detected from arity) +CTBase.Traits.dynamics_trait(vf) # StateDynamics + +vf([1.0, 2.0]) # natural call +vf(0.0, [1.0, 2.0], nothing) # uniform call (ignores t and v) +``` + +For more, see the **[Data guide](guide/data.md)**. + ## Next Steps | Topic | Guide | @@ -113,6 +133,7 @@ For more, see the **[Exceptions guide](guide/exceptions.md)**. | Descriptions catalogue and completion | [Descriptions](guide/descriptions.md) | | Exception hierarchy and best practices | [Exceptions](guide/exceptions.md) | | Compile-time traits and dispatch | [Traits](guide/traits.md) | +| Trait-carrying vector fields and Hamiltonians | [Data](guide/data.md) | | Modular test runner setup | [Test Runner](guide/test-runner.md) | | Coverage report generation | [Coverage](guide/coverage.md) | | Auto-generated API reference | [API Documentation](guide/api-documentation.md) | diff --git a/docs/src/index.md b/docs/src/index.md index cca940d1..5257053e 100644 --- a/docs/src/index.md +++ b/docs/src/index.md @@ -5,7 +5,7 @@ CurrentModule = CTBase ``` CTBase.jl is the foundational package of the [control-toolbox](https://github.com/control-toolbox) ecosystem. -It provides the **base layer** shared by all packages: common types, structured exceptions, description management, extension infrastructure, and developer tools. +It provides the **base layer** shared by all packages: common types, structured exceptions, description management, compile-time traits, trait-carrying data wrappers, extension infrastructure, and developer tools. !!! note "Qualified access" CTBase exports **no symbols** at the package level. Every public symbol is accessed @@ -28,6 +28,7 @@ It provides the **base layer** shared by all packages: common types, structured | [`CTBase.Descriptions`](@ref) | Symbolic description tuples: catalogue management, pattern completion, similarity search | | [`CTBase.Exceptions`](@ref) | Typed exception hierarchy with rich context fields | | [`CTBase.Traits`](@ref) | Compile-time trait types for time dependence, variable dependence, mutability, and dynamics dispatch | +| [`CTBase.Data`](@ref) | Trait-carrying function wrappers: `VectorField`, `Hamiltonian`, `HamiltonianVectorField` | | [`CTBase.DevTools`](@ref) | Developer tools with tag-based dispatch for `run_tests`, `postprocess_coverage`, and `automatic_reference_documentation` | | [`CTBase.Unicode`](@ref) | Unicode subscript/superscript helpers for display | @@ -37,6 +38,7 @@ It provides the **base layer** shared by all packages: common types, structured - **[Descriptions](guide/descriptions.md)** — catalogue API, pattern matching, error handling. - **[Exceptions](guide/exceptions.md)** — exception hierarchy, choosing the right type, best practices. - **[Traits](guide/traits.md)** — compile-time trait types, the opt-in contract, and predicate functions. +- **[Data](guide/data.md)** — trait-carrying wrappers for vector fields and Hamiltonians. - **[Test Runner](guide/test-runner.md)** — modular test infrastructure with `CTBase.DevTools.run_tests`. - **[Coverage](guide/coverage.md)** — post-processing coverage artifacts with `CTBase.postprocess_coverage`. - **[API Documentation](guide/api-documentation.md)** — auto-generating per-module API pages. From 59595f007eee11338f702d5f7773b05f91399d81 Mon Sep 17 00:00:00 2001 From: Olivier Cots Date: Thu, 25 Jun 2026 09:35:54 +0200 Subject: [PATCH 6/8] docs: reorder submodules and guides by conceptual layers MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Core → Exceptions → Traits → Data → Descriptions → DevTools → Unicode follows the dependency order rather than a loose alphabetical sort. Co-Authored-By: Claude Sonnet 4.6 --- docs/src/getting-started.md | 2 +- docs/src/index.md | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/src/getting-started.md b/docs/src/getting-started.md index 86eb2a2f..7199f1ba 100644 --- a/docs/src/getting-started.md +++ b/docs/src/getting-started.md @@ -130,10 +130,10 @@ For more, see the **[Data guide](guide/data.md)**. | Topic | Guide | | :--- | :--- | -| Descriptions catalogue and completion | [Descriptions](guide/descriptions.md) | | Exception hierarchy and best practices | [Exceptions](guide/exceptions.md) | | Compile-time traits and dispatch | [Traits](guide/traits.md) | | Trait-carrying vector fields and Hamiltonians | [Data](guide/data.md) | +| Descriptions catalogue and completion | [Descriptions](guide/descriptions.md) | | Modular test runner setup | [Test Runner](guide/test-runner.md) | | Coverage report generation | [Coverage](guide/coverage.md) | | Auto-generated API reference | [API Documentation](guide/api-documentation.md) | diff --git a/docs/src/index.md b/docs/src/index.md index 5257053e..858ef2ef 100644 --- a/docs/src/index.md +++ b/docs/src/index.md @@ -25,20 +25,20 @@ It provides the **base layer** shared by all packages: common types, structured | Submodule | Role | | :--- | :--- | | [`CTBase.Core`](@ref) | Fundamental numeric type alias (`ctNumber`) and internal display helpers | -| [`CTBase.Descriptions`](@ref) | Symbolic description tuples: catalogue management, pattern completion, similarity search | | [`CTBase.Exceptions`](@ref) | Typed exception hierarchy with rich context fields | | [`CTBase.Traits`](@ref) | Compile-time trait types for time dependence, variable dependence, mutability, and dynamics dispatch | | [`CTBase.Data`](@ref) | Trait-carrying function wrappers: `VectorField`, `Hamiltonian`, `HamiltonianVectorField` | +| [`CTBase.Descriptions`](@ref) | Symbolic description tuples: catalogue management, pattern completion, similarity search | | [`CTBase.DevTools`](@ref) | Developer tools with tag-based dispatch for `run_tests`, `postprocess_coverage`, and `automatic_reference_documentation` | | [`CTBase.Unicode`](@ref) | Unicode subscript/superscript helpers for display | ## User Guides - **[Getting Started](getting-started.md)** — installation, mental model, 5-minute walkthrough. -- **[Descriptions](guide/descriptions.md)** — catalogue API, pattern matching, error handling. - **[Exceptions](guide/exceptions.md)** — exception hierarchy, choosing the right type, best practices. - **[Traits](guide/traits.md)** — compile-time trait types, the opt-in contract, and predicate functions. - **[Data](guide/data.md)** — trait-carrying wrappers for vector fields and Hamiltonians. +- **[Descriptions](guide/descriptions.md)** — catalogue API, pattern matching, error handling. - **[Test Runner](guide/test-runner.md)** — modular test infrastructure with `CTBase.DevTools.run_tests`. - **[Coverage](guide/coverage.md)** — post-processing coverage artifacts with `CTBase.postprocess_coverage`. - **[API Documentation](guide/api-documentation.md)** — auto-generating per-module API pages. From 7eedf97f40a68ed800771b9a89577301ddfeffa0 Mon Sep 17 00:00:00 2001 From: Olivier Cots Date: Thu, 25 Jun 2026 09:38:55 +0200 Subject: [PATCH 7/8] docs(Data): fix variable_costate section with real @example Replace the incorrect julia snippet with a working @example data block that shows the correct out-of-place return values (dx, dp) and (dx, dp, dpv) with variable_costate=true, and the in-place dpv buffer argument that was missing. Co-Authored-By: Claude Sonnet 4.6 --- docs/src/guide/data.md | 53 +++++++++++++++++++++++++++++++----------- 1 file changed, 40 insertions(+), 13 deletions(-) diff --git a/docs/src/guide/data.md b/docs/src/guide/data.md index a8156d06..72d65a0b 100644 --- a/docs/src/guide/data.md +++ b/docs/src/guide/data.md @@ -159,21 +159,48 @@ dx, dp = hvf(x0, p0) # natural call: returns (ẋ, ṗ) ### The `variable_costate` keyword For **non-fixed** Hamiltonian vector fields, the call accepts an optional -`variable_costate` keyword (default `false`): +`variable_costate` keyword (default `false`). + +When `false` (default), the out-of-place call returns `(dx, dp)`; when `true`, +it returns the extended tuple `(dx, dp, dpv)` where `dpv = ∂ṗ/∂v` is the +derivative of the costate equations with respect to the variable `v`. For +in-place fields, `dpv` is passed as an optional pre-allocated buffer +(`dpv=nothing` skips the computation): + +```@example data +# Inner function that implements the variable_costate path. +# H(x, p, v) = v[1] * dot(p, x) → ẋ = v[1]*x, ṗ = -v[1]*p, ∂ṗ/∂v[1] = -p +function f_vc(x, p, v; variable_costate=false) + dx = v[1] .* x + dp = -v[1] .* p + variable_costate ? (dx, dp, -p) : (dx, dp) +end + +hvf_nf = Data.HamiltonianVectorField(f_vc; is_variable=true) +v0 = [2.0] + +dx, dp = hvf_nf(x0, p0, v0) # returns (ẋ, ṗ) +(dx, dp) +``` + +```@example data +dx, dp, dpv = hvf_nf(x0, p0, v0; variable_costate=true) # returns (ẋ, ṗ, ∂ṗ/∂v) +(dx, dp, dpv) +``` + +For in-place non-fixed fields the signature is: ```julia -hvf(x, p, v; variable_costate=true) # out-of-place, Autonomous/NonFixed -hvf(dx, dp, x, p, v; variable_costate=true) # in-place -``` - -When `true`, the field also propagates the derivative of the costate with respect -to the variable `v`. This is only supported by Hamiltonian vector fields whose -inner function implements that extra path — typically those **derived from a -[`Data.Hamiltonian`](@ref CTBase.Data.Hamiltonian)** by differentiation. For a -plain user-supplied function the default `variable_costate=false` is the only -valid value; passing `true` raises a -[`CTBase.Exceptions.PreconditionError`](@ref) (see the [Exceptions](exceptions.md) -guide). +dpv = zeros(length(p0)) +hvf_ip_nf(dx, dp, x, p, v; dpv=dpv, variable_costate=true) # fills dx, dp, dpv in place +``` + +This is only supported by inner functions that implement the `variable_costate` +keyword path — typically those **derived from a +[`Data.Hamiltonian`](@ref CTBase.Data.Hamiltonian)** by automatic differentiation. +For a plain user-supplied function that does not accept `variable_costate`, passing +`true` raises a [`CTBase.Exceptions.PreconditionError`](@ref) (see the +[Exceptions](exceptions.md) guide). --- From e887f7a5de6c24f7e6b844d9fc45abf00fbf9a03 Mon Sep 17 00:00:00 2001 From: Olivier Cots Date: Thu, 25 Jun 2026 09:42:04 +0200 Subject: [PATCH 8/8] docs: reorder Core Concepts sidebar by conceptual layers MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Exceptions → Traits → Data → Descriptions matches the dependency order established in index.md and getting-started.md. Co-Authored-By: Claude Sonnet 4.6 --- docs/make.jl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/make.jl b/docs/make.jl index fcb2a18f..3817c9b0 100644 --- a/docs/make.jl +++ b/docs/make.jl @@ -70,10 +70,10 @@ with_api_reference(src_dir) do api_pages pages=[ "Getting Started" => "getting-started.md", "Core Concepts" => [ - "Data" => joinpath("guide", "data.md"), - "Descriptions" => joinpath("guide", "descriptions.md"), "Exceptions" => joinpath("guide", "exceptions.md"), "Traits" => joinpath("guide", "traits.md"), + "Data" => joinpath("guide", "data.md"), + "Descriptions" => joinpath("guide", "descriptions.md"), ], "Strategies & Options" => [ "Options System" => joinpath("guide", "options-system.md"),