Convert or Reinterpret Between NamedTuple and SVector

My question is related to Vector to NamedTuple?

Assume my NamedTuple contains only floating point values T and SVector{N, T} entries, e.g.,

using StaticArrays 
nt = ( a = rand(), b = SA[1.0, 2.0 ,3.0] )

Taking such a NamedTuple and interpreting it as an SVector is straightforward

v = reinterpret(SVector{4, Float64}, nt)

I can write a @generated function that does the reverse, but it is awful (see below) and I just wondered whether is a more straightforward way to achieve this?

@generated function _svec2nt(v::SVector, x::NamedTuple) 
   SYMS = fieldnames(x)
   TT = fieldtypes(x)

   _len(::Type{T}) where {T <: Number} = 1
   _len(::Type{SVector{N, T}}) where {N, T <: Number} = N

   i0 = Int[] 
   idx = 1 
   for T in TT
      push!(i0, idx) 
      idx += _len(T)
   end
   push!(i0, idx) 

   # indexing into v::SVector 
   inds = [] 
   for i = 1:length(TT) 
      rg = i0[i]:i0[i+1]-1
      if length(rg) == 1 
         push!(inds, "$(first(rg))")
      else 
         rg = SVector(rg...)
         push!(inds, "SA$rg")
      end
   end

   code = "(; "
   for (sym, ind) in zip(SYMS, inds)
      code *= "$sym = v[$ind], "
   end 
   code *= ")"

   return quote 
      $(Meta.parse(code))
   end
end 

Rather than crafting a statement for parsing, you can use a NamedTuple constructor directly, just find the segments of the vector

julia> NamedTuple{fieldnames(typeof(nt)), Tuple{fieldtypes(typeof(nt))...}}((v[1], v[2:4]))
(a = 0.31259929654404184, b = [1.0, 2.0, 3.0])

or even just

julia> typeof(nt)((v[1], v[2:4]))

But you need a small program to find the indices 1 and 2:4.

That doesn’t sound conceptually easier or clearer. I was hoping some trick that exploits how these objects just occupy the same memory.

If you want, you can again use reinterpret:

julia> nt = ( a = rand(), b = SA[1.0, 2.0 ,3.0] )
(a = 0.4808058471551182, b = [1.0, 2.0, 3.0])

julia> v = reinterpret(SVector{4, Float64}, nt)
4-element SVector{4, Float64} with indices SOneTo(4):
 0.4808058471551182
 1.0
 2.0
 3.0

julia> reinterpret(typeof(nt), Tuple(v))
(a = 0.4808058471551182, b = [1.0, 2.0, 3.0])

For some reason, one has to use Tuple(v) instead of v.

1 Like

That’s what I was missing. Thanks!

reinterpret(T::DataType, A::AbstractArray) tries to lazily reinterpret an array as an array of a different element type, rather than an instance of said type.

julia> nt2 = reinterpret(typeof(nt), v); # can't be printed

julia> dump(nt2)
Base.ReinterpretArray{@NamedTuple{a::Float64, b::SVector{3, Float64}}, 1, Float64, SVector{4, Float64}, false}
  parent: SVector{4, Float64}
    data: NTuple{4, Float64}
      1: Float64 0.08744277749612084
      2: Float64 1.0
      3: Float64 2.0
      4: Float64 3.0
  readable: Bool true
  writable: Bool true

julia> eltype(nt2)
@NamedTuple{a::Float64, b::SVector{3, Float64}}

If we try to index it though, at some point it tries to convert the reinterpreted axes 1:1 to the type of the parent SVector’s axes SOneTo{4}, which is only compatible with 1:4. Not really sure why that convert happens, maybe for automatic offset indices?