From d733882f8c9b851dbc464b4fc519649cda8d1cd1 Mon Sep 17 00:00:00 2001 From: Tucker McClure Date: Sun, 26 Apr 2026 22:26:13 -0700 Subject: [PATCH] Clean up for initial public release --- .github/workflows/test.yml | 2 +- Project.toml | 1 + .../DimensionsStaticArraysExt.jl | 2 +- readme.md | 11 +++-- src/Dimensions.jl | 48 ++++++++++++++----- test/runtests.jl | 23 +++++++++ 6 files changed, 68 insertions(+), 19 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 1c101f5..e2d75a3 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -16,7 +16,7 @@ jobs: runs-on: ${{ matrix.os }} strategy: matrix: - julia-version: ['1.12'] + julia-version: ['1.9', '1'] julia-arch: [x64] os: [ubuntu-latest, windows-latest, macOS-latest] diff --git a/Project.toml b/Project.toml index b51b8f9..70a3cae 100644 --- a/Project.toml +++ b/Project.toml @@ -13,4 +13,5 @@ StaticArrays = "90137ffa-7385-5640-81b9-e52037218182" DimensionsStaticArraysExt = "StaticArrays" [compat] +julia = "1.9" StaticArrays = "1.9.15" diff --git a/ext/DimensionsStaticArraysExt/DimensionsStaticArraysExt.jl b/ext/DimensionsStaticArraysExt/DimensionsStaticArraysExt.jl index c964ff1..29486ec 100644 --- a/ext/DimensionsStaticArraysExt/DimensionsStaticArraysExt.jl +++ b/ext/DimensionsStaticArraysExt/DimensionsStaticArraysExt.jl @@ -5,7 +5,7 @@ import Dimensions using StaticArrays: SVector Dimensions.dimstyle(::Type{SVector{N, T}}) where {N, T <: Real} = RealVectorDimensionStyle() -Dimensions.numdims_for_type(::RealVectorDimensionStyle, ::Type{SVector{N, T}}) where {N, T} = N +Dimensions.numdims_for_type(::RealVectorDimensionStyle, ::Type{SVector{N, T}}) where {N, T <: Real} = N Dimensions.numdims(::RealVectorDimensionStyle, x::SVector{N, T}) where {N, T <: Real} = N Dimensions.getdim(::RealVectorDimensionStyle, x::SVector{N, T}, k) where {N, T <: Real} = x[k] Dimensions.eachdim(::RealVectorDimensionStyle, x::SVector{N, T}) where {N, T <: Real} = x diff --git a/readme.md b/readme.md index b58f229..498d115 100644 --- a/readme.md +++ b/readme.md @@ -7,11 +7,12 @@ This Julia package provides an interface for defining a type's "dimensions". E.g The key functionality comes from the following functions: * `numdims(x)` returns the number of dimensions of `x` +* `numdims_for_type(T)` returns the number of dimensions for values of type `T`, when that is known from the type alone * `getdim(x, k)` returns dimension `k` of `x` * `eachdim(x)` returns an iterator over the dimensions of `x` -* `dimstyle(t)` returns the dimension "style" of type `t` +* `dimstyle(T)` returns the dimension "style" of type `T` -By default, this works with `Real` numbers (1D), `Complex` numbers, (2D), enums (1D), and `Vector`s of `Real` elements. It also works for `SVectors` if you have StaticArrays imported. +By default, this works with `Real` numbers (1D), `Complex` numbers (2D), enums (1D), and `AbstractVector`s of `Real` elements. It also works for `SVectors` if you have StaticArrays imported. To add "dimensional" behavior to a custom type, just add methods for those functions for your type. And in fact, most types can be handled simply by adding a method to `dimstyle`. Here's an example: @@ -26,7 +27,7 @@ import Dimensions Dimensions.dimstyle(::Type{Position}) = Dimensions.StructDimensionStyle() ``` -The remaining functions are already implemented for that "style"; the dimensions will be taken as the combination of the dimensions of the fields. Here, `numdims(x)` will clearly be 3, `getdim(x, 2)` will return the `y` field, and `eachdim(x)` will return an iterator over `x`, then `y`, then `z`. +The remaining functions are already implemented for that "style"; the dimensions will be taken as the combination of the dimensions of the fields. Here, `numdims(x)` and `numdims_for_type(Position)` will clearly be 3, `getdim(x, 2)` will return the `y` field, and `eachdim(x)` will return an iterator over `x`, then `y`, then `z`. Further, structs of structs work just as well. Consider the following: @@ -43,9 +44,11 @@ That type has 6 dimensions, 3 from position and 3 from velocity. This is useful for anything that needs to break a type all the way down to its fundamental scalars. For instance, when plotting a vector of `Position` over time, a plotting package could first make a line for each position's dimension 1, then a line for each position's dimension 2, and then for each position's dimension 3, giving three lines. More generally, it is a way of serializing the data to scalars. +`numdims_for_type(T)` is useful when the dimensionality is fixed by the type, such as `Float64`, `ComplexF64`, `SVector{3, Float64}`, or a `StructDimensionStyle` type whose fields all have type-known dimensionality. It is not available for value-dependent types like ordinary dynamic vectors; call `numdims(x)` on a value in those cases. + ## Available Dimension Styles * `ScalarDimensionStyle`: Always 1 dimensional * `ComplexDimensionStyle`: Always 2 dimensional -* `RealVectorDimensionStyle`: The dimensions are the elements of the vector (all real) +* `RealVectorDimensionStyle`: The dimensions are the elements of the `AbstractVector` (all real) * `StructDimensionStyle`: The dimensions are the set containing the dimensions of the fields, recursively, all the way down. This works for most but not all types. An example where it doesn't work: the Rational type has `num` and `den` fields, but it's really a single real dimension, not two separate dimensions. diff --git a/src/Dimensions.jl b/src/Dimensions.jl index c089f4b..4c78aea 100644 --- a/src/Dimensions.jl +++ b/src/Dimensions.jl @@ -1,6 +1,6 @@ module Dimensions -export dimstyle, numdims, getdim, eachdim +export dimstyle, numdims, getdim, eachdim, numdims_for_type abstract type AbstractDimensionStyle end struct UnknownDimensionStyle <: AbstractDimensionStyle end @@ -12,9 +12,9 @@ struct RealVectorDimensionStyle <: AbstractDimensionStyle end struct StructDimensionStyle <: AbstractDimensionStyle end """ - dimstyle(x) + dimstyle(::Type{T}) -Returns the "dimension style" for `x`. The available dimension styles are: +Returns the "dimension style" for type `T`. The available dimension styles are: * `ScalarDimensionStyle`: Always 1 dimensional * `ComplexDimensionStyle`: Always 2 dimensional @@ -25,18 +25,35 @@ dimstyle(::Type{<:Any}) = UnknownDimensionStyle() dimstyle(::Type{<:Real}) = ScalarDimensionStyle() dimstyle(::Type{<:Complex}) = ComplexDimensionStyle() dimstyle(::Type{<:Enum}) = ScalarDimensionStyle() -dimstyle(::Type{<:Vector{<:Real}}) = RealVectorDimensionStyle() +dimstyle(::Type{<:AbstractVector{<:Real}}) = RealVectorDimensionStyle() # dimstyle(::Type{Vector{T}}) where {T} = VectorOfNdElementsDimensionStyle{numdims_for_type(T)}() +function _unsupported_type_error(t) + return ArgumentError( + "No dimension behavior is defined for values of type $t. " * + "Define `Dimensions.dimstyle(::Type{$t})` or add type-specific `numdims`, " * + "`getdim`, and `eachdim` methods." + ) +end + +function _unknown_type_dimension_error(t) + return ArgumentError( + "The number of dimensions for $t is not known from the type alone. " * + "Call `numdims(x)` on a value, or define a more specific " * + "`Dimensions.numdims_for_type` method." + ) +end + """ getdim(x, d) Returns dimension `d` of `x` and throws an error if the given dimension is invalid. """ getdim(x, d) = getdim(dimstyle(typeof(x)), x, d) -getdim(::ScalarDimensionStyle, x, d) = d == 1 ? x : error("Dimension $d does not exist for a type with a `ScalarDimensionStyle``.") -getdim(::ComplexDimensionStyle, x, d) = d == 1 ? real(x) : (d == 2 ? imag(x) : error("Dimension $d does not exist for a type with a `ComplexDimensionStyle``.")) -getdim(::RealVectorDimensionStyle, x, d) = x[d] +getdim(::UnknownDimensionStyle, x, d) = throw(_unsupported_type_error(typeof(x))) +getdim(::ScalarDimensionStyle, x, d) = d == 1 ? x : error("Dimension $d does not exist for a type with a `ScalarDimensionStyle`.") +getdim(::ComplexDimensionStyle, x, d) = d == 1 ? real(x) : (d == 2 ? imag(x) : error("Dimension $d does not exist for a type with a `ComplexDimensionStyle`.")) +getdim(::RealVectorDimensionStyle, x, d) = x[axes(x, 1)[d]] function getdim(::StructDimensionStyle, x::T, d) where {T} n_dims_so_far = 0 for f in fieldnames(T) @@ -53,17 +70,20 @@ end # getdim(::VectorOfNdElementsDimensionStyle{N}, x, d) where {N} = getdim(x[cld(d, N)], mod1(d, N)) """ - numdims_for_type(t) + numdims_for_type(::Type{T}) -Returns the number of dimensions for the given type. Note that this is not available for all -types. E.g., a Vector's dimensions are not available from its type. +Returns the number of dimensions for values of type `T`, when that number is known from the +type alone. Note that this is not available for all types. E.g., an `AbstractVector`'s +dimensions are generally value-dependent and are not available from its type. """ -numdims_for_type(t) = numdims_for_type(dimstyle(t), t) +numdims_for_type(t::Type) = numdims_for_type(dimstyle(t), t) +numdims_for_type(x) = throw(ArgumentError("`numdims_for_type` expects a type; use `numdims(x)` for values.")) +numdims_for_type(::UnknownDimensionStyle, t) = throw(_unsupported_type_error(t)) numdims_for_type(::ScalarDimensionStyle, t) = 1 numdims_for_type(::ComplexDimensionStyle, t) = 2 +numdims_for_type(::RealVectorDimensionStyle, t) = throw(_unknown_type_dimension_error(t)) numdims_for_type(::StructDimensionStyle, t::Type{T}) where {T} = sum(numdims_for_type(ft) for ft in fieldtypes(T)) -# Note that the following dimensions can't be known from their types: -# RealVectorDimensionStyle, VectorOfNdElementsDimensionStyle +# Note that the following dimensions can't be known from their types: RealVectorDimensionStyle """ numdims(x) @@ -71,6 +91,7 @@ numdims_for_type(::StructDimensionStyle, t::Type{T}) where {T} = sum(numdims_for Returns the number of dimensions for `x`. """ numdims(x) = numdims(dimstyle(typeof(x)), x) +numdims(::UnknownDimensionStyle, x) = throw(_unsupported_type_error(typeof(x))) numdims(::ScalarDimensionStyle, x) = 1 numdims(::ComplexDimensionStyle, x) = 2 numdims(::RealVectorDimensionStyle, x) = length(x) @@ -83,6 +104,7 @@ numdims(::StructDimensionStyle, x::T) where {T} = sum(numdims(getfield(x, f)) fo Returns an iterator over the dimensions of `x`. """ eachdim(x) = eachdim(dimstyle(typeof(x)), x) +eachdim(::UnknownDimensionStyle, x) = throw(_unsupported_type_error(typeof(x))) eachdim(::ScalarDimensionStyle, x) = (x,) eachdim(::ComplexDimensionStyle, x) = (x.re, x.im) eachdim(::RealVectorDimensionStyle, x) = x # already an iterator diff --git a/test/runtests.jl b/test/runtests.jl index c13b1f5..42888a1 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -63,9 +63,32 @@ end test_real_vector([1., 2., 3.]) test_real_vector([4,]) test_real_vector(Float64[]) + test_real_vector(1:3) + test_real_vector(@view [1., 2., 3.][2:3]) test_real_vector(SVector{3, Float64}(1., 2., 3.)) end +@testset "numdims_for_type" begin + @test numdims_for_type(Float64) == 1 + @test numdims_for_type(ComplexF64) == 2 + @test numdims_for_type(Tree) == 1 + @test numdims_for_type(Position) == 3 + @test numdims_for_type(Composite) == 2 + @test numdims_for_type(BigType) == 5 + @test numdims_for_type(SVector{3, Float64}) == 3 + @test_throws ArgumentError numdims_for_type(Vector{Float64}) + @test_throws ArgumentError numdims_for_type(1) +end + +@testset "unsupported types" begin + @test dimstyle(String) == Dimensions.UnknownDimensionStyle() + @test_throws ArgumentError numdims("hello") + @test_throws ArgumentError getdim("hello", 1) + @test_throws ArgumentError eachdim("hello") + @test_throws ArgumentError numdims([1 2; 3 4]) + @test_throws ArgumentError numdims_for_type(String) +end + @testset "structs" begin values = [1.1, 2.2, 3.3]