Broadcasting across array of SVectors

I have an array of S-vectors and I want to do something like:

b .%5

and perform the mod across all elements of each vector. So I built this:

function Base.rem(x::SVector{Float64},y::Float64)::SVector{Float64}
SVector(x[1]%y,x[2]%y)
end

and it didn’t work. It wasn’t until I modified it to be like:

function Base.rem(x::SArray{Tuple{2},Float64,1,2},y::Float64)::SArray{Tuple{2},Float64,1,2}
SVector(x[1]%y,x[2]%y)
end

that it worked for me. So I guess my question is exposing my lack of understanding of types. When I declare a variable to be an SVector, I do it like this:

vec:: SVector{2,Float64}

so why is the type different for the function definition I tried above? The issue is more general than just “How do I mod over an array of SVectors?” because I got it working. I think I just need help understanding types in general. (Yes, I’ll re-read the documentation on that too.)

Any thoughts or ideas to share?

Secondary question: Could I define rem so that it would take an SVector of any length?

That’s not what what the SVector type signature looks like — it is parameterized by two numbers, the length of the vector and the type of the elements. Since your code only works for an SVector with two elements, you could do: x::SVector{2,Float64}, for example.

But you seem to be making it much more complicated than necessary. Why not just do:

myrem(x::SVector, y::Number) = x .% y

which will do the elementwise % that you want for any number of elements and any types? (I wouldn’t advise overloading Base.rem, since that’s type piracy.)

(Realize that argument-type declarations and return-type declarations do nothing for performance here. Even with no argument-type declarations at all, Julia will compile a specialized version of the function for any argument types you pass. And it will infer the return type on its own.)

4 Likes

Hmm. OK, after playing a little bit I can see that this:

function Base.rem(x::SArray{T},y::Float64)::SArray{T}  where {T}
SVector(map(x -> x%y,x))
end

works too and will work for vectors of any length. So, I think I’m confused about knowing how to declare a type correctly. How many arguments go inside the {} and how can I figure that out?

The number (and kinds) of type parameters depends on the type. You have to look at the documentation (or the source code if there are no docs).

For example, this type has no parameters:

struct Foo
    x::Float64
    y::Float64
end

This has one parameter:

struct Bar{T}
    x::T
    y::T
end

and this has two parameters:

struct Baz{T,S}
    x::T
    y::S
end

In the case of StaticArrays, SVector{n,T} is a simplified alias for SArray{Tuple{n},T,n}, where T is the element type and n is the number of elements.

3 Likes

So why does this work:

function Base.rem(x::SVector,y::Float64)::SVector
SVector(map(x -> x%y,x))
end

Shouldn’t I be required to specify the type and number of elements in the type declaration?

Noted. Thank you. I guess I was just hoping to use the % operator instead of a custom function. But I see your points

No. Any trailing type parameters that you omit can be anything.

x::SVector means that x can be SVector{n,T} for any n and T. x::SVector{2} means that x can be an SVector{2,T} for any T.

5 Likes

Got it. Thanks for taking the time to school a newb. Much appreciated.

So is this type piracy?

struct Squares
    count::Int
end

Base.iterate(S::Squares, state=1) = state > S.count ? nothing : (state*state, state+1)

If it is, what is the best way to build a custom iterator? If not, why not?

It’s only type piracy if you define iteration for types you don’t “own”. This example is fine because you own Squares. The previous one, was redefining an operation in Base for Structs defined in StaticArrays.

4 Likes

I think I got it. So it’s type piracy even though there didn’t exist an operation in Base with type StaticArrays.

1 Like

Yes, because the function (rem) is from Base and the type (SVector) is from StaticArrays. So, if you did write this method in a package and someone loaded it, or you loaded a third-party package, and such package relied either in rem failing for SVector, or rem calling a generic fallback that allowed for SVector (like a fallback for AbstractVector), the third-party package could start acting differently, what is considered spooky-action-at-a-distance and a bad practice.

4 Likes