Skip to content

Commit

Permalink
Merge pull request #80 from Saityi/enforce-flow-stocks-exist
Browse files Browse the repository at this point in the history
77, 78: Check that the source and destination stocks of a flow exist; TODO => CLOUD
  • Loading branch information
Xiaoyan-Li authored Sep 6, 2023
2 parents 8405ff5 + 73bc569 commit 2dea56c
Show file tree
Hide file tree
Showing 4 changed files with 80 additions and 64 deletions.
38 changes: 19 additions & 19 deletions src/PremadeModels.jl
Original file line number Diff line number Diff line change
Expand Up @@ -48,8 +48,8 @@ seir_model = @stock_and_flow begin
tlatent
trecovery
δ
c
c


:dynamic_variables
v_prevalence = NI / NS
Expand All @@ -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
Expand All @@ -92,15 +92,15 @@ sis_model = @stock_and_flow begin
S
I


:parameters
μ
β
trec # 1 / trecovery. This corresponds to σ.
δ
c


:dynamic_variables
v_deathsX = δ * S
v_births = μ * N
Expand All @@ -126,7 +126,7 @@ sis_model = @stock_and_flow begin
N = [S, I]
NI = [I]
NS = [S, I]

end


Expand All @@ -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
Expand All @@ -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
Expand All @@ -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]
Expand Down
50 changes: 32 additions & 18 deletions src/Syntax.jl
Original file line number Diff line number Diff line change
Expand Up @@ -134,23 +134,26 @@ 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) => ☁
```
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

"""
Expand Down Expand Up @@ -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
Expand All @@ -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, _, _) =>
Expand Down Expand Up @@ -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} = []
Expand Down Expand Up @@ -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
Expand All @@ -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}()
Expand All @@ -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, (), ())
Expand Down
22 changes: 13 additions & 9 deletions test/Syntax.jl
Original file line number Diff line number Diff line change
Expand Up @@ -253,24 +253,31 @@ 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)
@test (@foot P => ()) == foot(:P, (), ())
@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, ())
Expand All @@ -297,7 +304,7 @@ end
@testset "feet syntax can create feet" begin

@test (@feet begin

A => B
C => D
() => ()
Expand Down Expand Up @@ -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



34 changes: 16 additions & 18 deletions test/SystemStructure.jl
Original file line number Diff line number Diff line change
Expand Up @@ -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]
Expand All @@ -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


0 comments on commit 2dea56c

Please sign in to comment.