From dbf44196e241a0abe3598881e931f69cce7f2a61 Mon Sep 17 00:00:00 2001 From: Thomas Purdy Date: Thu, 28 Sep 2023 14:48:44 -0600 Subject: [PATCH] Additional, project-wide conversion from four spaces to two spaces. --- src/CausalLoop.jl | 108 ++-- src/StockFlow.jl | 396 ++++++------- src/SystemStructure.jl | 404 ++++++------- test/Syntax.jl | 848 +++++++++++++-------------- test/SystemStructure.jl | 90 +-- test/runtests.jl | 4 +- test/syntax/Composition.jl | 176 +++--- test/syntax/Stratification.jl | 1026 ++++++++++++++++----------------- 8 files changed, 1526 insertions(+), 1526 deletions(-) diff --git a/src/CausalLoop.jl b/src/CausalLoop.jl index a9746af0..9d572cff 100644 --- a/src/CausalLoop.jl +++ b/src/CausalLoop.jl @@ -30,18 +30,18 @@ add_edges!(c::AbstractCausalLoop,n,s,t;kw...) = add_parts!(c,:E,n,s=s,t=t;kw...) Create causal loop diagram from collection of nodes and collection of edges. """ CausalLoop(ns,es) = begin - c = CausalLoop() - ns = vectorify(ns) - es = vectorify(es) - - ns_idx=state_dict(ns) - add_nodes!(c, length(ns), nname=ns) - - s=map(first,es) - t=map(last,es) - add_edges!(c, length(es), map(x->ns_idx[x], s), map(x->ns_idx[x], t)) - - c + c = CausalLoop() + ns = vectorify(ns) + es = vectorify(es) + + ns_idx=state_dict(ns) + add_nodes!(c, length(ns), nname=ns) + + s=map(first,es) + t=map(last,es) + add_edges!(c, length(es), map(x->ns_idx[x], s), map(x->ns_idx[x], t)) + + c end # return the count of each components @@ -62,25 +62,25 @@ nnames(c::AbstractCausalLoop) = [nname(c, n) for n in 1:nn(c)] function convertToCausalLoop(p::AbstractStockAndFlowStructure) - - sns=snames(p) - fns=fnames(p) - svns=svnames(p) - flowVariableIndexs=[flowVariableIndex(p,f) for f in 1:nf(p)] - vNotf=setdiff(1:nvb(p),flowVariableIndexs) - vNotfns=[vname(p,v) for v in vNotf] - - ns=vcat(sns,fns,svns,vNotfns) - - lses=[sname(p,subpart(p,ls,:lss))=>svname(p,subpart(p,ls,:lssv)) for ls in 1:nls(p)] - lsvfes=[svname(p,subpart(p,lsv,:lsvsv))=>subpart(p,lsv,:lsvv) in flowVariableIndexs ? fname(p,only(incident(p,subpart(p,lsv,:lsvv),:fv))) : vname(p,subpart(p,lsv,:lsvv)) for lsv in 1:nlsv(p)] - lfves=[sname(p,subpart(p,lv,:lvs))=>subpart(p,lv,:lvv) in flowVariableIndexs ? fname(p,only(incident(p,subpart(p,lv,:lvv),:fv))) : vname(p,subpart(p,lv,:lvv)) for lv in 1:nlv(p)] - fies=[fname(p,subpart(p,i,:ifn))=>sname(p,subpart(p,i,:is)) for i in 1:ni(p)] - foes=[fname(p,subpart(p,o,:ofn))=>sname(p,subpart(p,o,:os)) for o in 1:no(p)] - - es=vcat(lses,lsvfes,lfves,fies,foes) - - return CausalLoop(ns,es) + + sns=snames(p) + fns=fnames(p) + svns=svnames(p) + flowVariableIndexs=[flowVariableIndex(p,f) for f in 1:nf(p)] + vNotf=setdiff(1:nvb(p),flowVariableIndexs) + vNotfns=[vname(p,v) for v in vNotf] + + ns=vcat(sns,fns,svns,vNotfns) + + lses=[sname(p,subpart(p,ls,:lss))=>svname(p,subpart(p,ls,:lssv)) for ls in 1:nls(p)] + lsvfes=[svname(p,subpart(p,lsv,:lsvsv))=>subpart(p,lsv,:lsvv) in flowVariableIndexs ? fname(p,only(incident(p,subpart(p,lsv,:lsvv),:fv))) : vname(p,subpart(p,lsv,:lsvv)) for lsv in 1:nlsv(p)] + lfves=[sname(p,subpart(p,lv,:lvs))=>subpart(p,lv,:lvv) in flowVariableIndexs ? fname(p,only(incident(p,subpart(p,lv,:lvv),:fv))) : vname(p,subpart(p,lv,:lvv)) for lv in 1:nlv(p)] + fies=[fname(p,subpart(p,i,:ifn))=>sname(p,subpart(p,i,:is)) for i in 1:ni(p)] + foes=[fname(p,subpart(p,o,:ofn))=>sname(p,subpart(p,o,:os)) for o in 1:no(p)] + + es=vcat(lses,lsvfes,lfves,fies,foes) + + return CausalLoop(ns,es) end """ @@ -89,27 +89,27 @@ Nodes: stocks, flows, sum variables, parameters, nonflow dynamic variables Edges: morphisms in stock flow """ function convertToCausalLoop(p::AbstractStockAndFlowStructureF) - - sns=snames(p) - fns=fnames(p) - svns=svnames(p) - pns=pnames(p) - flowVariableIndexs=[flowVariableIndex(p,f) for f in 1:nf(p)] - vNotf=setdiff(1:nvb(p),flowVariableIndexs) - vNotfns=[vname(p,v) for v in vNotf] - - ns=vcat(sns,fns,svns,vNotfns,pns) - - lses=[sname(p,subpart(p,ls,:lss))=>svname(p,subpart(p,ls,:lssv)) for ls in 1:nls(p)] - lsvfes=[svname(p,subpart(p,lsv,:lsvsv))=>subpart(p,lsv,:lsvv) in flowVariableIndexs ? fname(p,only(incident(p,subpart(p,lsv,:lsvv),:fv))) : vname(p,subpart(p,lsv,:lsvv)) for lsv in 1:nlsv(p)] - lfves=[sname(p,subpart(p,lv,:lvs))=>subpart(p,lv,:lvv) in flowVariableIndexs ? fname(p,only(incident(p,subpart(p,lv,:lvv),:fv))) : vname(p,subpart(p,lv,:lvv)) for lv in 1:nlv(p)] - fies=[fname(p,subpart(p,i,:ifn))=>sname(p,subpart(p,i,:is)) for i in 1:ni(p)] - foes=[fname(p,subpart(p,o,:ofn))=>sname(p,subpart(p,o,:os)) for o in 1:no(p)] - lpvs=[pname(p,subpart(p,lp,:lpvp))=>subpart(p,lp,:lpvv) in flowVariableIndexs ? fname(p,only(incident(p,subpart(p,lp,:lpvv),:fv))) : vname(p,subpart(p,lp,:lpvv)) for lp in 1:nlpv(p)] - lvvs=[subpart(p,lv,:lvsrc) in flowVariableIndexs ? fname(p,only(incident(p,subpart(p,lv,:lvsrc),:fv))) : vname(p,subpart(p,lv,:lvsrc))=>subpart(p,lv,:lvtgt) in flowVariableIndexs ? fname(p,only(incident(p,subpart(p,lv,:lvtgt),:fv))) : vname(p,subpart(p,lv,:lvtgt)) for lv in 1:nlvv(p)] - - - es=vcat(lses,lsvfes,lfves,fies,foes,lpvs,lvvs) - - return CausalLoop(ns,es) + + sns=snames(p) + fns=fnames(p) + svns=svnames(p) + pns=pnames(p) + flowVariableIndexs=[flowVariableIndex(p,f) for f in 1:nf(p)] + vNotf=setdiff(1:nvb(p),flowVariableIndexs) + vNotfns=[vname(p,v) for v in vNotf] + + ns=vcat(sns,fns,svns,vNotfns,pns) + + lses=[sname(p,subpart(p,ls,:lss))=>svname(p,subpart(p,ls,:lssv)) for ls in 1:nls(p)] + lsvfes=[svname(p,subpart(p,lsv,:lsvsv))=>subpart(p,lsv,:lsvv) in flowVariableIndexs ? fname(p,only(incident(p,subpart(p,lsv,:lsvv),:fv))) : vname(p,subpart(p,lsv,:lsvv)) for lsv in 1:nlsv(p)] + lfves=[sname(p,subpart(p,lv,:lvs))=>subpart(p,lv,:lvv) in flowVariableIndexs ? fname(p,only(incident(p,subpart(p,lv,:lvv),:fv))) : vname(p,subpart(p,lv,:lvv)) for lv in 1:nlv(p)] + fies=[fname(p,subpart(p,i,:ifn))=>sname(p,subpart(p,i,:is)) for i in 1:ni(p)] + foes=[fname(p,subpart(p,o,:ofn))=>sname(p,subpart(p,o,:os)) for o in 1:no(p)] + lpvs=[pname(p,subpart(p,lp,:lpvp))=>subpart(p,lp,:lpvv) in flowVariableIndexs ? fname(p,only(incident(p,subpart(p,lp,:lpvv),:fv))) : vname(p,subpart(p,lp,:lpvv)) for lp in 1:nlpv(p)] + lvvs=[subpart(p,lv,:lvsrc) in flowVariableIndexs ? fname(p,only(incident(p,subpart(p,lv,:lvsrc),:fv))) : vname(p,subpart(p,lv,:lvsrc))=>subpart(p,lv,:lvtgt) in flowVariableIndexs ? fname(p,only(incident(p,subpart(p,lv,:lvtgt),:fv))) : vname(p,subpart(p,lv,:lvtgt)) for lv in 1:nlvv(p)] + + + es=vcat(lses,lsvfes,lfves,fies,foes,lpvs,lvvs) + + return CausalLoop(ns,es) end \ No newline at end of file diff --git a/src/StockFlow.jl b/src/StockFlow.jl index ebe439e2..8d15f736 100644 --- a/src/StockFlow.jl +++ b/src/StockFlow.jl @@ -293,62 +293,62 @@ get_lvtgt(sf::AbstractStockAndFlowF) = collect(values(sf.subparts[:lvtgt].m)) StockAndFlowStructure(s,f,sv) = begin - p = StockAndFlowStructure() - - s = vectorify(s) - f = vectorify(f) - sv = vectorify(sv) - - sname = map(first,s) - fname = map(first, f) - vname = map(last, f) - svname = map(first, sv) - s_idx = state_dict(sname) - f_idx = state_dict(fname) - v_idx = state_dict(vname) - sv_idx = state_dict(svname) - - # adding the objects that do not have out-morphisms firstly - add_variables!(p, length(vname), vname=vname) # add objects :V (auxiliary variables) - add_svariables!(p, length(svname), svname=svname) # add objects :SV (sum auxiliary variables) - add_flows!(p,map(x->v_idx[x], map(last,f)),length(fname),fname=fname) # add objects :F (flows) - - # Parse the elements included in "s" -- stocks - for (i, (name,(ins,outs,vs,svs))) in enumerate(s) - i = add_stock!(p,sname=name) # add objects :S (stocks) - ins=vectorify(ins) # inflows of each stock - outs=vectorify(outs) # outflows of each stock - vs=vectorify(vs) # auxiliary variables depends on the stock - svs=vectorify(svs) # sum auxiliary variables depends on the stock - # filter out the fake (empty) elements - ins = ins[ins .!= FK_FLOW_NAME] - outs = outs[outs .!= FK_FLOW_NAME] - vs = vs[vs .!= FK_VARIABLE_NAME] - svs = svs[svs .!= FK_SVARIABLE_NAME] - - if length(ins)>0 - add_inflows!(p, length(ins), repeat([i], length(ins)), map(x->f_idx[x], ins)) # add objects :I (inflows) - end - if length(outs)>0 - add_outflows!(p, length(outs), repeat([i], length(outs)), map(x->f_idx[x], outs)) # add objects :O (outflows) - end - if length(vs)>0 - add_Vlinks!(p, length(vs), repeat([i], length(vs)), map(x->v_idx[x], vs)) # add objects :LV (links from Stock to dynamic variable) - end - if length(svs)>0 - add_Slinks!(p, length(svs), repeat([i], length(svs)), map(x->sv_idx[x], svs)) # add objects :LS (links from Stock to sum dynamic variable) - end + p = StockAndFlowStructure() + + s = vectorify(s) + f = vectorify(f) + sv = vectorify(sv) + + sname = map(first,s) + fname = map(first, f) + vname = map(last, f) + svname = map(first, sv) + s_idx = state_dict(sname) + f_idx = state_dict(fname) + v_idx = state_dict(vname) + sv_idx = state_dict(svname) + + # adding the objects that do not have out-morphisms firstly + add_variables!(p, length(vname), vname=vname) # add objects :V (auxiliary variables) + add_svariables!(p, length(svname), svname=svname) # add objects :SV (sum auxiliary variables) + add_flows!(p,map(x->v_idx[x], map(last,f)),length(fname),fname=fname) # add objects :F (flows) + + # Parse the elements included in "s" -- stocks + for (i, (name,(ins,outs,vs,svs))) in enumerate(s) + i = add_stock!(p,sname=name) # add objects :S (stocks) + ins=vectorify(ins) # inflows of each stock + outs=vectorify(outs) # outflows of each stock + vs=vectorify(vs) # auxiliary variables depends on the stock + svs=vectorify(svs) # sum auxiliary variables depends on the stock + # filter out the fake (empty) elements + ins = ins[ins .!= FK_FLOW_NAME] + outs = outs[outs .!= FK_FLOW_NAME] + vs = vs[vs .!= FK_VARIABLE_NAME] + svs = svs[svs .!= FK_SVARIABLE_NAME] + + if length(ins)>0 + add_inflows!(p, length(ins), repeat([i], length(ins)), map(x->f_idx[x], ins)) # add objects :I (inflows) + end + if length(outs)>0 + add_outflows!(p, length(outs), repeat([i], length(outs)), map(x->f_idx[x], outs)) # add objects :O (outflows) + end + if length(vs)>0 + add_Vlinks!(p, length(vs), repeat([i], length(vs)), map(x->v_idx[x], vs)) # add objects :LV (links from Stock to dynamic variable) + end + if length(svs)>0 + add_Slinks!(p, length(svs), repeat([i], length(svs)), map(x->sv_idx[x], svs)) # add objects :LS (links from Stock to sum dynamic variable) end + end - # Parse the elements included in "sv" -- sum auxiliary vairables - for (i, (svname,vs)) in enumerate(sv) - vs=vectorify(vs) - vs = vs[vs .!= FK_SVVARIABLE_NAME] - if length(vs)>0 - add_SVlinks!(p, length(vs), repeat(collect(sv_idx[svname]), length(vs)), map(x->v_idx[x], collect(vs))) - end + # Parse the elements included in "sv" -- sum auxiliary vairables + for (i, (svname,vs)) in enumerate(sv) + vs=vectorify(vs) + vs = vs[vs .!= FK_SVVARIABLE_NAME] + if length(vs)>0 + add_SVlinks!(p, length(vs), repeat(collect(sv_idx[svname]), length(vs)), map(x->v_idx[x], collect(vs))) end - p + end + p end ###### TODO #### delete?? @@ -362,62 +362,62 @@ end StockAndFlow(s,f,v,sv) = begin - p = StockAndFlow() - - s = vectorify(s) - f = vectorify(f) - v = vectorify(v) - sv = vectorify(sv) - - sname = map(first,s) - fname = map(first, f) - vname = map(first, v) - svname = map(first, sv) - s_idx = state_dict(sname) - f_idx = state_dict(fname) - v_idx = state_dict(vname) - sv_idx = state_dict(svname) - - # adding the objects that do not have out-morphisms firstly - add_variables!(p, length(vname), vname=vname, funcDynam=map(last, v)) # add objects :V (auxiliary variables) - add_svariables!(p, length(svname), svname=svname) # add objects :SV (sum auxiliary variables) - add_flows!(p,map(x->v_idx[x], map(last,f)),length(fname),fname=fname) # add objects :F (flows) - - # Parse the elements included in "s" -- stocks - for (i, (name,(ins,outs,vs,svs))) in enumerate(s) - i = add_stock!(p,sname=name) # add objects :S (stocks) - ins=vectorify(ins) # inflows of each stock - outs=vectorify(outs) # outflows of each stock - vs=vectorify(vs) # auxiliary variables depends on the stock - svs=vectorify(svs) # sum auxiliary variables depends on the stock - # filter out the fake (empty) elements - ins = ins[ins .!= FK_FLOW_NAME] - outs = outs[outs .!= FK_FLOW_NAME] - vs = vs[vs .!= FK_VARIABLE_NAME] - svs = svs[svs .!= FK_SVARIABLE_NAME] - if length(ins)>0 - add_inflows!(p, length(ins), repeat([i], length(ins)), map(x->f_idx[x], ins)) # add objects :I (inflows) - end - if length(outs)>0 - add_outflows!(p, length(outs), repeat([i], length(outs)), map(x->f_idx[x], outs)) # add objects :O (outflows) - end - if length(vs)>0 - add_Vlinks!(p, length(vs), repeat([i], length(vs)), map(x->v_idx[x], vs)) # add objects :LV (links from Stock to dynamic variable) - end - if length(svs)>0 - add_Slinks!(p, length(svs), repeat([i], length(svs)), map(x->sv_idx[x], svs)) # add objects :LS (links from Stock to sum dynamic variable) - end + p = StockAndFlow() + + s = vectorify(s) + f = vectorify(f) + v = vectorify(v) + sv = vectorify(sv) + + sname = map(first,s) + fname = map(first, f) + vname = map(first, v) + svname = map(first, sv) + s_idx = state_dict(sname) + f_idx = state_dict(fname) + v_idx = state_dict(vname) + sv_idx = state_dict(svname) + + # adding the objects that do not have out-morphisms firstly + add_variables!(p, length(vname), vname=vname, funcDynam=map(last, v)) # add objects :V (auxiliary variables) + add_svariables!(p, length(svname), svname=svname) # add objects :SV (sum auxiliary variables) + add_flows!(p,map(x->v_idx[x], map(last,f)),length(fname),fname=fname) # add objects :F (flows) + + # Parse the elements included in "s" -- stocks + for (i, (name,(ins,outs,vs,svs))) in enumerate(s) + i = add_stock!(p,sname=name) # add objects :S (stocks) + ins=vectorify(ins) # inflows of each stock + outs=vectorify(outs) # outflows of each stock + vs=vectorify(vs) # auxiliary variables depends on the stock + svs=vectorify(svs) # sum auxiliary variables depends on the stock + # filter out the fake (empty) elements + ins = ins[ins .!= FK_FLOW_NAME] + outs = outs[outs .!= FK_FLOW_NAME] + vs = vs[vs .!= FK_VARIABLE_NAME] + svs = svs[svs .!= FK_SVARIABLE_NAME] + if length(ins)>0 + add_inflows!(p, length(ins), repeat([i], length(ins)), map(x->f_idx[x], ins)) # add objects :I (inflows) end + if length(outs)>0 + add_outflows!(p, length(outs), repeat([i], length(outs)), map(x->f_idx[x], outs)) # add objects :O (outflows) + end + if length(vs)>0 + add_Vlinks!(p, length(vs), repeat([i], length(vs)), map(x->v_idx[x], vs)) # add objects :LV (links from Stock to dynamic variable) + end + if length(svs)>0 + add_Slinks!(p, length(svs), repeat([i], length(svs)), map(x->sv_idx[x], svs)) # add objects :LS (links from Stock to sum dynamic variable) + end + end - # Parse the elements included in "sv" -- sum auxiliary vairables - for (i, (svname,vs)) in enumerate(sv) - vs=vectorify(vs) - vs = vs[vs .!= FK_SVVARIABLE_NAME] - if length(vs)>0 - add_SVlinks!(p, length(vs), repeat(collect(sv_idx[svname]), length(vs)), map(x->v_idx[x], collect(vs))) - end + # Parse the elements included in "sv" -- sum auxiliary vairables + for (i, (svname,vs)) in enumerate(sv) + vs=vectorify(vs) + vs = vs[vs .!= FK_SVVARIABLE_NAME] + if length(vs)>0 + add_SVlinks!(p, length(vs), repeat(collect(sv_idx[svname]), length(vs)), map(x->v_idx[x], collect(vs))) end - p + end + p end add_parameter!(p::AbstractStockAndFlowStructureF;kw...) = add_part!(p,:P;kw...) @@ -639,11 +639,11 @@ fvs(p::AbstractStockAndFlowStructure)=[fv(p,f) for f in 1:nf(p)] """ return the pair of names of (stock, sum-auxiliary-variable) for all linkages between them """ lsnames(p::AbstractStockAndFlow0) = begin - s = map(x->subpart(p,x,:lss),collect(1:nls(p))) - sv = map(x->subpart(p,x,:lssv),collect(1:nls(p))) - sn = map(x->sname(p,x),s) - svn = map(x->svname(p,x),sv) - pssv = collect(zip(sn, svn)) + s = map(x->subpart(p,x,:lss),collect(1:nls(p))) + sv = map(x->subpart(p,x,:lssv),collect(1:nls(p))) + sn = map(x->sname(p,x),s) + svn = map(x->svname(p,x),sv) + pssv = collect(zip(sn, svn)) end """ return inflows of stock index s """ @@ -695,56 +695,56 @@ lpvvposition(p::AbstractStockAndFlowF,v) = subpart(p,incident(p,v,:lpvv),:lpvppo # create a dictionary make_dict(ks, vs) = begin - @assert length(ks)==length(vs) - dic=() - for (k,v) in zip(ks,vs) - dic=(dic...,(k,v)) - end - return Dict(dic) + @assert length(ks)==length(vs) + dic=() + for (k,v) in zip(ks,vs) + dic=(dic...,(k,v)) + end + return Dict(dic) end """ create expresision of an auxiliary variable v """ function make_v_expr(p::AbstractStockAndFlowF,v) - op = vop(p,v) - srcsv=map(i->sname(p,i),stocksv(p,v)) - srcsvv=map(i->svname(p,i),svsv(p,v)) - srcpv=map(i->pname(p,i),vpsrc(p,v)) - srcvv=map(i->vname(p,i),vsrc(p,v)) + op = vop(p,v) + srcsv=map(i->sname(p,i),stocksv(p,v)) + srcsvv=map(i->svname(p,i),svsv(p,v)) + srcpv=map(i->pname(p,i),vpsrc(p,v)) + srcvv=map(i->vname(p,i),vsrc(p,v)) - lvvp=lvvposition(p,v) - lvtgtp=lvtgtposition(p,v) - lsvvp=lsvvposition(p,v) - lpvvp=lpvvposition(p,v) + lvvp=lvvposition(p,v) + lvtgtp=lvtgtposition(p,v) + lsvvp=lsvvposition(p,v) + lpvvp=lpvvposition(p,v) - if length(srcvv)>0 - srcvv=map(x->make_v_expr(p,vsrc(p,v)[x]),1:length(vsrc(p,v))) - end + if length(srcvv)>0 + srcvv=map(x->make_v_expr(p,vsrc(p,v)[x]),1:length(vsrc(p,v))) + end - # create dictionary of (key=position, value=symbole of source argument) - position_src=merge(make_dict(lvvp,srcsv),make_dict(lsvvp,srcsvv),make_dict(lpvvp,srcpv),make_dict(lvtgtp,srcvv)) - ordered_position_src=sort(collect(position_src), by = x->x[1]) - srcs=map(x->last(x),ordered_position_src) + # create dictionary of (key=position, value=symbole of source argument) + position_src=merge(make_dict(lvvp,srcsv),make_dict(lsvvp,srcsvv),make_dict(lpvvp,srcpv),make_dict(lvtgtp,srcvv)) + ordered_position_src=sort(collect(position_src), by = x->x[1]) + srcs=map(x->last(x),ordered_position_src) - return math_expr(op,srcs...) + return math_expr(op,srcs...) end # genreate an array of all arguments of an expression generate_expr_args(expr) = begin - args=expr.args - argsarray=[] - for arg in args - if arg isa Expr - argsarray=vcat(argsarray,generate_expr_args(arg)) - else - argsarray=vcat(argsarray,arg) - end - end - ops=vcat(collect(values(Operators))...) - return setdiff(unique(argsarray),ops) + args=expr.args + argsarray=[] + for arg in args + if arg isa Expr + argsarray=vcat(argsarray,generate_expr_args(arg)) + else + argsarray=vcat(argsarray,arg) + end + end + ops=vcat(collect(values(Operators))...) + return setdiff(unique(argsarray),ops) end # evaluate an expression to a function eval_function(expr,s,sv,p,us,uNs,ps) = begin - f=eval(Expr(:->, Expr(:tuple, s..., sv..., p...), Expr(:block,:(()),expr))) - return @eval $f($(us...), $(uNs...), $(ps...)) + f=eval(Expr(:->, Expr(:tuple, s..., sv..., p...), Expr(:block,:(()),expr))) + return @eval $f($(us...), $(uNs...), $(ps...)) end @@ -771,20 +771,20 @@ funcDynam(p::AbstractStockAndFlow,v) = subpart(p,v,:funcDynam) funcDynam(sf::AbstractStockAndFlowF,v) return the functions of variables give index v """ funcDynam(sf::AbstractStockAndFlowF,v) = begin - expr=make_v_expr(sf,v) - args=generate_expr_args(expr) - - args_s=args[findall(in(snames(sf)),args)] - args_sv=args[findall(in(svnames(sf)),args)] - args_p=args[findall(in(pnames(sf)),args)] - - f(u,uN,p,t)=begin - us=map(i->u[i],args_s) - uNs=map(i->uN[i](u,t),args_sv) - ps=map(i->p[i],args_p) - return eval_function(expr,args_s,args_sv,args_p,us,uNs,ps) - end - return f + expr=make_v_expr(sf,v) + args=generate_expr_args(expr) + + args_s=args[findall(in(snames(sf)),args)] + args_sv=args[findall(in(svnames(sf)),args)] + args_p=args[findall(in(pnames(sf)),args)] + + f(u,uN,p,t)=begin + us=map(i->u[i],args_s) + uNs=map(i->uN[i](u,t),args_sv) + ps=map(i->p[i],args_p) + return eval_function(expr,args_s,args_sv,args_p,us,uNs,ps) + end + return f end """ return the auxiliary variable's index that related to the flow with index of f """ @@ -798,29 +798,29 @@ funcFlowsRaw(p::Union{AbstractStockAndFlow,AbstractStockAndFlowF}) = begin end """ generate the function substituting sum variables in with flow index fn """ funcFlow(pn::Union{AbstractStockAndFlow,AbstractStockAndFlowF}, fn) = begin - func=funcFlowRaw(pn,fn) - f(u,p,t) = begin - uN=funcSVs(pn) - return valueat(func,u,uN,p,t) - end + func=funcFlowRaw(pn,fn) + f(u,p,t) = begin + uN=funcSVs(pn) + return valueat(func,u,uN,p,t) + end end """ return the LVector of pairs: fname => function (with function of sum variables substitue in) """ funcFlows(p::Union{AbstractStockAndFlow,AbstractStockAndFlowF})=begin - fnames = [fname(p, f) for f in 1:nf(p)] - LVector(;[(fnames[f]=>funcFlow(p, f)) for f in 1:nf(p)]...) + fnames = [fname(p, f) for f in 1:nf(p)] + LVector(;[(fnames[f]=>funcFlow(p, f)) for f in 1:nf(p)]...) end """ generate the function of a sum auxiliary variable (index sv) with the sum of all stocks links to it """ funcSV(p::AbstractStockAndFlow0,sv) = begin - uN(u,t) = begin - sumS = 0 - for i in stockssv(p,sv) - sumS=sumS+u[sname(p,i)] - end - return sumS - end - return uN + uN(u,t) = begin + sumS = 0 + for i in stockssv(p,sv) + sumS=sumS+u[sname(p,i)] + end + return sumS + end + return uN end """ return the LVector of pairs: svname => function """ funcSVs(p::AbstractStockAndFlow0) = begin @@ -834,11 +834,11 @@ end # given a stock and flow diagram in schema "StockAndFlow", return a stock and flow diagram in schema "StockAndFlow0" object_shift_right(p::StockAndFlowStructure) = begin - s = snames(p) - sv = svnames(p) - ssv = map(y->map(x->(sname(p,y),x),svname(p,svsstock(p,y))),collect(1:ns(p))) - ssv = vcat(ssv...) - StockAndFlow0(s,sv,ssv) + s = snames(p) + sv = svnames(p) + ssv = map(y->map(x->(sname(p,y),x),svname(p,svsstock(p,y))),collect(1:ns(p))) + ssv = vcat(ssv...) + StockAndFlow0(s,sv,ssv) end # create open acset, as the structured cospan @@ -853,27 +853,27 @@ foot(s, sv, ssv) = StockAndFlow0(s, sv, ssv) ntcomponent(a, x0) = map(x->state_dict(x0)[x], a) leg(a::StockAndFlow0, x::Union{StockAndFlowStructure,StockAndFlow,StockAndFlowStructureF,StockAndFlowF}) = begin - if ns(a)>0 # if have stocks - ϕs = ntcomponent(snames(a), snames(x)) - else - ϕs = Int[] - end + if ns(a)>0 # if have stocks + ϕs = ntcomponent(snames(a), snames(x)) + else + ϕs = Int[] + end - if nsv(a) > 0 # if have sum-auxiliary-variable - ϕsv = ntcomponent(svnames(a), svnames(x)) - else - ϕsv = Int[] - end + if nsv(a) > 0 # if have sum-auxiliary-variable + ϕsv = ntcomponent(svnames(a), svnames(x)) + else + ϕsv = Int[] + end - if nls(a)>0 # if have links between stocks and sum-auxiliary-variables - ϕls = ntcomponent(lsnames(a), lsnames(x)) - else - ϕls = Int[] - end + if nls(a)>0 # if have links between stocks and sum-auxiliary-variables + ϕls = ntcomponent(lsnames(a), lsnames(x)) + else + ϕls = Int[] + end - result = OpenACSetLeg(a, S=ϕs, LS=ϕls, SV=ϕsv) + result = OpenACSetLeg(a, S=ϕs, LS=ϕls, SV=ϕsv) - result + result end Open(p::StockAndFlow, feet...) = begin diff --git a/src/SystemStructure.jl b/src/SystemStructure.jl index 9e036f56..d9b3bea9 100644 --- a/src/SystemStructure.jl +++ b/src/SystemStructure.jl @@ -10,178 +10,178 @@ flattenTupleNames(sn::Vector)=[flattenTupleNames(x) for x in sn] function extracStocksStructureAndFlatten(p::AbstractStockAndFlowStructure) - s=[] + s=[] + + for is in 1:ns(p) + sn=sname(p,is) + sn=flattenTupleNames(sn) - for is in 1:ns(p) - sn=sname(p,is) - sn=flattenTupleNames(sn) - - ifs=inflows(p,is) - ofs=outflows(p,is) - vss=vsstock(p,is) - svss=svsstock(p,is) - - ifns=isempty(ifs) ? :F_NONE : fname(p,ifs) - ofns=isempty(ofs) ? :F_NONE : fname(p,ofs) - vsns=isempty(vss) ? :V_NONE : vname(p,vss) - svsns=isempty(svss) ? :SV_NONE : svname(p,svss) - - ifns=flattenTupleNames(ifns) - ofns=flattenTupleNames(ofns) - vsns=flattenTupleNames(vsns) - svsns=flattenTupleNames(svsns) - - ss=sn=>(ifns,ofns,vsns,svsns) - s=vcat(s,ss) - end + ifs=inflows(p,is) + ofs=outflows(p,is) + vss=vsstock(p,is) + svss=svsstock(p,is) + + ifns=isempty(ifs) ? :F_NONE : fname(p,ifs) + ofns=isempty(ofs) ? :F_NONE : fname(p,ofs) + vsns=isempty(vss) ? :V_NONE : vname(p,vss) + svsns=isempty(svss) ? :SV_NONE : svname(p,svss) - return s + ifns=flattenTupleNames(ifns) + ofns=flattenTupleNames(ofns) + vsns=flattenTupleNames(vsns) + svsns=flattenTupleNames(svsns) + + ss=sn=>(ifns,ofns,vsns,svsns) + s=vcat(s,ss) + end + + return s end """ Return stock names as Symbol, along with the linked flows and sum variables """ function extracStocksStructureAndFlatten(p::AbstractStockAndFlowStructureF) - s=[] + s=[] + + for is in 1:ns(p) + sn=sname(p,is) + sn=flattenTupleNames(sn) - for is in 1:ns(p) - sn=sname(p,is) - sn=flattenTupleNames(sn) - - ifs=inflows(p,is) - ofs=outflows(p,is) - svss=svsstock(p,is) - - ifns=isempty(ifs) ? :F_NONE : fname(p,ifs) - ofns=isempty(ofs) ? :F_NONE : fname(p,ofs) - svsns=isempty(svss) ? :SV_NONE : svname(p,svss) - - ifns=flattenTupleNames(ifns) - ofns=flattenTupleNames(ofns) - svsns=flattenTupleNames(svsns) - - ss=sn=>(ifns,ofns,svsns) - s=vcat(s,ss) - end + ifs=inflows(p,is) + ofs=outflows(p,is) + svss=svsstock(p,is) + + ifns=isempty(ifs) ? :F_NONE : fname(p,ifs) + ofns=isempty(ofs) ? :F_NONE : fname(p,ofs) + svsns=isempty(svss) ? :SV_NONE : svname(p,svss) - return s + ifns=flattenTupleNames(ifns) + ofns=flattenTupleNames(ofns) + svsns=flattenTupleNames(svsns) + + ss=sn=>(ifns,ofns,svsns) + s=vcat(s,ss) + end + + return s end """ Return flow names as Symbol, along with the linked flow variables """ function extracFlowsStructureAndFlatten(p::AbstractStockAndFlowStructure) - f=[] - - if nf(p)>0 - for ifl in 1:nf(p) - fn=fname(p,ifl) - vn=vname(p,fv(p,ifl)) - - fn=flattenTupleNames(fn) - vn=flattenTupleNames(vn) - - fvs=fn=>vn - f=vcat(f,fvs) - end + f=[] + + if nf(p)>0 + for ifl in 1:nf(p) + fn=fname(p,ifl) + vn=vname(p,fv(p,ifl)) + + fn=flattenTupleNames(fn) + vn=flattenTupleNames(vn) + + fvs=fn=>vn + f=vcat(f,fvs) end - - return f + end + + return f end """ Return parameter names as Symbol """ function extracPsStructureAndFlatten(p::AbstractStockAndFlowStructureF) - pns=[] - - if np(p)>0 - for pr in 1:np(p) - pn=pname(p,pr) - pn=flattenTupleNames(pn) - pns=vcat(pns,pn) - end + pns=[] + + if np(p)>0 + for pr in 1:np(p) + pn=pname(p,pr) + pn=flattenTupleNames(pn) + pns=vcat(pns,pn) end - - return pns + end + + return pns end function extracSumVStructureAndFlatten(p::AbstractStockAndFlowStructure) - sv=[] - - if nsv(p)>0 - for svi in 1:nsv(p) - svn=svname(p,svi) - vsvs=vssv(p,svi) - vsvns=isempty(vsvs) ? :SVV_NONE : vname(p,vsvs) - - svn=flattenTupleNames(svn) - vsvns=flattenTupleNames(vsvns) - - svs=svn=>vsvns - sv=vcat(sv,svs) - end - end - - return sv + sv=[] + + if nsv(p)>0 + for svi in 1:nsv(p) + svn=svname(p,svi) + vsvs=vssv(p,svi) + vsvns=isempty(vsvs) ? :SVV_NONE : vname(p,vsvs) + + svn=flattenTupleNames(svn) + vsvns=flattenTupleNames(vsvns) + + svs=svn=>vsvns + sv=vcat(sv,svs) + end + end + + return sv end """ Return sum variable names as Symbol, along with the linked dynamic variables """ function extracSumVStructureAndFlatten(p::AbstractStockAndFlowStructureF) - sv=[] - - if nsv(p)>0 - for svi in 1:nsv(p) - svn=svname(p,svi) - svn=flattenTupleNames(svn) - sv=vcat(sv,svn) - end - end - - return sv + sv=[] + + if nsv(p)>0 + for svi in 1:nsv(p) + svn=svname(p,svi) + svn=flattenTupleNames(svn) + sv=vcat(sv,svn) + end + end + + return sv end """ Return a Tuple of Vectors of Symbols of flattened stocks, sums, parameters and source dynamic variables a dynamic variable at index v links to. """ function args_vname(p::AbstractStockAndFlowStructureF,v) - srcsv=map(i->(flattenTupleNames(sname(p,i))),stocksv(p,v)) - srcsvv=map(i->(flattenTupleNames(svname(p,i))),svsv(p,v)) - srcpv=map(i->(flattenTupleNames(pname(p,i))),vpsrc(p,v)) - srcvv=map(i->(flattenTupleNames(vname(p,i))),vsrc(p,v)) + srcsv=map(i->(flattenTupleNames(sname(p,i))),stocksv(p,v)) + srcsvv=map(i->(flattenTupleNames(svname(p,i))),svsv(p,v)) + srcpv=map(i->(flattenTupleNames(pname(p,i))),vpsrc(p,v)) + srcvv=map(i->(flattenTupleNames(vname(p,i))),vsrc(p,v)) - return (srcsv,srcsvv,srcpv,srcvv) + return (srcsv,srcsvv,srcpv,srcvv) end """ - args(p::AbstractStockAndFlowStructureF,v) + args(p::AbstractStockAndFlowStructureF,v) Return a Vector of Symbols of flattened stocks, sums, parameters and source dynamic variables a dynamic variable at index v links to. """ function args(p::AbstractStockAndFlowStructureF,v) - (srcsv,srcsvv,srcpv,srcvv)=args_vname(p,v) - return vcat(srcsv,srcsvv,srcpv,srcvv) + (srcsv,srcsvv,srcpv,srcvv)=args_vname(p,v) + return vcat(srcsv,srcsvv,srcpv,srcvv) end """ - args(p::AbstractStockAndFlowF,v) + args(p::AbstractStockAndFlowF,v) Return a Vector of Symbols of flattened stocks, sums, parameters and source dynamic variables a dynamic variable at index v links to. """ function args(p::AbstractStockAndFlowF,v) - (srcsv,srcsvv,srcpv,srcvv)=args_vname(p,v) - - lvvp=lvvposition(p,v) - lvtgtp=lvtgtposition(p,v) - lsvvp=lsvvposition(p,v) - lpvvp=lpvvposition(p,v) - - # create dictionary of (key=position, value=symbole of source argument) - position_src=merge(make_dict(lvvp,srcsv),make_dict(lsvvp,srcsvv),make_dict(lpvvp,srcpv),make_dict(lvtgtp,srcvv)) - ordered_position_src=sort(collect(position_src), by = x->x[1]) - srcs=map(x->last(x),ordered_position_src) - - return srcs + (srcsv,srcsvv,srcpv,srcvv)=args_vname(p,v) + + lvvp=lvvposition(p,v) + lvtgtp=lvtgtposition(p,v) + lsvvp=lsvvposition(p,v) + lpvvp=lpvvposition(p,v) + + # create dictionary of (key=position, value=symbole of source argument) + position_src=merge(make_dict(lvvp,srcsv),make_dict(lsvvp,srcsvv),make_dict(lpvvp,srcpv),make_dict(lvtgtp,srcvv)) + ordered_position_src=sort(collect(position_src), by = x->x[1]) + srcs=map(x->last(x),ordered_position_src) + + return srcs end """ @@ -189,17 +189,17 @@ Return dynamic variable definitions as Vector with elements of form :dv => [:arg """ extracVStructureAndFlatten(p::AbstractStockAndFlowStructureF) = begin - vs=[] - - if nvb(p)>0 - for v in 1:nvb(p) - vn = flattenTupleNames(vname(p,v)) - vnp = vn=>args(p,v) - vs = vcat(vs,vnp) - end + vs=[] + + if nvb(p)>0 + for v in 1:nvb(p) + vn = flattenTupleNames(vname(p,v)) + vnp = vn=>args(p,v) + vs = vcat(vs,vnp) end - return vs - + end + return vs + end """ @@ -207,47 +207,47 @@ Convert dynamic variable names to Symbol, convert all operators to a single oper """ extracVAndAttrStructureAndFlatten(p::AbstractStockAndFlowF) = begin - vs=[] - - if nvb(p)>0 - for v in 1:nvb(p) - vn = flattenTupleNames(vname(p,v)) - v_op = allequal(vop(p,v)) ? vop(p,v)[1] : error("operators $(vop(p,v)) in the stratified model's auxiliary variable: $(join(vname(p,v))) should be the same!") - vnp = vn=>(args(p,v)=>v_op) - vs = vcat(vs,vnp) - end + vs=[] + + if nvb(p)>0 + for v in 1:nvb(p) + vn = flattenTupleNames(vname(p,v)) + v_op = allequal(vop(p,v)) ? vop(p,v)[1] : error("operators $(vop(p,v)) in the stratified model's auxiliary variable: $(join(vname(p,v))) should be the same!") + vnp = vn=>(args(p,v)=>v_op) + vs = vcat(vs,vnp) end - return vs + end + return vs end function rebuildStratifiedModelByFlattenSymbols(p::AbstractStockAndFlowStructure) - s=extracStocksStructureAndFlatten(p) - f=extracFlowsStructureAndFlatten(p) - sv=extracSumVStructureAndFlatten(p) - - return StockAndFlowStructure(s,f,sv) + s=extracStocksStructureAndFlatten(p) + f=extracFlowsStructureAndFlatten(p) + sv=extracSumVStructureAndFlatten(p) + + return StockAndFlowStructure(s,f,sv) end """ Return a new stock flow with flattened names, operators and positions from the old """ function rebuildStratifiedModelByFlattenSymbols(p::AbstractStockAndFlowF) - s=extracStocksStructureAndFlatten(p) - pr=extracPsStructureAndFlatten(p) - f=extracFlowsStructureAndFlatten(p) - sv=extracSumVStructureAndFlatten(p) - v=extracVAndAttrStructureAndFlatten(p) - - return StockAndFlowF(s,pr,v,f,sv) + s=extracStocksStructureAndFlatten(p) + pr=extracPsStructureAndFlatten(p) + f=extracFlowsStructureAndFlatten(p) + sv=extracSumVStructureAndFlatten(p) + v=extracVAndAttrStructureAndFlatten(p) + + return StockAndFlowF(s,pr,v,f,sv) end function convertSystemStructureToStockFlow(p::AbstractStockAndFlowStructure,v) - s=extracStocksStructureAndFlatten(p) - f=extracFlowsStructureAndFlatten(p) - sv=extracSumVStructureAndFlatten(p) - - return StockAndFlow(s,f,v,sv) + s=extracStocksStructureAndFlatten(p) + f=extracFlowsStructureAndFlatten(p) + sv=extracSumVStructureAndFlatten(p) + + return StockAndFlow(s,f,v,sv) end """ @@ -258,35 +258,35 @@ convertSystemStructureToStockFlow(MyStockFlowStructure, (:v_prevalence=>(:I,:N,: ``` """ function convertSystemStructureToStockFlow(p::AbstractStockAndFlowStructureF,v) - s=extracStocksStructureAndFlatten(p) - pr=extracPsStructureAndFlatten(p) - f=extracFlowsStructureAndFlatten(p) - sv=extracSumVStructureAndFlatten(p) - - return StockAndFlowF(s,pr,v,f,sv) + s=extracStocksStructureAndFlatten(p) + pr=extracPsStructureAndFlatten(p) + f=extracFlowsStructureAndFlatten(p) + sv=extracSumVStructureAndFlatten(p) + + return StockAndFlowF(s,pr,v,f,sv) end function convertStockFlowToSystemStructure(p::AbstractStockAndFlow) - - s=extracStocksStructureAndFlatten(p) - f=extracFlowsStructureAndFlatten(p) - sv=extracSumVStructureAndFlatten(p) - return StockAndFlowStructure(s,f,sv) + + s=extracStocksStructureAndFlatten(p) + f=extracFlowsStructureAndFlatten(p) + sv=extracSumVStructureAndFlatten(p) + return StockAndFlowStructure(s,f,sv) end """ Return a new StockAndFlowStructureF with flattened names, operators and positions from an AbstractStockAndFlowF. """ function convertStockFlowToSystemStructure(p::AbstractStockAndFlowF) - - s=extracStocksStructureAndFlatten(p) - pr=extracPsStructureAndFlatten(p) - v=extracVStructureAndFlatten(p) - f=extracFlowsStructureAndFlatten(p) - sv=extracSumVStructureAndFlatten(p) - - return StockAndFlowStructureF(s,pr,v,f,sv) + + s=extracStocksStructureAndFlatten(p) + pr=extracPsStructureAndFlatten(p) + v=extracVStructureAndFlatten(p) + f=extracFlowsStructureAndFlatten(p) + sv=extracSumVStructureAndFlatten(p) + + return StockAndFlowStructureF(s,pr,v,f,sv) end @@ -296,62 +296,62 @@ Concatenate Symbols. ++(a::Symbol,b::Symbol) = Symbol(string(a, b)) """ - add_suffix!(sf::AbstractStockAndFlow0, suffix) + add_suffix!(sf::AbstractStockAndFlow0, suffix) Modify a AbstractStockAndFlow0 so named elements end with suffix. Suffix can be anything which can be cast to a Symbol.""" function add_suffix!(sf::AbstractStockAndFlowStructureF, suffix) - suffix = Symbol(suffix) - set_snames!(sf, snames(sf) .++ suffix) - set_fnames!(sf, fnames(sf) .++ suffix) - set_svnames!(sf, svnames(sf) .++ suffix) - set_vnames!(sf, vnames(sf) .++ suffix) - set_pnames!(sf, pnames(sf) .++ suffix) - return sf + suffix = Symbol(suffix) + set_snames!(sf, snames(sf) .++ suffix) + set_fnames!(sf, fnames(sf) .++ suffix) + set_svnames!(sf, svnames(sf) .++ suffix) + set_vnames!(sf, vnames(sf) .++ suffix) + set_pnames!(sf, pnames(sf) .++ suffix) + return sf end """ - add_suffix!(sf::AbstractStockAndFlow0, suffix) + add_suffix!(sf::AbstractStockAndFlow0, suffix) Modify a AbstractStockAndFlow0 so named elements end with suffix. Suffix can be anything which can be cast to a Symbol. For feet. """ function add_suffix!(sf::AbstractStockAndFlow0, suffix) - suffix = Symbol(suffix) - set_snames!(sf, snames(sf) .++ suffix) - set_svnames!(sf, svnames(sf) .++ suffix) - return sf + suffix = Symbol(suffix) + set_snames!(sf, snames(sf) .++ suffix) + set_svnames!(sf, svnames(sf) .++ suffix) + return sf end """ - add_prefix!(sf::AbstractStockAndFlowStructureF, prefix) + add_prefix!(sf::AbstractStockAndFlowStructureF, prefix) Modify a AbstractStockAndFlowStructureF so named elements begin with prefix Prefix can be anything which can be cast to a Symbol. """ function add_prefix!(sf::AbstractStockAndFlowStructureF, prefix) - prefix = Symbol(prefix) - set_snames!(sf, prefix .++ snames(sf)) - set_fnames!(sf, prefix .++ fnames(sf)) - set_svnames!(sf, prefix .++ svnames(sf)) - set_vnames!(sf, prefix .++ vnames(sf)) - set_pnames!(sf, prefix .++ pnames(sf)) - return sf + prefix = Symbol(prefix) + set_snames!(sf, prefix .++ snames(sf)) + set_fnames!(sf, prefix .++ fnames(sf)) + set_svnames!(sf, prefix .++ svnames(sf)) + set_vnames!(sf, prefix .++ vnames(sf)) + set_pnames!(sf, prefix .++ pnames(sf)) + return sf end """ - add_prefix!(sf::AbstractStockAndFlowStructureF, prefix) + add_prefix!(sf::AbstractStockAndFlowStructureF, prefix) Modify a AbstractStockAndFlowStructureF so named elements begin with prefix Prefix can be anything which can be cast to a Symbol.For feet. """ function add_prefix!(sf::AbstractStockAndFlow0, prefix) - prefix = Symbol(prefix) - set_snames!(sf, prefix .++ snames(sf)) - set_svnames!(sf, prefix .++ svnames(sf)) - return sf + prefix = Symbol(prefix) + set_snames!(sf, prefix .++ snames(sf)) + set_svnames!(sf, prefix .++ svnames(sf)) + return sf end diff --git a/test/Syntax.jl b/test/Syntax.jl index d0fb2d5b..6242bcf4 100755 --- a/test/Syntax.jl +++ b/test/Syntax.jl @@ -5,557 +5,557 @@ using StockFlow.Syntax using StockFlow.Syntax: is_binop_or_unary, sum_variables, infix_expression_to_binops, fnone_value_or_vector, extract_function_name_and_args_expr, is_recursive_dyvar, create_foot, apply_flags, substitute_symbols @testset "Stratification DSL" begin - include("syntax/Stratification.jl") + include("syntax/Stratification.jl") end @testset "Composition DSL" begin - include("syntax/Composition.jl") + include("syntax/Composition.jl") end @testset "is_binop_or_unary recognises binops" begin - @test is_binop_or_unary(:(a + b)) - @test is_binop_or_unary(:(f(a, b))) - @test is_binop_or_unary(:(1.0 + x)) + @test is_binop_or_unary(:(a + b)) + @test is_binop_or_unary(:(f(a, b))) + @test is_binop_or_unary(:(1.0 + x)) end @testset "is_binop_or_unary recognises non-binops as non-binops" begin - @test !is_binop_or_unary(:(f())) - @test !is_binop_or_unary(:(a + b + c)) - @test !is_binop_or_unary(:(f(a, b, c))) + @test !is_binop_or_unary(:(f())) + @test !is_binop_or_unary(:(a + b + c)) + @test !is_binop_or_unary(:(f(a, b, c))) end @testset "sum_variables" begin - @test sum_variables([]) == [] - @test sum_variables([(:a, 1)]) == [:a] - @test sum_variables([(:a, 1), (:b, 2)]) == [:a, :b] + @test sum_variables([]) == [] + @test sum_variables([(:a, 1)]) == [:a] + @test sum_variables([(:a, 1), (:b, 2)]) == [:a, :b] end @testset "infix_expression_to_binops does nothing to binops and unary exprs" begin - @test infix_expression_to_binops(:(f(a)))[1][1][2] == :(f(a)) - @test infix_expression_to_binops(:(f(a, b)))[1][1][2] == :(f(a, b)) - @test infix_expression_to_binops(:(a + b))[1][1][2] == :(a + b) + @test infix_expression_to_binops(:(f(a)))[1][1][2] == :(f(a)) + @test infix_expression_to_binops(:(f(a, b)))[1][1][2] == :(f(a, b)) + @test infix_expression_to_binops(:(a + b))[1][1][2] == :(a + b) end @testset "infix_expression_to_binops creates right number of expressions" begin - @test length(infix_expression_to_binops(:(a + b + c))[1]) == 2 - @test length(infix_expression_to_binops(:(a + b + c + d))[1]) == 3 - @test length(infix_expression_to_binops(:(a + b + c + d + e))[1]) == 4 - @test length(infix_expression_to_binops(:(a + b + c + d + e + f))[1]) == 5 + @test length(infix_expression_to_binops(:(a + b + c))[1]) == 2 + @test length(infix_expression_to_binops(:(a + b + c + d))[1]) == 3 + @test length(infix_expression_to_binops(:(a + b + c + d + e))[1]) == 4 + @test length(infix_expression_to_binops(:(a + b + c + d + e + f))[1]) == 5 end @testset "infix_expression_to_binops throws exception when no binops provided" begin - @test_throws Exception infix_expression_to_binops(:(f())) + @test_throws Exception infix_expression_to_binops(:(f())) end @testset "infix_expression_to_binops uses final symbol" begin - @test infix_expression_to_binops(:(f(a, b)); finalsym=:testsym)[2] == :testsym - @test infix_expression_to_binops(:(a + b); finalsym=:testsym)[2] == :testsym + @test infix_expression_to_binops(:(f(a, b)); finalsym=:testsym)[2] == :testsym + @test infix_expression_to_binops(:(a + b); finalsym=:testsym)[2] == :testsym end @testset "fnone_value_or_vector" begin - empty_symbol_vector::Vector{Symbol} = [] - @test fnone_value_or_vector(empty_symbol_vector) == :F_NONE - @test fnone_value_or_vector([:a]) == :a - @test fnone_value_or_vector([:a, :b]) == [:a, :b] + empty_symbol_vector::Vector{Symbol} = [] + @test fnone_value_or_vector(empty_symbol_vector) == :F_NONE + @test fnone_value_or_vector([:a]) == :a + @test fnone_value_or_vector([:a, :b]) == [:a, :b] end @testset "extract_function_name_args_expr extracts the flow name and flow definition" begin - @test extract_function_name_and_args_expr(:(testf(a))) == (:testf, :a) - @test extract_function_name_and_args_expr(:(testf(a + b + c + d))) == (:testf, :(a + b + c + d)) + @test extract_function_name_and_args_expr(:(testf(a))) == (:testf, :a) + @test extract_function_name_and_args_expr(:(testf(a + b + c + d))) == (:testf, :(a + b + c + d)) end @testset "extract_function_name_args_expr rejects invalid flow expressions" begin - # Undefined flow equation - @test_throws Exception extract_function_name_and_args_expr(:(testf())) - # Multiparameter flows - @test_throws Exception extract_function_name_and_args_expr(:(testf(a, b))) + # Undefined flow equation + @test_throws Exception extract_function_name_and_args_expr(:(testf())) + # Multiparameter flows + @test_throws Exception extract_function_name_and_args_expr(:(testf(a, b))) end #@testset "non-variable parameters in functions" begin # @stock_and_flow begin -# :stocks -# A -# B -# C -# :parameters -# x -# y -# z -# :dynamic_variables +# :stocks +# A +# B +# C +# :parameters +# x +# y +# z +# :dynamic_variables # TODO? f = 1.0 - x -# :flows -# A => fname(f) => B +# :flows +# A => fname(f) => B # end #end @testset "model allows uses unary functions like log" begin - SIR_1_via_macro = @stock_and_flow begin - :stocks - S - I - R - - :parameters - c - beta - tRec - - :dynamic_variables - v_prevalence = exp(I) - v_meanInfectiousContactsPerS = c * v_prevalence - v_perSIncidenceRate = beta * v_meanInfectiousContactsPerS - v_newInfections = S * v_perSIncidenceRate - v_newRecovery = log(I) - - :flows - S => inf(v_newInfections) => I - I => rec(v_newRecovery) => R - - :sums - N = [S, I, R] - end - - SIR_1_canonical = StockAndFlowF( - # stocks - (:S => (:F_NONE, :inf, :N), :I => (:inf, :rec, :N), :R => (:rec, :F_NONE, :N)), - # parameters - (:c, :beta, :tRec), - # dynamical variables - (:v_prevalence => (:I => :exp), - :v_meanInfectiousContactsPerS => ((:c, :v_prevalence) => :*), - :v_perSIncidenceRate => ((:beta, :v_meanInfectiousContactsPerS) => :*), - :v_newInfections => ((:S, :v_perSIncidenceRate) => :*), - :v_newRecovery => (:I => :log), - ), - # flows - (:inf => :v_newInfections, :rec => :v_newRecovery), - # sum dynamical variables - (:N), - ) - @test SIR_1_via_macro == SIR_1_canonical + SIR_1_via_macro = @stock_and_flow begin + :stocks + S + I + R + + :parameters + c + beta + tRec + + :dynamic_variables + v_prevalence = exp(I) + v_meanInfectiousContactsPerS = c * v_prevalence + v_perSIncidenceRate = beta * v_meanInfectiousContactsPerS + v_newInfections = S * v_perSIncidenceRate + v_newRecovery = log(I) + + :flows + S => inf(v_newInfections) => I + I => rec(v_newRecovery) => R + + :sums + N = [S, I, R] + end + + SIR_1_canonical = StockAndFlowF( + # stocks + (:S => (:F_NONE, :inf, :N), :I => (:inf, :rec, :N), :R => (:rec, :F_NONE, :N)), + # parameters + (:c, :beta, :tRec), + # dynamical variables + (:v_prevalence => (:I => :exp), + :v_meanInfectiousContactsPerS => ((:c, :v_prevalence) => :*), + :v_perSIncidenceRate => ((:beta, :v_meanInfectiousContactsPerS) => :*), + :v_newInfections => ((:S, :v_perSIncidenceRate) => :*), + :v_newRecovery => (:I => :log), + ), + # flows + (:inf => :v_newInfections, :rec => :v_newRecovery), + # sum dynamical variables + (:N), + ) + @test SIR_1_via_macro == SIR_1_canonical end @testset "stock_and_flow macro generates the expected StockAndFlowF representations" begin - SIR_1_via_macro = @stock_and_flow begin - :stocks - S - I - R - - :parameters - c - beta - tRec - - :dynamic_variables - v_prevalence = I / N - v_meanInfectiousContactsPerS = c * v_prevalence - v_perSIncidenceRate = beta * v_meanInfectiousContactsPerS - v_newInfections = S * v_perSIncidenceRate - v_newRecovery = I / tRec - - :flows - S => inf(v_newInfections) => I - I => rec(v_newRecovery) => R - - :sums - N = [S, I, R] - end - - SIR_1_canonical = StockAndFlowF( - # stocks - (:S => (:F_NONE, :inf, :N), :I => (:inf, :rec, :N), :R => (:rec, :F_NONE, :N)), - # parameters - (:c, :beta, :tRec), - # dynamical variables - (:v_prevalence => ((:I, :N) => :/), - :v_meanInfectiousContactsPerS => ((:c, :v_prevalence) => :*), - :v_perSIncidenceRate => ((:beta, :v_meanInfectiousContactsPerS) => :*), - :v_newInfections => ((:S, :v_perSIncidenceRate) => :*), - :v_newRecovery => ((:I, :tRec) => :/), - ), - # flows - (:inf => :v_newInfections, :rec => :v_newRecovery), - # sum dynamical variables - (:N), - ) - @test SIR_1_via_macro == SIR_1_canonical - - SIR_2 = @stock_and_flow begin - :stocks - S - I - R - - :parameters - c - beta - tRec - - # We can leave out dynamic variables and let them be inferred from flows entirely! - - :flows - S => inf(S * beta * (c * (I / N))) => I - I => rec(I / tRec) => R - - :sums - N = [S, I, R] - end - - # Although the variable names are different - # due to gensym, the models should structurally be the same. - for part in keys(SIR_2.parts) - @test SIR_2.parts[part] == SIR_1_canonical.parts[part] - end + SIR_1_via_macro = @stock_and_flow begin + :stocks + S + I + R + + :parameters + c + beta + tRec + + :dynamic_variables + v_prevalence = I / N + v_meanInfectiousContactsPerS = c * v_prevalence + v_perSIncidenceRate = beta * v_meanInfectiousContactsPerS + v_newInfections = S * v_perSIncidenceRate + v_newRecovery = I / tRec + + :flows + S => inf(v_newInfections) => I + I => rec(v_newRecovery) => R + + :sums + N = [S, I, R] + end + + SIR_1_canonical = StockAndFlowF( + # stocks + (:S => (:F_NONE, :inf, :N), :I => (:inf, :rec, :N), :R => (:rec, :F_NONE, :N)), + # parameters + (:c, :beta, :tRec), + # dynamical variables + (:v_prevalence => ((:I, :N) => :/), + :v_meanInfectiousContactsPerS => ((:c, :v_prevalence) => :*), + :v_perSIncidenceRate => ((:beta, :v_meanInfectiousContactsPerS) => :*), + :v_newInfections => ((:S, :v_perSIncidenceRate) => :*), + :v_newRecovery => ((:I, :tRec) => :/), + ), + # flows + (:inf => :v_newInfections, :rec => :v_newRecovery), + # sum dynamical variables + (:N), + ) + @test SIR_1_via_macro == SIR_1_canonical + + SIR_2 = @stock_and_flow begin + :stocks + S + I + R + + :parameters + c + beta + tRec + + # We can leave out dynamic variables and let them be inferred from flows entirely! + + :flows + S => inf(S * beta * (c * (I / N))) => I + I => rec(I / tRec) => R + + :sums + N = [S, I, R] + end + + # Although the variable names are different + # due to gensym, the models should structurally be the same. + for part in keys(SIR_2.parts) + @test SIR_2.parts[part] == SIR_1_canonical.parts[part] + end end @testset "stock_and_flow macro base cases" begin - empty_via_macro = @stock_and_flow begin end - @test empty_via_macro == StockAndFlowF() - - no_sums = @stock_and_flow begin - :stocks - A - B - C - - :parameters - p - q - - :dynamic_variables - dyvar1 = A + B - dyvar2 = B * C - dyvar3 = sqrt(q) - dyvar4 = exp(p) - dyvar5 = log(dyvar3, dyvar4) - - :flows - A => f1(dyvar1) => B - B => f2(dyvar2) => C - C => f3(dyvar5) => A - end - no_sums_canonical = StockAndFlowF( - #stocks - (:A => (:f3, :f1, :SV_NONE), :B => (:f1, :f2, :SV_NONE), :C => (:f2, :f3, :SV_NONE)), - #params - (:p, :q), - # dyvars - (:dyvar1 => ((:A, :B) => :+), - :dyvar2 => ((:B, :C) => :*), - :dyvar3 => (:q => :sqrt), - :dyvar4 => (:p => :exp), - :dyvar5 => ((:dyvar3, :dyvar4) => :log)), - #flows - (:f1 => :dyvar1, - :f2 => :dyvar2, - :f3 => :dyvar5), - ()) - @test no_sums == no_sums_canonical + empty_via_macro = @stock_and_flow begin end + @test empty_via_macro == StockAndFlowF() + + no_sums = @stock_and_flow begin + :stocks + A + B + C + + :parameters + p + q + + :dynamic_variables + dyvar1 = A + B + dyvar2 = B * C + dyvar3 = sqrt(q) + dyvar4 = exp(p) + dyvar5 = log(dyvar3, dyvar4) + + :flows + A => f1(dyvar1) => B + B => f2(dyvar2) => C + C => f3(dyvar5) => A + end + no_sums_canonical = StockAndFlowF( + #stocks + (:A => (:f3, :f1, :SV_NONE), :B => (:f1, :f2, :SV_NONE), :C => (:f2, :f3, :SV_NONE)), + #params + (:p, :q), + # dyvars + (:dyvar1 => ((:A, :B) => :+), + :dyvar2 => ((:B, :C) => :*), + :dyvar3 => (:q => :sqrt), + :dyvar4 => (:p => :exp), + :dyvar5 => ((:dyvar3, :dyvar4) => :log)), + #flows + (:f1 => :dyvar1, + :f2 => :dyvar2, + :f3 => :dyvar5), + ()) + @test no_sums == no_sums_canonical end @testset "is_recursive_dyvar detects recursive dyvars" begin - @test is_recursive_dyvar(:v, :(v + v)) - @test is_recursive_dyvar(:a, :(b + c + d / (e + f + g / (h + i + j / (a - b * a))))) + @test is_recursive_dyvar(:v, :(v + v)) + @test is_recursive_dyvar(:a, :(b + c + d / (e + f + g / (h + i + j / (a - b * a))))) end @testset "is_recursive_dyvar does not flag non-recursive dyvars" begin - @test !is_recursive_dyvar(:w, :(v + v)) - @test !is_recursive_dyvar(:z, :(b + c + d / (e + f + g / (h + i + j / (a - b * a))))) + @test !is_recursive_dyvar(:w, :(v + v)) + @test !is_recursive_dyvar(:z, :(b + c + d / (e + f + g / (h + i + j / (a - b * a))))) end @testset "recursive definitions should be disallowed" begin - @test_throws Exception @stock_and_flow begin - :dynamic_variables - v = v + v - end + @test_throws Exception @stock_and_flow begin + :dynamic_variables + v = v + v + end 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 + @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 A11 => B22) == foot(:A11, :B22, :A11 => :B22) - - @test (@foot () => B, A => ()) == foot(:A, :B, ()) - @test (@foot A => B, A => C) == foot(:A, (:B, :C), (:A => :B, :A => :C)) - @test (@foot A => B, A => B, A => B) == foot(:A, :B, (:A => :B, :A => :B, :A => :B)) # at present, it deduplicates stocks and sums, but not links. - @test (@foot P => Q, R => (), () => ()) == foot((:P, :R), (:Q), (:P => :Q)) - @test (@foot () => (), () => ()) == foot((), (), ()) + @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 A11 => B22) == foot(:A11, :B22, :A11 => :B22) + + @test (@foot () => B, A => ()) == foot(:A, :B, ()) + @test (@foot A => B, A => C) == foot(:A, (:B, :C), (:A => :B, :A => :C)) + @test (@foot A => B, A => B, A => B) == foot(:A, :B, (:A => :B, :A => :B, :A => :B)) # at present, it deduplicates stocks and sums, but not links. + @test (@foot P => Q, R => (), () => ()) == foot((:P, :R), (:Q), (:P => :Q)) + @test (@foot () => (), () => ()) == foot((), (), ()) end @testset "foot syntax disallows invalid feet" begin # note, @feet calls create_foot for each line, so this should apply to both @foot and @feet - @test_throws Exception create_foot(:(A => B => C)) # Invalid syntax for second argument of foot: B => C - @test_throws Exception create_foot(:(oooo2 + f => C)) # Invalid syntax for first argument of foot: oooo2 + f - @test_throws Exception create_foot(:(A + B)) # Invalid syntax function for foot: + - @test_throws Exception create_foot(:(=>)) # no method matching create_foot(::Symbol) - @test_throws Exception create_foot(:(=>(A, B, C, D))) - @test_throws Exception create_foot(:()) - @test_throws Exception create_foot(:(([]) => ())) + @test_throws Exception create_foot(:(A => B => C)) # Invalid syntax for second argument of foot: B => C + @test_throws Exception create_foot(:(oooo2 + f => C)) # Invalid syntax for first argument of foot: oooo2 + f + @test_throws Exception create_foot(:(A + B)) # Invalid syntax function for foot: + + @test_throws Exception create_foot(:(=>)) # no method matching create_foot(::Symbol) + @test_throws Exception create_foot(:(=>(A, B, C, D))) + @test_throws Exception create_foot(:()) + @test_throws Exception create_foot(:(([]) => ())) - @test_throws Exception create_foot(:(A => B, P => Q, C)) - @test_throws Exception create_foot(:(() => E, () => (K,))) + @test_throws Exception create_foot(:(A => B, P => Q, C)) + @test_throws Exception create_foot(:(() => E, () => (K,))) end @testset "feet syntax can create feet" begin - @test (@feet begin + @test (@feet begin - A => B - C => D - () => () + A => B + C => D + () => () - P => () - () => Q + P => () + () => Q - end) == [foot(:A, :B, :A => :B), foot(:C, :D, :C => :D), foot((),(),()), foot(:P, (),()), foot((),:Q,())] + end) == [foot(:A, :B, :A => :B), foot(:C, :D, :C => :D), foot((),(),()), foot(:P, (),()), foot((),:Q,())] - @test (@feet P => Q) == [foot(:P, :Q, :P => :Q)] - @test (@feet begin end) == Vector{StockAndFlow0}() - @test (@feet begin P => Q; L => R end) == [foot(:P, :Q, :P => :Q), foot(:L, :R, :L => :R)] + @test (@feet P => Q) == [foot(:P, :Q, :P => :Q)] + @test (@feet begin end) == Vector{StockAndFlow0}() + @test (@feet begin P => Q; L => R end) == [foot(:P, :Q, :P => :Q), foot(:L, :R, :L => :R)] - @test (@feet P => P) == [foot(:P, :P, :P => :P)] + @test (@feet P => P) == [foot(:P, :P, :P => :P)] - @test (@feet begin A => NS, A => N; B => NS; C => (), D => () end) == [ - foot((:A), (:NS, :N), (:A => :NS, :A => :N)), - foot((:B), (:NS), (:B => :NS)), - foot((:C, :D), (), ()) - ] - - @test (@feet begin - P => Q, R => () - () => () - J => K, J => Q - end) == [ - foot((:P, :R), :Q, :P => :Q), - foot((), (), ()), - foot(:J, (:K, :Q), (:J => :K, :J => :Q)) + @test (@feet begin A => NS, A => N; B => NS; C => (), D => () end) == [ + foot((:A), (:NS, :N), (:A => :NS, :A => :N)), + foot((:B), (:NS), (:B => :NS)), + foot((:C, :D), (), ()) ] + @test (@feet begin + P => Q, R => () + () => () + J => K, J => Q + end) == [ + foot((:P, :R), :Q, :P => :Q), + foot((), (), ()), + foot(:J, (:K, :Q), (:J => :K, :J => :Q)) + ] + end @testset "feet syntax fails on invalid feet" begin # mostly to check that an exception is thrown even if some of the feet are valid. - @test_throws Exception @eval @feet A => B => C # eval required so the errors occur at runtime, rather than at compilation - @test_throws Exception @eval @feet begin A => B; =>(D,E,F) end - @test_throws Exception @eval @feet begin A => B; 1 => 2; end + @test_throws Exception @eval @feet A => B => C # eval required so the errors occur at runtime, rather than at compilation + @test_throws Exception @eval @feet begin A => B; =>(D,E,F) end + @test_throws Exception @eval @feet begin A => B; 1 => 2; end end ########################### @testset "infer_links works as expected" begin - # No prior mappings means no inferred mappings - @test (infer_links(StockAndFlowF(), StockAndFlowF(), Dict{Symbol, Vector{Int64}}(:S => [], :F => [], :SV => [], :P => [], :V => [])) - == Dict(:LS => [], :LSV => [], :LV => [], :I => [], :O => [], :LPV => [], :LVV => [])) - - # S: 1 -> 1 and SV: 1 -> 1 implies LS: 1 -> 1 - @test (infer_links( - (@stock_and_flow begin; :stocks; A; :sums; NA = [A]; end), - (@stock_and_flow begin; :stocks; B; :sums; NB = [B]; end), - Dict{Symbol, Vector{Int64}}(:S => [1], :F => [], :SV => [1], :P => [], :V => [])) - == Dict(:LS => [1], :LSV => [], :LV => [], :I => [], :O => [], :LPV => [], :LVV => [])) - - # annoying exanmple, required me to add code to disambiguate using position - # that is, vA = A + A, vA -> vB, A -> implies that the As in the vA definition map to the Bs in the vB definition - # But both As link to the same stock and dynamic variable so just looking at those isn't enough to figure out what it maps to. - # There will exist cases where it's impossible to tell - eg, when there exist multiple duplicate links, and some positions don't match up. + # No prior mappings means no inferred mappings + @test (infer_links(StockAndFlowF(), StockAndFlowF(), Dict{Symbol, Vector{Int64}}(:S => [], :F => [], :SV => [], :P => [], :V => [])) + == Dict(:LS => [], :LSV => [], :LV => [], :I => [], :O => [], :LPV => [], :LVV => [])) + + # S: 1 -> 1 and SV: 1 -> 1 implies LS: 1 -> 1 + @test (infer_links( + (@stock_and_flow begin; :stocks; A; :sums; NA = [A]; end), + (@stock_and_flow begin; :stocks; B; :sums; NB = [B]; end), + Dict{Symbol, Vector{Int64}}(:S => [1], :F => [], :SV => [1], :P => [], :V => [])) + == Dict(:LS => [1], :LSV => [], :LV => [], :I => [], :O => [], :LPV => [], :LVV => [])) + + # annoying exanmple, required me to add code to disambiguate using position + # that is, vA = A + A, vA -> vB, A -> implies that the As in the vA definition map to the Bs in the vB definition + # But both As link to the same stock and dynamic variable so just looking at those isn't enough to figure out what it maps to. + # There will exist cases where it's impossible to tell - eg, when there exist multiple duplicate links, and some positions don't match up. - # It does not currently look at the operator. You could therefore map vA = A + A -> vB = B * B - # I can see this being useful, actually, specifically when mapping between + and -, * and /, etc. Probably logs and powers too. - # Just need to be aware that it won't say it's invalid. - @test (infer_links( - (@stock_and_flow begin; :stocks; A; :dynamic_variables; vA = A + A; end), - (@stock_and_flow begin; :stocks; B; :dynamic_variables; vB = B + B; end), - Dict{Symbol, Vector{Int64}}(:S => [1], :F => [], :SV => [], :P => [], :V => [1])) - == Dict(:LS => [], :LSV => [], :LV => [2,2], :I => [], :O => [], :LPV => [], :LVV => [])) # If duplicate values, always map to end. - - @test (infer_links( - (@stock_and_flow begin; :stocks; A; :parameters; pA; :dynamic_variables; vA = A + pA; end), - (@stock_and_flow begin; :stocks; B; :parameters; pB; :dynamic_variables; vB = pB + B; end), - Dict{Symbol, Vector{Int64}}(:S => [1], :F => [], :SV => [], :P => [1], :V => [1])) - == Dict(:LS => [], :LSV => [], :LV => [1], :I => [], :O => [], :LPV => [1], :LVV => [])) - - @test (infer_links( - (@stock_and_flow begin - :stocks - S - I - R - - :parameters - p_inf - p_rec - - - :flows - S => f_StoI(p_inf * S) => I - I => f_ItoR(I * p_rec) => R - - :sums - N = [S,I,R] - NI = [I] - NS = [S,I,R] - end), - (@stock_and_flow begin - :stocks - pop - - :parameters - p_generic - - - :flows - pop => f_generic(p_generic * pop) => pop - - :sums - N = [pop] - NI = [pop] - NS = [pop] - end), - - Dict{Symbol, Vector{Int64}}(:S => [1,1,1], :F => [1,1], :SV => [1,2,3], :P => [1,1], :V => [1,1])) - == Dict(:LS => [1,3,1,2,3,1,3], :LSV => [], :LV => [1,1], :I => [1,1], :O => [1,1], :LPV => [1,1], :LVV => [])) + # It does not currently look at the operator. You could therefore map vA = A + A -> vB = B * B + # I can see this being useful, actually, specifically when mapping between + and -, * and /, etc. Probably logs and powers too. + # Just need to be aware that it won't say it's invalid. + @test (infer_links( + (@stock_and_flow begin; :stocks; A; :dynamic_variables; vA = A + A; end), + (@stock_and_flow begin; :stocks; B; :dynamic_variables; vB = B + B; end), + Dict{Symbol, Vector{Int64}}(:S => [1], :F => [], :SV => [], :P => [], :V => [1])) + == Dict(:LS => [], :LSV => [], :LV => [2,2], :I => [], :O => [], :LPV => [], :LVV => [])) # If duplicate values, always map to end. + + @test (infer_links( + (@stock_and_flow begin; :stocks; A; :parameters; pA; :dynamic_variables; vA = A + pA; end), + (@stock_and_flow begin; :stocks; B; :parameters; pB; :dynamic_variables; vB = pB + B; end), + Dict{Symbol, Vector{Int64}}(:S => [1], :F => [], :SV => [], :P => [1], :V => [1])) + == Dict(:LS => [], :LSV => [], :LV => [1], :I => [], :O => [], :LPV => [1], :LVV => [])) + + @test (infer_links( + (@stock_and_flow begin + :stocks + S + I + R + + :parameters + p_inf + p_rec + + + :flows + S => f_StoI(p_inf * S) => I + I => f_ItoR(I * p_rec) => R + + :sums + N = [S,I,R] + NI = [I] + NS = [S,I,R] + end), + (@stock_and_flow begin + :stocks + pop + + :parameters + p_generic + + + :flows + pop => f_generic(p_generic * pop) => pop + + :sums + N = [pop] + NI = [pop] + NS = [pop] + end), + + Dict{Symbol, Vector{Int64}}(:S => [1,1,1], :F => [1,1], :SV => [1,2,3], :P => [1,1], :V => [1,1])) + == Dict(:LS => [1,3,1,2,3,1,3], :LSV => [], :LV => [1,1], :I => [1,1], :O => [1,1], :LPV => [1,1], :LVV => [])) end @testset "Applying flags can correctly find substring matches" begin - @test apply_flags(:f_, Set([:~]), Vector{Symbol}()) == [] - @test apply_flags(:f_, Set([:~]), [:f_death, :f_birth]) == [:f_death, :f_birth] - @test apply_flags(:NOMATCH, Set([:~]), [:f_death, :f_birth]) == [] - @test apply_flags(:f_birth, Set([:~]), [:f_death, :f_birth]) == [:f_birth] - @test apply_flags(:f_birth, Set{Symbol}(), [:f_death, :f_birth]) == [:f_birth] - - # Note, apply_flags is specifically meant to work on vectors without duplicates; the vector which is input are the keys of a dictionary. - # Regardless, the following will hold: - @test apply_flags(:f_birth, Set{Symbol}(), [:f_death, :f_birth, :f_birth, :f_birth]) == [:f_birth] - @test apply_flags(:f_birth, Set{Symbol}([:~]), [:f_death, :f_birth, :f_birth, :f_birth]) == [:f_birth, :f_birth, :f_birth] + @test apply_flags(:f_, Set([:~]), Vector{Symbol}()) == [] + @test apply_flags(:f_, Set([:~]), [:f_death, :f_birth]) == [:f_death, :f_birth] + @test apply_flags(:NOMATCH, Set([:~]), [:f_death, :f_birth]) == [] + @test apply_flags(:f_birth, Set([:~]), [:f_death, :f_birth]) == [:f_birth] + @test apply_flags(:f_birth, Set{Symbol}(), [:f_death, :f_birth]) == [:f_birth] + + # Note, apply_flags is specifically meant to work on vectors without duplicates; the vector which is input are the keys of a dictionary. + # Regardless, the following will hold: + @test apply_flags(:f_birth, Set{Symbol}(), [:f_death, :f_birth, :f_birth, :f_birth]) == [:f_birth] + @test apply_flags(:f_birth, Set{Symbol}([:~]), [:f_death, :f_birth, :f_birth, :f_birth]) == [:f_birth, :f_birth, :f_birth] end @testset "substitute_symbols will correctly associate values of the two provided dictionaries based on user defined mappings" begin - # substitute_symbols(s::Dict{Symbol, Int}, t::Dict{Symbol, Int}, m::Vector{DSLArgument} ; use_flags::Bool=true)::Dict{Int, Int} + # substitute_symbols(s::Dict{Symbol, Int}, t::Dict{Symbol, Int}, m::Vector{DSLArgument} ; use_flags::Bool=true)::Dict{Int, Int} - # Note, these dictionaries represent a vector where all the entries are unique, and the values are the original indices. - # So, both keys and values should be unique. - # For stratification, first dictionary is strata or aggregate, second is type, and the vector of DSLArgument are the user-defined maps. - # For homomorphism, first argument is src, second is dest, vector are user-defined maps. - @test substitute_symbols(Dict{Symbol, Int}(), Dict{Symbol, Int}(), Vector{DSLArgument}()) == Dict{Int, Int}() - @test substitute_symbols(Dict{Symbol, Int}(), Dict(:B => 2), Vector{DSLArgument}()) == Dict{Int, Int}() + # Note, these dictionaries represent a vector where all the entries are unique, and the values are the original indices. + # So, both keys and values should be unique. + # For stratification, first dictionary is strata or aggregate, second is type, and the vector of DSLArgument are the user-defined maps. + # For homomorphism, first argument is src, second is dest, vector are user-defined maps. + @test substitute_symbols(Dict{Symbol, Int}(), Dict{Symbol, Int}(), Vector{DSLArgument}()) == Dict{Int, Int}() + @test substitute_symbols(Dict{Symbol, Int}(), Dict(:B => 2), Vector{DSLArgument}()) == Dict{Int, Int}() - @test substitute_symbols(Dict(:A => 1), Dict(:B => 1), [DSLArgument(:A, :B, Set{Symbol}())]) == Dict(1 => 1) - @test substitute_symbols(Dict(:A1 => 1, :A2 => 2), Dict(:B => 1), [DSLArgument(:A1, :B, Set{Symbol}()), DSLArgument(:A2, :B, Set{Symbol}())]) == Dict(1 => 1, 2 => 1) - @test substitute_symbols(Dict(:A1 => 1), Dict(:B1 => 1, :B2 => 2), [DSLArgument(:A1, :B2, Set{Symbol}())]) == Dict(1 => 2) + @test substitute_symbols(Dict(:A => 1), Dict(:B => 1), [DSLArgument(:A, :B, Set{Symbol}())]) == Dict(1 => 1) + @test substitute_symbols(Dict(:A1 => 1, :A2 => 2), Dict(:B => 1), [DSLArgument(:A1, :B, Set{Symbol}()), DSLArgument(:A2, :B, Set{Symbol}())]) == Dict(1 => 1, 2 => 1) + @test substitute_symbols(Dict(:A1 => 1), Dict(:B1 => 1, :B2 => 2), [DSLArgument(:A1, :B2, Set{Symbol}())]) == Dict(1 => 2) - @test substitute_symbols(Dict(:A1 => 1, :A2 => 2), Dict(:B1 => 1, :B2 => 2), [DSLArgument(:A, :B2, Set{Symbol}([:~]))]) == Dict(1 => 2, 2 => 2) + @test substitute_symbols(Dict(:A1 => 1, :A2 => 2), Dict(:B1 => 1, :B2 => 2), [DSLArgument(:A, :B2, Set{Symbol}([:~]))]) == Dict(1 => 2, 2 => 2) - # 1:100 - # 1:50 - # All multiples x of 14 below 100 go to x % 10 + 1 - @test (substitute_symbols(Dict(Symbol(i) => i for i ∈ 1:100), Dict(Symbol(-i) => i for i ∈ 1:50), [DSLArgument(Symbol(i), Symbol(-((i%10) + 1)), Set{Symbol}()) for i ∈ 1:100 if i % 14 == 0]) - == Dict(14 => 5, 28 => 9, 42 => 3, 56 => 7, 70 => 1, 84 => 5, 98 => 9)) + # 1:100 + # 1:50 + # All multiples x of 14 below 100 go to x % 10 + 1 + @test (substitute_symbols(Dict(Symbol(i) => i for i ∈ 1:100), Dict(Symbol(-i) => i for i ∈ 1:50), [DSLArgument(Symbol(i), Symbol(-((i%10) + 1)), Set{Symbol}()) for i ∈ 1:100 if i % 14 == 0]) + == Dict(14 => 5, 28 => 9, 42 => 3, 56 => 7, 70 => 1, 84 => 5, 98 => 9)) - # Captures everything with a 7 as a digit - @test (substitute_symbols(Dict(Symbol(i) => i for i ∈ 1:100), Dict(Symbol(-i) => i for i ∈ 1:50), [DSLArgument(Symbol(7), Symbol(-1), Set{Symbol}([:~]))]) - == Dict(7 => 1, 17 => 1, 27 => 1, 37 => 1, 47 => 1, 57 => 1, 67 => 1, 70 => 1, 71 => 1, 72 => 1, 73 => 1, 74 => 1, 75 => 1, 76 => 1, 77 => 1, 78 => 1, 79 => 1, 87 => 1, 97 => 1)) + # Captures everything with a 7 as a digit + @test (substitute_symbols(Dict(Symbol(i) => i for i ∈ 1:100), Dict(Symbol(-i) => i for i ∈ 1:50), [DSLArgument(Symbol(7), Symbol(-1), Set{Symbol}([:~]))]) + == Dict(7 => 1, 17 => 1, 27 => 1, 37 => 1, 47 => 1, 57 => 1, 67 => 1, 70 => 1, 71 => 1, 72 => 1, 73 => 1, 74 => 1, 75 => 1, 76 => 1, 77 => 1, 78 => 1, 79 => 1, 87 => 1, 97 => 1)) - @test substitute_symbols(Dict(Symbol("~") => 1), Dict(:R => 1), [DSLArgument(Symbol("~"), :R, Set([:~]))], ; use_flags = false) == Dict(1 => 1) # Note, the Set([:~]) is ignored because use_flags is false + @test substitute_symbols(Dict(Symbol("~") => 1), Dict(:R => 1), [DSLArgument(Symbol("~"), :R, Set([:~]))], ; use_flags = false) == Dict(1 => 1) # Note, the Set([:~]) is ignored because use_flags is false end @testset "non-natural transformations fail infer_links" begin - # Map both dynamic variables to the same - # Obviously, this will fail, as the new dynamic variable needs a LVV and one LV, but instead has two LV - @test_throws KeyError (infer_links( - (@stock_and_flow begin - :stocks - A - - :dynamic_variables - v1 = A + A - v2 = v1 + A - end), + # Map both dynamic variables to the same + # Obviously, this will fail, as the new dynamic variable needs a LVV and one LV, but instead has two LV + @test_throws KeyError (infer_links( (@stock_and_flow begin - :stocks - A - - :dynamic_variables - v1 = A + A - end), - Dict{Symbol, Vector{Int64}}(:S => [1], :V => [1,1]))) - + :stocks + A + + :dynamic_variables + v1 = A + A + v2 = v1 + A + end), + (@stock_and_flow begin + :stocks + A + :dynamic_variables + v1 = A + A + end), + Dict{Symbol, Vector{Int64}}(:S => [1], :V => [1,1]))) - # Mapping it all to I + - # This one fails when trying to figure out the inflow. Stock maps to 2, and flow maps to 2, - # But inflows on the target have (1,2) and (2,3) + # Mapping it all to I - # This also wouldn't work if we tried mapping flow to 1 instead. Outflows expect 1,1 or 2,2, - # so it fails on (2,1). - @test_throws KeyError (infer_links( - (@stock_and_flow begin - :stocks - pop + # This one fails when trying to figure out the inflow. Stock maps to 2, and flow maps to 2, + # But inflows on the target have (1,2) and (2,3) - :parameters - p_generic + # This also wouldn't work if we tried mapping flow to 1 instead. Outflows expect 1,1 or 2,2, + # so it fails on (2,1). + @test_throws KeyError (infer_links( + (@stock_and_flow begin + :stocks + pop + :parameters + p_generic - :flows - pop => f_generic(p_generic * pop) => pop - :sums - N = [pop] - NI = [pop] - NS = [pop] - end), - (@stock_and_flow begin - :stocks - S - I - R + :flows + pop => f_generic(p_generic * pop) => pop - :parameters - p_inf - p_rec + :sums + N = [pop] + NI = [pop] + NS = [pop] + end), + (@stock_and_flow begin + :stocks + S + I + R + :parameters + p_inf + p_rec - :flows - S => f_StoI(p_inf * S) => I - I => f_ItoR(I * p_rec) => R - :sums - N = [S,I,R] - NI = [I] - NS = [S,I,R] - end), - Dict{Symbol, Vector{Int64}}(:S => [2], :F => [2], :SV => [1,2,3], :P => [2], :V => [2]))) + :flows + S => f_StoI(p_inf * S) => I + I => f_ItoR(I * p_rec) => R + + :sums + N = [S,I,R] + NI = [I] + NS = [S,I,R] + end), + Dict{Symbol, Vector{Int64}}(:S => [2], :F => [2], :SV => [1,2,3], :P => [2], :V => [2]))) end @testset "Applying flags throws on invalid inputs" begin - @test_throws ErrorException apply_flags(:f_, Set([:+]), [:f_death, :f_birth]) # fails because :+ is not a defined operation - @test_throws ErrorException apply_flags(:f_birth, Set([:~, :+]), [:f_death, :f_birth]) # also fails for same reason + @test_throws ErrorException apply_flags(:f_, Set([:+]), [:f_death, :f_birth]) # fails because :+ is not a defined operation + @test_throws ErrorException apply_flags(:f_birth, Set([:~, :+]), [:f_death, :f_birth]) # also fails for same reason - @test_throws AssertionError apply_flags(:NOMATCH, Set{Symbol}(), Vector{Symbol}()) # fails because it's not looking for substrings, and :NOMATCH isn't in the list of options. - @test_throws AssertionError apply_flags(:NOMATCH, Set{Symbol}(), [:nomatch]) # same reason + @test_throws AssertionError apply_flags(:NOMATCH, Set{Symbol}(), Vector{Symbol}()) # fails because it's not looking for substrings, and :NOMATCH isn't in the list of options. + @test_throws AssertionError apply_flags(:NOMATCH, Set{Symbol}(), [:nomatch]) # same reason end \ No newline at end of file diff --git a/test/SystemStructure.jl b/test/SystemStructure.jl index 79e08abc..2ac12f8d 100644 --- a/test/SystemStructure.jl +++ b/test/SystemStructure.jl @@ -5,63 +5,63 @@ using StockFlow.Syntax empty = @stock_and_flow begin end p = @stock_and_flow begin - :stocks - S - I + :stocks + S + I - :dynamic_variables - v = S + I + :dynamic_variables + v = S + I - :parameters - p1 - p2 + :parameters + p1 + p2 - :flows - S => f1(v) => CLOUD + :flows + S => f1(v) => CLOUD - :sums - N = [S] - NI = [I] + :sums + N = [S] + NI = [I] end p_prefixed = @stock_and_flow begin - :stocks - prefS - prefI + :stocks + prefS + prefI - :dynamic_variables - prefv = prefS + prefI + :dynamic_variables + prefv = prefS + prefI - :parameters - prefp1 - prefp2 + :parameters + prefp1 + prefp2 - :flows - prefS => preff1(prefv) => CLOUD + :flows + prefS => preff1(prefv) => CLOUD - :sums - prefN = [prefS] - prefNI = [prefI] + :sums + prefN = [prefS] + prefNI = [prefI] end p_suffixed = @stock_and_flow begin - :stocks - Ssuf - Isuf + :stocks + Ssuf + Isuf - :dynamic_variables - vsuf = Ssuf + Isuf + :dynamic_variables + vsuf = Ssuf + Isuf - :parameters - p1suf - p2suf + :parameters + p1suf + p2suf - :flows - Ssuf => f1suf(vsuf) => CLOUD + :flows + Ssuf => f1suf(vsuf) => CLOUD - :sums - Nsuf = [Ssuf] - NIsuf = [Isuf] + :sums + Nsuf = [Ssuf] + NIsuf = [Isuf] end empty_foot = @foot () => () @@ -72,12 +72,12 @@ footA_suf = @foot Ssuf => Nsuf @testset "changing names act the same as if the stock flow/foot/open was created with the changed name" begin - @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 == 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 + @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 diff --git a/test/runtests.jl b/test/runtests.jl index db5631f2..949da5cc 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -2,9 +2,9 @@ using Test using StockFlow @testset "StockFlow DSL" begin - include("Syntax.jl") + include("Syntax.jl") end @testset "Attribute names prefixes and suffixes" begin - include("SystemStructure.jl") + include("SystemStructure.jl") end \ No newline at end of file diff --git a/test/syntax/Composition.jl b/test/syntax/Composition.jl index 34293096..05264c8c 100644 --- a/test/syntax/Composition.jl +++ b/test/syntax/Composition.jl @@ -5,105 +5,105 @@ using StockFlow.Syntax.Composition import StockFlow.Syntax.Composition: interpret_composition_notation @testset "Composition creates expected stock flows" begin - empty_sf = StockAndFlowF() - - - @test (@compose (begin # composing no stock flows returns an empty stock flow. - () - end)) == empty_sf - - @test (@compose empty_sf begin - (sf,) - end) == empty_sf - - @test (@compose (@stock_and_flow begin; :stocks; A; end;) (@stock_and_flow begin; :stocks; B; end;) (begin - (sf1, sf2) - end)) == (@stock_and_flow begin; :stocks; A; B; end;) # Combining without any composing - - @test (@compose (@stock_and_flow begin; :stocks; A; end;) (@stock_and_flow begin; :stocks; A; end;) (begin - (sf1, sf2) - end)) == (@stock_and_flow begin; :stocks; A; A; end;) - - @test (@compose (@stock_and_flow begin; :stocks; A; end;) (@stock_and_flow begin; :stocks; A; end;) (begin - (sf1, sf2) - sf1, sf2 ^ A => () - end)) == (@stock_and_flow begin; :stocks; A; end;) - - @test ((@compose (@stock_and_flow begin - :stocks - A - B - - :dynamic_variables - v1 = A + B - - :sums - N = [A,B] - end) (@stock_and_flow begin - :stocks - B - C - - :dynamic_variables - v2 = B + C - - :sums - N = [B,C] - end) (begin - (sfA, sfC) - sfA, sfC ^ B => N - end)) - == - (@stock_and_flow begin - :stocks - A - B - C - - :dynamic_variables - v1 = A + B - v2 = B + C - - :sums - N = [A, B, C] - end)) - + empty_sf = StockAndFlowF() + + + @test (@compose (begin # composing no stock flows returns an empty stock flow. + () + end)) == empty_sf + + @test (@compose empty_sf begin + (sf,) + end) == empty_sf + + @test (@compose (@stock_and_flow begin; :stocks; A; end;) (@stock_and_flow begin; :stocks; B; end;) (begin + (sf1, sf2) + end)) == (@stock_and_flow begin; :stocks; A; B; end;) # Combining without any composing + + @test (@compose (@stock_and_flow begin; :stocks; A; end;) (@stock_and_flow begin; :stocks; A; end;) (begin + (sf1, sf2) + end)) == (@stock_and_flow begin; :stocks; A; A; end;) + + @test (@compose (@stock_and_flow begin; :stocks; A; end;) (@stock_and_flow begin; :stocks; A; end;) (begin + (sf1, sf2) + sf1, sf2 ^ A => () + end)) == (@stock_and_flow begin; :stocks; A; end;) + + @test ((@compose (@stock_and_flow begin + :stocks + A + B + + :dynamic_variables + v1 = A + B + + :sums + N = [A,B] + end) (@stock_and_flow begin + :stocks + B + C + + :dynamic_variables + v2 = B + C + + :sums + N = [B,C] + end) (begin + (sfA, sfC) + sfA, sfC ^ B => N + end)) + == + (@stock_and_flow begin + :stocks + A + B + C + + :dynamic_variables + v1 = A + B + v2 = B + C + + :sums + N = [A, B, C] + end)) + end @testset "interpret_composition_notation interprets arguments correctly" begin - # @test interpret_composition_notation(:(() ^ A => N)) == (Vector{Symbol}(), (@foot A => N)) - @test interpret_composition_notation(:(sf ^ A => N)) == ([:sf], (@foot A => N)) - @test interpret_composition_notation(:(sf1, sf2 ^ A => N)) == ([:sf1,:sf2], (@foot A => N)) - @test interpret_composition_notation(:(sf1, sf2 ^ A => N, A => NI)) == ([:sf1,:sf2], (@foot A => N, A => NI)) - @test interpret_composition_notation(:(sf1, sf2, sf3, sf4 ^ () => NI)) == ([:sf1, :sf2, :sf3, :sf4], (@foot () => NI)) - @test interpret_composition_notation(:(sf1, sf2 ^ L => ())) == ([:sf1,:sf2], (@foot L => ())) + # @test interpret_composition_notation(:(() ^ A => N)) == (Vector{Symbol}(), (@foot A => N)) + @test interpret_composition_notation(:(sf ^ A => N)) == ([:sf], (@foot A => N)) + @test interpret_composition_notation(:(sf1, sf2 ^ A => N)) == ([:sf1,:sf2], (@foot A => N)) + @test interpret_composition_notation(:(sf1, sf2 ^ A => N, A => NI)) == ([:sf1,:sf2], (@foot A => N, A => NI)) + @test interpret_composition_notation(:(sf1, sf2, sf3, sf4 ^ () => NI)) == ([:sf1, :sf2, :sf3, :sf4], (@foot () => NI)) + @test interpret_composition_notation(:(sf1, sf2 ^ L => ())) == ([:sf1,:sf2], (@foot L => ())) - @test interpret_composition_notation(:(sf1, sf2 ^ () => ())) == ([:sf1,:sf2], (@foot () => ())) + @test interpret_composition_notation(:(sf1, sf2 ^ () => ())) == ([:sf1,:sf2], (@foot () => ())) end @testset "invalid composition expressions fail" begin - @test_throws AssertionError interpret_composition_notation(:(B => C)) - @test_throws AssertionError interpret_composition_notation(:(A, B, C)) - @test_throws AssertionError interpret_composition_notation(:(A ^ B ^ C)) - @test_throws AssertionError interpret_composition_notation(:(A => B => C)) - @test_throws ErrorException interpret_composition_notation(:(A ^ B => C => D)) # caught by create_foot + @test_throws AssertionError interpret_composition_notation(:(B => C)) + @test_throws AssertionError interpret_composition_notation(:(A, B, C)) + @test_throws AssertionError interpret_composition_notation(:(A ^ B ^ C)) + @test_throws AssertionError interpret_composition_notation(:(A => B => C)) + @test_throws ErrorException interpret_composition_notation(:(A ^ B => C => D)) # caught by create_foot end @testset "invalid sfcompose calls fail" begin - @test_throws AssertionError sfcompose([(@stock_and_flow begin; :stocks; A; end;), (@stock_and_flow begin; :stocks; A; end;)], quote - (sf1, sf2) - sf1, sf2 ^ () => () - end) # not allowed to map to empty - @test_throws AssertionError sfcompose([(@stock_and_flow begin; :stocks; A; end;), (@stock_and_flow begin; :stocks; A; end;)], quote - (sf1, sf2) - sf1 ^ A => () - sf2 ^ A => () - end) # not allowed to map to the same foot twice - @test_throws AssertionError sfcompose([(@stock_and_flow begin; :stocks; A; end;), (@stock_and_flow begin; :stocks; A; end;)], quote - (sf1,) - sf1 ^ A => () - end) # incorrect number of symbols on the first line in the quote + @test_throws AssertionError sfcompose([(@stock_and_flow begin; :stocks; A; end;), (@stock_and_flow begin; :stocks; A; end;)], quote + (sf1, sf2) + sf1, sf2 ^ () => () + end) # not allowed to map to empty + @test_throws AssertionError sfcompose([(@stock_and_flow begin; :stocks; A; end;), (@stock_and_flow begin; :stocks; A; end;)], quote + (sf1, sf2) + sf1 ^ A => () + sf2 ^ A => () + end) # not allowed to map to the same foot twice + @test_throws AssertionError sfcompose([(@stock_and_flow begin; :stocks; A; end;), (@stock_and_flow begin; :stocks; A; end;)], quote + (sf1,) + sf1 ^ A => () + end) # incorrect number of symbols on the first line in the quote end diff --git a/test/syntax/Stratification.jl b/test/syntax/Stratification.jl index bfdea7d2..52070dff 100755 --- a/test/syntax/Stratification.jl +++ b/test/syntax/Stratification.jl @@ -13,442 +13,442 @@ using Catlab.CategoricalAlgebra @testset "Pullback computed in standard way is equal to DSL pullbacks" begin - l_type = @stock_and_flow begin - :stocks - pop - - :parameters - μ - δ - rFstOrder - rage - - :dynamic_variables - v_aging = pop * rage - v_fstOrder = pop * rFstOrder - v_birth = N * μ - v_death = pop * δ - - :flows - pop => f_aging(v_aging) => pop - pop => f_fstOrder(v_fstOrder) => pop - CLOUD => f_birth(v_birth) => pop - pop => f_death(v_death) => CLOUD - - :sums - N = [pop] - - end; - l_type_noatts = map(l_type, Name=NothingFunction, Op=NothingFunction, Position=NothingFunction); - - - WeightModel = @stock_and_flow begin - :stocks - NormalWeight - OverWeight - Obese - - :parameters - μ - δw - rw - ro - δo - rage - - :dynamic_variables - v_NewBorn = N * μ - v_DeathNormalWeight = NormalWeight * δw - v_BecomingOverWeight = NormalWeight * rw - v_DeathOverWeight = OverWeight * δw - v_BecomingObese = OverWeight * ro - v_DeathObese = Obese * δo - v_idNW = NormalWeight * rage - v_idOW = OverWeight * rage - v_idOb = Obese * rage - - :flows - CLOUD => f_NewBorn(v_NewBorn) => NormalWeight - NormalWeight => f_DeathNormalWeight(v_DeathNormalWeight) => CLOUD - NormalWeight => f_BecomingOverWeight(v_BecomingOverWeight) => OverWeight - OverWeight => f_DeathOverWeight(v_DeathOverWeight) => CLOUD - - OverWeight => f_BecomingObese(v_BecomingObese) => Obese - Obese => f_DeathObese(v_DeathObese) => CLOUD - NormalWeight => f_idNW(v_idNW) => NormalWeight - OverWeight => f_idOW(v_idOW) => OverWeight - Obese => f_idOb(v_idOb) => Obese - - :sums - N = [NormalWeight, OverWeight, Obese] - - end; - - - ageWeightModel = @stock_and_flow begin - :stocks - Child - Adult - Senior - - :parameters - μ - δC - δA - δS - rageCA - rageAS - r - - :dynamic_variables - v_NB = N * μ - v_DeathC = Child * δC - v_idC = Child * r - v_agingCA = Child * rageCA - v_DeathA = Adult * δA - v_idA = Adult * r - v_agingAS = Adult * rageAS - v_DeathS = Senior * δS - v_idS = Senior * r - - :flows - CLOUD => f_NB(v_NB) => Child - Child => f_idC(v_idC) => Child - Child => f_DeathC(v_DeathC) => CLOUD - Child => f_agingCA(v_agingCA) => Adult - Adult => f_idA(v_idA) => Adult - Adult => f_DeathA(v_DeathA) => CLOUD - Adult => f_agingAS(v_agingAS) => Senior - Senior => f_idS(v_idS) => Senior - Senior => f_DeathS(v_DeathS) => CLOUD - - :sums - N = [Child, Adult, Senior] - - end; - - begin - s, = parts(l_type, :S) - N, = parts(l_type, :SV) - lsn, = parts(l_type, :LS) - f_aging, f_fstorder, f_birth, f_death = parts(l_type, :F) - i_aging, i_fstorder, i_birth = parts(l_type, :I) - o_aging, o_fstorder, o_death = parts(l_type, :O) - v_aging, v_fstorder, v_birth, v_death = parts(l_type, :V) - lv_aging1, lv_fstorder1, lv_death1 = parts(l_type, :LV) - lsv_birth1, = parts(l_type, :LSV) - p_μ, p_δ, p_rfstOrder, p_rage = parts(l_type, :P) - lpv_aging2, lpv_fstorder2, lpv_birth2, lpv_death2 = parts(l_type, :LPV) - end; - - typed_WeightModel=ACSetTransformation(WeightModel, l_type_noatts, - S = [s,s,s], - SV = [N], - LS = [lsn,lsn,lsn], - F = [f_birth, f_death, f_fstorder, f_death, f_fstorder, f_death, f_aging, f_aging, f_aging], - I = [i_birth, i_aging, i_fstorder, i_aging, i_fstorder, i_aging], - O = [o_death, o_fstorder, o_aging, o_death, o_fstorder, o_aging, o_death, o_aging], - V = [v_birth, v_death, v_fstorder, v_death, v_fstorder, v_death, v_aging, v_aging, v_aging], - LV = [lv_death1, lv_fstorder1, lv_death1, lv_fstorder1, lv_death1, lv_aging1, lv_aging1, lv_aging1], - LSV = [lsv_birth1], - P = [p_μ, p_δ, p_rfstOrder, p_rfstOrder, p_δ, p_rage], - LPV = [lpv_birth2, lpv_death2, lpv_fstorder2, lpv_death2, lpv_fstorder2, lpv_death2, lpv_aging2, lpv_aging2, lpv_aging2], - Name=NothingFunction, Op=NothingFunction, Position=NothingFunction - ); - @assert is_natural(typed_WeightModel); - - - - typed_ageWeightModel=ACSetTransformation(ageWeightModel, l_type_noatts, - S = [s,s,s], - SV = [N], - LS = [lsn,lsn,lsn], - F = [f_birth, f_fstorder, f_death, f_aging, f_fstorder, f_death, f_aging, f_fstorder, f_death], - I = [i_birth, i_fstorder, i_aging, i_fstorder, i_aging, i_fstorder], - O = [o_fstorder, o_death, o_aging, o_fstorder, o_death, o_aging, o_fstorder, o_death], - V = [v_birth, v_death, v_fstorder, v_aging, v_death, v_fstorder, v_aging, v_death, v_fstorder], - LV = [lv_death1, lv_fstorder1, lv_aging1, lv_death1, lv_fstorder1, lv_aging1, lv_death1, lv_fstorder1], - LSV = [lsv_birth1], - P = [p_μ, p_δ, p_δ, p_δ, p_rage, p_rage, p_rfstOrder], - LPV = [lpv_birth2, lpv_death2, lpv_fstorder2, lpv_aging2, lpv_death2, lpv_fstorder2, lpv_aging2, lpv_death2, lpv_fstorder2], - Name =NothingFunction, Op=NothingFunction, Position=NothingFunction - ); - @assert is_natural(typed_ageWeightModel); - - aged_weight = pullback(typed_WeightModel, typed_ageWeightModel) |> apex |> rebuildStratifiedModelByFlattenSymbols; - - # ######################################### - - age_weight_2 = @stratify WeightModel l_type ageWeightModel begin - :stocks - NormalWeight, OverWeight, Obese => pop <= Child, Adult, Senior - - :flows - f_NewBorn => f_birth <= f_NB - f_DeathNormalWeight, f_DeathOverWeight, f_DeathObese => f_death <= f_DeathC, f_DeathA, f_DeathS - f_idNW, f_idOW, f_idOb => f_aging <= f_agingCA, f_agingAS - f_BecomingOverWeight, f_BecomingObese => f_fstOrder <= f_idC, f_idA, f_idS - - :dynamic_variables - v_NewBorn => v_birth <= v_NB - v_DeathNormalWeight, v_DeathOverWeight, v_DeathObese => v_death <= v_DeathC, v_DeathA, v_DeathS - v_idNW, v_idOW, v_idOb => v_aging <= v_agingCA, v_agingAS - v_BecomingOverWeight, v_BecomingObese => v_fstOrder <= v_idC, v_idA, v_idS - - :parameters - μ => μ <= μ - δw, δo => δ <= δC, δA, δS - rw, ro => rFstOrder <= r - rage => rage <= rageCA, rageAS - - :sums - N => N <= N - - end - ######################################### - - age_weight_3 = @stratify WeightModel l_type ageWeightModel begin - - :flows - f_NewBorn => f_birth <= f_NB - ~Death => f_death <= ~Death - ~id => f_aging <= ~aging - ~Becoming => f_fstOrder <= ~id - - :dynamic_variables - v_NewBorn => v_birth <= v_NB - ~Death => v_death <= ~Death - ~id => v_aging <= ~aging - ~Becoming => v_fstOrder <= ~id - - :parameters - μ => μ <= μ - ~δ => δ <= ~δ - rage => rage <= rageCA, rageAS - _ => rFstOrder <= _ - - end - - age_weight_4 = @stratify WeightModel l_type ageWeightModel begin - - :flows - ~NO_MATCHES => f_birth <= ~NO_MATCHES - f_NewBorn => f_birth <= f_NB - ~Death => f_death <= ~Death - ~id => f_aging <= ~aging - ~Becoming => f_fstOrder <= ~id - ~Becoming => f_aging <= ~id # Everything already matched; ignored - _ => f_aging <= _ # also ignored - - :dynamic_variables - v_NewBorn => v_birth <= v_NB - ~Death => v_death <= ~Death - ~id => v_aging <= ~aging - _ => v_fstOrder <= _ - - :parameters - μ => μ <= μ - ~δ => δ <= ~δ - rage => rage <= rageCA, rageAS - _ => rFstOrder <= _ - - end - - age_weight_5 = @n_stratify WeightModel ageWeightModel l_type begin - :stocks - [_, _] => pop - - :flows - [~Death, ~Death] => f_death - [~id, ~aging] => f_aging - [~Becoming, ~id] => f_fstOrder - [_, f_NB] => f_birth - - - :dynamic_variables - [v_NewBorn, v_NB] => v_birth - [~Death, ~Death] => v_death - [~id, (v_agingCA, v_agingAS)] => v_aging - [(v_BecomingOverWeight, v_BecomingObese), (v_idC, v_idA, v_idS)] => v_fstOrder - - :parameters - [μ, μ] => μ - [(δw, δo), (δC, δA, δS)] => δ - [(rw, ro), r] => rFstOrder - [rage, (rageCA, rageAS)] => rage - - :sums - [N,N] => N - end - - age_weight_6 = @n_stratify WeightModel ageWeightModel l_type begin - - :flows - [~Death, ~Death] => f_death - [~id, ~aging] => f_aging - [~Becoming, ~id] => f_fstOrder - [_, f_NB] => f_birth - - - :dynamic_variables - [v_NewBorn, v_NB] => v_birth - [~Death, ~Death] => v_death - [~id, (v_agingCA, v_agingAS)] => v_aging - [(v_BecomingOverWeight, v_BecomingObese), (v_idC, v_idA, v_idS)] => v_fstOrder - - :parameters - [μ, μ] => μ - [(δw, δo), (δC, δA, δS)] => δ - [(rw, ro), r] => rFstOrder - [rage, (rageCA, rageAS)] => rage - - end - - - - @test aged_weight == age_weight_2 - @test aged_weight == age_weight_3 - @test aged_weight == age_weight_4 - @test aged_weight == age_weight_5 - @test aged_weight == age_weight_6 + l_type = @stock_and_flow begin + :stocks + pop + + :parameters + μ + δ + rFstOrder + rage + + :dynamic_variables + v_aging = pop * rage + v_fstOrder = pop * rFstOrder + v_birth = N * μ + v_death = pop * δ + + :flows + pop => f_aging(v_aging) => pop + pop => f_fstOrder(v_fstOrder) => pop + CLOUD => f_birth(v_birth) => pop + pop => f_death(v_death) => CLOUD + + :sums + N = [pop] + + end; + l_type_noatts = map(l_type, Name=NothingFunction, Op=NothingFunction, Position=NothingFunction); + + + WeightModel = @stock_and_flow begin + :stocks + NormalWeight + OverWeight + Obese + + :parameters + μ + δw + rw + ro + δo + rage + + :dynamic_variables + v_NewBorn = N * μ + v_DeathNormalWeight = NormalWeight * δw + v_BecomingOverWeight = NormalWeight * rw + v_DeathOverWeight = OverWeight * δw + v_BecomingObese = OverWeight * ro + v_DeathObese = Obese * δo + v_idNW = NormalWeight * rage + v_idOW = OverWeight * rage + v_idOb = Obese * rage + + :flows + CLOUD => f_NewBorn(v_NewBorn) => NormalWeight + NormalWeight => f_DeathNormalWeight(v_DeathNormalWeight) => CLOUD + NormalWeight => f_BecomingOverWeight(v_BecomingOverWeight) => OverWeight + OverWeight => f_DeathOverWeight(v_DeathOverWeight) => CLOUD + + OverWeight => f_BecomingObese(v_BecomingObese) => Obese + Obese => f_DeathObese(v_DeathObese) => CLOUD + NormalWeight => f_idNW(v_idNW) => NormalWeight + OverWeight => f_idOW(v_idOW) => OverWeight + Obese => f_idOb(v_idOb) => Obese + + :sums + N = [NormalWeight, OverWeight, Obese] + + end; + + + ageWeightModel = @stock_and_flow begin + :stocks + Child + Adult + Senior + + :parameters + μ + δC + δA + δS + rageCA + rageAS + r + + :dynamic_variables + v_NB = N * μ + v_DeathC = Child * δC + v_idC = Child * r + v_agingCA = Child * rageCA + v_DeathA = Adult * δA + v_idA = Adult * r + v_agingAS = Adult * rageAS + v_DeathS = Senior * δS + v_idS = Senior * r + + :flows + CLOUD => f_NB(v_NB) => Child + Child => f_idC(v_idC) => Child + Child => f_DeathC(v_DeathC) => CLOUD + Child => f_agingCA(v_agingCA) => Adult + Adult => f_idA(v_idA) => Adult + Adult => f_DeathA(v_DeathA) => CLOUD + Adult => f_agingAS(v_agingAS) => Senior + Senior => f_idS(v_idS) => Senior + Senior => f_DeathS(v_DeathS) => CLOUD + + :sums + N = [Child, Adult, Senior] + + end; + + begin + s, = parts(l_type, :S) + N, = parts(l_type, :SV) + lsn, = parts(l_type, :LS) + f_aging, f_fstorder, f_birth, f_death = parts(l_type, :F) + i_aging, i_fstorder, i_birth = parts(l_type, :I) + o_aging, o_fstorder, o_death = parts(l_type, :O) + v_aging, v_fstorder, v_birth, v_death = parts(l_type, :V) + lv_aging1, lv_fstorder1, lv_death1 = parts(l_type, :LV) + lsv_birth1, = parts(l_type, :LSV) + p_μ, p_δ, p_rfstOrder, p_rage = parts(l_type, :P) + lpv_aging2, lpv_fstorder2, lpv_birth2, lpv_death2 = parts(l_type, :LPV) + end; + + typed_WeightModel=ACSetTransformation(WeightModel, l_type_noatts, + S = [s,s,s], + SV = [N], + LS = [lsn,lsn,lsn], + F = [f_birth, f_death, f_fstorder, f_death, f_fstorder, f_death, f_aging, f_aging, f_aging], + I = [i_birth, i_aging, i_fstorder, i_aging, i_fstorder, i_aging], + O = [o_death, o_fstorder, o_aging, o_death, o_fstorder, o_aging, o_death, o_aging], + V = [v_birth, v_death, v_fstorder, v_death, v_fstorder, v_death, v_aging, v_aging, v_aging], + LV = [lv_death1, lv_fstorder1, lv_death1, lv_fstorder1, lv_death1, lv_aging1, lv_aging1, lv_aging1], + LSV = [lsv_birth1], + P = [p_μ, p_δ, p_rfstOrder, p_rfstOrder, p_δ, p_rage], + LPV = [lpv_birth2, lpv_death2, lpv_fstorder2, lpv_death2, lpv_fstorder2, lpv_death2, lpv_aging2, lpv_aging2, lpv_aging2], + Name=NothingFunction, Op=NothingFunction, Position=NothingFunction + ); + @assert is_natural(typed_WeightModel); + + + + typed_ageWeightModel=ACSetTransformation(ageWeightModel, l_type_noatts, + S = [s,s,s], + SV = [N], + LS = [lsn,lsn,lsn], + F = [f_birth, f_fstorder, f_death, f_aging, f_fstorder, f_death, f_aging, f_fstorder, f_death], + I = [i_birth, i_fstorder, i_aging, i_fstorder, i_aging, i_fstorder], + O = [o_fstorder, o_death, o_aging, o_fstorder, o_death, o_aging, o_fstorder, o_death], + V = [v_birth, v_death, v_fstorder, v_aging, v_death, v_fstorder, v_aging, v_death, v_fstorder], + LV = [lv_death1, lv_fstorder1, lv_aging1, lv_death1, lv_fstorder1, lv_aging1, lv_death1, lv_fstorder1], + LSV = [lsv_birth1], + P = [p_μ, p_δ, p_δ, p_δ, p_rage, p_rage, p_rfstOrder], + LPV = [lpv_birth2, lpv_death2, lpv_fstorder2, lpv_aging2, lpv_death2, lpv_fstorder2, lpv_aging2, lpv_death2, lpv_fstorder2], + Name =NothingFunction, Op=NothingFunction, Position=NothingFunction + ); + @assert is_natural(typed_ageWeightModel); + + aged_weight = pullback(typed_WeightModel, typed_ageWeightModel) |> apex |> rebuildStratifiedModelByFlattenSymbols; + + # ######################################### + + age_weight_2 = @stratify WeightModel l_type ageWeightModel begin + :stocks + NormalWeight, OverWeight, Obese => pop <= Child, Adult, Senior + + :flows + f_NewBorn => f_birth <= f_NB + f_DeathNormalWeight, f_DeathOverWeight, f_DeathObese => f_death <= f_DeathC, f_DeathA, f_DeathS + f_idNW, f_idOW, f_idOb => f_aging <= f_agingCA, f_agingAS + f_BecomingOverWeight, f_BecomingObese => f_fstOrder <= f_idC, f_idA, f_idS + + :dynamic_variables + v_NewBorn => v_birth <= v_NB + v_DeathNormalWeight, v_DeathOverWeight, v_DeathObese => v_death <= v_DeathC, v_DeathA, v_DeathS + v_idNW, v_idOW, v_idOb => v_aging <= v_agingCA, v_agingAS + v_BecomingOverWeight, v_BecomingObese => v_fstOrder <= v_idC, v_idA, v_idS + + :parameters + μ => μ <= μ + δw, δo => δ <= δC, δA, δS + rw, ro => rFstOrder <= r + rage => rage <= rageCA, rageAS + + :sums + N => N <= N + + end + ######################################### + + age_weight_3 = @stratify WeightModel l_type ageWeightModel begin + + :flows + f_NewBorn => f_birth <= f_NB + ~Death => f_death <= ~Death + ~id => f_aging <= ~aging + ~Becoming => f_fstOrder <= ~id + + :dynamic_variables + v_NewBorn => v_birth <= v_NB + ~Death => v_death <= ~Death + ~id => v_aging <= ~aging + ~Becoming => v_fstOrder <= ~id + + :parameters + μ => μ <= μ + ~δ => δ <= ~δ + rage => rage <= rageCA, rageAS + _ => rFstOrder <= _ + + end + + age_weight_4 = @stratify WeightModel l_type ageWeightModel begin + + :flows + ~NO_MATCHES => f_birth <= ~NO_MATCHES + f_NewBorn => f_birth <= f_NB + ~Death => f_death <= ~Death + ~id => f_aging <= ~aging + ~Becoming => f_fstOrder <= ~id + ~Becoming => f_aging <= ~id # Everything already matched; ignored + _ => f_aging <= _ # also ignored + + :dynamic_variables + v_NewBorn => v_birth <= v_NB + ~Death => v_death <= ~Death + ~id => v_aging <= ~aging + _ => v_fstOrder <= _ + + :parameters + μ => μ <= μ + ~δ => δ <= ~δ + rage => rage <= rageCA, rageAS + _ => rFstOrder <= _ + + end + + age_weight_5 = @n_stratify WeightModel ageWeightModel l_type begin + :stocks + [_, _] => pop + + :flows + [~Death, ~Death] => f_death + [~id, ~aging] => f_aging + [~Becoming, ~id] => f_fstOrder + [_, f_NB] => f_birth + + + :dynamic_variables + [v_NewBorn, v_NB] => v_birth + [~Death, ~Death] => v_death + [~id, (v_agingCA, v_agingAS)] => v_aging + [(v_BecomingOverWeight, v_BecomingObese), (v_idC, v_idA, v_idS)] => v_fstOrder + + :parameters + [μ, μ] => μ + [(δw, δo), (δC, δA, δS)] => δ + [(rw, ro), r] => rFstOrder + [rage, (rageCA, rageAS)] => rage + + :sums + [N,N] => N + end + + age_weight_6 = @n_stratify WeightModel ageWeightModel l_type begin + + :flows + [~Death, ~Death] => f_death + [~id, ~aging] => f_aging + [~Becoming, ~id] => f_fstOrder + [_, f_NB] => f_birth + + + :dynamic_variables + [v_NewBorn, v_NB] => v_birth + [~Death, ~Death] => v_death + [~id, (v_agingCA, v_agingAS)] => v_aging + [(v_BecomingOverWeight, v_BecomingObese), (v_idC, v_idA, v_idS)] => v_fstOrder + + :parameters + [μ, μ] => μ + [(δw, δo), (δC, δA, δS)] => δ + [(rw, ro), r] => rFstOrder + [rage, (rageCA, rageAS)] => rage + + end + + + + @test aged_weight == age_weight_2 + @test aged_weight == age_weight_3 + @test aged_weight == age_weight_4 + @test aged_weight == age_weight_5 + @test aged_weight == age_weight_6 end @testset "Ensuring interpret_stratification_standard_notation correctly reads lines" begin # This should be all valid cases. There's always going to be at least one value on both sides. - # Note the orders. The lists produced go left to right. A1, A2 => B <= C1, C2 results in [A1 => B, A2 => B], [C1 => B. C2 => B] - - - @test interpret_stratification_standard_notation(:(A => B <= C)) == [[DSLArgument(:A, :B, Set{Symbol}())], [DSLArgument(:C, :B, Set{Symbol}())]] - - @test interpret_stratification_standard_notation(:(A1, A2 => B <= C)) == [ - [DSLArgument(:A1, :B, Set{Symbol}()), DSLArgument(:A2, :B, Set{Symbol}())], - [DSLArgument(:C, :B, Set{Symbol}())] - ] - @test interpret_stratification_standard_notation(:(A => B <= C1, C2)) == [ - [DSLArgument(:A, :B, Set{Symbol}())], - [DSLArgument(:C1, :B, Set{Symbol}()), DSLArgument(:C2, :B, Set{Symbol}())], - ] - @test interpret_stratification_standard_notation(:(_ => B <= _)) == [ - [DSLArgument(:_, :B, Set{Symbol}())], - [DSLArgument(:_, :B, Set{Symbol}())], - ] - @test interpret_stratification_standard_notation(:(~A => B <= ~C)) == [ - [DSLArgument(:A, :B, Set{Symbol}([:~]))], - [DSLArgument(:C, :B, Set{Symbol}([:~]))], - ] - @test interpret_stratification_standard_notation(:(~A1, A2 => B <= ~C)) == [ - [DSLArgument(:A1, :B, Set{Symbol}([:~])), DSLArgument(:A2, :B, Set{Symbol}())], - [DSLArgument(:C, :B, Set{Symbol}([:~]))], - ] - - @test interpret_stratification_standard_notation(:(~_ => B <= ~_, C)) == [ # Weird case. Matches everything with _ as a substring. - [DSLArgument(:_, :B, Set{Symbol}([:~]))], - [DSLArgument(:_, :B, Set{Symbol}([:~])), DSLArgument(:C, :B, Set{Symbol}())] - ] + # Note the orders. The lists produced go left to right. A1, A2 => B <= C1, C2 results in [A1 => B, A2 => B], [C1 => B. C2 => B] + + + @test interpret_stratification_standard_notation(:(A => B <= C)) == [[DSLArgument(:A, :B, Set{Symbol}())], [DSLArgument(:C, :B, Set{Symbol}())]] + + @test interpret_stratification_standard_notation(:(A1, A2 => B <= C)) == [ + [DSLArgument(:A1, :B, Set{Symbol}()), DSLArgument(:A2, :B, Set{Symbol}())], + [DSLArgument(:C, :B, Set{Symbol}())] + ] + @test interpret_stratification_standard_notation(:(A => B <= C1, C2)) == [ + [DSLArgument(:A, :B, Set{Symbol}())], + [DSLArgument(:C1, :B, Set{Symbol}()), DSLArgument(:C2, :B, Set{Symbol}())], + ] + @test interpret_stratification_standard_notation(:(_ => B <= _)) == [ + [DSLArgument(:_, :B, Set{Symbol}())], + [DSLArgument(:_, :B, Set{Symbol}())], + ] + @test interpret_stratification_standard_notation(:(~A => B <= ~C)) == [ + [DSLArgument(:A, :B, Set{Symbol}([:~]))], + [DSLArgument(:C, :B, Set{Symbol}([:~]))], + ] + @test interpret_stratification_standard_notation(:(~A1, A2 => B <= ~C)) == [ + [DSLArgument(:A1, :B, Set{Symbol}([:~])), DSLArgument(:A2, :B, Set{Symbol}())], + [DSLArgument(:C, :B, Set{Symbol}([:~]))], + ] + + @test interpret_stratification_standard_notation(:(~_ => B <= ~_, C)) == [ # Weird case. Matches everything with _ as a substring. + [DSLArgument(:_, :B, Set{Symbol}([:~]))], + [DSLArgument(:_, :B, Set{Symbol}([:~])), DSLArgument(:C, :B, Set{Symbol}())] + ] end @testset "Unwrapping expressions works correctly" begin - @test unwrap_expression(:S) == (:S, Set{Symbol}()) - @test unwrap_expression(:(~S)) == (:S, Set{Symbol}([:~])) - @test unwrap_expression(:(~_)) == (:_, Set{Symbol}([:~])) + @test unwrap_expression(:S) == (:S, Set{Symbol}()) + @test unwrap_expression(:(~S)) == (:S, Set{Symbol}([:~])) + @test unwrap_expression(:(~_)) == (:_, Set{Symbol}([:~])) end # function substitute_symbols(s::Dict{Symbol, Int}, t::Dict{Symbol, Int}, m::Vector{DSLArgument} ; use_flags::Bool=true)::Dict{Int, Int} @testset "Testing substituting symbols" begin # underscore matching occurs at the very end, after this step. - s1 = Dict(:A => 1) - t1 = Dict(:B => 2) - m1₁ = [DSLArgument(:A, :B, Set{Symbol}())] - m1₂ = [DSLArgument(:A, :B, Set{Symbol}([:~]))] + s1 = Dict(:A => 1) + t1 = Dict(:B => 2) + m1₁ = [DSLArgument(:A, :B, Set{Symbol}())] + m1₂ = [DSLArgument(:A, :B, Set{Symbol}([:~]))] - @test substitute_symbols(s1, t1, m1₁) == Dict(1 => 2) # A=>B -> 1=>2 - @test substitute_symbols(s1, t1, m1₂) == Dict(1 => 2) # A=>B -> 1=>2 - @test substitute_symbols(s1, t1, m1₂, use_flags=false) == Dict(1 => 2) # A=>B -> 1=>2 + @test substitute_symbols(s1, t1, m1₁) == Dict(1 => 2) # A=>B -> 1=>2 + @test substitute_symbols(s1, t1, m1₂) == Dict(1 => 2) # A=>B -> 1=>2 + @test substitute_symbols(s1, t1, m1₂, use_flags=false) == Dict(1 => 2) # A=>B -> 1=>2 - s2 = Dict(:A1 => 10, :A2 => 20, :A3 => 30) # Unfortunately, cannot do substring matches starting with numbers, since it would require a symbol starting with a number. Might need to add something for this... - t2 = Dict(:B1 => 1, :B2 => 2) - m2₁ = [DSLArgument(:A, :B1, Set{Symbol}([:~]))] + s2 = Dict(:A1 => 10, :A2 => 20, :A3 => 30) # Unfortunately, cannot do substring matches starting with numbers, since it would require a symbol starting with a number. Might need to add something for this... + t2 = Dict(:B1 => 1, :B2 => 2) + m2₁ = [DSLArgument(:A, :B1, Set{Symbol}([:~]))] - @test substitute_symbols(s2, t2, m2₁) == Dict(10 => 1, 20 => 1, 30 => 1) #~A=>B -> 10=>1, 20=>1, 30=>1 + @test substitute_symbols(s2, t2, m2₁) == Dict(10 => 1, 20 => 1, 30 => 1) #~A=>B -> 10=>1, 20=>1, 30=>1 - s3 = Dict{Symbol, Int}() - t3 = Dict{Symbol, Int}() - m3 = Vector{DSLArgument}() + s3 = Dict{Symbol, Int}() + t3 = Dict{Symbol, Int}() + m3 = Vector{DSLArgument}() - @test substitute_symbols(s3, t3, m3) == Dict() - @test substitute_symbols(s3, t3, m3, use_flags=false) == Dict() + @test substitute_symbols(s3, t3, m3) == Dict() + @test substitute_symbols(s3, t3, m3, use_flags=false) == Dict() - s4 = Dict(:A1 => 1, :A2 => 2, :AB3 => 3, :AB4 => 4, :A5 => 5) - t4 = Dict(:B1 => 1, :B2 => 2, :B3 => 3) - m4 = [DSLArgument(:A1, :B1, Set{Symbol}()), DSLArgument(:B, :B2, Set{Symbol}([:~])), DSLArgument(:A, :B3, Set{Symbol}([:~]))] + s4 = Dict(:A1 => 1, :A2 => 2, :AB3 => 3, :AB4 => 4, :A5 => 5) + t4 = Dict(:B1 => 1, :B2 => 2, :B3 => 3) + m4 = [DSLArgument(:A1, :B1, Set{Symbol}()), DSLArgument(:B, :B2, Set{Symbol}([:~])), DSLArgument(:A, :B3, Set{Symbol}([:~]))] - # always goes with first match. A1 is taken, B matches AB3 and AB4, then A matches A2 and A5 - @test substitute_symbols(s4, t4, m4) == Dict(1 => 1, 3 => 2, 4 => 2, 2 => 3, 5 => 3) + # always goes with first match. A1 is taken, B matches AB3 and AB4, then A matches A2 and A5 + @test substitute_symbols(s4, t4, m4) == Dict(1 => 1, 3 => 2, 4 => 2, 2 => 3, 5 => 3) end @testset "nondefault flags work as expected" begin - A_ = (@stock_and_flow begin - :stocks - A - _ - end) - - X_ = (@stock_and_flow begin - :stocks - X - _ - end) - - B_ = (@stock_and_flow begin - :stocks - B - _ - end) - - strat_AXB = (quote # Note, we use a quote when calling the function, begin when calling the macro. + A_ = (@stock_and_flow begin :stocks - _ => _ <= _ - A => X <= B - ~A => X <= ~B # everything is already assigned, so does nothing (or throws error if strict_matches is true) - end) + A + _ + end) + X_ = (@stock_and_flow begin + :stocks + X + _ + end) - sfA = (@stock_and_flow begin; :stocks; A; end;) + B_ = (@stock_and_flow begin + :stocks + B + _ + end) - @test (sfstratify([A_, B_], X_, strat_AXB, use_temp_strat_default=false) - == (@stock_and_flow begin - :stocks - AB - __ - end)) + strat_AXB = (quote # Note, we use a quote when calling the function, begin when calling the macro. + :stocks + _ => _ <= _ + A => X <= B + ~A => X <= ~B # everything is already assigned, so does nothing (or throws error if strict_matches is true) + end) - # doesn't show up anywhere, so doesn't affect anything. Could also set it to something untypable in the DSL, like Symbol("") - @test (sfstratify([A_, B_], X_, strat_AXB, temp_strat_default=:ABABABABA) - == (@stock_and_flow begin - :stocks - AB - __ - end)) - @test_throws AssertionError (sfstratify([A_, B_], X_, strat_AXB, strict_matches=true)) # A matches against A and ~A, which is disallowed with this flag. + sfA = (@stock_and_flow begin; :stocks; A; end;) - @test_throws ErrorException (sfstratify([sfA,sfA],sfA,(quote end) ; strict_mappings=true)) # strict_mappings=false wouldn't throw an error, and would infer strata and aggregate need to map to the only stock. + @test (sfstratify([A_, B_], X_, strat_AXB, use_temp_strat_default=false) + == (@stock_and_flow begin + :stocks + AB + __ + end)) + + # doesn't show up anywhere, so doesn't affect anything. Could also set it to something untypable in the DSL, like Symbol("") + @test (sfstratify([A_, B_], X_, strat_AXB, temp_strat_default=:ABABABABA) + == (@stock_and_flow begin + :stocks + AB + __ + end)) + @test_throws AssertionError (sfstratify([A_, B_], X_, strat_AXB, strict_matches=true)) # A matches against A and ~A, which is disallowed with this flag. - nothing_sfA = map(sfA, Position=NothingFunction, Op=NothingFunction, Name=NothingFunction) + @test_throws ErrorException (sfstratify([sfA,sfA],sfA,(quote end) ; strict_mappings=true)) # strict_mappings=false wouldn't throw an error, and would infer strata and aggregate need to map to the only stock. - @test (sfstratify([sfA,sfA],sfA,(quote end), return_homs=true) == ( - (@stock_and_flow begin - :stocks - AA - end), - [ACSetTransformation(sfA, nothing_sfA ; S=[1], F=Vector{Int}(),V =Vector{Int}(),SV=Vector{Int}(),P=Vector{Int}(),LS=Vector{Int}(),I=Vector{Int}(),O=Vector{Int}(),LV=Vector{Int}(),LSV=Vector{Int}(),LVV=Vector{Int}(),LPV=Vector{Int}(), Position=NothingFunction, Op=NothingFunction, Name=NothingFunction), # strata -> type - ACSetTransformation(sfA, nothing_sfA ; S=[1], F=Vector{Int}(),V =Vector{Int}(),SV=Vector{Int}(),P=Vector{Int}(),LS=Vector{Int}(),I=Vector{Int}(),O=Vector{Int}(),LV=Vector{Int}(),LSV=Vector{Int}(),LVV=Vector{Int}(),LPV=Vector{Int}(), Position=NothingFunction, Op=NothingFunction, Name=NothingFunction)] # aggregate -> type - )) # the empty lists are necessary for equality, but it'd still be an equivalent homomorphism if you didn't specify them. + + nothing_sfA = map(sfA, Position=NothingFunction, Op=NothingFunction, Name=NothingFunction) + + @test (sfstratify([sfA,sfA],sfA,(quote end), return_homs=true) == ( + (@stock_and_flow begin + :stocks + AA + end), + [ACSetTransformation(sfA, nothing_sfA ; S=[1], F=Vector{Int}(),V =Vector{Int}(),SV=Vector{Int}(),P=Vector{Int}(),LS=Vector{Int}(),I=Vector{Int}(),O=Vector{Int}(),LV=Vector{Int}(),LSV=Vector{Int}(),LVV=Vector{Int}(),LPV=Vector{Int}(), Position=NothingFunction, Op=NothingFunction, Name=NothingFunction), # strata -> type + ACSetTransformation(sfA, nothing_sfA ; S=[1], F=Vector{Int}(),V =Vector{Int}(),SV=Vector{Int}(),P=Vector{Int}(),LS=Vector{Int}(),I=Vector{Int}(),O=Vector{Int}(),LV=Vector{Int}(),LSV=Vector{Int}(),LVV=Vector{Int}(),LPV=Vector{Int}(), Position=NothingFunction, Op=NothingFunction, Name=NothingFunction)] # aggregate -> type + )) # the empty lists are necessary for equality, but it'd still be an equivalent homomorphism if you didn't specify them. end @@ -458,120 +458,120 @@ end @testset "n_stratify works as expected" begin - l_type = @stock_and_flow begin - :stocks - pop - - :parameters - μ - δ - rFstOrder - rage - - :dynamic_variables - v_aging = pop * rage - v_fstOrder = pop * rFstOrder - v_birth = N * μ - v_death = pop * δ - - :flows - pop => f_aging(v_aging) => pop - pop => f_fstOrder(v_fstOrder) => pop - CLOUD => f_birth(v_birth) => pop - pop => f_death(v_death) => CLOUD - - :sums - N = [pop] - - end - - chain_ltype = @stock_and_flow begin - :stocks - poppoppop - - :parameters - μμμ - δδδ - rFstOrderrFstOrderrFstOrder - rageragerage - - :dynamic_variables - v_agingv_agingv_aging = poppoppop * rageragerage - v_fstOrderv_fstOrderv_fstOrder = poppoppop * rFstOrderrFstOrderrFstOrder - v_birthv_birthv_birth = NNN * μμμ - v_deathv_deathv_death = poppoppop * δδδ - - :flows - poppoppop => f_agingf_agingf_aging(v_agingv_agingv_aging) => poppoppop - poppoppop => f_fstOrderf_fstOrderf_fstOrder(v_fstOrderv_fstOrderv_fstOrder) => poppoppop - CLOUD => f_birthf_birthf_birth(v_birthv_birthv_birth) => poppoppop - poppoppop => f_deathf_deathf_death(v_deathv_deathv_death) => CLOUD - - :sums - NNN = [poppoppop] - end - - chain_ltype_nstratify = @n_stratify l_type l_type l_type l_type begin - - :stocks - [pop, ~pop, _] => pop - - :parameters - [μ, μ, μ] => μ - [δ, δ, δ] => δ - [rFstOrder, rFstOrder, rFstOrder] => rFstOrder - [rage, rage, rage] => rage - - :dynamic_variables - [v_aging, v_aging, v_aging] => v_aging - [v_fstOrder, v_fstOrder, v_fstOrder] => v_fstOrder - [v_birth, v_birth, v_birth] => v_birth - [v_death, v_death, v_death] => v_death - - :flows - [f_aging, f_aging, f_aging] => f_aging - [f_fstOrder, f_fstOrder, f_fstOrder] => f_fstOrder - [f_birth, f_birth, f_birth] => f_birth - [f_death, f_death, f_death] => f_death - - :sums - [N, N, N] => N - end - - - @test chain_ltype == chain_ltype_nstratify - - - ltype_nstratify = @n_stratify l_type l_type begin - - :stocks - [pop] => pop - - :parameters - [μ] => μ - [δ] => δ - [rFstOrder] => rFstOrder - [rage] => rage - - :dynamic_variables - [v_aging] => v_aging - [v_fstOrder] => v_fstOrder - [v_birth] => v_birth - [v_death] => v_death - - :flows - [f_aging] => f_aging - [f_fstOrder] => f_fstOrder - [f_birth] => f_birth - [f_death] => f_death - - :sums - [N] => N - end - - @test ltype_nstratify == l_type - + l_type = @stock_and_flow begin + :stocks + pop + + :parameters + μ + δ + rFstOrder + rage + + :dynamic_variables + v_aging = pop * rage + v_fstOrder = pop * rFstOrder + v_birth = N * μ + v_death = pop * δ + + :flows + pop => f_aging(v_aging) => pop + pop => f_fstOrder(v_fstOrder) => pop + CLOUD => f_birth(v_birth) => pop + pop => f_death(v_death) => CLOUD + + :sums + N = [pop] + + end + + chain_ltype = @stock_and_flow begin + :stocks + poppoppop + + :parameters + μμμ + δδδ + rFstOrderrFstOrderrFstOrder + rageragerage + + :dynamic_variables + v_agingv_agingv_aging = poppoppop * rageragerage + v_fstOrderv_fstOrderv_fstOrder = poppoppop * rFstOrderrFstOrderrFstOrder + v_birthv_birthv_birth = NNN * μμμ + v_deathv_deathv_death = poppoppop * δδδ + + :flows + poppoppop => f_agingf_agingf_aging(v_agingv_agingv_aging) => poppoppop + poppoppop => f_fstOrderf_fstOrderf_fstOrder(v_fstOrderv_fstOrderv_fstOrder) => poppoppop + CLOUD => f_birthf_birthf_birth(v_birthv_birthv_birth) => poppoppop + poppoppop => f_deathf_deathf_death(v_deathv_deathv_death) => CLOUD + + :sums + NNN = [poppoppop] + end + + chain_ltype_nstratify = @n_stratify l_type l_type l_type l_type begin + + :stocks + [pop, ~pop, _] => pop + + :parameters + [μ, μ, μ] => μ + [δ, δ, δ] => δ + [rFstOrder, rFstOrder, rFstOrder] => rFstOrder + [rage, rage, rage] => rage + + :dynamic_variables + [v_aging, v_aging, v_aging] => v_aging + [v_fstOrder, v_fstOrder, v_fstOrder] => v_fstOrder + [v_birth, v_birth, v_birth] => v_birth + [v_death, v_death, v_death] => v_death + + :flows + [f_aging, f_aging, f_aging] => f_aging + [f_fstOrder, f_fstOrder, f_fstOrder] => f_fstOrder + [f_birth, f_birth, f_birth] => f_birth + [f_death, f_death, f_death] => f_death + + :sums + [N, N, N] => N + end + + + @test chain_ltype == chain_ltype_nstratify + + + ltype_nstratify = @n_stratify l_type l_type begin + + :stocks + [pop] => pop + + :parameters + [μ] => μ + [δ] => δ + [rFstOrder] => rFstOrder + [rage] => rage + + :dynamic_variables + [v_aging] => v_aging + [v_fstOrder] => v_fstOrder + [v_birth] => v_birth + [v_death] => v_death + + :flows + [f_aging] => f_aging + [f_fstOrder] => f_fstOrder + [f_birth] => f_birth + [f_death] => f_death + + :sums + [N] => N + end + + @test ltype_nstratify == l_type + end