Unverified Commit 56d2894a authored by Miroslav Kratochvil's avatar Miroslav Kratochvil
Browse files

systematize, add tests

parent 9cd547d7
"""
screen_model_variants(
model::MetabolicModel,
model_variants::Array,
analysis;
function screen(
model::MetabolicModel;
variants::Maybe{Array{V,N}} = nothing,
analysis,
args::Maybe{Array{T,N}} = nothing,
workers = [myid()],
)::Array
)::Array where {V<:AbstractVector, T<:Tuple,N}
Take vectors of model modifications in `model_variants` array and execute the
function `analysis` on all variants of the `model` specified by
`model_variants`. The computation is distributed over worker IDs in `workers`.
Take an array of model-modifying function vectors in `variants`, and execute
the function `analysis` on all variants of the `model` specified by `variants`.
The computation is distributed over worker IDs in `workers`. If `args` are
supplied (as an array of the same size as the `variants`), they are forwarded
as arguments to the corresponding analysis function calls.
The array of variants must contain vectors of single-parameter functions, these
are applied to model in order. The functions must *not* modify the model, but
rather return a modified copy. The copy should be made as shallow as possible,
to increase memory efficiency of the process. Modifications that modify the
argument model in-place will cause unpredictable results. Refer to the
definition of [`screen_one_variant`](@ref) for details.
to increase memory efficiency of the process. Variant generators that modify
the argument model in-place will cause unpredictable results. Refer to the
definition of [`screen_variant`](@ref) for details.
The function `analysis` will receive a single argument (the modified model).
The results of running `analysis` are collected in to the resulting array, in a
way that preserves the shape of the `model_variants`, similarly as with `pmap`.
The function `analysis` will receive a single argument (the modified model),
together with an expanded tuple of arguments from `args`.
The modification and analysis functions are transferred to `workers` as-is; all
packages required to run them (e.g. the optimization solvers) must be loaded
there. Typically, you want to use the macro `@everywhere using
MyFavoriteSolver` from `Distributed` package for loading the solvers.
# Return value
The results of running `analysis` are collected in to the resulting array, in a
way that preserves the shape of the `variants`, similarly as with `pmap`.
The results of `analysis` function must be serializable, preferably made only
from pure Julia structures, because they may be transferred over the network
between the computation nodes. For that reason, functions that return whole
JuMP models that contain pointers to allocated C structures (such as
[`flux_balance_analysis`](@ref) used with `GLPK` or `Gurobi` otimizers) will
generally not in this context.
# Example
```
function reverse_reaction(i::Int)
......@@ -39,57 +53,92 @@ end
m = load_model(CoreModel, "e_coli_core.xml")
screen_model_variants(m,
screen_variants(m,
[
[reverse_reaction(5)],
[reverse_reaction(3), reverse_reaction(6)]
],
mod -> mod.S[:,3]) # observe the changes in S
screen_model_variants(m,
screen_variants(m,
[
[reverse_reaction(5)],
[reverse_reaction(3), reverse_reaction(6)]
],
mod -> flux_balance_analysis_vec(mod, GLPK.Optimizer)) # run analysis
"""
function screen_model_variants(
model::MetabolicModel,
model_variants::Array,
analysis;
function screen(
model::MetabolicModel;
variants::Maybe{Array{V,N}} = nothing,
analysis,
args::Maybe{Array{T,N}} = nothing,
workers = [myid()],
)::Array
)::Array where {V<:AbstractVector,T<:Tuple,N}
map(fetch, save_at.(workers, :cobrexa_screen_model, Ref(model)))
map(fetch, save_at.(workers, :cobrexa_screen_analysis_fn, Ref(analysis)))
map(fetch, save_at.(workers, :cobrexa_screen_variants_model, Ref(model)))
map(fetch, save_at.(workers, :cobrexa_screen_variants_analysis_fn, Ref(analysis)))
map(fetch, get_from.(workers, Ref(:(precache!(cobrexa_screen_model)))))
if isnothing(variants)
if isnothing(args)
throw(
DomainError(
args,
"at least one of `variants` and `args` must be non-empty",
),
)
end
variants = [[] for _ in args]
elseif isnothing(args)
args = [() for _ in variants]
else
if size(variants) != size(args)
throw(
DomainError(
"$(size(variants)) != $(size(args))",
"sizes of `variants` and `args` differ",
),
)
end
end
res = dpmap(
mods -> :($COBREXA.screen_one_variant(
cobrexa_screen_model,
$mods,
cobrexa_screen_analysis_fn,
(vars, args)::Tuple -> :($COBREXA.screen_variant(
cobrexa_screen_variants_model,
$vars,
cobrexa_screen_variants_analysis_fn,
$args,
)),
CachingPool(workers),
model_variants,
zip(variants, args),
)
map(fetch, remove_from.(workers, :cobrexa_screen_model))
map(fetch, remove_from.(workers, :cobrexa_screen_analysis_fn))
map(fetch, remove_from.(workers, :cobrexa_screen_variants_model))
map(fetch, remove_from.(workers, :cobrexa_screen_variants_analysis_fn))
return res
end
"""
screen_one_variant(model::MetabolicModel, model_modifications, analysis)
screen_variant(model::MetabolicModel, variant::Vector, analysis, args = ())
Helper function for [`screen_model_variants`](@ref) applies all single-argument
functions in `model_modifications` to the `model` (in order from "first" to
Helper function for [`screen`](@ref) that applies all single-argument
functions in `variant` to the `model` (in order from "first" to
"last"), and executes `analysis` on the result.
Can be used to test model variants locally.
"""
function screen_one_variant(model::MetabolicModel, model_modifications, analysis)
for mod in model_modifications
model = mod(model)
function screen_variant(model::MetabolicModel, variant::Vector, analysis, args = ())
for fn in variant
model = fn(model)
end
analysis(model)
analysis(model, args...)
end
"""
screen_variants(model, variants, analysis; workers=[myid()])
A shortcut for [`screen`](@ref) that only works with model variants.
"""
screen_variants(model, variants, analysis; workers = [myid()]) =
screen(model; variants = variants, analysis = analysis, workers = workers)
@testset "Screening functions" begin
m = load_model(CoreModel, joinpath("data", "toyModel1.mat"))
# nothing to analyze
@test_throws DomainError screen(m; analysis = identity)
# array dimensionalities must match (this is actually caught by typechecker)
@test_throws MethodError screen(
m;
analysis = identity,
variants = [[], []],
args = [() ()],
)
# sizes must match
@test_throws DomainError screen(
m;
analysis = identity,
variants = [[], []],
args = [()],
)
# argument handling
@test screen(m, analysis = identity, variants = [[]]) == [m]
@test screen(m, analysis = identity, args = [()]) == [m]
@test screen(m, analysis = (a, b) -> b, args = [(1,), (2,)]) == [1, 2]
# test modifying some reactions
quad_rxn(i) = (m::CoreModel) -> begin
mm = copy(m)
mm.S = copy(m.S)
mm.S[:, i] .^= 2
return mm
end
@test screen_variants(
m,
[[quad_rxn(i)] for i = 1:3],
m -> flux_balance_analysis_vec(m, Tulip.Optimizer),
) == [
[250.0, -250.0, -1000.0, 250.0, 1000.0, 250.0, 250.0],
[500.0, 500.0, 1000.0, 500.0, -1000.0, 500.0, 500.0],
[500.0, 500.0, 1000.0, -500.0, 1000.0, 500.0, 500.0],
]
# test solver modifications
@test screen(
m;
analysis = (m, sense) -> flux_balance_analysis_vec(
m,
Tulip.Optimizer;
modifications = [change_sense(sense)],
),
args = [(MIN_SENSE,), (MAX_SENSE,)],
) == [
[-500.0, -500.0, -1000.0, 500.0, 1000.0, -500.0, -500.0],
[500.0, 500.0, 1000.0, -500.0, -1000.0, 500.0, 500.0],
]
end
@testset "Serialized models" begin
m = load_model(CoreModel, joinpath("data", "toyModel1.mat"))
sm = serialize_model(m, joinpath("data", "toy1.smod"))
sm2 = serialize_model(sm, joinpath("data", "toy2.smod"))
@test typeof(sm) == Serialized{CoreModel} # expected type
@test typeof(sm2) == Serialized{CoreModel} # no multi-layer serialization
precache!(sm)
@test isequal(m, sm.m) # the data is kept okay
@test sm2.m == nothing # nothing is cached here
@test isequal(m, COBREXA.Serialization.deserialize(joinpath("data", "toy2.smod"))) # it was written as-is
end
......@@ -53,12 +53,14 @@ end
# load the test models
run_test_file("data", "test_models.jl")
# import base files
@testset "COBREXA test suite" begin
run_test_dir(joinpath("base", "types", "abstract"), "Abstract types")
run_test_dir(joinpath("base", "types"), "Base model types")
run_test_dir(joinpath("base", "logging"), "Logging")
run_test_dir("base", "Base functionality")
run_test_dir(joinpath("base", "utils"), "Utilities")
run_test_dir("io", "I/O functions")
run_test_dir("reconstruction")
run_test_dir("analysis")
......
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