Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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]

Expand Down
1 change: 1 addition & 0 deletions Project.toml
Original file line number Diff line number Diff line change
Expand Up @@ -13,4 +13,5 @@ StaticArrays = "90137ffa-7385-5640-81b9-e52037218182"
DimensionsStaticArraysExt = "StaticArrays"

[compat]
julia = "1.9"
StaticArrays = "1.9.15"
2 changes: 1 addition & 1 deletion ext/DimensionsStaticArraysExt/DimensionsStaticArraysExt.jl
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
11 changes: 7 additions & 4 deletions readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -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:

Expand All @@ -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:

Expand All @@ -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.
48 changes: 35 additions & 13 deletions src/Dimensions.jl
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -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
Expand All @@ -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)
Expand All @@ -53,24 +70,28 @@ 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)

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)
Expand All @@ -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
Expand Down
23 changes: 23 additions & 0 deletions test/runtests.jl
Original file line number Diff line number Diff line change
Expand Up @@ -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]
Expand Down
Loading