UndefVarError: θ not defined

Hi,

I stumbled upon this very nice example from @SebastianCallh in which Neural ODE are used to model and forecast the weather in the city of Delhi. I have a keen interest in learning how to apply differential equations using Julia, so I thought I could give it a try and adapt this code to work on my data.

Took some time to understand the basics of Julia (first timer coming from Python!), but in the end I managed to use @SebastianCallh example to put everything together as follows:

using Dates
using Plots
using Plotly
using DiffEqFlux
using Statistics
using DataFrames, CSV
using OrdinaryDiffEq, Flux, Random

function neural_ode(t, data_dim; saveat = t)
    f = FastChain(FastDense(data_dim, 64, swish),
		      FastDense(64, 32, swish),
		      FastDense(32, data_dim))

    node = NeuralODE(f, (minimum(t), maximum(t)), Tsit5(),
			 saveat = saveat, abstol = 1e-9,
			 reltol = 1e-9)
end

function train(node, θ, y, opt, maxiters, y0 = y[:, 1]; kwargs...)
	predict(θ) = Array(node(y0, θ))
	loss(θ) = begin
	    ŷ = predict(θ)
	    Flux.mse(ŷ, y)
    end

    θ = θ == nothing ? node.p : θ
    res = DiffEqFlux.sciml_train(
	    loss, θ, opt,
	    maxiters = maxiters;
	    kwargs...
    )
    return res.minimizer
end

log_results(θs, losses) =
    (θ, loss) -> begin
	    push!(θs, copy(θ))
	    push!(losses, loss)
	    false
    end

################
# Main program #
################

# Loading data and setting up metadata

meta_cols = ["rowid", "SiteId", "datetime", "longitude", "latitude"]
data_cols = ["datetime", "DryBulbTemperature_Celsius"]

# Loading 168 records of hourly temperature for a given week in July 2018
# Note: Contains 'missing' values that are casted to NaN and then ignored with filter(!isnan, )
path_in = "PATH/week_of_hourly_temperature_records.csv"

df = DataFrame!(CSV.read(path_in))[data_cols]
df["DryBulbTemperature_Celsius"] = replace(df["DryBulbTemperature_Celsius"], missing=>NaN)

# Normalizing and splitting on train/test
T = 72 # first 3 days for training, remaining 4 days for testing
mean_temp = mean(filter(!isnan, df["DryBulbTemperature_Celsius"])) # taking care of NaNs
stdev_temp = std(filter(!isnan, df["DryBulbTemperature_Celsius"]))

y = Matrix(df[:, ["DryBulbTemperature_Celsius"]] |>
    y -> Matrix(y)' |>
    y -> (y .- mean_temp) ./ stdev_temp)

train_dates, test_dates = df["datetime"][1:T], df["datetime"][T:end]
vals_train_norm, vals_test_norm = y[1:T], y[T:end]

# Starting with the analysis
Random.seed!(1);
maxiters = 150
θs, losses = [], []
θ = nothing
num_obs = 4:4:length(vals_train_norm)

for k in num_obs
    chunk = filter(!isnan, vals_train_norm[1:k])
    node = neural_ode(chunk, size(y, 1))

    θ = train(
	    node, θ, chunk,
	    ADAMW(8e-3), maxiters;
	    cb = log_results(θs, losses)
    )

end

However, I found this persistent error and I don’t know how to fix it:

ERROR: LoadError: **UndefVarError: θ not defined**
Stacktrace:
 [1] top-level scope at /PATH/JuliaProjects/basics_of/attempt.jl:87
in expression starting at /PATH/JuliaProjects/basics_of/attempt.jl:78

Because this θ is first set to “nothing” and it seems that will become iteratively calculated/updated. So it seems it makes sense to set up the variable with and empty value and let it change. I printed “node” and “f” and they contain values, so something starts happening, but then it crashes. I even changed the Greek symbol to “theta” just in case there was some problem there :sweat_smile:, but nothing.

Please, could you let me know what is missing in this code? How can I “define” theta properly? Perhaps an extra library import is required? Maybe the author can share the version creating these nice plots as well? :slight_smile:

Thanks for your help!

You need a global θ declaration in this loop, since the loop is in global scope. (It wouldn’t show up if you were writing your loop in a function.)

See https://docs.julialang.org/en/v1.3-dev/manual/variables-and-scoping/#Local-Scope-1

This is the infamous global-scope rule (https://github.com/JuliaLang/julia/issues/28789); the error message should be clearer in Julia 1.5.

3 Likes

This is due to Julia’s scoping rules, which regularly trip up beginners. Variables defined in global scope (in this case, θ) aren’t visible within for loops unless they’re explicitly annotated as global variables. See here for further discussion.

A quick example:

julia>  θ = 0
0

julia> for i = 1:10
         θ += 1
       end
ERROR: UndefVarError: θ not defined
Stacktrace:
 [1] top-level scope at .\REPL[8]:2
julia> for i = 1:10
         global θ += 1
       end

julia> θ
10
2 Likes

@Irene I’m glad you found it useful, and welcome to Julia! :slight_smile: @stevengj and @stillyslalom are absolutely right. The code you see in my blog post is inside a function which has different scoping rules. So it is totally fine to initialize a variable to nothing, but in this case the variable itself is not “seen” from inside the loop.

2 Likes

Well, this certainly did the trick! Thanks to all of your for your super fast response, much appreciated :slight_smile: Indeed, I could not imagine it was a scope problem, but now that I know (and read) about that, I might spot this issue sooner the next time. Thanks!

1 Like

Actually, only if the error could add the substring “in this scope” would probably be sufficient and/or informative for the user. As in "UndefVarError: θ not defined *in this scope*". Step by step, thanks for your efforts! :slightly_smiling_face:

2 Likes

That change is a good suggestion, but this has already been changed more significantly in the upcoming 1.5 release. This works heuristically in the REPL and errors more clearly in a file. In the REPL:

julia> θ = 0
0

julia> for i = 1:10
           θ += 1
       end

julia> θ
10

In a file:

┌ Warning: Assignment to `θ` in soft scope is ambiguous because
| a global variable by the same name exists: `θ` will be treated
| as a new local. Disambiguate by using `local θ` to suppress this
| warning or `global θ` to assign to the existing global variable.
└ @ string:5
ERROR: LoadError: UndefVarError: θ not defined

Hopefully that’s clear enough that it would have helped you figure it out.

3 Likes

Well this is a fantastic error message! :clap: :clap: :clap:

1 Like