@. macro for element-wise operations and array slicing


#1

Hello everybody,

I have a larger expression involving purely element-wise operations on vector elements. Using functions like abs(), log10() requires the use of dot-syntax in julia 0.6 unless one ignores the depreciation warning you get when using the program non-interactively, i.e. I will have to use for example
log10.(abs.(vector)). At this point I would like to use the @. macro instead of explicitely writing these dots .

According to https://julialang.org/blog/2017/01/moredots the @. macro should be equivalent to explicit dots. Only that it is not in connection with using length() as demonstrated in a brief made up example below ( I suspect this is a more general issue):

julia> abs.(x[1:Int64(round(length(x)/2.0))])
2-element Array{Float64,1}:
 1.0
 2.0
julia> @. abs(x[1:Int64(round(length(x)/2.0))])
ERROR: MethodError: no method matching colon(::Int64, ::Array{Int64,1})
Closest candidates are:
  colon(::T<:Real, ::Any, ::T<:Real) where T<:Real at range.jl:47
  colon(::A<:Real, ::Any, ::C<:Real) where {A<:Real, C<:Real} at range.jl:14
  colon(::T, ::Any, ::T) where T at range.jl:46
  ...

Naturally two questions arrive:

  1. Is this non-equivalence a feature, a bug, a missing feature or am I using it wrong ? If I am using it wrong how should I use it ?
  2. length(x)/2 is not automatically cast to integer as for example in matlab as far as I have observed. I apparently have to convert it explicitely with Int64(round(length(x)/2.0)) ? This looks as if I am doing something stupid. Is there a better way to do this ?

And no, I do not like to use throw-away-variables to do a

julia> lastindex=Int64(round(length(x)/2.0))
2

julia> @. abs(x[1:lastindex])
2-element Array{Float64,1}:
 1.0
 2.0

and clutter my code.

Thank you very much for your help in advance !

Best Regards

Christof


#2

Actually, the blog post is exactly right: adding @. in front of an expression is equivalent to changing all function calls from f(x) to f.(x). That’s actually what’s happening and it’s what’s causing your error. The reason is that you want length(x) to operate on the array x and return a scalar length. But by adding @. out front, you’ve turned that into length.(x) which returns an array consisting of the length of each element of x. That’s exactly what @. is supposed to do, but it’s just not what you want in this case. The error comes because what ends up being evaluated is 1:Int64.(round.(length.(x) ./ 2.0))) and Julia doesn’t have a method for a range from an integer to a vector.

Splitting out lastindex is a perfectly good solution (and has no performance penalty at all), as is just manually adding dots only where appropriate rather than using @.. Which one you pick is, of course, up to you.

As for (2), dividing two integers (or an integer and a float as you’re doing) always produces a float. Because of that, you can just divide by 2 and not 2.0 and you will still get the correct answer (unlike python2). Unlike Matlab, Julia doesn’t allow you to index into an array with a float, so yes, rounding and converting to Int is necessary. If you don’t like the nested function calls, you can also do round(Int, length(x) / 2)).


#3

The @. macro adds dots to all function calls in the expression. That is,

julia> @macroexpand @. abs(x[1:Int64(round(length(x)/2.0))])
:(abs.(x[1:Int64.(round.(/.(length.(x), 2.0)))]))

So if you want to use the @. macro, you’ll need to separate out the components that operate on the vector as a whole (like length here — you don’t want the length of every element in this case).

julia> n = Int64(round(length(x)/2.0))
       @macroexpand @. abs(x[1:n])
:(abs.(x[1:n]))

julia> @. abs(x[1:n])
2-element Array{Float64,1}:
 1.0
 2.0

As far as division, yes, you need to be explicit in how you want to convert the result to an index. In many cases, you can use the ÷ infix operator to do integer division, but that behaves differently from round — it truncates instead. You can also round directly to Int with round(Int, length(x)/2). Or similarly with ceil or floor. Unfortunately there’s not a “one true way” that everyone always wants, so we have chosen not to do this automatically.

(As an aside, in this particular instance, you can also use the special end keyword to represent the last index in the collection: @. abs(x[1:end÷2])).


#4

If you do @macroexpand on your expression, you find:

julia> @macroexpand @. a[1:round(Int64, length(a) / 2)]
:(a[1:round.(Int64, /.(length.(a), 2))])

So @. is also applied to length: the expression becomes a[1:[1, 1, 1, 1, 1]].

You might want to try instead

@. abs(x[1:round(Int64, end/2))

Also, round(Int64, x) is indeed necessary, since / promotes two integers to floating points (If I understand it right)


#5

Hello,

thank you very much for your answers !

Indeed I missed the emphasis on the word “every” which is in the online documentation of the macro.

Apparently this would work too without the need to introduce a throw-away-variable

2-element Array{Float64,1}:
 1.0
 4.0

which is of course simple once I understood the emphasis on “every” in the documentation. Not used to macros I guess, there must be of course limits on the complexity of transforms it can do.

With respect to the indexing:

round(Int64, end/2)

using “end” as a value is sure much nicer than what I have been doing. But I still will have to get used to it.

@mbauman: That would be the same as div(length(x),2) as fasr as I can see ? I am not sure how I would enter that infix operator, is that using UTF-8 I assume ?

Thank you very much again !

Best Regards

Christof


#6

Sorry, copy and paste , it should have been

julia> @. abs(x[1:Int64(round($length(x)/2.0))])^2
2-element Array{Float64,1}:
 1.0
 4.0

#7

Yes, exactly. You can ask Julia how to enter it by copy/pasting it into a ? help prompt:

help?> ÷
"÷" can be typed by \div<tab>

search: ÷ .÷

  div(x, y)
  ÷(x, y)

  The quotient from Euclidean division. Computes x/y, truncated to an integer.

#8

Hello,

that is of course nicely documented and might make code more readable.

I will probably stick to div(), though, as I would have to find out how to for example search for it in vim without copy&paste of the character (among other things). I remain a bit sceptical of using UTF-8 characters I do not have on my keyboard :slight_smile: Of course all of this has been discussed elsewhere, so I would like to just thank you again for your help.

Best Regards

Christof


#9

If you are using vim without juliavim you are missing out on, well, automatic latex to unicode. But that’s getting a bit off topic!