Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

draft Model to MTK.___System conversion. #40

Closed
wants to merge 71 commits into from
Closed
Show file tree
Hide file tree
Changes from 65 commits
Commits
Show all changes
71 commits
Select commit Hold shift + click to select a range
cfe431e
draft Model to MTK.___System conversion.
paulflang Apr 14, 2021
fd6ad63
add kwargs to constructors of MTK types (to support @named)
paulflang Apr 22, 2021
32f5e4d
throw error when model contains reversible reactions
paulflang Apr 22, 2021
5f94736
add initialConcentration to species
paulflang Apr 22, 2021
d188def
convert initial_concentrations to initial_amounts
paulflang Apr 22, 2021
402acbc
draft to_extensive_math function
paulflang Apr 22, 2021
6558506
add test case for `to_initial_amounts`
paulflang Apr 23, 2021
de7d16b
add test for mtk_reaktion() and -getsubstitution()
paulflang Apr 23, 2021
bf6b080
untrack Manifest.toml
paulflang Apr 23, 2021
f837186
add test case for u0map
paulflang Apr 23, 2021
f9221d1
add test for get_paramap()
paulflang Apr 23, 2021
09a7228
add tests for creating ReactionSystem
paulflang Apr 23, 2021
8e6a03c
add tests for ODESystem and ODEProblem constructors
paulflang Apr 24, 2021
7e58ca6
add conversion_options argument to readSBML()
paulflang May 3, 2021
4c7e8dd
make OrdinaryDiffEq a test dependency
paulflang May 3, 2021
2c483c0
add test cases for promoting localParameters
paulflang May 3, 2021
3347462
add test case to expandFunctionDefinitions
paulflang May 3, 2021
b235622
add SBML level and version converter
paulflang May 10, 2021
4da59f3
add SBML.Math to Symbolics conversion
paulflang May 28, 2021
f7af82d
resolve merge conflicts from upstream master
paulflang May 29, 2021
6b64f41
resolve merge conflicts from upstream master
paulflang May 29, 2021
fb84639
fix test cases after last merge
paulflang May 29, 2021
a3dce09
impement hOSU handling
paulflang May 30, 2021
570bc7c
add test for `make_extensive()`
paulflang May 30, 2021
beda38e
update test cases
paulflang May 30, 2021
ec1fa52
add test cases
paulflang May 30, 2021
6ee8876
fix failing test cases
paulflang May 30, 2021
bfe4170
centralize imports to src/SBML.jl
paulflang May 31, 2021
40b1d98
add `reversible` field to Reaction
paulflang May 31, 2021
ce4f03b
add checksupport function
paulflang May 31, 2021
537296b
simplify isequal(x, nothing) to isnothing(x)
paulflang May 31, 2021
5455600
clean up comments and file endings
paulflang May 31, 2021
e686e91
allow SBML models with unset SubstanceUnits
paulflang May 31, 2021
6913135
overload Base.convert instead of writing an sbml2symbolics converter …
paulflang May 31, 2021
a1af25a
change type of Model.gene_products and Model.function_definitions
paulflang May 31, 2021
1ffe96a
clean loaddynamimodels
paulflang May 31, 2021
dce53c3
update test/loaddynamicmodels.jl
paulflang Jun 1, 2021
44d88f5
fix failing tests in test/reactionsystem.jl; put variables in testset…
paulflang Jun 1, 2021
69fa2ff
move sbml files to test/data
paulflang Jun 1, 2021
3d0e09f
move variables in test/sbml2symbolics to testset scope
paulflang Jun 1, 2021
667240f
include all test files
paulflang Jun 1, 2021
2d98356
add power as valid SBML function
paulflang Jun 3, 2021
42fcd07
add multiply as valid SBML function
paulflang Jun 3, 2021
d56ab94
add factorial as valid SBML function
paulflang Jun 3, 2021
b7d9abb
add `checksupport` function
paulflang Jun 3, 2021
f528a2f
support hOSU species in compartments without size but raise warning
paulflang Jun 3, 2021
1909a19
add support for bidirectional kineticLaws
paulflang Jun 3, 2021
3da6440
fix conversion to unidirectional reactions
paulflang Jun 4, 2021
1202b30
add ceiling and factorial as valid SBML functions
paulflang Jun 4, 2021
a9a19af
add `piecewise` parsing
paulflang Jun 5, 2021
f23b0c1
use IfElse to evaluate `piecewise`
paulflang Jun 6, 2021
77a5743
report unsupported math elements by name
paulflang Jun 6, 2021
e51201d
remove `println` statements that caused errors bu returning tuple of …
paulflang Jun 6, 2021
778ba4a
add tests for conversion of ReactionSystem to ODESystem and ODESystem…
paulflang Jun 6, 2021
48db58b
remove compartments with no size from parameters
paulflang Jun 6, 2021
3600346
fix simulation for with `ceil` or `factorial` in kineticLaw
paulflang Jun 6, 2021
9932b3b
parse SBML Booleans to SBML.MathVal{Bool} instead of SBML.MathVal{Str…
paulflang Jun 6, 2021
7e11c2e
add `floor` as supported SBML function.
paulflang Jun 6, 2021
d714fb5
use `initial_concentration` as `initial_amount` when Compartment.size…
paulflang Jun 6, 2021
ea55421
throw error when attempting to convert a model without reactions to a…
paulflang Jun 6, 2021
b11f448
set version to 0.4.1
paulflang Jun 7, 2021
4169c47
add independent variable to the states
paulflang Jun 8, 2021
87766de
make all species function of time to get structural_simplify working
paulflang Jun 13, 2021
f460328
add tests for structural_simplify
paulflang Jun 13, 2021
59fd135
raise warning when species are `boundaryCondition` or `constant` and …
paulflang Jun 13, 2021
f726cdb
fix handling of hOSU species
paulflang Jun 14, 2021
3c9d313
sync with upstream master
paulflang Jun 15, 2021
67a8f84
sync upstream master
paulflang Jun 15, 2021
bbe19eb
fix initialConcentration parsing
paulflang Jun 15, 2021
4ae0332
fix hOSU handling
paulflang Jun 15, 2021
1070601
fix tests after merging upstream master
paulflang Jun 16, 2021
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
12 changes: 11 additions & 1 deletion Project.toml
Original file line number Diff line number Diff line change
@@ -1,15 +1,25 @@
name = "SBML"
uuid = "e5567a89-2604-4b09-9718-f5f78e97c3bb"
authors = ["Mirek Kratochvil <[email protected]>", "LCSB R3 team <[email protected]>"]
version = "0.4.0"
version = "0.4.1"

[deps]
Catalyst = "479239e8-5488-4da2-87a7-35f2df7eef83"
IfElse = "615f187c-cbe4-4ef1-ba3b-2fcf58d6d173"
Libdl = "8f399da3-3557-5675-b5ff-fb832c97cbdb"
ModelingToolkit = "961ee093-0014-501f-94e3-6117800e7a78"
Pkg = "44cfe95a-1eb2-52ea-b672-e2afdf69b78f"
SBML_jll = "bb12108a-f4ef-5f88-8ef3-0b33ff7017f1"
SHA = "ea8e919c-243c-51af-8825-aaa63cd721ce"
SparseArrays = "2f01184e-e22b-5df5-ae63-d93ebab69eaf"
Symbolics = "0c5d862f-8b57-4792-8d23-62f2024744c7"
Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40"

[compat]
julia = "1"

[extras]
OrdinaryDiffEq = "1dea7af3-3e70-54e6-95c3-0bf5283fa5ed"

[targets]
test = ["OrdinaryDiffEq"]
4 changes: 4 additions & 0 deletions src/SBML.jl
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,16 @@ module SBML

using SBML_jll, Libdl, Pkg
using SparseArrays
using Catalyst, ModelingToolkit, Symbolics
using IfElse

include("structs.jl")
include("version.jl")
include("readsbml.jl")
include("math.jl")
include("utils.jl")
include("reactionsystem.jl")
include("sbml2symbolics.jl")

sbml = (sym::Symbol) -> dlsym(SBML_jll.libsbml_handle, sym)

Expand Down
9 changes: 6 additions & 3 deletions src/math.jl
Original file line number Diff line number Diff line change
Expand Up @@ -23,13 +23,16 @@ This attempts to parse out a decent Julia-esque ([`Math`](@ref) AST from a
pointer to `ASTNode_t`.
"""
function parse_math(ast::VPtr)::Math
if ast_is(ast, :ASTNode_isName) || ast_is(ast, :ASTNode_isConstant)
# PL: @Mirek: can you parse nodes of type AST_NAME_TIME MathVals where val is Catalyst.DEFAUL_IV?
if ast_is(ast, :ASTNode_isBoolean)
return MathVal(Bool(ccall(sbml(:ASTNode_getInteger), Cint, (VPtr,), ast)))
elseif ast_is(ast, :ASTNode_isName) || ast_is(ast, :ASTNode_isConstant)
return MathIdent(get_string(ast, :ASTNode_getName))
elseif ast_is(ast, :ASTNode_isInteger)
return MathVal(ccall(sbml(:ASTNode_getInteger), Cint, (VPtr,), ast))
elseif ast_is(ast, :ASTNode_isReal)
return MathVal(ccall(sbml(:ASTNode_getReal), Cdouble, (VPtr,), ast))
elseif ast_is(ast, :ASTNode_isFunction)
elseif ast_is(ast, :ASTNode_isFunction) || ast_is(ast, :ASTNode_isRelational)
return MathApply(get_string(ast, :ASTNode_getName), parse_math_children(ast))
elseif ast_is(ast, :ASTNode_isOperator)
return MathApply(
Expand All @@ -46,7 +49,7 @@ function parse_math(ast::VPtr)::Math
return MathIdent("?invalid?")
end
else
@warn "unsupported math element found"
@warn "unsupported math element found: $(get_string(ast, :ASTNode_getName))"
return MathIdent("?unsupported?")
end
end
224 changes: 224 additions & 0 deletions src/reactionsystem.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,224 @@
""" ReactionSystem constructor """
function ModelingToolkit.ReactionSystem(model::Model; kwargs...) # Todo: requires unique parameters (i.e. SBML must have been imported with localParameter promotion in libSBML)
checksupport(model)
model = make_extensive(model)
rxs = mtk_reactions(model)
species = []
for k in keys(model.species)
push!(species, create_var(k,Catalyst.DEFAULT_IV))
end
params = vcat([create_param(k) for k in keys(model.parameters)], [create_param(k) for (k,v) in model.compartments if !isnothing(v.size)])
ReactionSystem(rxs,Catalyst.DEFAULT_IV,species,params; kwargs...)
end

""" ReactionSystem constructor """
function ModelingToolkit.ReactionSystem(sbmlfile::String; kwargs...)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure we will want to steal the String dispatch, since there are other formats that create a ReactionSystem (BioNetGen IIRC).

I think just having ModelingToolkit.ReactionSystem(model::Model; kwargs...) might be good enough.
It's two function calls, but saves us from committing type piracy down the line.

conversion_options = Dict("promoteLocalParameters" => nothing,
"expandFunctionDefinitions" => nothing,
"expandInitialAssignments" => nothing)
model = readSBML(sbmlfile;conversion_options=conversion_options)
ReactionSystem(model; kwargs...)
end

""" ODESystem constructor """
function ModelingToolkit.ODESystem(model::Model; kwargs...)
rs = ReactionSystem(model; kwargs...)
model = make_extensive(model) # PL: consider making `make_extensive!` to avoid duplicate calling in ReactionSystem and here
u0map = get_u0(model)
parammap = get_paramap(model)
defaults = Dict(vcat(u0map, parammap))
convert(ODESystem, rs, defaults=defaults)
end

""" ODESystem constructor """
function ModelingToolkit.ODESystem(sbmlfile::String; kwargs...)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same for this dispatch, I think this is type-piracy https://docs.julialang.org/en/v1/manual/style-guide/#Avoid-type-piracy. It's not a big deal ATM though, so I'm fine with it

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not a problem now, I think:

julia> using ModelingToolkit, Catalyst

julia> methods(ReactionSystem)
# 3 methods for type constructor:
[1] ReactionSystem(iv; kwargs...) in ModelingToolkit at C:\Users\wolf5212\.julia\packages\ModelingToolkit\Bb2DA\src\systems\reaction\reactionsystem.jl:161
[2] ReactionSystem(eqs, iv, species, params; observed, systems, name) in ModelingToolkit at C:\Users\wolf5212\.julia\packages\ModelingToolkit\Bb2DA\src\systems\reaction\reactionsystem.jl:152
[3] ReactionSystem(eqs, iv, states, ps, observed, name, systems) in ModelingToolkit at C:\Users\wolf5212\.julia\packages\ModelingToolkit\Bb2DA\src\systems\reaction\reactionsystem.jl:147

julia> methods(ODESystem)
# 6 methods for type constructor:
[1] ODESystem(eq::Equation, args...; kwargs...) in ModelingToolkit at C:\Users\wolf5212\.julia\packages\ModelingToolkit\Bb2DA\src\systems\diffeqs\odesystem.jl:226
[2] ODESystem(eqs::Vector{Equation}, iv::Sym, states::Vector{T} where T, ps::Vector{T} where T, observed::Vector{Equation}, tgrad::Base.RefValue{Vector{Num}}, jac::Base.RefValue{Any}, Wfact::Base.RefValue{Matrix{Num}}, 
Wfact_t::Base.RefValue{Matrix{Num}}, name::Symbol, systems::Vector{ODESystem}, defaults::Dict, structure, reduced_states::Vector{T} where T) in ModelingToolkit at C:\Users\wolf5212\.julia\packages\ModelingToolkit\Bb2DA\src\systems\diffeqs\odesystem.jl:26
[3] ODESystem(deqs::AbstractVector{var"#s6"} where var"#s6"<:Equation, iv, dvs, ps; observed, systems, name, default_u0, default_p, defaults) in ModelingToolkit at C:\Users\wolf5212\.julia\packages\ModelingToolkit\Bb2DA\src\systems\diffeqs\odesystem.jl:75
[4] ODESystem(eqs) in ModelingToolkit at C:\Users\wolf5212\.julia\packages\ModelingToolkit\Bb2DA\src\systems\diffeqs\odesystem.jl:137
[5] ODESystem(eqs, iv; kwargs...) in ModelingToolkit at C:\Users\wolf5212\.julia\packages\ModelingToolkit\Bb2DA\src\systems\diffeqs\odesystem.jl:137
[6] ODESystem(eqs, iv, states, ps, observed, tgrad, jac, Wfact, Wfact_t, name, systems, defaults, structure, reduced_states) in ModelingToolkit at C:\Users\wolf5212\.julia\packages\ModelingToolkit\Bb2DA\src\systems\diffeqs\odesystem.jl:26

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The real question is where reaction network importers like our SBML importer (but also CellML and BioNetGen importers) should go in the end. I am not yet convinced that having separate packages for every model specification language is the right way to go. What type of file is handled can be automatically inferred from the ending or header given a filename as String.
We could put the importers in:

  • ModelingToolkit.jl: For instance, we could dispatch the filename as String to the ReactionSystem constructor: ModelingToolkit.ReactionNetwork(filename::String)
  • ReactionNetworkImporters.jl: For instance, we could dispatch a NetworkFileFormat constructor such as BNGNetwork() and filename to loadrxnnetwork: ReactionNetworkImporters.loadrxnetwork(BNGNetwork(), filename)

@isaacsas: any thoughts?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@shahriariravanian: what are your plans for CellMLToolkit. Do you think it should be moved under ReactionNetworkImporters.jl (see also Sam's and Chris' comment below)? Or shall it remain a standalone library?

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think eventually it is a good idea to put all of them under the same package, but ReactionNetworkImporters doesn't seem like the right place for CellMLToolkit.

First, CellMLToolkit mainly outputs ODESystems (and possibly DAESystems in the next version), not reaction networks. While CellMLToolkit specification can handle reaction networks, very few actual CellML files contain one (the same that SBML files can define an ODE, but most don't). For whatever historical reason, CellML and SBML have diverged to their own niches.

Second, while it is currently only an importer, the future plan for CellMLToolkit is -- after adding DAEs and implementing units handling and Unitful integration -- to write an exporter. I don't know if you plan to add export functionality to SBML, but I feel that to use the full benefits of symbolic manipulation, we need to have the capability to output the results.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@ChrisRackauckas @isaacsas @shahriariravanian @anandijain : After @shahriariravanian 's response that CellMLToolkit will grow bigger and does not fit well under the ReactionNetworkImporters umbrella I am inclined to have all Toolkits for biomodel importers as independent packages. Most users have their favourite model specification language and will ever only need one importer, so there are few reasons to combine them in one package. The only reason I can think of, is to make sure they have a similar API. But for the few users who use two or more model specification languages we can still take care that the API looks similar across importers.

To this end, I could imagine mimicking the constructors of the MTK.AbstractSystem types without overloading them to avoid type piracy. A bit like so (or is this a bad idea?):

module MTK
ODEsys(x) = println(x)
export ODEsys
end

module CellMLTK
using Main.MTK
ODEsys(x::String) = Main.MTK.ODEsys("Creating ODEsys from CellML file")
end

module SbmlTK
using Main.MTK
ODEsys(x::String) = Main.MTK.ODEsys("Creating ODEsys from SBML file")
end

using Main.MTK
x = "Creating ODEsys from scratch"
ODEsys(x)  # Creating ODEsys from scratch

x="Cannot create ODEsys from filename"
ODEsys(x)  # Cannot create ODEsys from filename
Main.CellMLTK.ODEsys(x)  # Creating ODEsys from CellML file
Main.SbmlTK.ODEsys(x)  # Creating ODEsys from SBML file

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please don't shadow, just extend.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To avoid misunderstandings, I assume shadowing is creating another function with the same name that takes precedence over the old function. And extending means overloading with a more specific type to make use of multiple dispatch. Is this correct?

Anyway, I do not get it to work. Especially not without avoiding type piracy.

module MTK
ODEsys(x) = println(x)
export ODEsys
end
module CellMLTK
using Main.MTK
Main.MTK.ODEsys(x::String) = ODEsys("Creating ODEsys from CellML file")
end
# module SbmlTK
# using Main.MTK
# Main.MTK.ODEsys(x::String) = ODEsys("Creating ODEsys from SBML file")
# export ODEsys
# end
using Main.MTK, Main.CellMLTK  #, Main.SbmlTK
x = "Creating ODEsys from scratch"
ODEsys(x)  # Creating ODEsys from scratch
ERROR: StackOverflowError:
Stacktrace:
 [1] ODEsys(x::String) (repeats 79984 times)
   @ Main.CellMLTK .\REPL[2]:3

Do you have any suggestions?

model = readSBML(sbmlfile)
ODESystem(model; kwargs...)
end

""" ODEProblem constructor """
function ModelingToolkit.ODEProblem(model::Model,tspan;kwargs...) # PL: Todo: add u0 and parameters argument
odesys = ODESystem(model)
ODEProblem(odesys, [], tspan; kwargs...)
end

""" ODEProblem constructor """
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

regarding the docstrings, please include full function types and signatures, and preferably some hint about what precisely the function does it & how.

function ModelingToolkit.ODEProblem(sbmlfile::String,tspan;kwargs...) # PL: Todo: add u0 and parameters argument
odesys = ODESystem(sbmlfile)
ODEProblem(odesys, [], tspan; kwargs...)
end
Comment on lines +36 to +46
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would assume it would be best to have the user explicitly construct the ODESystem -> ODEProblem. This is one of those shorthands that causes doc problems.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks fishy for sure, and from the user perspective the code with the shorthand is almost as long as without the shorthand.


""" Check if conversion to ReactionSystem is possible """
function checksupport(model::Model)
for s in values(model.species)
if s.boundary_condition
@warn "Species $(s.name) has `boundaryCondition` or is `constant`. This will lead to wrong results when simulating the `ReactionSystem`."
end
end
end

""" Convert intensive to extensive expressions """
function make_extensive(model)
model = to_initial_amounts(model)
model = to_extensive_math!(model)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is this an inplace modification (implied by !) or a normal new-value-returning function?

model
end

""" Convert initial_concentration to initial_amount """
function to_initial_amounts(model::Model)
model = deepcopy(model)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

you don't need a full copy here, just copy()ing the model struct and deepcopying the species is okay.

for specie in values(model.species)
if isnothing(specie.initial_amount)
compartment = model.compartments[specie.compartment]
if !isnothing(compartment.size)
specie.initial_amount = (specie.initial_concentration[1] * compartment.size, "")
else
@warn "Compartment $(compartment.name) has no `size`. Cannot calculate `initial_amount` of Species $(specie.name). Setting `initial_amount` to `initial_concentration`."
specie.initial_amount = (specie.initial_concentration[1], "")
end
specie.initial_concentration = nothing
end
end
model
end

""" Convert intensive to extensive mathematical expression """
function to_extensive_math!(model::SBML.Model)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I find the intensive vs. extensive naming a bit confusing, but that's maybe because I didn't work with this much.

If I get it correctly, this here means basically "substitute the compartment sizes" right?

Copy link
Collaborator Author

@paulflang paulflang May 31, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes it is confusing. Even the SBML specs confused it (should be fixed now or soon). But it has a clear definition in physics and I cannot think of a better terminology. I am open to alternative suggestions, though.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What does it really mean then? "Relative to volume of the container?"

function conv(x::SBML.MathApply)
SBML.MathApply(x.fn, SBML.Math[conv(x) for x in x.args])
end
function conv(x::SBML.MathIdent)
x_new = x
if x.id in keys(model.species)
specie = model.species[x.id]
if !specie.only_substance_units
compartment = model.compartments[specie.compartment]
if isnothing(compartment.size)
@warn "Specie $(x.id) hasOnlySubstanceUnits but its compartment $(compartment.name) has no size. Cannot auto-correct the rate laws $(x.id) is involved in. Please check manually."
else
x_new = SBML.MathApply("*", SBML.Math[
SBML.MathVal(compartment.size),
x])
specie.only_substance_units = true
end
end
end
x_new
end
conv(x::SBML.MathVal) = x
conv(x::SBML.MathLambda) =
throw(DomainError(x, "can't translate lambdas to extensive units"))
for reaction in values(model.reactions)
reaction.kinetic_math = conv(reaction.kinetic_math)
end
model
end

""" Get dictonary to change types in kineticLaw """
function _get_substitutions(model)
subsdict = Dict()
for k in keys(model.species)
push!(subsdict, Pair(create_var(k),create_var(k,Catalyst.DEFAULT_IV)))
end
for k in keys(model.parameters)
push!(subsdict, Pair(create_var(k),create_param(k)))
end
for k in keys(model.compartments)
push!(subsdict, Pair(create_var(k),create_param(k)))
end
subsdict
end

""" Convert SBML.Reaction to MTK.Reaction """
function mtk_reactions(model::Model)
subsdict = _get_substitutions(model)
rxs = []
if length(model.reactions) == 0
throw(ErrorException("Model contains no reactions."))
end
for reaction in values(model.reactions)
reactants = Num[]
rstoich = Num[]
products = Num[]
pstoich = Num[]
for (k,v) in reaction.stoichiometry
if v < 0
push!(reactants, create_var(k,Catalyst.DEFAULT_IV))
push!(rstoich, -v)
elseif v > 0
push!(products, create_var(k,Catalyst.DEFAULT_IV))
push!(pstoich, v)
else
@error("Stoichiometry of $k must be non-zero")
end
end
if (length(reactants)==0) reactants = nothing; rstoich = nothing end
if (length(products)==0) products = nothing; pstoich = nothing end
symbolic_math = convert(Num, reaction.kinetic_math)

if reaction.reversible
symbolic_math = getunidirectionalcomponents(symbolic_math)
kl = [substitute(x, subsdict) for x in symbolic_math]
push!(rxs, ModelingToolkit.Reaction(kl[1],reactants,products,rstoich,pstoich;only_use_rate=true))
push!(rxs, ModelingToolkit.Reaction(kl[2],products,reactants,pstoich,rstoich;only_use_rate=true))
else
kl = substitute(symbolic_math, subsdict)
push!(rxs, ModelingToolkit.Reaction(kl,reactants,products,rstoich,pstoich;only_use_rate=true))
end
end
rxs
end

""" Infer forward and reverse components of bidirectional kineticLaw """
function getunidirectionalcomponents(bidirectional_math)
err = "Cannot separate bidirectional kineticLaw `$bidirectional_math` to forward and reverse part. Please make reaction irreversible or rearrange kineticLaw to the form `term1 - term2`."
bidirectional_math = Symbolics.tosymbol(bidirectional_math)
bidirectional_math = simplify(bidirectional_math; expand=true)
if SymbolicUtils.operation(bidirectional_math) != +
throw(ErrorException(err))
end
terms = SymbolicUtils.arguments(bidirectional_math)
fw_terms = []
rv_terms = []
for term in terms
if (term isa SymbolicUtils.Mul) && (term.coeff < 0)
push!(rv_terms, Num(-term)) # PL: @Anand: Perhaps we should to create_var(term) or so?
else
push!(fw_terms, Num(term)) # PL: @Anand: Perhaps we should to create_var(term) or so?
end
end
if (length(fw_terms) != 1) || (length(rv_terms) != 1)
throw(ErrorException(err))
end
return (fw_terms[1], rv_terms[1])
end

""" Extract u0map from Model """
function get_u0(model)
u0map = []
for (k,v) in model.species
push!(u0map,Pair(create_var(k,Catalyst.DEFAULT_IV), v.initial_amount[1]))
end
u0map
end

""" Extract paramap from Model """
function get_paramap(model)
paramap = Pair{Num, Float64}[]
for (k,v) in model.parameters
push!(paramap,Pair(create_param(k),v))
end
for (k,v) in model.compartments
if !isnothing(v.size)
push!(paramap,Pair(create_param(k),v.size))
end
end
paramap
end

create_var(x, iv) = Num(Variable{Symbolics.FnType{Tuple{Any},Real}}(Symbol(x)))(iv).val
create_var(x) = Num(Variable(Symbol(x))).val
function create_param(x)
p = Sym{Real}(Symbol(x))
ModelingToolkit.toparam(p)
end
Loading