juliasimpsons-rule

Load Error when trying to pass complicated function into Simpson's rule


I have written a method that approximates a definite integral by the composite Simpson's rule.

#=
f  integrand
a  lower integration bound
b  upper integration bound
n  number of iterations or panels

h     step size
=#
function simpson(f::Function, a::Number, b::Number, n::Number)
   n % 2 == 0 || error("`n` must be even")
   h = (b - a) / n
   s = f(a) + f(b)
   s += 4*sum(f(a .+ collect(1:2:n) .* h))
   s += 2*sum(f(a .+ collect(2:2:n-1) .* h))
   return h/3 * s
end

For "simple" functions, like e^(-x^2), the simpson function works.

Input: simpson(x -> simpson(x -> exp.(-x.^2), 0, 5, 100)
Output: 0.8862269254513949

However, for the more complicated function f(x)

gArgs(x) = (30 .+ x, 0)
f(x) = exp.(-x.^2) .* maximum(generator.(gArgs.(x)...)[1])

where generator(θ, plotsol) is a function that takes in a defect θ in percent and a boolean value plotsol (either 0 or 1) that determines whether the generator should be plotted, and returns a vector with the magnetization in certain points in the generator.

When I try to compute the integral by running the below code

gArgs(x) = (30 .+ x, 0)
f(x) = exp.(-x.^2) .* maximum(generator.(gArgs.(x)...)[1])
println(simpson(x -> f(x), 0, 5, 10))

I encounter the error MethodError: no method matching generator(::Float64). With slight variants of the expression for f(x) I run into different errors like DimensionMismatch("array could not be broadcast to match destination") and InexactError: Bool(33.75). In the end, I think the cause of the error boils down to that I cannot figure out how to properly enter an expression for the integrand f(x). Could someone help me figure out how to enter f(x) correctly? Let me know if anything is unclear in my question.


Solution

  • Given an array x , gArgs.(x) returns an array of Tuples and you are trying to broadcast over an array of tuples. But the behavior of broadcasting with tuples is a bit different. Tuples are not treated as a single element and they themselves broadcast.

    julia> println.(gArgs.([0.5, 1.5, 2.5, 3.5, 4.5])...)
    30.531.532.533.534.5
    00000
    

    This is not what you expected, is it?

    You can also see the problem with the following example;

    julia> (2, 5) .!= [(2, 5)]
    2-element BitArray{1}:
     true
     true
    

    I believe f is a function that actually takes a scalar and returns a scalar. Instead of making f work on arrays, you should leave the broadcasting to the caller. You are very likely to be better of implementing f element-wise. This is the more Julia way of doing things and will make your job much easier.

    That said, I believe your implementation should work with the following modifications, if you do not have an error in generator.

    function simpson(f::Function, a::Number, b::Number, n::Number)
       n % 2 == 0 || error("`n` must be even")
       h = (b - a) / n
       s = f(a) + f(b)
       s += 4*sum(f.(a .+ collect(1:2:n) .* h)) # broadcast `f`
       s += 2*sum(f.(a .+ collect(2:2:n-1) .* h)) # broadcast `f`
       return h/3 * s
    end
    
    # define `gArg` and `f` element-wise and `generator`, too.
    gArgs(x) = (30 + x, 0) # get rid of broadcasting dot. Shouldn't `0` be `false`?
    f(x) = exp(-x^2) * maximum(generator(gArgs(x)...)[1]) # get rid of broadcasting dots
    println(simpson(f, 0, 5, 10)) # you can just write `f`
    

    You should also define the generator function element-wise.