Converting a vector of structs to a struct of vectors

Hello,

I have the following struct:

using Rotations, StaticArrays

const MyFloat = Float32
const SEGMENTS = 7                    # number of tether segments

struct SysState
    time::Float64                     # time since launch in seconds
    orient::Quat                      # orientation of the kite
    X::MVector{SEGMENTS+1, MyFloat}   # vector of particle positions in x
    Y::MVector{SEGMENTS+1, MyFloat}   # vector of particle positions in y
    Z::MVector{SEGMENTS+1, MyFloat}   # vector of particle positions in z
end 

Then I create an array of these structs:

# create a demo state with a given height and time
function demo_state(height=6.0, time=0.0)
    a = 10
    X = range(0, stop=10, length=SEGMENTS+1)
    Y = zeros(length(X))
    Z = (a .* cosh.(X./a) .- a) * height/ 5.430806 
    orient = UnitQuaternion(1.0,0,0,0)
    return SysState(time, orient, X, Y, Z)
end

# create a demo flight log with given name [String] and duration [s]
const SAMPLE_FREQ=20
function demo_log(name="Test flight", duration=10)
    delta_t = 1.0 / SAMPLE_FREQ
    t_max   = 10.0
    max_height = 6.0
    steps   = Int(t_max * SAMPLE_FREQ) + 1
    log = Vector{SysState}(undef, steps)
    for i in range(0, length=steps)
        log[i+1] = demo_state(max_height * i/steps, i*delta_t)
    end
    return log
end

Finally I want to create a struct of vectors (sov) out of this vector of structs (vos) to be able to access the elements easily, like that:

vos = demo_log()
sov = vos2sov(vos)   # how can I implement this function?
println(sov.time)    # should print a vector of Float64
println(sov.orient)  # should print a vector of quaternions

How can I implement the function vos2sov?

I looked at the package StructArrays.jl, but it seams as if it cannot handle structs that are composed of different types.

I was wrong, StructArrays CAN handle structs that are composed of different types. Example:

using StructArrays

struct Compound
    time::Float64
    name::String
end

sa = StructArray{Compound}(([1.0, 2.0], ["Hund", "Hase"]))

println(sa.time)
println(sa.name)

I still have to figure out how to implement the function vos2sov in a generic way. I don’t want to change this function when the struct changes.

1 Like

Ok, this function partially works:

function vos2sov(log::Vector)
    steps=length(log)
    time_vec = Vector{Float64}(undef, steps)
    orient_vec = Vector{Quat}(undef, steps)
    X_vec = Vector{MVector{SEGMENTS+1, MyFloat}}(undef, steps)
    Y_vec = Vector{MVector{SEGMENTS+1, MyFloat}}(undef, steps)
    Z_vec = Vector{MVector{SEGMENTS+1, MyFloat}}(undef, steps)
    for i in range(1, length=steps)
        state=log[i]
        time_vec[i] = state.time
        orient_vec[i] = state.orient
        X_vec[i] = state.X
        Y_vec[i] = state.Y
        Z_vec[i] = state.Z
    end
    return StructArray{SysState}((time_vec, orient_vec, X_vec, Y_vec, Z_vec))
end

How can I rewrite it such that it works with any struct in a generic way?

Maybe you could try working with list comprehension.

julia> struct Foo
         a
       end

julia> foo = Foo(1.0)
Foo(1.0)

julia> fa = [foo, foo]
2-element Vector{Foo}:
 Foo(1.0)
 Foo(1.0)

julia> a = [x.a for x in fa]
2-element Vector{Float64}:
 1.0
 1.0

julia> struct Sol
         a
       end

julia> sol = Sol(a)
Sol([1.0, 1.0])