# How to think about indices and cartesian coordinates?

Hello everyone!

I am new to Julia and I am writing a simple simulation that uses a cartesian grid and numerical derivatives. I would like to confirm if my thinking is correct.

I start by defining simple grids for X, Y and a scalar field f which in this case is just Y.

``````n = 3
X = ones(Float64, n) * collect(1:n)'
Y = collect(1:n) * ones(Float64, n)'
f = Y
``````

f is what I would expect:

``````3×3 Matrix{Float64}:
1.0  1.0  1.0
2.0  2.0  2.0
3.0  3.0  3.0
``````

That is, `f[3,1] #=> 3`

I also define a simple gradient function:

``````function ∇(q::Matrix{Float64})
∇q_x = zeros(n,n)
∇q_y = zeros(n,n)

for j in 2:n-1, i in 2:n-1
∇q_x[i,j] = (q[i,j+1] - q[i,j-1])/2
∇q_y[i,j] = (q[i+1,j] - q[i-1,j])/2
end

Dict(:x => ∇q_x, :y => ∇q_y)
end
``````

Is it correct to think of the first index (`i`) as the y direction, and the second index (`j`) as the x direction? That feels odd to me.
I understand it may just be a matter of convention, but when I try to visualize it, it does appear that Julia prefers it that way.

``````using Plots; gr()
heatmap(f, c=:blues)
quiver!(X, Y, quiver=(∇(f)[:x], ∇(f)[:y]), c=:white, linewidth=5)
``````

Any thoughts are greatly appreciated!

I think this is more about how the plotting library does the heatmap (not julia itself). For example, in CairoMakie,

``````fig,ax = heatmap(ff, colormap=:blues)
arrows!(ax,X[1,:], Y[:,1], ∇(f)[:x], ∇(f)[:y], color=:white, linewidth=5)
fig
``````

Produces this:

There is not a “correct” way for showing this, so that is why some libraries (even outside julia) have different approaches.

For Makie it has been discussed for example here:

As for your simulations, as long as you are consistent, there should not be a problem, I think!

(Edit: to show the CairoMakie also with arrows)

3 Likes

It is really neither correct nor incorrect, it is really up to you to choose a convention for yourself. Unfortunately.

The problem is that we are used to thinking of x as the first dimension and y as the second dimension, while in linear algebra the first dimension is ‘downwards’ and the second dimension is ‘across’.

Every time, I struggle with choosing a convention, and I personally believe everything would be much nicer if the first index always was the x-direction, and also indicated column (the opposite of today).

So, it’s up to you.

I urge you to not get into the habit of using `collect`. It is wasteful and pointless here. Forget you ever heard of that function. Remove it from all your codes, and don’t use until your code stops working without it.

4 Likes

To elaborate a little on @DNF’s good advice, in case it’s not clear:

``````julia> n = 3
3

julia> xs = 1:n
1:3

julia> ys = 1:n
1:3

julia> f = [y for y in ys, x in xs] # or any other function of (x,y)
3×3 Matrix{Int64}:
1  1  1
2  2  2
3  3  3
``````

You never need to explicitly form your `X` or `Y` matrices. You can either broadcast, or use a multidimensional array comprehension as above. Here’s an example of broadcasting:

``````julia> fun(x,y) = y
fun (generic function with 1 method)

julia> fun.(xs', ys)
3×3 Matrix{Int64}:
1  1  1
2  2  2
3  3  3
``````
2 Likes

Glad I’m not the only person who dislikes this function. I always had an uneasy feeling about it. IMO, most of what `collect` is used for should be functionality of the `Array` constructor.

To elaborate a little further: not only is using comprehensions more legible, but it’s also faster:

``````julia> using BenchmarkTools
a = @btime ones(Float64, 3) * collect(1:3)'
b = @btime Float64[j for i=1:3, j=1:3]
c = @btime ((x,y)->Float64(x)).((1:3)',1:3)
a == b == c
60.652 ns (3 allocations: 288 bytes)
16.917 ns (1 allocation: 128 bytes)
16.817 ns (1 allocation: 128 bytes)
true
``````
2 Likes

Great to know! That’s what I assumed but I wondered about the convention in the Julia community. Thank you for introducing me to CairoMakie.

I agree with you. For now I am inclined to adopt the convention where the first index is the y-coordinate since it is “downwards”, even if it feels a bit odd. In any case, it is great to know that I’m not alone in struggling to choose a convention.

Thank you for the great advice @DNF! And thank you @PeterSimon and @uniment for elaborating and providing examples. That was very helpful for better understand the problem with `collect`. I was able to shave a significant amount of time from my simulation by eliminating all the calls to `collect`!

1 Like