Trouble with writing a mutable struct with MVector fields

I’m working on implementing a Particle Swarm Optimization routine in Julia and would like to define a “Particle” mutable struct with MVector fields (from StaticArrays.jl) but am running into some issues.

Here is my current struct:

mutable struct Particle{M,T} where {M <: Integer, T <: AbstractFloat}

    x::MVector{M,T} # Current Position
    v::MVector{M,T} # Current Velocity
    p::MVector{M,T} # Best Position

    fx::T # Current Objective Function Value
    fp::T # Best Objective Function Value

    function Particle(x::MVector{M,T}, v::MVector{M,T}) where {M <: Integer, T <: AbstractFloat}
        xl = length(x)
        vl = length(v)
        if xl != vl
            throw(ArgumentError("Particle's position and velocity must be the same length."))
        else
            new{M,T}(x, v, NaN*similar(x), NaN*x[1], NaN*x[1])
        end
    end
end

Using my defined constructor, I get the following error:

julia> a = MVector{3}(rand(3))
3-element MArray{Tuple{3},Float64,1,3} with indices SOneTo(3):
 0.6821892643778722
 0.41842430858799906

julia> b = MVector{3}(rand(3))
3-element MArray{Tuple{3},Float64,1,3} with indices SOneTo(3):
 0.9403860675524811
 0.163642437664691
 0.1349743741370517

julia> Particle(a,b)
ERROR: MethodError: no method matching Particle(::MArray{Tuple{3},Float64,1,3}, ::MArray{Tuple{3},Float64,1,3})
Stacktrace:
 [1] top-level scope at REPL[3]:1

I’ve primarily used MATLAB for all of my work and am relatively new to Julia and this error has me stumped. What confuses me most is, upon running the following command:

julia> methods(Particle)
# 1 method for type constructor:
[1] (::Type{Particle})(x::MArray{Tuple{M},T,1,M}, v::MArray{Tuple{M},T,1,M}) where {M<:Integer, T<:AbstractFloat} in Main at c:\Users\grant\.julia\dev\JuSwarm\src\particle.jl:36

it appears (to the best of my knowledge) that the correct method for constructing my Particle type does exist… Any and all help on this issue would be greatly appreciated!

I am not 100% sure but I think the problem is a subtle one.

Basically, your type restrictions are wrong. M <: Integer means a type M that is a subtype of Integer. 3 is no type, it is a value.

julia> 3 <: Integer
ERROR: TypeError: in <:, expected Type, got a value of type Int64
Stacktrace:
 [1] top-level scope at REPL[7]:1

If you remove the type restriction (the <: Integer bit) from both where clauses (on the struct and on the constructor), then the problem disappears.

Also, you do not need that test with the lengths. You are matching the M values of both x and v MVectors in the signature, it will already error if your try to pass two MVectors of different lengths (as the length is encoded in the type).

4 Likes

Also, I forgot to say before: Welcome to our community! :confetti_ball:

1 Like

Here (Julia 1.5.3) I cannot define the struct as you did:

julia> mutable struct Particle{M,T} where {M <: Integer, T <: AbstractFloat}
         x::MVector{M,T} # Current Position
         v::MVector{M,T} # Current Velocity
       end
ERROR: syntax: invalid type signature around REPL[24]:1
Stacktrace:
 [1] top-level scope at REPL[24]:1

(that is not a good error message, clearly)

Anyway, as mentioned above, removing most of the restrictions works:

julia> mutable struct Particle{M,T}
         x::MVector{M,T} # Current Position
         v::MVector{M,T} # Current Velocity
       end

julia> Particle(x::MVector{M,T}) where {M,T} = Particle(x,x)
Particle

julia> x = rand(MVector{3,Float64})
3-element MArray{Tuple{3},Float64,1,3} with indices SOneTo(3):
 0.2803399080247968
 0.11607884285749992
 0.6952047979171718

julia> Particle(x)
Particle{3,Float64}([0.2803399080247968, 0.11607884285749992, 0.6952047979171718], [0.2803399080247968, 0.11607884285749992, 0.6952047979171718])


And note that the final Particle is completely type-specific, as you want.

One additional thing: you may not need (want) mutable static arrays. You can work with immutable static arrays, and mutate the fields of your mutable structs.

julia> using StaticArrays

julia> mutable struct Particle{M,T}
         x::SVector{M,T} # Current Position
         v::SVector{M,T} # Current Velocity
       end

julia> p = Particle(rand(SVector{3,Float64}),rand(SVector{3,Float64}))
Particle{3,Float64}([0.9743653173834737, 0.3775182744632197, 0.37405647923023433], [0.5096490765009536, 0.530903496717793, 0.1187232054938756])

julia> y = SVector{3,Float64}(0., 0., 0.)
3-element SArray{Tuple{3},Float64,1,3} with indices SOneTo(3):
 0.0
 0.0
 0.0

julia> p.x = y
3-element SArray{Tuple{3},Float64,1,3} with indices SOneTo(3):
 0.0
 0.0
 0.0

julia> p
Particle{3,Float64}([0.0, 0.0, 0.0], [0.5096490765009536, 0.530903496717793, 0.1187232054938756])

(this mutation is allocation-free, and fast)

1 Like

Thank you for the quick reply!

Just removing the <: Integer type restriction still resulted in an error but removing both type restrictions as leandromartinez98 suggested solved my issues.

1 Like

This solves my issue. Thank you very much!

1 Like

Strange, in Julia 1.5.3 the below works for me:

julia> mutable struct Particle{M,T <: AbstractFloat}
       
           x::MVector{M,T} # Current Position
           v::MVector{M,T} # Current Velocity
           p::MVector{M,T} # Best Position
       
           fx::T # Current Objective Function Value
           fp::T # Best Objective Function Value
       
           function Particle(x::MVector{M,T}, v::MVector{M,T}) where {M, T <: AbstractFloat}
               xl = length(x)
               vl = length(v)
               if xl != vl
                   throw(ArgumentError("Particle's position and velocity must be the same length."))
               else
                   new{M,T}(x, v, NaN*similar(x), NaN*x[1], NaN*x[1])
               end
           end
       end

julia> a = MVector{3}(rand(3));

julia> b = MVector{3}(rand(3));

julia> Particle(a, b)
Particle{3,Float64}([0.32231386156690256, 0.41352687412221245, 0.924715838687213], [0.8347980122662781, 0.6946471924954924, 0.5798756616696423], [NaN, NaN, NaN], NaN, NaN)
2 Likes

Here it works as well as Henrique indicates.

Nevertheless, I think the general advice here would be to not put that <:AbstractFloat restriction. Even if somewhat artificial, your particle structure could be used to store particles that have only integer positions, for example. The restriction does not add anything in terms of performance, because the instances of Particle will be of the number types you define on construction anyway.

I think you can be even more permissive, with something like:

julia> mutable struct P{T,M}
         x :: T
         y :: T
         p :: T
         fx :: M
         fp :: M
       end

julia> P(x) = P(x,x,x,zero(eltype(x)),zero(eltype(x)))
P

julia> x = rand(MVector{3,Float64})
3-element MArray{Tuple{3},Float64,1,3} with indices SOneTo(3):
 0.6445027572690152
 0.8155124942309746
 0.2684031014000019

julia> P(x)
P{MArray{Tuple{3},Float64,1,3},Float64}([0.6445027572690152, 0.8155124942309746, 0.2684031014000019], [0.6445027572690152, 0.8155124942309746, 0.2684031014000019], [0.6445027572690152, 0.8155124942309746, 0.2684031014000019], 0.0, 0.0)

The instance of “particle” has now a type signature which is even more specific than before, and that can help the compiler further, probably (maybe that does not make any difference, but certainly it won’t be worse).

2 Likes

My apologies, I must have had a typo when implementing your suggestion Henrique. Nevertheless, thank you both for the great advice!

2 Likes