Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
45 changes: 44 additions & 1 deletion src/Abstract_model_structs.jl
Original file line number Diff line number Diff line change
Expand Up @@ -25,4 +25,47 @@ model_(m::AbstractModel) = m
get_models(m::AbstractModel) = [model_(m)] # Get the models of an AbstractModel
# Note: it is returning a vector of models, because in this case the user provided a single model instead of a vector of.
get_status(m::AbstractModel) = nothing
get_mapped_variables(m::AbstractModel) = Pair{Symbol,String}[]
get_mapped_variables(m::AbstractModel) = Pair{Symbol,String}[]


#using Dates
struct TimestepRange
lower_bound::Period
upper_bound::Period
end

# Default, no specified range, meaning the model either doesn't depend on time or uses the simulation's default (eg smallest) timestep
TimestepRange() = TimestepRange(Second(0), Second(0))
# Only a single timestep type possible
TimestepRange(p::Period) = TimestepRange(p, p)

"""
timestep_range_(tsr::TimestepRange)

Return the model's valid range for timesteps (which corresponds to the simulation base timestep in the default case).
"""
function timestep_range_(model::AbstractModel)
return TimestepRange()
end

"""
timestep_valid(tsr::TimestepRange)

Checks whether a TimestepRange
"""
timestep_valid(tsr::TimestepRange) = tsr.lower_bound <= tsr.upper_bound

function model_timestep_range_compatible_with_timestep(tsr::TimestepRange, p::Period)
if !timestep_valid(tsr)
return false
end

# 0 means any timestep is valid, no timestep constraints
if tsr.upper_bound == Seconds(0)
return true
end

return p >= tsr.lower_bound && p <= tsr.lower_bound
end

# TODO should i set all timestep ranges to default and hope the modeler gets it right or should i force them to write something ?
5 changes: 5 additions & 0 deletions src/PlantSimEngine.jl
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import Statistics
import SHA: sha1

using PlantMeteo
using PlantMeteo.Dates

# UninitializedVar + PreviousTimeStep:
include("variables_wrappers.jl")
Expand Down Expand Up @@ -65,6 +66,9 @@ include("dependencies/printing.jl")
include("dependencies/dependencies.jl")
include("dependencies/get_model_in_dependency_graph.jl")

# Timesteps. :
include("timestep/timestep_mapping.jl")

# MTG compatibility:
include("mtg/GraphSimulation.jl")
include("mtg/mapping/getters.jl")
Expand Down Expand Up @@ -103,6 +107,7 @@ include("examples_import.jl")
export PreviousTimeStep
export AbstractModel
export ModelList, MultiScaleModel
export Orchestrator
export RMSE, NRMSE, EF, dr
export Status, TimeStepTable, status
export init_status!
Expand Down
7 changes: 4 additions & 3 deletions src/mtg/GraphSimulation.jl
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ A type that holds all information for a simulation over a graph.
- `var_need_init`: a dictionary indicating if a variable needs to be initialized
- `dependency_graph`: the dependency graph of the models applied to the graph
- `models`: a dictionary of models
- `Orchestrator : the structure that handles timestep peculiarities
- `outputs`: a dictionary of outputs
"""
struct GraphSimulation{T,S,U,O,V}
Expand All @@ -24,12 +25,12 @@ struct GraphSimulation{T,S,U,O,V}
var_need_init::Dict{String,V}
dependency_graph::DependencyGraph
models::Dict{String,U}
orchestrator::Orchestrator
outputs::Dict{String,O}
outputs_index::Dict{String, Int}
end

function GraphSimulation(graph, mapping; nsteps=1, outputs=nothing, type_promotion=nothing, check=true, verbose=false)
GraphSimulation(init_simulation(graph, mapping; nsteps=nsteps, outputs=outputs, type_promotion=type_promotion, check=check, verbose=verbose)...)
function GraphSimulation(graph, mapping; nsteps=1, outputs=nothing, type_promotion=nothing, check=true, verbose=false, orchestrator=Orchestrator())
GraphSimulation(init_simulation(graph, mapping; nsteps=nsteps, outputs=outputs, type_promotion=type_promotion, check=check, verbose=verbose)..., orchestrator)
end

dep(g::GraphSimulation) = g.dependency_graph
Expand Down
2 changes: 1 addition & 1 deletion src/mtg/initialisation.jl
Original file line number Diff line number Diff line change
Expand Up @@ -305,7 +305,7 @@ The value is not a reference to the one in the attribute of the MTG, but a copy
a value in a Dict. If you need a reference, you can use a `Ref` for your variable in the MTG directly, and it will be
automatically passed as is.
"""
function init_simulation(mtg, mapping; nsteps=1, outputs=nothing, type_promotion=nothing, check=true, verbose=false)
function init_simulation(mtg, mapping; nsteps=1, outputs=nothing, type_promotion=nothing, check=true, verbose=false, orchestrator=Orchestrator())

# Ensure the user called the model generation function to handle vectors passed into a status
# before we keep going
Expand Down
20 changes: 18 additions & 2 deletions src/run.jl
Original file line number Diff line number Diff line change
Expand Up @@ -365,17 +365,21 @@ function run!(
meteo=nothing,
constants=PlantMeteo.Constants(),
extra=nothing;
orchestrator::Orchestrator=nothing,
nsteps=nothing,
tracked_outputs=nothing,
check=true,
executor=ThreadedEx()
executor=ThreadedEx(),
default_timestep::Int,
model_timesteps::Dict{T, Int} where {T}

)
isnothing(nsteps) && (nsteps = get_nsteps(meteo))
meteo_adjusted = adjust_weather_timesteps_to_given_length(nsteps, meteo)

# NOTE : replace_mapping_status_vectors_with_generated_models is assumed to have already run if used
# otherwise there might be vector length conflicts with timesteps
sim = GraphSimulation(object, mapping, nsteps=nsteps, check=check, outputs=tracked_outputs)
sim = GraphSimulation(object, mapping, nsteps=nsteps, check=check, outputs=tracked_outputs, default_timestep=default_timestep, model_timesteps=model_timesteps)
run!(
sim,
meteo_adjusted,
Expand Down Expand Up @@ -455,6 +459,18 @@ function run_node_multiscale!(
return nothing
end

model_timestep = object.model_timesteps[typeof(node.value)]

if model_timestep != object.default_timestep
# do accumulation


# run if necessary
if i % model_timestep != 0
return nothing
end
end

node_statuses = status(object)[node.scale] # Get the status of the nodes at the current scale
models_at_scale = models[node.scale]

Expand Down
42 changes: 42 additions & 0 deletions src/timestep/timestep_mapping.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@

# Those names all suck, need to change them
# Some of them are probably not ideal for new users, too

# Some types can also be constrained a lot more, probably

struct TimestepMapper#{V}
variable_from#::V
timestep_from::Int
mapping_function
end

struct SimulationTimestepHandler#{W,V}
model_timesteps::Dict{Any, Int} # where {W <: AbstractModel} # if a model isn't in there, then it follows the default, todo check if the given timestep respects the model's range
timestep_variable_mapping::Dict{Any, TimestepMapper} #where {V}
end

SimulationTimestepHandler() = SimulationTimestepHandler(Dict(), Dict()) #Dict{W, Int}(), Dict{V, TimestepMapper}()) where {W, V}

mutable struct Orchestrator
# This is actually a general simulation parameter, not-scale specific
# todo change to Period
default_timestep::Int64

# This needs to be per-scale : if a model is used at two different scales,
# and the same variable of that model maps to some other timestep to two *different* variables
# then I believe we can only rely on the different scale to disambiguate
non_default_timestep_data_per_scale::Dict{String, SimulationTimestepHandler}

function Orchestrator(default::Int64, per_scale::Dict{String, SimulationTimestepHandler})
@assert default >= 0 "The default_timestep should be greater than or equal to 0."
return new(default, per_scale)
end
end

# TODO have a default constructor take in a meteo or something, and set up the default timestep automagically to be the finest weather timestep
# Other options are possible
Orchestrator() = Orchestrator(1, Dict{String, SimulationTimestepHandler}())


#o = Orchestrator()
#oo = Orchestrator(1, Dict{String, SimulationTimestepHandler}())
151 changes: 150 additions & 1 deletion test/test-simulation.jl
Original file line number Diff line number Diff line change
Expand Up @@ -292,4 +292,153 @@ end;
end
end
end
end
end




using PlantSimEngine
# Include the example dummy processes:
using PlantSimEngine.Examples
using Test, Aqua
using Tables, DataFrames, CSV
using MultiScaleTreeGraph
using PlantMeteo, Statistics
using Documenter # for doctests

using PlantMeteo.Dates
include("helper-functions.jl")



# These models might be worth exposing in the future ?
PlantSimEngine.@process "basic_current_timestep" verbose = false

struct HelperCurrentTimestepModel <: AbstractBasic_Current_TimestepModel
end

PlantSimEngine.inputs_(::HelperCurrentTimestepModel) = (next_timestep=1,)
PlantSimEngine.outputs_(m::HelperCurrentTimestepModel) = (current_timestep=1,)

function PlantSimEngine.run!(m::HelperCurrentTimestepModel, models, status, meteo, constants=nothing, extra=nothing)
status.current_timestep = status.next_timestep
end

PlantSimEngine.ObjectDependencyTrait(::Type{<:HelperCurrentTimestepModel}) = PlantSimEngine.IsObjectDependent()
PlantSimEngine.TimeStepDependencyTrait(::Type{<:HelperCurrentTimestepModel}) = PlantSimEngine.IsTimeStepDependent()

PlantSimEngine.timestep_range_(m::HelperCurrentTimestepModel) = Day(1)


PlantSimEngine.@process "basic_next_timestep" verbose = false
struct HelperNextTimestepModel <: AbstractBasic_Next_TimestepModel
end

PlantSimEngine.inputs_(::HelperNextTimestepModel) = (current_timestep=1,)
PlantSimEngine.outputs_(m::HelperNextTimestepModel) = (next_timestep=1,)

function PlantSimEngine.run!(m::HelperNextTimestepModel, models, status, meteo, constants=nothing, extra=nothing)
status.next_timestep = status.current_timestep + 1
end

PlantSimEngine.timestep_range_(m::HelperNextTimestepModel) = Day(1)





PlantSimEngine.@process "ToyDay" verbose = false

struct MyToyDayModel <: AbstractToydayModel end

PlantSimEngine.inputs_(m::MyToyDayModel) = (a=1,)
PlantSimEngine.outputs_(m::MyToyDayModel) = (daily_temperature=-Inf,)

function PlantSimEngine.run!(m::MyToyDayModel, models, status, meteo, constants=nothing, extra=nothing)
status.daily_temperature = meteo.T
end

PlantSimEngine.@process "ToyWeek" verbose = false

struct MyToyWeekModel <: AbstractToyweekModel
temperature_threshold::Float64
end

MyToyWeekModel() = MyToyWeekModel(30.0)
function PlantSimEngine.inputs_(::MyToyWeekModel)
(weekly_max_temperature=-Inf,)
end
PlantSimEngine.outputs_(m::MyToyWeekModel) = (hot = false,)

function PlantSimEngine.run!(m::MyToyWeekModel, models, status, meteo, constants=nothing, extra=nothing)
status.hot = status.weekly_max_temperature > m.temperature_threshold
end

PlantSimEngine.timestep_range_(m::MyToyWeekModel) = Week(1)



PlantSimEngine.@process "DWConnector" verbose = false

struct MyDwconnectorModel <: AbstractDwconnectorModel
T_daily::Array{Float64}
end

MyDwconnectorModel() = MyDwconnectorModel(Array{Float64}(undef, 7))

function PlantSimEngine.inputs_(::MyDwconnectorModel)
(daily_temperature=-Inf, current_timestep=1,)
end
PlantSimEngine.outputs_(m::MyDwconnectorModel) = (weekly_max_temperature = 0.0,)

function PlantSimEngine.run!(m::MyDwconnectorModel, models, status, meteo, constants=nothing, extra=nothing)
m.T_daily[1 + (status.current_timestep % 7)] = status.daily_temperature

if(status.current_timestep % 7 == 1)
status.weekly_max_temperature = sum(m.T_daily)/7.0
else
status.weekly_max_temperature = 0
end
end

PlantSimEngine.timestep_range_(m::MyDwconnectorModel) = Day(1)





meteo_day = read_weather(joinpath(pkgdir(PlantSimEngine), "examples/meteo_day.csv"), duration=Day)

m = Dict("Default" => (
MyToyDayModel(),
MyToyWeekModel(),
MyDwconnectorModel(),
HelperNextTimestepModel(),
MultiScaleModel(
model=HelperCurrentTimestepModel(),
mapped_variables=[PreviousTimeStep(:next_timestep),],
),
Status(a=1,)))

to_initialize(m)

models_timestep = Dict(MyToyDayModel=>1, MyDwconnectorModel => 1, MyToyWeekModel =>7, HelperNextTimestepModel => 1, HelperCurrentTimestepModel => 1)

mtg = Node(MultiScaleTreeGraph.NodeMTG("/", "Default", 1, 1))

out = run!(mtg, m, meteo_day, default_timestep=1, model_timesteps=models_timestep)

@testset "Test varying timestep" begin


@test
@test

end


# NOTE : replace_mapping_status_vectors_with_generated_models is assumed to have already run if used
# otherwise there might be vector length conflicts with timesteps
sim = @enter PlantSimEngine.GraphSimulation(mtg, m, nsteps=nothing, check=true, outputs=nothing, default_timestep=1, model_timesteps=models_timestep)

using PlantSimEngine
Loading