diff --git a/src/categorical_algebra/CategoricalAlgebra.jl b/src/categorical_algebra/CategoricalAlgebra.jl index 1f4c9aee9..d672e48b2 100644 --- a/src/categorical_algebra/CategoricalAlgebra.jl +++ b/src/categorical_algebra/CategoricalAlgebra.jl @@ -22,6 +22,7 @@ include("Chase.jl") include("FunctorialDataMigrations.jl") include("StructuredCospans.jl") include("Slices.jl") +include("JSONCSetTransformations.jl") @reexport using .Categories @reexport using .FinCats @@ -43,4 +44,6 @@ include("Slices.jl") @reexport using .StructuredCospans @reexport using .Slices +@reexport using .JSONCSetTransformations + end diff --git a/src/categorical_algebra/JSONCSetTransformations.jl b/src/categorical_algebra/JSONCSetTransformations.jl new file mode 100644 index 000000000..c10847c2d --- /dev/null +++ b/src/categorical_algebra/JSONCSetTransformations.jl @@ -0,0 +1,114 @@ +""" JSON serialization of acset transformations. +""" +module JSONCSetTransformations +export generate_json_fin_function, parse_json_fin_function, + read_json_fin_function, write_json_fin_function, + generate_json_acset_transformation, parse_json_acset_transformation, + read_json_acset_transformation, write_json_acset_transformation + +import JSON +using DataStructures: OrderedDict + +using ..FinSets, ..CSets + +# ACSetTransformation serialization +##################### + +""" Generate JSON-able object representing a FinFunction. + +Inverse to [`parse_json_fin_function`](@ref). +""" +function generate_json_fin_function(F::FinFunction) + OrderedDict{Symbol,Any}( + :dom => dom(F), + :codom => codom(F), + :map => collect(F)) +end + +""" Serialize a FinFunction object to a JSON file. + +Inverse to [`read_json_fin_function`](@ref). +""" +function write_json_fin_function(x::FinFunction, fname::AbstractString) + open(fname, "w") do f + write(f, JSON.json(generate_json_fin_function(x))) + end +end + +function parse_json_fin_function(input::AbstractDict) + FinFunction( + input["dom"]["n"] != 0 ? Int.(input["map"]) : 1:0, + input["dom"]["n"], + input["codom"]["n"]) +end + +""" Deserialize a FinFunction object from a JSON file. + +Inverse to [`write_json_fin_function`](@ref). +""" +function read_json_fin_function(fname::AbstractString) + parse_json_fin_function(JSON.parsefile(fname)) +end + +""" Generate JSON-able object representing an ACSetTransformation. + +Inverse to [`parse_json_acset_transformation`](@ref). +""" +function generate_json_acset_transformation(X::ACSetTransformation) + OrderedDict{Symbol,Any}( + :dom => (generate_json_acset ∘ dom)(X), + :codom => (generate_json_acset ∘ codom)(X), + :components => OrderedDict{Symbol,Any}( + Iterators.map((keys ∘ components)(X), (values ∘ components)(X)) do k,v + k , k ∈ (attrtypes ∘ acset_schema ∘ dom)(X) ? + # TODO: Support VarFunctions that are not empty. + "TODO: VarFunctions are current not supported." : + generate_json_fin_function(v) + end)) +end + +""" Serialize an ACSetTransformation object to a JSON file. + +Inverse to [`read_json_acset_transformation`](@ref). +""" +function write_json_acset_transformation(x::ACSetTransformation, fname::AbstractString) + open(fname, "w") do f + write(f, JSON.json(generate_json_acset_transformation(x))) + end +end + +""" Parse JSON-able object or JSON string representing an ACSetTransformation. + +Inverse to [`generate_json_acset_transformation`](@ref). +""" +parse_json_acset_transformation(cons, input::AbstractString) = + parse_json_acset_transformation(cons, JSON.parse(input)) +parse_json_acset_transformation(acs::ACSet, input::AbstractDict) = + parse_json_acset_transformation(constructor(acs), input) + +function parse_json_acset_transformation(cons, input::AbstractDict) + domain = parse_json_acset(cons(), input["dom"]) + codomain = parse_json_acset(cons(), input["codom"]) + hom_keys = filter(keys(input["components"])) do k + Symbol(k) ∉ (attrtypes ∘ acset_schema)(domain) + end + # TODO: Support VarFunctions that are not empty. + ACSetTransformation( + NamedTuple{Tuple(Symbol.(hom_keys))}( + Iterators.map(hom_keys) do k + parse_json_fin_function(input["components"][k]) + end), + domain, + codomain) +end + +""" Deserialize an ACSetTransformation object from a JSON file. + +Inverse to [`write_json_acset_transformation`](@ref). +""" +function read_json_acset_transformation(ty, fname::AbstractString) + parse_json_acset_transformation(ty, JSON.parsefile(fname)) +end + +end # module + diff --git a/test/categorical_algebra/CategoricalAlgebra.jl b/test/categorical_algebra/CategoricalAlgebra.jl index 40e3d64f9..dfe6abf05 100644 --- a/test/categorical_algebra/CategoricalAlgebra.jl +++ b/test/categorical_algebra/CategoricalAlgebra.jl @@ -60,5 +60,9 @@ end include("Slices.jl") end +@testset "JSONCSetTransformations" begin + include("JSONCSetTransformations.jl") +end + end diff --git a/test/categorical_algebra/JSONCSetTransformations.jl b/test/categorical_algebra/JSONCSetTransformations.jl new file mode 100644 index 000000000..39a0852fb --- /dev/null +++ b/test/categorical_algebra/JSONCSetTransformations.jl @@ -0,0 +1,90 @@ +module TestJSONCSetTransformations +using Test + +using Catlab.CategoricalAlgebra, Catlab.Graphs + +# FinFunction serialization +########################### +function roundtrip_json_fin_function(f::T) where T <: FinFunction + mktempdir() do dir + path = joinpath(dir, "fin_function.json") + write_json_fin_function(f, path) + read_json_fin_function(path) + end +end + +f = FinFunction([2,3], 2, 4) +g = FinFunction([2], 1, 3) + +for ϕ in [f,g] + @test roundtrip_json_fin_function(ϕ) == ϕ +end + +# ACSetTransformation serialization +################################### + +function roundtrip_json_acset_transformation(x, t) + mktempdir() do dir + path = joinpath(dir, "acset_transformation.json") + write_json_acset_transformation(x, path) + read_json_acset_transformation(t, path) + end +end + +g = path_graph(WeightedGraph{Float64}, 2, E=(weight=2,)) +h = path_graph(WeightedGraph{Float64}, 4, E=(weight=[1,2,3],)) +α = ACSetTransformation((V=[2,3], E=[2]), g, h) +@test roundtrip_json_acset_transformation(α, WeightedGraph{Float64}) == α + +@present SchFalseDecapode(FreeSchema) begin + (Var, TVar, Op1, Op2)::Ob + (Type, Operator)::AttrType + src::Hom(Op1, Var) + tgt::Hom(Op1, Var) + proj1::Hom(Op2, Var) + proj2::Hom(Op2, Var) + res::Hom(Op2, Var) + incl::Hom(TVar, Var) + + op1::Attr(Op1, Operator) + op2::Attr(Op2, Operator) + type::Attr(Var, Type) + + Name::AttrType + name::Attr(Var, Name) + + (Σ, Summand)::Ob + summand::Hom(Summand, Var) + summation::Hom(Summand, Σ) + sum::Hom(Σ, Var) +end + +@acset_type FalseDecapode(SchFalseDecapode, + index=[:src, :tgt, :res, :incl, :op1, :op2, :type]) + +dynamics = @acset FalseDecapode{Symbol,Symbol,Symbol} begin + Var = 2 + name = [:Q, :Qᵤ] + type = [:Form0, :Form0] + Op1 = 2 + src = [1,1] + tgt = [2,2] + op1 = [:dt,:Δ] + TVar = 1 + incl = [2] +end +boundaries = @acset FalseDecapode{Symbol,Symbol,Symbol} begin + Var = 1 + name = [:Q] + type = [:Form0] +end + +boundary_conditions = ACSetTransformation( + (Var=[1],), + boundaries, dynamics) + +@test roundtrip_json_acset_transformation(boundary_conditions, + FalseDecapode{Symbol,Symbol,Symbol}()) == boundary_conditions + +end +