AlgebraicJulia / Catlab.jl

A framework for applied category theory in the Julia language
https://www.algebraicjulia.org
MIT License
614 stars 58 forks source link

Issue with `parse_json_acset` #711

Open p-stokes opened 2 years ago

p-stokes commented 2 years ago

While trying to write a serialized directed wiring diagram ACSet out to file and then read it back in, I encountered an issue with parse_json_acset. write_json_acset and generate_json_acset work (in this case), but read_json_acset does not because parse_json_acset errors. I have included a script demonstrating the issue and a proposed fixed below. Note that the problematic calls to read_json_acset and parse_json_acset have been commented out at lines 36 and 59, respectively, to enable the script to run through entirely.

using Catlab, Catlab.Theories
using Catlab.CategoricalAlgebra
using Catlab.WiringDiagrams
using Catlab.Programs
using Catlab.Programs.RelationalPrograms
import Catlab.WiringDiagrams.DirectedWiringDiagrams: WiringDiagramACSet

# Form OfficeSpace presentation of FreeBiproductCategory
@present OfficeSpace(FreeBiproductCategory) begin
    (LatePeter,CubiclePeter,LookLikeWorkingPeter,UncoveredTPSReport,HassledPeter,Milton,StaplerStatus,Lumbergh,BuildingOnFire)::Ob 
    UseSideDoorSoLumberghDoesntSee::Hom(LatePeter,CubiclePeter)
    SpaceOutForAnHour::Hom(CubiclePeter,LookLikeWorkingPeter)
    Do15MinActualWork::Hom(LookLikeWorkingPeter,CubiclePeter)
    FileTPSReport::Hom(CubiclePeter⊗UncoveredTPSReport,HassledPeter)
    TakeStapler::Hom(Milton⊗Lumbergh,BuildingOnFire)
end

# Form case_of_modays wiring diagram of OfficeSpace
case_of_mondays = @program OfficeSpace (lp::LatePeter,m::Milton,tps::UncoveredTPSReport,l::Lumbergh) begin # 
    cp = UseSideDoorSoLumberghDoesntSee(lp)
    llwp = SpaceOutForAnHour(cp)
    cp = Do15MinActualWork(llwp)
    hassled_status = FileTPSReport(cp,tps)
    building_status = TakeStapler(m,l)
    return hassled_status, building_status
end

# Round trip of write/read json of wiring diagram acset errors in read_json_acset
function roundtrip_json_acset(x::T) where T <: ACSet
    mktempdir() do dir
      path = joinpath(dir, "acset.json")
      write_json_acset(x, path)
      read_json_acset(T, path)
    end
  end
# rt_json_acset = roundtrip_json_acset(case_of_mondays.diagram)

#=
ERROR: MethodError: no method matching DataType(::String)
Stacktrace:
 [1] parse_json_acset(#unused#::Type{WiringDiagramACSet{Any, Any, Any, DataType}}, input::Dict{String, Any})
   @ Catlab.CategoricalAlgebra.CSets ~/.julia/packages/Catlab/hMLnj/src/categorical_algebra/CSets.jl:1199
 [2] read_json_acset(#unused#::Type{WiringDiagramACSet{Any, Any, Any, DataType}}, fname::String)
   @ Catlab.CategoricalAlgebra.CSets ~/.julia/packages/Catlab/hMLnj/src/categorical_algebra/CSets.jl:1216
 [3] #29
   @ ./REPL[67]:5 [inlined]
 [4] mktempdir(fn::var"#29#30"{WiringDiagramACSet{Any, Any, Any, DataType}, WiringDiagramACSet{Any, Any, Any, DataType}}, parent::String; prefix::String)
   @ Base.Filesystem ./file.jl:764
 [5] mktempdir (repeats 2 times)
   @ ./file.jl:760 [inlined]
 [6] roundtrip_json_acset(x::WiringDiagramACSet{Any, Any, Any, DataType})
   @ Main ./REPL[67]:2
 [7] top-level scope
   @ REPL[69]:1
=#

# Round trip of generate/parse json of acset errors in parse_json_acset
json_from_wd_acset = generate_json_acset(case_of_mondays.diagram)
# rt_wd_acset = parse_json_acset(WiringDiagramACSet{Any,Any,Any,Any},json_from_wd_acset)

#=
ERROR: MethodError: no method matching iterate(::Symbol)
Closest candidates are:
  iterate(::Union{LinRange, StepRangeLen}) at range.jl:872
  iterate(::Union{LinRange, StepRangeLen}, ::Integer) at range.jl:872
  iterate(::T) where T<:Union{Base.KeySet{<:Any, <:Dict}, Base.ValueIterator{<:Dict}} at dict.jl:712
  ...
Stacktrace:
 [1] indexed_iterate(I::Symbol, i::Int64)
   @ Base ./tuple.jl:91
 [2] parse_json_acset(#unused#::Type{WiringDiagramACSet{Any, Any, Any, DataType}}, input::OrderedCollections.OrderedDict{Symbol, Vector})
   @ Catlab.CategoricalAlgebra.CSets ~/.julia/packages/Catlab/hMLnj/src/categorical_algebra/CSets.jl:1196
 [3] top-level scope
   @ REPL[66]:1
=#

#**********
# HOWEVER *
#**********
# Modifying the parse_json_acset function from CSets.jl as follows works
function new_parse_json_acset(::Type{T}, input::AbstractDict) where T <: ACSet
  out = T()
  for (k,v) ∈ input
    add_parts!(out, Symbol(k), length(v))
  end
  for l ∈ values(input)
    for (i, j) ∈ enumerate(l)
      for k ∈ keys(j) # (k,v) = j # 
        v = j[k]
        vtype = eltype(out[Symbol(k)])
        if !(v isa vtype)
          v = vtype(v)
        end
        set_subpart!(out, i, Symbol(k), v)
      end
    end
  end
  out
end
rt_wd_acset = new_parse_json_acset(WiringDiagramACSet{Any,Any,Any,DataType},json_from_wd_acset)
println("Check Roundtrip WD ACSet Equals Original WD ACSet")
println(rt_wd_acset == case_of_mondays.diagram)
println("")

rt_wd = WiringDiagram{ThBiproductCategory,Any,Any,Any}(rt_wd_acset,nothing)
println("Check Roundtrip WD Equals Original WD")
println(rt_wd == case_of_mondays)
println("")

# This check is actually for comparison with a separate issue that has been avoided here
println("Check Types of Roundtrip WD ACSet and Original WD ACSet")
print("Orig: ") 
println(typeof(case_of_mondays.diagram))
print("RT: ") 
println(typeof(rt_wd_acset))
print("Equality of types: ")
println(typeof(rt_wd_acset) == typeof(case_of_mondays.diagram))
epatters commented 1 year ago

Hi @p-stokes, has this issue been resolved?

p-stokes commented 1 year ago

I'm not sure. I'll test it out.