natemcintosh
natemcintosh

Reputation: 880

Variable defined outside a while loop not defined inside?

I'm attempting to write a Newton-Raphson solver in Julia. The Newton-Raphson method is shown in this image.

Newton-Raphson solver equation

f(x) = x^2.5 - 3x^1.5 - 10
fprime(x) = 2.5x^1.5 - 4.5x^0.5
x = zeros(1000)
x[1] = 10
δ = 1 # a relatively large number compared to what we want the error to be 
iter = 1
while δ > 1e-6
    x[iter + 1] = x[iter] - f(x[iter])/fprime(x[iter])
    iter += 1
    δ = abs(x[iter] - x[iter + 1])

    if iter == 100 
        break
    end
end
println("The solution is ")
show(x[iter])

However, when I run the code, I get an error saying iter is not defined, even though I defined it just before the start of the loop. Is there some scoping problem I'm completely missing?

ERROR: LoadError: UndefVarError: iter not defined
Stacktrace:
 [1] top-level scope at /Users/natemcintosh/Documents/Julia/Learning_julia.jl:11 [inlined]
 [2] top-level scope at ./none:0
 [3] include_string(::Module, ::String, ::String) at ./loading.jl:1002
 [4] (::getfield(Atom, Symbol("##120#125")){String,String,Module})() at /Users/natemcintosh/.julia/packages/Atom/Pab0Z/src/eval.jl:120
 [5] withpath(::getfield(Atom, Symbol("##120#125")){String,String,Module}, ::String) at /Users/natemcintosh/.julia/packages/CodeTools/8CjYJ/src/utils.jl:30
 [6] withpath at /Users/natemcintosh/.julia/packages/Atom/Pab0Z/src/eval.jl:46 [inlined]
 [7] #119 at /Users/natemcintosh/.julia/packages/Atom/Pab0Z/src/eval.jl:117 [inlined]
 [8] hideprompt(::getfield(Atom, Symbol("##119#124")){String,String,Module}) at /Users/natemcintosh/.julia/packages/Atom/Pab0Z/src/repl.jl:76
 [9] macro expansion at /Users/natemcintosh/.julia/packages/Atom/Pab0Z/src/eval.jl:116 [inlined]
 [10] (::getfield(Atom, Symbol("##118#123")){Dict{String,Any}})() at ./task.jl:85
in expression starting at /Users/natemcintosh/Documents/Julia/Learning_julia.jl:10

I've tried printing x at the beginning of the while loop and it knows what x is, but thinks iter is undefined.

Upvotes: 2

Views: 2622

Answers (1)

Bogumił Kamiński
Bogumił Kamiński

Reputation: 69949

First let me give the solution:

There are three possible approaches

Approach 1. Prepend global before iter += 1 and change it to global iter += 1 and all will work (note however the comment below about δ - because it will not work correctly unless you also prepend global before δ = abs(x[iter] - x[iter + 1]), i.e. the code will run but will produce wrong results - approaches 2 and 3 do not have this problem).

Approach 2. Wrap your code inside a function like this:

f(x) = x^2.5 - 3x^1.5 - 10
fprime(x) = 2.5x^1.5 - 4.5x^0.5

function sol(f, fprime)
    x = zeros(1000)
    x[1] = 10
    δ = 1 # a relatively large number compared to what we want the error to be 
    iter = 1
    while δ > 1e-6
        x[iter + 1] = x[iter] - f(x[iter])/fprime(x[iter])
        iter += 1
        δ = abs(x[iter] - x[iter + 1])

        if iter == 100 
            break
        end
    end
    println("The solution is ")
    show(x[iter])
end

sol(f, fprime) # now we call it

Solution 3. Wrap your code in a let block by changing line function sol(f, fprime) in solution 2 to simply say let (you do not need to call sol then).

Now the reason why you have to do it.

In Julia 1.0 while introduces a new scope. The scoping rules in Julia 1.0 are that each variable that is assigned to inside a while loop is considered a local variable (this has changed, because Julia 0.6 distinguished hard and soft local scope, in Julia 1.0 this distinction is gone - all local scopes are the same).

In your code you assign values to two variables: iter and δ inside the loop. This means that they are treated by Julia as local so you are not allowed to access their value before they have a value assigned inside the loop.

You want to read iter in line x[iter + 1] = x[iter] - f(x[iter])/fprime(x[iter]) but assign a value to it only in the following line.

As for δ the thing is more tricky. You assign a value to it, but it is used in a loop condition while δ > 1e-6. However, this condition operates on variables defined in outer scope (global in the original case). So all will work, but the condition while δ > 1e-6 will always see that δ is equal to 1 as it looks at the value of the variable outside of the loop. So this condition will never trigger (and you will always run 100 iterations). In summary the code that does what you want is (although if you did not fix δ assignment you would not get a warning):

f(x) = x^2.5 - 3x^1.5 - 10
fprime(x) = 2.5x^1.5 - 4.5x^0.5
x = zeros(1000)
x[1] = 10
δ = 1 # a relatively large number compared to what we want the error to be 
iter = 1
while δ > 1e-6
    x[iter + 1] = x[iter] - f(x[iter])/fprime(x[iter])
    global iter += 1
    global δ = abs(x[iter] - x[iter + 1])

    if iter == 100 
        break
    end
end
println("The solution is ")
show(x[iter])

Finally notice that the line x[iter + 1] = x[iter] - f(x[iter])/fprime(x[iter]) works fine even if there is an assignment in it, because you do not rebind variable x in it, but only change one element of an array (so x points to the same address in memory and Julia treats it as a global variable all the time).

Also you might want to read this https://docs.julialang.org/en/latest/manual/variables-and-scoping/ in the Julia manual or the answer to this question Julia Variable scope is similar

Upvotes: 3

Related Questions