From f976984293ba159539246103073f3bde6909998c Mon Sep 17 00:00:00 2001 From: Mirek Kratochvil Date: Mon, 11 Dec 2023 11:08:20 +0100 Subject: [PATCH 01/13] regular spring cleaning: simpler metadata reading&writing --- src/readsbml.jl | 35 ++++++++++++++++++----------------- src/writesbml.jl | 45 ++++++++++++++++++++++++--------------------- 2 files changed, 42 insertions(+), 38 deletions(-) diff --git a/src/readsbml.jl b/src/readsbml.jl index 279c63e..21d0111 100644 --- a/src/readsbml.jl +++ b/src/readsbml.jl @@ -252,6 +252,7 @@ readSBMLFromString( get_notes(x::VPtr)::Maybe{String} = get_optional_string(x, :SBase_getNotesString) get_annotation(x::VPtr)::Maybe{String} = get_optional_string(x, :SBase_getAnnotationString) +get_metaid(x::VPtr)::Maybe{String} = get_optional_string(x, :SBase_getMetaId) """ $(TYPEDSIGNATURES) @@ -293,7 +294,7 @@ get_parameter(p::VPtr)::Pair{String,Parameter} = value = get_optional_double(p, :Parameter_isSetValue, :Parameter_getValue), units = get_optional_string(p, :Parameter_getUnits), constant = get_optional_bool(p, :Parameter_isSetConstant, :Parameter_getConstant), - metaid = get_optional_string(p, :SBase_getMetaId), + metaid = get_metaid(p), notes = get_notes(p), annotation = get_annotation(p), sbo = get_sbo_term(p), @@ -364,7 +365,7 @@ function get_model(mdl::VPtr)::SBML.Model ), size = get_optional_double(co, :Compartment_isSetSize, :Compartment_getSize), units = get_optional_string(co, :Compartment_getUnits), - metaid = get_optional_string(co, :SBase_getMetaId), + metaid = get_metaid(co), notes = get_notes(co), annotation = get_annotation(co), sbo = get_sbo_term(co), @@ -423,7 +424,7 @@ function get_model(mdl::VPtr)::SBML.Model :Species_getHasOnlySubstanceUnits, ), constant = get_optional_bool(sp, :Species_isSetConstant, :Species_getConstant), - metaid = get_optional_string(sp, :SBase_getMetaId), + metaid = get_metaid(sp), notes = get_notes(sp), annotation = get_annotation(sp), sbo = get_sbo_term(sp), @@ -567,7 +568,7 @@ function get_model(mdl::VPtr)::SBML.Model gene_product_association = association, kinetic_math = math, reversible, - metaid = get_optional_string(re, :SBase_getMetaId), + metaid = get_metaid(re), notes = get_notes(re), annotation = get_annotation(re), sbo = get_sbo_term(re), @@ -593,7 +594,7 @@ function get_model(mdl::VPtr)::SBML.Model gene_products[id] = GeneProduct( label = get_string(gp, :GeneProduct_getLabel), name = get_optional_string(gp, :GeneProduct_getName), - metaid = get_optional_string(gp, :SBase_getMetaId), + metaid = get_metaid(gp), notes = get_notes(gp), annotation = get_annotation(gp), sbo = get_sbo_term(gp), @@ -614,7 +615,7 @@ function get_model(mdl::VPtr)::SBML.Model function_definitions[get_string(fd, :FunctionDefinition_getId)] = FunctionDefinition( name = get_optional_string(fd, :FunctionDefinition_getName), - metaid = get_optional_string(fd, :SBase_getMetaId), + metaid = get_metaid(fd), body = def, notes = get_notes(fd), annotation = get_annotation(fd), @@ -625,8 +626,8 @@ function get_model(mdl::VPtr)::SBML.Model initial_assignments = Dict{String,Math}() num_ias = ccall(sbml(:Model_getNumInitialAssignments), Cuint, (VPtr,), mdl) - for n = 0:(num_ias-1) - ia = ccall(sbml(:Model_getInitialAssignment), VPtr, (VPtr, Cuint), mdl, n) + for i = 0:(num_ias-1) + ia = ccall(sbml(:Model_getInitialAssignment), VPtr, (VPtr, Cuint), mdl, i) sym = ccall(sbml(:InitialAssignment_getSymbol), Cstring, (VPtr,), ia) math_ptr = ccall(sbml(:InitialAssignment_getMath), VPtr, (VPtr,), ia) if math_ptr != C_NULL @@ -637,8 +638,8 @@ function get_model(mdl::VPtr)::SBML.Model # events events = Pair{Maybe{String},Event}[] num_events = ccall(sbml(:Model_getNumEvents), Cuint, (VPtr,), mdl) - for n = 0:(num_events-1) - ev = ccall(sbml(:Model_getEvent), VPtr, (VPtr, Cuint), mdl, n) + for i = 0:(num_events-1) + ev = ccall(sbml(:Model_getEvent), VPtr, (VPtr, Cuint), mdl, i) event_assignments = EventAssignment[] for j = 0:(ccall(sbml(:Event_getNumEventAssignments), Cuint, (VPtr,), ev)-1) @@ -684,11 +685,11 @@ function get_model(mdl::VPtr)::SBML.Model ) end - # Rules + # rules rules = Rule[] num_rules = ccall(sbml(:Model_getNumRules), Cuint, (VPtr,), mdl) - for n = 0:(num_rules-1) - rule_ptr = ccall(sbml(:Model_getRule), VPtr, (VPtr, Cuint), mdl, n) + for i = 0:(num_rules-1) + rule_ptr = ccall(sbml(:Model_getRule), VPtr, (VPtr, Cuint), mdl, i) type = if ccall(sbml(:Rule_isAlgebraic), Bool, (VPtr,), rule_ptr) AlgebraicRule elseif ccall(sbml(:Rule_isAssignment), Bool, (VPtr,), rule_ptr) @@ -711,11 +712,11 @@ function get_model(mdl::VPtr)::SBML.Model end end - # Constraints + # constraints constraints = Constraint[] num_constraints = ccall(sbml(:Model_getNumConstraints), Cuint, (VPtr,), mdl) - for n = 0:(num_constraints-1) - constraint_ptr = ccall(sbml(:Model_getConstraint), VPtr, (VPtr, Cuint), mdl, n) + for i = 0:(num_constraints-1) + constraint_ptr = ccall(sbml(:Model_getConstraint), VPtr, (VPtr, Cuint), mdl, i) xml_ptr = ccall(sbml(:Constraint_getMessage), VPtr, (VPtr,), constraint_ptr) message = get_string_from_xmlnode(xml_ptr) math_ptr = ccall(sbml(:Constraint_getMath), VPtr, (VPtr,), constraint_ptr) @@ -742,7 +743,7 @@ function get_model(mdl::VPtr)::SBML.Model events, name = get_optional_string(mdl, :Model_getName), id = get_optional_string(mdl, :Model_getId), - metaid = get_optional_string(mdl, :SBase_getMetaId), + metaid = get_metaid(mdl), conversion_factor = get_optional_string(mdl, :Model_getConversionFactor), area_units = get_optional_string(mdl, :Model_getAreaUnits), extent_units = get_optional_string(mdl, :Model_getExtentUnits), diff --git a/src/writesbml.jl b/src/writesbml.jl index 658f8b0..6a84647 100644 --- a/src/writesbml.jl +++ b/src/writesbml.jl @@ -97,13 +97,13 @@ end function set_parameter_ptr!(parameter_ptr::VPtr, id::String, parameter::Parameter)::VPtr set_string!(parameter_ptr, :Parameter_setId, id) set_string!(parameter_ptr, :Parameter_setName, parameter.name) - set_string!(parameter_ptr, :SBase_setMetaId, parameter.metaid) + set_metaid!(parameter_ptr, parameter.metaid) set_double!(parameter_ptr, :Parameter_setValue, parameter.value) set_string!(parameter_ptr, :Parameter_setUnits, parameter.units) add_cvterms!(parameter_ptr, parameter.cv_terms) set_bool!(parameter_ptr, :Parameter_setConstant, parameter.constant) - set_string!(parameter_ptr, :SBase_setAnnotationString, parameter.annotation) - set_string!(parameter_ptr, :SBase_setNotesString, parameter.notes) + set_annotation_string!(parameter_ptr, parameter.annotation) + set_notes_string!(parameter_ptr, parameter.notes) set_sbo_term!(parameter_ptr, parameter.sbo) return parameter_ptr end @@ -138,6 +138,9 @@ function set_double!(ptr::VPtr, fn_sym::Symbol, x::Maybe{Float64}) error("$fn_sym failed for value $x !") end +set_annotation_string!(ptr, x) = set_string!(ptr, :SBase_setAnnotationString, x) +set_notes_string!(ptr, x) = set_string!(ptr, :SBase_setNotesString, x) +set_metaid!(ptr, x) = set_string!(ptr, :SBase_setMetaId, x) set_sbo_term!(ptr, x) = set_string!(ptr, :SBase_setSBOTermID, x) add_cvterms!(ptr, x) = add_cvterm!.(Ref(ptr), x) @@ -202,7 +205,7 @@ function model_to_sbml!(doc::VPtr, mdl::Model)::VPtr # Set ids and name set_string!(model, :Model_setId, mdl.id) - set_string!(model, :SBase_setMetaId, mdl.metaid) + set_metaid!(model, mdl.metaid) set_string!(model, :Model_setName, mdl.name) # Add parameters @@ -221,7 +224,7 @@ function model_to_sbml!(doc::VPtr, mdl::Model)::VPtr compartment_ptr = ccall(sbml(:Model_createCompartment), VPtr, (VPtr,), model) set_string!(compartment_ptr, :Compartment_setId, id) set_string!(compartment_ptr, :Compartment_setName, compartment.name) - set_string!(compartment_ptr, :SBase_setMetaId, compartment.metaid) + set_metaid!(compartment_ptr, compartment.metaid) set_bool!(compartment_ptr, :Compartment_setConstant, compartment.constant) set_uint!( compartment_ptr, @@ -231,8 +234,8 @@ function model_to_sbml!(doc::VPtr, mdl::Model)::VPtr set_double!(compartment_ptr, :Compartment_setSize, compartment.size) set_string!(compartment_ptr, :Compartment_setUnits, compartment.units) add_cvterms!(compartment_ptr, compartment.cv_terms) - set_string!(compartment_ptr, :SBase_setNotesString, compartment.notes) - set_string!(compartment_ptr, :SBase_setAnnotationString, compartment.annotation) + set_notes_string!(compartment_ptr, compartment.notes) + set_annotation_string!(compartment_ptr, compartment.annotation) set_sbo_term!(compartment_ptr, compartment.sbo) end @@ -246,10 +249,10 @@ function model_to_sbml!(doc::VPtr, mdl::Model)::VPtr set_string!(geneproduct_ptr, :GeneProduct_setId, id) set_string!(geneproduct_ptr, :GeneProduct_setLabel, gene_product.label) set_string!(geneproduct_ptr, :GeneProduct_setName, gene_product.name) - set_string!(geneproduct_ptr, :SBase_setMetaId, gene_product.metaid) + set_metaid!(geneproduct_ptr, gene_product.metaid) add_cvterms!(geneproduct_ptr, gene_product.cv_terms) - set_string!(geneproduct_ptr, :SBase_setNotesString, gene_product.notes) - set_string!(geneproduct_ptr, :SBase_setAnnotationString, gene_product.annotation) + set_notes_string!(geneproduct_ptr, gene_product.notes) + set_annotation_string!(geneproduct_ptr, gene_product.annotation) set_sbo_term!(geneproduct_ptr, gene_product.sbo) end @@ -359,10 +362,10 @@ function model_to_sbml!(doc::VPtr, mdl::Model)::VPtr :GeneProductAssociation_createGeneProductRef, ) end - set_string!(reaction_ptr, :SBase_setMetaId, reaction.metaid) + set_metaid!(reaction_ptr, reaction.metaid) add_cvterms!(reaction_ptr, reaction.cv_terms) - set_string!(reaction_ptr, :SBase_setNotesString, reaction.notes) - set_string!(reaction_ptr, :SBase_setAnnotationString, reaction.annotation) + set_notes_string!(reaction_ptr, reaction.notes) + set_annotation_string!(reaction_ptr, reaction.annotation) set_sbo_term!(reaction_ptr, reaction.sbo) end @@ -393,7 +396,7 @@ function model_to_sbml!(doc::VPtr, mdl::Model)::VPtr for (id, species) in mdl.species species_ptr = ccall(sbml(:Model_createSpecies), VPtr, (VPtr,), model) set_string!(species_ptr, :Species_setId, id) - set_string!(species_ptr, :SBase_setMetaId, species.metaid) + set_metaid!(species_ptr, species.metaid) add_cvterms!(species_ptr, species.cv_terms) set_string!(species_ptr, :Species_setName, species.name) set_string!(species_ptr, :Species_setCompartment, species.compartment) @@ -422,8 +425,8 @@ function model_to_sbml!(doc::VPtr, mdl::Model)::VPtr species.only_substance_units, ) set_bool!(species_ptr, :Species_setConstant, species.constant) - set_string!(species_ptr, :SBase_setNotesString, species.notes) - set_string!(species_ptr, :SBase_setAnnotationString, species.annotation) + set_notes_string!(species_ptr, species.notes) + set_annotation_string!(species_ptr, species.annotation) set_sbo_term!(species_ptr, species.sbo) end @@ -432,7 +435,7 @@ function model_to_sbml!(doc::VPtr, mdl::Model)::VPtr functiondefinition_ptr = ccall(sbml(:Model_createFunctionDefinition), VPtr, (VPtr,), model) set_string!(functiondefinition_ptr, :FunctionDefinition_setId, id) - set_string!(functiondefinition_ptr, :SBase_setMetaId, func_def.metaid) + set_metaid!(functiondefinition_ptr, func_def.metaid) add_cvterms!(functiondefinition_ptr, func_def.cv_terms) set_string!(functiondefinition_ptr, :FunctionDefinition_setName, func_def.name) isnothing(func_def.body) || @@ -444,8 +447,8 @@ function model_to_sbml!(doc::VPtr, mdl::Model)::VPtr get_astnode_ptr(func_def.body), ) == 0 || error("setting function definition math failed!") - set_string!(functiondefinition_ptr, :SBase_setNotesString, func_def.notes) - set_string!(functiondefinition_ptr, :SBase_setAnnotationString, func_def.annotation) + set_notes_string!(functiondefinition_ptr, func_def.notes) + set_annotation_string!(functiondefinition_ptr, func_def.annotation) set_sbo_term!(functiondefinition_ptr, func_def.sbo) end @@ -513,8 +516,8 @@ function model_to_sbml!(doc::VPtr, mdl::Model)::VPtr # Notes and annotations add_cvterms!(model, mdl.cv_terms) - set_string!(model, :SBase_setNotesString, mdl.notes) - set_string!(model, :SBase_setAnnotationString, mdl.annotation) + set_notes_string!(model, mdl.notes) + set_annotation_string!(model, mdl.annotation) set_sbo_term!(model, mdl.sbo) # We can finally return the model From 7e38e29e36e822379fb9abbc9086598155d39abe Mon Sep 17 00:00:00 2001 From: Mirek Kratochvil Date: Mon, 11 Dec 2023 11:10:01 +0100 Subject: [PATCH 02/13] add groups reading & writing support --- Project.toml | 2 +- src/readsbml.jl | 40 ++++++++++++++++++++++++++++++++++++++++ src/structs.jl | 36 ++++++++++++++++++++++++++++++++++++ src/writesbml.jl | 38 +++++++++++++++++++++++++++++++++----- 4 files changed, 110 insertions(+), 6 deletions(-) diff --git a/Project.toml b/Project.toml index 171ec34..9a41818 100644 --- a/Project.toml +++ b/Project.toml @@ -1,7 +1,7 @@ name = "SBML" uuid = "e5567a89-2604-4b09-9718-f5f78e97c3bb" authors = ["Mirek Kratochvil ", "LCSB R3 team "] -version = "1.5.1" +version = "1.6" [deps] DocStringExtensions = "ffbed154-4ef7-542d-bbb7-c09d3a79fcae" diff --git a/src/readsbml.jl b/src/readsbml.jl index 21d0111..7d82209 100644 --- a/src/readsbml.jl +++ b/src/readsbml.jl @@ -310,6 +310,8 @@ valid [`SBML.Model`](@ref) structure. function get_model(mdl::VPtr)::SBML.Model # get the FBC plugin pointer (FbcModelPlugin_t) mdl_fbc = ccall(sbml(:SBase_getPlugin), VPtr, (VPtr, Cstring), mdl, "fbc") + # and the Groups plugin pointer (GroupModelPlugin_t) + mdl_groups = ccall(sbml(:SBase_getPlugin), VPtr, (VPtr, Cstring), mdl, "groups") # get the parameters parameters = Dict{String,Parameter}() @@ -727,6 +729,44 @@ function get_model(mdl::VPtr)::SBML.Model end end + # groups (these require the groups extension) + groups = Dict{String,Group}() + if mdl_groups != C_NULL + num_groups = + ccall(sbml(:GroupsModelPlugin_getNumGroups), Cuint, (VPtr,), mdl_groups) + for i = 0:(num_groups-1) + grp = + ccall(sbml(:GroupsModelPlugin_getGroup), VPtr, (VPtr, Cuint), mdl_groups, i) + members = Member[ + let mem = ccall(sbml(:Group_getMember), VPtr, (VPtr, Cuint), grp, mi) + Member(; + id = get_optional_string(mem, :Member_getId), + metaid = get_metaid(mem), + name = get_optional_string(mem, :Member_getName), + id_ref = get_optional_string(mem, :Member_getIdRef), + metaid_ref = get_optional_string(mem, :Member_getMetaIdRef), + notes = get_notes(mem), + annotation = get_annotation(mem), + sbo = get_sbo_term(mem), + cv_terms = get_cv_terms(mem), + ) + end for + mi = 0:(ccall(sbml(:Group_getNumMembers), Cuint, (VPtr,), grp)-1) + ] + + groups[get_string(grp, :Group_getId)] = Group(; + metaid = get_metaid(grp), + kind = get_optional_string(grp, :Group_getKind), + name = get_optional_string(grp, :Group_getName), + members, + notes = get_notes(grp), + annotation = get_annotation(grp), + sbo = get_sbo_term(grp), + cv_terms = get_cv_terms(grp), + ) + end + end + return Model(; parameters, units, diff --git a/src/structs.jl b/src/structs.jl index fde1f9b..241171c 100644 --- a/src/structs.jl +++ b/src/structs.jl @@ -450,6 +450,41 @@ end """ $(TYPEDEF) +# Fields +$(TYPEDFIELDS) +""" +Base.@kwdef struct Member + id::Maybe{String} = nothing + metaid::Maybe{String} = nothing + name::Maybe{String} = nothing + id_ref::Maybe{String} = nothing + metaid_ref::Maybe{String} = nothing + notes::Maybe{String} = nothing + annotation::Maybe{String} = nothing + sbo::Maybe{String} = nothing + cv_terms::Vector{CVTerm} = [] +end + +""" +$(TYPEDEF) + +# Fields +$(TYPEDFIELDS) +""" +Base.@kwdef struct Group + metaid::Maybe{String} = nothing + kind::Maybe{String} = nothing + name::Maybe{String} = nothing + members::Vector{Member} = [] + notes::Maybe{String} = nothing + annotation::Maybe{String} = nothing + sbo::Maybe{String} = nothing + cv_terms::Vector{CVTerm} = [] +end + +""" +$(TYPEDEF) + Structure that collects the model-related data. Contains `parameters`, `units`, `compartments`, `species` and `reactions` and `gene_products`, and additional `notes` and `annotation` (also present internally in some of the data fields). @@ -473,6 +508,7 @@ Base.@kwdef struct Model gene_products::Dict{String,GeneProduct} = Dict() function_definitions::Dict{String,FunctionDefinition} = Dict() events::Vector{Pair{Maybe{String},Event}} = Pair{Maybe{String},Event}[] + groups::Dict{String,Group} = Dict() name::Maybe{String} = nothing id::Maybe{String} = nothing metaid::Maybe{String} = nothing diff --git a/src/writesbml.jl b/src/writesbml.jl index 6a84647..64132c3 100644 --- a/src/writesbml.jl +++ b/src/writesbml.jl @@ -196,12 +196,10 @@ end function model_to_sbml!(doc::VPtr, mdl::Model)::VPtr # Create the model pointer model = ccall(sbml(:SBMLDocument_createModel), VPtr, (VPtr,), doc) + + # Init the pluings fbc_plugin = ccall(sbml(:SBase_getPlugin), VPtr, (VPtr, Cstring), model, "fbc") - fbc_plugin == C_NULL || - isempty(mdl.gene_products) || - isempty(mdl.objectives) || - isempty(mdl.species) || - set_bool!(fbc_plugin, :FbcModelPlugin_setStrict, true) + groups_plugin = ccall(sbml(:SBase_getPlugin), VPtr, (VPtr, Cstring), model, "groups") # Set ids and name set_string!(model, :Model_setId, mdl.id) @@ -503,6 +501,35 @@ function model_to_sbml!(doc::VPtr, mdl::Model)::VPtr end end + # Add groups + groups_plugin == C_NULL || + isempty(mdl.groups) || + set_bool!(groups_plugin, :FbcModelPlugin_setStrict, true) + for (id, group) in mdl.groups + group_ptr = + ccall(sbml(:GroupsModelPlugin_createGroup), VPtr, (VPtr,), groups_plugin) + set_string!(group_ptr, :Group_setId, group.id) + set_metaid!(group_ptr, group.metaid) + set_string!(group_ptr, :Group_setKind, group.kind) + set_string!(group_ptr, :Group_setName, group.name) + for mem in group.members + mem_ptr = ccall(sbml(:Group_createMember), VPtr, (VPtr,), group_ptr) + set_string!(mem_ptr, :Member_setId, mem.id) + set_metaid!(mem_ptr, mem.metaid) + set_string!(mem_ptr, :Member_setName, mem.name) + set_string!(mem_ptr, :Member_setIdRef, mem.id_ref) + set_string!(mem_ptr, :Member_setMetaIdRef, mem.metaid_ref) + set_notes_string!(mem_ptr, mem.notes) + set_annotation_string!(mem_ptr, mem.annotation) + set_sbo_term!(mem_ptr, mem.sbo) + set_cv_terms!(mem_ptr, mem.cv_terms) + end + set_notes_string!(group_ptr, group.notes) + set_annotation_string!(group_ptr, group.annotation) + set_sbo_term!(group_ptr, group.sbo) + set_cv_terms!(group_ptr, group.cv_terms) + end + # Add conversion factor set_string!(model, :Model_setConversionFactor, mdl.conversion_factor) @@ -525,6 +552,7 @@ function model_to_sbml!(doc::VPtr, mdl::Model)::VPtr end function _create_doc(mdl::Model)::VPtr + # TODO groups extension namespaces here doc = if isempty(mdl.gene_products) && isempty(mdl.objectives) && isempty(mdl.species) ccall( sbml(:SBMLDocument_createWithLevelAndVersion), From 7a1407bc7a45f19bfae2fb8eaae847038730db86 Mon Sep 17 00:00:00 2001 From: Mirek Kratochvil Date: Mon, 11 Dec 2023 12:08:22 +0100 Subject: [PATCH 03/13] properly create xmlns for SBML --- src/writesbml.jl | 133 +++++++++++++++++++++++++++++++++-------------- 1 file changed, 95 insertions(+), 38 deletions(-) diff --git a/src/writesbml.jl b/src/writesbml.jl index 64132c3..1ec77bd 100644 --- a/src/writesbml.jl +++ b/src/writesbml.jl @@ -1,10 +1,13 @@ # Level/Version for the document const WRITESBML_DEFAULT_LEVEL = 3 const WRITESBML_DEFAULT_VERSION = 2 -# Level/Version/Package version for the package -const WRITESBML_PKG_DEFAULT_LEVEL = 3 -const WRITESBML_PKG_DEFAULT_VERSION = 1 -const WRITESBML_PKG_DEFAULT_PKGVERSION = 2 +# Level/Version/Package version for the packages +const WRITESBML_FBC_DEFAULT_LEVEL = 3 +const WRITESBML_FBC_DEFAULT_VERSION = 1 +const WRITESBML_FBC_DEFAULT_PKGVERSION = 2 +const WRITESBML_GROUPS_DEFAULT_LEVEL = 3 +const WRITESBML_GROUPS_DEFAULT_VERSION = 1 +const WRITESBML_GROUPS_DEFAULT_PKGVERSION = 1 function create_gene_product_association( @@ -552,53 +555,107 @@ function model_to_sbml!(doc::VPtr, mdl::Model)::VPtr end function _create_doc(mdl::Model)::VPtr - # TODO groups extension namespaces here - doc = if isempty(mdl.gene_products) && isempty(mdl.objectives) && isempty(mdl.species) + # Create a namespaces object + sbmlns = ccall( + sbml(:SBMLNamespaces_create), + VPtr, + (Cuint, Cuint), + WRITESBML_DEFAULT_LEVEL, + WRITESBML_DEFAULT_VERSION, + ) + + fbc_required = + !isempty(mdl.objectives) || + !isempty(mdl.gene_products) || + any(!isnothing, (sp.formula for (_, sp) in mdl.species)) || + any(!isnothing, (sp.charge for (_, sp) in mdl.species)) + + groups_required = !isempty(mdl.groups) + + # Test if we have FBC and add it if required + if fbc_required + # we have FBC features, let's add FBC. + fbc_ext = ccall(sbml(:SBMLExtensionRegistry_getExtension), VPtr, (Cstring,), "fbc") + fbc_ns = ccall(sbml(:XMLNamespaces_create), VPtr, ()) + # create the sbml namespaces object with fbc + fbc_uri = ccall( + sbml(:SBMLExtension_getURI), + Cstring, + (VPtr, Cuint, Cuint, Cuint), + sbmlext, + WRITESBML_FBC_DEFAULT_LEVEL, + WRITESBML_FBC_DEFAULT_VERSION, + WRITESBML_FBC_DEFAULT_PKGVERSION, + ) ccall( - sbml(:SBMLDocument_createWithLevelAndVersion), - VPtr, - (Cuint, Cuint), - WRITESBML_DEFAULT_LEVEL, - WRITESBML_DEFAULT_VERSION, + sbml(:XMLNamespaces_add), + Cint, + (VPtr, Cstring, Cstring), + fbc_ns, + fbc_uri, + "fbc", ) - else - # Get fbc registry entry - sbmlext = ccall(sbml(:SBMLExtensionRegistry_getExtension), VPtr, (Cstring,), "fbc") - # create the sbml namespaces object with fbc - fbc = ccall(sbml(:XMLNamespaces_create), VPtr, ()) + ccall( + sbml(:SBMLNamespaces_addPackageNamespaces), + Cint, + (VPtr, VPtr), + sbmlns, + fbc_ns, + ) + end + + # Again, test if we have groups and add it (this might deserve its own function now) + if groups_required + fbc_ext = + ccall(sbml(:SBMLExtensionRegistry_getExtension), VPtr, (Cstring,), "groups") + fbc_ns = ccall(sbml(:XMLNamespaces_create), VPtr, ()) # create the sbml namespaces object with fbc - uri = ccall( + fbc_uri = ccall( sbml(:SBMLExtension_getURI), Cstring, (VPtr, Cuint, Cuint, Cuint), sbmlext, - WRITESBML_PKG_DEFAULT_LEVEL, - WRITESBML_PKG_DEFAULT_VERSION, - WRITESBML_PKG_DEFAULT_PKGVERSION, + WRITESBML_GROUPS_DEFAULT_LEVEL, + WRITESBML_GROUPS_DEFAULT_VERSION, + WRITESBML_GROUPS_DEFAULT_PKGVERSION, ) - ccall(sbml(:XMLNamespaces_add), Cint, (VPtr, Cstring, Cstring), fbc, uri, "fbc") - # Create SBML namespace with fbc package - sbmlns = ccall( - sbml(:SBMLNamespaces_create), - VPtr, - (Cuint, Cuint), - WRITESBML_DEFAULT_LEVEL, - WRITESBML_DEFAULT_VERSION, + ccall( + sbml(:XMLNamespaces_add), + Cint, + (VPtr, Cstring, Cstring), + fbc_ns, + fbc_uri, + "groups", ) - ccall(sbml(:SBMLNamespaces_addPackageNamespaces), Cint, (VPtr, VPtr), sbmlns, fbc) - # Create document from SBML namespace - d = ccall(sbml(:SBMLDocument_createWithSBMLNamespaces), VPtr, (VPtr,), sbmlns) - # Do not require fbc package ccall( - sbml(:SBMLDocument_setPackageRequired), + sbml(:SBMLNamespaces_addPackageNamespaces), Cint, - (VPtr, Cstring, Cint), - d, - "fbc", - false, + (VPtr, VPtr), + sbmlns, + fbc_ns, ) - d end + + # Now, create document with the required SBML namespaces + doc = ccall(sbml(:SBMLDocument_createWithSBMLNamespaces), VPtr, (VPtr,), sbmlns) + + # Add notes about required packages + fbc_required && ccall( + sbml(:SBMLDocument_setPackageRequired), + Cint, + (VPtr, Cstring, Cint), + doc, + "fbc", + false, + ) + groups_required && ccall( + sbml(:SBMLDocument_setPackageRequired), + Cint, + (VPtr, Cstring, Cint), + doc, + "groups", + false, + ) return doc end From 4ebc4442a96fe6305939ddcd90b7f84101ba2ce2 Mon Sep 17 00:00:00 2001 From: Mirek Kratochvil Date: Mon, 11 Dec 2023 12:33:50 +0100 Subject: [PATCH 04/13] symbolics evolved (v5 doesn't cause the ugly warning) --- Project.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Project.toml b/Project.toml index 9a41818..74c3179 100644 --- a/Project.toml +++ b/Project.toml @@ -16,7 +16,7 @@ ConstructionBase = "1.3" DocStringExtensions = "0.8, 0.9" IfElse = "0.1" SBML_jll = "5.19.5" -Symbolics = "3, 4" +Symbolics = "5" Unitful = "1" julia = "1.6" From e2ab977f63a0cfe800fc234913c11b3c4196a700 Mon Sep 17 00:00:00 2001 From: Mirek Kratochvil Date: Mon, 11 Dec 2023 12:34:53 +0100 Subject: [PATCH 05/13] group kinds are set by string here --- src/readsbml.jl | 2 +- src/writesbml.jl | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/readsbml.jl b/src/readsbml.jl index 7d82209..96a2e10 100644 --- a/src/readsbml.jl +++ b/src/readsbml.jl @@ -756,7 +756,7 @@ function get_model(mdl::VPtr)::SBML.Model groups[get_string(grp, :Group_getId)] = Group(; metaid = get_metaid(grp), - kind = get_optional_string(grp, :Group_getKind), + kind = get_optional_string(grp, :Group_getKindAsString), name = get_optional_string(grp, :Group_getName), members, notes = get_notes(grp), diff --git a/src/writesbml.jl b/src/writesbml.jl index 1ec77bd..b2bee71 100644 --- a/src/writesbml.jl +++ b/src/writesbml.jl @@ -513,7 +513,7 @@ function model_to_sbml!(doc::VPtr, mdl::Model)::VPtr ccall(sbml(:GroupsModelPlugin_createGroup), VPtr, (VPtr,), groups_plugin) set_string!(group_ptr, :Group_setId, group.id) set_metaid!(group_ptr, group.metaid) - set_string!(group_ptr, :Group_setKind, group.kind) + set_string!(group_ptr, :Group_setKindAsString, group.kind) set_string!(group_ptr, :Group_setName, group.name) for mem in group.members mem_ptr = ccall(sbml(:Group_createMember), VPtr, (VPtr,), group_ptr) From b37c4f5dcfe4a6421e22bd85c725a29369cfd86b Mon Sep 17 00:00:00 2001 From: Mirek Kratochvil Date: Mon, 11 Dec 2023 12:35:13 +0100 Subject: [PATCH 06/13] fix groups xmlns creation --- src/writesbml.jl | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/src/writesbml.jl b/src/writesbml.jl index b2bee71..e68e4a3 100644 --- a/src/writesbml.jl +++ b/src/writesbml.jl @@ -507,7 +507,7 @@ function model_to_sbml!(doc::VPtr, mdl::Model)::VPtr # Add groups groups_plugin == C_NULL || isempty(mdl.groups) || - set_bool!(groups_plugin, :FbcModelPlugin_setStrict, true) + set_bool!(groups_plugin, :GroupsModelPlugin_setStrict, true) for (id, group) in mdl.groups group_ptr = ccall(sbml(:GroupsModelPlugin_createGroup), VPtr, (VPtr,), groups_plugin) @@ -582,7 +582,7 @@ function _create_doc(mdl::Model)::VPtr sbml(:SBMLExtension_getURI), Cstring, (VPtr, Cuint, Cuint, Cuint), - sbmlext, + fbc_ext, WRITESBML_FBC_DEFAULT_LEVEL, WRITESBML_FBC_DEFAULT_VERSION, WRITESBML_FBC_DEFAULT_PKGVERSION, @@ -606,15 +606,15 @@ function _create_doc(mdl::Model)::VPtr # Again, test if we have groups and add it (this might deserve its own function now) if groups_required - fbc_ext = + groups_ext = ccall(sbml(:SBMLExtensionRegistry_getExtension), VPtr, (Cstring,), "groups") - fbc_ns = ccall(sbml(:XMLNamespaces_create), VPtr, ()) - # create the sbml namespaces object with fbc - fbc_uri = ccall( + groups_ns = ccall(sbml(:XMLNamespaces_create), VPtr, ()) + # create the sbml namespaces object with groups + groups_uri = ccall( sbml(:SBMLExtension_getURI), Cstring, (VPtr, Cuint, Cuint, Cuint), - sbmlext, + groups_ext, WRITESBML_GROUPS_DEFAULT_LEVEL, WRITESBML_GROUPS_DEFAULT_VERSION, WRITESBML_GROUPS_DEFAULT_PKGVERSION, @@ -623,8 +623,8 @@ function _create_doc(mdl::Model)::VPtr sbml(:XMLNamespaces_add), Cint, (VPtr, Cstring, Cstring), - fbc_ns, - fbc_uri, + groups_ns, + groups_uri, "groups", ) ccall( @@ -632,7 +632,7 @@ function _create_doc(mdl::Model)::VPtr Cint, (VPtr, VPtr), sbmlns, - fbc_ns, + groups_ns, ) end From 25af259065f38405417a7f570c4b72bfe3c4f9c5 Mon Sep 17 00:00:00 2001 From: Mirek Kratochvil Date: Mon, 11 Dec 2023 12:35:34 +0100 Subject: [PATCH 07/13] simplify --- src/writesbml.jl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/writesbml.jl b/src/writesbml.jl index e68e4a3..37f47f6 100644 --- a/src/writesbml.jl +++ b/src/writesbml.jl @@ -567,8 +567,8 @@ function _create_doc(mdl::Model)::VPtr fbc_required = !isempty(mdl.objectives) || !isempty(mdl.gene_products) || - any(!isnothing, (sp.formula for (_, sp) in mdl.species)) || - any(!isnothing, (sp.charge for (_, sp) in mdl.species)) + any(!isnothing(sp.formula) for (_, sp) in mdl.species) || + any(!isnothing(sp.charge) for (_, sp) in mdl.species) groups_required = !isempty(mdl.groups) From d9d0cf772ce16bc6b14f33649c3f6dba90a870af Mon Sep 17 00:00:00 2001 From: Mirek Kratochvil Date: Mon, 11 Dec 2023 12:36:04 +0100 Subject: [PATCH 08/13] fix Dasgupta model testing --- test/data/Dasgupta2020-written.xml | 4 ++-- test/writemodels.jl | 28 +++++++++++++++------------- 2 files changed, 17 insertions(+), 15 deletions(-) diff --git a/test/data/Dasgupta2020-written.xml b/test/data/Dasgupta2020-written.xml index 123c60b..8f2c69d 100644 --- a/test/data/Dasgupta2020-written.xml +++ b/test/data/Dasgupta2020-written.xml @@ -1,6 +1,6 @@ - - + +

a simple kinetic mass-action-law-based model could be utilized to adequately describe clustering in diff --git a/test/writemodels.jl b/test/writemodels.jl index b93da85..f7160da 100644 --- a/test/writemodels.jl +++ b/test/writemodels.jl @@ -37,20 +37,22 @@ function remove_some_annotation_strings!(model::SBML.Model) end @testset "writeSBML" begin - model = readSBML(joinpath(@__DIR__, "data", "Dasgupta2020.xml")) - fix_constant!(model) - # uncomment the following line to re-create reference XML - # writeSBML(model,joinpath(@__DIR__, "data", "Dasgupta2020-written.xml")) - expected = read(joinpath(@__DIR__, "data", "Dasgupta2020-written.xml"), String) - # Remove carriage returns, if any - expected = replace(expected, '\r' => "") - @test @test_logs(writeSBML(model)) == expected - mktemp() do filename, _ - @test_logs(writeSBML(model, filename)) - content = read(filename, String) + @testset "Model Dasgupta2020.xml writes out as expected" begin + model = readSBML(joinpath(@__DIR__, "data", "Dasgupta2020.xml")) + fix_constant!(model) + # uncomment the following line to re-create reference XML + #writeSBML(model, joinpath(@__DIR__, "data", "Dasgupta2020-debug.xml")) + expected = read(joinpath(@__DIR__, "data", "Dasgupta2020-written.xml"), String) # Remove carriage returns, if any - content = replace(content, '\r' => "") - @test content == expected + expected = replace(expected, '\r' => "") + @test @test_logs(writeSBML(model)) == expected + mktemp() do filename, _ + @test_logs(writeSBML(model, filename)) + content = read(filename, String) + # Remove carriage returns, if any + content = replace(content, '\r' => "") + @test content == expected + end end # Make sure that the model we read from the written out file is consistent From 696e26600fa3b6409c4c099df5d21f1c5e6e543e Mon Sep 17 00:00:00 2001 From: Mirek Kratochvil Date: Mon, 11 Dec 2023 13:19:50 +0100 Subject: [PATCH 09/13] add something important that we forgot --- src/readsbml.jl | 1 + 1 file changed, 1 insertion(+) diff --git a/src/readsbml.jl b/src/readsbml.jl index 96a2e10..2124301 100644 --- a/src/readsbml.jl +++ b/src/readsbml.jl @@ -781,6 +781,7 @@ function get_model(mdl::VPtr)::SBML.Model gene_products, function_definitions, events, + groups, name = get_optional_string(mdl, :Model_getName), id = get_optional_string(mdl, :Model_getId), metaid = get_metaid(mdl), From b55c9589f0b66e5512abe53e2953fb5b80deae2f Mon Sep 17 00:00:00 2001 From: Mirek Kratochvil Date: Mon, 11 Dec 2023 21:40:49 +0100 Subject: [PATCH 10/13] minor fixes --- src/writesbml.jl | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/src/writesbml.jl b/src/writesbml.jl index 37f47f6..be1dd4b 100644 --- a/src/writesbml.jl +++ b/src/writesbml.jl @@ -505,13 +505,10 @@ function model_to_sbml!(doc::VPtr, mdl::Model)::VPtr end # Add groups - groups_plugin == C_NULL || - isempty(mdl.groups) || - set_bool!(groups_plugin, :GroupsModelPlugin_setStrict, true) for (id, group) in mdl.groups group_ptr = ccall(sbml(:GroupsModelPlugin_createGroup), VPtr, (VPtr,), groups_plugin) - set_string!(group_ptr, :Group_setId, group.id) + set_string!(group_ptr, :Group_setId, id) set_metaid!(group_ptr, group.metaid) set_string!(group_ptr, :Group_setKindAsString, group.kind) set_string!(group_ptr, :Group_setName, group.name) @@ -525,12 +522,12 @@ function model_to_sbml!(doc::VPtr, mdl::Model)::VPtr set_notes_string!(mem_ptr, mem.notes) set_annotation_string!(mem_ptr, mem.annotation) set_sbo_term!(mem_ptr, mem.sbo) - set_cv_terms!(mem_ptr, mem.cv_terms) + add_cvterms!(mem_ptr, mem.cv_terms) end set_notes_string!(group_ptr, group.notes) set_annotation_string!(group_ptr, group.annotation) set_sbo_term!(group_ptr, group.sbo) - set_cv_terms!(group_ptr, group.cv_terms) + add_cvterms!(group_ptr, group.cv_terms) end # Add conversion factor From 609718147ec616a4061cd8b488cf7dcb204efb89 Mon Sep 17 00:00:00 2001 From: Mirek Kratochvil Date: Mon, 11 Dec 2023 21:40:58 +0100 Subject: [PATCH 11/13] test the new structs --- test/common.jl | 2 ++ 1 file changed, 2 insertions(+) diff --git a/test/common.jl b/test/common.jl index 51c3c2e..8bb8dbe 100644 --- a/test/common.jl +++ b/test/common.jl @@ -25,6 +25,8 @@ const ANNOTATED_TYPES = Union{ SBML.Compartment, SBML.FunctionDefinition, SBML.GeneProduct, + SBML.Group, + SBML.Member, SBML.Model, SBML.Parameter, SBML.Reaction, From 3d487532f283aac0749f7e9d8d1518a651fd16cb Mon Sep 17 00:00:00 2001 From: Mirek Kratochvil Date: Mon, 11 Dec 2023 21:41:05 +0100 Subject: [PATCH 12/13] add YeastGEM to tests (thx @mihai-sysbio for reporting a real GEM with groups :] ) --- test/loadmodels.jl | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/test/loadmodels.jl b/test/loadmodels.jl index eda6d60..2e5f1f2 100644 --- a/test/loadmodels.jl +++ b/test/loadmodels.jl @@ -27,6 +27,15 @@ sbmlfiles = [ 6, fill(Inf, 3), ), + # a highly curated model full of features + ( + joinpath(@__DIR__, "data", "yeast-GEM.xml"), + "https://raw.githubusercontent.com/SysBioChalmers/yeast-GEM/v9.0.0/model/yeast-GEM.xml", + "0e120b0d4015048ef2edaf86ea039c533a72827ff00a34ca35d9fbe87a2781e5", + 2805, + 4130, + fill(1000.0, 3), + ), # a cool model with `time` from SBML testsuite ( joinpath(@__DIR__, "data", "00852-sbml-l3v2.xml"), From 1d95e0fe932babc2b2b219c6cdc91c7382b0960c Mon Sep 17 00:00:00 2001 From: Mirek Kratochvil Date: Mon, 11 Dec 2023 21:44:38 +0100 Subject: [PATCH 13/13] clean up the Model comment --- src/structs.jl | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/structs.jl b/src/structs.jl index 241171c..0903af7 100644 --- a/src/structs.jl +++ b/src/structs.jl @@ -485,11 +485,11 @@ end """ $(TYPEDEF) -Structure that collects the model-related data. Contains `parameters`, `units`, -`compartments`, `species` and `reactions` and `gene_products`, and additional -`notes` and `annotation` (also present internally in some of the data fields). -The contained dictionaries are indexed by identifiers of the corresponding -objects. +Julia representation of SBML Model structure, with the reactions, species, +units, compartments, and many other things. + +Where available, all objects are contained in dictionaries indexed by SBML +identifiers. # Fields $(TYPEDFIELDS)