Skip to content

Commit

Permalink
Merge pull request #35 from MSeeker1340/named-tuple
Browse files Browse the repository at this point in the history
Named tuple conversion
  • Loading branch information
ChrisRackauckas authored Nov 21, 2018
2 parents 3e272be + 53e751c commit a825202
Show file tree
Hide file tree
Showing 7 changed files with 248 additions and 81 deletions.
89 changes: 78 additions & 11 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,27 @@ typed `SLArray` via:
SVType = @SLVector Float64 (:a,:b,:c)
```

Alternatively, you can also construct a static labelled array using the
`SLVector` constructor by writing out the entries as keyword arguments:

```julia
julia> SLVector(a=1, b=2, c=3)
3-element SLArray{Tuple{3},1,(:a, :b, :c),Int64}:
1
2
3
```

For general N-dimensional labelled arrays, you need to specify the size
(`Tuple{dim1,dim2,...}`) as the type parameter to the `SLArray` constructor:

```julia
julia> SLArray{Tuple{2,2}}(a=1, b=2, c=3, d=4)
2×2 SLArray{Tuple{2,2},2,(:a, :b, :c, :d),Int64}:
1 3
2 4
```

## LArrays

The `LArrayz`s are fully mutable arrays with labels. There is no performance
Expand Down Expand Up @@ -61,6 +82,22 @@ A = @LVector Float64 (:a,:b,:c,:d)
A .= rand(4)
```

As with `SLArray`, alternative constructors exist that use the keyword argument
form:

```julia
julia> LVector(a=1, b=2, c=3)
3-element LArray{Int64,1,(:a, :b, :c)}:
1
2
3

julia> LArray((2,2); a=1, b=2, c=3, d=4) # need to specify size as first argument
2×2 LArray{Int64,2,(:a, :b, :c, :d)}:
1 3
2 4
```

## Example: Nice DiffEq Syntax Without A DSL

LabelledArrays.jl are a way to get DSL-like syntax without a macro. In this case,
Expand Down Expand Up @@ -107,21 +144,51 @@ prob = ODEProblem(f,u0,tspan,p)
sol = solve(prob,Tsit5())
```

## Differences from NamedTuples
## Relation to NamedTuples

Julia's Base has NamedTuples in v0.7+. They are constructed as:

```julia
p == 10.0= 28.0= 8/3)
```

and they support `p[1]` and `p.σ` as well. However, there are some
crucial differences between a labelled array and static array.
But `@SLVector` also differs from a NamedTuple due to how the
type information is stored. A NamedTuple can have different types
on each element, while an `@SLVector` can only have one element
type and has the actions of a static vector. Thus `@SLVector`
has less element type information, improving compilation speed,
while giving more vector functionality than a NamedTuple.
`@LVector` also only has a single element type and, a crucial
difference, is mutable.
and they support `p[1]` and `p.σ` as well. The `LVector`, `SLVector`, `LArray`
and `SLArray` constructors also support named tuples as their arguments:

```julia
julia> LVector((a=1, b=2))
2-element LArray{Int64,1,(:a, :b)}:
1
2

julia> SLVector((a=1, b=2))
2-element SLArray{Tuple{2},1,(:a, :b),Int64}:
1
2

julia> LArray((2,2), (a=1, b=2, c=3, d=4))
2×2 LArray{Int64,2,(:a, :b, :c, :d)}:
1 3
2 4

julia> SLArray{Tuple{2,2}}((a=1, b=2, c=3, d=4))
2×2 SLArray{Tuple{2,2},2,(:a, :b, :c, :d),Int64}:
1 3
2 4
```

Converting to a named tuple from a labelled array x is available
using `convert(NamedTuple, x)`. Furthermore, `pairs(x)`
creates an iterator that is functionally the same as
`pairs(convert(NamedTuple, x))`, yielding `:label => x.label`
for each label of the array.

There are some crucial differences between a labelled array and
a named tuple. Labelled arrays can have any dimensions while
named tuples are always 1D. A named tuple can have different types
on each element, while an `SLArray` can only have one element
type and furthermore it has the actions of a static vector.
As a result `SLArray` has less element type information, which
improves compilation speed while giving more vector functionality
than a NamedTuple. `LArray` also only has a single element type and,
unlike a named tuple, is mutable.
2 changes: 1 addition & 1 deletion src/LabelledArrays.jl
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,6 @@ include("slarray.jl")
include("larray.jl")
include("display.jl")

export SLArray, LArray, @SLVector, @LArray, @LVector, @SLArray
export SLArray, LArray, SLVector, LVector, @SLVector, @LArray, @LVector, @SLArray

end # module
32 changes: 32 additions & 0 deletions src/larray.jl
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,38 @@ struct LArray{T,N,Syms} <: DenseArray{T,N}
LArray{T,N,Syms}(__x) where {T,N,Syms} = new{T,N,Syms}(__x)
end

#####################################
# NamedTuple compatibility
#####################################
## LArray to named tuple
function Base.convert(::Type{NamedTuple}, x::LArray{T,N,Syms}) where {T,N,Syms}
tup = NTuple{length(Syms),T}(x)
NamedTuple{Syms,typeof(tup)}(tup)
end

## Named tuple to LArray
#=
1. `LArray((2,2), (a=1, b=2, c=3, d=4))` (need to specify size)
2. `LArray((2,2); a=1, b=2, c=3, d=4)` : alternative form using kwargs
3. `LVector((a=1, b=2))` : can infer size for vectors
4. `LVector(a=1, b=2)` : alternative form using kwargs
=#
function LArray(size::NTuple{S,Int}, tup::NamedTuple{Syms,Tup}) where {S,Syms,Tup}
__x = reshape(collect(tup), size)
LArray{Syms}(__x)
end
LArray(size::NTuple{S,Int}; kwargs...) where {S} = LArray(size, kwargs.data)
LVector(tup::NamedTuple) = LArray((length(tup),), tup)
LVector(;kwargs...) = LVector(kwargs.data)

## pairs iterator
Base.pairs(x::LArray{T,N,Syms}) where {T,N,Syms} =
# (label => getproperty(x, label) for label in Syms) # not type stable?
(Syms[i] => x[i] for i in 1:length(Syms))

#####################################
# Array Interface
#####################################
Base.size(x::LArray) = size(getfield(x,:__x))
@inline Base.getindex(x::LArray,i...) = getfield(x,:__x)[i...]
@inline Base.setindex!(x::LArray,y,i...) = getfield(x,:__x)[i...] = y
Expand Down
33 changes: 32 additions & 1 deletion src/slarray.jl
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,38 @@ struct SLArray{S,N,Syms,T} <: StaticArray{S,T,N}
SLArray{S,N,Syms,T}(x::Tuple) where {S,N,Syms,T} = new{S,N,Syms,T}(SArray{S,T,N}(T.(x)))
end

# Implement the StaticVector interface
#####################################
# NamedTuple compatibility
#####################################
## SLArray to named tuple
function Base.convert(::Type{NamedTuple}, x::SLArray{S,N,Syms,T}) where {S,N,Syms,T}
tup = NTuple{length(Syms),T}(x)
NamedTuple{Syms,typeof(tup)}(tup)
end

## Named tuple to SLArray
#=
1. `SLArray{Tuple{2,2}}((a=1, b=2, c=3, d=4))` (need to specify size)
2. `SLArray{Tuple{2,2}}(a=1, b=2, c=3, d=4)` : alternative form using kwargs
3. `SLVector((a=1, b=2))` : infer size for vectors
4. `SLVector(a=1, b=2)` : alternative form using kwargs
=#
function SLArray{Size}(tup::NamedTuple{Syms,Tup}) where {Size,Syms,Tup}
__x = Tup(tup) # drop symbols
SLArray{Size,length(Size.parameters),Syms}(__x)
end
SLArray{Size}(;kwargs...) where {Size} = SLArray{Size}(kwargs.data)
SLVector(tup::NamedTuple) = SLArray{Tuple{length(tup)}}(tup)
SLVector(;kwargs...) = SLVector(kwargs.data)

## pairs iterator
Base.pairs(x::SLArray{S,N,Syms,T}) where {S,N,Syms,T} =
# (label => getproperty(x, label) for label in Syms) # not type stable?
(Syms[i] => x[i] for i in 1:length(Syms))

#####################################
# StaticArray Interface
#####################################
@inline Base.getindex(x::SLArray, i::Int) = getfield(x,:__x)[i]
@inline Base.Tuple(x::SLArray) = Tuple(x.__x)
function StaticArrays.similar_type(::Type{SLArray{S,N,Syms,T}}, ::Type{NewElType},
Expand Down
110 changes: 65 additions & 45 deletions test/larrays.jl
Original file line number Diff line number Diff line change
@@ -1,51 +1,71 @@
using LabelledArrays, Test, InteractiveUtils

x = @LArray [1.0,2.0,3.0] (:a,:b,:c)
y = @LVector Float64 (:a,:b,:c)
y .= [1,2,3.]
@test x == y
@testset "Basic interface" begin
x = @LArray [1.0,2.0,3.0] (:a,:b,:c)
y = @LVector Float64 (:a,:b,:c)
y .= [1,2,3.]
@test x == y

syms = (:a,:b,:c)
syms = (:a,:b,:c)

for (i,s) in enumerate(syms)
@show i,s
@test x[i] == x[s]
for (i,s) in enumerate(syms)
@show i,s
@test x[i] == x[s]
end

x[:a]

f(x) = x[1]
g(x) = x.a
@time f(x)
@time f(x)
@time g(x)
@time g(x)

#@code_warntype x.a
#@inferred getindex(x,:a)
@code_warntype g(x)
@inferred g(x)

x = @LArray [1,2,3] (:a,:b,:c)
@test x .* x isa LArray
@test x .+ 1 isa LArray
@test x .+ 1. isa LArray
z = x .+ ones(Int, 3)
@test z isa LArray && eltype(z) === Int
z = x .+ ones(Float64, 3)
@test z isa LArray && eltype(z) === Float64
@test eltype(x .+ 1.) === Float64

z = @LArray Float64 (2,2) (:a,:b,:c,:d)
w = rand(2,2)
z .= w

@test z[:a] == w[1,1]
@test z[:b] == w[2,1]
@test z[:c] == w[1,2]
@test z[:d] == w[2,2]
end

x[:a]

f(x) = x[1]
g(x) = x.a
@time f(x)
@time f(x)
@time g(x)
@time g(x)

#@code_warntype x.a
#@inferred getindex(x,:a)
@code_warntype g(x)
@inferred g(x)

x = @LArray [1,2,3] (:a,:b,:c)
@test x .* x isa LArray
@test x .+ 1 isa LArray
@test x .+ 1. isa LArray
z = x .+ ones(Int, 3)
@test z isa LArray && eltype(z) === Int
z = x .+ ones(Float64, 3)
@test z isa LArray && eltype(z) === Float64
@test eltype(x .+ 1.) === Float64

z = @LArray Float64 (2,2) (:a,:b,:c,:d)
w = rand(2,2)
z .= w

@test z[:a] == w[1,1]
@test z[:b] == w[2,1]
@test z[:c] == w[1,2]
@test z[:d] == w[2,2]

t = similar(z, String) # t's elements are uninitialized
@test_throws UndefRefError t[1]
copy(t) # should be ok
deepcopy(t) # should also be ok
@testset "NameTuple conversion" begin
x_tup = (a=1, b=2)
y_tup = (a=1, b=2, c=3, d=4)
x = LVector(a=1, b=2)
y = LArray((2,2); a=1, b=2, c=3, d=4)
@test convert(NamedTuple, x) == x_tup
@test convert(NamedTuple, y) == y_tup
@test collect(pairs(x)) == collect(pairs(x_tup))
@test collect(pairs(y)) == collect(pairs(y_tup))

@code_warntype LArray((2,2); a=1, b=2, c=3, d=4)
@code_warntype convert(NamedTuple, y)
@code_warntype collect(pairs(y))
end

@testset "undef copy" begin
z = @LArray Float64 (2,2) (:a,:b,:c,:d)
t = similar(z, String) # t's elements are uninitialized
@test_throws UndefRefError t[1]
copy(t) # should be ok
deepcopy(t) # should also be ok
end
2 changes: 1 addition & 1 deletion test/runtests.jl
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ using Test
using StaticArrays

@time begin
@time @testset "SLArrays Macros" begin include("slarrays.jl") end
@time @testset "SLArrays" begin include("slarrays.jl") end
@time @testset "LArrays" begin include("larrays.jl") end
@time @testset "DiffEq" begin include("diffeq.jl") end
@time @testset "Display" begin include("display.jl") end
Expand Down
61 changes: 39 additions & 22 deletions test/slarrays.jl
Original file line number Diff line number Diff line change
@@ -1,26 +1,43 @@
using LabelledArrays
using Test
using LabelledArrays, StaticArrays
using Test, InteractiveUtils

ABC = @SLVector (:a,:b,:c)
b = ABC(1,2,3)
@testset "Basic interface" begin
ABC = @SLVector (:a,:b,:c)
b = ABC(1,2,3)

@test b.a == 1
@test b.b == 2
@test b.c == 3
@test b[1] == b.a
@test b[2] == b.b
@test b[3] == b.c
@test b.a == 1
@test b.b == 2
@test b.c == 3
@test b[1] == b.a
@test b[2] == b.b
@test b[3] == b.c

@test_throws UndefVarError fill!(a,1)
@test typeof(b.__x) == SVector{3,Int}
@test_throws UndefVarError fill!(a,1)
@test typeof(b.__x) == SVector{3,Int}

# Type stability tests
ABC_fl = @SLVector Float64 (:a, :b, :c)
ABC_int = @SLVector Int (:a, :b, :c)
@test similar_type(b, Float64) == ABC_fl
@test typeof(copy(b)) == ABC_int
@test typeof(Float64.(b)) == ABC_fl
@test typeof(b .+ b) == ABC_int
@test typeof(b .+ 1.0) == ABC_fl
@test typeof(zero(b)) == ABC_int
@test similar(b) isa MArray # similar should return a mutable copy
# Type stability tests
ABC_fl = @SLVector Float64 (:a, :b, :c)
ABC_int = @SLVector Int (:a, :b, :c)
@test similar_type(b, Float64) == ABC_fl
@test typeof(copy(b)) == ABC_int
@test typeof(Float64.(b)) == ABC_fl
@test typeof(b .+ b) == ABC_int
@test typeof(b .+ 1.0) == ABC_fl
@test typeof(zero(b)) == ABC_int
@test similar(b) isa MArray # similar should return a mutable copy
end

@testset "NamedTuple conversion" begin
x_tup = (a=1, b=2)
y_tup = (a=1, b=2, c=3, d=4)
x = SLVector(a=1, b=2)
y = SLArray{Tuple{2,2}}(a=1, b=2, c=3, d=4)
@test convert(NamedTuple, x) == x_tup
@test convert(NamedTuple, y) == y_tup
@test collect(pairs(x)) == collect(pairs(x_tup))
@test collect(pairs(y)) == collect(pairs(y_tup))

@code_warntype SLArray{Tuple{2,2}}(a=1, b=2, c=3, d=4)
@code_warntype convert(NamedTuple, y)
@code_warntype collect(pairs(y))
end

0 comments on commit a825202

Please sign in to comment.