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
8182function compare_outputs_modellist_mapping (models, graphsim)
8283 graphsim_df = outputs (graphsim, DataFrame)
@@ -100,7 +101,7 @@ function compare_outputs_graphsim(graphsim, graphsim2)
100101end
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
158149end
165156PlantSimEngine. inputs_ (:: ToyCurrenttimestepModel ) = (next_timestep= 1 ,)
166157PlantSimEngine. outputs_ (m:: ToyCurrenttimestepModel ) = (current_timestep= 1 ,)
167158
168- # Implementing the actual algorithm by adding a method to the run! function for our model:
169159function 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}
194183end
195184
196- # Defining the inputs and outputs of the model:
197185PlantSimEngine. inputs_ (:: ToyTestDegreeDaysCumulModel ) = (current_timestep= 1 ,)
198186PlantSimEngine. outputs_ (:: ToyTestDegreeDaysCumulModel ) = (TT_cu= 0.0 ,)
199187
200188ToyTestDegreeDaysCumulModel (; 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:
204191function PlantSimEngine. run! (m:: ToyTestDegreeDaysCumulModel , models, status, meteo, constants= nothing , extra= nothing )
205192 status. TT_cu = m. TT_cu_vec[status. current_timestep]
206193end
207194
208195PlantSimEngine. 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
243230end
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
252233import 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,)\n end\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 ,)\n end\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
318302end
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)
392363end
393364
@@ -405,7 +376,7 @@ function check_statuses_contain_no_remaining_vectors(mapping)
405376 return true
406377end
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)
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
468439meteo_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)
488459end
460+
461+ # [getproperty(a,i) for i in fieldnames(typeof(a))]
0 commit comments