# -*- coding: utf-8 -*-
"""Test functions of model.py"""
from __future__ import absolute_import
import os
import warnings
from copy import copy, deepcopy
from math import isnan
import numpy as np
import pandas as pd
import pytest
from optlang.symbolics import Zero
from cobra.core import Group, Metabolite, Model, Reaction
from cobra.exceptions import OptimizationError
from cobra.util import solver as su
from cobra.util.solver import SolverNotFound, set_objective, solvers
[docs]stable_optlang = ["glpk", "cplex", "gurobi"]
[docs]optlang_solvers = ["optlang-" + s for s in stable_optlang if s in su.solvers]
[docs]def same_ex(ex1, ex2):
"""Compare to expressions for mathematical equality."""
return ex1.simplify() == ex2.simplify()
@pytest.mark.parametrize("solver", stable_optlang)
[docs]def test_add_remove_reaction_benchmark(model, benchmark, solver):
metabolite_foo = Metabolite("test_foo")
metabolite_bar = Metabolite("test_bar")
metabolite_baz = Metabolite("test_baz")
actual_metabolite = model.metabolites[0]
dummy_reaction = Reaction("test_foo_reaction")
dummy_reaction.add_metabolites(
{
metabolite_foo: -1,
metabolite_bar: 1,
metabolite_baz: -2,
actual_metabolite: 1,
}
)
def benchmark_add_reaction():
model.add_reaction(dummy_reaction)
if not getattr(model, "solver", None):
solver_dict[solver].create_problem(model)
model.remove_reactions([dummy_reaction])
benchmark(benchmark_add_reaction)
[docs]def test_compartments(model):
assert set(model.compartments) == {"c", "e"}
model = Model("test", "test")
met_c = Metabolite("a_c", compartment="c")
met_e = Metabolite("a_e", compartment="e")
rxn = Reaction("foo")
rxn.add_metabolites({met_e: -1, met_c: 1})
model.add_reactions([rxn])
assert model.compartments == {"c": "", "e": ""}
model.compartments = {"c": "cytosol"}
assert model.compartments == {"c": "cytosol", "e": ""}
[docs]def test_add_reaction(model):
old_reaction_count = len(model.reactions)
old_metabolite_count = len(model.metabolites)
dummy_metabolite_1 = Metabolite("test_foo_1")
dummy_metabolite_2 = Metabolite("test_foo_2")
actual_metabolite = model.metabolites[0]
copy_metabolite = model.metabolites[1].copy()
dummy_reaction = Reaction("test_foo_reaction")
dummy_reaction.add_metabolites(
{
dummy_metabolite_1: -1,
dummy_metabolite_2: 1,
copy_metabolite: -2,
actual_metabolite: 1,
}
)
model.add_reaction(dummy_reaction)
assert model.reactions.get_by_id(dummy_reaction.id) == dummy_reaction
for x in [dummy_metabolite_1, dummy_metabolite_2]:
assert model.metabolites.get_by_id(x.id) == x
# Should add 1 reaction and 2 metabolites
assert len(model.reactions) == old_reaction_count + 1
assert len(model.metabolites) == old_metabolite_count + 2
# Test the added reaction
reaction_in_model = model.reactions.get_by_id(dummy_reaction.id)
assert type(reaction_in_model) is Reaction
assert reaction_in_model is dummy_reaction
assert len(reaction_in_model._metabolites) == 4
for i in reaction_in_model._metabolites:
assert type(i) == Metabolite
# Test the added metabolites
met1_in_model = model.metabolites.get_by_id(dummy_metabolite_1.id)
assert met1_in_model is dummy_metabolite_1
copy_in_model = model.metabolites.get_by_id(copy_metabolite.id)
assert copy_metabolite is not copy_in_model
assert type(copy_in_model) is Metabolite
assert dummy_reaction in actual_metabolite._reaction
# Test addition of a different metabolite with the same name as an
# existing one uses the metabolite in the model
r2 = Reaction("test_foo_reaction2")
model.add_reaction(r2)
r2.add_metabolites({Metabolite(model.metabolites[0].id): 1})
assert model.metabolites[0] is list(r2._metabolites)[0]
[docs]def test_add_reaction_context(model):
old_reaction_count = len(model.reactions)
old_metabolite_count = len(model.metabolites)
dummy_metabolite_1 = Metabolite("test_foo_1")
dummy_metabolite_2 = Metabolite("test_foo_2")
actual_metabolite = model.metabolites[0]
copy_metabolite = model.metabolites[1].copy()
dummy_reaction = Reaction("test_foo_reaction")
dummy_reaction.add_metabolites(
{
dummy_metabolite_1: -1,
dummy_metabolite_2: 1,
copy_metabolite: -2,
actual_metabolite: 1,
}
)
dummy_reaction.gene_reaction_rule = "dummy_gene"
with model:
model.add_reaction(dummy_reaction)
assert model.reactions.get_by_id(dummy_reaction.id) == dummy_reaction
assert len(model.reactions) == old_reaction_count + 1
assert len(model.metabolites) == old_metabolite_count + 2
assert dummy_metabolite_1._model == model
assert "dummy_gene" in model.genes
assert len(model.reactions) == old_reaction_count
assert len(model.metabolites) == old_metabolite_count
with pytest.raises(KeyError):
model.reactions.get_by_id(dummy_reaction.id)
assert dummy_metabolite_1._model is None
assert "dummy_gene" not in model.genes
[docs]def test_add_reaction_from_other_model(model):
other = model.copy()
for i in other.reactions:
i.id += "_other"
other.repair()
model.add_reactions(other.reactions)
# Check if the other reaction has an error in its GPR
m1 = model.copy()
m2 = model.copy()
m1.reactions.PGI.remove_from_model()
m2.genes.b4025._reaction.clear()
m1.add_reaction(m2.reactions.PGI)
[docs]def test_model_remove_reaction(model):
old_reaction_count = len(model.reactions)
with model:
model.remove_reactions(["PGI"])
assert len(model.reactions) == old_reaction_count - 1
with pytest.raises(KeyError):
model.reactions.get_by_id("PGI")
model.remove_reactions(model.reactions[:1])
assert len(model.reactions) == old_reaction_count - 2
assert len(model.reactions) == old_reaction_count
assert "PGI" in model.reactions
tmp_metabolite = Metabolite("testing")
model.reactions[0].add_metabolites({tmp_metabolite: 1})
assert tmp_metabolite in model.metabolites
model.remove_reactions(model.reactions[:1], remove_orphans=True)
assert tmp_metabolite not in model.metabolites
with model:
model.reactions[0].add_metabolites({tmp_metabolite: 1})
assert tmp_metabolite in model.metabolites
assert tmp_metabolite not in model.metabolites
biomass_before = model.slim_optimize()
with model:
model.remove_reactions([model.reactions.Biomass_Ecoli_core])
assert np.isclose(model.slim_optimize(), 0)
assert np.isclose(model.slim_optimize(), biomass_before)
[docs]def test_reaction_remove(model):
old_reaction_count = len(model.reactions)
tmp_metabolite = Metabolite("testing")
# Delete without removing orphan
model.reactions[0].add_metabolites({tmp_metabolite: 1})
assert len(tmp_metabolite.reactions) == 1
# Esnsure the stoichiometry is still the same using different objects
removed_reaction = model.reactions[0]
original_stoich = {
i.id: value for i, value in removed_reaction._metabolites.items()
}
model.reactions[0].remove_from_model(remove_orphans=False)
assert len(original_stoich) == len(removed_reaction._metabolites)
for met in removed_reaction._metabolites:
assert original_stoich[met.id] == removed_reaction._metabolites[met]
assert met is not model.metabolites
# Make sure it's still in the model
assert tmp_metabolite in model.metabolites
assert len(tmp_metabolite.reactions) == 0
assert len(model.reactions) == old_reaction_count - 1
# Now try with removing orphans
model.reactions[0].add_metabolites({tmp_metabolite: 1})
assert len(tmp_metabolite.reactions) == 1
model.reactions[0].remove_from_model(remove_orphans=True)
assert tmp_metabolite not in model.metabolites
assert len(tmp_metabolite.reactions) == 0
assert len(model.reactions) == old_reaction_count - 2
# It shouldn't remove orphans if it's in 2 reactions however
model.reactions[0].add_metabolites({tmp_metabolite: 1})
model.reactions[1].add_metabolites({tmp_metabolite: 1})
assert len(tmp_metabolite.reactions) == 2
model.reactions[0].remove_from_model(remove_orphans=False)
assert tmp_metabolite in model.metabolites
assert len(tmp_metabolite.reactions) == 1
assert len(model.reactions) == old_reaction_count - 3
[docs]def test_reaction_delete(model):
old_reaction_count = len(model.reactions)
tmp_metabolite = Metabolite("testing")
# Delete without removing orphan
model.reactions[0].add_metabolites({tmp_metabolite: 1})
assert len(tmp_metabolite.reactions) == 1
with pytest.warns(DeprecationWarning):
model.reactions[0].delete(remove_orphans=False)
# Make sure it's still in the model
assert tmp_metabolite in model.metabolites
assert len(tmp_metabolite.reactions) == 0
assert len(model.reactions) == old_reaction_count - 1
# Now try it with removing orphans
model.reactions[0].add_metabolites({tmp_metabolite: 1})
assert len(tmp_metabolite.reactions) == 1
model.reactions[0].delete(remove_orphans=True)
assert tmp_metabolite not in model.metabolites
assert len(tmp_metabolite.reactions) == 0
assert len(model.reactions) == old_reaction_count - 2
# It shouldn't remove orphans if it's in 2 reactions however
model.reactions[0].add_metabolites({tmp_metabolite: 1})
model.reactions[1].add_metabolites({tmp_metabolite: 1})
assert len(tmp_metabolite.reactions) == 2
model.reactions[0].delete(remove_orphans=False)
assert tmp_metabolite in model.metabolites
assert len(tmp_metabolite.reactions) == 1
assert len(model.reactions) == old_reaction_count - 3
[docs]def test_remove_gene(model):
target_gene = model.genes[0]
gene_reactions = list(target_gene.reactions)
with warnings.catch_warnings():
warnings.simplefilter("ignore")
target_gene.remove_from_model()
assert target_gene.model is None
# Make sure the reaction was removed from the model
assert target_gene not in model.genes
# Ensure the old reactions no longer have a record of the gene
for reaction in gene_reactions:
assert target_gene not in reaction.genes
[docs]def test_group_model_reaction_association(model):
num_members = 5
reactions_for_group = model.reactions[0:num_members]
group = Group("arbitrary_group1")
group.add_members(reactions_for_group)
group.kind = "collection"
model.add_groups([group])
# group should point to and be associated with the model
assert group._model is model
assert group in model.groups
# model.get_associated_groups should find the group for each reaction
# we added to the group
for reaction in reactions_for_group:
assert group in model.get_associated_groups(reaction)
# remove the group from the model and check that reactions are no
# longer associated with the group
model.remove_groups([group])
assert group not in model.groups
assert group._model is not model
for reaction in reactions_for_group:
assert group not in model.get_associated_groups(reaction)
[docs]def test_group_members_add_to_model(model):
# remove a few reactions from the model and add them to a new group
num_members = 5
reactions_for_group = model.reactions[0:num_members]
model.remove_reactions(reactions_for_group, remove_orphans=False)
group = Group("arbitrary_group1")
group.add_members(reactions_for_group)
group.kind = "collection"
# the old reactions should not be in the model
for reaction in reactions_for_group:
assert reaction not in model.reactions
# add the group to the model and check that the reactions were added
model.add_groups([group])
assert group in model.groups
for reaction in reactions_for_group:
assert reaction in model.reactions
[docs]def test_group_loss_of_elements(model):
# when a metabolite, reaction, or gene is removed from a model, it
# should no longer be a member of any groups
num_members_each = 5
elements_for_group = model.reactions[0:num_members_each]
elements_for_group.extend(model.metabolites[0:num_members_each])
elements_for_group.extend(model.genes[0:num_members_each])
group = Group("arbitrary_group1")
group.add_members(elements_for_group)
group.kind = "collection"
model.add_groups([group])
remove_met = model.metabolites[0]
model.remove_metabolites([remove_met])
remove_rxn = model.reactions[0]
model.remove_reactions([remove_rxn])
remove_gene = model.genes[0]
remove_gene.remove_from_model()
assert remove_met not in group.members
assert remove_rxn not in group.members
assert remove_gene not in group.members
[docs]def test_exchange_reactions(model):
assert set(model.exchanges) == set(
[rxn for rxn in model.reactions if rxn.id.startswith("EX")]
)
@pytest.mark.parametrize(
"metabolites, reaction_type, prefix",
[
("exchange", "exchange", "EX_"),
("demand", "demand", "DM_"),
("sink", "sink", "SK_"),
],
indirect=["metabolites"],
[docs])
def test_add_boundary(model, metabolites, reaction_type, prefix):
for metabolite in metabolites:
reaction = model.add_boundary(metabolite, reaction_type)
assert model.reactions.get_by_id(reaction.id) == reaction
assert reaction.reactants == [metabolite]
assert model.constraints[metabolite.id].expression.has(
model.variables[prefix + metabolite.id]
)
@pytest.mark.parametrize(
"metabolites, reaction_type, prefix",
[
("exchange", "exchange", "EX_"),
("demand", "demand", "DM_"),
("sink", "sink", "SK_"),
],
indirect=["metabolites"],
[docs])
def test_add_boundary_context(model, metabolites, reaction_type, prefix):
with model:
for metabolite in metabolites:
reaction = model.add_boundary(metabolite, reaction_type)
assert model.reactions.get_by_id(reaction.id) == reaction
assert reaction.reactants == [metabolite]
assert -model.constraints[metabolite.id].expression.has(
model.variables[prefix + metabolite.id]
)
for metabolite in metabolites:
assert prefix + metabolite.id not in model.reactions
assert prefix + metabolite.id not in model.variables.keys()
@pytest.mark.parametrize(
"metabolites, reaction_type",
[("exchange", "exchange"), ("demand", "demand"), ("sink", "sink")],
indirect=["metabolites"],
[docs])
def test_add_existing_boundary(model, metabolites, reaction_type):
for metabolite in metabolites:
model.add_boundary(metabolite, reaction_type)
with pytest.raises(ValueError):
model.add_boundary(metabolite, reaction_type)
@pytest.mark.parametrize("solver", stable_optlang)
[docs]def test_copy_benchmark(model, solver, benchmark):
def _():
model.copy()
if not getattr(model, "solver", None):
solver_dict[solver].create_problem(model)
benchmark(_)
@pytest.mark.parametrize("solver", stable_optlang)
[docs]def test_copy_benchmark_large_model(large_model, solver, benchmark):
def _():
large_model.copy()
if not getattr(large_model, "solver", None):
solver_dict[solver].create_problem(large_model)
benchmark(_)
[docs]def test_copy(model):
# Modifying copy should not modify the original
# Test that deleting reactions in the copy does not change the
# number of reactions in the original model
model_copy = model.copy()
old_reaction_count = len(model.reactions)
assert model_copy.notes is not model.notes
assert model_copy.annotation is not model.annotation
assert len(model.reactions) == len(model_copy.reactions)
assert len(model.metabolites) == len(model_copy.metabolites)
model_copy.remove_reactions(model_copy.reactions[0:5])
assert old_reaction_count == len(model.reactions)
assert len(model.reactions) != len(model_copy.reactions)
# Copying a model should not copy its context
with model:
model.remove_reactions([model.reactions.ACALD])
cp_model = model.copy()
assert len(cp_model._contexts) == 0
assert "ACALD" not in cp_model.reactions
[docs]def test_copy_with_groups(model):
sub = Group("pathway", members=[model.reactions.PFK, model.reactions.FBA])
model.add_groups([sub])
copy = model.copy()
assert len(copy.groups) == len(model.groups)
assert len(copy.groups.get_by_id("pathway")) == len(
model.groups.get_by_id("pathway")
)
[docs]def test_deepcopy_benchmark(model, benchmark):
benchmark(deepcopy, model)
[docs]def test_deepcopy(model):
# Reference structures are maintained when deepcopying
model_copy = deepcopy(model)
for gene, gene_copy in zip(model.genes, model_copy.genes):
assert gene.id == gene_copy.id
reactions = sorted(i.id for i in gene.reactions)
reactions_copy = sorted(i.id for i in gene_copy.reactions)
assert reactions == reactions_copy
for reaction, reaction_copy in zip(model.reactions, model_copy.reactions):
assert reaction.id == reaction_copy.id
metabolites = sorted(i.id for i in reaction._metabolites)
metabolites_copy = sorted(i.id for i in reaction_copy._metabolites)
assert metabolites == metabolites_copy
[docs]def test_add_reaction_orphans(model):
# Test reaction addition
# Need to verify that no orphan genes or metabolites are
# contained in reactions after adding them to the model.
model = model.__class__("test")
model.add_reactions((x.copy() for x in model.reactions))
genes = []
metabolites = []
for x in model.reactions:
genes.extend(x.genes)
metabolites.extend(x._metabolites)
orphan_genes = [x for x in genes if x.model is not model]
orphan_metabolites = [x for x in metabolites if x.model is not model]
# Check for dangling genes when running Model.add_reactions
assert len(orphan_genes) == 0
# Check for dangling metabolites when running Model.add_reactions
assert len(orphan_metabolites) == 0
[docs]def test_merge_models(model, tiny_toy_model):
with model, tiny_toy_model:
# Add some cons/vars to tiny_toy_model for testing merging
tiny_toy_model.add_reactions([Reaction("EX_glc__D_e")])
variable = tiny_toy_model.problem.Variable("foo")
constraint = tiny_toy_model.problem.Constraint(
variable, ub=0, lb=0, name="constraint"
)
tiny_toy_model.add_cons_vars([variable, constraint])
# Test merging to new model
merged = model.merge(
tiny_toy_model, inplace=False, objective="sum", prefix_existing="tiny_"
)
assert "ex1" in merged.reactions
assert "ex1" not in model.reactions
assert merged.reactions.ex1.objective_coefficient == 1
assert (
merged.reactions.get_by_id("Biomass_Ecoli_core").objective_coefficient == 1
)
assert "tiny_EX_glc__D_e" in merged.reactions
assert "foo" in merged.variables
# Test reversible in-place model merging
with model:
model.merge(
tiny_toy_model, inplace=True, objective="left", prefix_existing="tiny_"
)
assert "ex1" in model.reactions
assert "constraint" in model.constraints
assert "foo" in model.variables
assert "tiny_EX_glc__D_e" in model.reactions
assert (
model.objective.expression.simplify()
== model.reactions.get_by_id(
"Biomass_Ecoli_core"
).flux_expression.simplify()
)
assert "ex1" not in model.reactions
assert "constraint" not in model.constraints
assert "foo" not in model.variables
assert "tiny_EX_glc__D_e" not in model.reactions
# Test the deprecated operator overloading
with model:
merged = model + tiny_toy_model
assert "ex1" in merged.reactions
with model:
model += tiny_toy_model
assert "ex1" in model.reactions
@pytest.mark.parametrize("solver", stable_optlang)
[docs]def test_change_objective_benchmark(model, benchmark, solver):
atpm = model.reactions.get_by_id("ATPM")
def benchmark_change_objective():
model.objective = atpm.id
if not getattr(model, "solver", None):
solver_dict[solver].create_problem(model)
benchmark(benchmark_change_objective)
[docs]def test_get_objective_direction(model):
assert model.objective_direction == "max"
value = model.slim_optimize()
assert np.isclose(value, 0.874, 1e-3)
[docs]def test_set_objective_direction(model):
with model:
model.objective_direction = "min"
assert model.objective_direction == "min"
value = model.slim_optimize()
assert value == 0.0
assert model.objective_direction == "max"
[docs]def test_slim_optimize(model):
with model:
assert model.slim_optimize() > 0.872
model.reactions.Biomass_Ecoli_core.lower_bound = 10
assert isnan(model.slim_optimize())
with pytest.raises(OptimizationError):
model.slim_optimize(error_value=None)
@pytest.mark.parametrize("solver", optlang_solvers)
[docs]def test_optimize(model, solver):
model.solver = solver
with model:
assert model.optimize().objective_value > 0.872
model.reactions.Biomass_Ecoli_core.lower_bound = 10
with pytest.warns(UserWarning):
model.optimize()
with pytest.raises(OptimizationError):
model.optimize(raise_error=True)
def test_change_objective(model):
# Test for correct optimization behavior
model.optimize()
assert model.reactions.Biomass_Ecoli_core.x > 0.5
with model:
model.objective = model.reactions.EX_etoh_e
model.optimize()
assert model.reactions.Biomass_Ecoli_core.x < 0.5
assert model.reactions.Biomass_Ecoli_core.objective_coefficient == 1
model.optimize()
assert model.reactions.Biomass_Ecoli_core.x > 0.5
# Test changing objective
biomass = model.reactions.get_by_id("Biomass_Ecoli_core")
atpm = model.reactions.get_by_id("ATPM")
model.objective = atpm.id
assert atpm.objective_coefficient == 1.0
assert biomass.objective_coefficient == 0.0
assert su.linear_reaction_coefficients(model) == {atpm: 1.0}
# Change it back using object itself
model.objective = biomass
assert atpm.objective_coefficient == 0.0
assert biomass.objective_coefficient == 1.0
# Set both to 1 with a list
model.objective = [atpm, biomass]
assert atpm.objective_coefficient == 1.0
assert biomass.objective_coefficient == 1.0
# Set both using a dict
model.objective = {atpm: 0.2, biomass: 0.3}
assert abs(atpm.objective_coefficient - 0.2) < 10**-9
assert abs(biomass.objective_coefficient - 0.3) < 10**-9
# Test setting by index
model.objective = model.reactions.index(atpm)
assert su.linear_reaction_coefficients(model) == {atpm: 1.0}
# Test by setting list of indexes
model.objective = [model.reactions.index(reaction) for reaction in [atpm, biomass]]
assert su.linear_reaction_coefficients(model) == {atpm: 1.0, biomass: 1.0}
[docs]def test_problem_properties(model):
new_variable = model.problem.Variable("test_variable")
new_constraint = model.problem.Constraint(Zero, name="test_constraint", lb=0)
model.add_cons_vars([new_variable, new_constraint])
assert "test_variable" in model.variables
assert "test_constraint" in model.constraints
model.remove_cons_vars([new_constraint, new_variable])
assert "test_variable" not in model.variables
assert "test_constraint" not in model.variables
[docs]def test_solution_data_frame(model):
solution = model.optimize().to_frame()
assert isinstance(solution, pd.DataFrame)
assert "fluxes" in solution
assert "reduced_costs" in solution
[docs]def test_context_manager(model):
bounds0 = model.reactions[0].bounds
bounds1 = (1, 2)
bounds2 = (3, 4)
# Trigger a nested model context, ensuring that bounds are
# preserved at each level
with model:
model.reactions[0].bounds = bounds1
with model:
model.reactions[0].bounds = bounds2
assert model.reactions[0].bounds == bounds2
assert model.reactions[0].bounds == bounds1
assert model.reactions[0].bounds == bounds0
[docs]def test_objective_coefficient_reflects_changed_objective(model):
biomass_r = model.reactions.get_by_id("Biomass_Ecoli_core")
assert biomass_r.objective_coefficient == 1
model.objective = "PGI"
assert biomass_r.objective_coefficient == 0
assert model.reactions.PGI.objective_coefficient == 1
[docs]def test_change_objective_through_objective_coefficient(model):
biomass_r = model.reactions.get_by_id("Biomass_Ecoli_core")
pgi = model.reactions.PGI
pgi.objective_coefficient = 2
coef_dict = model.objective.expression.as_coefficients_dict()
# Check that objective has been updated
assert coef_dict[pgi.forward_variable] == 2.0
assert coef_dict[pgi.reverse_variable] == -2.0
# Check that original objective is still in there
assert coef_dict[biomass_r.forward_variable] == 1.0
assert coef_dict[biomass_r.reverse_variable] == -1.0
[docs]def test_transfer_objective(model):
new_mod = Model("new model")
new_mod.add_reactions(model.reactions)
new_mod.objective = model.objective
assert set(str(x) for x in model.objective.expression.args) == set(
str(x) for x in new_mod.objective.expression.args
)
new_mod.slim_optimize()
assert abs(new_mod.objective.value - 0.874) < 0.001
[docs]def test_model_from_other_model(model):
model = Model(id_or_model=model)
for reaction in model.reactions:
assert reaction == model.reactions.get_by_id(reaction.id)
[docs]def test_add_reactions(model):
r1 = Reaction("r1")
r1.add_metabolites({Metabolite("A"): -1, Metabolite("B"): 1})
r1.lower_bound, r1.upper_bound = -999999.0, 999999.0
r2 = Reaction("r2")
r2.add_metabolites({Metabolite("A"): -1, Metabolite("C"): 1, Metabolite("D"): 1})
r2.lower_bound, r2.upper_bound = 0.0, 999999.0
model.add_reactions([r1, r2])
r2.objective_coefficient = 3.0
assert r2.objective_coefficient == 3.0
assert model.reactions[-2] == r1
assert model.reactions[-1] == r2
assert isinstance(model.reactions[-2].reverse_variable, model.problem.Variable)
coefficients_dict = model.objective.expression.as_coefficients_dict()
biomass_r = model.reactions.get_by_id("Biomass_Ecoli_core")
assert coefficients_dict[biomass_r.forward_variable] == 1.0
assert coefficients_dict[biomass_r.reverse_variable] == -1.0
assert coefficients_dict[model.reactions.r2.forward_variable] == 3.0
assert coefficients_dict[model.reactions.r2.reverse_variable] == -3.0
[docs]def test_add_reactions_single_existing(model):
rxn = model.reactions[0]
r1 = Reaction(rxn.id)
r1.add_metabolites({Metabolite("A"): -1, Metabolite("B"): 1})
r1.lower_bound, r1.upper_bound = -999999.0, 999999.0
model.add_reactions([r1])
assert rxn in model.reactions
assert r1 is not model.reactions.get_by_id(rxn.id)
[docs]def test_add_reactions_duplicate(model):
rxn = model.reactions[0]
r1 = Reaction("r1")
r1.add_metabolites({Metabolite("A"): -1, Metabolite("B"): 1})
r1.lower_bound, r1.upper_bound = -999999.0, 999999.0
r2 = Reaction(rxn.id)
r2.add_metabolites({Metabolite("A"): -1, Metabolite("C"): 1, Metabolite("D"): 1})
model.add_reactions([r1, r2])
assert r1 in model.reactions
assert rxn in model.reactions
assert r2 is not model.reactions.get_by_id(rxn.id)
[docs]def test_add_cobra_reaction(model):
r = Reaction(id="c1")
model.add_reaction(r)
assert isinstance(model.reactions.c1, Reaction)
[docs]def test_all_objects_point_to_all_other_correct_objects(model):
for reaction in model.reactions:
assert reaction.model == model
for gene in reaction.genes:
assert gene == model.genes.get_by_id(gene.id)
assert gene.model == model
for reaction2 in gene.reactions:
assert reaction2.model == model
assert reaction2 == model.reactions.get_by_id(reaction2.id)
for metabolite in reaction.metabolites:
assert metabolite.model == model
assert metabolite == model.metabolites.get_by_id(metabolite.id)
for reaction2 in metabolite.reactions:
assert reaction2.model == model
assert reaction2 == model.reactions.get_by_id(reaction2.id)
[docs]def test_objects_point_to_correct_other_after_copy(model):
for reaction in model.reactions:
assert reaction.model == model
for gene in reaction.genes:
assert gene == model.genes.get_by_id(gene.id)
assert gene.model == model
for reaction2 in gene.reactions:
assert reaction2.model == model
assert reaction2 == model.reactions.get_by_id(reaction2.id)
for metabolite in reaction.metabolites:
assert metabolite.model == model
assert metabolite == model.metabolites.get_by_id(metabolite.id)
for reaction2 in metabolite.reactions:
assert reaction2.model == model
assert reaction2 == model.reactions.get_by_id(reaction2.id)
[docs]def test_remove_reactions(model):
reactions_to_remove = model.reactions[10:30]
assert all([reaction.model is model for reaction in reactions_to_remove])
assert all(
[
model.reactions.get_by_id(reaction.id) == reaction
for reaction in reactions_to_remove
]
)
model.remove_reactions(reactions_to_remove)
assert all([reaction.model is None for reaction in reactions_to_remove])
for reaction in reactions_to_remove:
assert reaction.id not in list(model.variables.keys())
model.add_reactions(reactions_to_remove)
for reaction in reactions_to_remove:
assert reaction in model.reactions
[docs]def test_objective(model):
obj = model.objective
assert obj.get_linear_coefficients(obj.variables) == {
model.variables["Biomass_Ecoli_core_reverse_2cdba"]: -1,
model.variables["Biomass_Ecoli_core"]: 1,
}
assert obj.direction == "max"
[docs]def test_change_objective(model):
expression = 1.0 * model.variables["ENO"] + 1.0 * model.variables["PFK"]
model.objective = model.problem.Objective(expression)
assert same_ex(model.objective.expression, expression)
model.objective = "ENO"
eno_obj = model.problem.Objective(
model.reactions.ENO.flux_expression, direction="max"
)
pfk_obj = model.problem.Objective(
model.reactions.PFK.flux_expression, direction="max"
)
assert same_ex(model.objective.expression, eno_obj.expression)
with model:
model.objective = "PFK"
assert same_ex(model.objective.expression, pfk_obj.expression)
assert same_ex(model.objective.expression, eno_obj.expression)
expression = model.objective.expression
atpm = model.reactions.get_by_id("ATPM")
biomass = model.reactions.get_by_id("Biomass_Ecoli_core")
with model:
model.objective = atpm
assert same_ex(model.objective.expression, expression)
with model:
atpm.objective_coefficient = 1
biomass.objective_coefficient = 2
assert same_ex(model.objective.expression, expression)
with model:
set_objective(model, model.problem.Objective(atpm.flux_expression))
assert same_ex(model.objective.expression, atpm.flux_expression)
assert same_ex(model.objective.expression, expression)
expression = model.objective.expression
with model:
with model: # Test to make sure nested contexts are OK
set_objective(model, atpm.flux_expression, additive=True)
assert same_ex(
model.objective.expression, expression + atpm.flux_expression
)
assert same_ex(model.objective.expression, expression)
[docs]def test_set_reaction_objective(model):
model.objective = model.reactions.ACALD
assert same_ex(
model.objective.expression,
1.0 * model.reactions.ACALD.forward_variable
- 1.0 * model.reactions.ACALD.reverse_variable,
)
[docs]def test_set_reaction_objective_str(model):
model.objective = model.reactions.ACALD.id
assert same_ex(
model.objective.expression,
1.0 * model.reactions.ACALD.forward_variable
- 1.0 * model.reactions.ACALD.reverse_variable,
)
[docs]def test_invalid_objective_raises(model):
with pytest.raises(ValueError):
setattr(model, "objective", "This is not a valid objective!")
with pytest.raises(TypeError):
setattr(model, "objective", 3.0)
@pytest.mark.skipif("cplex" not in solvers, reason="need cplex")
[docs]def test_solver_change(model):
model.solver = "glpk"
solver_id = id(model.solver)
problem_id = id(model.solver.problem)
solution = model.optimize().fluxes
model.solver = "cplex"
assert id(model.solver) != solver_id
assert id(model.solver.problem) != problem_id
new_solution = model.optimize().fluxes
assert np.allclose(solution, new_solution, rtol=0, atol=1e-06)
[docs]def test_no_change_for_same_solver(model):
model.solver = "glpk"
solver_id = id(model.solver)
problem_id = id(model.solver.problem)
model.solver = "glpk"
assert id(model.solver) == solver_id
assert id(model.solver.problem) == problem_id
[docs]def test_invalid_solver_change_raises(model):
with pytest.raises(SolverNotFound):
setattr(model, "solver", [1, 2, 3])
with pytest.raises(SolverNotFound):
setattr(model, "solver", "ThisIsDefinitelyNotAvalidSolver")
with pytest.raises(SolverNotFound):
setattr(model, "solver", os)
@pytest.mark.skipif("cplex" not in solvers, reason="no cplex")
[docs]def test_change_solver_to_cplex_and_check_copy_works(model):
assert (model.slim_optimize() - 0.8739215069684306) == pytest.approx(0.0)
model_copy = model.copy()
assert (model_copy.slim_optimize() - 0.8739215069684306) == pytest.approx(0.0)
# Second, change existing glpk based model to cplex
model.solver = "cplex"
assert (model.slim_optimize() - 0.8739215069684306) == pytest.approx(0.0)
model_copy = copy(model)
assert (model_copy.slim_optimize() - 0.8739215069684306) == pytest.approx(0.0)
[docs]def test_copy_preserves_existing_solution(solved_model):
solution, model = solved_model
model_cp = copy(model)
primals_original = [variable.primal for variable in model.variables]
primals_copy = [variable.primal for variable in model_cp.variables]
abs_diff = abs(np.array(primals_copy) - np.array(primals_original))
assert not any(abs_diff > 1e-6)
[docs]def test_repr_html_(model):
assert "<table>" in model._repr_html_()