diff --git a/Project.toml b/Project.toml index 439c56358e79e7c2eae3e246bf80341d27808efc..47457510176461e09f3d76db4d9d89153b8e1eb8 100644 --- a/Project.toml +++ b/Project.toml @@ -16,6 +16,7 @@ OrderedCollections = "bac558e1-5e72-5ebc-8fee-abe8a469f55d" Pkg = "44cfe95a-1eb2-52ea-b672-e2afdf69b78f" Random = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c" SBML = "e5567a89-2604-4b09-9718-f5f78e97c3bb" +Serialization = "9e88b42a-f829-5b0c-bbe9-9e923198166b" SparseArrays = "2f01184e-e22b-5df5-ae63-d93ebab69eaf" Statistics = "10745b16-79ce-11e8-11f9-7d13ad32a3b2" diff --git a/src/COBREXA.jl b/src/COBREXA.jl index e32108f69bce7eab9f93bcc4111c21a25656bde9..02d410c3c2a83410507e4bd88eb7b06d44a1cb92 100644 --- a/src/COBREXA.jl +++ b/src/COBREXA.jl @@ -10,6 +10,7 @@ using MAT using MacroTools using OrderedCollections using Random +using Serialization using SparseArrays using Statistics diff --git a/src/base/types/Serialized.jl b/src/base/types/Serialized.jl new file mode 100644 index 0000000000000000000000000000000000000000..8924ba08a26452e107445007e4f6325be585ca70 --- /dev/null +++ b/src/base/types/Serialized.jl @@ -0,0 +1,56 @@ + +""" + mutable struct Serialized{M <: MetabolicModel} + m::Maybe{M} + filename::String + end + +A meta-model that represents a model that is serialized on the disk. The +internal model will be loaded on-demand by using any accessor, or by calling +[`precache!`](@ref) directly. +""" +mutable struct Serialized{M} <: MetabolicModel where {M<:MetabolicModel} + m::Maybe{M} + filename::String +end + +function _on_precached(m::Serialized, f) + precache!(m) + f(m.m) +end + +reactions(m::Serialized) = _on_precached(m, reactions) +n_reactions(m::Serialized) = _on_precached(m, n_reactions) +metabolites(m::Serialized) = _on_precached(m, metabolites) +n_metabolites(m::Serialized) = _on_precached(m, n_metabolites) +stoichiometry(m::Serialized) = _on_precached(m, stoichiometry) +bounds(m::Serialized) = _on_precached(m, bounds) +balance(m::Serialized) = _on_precached(m, balance) +objective(m::Serialized) = _on_precached(m, objective) +coupling(m::Serialized) = _on_precached(m, coupling) +n_coupling_constraints(m::Serialized) = _on_precached(m, n_coupling_constraints) +coupling_bounds(m::Serialized) = _on_precached(m, coupling_bounds) +genes(m::Serialized) = _on_precached(m, genes) +n_genes(m::Serialized) = _on_precached(m, n_genes) +metabolite_formula(m::Serialized) = _on_precached(m, metabolite_formula) +metabolite_charge(m::Serialized) = _on_precached(m, metabolite_charge) +reaction_annotations(m::Serialized) = _on_precached(m, reaction_annotations) +metabolite_annotations(m::Serialized) = _on_precached(m, metabolite_annotations) +gene_annotations(m::Serialized) = _on_precached(m, gene_annotations) +reaction_nodes(m::Serialized) = _on_precached(m, reaction_nodes) +metabolite_nodes(m::Serialized) = _on_precached(m, metabolite_nodes) +gene_notes(m::Serialized) = _on_precached(m, gene_notes) +metabolite_compartment(m::Serialized) = _on_precached(m, metabolite_compartment) +reaction_subsystem(m::Serialized) = _on_precached(m, reaction_subsystem) + +""" + precache!(model::Serialized{MetabolicModel})::Nothing + +Load the `Serialized` model from disk in case it's not alreadly loaded. +""" +function precache!(model::Serialized)::Nothing + if isnothing(model.m) + model.m = deserialize(model.filename) + end + nothing +end diff --git a/src/base/types/abstract/MetabolicModel.jl b/src/base/types/abstract/MetabolicModel.jl index 4311ff800c11d9e37d20d798aba1c3edfa53a0b0..163615c4cdbb42ca310b443445ec4a813edd1ffc 100644 --- a/src/base/types/abstract/MetabolicModel.jl +++ b/src/base/types/abstract/MetabolicModel.jl @@ -305,3 +305,21 @@ return `nothing`. function reaction_subsystem(model::MetabolicModel, reaction_id::String)::Maybe{String} return nothing end + +""" + precache!(a::MetabolicModel)::Nothing + +Do whatever is feasible to get the model into a state that can be read from +as-quickly-as-possible. This may include e.g. generating helper index +structures and loading delayed parts of the model from disk. The model should +be modified "transparently" in-place. Analysis functions call this right before +applying modifications or converting the model to the optimization model using +[`make_optimization_model`](@ref); usually on the same machine where the +optimizers (and, generally, the core analysis algorithms) will run. The calls +are done in a good hope that the performance will be improved. + +By default, it should be safe to do nothing. +""" +function precache!(a::MetabolicModel)::Nothing + nothing +end diff --git a/src/base/utils/Serialized.jl b/src/base/utils/Serialized.jl new file mode 100644 index 0000000000000000000000000000000000000000..741e128aef769a3b3e60fb8f0e9dd08b9050c4ef --- /dev/null +++ b/src/base/utils/Serialized.jl @@ -0,0 +1,26 @@ + +""" + serialize_model(model::MM, filename::String)::Serialized{MM} where {MM<:MetabolicModel} + +Serialize the `model` to file `filename`, returning a [`Serialized`](@ref) +model that is able to load itself back automatically upon precaching by +[`precache!`](@ref). +""" +function serialize_model( + model::MM, + filename::String, +)::Serialized{MM} where {MM<:MetabolicModel} + open(f -> serialize(f, model), filename, "w") + Serialized{MM}(nothing, filename) +end + +""" + serialize_model(model::Serialized, filename::String)::Serialized + +Specialization of [`serialize_model`](@ref) that prevents nested serialization +of already-serialized models. +""" +function serialize_model(model::Serialized, filename::String) + precache!(model) + serialize_model(model.m, filename) +end diff --git a/src/io/show/models.jl b/src/io/show/MetabolicModel.jl similarity index 100% rename from src/io/show/models.jl rename to src/io/show/MetabolicModel.jl diff --git a/src/io/show/Serialized.jl b/src/io/show/Serialized.jl new file mode 100644 index 0000000000000000000000000000000000000000..83e427b930d775f678be9f9fe482f82a3acd388e --- /dev/null +++ b/src/io/show/Serialized.jl @@ -0,0 +1,12 @@ + +""" + Base.show(io::IO, ::MIME"text/plain", m::Serialized{M}) where {M} + +Show the [`Serialized`](@ref) model without unnecessarily loading it. +""" +function Base.show(io::IO, ::MIME"text/plain", m::Serialized{M}) where {M} + print( + io, + "Serialized{$M} saved in \"$(m.filename)\" ($(isnothing(m.m) ? "not loaded" : "loaded"))", + ) +end