From da112b7e40d3543f39214b07148612b7a1334d17 Mon Sep 17 00:00:00 2001 From: Thomas Purdy Date: Sun, 13 Oct 2024 00:48:47 -0600 Subject: [PATCH 1/5] Add ternary operation in stockflow macro syntax --- src/StockFlow.jl | 3 +++ src/Syntax.jl | 31 +++++++++++++++++++++++++++++-- test/Syntax.jl | 14 ++++++++++++++ 3 files changed, 46 insertions(+), 2 deletions(-) diff --git a/src/StockFlow.jl b/src/StockFlow.jl index 155a8a95..eda3e56f 100644 --- a/src/StockFlow.jl +++ b/src/StockFlow.jl @@ -39,6 +39,8 @@ vectorify(n) = [n] state_dict(n) = Dict(s=>i for (i, s) in enumerate(n)) +cond(c,t,f) = c ? t : f + #= operators definition # Operators: @@ -523,6 +525,7 @@ sir_StockAndFlow=StockAndFlowF((:S=>(:F_NONE,:inf,:N), :I=>(:inf,:F_NONE,:N)), ``` """ StockAndFlowF(s,p,v,f,sv) = begin + @show s, p, v, f, sv sf = StockAndFlowF() s = vectorify(s) diff --git a/src/Syntax.jl b/src/Syntax.jl index 8dfff26a..c0ad583e 100644 --- a/src/Syntax.jl +++ b/src/Syntax.jl @@ -194,8 +194,13 @@ end struct Ref <: DyvarExprT ref::Pair{Symbol,Symbol} end +struct Cond{C,T,F} <: DyvarExprT + cond::Tuple{C,T,F} +end + get(r::Ref) = r.ref get(b::Binop) = b.binop +get(c::Cond) = (c.cond => :cond) struct StockAndFlowArguments stocks::Vector{ Pair{ @@ -376,6 +381,12 @@ function parse_dyvar!(dyvars::Vector{Tuple{Symbol,Expr}}, dyvar::Expr) function parse_dyvar(dyvar::Expr) @match dyvar begin + :($dyvar_name = $(cond::Union{Symbol, Expr}) ? $(true_val::Union{Symbol, Expr}) : $(false_val::Union{Symbol, Expr})) => + if !is_recursive_dyvar(dyvar_name, cond) && !is_recursive_dyvar(dyvar_name, true_val) && !is_recursive_dyvar(dyvar_name, false_val) + return (dyvar_name, :($cond ? $true_val : $false_val)) + else + error("Recursive dyvar detected in " * String(dyvar_name)) + end :($dyvar_name = $dyvar_def) => if !is_recursive_dyvar(dyvar_name, dyvar_def) return (dyvar_name, dyvar_def) @@ -588,6 +599,9 @@ function dyvar_exprs_to_symbolic_repr(dyvars::Vector{Tuple{Symbol,Expr}}) Expr(:call, op, a, b) => begin push!(syms, (dyvar_name => Binop((a, b) => op))) end + Expr(:if, c, t, f) => begin + push!(syms, (dyvar_name => Cond((c, t, f),))) + end Expr(c, _, _) || Expr(c, _, _, _) => error( "Unhandled expression in dynamic variable definition " * String(c), ) @@ -846,9 +860,17 @@ function infix_expression_to_binops( end lastsym end + Expr(:if, c, t, f) => begin + csym = loop(c) + tsym = loop(t) + fsym = loop(f) + varname = gensym(gensymbase) + push!(exprs, (varname, :($csym ? $tsym : $fsym))) + varname + end Expr(en, _, _, _) || Expr(en, _, _) => begin error( - "Unhandled expression type " * String(en) * " cannot be converted into form f(a, b)", + "Unhandled expression type " * String(en) * " cannot be converted into form f(a, b)" ) end end @@ -887,7 +909,7 @@ Check if a Julia expression is a call of the form `op(a, b)` or `a op b`, where - `e` -- a Julia expression ### Output -A boolean indicating if the given julia expression is a function call of non-expression parameter(s). +A boolean indicating if the given julia expression is a function call or if statement of non-expression parameter(s). ### Examples ```julia-repl @@ -903,12 +925,17 @@ julia> is_simple_dyvar(:(f(a, b, c))) false julia> is_simple_dyvar(:f(a + b, c + d)) false +julia> is_simple_dyvar(:(A ? B : C)) +true +julia> is_simple_dyvar(:(A < B ? X : Y)) +false ``` """ function is_simple_dyvar(e::Expr) @match e begin Expr(:call, f::Symbol, a) => !(typeof(a) <: Expr) Expr(:call, f::Symbol, a, b) => !(typeof(a) <: Expr) && !(typeof(b) <: Expr) + Expr(:if, c, t, f) => !(typeof(c) <: Expr) && !(typeof(t) <: Expr) && !(typeof(f) <: Expr) _ => false end end diff --git a/test/Syntax.jl b/test/Syntax.jl index 6cfe24dc..e1376d47 100755 --- a/test/Syntax.jl +++ b/test/Syntax.jl @@ -597,3 +597,17 @@ end end) == CausalLoopPM([:A, :B], [:A => :B, :B => :A], [POL_NEGATIVE, POL_POSITIVE]) end + + + +@testset "Ternary" begin + @test ((@stock_and_flow begin + :stocks + A + B + C + :dynamic_variables + cond = A == B + v = cond ? A : B + end) == StockAndFlowF([:A => (:F_NONE, :F_NONE, :SV_NONE), :B => (:F_NONE, :F_NONE, :SV_NONE)], [], [:cond => ((:A, :B) => :(==)), :v => ((:cond, :A, :B) => :cond)], [], [])) +end From dfe0a3ae4a369b02d238388b62cbf7ebc0ed0fe4 Mon Sep 17 00:00:00 2001 From: Thomas Purdy Date: Sun, 13 Oct 2024 01:02:37 -0600 Subject: [PATCH 2/5] Remove testing print --- src/StockFlow.jl | 1 - 1 file changed, 1 deletion(-) diff --git a/src/StockFlow.jl b/src/StockFlow.jl index eda3e56f..1ed55e87 100644 --- a/src/StockFlow.jl +++ b/src/StockFlow.jl @@ -525,7 +525,6 @@ sir_StockAndFlow=StockAndFlowF((:S=>(:F_NONE,:inf,:N), :I=>(:inf,:F_NONE,:N)), ``` """ StockAndFlowF(s,p,v,f,sv) = begin - @show s, p, v, f, sv sf = StockAndFlowF() s = vectorify(s) From 9df5ee984f0c0fea69923d6ebf1b6cf478f22201 Mon Sep 17 00:00:00 2001 From: Thomas Purdy Date: Sun, 13 Oct 2024 16:44:45 -0600 Subject: [PATCH 3/5] Update ternary test --- test/Syntax.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/Syntax.jl b/test/Syntax.jl index e1376d47..73eaee48 100755 --- a/test/Syntax.jl +++ b/test/Syntax.jl @@ -609,5 +609,5 @@ end :dynamic_variables cond = A == B v = cond ? A : B - end) == StockAndFlowF([:A => (:F_NONE, :F_NONE, :SV_NONE), :B => (:F_NONE, :F_NONE, :SV_NONE)], [], [:cond => ((:A, :B) => :(==)), :v => ((:cond, :A, :B) => :cond)], [], [])) + end) == StockAndFlowF([:A => (:F_NONE, :F_NONE, :SV_NONE), :B => (:F_NONE, :F_NONE, :SV_NONE), :C => (:F_NONE, :F_NONE, :SV_NONE)], [], [:cond => ((:A, :B) => :(==)), :v => ((:cond, :A, :B) => :cond)], [], [])) end From 9f8130b53c93c7fa1c5e2c36bac4d81ca67bf4e1 Mon Sep 17 00:00:00 2001 From: Thomas Purdy Date: Thu, 17 Oct 2024 18:47:13 -0600 Subject: [PATCH 4/5] Convert || and && to function calls --- src/StockFlow.jl | 2 ++ src/Syntax.jl | 16 +++++++++++++++- 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/src/StockFlow.jl b/src/StockFlow.jl index 5d9ebdff..fa5c4345 100644 --- a/src/StockFlow.jl +++ b/src/StockFlow.jl @@ -40,6 +40,8 @@ vectorify(n) = [n] state_dict(n) = Dict(s=>i for (i, s) in enumerate(n)) cond(c,t,f) = c ? t : f +or(a,b) = a || b +and(a,b) = a && b #= operators definition # Operators: diff --git a/src/Syntax.jl b/src/Syntax.jl index 2e1f9ba9..019414a8 100644 --- a/src/Syntax.jl +++ b/src/Syntax.jl @@ -358,7 +358,7 @@ function is_recursive_dyvar(dyvar_name, dyvar_def) @match dyvar_def begin ::Symbol => dyvar_def == dyvar_name :($f()) => f == dyvar_name - Expr(:call, args...) => true in map(arg -> is_recursive_dyvar(dyvar_name, arg), args) + Expr(:call, args...) || Expr(:if, args...) || Expr(:||, args...) || Expr(:&&, args...) => true in map(arg -> is_recursive_dyvar(dyvar_name, arg), args) end end """ @@ -387,6 +387,18 @@ function parse_dyvar!(dyvars::Vector{Tuple{Symbol,Expr}}, dyvar::Expr) else error("Recursive dyvar detected in " * String(dyvar_name)) end + :($dyvar_name = $A || $B) => + if !is_recursive_dyvar(dyvar_name, A) && !is_recursive_dyvar(dyvar_name, B) + return (dyvar_name, :(or($A,$B))) + else + error("Recursive dyvar detected in " * String(dyvar_name)) + end + :($dyvar_name = $A && $B) => + if !is_recursive_dyvar(dyvar_name, A) && !is_recursive_dyvar(dyvar_name, B) + return (dyvar_name, :(and($A,$B))) + else + error("Recursive dyvar detected in " * String(dyvar_name)) + end :($dyvar_name = $dyvar_def) => if !is_recursive_dyvar(dyvar_name, dyvar_def) return (dyvar_name, dyvar_def) @@ -830,6 +842,8 @@ function infix_expression_to_binops( function loop(e) @match e begin ::Symbol || ::Float32 || ::Float64 || ::Int || ::String => e + Expr(:||, args...) => loop(Expr(:call, :or, args...)) + Expr(:&&, args...) => loop(Expr(:call, :and, args...)) Expr(:call, f, a) => begin asym = loop(a) varname = gensym(gensymbase) From a3dc577c6f50063e0ffd2937f320fce46be6685c Mon Sep 17 00:00:00 2001 From: Thomas Purdy Date: Sat, 19 Oct 2024 19:59:05 -0600 Subject: [PATCH 5/5] Add new math_expr to work with cond --- src/StockFlow.jl | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/StockFlow.jl b/src/StockFlow.jl index fa5c4345..4737e7a1 100644 --- a/src/StockFlow.jl +++ b/src/StockFlow.jl @@ -40,8 +40,8 @@ vectorify(n) = [n] state_dict(n) = Dict(s=>i for (i, s) in enumerate(n)) cond(c,t,f) = c ? t : f -or(a,b) = a || b -and(a,b) = a && b +or(a,b) = Bool(a) || Bool(b) +and(a,b) = Bool(a) && Bool(b) #= operators definition # Operators: @@ -70,7 +70,7 @@ https://docs.julialang.org/en/v1/manual/mathematical-operations/ Operators = Dict(2 => [:+, :-, :*, :/, :รท, :^, :%, :log, Symbol("")], 1 => [:log, :exp, :sqrt, Symbol("")]) #:NN is for NONE, which is used in create the special diagram using graph rewriting - +math_expr(op, op1, op2, op3) = Expr(:call, op, op1, op2, op3) math_expr(op, op1, op2) = Expr(:call, op, op1, op2) math_expr(op, op1) = Expr(:call, op, op1) #math_expr(op) = Expr(:call, op)