Execute a matrix of functions

I have defined a 3x3 matrix of functions (all cos(t)) as follows:

vij = SizedMatrix{3,3}([t->cos(t) for i in 1:3 for j in 1:3])

I’d like to execute the matrix for a particular value of t, say t=2.:

values = [vij[i,j](2.) for i in 1:3 for j in 1:3]

This works.

Is there a more idiomatic way to generate this matrix of values, for example something like:

vij(3.)

which I know does not work. I can obviously write a function to do this, but I am wondering if such an approach already exists in the Julia language.

Thanks!

It can be discussed how idiomatic it is, but this is one way to do it:

julia> vij = [t->cos(t) for i in 1:3, j in 1:3]
3×3 Matrix{var"#14#16"}:
 #14  #14  #14
 #14  #14  #14
 #14  #14  #14

julia> 3 .|> vij
3×3 Matrix{Float64}:
 -0.989992  -0.989992  -0.989992
 -0.989992  -0.989992  -0.989992
 -0.989992  -0.989992  -0.989992
1 Like

Cool! I like your solution. I like the pipe operator. I wonder if it is possible without pipes. I had tried, but did not succeed. Thanks!

Not as pretty as the pipe operator, but one could also do

map(f->f(3.0), vij)

can be simplified to just

vij = [cos for i in 1:3, j in 1:3]
2 Likes

Timings with @time:

Pipe operator: 0.00003 sec

@time 3. .|> vij;

map operator: 0.012

@time map(3., f -> f(3.))

Speed ratio: 400x

The solution should use vij.

I’m saying how you get vij can be simplified

1 Like

I think it’s a timing artefact, both should be equally fast. (But the pipe is in my opinion the nicer solution anyway :smiley: )

using BenchmarkTools
@btime 3.0 .|> $vij;            #  23.394 ns (1 allocation: 128 bytes)
@btime map(f -> f(3.0), $vij);  #  23.092 ns (1 allocation: 128 bytes)
4 Likes

Thanks. The use of $vij makes a big difference. I had not considered that.

The variance usually comes from other processes going on in your system unrelated to the thing you want to measure. That’s why BenchmarkTools reports the minimum time.

@time 3 .|> vij;
  0.000017 seconds (4 allocations: 208 bytes)

even if I execute the command multiple times.
How is it that @btime gives a single allocation?

@btime 3. .|> vij;
  374.595 ns (4 allocations: 208 bytes)
@btime map(f->f(3.), vij);
  121.870 ns (3 allocations: 160 bytes)

The distributions of the timing (e.g. output of @benchmark including the mean and median times per call) are essentially identical for both cases, so, it’s not just the minimal time which is the same. (Anyway, not so important. I don’t want to distract the main discussion.)


To reply to the last post. There is the interpolation $ used. The problem with timing is that one often calls in the global scope which introduces extra complications. (See Performance Tips · The Julia Language ) With the $ one can inject basically some variable from the global scope such that they behave like local scope variables. That is closer to the setting one usually has when a line of code appears inside a function call.

1 Like

For comparison:

@btime [$vij[i,j](3.) for i in 1:3, j in 1:3]

I learned a lot from all your answers. I simply do not use Julia enough to permanently change my way of thinking. Thanks!

1 Like

Maybe I missed it, but has:

julia> [f(3.0) for f in vij]
3×3 Matrix{Float64}:
 -0.989992  -0.989992  -0.989992
 -0.989992  -0.989992  -0.989992
 -0.989992  -0.989992  -0.989992

been suggested?
Except for performance, the clarity of the code should always be judged in code sections known not to be time critical.

4 Likes

I love this solution because it is the same regardless of the matrix structure!
Efficiency is the same as the other solutions.

@btime [f(3.0) for f in $vij]
  21.480 ns (1 allocation: 128 bytes)

Thank you.

1 Like