From 0db6bf3444cd6d2bfd61b02f12b488a97755c6b9 Mon Sep 17 00:00:00 2001
From: anand jain <anandj@uchicago.edu>
Date: Wed, 18 Jan 2023 16:52:37 -0500
Subject: [PATCH] basic nameswapping utility for SimBiology

---
 Project.toml               |  1 +
 src/SBMLToolkit.jl         |  2 ++
 src/nameswap.jl            | 72 ++++++++++++++++++++++++++++++++++++++
 test/data/simpleModel.sbml | 39 +++++++++++++++++++++
 test/nameswap.jl           | 13 +++++++
 test/runtests.jl           |  1 +
 6 files changed, 128 insertions(+)
 create mode 100644 src/nameswap.jl
 create mode 100644 test/data/simpleModel.sbml
 create mode 100644 test/nameswap.jl

diff --git a/Project.toml b/Project.toml
index ab80221..161fb88 100644
--- a/Project.toml
+++ b/Project.toml
@@ -7,6 +7,7 @@ version = "0.1.19"
 Catalyst = "479239e8-5488-4da2-87a7-35f2df7eef83"
 MathML = "abcecc63-2b08-419c-80c4-c63dca6fa478"
 SBML = "e5567a89-2604-4b09-9718-f5f78e97c3bb"
+Setfield = "efcf1570-3423-57d1-acb7-fd33fddbac46"
 SymbolicUtils = "d1185830-fcd6-423d-90d6-eec64667417b"
 
 [compat]
diff --git a/src/SBMLToolkit.jl b/src/SBMLToolkit.jl
index 897094b..019e88b 100644
--- a/src/SBMLToolkit.jl
+++ b/src/SBMLToolkit.jl
@@ -3,12 +3,14 @@ module SBMLToolkit
 using Catalyst
 using SBML
 using SymbolicUtils
+using Setfield
 
 include("systems.jl")
 include("reactions.jl")
 include("rules.jl")
 include("events.jl")
 include("utils.jl")
+include("nameswap.jl")
 
 export ReactionSystem, ODESystem
 export readSBML, readSBMLFromString, set_level_and_version, convert_simplify_math
diff --git a/src/nameswap.jl b/src/nameswap.jl
new file mode 100644
index 0000000..613a88b
--- /dev/null
+++ b/src/nameswap.jl
@@ -0,0 +1,72 @@
+function swap_id_name(k, v; prop = :name)
+    new_prop = getproperty(v, prop)
+    @set! v.$prop = k
+    new_prop, v
+end
+
+function replace_math_ident(id_name_dict, node)
+    if typeof(node) == SBML.MathIdent
+        return SBML.MathIdent(id_name_dict[node.id])
+    elseif typeof(node) == SBML.MathApply
+        return SBML.MathApply(node.fn, replace_math_ident(id_name_dict, node.args))
+    elseif isa(node, AbstractArray)
+        return map(x -> replace_math_ident(id_name_dict, x), node)
+    else
+        return node
+    end
+end
+
+"SimBiology makes the names of everything a hashed ID, but keeps the ID in the name"
+function fix_simbio_names(m::SBML.Model)
+    cid_name_d = Dict(keys(m.compartments) .=> getproperty.(values(m.compartments), :name))
+    pid_name_d = Dict(keys(m.parameters) .=> getproperty.(values(m.parameters), :name))
+    rid_name_d = Dict(keys(m.reactions) .=> getproperty.(values(m.reactions), :name))
+
+    for prop in [:compartments, :parameters, :reactions]
+        xs = getproperty(m, prop)
+        new_xs = Dict()
+        for (k, v) in xs
+            nk, nv = swap_id_name(k, v)
+            new_xs[nk] = nv
+        end
+        @set! m.$prop = new_xs
+    end
+
+    new_ss = Dict()
+    for (k, v) in m.species
+        nk, nv = swap_id_name(k, v)
+        cname = cid_name_d[v.compartment]
+        # for species, we want to concat the name and compartment name like in simbiology and MTK
+        sname = string(cname, "₊", nk)
+        @set! nv.compartment = cname
+        new_ss[sname] = nv
+    end
+    @set! m.species = new_ss
+
+    sid_name_d = Dict(reverse.(keys(new_ss) .=> getproperty.(values(new_ss), :name)))
+
+    ds = [cid_name_d, pid_name_d, rid_name_d, sid_name_d]
+    slen = sum(length.(ds))
+    id_name_dict = merge(ds...)
+    @assert slen == length(id_name_dict)
+
+    for (k, v) in m.reactions
+        for (i, sr) in enumerate(v.reactants)
+            @set! sr.species = id_name_dict[sr.species]
+            m.reactions[k].reactants[i] = sr
+        end
+        for (i, sr) in enumerate(v.products)
+            @set! sr.species = id_name_dict[sr.species]
+            m.reactions[k].products[i] = sr
+        end
+        @set! m.reactions[k].kinetic_math = replace_math_ident(id_name_dict, v.kinetic_math)
+    end
+
+    for (i, r) in enumerate(m.rules)
+        @set! m.rules[i].variable = id_name_dict[r.variable]
+        new_tree = replace_math_ident(id_name_dict, r.math)
+        @set! m.rules[i].math = new_tree
+    end
+
+    m
+end
diff --git a/test/data/simpleModel.sbml b/test/data/simpleModel.sbml
new file mode 100644
index 0000000..c2a20e6
--- /dev/null
+++ b/test/data/simpleModel.sbml
@@ -0,0 +1,39 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<sbml xmlns="http://www.sbml.org/sbml/level2/version4" level="2" version="4">
+  <annotation>
+    <SimBiology xmlns="http://www.mathworks.com">
+      <Version Major="6" Minor="4" Point="0"/>
+    </SimBiology>
+  </annotation>
+  <model id="mw97c68eca_aecf_484b_b128_48c812a0c25c" name="simpleModel">
+    <listOfCompartments>
+      <compartment id="mwfb3bbd58_15e9_4f26_9be2_0c1221191847" name="unnamed" size="1" constant="true"/>
+    </listOfCompartments>
+    <listOfSpecies>
+      <species id="mw31016a43_49a9_41bf_8a3f_346484470a6e" name="A" compartment="mwfb3bbd58_15e9_4f26_9be2_0c1221191847" initialConcentration="10" boundaryCondition="false" constant="false"/>
+      <species id="mw6966d9de_9719_43d5_8fbe_40a117eee9c7" name="B" compartment="mwfb3bbd58_15e9_4f26_9be2_0c1221191847" initialConcentration="0" boundaryCondition="false" constant="false"/>
+    </listOfSpecies>
+    <listOfReactions>
+      <reaction id="mw8998ca1f_76ee_4090_948e_b455153beda5" name="Reaction_1" reversible="false">
+        <listOfReactants>
+          <speciesReference species="mw31016a43_49a9_41bf_8a3f_346484470a6e" stoichiometry="1"/>
+        </listOfReactants>
+        <listOfProducts>
+          <speciesReference species="mw6966d9de_9719_43d5_8fbe_40a117eee9c7" stoichiometry="1"/>
+        </listOfProducts>
+        <kineticLaw>
+          <math xmlns="http://www.w3.org/1998/Math/MathML">
+            <apply>
+              <times/>
+              <ci> mwbb21d5b0_5b44_4d2f_8d51_1aaff3f8bf6d </ci>
+              <ci> mw31016a43_49a9_41bf_8a3f_346484470a6e </ci>
+            </apply>
+          </math>
+          <listOfParameters>
+            <parameter id="mwbb21d5b0_5b44_4d2f_8d51_1aaff3f8bf6d" name="k" value="0.5" constant="true"/>
+          </listOfParameters>
+        </kineticLaw>
+      </reaction>
+    </listOfReactions>
+  </model>
+</sbml>
diff --git a/test/nameswap.jl b/test/nameswap.jl
new file mode 100644
index 0000000..37c0328
--- /dev/null
+++ b/test/nameswap.jl
@@ -0,0 +1,13 @@
+using SBML
+
+sbml_fn = joinpath(@__DIR__, "data/simpleModel.sbml")
+
+m_ = readSBML(sbml_fn, doc -> begin
+    set_level_and_version(3, 2)(doc)
+    convert_simplify_math(doc)
+end)
+
+m = deepcopy(m_)
+m2 = SBML.fix_simbio_names(m)
+
+@test keys(m2.species) == Set(["unnamed₊A", "unnamed₊B"])
diff --git a/test/runtests.jl b/test/runtests.jl
index 545d195..9737dda 100644
--- a/test/runtests.jl
+++ b/test/runtests.jl
@@ -8,4 +8,5 @@ using SafeTestsets, Test
     @safetestset "Utils" begin include("utils.jl") end
     @safetestset "Simulation results" begin include("simresults.jl") end
     @safetestset "Wuschel" begin include("wuschel.jl") end
+    @safetestset "Nameswap" begin include("nameswap.jl") end
 end