ForwardDiff saving unwanted variable

Hi everyone,

I am migrating from Matlab to Julia and I have noticed some weird (perhaps unwanted?) behavior of the ForwardDiff package.

using ForwardDiff
function eq()
    
    function qfun(x)
        q = x[1] + x[2]
        return q
    end
   
    q = qfun([1.0, 2.0])
    
    derq(x) = ForwardDiff.gradient(qfun,x)
    dq = derq([1.0, 2.0])
    
    return q
end

a = eq()

This does not return 3.0 but instead:

Dual{ForwardDiff.Tag{var"#qfun#17",Float64}}(3.0,1.0,1.0)

However, if I change the name of q inside qfun I get what I expected (3.0 as output):

using ForwardDiff
function eq()
    
    function qfun(x)
        q2 = x[1] + x[2]
        return q2
    end
   
    q = qfun([1.0, 2.0])
    
    derq(x) = ForwardDiff.gradient(qfun,x)
    dq = derq([1.0, 2.0])
    
    return q
end

a = eq()
3.0

Also, if I run this code outside a function, I get an output of 3.0 to q:

using ForwardDiff
function qfun(x)
    q = x[1] + x[2]
    return q
end
   
q = qfun([1.0, 2.0])
    
derq(x) = ForwardDiff.gradient(qfun,x)
dq = derq([1.0, 2.0])
    
q
3.0

What is going on? Is this a bug or am I missing something from some expected behavior of ForwardDiff? It sounds super weird to me that the label used inside a function can affect the final output.

Best,

Caio

This isn’t a bug, and it doesn’t actually have anything to do with ForwardDiff. Here’s a simpler example of the same behavior:

julia> function outer()
         function inner()
           x = 1
         end
         
         x = 2
         inner()
         return x
       end
outer (generic function with 1 method)

julia> outer()
1

The call to inner() sets x = 1, and the x inside inner() is the same one in outer().

You’re seeing this happen because ForwardDiff.gradient(qfun, ...) results in qfun being called, which has the side-effect of modifying the local variable q.

You are correct that this doesn’t happen at global scope:

julia> function inner()
         x = 1
       end
inner (generic function with 1 method)

julia> x = 2
2

julia> inner()
1

julia> x
2

Global scope is a bit different in Julia–if you want to re-assign a global variable from within a function or a loop, you need to be explicit and use the global keyword:

julia> function inner()
         global x
         x = 1
       end
inner (generic function with 1 method)                                                                                                                                                                                                                                                                                                
                                                                                                                                                                                                                                                                                                                                      
julia> x = 2
2

julia> inner()
1

julia> x
1
3 Likes

After checking the FAQ about the scope of variables and your answer I think I got it, thanks.

In fact, I would not like the variables to be reassigned when I call ForwarDiff to compute the derivatives (this is the only reason why qfun needs to be defined in my code). One option is to rename “q”, but this is costly, since in my actual code there are many variables being reassigned, and they appear many times in a formula. The best solution I could find was declare “q” local inside the function, which gives 3.0 as output.

using ForwardDiff
function eq()
    
    function qfun(x)
        local q
        q = x[1] + x[2]
        return q
    end
   
    q = qfun([1.0, 2.0])
    
    derq(x) = ForwardDiff.gradient(qfun,x)
    dq = derq([1.0, 2.0])
    
    return q
end

a = eq()

Does qfun actually have to live inside eq()? If not, then this problem is easy to avoid: you could simply do:

using ForwardDiff

function qfun(x)
    q = x[1] + x[2]
    return q
end

function eq()
    q = qfun([1.0, 2.0])
    
    derq(x) = ForwardDiff.gradient(qfun,x)
    dq = derq([1.0, 2.0])
    
    return q
end

a = eq()

In my application it should be inside, since the inner function changes depending on some arguments of the outer function (which is not implied in my minimal working example). Otherwise what you propose would work too.