Unverified Commit 49ef7951 authored by Miroslav Kratochvil's avatar Miroslav Kratochvil Committed by GitHub
Browse files

Merge branch 'master' into mg/renamings

parents 41feebfb 6ac1c0cb
......@@ -18,7 +18,7 @@ include("utils.jl")
sbml(sym::Symbol) = dlsym(SBML_jll.libsbml_handle, sym)
export readSBML, stoichiometry_matrix, flux_bounds, flux_objective
export readSBML, readSBMLFromString, stoichiometry_matrix, flux_bounds, flux_objective
export set_level_and_version, libsbml_convert, convert_simplify_math
end # module
"""
set_level_and_version(level, version)
set_level_and_version(level, version, report_severities = ["Fatal", "Error"])
A converter to pass into [`readSBML`](@ref) that enforces certain SBML level
and version.
and version. `report_severities` switches on and off reporting of certain
errors; see the documentation of [`get_error_messages`](@ref) for details.
"""
set_level_and_version(level, version) =
set_level_and_version(level, version, report_severities = ["Fatal", "Error"]) =
doc -> check_errors(
ccall(
sbml(:SBMLDocument_setLevelAndVersion),
......@@ -17,17 +18,20 @@ set_level_and_version(level, version) =
),
doc,
ErrorException("Setting of level and version did not succeed"),
report_severities,
)
"""
libsbml_convert(conversion_options::Vector{Pair{String, Dict{String, String}}})
libsbml_convert(conversion_options::Vector{Pair{String, Dict{String, String}}}, report_severities = ["Fatal", "Error"])
A converter that runs the SBML conversion routine, with specified conversion
options. The argument is a vector of pairs to allow specifying the order of
conversions.
conversions. `report_severities` switches on and off reporting of certain
errors; see the documentation of [`get_error_messages`](@ref) for details.
"""
libsbml_convert(
conversion_options::AbstractVector{<:Pair{String,<:AbstractDict{String,String}}},
report_severities = ["Fatal", "Error"],
) =
doc -> begin
for (converter, options) in conversion_options
......@@ -46,26 +50,33 @@ libsbml_convert(
)
end
check_errors(
ccall(sbml(:SBMLDocument_convert), Cint, (VPtr, VPtr), doc, props),
# `SBMLDocument_convert` returns `LIBSBML_OPERATION_SUCCESS` (== 0) for a
# successful operation, something else when there is a failure.
iszero(ccall(sbml(:SBMLDocument_convert), Cint, (VPtr, VPtr), doc, props)),
doc,
ErrorException("Conversion returned errors"),
report_severities,
)
end
end
"""
libsbml_convert(converter::String; kwargs...)
libsbml_convert(converter::String, report_severities = ["Fatal", "Error"]; kwargs...)
Quickly construct a single run of a `libsbml` converter from keyword arguments.
`report_severities` switches on and off reporting of certain errors; see the
documentation of [`get_error_messages`](@ref) for details.
# Example
```
readSBML("example.xml", libsbml_convert("stripPackage", package="layout"))
```
"""
libsbml_convert(converter::String; kwargs...) = libsbml_convert([
converter => Dict{String,String}(string(k) => string(v) for (k, v) in kwargs),
])
libsbml_convert(converter::String, report_severities = ["Fatal", "Error"]; kwargs...) =
libsbml_convert([
converter => Dict{String,String}(string(k) => string(v) for (k, v) in kwargs),
],
report_severities)
"""
convert_simplify_math
......
......@@ -16,6 +16,25 @@ parse_math_children(ast::VPtr)::Vector{Math} = [
i = 1:ccall(sbml(:ASTNode_getNumChildren), Cuint, (VPtr,), ast)
]
# Mapping of AST node type value subset to relational operations. Depends on
# `ASTNodeType.h` (also see below the case with AST_NAME_TIME)
const relational_opers = Dict{Int32,String}(
308 => "eq",
309 => "geq",
310 => "gt",
311 => "leq",
312 => "lt",
313 => "neq",
)
function relational_oper(t::Int)
haskey(relational_opers, t) ||
throw(DomainError(t, "Unknown ASTNodeType value for relational operator"))
relational_opers[t]
end
"""
parse_math(ast::VPtr)::Math
......@@ -42,11 +61,16 @@ function parse_math(ast::VPtr)::Math
return MathVal(ccall(sbml(:ASTNode_getReal), Cdouble, (VPtr,), ast))
elseif ast_is(ast, :ASTNode_isFunction)
return MathApply(get_string(ast, :ASTNode_getName), parse_math_children(ast))
elseif ast_is(ast, :ASTNode_isOperator) || ast_is(ast, :ASTNode_isRelational)
elseif ast_is(ast, :ASTNode_isOperator)
return MathApply(
string(Char(ccall(sbml(:ASTNode_getCharacter), Cchar, (VPtr,), ast))),
parse_math_children(ast),
)
elseif ast_is(ast, :ASTNode_isRelational)
return MathApply(
relational_oper(Int(ccall(sbml(:ASTNode_getType), Cint, (VPtr,), ast))),
parse_math_children(ast),
)
elseif ast_is(ast, :ASTNode_isLambda)
children = parse_math_children(ast)
if !isempty(children)
......
......@@ -71,6 +71,34 @@ function get_optional_double(x::VPtr, is_sym, get_sym)::Maybe{Float64}
end
end
function _readSBML(
symbol::Symbol,
fn::String,
sbml_conversion,
report_severities,
)::SBML.Model
doc = ccall(sbml(symbol), VPtr, (Cstring,), fn)
try
get_error_messages(
doc,
AssertionError("Opening SBML document has reported errors"),
report_severities,
)
sbml_conversion(doc)
if 0 == ccall(sbml(:SBMLDocument_isSetModel), Cint, (VPtr,), doc)
throw(AssertionError("SBML document contains no model"))
end
model = ccall(sbml(:SBMLDocument_getModel), VPtr, (VPtr,), doc)
return extractModel(model)
finally
ccall(sbml(:SBMLDocument_free), Nothing, (VPtr,), doc)
end
end
"""
readSBML(
fn::String,
......@@ -89,6 +117,8 @@ single parameter, which is the C pointer to the loaded SBML document (C type
`report_severities` switches on and off reporting of certain errors; see the
documentation of [`get_error_messages`](@ref) for details.
To read from a string instead of a file, use [`readSBMLFromString`](@ref).
# Example
```
m = readSBML("my_model.xml", doc -> begin
......@@ -97,32 +127,29 @@ m = readSBML("my_model.xml", doc -> begin
end)
```
"""
function readSBML(
readSBML(
fn::String,
sbml_conversion = document -> nothing;
report_severities = ["Fatal", "Error"],
)::SBML.Model
doc = ccall(sbml(:readSBML), VPtr, (Cstring,), fn)
try
get_error_messages(
doc,
AssertionError("Opening SBML document has reported errors"),
report_severities,
)
)::SBML.Model = _readSBML(:readSBML, fn, sbml_conversion, report_severities)
sbml_conversion(doc)
if 0 == ccall(sbml(:SBMLDocument_isSetModel), Cint, (VPtr,), doc)
throw(AssertionError("SBML document contains no model"))
end
"""
readSBML(
str::String,
sbml_conversion = document -> nothing;
report_severities = ["Fatal", "Error"],
)::SBML.Model
model = ccall(sbml(:SBMLDocument_getModel), VPtr, (VPtr,), doc)
Read the SBML from the string `str` and return the contained `SBML.Model`.
return extractModel(model)
finally
ccall(sbml(:SBMLDocument_free), Nothing, (VPtr,), doc)
end
end
For the other arguments see the docstring of [`readSBML`](@ref), which can be
used to read from a file instead of a string.
"""
readSBMLFromString(
str::AbstractString,
sbml_conversion = document -> nothing;
report_severities = ["Fatal", "Error"],
)::SBML.Model = _readSBML(:readSBMLFromString, String(str), sbml_conversion, report_severities)
get_notes(x::VPtr)::Maybe{String} = get_optional_string(x, :SBase_getNotesString)
get_annotation(x::VPtr)::Maybe{String} = get_optional_string(x, :SBase_getAnnotationString)
......
......@@ -45,6 +45,7 @@ const default_symbolics_mapping = Dict{String,Any}(
"ln" => :log,
"log" => :sbmlLog,
"lt" => :<,
"neq" => :(sbmlNeq),
"piecewise" => :(sbmlPiecewise),
"power" => :^,
"root" => :sbmlRoot,
......@@ -66,6 +67,7 @@ function sbmlPiecewise(args...)
end
end
sbmlNeq(a, b) = !isequal(a, b)
sbmlLog(x) = log(x, 10)
sbmlLog(base, x) = log(base, x)
......
......@@ -56,9 +56,25 @@ sbmlfiles = [
0,
0,
),
# this contains a relational operator
(
joinpath(@__DIR__, "data", "sbml00191.xml"),
"https://raw.githubusercontent.com/sbmlteam/sbml-test-suite/master/cases/semantic/00191/00191-sbml-l3v2.xml",
"c474e94888767d70f9e9e03b32778f18069641563953de60dabac7daa7f481ce",
4,
2,
),
# expandInitialAssignments converter gives some warning
(
joinpath(@__DIR__, "data", "01234-sbml-l3v2.xml"),
"https://raw.githubusercontent.com/sbmlteam/sbml-test-suite/52d94baf97a005b6e1fdbdb6116f5c7b4a8a100c/cases/semantic/01234/01234-sbml-l3v2.xml",
"9610ef29f2d767af627042a15bde505b068ab75bbf00b8983823800ea8ef67c8",
0,
0,
),
]
@testset "Loading of models from various sources" begin
@testset "Loading of models from various sources - $(reader)" for reader in (readSBML, readSBMLFromString)
for (sbmlfile, url, hash, expected_mets, expected_rxns) in sbmlfiles
if !isfile(sbmlfile)
Downloads.download(url, sbmlfile)
......@@ -70,7 +86,11 @@ sbmlfiles = [
end
@testset "Loading of $sbmlfile" begin
mdl = readSBML(sbmlfile)
mdl = if reader === readSBML
readSBML(sbmlfile)
else
readSBMLFromString(readchomp(sbmlfile))
end
@test typeof(mdl) == Model
......@@ -82,6 +102,10 @@ sbmlfiles = [
end
end
@testset "readSBMLFromString" begin
@test_logs (:error, r"^SBML reported error") @test_throws AssertionError readSBMLFromString("")
end
@testset "Time variables in math" begin
# this test is here mainly for keeping a magical constant that we need for
# parsing time synced with libsbml source
......@@ -162,4 +186,15 @@ end
@test test_math.args[2].fn == "sin"
@test test_math.args[2].args[1].val == 2.1
@test_logs (:warn,) (:warn,) (:warn,) (:warn,) readSBML(joinpath(@__DIR__, "data", "01234-sbml-l3v2.xml"),
doc -> libsbml_convert("expandInitialAssignments", ["Fatal", "Error", "Warning"])(doc)
)
end
@testset "relational operators are decoded correctly" begin
test_math =
readSBML(joinpath(@__DIR__, "data", "sbml00191.xml")).reactions["reaction2"].kinetic_math
@test test_math.args[2].fn == "geq"
end
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment