diff --git a/src/BUGSExamples/Volume_1/15_Bones.jl b/src/BUGSExamples/Volume_1/15_Bones.jl index 4d90a91d7..1c2831aee 100644 --- a/src/BUGSExamples/Volume_1/15_Bones.jl +++ b/src/BUGSExamples/Volume_1/15_Bones.jl @@ -79,7 +79,10 @@ data = ( 0.8021 2.3873 3.9525 5.3198 5.0022 6.3704 8.2832 10.4988 4.0168 5.1537 7.1053 10.3038], - delta = [2.9541 0.6603 0.7965 1.0495 5.7874 3.8376 0.6324 0.8272 0.6968 0.8747 0.8136 0.8246 0.6711 0.978 1.1528 1.6923 1.0331 0.5381 1.0688 8.1123 0.9974 1.2656 1.1802 1.368 1.5435 1.5006 1.6766 1.4297 3.385 3.3085 3.4007 2.0906 1.0954 1.5329], + delta = [2.9541, 0.6603, 0.7965, 1.0495, 5.7874, 3.8376, 0.6324, 0.8272, + 0.6968, 0.8747, 0.8136, 0.8246, 0.6711, 0.978, 1.1528, 1.6923, 1.0331, + 0.5381, 1.0688, 8.1123, 0.9974, 1.2656, 1.1802, 1.368, 1.5435, 1.5006, + 1.6766, 1.4297, 3.385, 3.3085, 3.4007, 2.0906, 1.0954, 1.5329], ncat = [2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 3, 3, 3, 3, 3, 3, 3, 3, 4, 5, 5, 5, 5, 5, 5], grade = [1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 2 1 1 1 1 1 1 1 1 2 1 1 2 1 1 diff --git a/src/JuliaBUGS.jl b/src/JuliaBUGS.jl index 34a6db38c..984bb4d9c 100644 --- a/src/JuliaBUGS.jl +++ b/src/JuliaBUGS.jl @@ -77,7 +77,7 @@ end function determine_array_sizes(model_def, data) pass = CollectVariables(model_def, data) - analyze_block(pass, model_def; warn_loop_bounds=true) + analyze_block(pass, model_def; warn_loop_bounds=Ref(true)) non_data_scalars, non_data_array_sizes = post_process(pass) return non_data_scalars, non_data_array_sizes end diff --git a/src/compiler_pass.jl b/src/compiler_pass.jl index 5fd6e9f8a..361976f30 100644 --- a/src/compiler_pass.jl +++ b/src/compiler_pass.jl @@ -7,7 +7,7 @@ function analyze_block( pass::CompilerPass, expr::Expr, loop_vars::NamedTuple=NamedTuple(); - warn_loop_bounds=false, + warn_loop_bounds::Ref{Bool}=Ref(false), ) if !Meta.isexpr(expr, :block) error("The top level expression must be a block.") @@ -21,13 +21,17 @@ function analyze_block( lb = Int(simple_arithmetic_eval(env, lb)) ub = Int(simple_arithmetic_eval(env, ub)) if lb > ub - if warn_loop_bounds + if warn_loop_bounds[] @warn "In BUGS, if the lower bound of for loop is greater than the upper bound, the loop will be skipped." + warn_loop_bounds[] = false end else for loop_var_value in lb:ub analyze_block( - pass, body, merge(loop_vars, (loop_var => loop_var_value,)) + pass, + body, + merge(loop_vars, (loop_var => loop_var_value,)); + warn_loop_bounds=warn_loop_bounds, ) end end @@ -85,6 +89,14 @@ function CollectVariables(model_def::Expr, data::NamedTuple{data_vars}) where {d end end + for (var, num_dim) in zip(arrays, num_dims) + if var ∈ data_vars && num_dim != ndims(data[var]) + error( + "Array variable $var has $num_dim dimensions in the model, but $(ndims(data[var])) dimensions in the data.", + ) + end + end + data_arrays = Symbol[] data_array_sizes = SVector[] for k in data_vars @@ -99,10 +111,12 @@ function CollectVariables(model_def::Expr, data::NamedTuple{data_vars}) where {d for (var, num_dim) in zip(arrays, num_dims) if var ∉ data_vars push!(non_data_arrays, var) - push!(non_data_array_sizes, MVector{num_dim}(fill(1, num_dim))) + push!(non_data_array_sizes, MVector{num_dim}(fill(0, num_dim))) end end + error_on_undeclared_variables(model_def, non_data_scalars, non_data_arrays) + return CollectVariables{data_vars}( data, Tuple(data_scalars), @@ -112,6 +126,28 @@ function CollectVariables(model_def::Expr, data::NamedTuple{data_vars}) where {d ) end +function error_on_undeclared_variables(model_def::Expr, non_data_scalars, non_data_arrays) + logical_scalars, stochastic_scalars, logical_arrays, stochastic_arrays = extract_variables_assigned_to( + model_def + ) + + for scalar in non_data_scalars + if !(scalar in union(logical_scalars, stochastic_scalars)) + error( + "Scalar variable $scalar is used in the model, but it is not defined in the model or data.", + ) + end + end + + for array in non_data_arrays + if !(array in union(logical_arrays, stochastic_arrays)) + error( + "Array variable $array is used in the model, but it is not defined in the model or data.", + ) + end + end +end + """ evaluate(expr, env) @@ -172,6 +208,13 @@ function evaluate(expr::Expr, env::NamedTuple{variable_names}) where {variable_n end if var in variable_names if all_resolved + _indices = map(i -> i === Colon() ? 0 : last(i), indices) # if index is `:`, then we skip the check + if any(_indices .> size(env[var])) + @show indices size(env[var]) + error( + "Variable $var[$(join(indices, ", "))] is used in the model, but it or its elements are not defined in the model or data.", + ) + end value = env[var][indices...] if is_resolved(value) return value @@ -546,9 +589,12 @@ function evaluate_and_track_dependencies(var::Expr, env) end value = nothing - if all(indices) do i - i isa Int || i isa UnitRange{Int} - end + if all(i -> i isa Int || i isa UnitRange{Int}, indices) + if any(last.(indices) .> size(env[v])) + error( + "$v[$(join(indices, ", "))] is used in the model, but it or its elements are not defined in the model or data.", + ) + end value = env[v][indices...] if is_resolved(value) return value, Tuple(dependencies) @@ -735,9 +781,7 @@ function analyze_statement(pass::AddVertices, expr::Expr, loop_vars::NamedTuple) ) else v, indices... = lhs - if any(indices) do i - i isa UnitRange - end + if any(i -> i isa UnitRange, indices) pass.vertex_id_tracker[v][indices...] .= code_for(pass.g, vn) else pass.vertex_id_tracker[v][indices...] = code_for(pass.g, vn) @@ -781,15 +825,27 @@ function analyze_statement(pass::AddEdges, expr::Expr, loop_vars::NamedTuple) for var in dependencies vertex_code = if var isa Symbol - pass.vertex_id_tracker[var] + _vertex_code = pass.vertex_id_tracker[var] + if _vertex_code isa AbstractArray + error("$(var) is an array, but referenced as a scalar at $expr") + end + [_vertex_code] else v, indices... = var - pass.vertex_id_tracker[v][indices...] + for idx in Iterators.product(indices...) + if iszero(pass.vertex_id_tracker[v][idx...]) && ismissing(pass.env[v][idx...]) + error( + "Variable $v[$(join(indices, ", "))] is referenced, but not defined, at $expr.", + ) + end + end + _vertex_code = pass.vertex_id_tracker[v][indices...] + if _vertex_code isa AbstractArray + filter(!iszero, _vertex_code) + else + iszero(_vertex_code) ? [] : [_vertex_code] + end end - - vertex_code = filter( - !iszero, vertex_code isa AbstractArray ? vertex_code : [vertex_code] - ) vertex_labels = [label_for(pass.g, code) for code in vertex_code] for r in vertex_labels if r != lhs_vn diff --git a/test/compile.jl b/test/compile.jl index b484c5470..1740e7e85 100644 --- a/test/compile.jl +++ b/test/compile.jl @@ -1,26 +1,69 @@ @testset "compile corner cases" begin - # test variables exist on the left hand side of the both kinds of assignment - let ex = @bugs begin - a ~ Normal(0, 1) - b = a - b ~ Normal(0, 1) + @testset "test variables exist on the left hand side of the both kinds of assignment" begin + @testset "not transformed variable, so error" begin + ex = @bugs begin + a ~ Normal(0, 1) + b = a + b ~ Normal(0, 1) + end + @test_throws ErrorException compile(ex, (;), (;)) + end + + @testset "transformed variable, so no error" begin + ex = @bugs begin + a ~ Normal(0, 1) + b = a + b ~ Normal(0, 1) + end + compile(ex, (; a=1), (;)) end - @test_throws ErrorException compile(ex, (;), (;)) end - let ex = @bugs begin - a ~ Normal(0, 1) - b = a - b ~ Normal(0, 1) + @testset "assign array variable to another array variable" begin + model = compile( + (@bugs begin + b[1:2] ~ dmnorm(μ[:], σ[:, :]) + a[1:2] = b[:] + end), (; μ=[0, 1], σ=[1 0; 0 1]), (;) + ) + end +end + +@testset "error messages on undeclared variables" begin + @testset "same variable names refer to both scalar and array" begin + model_def = @bugs begin + x[1] ~ dnorm(0, 1) + y ~ dnorm(x, 1) end - compile(ex, (; a=1), (;)) + @test_throws ErrorException compile(model_def, (;)) end - # assign array variable to another array variable - model = compile((@bugs begin - b[1:2] ~ dmnorm(μ[:], σ[:, :]) - a[1:2] = b[:] - end), (; μ=[0, 1], σ=[1 0; 0 1]), (;)) + @testset "undeclared scalar variable" begin + model_def = @bugs begin + x[1] ~ dnorm(0, 1) + y ~ dnorm(x[1], z) + end + @test_throws ErrorException compile(model_def, (;)) + end + @testset "undeclared array variable" begin + model_def = @bugs begin + x[1] ~ dnorm(0, 1) + y ~ dnorm(0, x[2]) + end + @test_throws ErrorException compile(model_def, (;)) + + model_def = @bugs begin + x[2] ~ dnorm(0, 1) + y ~ dnorm(x[1], 0) + end + @test_throws ErrorException compile(model_def, (;)) + + model = @bugs begin + x = sum(y[1:2]) + y[1] ~ dnorm(0, 1) + end + @test_throws ErrorException compile(model, (;)) + end end @testset "initialize!" begin diff --git a/test/passes.jl b/test/passes.jl index c5a01a7a6..e26431abd 100644 --- a/test/passes.jl +++ b/test/passes.jl @@ -106,53 +106,57 @@ end @testset "error cases" begin data = (;) - model_def = @bugs begin - a = 1 - a = 2 + @testset "repeated scalar logical assignment" begin + model_def = @bugs begin + a = 1 + a = 2 + end + @test_throws ErrorException JuliaBUGS.determine_array_sizes(model_def, data) end - scalars, array_sizes = JuliaBUGS.determine_array_sizes(model_def, data) - @test_throws ErrorException JuliaBUGS.check_repeated_assignments( - model_def, data, array_sizes - ) - model_def = @bugs begin - a ~ Normal(0, 1) - a ~ Normal(0, 2) + @testset "repeated scalar stochastic assignment" begin + model_def = @bugs begin + a ~ Normal(0, 1) + a ~ Normal(0, 2) + end + @test_throws ErrorException JuliaBUGS.determine_array_sizes(model_def, data) end - scalars, array_sizes = JuliaBUGS.determine_array_sizes(model_def, data) - @test_throws ErrorException JuliaBUGS.check_repeated_assignments( - model_def, data, array_sizes - ) - model_def = @bugs begin - x[1] = 1 - x[1] = 2 + @testset "repeated array element assignment" begin + model_def = @bugs begin + x[1] = 1 + x[1] = 2 + end + scalars, array_sizes = JuliaBUGS.determine_array_sizes(model_def, data) + @test_throws ErrorException JuliaBUGS.check_repeated_assignments( + model_def, data, array_sizes + ) end - scalars, array_sizes = JuliaBUGS.determine_array_sizes(model_def, data) - @test_throws ErrorException JuliaBUGS.check_repeated_assignments( - model_def, data, array_sizes - ) - - model_def = @bugs begin - x[1] = 1 - for i in 1:2 - x[i:(i + 1)] = i + + @testset "repeated array assignment with range" begin + model_def = @bugs begin + x[1] = 1 + for i in 1:2 + x[i:(i + 1)] = i + end end + scalars, array_sizes = JuliaBUGS.determine_array_sizes(model_def, data) + @test_throws ErrorException JuliaBUGS.check_repeated_assignments( + model_def, data, array_sizes + ) end - scalars, array_sizes = JuliaBUGS.determine_array_sizes(model_def, data) - @test_throws ErrorException JuliaBUGS.check_repeated_assignments( - model_def, data, array_sizes - ) - model_def = @bugs begin - x[1] ~ Normal(0, 1) - x[1:2] ~ MvNormal(a[:], b[:, :]) + @testset "repeated array assignment with range" begin + model_def = @bugs begin + x[1] ~ Normal(0, 1) + x[1:2] ~ MvNormal(a[:], b[:, :]) + end + data = (a=[1, 2], b=[1 0; 0 1]) + scalars, array_sizes = JuliaBUGS.determine_array_sizes(model_def, data) + @test_throws ErrorException JuliaBUGS.check_repeated_assignments( + model_def, data, array_sizes + ) end - data = (a=[1, 2], b=[1 0; 0 1]) - scalars, array_sizes = JuliaBUGS.determine_array_sizes(model_def, data) - @test_throws ErrorException JuliaBUGS.check_repeated_assignments( - model_def, data, array_sizes - ) end end