diff --git a/src/PremadeModels.jl b/src/PremadeModels.jl index 13ca65a3..b6f9f8e3 100644 --- a/src/PremadeModels.jl +++ b/src/PremadeModels.jl @@ -48,8 +48,8 @@ seir_model = @stock_and_flow begin tlatent trecovery δ - c - + c + :dynamic_variables v_prevalence = NI / NS @@ -61,14 +61,14 @@ seir_model = @stock_and_flow begin v_inf = E / tlatent - v_rec = I / trecovery + v_rec = I / trecovery - v_deathS = δ * S + v_deathS = δ * S v_deathE = δ * E v_deathI = δ * I v_deathR = δ * R - + :flows CLOUD => f_birth(v_birth) => S @@ -92,7 +92,7 @@ sis_model = @stock_and_flow begin S I - + :parameters μ β @@ -100,7 +100,7 @@ sis_model = @stock_and_flow begin δ c - + :dynamic_variables v_deathsX = δ * S v_births = μ * N @@ -126,7 +126,7 @@ sis_model = @stock_and_flow begin N = [S, I] NI = [I] NS = [S, I] - + end @@ -135,35 +135,35 @@ sir_model = @stock_and_flow begin S I R - + :parameters c β rRec - + :dynamic_variables v_prevalence = NI / NS v_meanInfectiousContactsPerS = c * v_prevalence v_perSIncidenceRate = β * v_meanInfectiousContactsPerS v_newInfections = S * v_perSIncidenceRate v_newRecovery = I * rRec - + :flows S => f_inf(v_newInfections) => I I => f_rec(v_newRecovery) => R - + :sums N = [S, I, R] NI = [I] NS = [S,I,R] - - + + end svi_model = @stock_and_flow begin - + :stocks S V @@ -177,9 +177,9 @@ svi_model = @stock_and_flow begin β :dynamic_variables - v_vacc = S * rvaccine + v_vacc = S * rvaccine v_deathV = δ * V - + v_prevalence = NI / NS v_meanInfectiousContactsPerS = c * v_prevalence v_perSIncidenceRate = β * v_meanInfectiousContactsPerS @@ -190,9 +190,9 @@ svi_model = @stock_and_flow begin :flows S => f_vacc(v_vacc) => V V => f_deathV(v_deathV) => CLOUD - V => f_infV(v_perSIncidenceVaccinated) => I + V => f_infV(v_perSIncidenceVaccinated) => I + - :sums N = [S, V, I] NI = [I] diff --git a/src/Syntax.jl b/src/Syntax.jl index ec5aa14f..457d59ee 100644 --- a/src/Syntax.jl +++ b/src/Syntax.jl @@ -134,7 +134,7 @@ Compiles stock and flow syntax of the line-based block form symbol_r => flow_name_1(dyvar_k) => symbol_q symbol_z => flow_name_2(dyvar_g * param_v) => symbol_p ☁ => flow_name_3(symbol_c + dyvar_b) => symbol_r - symbol_j => flow_name_4(param_l + symbol_m) => TODO + symbol_j => flow_name_4(param_l + symbol_m) => CLOUD ... symbol_y => flow_name_n(dyvar_f) => ☁ ``` @@ -142,15 +142,18 @@ into a StockAndFlowF data type for use with the StockFlow.jl modelling system. """ macro stock_and_flow(block) Base.remove_linenums!(block) - syntax_lines = parse_stock_and_flow_syntax(block.args) - saff_args = stock_and_flow_syntax_to_arguments(syntax_lines) - return StockAndFlowF( - saff_args.stocks, - saff_args.params, - map(kv -> kv.first => get(kv.second), saff_args.dyvars), - saff_args.flows, - saff_args.sums, - ) + block_args = block.args + return quote + local syntax_lines = parse_stock_and_flow_syntax($block_args) + local saff_args = stock_and_flow_syntax_to_arguments(syntax_lines) + StockAndFlowF( + saff_args.stocks, + saff_args.params, + map(kv -> kv.first => get(kv.second), saff_args.dyvars), + saff_args.flows, + saff_args.sums, + ) + end end """ @@ -378,7 +381,7 @@ and the flow equations's definition as an expression. ### Input - `flow_definition` -- A flow definition of the form `SYMBOL => flow_name(flow_equation) => SYMBOL`, - where SYMBOL can be an arbitrary name or special cases of ☁ or TODO, + where SYMBOL can be an arbitrary name or special cases of ☁ or CLOUD, which corresponds to a flow from nowhere. ### Output @@ -387,15 +390,15 @@ may be :F_NONE for a flow from nowhere) and the flow equation as a julia express ### Examples ```julia-repl -julia> Syntax.parse_flow_io(:(TODO => birthRate(a * b * c) => S)) +julia> Syntax.parse_flow_io(:(CLOUD => birthRate(a * b * c) => S)) (:F_NONE, :(birthRate(a * b * c)), :S) ``` """ function parse_flow(flow_definition::Expr) @match flow_definition begin - :(TODO => $flow => $stock_out) || :(☁ => $flow => $stock_out) => + :(CLOUD => $flow => $stock_out) || :(☁ => $flow => $stock_out) => (:F_NONE, flow, stock_out) - :($stock_in => $flow => TODO) || :($stock_in => $flow => ☁) => + :($stock_in => $flow => CLOUD) || :($stock_in => $flow => ☁) => (stock_in, flow, :F_NONE) :($stock_in => $flow => $stock_out) => (stock_in, flow, stock_out) Expr(en, _, _, _) || Expr(en, _, _) => @@ -470,6 +473,17 @@ function assemble_stock_definitions( flows::Vector{Tuple{Symbol,Expr,Symbol}}, sum_variables::Vector{Tuple{Symbol,Vector{Symbol}}}, ) + # Check that all of the stocks involved in flow definitions exist + stock_set = Set(stocks) + push!(stock_set, :F_NONE) # for error handling step: any 'clouds' should be F_NONE by now. + for (start_object, flow, end_object) in flows + if !(start_object in stock_set) + error(String(start_object) * " is not a known stock.") + elseif !(end_object in stock_set) + error(String(end_object) * " is not a known stock.") + end + end + formatted_stocks = [] for stock in stocks input_arrows::Vector{Symbol} = [] @@ -974,7 +988,7 @@ end Create foot (StockAndFlow0) using format A => B, where A is a stock and B is a sum variable. Use () to represent no stock or sum variable. To have multiple stocks or sum variables, chain together multiple pairs with commas. Repeated occurences of the same symbol will be treated as the same stock or sum variable. -You cannot create distinct stocks or sum variables with the same name using this format. +You cannot create distinct stocks or sum variables with the same name using this format. ```julia standard_foot = @foot A => N @@ -989,8 +1003,8 @@ multiple_links = @foot A => B, A => B # will have two links from A to B. function create_foot(block::Expr) @match block.head begin - :tuple => begin - if isempty(block.args) # case for create_foot(:()) + :tuple => begin + if isempty(block.args) # case for create_foot(:()) error("Cannot create foot with no arguments.") end foot_s = Vector{Symbol}() @@ -1016,7 +1030,7 @@ Takes as argument an expression of the form A => B and returns a tuple in a form Return type is Tuple{Union{Tuple{}, Symbol}, Union{Tuple{}, Symbol}, Union{Tuple{}, Pair{Symbol, Symbol}}}. The empty tuple represents no stocks, no flows, or no links. """ -function match_foot_format(footblock::Expr) +function match_foot_format(footblock::Expr) @match footblock begin :(() => ()) => ((), (), ()) :($(s :: Symbol) => ()) => (s, (), ()) diff --git a/test/Syntax.jl b/test/Syntax.jl index 36df5324..34f12526 100644 --- a/test/Syntax.jl +++ b/test/Syntax.jl @@ -253,16 +253,23 @@ end end @testset "recursive definitions should be disallowed" begin - expr = quote + @test_throws Exception @stock_and_flow begin :dynamic_variables v = v + v end - # When used as a macro -- @stock_and_flow -- this exception is thrown - # at a point that @test_throws cannot capture it. - @test_throws Exception stock_and_flow(expr) end +@testset "the existence of references is checked" begin + @test_throws Exception @stock_and_flow begin + :stocks + A + B + C + :flows + a => f(A * B) => B + end +end @testset "foot syntax can create all types of feet" begin @test (@foot A => B) == foot(:A, :B, :A => :B) @@ -270,7 +277,7 @@ end @test (@foot () => Q) == foot((), :Q, ()) @test (@foot () => ()) == foot((),(),()) - @test (@foot =>((), SV)) == foot((),:SV,()) + @test (@foot =>((), SV)) == foot((),:SV,()) @test (@foot A11 => B22) == foot(:A11, :B22, :A11 => :B22) @test (@foot () => B, A => ()) == foot(:A, :B, ()) @@ -297,7 +304,7 @@ end @testset "feet syntax can create feet" begin @test (@feet begin - + A => B C => D () => () @@ -336,6 +343,3 @@ end @test_throws Exception @eval @feet begin A => B; =>(D,E,F) end @test_throws Exception @eval @feet begin A => B; 1 => 2; end end - - - diff --git a/test/SystemStructure.jl b/test/SystemStructure.jl index 6b448205..79e08abc 100644 --- a/test/SystemStructure.jl +++ b/test/SystemStructure.jl @@ -4,61 +4,61 @@ using StockFlow.Syntax empty = @stock_and_flow begin end -p = @stock_and_flow begin +p = @stock_and_flow begin :stocks S I - + :dynamic_variables v = S + I - + :parameters p1 p2 - + :flows S => f1(v) => CLOUD - + :sums N = [S] NI = [I] end -p_prefixed = @stock_and_flow begin +p_prefixed = @stock_and_flow begin :stocks prefS prefI - + :dynamic_variables prefv = prefS + prefI - + :parameters prefp1 prefp2 - + :flows prefS => preff1(prefv) => CLOUD - + :sums prefN = [prefS] prefNI = [prefI] end -p_suffixed = @stock_and_flow begin +p_suffixed = @stock_and_flow begin :stocks Ssuf Isuf - + :dynamic_variables vsuf = Ssuf + Isuf - + :parameters p1suf p2suf - + :flows Ssuf => f1suf(vsuf) => CLOUD - + :sums Nsuf = [Ssuf] NIsuf = [Isuf] @@ -75,11 +75,9 @@ footA_suf = @foot Ssuf => Nsuf @test empty == add_suffix!(deepcopy(empty), "AAA") == add_prefix!(deepcopy(empty), "BBB") @test add_prefix!(deepcopy(p), "pref") == p_prefixed @test add_suffix!(deepcopy(p), "suf") == p_suffixed - + @test empty_foot == add_suffix!(deepcopy(empty_foot), "CCC") == add_prefix!(deepcopy(empty_foot), "DDD") @test add_prefix!(deepcopy(footA), "pref") == footA_pref @test add_suffix!(deepcopy(footA), "suf") == footA_suf end - -