How to create a struct that contains an SVector of structs?

The following code does not work:

using StaticArrays, Parameters
import Base.zero

const SEGMENTS = 6

# struct, defining the physical parameters of one spring
@with_kw mutable struct Spring{I, S}
    p1::I = 1         # number of the first point
    p2::I = 2         # number of the second point
    length::S = 1.0   # Current unstressed spring length
    c_spring::S = 1.0 # 
    damping::S  = 0.1 # 
end

const SP = Spring{Int16, Float64}

function zero(::Type{SP})
    s = SP()
    s.p1 = 0
    s.p2 = 0
    s.length = 0.0
    s.c_spring = 0.0
    s.damping = 0.0
    s
end

@with_kw mutable struct KPS4{S, P, SP} 
    masses::MVector{P, S}         = ones(P)
    springs::SVector{P-1, SP}     = SVector{P-1, SP}(zeros(SP, P-1))
end

function KPS4()
    KPS4{Float64, SEGMENTS+1, SP}()
end

kps = KPS4()

Error message:

julia> include("src/bug.jl")
ERROR: LoadError: MethodError: no method matching -(::TypeVar, ::Int64)
Closest candidates are:
  -(::T, ::T) where T<:Union{Int128, Int16, Int32, Int64, Int8, UInt128, UInt16, UInt32, UInt64, UInt8} at ~/packages/julias/julia-1.7/share/julia/base/int.jl:86
  -(::Base.TwicePrecision, ::Number) at ~/packages/julias/julia-1.7/share/julia/base/twiceprecision.jl:293
  -(::LinearAlgebra.UniformScaling, ::Number) at ~/packages/julias/julia-1.7/share/julia/stdlib/v1.7/LinearAlgebra/src/uniformscaling.jl:147
  ...
Stacktrace:
 [1] top-level scope
   @ ~/.julia/packages/Parameters/MK0O4/src/Parameters.jl:611
 [2] include(fname::String)
   @ Base.MainInclude ./client.jl:451
 [3] top-level scope
   @ REPL[1]:1
in expression starting at /home/ufechner/repos/KiteModels/src/bug.jl:27

What am I doing wrong?

The following code works fine:

using StaticArrays, Parameters
import Base.zero

const SEGMENTS = 6

# struct, defining the phyical parameters of one spring
@with_kw mutable struct Spring{I, S}
    p1::I = 1         # number of the first point
    p2::I = 2         # number of the second point
    length::S = 1.0   # Current unstressed spring length
    c_spring::S = 1.0 # 
    damping::S  = 0.1 # 
end

const SP = Spring{Int16, Float64}

function zero(::Type{SP})
    s = SP()
    s.p1 = 0
    s.p2 = 0
    s.length = 0.0
    s.c_spring = 0.0
    s.damping = 0.0
    s
end

springs = SVector{3, SP}(zeros(SP, 3))

Output:


julia> include("src/working.jl")
3-element SVector{3, SP} with indices SOneTo(3):
 SP
  p1: Int16 0
  p2: Int16 0
  length: Float64 0.0
  c_spring: Float64 0.0
  damping: Float64 0.0

 SP
  p1: Int16 0
  p2: Int16 0
  length: Float64 0.0
  c_spring: Float64 0.0
  damping: Float64 0.0

 SP
  p1: Int16 0
  p2: Int16 0
  length: Float64 0.0
  c_spring: Float64 0.0
  damping: Float64 0.0

But why can’t I use this array of structs as element of another struct?

ERROR: LoadError: MethodError: no method matching -(::TypeVar, ::Int64)

This comes from

@with_kw mutable struct KPS4{S, P, SP} 
    masses::MVector{P, S}         = ones(P)
    springs::SVector{P-1, SP}     = SVector{P-1, SP}(zeros(SP, P-1))
end

Most specifically because of springs::SVector{P-1, SP} = .... You cannot do arithmetic with the type parameters of a parameterized struct in the definition of its fields. Julia simply do not support it. If I am not wrong the ComputedFieldTypes package can be used to overcome this limitation (not sure if in all cases).

1 Like

Ok, this works:

using StaticArrays, Parameters
import Base.zero

const SEGMENTS = 6

# struct, defining the phyical parameters of one spring
@with_kw mutable struct Spring{I, S}
    p1::I = 1         # number of the first point
    p2::I = 2         # number of the second point
    length::S = 1.0   # Current unstressed spring length
    c_spring::S = 1.0 # 
    damping::S  = 0.1 # 
end

const SP = Spring{Int16, Float64}

function zero(::Type{SP})
    s = SP()
    s.p1 = 0
    s.p2 = 0
    s.length = 0.0
    s.c_spring = 0.0
    s.damping = 0.0
    s
end

@with_kw mutable struct KPS4{S, P, Q} 
    masses::MVector{P, S}      = ones(P)
    springs::Vector{Q}         = zeros(Q, P)
end

function KPS4()
    KPS4{Float64, SEGMENTS+1, SP}()
end

kps = KPS4()

But I want to have an SVector of Springs, not a vector. Any idea how to achieve that?

This works:

using StaticArrays, Parameters
import Base.zero

const SEGMENTS = 6

# struct, defining the phyical parameters of one spring
@with_kw mutable struct Spring{I, S}
    p1::I = 1         # number of the first point
    p2::I = 2         # number of the second point
    length::S = 1.0   # Current unstressed spring length
    c_spring::S = 1.0 # 
    damping::S  = 0.1 # 
end

const SP = Spring{Int16, Float64}

function zero(::Type{SP})
    s = SP()
    s.p1 = 0
    s.p2 = 0
    s.length = 0.0
    s.c_spring = 0.0
    s.damping = 0.0
    s
end

@with_kw mutable struct KPS4{S, P, Q, SP} 
    masses::MVector{P, S}      = ones(P)
    springs::SVector{Q, SP}    = zeros(SP, Q)
end

function KPS4()
    KPS4{Float64, SEGMENTS+1, SEGMENTS, SP}()
end

kps = KPS4()

Slightly inconvenient, but not much…

Thanks a lot!

1 Like

My pleasure. You did most of the work. I just gave a nudge in the right direction.