Skip to content

Commit

Permalink
slight reorganization, minor fixes of nothings, add tests
Browse files Browse the repository at this point in the history
  • Loading branch information
exaexa committed Feb 27, 2025
1 parent 91be756 commit 69f5d3d
Show file tree
Hide file tree
Showing 4 changed files with 120 additions and 76 deletions.
47 changes: 45 additions & 2 deletions docs/src/canonical.jl
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,8 @@

# ## Defining the model
#
# For convenience in the later explanation, we list the whole definition of the
# module here:
# For convenience in the later explanation, we list the definition of the
# interface for `AbstractFBCModels.CanonicalModel.Model` here:

# ##LIST src/canonical.jl

Expand Down Expand Up @@ -147,3 +147,46 @@ mktempdir() do dir
A.save(m, path)
A.run_fbcmodel_file_tests(Model, path, name = "small model")
end;

# ### Changing identifiers in the model
#
# For convenience, function [`identifier_map`](@ref) can be used to mass-update
# all identifiers and references in a whole canonical model. This is useful
# e.g. when exporting model formats that require certain formatting of the
# identifiers, such as SBML. We demonstrate this by changing all identifiers in
# the above model to have a "type prefix" -- reaction identifiers start with
# `R_`, metabolites start with `M_`, etc.

import AbstractFBCModels.CanonicalModel: identifier_map

m2 = identifier_map(
m,
reaction_map = id -> "R_" * id,
metabolite_map = id -> "M_" * id,
gene_map = id -> "G_" * id,
compartment_map = id -> "C_" * id,
coupling_map = id -> "X_" * id,
);

@test all(startswith("R_"), keys(m2.reactions)) #src
@test all(startswith("M_"), keys(m2.metabolites)) #src
@test all(startswith("G_"), keys(m2.genes)) #src
@test all(startswith("C_"), keys(m2.compartments)) #src
@test all(startswith("X_"), keys(m2.couplings)) #src

mktempdir() do dir #src
path = joinpath(dir, "model.canonical-serialized-fbc") #src
A.save(m2, path) #src
res = A.run_fbcmodel_file_tests(Model, path, name = "reidentified model") #src
end #src

# We can see that the identifiers have changed:

m2.reactions
@test "R_and_back" in keys(m2.reactions) #src

# ...together with the references:

m2.reactions["R_forward"].stoichiometry

@test "M_m1" in keys(m2.reactions["R_forward"].stoichiometry) #src
4 changes: 4 additions & 0 deletions src/AbstractFBCModels.jl
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,10 @@ include("utils.jl")
include("accessors.jl")
include("io.jl")
include("testing.jl")

module CanonicalModel
include("canonical.jl")
include("canonical_utils.jl")
end

end # module AbstractFBCModel
74 changes: 0 additions & 74 deletions src/canonical.jl
Original file line number Diff line number Diff line change
@@ -1,6 +1,4 @@

module CanonicalModel

using DocStringExtensions

import ..AbstractFBCModels as A
Expand Down Expand Up @@ -237,75 +235,3 @@ function Base.convert(::Type{Model}, x::A.AbstractFBCModel)
),
)
end

"""
$(TYPEDSIGNATURES)
Rebuild the given [`Model`](@ref) with all identifiers changed accordingly to
the given individual identifier-mapping functions in arguments. These take a
single `String` identifier as an argument, and return a new `String`
identifier. By default, the identifier maps are `identity`, i.e., no
identifiers are changed.
The identifier maps *must be injective and pure within `identifier_map`*, i.e.,
they must not create identifier name conflicts, and the results returned for a
given argument must be the same during the whole call of `identifier_map`.
Errors stemming from use of non-injective and impure identifier maps are not
handled.
Internal data structures are copied only by reference wherever possible.
"""
function identifier_map(
m::Model;
reaction_map = identity,
metabolite_map = identity,
gene_map = identity,
coupling_map = identity,
compartment_map = identity,
)
return Model(;
reactions = Dict(
reaction_map(k) => Reaction(;
name = v.name,
lower_bound = v.lower_bound,
upper_bound = v.upper_bound,
stoichiometry = Dict(
metabolite_map(sk) => sv for (sk, sv) in v.stoichiometry
),
objective_coefficient = v.objective_coefficient,
gene_association_dnf = [
String[gene_map(g) for g in gs] for gs in a.gene_association_dnf
],
annotations = v.annotations,
notes = v.notes,
) for (k, v) in m.reactions
),
metabolites = Dict(
metabolite_map(k) => Metabolite(;
name = v.name,
compartment = isnothing(v.compartment) ? nothing :
compartment_map(v.compartment),
formula = v.formula,
charge = v.charge,
balance = v.balance,
annotations = v.annotations,
notes = v.notes,
) for (k, v) in m.metabolites
),
genes = Dict(gene_map(k) => v for (k, v) in m.genes),
couplings = Dict(
coupling_map(k) => Coupling(;
name = v.name,
reaction_weights = Dict(
reaction_map(rk) => rv for (rk, rv) in v.reaction_weights
),
lower_bound = v.lower_bound,
upper_bound = v.upper_bound,
annotations = v.annotations,
notes = v.notes,
) for (k, v) in m.couplings
),
)
end

end # module CanonicalModel
71 changes: 71 additions & 0 deletions src/canonical_utils.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@

"""
$(TYPEDSIGNATURES)
Rebuild the given [`Model`](@ref) with all identifiers changed accordingly to
the given individual identifier-mapping functions in arguments. These take a
single `String` identifier as an argument, and return a new `String`
identifier. By default, the identifier maps are `identity`, i.e., no
identifiers are changed.
The identifier maps *must be injective and pure within `identifier_map`*, i.e.,
they must not create identifier name conflicts, and the results returned for a
given argument must be the same during the whole call of `identifier_map`.
Errors stemming from use of non-injective and impure identifier maps are not
handled.
Internal data structures are copied only by reference wherever possible.
"""
function identifier_map(
m::Model;
reaction_map = identity,
metabolite_map = identity,
gene_map = identity,
coupling_map = identity,
compartment_map = identity,
)
return Model(;
reactions = Dict(
reaction_map(k) => Reaction(;
name = v.name,
lower_bound = v.lower_bound,
upper_bound = v.upper_bound,
stoichiometry = Dict(
metabolite_map(sk) => sv for (sk, sv) in v.stoichiometry
),
objective_coefficient = v.objective_coefficient,
gene_association_dnf = isnothing(v.gene_association_dnf) ? nothing :
[
String[gene_map(g) for g in gs] for gs in v.gene_association_dnf
],
annotations = v.annotations,
notes = v.notes,
) for (k, v) in m.reactions
),
metabolites = Dict(
metabolite_map(k) => Metabolite(;
name = v.name,
compartment = isnothing(v.compartment) ? nothing :
compartment_map(v.compartment),
formula = v.formula,
charge = v.charge,
balance = v.balance,
annotations = v.annotations,
notes = v.notes,
) for (k, v) in m.metabolites
),
genes = Dict(gene_map(k) => v for (k, v) in m.genes),
couplings = Dict(
coupling_map(k) => Coupling(;
name = v.name,
reaction_weights = Dict(
reaction_map(rk) => rv for (rk, rv) in v.reaction_weights
),
lower_bound = v.lower_bound,
upper_bound = v.upper_bound,
annotations = v.annotations,
notes = v.notes,
) for (k, v) in m.couplings
),
)
end

0 comments on commit 69f5d3d

Please sign in to comment.