diff --git a/Project.toml b/Project.toml index 30fc38d..30d5344 100644 --- a/Project.toml +++ b/Project.toml @@ -8,6 +8,7 @@ version = "0.1.0" ACSets = "227ef7b5-1206-438b-ac65-934d6da304b8" Catlab = "134e5e36-593f-5add-ad60-77f754baafbe" Decapodes = "679ab3ea-c928-4fe6-8d59-fd451142d391" +InteractiveUtils = "b77e0a4c-d291-57a0-90e8-8db25a27a240" JSON = "682c06a0-de6a-54ab-a142-c8b1cf79cde6" JSON3 = "0f8b85d8-7281-11e9-16c2-39a750bddbf1" MLStyle = "d8e11817-5142-5d16-987a-aa16d5891078" @@ -15,12 +16,12 @@ Reexport = "189a3867-3050-52da-a836-e630ba90ab69" StructTypes = "856f2bd8-1eba-4b0a-8007-ebc267875bd4" [compat] -Reexport = "1" -Catlab = "0.15" -JSON = "0.21" ACSets = "0.2" +Catlab = "0.15" Decapodes = "0.4" -StructTypes = "1" +JSON = "0.21" JSON3 = "1" MLStyle = "0.4" +Reexport = "1" +StructTypes = "1" julia = "1.9" diff --git a/src/SyntacticModels.jl b/src/SyntacticModels.jl index a996cdf..7b8f02f 100644 --- a/src/SyntacticModels.jl +++ b/src/SyntacticModels.jl @@ -1,9 +1,59 @@ module SyntacticModels -include("SyntacticModelsBase.jl") +#-----------------------------------------------------------------------------# "SyntacticModelsBase" +using InteractiveUtils: subtypes +using StructTypes + +""" AbstractTerm + +The super type for all SyntacticModsels types. This abstract type exists so that we can write generic methods that work on any term in any of the domain specific syntaxes. +For example, serializing to a Dictionary uses some reflection snippet that works for arbitrary types, but we only want to apply it to things that should be serialized like a Term. +""" +abstract type AbstractTerm end + +function StructTypes.StructType(::Type{T}) where {T <: AbstractTerm} + isconcretetype(T) ? StructTypes.CustomStruct() : StructTypes.AbstractType() +end + + +# lowering/unlowering +function StructTypes.lower(x::T) where {T <: AbstractTerm} + (_type = T.name.name, NamedTuple(k => getfield(x, k) for k in fieldnames(T))...) +end + +function StructTypes.lowertype(::Type{T}) where {T <: AbstractTerm} + NamedTuple{(:_type, fieldnames(T)...), Tuple{Symbol, fieldtypes(T)...}} +end + +# Doesn't work with 1-field structs +# (::Type{T})(x::StructTypes.lowertype(Type{T})) where {T <: AbstractTerm} = T(x[fieldnames(T)]...) + + +function concrete_subtypes(T) + out = Type[] + for S in subtypes(T) + isconcretetype(S) ? push!(out, S) : append!(out, concrete_subtypes(S)) + end + return out +end + +StructTypes.subtypes(::Type{T}) where {T <: AbstractTerm} = Dict(T.name.name => T for T in concrete_subtypes(T)) + +StructTypes.subtypekey(T::Type{<: AbstractTerm}) = :_type + + +#-----------------------------------------------------------------------------# includes include("amr.jl") include("decapodes.jl") include("uwd.jl") include("composite_models.jl") -end \ No newline at end of file +#-----------------------------------------------------------------------------# Constructors +for T in concrete_subtypes(AbstractTerm) + @eval function $(parentmodule(T)).$(T.name.name)(x::NamedTuple) + args = x[fieldnames($T)] + $(parentmodule(T)).$(T.name.name)(args...) + end +end + +end diff --git a/src/SyntacticModelsBase.jl b/src/SyntacticModelsBase.jl deleted file mode 100644 index 79ea82e..0000000 --- a/src/SyntacticModelsBase.jl +++ /dev/null @@ -1,63 +0,0 @@ -module SyntacticModelsBase - -using StructTypes -using JSON3 -import Base: Dict - -export AbstractTerm, _dict, typename_last - -""" AbstractTerm - -The super type for all SyntacticModels types. This abstract type exists so that we can write generic methods that work on any term in any of the domain specific syntaxes. -For example, serializing to a Dictionary uses some reflection snippet that works for arbitrary types, but we only want to apply it to things that should be serialized like a Term. -""" -abstract type AbstractTerm end - -""" typename_last(T::Type) - -Truncate a type name keeping only the last components of the FQTN. -""" -typename_last(T::Type) = T.name.name - -_dict(x::Symbol) = x -_dict(x::String) = x -_dict(x::Number) = x -_dict(x::AbstractVector) = map(_dict, x) - -""" _dict(x::T) where T <: AbstractTerm - -We are going to convert our structs to Dict before we call JSON3.write and -add the type information in a generic construction. This uses some reflection -to ask the julia type for its fieldnames and then use those as the keys in the Dict. -We use splatting, so don't make a struct with more than 32 fields if you want to go fast. -We use this _dict function to avoid an "I'm the captain now" level of type piracy. -""" -function _dict(x::T) where {T<:AbstractTerm} - Dict(:_type => typename_last(T), [k=>_dict(getfield(x, k)) for k in fieldnames(T)]...) -end - -""" Dict(x::AbstractTerm) - -to register your type with JSON3, you need to overload JSON3.write to use this Dict approach. -we only overload the Dict function for our type Formula, so this is not piracy. -""" -Dict(x::AbstractTerm) = _dict(x) - -""" JSON3.write - -Now JSON3.write(f) puts the type information in our reserved field. -""" -JSON3.write(f::AbstractTerm) = JSON3.write(Dict(f)) - -""" StructTypes.StructType - -This is how you tell StructTypes to use an interface for you. -""" -StructTypes.StructType(::Type{AbstractTerm}) = StructTypes.AbstractType() - -""" StructTypes.subtypekey - -This is how you tell StructTypes where to look for the name of a type when reading the type back in from JSON. -""" -StructTypes.subtypekey(::Type{AbstractTerm}) = :_type -end \ No newline at end of file diff --git a/src/amr.jl b/src/amr.jl index d81851c..254a5b9 100644 --- a/src/amr.jl +++ b/src/amr.jl @@ -12,7 +12,7 @@ using Reexport using ACSets.ADTs using ACSets.ACSetInterface -using ..SyntacticModelsBase +using ..SyntacticModels: AbstractTerm @data MathML <: AbstractTerm begin Math(String) @@ -42,6 +42,8 @@ nounit = Unit("", nomath) PointMass(value) end +StandardNormal(::Dict) = StandardNormal() + @as_record struct Observable{T <: AbstractTerm} id::Symbol name::String @@ -73,7 +75,7 @@ end end @as_record struct ASKEModel <: AbstractTerm - header::Header + header::Header model::ACSetSpec semantics::Vector{Semantic} end @@ -114,7 +116,7 @@ function amr_to_string(amr) m::ACSetSpec => "Model = begin\n$(padlines(sprint(show, m),2))\nend" ODEList(l) => "ODE_Equations = begin\n" * padlines(join(map(!, l), "\n")) * "\nend" ODERecord(rts, init, para, time) => join(vcat(["ODE_Record = begin\n"], !rts , !init, !para, [!time, "end"]), "\n") - vs::Vector{Pair} => map(vs) do v; "$(v[1]) => $(v[2])," end |> x-> join(x, "\n") + vs::Vector{Pair} => map(vs) do v; "$(v[1]) => $(v[2])," end |> x-> join(x, "\n") vs::Vector{Semantic} => join(map(!, vs), "\n\n") xs::Vector => map(!, xs) Typing(system, map) => "Typing = begin\n$(padlines(!system, 2))\nTypeMap = [\n$(padlines(!map, 2))]\nend" @@ -125,7 +127,7 @@ end block(exprs) = begin q = :(begin - + end) append!(q.args, exprs) return q @@ -252,14 +254,14 @@ end function load(::Type{Header}, d::AbstractDict) @match d begin - Dict("name"=>n, "schema"=>s, "description"=>d, "schema_name"=>sn, "model_version"=>mv) => Header(n,s,d,sn,mv) + Dict("name"=>n, "schema"=>s, "description"=>d, "schema_name"=>sn, "model_version"=>mv) => Header(n,s,d,sn,mv) _ => error("Information for Header was not found in $d") end end function load(::Type{Typing}, d::AbstractDict) @match d begin - Dict("type_system"=>s, "type_map"=>m) => begin @show m; Typing(petrispec(s), [x[1]=> x[2] for x in m]) end + Dict("type_system"=>s, "type_map"=>m) => begin @show m; Typing(petrispec(s), [x[1]=> x[2] for x in m]) end _ => error("Typing judgement was not properly encoded in $d") end end @@ -323,11 +325,11 @@ end function load(d::Type{Distribution}, ex::Expr) @matchast ex quote - U(0,1) => StandardUniform - U($min,$max) => Uniform(min, max) - N(0,1) => StandardNormal - N($mu,$var) => Normal(mu, var) - δ($value) => PointMass(value) + U(0,1) => StandardUniform + U($min,$max) => Uniform(min, max) + N(0,1) => StandardNormal + N($mu,$var) => Normal(mu, var) + δ($value) => PointMass(value) _ => error("Failed to find distribution in $ex") end end @@ -346,16 +348,16 @@ end function load(::Type{ODEList}, ex::Expr) map(ex.args[2].args) do arg - try + try return load(Rate, arg) - catch ErrorException - try + catch ErrorException + try return load(Initial, arg) - catch ErrorException - try + catch ErrorException + try return load(Parameter, arg) - catch ErrorException - try + catch ErrorException + try return load(Time, arg) catch return nothing @@ -410,9 +412,9 @@ function load(::Type{ASKEModel}, ex::Expr) Expr(:(=), :ODE_Record, body) => load(ODEList, arg) Expr(:(=), :ODE_Equations, body) => load(ODEList, arg) Expr(:(=), :Typing, body) => load(Typing, arg) - _ => arg + _ => arg end end ASKEModel(elts[2][1], elts[2][2], [elts[4], elts[6]]) end -end # module end \ No newline at end of file +end # module end diff --git a/src/composite_models.jl b/src/composite_models.jl index f2b9aef..9418e8a 100644 --- a/src/composite_models.jl +++ b/src/composite_models.jl @@ -5,9 +5,8 @@ export CompositeModelExpr, OpenModel, OpenDecapode, CompositeModel, interface, o using MLStyle using Catlab using Decapodes -using StructTypes -using ..SyntacticModelsBase +using ..SyntacticModels: AbstractTerm using ..AMR using ..ASKEMDecapodes using ..ASKEMUWDs @@ -30,10 +29,6 @@ end """ CompositeModel -StructTypes.StructType(::Type{CompositeModel}) = StructTypes.AbstractType() -StructTypes.subtypekey(::Type{CompositeModel}) = :_type -StructTypes.subtypes(::Type{CompositeModel}) = (OpenModel=OpenModel, OpenDecapode=OpenDecapode, CompositeModelExpr) - """ interface(m::CompositeModel) @@ -53,9 +48,9 @@ open_decapode(d::ASKEMDecapode, interface) = Open(d.model, interface) CompositeModels can be flattened into a single level of model with the oapply function. -!!! warning - Because the oapply algorithm operates on the compute graph representation of the equations, it does not produce syntactic equations. - Calls to oapply produce instances of OpenDecapode and not DecaExpr. +!!! warning + Because the oapply algorithm operates on the compute graph representation of the equations, it does not produce syntactic equations. + Calls to oapply produce instances of OpenDecapode and not DecaExpr. Software that expects to consume decapodes should plan to interact with both forms. """ function Catlab.oapply(m::CompositeModel) @@ -67,7 +62,7 @@ function Catlab.oapply(m::CompositeModel) # For a composite model, we have to recurse CompositeModelExpr(h, pattern, components) => begin uwd = ASKEMUWDs.construct(RelationDiagram, pattern) - Ms = map(m.components) do mᵢ; + Ms = map(m.components) do mᵢ; !(mᵢ) # oapply all the component models recursively end # OpenDecapode(ASKEMDecapode(h, apex(!(uwd, Ms))), interface(m)) # Then we call the oapply from Decapodes. @@ -83,4 +78,4 @@ function OpenDecapode(m::CompositeModel) OpenDecapode(ASKEMDecapode(m.header,apex(composite)), feet) end -end \ No newline at end of file +end diff --git a/src/decapodes.jl b/src/decapodes.jl index 22fbfda..a5749ce 100644 --- a/src/decapodes.jl +++ b/src/decapodes.jl @@ -2,10 +2,9 @@ module ASKEMDecapodes export ASKEMDecaExpr, ASKEMDecapode -using ..SyntacticModelsBase +using ..SyntacticModels: AbstractTerm using ..AMR -using StructTypes using Decapodes using MLStyle @@ -34,28 +33,4 @@ model metadata for ASKEM AMR conformance. """ ASKEMDecapode -StructTypes.StructType(::Type{ASKEMDeca}) = StructTypes.AbstractType() -StructTypes.subtypekey(::Type{ASKEMDeca}) = :_type -StructTypes.subtypes(::Type{ASKEMDeca}) = (ASKEMDecaExpr=ASKEMDecaExpr, ASKEMDecapode=ASKEMDecapode) - -SyntacticModelsBase._dict(x::T) where {T<:Union{Decapodes.DecaExpr, Decapodes.Equation, Decapodes.Term}} = begin - Dict(:_type => typename_last(T), [k=>_dict(getfield(x, k)) for k in fieldnames(T)]...) end - -StructTypes.StructType(::Type{Decapodes.Equation}) = StructTypes.AbstractType() -StructTypes.subtypekey(::Type{Decapodes.Equation}) = :_type -StructTypes.subtypes(::Type{Decapodes.Equation}) = (Eq=Eq,) - -StructTypes.StructType(::Type{Decapodes.Term}) = StructTypes.AbstractType() -StructTypes.subtypekey(::Type{Decapodes.Term}) = :_type -StructTypes.subtypes(::Type{Decapodes.Term}) = (Var=Decapodes.Var, - Lit=Decapodes.Lit, - Judgement=Decapodes.Judgement, - AppCirc1=Decapodes.AppCirc1, - App1=Decapodes.App1, - App2=Decapodes.App2, - Plus=Decapodes.Plus, - Mult=Decapodes.Mult, - Tan=Decapodes.Tan) - -end \ No newline at end of file diff --git a/src/uwd.jl b/src/uwd.jl index d6bc97b..db6a183 100644 --- a/src/uwd.jl +++ b/src/uwd.jl @@ -3,11 +3,10 @@ module ASKEMUWDs # include("amr.jl") export Var, Typed, Untyped, Statement, UWDExpr, UWDModel, UWDTerm, context -using ..SyntacticModelsBase +using ..SyntacticModels: AbstractTerm using ..AMR using MLStyle -using StructTypes using Catlab using Catlab.RelationalPrograms using Catlab.WiringDiagrams @@ -32,10 +31,6 @@ which are used for representing typed or untyped variables. """ Var -StructTypes.StructType(::Type{Var}) = StructTypes.AbstractType() -StructTypes.subtypekey(::Type{Var}) = :_type -StructTypes.subtypes(::Type{Var}) = (Untyped=Untyped, Typed=Typed) - @data UWDTerm <: AbstractTerm begin Statement(relation::Symbol, variables::Vector{Var}) UWDExpr(context::Vector{Var}, statements::Vector{Statement}) @@ -81,10 +76,6 @@ u = UWDExpr(c, s) """ UWDTerm -StructTypes.StructType(::Type{UWDTerm}) = StructTypes.AbstractType() -StructTypes.subtypekey(::Type{UWDTerm}) = :_type -StructTypes.subtypes(::Type{UWDTerm}) = (Statement=Statement, UWDExpr=UWDExpr, UWDModel=UWDModel) - varname(v::Var) = @match v begin Untyped(v) => v Typed(v, t) => v @@ -109,7 +100,7 @@ function show(io::IO, s::UWDTerm) let ! = show @match s begin Statement(r, v) => begin print(io, "$r("); show(io, v, wrap=false); print(io, ")") end - UWDExpr(c, body) => begin + UWDExpr(c, body) => begin map(enumerate(body)) do (i,s) if i == 1 print(io, "{ ") @@ -182,4 +173,4 @@ function construct(::Type{RelationDiagram}, ex::UWDExpr) end return uwd end -end \ No newline at end of file +end diff --git a/test/uwd_examples.jl b/test/uwd_examples.jl index 8b63d05..1eef21a 100644 --- a/test/uwd_examples.jl +++ b/test/uwd_examples.jl @@ -1,5 +1,5 @@ using ..SyntacticModels -using ..SyntacticModels.SyntacticModelsBase +using ..SyntacticModels.SyntacticModels using ..SyntacticModels.AMR using ..SyntacticModels.ASKEMUWDs @@ -61,4 +61,4 @@ end to_graphviz(uwd, box_labels=:name, junction_labels=:variable) -display(uwd) \ No newline at end of file +display(uwd)