From 5e24202257d1cac3199748e134e60a11368e958a Mon Sep 17 00:00:00 2001 From: Luke Morris Date: Tue, 31 Oct 2023 10:21:59 -0400 Subject: [PATCH 1/3] Read and write FinFunction JSON --- src/categorical_algebra/CategoricalAlgebra.jl | 3 + .../JSONCSetTransformations.jl | 136 ++++++++++++++++++ .../categorical_algebra/CategoricalAlgebra.jl | 4 + .../JSONCSetTransformations.jl | 33 +++++ 4 files changed, 176 insertions(+) create mode 100644 src/categorical_algebra/JSONCSetTransformations.jl create mode 100644 test/categorical_algebra/JSONCSetTransformations.jl 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..6547ea95d --- /dev/null +++ b/src/categorical_algebra/JSONCSetTransformations.jl @@ -0,0 +1,136 @@ +""" 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 +#import Pkg +#import Tables + +using ..FinSets, ..CSets +# TODO: Some of these `using`s might not be necessary. +using ACSets.ACSetInterface, ACSets.Schemas, ACSets.DenseACSets +using ACSets.DenseACSets: attr_type +using ACSets.ColumnImplementations: AttrVar + +# 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( + Int.(input["map"]), + 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) ? + "foo" : + generate_json_fin_function(k) + end)) +end + + +#attr_to_json(var::AttrVar) = (_var = var.val,) +#attr_to_json(val) = val +# +#""" Parse JSON-able object or JSON string representing an ACSet. +# +#Inverse to [`generate_json_acset`](@ref). +#""" +#parse_json_acset_transformation(cons, input::AbstractDict) = +# parse_json_acset!(cons(), input) +#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) +# +## TODO +#function parse_json_acset_transformation!(out::ACSetTransformation, input::AbstractDict) +# schema = acset_schema(out) +# parts = Iterators.map(input) do (type, rows) +# Symbol(type) => add_parts!(out, Symbol(type), length(rows)) +# end |> Dict +# for rows ∈ values(input) +# for (rownum, row) ∈ enumerate(rows) +# for (k, v) ∈ pairs(row) +# k = Symbol(k) +# if k == :_id +# # For now, IDs are assumed to coincide with row number. +# @assert rownum == v +# continue +# end +# if k ∈ attrs(schema; just_names=true) +# vtype = attr_type(out, k) +# v = v isa AbstractDict && haskey(v, "_var") ? +# AttrVar(v["_var"]) : vtype(v) +# end +# set_subpart!(out, parts[dom(schema, k)][rownum], k, v) +# end +# end +# end +# out +#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 +# +#""" 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 + +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..dfc60bef1 --- /dev/null +++ b/test/categorical_algebra/JSONCSetTransformations.jl @@ -0,0 +1,33 @@ +# 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) where T <: ACSetTransformation + 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_broken roundtrip_json_acset_transformation(g) == g + From 8ff21ea2ecd4793b3908d6bfa7115be660f065ba Mon Sep 17 00:00:00 2001 From: Luke Morris Date: Tue, 31 Oct 2023 12:16:14 -0400 Subject: [PATCH 2/3] Read and Write JSON ACSetTransformations --- .../JSONCSetTransformations.jl | 118 +++++++----------- .../JSONCSetTransformations.jl | 6 +- 2 files changed, 51 insertions(+), 73 deletions(-) diff --git a/src/categorical_algebra/JSONCSetTransformations.jl b/src/categorical_algebra/JSONCSetTransformations.jl index 6547ea95d..39188ba83 100644 --- a/src/categorical_algebra/JSONCSetTransformations.jl +++ b/src/categorical_algebra/JSONCSetTransformations.jl @@ -2,20 +2,14 @@ """ module JSONCSetTransformations export generate_json_fin_function, parse_json_fin_function, - read_json_fin_function, write_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=# + read_json_acset_transformation, write_json_acset_transformation import JSON using DataStructures: OrderedDict -#import Pkg -#import Tables using ..FinSets, ..CSets -# TODO: Some of these `using`s might not be necessary. -using ACSets.ACSetInterface, ACSets.Schemas, ACSets.DenseACSets -using ACSets.DenseACSets: attr_type -using ACSets.ColumnImplementations: AttrVar # ACSetTransformation serialization ##################### @@ -65,72 +59,56 @@ function generate_json_acset_transformation(X::ACSetTransformation) :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) ? - "foo" : - generate_json_fin_function(k) + 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. -#attr_to_json(var::AttrVar) = (_var = var.val,) -#attr_to_json(val) = val -# -#""" Parse JSON-able object or JSON string representing an ACSet. -# -#Inverse to [`generate_json_acset`](@ref). -#""" -#parse_json_acset_transformation(cons, input::AbstractDict) = -# parse_json_acset!(cons(), input) -#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) -# -## TODO -#function parse_json_acset_transformation!(out::ACSetTransformation, input::AbstractDict) -# schema = acset_schema(out) -# parts = Iterators.map(input) do (type, rows) -# Symbol(type) => add_parts!(out, Symbol(type), length(rows)) -# end |> Dict -# for rows ∈ values(input) -# for (rownum, row) ∈ enumerate(rows) -# for (k, v) ∈ pairs(row) -# k = Symbol(k) -# if k == :_id -# # For now, IDs are assumed to coincide with row number. -# @assert rownum == v -# continue -# end -# if k ∈ attrs(schema; just_names=true) -# vtype = attr_type(out, k) -# v = v isa AbstractDict && haskey(v, "_var") ? -# AttrVar(v["_var"]) : vtype(v) -# end -# set_subpart!(out, parts[dom(schema, k)][rownum], k, v) -# end -# end -# end -# out -#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 -# -#""" 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 +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/JSONCSetTransformations.jl b/test/categorical_algebra/JSONCSetTransformations.jl index dfc60bef1..2c9efc590 100644 --- a/test/categorical_algebra/JSONCSetTransformations.jl +++ b/test/categorical_algebra/JSONCSetTransformations.jl @@ -18,16 +18,16 @@ end # ACSetTransformation serialization ################################### -function roundtrip_json_acset_transformation(x::T) where T <: ACSetTransformation +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) + 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_broken roundtrip_json_acset_transformation(g) == g +@test roundtrip_json_acset_transformation(α, WeightedGraph{Float64}) == α From a02baa5731c0610b0b961c4586aaa4b1d43e6261 Mon Sep 17 00:00:00 2001 From: Luke Morris Date: Tue, 31 Oct 2023 14:39:07 -0400 Subject: [PATCH 3/3] Parse FinFunctions of domain 0 --- .../JSONCSetTransformations.jl | 10 ++-- .../JSONCSetTransformations.jl | 57 +++++++++++++++++++ 2 files changed, 62 insertions(+), 5 deletions(-) diff --git a/src/categorical_algebra/JSONCSetTransformations.jl b/src/categorical_algebra/JSONCSetTransformations.jl index 39188ba83..c10847c2d 100644 --- a/src/categorical_algebra/JSONCSetTransformations.jl +++ b/src/categorical_algebra/JSONCSetTransformations.jl @@ -20,9 +20,9 @@ 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)) + :dom => dom(F), + :codom => codom(F), + :map => collect(F)) end """ Serialize a FinFunction object to a JSON file. @@ -37,8 +37,8 @@ end function parse_json_fin_function(input::AbstractDict) FinFunction( - Int.(input["map"]), - input["dom"]["n"], + input["dom"]["n"] != 0 ? Int.(input["map"]) : 1:0, + input["dom"]["n"], input["codom"]["n"]) end diff --git a/test/categorical_algebra/JSONCSetTransformations.jl b/test/categorical_algebra/JSONCSetTransformations.jl index 2c9efc590..39a0852fb 100644 --- a/test/categorical_algebra/JSONCSetTransformations.jl +++ b/test/categorical_algebra/JSONCSetTransformations.jl @@ -1,3 +1,8 @@ +module TestJSONCSetTransformations +using Test + +using Catlab.CategoricalAlgebra, Catlab.Graphs + # FinFunction serialization ########################### function roundtrip_json_fin_function(f::T) where T <: FinFunction @@ -31,3 +36,55 @@ 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 +