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 7 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
510 changes: 510 additions & 0 deletions Manifest.toml

Large diffs are not rendered by default.

3 changes: 3 additions & 0 deletions Project.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,14 @@ authors = ["Mirek Kratochvil <[email protected]>", "LCSB R3 team <lcsb-
version = "0.4.0"

[deps]
Catalyst = "479239e8-5488-4da2-87a7-35f2df7eef83"
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]
Expand Down
3 changes: 2 additions & 1 deletion src/SBML.jl
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,11 @@ include("version.jl")
include("readsbml.jl")
include("math.jl")
include("utils.jl")
include("reactionsystem.jl")

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

export SBMLVersion,
readSBML, Model, UnitPart, Species, Reaction, getS, getLBs, getUBs, getOCs
readSBML, Model, UnitPart, Compartment, Species, Reaction, getS, getLBs, getUBs, getOCs

end # module
152 changes: 152 additions & 0 deletions src/reactionsystem.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,152 @@
using Catalyst, ModelingToolkit, Symbolics
paulflang marked this conversation as resolved.
Show resolved Hide resolved

# module SBML
# struct Model end
# struct Reaction end
# struct Compartment end
# struct Species end
# end

""" ReactionSystem constructor """
function ModelingToolkit.ReactionSystem(model::Model; kwargs...) # Todo: requires unique parameters (i.e. SBML must have been imported with localParameter promotion in libSBML)
model = make_extensive(model)
model = expand_reversible(model)
rxs = mtk_reaction.(model.reactions)
t = DEFAULT_IV
species = [create_var(k) for k in keys(model.species)]
params = vcat([create_var(k) for k in keys(model.parameters)], [create_par(k) for k in keys(model.compartments)])
ReactionSystem(rxs,t,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.

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

""" ODESystem constructor """
function ModelingToolkit.ODESystem(model::Model; kwargs...)
rs = ReactionSystem(model, kwargs...)
u0map = get_u0(model)
parammap = get_paramap(model)
defaults = 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,kwargs...)
ODEProblem(odesys, [], tspan)
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;kwargs...)
ODEProblem(odesys, [], tspan)
end

""" Convert intensive to extensive expressions """
function make_extensive(model)
model = to_initial_amounts(model)
# model = to_extensive_math(model)
model # Todo: For spevies with `hOSU=false` multiply all occurences in mathematical expressions by compartment size.
# Also convert species initialConcentrations to initialAmounts
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 isequal(specie.initial_amount, nothing)
paulflang marked this conversation as resolved.
Show resolved Hide resolved
compartment = model.compartments[specie.compartment]
specie.initial_amount = (specie.initial_concentration[1] * compartment.size, "")
specie.initial_concentration = nothing
end
end
model
end

""" Convert intensive to extensive mathematical expression """
function to_extensive_math(model::Model)
model = deepcopy(model)
for reaction in model.reactions
km = reaction.kinetic_math
reaction.km = 1. # PL: Todo: @Anand can you multiply species with `hOSU=true` with their compartment volume?
end
reaction
end

""" Expand reversible reactions to two reactions """
function expand_reversible(model)
model # Todo: convert all Reactions that are `reversible=true` to a forward and reverse reaction with `reversible=false`.
Copy link
Collaborator

Choose a reason for hiding this comment

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

Do we need to do this if X + Y ↔ XY is valid catalyst? https://catalyst.sciml.ai/stable/tutorials/basics/#Combining-several-reactions-in-one-line

Alternatively, it could be something that Catalyst needs. It seems like a useful transform to have.

Copy link
Collaborator

Choose a reason for hiding this comment

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

Maybe worth pinging Sam about

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 way as SBML would describe the kineticLaw for a reversible reaction is X + Y ↔ XY is kAs*X*Y-kDi*XY. Catalyst however needs kAs*X*Y, kDi*XY, that is two kineticLaws. I mean this is a simple example, but somehow we need to find a way that generally works in splitting a combined kineticLaw in a forward and reverse kineticLaw. Any suggestions how to do this in a clean way?

end

""" Convert SBML.Reaction to MTK.Reaction """
function mtk_reaction(reaction::SBML.Reaction)
reactants = []
rstoich = []
products = []
pstoich = []
for (k,v) in reaction.stoichiometry
if v < 0
push!(reactants, create_var(k))
push!(rstoich, -v)
elseif v > 0
push!(products, create_var(k))
push!(pstoich, -v)
else
@error("Stoichiometry of $k must be non-zero")
end
end
subsdict = _get_substitutions(model)
# PL: Todo: @Anand: can you convert kinetic_math to Symbolic expression. Perhaps it would actually better if kinetic Math would be a Symbolics.jl expression rather than of type `Math`? But Mirek wants `Math`, I think.
kl = substitute(reaction.kinetic_math, subsdict) # PL: Todo: might need conversion of kinetic_math to Symbolic MTK expression
Copy link
Collaborator

Choose a reason for hiding this comment

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

IIRC @exaexa was taking this Math* sublanguage conversion. But I can get started on it for sure

ModelingToolkit.Reaction(reaction.kinetic_math,reactants,prodcts,rstoich,pstoich;only_use_rate=true)
end

""" Get dictonary to change types in kineticLaw """
function _get_substitution(model)
subsdict = Dict()
for k in keys(model.species)
push!(substict, Pair(Num(Variable(Symbol(k))),create_var(k)))
end
for k in keys(model.parameters)
push!(subsdict, Pair(Num(Variable(Symbol(k))),create_par(k)))
end
for k in keys(model.compartments)
push!(subsdict, Pair(Num(Variable(Symbol(k))),create_par(k)))
end
subsdict
end

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

""" Extract paramap from Model """
function get_paramap(model)
paramap = []
for (k,v) in model.parameters
push!(Pair(create_par(k),v))
end
for (k,v) in model.compartments
push!(Pair(create_par(k),v.size))
end
paramap
end

create_var(x) = Num(Variable(Symbol(x)))
# # create_var(x, iv) = Num(Sym{FnType{Tuple{Real}}}(Symbol(x))(Variable(Symbol(iv)))).val
# # create_var(x, iv) = Num(Variable{Symbolics.FnType{Tuple{Any},Real}}(Symbol(x)))(Variable(Symbol(iv)))
create_param(x) = Num(Sym{ModelingToolkit.Parameter{Real}}(Symbol(x)))
12 changes: 12 additions & 0 deletions src/readsbml.jl
Original file line number Diff line number Diff line change
Expand Up @@ -228,6 +228,14 @@ function extractModel(mdl::VPtr)::Model
)
end

ic = nothing
if ccall(sbml(:Species_isSetInitialConcentration), Cint, (VPtr,), sp) != 0
ic = (
ccall(sbml(:Species_getInitialConcentration), Cdouble, (VPtr,), sp),
get_string(sp, :Species_getSubstanceUnits),
)
end

species[get_string(sp, :Species_getId)] = Species(
get_optional_string(sp, :Species_getName),
get_string(sp, :Species_getCompartment),
Expand All @@ -239,6 +247,7 @@ function extractModel(mdl::VPtr)::Model
formula,
charge,
ia,
ic,
get_optional_bool(
sp,
:Species_isSetHasOnlySubstanceUnits,
Expand Down Expand Up @@ -273,6 +282,9 @@ function extractModel(mdl::VPtr)::Model
reactions = Dict{String,Reaction}()
for i = 1:ccall(sbml(:Model_getNumReactions), Cuint, (VPtr,), mdl)
re = ccall(sbml(:Model_getReaction), VPtr, (VPtr, Cuint), mdl, i - 1)
if ccall(sbml(:Reaction_getReversible), Cint, (VPtr,), re)
throw(AssertionError("Reaction $(get_string(re, :Reaction_getId)) is reversible, but currently only irreversible reactions are supported."))
end
lb = (-Inf, "") # (bound value, unit id)
ub = (Inf, "")
oc = 0.0
Expand Down
17 changes: 9 additions & 8 deletions src/structs.jl
Original file line number Diff line number Diff line change
Expand Up @@ -110,11 +110,11 @@ consumption/production rates (accessible in field `stoichiometry`), lower/upper
bounds (in tuples `lb` and `ub`, with unit names), and objective coefficient
(`oc`). Also may contains `notes` and `annotation`.
"""
struct Reaction
stoichiometry::Dict{String,Float64}
lb::Tuple{Float64,String}
ub::Tuple{Float64,String}
oc::Float64
mutable struct Reaction
Copy link
Collaborator

Choose a reason for hiding this comment

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

mutable

stoichiometry::Dict{String,Float64} # PL: Why do we allow/require non-integer stoichiometries?
Copy link
Collaborator

Choose a reason for hiding this comment

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

Unfortunately it seems nature is not made of integers, at least not at this zoom level. :D

SBML allows full floats here and many models use that. (There's even functionality that uses additional denominators to write 0.33333333 precisely as 1/3, but let's pretend that one doesn't exist.)

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

In that case I think SBML allows stuff that nature doesn't (what on earth is a half-molecule?). But they sure have their reasons (e.g. some people might want to 1H + 0.5O -> 0.5*H2O). Thanks for the explanation, I was just puzzled.

lb::Maybe{Tuple{Float64,String}}
ub::Maybe{Tuple{Float64,String}}
oc::Maybe{Float64}
gene_product_association::Maybe{GeneProductAssociation}
kinetic_math::Maybe{Math}
notes::Maybe{String}
Expand All @@ -126,13 +126,14 @@ end
Species metadata -- contains a human-readable `name`, a `compartment`
identifier, `formula`, `charge`, and additional `notes` and `annotation`.
"""
struct Species
mutable struct Species
Copy link
Collaborator

Choose a reason for hiding this comment

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

mutable

name::Maybe{String}
compartment::String
boundary_condition::Maybe{Bool}
formula::Maybe{String}
charge::Maybe{Int}
initial_amount::Maybe{Tuple{Float64,String}}
initial_concentration::Maybe{Tuple{Float64,String}}
only_substance_units::Maybe{Bool}
notes::Maybe{String}
annotation::Maybe{String}
Expand Down Expand Up @@ -175,8 +176,8 @@ struct Model
compartments::Dict{String,Compartment}
species::Dict{String,Species}
reactions::Dict{String,Reaction}
gene_products::Dict{String,GeneProduct}
function_definitions::Dict{String,FunctionDefinition}
gene_products::Maybe{Dict{String,GeneProduct}}
function_definitions::Maybe{Dict{String,FunctionDefinition}}
Copy link
Collaborator

Choose a reason for hiding this comment

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

Why have Maybe here? IMHO it may make some sense for the gene products (there might be a difference between "there are no gene products linked" vs "we don't talk about gene products here"), but certainly not for fundefs, if these are empty then they are just empty with no additional semantics right?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Makes sense. Fixed with a1af25a. I also took Maybe out of gene_products, because I do not see how the difference between "there are no gene products linked" vs "we don't talk about gene products here" can be seen in the SBML file.

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 changes I made in a1af25a caused test cases to break. fixed in 44d88f5.

notes::Maybe{String}
annotation::Maybe{String}
Model(p, u, c, s, r, g, f, n = nothing, a = nothing) = new(p, u, c, s, r, g, f, n, a)
Expand Down
67 changes: 67 additions & 0 deletions test/reactionsystem.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@

# sbmlfile = "reactionsystem_01.xml"

COMP1 = Compartment("c1", true, 3, 2., "nl")
SPECIES1 = Species("s1", "c1", false, nothing, nothing, (1., "substance"), nothing, true) # Todo: Maybe not support units in initial_concentration?
SPECIES2 = Species("s2", "c1", false, nothing, nothing, nothing, (1., "substance/nl"), false)
KINETICMATH1 = SBML.MathVal(1.) # PL: @anand or @mirek help needed. Can someone create `k1 * SPECIES1` as a `Math` type here?
REACTION1 = Reaction(Dict("r1" => 1), nothing, nothing, nothing, nothing, KINETICMATH1)
REACTION2 = Reaction(Dict("r2" => 1), nothing, nothing, nothing, nothing, KINETICMATH1)
MODEL1 = Model(Dict("k1" => 1.), Dict(), Dict("c1" => COMP1), Dict("s1" => SPECIES1), Dict("r1" => REACTION1), nothing, nothing) # PL: For instance in the compartments dict, we may want to enforce that key and compartment.name are identical
MODEL2 = Model(Dict("k1" => 1.), Dict(), Dict("c1" => COMP1), Dict("s2" => SPECIES2), Dict("r2" => REACTION2), nothing, nothing)

@testset "Model to MTK conversions" begin

# Test to_initial_amounts
model = SBML.to_initial_amounts(MODEL2)
@test isequal(model.species["s2"].initial_amount, (2., ""))
@test isequal(model.species["s2"].initial_concentration, nothing)
model = SBML.to_initial_amounts(MODEL1)
@test isequal(model.species["s1"].initial_amount, (1., "substance"))
@test isequal(model.species["s1"].initial_concentration, nothing)



# @test length(mdl.compartments) == 2

# mets, rxns, S = getS(mdl)

# @test typeof(S) <: AbstractMatrix{Float64}
# @test typeof(getS(mdl; zeros = spzeros)[3]) <: SparseMatrixCSC{Float64}
# @test typeof(getS(mdl; zeros = zeros)[3]) == Matrix{Float64}

# @test length(mets) == 77
# @test length(rxns) == 77
# @test size(S) == (length(mets), length(rxns))

# # totally arbitrary value tests
# @test isapprox(sum(S), 42.1479)
# @test mets[10:12] == ["M_akg_e", "M_fum_c", "M_pyr_c"]
# @test rxns[10:12] == ["R_H2Ot", "R_PGL", "R_EX_glc_e_"]

# lbs = getLBs(mdl)
# ubs = getUBs(mdl)
# ocs = getOCs(mdl)

# @test length(ocs) == length(mets)
# @test ocs[40] == 1.0
# deleteat!(ocs, 40)
# @test all(ocs .== 0.0)

# @test length(getLBs(mdl)) == length(rxns)
# @test length(getUBs(mdl)) == length(rxns)

# getunit = (val, unit)::Tuple -> unit
# @test all([broadcast(getunit, lbs) broadcast(getunit, ubs)] .== "mmol_per_gDW_per_hr")

# getval = (val, unit)::Tuple -> val
# lvals = broadcast(getval, lbs)
# uvals = broadcast(getval, ubs)
# @test isapprox(lvals[27], uvals[27])
# @test isapprox(lvals[27], 7.6)
# @test isapprox(lvals[12], -10)

# @test count(isapprox.(lvals, -999999)) == 40
# @test count(isapprox.(lvals, 0)) == 35
# @test count(isapprox.(uvals, 999999)) == 76
end
7 changes: 4 additions & 3 deletions test/runtests.jl
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@ using SBML
import Pkg

@testset "SBML test suite" begin
include("version.jl")
include("ecoli_flux.jl")
include("loadmodels.jl")
# include("version.jl")
Copy link
Collaborator

Choose a reason for hiding this comment

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

?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

fixed with 667240f

# include("ecoli_flux.jl")
# include("loadmodels.jl")
include("reactionsystem.jl")
end