Apply function to array in Julia

Dear all,
I am reading a book on how to create a procedure for solving integrants using python. It is said to create an array and apply the function over an array in order to speed up the procedure. The code provided is:

from numpy import linspace, sum

def midpoint(f, a, b, n):
  h = float(b-a)/n
  x = linspace(a+h/2, b-h/2, n)
  return h*sum(f(x))

where f is the function to be integrated between the pioints a and b, and n is the number of iteractions.
I converted this in Julia with:

function midpoint(f, a, b, n)
    h = (b-a)/n
    res = 0
    x = collect(range(z+h/2,stop=b-h/2,length=n) )
    res = sum(f(x))
    return res
end

x has been correctly generated: I set a=0, b=1, n=100 and:

julia> x
100-element Array{Float64,1}:
 0.005
 0.015
 0.025
 0.035
 â‹®
 0.965
 0.975
 0.985
 0.995

and I assigned a function to f:

julia> f=x->x^3+x^2
#11 (generic function with 1 method)

But when I try the apply it:

julia> res = sum(f(x))
ERROR: MethodError: no method matching ^(::Array{Float64,1}, ::Int64)
Closest candidates are:
  ^(::Float16, ::Integer) at math.jl:795
  ^(::Missing, ::Integer) at missing.jl:120
  ^(::Missing, ::Number) at missing.jl:93
  ...
Stacktrace:
 [1] macro expansion at ./none:0 [inlined]
 [2] literal_pow at ./none:0 [inlined]
 [3] (::getfield(Main, Symbol("##13#14")))(::Array{Float64,1}) at ./none:1
 [4] top-level scope at none:0

What am I missing? how can I apply a function to all elements of an array directly?

Thank you

try sum(f,x) or sum(f.(x))

3 Likes

Thank you!

You want to broadcast f over the array x, so you’re looking for f.(x). Better still, you can avoid allocations by passing f directly to sum, i.e. sum(f, x). You might also want to take a look at https://docs.julialang.org/en/v1/manual/arrays/index.html#Array-and-Vectorized-Operators-and-Functions-1. Also note that the collect in the function above is unnecessary in most cases and you will get better performance by not collecting x into an array.

but if I don’t use collect I don’t get an array:

julia> range(Start+h/2,stop=End-h/2,length=Iter)
0.005:0.01:0.995
julia> collect(range(Start+h/2,stop=End-h/2,length=Iter))
100-element Array{Float64,1}:
 0.005
 0.015
 0.025
 0.035
 â‹®
 0.965
 0.975
 0.985
 0.995

Incidentally, there is no reason to pre-assign res = 0, or even to name it as a variable. Just sum(f, x) in the last line would be fine.

Why do you want an array specifically? The range contains the same information, only more efficiently.

2 Likes

A range is an array, it just isn’t an Array. Specifically, it’s a StepRangeLen which is a type of AbstractArray.

You should definitely not use collect here, in fact, you should almost never collect ranges. The whole point of ranges is that they are smarter and faster and lighter than a full Array. Using collect just ruins that.

You should write

x = range(z+h/2, b-h/2, length=n)
res = sum(f, x)

Not sum(f.(x)). This is the best and fastest way.

3 Likes

I will be happy NOT to use collect, but so why the difference in output that I have shown?

0.005:0.01:0.995

vs

100-element Array{Float64,1}:

In fact, following the manual:

julia> x = range(1, stop=100, step=10)
1:10:91

julia> println(x)
1:10:91

julia> x = collect(range(1, stop=100, step=10))
10-element Array{Int64,1}:
  1
 11
 21
 31
 41
 51
 61
 71
 81
 91

julia> println(x)
[1, 11, 21, 31, 41, 51, 61, 71, 81, 91]

And the manual also added:

If you really want it in array form, you can use the range object to build an array:

julia> collect(range(1, length=12, stop=100))

I don’t need an Array, and I can see that range is an array:

julia> x = range(1, stop=100, step=10)
1:10:91

julia> typeof(x)
StepRange{Int64,Int64}

but I do I use range then? for iteractions, for instance:

julia> for i in x

           println(x)

       end
1:10:91
1:10:91
1:10:91
1:10:91
1:10:91
1:10:91
1:10:91
1:10:91
1:10:91
1:10:91

Well, of course they print differently, otherwise how could you tell that one is a smart compact array, and the other a dumb big one? :slight_smile:

Presumably, you mean

for i in x 
    println(i)
end

?

BTW, for integer ranges it is more convenient to write 1:10:100 rather than range(1, stop=100, step=10).

2 Likes

The StepRange prints differently than an Array, because it is a different kind of object, but it does everything almost everything an Array can do, for instance indexing,

julia> x[3]
21

julia> collect(x)[3]
21

scalar multiplication,

julia> 3.14159 * x
3.14159:31.4159:285.88469 # note the result is another StepRange

and matrix-array multiplication:

julia> M = randn(10, 10);
julia> M * x # "just works"

Edit But not mutation:

julia> x[3] = 22
ERROR: setindex! not defined for StepRange{Int64,Int64}

because StepRanges are immutable, and, more fundamentally, because the value at x[3] isn’t actually stored anywhere: it’s calculated on-demand when you ask for it.

1 Like

Ops! I see what you mean.

Except being mutated

1 Like