Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions src/categorical_algebra/CategoricalAlgebra.jl
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -43,4 +44,6 @@ include("Slices.jl")
@reexport using .StructuredCospans
@reexport using .Slices

@reexport using .JSONCSetTransformations

end
114 changes: 114 additions & 0 deletions src/categorical_algebra/JSONCSetTransformations.jl
Original file line number Diff line number Diff line change
@@ -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

4 changes: 4 additions & 0 deletions test/categorical_algebra/CategoricalAlgebra.jl
Original file line number Diff line number Diff line change
Expand Up @@ -60,5 +60,9 @@ end
include("Slices.jl")
end

@testset "JSONCSetTransformations" begin
include("JSONCSetTransformations.jl")
end


end
90 changes: 90 additions & 0 deletions test/categorical_algebra/JSONCSetTransformations.jl
Original file line number Diff line number Diff line change
@@ -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