Skip to content

Commit 1c07e00

Browse files
committed
Cleanup, add a hash suffix to the global variables to reduce likelihood of naming conflicts
1 parent 2c071bd commit 1c07e00

1 file changed

Lines changed: 50 additions & 77 deletions

File tree

test/test-mapping.jl

Lines changed: 50 additions & 77 deletions
Original file line numberDiff line numberDiff line change
@@ -62,21 +62,22 @@ end
6262
###########################
6363

6464
# This approach feels brittle but works. An improvement would be to directly fiddle with the AST, but it's a little more involved
65+
# Another approach might be to generate a string to be included with include_string, that might avoid awkward global variables and world age problems
66+
67+
# There will still be brittleness given that it's not trivial to handle user/modeler errors :
68+
# For instance, providing a vector that is called in a scale mapping is likely to cause things to go badly
6569

6670
# Currently untested in 'real' multi-scale modes, or with complex configs (hard dependencies).
6771
# Need to place the simple timestep models in PlantSimEngine, and probably provide more complex ones at some point
68-
# and work out how to reset generated_models which is unfortunately in global scope.
69-
# Might also need more work on parameter initialisation.
70-
# Variable name conflicts are currently a liability.
72+
73+
# Variable name conflicts are currently a minor liability with the globals.
74+
7175
# And then need to insert it at the graph sim generation level, and modify tests to consistently do modellist <-> mapping conversions
7276
# And then implement tests with proper output filtering
7377

74-
# Ah, another point that remains to be seen is that those SentinelArrays the TT_cu returns as from the CSV isn't an AbstractVector
78+
# Ah, another point that remains to be seen is that those CSV.SentinelArrays.ChainedVector obtained from the meteo file isn't an AbstractVector
7579
# meaning currently we won't generate models from them unless the conversion is made before that
76-
77-
# And another issue : the first call to the mapping conversion appears to fail with process() not being defined on TT_cu model from the vector
78-
# More accurately : ERROR: process() is not defined for AbstractTt_CuModel
79-
# It does NOT happen when I run through it with the debugger, not sure what is going on here, I thought it might be due to the macro needing to be executed
80+
# So another minor potential improvement would be to return a warning to the user and do the conversion when generating the model
8081

8182
function compare_outputs_modellist_mapping(models, graphsim)
8283
graphsim_df = outputs(graphsim, DataFrame)
@@ -100,7 +101,7 @@ function compare_outputs_graphsim(graphsim, graphsim2)
100101
end
101102

102103
# simple conversion to a mapping, with manually written models
103-
function modellist_to_mapping(modellist_original::ModelList, modellist_status, nsteps; check=true, outputs=nothing, TT_cu_vec=Vector{Float64}())
104+
function modellist_to_mapping_manual(modellist_original::ModelList, modellist_status, nsteps; check=true, outputs=nothing, TT_cu_vec=Vector{Float64}())
104105

105106
modellist = Base.copy(modellist_original, modellist_original.status)
106107

@@ -143,16 +144,6 @@ function modellist_to_mapping(modellist_original::ModelList, modellist_status, n
143144
# TODO sanity check
144145
end
145146

146-
#=run!(mtg, mapping,
147-
meteo_day,
148-
constants=PlantMeteo.Constants(),
149-
extra=nothing,
150-
nsteps = nsteps,
151-
outputs = all_vars,
152-
check=true,
153-
executor=ThreadedEx()
154-
)=#
155-
156147
sim = PlantSimEngine.GraphSimulation(mtg, mapping, nsteps=nsteps, check=check, outputs=Dict(default_scale => all_vars))
157148
return sim
158149
end
@@ -165,7 +156,6 @@ end
165156
PlantSimEngine.inputs_(::ToyCurrenttimestepModel) = (next_timestep=1,)
166157
PlantSimEngine.outputs_(m::ToyCurrenttimestepModel) = (current_timestep=1,)
167158

168-
# Implementing the actual algorithm by adding a method to the run! function for our model:
169159
function PlantSimEngine.run!(m::ToyCurrenttimestepModel, models, status, meteo, constants=nothing, extra=nothing)
170160
status.current_timestep = status.next_timestep
171161
end
@@ -179,7 +169,6 @@ function PlantSimEngine.run!(m::ToyCurrenttimestepModel, models, status, meteo,
179169
PlantSimEngine.inputs_(::ToyNexttimestepModel) = (current_timestep=1,)
180170
PlantSimEngine.outputs_(m::ToyNexttimestepModel) = (next_timestep=1,)
181171

182-
# Implementing the actual algorithm by adding a method to the run! function for our model:
183172
function PlantSimEngine.run!(m::ToyNexttimestepModel, models, status, meteo, constants=nothing, extra=nothing)
184173
status.next_timestep = status.current_timestep + 1
185174
end
@@ -193,23 +182,21 @@ struct ToyTestDegreeDaysCumulModel <: AbstractDegreedaysModel
193182
TT_cu_vec::Vector{Float64}
194183
end
195184

196-
# Defining the inputs and outputs of the model:
197185
PlantSimEngine.inputs_(::ToyTestDegreeDaysCumulModel) = (current_timestep=1,)
198186
PlantSimEngine.outputs_(::ToyTestDegreeDaysCumulModel) = (TT_cu=0.0,)
199187

200188
ToyTestDegreeDaysCumulModel(; TT_cu_vec = Vector{Float64}()) = ToyTestDegreeDaysCumulModel(TT_cu_vec)
201189

202190

203-
# Implementing the actual algorithm by adding a method to the run! function for our model:
204191
function PlantSimEngine.run!(m::ToyTestDegreeDaysCumulModel, models, status, meteo, constants=nothing, extra=nothing)
205192
status.TT_cu = m.TT_cu_vec[status.current_timestep]
206193
end
207194

208195
PlantSimEngine.ObjectDependencyTrait(::Type{<:ToyTestDegreeDaysCumulModel}) = PlantSimEngine.IsObjectDependent()
209196

210197
# TODO should the new_status be copied ?
211-
# User specifies at which level they want the basic timestep model to be inserted at
212-
function replace_mapping_status_vectors_with_generated_models(mapping_with_vectors_in_status, timestep_model_organ_level)
198+
# Note : User specifies at which level they want the basic timestep model to be inserted at
199+
function replace_mapping_status_vectors_with_generated_models(mapping_with_vectors_in_status, timestep_model_organ_level, nsteps)
213200

214201
mapping = Dict(organ => models for (organ, models) in mapping_with_vectors_in_status)
215202
for (organ,models) in mapping
@@ -242,34 +229,32 @@ function replace_mapping_status_vectors_with_generated_models(mapping_with_vecto
242229
return mapping
243230
end
244231

245-
# TODO this being in global scope (due to the way eval() works) is awkward
246-
# need to reset it between runs, and/or avoid overwriting it with the same models
247-
# or find a way to keep it local
248-
generated_models = ()
249-
250-
# Might need to fiddle with timesteps in the future
251232

252233
import SHA: sha1
253234

254-
function generate_model_from_status_vector_variable(mapping, timestep_scale, status, organ)
255-
256-
global generated_models = ()
257-
global new_status = NamedTuple()
258-
#var = keys(st)[1]
259-
#var_vals = values(st)[1]
260-
#print(typeof(status))
235+
# Note : eval works in global scope, and state synchronisation doesn't occur until one returns to top-level
236+
# This is to enable optimisations. See 'world-age problem'. The doc for eval currently isn't detailed enough.
237+
# Essentially, generating a struct with a process_ method and then immediately creating a simulation graph
238+
# that calls process_ will fail as it won't yet be defined since state hasn't synchronised.
239+
# Returning a new mapping to top-level and *then* creating the graph will work.
240+
# The fact that eval works in global scope is also why we make use of some global variables here
241+
function generate_model_from_status_vector_variable(mapping, timestep_scale, status, organ, nsteps)
242+
243+
# Note : 534f1c161f91bb346feba1a84a55e8251f5ad446 is a prefix to reduce likelihood of global variable name conflicts
244+
# it is the hash generated by bytes2hex(sha1("PlantSimEngine_prototype"))
245+
global generated_models_534f1c161f91bb346feba1a84a55e8251f5ad446 = ()
246+
global new_status_534f1c161f91bb346feba1a84a55e8251f5ad446 = NamedTuple()
247+
261248
for symbol in keys(status)
262-
global value = getproperty(status, symbol)
263-
if isa(value, AbstractVector)
264-
# TODO assert length matches with timestep count
265-
@assert length(value) > 0 "Error during generation of models from vector values provided at the $organ-level status : provided $symbol vector is empty"
266-
var_type = eltype(value)
267-
base_name = string(symbol) * bytes2hex(sha1(join(value)))
249+
global value_534f1c161f91bb346feba1a84a55e8251f5ad446 = getproperty(status, symbol)
250+
if isa(value_534f1c161f91bb346feba1a84a55e8251f5ad446, AbstractVector)
251+
@assert length(value_534f1c161f91bb346feba1a84a55e8251f5ad446) > 0 "Error during generation of models from vector values provided at the $organ-level status : provided $symbol vector is empty"
252+
# TODO : Might need to fiddle with timesteps here in the future in case of varying timestep models
253+
@assert nsteps == length(value_534f1c161f91bb346feba1a84a55e8251f5ad446) "Error during generation of models from vector values provided at the $organ-level status : provided $symbol vector length doesn't match the expected # of timesteps"
254+
var_type = eltype(value_534f1c161f91bb346feba1a84a55e8251f5ad446)
255+
base_name = string(symbol) * bytes2hex(sha1(join(value_534f1c161f91bb346feba1a84a55e8251f5ad446)))
268256
process_name = lowercase(base_name)
269-
270-
#process_decl = "PlantSimEngine.@process \"$process_name\" verbose = false\n\n"
271-
#eval(Meta.parse(process_decl))
272-
257+
273258
var_titlecase::String = titlecase(base_name)
274259
model_name = "My$(var_titlecase)Model"
275260
process_abstract_name = "Abstract$(var_titlecase)Model"
@@ -287,7 +272,7 @@ function generate_model_from_status_vector_variable(mapping, timestep_scale, sta
287272
inputs_decl::String = "function PlantSimEngine.inputs_(::$model_name)\n(current_timestep=1,)\nend\n\n"
288273
eval(Meta.parse(inputs_decl))
289274

290-
default_value = value[1]
275+
default_value = value_534f1c161f91bb346feba1a84a55e8251f5ad446[1]
291276
outputs_decl::String = "function PlantSimEngine.outputs_(::$model_name)\n($symbol=$default_value,)\nend\n\n"
292277
eval(Meta.parse(outputs_decl))
293278

@@ -304,28 +289,26 @@ function generate_model_from_status_vector_variable(mapping, timestep_scale, sta
304289
else
305290
end
306291

307-
model_add_decl = "generated_models = (generated_models..., $model_name($var_vector=$value),)"
292+
model_add_decl = "generated_models_534f1c161f91bb346feba1a84a55e8251f5ad446 = (generated_models_534f1c161f91bb346feba1a84a55e8251f5ad446..., $model_name($var_vector=$value_534f1c161f91bb346feba1a84a55e8251f5ad446),)"
308293
eval(Meta.parse(model_add_decl))
309294
else
310-
#setproperty!(new_status, symbol, value)
311-
new_status_decl = "new_status = Status(; NamedTuple(new_status)..., $symbol=value)"
295+
new_status_decl = "new_status_534f1c161f91bb346feba1a84a55e8251f5ad446 = Status(; NamedTuple(new_status_534f1c161f91bb346feba1a84a55e8251f5ad446)..., $symbol=$value_534f1c161f91bb346feba1a84a55e8251f5ad446)"
312296
eval(Meta.parse(new_status_decl))
313297
end
314298
end
315299

316-
@assert length(status) == length(new_status) + length(generated_models) "Error during generation of models from vector values provided at the $organ-level status"
317-
return new_status, generated_models
300+
@assert length(status) == length(new_status_534f1c161f91bb346feba1a84a55e8251f5ad446) + length(generated_models_534f1c161f91bb346feba1a84a55e8251f5ad446) "Error during generation of models from vector values provided at the $organ-level status"
301+
return new_status_534f1c161f91bb346feba1a84a55e8251f5ad446, generated_models_534f1c161f91bb346feba1a84a55e8251f5ad446
318302
end
319303

320304

321-
function modellist_to_mapping_2(modellist_original::ModelList, modellist_status, nsteps; check=true, outputs=nothing)
305+
function modellist_to_mapping(modellist_original::ModelList, modellist_status, nsteps; outputs=nothing)
322306

323307
modellist = Base.copy(modellist_original, modellist_original.status)
324308

325309
default_scale = "Default"
326310
mtg = MultiScaleTreeGraph.Node(MultiScaleTreeGraph.NodeMTG("/", default_scale, 0, 0),)
327311

328-
#models = collect(values(object))
329312
models = modellist.models
330313

331314
mapping_incomplete = Dict(
@@ -342,9 +325,9 @@ function modellist_to_mapping_2(modellist_original::ModelList, modellist_status,
342325
timestep_scale = "Default"
343326
organ = "Default"
344327

345-
# recovering the status is a bit awkward
328+
# todo improve on this
346329
st = (last(mapping_incomplete["Default"]))
347-
new_status, generated_models = generate_model_from_status_vector_variable(mapping_incomplete, timestep_scale, st, organ)
330+
new_status, generated_models = generate_model_from_status_vector_variable(mapping_incomplete, timestep_scale, st, organ, nsteps)
348331

349332
mapping = Dict(default_scale => (
350333
models..., generated_models...,
@@ -376,18 +359,6 @@ function modellist_to_mapping_2(modellist_original::ModelList, modellist_status,
376359
# TODO sanity check
377360
end
378361

379-
#=run!(mtg, mapping,
380-
meteo_day,
381-
constants=PlantMeteo.Constants(),
382-
extra=nothing,
383-
nsteps = nsteps,
384-
outputs = all_vars,
385-
check=true,
386-
executor=ThreadedEx()
387-
)=#
388-
389-
#sim = PlantSimEngine.GraphSimulation(mtg, mapping, nsteps=nsteps, check=check, outputs=Dict(default_scale => all_vars))
390-
#return sim
391362
return mtg, mapping, Dict(default_scale => all_vars)
392363
end
393364

@@ -405,7 +376,7 @@ function check_statuses_contain_no_remaining_vectors(mapping)
405376
return true
406377
end
407378

408-
#@testset "ModelList and Mapping result consistency" begin
379+
@testset "ModelList and Mapping result consistency" begin
409380

410381
meteo_day = CSV.read(joinpath(pkgdir(PlantSimEngine), "examples/meteo_day.csv"), DataFrame, header=18)
411382

@@ -426,11 +397,11 @@ end
426397
meteo_day
427398
;
428399
check=true,
429-
executor=SequentialEx()
400+
executor=ThreadedEx()
430401
)
431402

432403
nsteps = nrow(meteo_day)
433-
#=graphsim = modellist_to_mapping(models, st, nsteps; outputs=nothing, TT_cu_vec=TT_cu_vec)
404+
graphsim = modellist_to_mapping_manual(models, st, nsteps; outputs=nothing, TT_cu_vec=TT_cu_vec)
434405

435406
sim = run!(graphsim,
436407
meteo_day,
@@ -439,15 +410,15 @@ end
439410
check=true,
440411
executor=SequentialEx()
441412
)
442-
sim
443413

444-
@test compare_outputs_modellist_mapping(models, graphsim)=#
414+
@test compare_outputs_modellist_mapping(models, graphsim)
445415

446416
# fully automated model generation
447417
st2 = (TT_cu=Vector(cumsum(meteo_day.TT)),)
448418

449419
# TODO outputs name conflict if this is just named outputs
450-
mtg, mapping, outputs_mapping = modellist_to_mapping_2(models, st2, nsteps; outputs=nothing)
420+
# TODO when outputs filtering is implemented, can test it with this function
421+
mtg, mapping, outputs_mapping = modellist_to_mapping(models, st2, nsteps; outputs=nothing)
451422

452423
@test check_statuses_contain_no_remaining_vectors(mapping)
453424
graphsim2 = PlantSimEngine.GraphSimulation(mtg, mapping, nsteps=nsteps, check=true, outputs=outputs_mapping)
@@ -462,7 +433,7 @@ end
462433
@test compare_outputs_modellist_mapping(models, graphsim2)
463434
@test compare_outputs_graphsim(graphsim, graphsim2)
464435

465-
#end
436+
end
466437

467438
@testset "check_statuses_contain_no_remaining_vectors behaviour" begin
468439
meteo_day = CSV.read(joinpath(pkgdir(PlantSimEngine), "examples/meteo_day.csv"), DataFrame, header=18)
@@ -486,3 +457,5 @@ mapping_with_vector = Dict(
486457

487458
@test check_statuses_contain_no_remaining_vectors(mapping_with_empty_status)
488459
end
460+
461+
#[getproperty(a,i) for i in fieldnames(typeof(a))]

0 commit comments

Comments
 (0)