diff --git a/.gitignore b/.gitignore index 58395cd38..a0f8c4b75 100644 --- a/.gitignore +++ b/.gitignore @@ -51,3 +51,5 @@ docs/_build .vagrant *~ /.DS_Store +.cache +/coverage.xml diff --git a/.travis.yml b/.travis.yml index a3a6253a1..15b9216dd 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,5 +1,6 @@ language: python -sudo: false +sudo: required +dist: trusty python: - '2.7' @@ -37,11 +38,11 @@ before_install: - 'echo "this is a build for: $TRAVIS_BRANCH"' - 'if [[ "$TRAVIS_BRANCH" != "devel" && ($TRAVIS_PYTHON_VERSION == "3.4" || $TRAVIS_PYTHON_VERSION == "2.7") ]]; then bash ./.travis/install_cplex.sh; fi' install: -- pip install flake8 numpy scipy pyzmq pandas +- pip install flake8 numpy scipy pyzmq pandas pytest pytest-cov - pip install .[swiglpk,test,parallel,cli] before_script: - if [[ $TRAVIS_PYTHON_VERSION == "3.6" ]]; then flake8 .; fi -script: nosetests +script: pytest -v -rsx --cov --cov-report=xml after_success: - codecov notifications: diff --git a/cameo/api/designer.py b/cameo/api/designer.py index 9d6eb685b..c1f6f6ef8 100644 --- a/cameo/api/designer.py +++ b/cameo/api/designer.py @@ -20,7 +20,11 @@ import re import numpy as np -from six.moves import reduce +from IProgress import ProgressBar, ETA, Bar + +from cameo.core import SolverBasedModel +from cameo.core.strain_design import StrainDesign +from cameo.strain_design.pathway_prediction.pathway_predictor import PathwayResult try: from IPython.core.display import display @@ -36,8 +40,7 @@ def HTML(*args, **kwargs): from cameo import Metabolite, Model, fba from cameo import config, util -from cameo.core.result import Result -from cameo.api.hosts import hosts, Host +from cameo.api.hosts import hosts as HOSTS, Host from cameo.api.products import products from cameo.exceptions import SolveError from cameo.strain_design import OptGene, DifferentialFVA @@ -45,6 +48,8 @@ def HTML(*args, **kwargs): from cameo.strain_design import pathway_prediction from cameo.util import TimeMachine from cameo.models import universal +from cameo.strain_design.heuristic.evolutionary.objective_functions import biomass_product_coupled_min_yield +from cameo.strain_design.heuristic.evolutionary.objective_functions import product_yield from cameo.visualization import visualization @@ -59,45 +64,65 @@ def HTML(*args, **kwargs): class _OptimizationRunner(object): + def __init__(self, debug=False): + self.debug = debug + def __call__(self, strategy): raise NotImplementedError class _OptGeneRunner(_OptimizationRunner): def __call__(self, strategy): - (model, pathway) = (strategy[1], strategy[2]) + max_evaluations = 20000 + + if self.debug: + max_evaluations = 1000 + + (model, pathway, aerobic) = (strategy[1], strategy[2], strategy[3]) + model = model.copy() + assert isinstance(model, SolverBasedModel) + assert isinstance(pathway, PathwayResult) + assert isinstance(aerobic, bool) + with TimeMachine() as tm: - pathway.plug_model(model, tm) + if not aerobic and 'EX_o2_e' in model.reactions: + model.reactions.EX_o2_e.change_bounds(lb=0, time_machine=tm) + pathway.apply(model, tm) + model.objective = model.biomass opt_gene = OptGene(model=model, plot=False) designs = opt_gene.run(target=pathway.product.id, biomass=model.biomass, substrate=model.carbon_source, - max_evaluations=10000) + max_evaluations=max_evaluations, max_knockouts=15) - return designs, pathway + return designs class _DifferentialFVARunner(_OptimizationRunner): def __call__(self, strategy): - (model, pathway) = (strategy[1], strategy[2]) - with TimeMachine() as tm: - pathway.plug_model(model, tm) - opt_gene = DifferentialFVA(design_space_model=model, objective=pathway.product.id) - designs = opt_gene.run() - - return designs, pathway + points = 50 + surface_only = False + if self.debug: + points = 5 + surface_only = True + + (model, pathway, aerobic) = (strategy[1], strategy[2], strategy[3]) + model = model.copy() + assert isinstance(model, SolverBasedModel) + assert isinstance(pathway, PathwayResult) + assert isinstance(aerobic, bool) + with TimeMachine() as tm: + if not aerobic and 'EX_o2_e' in model.reactions: + model.reactions.EX_o2_e.change_bounds(lb=0, time_machine=tm) -class DesignerResult(Result): - def __init__(self, designs, *args, **kwargs): - super(DesignerResult, self).__init__(*args, **kwargs) - self.designs = designs - - def _repr_latex_(self): - pass - + pathway.apply(model, tm) + model.objective = model.biomass + diff_fva = DifferentialFVA(design_space_model=model, + objective=pathway.product.id, + variables=[model.biomass], + points=points) + designs = diff_fva.run(improvements_only=True, surface_only=surface_only) -class StrainDesings(Result): - def __init__(self, organism, designs, *args, **kwargs): - super(StrainDesings, self).__init__(*args, **kwargs) + return designs class Designer(object): @@ -109,11 +134,11 @@ class Designer(object): designs = design(product='L-glutamate') """ - def __init__(self): + def __init__(self, debug=False): """""" - pass + self.debug = debug - def __call__(self, product='L-glutamate', hosts=hosts, database=None, view=config.default_view): + def __call__(self, product='L-glutamate', hosts=HOSTS, database=None, aerobic=True, view=config.default_view): """The works. The following workflow will be followed to determine suitable @@ -132,6 +157,8 @@ def __call__(self, product='L-glutamate', hosts=hosts, database=None, view=confi The desired product. hosts : list or Model or Host A list of hosts (e.g. cameo.api.hosts), models, mixture thereof, or a single model or host. + aerobic: bool + If false, sets `model.reaction.EX_o2_e.lower_bound = 0` Returns ------- @@ -145,48 +172,134 @@ def __call__(self, product='L-glutamate', hosts=hosts, database=None, view=confi product = self.__translate_product_to_universal_reactions_model_metabolite(product, database) except KeyError: raise KeyError("Product %s is not in the %s database" % (product, database.id)) - pathways = self.predict_pathways(product, hosts=hosts, database=database) - print("Optimizing %i pathways" % sum(len(p) for p in pathways.values())) - optimization_reports = self.optimize_strains(pathways, view) + pathways = self.predict_pathways(product, hosts=hosts, database=database, aerobic=aerobic) + optimization_reports = self.optimize_strains(pathways, view, aerobic=aerobic) return optimization_reports - def optimize_strains(self, pathways, view): + def optimize_strains(self, pathways, view, aerobic=True): """ - Optimize targets for the identified pathways. The optimization will only run if the patwhay can be optimized. + Optimize targets for the identified pathways. The optimization will only run if the pathway can be optimized. Arguments --------- pathways: list - A list of dictionaries to optimize. + A list of dictionaries to optimize ([Host, Model] -> PredictedPathways). view: object A view for multi, single os distributed processing. + aerobic: bool + If True, it will set `model.reactions.EX_o2_e.lower_bound` to 0. + + Returns + ------- + pandas.DataFrame + A data frame with strain designs processed and ranked. """ - opt_gene_runner = _OptGeneRunner() - differential_fva_runner = _DifferentialFVARunner() - designs = [(host, model, pathway) for (host, model) in pathways for pathway in pathways[host, model] - if pathway.needs_optimization(model, objective=model.biomass)] + opt_gene_runner = _OptGeneRunner(self.debug) + differential_fva_runner = _DifferentialFVARunner(self.debug) + strategies = [(host, model, pathway, aerobic) for (host, model) in pathways for pathway in pathways[host, model] + if pathway.needs_optimization(model, objective=model.biomass)] - final_designs = [] + print("Optimizing %i pathways" % len(strategies)) - results = view.map(opt_gene_runner, designs) - final_designs += self.process_strain_design_results(results) - results = view.map(differential_fva_runner, designs) - final_designs += self.process_strain_design_results(results) + results = DataFrame(columns=["host", "model", "manipulations", "heterologous_pathway", + "fitness", "yield", "product", "biomass", "method"]) - return reduce(lambda x, y: x + y, final_designs) + mapped_designs1 = view.map(opt_gene_runner, strategies) + mapped_designs2 = view.map(differential_fva_runner, strategies) + progress = ProgressBar(maxval=len(mapped_designs1) + len(mapped_designs2), + widgets=["Processing solutions: ", Bar(), ETA()]) + progress.start() + results = self.build_results_data(mapped_designs1, strategies, results, progress) + results = self.build_results_data(mapped_designs2, strategies, results, progress, offset=len(mapped_designs1)) + progress.finish() + + return results + + def build_results_data(self, strategy_designs, strategies, results, progress, offset=0): + """ + Process the designs and add them to the `results` DataFrame. + + Parameters + ---------- + strategy_designs: list + A list of list[StrainDesign]. Each list corresponds to a strategy. + strategies: list + List of [(Host, SolverBasedModel, PathwayResult, Boolean)]. Note: variables: host, model, pathway, anaerobic + results: pandas.DataFrame + An existing DataFrame to be extended. + progress: IProgress.ProgressBar + A progress bar handler. + offset: int + An offset tracker for the progress bar. + + Returns + ------- + pandas.DataFrame + A data frame with the processed designs appended + + """ + for i, strain_designs in enumerate(strategy_designs): + strategy = strategies[i] + _results = DataFrame(columns=results.columns, index=[j for j in range(len(strain_designs))]) + manipulations, fitness, yields, target_flux, biomass = self.process_strain_designs(strain_designs, + *strategy[1:]) + for j, strain_design in enumerate(manipulations): + _results.loc[j, 'manipulations'] = strain_design + _results.loc[j, 'heterologous_pathway'] = strategy[2] + _results['host'] = strategy[0].name + _results['model'] = strategy[1].id + _results['fitness'] = fitness + _results['yield'] = yields + _results['biomass'] = biomass + _results['product'] = target_flux + _results['method'] = "DifferentialFVA+PathwayFinder" + + results = results.append(_results, ignore_index=True) + progress.update(i + offset) + return results + + def process_strain_designs(self, strain_designs, model, pathway, aerobic): + model = model.copy() + assert isinstance(pathway, StrainDesign) + assert isinstance(pathway, PathwayResult) + final_strain_designs = [] + fitness = [] + yields = [] + biomass = [] + target_flux = [] + pyield = product_yield(pathway.product, model.carbon_source) + bpcy = biomass_product_coupled_min_yield(model.biomass, pathway.product, model.carbon_source) + for strain_design in strain_designs: + assert isinstance(strain_design, StrainDesign) + _fitness, _yield, _target_flux, _biomass = self.evaluate_design(model, strain_design, + pathway, aerobic, bpcy, pyield) + fitness.append(_fitness) + yields.append(_yield) + final_strain_designs.append(strain_design) + biomass.append(_biomass) + target_flux.append(_target_flux) + + return final_strain_designs, fitness, yields, target_flux, biomass @staticmethod - def process_strain_design_results(results): - designs = [] - for res in results: - (design_result, pathway) = (res[0], res[1]) - for strain_design in design_result: - strain_design += pathway - designs.append(design_result) - return designs - - def predict_pathways(self, product, hosts=None, database=None): # TODO: make this work with a single host or model + def evaluate_design(model, strain_design, pathway, aerobic, bpcy, pyield): + with TimeMachine() as tm: + if not aerobic and 'EX_o2_e' in model.reactions: + model.reactions.EX_o2_e.change_bounds(lb=0, time_machine=tm) + pathway.apply(model, time_machine=tm) + strain_design.apply(model, time_machine=tm) + try: + solution = fba(model, objective=model.biomass) + _bpcy = bpcy(model, solution, strain_design.targets) + _pyield = pyield(model, solution, strain_design.targets) + target_flux = solution.fluxes[pyield.product] + biomass = solution.fluxes[bpcy.biomass] + except SolveError: + _bpcy, _pyield, target_flux, biomass = np.nan, np.nan, np.nan, np.nan + return _bpcy, _pyield, target_flux, biomass + + def predict_pathways(self, product, hosts=None, database=None, aerobic=True): """Predict production routes for a desired product and host spectrum. Parameters ---------- @@ -194,12 +307,19 @@ def predict_pathways(self, product, hosts=None, database=None): # TODO: make th The desired product. hosts : list or Model or Host A list of hosts (e.g. cameo.api.hosts), models, mixture thereof, or a single model or host. + database: SolverBasedModel + A model to use as database. See also: cameo.models.universal + aerobic: bool + If True, it will set `model.reactions.EX_o2_e.lower_bound` to 0. Returns ------- dict - ... + ([Host, Model] -> PredictedPathways) """ + max_predictions = 8 + timeout = 3 * 60 + pathways = dict() product = self.__translate_product_to_universal_reactions_model_metabolite(product, database) for host in hosts: @@ -207,19 +327,29 @@ def predict_pathways(self, product, hosts=None, database=None): # TODO: make th if isinstance(host, Model): host = Host(name='UNKNOWN_HOST', models=[host]) for model in list(host.models): - identifier = searching() - logging.debug('Processing model {} for host {}'.format(model.id, host.name)) - notice('Predicting pathways for product %s in %s (using model %s).' - % (product.name, host, model.id)) - logging.debug('Predicting pathways for model {}'.format(model.id)) - pathway_predictor = pathway_prediction.PathwayPredictor(model, - universal_model=database, - compartment_regexp=re.compile(".*_c$")) - # TODO adjust these numbers to something reasonable - predicted_pathways = pathway_predictor.run(product, max_predictions=4, timeout=3 * 60, silent=True) - stop_loader(identifier) - pathways[(host, model)] = predicted_pathways - self.__display_pathways_information(predicted_pathways, host, model) + with TimeMachine() as tm: + if not aerobic and "EX_o2_e" in model.reactions: + model.reactions.EX_o2_e.change_bounds(lb=0, time_machin=tm) + identifier = searching() + logging.debug('Processing model {} for host {}'.format(model.id, host.name)) + notice('Predicting pathways for product %s in %s (using model %s).' + % (product.name, host, model.id)) + logging.debug('Predicting pathways for model {}'.format(model.id)) + pathway_predictor = pathway_prediction.PathwayPredictor(model, + universal_model=database, + compartment_regexp=re.compile(".*_c$")) + + if self.debug: + max_predictions = 2 + timeout = 60 + + predicted_pathways = pathway_predictor.run(product, + max_predictions=max_predictions, + timeout=timeout, + silent=True) + stop_loader(identifier) + pathways[(host, model)] = predicted_pathways + self.__display_pathways_information(predicted_pathways, host, model) return pathways def __translate_product_to_universal_reactions_model_metabolite(self, product, database): @@ -307,10 +437,8 @@ def __generate_ascii(inchi): @staticmethod def __display_pathways_information(predicted_pathways, host, original_model): if util.in_ipnb(): - predicted_pathways.plot_production_envelopes(original_model, - title="Production envelopes for %s (%s)" % ( - host.name, original_model.id), - objective=original_model.biomass) + title = "Production envelopes for %s (%s)" % (host.name, original_model.id) + predicted_pathways.plot_production_envelopes(original_model, title=title, objective=original_model.biomass) @staticmethod def calculate_yield(model, source, product): diff --git a/cameo/cli/controllers.py b/cameo/cli/controllers.py index 0d57bcafa..96c2c422f 100644 --- a/cameo/cli/controllers.py +++ b/cameo/cli/controllers.py @@ -43,8 +43,10 @@ class Meta: (['-m', '--multiprocess'], dict(help='Run multiprocess mode', action='store_true')), (['-c', '--cores'], dict(help="Number of cores (if multiprocess)")), (['-o', '--output'], dict(help="Output file")), - (['-of', '--output-format'], dict(help="Output file format (default xlsx)\nOptions:%s" % VALID_OUTPUT_FORMATS)), - (['-y', '--yes-all'], dict(help="Auto select suggested options", action="store_true"))] + (['-of', '--output-format'], dict(help="Output file format (default xlsx)\nOptions:%s" % + VALID_OUTPUT_FORMATS)), + (['-y', '--yes-all'], dict(help="Auto select suggested options", action="store_true")), + (['-t', '--test'], dict(help="Test mode", action="store_true"))] @expose(hide=True) def default(self): @@ -105,9 +107,14 @@ def default(self): else: view = SequentialView() + design.debug = self.app.pargs.test + results = design(product=product, hosts=_hosts, view=view, aerobic=not self.app.pargs.anaerobic) + results['heterologous_pathway'] = results.heterologous_pathway.apply(str) + results['manipulations'] = results.manipulations.apply(str) + OUTPUT_WRITER[output_format](results, output) @expose(help="Search for products in our internal database") diff --git a/cameo/core/metabolite.py b/cameo/core/metabolite.py index 2274a8dbf..8019ca624 100644 --- a/cameo/core/metabolite.py +++ b/cameo/core/metabolite.py @@ -25,7 +25,6 @@ @six.add_metaclass(inheritdocstring) class Metabolite(cobra.core.Metabolite): - # TODO: figure out how to handle the _reaction attribute @classmethod def clone(cls, metabolite, model=None): @@ -35,7 +34,8 @@ def clone(cls, metabolite, model=None): setattr(new_metabolite, attribute, value) except AttributeError: logger.info( - "Can't set attribute %s for metabolite %s (while cloning it to a cameo style metabolite). Skipping it ..." % + "Can't set attribute %s for metabolite %s (while cloning it to a " + "cameo style metabolite). Skipping it ..." % (attribute, metabolite) ) if model is not None: diff --git a/cameo/core/reaction.py b/cameo/core/reaction.py index 08ea794c2..58170f300 100644 --- a/cameo/core/reaction.py +++ b/cameo/core/reaction.py @@ -64,7 +64,8 @@ def clone(cls, reaction, model=None): setattr(new_reaction, attribute, value) except AttributeError: logger.info( - "Can't set attribute %s for reaction %s (while cloning it to a cameo style reaction). Skipping it ..." % ( + "Can't set attribute %s for reaction %s (while cloning it to a " + "cameo style reaction). Skipping it ..." % ( attribute, reaction)) if not isinstance(reaction.model, cameo.core.solver_based_model.SolverBasedModel): new_reaction._model = None @@ -333,7 +334,8 @@ def model(self, value): @property def objective_coefficient(self): - if self.model is not None and isinstance(self.model, cameo.core.SolverBasedModel) and self.model.objective is not None: + if self.model is not None and isinstance(self.model, + cameo.core.SolverBasedModel) and self.model.objective is not None: coefficients_dict = self.model.objective.expression.as_coefficients_dict() forw_coef = coefficients_dict.get(self.forward_variable, 0) rev_coef = coefficients_dict.get(self.reverse_variable, 0) diff --git a/cameo/core/solution.py b/cameo/core/solution.py index 4d7d96ea4..c87bf80c2 100644 --- a/cameo/core/solution.py +++ b/cameo/core/solution.py @@ -162,9 +162,11 @@ def __init__(self, model, *args, **kwargs): self._reduced_values = model.solver.reduced_costs for reaction in model.reactions: - self.fluxes[reaction.id] = self._primal_values[reaction._get_forward_id()] - self._primal_values[reaction._get_reverse_id()] + self.fluxes[reaction.id] = self._primal_values[reaction._get_forward_id()] - self._primal_values[ + reaction._get_reverse_id()] - self.reduced_costs[reaction.id] = self._reduced_values[reaction._get_forward_id()] - self._reduced_values[reaction._get_reverse_id()] + self.reduced_costs[reaction.id] = self._reduced_values[reaction._get_forward_id()] - self._reduced_values[ + reaction._get_reverse_id()] self.status = model.solver.status self._reaction_ids = [r.id for r in self.model.reactions] @@ -228,7 +230,8 @@ def timestamp_formatter(timestamp): "%Y-%m-%d %H:%M:%S:%f") raise UndefinedSolution( - 'The solution (captured around %s) has become invalid as the model has been re-optimized recently (%s).' % ( + 'The solution (captured around %s) has become invalid as the model has been ' + 're-optimized recently (%s).' % ( timestamp_formatter(self._time_stamp), timestamp_formatter(self.model._timestamp_last_optimization)) ) @@ -268,7 +271,8 @@ def reduced_costs(self): reduced_costs = OrderedDict() for reaction in self.model.reactions: - reduced_costs[reaction.id] = reduced_values[reaction._get_forward_id()] - reduced_values[reaction._get_reverse_id()] + reduced_costs[reaction.id] = reduced_values[reaction._get_forward_id()] - reduced_values[ + reaction._get_reverse_id()] return reduced_costs @property diff --git a/cameo/core/solver_based_model.py b/cameo/core/solver_based_model.py index 9402aecdb..5b51736b6 100644 --- a/cameo/core/solver_based_model.py +++ b/cameo/core/solver_based_model.py @@ -315,7 +315,8 @@ def add_reactions(self, reaction_list): else: cloned_reaction_list.append(reaction) - # cobrapy will raise an exceptions if one of the reactions already exists in the model (before adding any reactions) + # cobrapy will raise an exceptions if one of the reactions already exists in the model (before adding any + # reactions) super(SolverBasedModel, self).add_reactions(cloned_reaction_list) for reac in cloned_reaction_list: reac.model = self diff --git a/cameo/core/strain_design.py b/cameo/core/strain_design.py index 3912e498a..9616b9839 100644 --- a/cameo/core/strain_design.py +++ b/cameo/core/strain_design.py @@ -56,10 +56,10 @@ def __init__(self, targets): self.targets = DictList(targets) def __str__(self): - return "".join(str(t) for t in self.targets) + return ", ".join(str(t) for t in self.targets) def __repr__(self): - return str(self) + return "" def __iter__(self): return iter(self.targets) @@ -92,35 +92,43 @@ def apply(self, model, time_machine=None): target.apply(model, time_machine) def __add__(self, other): + if not isinstance(other, StrainDesign): + raise AssertionError("Only instances of StrainDesign can be added together") + targets = {} for target in self.targets: if target.id not in targets: - targets[target.id] = [] - targets[target.id].append(target) + targets[target.id] = set() + targets[target.id].add(target) for target in other.targets: if target.id not in targets: - targets[target.id] = [] - targets[target.id].append(target) + targets[target.id] = set() + targets[target.id].add(target) - targets = [t[0] if len(t) == 1 else EnsembleTarget(id, t) for id, t in six.iteritems(targets)] + targets = [next(iter(t)) if len(t) == 1 else EnsembleTarget(id, t) for id, t in six.iteritems(targets)] return StrainDesign(targets) def __iadd__(self, other): + if not isinstance(other, StrainDesign): + raise AssertionError("Only instances of StrainDesign can be added together") + targets = {} for target in self.targets: if target.id not in targets: - targets[target.id] = [] - targets[target.id].append(target) + targets[target.id] = set() + targets[target.id].add(target) for target in other.targets: if target.id not in targets: - targets[target.id] = [] - targets[target.id].append(target) + targets[target.id] = set() + targets[target.id].add(target) + + targets = [next(iter(t)) if len(t) == 1 else EnsembleTarget(id, t) for id, t in six.iteritems(targets)] - targets = [t[0] if len(t) == 1 else EnsembleTarget(id, t) for id, t in six.iteritems(targets)] self.targets = DictList(targets) + return self def _repr_html_(self): diff --git a/cameo/core/target.py b/cameo/core/target.py index c94353a09..12d7e7112 100644 --- a/cameo/core/target.py +++ b/cameo/core/target.py @@ -15,11 +15,13 @@ from functools import partial import numpy +import six from cameo.core.manipulation import swap_cofactors, increase_flux, decrease_flux, reverse_flux try: from gnomic import Accession, Feature, Del, Mutation, Sub, Ins + _gnomic_available_ = True except (ImportError, SyntaxError): # SyntaxError from py2 incompatible syntax _gnomic_available_ = False @@ -27,7 +29,6 @@ from cameo import ui from cameo.exceptions import IncompatibleTargets - __all__ = ["GeneModulationTarget", "GeneKnockoutTarget", "ReactionCofactorSwapTarget", "ReactionKnockinTarget", "ReactionKnockoutTarget", "ReactionModulationTarget"] @@ -45,6 +46,7 @@ class Target(object): The identifier of the target. The id must be present in the COBRA model. """ + def __init__(self, id): self.id = id @@ -79,9 +81,15 @@ def to_gnomic(self): raise SystemError("Gnomic is only compatible with python >= 3 (%i.%i)" % (sys.version_info.major, sys.version_info.minor)) + def __repr__(self): + return "" % self.id + def __str__(self): return self.id + def __hash__(self): + return hash(str(self)) + class FluxModulationTarget(Target): """ @@ -148,14 +156,31 @@ def fold_change(self): def __str__(self): if self._value == 0: - return ui.delta() + self.id + s = ui.delta() + self.id elif self.fold_change > 0: - return ui.upreg(self.fold_change) + self.id + s = ui.upreg(self.fold_change) + self.id elif self.fold_change < 0: - return ui.downreg(self.fold_change) + self.id + s = ui.downreg(self.fold_change) + self.id + else: + s = RuntimeError("fold_change shouldn't be 0") + if six.PY2: + return s.encode('utf-8') + + return s + + def __repr__(self): + if self._value == 0: + return "" % self.id + elif self.fold_change > 0: + return "" % (self.fold_change, self.id) + elif self.fold_change < 0: + return "" % (self.fold_change, self.id) else: raise RuntimeError("fold_change shouldn't be 0") + def __hash__(self): + return hash(str(self)) + def _repr_html_(self): if self._value == 0: return "Δ%s" % self.id @@ -185,6 +210,7 @@ class ReactionCofactorSwapTarget(Target): """ Swap cofactors of a given reaction. """ + def __init__(self, id, swap_pairs): super(ReactionCofactorSwapTarget, self).__init__(id) self.swap_pairs = swap_pairs @@ -204,9 +230,12 @@ def to_gnomic(self): new_feature = Feature(accession=new_accession, type='reaction') return Sub(original_feature, new_feature) - def __str__(self): + def __repr__(self): return "" % (self.id, self.swap_str) + def __str__(self): + return "%s[%s]" % (self.id, self.swap_str) + def __gt__(self, other): if self.id == other.id: if isinstance(other, ReactionKnockoutTarget): @@ -215,6 +244,8 @@ def __gt__(self, other): return False if isinstance(other, ReactionKnockinTarget): return True + elif isinstance(other, EnsembleTarget): + return not other > self else: raise IncompatibleTargets(self, other) else: @@ -227,7 +258,10 @@ def __eq__(self, other): return False def _repr_html_(self): - return self.id + "|" + self.swap_str.replace("⇄") + return self.id + "|" + self.swap_str.replace("<->", "⇄") + + def __hash__(self): + return hash(str(self)) class KnockinTarget(Target): @@ -238,6 +272,15 @@ def __init__(self, id, value): def to_gnomic(self): raise NotImplementedError + def __repr__(self): + return "" % self.id + + def __str__(self): + return "::%s" % self.id + + def __hash__(self): + return hash(str(self)) + class ReactionKnockinTarget(KnockinTarget): def __init__(self, id, value): @@ -270,6 +313,8 @@ def __gt__(self, other): return False elif isinstance(other, ReactionCofactorSwapTarget): return False + elif isinstance(other, EnsembleTarget): + return not other > self else: raise IncompatibleTargets(self, other) else: @@ -282,8 +327,14 @@ def __eq__(self, other): return False def __str__(self): + return "::%s" % self.id + + def __repr__(self): return "" % self.id + def __hash__(self): + return hash(str(self)) + def _repr_html_(self): return "::%s" % self.id @@ -303,6 +354,8 @@ def __gt__(self, other): return False elif isinstance(other, GeneModulationTarget) and not isinstance(other, GeneKnockoutTarget): return self.fold_change > other.fold_change + elif isinstance(other, EnsembleTarget): + return not other > self else: raise IncompatibleTargets(self, other) else: @@ -310,15 +363,20 @@ def __gt__(self, other): def __eq__(self, other): if isinstance(other, GeneModulationTarget): - return self.id == other.id and self._value == other._value and self._reference_value == other._reference_value + return (self.id == other.id and self._value == other._value and + self._reference_value == other._reference_value) else: return False + def __hash__(self): + return hash(str(self)) + class GeneKnockoutTarget(GeneModulationTarget): """ Gene Knockout Target. Knockout a gene present in a COBRA model. """ + def __init__(self, id): super(GeneKnockoutTarget, self).__init__(id, 0, None) @@ -332,6 +390,8 @@ def __gt__(self, other): return self.fold_change > other.fold_change elif isinstance(other, GeneKnockoutTarget): return False + elif isinstance(other, EnsembleTarget): + return not other > self else: raise IncompatibleTargets(self, other) else: @@ -345,9 +405,12 @@ def __eq__(self, other): else: return False - def __str__(self): + def __repr__(self): return "" % self.id + def __hash__(self): + return hash(str(self)) + class ReactionModulationTarget(FluxModulationTarget): __gnomic_feature_type__ = "reaction" @@ -369,6 +432,8 @@ def __gt__(self, other): return True elif isinstance(other, ReactionModulationTarget) and not isinstance(other, ReactionKnockoutTarget): return self.fold_change > other.fold_change + elif isinstance(other, EnsembleTarget): + return not other > self else: raise IncompatibleTargets(self, other) else: @@ -376,15 +441,20 @@ def __gt__(self, other): def __eq__(self, other): if isinstance(other, ReactionModulationTarget): - return self.id == other.id and self._value == other._value and self._reference_value == other._reference_value + return (self.id == other.id and self._value == other._value and + self._reference_value == other._reference_value) else: return False + def __hash__(self): + return hash(str(self)) + class ReactionKnockoutTarget(ReactionModulationTarget): """ Reaction Knockout Target. Knockout a reaction present in a COBRA model. """ + def __init__(self, id): super(ReactionKnockoutTarget, self).__init__(id, 0, None) @@ -401,6 +471,8 @@ def __gt__(self, other): raise IncompatibleTargets(self, other) elif isinstance(other, ReactionCofactorSwapTarget): raise IncompatibleTargets(self, other) + elif isinstance(other, EnsembleTarget): + return not other > self else: raise IncompatibleTargets(self, other) else: @@ -414,13 +486,18 @@ def __eq__(self, other): else: return False - def __str__(self): + def __repr__(self): return "" % self.id + def __hash__(self): + return hash(str(self)) -class ReactionInversionTarget(ReactionModulationTarget): +class ReactionInversionTarget(ReactionModulationTarget): def __str__(self): + return "INV(%.3f -> %.3f)-%s" % (self._reference_value, self._value, self.id) + + def __repr__(self): return " %.5f)>" % (self.id, self._reference_value, self._value) def _repr_html_(self): @@ -452,6 +529,9 @@ def apply(self, model, time_machine=None): reaction = self.get_model_target(model) reverse_flux(reaction, self._reference_value, self._value, time_machine=time_machine) + def __hash__(self): + return hash(str(self)) + class EnsembleTarget(Target): """ @@ -463,6 +543,7 @@ class EnsembleTarget(Target): The Targets are prioritized by what should happen first in order to don't break. """ + def __init__(self, id, targets): super(EnsembleTarget, self).__init__(id) assert all(t.id == id for t in targets) @@ -472,12 +553,12 @@ def apply(self, model, time_machine=None): for target in self.targets: target.apply(model, time_machine=time_machine) - def __str__(self): + def __repr__(self): head = " other.id + + else: + is_greater = False + for t in self.targets: + is_greater = is_greater or t >= other + + def __eq__(self, other): + if isinstance(other, EnsembleTarget): + if len(self.targets) == len(other.targets): + return all(target == other.targets[i] for i, target in enumerate(self.targets)) + + return False + + def __str__(self): + return "%s[%s]" % (self.id, ", ".join(str(t) for t in self.targets)) + + def __hash__(self): + return hash(str(self)) diff --git a/cameo/flux_analysis/structural.py b/cameo/flux_analysis/structural.py index 0ecd57c24..47199db6e 100644 --- a/cameo/flux_analysis/structural.py +++ b/cameo/flux_analysis/structural.py @@ -36,10 +36,8 @@ from cameo.exceptions import SolveError, Infeasible from cameo.util import TimeMachine - __all__ = ['find_dead_end_reactions', 'find_coupled_reactions', 'ShortestElementaryFluxModes'] - logger = logging.getLogger(__name__) logger.setLevel(logging.DEBUG) @@ -204,8 +202,7 @@ def find_dead_end_reactions(model): # Remove blocked reactions from stoichiometries stoichiometries = { met_id: {reac: coef for reac, coef in stoichiometry.items() if reac not in new_blocked} - for met_id, stoichiometry in stoichiometries.items() - } + for met_id, stoichiometry in stoichiometries.items()} blocked_reactions.update(new_blocked) return frozenset(blocked_reactions) @@ -348,7 +345,8 @@ def __generate_elementary_modes(self): reaction_copy.upper_bound = 0 elementary_flux_mode.append(reaction_copy) exclusion_list.append(reaction._indicator_variable_rev) - exclusion_constraint = self.model.solver.interface.Constraint(sympy.Add(*exclusion_list), ub=len(exclusion_list) - 1) + exclusion_constraint = self.model.solver.interface.Constraint(sympy.Add(*exclusion_list), + ub=len(exclusion_list) - 1) self.model.solver._add_constraint(exclusion_constraint, sloppy=True) yield elementary_flux_mode @@ -370,13 +368,14 @@ def __generate_elementary_modes_via_fixed_size_constraint(self): for i in range(solution_num): elementary_flux_mode = list() exclusion_list = list() + problem_solution = self.model.solver.problem.solution for reaction in self._reactions: - if self.model.solver.problem.solution.pool.get_values(i, reaction._indicator_variable_fwd.name) >= 0.9: + if problem_solution.pool.get_values(i, reaction._indicator_variable_fwd.name) >= 0.9: reaction_copy = copy(reaction) reaction_copy.lower_bound = 0 elementary_flux_mode.append(reaction_copy) exclusion_list.append(reaction._indicator_variable_fwd) - elif self.model.solver.problem.solution.pool.get_values(i, reaction._indicator_variable_rev.name) >= 0.9: + elif problem_solution.pool.get_values(i, reaction._indicator_variable_rev.name) >= 0.9: reaction_copy = copy(reaction) reaction_copy.upper_bound = 0 elementary_flux_mode.append(reaction_copy) @@ -384,14 +383,14 @@ def __generate_elementary_modes_via_fixed_size_constraint(self): exclusion_lists.append(exclusion_list) yield elementary_flux_mode for exclusion_list in exclusion_lists: - exclusion_constraint = self.model.solver.interface.Constraint(sympy.Add(*exclusion_list), ub=len(exclusion_list) - 1) + exclusion_constraint = self.model.solver.interface.Constraint(sympy.Add(*exclusion_list), + ub=len(exclusion_list) - 1) self.model.solver._add_constraint(exclusion_constraint, sloppy=True) new_fixed_size = fixed_size_constraint.ub + 1 if new_fixed_size > len(self._reactions): break fixed_size_constraint.ub, fixed_size_constraint.lb = new_fixed_size, new_fixed_size - @property def model(self): return self._model @@ -501,7 +500,8 @@ def _make_dual_model(self, model): if reaction._get_forward_id() in dual_metabolite_names: transposed_stoichiometry.setdefault(met, {})[ dual_model.metabolites.get_by_id(reaction._get_forward_id())] = coef - elif reaction.id in dual_metabolite_names: # This should be the same as forward_var.name but in general it might not be + # This should be the same as forward_var.name but in general it might not be + elif reaction.id in dual_metabolite_names: transposed_stoichiometry.setdefault(met, {})[ dual_model.metabolites.get_by_id(reaction.id)] = coef @@ -583,13 +583,11 @@ def _add_target_constraints(self, c): if target.ub is not None: coefficients = { self._dual_model.metabolites.get_by_id(var.name): coef for var, coef in coefficients_dict.items() - if var.name in self._dual_model.metabolites - } + if var.name in self._dual_model.metabolites} elif target.lb is not None: coefficients = { self._dual_model.metabolites.get_by_id(var.name): -coef for var, coef in coefficients_dict.items() - if var.name in self._dual_model.metabolites - } + if var.name in self._dual_model.metabolites} w_reac.add_metabolites(coefficients) w_reactions.append(w_reac) self._dual_model.add_reactions(w_reactions) @@ -603,8 +601,7 @@ def _construct_constraints(self, constraints): else: cloned_constraints = [ self._primal_model.solver.interface.Constraint.clone(constraint, model=self._primal_model.solver) - for constraint in constraints - ] + for constraint in constraints] self._primal_model.solver.add(cloned_constraints) illegal_knockouts = [] for reaction in self._primal_model.reactions: diff --git a/cameo/models/json/e_coli_core.json b/cameo/models/json/e_coli_core.json new file mode 100644 index 000000000..186860cf3 --- /dev/null +++ b/cameo/models/json/e_coli_core.json @@ -0,0 +1 @@ +{"reactions": [{"subsystem": "Pyruvate Metabolism", "name": "Acetaldehyde dehydrogenase (acetylating)", "upper_bound": 1000.0, "lower_bound": -1000.0, "notes": {"original_bigg_ids": ["ACALD"]}, "metabolites": {"nad_c": -1.0, "acald_c": -1.0, "coa_c": -1.0, "h_c": 1.0, "accoa_c": 1.0, "nadh_c": 1.0}, "id": "ACALD", "gene_reaction_rule": "b0351 or b1241"}, {"subsystem": "Transport, Extracellular", "name": "Acetaldehyde reversible transport", "upper_bound": 1000.0, "lower_bound": -1000.0, "notes": {"original_bigg_ids": ["ACALDt"]}, "metabolites": {"acald_c": 1.0, "acald_e": -1.0}, "id": "ACALDt", "gene_reaction_rule": "s0001"}, {"subsystem": "Pyruvate Metabolism", "name": "Acetate kinase", "upper_bound": 1000.0, "lower_bound": -1000.0, "notes": {"original_bigg_ids": ["ACKr"]}, "metabolites": {"ac_c": -1.0, "actp_c": 1.0, "adp_c": 1.0, "atp_c": -1.0}, "id": "ACKr", "gene_reaction_rule": "b3115 or b2296 or b1849"}, {"subsystem": "Citric Acid Cycle", "name": "Aconitase (half-reaction A, Citrate hydro-lyase)", "upper_bound": 1000.0, "lower_bound": -1000.0, "notes": {"original_bigg_ids": ["ACONTa"]}, "metabolites": {"h2o_c": 1.0, "cit_c": -1.0, "acon_C_c": 1.0}, "id": "ACONTa", "gene_reaction_rule": "b0118 or b1276"}, {"subsystem": "Citric Acid Cycle", "name": "Aconitase (half-reaction B, Isocitrate hydro-lyase)", "upper_bound": 1000.0, "lower_bound": -1000.0, "notes": {"original_bigg_ids": ["ACONTb"]}, "metabolites": {"h2o_c": -1.0, "icit_c": 1.0, "acon_C_c": -1.0}, "id": "ACONTb", "gene_reaction_rule": "b0118 or b1276"}, {"subsystem": "Transport, Extracellular", "name": "Acetate reversible transport via proton symport", "upper_bound": 1000.0, "lower_bound": -1000.0, "notes": {"original_bigg_ids": ["ACt2r"]}, "metabolites": {"ac_c": 1.0, "h_e": -1.0, "ac_e": -1.0, "h_c": 1.0}, "id": "ACt2r", "gene_reaction_rule": ""}, {"subsystem": "Oxidative Phosphorylation", "name": "Adenylate kinase", "upper_bound": 1000.0, "lower_bound": -1000.0, "notes": {"original_bigg_ids": ["ADK1"]}, "metabolites": {"adp_c": 2.0, "atp_c": -1.0, "amp_c": -1.0}, "id": "ADK1", "gene_reaction_rule": "b0474"}, {"subsystem": "Citric Acid Cycle", "name": "2-Oxogluterate dehydrogenase", "upper_bound": 1000.0, "lower_bound": 0.0, "notes": {"original_bigg_ids": ["AKGDH"]}, "metabolites": {"akg_c": -1.0, "co2_c": 1.0, "succoa_c": 1.0, "coa_c": -1.0, "nad_c": -1.0, "nadh_c": 1.0}, "id": "AKGDH", "gene_reaction_rule": "b0116 and b0726 and b0727"}, {"subsystem": "Transport, Extracellular", "name": "2 oxoglutarate reversible transport via symport", "upper_bound": 1000.0, "lower_bound": -1000.0, "notes": {"original_bigg_ids": ["AKGt2r"]}, "metabolites": {"akg_c": 1.0, "h_e": -1.0, "h_c": 1.0, "akg_e": -1.0}, "id": "AKGt2r", "gene_reaction_rule": "b2587"}, {"subsystem": "Pyruvate Metabolism", "name": "Alcohol dehydrogenase (ethanol)", "upper_bound": 1000.0, "lower_bound": -1000.0, "notes": {"original_bigg_ids": ["ALCD2x"]}, "metabolites": {"acald_c": 1.0, "nadh_c": 1.0, "nad_c": -1.0, "etoh_c": -1.0, "h_c": 1.0}, "id": "ALCD2x", "gene_reaction_rule": "b0356 or b1478 or b1241"}, {"subsystem": "Oxidative Phosphorylation", "name": "ATP maintenance requirement", "upper_bound": 1000.0, "lower_bound": 8.39, "notes": {"original_bigg_ids": ["ATPM"]}, "metabolites": {"h2o_c": -1.0, "pi_c": 1.0, "h_c": 1.0, "atp_c": -1.0, "adp_c": 1.0}, "id": "ATPM", "gene_reaction_rule": ""}, {"subsystem": "Oxidative Phosphorylation", "name": "ATP synthase (four protons for one ATP)", "upper_bound": 1000.0, "lower_bound": -1000.0, "notes": {"original_bigg_ids": ["ATPS4r"]}, "metabolites": {"h2o_c": 1.0, "atp_c": 1.0, "h_e": -4.0, "h_c": 3.0, "adp_c": -1.0, "pi_c": -1.0}, "id": "ATPS4r", "gene_reaction_rule": "((b3736 and b3737 and b3738) and (b3731 and b3732 and b3733 and b3734 and b3735)) or ((b3736 and b3737 and b3738) and (b3731 and b3732 and b3733 and b3734 and b3735) and b3739)"}, {"subsystem": "Biomass and maintenance functions", "name": "Biomass Objective Function with GAM", "upper_bound": 1000.0, "lower_bound": 0.0, "notes": {"original_bigg_ids": ["Biomass_Ecoli_core_w_GAM"]}, "metabolites": {"h2o_c": -59.81, "glu__L_c": -4.9414, "g3p_c": -0.129, "e4p_c": -0.361, "h_c": 59.81, "r5p_c": -0.8977, "atp_c": -59.81, "f6p_c": -0.0709, "pyr_c": -2.8328, "nad_c": -3.547, "coa_c": 3.7478, "adp_c": 59.81, "g6p_c": -0.205, "nadh_c": 3.547, "akg_c": 4.1182, "accoa_c": -3.7478, "3pg_c": -1.496, "pep_c": -0.5191, "gln__L_c": -0.2557, "nadp_c": 13.0279, "nadph_c": -13.0279, "oaa_c": -1.7867, "pi_c": 59.81}, "objective_coefficient": 1.0, "id": "BIOMASS_Ecoli_core_w_GAM", "gene_reaction_rule": ""}, {"subsystem": "Transport, Extracellular", "name": "CO2 transporter via diffusion", "upper_bound": 1000.0, "lower_bound": -1000.0, "notes": {"original_bigg_ids": ["CO2t"]}, "metabolites": {"co2_c": 1.0, "co2_e": -1.0}, "id": "CO2t", "gene_reaction_rule": "s0001"}, {"subsystem": "Citric Acid Cycle", "name": "Citrate synthase", "upper_bound": 1000.0, "lower_bound": 0.0, "notes": {"original_bigg_ids": ["CS"]}, "metabolites": {"h2o_c": -1.0, "oaa_c": -1.0, "cit_c": 1.0, "coa_c": 1.0, "h_c": 1.0, "accoa_c": -1.0}, "id": "CS", "gene_reaction_rule": "b0720"}, {"subsystem": "Oxidative Phosphorylation", "name": "Cytochrome oxidase bd (ubiquinol-8: 2 protons)", "upper_bound": 1000.0, "lower_bound": 0.0, "notes": {"original_bigg_ids": ["CYTBD"]}, "metabolites": {"o2_c": -0.5, "h2o_c": 1.0, "q8h2_c": -1.0, "h_e": 2.0, "h_c": -2.0, "q8_c": 1.0}, "id": "CYTBD", "gene_reaction_rule": "(b0978 and b0979) or (b0733 and b0734)"}, {"subsystem": "Transport, Extracellular", "name": "D lactate transport via proton symport", "upper_bound": 1000.0, "lower_bound": -1000.0, "notes": {"original_bigg_ids": ["D_LACt2"]}, "metabolites": {"lac__D_e": -1.0, "h_e": -1.0, "lac__D_c": 1.0, "h_c": 1.0}, "id": "D_LACt2", "gene_reaction_rule": "b2975 or b3603"}, {"subsystem": "Glycolysis/Gluconeogenesis", "name": "Enolase", "upper_bound": 1000.0, "lower_bound": -1000.0, "notes": {"original_bigg_ids": ["ENO"]}, "metabolites": {"pep_c": 1.0, "h2o_c": 1.0, "2pg_c": -1.0}, "id": "ENO", "gene_reaction_rule": "b2779"}, {"subsystem": "Transport, Extracellular", "name": "ETOHt2r", "upper_bound": 1000.0, "lower_bound": -1000.0, "notes": {"original_bigg_ids": ["ETOHt2r"]}, "metabolites": {"etoh_e": -1.0, "h_e": -1.0, "h_c": 1.0, "etoh_c": 1.0}, "id": "ETOHt2r", "gene_reaction_rule": ""}, {"subsystem": "Exchange", "name": "Acetate exchange", "upper_bound": 1000.0, "lower_bound": 0.0, "notes": {"original_bigg_ids": ["EX_ac_e"]}, "annotation": {"SBO": "SBO:0000627"}, "metabolites": {"ac_e": -1.0}, "id": "EX_ac_e", "gene_reaction_rule": ""}, {"subsystem": "Exchange", "name": "Acetaldehyde exchange", "upper_bound": 1000.0, "lower_bound": 0.0, "notes": {"original_bigg_ids": ["EX_acald_e"]}, "annotation": {"SBO": "SBO:0000627"}, "metabolites": {"acald_e": -1.0}, "id": "EX_acald_e", "gene_reaction_rule": ""}, {"subsystem": "Exchange", "name": "2-Oxoglutarate exchange", "upper_bound": 1000.0, "lower_bound": 0.0, "notes": {"original_bigg_ids": ["EX_akg_e"]}, "annotation": {"SBO": "SBO:0000627"}, "metabolites": {"akg_e": -1.0}, "id": "EX_akg_e", "gene_reaction_rule": ""}, {"subsystem": "Exchange", "name": "CO2 exchange", "upper_bound": 1000.0, "lower_bound": -1000.0, "notes": {"original_bigg_ids": ["EX_co2_e"]}, "annotation": {"SBO": "SBO:0000627"}, "metabolites": {"co2_e": -1.0}, "id": "EX_co2_e", "gene_reaction_rule": ""}, {"subsystem": "Exchange", "name": "Ethanol exchange", "upper_bound": 1000.0, "lower_bound": 0.0, "notes": {"original_bigg_ids": ["EX_etoh_e"]}, "annotation": {"SBO": "SBO:0000627"}, "metabolites": {"etoh_e": -1.0}, "id": "EX_etoh_e", "gene_reaction_rule": ""}, {"subsystem": "Exchange", "name": "Formate exchange", "upper_bound": 1000.0, "lower_bound": 0.0, "notes": {"original_bigg_ids": ["EX_for_e"]}, "annotation": {"SBO": "SBO:0000627"}, "metabolites": {"for_e": -1.0}, "id": "EX_for_e", "gene_reaction_rule": ""}, {"subsystem": "Exchange", "name": "D-Fructose exchange", "upper_bound": 1000.0, "lower_bound": 0.0, "notes": {"original_bigg_ids": ["EX_fru_e"]}, "annotation": {"SBO": "SBO:0000627"}, "metabolites": {"fru_e": -1.0}, "id": "EX_fru_e", "gene_reaction_rule": ""}, {"subsystem": "Exchange", "name": "Fumarate exchange", "upper_bound": 1000.0, "lower_bound": 0.0, "notes": {"original_bigg_ids": ["EX_fum_e"]}, "annotation": {"SBO": "SBO:0000627"}, "metabolites": {"fum_e": -1.0}, "id": "EX_fum_e", "gene_reaction_rule": ""}, {"subsystem": "Extracellular exchange", "name": "D-Glucose exchange", "upper_bound": 1000.0, "lower_bound": -10.0, "notes": {"original_bigg_ids": ["EX_glc_e"]}, "annotation": {"SBO": "SBO:0000627"}, "metabolites": {"glc__D_e": -1.0}, "id": "EX_glc__D_e", "gene_reaction_rule": ""}, {"subsystem": "Exchange", "name": "L-Glutamine exchange", "upper_bound": 1000.0, "lower_bound": 0.0, "notes": {"original_bigg_ids": ["EX_gln_L_e"]}, "annotation": {"SBO": "SBO:0000627"}, "metabolites": {"gln__L_e": -1.0}, "id": "EX_gln__L_e", "gene_reaction_rule": ""}, {"subsystem": "Exchange", "name": "L-Glutamate exchange", "upper_bound": 1000.0, "lower_bound": 0.0, "notes": {"original_bigg_ids": ["EX_glu_L_e"]}, "annotation": {"SBO": "SBO:0000627"}, "metabolites": {"glu__L_e": -1.0}, "id": "EX_glu__L_e", "gene_reaction_rule": ""}, {"subsystem": "Exchange", "name": "H+ exchange", "upper_bound": 1000.0, "lower_bound": -1000.0, "notes": {"original_bigg_ids": ["EX_h_e"]}, "annotation": {"SBO": "SBO:0000627"}, "metabolites": {"h_e": -1.0}, "id": "EX_h_e", "gene_reaction_rule": ""}, {"subsystem": "Exchange", "name": "H2O exchange", "upper_bound": 1000.0, "lower_bound": -1000.0, "notes": {"original_bigg_ids": ["EX_h2o_e"]}, "annotation": {"SBO": "SBO:0000627"}, "metabolites": {"h2o_e": -1.0}, "id": "EX_h2o_e", "gene_reaction_rule": ""}, {"subsystem": "Exchange", "name": "D-lactate exchange", "upper_bound": 1000.0, "lower_bound": 0.0, "notes": {"original_bigg_ids": ["EX_lac_D_e"]}, "annotation": {"SBO": "SBO:0000627"}, "metabolites": {"lac__D_e": -1.0}, "id": "EX_lac__D_e", "gene_reaction_rule": ""}, {"subsystem": "Exchange", "name": "L-Malate exchange", "upper_bound": 1000.0, "lower_bound": 0.0, "notes": {"original_bigg_ids": ["EX_mal_L_e"]}, "annotation": {"SBO": "SBO:0000627"}, "metabolites": {"mal__L_e": -1.0}, "id": "EX_mal__L_e", "gene_reaction_rule": ""}, {"subsystem": "Exchange", "name": "Ammonia exchange", "upper_bound": 1000.0, "lower_bound": -1000.0, "notes": {"original_bigg_ids": ["EX_nh4_e"]}, "annotation": {"SBO": "SBO:0000627"}, "metabolites": {"nh4_e": -1.0}, "id": "EX_nh4_e", "gene_reaction_rule": ""}, {"subsystem": "Exchange", "name": "O2 exchange", "upper_bound": 1000.0, "lower_bound": -1000.0, "notes": {"original_bigg_ids": ["EX_o2_e"]}, "annotation": {"SBO": "SBO:0000627"}, "metabolites": {"o2_e": -1.0}, "id": "EX_o2_e", "gene_reaction_rule": ""}, {"subsystem": "Exchange", "name": "Phosphate exchange", "upper_bound": 1000.0, "lower_bound": -1000.0, "notes": {"original_bigg_ids": ["EX_pi_e"]}, "annotation": {"SBO": "SBO:0000627"}, "metabolites": {"pi_e": -1.0}, "id": "EX_pi_e", "gene_reaction_rule": ""}, {"subsystem": "Exchange", "name": "Pyruvate exchange", "upper_bound": 1000.0, "lower_bound": 0.0, "notes": {"original_bigg_ids": ["EX_pyr_e"]}, "annotation": {"SBO": "SBO:0000627"}, "metabolites": {"pyr_e": -1.0}, "id": "EX_pyr_e", "gene_reaction_rule": ""}, {"subsystem": "Exchange", "name": "Succinate exchange", "upper_bound": 1000.0, "lower_bound": 0.0, "notes": {"original_bigg_ids": ["EX_succ_e"]}, "annotation": {"SBO": "SBO:0000627"}, "metabolites": {"succ_e": -1.0}, "id": "EX_succ_e", "gene_reaction_rule": ""}, {"subsystem": "Glycolysis/Gluconeogenesis", "name": "Fructose-bisphosphate aldolase", "upper_bound": 1000.0, "lower_bound": -1000.0, "notes": {"original_bigg_ids": ["FBA"]}, "metabolites": {"dhap_c": 1.0, "g3p_c": 1.0, "fdp_c": -1.0}, "id": "FBA", "gene_reaction_rule": "b2097 or b1773 or b2925"}, {"subsystem": "Glycolysis/Gluconeogenesis", "name": "Fructose-bisphosphatase", "upper_bound": 1000.0, "lower_bound": 0.0, "notes": {"original_bigg_ids": ["FBP"]}, "metabolites": {"h2o_c": -1.0, "pi_c": 1.0, "fdp_c": -1.0, "f6p_c": 1.0}, "id": "FBP", "gene_reaction_rule": "b3925 or b4232"}, {"subsystem": "Transport, Extracellular", "name": "Formate transport in via proton symport", "upper_bound": 1000.0, "lower_bound": 0.0, "notes": {"original_bigg_ids": ["FORt2"]}, "metabolites": {"for_e": -1.0, "h_e": -1.0, "h_c": 1.0, "for_c": 1.0}, "id": "FORt2", "gene_reaction_rule": "b0904 or b2492"}, {"subsystem": "Transport, Extracellular", "name": "Formate transport via diffusion", "upper_bound": 1000.0, "lower_bound": 0.0, "notes": {"original_bigg_ids": ["FORti"]}, "metabolites": {"for_e": 1.0, "for_c": -1.0}, "id": "FORti", "gene_reaction_rule": "b0904 or b2492"}, {"subsystem": "Oxidative Phosphorylation", "name": "Fumarate reductase", "upper_bound": 1000.0, "lower_bound": 0.0, "notes": {"original_bigg_ids": ["FRD7"]}, "metabolites": {"q8h2_c": -1.0, "succ_c": 1.0, "fum_c": -1.0, "q8_c": 1.0}, "id": "FRD7", "gene_reaction_rule": "b4151 and b4152 and b4153 and b4154"}, {"subsystem": "Transport, Extracellular", "name": "Fructose transport via PEP:Pyr PTS (f6p generating)", "upper_bound": 1000.0, "lower_bound": 0.0, "notes": {"original_bigg_ids": ["FRUpts2"]}, "metabolites": {"fru_e": -1.0, "pep_c": -1.0, "pyr_c": 1.0, "f6p_c": 1.0}, "id": "FRUpts2", "gene_reaction_rule": "b1817 and b1818 and b1819 and b2415 and b2416"}, {"subsystem": "Citric Acid Cycle", "name": "Fumarase", "upper_bound": 1000.0, "lower_bound": -1000.0, "notes": {"original_bigg_ids": ["FUM"]}, "metabolites": {"mal__L_c": 1.0, "fum_c": -1.0, "h2o_c": -1.0}, "id": "FUM", "gene_reaction_rule": "b1612 or b4122 or b1611"}, {"subsystem": "Transport, Extracellular", "name": "Fumarate transport via proton symport (2 H)", "upper_bound": 1000.0, "lower_bound": 0.0, "notes": {"original_bigg_ids": ["FUMt2_2"]}, "metabolites": {"fum_c": 1.0, "h_e": -2.0, "fum_e": -1.0, "h_c": 2.0}, "id": "FUMt2_2", "gene_reaction_rule": "b3528"}, {"subsystem": "Pentose Phosphate Pathway", "name": "Glucose 6-phosphate dehydrogenase", "upper_bound": 1000.0, "lower_bound": -1000.0, "notes": {"original_bigg_ids": ["G6PDH2r"]}, "metabolites": {"g6p_c": -1.0, "6pgl_c": 1.0, "nadph_c": 1.0, "h_c": 1.0, "nadp_c": -1.0}, "id": "G6PDH2r", "gene_reaction_rule": "b1852"}, {"subsystem": "Glycolysis/Gluconeogenesis", "name": "Glyceraldehyde-3-phosphate dehydrogenase", "upper_bound": 1000.0, "lower_bound": -1000.0, "notes": {"original_bigg_ids": ["GAPD"]}, "metabolites": {"nadh_c": 1.0, "13dpg_c": 1.0, "g3p_c": -1.0, "h_c": 1.0, "nad_c": -1.0, "pi_c": -1.0}, "id": "GAPD", "gene_reaction_rule": "b1779"}, {"subsystem": "Transport, Extracellular", "name": "D-glucose transport via PEP:Pyr PTS", "upper_bound": 1000.0, "lower_bound": 0.0, "notes": {"original_bigg_ids": ["GLCpts"]}, "metabolites": {"pep_c": -1.0, "g6p_c": 1.0, "pyr_c": 1.0, "glc__D_e": -1.0}, "id": "GLCpts", "gene_reaction_rule": "(b2417 and b1101 and b2415 and b2416) or (b1817 and b1818 and b1819 and b2415 and b2416) or (b2417 and b1621 and b2415 and b2416)"}, {"subsystem": "Glutamate Metabolism", "name": "Glutamine synthetase", "upper_bound": 1000.0, "lower_bound": 0.0, "notes": {"original_bigg_ids": ["GLNS"]}, "metabolites": {"atp_c": -1.0, "glu__L_c": -1.0, "h_c": 1.0, "adp_c": 1.0, "pi_c": 1.0, "nh4_c": -1.0, "gln__L_c": 1.0}, "id": "GLNS", "gene_reaction_rule": "b3870 or b1297"}, {"subsystem": "Transport, Extracellular", "name": "GLNabc", "upper_bound": 1000.0, "lower_bound": 0.0, "notes": {"original_bigg_ids": ["GLNabc"]}, "metabolites": {"h2o_c": -1.0, "atp_c": -1.0, "h_c": 1.0, "adp_c": 1.0, "gln__L_e": -1.0, "pi_c": 1.0, "gln__L_c": 1.0}, "id": "GLNabc", "gene_reaction_rule": "b0811 and b0810 and b0809"}, {"subsystem": "Glutamate Metabolism", "name": "Glutamate dehydrogenase (NADP)", "upper_bound": 1000.0, "lower_bound": -1000.0, "notes": {"original_bigg_ids": ["GLUDy"]}, "metabolites": {"akg_c": 1.0, "h2o_c": -1.0, "nadp_c": -1.0, "nadph_c": 1.0, "glu__L_c": -1.0, "h_c": 1.0, "nh4_c": 1.0}, "id": "GLUDy", "gene_reaction_rule": "b1761"}, {"subsystem": "Glutamate Metabolism", "name": "Glutaminase", "upper_bound": 1000.0, "lower_bound": 0.0, "notes": {"original_bigg_ids": ["GLUN"]}, "metabolites": {"glu__L_c": 1.0, "gln__L_c": -1.0, "h2o_c": -1.0, "nh4_c": 1.0}, "id": "GLUN", "gene_reaction_rule": "b1812 or b0485 or b1524"}, {"subsystem": "Glutamate Metabolism", "name": "Glutamate synthase (NADPH)", "upper_bound": 1000.0, "lower_bound": 0.0, "notes": {"original_bigg_ids": ["GLUSy"]}, "metabolites": {"akg_c": -1.0, "nadp_c": 1.0, "nadph_c": -1.0, "glu__L_c": 2.0, "h_c": -1.0, "gln__L_c": -1.0}, "id": "GLUSy", "gene_reaction_rule": "b3212 and b3213"}, {"subsystem": "Transport, Extracellular", "name": "L glutamate transport via proton symport reversible", "upper_bound": 1000.0, "lower_bound": -1000.0, "notes": {"original_bigg_ids": ["GLUt2r"]}, "metabolites": {"glu__L_c": 1.0, "h_e": -1.0, "glu__L_e": -1.0, "h_c": 1.0}, "id": "GLUt2r", "gene_reaction_rule": "b4077"}, {"subsystem": "Pentose Phosphate Pathway", "name": "Phosphogluconate dehydrogenase", "upper_bound": 1000.0, "lower_bound": 0.0, "notes": {"original_bigg_ids": ["GND"]}, "metabolites": {"6pgc_c": -1.0, "co2_c": 1.0, "nadp_c": -1.0, "nadph_c": 1.0, "ru5p__D_c": 1.0}, "id": "GND", "gene_reaction_rule": "b2029"}, {"subsystem": "Transport, Extracellular", "name": "H2O transport via diffusion", "upper_bound": 1000.0, "lower_bound": -1000.0, "notes": {"original_bigg_ids": ["H2Ot"]}, "metabolites": {"h2o_c": 1.0, "h2o_e": -1.0}, "id": "H2Ot", "gene_reaction_rule": "b0875 or s0001"}, {"subsystem": "Citric Acid Cycle", "name": "Isocitrate dehydrogenase (NADP)", "upper_bound": 1000.0, "lower_bound": -1000.0, "notes": {"original_bigg_ids": ["ICDHyr"]}, "metabolites": {"akg_c": 1.0, "icit_c": -1.0, "co2_c": 1.0, "nadph_c": 1.0, "nadp_c": -1.0}, "id": "ICDHyr", "gene_reaction_rule": "b1136"}, {"subsystem": "Anaplerotic reactions", "name": "Isocitrate lyase", "upper_bound": 1000.0, "lower_bound": 0.0, "notes": {"original_bigg_ids": ["ICL"]}, "metabolites": {"succ_c": 1.0, "icit_c": -1.0, "glx_c": 1.0}, "id": "ICL", "gene_reaction_rule": "b4015"}, {"subsystem": "Pyruvate Metabolism", "name": "D-lactate dehydrogenase", "upper_bound": 1000.0, "lower_bound": -1000.0, "notes": {"original_bigg_ids": ["LDH_D"]}, "metabolites": {"pyr_c": 1.0, "nadh_c": 1.0, "lac__D_c": -1.0, "h_c": 1.0, "nad_c": -1.0}, "id": "LDH_D", "gene_reaction_rule": "b2133 or b1380"}, {"subsystem": "Anaplerotic reactions", "name": "Malate synthase", "upper_bound": 1000.0, "lower_bound": 0.0, "notes": {"original_bigg_ids": ["MALS"]}, "metabolites": {"h2o_c": -1.0, "mal__L_c": 1.0, "coa_c": 1.0, "h_c": 1.0, "accoa_c": -1.0, "glx_c": -1.0}, "id": "MALS", "gene_reaction_rule": "b4014 or b2976"}, {"subsystem": "Transport, Extracellular", "name": "Malate transport via proton symport (2 H)", "upper_bound": 1000.0, "lower_bound": 0.0, "notes": {"original_bigg_ids": ["MALt2_2"]}, "metabolites": {"mal__L_c": 1.0, "h_e": -2.0, "mal__L_e": -1.0, "h_c": 2.0}, "id": "MALt2_2", "gene_reaction_rule": "b3528"}, {"subsystem": "Citric Acid Cycle", "name": "Malate dehydrogenase", "upper_bound": 1000.0, "lower_bound": -1000.0, "notes": {"original_bigg_ids": ["MDH"]}, "metabolites": {"mal__L_c": -1.0, "nad_c": -1.0, "nadh_c": 1.0, "h_c": 1.0, "oaa_c": 1.0}, "id": "MDH", "gene_reaction_rule": "b3236"}, {"subsystem": "Anaplerotic reactions", "name": "Malic enzyme (NAD)", "upper_bound": 1000.0, "lower_bound": 0.0, "notes": {"original_bigg_ids": ["ME1"]}, "metabolites": {"mal__L_c": -1.0, "nadh_c": 1.0, "co2_c": 1.0, "pyr_c": 1.0, "nad_c": -1.0}, "id": "ME1", "gene_reaction_rule": "b1479"}, {"subsystem": "Anaplerotic reactions", "name": "Malic enzyme (NADP)", "upper_bound": 1000.0, "lower_bound": 0.0, "notes": {"original_bigg_ids": ["ME2"]}, "metabolites": {"pyr_c": 1.0, "co2_c": 1.0, "nadph_c": 1.0, "nadp_c": -1.0, "mal__L_c": -1.0}, "id": "ME2", "gene_reaction_rule": "b2463"}, {"subsystem": "Oxidative Phosphorylation", "name": "NADH dehydrogenase (ubiquinone-8 & 3 protons)", "upper_bound": 1000.0, "lower_bound": 0.0, "notes": {"original_bigg_ids": ["NADH16"]}, "metabolites": {"q8h2_c": 1.0, "h_e": 3.0, "h_c": -4.0, "nad_c": 1.0, "nadh_c": -1.0, "q8_c": -1.0}, "id": "NADH16", "gene_reaction_rule": "b2276 and b2277 and b2278 and b2279 and b2280 and b2281 and b2282 and b2283 and b2284 and b2285 and b2286 and b2287 and b2288"}, {"subsystem": "Oxidative Phosphorylation", "name": "NAD transhydrogenase", "upper_bound": 1000.0, "lower_bound": 0.0, "notes": {"original_bigg_ids": ["NADTRHD"]}, "metabolites": {"nadh_c": 1.0, "nadph_c": -1.0, "nadp_c": 1.0, "nad_c": -1.0}, "id": "NADTRHD", "gene_reaction_rule": "b3962 or (b1602 and b1603)"}, {"subsystem": "Inorganic Ion Transport and Metabolism", "name": "Ammonia reversible transport", "upper_bound": 1000.0, "lower_bound": -1000.0, "notes": {"original_bigg_ids": ["NH4t"]}, "metabolites": {"nh4_e": -1.0, "nh4_c": 1.0}, "id": "NH4t", "gene_reaction_rule": "s0001 or b0451"}, {"subsystem": "Transport, Extracellular", "name": "O2 transport diffusion ", "upper_bound": 1000.0, "lower_bound": -1000.0, "notes": {"original_bigg_ids": ["O2t"]}, "metabolites": {"o2_c": 1.0, "o2_e": -1.0}, "id": "O2t", "gene_reaction_rule": "s0001"}, {"subsystem": "Glycolysis/Gluconeogenesis", "name": "Pyruvate dehydrogenase", "upper_bound": 1000.0, "lower_bound": 0.0, "notes": {"original_bigg_ids": ["PDH"]}, "metabolites": {"co2_c": 1.0, "nad_c": -1.0, "pyr_c": -1.0, "coa_c": -1.0, "accoa_c": 1.0, "nadh_c": 1.0}, "id": "PDH", "gene_reaction_rule": "b0114 and b0115 and b0116"}, {"subsystem": "Glycolysis/Gluconeogenesis", "name": "Phosphofructokinase", "upper_bound": 1000.0, "lower_bound": 0.0, "notes": {"original_bigg_ids": ["PFK"]}, "metabolites": {"adp_c": 1.0, "h_c": 1.0, "fdp_c": 1.0, "atp_c": -1.0, "f6p_c": -1.0}, "id": "PFK", "gene_reaction_rule": "b3916 or b1723"}, {"subsystem": "Pyruvate Metabolism", "name": "Pyruvate formate lyase", "upper_bound": 1000.0, "lower_bound": 0.0, "notes": {"original_bigg_ids": ["PFL"]}, "metabolites": {"pyr_c": -1.0, "coa_c": -1.0, "accoa_c": 1.0, "for_c": 1.0}, "id": "PFL", "gene_reaction_rule": "((b0902 and b0903) and b2579) or (b0902 and b0903) or (b0902 and b3114) or (b3951 and b3952)"}, {"subsystem": "Glycolysis/Gluconeogenesis", "name": "Glucose-6-phosphate isomerase", "upper_bound": 1000.0, "lower_bound": -1000.0, "notes": {"original_bigg_ids": ["PGI"]}, "metabolites": {"g6p_c": -1.0, "f6p_c": 1.0}, "id": "PGI", "gene_reaction_rule": "b4025"}, {"subsystem": "Glycolysis/Gluconeogenesis", "name": "Phosphoglycerate kinase", "upper_bound": 1000.0, "lower_bound": -1000.0, "notes": {"original_bigg_ids": ["PGK"]}, "metabolites": {"3pg_c": -1.0, "atp_c": -1.0, "13dpg_c": 1.0, "adp_c": 1.0}, "id": "PGK", "gene_reaction_rule": "b2926"}, {"subsystem": "Pentose Phosphate Pathway", "name": "6-phosphogluconolactonase", "upper_bound": 1000.0, "lower_bound": 0.0, "notes": {"original_bigg_ids": ["PGL"]}, "metabolites": {"6pgc_c": 1.0, "h2o_c": -1.0, "6pgl_c": -1.0, "h_c": 1.0}, "id": "PGL", "gene_reaction_rule": "b0767"}, {"subsystem": "Glycolysis/Gluconeogenesis", "name": "Phosphoglycerate mutase", "upper_bound": 1000.0, "lower_bound": -1000.0, "notes": {"original_bigg_ids": ["PGM"]}, "metabolites": {"2pg_c": -1.0, "3pg_c": 1.0}, "id": "PGM", "gene_reaction_rule": "b3612 or b4395 or b0755"}, {"subsystem": "Inorganic Ion Transport and Metabolism", "name": "Phosphate reversible transport via symport", "upper_bound": 1000.0, "lower_bound": -1000.0, "notes": {"original_bigg_ids": ["PIt2r"]}, "metabolites": {"pi_c": 1.0, "h_e": -1.0, "pi_e": -1.0, "h_c": 1.0}, "id": "PIt2r", "gene_reaction_rule": "b2987 or b3493"}, {"subsystem": "Anaplerotic reactions", "name": "Phosphoenolpyruvate carboxylase", "upper_bound": 1000.0, "lower_bound": 0.0, "notes": {"original_bigg_ids": ["PPC"]}, "metabolites": {"h2o_c": -1.0, "pi_c": 1.0, "pep_c": -1.0, "h_c": 1.0, "oaa_c": 1.0, "co2_c": -1.0}, "id": "PPC", "gene_reaction_rule": "b3956"}, {"subsystem": "Anaplerotic reactions", "name": "Phosphoenolpyruvate carboxykinase", "upper_bound": 1000.0, "lower_bound": 0.0, "notes": {"original_bigg_ids": ["PPCK"]}, "metabolites": {"pep_c": 1.0, "co2_c": 1.0, "atp_c": -1.0, "oaa_c": -1.0, "adp_c": 1.0}, "id": "PPCK", "gene_reaction_rule": "b3403"}, {"subsystem": "Glycolysis/Gluconeogenesis", "name": "Phosphoenolpyruvate synthase", "upper_bound": 1000.0, "lower_bound": 0.0, "notes": {"original_bigg_ids": ["PPS"]}, "metabolites": {"h2o_c": -1.0, "atp_c": -1.0, "pyr_c": -1.0, "pep_c": 1.0, "h_c": 2.0, "pi_c": 1.0, "amp_c": 1.0}, "id": "PPS", "gene_reaction_rule": "b1702"}, {"subsystem": "Pyruvate Metabolism", "name": "Phosphotransacetylase", "upper_bound": 1000.0, "lower_bound": -1000.0, "notes": {"original_bigg_ids": ["PTAr"]}, "metabolites": {"actp_c": 1.0, "pi_c": -1.0, "coa_c": 1.0, "accoa_c": -1.0}, "id": "PTAr", "gene_reaction_rule": "b2297 or b2458"}, {"subsystem": "Glycolysis/Gluconeogenesis", "name": "Pyruvate kinase", "upper_bound": 1000.0, "lower_bound": 0.0, "notes": {"original_bigg_ids": ["PYK"]}, "metabolites": {"pep_c": -1.0, "pyr_c": 1.0, "h_c": -1.0, "adp_c": -1.0, "atp_c": 1.0}, "id": "PYK", "gene_reaction_rule": "b1854 or b1676"}, {"subsystem": "Transport, Extracellular", "name": "Pyruvate transport in via proton symport", "upper_bound": 1000.0, "lower_bound": -1000.0, "notes": {"original_bigg_ids": ["PYRt2r"]}, "metabolites": {"pyr_c": 1.0, "h_e": -1.0, "pyr_e": -1.0, "h_c": 1.0}, "id": "PYRt2", "gene_reaction_rule": ""}, {"subsystem": "Pentose Phosphate Pathway", "name": "Ribulose 5-phosphate 3-epimerase", "upper_bound": 1000.0, "lower_bound": -1000.0, "notes": {"original_bigg_ids": ["RPE"]}, "metabolites": {"xu5p__D_c": 1.0, "ru5p__D_c": -1.0}, "id": "RPE", "gene_reaction_rule": "b3386 or b4301"}, {"subsystem": "Pentose Phosphate Pathway", "name": "Ribose-5-phosphate isomerase", "upper_bound": 1000.0, "lower_bound": -1000.0, "notes": {"original_bigg_ids": ["RPI"]}, "metabolites": {"r5p_c": -1.0, "ru5p__D_c": 1.0}, "id": "RPI", "gene_reaction_rule": "b2914 or b4090"}, {"subsystem": "Transport, Extracellular", "name": "Succinate transport via proton symport (2 H)", "upper_bound": 1000.0, "lower_bound": 0.0, "notes": {"original_bigg_ids": ["SUCCt2_2"]}, "metabolites": {"succ_c": 1.0, "h_e": -2.0, "succ_e": -1.0, "h_c": 2.0}, "id": "SUCCt2_2", "gene_reaction_rule": "b3528"}, {"subsystem": "Transport, Extracellular", "name": "Succinate transport out via proton antiport", "upper_bound": 1000.0, "lower_bound": 0.0, "notes": {"original_bigg_ids": ["SUCCt3"]}, "metabolites": {"succ_c": -1.0, "h_e": -1.0, "succ_e": 1.0, "h_c": 1.0}, "id": "SUCCt3", "gene_reaction_rule": ""}, {"subsystem": "Oxidative Phosphorylation", "name": "Succinate dehydrogenase (irreversible)", "upper_bound": 1000.0, "lower_bound": 0.0, "notes": {"original_bigg_ids": ["SUCDi"]}, "metabolites": {"q8h2_c": 1.0, "succ_c": -1.0, "fum_c": 1.0, "q8_c": -1.0}, "id": "SUCDi", "gene_reaction_rule": "b0721 and b0722 and b0723 and b0724"}, {"subsystem": "Citric Acid Cycle", "name": "Succinyl-CoA synthetase (ADP-forming)", "upper_bound": 1000.0, "lower_bound": -1000.0, "notes": {"original_bigg_ids": ["SUCOAS"]}, "metabolites": {"atp_c": -1.0, "succoa_c": 1.0, "succ_c": -1.0, "coa_c": -1.0, "adp_c": 1.0, "pi_c": 1.0}, "id": "SUCOAS", "gene_reaction_rule": "b0728 and b0729"}, {"subsystem": "Pentose Phosphate Pathway", "name": "Transaldolase", "upper_bound": 1000.0, "lower_bound": -1000.0, "notes": {"original_bigg_ids": ["TALA"]}, "metabolites": {"s7p_c": -1.0, "e4p_c": 1.0, "g3p_c": -1.0, "f6p_c": 1.0}, "id": "TALA", "gene_reaction_rule": "b2464 or b0008"}, {"subsystem": "Oxidative Phosphorylation", "name": "NAD(P) transhydrogenase", "upper_bound": 1000.0, "lower_bound": 0.0, "notes": {"original_bigg_ids": ["THD2"]}, "metabolites": {"nadp_c": -1.0, "nadph_c": 1.0, "h_e": -2.0, "h_c": 2.0, "nad_c": 1.0, "nadh_c": -1.0}, "id": "THD2", "gene_reaction_rule": "b1602 and b1603"}, {"subsystem": "Pentose Phosphate Pathway", "name": "Transketolase", "upper_bound": 1000.0, "lower_bound": -1000.0, "notes": {"original_bigg_ids": ["TKT1"]}, "metabolites": {"r5p_c": -1.0, "xu5p__D_c": -1.0, "g3p_c": 1.0, "s7p_c": 1.0}, "id": "TKT1", "gene_reaction_rule": "b2935 or b2465"}, {"subsystem": "Pentose Phosphate Pathway", "name": "Transketolase", "upper_bound": 1000.0, "lower_bound": -1000.0, "notes": {"original_bigg_ids": ["TKT2"]}, "metabolites": {"e4p_c": -1.0, "xu5p__D_c": -1.0, "g3p_c": 1.0, "f6p_c": 1.0}, "id": "TKT2", "gene_reaction_rule": "b2935 or b2465"}, {"subsystem": "Glycolysis/Gluconeogenesis", "name": "Triose-phosphate isomerase", "upper_bound": 1000.0, "lower_bound": -1000.0, "notes": {"original_bigg_ids": ["TPI"]}, "metabolites": {"dhap_c": -1.0, "g3p_c": 1.0}, "id": "TPI", "gene_reaction_rule": "b3919"}], "genes": [{"notes": {"original_bigg_ids": ["GMHEPK", "dmso_c", "b0351"]}, "name": "mhpF", "id": "b0351"}, {"notes": {"original_bigg_ids": ["GMHEPPA", "2dmmq8_c", "b1241"]}, "name": "adhE", "id": "b1241"}, {"notes": {"original_bigg_ids": ["GMPR", "dmso_p", "s0001"]}, "name": "None", "id": "s0001"}, {"notes": {"original_bigg_ids": ["GMPS2", "dms_p", "b3115"]}, "name": "tdcD", "id": "b3115"}, {"notes": {"original_bigg_ids": ["GMPtex", "dhpmp_c", "b1849"]}, "name": "purT", "id": "b1849"}, {"notes": {"original_bigg_ids": ["GND", "23doguln_c", "b2296"]}, "name": "ackA", "id": "b2296"}, {"notes": {"original_bigg_ids": ["GNK", "dopa_p", "b0118"]}, "name": "acnB", "id": "b0118"}, {"notes": {"original_bigg_ids": ["GP4GH", "doxrbcn_p", "b1276"]}, "name": "acnA", "id": "b1276"}, {"notes": {"original_bigg_ids": ["GPDDA1", "dpcoa_c", "b0474"]}, "name": "adk", "id": "b0474"}, {"notes": {"original_bigg_ids": ["GPDDA1pp", "pant_DASH_R_c", "b0116"]}, "name": "lpd", "id": "b0116"}, {"notes": {"original_bigg_ids": ["GPDDA2", "2dhp_c", "b0727"]}, "name": "sucB", "id": "b0727"}, {"notes": {"original_bigg_ids": ["GPDDA2pp", "2dr5p_c", "b0726"]}, "name": "sucA", "id": "b0726"}, {"notes": {"original_bigg_ids": ["GPDDA3", "dsbaox_p", "b2587"]}, "name": "kgtP", "id": "b2587"}, {"notes": {"original_bigg_ids": ["GPDDA3pp", "dsbard_p", "b0356"]}, "name": "frmA", "id": "b0356"}, {"notes": {"original_bigg_ids": ["GPDDA4", "gthrd_p", "b1478"]}, "name": "adhP", "id": "b1478"}, {"notes": {"original_bigg_ids": ["GPDDA4pp", "dsbcox_p", "b3733"]}, "name": "atpG", "id": "b3733"}, {"notes": {"original_bigg_ids": ["GPDDA5", "gthox_p", "b3739"]}, "name": "atpI", "id": "b3739"}, {"notes": {"original_bigg_ids": ["GPDDA5pp", "dsbcrd_p", "b3735"]}, "name": "atpH", "id": "b3735"}, {"notes": {"original_bigg_ids": ["GRTT", "dsbdrd_c", "b3734"]}, "name": "atpA", "id": "b3734"}, {"notes": {"original_bigg_ids": ["GRXR", "trdrd_c", "b3738"]}, "name": "atpB", "id": "b3738"}, {"notes": {"original_bigg_ids": ["GSNK", "trdox_c", "b3736"]}, "name": "atpF", "id": "b3736"}, {"notes": {"original_bigg_ids": ["GSNt2pp", "dsbdox_c", "b3732"]}, "name": "atpD", "id": "b3732"}, {"notes": {"original_bigg_ids": ["GSNtex", "dsbgrd_p", "b3737"]}, "name": "atpE", "id": "b3737"}, {"notes": {"original_bigg_ids": ["GSPMDA", "dsbgox_p", "b3731"]}, "name": "atpC", "id": "b3731"}, {"notes": {"original_bigg_ids": ["GSPMDS", "2amsa_c", "b0720"]}, "name": "gltA", "id": "b0720"}, {"notes": {"original_bigg_ids": ["GTHOXtex", "ser_DASH_D_c", "b0734"]}, "name": "cydB", "id": "b0734"}, {"notes": {"original_bigg_ids": ["GTHOr", "ser_DASH_D_p", "b0978"]}, "name": "cbdA", "id": "b0978"}, {"notes": {"original_bigg_ids": ["GTHPi", "dtmp_c", "b0733"]}, "name": "cydA", "id": "b0733"}, {"notes": {"original_bigg_ids": ["GTHRDHpp", "dtmp_p", "b0979"]}, "name": "cbdB", "id": "b0979"}, {"notes": {"original_bigg_ids": ["GTHRDabc2pp", "dump_p", "b2975"]}, "name": "glcA", "id": "b2975"}, {"notes": {"original_bigg_ids": ["GTHRDabcpp", "56dura_c", "b3603"]}, "name": "lldP", "id": "b3603"}, {"notes": {"original_bigg_ids": ["GTHRDtex", "dump_c", "b2779"]}, "name": "eno", "id": "b2779"}, {"notes": {"original_bigg_ids": ["GTHS", "2dr1p_c", "b2097"]}, "name": "fbaB", "id": "b2097"}, {"notes": {"original_bigg_ids": ["GTPCI", "duri_p", "b2925"]}, "name": "fbaA", "id": "b2925"}, {"notes": {"original_bigg_ids": ["GTPCII2", "2me4p_c", "b1773"]}, "name": "ydjI", "id": "b1773"}, {"notes": {"original_bigg_ids": ["GTPDPDP", "dxyl5p_c", "b3925"]}, "name": "glpX", "id": "b3925"}, {"notes": {"original_bigg_ids": ["GTPDPK", "dxyl_c", "b4232"]}, "name": "fbp", "id": "b4232"}, {"notes": {"original_bigg_ids": ["GTPHs", "4per_c", "b2492"]}, "name": "focB", "id": "b2492"}, {"notes": {"original_bigg_ids": ["GTPtex", "eca4colipa_p", "b0904"]}, "name": "focA", "id": "b0904"}, {"notes": {"original_bigg_ids": ["GUACYC", "eca2und_p", "b4151"]}, "name": "frdD", "id": "b4151"}, {"notes": {"original_bigg_ids": ["GUAD", "unagamuf_p", "b4152"]}, "name": "frdC", "id": "b4152"}, {"notes": {"original_bigg_ids": ["GUAPRT", "eca3und_p", "b4154"]}, "name": "frdA", "id": "b4154"}, {"notes": {"original_bigg_ids": ["GUAt2pp", "eca4und_p", "b4153"]}, "name": "frdB", "id": "b4153"}, {"notes": {"original_bigg_ids": ["GUAtex", "3hbcoa_c", "b2416"]}, "name": "ptsI", "id": "b2416"}, {"notes": {"original_bigg_ids": ["GUAtpp", "3hhcoa_c", "b1818"]}, "name": "manY", "id": "b1818"}, {"notes": {"original_bigg_ids": ["GUI1", "3hocoa_c", "b1817"]}, "name": "manX", "id": "b1817"}, {"notes": {"original_bigg_ids": ["GUI2", "3hdcoa_c", "b1819"]}, "name": "manZ", "id": "b1819"}, {"notes": {"original_bigg_ids": ["GUR1PPpp", "3hddcoa_c", "b2415"]}, "name": "ptsH", "id": "b2415"}, {"notes": {"original_bigg_ids": ["H2O2tex", "3htdcoa_c", "b1611"]}, "name": "fumC", "id": "b1611"}, {"notes": {"original_bigg_ids": ["H2Otex", "3hhdcoa_c", "b4122"]}, "name": "fumB", "id": "b4122"}, {"notes": {"original_bigg_ids": ["H2Otpp", "3hodcoa_c", "b1612"]}, "name": "fumA", "id": "b1612"}, {"notes": {"original_bigg_ids": ["H2SO", "6pgc_c", "b3528"]}, "name": "dctA", "id": "b3528"}, {"notes": {"original_bigg_ids": ["H2St1pp", "kdo2lipid4L_c", "b1852"]}, "name": "zwf", "id": "b1852"}, {"notes": {"original_bigg_ids": ["H2Stex", "kdo2lipid4_c", "b1779"]}, "name": "gapA", "id": "b1779"}, {"notes": {"original_bigg_ids": ["H2tex", "lipa_c", "b1621"]}, "name": "malX", "id": "b1621"}, {"notes": {"original_bigg_ids": ["H2tpp", "kdo2lipid4p_c", "b2417"]}, "name": "crr", "id": "b2417"}, {"notes": {"original_bigg_ids": ["HACD1", "lipa_cold_c", "b1101"]}, "name": "ptsG", "id": "b1101"}, {"notes": {"original_bigg_ids": ["HACD2", "gmeACP_c", "b1297"]}, "name": "puuA", "id": "b1297"}, {"notes": {"original_bigg_ids": ["HACD3", "egmeACP_c", "b3870"]}, "name": "glnA", "id": "b3870"}, {"notes": {"original_bigg_ids": ["HACD4", "enlipa_p", "b0810"]}, "name": "glnP", "id": "b0810"}, {"notes": {"original_bigg_ids": ["HACD5", "2pg_c", "b0811"]}, "name": "glnH", "id": "b0811"}, {"notes": {"original_bigg_ids": ["HACD6", "seramp_c", "b0809"]}, "name": "glnQ", "id": "b0809"}, {"notes": {"original_bigg_ids": ["HACD7", "feenter_c", "b1761"]}, "name": "gdhA", "id": "b1761"}, {"notes": {"original_bigg_ids": ["HACD8", "epmeACP_c", "b0485"]}, "name": "glsA", "id": "b0485"}, {"notes": {"original_bigg_ids": ["HADPCOADH3", "pmeACP_c", "b1812"]}, "name": "pabB", "id": "b1812"}, {"notes": {"original_bigg_ids": ["HBZOPT", "etha_c", "b1524"]}, "name": "glsB", "id": "b1524"}, {"notes": {"original_bigg_ids": ["HCINNMt2rpp", "etha_p", "b3213"]}, "name": "gltD", "id": "b3213"}, {"notes": {"original_bigg_ids": ["HCINNMtex", "ethso3_c", "b3212"]}, "name": "gltB", "id": "b3212"}, {"notes": {"original_bigg_ids": ["HCO3E", "ethso3_p", "b4077"]}, "name": "gltP", "id": "b4077"}, {"notes": {"original_bigg_ids": ["HDCAtexi", "etoh_p", "b2029"]}, "name": "gnd", "id": "b2029"}, {"notes": {"original_bigg_ids": ["HDCEAtexi", "f6p_c", "b0875"]}, "name": "aqpZ", "id": "b0875"}, {"notes": {"original_bigg_ids": ["HEMEOS", "fru_c", "b1136"]}, "name": "icd", "id": "b1136"}, {"notes": {"original_bigg_ids": ["HEPK1", "f6p_p", "b4015"]}, "name": "aceA", "id": "b4015"}, {"notes": {"original_bigg_ids": ["HEPT1", "hxa_c", "b2133"]}, "name": "dld", "id": "b2133"}, {"notes": {"original_bigg_ids": ["HEPT2", "ttdca_p", "b1380"]}, "name": "ldhA", "id": "b1380"}, {"notes": {"original_bigg_ids": ["HEPT3", "ttdcea_p", "b4014"]}, "name": "aceB", "id": "b4014"}, {"notes": {"original_bigg_ids": ["HETZK", "hdca_p", "b2976"]}, "name": "glcB", "id": "b2976"}, {"notes": {"original_bigg_ids": ["HEX1", "hdcea_p", "b3236"]}, "name": "mdh", "id": "b3236"}, {"notes": {"original_bigg_ids": ["HEX4", "ocdca_p", "b1479"]}, "name": "maeA", "id": "b1479"}, {"notes": {"original_bigg_ids": ["HEX7", "ocdcea_p", "b2463"]}, "name": "maeB", "id": "b2463"}, {"notes": {"original_bigg_ids": ["HG2abcpp", "hxa_p", "b2280"]}, "name": "nuoJ", "id": "b2280"}, {"notes": {"original_bigg_ids": ["HG2t3pp", "octa_p", "b2278"]}, "name": "nuoL", "id": "b2278"}, {"notes": {"original_bigg_ids": ["HG2tex", "Sfglutth_c", "b2281"]}, "name": "nuoI", "id": "b2281"}, {"notes": {"original_bigg_ids": ["HISTD", "hmgth_c", "b2288"]}, "name": "nuoA", "id": "b2288"}, {"notes": {"original_bigg_ids": ["HISTP", "fald_p", "b2282"]}, "name": "nuoH", "id": "b2282"}, {"notes": {"original_bigg_ids": ["HISTRS", "fald_c", "b2286"]}, "name": "nuoC", "id": "b2286"}, {"notes": {"original_bigg_ids": ["HISabcpp", "fdp_c", "b2283"]}, "name": "nuoG", "id": "b2283"}, {"notes": {"original_bigg_ids": ["HISt2rpp", "s17bp_c", "b2277"]}, "name": "nuoM", "id": "b2277"}, {"notes": {"original_bigg_ids": ["HIStex", "fuc_DASH_L_c", "b2287"]}, "name": "nuoB", "id": "b2287"}, {"notes": {"original_bigg_ids": ["HKNDDH", "fcl_DASH_L_c", "b2284"]}, "name": "nuoF", "id": "b2284"}, {"notes": {"original_bigg_ids": ["HKNTDH", "fc1p_c", "b2279"]}, "name": "nuoK", "id": "b2279"}, {"notes": {"original_bigg_ids": ["HMBS", "lald_DASH_L_c", "b2276"]}, "name": "nuoN", "id": "b2276"}, {"notes": {"original_bigg_ids": ["HMPK1", "ppp9_c", "b2285"]}, "name": "nuoE", "id": "b2285"}, {"notes": {"original_bigg_ids": ["HOMt2pp", "for_p", "b3962"]}, "name": "sthA", "id": "b3962"}, {"notes": {"original_bigg_ids": ["HOMtex", "isetac_c", "b1602"]}, "name": "pntB", "id": "b1602"}, {"notes": {"original_bigg_ids": ["HOPNTAL", "mso3_c", "b1603"]}, "name": "pntA", "id": "b1603"}, {"notes": {"original_bigg_ids": ["HPPK2", "glx_c", "b0451"]}, "name": "amtB", "id": "b0451"}, {"notes": {"original_bigg_ids": ["HPPPNDO", "sulfac_c", "b0115"]}, "name": "aceF", "id": "b0115"}, {"notes": {"original_bigg_ids": ["HPPPNt2rpp", "fe2_p", "b0114"]}, "name": "aceE", "id": "b0114"}, {"notes": {"original_bigg_ids": ["HPPPNtex", "fe3dcit_p", "b3916"]}, "name": "pfkA", "id": "b3916"}, {"notes": {"original_bigg_ids": ["HPYRI", "fe3dhbzs_c", "b1723"]}, "name": "pfkB", "id": "b1723"}, {"notes": {"original_bigg_ids": ["HPYRRx", "fe3dhbzs_p", "b0903"]}, "name": "pflB", "id": "b0903"}, {"notes": {"original_bigg_ids": ["HPYRRy", "fe3hox_c", "b3952"]}, "name": "pflC", "id": "b3952"}, {"notes": {"original_bigg_ids": ["HSDy", "fe3hox_DASH_un_c", "b3114"]}, "name": "tdcE", "id": "b3114"}, {"notes": {"original_bigg_ids": ["HSK", "fe3hox_DASH_un_p", "b0902"]}, "name": "pflA", "id": "b0902"}, {"notes": {"original_bigg_ids": ["HSST", "fe3hox_p", "b2579"]}, "name": "grcA", "id": "b2579"}, {"notes": {"original_bigg_ids": ["HSTPT", "fe3_p", "b3951"]}, "name": "pflD", "id": "b3951"}, {"notes": {"original_bigg_ids": ["HXAND", "fecrm_DASH_un_c", "b4025"]}, "name": "pgi", "id": "b4025"}, {"notes": {"original_bigg_ids": ["HXAtex", "fecrm_c", "b2926"]}, "name": "pgk", "id": "b2926"}, {"notes": {"original_bigg_ids": ["HXPRT", "fecrm_DASH_un_p", "b0767"]}, "name": "pgl", "id": "b0767"}, {"notes": {"original_bigg_ids": ["HYD1pp", "fecrm_p", "b0755"]}, "name": "gpmA", "id": "b0755"}, {"notes": {"original_bigg_ids": ["HYD2pp", "feenter_p", "b3612"]}, "name": "gpmM", "id": "b3612"}, {"notes": {"original_bigg_ids": ["HYD3pp", "enter_p", "b4395"]}, "name": "ytjC", "id": "b4395"}, {"notes": {"original_bigg_ids": ["HYPOE", "feoxam_c", "b2987"]}, "name": "pitB", "id": "b2987"}, {"notes": {"original_bigg_ids": ["HYXNtex", "feoxam_DASH_un_c", "b3493"]}, "name": "pitA", "id": "b3493"}, {"notes": {"original_bigg_ids": ["HYXNtpp", "feoxam_DASH_un_p", "b3956"]}, "name": "ppc", "id": "b3956"}, {"notes": {"original_bigg_ids": ["Htex", "feoxam_p", "b3403"]}, "name": "pck", "id": "b3403"}, {"notes": {"original_bigg_ids": ["I2FE2SR", "3fe4s_c", "b1702"]}, "name": "ppsA", "id": "b1702"}, {"notes": {"original_bigg_ids": ["I2FE2SS", "no_c", "b2297"]}, "name": "pta", "id": "b2297"}, {"notes": {"original_bigg_ids": ["I2FE2SS2", "n2o_c", "b2458"]}, "name": "eutD", "id": "b2458"}, {"notes": {"original_bigg_ids": ["I2FE2ST", "h2_c", "b1676"]}, "name": "pykF", "id": "b1676"}, {"notes": {"original_bigg_ids": ["I4FE4SR", "flxso_c", "b1854"]}, "name": "pykA", "id": "b1854"}, {"notes": {"original_bigg_ids": ["I4FE4ST", "flxr_c", "b4301"]}, "name": "sgcE", "id": "b4301"}, {"notes": {"original_bigg_ids": ["ICDHyr", "fmettrna_c", "b3386"]}, "name": "rpe", "id": "b3386"}, {"notes": {"original_bigg_ids": ["ICHORS", "mettrna_c", "b2914"]}, "name": "rpiA", "id": "b2914"}, {"notes": {"original_bigg_ids": ["ICHORSi", "5fthf_c", "b4090"]}, "name": "rpiB", "id": "b4090"}, {"notes": {"original_bigg_ids": ["ICHORT", "methf_c", "b0723"]}, "name": "sdhA", "id": "b0723"}, {"notes": {"original_bigg_ids": ["ICL", "oxa_c", "b0724"]}, "name": "sdhB", "id": "b0724"}, {"notes": {"original_bigg_ids": ["ICYSDS", "oxalcoa_c", "b0722"]}, "name": "sdhD", "id": "b0722"}, {"notes": {"original_bigg_ids": ["IDONtex", "forcoa_c", "b0721"]}, "name": "sdhC", "id": "b0721"}, {"notes": {"original_bigg_ids": ["IG3PS", "f1p_c", "b0728"]}, "name": "sucC", "id": "b0728"}, {"notes": {"original_bigg_ids": ["IGPDH", "frulys_p", "b0729"]}, "name": "sucD", "id": "b0729"}, {"notes": {"original_bigg_ids": ["IGPS", "fruur_p", "b2464"]}, "name": "talA", "id": "b2464"}, {"notes": {"original_bigg_ids": ["ILETA", "fruur_c", "b0008"]}, "name": "talB", "id": "b0008"}, {"notes": {"original_bigg_ids": ["ILETRS", "fru_p", "b2465"]}, "name": "tktB", "id": "b2465"}, {"notes": {"original_bigg_ids": ["ILEabcpp", "fuc_DASH_L_p", "b2935"]}, "name": "tktA", "id": "b2935"}, {"notes": {"original_bigg_ids": ["ILEt2rpp", "mal_DASH_L_c", "b3919"]}, "name": "tpiA", "id": "b3919"}], "compartments": {"c": "cytosol", "e": "extracellular space"}, "metabolites": [{"formula": "C3H4O10P2", "notes": {"original_bigg_ids": ["PLIPA2A160pp", "13dpg_c"]}, "compartment": "c", "name": "3-Phospho-D-glyceroyl phosphate", "id": "13dpg_c"}, {"formula": "C3H4O7P", "notes": {"original_bigg_ids": ["PLIPA2A161pp", "2pg_c"]}, "compartment": "c", "name": "D-Glycerate 2-phosphate", "id": "2pg_c"}, {"formula": "C3H4O7P", "notes": {"original_bigg_ids": ["PLIPA2A180pp", "3pg_c"]}, "compartment": "c", "name": "3-Phospho-D-glycerate", "id": "3pg_c"}, {"formula": "C6H10O10P", "notes": {"original_bigg_ids": ["PLIPA2A181pp", "6pgc_c"]}, "compartment": "c", "name": "6-Phospho-D-gluconate", "id": "6pgc_c"}, {"formula": "C6H9O9P", "notes": {"original_bigg_ids": ["PLIPA2E120pp", "6pgl_c"]}, "compartment": "c", "name": "6-phospho-D-glucono-1,5-lactone", "id": "6pgl_c"}, {"formula": "C2H3O2", "notes": {"original_bigg_ids": ["PLIPA2E140pp", "ac_c"]}, "compartment": "c", "name": "Acetate", "id": "ac_c"}, {"formula": "C2H3O2", "notes": {"original_bigg_ids": ["PLIPA2E141pp", "ac_e"]}, "compartment": "e", "name": "Acetate", "id": "ac_e"}, {"formula": "C2H4O", "notes": {"original_bigg_ids": ["PLIPA2E160pp", "acald_c"]}, "compartment": "c", "name": "Acetaldehyde", "id": "acald_c"}, {"formula": "C2H4O", "notes": {"original_bigg_ids": ["PLIPA2E161pp", "acald_e"]}, "compartment": "e", "name": "Acetaldehyde", "id": "acald_e"}, {"formula": "C23H34N7O17P3S", "notes": {"original_bigg_ids": ["PLIPA2E180pp", "accoa_c"]}, "compartment": "c", "name": "Acetyl-CoA", "id": "accoa_c"}, {"formula": "C6H3O6", "notes": {"original_bigg_ids": ["PLIPA2E181pp", "acon_C_c"]}, "compartment": "c", "name": "Cis-Aconitate", "id": "acon_C_c"}, {"formula": "C2H3O5P", "notes": {"original_bigg_ids": ["PLIPA2G120pp", "actp_c"]}, "compartment": "c", "name": "Acetyl phosphate", "id": "actp_c"}, {"formula": "C10H12N5O10P2", "notes": {"original_bigg_ids": ["PLIPA2G140pp", "adp_c"]}, "compartment": "c", "name": "ADP", "id": "adp_c"}, {"formula": "C5H4O5", "notes": {"original_bigg_ids": ["PLIPA2G141pp", "akg_c"]}, "compartment": "c", "name": "2-Oxoglutarate", "id": "akg_c"}, {"formula": "C5H4O5", "notes": {"original_bigg_ids": ["PLIPA2G160pp", "akg_e"]}, "compartment": "e", "name": "2-Oxoglutarate", "id": "akg_e"}, {"formula": "C10H12N5O7P", "notes": {"original_bigg_ids": ["PLIPA2G161pp", "amp_c"]}, "compartment": "c", "name": "AMP", "id": "amp_c"}, {"formula": "C10H12N5O13P3", "notes": {"original_bigg_ids": ["PLIPA2G180pp", "atp_c"]}, "compartment": "c", "name": "ATP", "id": "atp_c"}, {"formula": "C6H5O7", "notes": {"original_bigg_ids": ["PLIPA2G181pp", "cit_c"]}, "compartment": "c", "name": "Citrate", "id": "cit_c"}, {"formula": "CO2", "notes": {"original_bigg_ids": ["PMANM", "co2_c"]}, "compartment": "c", "name": "CO2", "id": "co2_c"}, {"formula": "CO2", "notes": {"original_bigg_ids": ["PMDPHT", "co2_e"]}, "compartment": "e", "name": "CO2", "id": "co2_e"}, {"formula": "C21H32N7O16P3S", "notes": {"original_bigg_ids": ["PMEACPE", "coa_c"]}, "compartment": "c", "name": "Coenzyme A", "id": "coa_c"}, {"formula": "C3H5O6P", "notes": {"original_bigg_ids": ["PMPK", "dhap_c"]}, "compartment": "c", "name": "Dihydroxyacetone phosphate", "id": "dhap_c"}, {"formula": "C4H7O7P", "notes": {"original_bigg_ids": ["PNTK", "e4p_c"]}, "compartment": "c", "name": "D-Erythrose 4-phosphate", "id": "e4p_c"}, {"formula": "C2H6O", "notes": {"original_bigg_ids": ["PNTOt4pp", "etoh_c"]}, "compartment": "c", "name": "Ethanol", "id": "etoh_c"}, {"formula": "C2H6O", "notes": {"original_bigg_ids": ["PNTOtex", "etoh_e"]}, "compartment": "e", "name": "Ethanol", "id": "etoh_e"}, {"formula": "C6H11O9P", "notes": {"original_bigg_ids": ["POAACR", "f6p_c"]}, "compartment": "c", "name": "D-Fructose 6-phosphate", "id": "f6p_c"}, {"formula": "C6H10O12P2", "notes": {"original_bigg_ids": ["POR5", "fdp_c"]}, "compartment": "c", "name": "D-Fructose 1,6-bisphosphate", "id": "fdp_c"}, {"formula": "CH1O2", "notes": {"original_bigg_ids": ["POX", "for_c"]}, "compartment": "c", "name": "Formate", "id": "for_c"}, {"formula": "CH1O2", "notes": {"original_bigg_ids": ["PPA", "for_e"]}, "compartment": "e", "name": "Formate", "id": "for_e"}, {"formula": "C6H12O6", "notes": {"original_bigg_ids": ["PPA2", "fru_e"]}, "compartment": "e", "name": "D-Fructose", "id": "fru_e"}, {"formula": "C4H2O4", "notes": {"original_bigg_ids": ["PPAKr", "fum_c"]}, "compartment": "c", "name": "Fumarate", "id": "fum_c"}, {"formula": "C4H2O4", "notes": {"original_bigg_ids": ["PPALtex", "fum_e"]}, "compartment": "e", "name": "Fumarate", "id": "fum_e"}, {"formula": "C3H5O6P", "notes": {"original_bigg_ids": ["PPALtpp", "g3p_c"]}, "compartment": "c", "name": "Glyceraldehyde 3-phosphate", "id": "g3p_c"}, {"formula": "C6H11O9P", "notes": {"original_bigg_ids": ["PPAt4pp", "g6p_c"]}, "compartment": "c", "name": "D-Glucose 6-phosphate", "id": "g6p_c"}, {"formula": "C6H12O6", "notes": {"original_bigg_ids": ["PPAtex", "glc_D_e"]}, "compartment": "e", "name": "D-Glucose", "id": "glc__D_e"}, {"formula": "C5H10N2O3", "notes": {"original_bigg_ids": ["PPBNGS", "gln_L_c"]}, "compartment": "c", "name": "L-Glutamine", "id": "gln__L_c"}, {"formula": "C5H10N2O3", "notes": {"original_bigg_ids": ["PPC", "gln_L_e"]}, "compartment": "e", "name": "L-Glutamine", "id": "gln__L_e"}, {"formula": "C5H8NO4", "notes": {"original_bigg_ids": ["PPCDC", "glu_L_c"]}, "compartment": "c", "name": "L-Glutamate", "id": "glu__L_c"}, {"formula": "C5H8NO4", "notes": {"original_bigg_ids": ["PPCK", "glu_L_e"]}, "compartment": "e", "name": "L-Glutamate", "id": "glu__L_e"}, {"formula": "C2H1O3", "notes": {"original_bigg_ids": ["PPCSCT", "glx_c"]}, "compartment": "c", "name": "Glyoxylate", "id": "glx_c"}, {"formula": "H2O", "notes": {"original_bigg_ids": ["PPGPPDP", "h2o_c"]}, "compartment": "c", "name": "H2O", "id": "h2o_c"}, {"formula": "H2O", "notes": {"original_bigg_ids": ["PPK2r", "h2o_e"]}, "compartment": "e", "name": "H2O", "id": "h2o_e"}, {"formula": "H", "notes": {"original_bigg_ids": ["PPKr", "h_c"]}, "compartment": "c", "name": "H+", "id": "h_c"}, {"formula": "H", "notes": {"original_bigg_ids": ["PPM", "h_e"]}, "compartment": "e", "name": "H+", "id": "h_e"}, {"formula": "C6H5O7", "notes": {"original_bigg_ids": ["PPM2", "icit_c"]}, "compartment": "c", "name": "Isocitrate", "id": "icit_c"}, {"formula": "C3H5O3", "notes": {"original_bigg_ids": ["PPNCL2", "lac_D_c"]}, "compartment": "c", "name": "D-Lactate", "id": "lac__D_c"}, {"formula": "C3H5O3", "notes": {"original_bigg_ids": ["PPND", "lac_D_e"]}, "compartment": "e", "name": "D-Lactate", "id": "lac__D_e"}, {"formula": "C4H4O5", "notes": {"original_bigg_ids": ["PPNDH", "mal_L_c"]}, "compartment": "c", "name": "L-Malate", "id": "mal__L_c"}, {"formula": "C4H4O5", "notes": {"original_bigg_ids": ["PPPGO", "mal_L_e"]}, "compartment": "e", "name": "L-Malate", "id": "mal__L_e"}, {"formula": "C21H26N7O14P2", "notes": {"original_bigg_ids": ["PPPGO3", "nad_c"]}, "compartment": "c", "name": "Nicotinamide adenine dinucleotide", "id": "nad_c"}, {"formula": "C21H27N7O14P2", "notes": {"original_bigg_ids": ["PPPNDO", "nadh_c"]}, "compartment": "c", "name": "Nicotinamide adenine dinucleotide - reduced", "id": "nadh_c"}, {"formula": "C21H25N7O17P3", "notes": {"original_bigg_ids": ["PPPNt2rpp", "nadp_c"]}, "compartment": "c", "name": "Nicotinamide adenine dinucleotide phosphate", "id": "nadp_c"}, {"formula": "C21H26N7O17P3", "notes": {"original_bigg_ids": ["PPPNtex", "nadph_c"]}, "compartment": "c", "name": "Nicotinamide adenine dinucleotide phosphate - reduced", "id": "nadph_c"}, {"formula": "H4N", "notes": {"original_bigg_ids": ["PPS", "nh4_c"]}, "compartment": "c", "name": "Ammonium", "id": "nh4_c"}, {"formula": "H4N", "notes": {"original_bigg_ids": ["PPTHpp", "nh4_e"]}, "compartment": "e", "name": "Ammonium", "id": "nh4_e"}, {"formula": "O2", "notes": {"original_bigg_ids": ["PPTtex", "o2_c"]}, "compartment": "c", "name": "O2", "id": "o2_c"}, {"formula": "O2", "notes": {"original_bigg_ids": ["PRAGSr", "o2_e"]}, "compartment": "e", "name": "O2", "id": "o2_e"}, {"formula": "C4H2O5", "notes": {"original_bigg_ids": ["PRAIS", "oaa_c"]}, "compartment": "c", "name": "Oxaloacetate", "id": "oaa_c"}, {"formula": "C3H2O6P", "notes": {"original_bigg_ids": ["PRAIi", "pep_c"]}, "compartment": "c", "name": "Phosphoenolpyruvate", "id": "pep_c"}, {"formula": "HO4P", "notes": {"original_bigg_ids": ["PRAMPC", "pi_c"]}, "compartment": "c", "name": "Phosphate", "id": "pi_c"}, {"formula": "HO4P", "notes": {"original_bigg_ids": ["PRASCSi", "pi_e"]}, "compartment": "e", "name": "Phosphate", "id": "pi_e"}, {"formula": "C3H3O3", "notes": {"original_bigg_ids": ["PRATPP", "pyr_c"]}, "compartment": "c", "name": "Pyruvate", "id": "pyr_c"}, {"formula": "C3H3O3", "notes": {"original_bigg_ids": ["PRFGS", "pyr_e"]}, "compartment": "e", "name": "Pyruvate", "id": "pyr_e"}, {"formula": "C49H74O4", "notes": {"original_bigg_ids": ["PRMICI", "q8_c"]}, "compartment": "c", "name": "Ubiquinone-8", "id": "q8_c"}, {"formula": "C49H76O4", "notes": {"original_bigg_ids": ["PROD2", "q8h2_c"]}, "compartment": "c", "name": "Ubiquinol-8", "id": "q8h2_c"}, {"formula": "C5H9O8P", "notes": {"original_bigg_ids": ["PROGLYabcpp", "r5p_c"]}, "compartment": "c", "name": "Alpha-D-Ribose 5-phosphate", "id": "r5p_c"}, {"formula": "C5H9O8P", "notes": {"original_bigg_ids": ["PROGLYtex", "ru5p_D_c"]}, "compartment": "c", "name": "D-Ribulose 5-phosphate", "id": "ru5p__D_c"}, {"formula": "C7H13O10P", "notes": {"original_bigg_ids": ["PROTRS", "s7p_c"]}, "compartment": "c", "name": "Sedoheptulose 7-phosphate", "id": "s7p_c"}, {"formula": "C4H4O4", "notes": {"original_bigg_ids": ["PROabcpp", "succ_c"]}, "compartment": "c", "name": "Succinate", "id": "succ_c"}, {"formula": "C4H4O4", "notes": {"original_bigg_ids": ["PROt2rpp", "succ_e"]}, "compartment": "e", "name": "Succinate", "id": "succ_e"}, {"formula": "C25H35N7O19P3S", "notes": {"original_bigg_ids": ["PROt4pp", "succoa_c"]}, "compartment": "c", "name": "Succinyl-CoA", "id": "succoa_c"}, {"formula": "C5H9O8P", "notes": {"original_bigg_ids": ["PROtex", "xu5p_D_c"]}, "compartment": "c", "name": "D-Xylulose 5-phosphate", "id": "xu5p__D_c"}], "version": 1, "id": "e_coli_core"} \ No newline at end of file diff --git a/cameo/models/webmodels.py b/cameo/models/webmodels.py index 5927ee1b2..f47ff3c4d 100644 --- a/cameo/models/webmodels.py +++ b/cameo/models/webmodels.py @@ -102,7 +102,8 @@ def index_models_minho(host="http://darwin.di.uminho.pt/models"): raise Exception("Could not index available models. %s returned status code %d" % (host, response.status_code)) -def get_model_from_uminho(query, index, host="http://darwin.di.uminho.pt/models", solver_interface=optlang, sanitize=True): +def get_model_from_uminho(query, index, host="http://darwin.di.uminho.pt/models", solver_interface=optlang, + sanitize=True): model_index = index[index["name"] == query]['id'].values[0] sbml_file = get_sbml_file(model_index, host) sbml_file.close() diff --git a/cameo/network_analysis/networkx_based.py b/cameo/network_analysis/networkx_based.py index 2fcb16033..9703b24a1 100644 --- a/cameo/network_analysis/networkx_based.py +++ b/cameo/network_analysis/networkx_based.py @@ -49,8 +49,8 @@ def reactions_to_network(reactions, max_distance=0.3): reactions : list The list of reactions. max_distance : float, optional - A threshold on the normalized distance between two compounds. If distance is above this threshold, no edge between - those compounds will be created. + A threshold on the normalized distance between two compounds. If distance is above this threshold, + no edge between those compounds will be created. Returns ------- diff --git a/cameo/strain_design/deterministic/flux_variability_based.py b/cameo/strain_design/deterministic/flux_variability_based.py index 78f15fc86..8554f3f3e 100644 --- a/cameo/strain_design/deterministic/flux_variability_based.py +++ b/cameo/strain_design/deterministic/flux_variability_based.py @@ -287,7 +287,7 @@ def run(self, surface_only=True, improvements_only=True, view=None): included_reactions = [reaction.id for reaction in self.reference_model.reactions if reaction.id not in self.exclude] + self.variables + [self.objective] - self.reference_flux_dist = pfba(self.reference_model) + self.reference_flux_dist = pfba(self.reference_model, fraction_of_optimum=0.99) self.reference_flux_ranges = flux_variability_analysis(self.reference_model, reactions=included_reactions, view=view, remove_cycles=False, @@ -317,8 +317,10 @@ def run(self, surface_only=True, improvements_only=True, view=None): else: sol['normalized_gaps'] = gaps - ref_upper_bound = self.reference_flux_ranges.upper_bound.apply(lambda v: 0 if abs(v) < non_zero_flux_threshold else v) - ref_lower_bound = self.reference_flux_ranges.lower_bound.apply(lambda v: 0 if abs(v) < non_zero_flux_threshold else v) + ref_upper_bound = self.reference_flux_ranges.upper_bound.apply( + lambda v: 0 if abs(v) < non_zero_flux_threshold else v) + ref_lower_bound = self.reference_flux_ranges.lower_bound.apply( + lambda v: 0 if abs(v) < non_zero_flux_threshold else v) for df in six.itervalues(solutions): df['KO'] = False @@ -455,11 +457,11 @@ def _generate_designs(cls, solutions, reference_fva, reference_fluxes): elif relevant_row.flux_reversal: if reference_fva['upper_bound'][rid] > 0: targets.append(ReactionInversionTarget(rid, - value=relevant_row.upper_bound, + value=float_ceil(relevant_row.upper_bound, ndecimals), reference_value=reference_fluxes[rid])) else: targets.append(ReactionInversionTarget(rid, - value=relevant_row.lower_bound, + value=float_floor(relevant_row.lower_bound, ndecimals), reference_value=reference_fluxes[rid])) else: gap_sign = relevant_row.normalized_gaps > 0 @@ -470,9 +472,9 @@ def _generate_designs(cls, solutions, reference_fva, reference_fluxes): closest_bound, ref_sign = cls._closest_bound(ref_interval, row_interval) if gap_sign ^ ref_sign: - value = float_floor(relevant_row.lower_bound) + value = float_ceil(relevant_row.upper_bound, ndecimals) else: - value = float_ceil(relevant_row.upper_bound) + value = float_floor(relevant_row.lower_bound, ndecimals) targets.append(ReactionModulationTarget(rid, value=value, @@ -773,7 +775,8 @@ def run(self, target=None, max_enforced_flux=0.9, number_of_results=10, exclude= target: str, Reaction, Metabolite The target for optimization. max_enforced_flux : float, optional - The maximal flux of secondary_objective that will be enforced, relative to the theoretical maximum (defaults to 0.9). + The maximal flux of secondary_objective that will be enforced, relative to the theoretical maximum ( + defaults to 0.9). number_of_results : int, optional The number of enforced flux levels (defaults to 10). exclude : Iterable of reactions or reaction ids that will not be included in the output. @@ -962,66 +965,3 @@ def data_frame(self): df.columns = (i + 1 for i in range(len(self._enforced_levels))) df.loc[self.target.id] = self._enforced_levels return df - - # if __name__ == '__main__': - # from cameo.io import load_model - # from cameo.util import Timer - # - # model = load_model( - # '/Users/niko/Arbejder/Dev/cameo/tests/data/EcoliCore.xml') - # - # solution = model.solve() - # max_growth = solution.f - # - # reference_model = model.copy() - # biomass_rxn = model.reactions.get_by_id('Biomass_Ecoli_core_N_LPAREN_w_FSLASH_GAM_RPAREN__Nmet2') - # reference_model.reactions.get_by_id( - # 'Biomass_Ecoli_core_N_LPAREN_w_FSLASH_GAM_RPAREN__Nmet2').lower_bound = max_growth - # - # diffFVA = DifferentialFVA(design_space_model=model, - # reference_model=reference_model, - # objective='EX_succ_LPAREN_e_RPAREN_', - # variables=['Biomass_Ecoli_core_N_LPAREN_w_FSLASH_GAM_RPAREN__Nmet2', - # 'EX_o2_LPAREN_e_RPAREN_'], - # normalize_ranges_by='Biomass_Ecoli_core_N_LPAREN_w_FSLASH_GAM_RPAREN__Nmet2', - # points=10 - # ) - # result = diffFVA.run(surface_only=True, view=SequentialView()) - # - # with Timer('Sequential'): - # result = diffFVA.run(surface_only=True, view=SequentialView()) - # with Timer('Multiprocessing'): - # result = diffFVA.run(surface_only=True, view=MultiprocessingView()) - # try: - # from IPython.parallel import Client - # client = Client() - # view = client.load_balanced_view() - # view.block = True - # except: - # pass - # else: - # with Timer('IPython'): - # result = diffFVA.run(surface_only=False, view=view) - - # model = load_model( - # '/Users/niko/Arbejder/Dev/cameo/tests/data/iJO1366.xml') - # - # reference_model = model.copy() - # biomass_rxn = reference_model.reactions.get_by_id('Ec_biomass_iJO1366_core_53p95M') - # biomass_rxn.lower_bound = .9 * reference_model.solve().f - # - # - # diffFVA = DifferentialFVA(model, reference_model, 'EX_trp_DASH_L_LPAREN_e_RPAREN_', ['Ec_biomass_iJO1366_core_53p95M'], - # normalize_ranges_by='Ec_biomass_iJO1366_core_53p95M', points=10) - # with Timer('Sequential'): - # result = diffFVA.run(surface_only=True, view=SequentialView()) - # with Timer('Multiprocessing'): - # result = diffFVA.run(surface_only=True, view=MultiprocessingView()) - # try: - # from IPython.parallel import Client - # client = Client() - # view = client.load_balanced_view() - # with Timer('IPython'): - # result = diffFVA.run(surface_only=True, view=()) - # except: - # pass diff --git a/cameo/strain_design/heuristic/evolutionary/objective_functions.py b/cameo/strain_design/heuristic/evolutionary/objective_functions.py index da9c76a41..83490381b 100644 --- a/cameo/strain_design/heuristic/evolutionary/objective_functions.py +++ b/cameo/strain_design/heuristic/evolutionary/objective_functions.py @@ -299,7 +299,8 @@ def _repr_latex_(self): else: return "$$bpcy = \\frac{(%s * min(%s))}{%s}$$" % (self.biomass.replace("_", "\\_"), self.product.replace("_", "\\_"), - " + ".join(s.replace("_", "\\_") for s in self.substrates)) + " + ".join( + s.replace("_", "\\_") for s in self.substrates)) @property def name(self): diff --git a/cameo/strain_design/heuristic/evolutionary/optimization.py b/cameo/strain_design/heuristic/evolutionary/optimization.py index f8dfa11a2..9ac45b2df 100644 --- a/cameo/strain_design/heuristic/evolutionary/optimization.py +++ b/cameo/strain_design/heuristic/evolutionary/optimization.py @@ -28,7 +28,8 @@ from cameo.core.result import Result from cameo.core.solver_based_model import SolverBasedModel from cameo.flux_analysis.simulation import pfba, lmoma, moma, room, logger as simulation_logger -from cameo.flux_analysis.structural import find_blocked_reactions_nullspace, find_coupled_reactions_nullspace, nullspace, \ +from cameo.flux_analysis.structural import find_blocked_reactions_nullspace, find_coupled_reactions_nullspace, \ + nullspace, \ create_stoichiometric_array from cameo.strain_design.heuristic.evolutionary import archives from cameo.strain_design.heuristic.evolutionary import decoders @@ -44,7 +45,6 @@ from cameo.util import in_ipnb from cameo.util import partition - __all__ = ['ReactionKnockoutOptimization', 'GeneKnockoutOptimization', 'CofactorSwapOptimization'] REACTION_KNOCKOUT_TYPE = "reaction" @@ -399,6 +399,7 @@ class KnockoutOptimization(TargetOptimization): """ Abstract knockout optimization class. """ + def __init__(self, simulation_method=pfba, wt_reference=None, *args, **kwargs): super(KnockoutOptimization, self).__init__(simulation_method=simulation_method, wt_reference=wt_reference, @@ -409,6 +410,7 @@ class SolutionSimplification(object): """ Solution Simplification Method """ + def __init__(self, evaluator): if not isinstance(evaluator, evaluators.Evaluator): raise ValueError("Evaluator must be instance of " @@ -419,7 +421,8 @@ def __call__(self, population): return [self.simplify(individual) for individual in population] def simplify(self, individual): - new_individual = Individual(individual.candidate, individual.fitness, individual.maximize, birthdate=individual.birthdate) + new_individual = Individual(individual.candidate, individual.fitness, individual.maximize, + birthdate=individual.birthdate) for target in individual.candidate: new_individual.candidate.remove(target) @@ -756,7 +759,7 @@ def __init__(self, genes=None, essential_genes=None, use_nullspace_simplificatio if use_nullspace_simplification: ns = nullspace(create_stoichiometric_array(self.model)) dead_end_reactions = find_blocked_reactions_nullspace(self.model, ns=ns) - dead_end_genes = {g for g in self.model.genes if all(r in dead_end_reactions for r in g.reaction)} + dead_end_genes = {g for g in self.model.genes if all(r in dead_end_reactions for r in g.reactions)} genes = [g for g in self.model.genes if g not in self.essential_genes and g.id not in dead_end_genes] self.representation = [g.id for g in genes] diff --git a/cameo/strain_design/pathway_prediction/pathway_predictor.py b/cameo/strain_design/pathway_prediction/pathway_predictor.py index 68111878d..0e4a095b2 100644 --- a/cameo/strain_design/pathway_prediction/pathway_predictor.py +++ b/cameo/strain_design/pathway_prediction/pathway_predictor.py @@ -279,8 +279,8 @@ def run(self, product=None, max_predictions=float("inf"), min_production=.1, Returns ------- - list - A list of pathways (list of reactions) + PathwayPredictions + The predicted pathways. """ product = self._find_product(product) diff --git a/cameo/ui/__init__.py b/cameo/ui/__init__.py index e0ddf44c8..59b594c1b 100644 --- a/cameo/ui/__init__.py +++ b/cameo/ui/__init__.py @@ -1,3 +1,4 @@ +# -*- coding: utf-8 -*- # Copyright 2015 Novo Nordisk Foundation Center for Biosustainability, DTU. # Licensed under the Apache License, Version 2.0 (the "License"); @@ -98,7 +99,7 @@ def delta(): if util.in_ipnb(): return "Δ" else: - return str(b'\xce\x94') + return b'\xce\x94'.decode('utf-8') def knockin(): @@ -112,11 +113,11 @@ def upreg(coeff): if util.in_ipnb(): return "↑%.3f" % coeff else: - return str(b'\xe2\x86\x91') + "%.3f" % coeff + return b'\xe2\x86\x91'.decode('utf-8') + "%.3f" % coeff def downreg(coeff): if util.in_ipnb(): return "↓%.3f" % coeff else: - return str(b'\xe2\x86\x93') + "%.3f" % coeff + return b'\xe2\x86\x93'.decode('utf-8') + "%.3f" % coeff diff --git a/cameo/util.py b/cameo/util.py index d5361438e..8537d8824 100644 --- a/cameo/util.py +++ b/cameo/util.py @@ -32,7 +32,6 @@ import pandas import pip import six -from numpy.linalg import svd from numpy.random import RandomState from six.moves import range @@ -381,7 +380,8 @@ def _history_item_to_str(item): info += datetime.fromtimestamp(entry['unix_epoch']).strftime('%Y-%m-%d %H:%M:%S') + '\n' undo_entry = entry['undo'] try: - elements = undo_entry.func, undo_entry.args, undo_entry.keywords or {} # partial (if .keywords is None print {} instead) + # partial (if .keywords is None print {} instead) + elements = undo_entry.func, undo_entry.args, undo_entry.keywords or {} info += 'undo: ' + ' '.join([str(elem) for elem in elements]) + '\n' except AttributeError: # normal python function info += 'undo: ' + undo_entry.__name__ + '\n' @@ -600,3 +600,20 @@ def reduce_reaction_set(reaction_set, groups): reaction_set = reaction_set - intersection result = set(result) | reaction_set # Add the remaining reactions to result return result + + +def current_solver_name(model): + """Give a string representation for an optlang interface. + + Parameters + ---------- + model : cameo.core.SolverBasedModel + A model + + Returns + ------- + string + The name of the interface as a string + """ + interface = model.solver.interface.__name__ + return re.sub(r"optlang.|.interface", "", interface) diff --git a/docs/conf.py b/docs/conf.py index 17dc342ac..663046aeb 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -27,7 +27,8 @@ # # # MOCK_MODULES = ['numpy', 'numpy.random', 'numpy.linalg', 'matplotlib', 'pandas', 'scipy', 'scipy.sparse', # # 'scipy.io', 'scipy.stats', 'scipy.version', 'bokeh', 'bokeh.plotting', 'swiglpk', -# # 'glpk', 'gurobipy', 'gurobipy.GRB', 'cplex', 'mlabwrap', 'pp', 'libsbml', 'METANETX', '_METANETX', +# # 'glpk', 'gurobipy', 'gurobipy.GRB', 'cplex', 'mlabwrap', 'pp', 'libsbml', 'METANETX', +# '_METANETX', # # 'IPython.core.display', 'IPython.core', 'IPython'] # MOCK_MODULES = ['matplotlib', 'pandas', 'scipy', 'scipy.sparse', # 'scipy.io', 'scipy.stats', 'scipy.version', 'bokeh', 'bokeh.plotting', 'swiglpk', diff --git a/requirements_dev.txt b/requirements_dev.txt index e0a9a0d25..e02c961e2 100644 --- a/requirements_dev.txt +++ b/requirements_dev.txt @@ -1,6 +1,5 @@ -nose +pytest lxml -rednose swiglpk python-libsbml bokeh diff --git a/scripts/parse_metanetx.py b/scripts/parse_metanetx.py index 0a12fbcef..20589c6f0 100644 --- a/scripts/parse_metanetx.py +++ b/scripts/parse_metanetx.py @@ -86,8 +86,10 @@ def construct_universal_model(list_of_db_prefixes): logger.debug('Cannot parse formula %s. Skipping formula' % chem_prop.loc[met.id].formula) continue # if met.formula.weight is None: - # logger.debug('Cannot calculate weight for formula %s. Skipping reaction %s' % (met.formula, row.Equation)) - # # print('Cannot calculate weight for formula %s. Skipping reaction %s' % (met.formula, row.Equation)) + # logger.debug('Cannot calculate weight for formula %s. + # Skipping reaction %s' % (met.formula, row.Equation)) + # # print('Cannot calculate weight for formula %s. + # Skipping reaction %s' % (met.formula, row.Equation)) # continue try: met.charge = int(chem_prop.loc[met.id].charge) diff --git a/setup.cfg b/setup.cfg index 9e9e4530e..3dfd00ac8 100644 --- a/setup.cfg +++ b/setup.cfg @@ -12,8 +12,8 @@ universal = 1 [flake8] ignore = E402,E303,E122,F401,F812 -max-line-length = 160 -exclude = tests/*,versioneer.py,__init__.py,_version.py,plotting_old.py,cplex/*,docs/notebooks +max-line-length = 120 +exclude = versioneer.py,__init__.py,_version.py,plotting_old.py,cplex/*,docs/notebooks # TODO: the following needs to go down significantly max-complexity = 26 # See the docstring in versioneer.py for instructions. Note that you must @@ -27,3 +27,14 @@ versionfile_source = cameo/_version.py versionfile_build = cameo/_version.py tag_prefix = versioneer.parentdir_prefix = myproject- + +[tool:pytest] +testpaths = tests + +[isort] +not_skip = __init__.py +indent = 4 +line_length = 120 +multi_line_output = 4 +known_third_party = future,six +known_first_party = cameo diff --git a/setup.py b/setup.py index 85852c322..554b8c528 100644 --- a/setup.py +++ b/setup.py @@ -47,7 +47,7 @@ 'plotly': ['plotly>=1.9.6'], 'bokeh': ['bokeh>=0.11.1'], 'jupyter': ['jupyter>=1.0.0', 'ipywidgets>=4.1.1'], - 'test': ['nose>=1.3.7', 'rednose>=0.4.3', 'coverage>=4.0.3'], + 'test': ['pytest', 'pytest-cov'], 'parallel': ['redis>=2.10.5', 'ipyparallel>=5.0.1'], 'sbml': ['python-libsbml>=5.13.0', 'lxml>=3.6.0'], 'cli': ['cement>=2.10.2', 'xlwt>=1.2.0'] diff --git a/tests/__init__.py b/tests/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/tests/conftest.py b/tests/conftest.py new file mode 100644 index 000000000..55b79c6bd --- /dev/null +++ b/tests/conftest.py @@ -0,0 +1,69 @@ +from os.path import abspath, dirname, join + +import cobra.test +import pytest + +from cameo import load_model +from cameo.config import solvers + + +cameo_directory = abspath(join(dirname(abspath(__file__)), "..")) +cameo_location = abspath(join(cameo_directory, "..")) +data_dir = join(cameo_directory, "tests", "data", "") + + +@pytest.fixture(scope="session") +def data_directory(): + return data_dir + + +@pytest.fixture(scope='session') +def model(data_directory): + return load_model(join(data_directory, 'EcoliCore.xml')) + + +@pytest.fixture(scope="session") +def salmonella(): + return cobra.test.create_test_model('salmonella') + + +@pytest.fixture(scope="session", params=list(solvers)) +def ijo1366(request, data_directory): + ijo = load_model(join(data_directory, 'iJO1366.xml'), sanitize=False) + ijo.solver = request.param + return ijo + + +@pytest.fixture(scope='session') +def iaf1260(data_directory): + return load_model(join(data_directory, 'iAF1260.xml')) + + +@pytest.fixture(scope="session") +def universal_model(data_directory): + universal = load_model(join(data_directory, 'iJO1366.xml'), sanitize=False) + universal.remove_reactions(universal.exchanges) + return universal + + +@pytest.fixture(scope="session", params=list(solvers)) +def imm904(request, data_directory): + imm = load_model(join(data_directory, 'iMM904.xml')) + imm.solver = request.param + return imm.copy() + + +# FIXME: should be possible to scope at least to class +@pytest.fixture(scope="function", params=list(solvers)) +def toy_model(request, data_directory): + toy = load_model(join(data_directory, "toy_model_Papin_2003.xml")) + toy.solver = request.param + return toy + + +# FIXME: should be possible to scope at least to class +@pytest.fixture(scope="function", params=list(solvers)) +def core_model(request, data_directory): + ecoli_core = load_model(join(data_directory, 'EcoliCore.xml'), sanitize=False) + ecoli_core.solver = request.param + return ecoli_core diff --git a/tests/test_api.py b/tests/test_api.py index 4f2824fe5..e9c3687ca 100644 --- a/tests/test_api.py +++ b/tests/test_api.py @@ -16,32 +16,47 @@ import os import re -import nose -from nose.plugins.skip import SkipTest -from nose.tools import assert_equal, assert_raises_regexp, assert_true +import pytest -from cameo import api +from cameo import api, load_model +from cameo import models, config +from cameo.api.hosts import Host from cameo.api.products import Compound +MODELS = os.path.dirname(models.__file__) + +UNIVERSALMODEL = load_model(os.path.join(MODELS, 'json/iJO1366.json')) +UNIVERSALMODEL.remove_reactions(UNIVERSALMODEL.exchanges) + + +def test_api(): + mock_host = Host('core', + models=['e_coli_core'], + biomass=['BIOMASS_Ecoli_core_w_GAM'], + carbon_sources=['EX_glc__D_e']) + + api.design.debug = True + pathways = api.design.predict_pathways(product=UNIVERSALMODEL.metabolites.ser__L_c, hosts=[mock_host], + database=UNIVERSALMODEL, aerobic=True) + optimization_reports = api.design.optimize_strains(pathways, config.default_view, aerobic=True) + assert len(optimization_reports) > 0 + def test_compound_repr(): - if not re.match('Open Babel.*', os.popen('obabel').read()): - raise SkipTest('Skipping because OpenBabel is not installed.') + pytest.mark.skipif(not re.match('Open Babel.*', os.popen('obabel').read()), + reason='Skipping because OpenBabel is not installed.') compound = Compound('InChI=1S/H2O/h1H2') - assert_true(re.match(r"^<\?xml version=\"1\.0\"\?>.*$", compound._repr_svg_().replace('\n', ''))) - assert_equal(compound._repr_html_(), compound._repr_svg_()) + assert re.match(r"^<\?xml version=\"1\.0\"\?>.*$", compound._repr_svg_().replace('\n', '')) + assert compound._repr_html_() == compound._repr_svg_() def test_products(): - assert_equal(api.products.search('3-hydroxy propionate').index[0], 'MNXM872') - with assert_raises_regexp(Exception, "No compound matches found for query.*"): + assert api.products.search('3-hydroxy propionate').index[0] == 'MNXM872' + with pytest.raises(Exception) as excinfo: api.products.search('old spice') + excinfo.match("No compound matches found for query.*") def test_hosts(): - assert_equal(api.hosts.ecoli.models.iJO1366.id, 'iJO1366') - assert_equal(api.hosts.scerevisiae.models.iMM904.id, 'iMM904') - - -if __name__ == '__main__': - nose.runmodule() + assert api.hosts.ecoli.models.iJO1366.id == 'iJO1366' + assert api.hosts.scerevisiae.models.iMM904.id == 'iMM904' diff --git a/tests/test_cobrapy_compatibility.py b/tests/test_cobrapy_compatibility.py index 78d35e672..ff5a9f842 100644 --- a/tests/test_cobrapy_compatibility.py +++ b/tests/test_cobrapy_compatibility.py @@ -11,34 +11,34 @@ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. +import pytest -# import types -# from cobra.test import create_test_model from cameo.core.solver_based_model import to_solver_based_model -import unittest + import six -from cobra.flux_analysis import * +from cobra.flux_analysis import calculate_phenotype_phase_plane, single_gene_deletion + +@pytest.fixture(scope="module") +def model(): + return to_solver_based_model(create_test_model("textbook")) -class TestCobrapyCompatibility(unittest.TestCase): - def setUp(self): - # Make Model pickable and then load a solver based version of test_pickle - self.model = to_solver_based_model(create_test_model("textbook")) - def test_cobra_phenotypic_phase_plane(self): - data = calculate_phenotype_phase_plane(self.model, "EX_glc__D_e", "EX_o2_e", +class TestCobrapyCompatibility: + def test_cobra_phenotypic_phase_plane(self, model): + data = calculate_phenotype_phase_plane(model, "EX_glc__D_e", "EX_o2_e", reaction1_npoints=20, reaction2_npoints=20) - self.assertEquals(data.growth_rates.shape, (20, 20)) - self.assertTrue(abs(data.growth_rates.max()) - 1.20898 < 0.001) - self.assertTrue(abs(data.growth_rates[0, :].max()) < 0.0001) + assert data.growth_rates.shape == (20, 20) + assert abs(data.growth_rates.max()) - 1.20898 < 0.001 + assert abs(data.growth_rates[0, :].max()) < 0.0001 - def test_single_gene_deletion_fba(self): + def test_single_gene_deletion_fba(self, model): growth_dict = {"b0008": 0.87, "b0114": 0.80, "b0116": 0.78, "b2276": 0.21, "b1779": 0.00} - rates, statuses = single_gene_deletion(self.model, + rates, statuses = single_gene_deletion(model, gene_list=growth_dict.keys(), method="fba") for gene, expected_value in six.iteritems(growth_dict): - self.assertTrue(statuses[gene] == 'optimal') - self.assertTrue(abs(rates[gene] - expected_value) < 0.01) + assert statuses[gene] == 'optimal' + assert abs(rates[gene] - expected_value) < 0.01 diff --git a/tests/test_core_strain_design.py b/tests/test_core_strain_design.py index 430565790..c058f697f 100644 --- a/tests/test_core_strain_design.py +++ b/tests/test_core_strain_design.py @@ -7,59 +7,56 @@ # http://www.apache.org/licenses/LICENSE-2.0 # Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. -import os -import unittest +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +import pytest import six -from cameo import load_model, Reaction, Metabolite +from cameo import Metabolite, Reaction from cameo.core.strain_design import StrainDesign -from cameo.core.target import ReactionKnockoutTarget, ReactionKnockinTarget, ReactionModulationTarget +from cameo.core.target import (ReactionKnockinTarget, ReactionKnockoutTarget, + ReactionModulationTarget) from cameo.exceptions import IncompatibleTargets -TESTDIR = os.path.dirname(__file__) +@pytest.fixture(scope="function") +def cad_reaction(core_model): + reaction = Reaction(id="CAD", name="Cis-Aconitate Decarboxylase") + acon = core_model.metabolites.acon_DASH_C_c + co2_c = core_model.metabolites.co2_c + ita_c = Metabolite(id="ita_c", name="Itaconate", compartment="c") + reaction.add_metabolites({acon: -1, co2_c: 1, ita_c: 1}) + return reaction -class StrainDesignAbstractionTestCase(unittest.TestCase): - model = None - cad_reaction = None - @classmethod - def setUpClass(cls): - cls.model = load_model(os.path.join(TESTDIR, 'data', 'EcoliCore.xml')) - cls.cad_reaction = Reaction(id="CAD", name="Cis-Aconitate Decarboxylase") - acon_C_c = cls.model.metabolites.acon_dsh_C_c - co2_c = cls.model.metabolites.co2_c - ita_c = Metabolite(id="ita_c", name="Itaconate", compartment="c") - cls.cad_reaction.add_metabolites({acon_C_c: -1, co2_c: 1, ita_c: 1}) +class TestStrainDesign: - def test_create_strain_design(self): + def test_create_strain_design(self, cad_reaction): t1 = ReactionKnockoutTarget('PGI') t2 = ReactionKnockoutTarget('GAPD') - t3 = ReactionKnockinTarget("CAD", self.cad_reaction) + t3 = ReactionKnockinTarget("CAD", cad_reaction) strain_design = StrainDesign([t1, t2, t3]) - self.assertEqual(len(strain_design), 3) + assert len(strain_design) == 3 strain_design2 = StrainDesign([t1, t2, t3]) strain_design3 = StrainDesign([t2, t1, t3]) - self.assertEqual(strain_design, strain_design2) - self.assertEqual(strain_design, strain_design3) - self.assertEqual(strain_design3, strain_design2) + assert strain_design == strain_design2 + assert strain_design == strain_design3 + assert strain_design3 == strain_design2 - self.assertIn(t1, strain_design) - self.assertIn(t2, strain_design) - self.assertIn(t3, strain_design) + assert t1 in strain_design + assert t2 in strain_design + assert t3 in strain_design - def test_add_strain_design(self): + def test_add_strain_design(self, cad_reaction): t1 = ReactionKnockoutTarget('PGI') t2 = ReactionKnockoutTarget('GAPD') - t3 = ReactionKnockinTarget("CAD", self.cad_reaction) + t3 = ReactionKnockinTarget("CAD", cad_reaction) strain_design1 = StrainDesign([t1, t2, t3]) @@ -67,41 +64,45 @@ def test_add_strain_design(self): strain_design2 = StrainDesign([t4]) - self.assertRaises(IncompatibleTargets, strain_design1.__add__, strain_design2) - self.assertRaises(IncompatibleTargets, strain_design2.__add__, strain_design1) + with pytest.raises(IncompatibleTargets): + strain_design1.__add__(strain_design2) + with pytest.raises(IncompatibleTargets): + strain_design2.__add__(strain_design1) - self.assertRaises(IncompatibleTargets, strain_design1.__iadd__, strain_design2) - self.assertRaises(IncompatibleTargets, strain_design2.__iadd__, strain_design1) + with pytest.raises(IncompatibleTargets): + strain_design1.__iadd__(strain_design2) + with pytest.raises(IncompatibleTargets): + strain_design2.__iadd__(strain_design1) t5 = ReactionModulationTarget("RPI", 2, 0) strain_design3 = StrainDesign([t5]) strain_design4 = strain_design3 + strain_design1 - self.assertIn(t1, strain_design4) - self.assertIn(t2, strain_design4) - self.assertIn(t3, strain_design4) - self.assertNotIn(t4, strain_design4) - self.assertIn(t5, strain_design4) + assert t1 in strain_design4 + assert t2 in strain_design4 + assert t3 in strain_design4 + assert t4 not in strain_design4 + assert t5 in strain_design4 strain_design3 += strain_design1 - self.assertIn(t1, strain_design3) - self.assertIn(t2, strain_design3) - self.assertIn(t3, strain_design3) - self.assertNotIn(t4, strain_design3) - self.assertIn(t5, strain_design3) + assert t1 in strain_design3 + assert t2 in strain_design3 + assert t3 in strain_design3 + assert t4 not in strain_design3 + assert t5 in strain_design3 - @unittest.skipIf(six.PY2, "Gnomic is not compatible with python 2") - def test_design_to_gnomic(self): + @pytest.mark.skipif(six.PY2, reason="Gnomic is not compatible with python 2") + def test_design_to_gnomic(self, cad_reaction): from gnomic import Genotype t1 = ReactionKnockoutTarget('PGI') t2 = ReactionKnockoutTarget('GAPD') - t3 = ReactionKnockinTarget("CAD", self.cad_reaction) + t3 = ReactionKnockinTarget("CAD", cad_reaction) strain_design1 = StrainDesign([t1, t2, t3]) sd_gnomic = strain_design1.to_gnomic() - self.assertIsInstance(sd_gnomic, Genotype) - self.assertEqual(len(sd_gnomic.added_features), 1) - self.assertEqual(len(sd_gnomic.removed_features), 2) + assert isinstance(sd_gnomic, Genotype) + assert len(sd_gnomic.added_features) == 1 + assert len(sd_gnomic.removed_features) == 2 diff --git a/tests/test_flux_analysis.py b/tests/test_flux_analysis.py index 3a20b058f..2f88d8de8 100644 --- a/tests/test_flux_analysis.py +++ b/tests/test_flux_analysis.py @@ -19,28 +19,35 @@ import copy import os import re -import unittest from functools import partial import numpy as np import pandas -from nose.tools import assert_almost_equal -from optlang.exceptions import IndicatorConstraintsNotSupported +import pytest from sympy import Add import cameo -from cameo.config import solvers -from cameo.flux_analysis import remove_infeasible_cycles, fix_pfba_as_constraint -from cameo.flux_analysis import structural -from cameo.flux_analysis.analysis import flux_variability_analysis, phenotypic_phase_plane, find_blocked_reactions -from cameo.flux_analysis.simulation import fba, pfba, lmoma, room, moma, add_pfba +from cameo.flux_analysis import remove_infeasible_cycles, structural +from cameo.flux_analysis.analysis import (find_blocked_reactions, + flux_variability_analysis, + phenotypic_phase_plane, + fix_pfba_as_constraint) +from cameo.flux_analysis.simulation import fba, lmoma, moma, pfba, room, add_pfba from cameo.flux_analysis.structural import nullspace -from cameo.io import load_model -from cameo.parallel import SequentialView, MultiprocessingView -from cameo.util import TimeMachine, pick_one +from cameo.parallel import MultiprocessingView, SequentialView +from cameo.util import TimeMachine, current_solver_name, pick_one +TRAVIS = bool(os.getenv('TRAVIS', False)) +TEST_DIR = os.path.dirname(__file__) -def assert_data_frames_equal(obj, expected, delta=0.001, sort_by=None, nan=0): +REFERENCE_FVA_SOLUTION_ECOLI_CORE = pandas.read_csv(os.path.join(TEST_DIR, 'data/REFERENCE_flux_ranges_EcoliCore.csv'), + index_col=0) +REFERENCE_PPP_o2_EcoliCore = pandas.read_csv(os.path.join(TEST_DIR, 'data/REFERENCE_PPP_o2_EcoliCore.csv')) +REFERENCE_PPP_o2_EcoliCore_ac = pandas.read_csv(os.path.join(TEST_DIR, 'data/REFERENCE_PPP_o2_EcoliCore_ac.csv')) +REFERENCE_PPP_o2_glc_EcoliCore = pandas.read_csv(os.path.join(TEST_DIR, 'data/REFERENCE_PPP_o2_glc_EcoliCore.csv')) + + +def assert_data_frames_equal(obj, expected, delta=0.001, sort_by=None): df = obj.data_frame expected_names = [name for name in expected.columns.values if not re.match(r'^Unnamed.*', name)] df = df.fillna(value=0) @@ -50,521 +57,403 @@ def assert_data_frames_equal(obj, expected, delta=0.001, sort_by=None, nan=0): expected = expected.sort_values(sort_by).reset_index(drop=True) for column in expected_names: for key in df.index: - assert_almost_equal(df[column][key], expected[column][key], delta=delta) - -TRAVIS = os.getenv('TRAVIS', False) -TEST_DIR = os.path.dirname(__file__) - -REFERENCE_FVA_SOLUTION_ECOLI_CORE = pandas.read_csv(os.path.join(TEST_DIR, 'data/REFERENCE_flux_ranges_EcoliCore.csv'), - index_col=0) -REFERENCE_PPP_o2_EcoliCore = pandas.read_csv(os.path.join(TEST_DIR, 'data/REFERENCE_PPP_o2_EcoliCore.csv')) -REFERENCE_PPP_o2_EcoliCore_ac = pandas.read_csv(os.path.join(TEST_DIR, 'data/REFERENCE_PPP_o2_EcoliCore_ac.csv')) -REFERENCE_PPP_o2_glc_EcoliCore = pandas.read_csv(os.path.join(TEST_DIR, 'data/REFERENCE_PPP_o2_glc_EcoliCore.csv')) - - -class Wrapper: - class CommonGround(unittest.TestCase): - @classmethod - def setUpClass(cls): - cls.ecoli_core = load_model(os.path.join(TEST_DIR, 'data/EcoliCore.xml'), sanitize=False) - cls.toy_model = load_model(os.path.join(TEST_DIR, "data/toy_model_Papin_2003.xml")) - - def __init__(self, *args, **kwargs): - super(Wrapper.CommonGround, self).__init__(*args, **kwargs) - self._iJO1366 = None - - @property - def iJO1366(self): - if self._iJO1366 is None: - self._iJO1366 = load_model(os.path.join(TEST_DIR, 'data/iJO1366.xml'), sanitize=False) - if self._iJO1366.solver.interface.__name__ != self.ecoli_core.solver.interface.__name__: - self._iJO1366.solver = self.ecoli_core.solver.interface - return self._iJO1366 - - class AbstractTestFindBlockedReactions(CommonGround): - def test_find_blocked_reactions(self): - self.ecoli_core.reactions.PGK.knock_out() # there are no blocked reactions in EcoliCore - blocked_reactions = find_blocked_reactions(self.ecoli_core) - self.assertEqual(blocked_reactions, {self.ecoli_core.reactions.GAPD, self.ecoli_core.reactions.PGK}) - - class AbstractTestFluxVariabilityAnalysis(CommonGround): - - def test_flux_variability_parallel(self): - original_objective = self.ecoli_core.objective - mp_view = MultiprocessingView(2) - fva_solution = flux_variability_analysis(self.ecoli_core, fraction_of_optimum=0.999999419892, - remove_cycles=False, view=mp_view) - pfba_fva = flux_variability_analysis(self.ecoli_core, fraction_of_optimum=1, pfba_factor=1, - view=mp_view).data_frame - mp_view.shutdown() - assert_data_frames_equal(fva_solution, REFERENCE_FVA_SOLUTION_ECOLI_CORE) - self.assertEqual(original_objective, self.ecoli_core.objective) - self.assertAlmostEqual(sum(abs(pfba_fva.lower_bound)), 518.422, delta=0.001) - self.assertAlmostEqual(sum(abs(pfba_fva.upper_bound)), 518.422, delta=0.001) - - def test_add_remove_pfb(self): - with TimeMachine() as tm: - add_pfba(self.ecoli_core, time_machine=tm) - self.assertEquals('_pfba_objective', self.ecoli_core.objective.name) - self.assertNotEqual('_pfba_objective', self.ecoli_core.solver.constraints) - with TimeMachine() as tm: - fix_pfba_as_constraint(self.ecoli_core, time_machine=tm) - self.assertTrue('_fixed_pfba_constraint' in self.ecoli_core.solver.constraints) - self.assertTrue('_fixed_pfba_constraint' not in self.ecoli_core.solver.constraints) - - - @unittest.skipIf(TRAVIS, 'Skip multiprocessing in Travis') - def test_flux_variability_parallel_remove_cycles(self): - original_objective = self.ecoli_core.objective - fva_solution = flux_variability_analysis(self.ecoli_core, fraction_of_optimum=0.999999419892, - remove_cycles=True, view=MultiprocessingView()) - self.assertGreater(REFERENCE_FVA_SOLUTION_ECOLI_CORE['upper_bound']['FRD7'], 666.) - self.assertAlmostEqual(fva_solution['upper_bound']['FRD7'], 0.) - for key in fva_solution.data_frame.index: - if REFERENCE_FVA_SOLUTION_ECOLI_CORE['lower_bound'][key] > -666: - self.assertAlmostEqual(fva_solution['lower_bound'][key], - REFERENCE_FVA_SOLUTION_ECOLI_CORE['lower_bound'][key], delta=0.0001) - if REFERENCE_FVA_SOLUTION_ECOLI_CORE['upper_bound'][key] < 666: - self.assertAlmostEqual(fva_solution['upper_bound'][key], - REFERENCE_FVA_SOLUTION_ECOLI_CORE['upper_bound'][key], delta=0.0001) - self.assertEqual(original_objective, self.ecoli_core.objective) - - def test_flux_variability_sequential(self): - original_objective = self.ecoli_core.objective - fva_solution = flux_variability_analysis(self.ecoli_core, fraction_of_optimum=0.999999419892, - remove_cycles=False, view=SequentialView()) - pfba_fva = flux_variability_analysis(self.ecoli_core, fraction_of_optimum=1, pfba_factor=1).data_frame - assert_data_frames_equal(fva_solution, REFERENCE_FVA_SOLUTION_ECOLI_CORE) - self.assertEqual(original_objective, self.ecoli_core.objective) - self.assertAlmostEqual(sum(abs(pfba_fva.lower_bound)), 518.422, delta=0.001) - self.assertAlmostEqual(sum(abs(pfba_fva.upper_bound)), 518.422, delta=0.001) - - def test_flux_variability_sequential_remove_cycles(self): - original_objective = self.ecoli_core.objective - fva_solution = flux_variability_analysis(self.ecoli_core, fraction_of_optimum=0.999999419892, remove_cycles=True, - view=SequentialView()) - self.assertGreater(REFERENCE_FVA_SOLUTION_ECOLI_CORE['upper_bound']['FRD7'], 666.) - self.assertAlmostEqual(fva_solution['upper_bound']['FRD7'], 0.) - for key in fva_solution.data_frame.index: - if REFERENCE_FVA_SOLUTION_ECOLI_CORE['lower_bound'][key] > -666: - self.assertAlmostEqual(fva_solution['lower_bound'][key], - REFERENCE_FVA_SOLUTION_ECOLI_CORE['lower_bound'][key], delta=0.0001) - if REFERENCE_FVA_SOLUTION_ECOLI_CORE['upper_bound'][key] < 666: - self.assertAlmostEqual(fva_solution['upper_bound'][key], - REFERENCE_FVA_SOLUTION_ECOLI_CORE['upper_bound'][key], delta=0.0001) - - cycle_reac = cameo.Reaction("minus_PGI") # Create fake cycle - cycle_reac.lower_bound = -1000 - self.ecoli_core.add_reaction(cycle_reac) - cycle_reac.add_metabolites({met: -c for met, c in self.ecoli_core.reactions.PGI.metabolites.items()}) - fva_solution = flux_variability_analysis(self.ecoli_core, remove_cycles=False, reactions=["PGI"]) - self.assertEqual(fva_solution.data_frame.loc["PGI", "upper_bound"], 1000) - fva_solution = flux_variability_analysis(self.ecoli_core, remove_cycles=True, reactions=["PGI"]) - self.assertTrue(fva_solution.data_frame.loc["PGI", "upper_bound"] < 666) - self.assertEqual(original_objective, self.ecoli_core.objective) - - class AbstractTestPhenotypicPhasePlane(CommonGround): - @unittest.skipIf(TRAVIS, 'Running in Travis') - def test_one_variable_parallel(self): - ppp = phenotypic_phase_plane(self.ecoli_core, ['EX_o2_LPAREN_e_RPAREN_'], view=MultiprocessingView()) - assert_data_frames_equal(ppp, REFERENCE_PPP_o2_EcoliCore, sort_by=['EX_o2_LPAREN_e_RPAREN_']) - ppp = phenotypic_phase_plane(self.ecoli_core, 'EX_o2_LPAREN_e_RPAREN_', view=MultiprocessingView()) - assert_data_frames_equal(ppp, REFERENCE_PPP_o2_EcoliCore, sort_by=['EX_o2_LPAREN_e_RPAREN_']) - - def test_one_variable_sequential(self): - ppp = phenotypic_phase_plane(self.ecoli_core, ['EX_o2_LPAREN_e_RPAREN_'], view=SequentialView()) - assert_data_frames_equal(ppp, REFERENCE_PPP_o2_EcoliCore, sort_by=['EX_o2_LPAREN_e_RPAREN_']) - ppp = phenotypic_phase_plane(self.ecoli_core, 'EX_o2_LPAREN_e_RPAREN_', view=SequentialView()) - assert_data_frames_equal(ppp, REFERENCE_PPP_o2_EcoliCore, sort_by=['EX_o2_LPAREN_e_RPAREN_']) - - def test_one_variable_sequential_yield(self): - ppp = phenotypic_phase_plane(self.ecoli_core, ['EX_o2_LPAREN_e_RPAREN_'], view=SequentialView()) - assert_data_frames_equal(ppp, REFERENCE_PPP_o2_EcoliCore, sort_by=['EX_o2_LPAREN_e_RPAREN_']) - ppp = phenotypic_phase_plane(self.ecoli_core, 'EX_o2_LPAREN_e_RPAREN_', view=SequentialView()) - assert_data_frames_equal(ppp, REFERENCE_PPP_o2_EcoliCore, sort_by=['EX_o2_LPAREN_e_RPAREN_']) - - @unittest.skipIf(TRAVIS, 'Running in Travis') - def test_two_variables_parallel(self): - ppp2d = phenotypic_phase_plane(self.ecoli_core, ['EX_o2_LPAREN_e_RPAREN_', 'EX_glc_LPAREN_e_RPAREN_'], - view=MultiprocessingView()) - assert_data_frames_equal(ppp2d, REFERENCE_PPP_o2_glc_EcoliCore, - sort_by=['EX_o2_LPAREN_e_RPAREN_', 'EX_glc_LPAREN_e_RPAREN_']) - - def test_two_variables_sequential(self): - ppp2d = phenotypic_phase_plane(self.ecoli_core, ['EX_o2_LPAREN_e_RPAREN_', 'EX_glc_LPAREN_e_RPAREN_'], - view=SequentialView()) - assert_data_frames_equal(ppp2d, REFERENCE_PPP_o2_glc_EcoliCore, - sort_by=['EX_o2_LPAREN_e_RPAREN_', 'EX_glc_LPAREN_e_RPAREN_']) - - class AbstractTestSimulationMethods(CommonGround): - def test_fba(self): - solution = fba(self.ecoli_core) - original_objective = self.ecoli_core.objective - self.assertAlmostEqual(solution.objective_value, 0.873921, delta=0.000001) - self.assertEqual(len(solution.fluxes), len(self.ecoli_core.reactions)) - self.assertIs(self.ecoli_core.objective, original_objective) - - def test_fba_with_reaction_filter(self): - original_objective = self.ecoli_core.objective - solution = fba(self.ecoli_core, reactions=['EX_o2_LPAREN_e_RPAREN_', 'EX_glc_LPAREN_e_RPAREN_']) - self.assertAlmostEqual(solution.objective_value, 0.873921, delta=0.000001) - self.assertEqual(len(solution.fluxes), 2) - self.assertIs(self.ecoli_core.objective, original_objective) - - def test_pfba(self): - original_objective = self.ecoli_core.objective - fba_solution = fba(self.ecoli_core) - fba_flux_sum = sum((abs(val) for val in list(fba_solution.fluxes.values()))) - pfba_solution = pfba(self.ecoli_core) - pfba_flux_sum = sum((abs(val) for val in list(pfba_solution.fluxes.values()))) - # looks like GLPK finds a parsimonious solution without the flux minimization objective - self.assertTrue((pfba_flux_sum - fba_flux_sum) < 1e-6, - msg="FBA sum is suppose to be lower than PFBA (was %f)" % (pfba_flux_sum - fba_flux_sum)) - self.assertIs(self.ecoli_core.objective, original_objective) - - def test_pfba_with_reaction_filter(self): - original_objective = self.ecoli_core.objective - pfba_solution = pfba(self.ecoli_core, reactions=['EX_o2_LPAREN_e_RPAREN_', 'EX_glc_LPAREN_e_RPAREN_']) - self.assertEqual(len(pfba_solution.fluxes), 2) - self.assertIs(self.ecoli_core.objective, original_objective) - - def test_pfba_iJO(self): - original_objective = self.iJO1366.objective - fba_solution = fba(self.iJO1366) - fba_flux_sum = sum((abs(val) for val in fba_solution.fluxes.values())) - pfba_solution = pfba(self.iJO1366) - pfba_flux_sum = sum((abs(val) for val in pfba_solution.fluxes.values())) - self.assertTrue((pfba_flux_sum - fba_flux_sum) < 1e-6, - msg="FBA sum is suppose to be lower than PFBA (was %f)" % (pfba_flux_sum - fba_flux_sum)) - self.assertIs(self.iJO1366.objective, original_objective) - - def test_lmoma(self): - original_objective = self.ecoli_core.objective - pfba_solution = pfba(self.ecoli_core) - solution = lmoma(self.ecoli_core, reference=pfba_solution) - distance = sum((abs(solution[v] - pfba_solution[v]) for v in pfba_solution.keys())) - self.assertAlmostEqual(0, distance, - delta=1e-6, - msg="lmoma distance without knockouts must be 0 (was %f)" % distance) - self.assertIs(self.ecoli_core.objective, original_objective) - - def test_lmoma_change_ref(self): - original_objective = self.ecoli_core.objective - pfba_solution = pfba(self.ecoli_core) - fluxes = {rid: 10*flux for rid, flux in pfba_solution.items()} - solution = lmoma(self.ecoli_core, reference=fluxes) - distance = sum((abs(solution[v] - pfba_solution[v]) for v in pfba_solution.keys())) - self.assertNotAlmostEqual(0, distance, - delta=1e-6, - msg="lmoma distance without knockouts must be 0 (was %f)" % distance) - self.assertIs(self.ecoli_core.objective, original_objective) - - def test_lmoma_with_reaction_filter(self): - original_objective = self.ecoli_core.objective - pfba_solution = pfba(self.ecoli_core) - solution = lmoma(self.ecoli_core, reference=pfba_solution, - reactions=['EX_o2_LPAREN_e_RPAREN_', 'EX_glc_LPAREN_e_RPAREN_']) - self.assertEqual(len(solution.fluxes), 2) - self.assertIs(self.ecoli_core.objective, original_objective) - - def test_moma(self): - original_objective = self.ecoli_core.objective - pfba_solution = pfba(self.ecoli_core) - solution = moma(self.ecoli_core, reference=pfba_solution) - distance = sum((abs(solution[v] - pfba_solution[v]) for v in pfba_solution.keys())) - self.assertAlmostEqual(0, distance, - delta=1e-6, - msg="moma distance without knockouts must be 0 (was %f)" % distance) - self.assertIs(self.ecoli_core.objective, original_objective) - - def test_room(self): - original_objective = self.ecoli_core.objective - pfba_solution = pfba(self.ecoli_core) - solution = room(self.ecoli_core, reference=pfba_solution) - self.assertAlmostEqual(0, solution.objective_value, - delta=1e-6, - msg="room objective without knockouts must be 0 (was %f)" % solution.objective_value) - self.assertIs(self.ecoli_core.objective, original_objective) - - def test_room_with_reaction_filter(self): - original_objective = self.ecoli_core.objective - pfba_solution = pfba(self.ecoli_core) - solution = room(self.ecoli_core, reference=pfba_solution, - reactions=['EX_o2_LPAREN_e_RPAREN_', 'EX_glc_LPAREN_e_RPAREN_']) - self.assertEqual(len(solution.fluxes), 2) - self.assertIs(self.ecoli_core.objective, original_objective) - - def test_room_shlomi_2005(self): - original_objective = self.toy_model.objective - reference = {"b1": 10, "v1": 10, "v2": 5, "v3": 0, "v4": 0, "v5": 0, "v6": 5, "b2": 5, "b3": 5} - expected = {'b1': 10.0, 'b2': 5.0, 'b3': 5.0, 'v1': 10.0, - 'v2': 5.0, 'v3': 0.0, 'v4': 5.0, 'v5': 5.0, 'v6': 0.0} - with TimeMachine() as tm: - self.toy_model.reactions.v6.knock_out(tm) - result = room(self.toy_model, reference=reference, delta=0, epsilon=0) - - for k in reference.keys(): - self.assertAlmostEqual(expected[k], result.fluxes[k], delta=0.1, msg="%s: %f | %f") - self.assertIs(self.toy_model.objective, original_objective) - - def test_moma_shlomi_2005(self): - original_objective = self.toy_model.objective - reference = {"b1": 10, "v1": 10, "v2": 5, "v3": 0, "v4": 0, "v5": 0, "v6": 5, "b2": 5, "b3": 5} - expected = {'b1': 8.8, 'b2': 4.4, 'b3': 4.4, 'v1': 8.8, - 'v2': 3.1, 'v3': 1.3, 'v4': 4.4, 'v5': 3.1, 'v6': 0.0} - - with TimeMachine() as tm: - self.toy_model.reactions.v6.knock_out(tm) - result = moma(self.toy_model, reference=reference) - - for k in reference.keys(): - self.assertAlmostEqual(expected[k], result.fluxes[k], delta=0.1, msg="%s: %f | %f") - self.assertIs(self.toy_model.objective, original_objective) - - def test_moma_shlomi_2005_change_ref(self): - original_objective = self.toy_model.objective - reference = {"b1": 10, "v1": 10, "v2": 5, "v3": 0, "v4": 0, "v5": 0, "v6": 5, "b2": 5, "b3": 5} - expected = {'b1': 8.8, 'b2': 4.4, 'b3': 4.4, 'v1': 8.8, - 'v2': 3.1, 'v3': 1.3, 'v4': 4.4, 'v5': 3.1, 'v6': 0.0} - - with TimeMachine() as tm: - self.toy_model.reactions.v6.knock_out(tm) - result = moma(self.toy_model, reference=reference) - - for k in reference.keys(): - self.assertAlmostEqual(expected[k], result.fluxes[k], delta=0.1, msg="%s: %f | %f") - self.assertIs(self.toy_model.objective, original_objective) - - reference_changed = {"b1": 5, "v1": 5, "v2": 5, "v3": 0, "v4": 0, "v5": 0, "v6": 5, "b2": 5, "b3": 5} - with TimeMachine() as tm: - self.toy_model.reactions.v6.knock_out(tm) - result_changed = moma(self.toy_model, reference=reference_changed) - - self.assertNotEqual(expected, result_changed.fluxes) - - class AbstractTestRemoveCycles(CommonGround): - def test_remove_cyles(self): - model = self.ecoli_core - with TimeMachine() as tm: - model.fix_objective_as_constraint(time_machine=tm) - original_objective = copy.copy(self.ecoli_core.objective) - model.objective = self.ecoli_core.solver.interface.Objective(Add(*model.solver.variables.values()), - name='Max all fluxes') - solution = model.solve() - self.assertAlmostEqual(solution.data_frame.fluxes.abs().sum(), 2508.293334, delta=1e-6) - fluxes = solution.fluxes - model.objective = original_objective - clean_fluxes = remove_infeasible_cycles(model, fluxes) - self.assertAlmostEqual(pandas.Series(clean_fluxes).abs().sum(), 518.42208550050827, delta=1e-6) - - class AbstractTestStructural(CommonGround): - def test_find_blocked_reactions(self): - self.assertIn("PGK", self.ecoli_core.reactions) - self.ecoli_core.reactions.PGK.knock_out() # there are no blocked reactions in EcoliCore - blocked_reactions = structural.find_blocked_reactions_nullspace(self.ecoli_core) - self.assertEqual(len(blocked_reactions), 0) - - def test_find_dead_end_reactions(self): - self.assertEqual(len(structural.find_dead_end_reactions(self.ecoli_core)), 0) - met1 = cameo.Metabolite("fake_metabolite_1") - met2 = cameo.Metabolite("fake_metabolite_2") - reac = cameo.Reaction("fake_reac") - reac.add_metabolites({met1: -1, met2: 1}) - self.ecoli_core.add_reaction(reac) - self.assertEqual(structural.find_dead_end_reactions(self.ecoli_core), {reac}) - - def test_find_coupled_reactions(self): - couples = structural.find_coupled_reactions(self.ecoli_core) - fluxes = self.ecoli_core.solve().fluxes - for coupled_set in couples: - coupled_set = list(coupled_set) - self.assertAlmostEqual(fluxes[coupled_set[0].id], fluxes[coupled_set[1].id]) - - couples, blocked = structural.find_coupled_reactions(self.ecoli_core, return_dead_ends=True) - self.assertEqual(blocked, structural.find_dead_end_reactions(self.ecoli_core)) - - @unittest.skipIf(TRAVIS, "ShortestElementaryFluxModes needs refactor") - def test_shortest_elementary_flux_modes(self): - sefm = structural.ShortestElementaryFluxModes(self.ecoli_core) - ems = [] - for i, em in enumerate(sefm): - if i > 10: - break - ems.append(em) - self.assertEqual(list(map(len, ems)), sorted(map(len, ems))) - - def test_dead_end_metabolites_are_in_dead_end_reactions(self): - dead_end_reactions = structural.find_dead_end_reactions(self.ecoli_core) - dead_end_metabolites = {m for m in self.ecoli_core.metabolites if len(m.reactions) == 1} - for dead_end_metabolite in dead_end_metabolites: - self.assertTrue(any(dead_end_metabolite in r.metabolites for r in dead_end_reactions)) - - def test_coupled_reactions(self): - # If a reaction is essential, all coupled reactions are essential - essential_reactions = self.ecoli_core.essential_reactions() - coupled_reactions = structural.find_coupled_reactions_nullspace(self.ecoli_core) - for essential_reaction in essential_reactions: - for group in coupled_reactions: - self.assertIsInstance(group, frozenset) - if essential_reaction in group: - self.assertTrue(all(group_reaction in essential_reactions for group_reaction in group)) - - # FIXME: this test has everything to run, but sometimes removing the reactions doesn't seem to work. - @unittest.skipIf(TRAVIS, "Inconsistent behaviour") - def test_reactions_in_group_become_blocked_if_one_is_removed(self): - essential_reactions = self.ecoli_core.essential_reactions() - coupled_reactions = structural.find_coupled_reactions_nullspace(self.ecoli_core) - for group in coupled_reactions: - representative = pick_one(group) - if representative not in essential_reactions: - with TimeMachine() as tm: - self.assertEqual(self.ecoli_core, representative.model) - tm(do=partial(self.ecoli_core.remove_reactions, [representative], delete=False), - undo=partial(self.ecoli_core.add_reactions, [representative])) - # FIXME: Hack because of optlang queue issues with GLPK - self.ecoli_core.solver.update() - self.assertNotIn(representative, self.ecoli_core.reactions) - self.assertNotIn(representative.forward_variable, self.ecoli_core.solver.variables) - self.assertNotIn(representative.reverse_variable, self.ecoli_core.solver.variables) - self.assertNotIn(representative, self.ecoli_core.reactions) - self.assertEqual(representative.model, None) - blocked_reactions = find_blocked_reactions(self.ecoli_core) - self.assertTrue(all(r in blocked_reactions for r in group if r != representative)) - self.assertIn(representative, self.ecoli_core.reactions) - - coupled_reactions = structural.find_coupled_reactions(self.ecoli_core) - for group in coupled_reactions: - representative = pick_one(group) - if representative not in essential_reactions: - with TimeMachine() as tm: - fwd_var_name = representative.forward_variable.name - rev_var_name = representative.reverse_variable.name - self.assertEqual(self.ecoli_core, representative.model) - tm(do=partial(self.ecoli_core.remove_reactions, [representative], delete=False), - undo=partial(self.ecoli_core.add_reactions, [representative])) - # FIXME: Hack because of optlang queue issues with GLPK - self.ecoli_core.solver.update() - self.assertNotIn(representative, self.ecoli_core.reactions) - self.assertNotIn(fwd_var_name, self.ecoli_core.solver.variables) - self.assertNotIn(rev_var_name, self.ecoli_core.solver.variables) - self.assertNotIn(representative, self.ecoli_core.reactions) - self.assertEqual(representative.model, None) - blocked_reactions = find_blocked_reactions(self.ecoli_core) - self.assertNotIn(representative, self.ecoli_core.reactions) - self.assertTrue(all(r in blocked_reactions for r in group if r != representative)) - self.assertIn(representative, self.ecoli_core.reactions) - - -class TestFindBlockedReactionsGLPK(Wrapper.AbstractTestFindBlockedReactions): - def setUp(self): - self.ecoli_core.solver = 'glpk' - self.toy_model.solver = 'glpk' - - -@unittest.skipIf('cplex' not in solvers, "No cplex interface available") -class TestFindBlockedReactionsCPLEX(Wrapper.AbstractTestFindBlockedReactions): - def setUp(self): - self.ecoli_core.solver = 'cplex' - self.toy_model.solver = 'cplex' - - -class TestFluxVariabilityAnalysisGLPK(Wrapper.AbstractTestFluxVariabilityAnalysis): - def setUp(self): - self.ecoli_core.solver = 'glpk' - self.toy_model.solver = 'glpk' - - -@unittest.skipIf('cplex' not in solvers, "No cplex interface available") -class TestFluxVariabilityAnalysisCPLEX(Wrapper.AbstractTestFluxVariabilityAnalysis): - def setUp(self): - self.ecoli_core.solver = 'cplex' - self.toy_model.solver = 'cplex' - - -class TestPhenotypicPhasePlaneGLPK(Wrapper.AbstractTestPhenotypicPhasePlane): - def setUp(self): - self.ecoli_core.solver = 'glpk' - self.toy_model.solver = 'glpk' - - def test_one_variable_sequential_metabolite(self): - ppp = phenotypic_phase_plane(self.ecoli_core, ['EX_o2_LPAREN_e_RPAREN_'], self.ecoli_core.metabolites.ac_c, + assert abs(df[column][key] - expected[column][key]) < delta + + +def test_find_blocked_reactions(core_model): + core_model.reactions.PGK.knock_out() + blocked_reactions = find_blocked_reactions(core_model) + assert blocked_reactions == {core_model.reactions.GAPD, core_model.reactions.PGK} + + +class TestFluxVariabilityAnalysis: + def test_flux_variability_parallel(self, core_model): + original_objective = core_model.objective + mp_view = MultiprocessingView(2) + fva_solution = flux_variability_analysis(core_model, fraction_of_optimum=0.999999419892, + remove_cycles=False, view=mp_view) + pfba_fva = flux_variability_analysis(core_model, fraction_of_optimum=1, pfba_factor=1, + view=mp_view).data_frame + mp_view.shutdown() + assert_data_frames_equal(fva_solution, REFERENCE_FVA_SOLUTION_ECOLI_CORE) + assert original_objective == core_model.objective + assert sum(abs(pfba_fva.lower_bound)) - 518.422 < .001 + assert sum(abs(pfba_fva.upper_bound)) - 518.422 < .001 + + def test_add_remove_pfb(self, core_model): + with TimeMachine() as tm: + add_pfba(core_model, time_machine=tm) + assert '_pfba_objective' == core_model.objective.name + assert '_pfba_objective' != core_model.solver.constraints + with TimeMachine() as tm: + fix_pfba_as_constraint(core_model, time_machine=tm) + assert '_fixed_pfba_constraint' in core_model.solver.constraints + assert '_fixed_pfba_constraint' not in core_model.solver.constraints + + @pytest.mark.skipif(TRAVIS, reason='Skip multiprocessing on Travis') + def test_flux_variability_parallel_remove_cycles(self, core_model): + original_objective = core_model.objective + fva_solution = flux_variability_analysis(core_model, fraction_of_optimum=0.999999419892, + remove_cycles=True, view=MultiprocessingView()) + assert REFERENCE_FVA_SOLUTION_ECOLI_CORE['upper_bound']['FRD7'] > 666. + assert round(abs(fva_solution['upper_bound']['FRD7'] - 0.), 7) == 0 + for key in fva_solution.data_frame.index: + if REFERENCE_FVA_SOLUTION_ECOLI_CORE['lower_bound'][key] > -666: + assert abs( + fva_solution['lower_bound'][key] - REFERENCE_FVA_SOLUTION_ECOLI_CORE['lower_bound'][key]) < 0.0001 + if REFERENCE_FVA_SOLUTION_ECOLI_CORE['upper_bound'][key] < 666: + assert abs( + fva_solution['upper_bound'][key] - REFERENCE_FVA_SOLUTION_ECOLI_CORE['upper_bound'][key]) < 0.0001 + assert original_objective == core_model.objective + + def test_flux_variability_sequential(self, core_model): + original_objective = core_model.objective + fva_solution = flux_variability_analysis(core_model, fraction_of_optimum=0.999999419892, + remove_cycles=False, view=SequentialView()) + pfba_fva = flux_variability_analysis(core_model, fraction_of_optimum=1, pfba_factor=1).data_frame + assert_data_frames_equal(fva_solution, REFERENCE_FVA_SOLUTION_ECOLI_CORE) + assert_data_frames_equal(fva_solution, REFERENCE_FVA_SOLUTION_ECOLI_CORE) + assert original_objective == core_model.objective + assert sum(abs(pfba_fva.lower_bound)) - 518.422 < 0.001 + assert sum(abs(pfba_fva.upper_bound)) - 518.422 < 0.001 + + def test_flux_variability_sequential_remove_cycles(self, core_model): + original_objective = core_model.objective + fva_solution = flux_variability_analysis(core_model, fraction_of_optimum=0.999999419892, + remove_cycles=True, + view=SequentialView()) + assert REFERENCE_FVA_SOLUTION_ECOLI_CORE['upper_bound']['FRD7'] > 666. + assert round(abs(fva_solution['upper_bound']['FRD7'] - 0.), 7) == 0 + for key in fva_solution.data_frame.index: + if REFERENCE_FVA_SOLUTION_ECOLI_CORE['lower_bound'][key] > -666: + assert abs( + fva_solution['lower_bound'][key] - REFERENCE_FVA_SOLUTION_ECOLI_CORE['lower_bound'][key]) < 0.0001 + if REFERENCE_FVA_SOLUTION_ECOLI_CORE['upper_bound'][key] < 666: + assert abs( + fva_solution['upper_bound'][key] - REFERENCE_FVA_SOLUTION_ECOLI_CORE['upper_bound'][key]) < 0.0001 + + cycle_reac = cameo.Reaction("minus_PGI") # Create fake cycle + cycle_reac.lower_bound = -1000 + core_model.add_reaction(cycle_reac) + cycle_reac.add_metabolites({met: -c for met, c in core_model.reactions.PGI.metabolites.items()}) + fva_solution = flux_variability_analysis(core_model, remove_cycles=False, reactions=["PGI"]) + assert fva_solution.data_frame.loc["PGI", "upper_bound"] == 1000 + fva_solution = flux_variability_analysis(core_model, remove_cycles=True, reactions=["PGI"]) + assert fva_solution.data_frame.loc["PGI", "upper_bound"] < 666 + assert original_objective == core_model.objective + + +class TestPhenotypicPhasePlane: + + @pytest.mark.skipif(TRAVIS, reason='Running in Travis') + def test_one_variable_parallel(self, core_model): + ppp = phenotypic_phase_plane(core_model, ['EX_o2_LPAREN_e_RPAREN_'], view=MultiprocessingView()) + assert_data_frames_equal(ppp, REFERENCE_PPP_o2_EcoliCore, sort_by=['EX_o2_LPAREN_e_RPAREN_']) + ppp = phenotypic_phase_plane(core_model, 'EX_o2_LPAREN_e_RPAREN_', view=MultiprocessingView()) + assert_data_frames_equal(ppp, REFERENCE_PPP_o2_EcoliCore, sort_by=['EX_o2_LPAREN_e_RPAREN_']) + + def test_one_variable_sequential(self, core_model): + ppp = phenotypic_phase_plane(core_model, ['EX_o2_LPAREN_e_RPAREN_'], view=SequentialView()) + assert_data_frames_equal(ppp, REFERENCE_PPP_o2_EcoliCore, sort_by=['EX_o2_LPAREN_e_RPAREN_']) + ppp = phenotypic_phase_plane(core_model, 'EX_o2_LPAREN_e_RPAREN_', view=SequentialView()) + assert_data_frames_equal(ppp, REFERENCE_PPP_o2_EcoliCore, sort_by=['EX_o2_LPAREN_e_RPAREN_']) + + def test_one_variable_sequential_yield(self, core_model): + ppp = phenotypic_phase_plane(core_model, ['EX_o2_LPAREN_e_RPAREN_'], view=SequentialView()) + assert_data_frames_equal(ppp, REFERENCE_PPP_o2_EcoliCore, sort_by=['EX_o2_LPAREN_e_RPAREN_']) + ppp = phenotypic_phase_plane(core_model, 'EX_o2_LPAREN_e_RPAREN_', view=SequentialView()) + assert_data_frames_equal(ppp, REFERENCE_PPP_o2_EcoliCore, sort_by=['EX_o2_LPAREN_e_RPAREN_']) + + @pytest.mark.skipif(TRAVIS, reason='Running in Travis') + def test_two_variables_parallel(self, core_model): + ppp2d = phenotypic_phase_plane(core_model, ['EX_o2_LPAREN_e_RPAREN_', 'EX_glc_LPAREN_e_RPAREN_'], + view=MultiprocessingView()) + assert_data_frames_equal(ppp2d, REFERENCE_PPP_o2_glc_EcoliCore, + sort_by=['EX_o2_LPAREN_e_RPAREN_', 'EX_glc_LPAREN_e_RPAREN_']) + + def test_two_variables_sequential(self, core_model): + ppp2d = phenotypic_phase_plane(core_model, ['EX_o2_LPAREN_e_RPAREN_', 'EX_glc_LPAREN_e_RPAREN_'], + view=SequentialView()) + assert_data_frames_equal(ppp2d, REFERENCE_PPP_o2_glc_EcoliCore, + sort_by=['EX_o2_LPAREN_e_RPAREN_', 'EX_glc_LPAREN_e_RPAREN_']) + + def test_one_variable_sequential_metabolite(self, core_model): + ppp = phenotypic_phase_plane(core_model, ['EX_o2_LPAREN_e_RPAREN_'], core_model.metabolites.ac_c, view=SequentialView()) assert_data_frames_equal(ppp, REFERENCE_PPP_o2_EcoliCore_ac, sort_by=['EX_o2_LPAREN_e_RPAREN_']) -@unittest.skipIf('cplex' not in solvers, "No cplex interface available") -class TestPhenotypicPhasePlaneCPLEX(Wrapper.AbstractTestPhenotypicPhasePlane): - def setUp(self): - self.ecoli_core.solver = 'cplex' - self.toy_model.solver = 'cplex' - - -class TestSimulationMethodsGLPK(Wrapper.AbstractTestSimulationMethods): - def setUp(self): - self.ecoli_core.solver = 'glpk' - self.toy_model.solver = 'glpk' - - def test_moma(self): - self.assertRaises(ValueError, super(TestSimulationMethodsGLPK, self).test_moma) # GLPK has no QP support - - def test_moma_shlomi_2005(self): - self.assertRaises(ValueError, super(TestSimulationMethodsGLPK, self).test_moma) # GLPK has no QP support - - def test_moma_shlomi_2005_change_ref(self): - self.assertRaises(ValueError, super(TestSimulationMethodsGLPK, self).test_moma) # GLPK has no QP support - - -@unittest.skipIf('cplex' not in solvers, "No cplex interface available") -class TestSimulationMethodsCPLEX(Wrapper.AbstractTestSimulationMethods): - def setUp(self): - self.ecoli_core.solver = 'cplex' - self.toy_model.solver = 'cplex' - - -class TestStructuralMethodsGLPK(Wrapper.AbstractTestStructural): - def setUp(self): - self.ecoli_core.solver = 'glpk' - self.toy_model.solver = 'glpk' - - def test_shortest_elementary_flux_modes(self): - self.assertRaises(IndicatorConstraintsNotSupported, structural.ShortestElementaryFluxModes, self.ecoli_core) - - -@unittest.skipIf('cplex' not in solvers, "No cplex interface available") -class TestStructuralMethodsCPLEX(Wrapper.AbstractTestStructural): - def setUp(self): - self.ecoli_core.solver = 'cplex' - self.toy_model.solver = 'cplex' - - -class TestRemoveCyclesGLPK(Wrapper.AbstractTestRemoveCycles): - def setUp(self): - self.ecoli_core.solver = 'glpk' - self.toy_model.solver = 'glpk' - - -@unittest.skipIf('cplex' not in solvers, "No cplex interface available") -class TestRemoveCyclesCPLEX(Wrapper.AbstractTestRemoveCycles): - def setUp(self): - self.ecoli_core.solver = 'cplex' - self.toy_model.solver = 'cplex' - - -class NullSpaceTestCase(Wrapper.CommonGround): - # toy from https://en.wikipedia.org/wiki/Kernel_(linear_algebra)#Illustration +class TestSimulationMethods: + def test_fba(self, core_model): + solution = fba(core_model) + original_objective = core_model.objective + assert abs(solution.objective_value - 0.873921) < 0.000001 + assert len(solution.fluxes) == len(core_model.reactions) + assert core_model.objective is original_objective + + def test_fba_with_reaction_filter(self, core_model): + original_objective = core_model.objective + solution = fba(core_model, reactions=['EX_o2_LPAREN_e_RPAREN_', 'EX_glc_LPAREN_e_RPAREN_']) + assert abs(solution.objective_value - 0.873921) < 0.000001 + assert len(solution.fluxes) == 2 + assert core_model.objective is original_objective + + def test_pfba(self, core_model): + original_objective = core_model.objective + fba_solution = fba(core_model) + fba_flux_sum = sum((abs(val) for val in list(fba_solution.fluxes.values()))) + pfba_solution = pfba(core_model) + pfba_flux_sum = sum((abs(val) for val in list(pfba_solution.fluxes.values()))) + # looks like GLPK finds a parsimonious solution without the flux minimization objective + assert (pfba_flux_sum - fba_flux_sum) < 1e-6, \ + "FBA sum is suppose to be lower than PFBA (was %f)" % (pfba_flux_sum - fba_flux_sum) + assert core_model.objective is original_objective + + def test_pfba_with_reaction_filter(self, core_model): + original_objective = core_model.objective + pfba_solution = pfba(core_model, reactions=['EX_o2_LPAREN_e_RPAREN_', 'EX_glc_LPAREN_e_RPAREN_']) + assert len(pfba_solution.fluxes) == 2 + assert core_model.objective is original_objective + + def test_pfba_ijo1366(self, ijo1366): + original_objective = ijo1366.objective + fba_solution = fba(ijo1366) + fba_flux_sum = sum((abs(val) for val in fba_solution.fluxes.values())) + pfba_solution = pfba(ijo1366) + pfba_flux_sum = sum((abs(val) for val in pfba_solution.fluxes.values())) + assert (pfba_flux_sum - fba_flux_sum) < 1e-6, \ + "FBA sum is suppose to be lower than PFBA (was %f)" % (pfba_flux_sum - fba_flux_sum) + assert ijo1366.objective is original_objective + + def test_lmoma(self, core_model): + original_objective = core_model.objective + pfba_solution = pfba(core_model) + solution = lmoma(core_model, reference=pfba_solution) + distance = sum((abs(solution[v] - pfba_solution[v]) for v in pfba_solution.keys())) + assert abs(0 - distance) < 1e-6, "lmoma distance without knockouts must be 0 (was %f)" % distance + assert core_model.objective is original_objective + + def test_lmoma_change_ref(self, core_model): + original_objective = core_model.objective + pfba_solution = pfba(core_model) + fluxes = {rid: 10 * flux for rid, flux in pfba_solution.items()} + solution = lmoma(core_model, reference=fluxes) + distance = sum((abs(solution[v] - pfba_solution[v]) for v in pfba_solution.keys())) + assert abs(0 - distance) > 1e-6, "lmoma distance without knockouts must be 0 (was %f)" % distance + assert core_model.objective is original_objective + + def test_lmoma_with_reaction_filter(self, core_model): + original_objective = core_model.objective + pfba_solution = pfba(core_model) + solution = lmoma(core_model, reference=pfba_solution, + reactions=['EX_o2_LPAREN_e_RPAREN_', 'EX_glc_LPAREN_e_RPAREN_']) + assert len(solution.fluxes) == 2 + assert core_model.objective is original_objective + + def test_moma(self, core_model): + if current_solver_name(core_model) == 'glpk': + pytest.skip('glpk does not support qp') + original_objective = core_model.objective + pfba_solution = pfba(core_model) + solution = moma(core_model, reference=pfba_solution) + distance = sum((abs(solution[v] - pfba_solution[v]) for v in pfba_solution.keys())) + assert abs(0 - distance) < 1e-6, "moma distance without knockouts must be 0 (was %f)" % distance + assert core_model.objective is original_objective + + def test_room(self, core_model): + original_objective = core_model.objective + pfba_solution = pfba(core_model) + solution = room(core_model, reference=pfba_solution) + assert abs(0 - solution.objective_value) < 1e-6, \ + "room objective without knockouts must be 0 (was %f)" % solution.objective_value + assert core_model.objective is original_objective + + def test_room_with_reaction_filter(self, core_model): + original_objective = core_model.objective + pfba_solution = pfba(core_model) + solution = room(core_model, reference=pfba_solution, + reactions=['EX_o2_LPAREN_e_RPAREN_', 'EX_glc_LPAREN_e_RPAREN_']) + assert len(solution.fluxes) == 2 + assert core_model.objective is original_objective + + def test_room_shlomi_2005(self, toy_model): + original_objective = toy_model.objective + reference = {"b1": 10, "v1": 10, "v2": 5, "v3": 0, "v4": 0, "v5": 0, "v6": 5, "b2": 5, "b3": 5} + expected = {'b1': 10.0, 'b2': 5.0, 'b3': 5.0, 'v1': 10.0, + 'v2': 5.0, 'v3': 0.0, 'v4': 5.0, 'v5': 5.0, 'v6': 0.0} + with TimeMachine() as tm: + toy_model.reactions.v6.knock_out(tm) + result = room(toy_model, reference=reference, delta=0, epsilon=0) + + for k in reference.keys(): + assert abs(expected[k] - result.fluxes[k]) < 0.1, "%s: %f | %f" + assert toy_model.objective is original_objective + + def test_moma_shlomi_2005(self, toy_model): + if current_solver_name(toy_model) == 'glpk': + pytest.skip('glpk does not support qp') + + original_objective = toy_model.objective + reference = {"b1": 10, "v1": 10, "v2": 5, "v3": 0, "v4": 0, "v5": 0, "v6": 5, "b2": 5, "b3": 5} + expected = {'b1': 8.8, 'b2': 4.4, 'b3': 4.4, 'v1': 8.8, + 'v2': 3.1, 'v3': 1.3, 'v4': 4.4, 'v5': 3.1, 'v6': 0.0} + + with TimeMachine() as tm: + toy_model.reactions.v6.knock_out(tm) + result = moma(toy_model, reference=reference) + + for k in reference.keys(): + assert abs(expected[k] - result.fluxes[k]) < 0.1, "%s: %f | %f" + assert toy_model.objective is original_objective + + def test_moma_shlomi_2005_change_ref(self, toy_model): + if current_solver_name(toy_model) == 'glpk': + pytest.skip('glpk does not support qp') + + original_objective = toy_model.objective + reference = {"b1": 10, "v1": 10, "v2": 5, "v3": 0, "v4": 0, "v5": 0, "v6": 5, "b2": 5, "b3": 5} + expected = {'b1': 8.8, 'b2': 4.4, 'b3': 4.4, 'v1': 8.8, + 'v2': 3.1, 'v3': 1.3, 'v4': 4.4, 'v5': 3.1, 'v6': 0.0} + + with TimeMachine() as tm: + toy_model.reactions.v6.knock_out(tm) + result = moma(toy_model, reference=reference) + + for k in reference.keys(): + assert abs(expected[k] - result.fluxes[k]) < 0.1, "%s: %f | %f" + assert toy_model.objective is original_objective + + reference_changed = {"b1": 5, "v1": 5, "v2": 5, "v3": 0, "v4": 0, "v5": 0, "v6": 5, "b2": 5, "b3": 5} + with TimeMachine() as tm: + toy_model.reactions.v6.knock_out(tm) + result_changed = moma(toy_model, reference=reference_changed) + + assert expected != result_changed.fluxes + + +class TestRemoveCycles: + def test_remove_cycles(self, core_model): + with TimeMachine() as tm: + core_model.fix_objective_as_constraint(time_machine=tm) + original_objective = copy.copy(core_model.objective) + core_model.objective = core_model.solver.interface.Objective( + Add(*core_model.solver.variables.values()), name='Max all fluxes') + solution = core_model.solve() + assert abs(solution.data_frame.fluxes.abs().sum() - 2508.293334) < 1e-6 + fluxes = solution.fluxes + core_model.objective = original_objective + clean_fluxes = remove_infeasible_cycles(core_model, fluxes) + assert abs(sum(abs(pandas.Series(clean_fluxes))) - 518.42208550050827) < 1e-6 + + +class TestStructural: + def test_find_blocked_reactions(self, core_model): + assert "PGK" in core_model.reactions + core_model.reactions.PGK.knock_out() # there are no blocked reactions in EcoliCore + blocked_reactions = structural.find_blocked_reactions_nullspace(core_model) + assert len(blocked_reactions) == 0 + + def test_find_dead_end_reactions(self, core_model): + assert len(structural.find_dead_end_reactions(core_model)) == 0 + met1 = cameo.Metabolite("fake_metabolite_1") + met2 = cameo.Metabolite("fake_metabolite_2") + reac = cameo.Reaction("fake_reac") + reac.add_metabolites({met1: -1, met2: 1}) + core_model.add_reaction(reac) + assert structural.find_dead_end_reactions(core_model) == {reac} + + def test_find_coupled_reactions(self, core_model): + couples = structural.find_coupled_reactions(core_model) + fluxes = core_model.solve().fluxes + for coupled_set in couples: + coupled_set = list(coupled_set) + assert round(abs(fluxes[coupled_set[0].id] - fluxes[coupled_set[1].id]), 7) == 0 + + couples, blocked = structural.find_coupled_reactions(core_model, return_dead_ends=True) + assert blocked == structural.find_dead_end_reactions(core_model) + + @pytest.mark.skipif(TRAVIS, reason="ShortestElementaryFluxModes needs refactor") + def test_shortest_elementary_flux_modes(self, core_model): + if current_solver_name(core_model) == 'glpk': + pytest.skip('sefm not supported for glpk') + sefm = structural.ShortestElementaryFluxModes(core_model) + ems = [] + for i, em in enumerate(sefm): + if i > 10: + break + ems.append(em) + assert list(map(len, ems)) == sorted(map(len, ems)) + + def test_dead_end_metabolites_are_in_dead_end_reactions(self, core_model): + dead_end_reactions = structural.find_dead_end_reactions(core_model) + dead_end_metabolites = {m for m in core_model.metabolites if len(m.reactions) == 1} + for dead_end_metabolite in dead_end_metabolites: + assert any(dead_end_metabolite in r.metabolites for r in dead_end_reactions) + + def test_coupled_reactions(self, core_model): + # If a reaction is essential, all coupled reactions are essential + essential_reactions = core_model.essential_reactions() + coupled_reactions = structural.find_coupled_reactions_nullspace(core_model) + for essential_reaction in essential_reactions: + for group in coupled_reactions: + assert isinstance(group, frozenset) + if essential_reaction in group: + assert all(group_reaction in essential_reactions for group_reaction in group) + + # # FIXME: this test has everything to run, but sometimes removing the reactions doesn't seem to work. + # @pytest.mark.skipif(TRAVIS, reason="Inconsistent behaviour (bug)") + def test_reactions_in_group_become_blocked_if_one_is_removed(self, core_model): + essential_reactions = core_model.essential_reactions() + coupled_reactions = structural.find_coupled_reactions_nullspace(core_model) + for group in coupled_reactions: + representative = pick_one(group) + if representative not in essential_reactions: + with TimeMachine() as tm: + assert core_model == representative.model + tm(do=partial(core_model.remove_reactions, [representative], delete=False), + undo=partial(core_model.add_reactions, [representative])) + # # FIXME: Hack because of optlang queue issues with GLPK + # core_model.solver.update() + assert representative not in core_model.reactions + assert representative.forward_variable not in core_model.solver.variables + assert representative.reverse_variable not in core_model.solver.variables + assert representative not in core_model.reactions + assert representative.model is None + blocked_reactions = find_blocked_reactions(core_model) + assert all(r in blocked_reactions for r in group if r != representative) + assert representative in core_model.reactions + + coupled_reactions = structural.find_coupled_reactions(core_model) + for group in coupled_reactions: + representative = pick_one(group) + if representative not in essential_reactions: + with TimeMachine() as tm: + fwd_var_name = representative.forward_variable.name + rev_var_name = representative.reverse_variable.name + assert core_model == representative.model + tm(do=partial(core_model.remove_reactions, [representative], delete=False), + undo=partial(core_model.add_reactions, [representative])) + # # FIXME: Hack because of optlang queue issues with GLPK + # core_model.solver.update() + assert representative not in core_model.reactions + assert fwd_var_name not in core_model.solver.variables + assert rev_var_name not in core_model.solver.variables + assert representative not in core_model.reactions + assert representative.model is None + blocked_reactions = find_blocked_reactions(core_model) + assert representative not in core_model.reactions + assert all(r in blocked_reactions for r in group if r != representative) + assert representative in core_model.reactions + + +class TestNullSpace: def test_wikipedia_toy(self): - A = np.array([[2, 3, 5], [-4, 2, 3]]) - NS = nullspace(A) - self.assertAlmostEqual(np.dot(NS.T, A[0])[0], 0, places=10) - self.assertAlmostEqual(np.dot(NS.T, A[1])[0], 0, places=10) - - def test_with_core_model(self): - S = self.ecoli_core.S - NS = nullspace(S) - self.assertAlmostEqual(np.abs(S.dot(NS)).max(), 0, places=10) - - -if __name__ == '__main__': - import nose - - nose.runmodule() + a = np.array([[2, 3, 5], [-4, 2, 3]]) + ns = nullspace(a) + assert round(abs(np.dot(ns.T, a[0])[0] - 0), 10) == 0 + assert round(abs(np.dot(ns.T, a[1])[0] - 0), 10) == 0 + + def test_with_core_model(self, core_model): + s = core_model.S + ns = nullspace(s) + assert round(abs(np.abs(s.dot(ns)).max() - 0), 10) == 0 diff --git a/tests/test_io.py b/tests/test_io.py index f831394bc..8feeb3a57 100644 --- a/tests/test_io.py +++ b/tests/test_io.py @@ -15,72 +15,61 @@ from __future__ import absolute_import, print_function import os -import unittest import cobra -import optlang +import pytest import cameo -from cameo.config import solvers from cameo import load_model +from cameo.config import solvers + +try: + import libsbml +except ImportError: + libsbml = None TESTDIR = os.path.dirname(__file__) -TRAVIS = os.getenv('TRAVIS', False) -class AbstractTestModelLoading(object): - def test_load_model_pickle_path(self): - model = load_model(os.path.join(TESTDIR, 'data/iJO1366.pickle'), solver_interface=self.interface) - self.assertAlmostEqual(model.optimize().f, 0.9823718127269768, places=6) +@pytest.fixture(scope="module", params=list(solvers)) +def solver_interface(request): + return solvers[request.param] + - def test_load_model_pickle_handle(self): +class TestModelLoading(object): + def test_load_model_pickle_path(self, solver_interface): + model = load_model(os.path.join(TESTDIR, 'data/iJO1366.pickle'), solver_interface=solver_interface) + assert abs(model.optimize().f - 0.9823718127269768) < 10e-6 + + def test_load_model_pickle_handle(self, solver_interface): with open(os.path.join(TESTDIR, 'data/iJO1366.pickle'), 'rb') as handle: - model = load_model(handle, solver_interface=self.interface) - self.assertAlmostEqual(model.optimize().f, 0.9823718127269768, places=6) + model = load_model(handle, solver_interface=solver_interface) + assert abs(model.optimize().f - 0.9823718127269768) < 10e-6 - # @unittest.skipIf(six.PY3, 'cobra.io.read_sbml_model broken in py3.') - def test_load_model_sbml_path(self): - model = load_model(os.path.join(TESTDIR, 'data/iJO1366.xml'), solver_interface=self.interface) - self.assertAlmostEqual(model.optimize().f, 0.9823718127269768, places=6) + def test_load_model_sbml_path(self, solver_interface): + model = load_model(os.path.join(TESTDIR, 'data/iJO1366.xml'), solver_interface=solver_interface) + assert abs(model.optimize().f - 0.9823718127269768) < 10e-6 - # @unittest.skipIf(six.PY3, 'cobra.io.read_sbml_model broken in py3.') - def test_load_model_sbml_handle(self): + def test_load_model_sbml_handle(self, solver_interface): with open(os.path.join(TESTDIR, 'data/iJO1366.xml')) as handle: - model = load_model(handle, solver_interface=self.interface) - self.assertAlmostEqual(model.optimize().f, 0.9823718127269768, places=6) + model = load_model(handle, solver_interface=solver_interface) + assert abs(model.optimize().f - 0.9823718127269768) < 10e-6 - # @unittest.skipIf(six.PY3, 'cobra.io.read_sbml_model broken in py3.') def test_load_model_sbml_path_set_none_interface(self): model = load_model(os.path.join(TESTDIR, 'data/EcoliCore.xml'), solver_interface=None) - self.assertAlmostEqual(model.optimize().f, 0.8739215069684306, places=6) - self.assertTrue(isinstance(model, cobra.core.Model)) - self.assertFalse(hasattr(model, 'solver')) + assert abs(model.optimize().f - 0.8739215069684306) < 10e-6 + assert isinstance(model, cobra.core.Model) + assert not hasattr(model, 'solver') def test_import_model_bigg(self): model = cameo.models.bigg.e_coli_core - self.assertEqual(model.id, 'e_coli_core') + assert model.id == 'e_coli_core' + @pytest.mark.skipif(libsbml is None, reason="minho has fbc < 2, requiring missing lisbml") def test_import_model_minho(self): - model = cameo.models.bigg.e_coli_core - self.assertEqual(model.id, 'e_coli_core') + model = cameo.models.minho.__getattr__('Ecoli core Model') + assert model.id == 'Ecoli_core_model' def test_invalid_path(self): - self.assertRaises(Exception, load_model, "blablabla_model") - - -class TestModelLoadingGLPK(AbstractTestModelLoading, unittest.TestCase): - def setUp(self): - self.interface = optlang.glpk_interface - - -# @unittest.skipIf(six.PY2, 'Build stalling in python 2.7.') -@unittest.skipIf('cplex' not in solvers, "No cplex interface available") -class TestModelLoadingCPLEX(AbstractTestModelLoading, unittest.TestCase): - def setUp(self): - self.interface = optlang.cplex_interface - - -if __name__ == '__main__': - import nose - - nose.runmodule() + with pytest.raises(Exception): + load_model("blablabla_model") diff --git a/tests/test_network_analysis.py b/tests/test_network_analysis.py index 884f8a51e..2c6bd7124 100644 --- a/tests/test_network_analysis.py +++ b/tests/test_network_analysis.py @@ -13,53 +13,38 @@ # See the License for the specific language governing permissions and # limitations under the License. -from cobra.test import create_test_model -from nose.tools import assert_equal, assert_in, assert_not_in +from cameo.network_analysis import (model_to_network, reactions_to_network, + remove_highly_connected_nodes) +from cameo.network_analysis.util import \ + distance_based_on_molecular_formula as dbmf -from cameo.network_analysis import model_to_network, reactions_to_network, remove_highly_connected_nodes -from cameo.network_analysis.util import distance_based_on_molecular_formula -TEST_MODEL = create_test_model() +def test_distance_based_on_molecular_formula(salmonella): + assert dbmf(salmonella.metabolites[0], salmonella.metabolites[0], normalize=False) == 0 + assert dbmf(salmonella.metabolites[0], salmonella.metabolites[0], normalize=True) == 0 + assert dbmf(salmonella.metabolites[0], salmonella.metabolites[1], normalize=False) == 58.0 + assert dbmf(salmonella.metabolites[0], salmonella.metabolites[1], normalize=True) == 0.6590909090909091 -def test_distance_based_on_molecular_formula(): - assert_equal( - distance_based_on_molecular_formula(TEST_MODEL.metabolites[0], TEST_MODEL.metabolites[0], normalize=False), 0) - assert_equal( - distance_based_on_molecular_formula(TEST_MODEL.metabolites[0], TEST_MODEL.metabolites[0], normalize=True), 0) - assert_equal( - distance_based_on_molecular_formula(TEST_MODEL.metabolites[0], TEST_MODEL.metabolites[1], normalize=False), - 58.0) - assert_equal( - distance_based_on_molecular_formula(TEST_MODEL.metabolites[0], TEST_MODEL.metabolites[1], normalize=True), - 0.6590909090909091) - - -def test_model_to_network(): - assert_equal(model_to_network(TEST_MODEL).edges(), reactions_to_network(TEST_MODEL.reactions).edges()) - network = model_to_network(TEST_MODEL) - assert_equal(len(network.nodes()), 1761) - assert_equal(len(network.edges()), 4924) - network = model_to_network(TEST_MODEL, max_distance=1.) +def test_model_to_network(salmonella): + assert model_to_network(salmonella).edges() == reactions_to_network(salmonella.reactions).edges() + network = model_to_network(salmonella) + assert len(network.nodes()) == 1761 + assert len(network.edges()) == 4924 + network = model_to_network(salmonella, max_distance=1.) # nodes = network.nodes() - # print(set(nodes).difference(set(TEST_MODEL.metabolites))) - # print(set(TEST_MODEL.metabolites).difference(set(nodes))) - assert_equal(len(network.nodes()), 1800) - assert_equal(len(network.edges()), 12853) - - -def test_remove_highly_connected_nodes(): - network = model_to_network(TEST_MODEL) - assert_in(TEST_MODEL.metabolites.atp_c, network.nodes()) - assert_in(TEST_MODEL.metabolites.adp_c, network.nodes()) - remove_highly_connected_nodes(network, max_degree=10, ignore=[TEST_MODEL.metabolites.atp_c]) - assert_equal(len(network.nodes()), 1671) - assert_equal(len(network.edges()), 2342) - assert_in(TEST_MODEL.metabolites.atp_c, network.nodes()) - assert_not_in(TEST_MODEL.metabolites.adp_c, network.nodes()) - - -if __name__ == '__main__': - import nose - - nose.runmodule() + # print(set(nodes).difference(set(core_model_one.metabolites))) + # print(set(core_model_one.metabolites).difference(set(nodes))) + assert len(network.nodes()) == 1800 + assert len(network.edges()) == 12853 + + +def test_remove_highly_connected_nodes(salmonella): + network = model_to_network(salmonella) + assert salmonella.metabolites.atp_c in network.nodes() + assert salmonella.metabolites.adp_c in network.nodes() + remove_highly_connected_nodes(network, max_degree=10, ignore=[salmonella.metabolites.atp_c]) + assert len(network.nodes()) == 1671 + assert len(network.edges()) == 2342 + assert salmonella.metabolites.atp_c in network.nodes() + assert salmonella.metabolites.adp_c not in network.nodes() diff --git a/tests/test_parallel.py b/tests/test_parallel.py index 03566ffcc..8a31b45d6 100644 --- a/tests/test_parallel.py +++ b/tests/test_parallel.py @@ -15,13 +15,27 @@ from __future__ import absolute_import, print_function import os -import unittest import warnings from multiprocessing import cpu_count -import six.moves.queue +import pytest +import six.moves.queue from cameo.parallel import SequentialView +from six.moves import range + +views = [SequentialView()] + +try: + from cameo.parallel import MultiprocessingView + views.append(MultiprocessingView()) +except ImportError: + MultiprocessingView = None + +try: + from cameo.parallel import RedisQueue +except ImportError: + RedisQueue = None try: with warnings.catch_warnings(): @@ -33,12 +47,9 @@ except ImportError: def interactive(f): return f -from six.moves import range SOLUTION = [x ** 2 for x in range(100)] -TRAVIS = os.getenv('TRAVIS', False) -SKIP_PARALLEL = TRAVIS if os.getenv('REDIS_PORT_6379_TCP_ADDR'): REDIS_HOST = os.getenv('REDIS_PORT_6379_TCP_ADDR') # wercker @@ -55,151 +66,94 @@ def to_the_power_of_2(arg): return arg ** 2 -class TestSequentialView(unittest.TestCase): - def setUp(self): - self.view = SequentialView() +class TestView: + @pytest.mark.parametrize('view', views) + def test_map(self, view): + assert view.map(to_the_power_of_2, list(range(100))) == SOLUTION - def test_map(self): - self.assertEqual(self.view.map(to_the_power_of_2, list(range(100))), SOLUTION) - - def test_apply(self): + @pytest.mark.parametrize('view', views) + def test_apply(self, view): for i in range(100): - self.assertEqual(self.view.apply(to_the_power_of_2, i), SOLUTION[i]) - - -try: - from cameo.parallel import MultiprocessingView - - - @unittest.skipIf(SKIP_PARALLEL, "This is Travis") - class TestMultiprocessingView(unittest.TestCase): - def setUp(self): - self.view = MultiprocessingView() + assert view.apply(to_the_power_of_2, i) == SOLUTION[i] - def test_map(self): - self.assertEqual(self.view.map(to_the_power_of_2, list(range(100))), SOLUTION) + @pytest.mark.skipif(not MultiprocessingView, reason="no multiprocessing available") + def test_length(self): + view = MultiprocessingView() + assert len(view) == cpu_count() - def test_apply(self): - for i in range(100): - self.assertEqual(self.view.apply(to_the_power_of_2, i), SOLUTION[i]) + view = MultiprocessingView(4) + assert len(view) == 4 - def test_length(self): - self.assertEqual(len(self.view), cpu_count()) + view = MultiprocessingView(processes=3) + assert len(view) == 3 - view = MultiprocessingView(4) - self.assertEqual(len(view), 4) - view = MultiprocessingView(processes=3) - self.assertEqual(len(view), 3) +@pytest.mark.skipif(not RedisQueue, reason='no redis queue available') +class TestRedisQueue: -except ImportError: - print("Skipping MultiprocessingView tests ...") - -try: - from cameo.parallel import RedisQueue - - - class TestRedisQueue(unittest.TestCase): - def test_queue_size(self): - print(REDIS_HOST) - print(os.getenv('REDIS_PORT_6379_TCP_ADDR')) - queue = RedisQueue("test-queue-size-1", maxsize=1, host=REDIS_HOST) + def test_queue_size(self): + print(REDIS_HOST) + print(os.getenv('REDIS_PORT_6379_TCP_ADDR')) + queue = RedisQueue("test-queue-size-1", maxsize=1, host=REDIS_HOST) + queue.put(1) + with pytest.raises(six.moves.queue.Full): queue.put(1) - self.assertRaises(six.moves.queue.Full, queue.put, 1) - queue = RedisQueue("test-queue-size-2", maxsize=2, host=REDIS_HOST) - queue.put(1) - queue.put(1) - self.assertRaises(six.moves.queue.Full, queue.put, 1) - queue.get() - queue.get() - self.assertRaises(six.moves.queue.Empty, queue.get_nowait) - - def test_queue_objects(self): - queue = RedisQueue("test-queue", maxsize=100, host=REDIS_HOST) - # put int - queue.put(1) - v = queue.get_nowait() - self.assertEqual(v, 1) - self.assertIsInstance(v, int) - - # put str - queue.put("a") - v = queue.get_nowait() - self.assertEqual(v, "a") - self.assertIsInstance(v, str) - - # put float - queue.put(1.) - - v = queue.get_nowait() - self.assertEqual(v, 1.) - self.assertIsInstance(v, float) - - # put list - queue.put([1, 3, 4, 5, "a", "b", "c", 1., 2., 3.]) - v = queue.get_nowait() - self.assertEqual(v, [1, 3, 4, 5, "a", "b", "c", 1., 2., 3.]) - self.assertIsInstance(v, list) - - # put dict - queue.put({"x": "y"}) - v = queue.get_nowait() - self.assertEqual(v, {"x": "y"}) - self.assertIsInstance(v, dict) - - def test_queue_len(self): - queue = RedisQueue("test-queue-len", maxsize=100, host=REDIS_HOST) - self.assertEqual(queue.length, 0) - queue.put(1) - self.assertEqual(queue.length, 1) + queue = RedisQueue("test-queue-size-2", maxsize=2, host=REDIS_HOST) + queue.put(1) + queue.put(1) + with pytest.raises(six.moves.queue.Full): queue.put(1) - self.assertEqual(queue.length, 2) - queue.put(1) - self.assertEqual(queue.length, 3) - queue.get_nowait() - self.assertEqual(queue.length, 2) - queue.get_nowait() - self.assertEqual(queue.length, 1) + queue.get() + queue.get() + with pytest.raises(six.moves.queue.Empty): queue.get_nowait() - self.assertEqual(queue.length, 0) - -except ImportError: - print("Skipping MultiprocessingView tests ...") - -# class TestIPythonParallelView(unittest.TestCase): -# def setUp(self): -# try: -# subprocess.check_output(["ipcluster", "start", "--daemonize"]) -# sleep(6) -# except subprocess.CalledProcessError: -# subprocess.check_output(["ipcluster", "stop"]) -# subprocess.check_output(["ipcluster", "start", "--daemonize"]) -# sleep(6) -# -# def test_map_load_balanced_view(self): -# client = Client() -# self.view = client.load_balanced_view() -# self.view.block = False -# self.assertEqual(self.view.map_sync(to_the_power_of_2_interactive, range(100)), -# map(lambda x: x ** 2, range(100))) -# -# def test_map_direct_view(self): -# client = Client() -# self.view = client.direct_view() -# self.view.block = False -# self.assertEqual(self.view.map_sync(to_the_power_of_2_interactive, range(100)), -# map(lambda x: x ** 2, range(100)) -# -# def tearDown(self): -# try: -# subprocess.check_output(["ipcluster", "stop"]) -# sleep(4) -# except subprocess.CalledProcessError: -# pass - - -if __name__ == '__main__': - import nose - nose.runmodule() + def test_queue_objects(self): + queue = RedisQueue("test-queue", maxsize=100, host=REDIS_HOST) + # put int + queue.put(1) + v = queue.get_nowait() + assert v == 1 + assert isinstance(v, int) + + # put str + queue.put("a") + v = queue.get_nowait() + assert v == "a" + assert isinstance(v, str) + + # put float + queue.put(1.) + + v = queue.get_nowait() + assert v == 1. + assert isinstance(v, float) + + # put list + queue.put([1, 3, 4, 5, "a", "b", "c", 1., 2., 3.]) + v = queue.get_nowait() + assert v == [1, 3, 4, 5, "a", "b", "c", 1., 2., 3.] + assert isinstance(v, list) + + # put dict + queue.put({"x": "y"}) + v = queue.get_nowait() + assert v == {"x": "y"} + assert isinstance(v, dict) + + def test_queue_len(self): + queue = RedisQueue("test-queue-len", maxsize=100, host=REDIS_HOST) + assert queue.length == 0 + queue.put(1) + assert queue.length == 1 + queue.put(1) + assert queue.length == 2 + queue.put(1) + assert queue.length == 3 + queue.get_nowait() + assert queue.length == 2 + queue.get_nowait() + assert queue.length == 1 + queue.get_nowait() + assert queue.length == 0 diff --git a/tests/test_pathway_predictions.py b/tests/test_pathway_predictions.py index 4f0c96139..8e918e4bc 100644 --- a/tests/test_pathway_predictions.py +++ b/tests/test_pathway_predictions.py @@ -14,117 +14,112 @@ from __future__ import absolute_import, print_function -import os -import unittest +import re +from os.path import join + +import pytest from cameo import load_model from cameo.config import solvers from cameo.core.pathway import Pathway from cameo.flux_analysis.analysis import PhenotypicPhasePlaneResult from cameo.strain_design.pathway_prediction import PathwayPredictor -from cameo.strain_design.pathway_prediction.pathway_predictor import PathwayResult +from cameo.strain_design.pathway_prediction.pathway_predictor import \ + PathwayResult from cameo.util import TimeMachine -TESTDIR = os.path.dirname(__file__) -TESTMODEL = load_model(os.path.join(TESTDIR, 'data/EcoliCore.xml')) -UNIVERSALMODEL = load_model(os.path.join(TESTDIR, 'data/iJO1366.xml')) -UNIVERSALMODEL.remove_reactions(UNIVERSALMODEL.exchanges) - -TRAVIS = os.getenv('TRAVIS', False) - -PATHWAYPREDICTOR = PathwayPredictor(TESTMODEL, universal_model=UNIVERSALMODEL) - - -class Wrapper: - class AbstractPathwayPredictorTestCase(unittest.TestCase): - - def test_setting_incorrect_universal_model_raises(self): - with self.assertRaisesRegexp(ValueError, 'Provided universal_model.*'): - PathwayPredictor(TESTMODEL, universal_model='Mickey_Mouse') - - # def test_predict_native_compound_returns_shorter_alternatives(self): - # result = self.pathway_predictor.run(product='Phosphoenolpyruvate', max_predictions=1) - # self.assertTrue(len(result.pathways) == 1) - # self.assertTrue(len(result.pathways[0].pathway) == 3) - # self.assertTrue(len(result.pathways[0].adapters) == 0) - - def test_predict_non_native_compound(self): - result = self.pathway_predictor.run(product='L-Serine', max_predictions=1) - self.assertTrue(len(result) == 1) - self.assertTrue(len(result.pathways) == 1) - self.assertTrue(len(result.pathways[0].reactions) == 3) - self.assertTrue(len(result.pathways[0].adapters) == 0) - - def test_contains_right_adapters_and_exchanges(self): - result = self.pathway_predictor.run(product='L-Serine', max_predictions=1) - for pathway in result: - for reaction in pathway.reactions: - for metabolite in reaction.metabolites: - try: - met = TESTMODEL.metabolites.get_by_id(metabolite.id) - except KeyError: - metabolite_ids = [met.id in adapter for adapter in pathway.adapters - for met in adapter.metabolites] - - metabolite_ids += [met.id in exchange for exchange in pathway.exchanges - for met in exchange.metabolites] - for r in pathway.reactions: - if r != reaction: - metabolite_ids += [met.id for met in r.metabolites] - - metabolite_ids += [met.id for met in pathway.product.metabolites] - - self.assertTrue(metabolite.id in metabolite_ids) - - -@unittest.skipIf('cplex' not in solvers, "No cplex interface available") -class PathwayPredictorCPLEXTestCase(Wrapper.AbstractPathwayPredictorTestCase): - def setUp(self): - TESTMODEL.solver = "cplex" - self.pathway_predictor = PathwayPredictor(TESTMODEL, universal_model=UNIVERSALMODEL) - - -class PathwayPredictorGLPKTestCase(Wrapper.AbstractPathwayPredictorTestCase): - def setUp(self): - TESTMODEL.solver = "glpk" - self.pathway_predictor = PathwayPredictor(TESTMODEL, universal_model=UNIVERSALMODEL) - - -class PathwayPredictionsTestCase(unittest.TestCase): - def setUp(self): - self.result = PATHWAYPREDICTOR.run(product='L-Serine', max_predictions=1) - def test_pathway(self): - model = TESTMODEL.copy() - pathway = self.result[0] +@pytest.fixture(scope="module", params=list(solvers)) +def pathway_predictor(request, data_directory, universal_model): + core_model = load_model(join(data_directory, 'EcoliCore.xml'), sanitize=False) + core_model.solver = request.param + predictor = PathwayPredictor(core_model, universal_model=universal_model) + return core_model, predictor + + +@pytest.fixture(scope="module") +def pathway_predictor_result(pathway_predictor): + core_model, predictor = pathway_predictor + return core_model, predictor.run(product='L-Serine', max_predictions=1) + + +class TestPathwayPredictor: + + def test_setting_incorrect_universal_model_raises(self, pathway_predictor): + model, predictor = pathway_predictor + with pytest.raises(ValueError) as excinfo: + PathwayPredictor(model, universal_model='Mickey_Mouse') + assert re.search(r'Provided universal_model.*', str(excinfo.value)) + + # def test_predict_native_compound_returns_shorter_alternatives(self): + # result = self.pathway_predictor.run(product='Phosphoenolpyruvate', max_predictions=1) + # self.assertTrue(len(result.pathways) == 1) + # self.assertTrue(len(result.pathways[0].pathway) == 3) + # self.assertTrue(len(result.pathways[0].adapters) == 0) + + def test_predict_non_native_compound(self, pathway_predictor): + model, predictor = pathway_predictor + result = predictor.run(product='L-Serine', max_predictions=1) + assert len(result) == 1 + assert len(result.pathways) == 1 + assert len(result.pathways[0].reactions) == 3 + assert len(result.pathways[0].adapters) == 0 + + def test_contains_right_adapters_and_exchanges(self, pathway_predictor): + model, predictor = pathway_predictor + result = predictor.run(product='L-Serine', max_predictions=1) + for pathway in result: + for reaction in pathway.reactions: + for metabolite in reaction.metabolites: + try: + model.metabolites.get_by_id(metabolite.id) + except KeyError: + metabolite_ids = [met.id in adapter for adapter in pathway.adapters + for met in adapter.metabolites] + + metabolite_ids += [met.id in exchange for exchange in pathway.exchanges + for met in exchange.metabolites] + for r in pathway.reactions: + if r != reaction: + metabolite_ids += [met.id for met in r.metabolites] + + metabolite_ids += [met.id for met in pathway.product.metabolites] + + assert metabolite.id in metabolite_ids + + +class PathwayPredictionsTestCase: + def test_pathway(self, pathway_predictor_result): + model, result = pathway_predictor_result + pathway = result[0] biomass = 'Biomass_Ecoli_core_N_lp_w_fsh_GAM_rp__Nmet2' - self.assertIsInstance(pathway, PathwayResult) - self.assertIsInstance(pathway, Pathway) - self.assertIsInstance(pathway.production_envelope(model, objective=biomass), PhenotypicPhasePlaneResult) - self.assertTrue(pathway.needs_optimization(model, objective=biomass)) + assert isinstance(pathway, PathwayResult) + assert isinstance(pathway, Pathway) + assert isinstance(pathway.production_envelope(model, objective=biomass), PhenotypicPhasePlaneResult) + assert pathway.needs_optimization(model, objective=biomass) - def test_plug_model_without_time_machine(self): - model = TESTMODEL.copy() - self.result[0].plug_model(model) - for reaction in self.result[0].reactions: - self.assertIn(reaction, model.reactions) + def test_plug_model_without_time_machine(self, pathway_predictor_result): + model, result = pathway_predictor_result + result[0].plug_model(model) + for reaction in result[0].reactions: + assert reaction in model.reactions - for reaction in self.result[0].exchanges: - self.assertIn(reaction, model.reactions) + for reaction in result[0].exchanges: + assert reaction in model.reactions - for reaction in self.result[0].adapters: - self.assertIn(reaction, model.reactions) + for reaction in result[0].adapters: + assert reaction in model.reactions - def test_plug_model_with_time_machine(self): - model = TESTMODEL.copy() + def test_plug_model_with_time_machine(self, pathway_predictor_result): + model, result = pathway_predictor_result with TimeMachine() as tm: - self.result[0].plug_model(model, tm=tm) + result[0].plug_model(model, tm=tm) - for reaction in self.result[0].reactions: - self.assertNotIn(reaction, model.reactions) + for reaction in result[0].reactions: + assert reaction not in model.reactions - for reaction in self.result[0].exchanges: - self.assertNotIn(reaction, model.reactions) + for reaction in result[0].exchanges: + assert reaction not in model.reactions - for reaction in self.result[0].adapters: - self.assertNotIn(reaction, model.reactions) \ No newline at end of file + for reaction in result[0].adapters: + assert reaction not in model.reactions diff --git a/tests/test_solver_based_model.py b/tests/test_solver_based_model.py index 7ec770b8d..5b3589973 100644 --- a/tests/test_solver_based_model.py +++ b/tests/test_solver_based_model.py @@ -19,17 +19,16 @@ import copy import os import pickle -import unittest -import cobra +import cobra.test import numpy import optlang import pandas +import pytest import six -from cobra.io import read_sbml_model import cameo -from cameo import load_model, Model +from cameo import Model, load_model from cameo.config import solvers from cameo.core.gene import Gene from cameo.core.metabolite import Metabolite @@ -38,12 +37,10 @@ from cameo.flux_analysis.structural import create_stoichiometric_array from cameo.util import TimeMachine -TRAVIS = os.getenv('TRAVIS', False) +TRAVIS = bool(os.getenv('TRAVIS', False)) TESTDIR = os.path.dirname(__file__) REFERENCE_FVA_SOLUTION_ECOLI_CORE = pandas.read_csv(os.path.join(TESTDIR, 'data/REFERENCE_flux_ranges_EcoliCore.csv'), index_col=0) -TESTMODEL = load_model(os.path.join(TESTDIR, 'data/EcoliCore.xml'), sanitize=False) -COBRAPYTESTMODEL = read_sbml_model(os.path.join(TESTDIR, 'data/EcoliCore.xml')) ESSENTIAL_GENES = ['b2779', 'b1779', 'b0720', 'b0451', 'b2416', 'b2926', 'b1136', 'b2415'] ESSENTIAL_METABOLITES = ['13dpg_c', '2pg_c', '3pg_c', 'accoa_c', 'acon_DASH_C_c', 'adp_c', 'akg_c', 'atp_c', 'cit_c', 'coa_c', 'e4p_c', 'f6p_c', 'g3p_c', 'g6p_c', 'glc_DASH_D_e', 'gln_DASH_L_c', 'glu_DASH_L_c', @@ -54,1118 +51,1006 @@ 'CS', 'NH4t', 'GLCpts', 'PGM', 'EX_pi_LPAREN_e_RPAREN_', 'PGK', 'RPI', 'ACONTa'] -class WrappedCommonGround: - class CommonGround(unittest.TestCase): - def setUp(self): - self.model = TESTMODEL.copy() - self.model.optimize() - - -class AbstractTestLazySolution(WrappedCommonGround.CommonGround): - def setUp(self): - super(AbstractTestLazySolution, self).setUp() - self.solution = self.model.optimize() - - def test_self_invalidation(self): - solution = self.model.solve() - self.assertAlmostEqual(solution.f, 0.873921506968431, delta=0.000001) - self.model.optimize() - self.assertRaises(UndefinedSolution, getattr, solution, 'f') - - def test_solution_contains_only_reaction_specific_values(self): - reaction_ids = set([reaction.id for reaction in self.model.reactions]) - self.assertEqual(set(self.solution.x_dict.keys()).difference(reaction_ids), set()) - self.assertEqual(set(self.solution.y_dict.keys()).difference(reaction_ids), set()) - self.assertEqual(set(self.solution.reduced_costs.keys()).difference(reaction_ids), set()) - metabolite_ids = set([metabolite.id for metabolite in self.model.metabolites]) - self.assertEqual(set(self.solution.shadow_prices.keys()).difference(metabolite_ids), set()) - - -class TestLazySolutionGLPK(AbstractTestLazySolution): - def setUp(self): - super(TestLazySolutionGLPK, self).setUp() - self.model.solver = 'glpk' - self.solution = self.model.optimize() - - -@unittest.skipIf('cplex' not in solvers, "No cplex interface available") -class TestLazySolutionCPLEX(AbstractTestLazySolution): - def setUp(self): - super(TestLazySolutionCPLEX, self).setUp() - self.model.solver = 'cplex' - self.solution = self.model.optimize() - - -class WrappedAbstractTestReaction: - class AbstractTestReaction(unittest.TestCase): - - def test_clone_cobrapy_reaction(self): - for reaction in self.cobrapy_model.reactions: - cloned_reaction = Reaction.clone(reaction) - self.assertEqual(cloned_reaction.objective_coefficient, reaction.objective_coefficient) - self.assertEqual(cloned_reaction.gene_reaction_rule, reaction.gene_reaction_rule) - self.assertSetEqual(set([gene.id for gene in cloned_reaction.genes]), - set([gene.id for gene in reaction.genes])) - self.assertTrue(all(isinstance(gene, cameo.core.Gene) for gene in list(cloned_reaction.genes))) - self.assertSetEqual({metabolite.id for metabolite in cloned_reaction.metabolites}, - {metabolite.id for metabolite in reaction.metabolites}) - self.assertTrue( - all(isinstance(metabolite, cameo.core.Metabolite) for metabolite in cloned_reaction.metabolites)) - self.assertSetEqual({metabolite.id for metabolite in cloned_reaction.products}, - {metabolite.id for metabolite in reaction.products}) - self.assertSetEqual({metabolite.id for metabolite in cloned_reaction.reactants}, - {metabolite.id for metabolite in reaction.reactants}) - - self.assertEqual(reaction.id, cloned_reaction.id) - self.assertEqual(reaction.name, cloned_reaction.name) - self.assertEqual(reaction.upper_bound, cloned_reaction.upper_bound) - self.assertEqual(reaction.lower_bound, cloned_reaction.lower_bound) - - def test_gene_reaction_rule_setter(self): - m = self.model.copy() - rxn = Reaction('rxn') - rxn.add_metabolites({Metabolite('A'): -1, - Metabolite('B'): 1}) - rxn.gene_reaction_rule = 'A2B1 or A2B2 and A2B3' - self.assertTrue(hasattr(list(rxn.genes)[0], 'knock_out')) - m.add_reaction(rxn) - with cameo.util.TimeMachine() as tm: - m.genes.A2B1.knock_out(time_machine=tm) - self.assertFalse(m.genes.A2B1.functional) - m.genes.A2B3.knock_out(time_machine=tm) - self.assertFalse(rxn.functional) - self.assertTrue(m.genes.A2B3.functional) - self.assertTrue(rxn.functional) - m.genes.A2B1.knock_out() - self.assertFalse(m.genes.A2B1.functional) - self.assertTrue(m.reactions.rxn.functional) - m.genes.A2B3.knock_out() - self.assertFalse(m.reactions.rxn.functional) - non_functional = [gene.id for gene in m.non_functional_genes] - self.assertTrue(all(gene in non_functional for gene in ['A2B3', 'A2B1'])) - - def test_gene_reaction_rule_setter_reaction_already_added_to_model(self): - m = self.model - rxn = Reaction('rxn') - rxn.add_metabolites({Metabolite('A'): -1, - Metabolite('B'): 1}) - m.add_reaction(rxn) - rxn.gene_reaction_rule = 'A2B' - print(list(rxn.genes)[0], type(list(rxn.genes)[0])) - self.assertTrue(hasattr(list(rxn.genes)[0], 'knock_out')) - tm = cameo.util.TimeMachine() - for gene in m.genes: - try: - gene.knock_out(time_machine=tm) - except AttributeError: - print(gene.id) - except: - pass - - def test_str(self): - self.assertTrue(self.model.reactions[0].__str__().startswith('ACALD')) - - def test_add_metabolite(self): - model = self.model - pgi_reaction = model.reactions.PGI - test_met = model.metabolites[0] - pgi_reaction.add_metabolites({test_met: 42}, combine=False) - self.assertEqual(pgi_reaction.metabolites[test_met], 42) - self.assertEqual( - model.solver.constraints[test_met.id].expression.as_coefficients_dict()[pgi_reaction.forward_variable], - 42) - self.assertEqual( - model.solver.constraints[test_met.id].expression.as_coefficients_dict()[pgi_reaction.reverse_variable], - -42) - - pgi_reaction.add_metabolites({test_met: -10}, combine=True) - self.assertEqual(pgi_reaction.metabolites[test_met], 32) - self.assertEqual( - model.solver.constraints[test_met.id].expression.as_coefficients_dict()[pgi_reaction.forward_variable], - 32) - self.assertEqual( - model.solver.constraints[test_met.id].expression.as_coefficients_dict()[pgi_reaction.reverse_variable], - -32) - - pgi_reaction.add_metabolites({test_met: 0}, combine=False) - self.assertRaises(KeyError, pgi_reaction.metabolites.__getitem__, test_met) - self.assertEqual( - model.solver.constraints[test_met.id].expression.as_coefficients_dict()[pgi_reaction.forward_variable], - 0) - self.assertEqual( - model.solver.constraints[test_met.id].expression.as_coefficients_dict()[pgi_reaction.reverse_variable], - 0) - - # test_met_2 = Metabolite("Test2", compartment="c") - # pgi_reaction.add_metabolites({test_met_2: 43}, combine=False) - # self.assertEqual(pgi_reaction.metabolites[test_met], 43) - # self.assertEqual( - # model.solver.constraints[test_met.id].expression.as_coefficients_dict()[pgi_reaction.forward_variable], 43) - # self.assertEqual( - # model.solver.constraints[test_met.id].expression.as_coefficients_dict()[pgi_reaction.reverse_variable], -43) - - def test_removal_from_model_retains_bounds(self): - model_cp = self.model.copy() - reaction = model_cp.reactions.ACALD - self.assertEqual(reaction.model, model_cp) - self.assertEqual(reaction.lower_bound, -1000.0) - self.assertEqual(reaction.upper_bound, 1000.0) - self.assertEqual(reaction._lower_bound, -1000.0) - self.assertEqual(reaction._upper_bound, 1000.0) - model_cp.remove_reactions([reaction]) - self.assertEqual(reaction.model, None) - self.assertEqual(reaction.lower_bound, -1000.0) - self.assertEqual(reaction.upper_bound, 1000.0) - self.assertEqual(reaction._lower_bound, -1000.0) - self.assertEqual(reaction._upper_bound, 1000.0) - - def test_set_bounds_scenario_1(self): - model = self.model - acald_reaction = model.reactions.ACALD - self.assertEqual(acald_reaction.lower_bound, -1000.) - self.assertEqual(acald_reaction.upper_bound, 1000.) - self.assertEqual(acald_reaction.forward_variable.lb, 0.) - self.assertEqual(acald_reaction.forward_variable.ub, 1000.) - self.assertEqual(acald_reaction.reverse_variable.lb, 0) - self.assertEqual(acald_reaction.reverse_variable.ub, 1000.) - acald_reaction.upper_bound = acald_reaction.lower_bound - 100 - self.assertEqual(acald_reaction.lower_bound, -1100.0) - self.assertEqual(acald_reaction.upper_bound, -1100.0) - self.assertEqual(acald_reaction.forward_variable.lb, 0) - self.assertEqual(acald_reaction.forward_variable.ub, 0) - self.assertEqual(acald_reaction.reverse_variable.lb, 1100.) - self.assertEqual(acald_reaction.reverse_variable.ub, 1100.) - acald_reaction.upper_bound = 100 - self.assertEqual(acald_reaction.lower_bound, -1100.0) - self.assertEqual(acald_reaction.upper_bound, 100) - self.assertEqual(acald_reaction.forward_variable.lb, 0) - self.assertEqual(acald_reaction.forward_variable.ub, 100) - self.assertEqual(acald_reaction.reverse_variable.lb, 0) - self.assertEqual(acald_reaction.reverse_variable.ub, 1100.0) - - def test_set_bounds_scenario_3(self): - model = self.model - reac = model.reactions.ACALD - reac.upper_bound = -10 - reac.lower_bound = -10 - self.assertEqual(reac.lower_bound, -10) - self.assertEqual(reac.upper_bound, -10) - reac.lower_bound = -9 - self.assertEqual(reac.lower_bound, -9) - self.assertEqual(reac.upper_bound, -9) - reac.lower_bound = 2 - self.assertEqual(reac.lower_bound, 2) - self.assertEqual(reac.upper_bound, 2) - - reac.upper_bound = -10 - self.assertEqual(reac.lower_bound, -10) - self.assertEqual(reac.upper_bound, -10) - reac.upper_bound = -11 - self.assertEqual(reac.lower_bound, -11) - self.assertEqual(reac.upper_bound, -11) - reac.upper_bound = 2 - self.assertEqual(reac.lower_bound, -11) - self.assertEqual(reac.upper_bound, 2) - - def test_set_bounds_scenario_4(self): - reac = self.model.reactions.ACALD - reac.lower_bound = reac.upper_bound = 0 - reac.lower_bound = 2 - self.assertEqual(reac.lower_bound, 2) - self.assertEqual(reac.upper_bound, 2) - self.assertEqual(reac.forward_variable.lb, 2) - self.assertEqual(reac.forward_variable.ub, 2) - - reac.knock_out() - reac.upper_bound = -2 - self.assertEqual(reac.lower_bound, -2) - self.assertEqual(reac.upper_bound, -2) - self.assertEqual(reac.reverse_variable.lb, 2) - self.assertEqual(reac.reverse_variable.ub, 2) - - def test_set_upper_before_lower_bound_to_0(self): - model = self.model - model.reactions.GAPD.upper_bound = 0 - model.reactions.GAPD.lower_bound = 0 - self.assertEqual(model.reactions.GAPD.lower_bound, 0) - self.assertEqual(model.reactions.GAPD.upper_bound, 0) - self.assertEqual(model.reactions.GAPD.forward_variable.lb, 0) - self.assertEqual(model.reactions.GAPD.forward_variable.ub, 0) - self.assertEqual(model.reactions.GAPD.reverse_variable.lb, 0) - self.assertEqual(model.reactions.GAPD.reverse_variable.ub, 0) - - def test_set_bounds_scenario_2(self): - model = self.model - acald_reaction = model.reactions.ACALD - self.assertEqual(acald_reaction.lower_bound, -1000.) - self.assertEqual(acald_reaction.upper_bound, 1000.) - self.assertEqual(acald_reaction.forward_variable.lb, 0.) - self.assertEqual(acald_reaction.forward_variable.ub, 1000.) - self.assertEqual(acald_reaction.reverse_variable.lb, 0) - self.assertEqual(acald_reaction.reverse_variable.ub, 1000.) - acald_reaction.lower_bound = acald_reaction.upper_bound + 100 - self.assertEqual(acald_reaction.lower_bound, 1100.0) - self.assertEqual(acald_reaction.upper_bound, 1100.0) - self.assertEqual(acald_reaction.forward_variable.lb, 1100.0) - self.assertEqual(acald_reaction.forward_variable.ub, 1100.0) - self.assertEqual(acald_reaction.reverse_variable.lb, 0) - self.assertEqual(acald_reaction.reverse_variable.ub, 0) - acald_reaction.lower_bound = -100 - self.assertEqual(acald_reaction.lower_bound, -100.) - self.assertEqual(acald_reaction.upper_bound, 1100.) - self.assertEqual(acald_reaction.forward_variable.lb, 0) - self.assertEqual(acald_reaction.forward_variable.ub, 1100.) - self.assertEqual(acald_reaction.reverse_variable.lb, 0) - self.assertEqual(acald_reaction.reverse_variable.ub, 100) - - def test_change_bounds(self): - model = self.model - reac = model.reactions.ACALD - reac.change_bounds(lb=2, ub=2) - self.assertEqual(reac.lower_bound, 2) - self.assertEqual(reac.upper_bound, 2) - - with TimeMachine() as tm: - reac.change_bounds(lb=5, time_machine=tm) - self.assertEqual(reac.lower_bound, 5) - self.assertEqual(reac.upper_bound, 5) - self.assertEqual(reac.lower_bound, 2) - self.assertEqual(reac.upper_bound, 2) - - def test_make_irreversible(self): - model = self.model - acald_reaction = model.reactions.ACALD - self.assertEqual(acald_reaction.lower_bound, -1000.) - self.assertEqual(acald_reaction.upper_bound, 1000.) - self.assertEqual(acald_reaction.forward_variable.lb, 0.) - self.assertEqual(acald_reaction.forward_variable.ub, 1000.) - self.assertEqual(acald_reaction.reverse_variable.lb, 0) - self.assertEqual(acald_reaction.reverse_variable.ub, 1000.) - acald_reaction.lower_bound = 0 - self.assertEqual(acald_reaction.lower_bound, 0) - self.assertEqual(acald_reaction.upper_bound, 1000.) - self.assertEqual(acald_reaction.forward_variable.lb, 0) - self.assertEqual(acald_reaction.forward_variable.ub, 1000.0) - self.assertEqual(acald_reaction.reverse_variable.lb, 0) - self.assertEqual(acald_reaction.reverse_variable.ub, 0) - acald_reaction.lower_bound = -100 - self.assertEqual(acald_reaction.lower_bound, -100.) - self.assertEqual(acald_reaction.upper_bound, 1000.) - self.assertEqual(acald_reaction.forward_variable.lb, 0) - self.assertEqual(acald_reaction.forward_variable.ub, 1000.) - self.assertEqual(acald_reaction.reverse_variable.lb, 0) - self.assertEqual(acald_reaction.reverse_variable.ub, 100) - - def test_make_reversible(self): - model = self.model - pfk_reaction = model.reactions.PFK - self.assertEqual(pfk_reaction.lower_bound, 0.) - self.assertEqual(pfk_reaction.upper_bound, 1000.) - self.assertEqual(pfk_reaction.forward_variable.lb, 0.) - self.assertEqual(pfk_reaction.forward_variable.ub, 1000.) - self.assertEqual(pfk_reaction.reverse_variable.lb, 0) - self.assertEqual(pfk_reaction.reverse_variable.ub, 0) - pfk_reaction.lower_bound = -100. - self.assertEqual(pfk_reaction.lower_bound, -100.) - self.assertEqual(pfk_reaction.upper_bound, 1000.) - self.assertEqual(pfk_reaction.forward_variable.lb, 0) - self.assertEqual(pfk_reaction.forward_variable.ub, 1000.0) - self.assertEqual(pfk_reaction.reverse_variable.lb, 0) - self.assertEqual(pfk_reaction.reverse_variable.ub, 100.) - pfk_reaction.lower_bound = 0 - self.assertEqual(pfk_reaction.lower_bound, 0) - self.assertEqual(pfk_reaction.upper_bound, 1000.) - self.assertEqual(pfk_reaction.forward_variable.lb, 0) - self.assertEqual(pfk_reaction.forward_variable.ub, 1000.) - self.assertEqual(pfk_reaction.reverse_variable.lb, 0) - self.assertEqual(pfk_reaction.reverse_variable.ub, 0) - - def test_make_irreversible_irreversible_to_the_other_side(self): - model = self.model - pfk_reaction = model.reactions.PFK - self.assertEqual(pfk_reaction.lower_bound, 0.) - self.assertEqual(pfk_reaction.upper_bound, 1000.) - self.assertEqual(pfk_reaction.forward_variable.lb, 0.) - self.assertEqual(pfk_reaction.forward_variable.ub, 1000.) - self.assertEqual(pfk_reaction.reverse_variable.lb, 0) - self.assertEqual(pfk_reaction.reverse_variable.ub, 0) - pfk_reaction.upper_bound = -100. - self.assertEqual(pfk_reaction.forward_variable.lb, 0) - self.assertEqual(pfk_reaction.forward_variable.ub, 0) - self.assertEqual(pfk_reaction.reverse_variable.lb, 100) - self.assertEqual(pfk_reaction.reverse_variable.ub, 100) - pfk_reaction.lower_bound = -1000. - self.assertEqual(pfk_reaction.lower_bound, -1000.) - self.assertEqual(pfk_reaction.upper_bound, -100.) - self.assertEqual(pfk_reaction.forward_variable.lb, 0) - self.assertEqual(pfk_reaction.forward_variable.ub, 0) - self.assertEqual(pfk_reaction.reverse_variable.lb, 100) - self.assertEqual(pfk_reaction.reverse_variable.ub, 1000.) - # self.assertEqual(pfk_reaction.reverse_variable.lb, 0.) - # self.assertEqual(pfk_reaction.reverse_variable.ub, 0.) - - def test_make_lhs_irreversible_reversible(self): - model = self.model - rxn = Reaction('test') - rxn.add_metabolites({model.metabolites[0]: -1., model.metabolites[1]: 1.}) - rxn.lower_bound = -1000. - rxn.upper_bound = -100 - model.add_reaction(rxn) - self.assertEqual(rxn.lower_bound, -1000.) - self.assertEqual(rxn.upper_bound, -100.) - self.assertEqual(rxn.forward_variable.lb, 0.) - self.assertEqual(rxn.forward_variable.ub, 0.) - self.assertEqual(rxn.reverse_variable.lb, 100.) - self.assertEqual(rxn.reverse_variable.ub, 1000.) - rxn.upper_bound = 666. - self.assertEqual(rxn.lower_bound, -1000.) - self.assertEqual(rxn.upper_bound, 666.) - self.assertEqual(rxn.forward_variable.lb, 0.) - self.assertEqual(rxn.forward_variable.ub, 666) - self.assertEqual(rxn.reverse_variable.lb, 0.) - self.assertEqual(rxn.reverse_variable.ub, 1000.) - - def test_model_less_reaction(self): - # self.model.solver.configuration.verbosity = 3 - self.model.solve() - print(self.model.reactions.ACALD.flux) - for reaction in self.model.reactions: - self.assertTrue(isinstance(reaction.flux, float)) - self.assertTrue(isinstance(reaction.reduced_cost, float)) - for reaction in self.model.reactions: - self.model.remove_reactions([reaction]) - self.assertEqual(reaction.flux, None) - self.assertEqual(reaction.reduced_cost, None) - - def test_knockout(self): - original_bounds = dict() - for reaction in self.model.reactions: - original_bounds[reaction.id] = (reaction.lower_bound, reaction.upper_bound) - reaction.knock_out() - self.assertEqual(reaction.lower_bound, 0) - self.assertEqual(reaction.upper_bound, 0) - for k, (lb, ub) in six.iteritems(original_bounds): - self.model.reactions.get_by_id(k).lower_bound = lb - self.model.reactions.get_by_id(k).upper_bound = ub - for reaction in self.model.reactions: - self.assertEqual(reaction.lower_bound, original_bounds[reaction.id][0]) - self.assertEqual(reaction.upper_bound, original_bounds[reaction.id][1]) - with TimeMachine() as tm: - for reaction in self.model.reactions: - original_bounds[reaction.id] = (reaction.lower_bound, reaction.upper_bound) - reaction.knock_out(time_machine=tm) - self.assertEqual(reaction.lower_bound, 0) - self.assertEqual(reaction.upper_bound, 0) - for reaction in self.model.reactions: - self.assertEqual(reaction.lower_bound, original_bounds[reaction.id][0]) - self.assertEqual(reaction.upper_bound, original_bounds[reaction.id][1]) - - def test__repr_html_(self): - self.assertIn('', self.model.reactions[0]._repr_html_()) - - def test_reaction_without_model(self): - r = Reaction('blub') - self.assertEqual(r.flux_expression, None) - self.assertEqual(r.forward_variable, None) - self.assertEqual(r.reverse_variable, None) - - # def test_clone_cobrapy_reaction(self): - # from cobra.core import Reaction as CobrapyReaction - # reaction = CobrapyReaction('blug') - # self.assertEqual(Reaction.clone(reaction).id, 'blug') - - def test_weird_left_to_right_reaction_issue(self): - - model = Model("Toy Model") - - m1 = Metabolite("M1") - d1 = Reaction("ex1") - d1.add_metabolites({m1: -1}) - d1.upper_bound = 0 - d1.lower_bound = -1000 - # print d1.reaction, d1.lower_bound, d1.upper_bound - model.add_reactions([d1]) - self.assertFalse(d1.reversibility) - self.assertEqual(d1.lower_bound, -1000) - self.assertEqual(d1._lower_bound, -1000) - self.assertEqual(d1.upper_bound, 0) - self.assertEqual(d1._upper_bound, 0) - with TimeMachine() as tm: - d1.knock_out(time_machine=tm) - self.assertEqual(d1.lower_bound, 0) - self.assertEqual(d1._lower_bound, 0) - self.assertEqual(d1.upper_bound, 0) - self.assertEqual(d1._upper_bound, 0) - self.assertEqual(d1.lower_bound, -1000) - self.assertEqual(d1._lower_bound, -1000) - self.assertEqual(d1.upper_bound, 0) - self.assertEqual(d1._upper_bound, 0) - - def test_one_left_to_right_reaction_set_positive_ub(self): - - model = Model("Toy Model") - - m1 = Metabolite("M1") - d1 = Reaction("ex1") - d1.add_metabolites({m1: -1}) - d1.upper_bound = 0 - d1.lower_bound = -1000 - model.add_reactions([d1]) - self.assertEqual(d1.reverse_variable.lb, 0) - self.assertEqual(d1.reverse_variable.ub, 1000) - self.assertEqual(d1._lower_bound, -1000) - self.assertEqual(d1.lower_bound, -1000) - self.assertEqual(d1._upper_bound, 0) - self.assertEqual(d1.upper_bound, 0) - self.assertEqual(d1.forward_variable.lb, 0) - self.assertEqual(d1.forward_variable.ub, 0) - d1.upper_bound = .1 - self.assertEqual(d1.forward_variable.lb, 0) - self.assertEqual(d1.forward_variable.ub, .1) - self.assertEqual(d1.reverse_variable.lb, 0) - self.assertEqual(d1.reverse_variable.ub, 1000) - self.assertEqual(d1._lower_bound, -1000) - self.assertEqual(d1.upper_bound, .1) - self.assertEqual(d1._lower_bound, -1000) - self.assertEqual(d1.upper_bound, .1) - - def test_irrev_reaction_set_negative_lb(self): - self.assertFalse(self.model.reactions.PFK.reversibility) - self.assertEqual(self.model.reactions.PFK.lower_bound, 0) - self.assertEqual(self.model.reactions.PFK.upper_bound, 1000.0) - self.assertEqual(self.model.reactions.PFK.forward_variable.lb, 0) - self.assertEqual(self.model.reactions.PFK.forward_variable.ub, 1000.0) - self.assertEqual(self.model.reactions.PFK.reverse_variable.lb, 0) - self.assertEqual(self.model.reactions.PFK.reverse_variable.ub, 0) - self.model.reactions.PFK.lower_bound = -1000 - self.assertEqual(self.model.reactions.PFK.lower_bound, -1000) - self.assertEqual(self.model.reactions.PFK.upper_bound, 1000.0) - self.assertEqual(self.model.reactions.PFK.forward_variable.lb, 0) - self.assertEqual(self.model.reactions.PFK.forward_variable.ub, 1000.0) - self.assertEqual(self.model.reactions.PFK.reverse_variable.lb, 0) - self.assertEqual(self.model.reactions.PFK.reverse_variable.ub, 1000) - self.assertTrue(self.model.reactions.PFK.reversibility) - - def test_twist_irrev_right_to_left_reaction_to_left_to_right(self): - self.assertFalse(self.model.reactions.PFK.reversibility) - self.assertEqual(self.model.reactions.PFK.lower_bound, 0) - self.assertEqual(self.model.reactions.PFK.upper_bound, 1000.0) - self.assertEqual(self.model.reactions.PFK.forward_variable.lb, 0) - self.assertEqual(self.model.reactions.PFK.forward_variable.ub, 1000.0) - self.assertEqual(self.model.reactions.PFK.reverse_variable.lb, 0) - self.assertEqual(self.model.reactions.PFK.reverse_variable.ub, 0) - self.model.reactions.PFK.lower_bound = -1000 - self.model.reactions.PFK.upper_bound = 0 - self.assertEqual(self.model.reactions.PFK.lower_bound, -1000) - self.assertEqual(self.model.reactions.PFK.upper_bound, 0) - self.assertEqual(self.model.reactions.PFK.forward_variable.lb, 0) - self.assertEqual(self.model.reactions.PFK.forward_variable.ub, 0) - self.assertEqual(self.model.reactions.PFK.reverse_variable.lb, 0) - self.assertEqual(self.model.reactions.PFK.reverse_variable.ub, 1000) - - @unittest.skipIf(TRAVIS, "This is slow on Travis") - def test_iMM904_4HGLSDm_problem(self): - model = load_model(os.path.join(TESTDIR, 'data/iMM904.xml')) - # set upper bound before lower bound after knockout - cp = model.copy() - rxn = cp.reactions.get_by_id('4HGLSDm') - prev_lb, prev_ub = rxn.lower_bound, rxn.upper_bound - rxn.lower_bound = 0 - rxn.upper_bound = 0 - rxn.upper_bound = prev_ub - rxn.lower_bound = prev_lb - self.assertEquals(rxn.lower_bound, prev_lb) - self.assertEquals(rxn.upper_bound, prev_ub) - # set lower bound before upper bound after knockout - cp = model.copy() - rxn = cp.reactions.get_by_id('4HGLSDm') - prev_lb, prev_ub = rxn.lower_bound, rxn.upper_bound - rxn.lower_bound = 0 - rxn.upper_bound = 0 - rxn.lower_bound = prev_lb - rxn.upper_bound = prev_ub - self.assertEquals(rxn.lower_bound, prev_lb) - self.assertEquals(rxn.upper_bound, prev_ub) - - def test_setting_lower_bound_higher_than_higher_bound_sets_higher_bound_to_new_lower_bound(self): - for reaction in self.model.reactions: - self.assertTrue(reaction.lower_bound <= reaction.upper_bound) - reaction.lower_bound = reaction.upper_bound + 100 - self.assertEqual(reaction.lower_bound, reaction.upper_bound) - - def test_setting_higher_bound_lower_than_lower_bound_sets_lower_bound_to_new_higher_bound(self): - for reaction in self.model.reactions: - self.assertTrue(reaction.lower_bound <= reaction.upper_bound) - reaction.upper_bound = reaction.lower_bound - 100 - self.assertEqual(reaction.lower_bound, reaction.upper_bound) - - def test_add_metabolites_combine_true(self): - test_metabolite = Metabolite('test') - for reaction in self.model.reactions: - reaction.add_metabolites({test_metabolite: -66}, combine=True) - self.assertEqual(reaction.metabolites[test_metabolite], -66) - self.assertTrue(self.model.solver.constraints['test'].expression.has(-66. * reaction.forward_variable)) - self.assertTrue(self.model.solver.constraints['test'].expression.has(66. * reaction.reverse_variable)) - already_included_metabolite = list(reaction.metabolites.keys())[0] - previous_coefficient = reaction.get_coefficient(already_included_metabolite.id) - reaction.add_metabolites({already_included_metabolite: 10}, combine=True) - new_coefficient = previous_coefficient + 10 - self.assertEqual(reaction.metabolites[already_included_metabolite], new_coefficient) - self.assertTrue(self.model.solver.constraints[already_included_metabolite.id].expression.has( - new_coefficient * reaction.forward_variable)) - self.assertTrue(self.model.solver.constraints[already_included_metabolite.id].expression.has( - -1 * new_coefficient * reaction.reverse_variable)) - - @unittest.skipIf(TRAVIS, 'This test behaves non-deterministic on travis-ci') - def test_add_metabolites_combine_false(self): - test_metabolite = Metabolite('test') - for reaction in self.model.reactions: - reaction.add_metabolites({test_metabolite: -66}, combine=False) - self.assertEqual(reaction.metabolites[test_metabolite], -66) - self.assertTrue(self.model.solver.constraints['test'].expression.has(-66. * reaction.forward_variable)) - self.assertTrue(self.model.solver.constraints['test'].expression.has(66. * reaction.reverse_variable)) - already_included_metabolite = list(reaction.metabolites.keys())[0] - reaction.add_metabolites({already_included_metabolite: 10}, combine=False) - self.assertEqual(reaction.metabolites[already_included_metabolite], 10) - self.assertTrue(self.model.solver.constraints[already_included_metabolite.id].expression.has( - 10 * reaction.forward_variable)) - self.assertTrue(self.model.solver.constraints[already_included_metabolite.id].expression.has( - -10 * reaction.reverse_variable)) - - def test_pop(self): - pgi = self.model.reactions.PGI - g6p = self.model.metabolites.get_by_id("g6p_c") - f6p = self.model.metabolites.get_by_id("f6p_c") - g6p_expr = self.model.solver.constraints["g6p_c"].expression - g6p_coef = pgi.pop("g6p_c") - self.assertNotIn(g6p, pgi.metabolites) - self.assertEqual( - self.model.solver.constraints["g6p_c"].expression.as_coefficients_dict(), - (g6p_expr - g6p_coef * pgi.flux_expression).as_coefficients_dict() - ) - self.assertEqual(pgi.metabolites[f6p], 1) - - f6p_expr = self.model.solver.constraints["f6p_c"].expression - f6p_coef = pgi.pop(f6p) - self.assertNotIn(f6p, pgi.metabolites) - self.assertEqual( - self.model.solver.constraints["f6p_c"].expression.as_coefficients_dict(), - (f6p_expr - f6p_coef * pgi.flux_expression).as_coefficients_dict() - ) - - def test_remove_from_model(self): - pgi = self.model.reactions.PGI - pgi.remove_from_model() - self.assertTrue(pgi.model is None) - self.assertFalse("PGI" in self.model.reactions) - self.assertFalse(pgi._get_forward_id() in self.model.solver.variables) - self.assertFalse(pgi._get_reverse_id() in self.model.solver.variables) - - def test_delete(self): - pgi = self.model.reactions.PGI - pgi.delete() - self.assertTrue(pgi.model is None) - self.assertFalse("PGI" in self.model.reactions) - self.assertFalse(pgi._get_forward_id() in self.model.solver.variables) - self.assertFalse(pgi._get_reverse_id() in self.model.solver.variables) - - def test_change_id_is_reflected_in_solver(self): - for i, reaction in enumerate(self.model.reactions): - old_reaction_id = reaction.id - self.assertEqual(self.model.solver.variables[old_reaction_id].name, old_reaction_id) - self.assertIn(old_reaction_id, self.model.solver.variables) - new_reaction_id = reaction.id + '_' + str(i) - reaction.id = new_reaction_id - self.assertEqual(reaction.id, new_reaction_id) - self.assertFalse(old_reaction_id in self.model.solver.variables) - self.assertTrue(reaction._get_forward_id() in self.model.solver.variables) - self.assertTrue(reaction._get_reverse_id() in self.model.solver.variables) - self.assertEqual(self.model.solver.variables[reaction._get_forward_id()].name, - reaction._get_forward_id()) - - -class TestReactionGLPK(WrappedAbstractTestReaction.AbstractTestReaction): - def setUp(self): - self.cobrapy_model = COBRAPYTESTMODEL.copy() - self.model = TESTMODEL.copy() - self.model.solver = 'glpk' - - -@unittest.skipIf('cplex' not in solvers, "No cplex interface available") -class TestReactionCPLEX(WrappedAbstractTestReaction.AbstractTestReaction): - def setUp(self): - self.cobrapy_model = COBRAPYTESTMODEL.copy() - self.model = TESTMODEL.copy() - self.model.solver = 'cplex' - - -class WrappedAbstractTestSolverBasedModel: - class AbstractTestSolverBasedModel(unittest.TestCase): - def setUp(self): - self.model = TESTMODEL.copy() - self.model.solve() - - def test_model_is_subclassed(self): - model = self.model - self.assertTrue(isinstance(model, cameo.core.SolverBasedModel)) - for reac in self.model.reactions: - self.assertTrue(isinstance(reac, Reaction)) - for met in reac.metabolites: - self.assertTrue(isinstance(met, Metabolite)) - self.assertTrue(met in model.metabolites) - self.assertTrue(met is model.metabolites.get_by_id(met.id)) - for gene in reac.genes: - self.assertTrue(isinstance(gene, Gene)) - self.assertTrue(gene in model.genes) - self.assertTrue(gene is model.genes.get_by_id(gene.id)) - - for gene in model.genes: - self.assertTrue(isinstance(gene, Gene)) - for reac in gene.reactions: - self.assertTrue(isinstance(reac, Reaction)) - self.assertTrue(reac in model.reactions) - self.assertTrue(reac is model.reactions.get_by_id(reac.id)) - - for met in model.metabolites: - self.assertTrue(isinstance(met, Metabolite)) - for reac in met.reactions: - self.assertTrue(isinstance(reac, Reaction)) - self.assertTrue(reac in model.reactions) - self.assertTrue(reac is model.reactions.get_by_id(reac.id)) - - def test_objective_coefficient_reflects_changed_objective(self): - biomass_r = self.model.reactions.Biomass_Ecoli_core_N_LPAREN_w_FSLASH_GAM_RPAREN__Nmet2 - self.assertEqual(biomass_r.objective_coefficient, 1) - self.model.objective = "PGI" - self.assertEqual(biomass_r.objective_coefficient, 0) - self.assertEqual(self.model.reactions.PGI.objective_coefficient, 1) - - def test_objective_can_be_changed_through_objective_coefficient(self): - biomass_r = self.model.reactions.Biomass_Ecoli_core_N_LPAREN_w_FSLASH_GAM_RPAREN__Nmet2 - pgi = self.model.reactions.PGI - pgi.objective_coefficient = 2 - coef_dict = self.model.objective.expression.as_coefficients_dict() - # Check that objective has been updated - self.assertEqual(coef_dict[pgi.forward_variable], 2) - self.assertEqual(coef_dict[pgi.reverse_variable], -2) - # Check that original objective is still in there - self.assertEqual(coef_dict[biomass_r.forward_variable], 1) - self.assertEqual(coef_dict[biomass_r.reverse_variable], -1) - - def test_model_from_other_cameo_model(self): - model = Model(description=self.model) - for reaction in model.reactions: - self.assertEqual(reaction, self.model.reactions.get_by_id(reaction.id)) - - def test_add_reactions(self): - r1 = Reaction('r1') - r1.add_metabolites({Metabolite('A'): -1, Metabolite('B'): 1}) - r1.lower_bound, r1.upper_bound = -999999., 999999. - r2 = Reaction('r2') - r2.add_metabolites({Metabolite('A'): -1, Metabolite('C'): 1, Metabolite('D'): 1}) - r2.lower_bound, r2.upper_bound = 0., 999999. - r2.objective_coefficient = 3. - self.assertEqual(r2.objective_coefficient, 3.) - self.model.add_reactions([r1, r2]) - self.assertEqual(self.model.reactions[-2], r1) - self.assertEqual(self.model.reactions[-1], r2) - self.assertTrue(isinstance(self.model.reactions[-2].reverse_variable, self.model.solver.interface.Variable)) - coefficients_dict = self.model.objective.expression.as_coefficients_dict() - biomass_r = self.model.reactions.Biomass_Ecoli_core_N_LPAREN_w_FSLASH_GAM_RPAREN__Nmet2 - self.assertEqual(coefficients_dict[biomass_r.forward_variable], 1.) - self.assertEqual(coefficients_dict[biomass_r.reverse_variable], -1.) - self.assertEqual(coefficients_dict[self.model.reactions.r2.forward_variable], 3.) - self.assertEqual(coefficients_dict[self.model.reactions.r2.reverse_variable], -3.) - - def test_remove_reactions(self): - model = self.model.copy() - model.remove_reactions([model.reactions.PGI, model.reactions.PGK], delete=False) - self.assertNotIn("PGI", model.reactions) - self.assertNotIn("PGK", model.reactions) - self.assertIn("PGI", self.model.reactions) - self.assertIn("PGK", self.model.reactions) - - def test_remove_and_add_reactions(self): - model = self.model.copy() - pgi, pgk = model.reactions.PGI, model.reactions.PGK - model.remove_reactions([pgi, pgk], delete=False) - self.assertNotIn("PGI", model.reactions) - self.assertNotIn("PGK", model.reactions) - self.assertIn("PGI", self.model.reactions) - self.assertIn("PGK", self.model.reactions) - model.add_reactions([pgi, pgk]) - self.assertIn("PGI", self.model.reactions) - self.assertIn("PGK", self.model.reactions) - self.assertIn("PGI", model.reactions) - self.assertIn("PGK", model.reactions) - - def test_add_cobra_reaction(self): - r = cobra.Reaction(id="c1") - self.model.add_reaction(r) - self.assertIsInstance(self.model.reactions.c1, Reaction) - - def test_all_objects_point_to_all_other_correct_objects(self): - model = load_model(os.path.join(TESTDIR, 'data/EcoliCore.xml')) - for reaction in model.reactions: - self.assertEqual(reaction.model, model) - for gene in reaction.genes: - self.assertEqual(gene, model.genes.get_by_id(gene.id)) - self.assertEqual(gene.model, model) - for reaction2 in gene.reactions: - self.assertEqual(reaction2.model, model) - self.assertEqual(reaction2, model.reactions.get_by_id(reaction2.id)) - - for metabolite in reaction.metabolites: - self.assertEqual(metabolite.model, model) - self.assertEqual(metabolite, model.metabolites.get_by_id(metabolite.id)) - for reaction2 in metabolite.reactions: - self.assertEqual(reaction2.model, model) - self.assertEqual(reaction2, model.reactions.get_by_id(reaction2.id)) - - def test_all_objects_point_to_all_other_correct_objects_after_copy(self): - model = load_model(os.path.join(TESTDIR, 'data/EcoliCore.xml')) - model = model.copy() - for reaction in model.reactions: - self.assertEqual(reaction.model, model) - for gene in reaction.genes: - self.assertEqual(gene, model.genes.get_by_id(gene.id)) - self.assertEqual(gene.model, model) - for reaction2 in gene.reactions: - self.assertEqual(reaction2.model, model) - self.assertEqual(reaction2, model.reactions.get_by_id(reaction2.id)) - - for metabolite in reaction.metabolites: - self.assertEqual(metabolite.model, model) - self.assertEqual(metabolite, model.metabolites.get_by_id(metabolite.id)) - for reaction2 in metabolite.reactions: - self.assertEqual(reaction2.model, model) - self.assertEqual(reaction2, model.reactions.get_by_id(reaction2.id)) - - def test_remove_reactions(self): - reactions_to_remove = self.model.reactions[10:30] - self.assertTrue(all([reaction.model is self.model for reaction in reactions_to_remove])) - self.assertTrue( - all([self.model.reactions.get_by_id(reaction.id) == reaction for reaction in reactions_to_remove])) - - self.model.remove_reactions(reactions_to_remove) - self.assertTrue(all([reaction.model is None for reaction in reactions_to_remove])) - for reaction in reactions_to_remove: - self.assertNotIn(reaction.id, list(self.model.solver.variables.keys())) - - self.model.add_reactions(reactions_to_remove) - for reaction in reactions_to_remove: - self.assertIn(reaction, self.model.reactions) - - def test_add_exchange(self): - for demand, prefix in {True: 'DemandReaction_', False: 'SupplyReaction_'}.items(): - for metabolite in self.model.metabolites: - demand_reaction = self.model.add_exchange(metabolite, demand=demand, prefix=prefix) - self.assertEqual(self.model.reactions.get_by_id(demand_reaction.id), demand_reaction) - self.assertEqual(demand_reaction.reactants, [metabolite]) - self.assertTrue(self.model.solver.constraints[metabolite.id].expression.has( - self.model.solver.variables[prefix + metabolite.id])) - - def test_add_exchange_time_machine(self): - for demand, prefix in {True: 'DemandReaction_', False: 'SupplyReaction_'}.items(): - with TimeMachine() as tm: - for metabolite in self.model.metabolites: - demand_reaction = self.model.add_exchange(metabolite, demand=demand, prefix=prefix, - time_machine=tm) - self.assertEqual(self.model.reactions.get_by_id(demand_reaction.id), demand_reaction) - self.assertEqual(demand_reaction.reactants, [metabolite]) - self.assertTrue(-self.model.solver.constraints[metabolite.id].expression.has( - self.model.solver.variables[prefix + metabolite.id])) - for metabolite in self.model.metabolites: - self.assertNotIn(prefix + metabolite.id, self.model.reactions) - self.assertNotIn(prefix + metabolite.id, self.model.solver.variables.keys()) - - def test_add_existing_exchange(self): - for metabolite in self.model.metabolites: - self.model.add_exchange(metabolite, prefix="test") - self.assertRaises(ValueError, self.model.add_exchange, metabolite, prefix="test") - - def test_objective(self): - obj = self.model.objective - self.assertEqual( - {var.name: coef for var, coef in obj.expression.as_coefficients_dict().items()}, - {'Biomass_Ecoli_core_N_LPAREN_w_FSLASH_GAM_RPAREN__Nmet2_reverse_9ebcd': -1, - 'Biomass_Ecoli_core_N_LPAREN_w_FSLASH_GAM_RPAREN__Nmet2': 1}) - self.assertEqual( - obj.direction, - "max" - ) - - def test_change_objective(self): - expression = 1.0 * self.model.solver.variables['ENO'] + 1.0 * self.model.solver.variables['PFK'] - self.model.objective = self.model.solver.interface.Objective(expression) - self.assertEqual(self.model.objective.expression, expression) - - self.model.change_objective("ENO") - eno_obj = self.model.solver.interface.Objective(self.model.reactions.ENO.flux_expression, direction="max") - pfk_obj = self.model.solver.interface.Objective(self.model.reactions.PFK.flux_expression, direction="max") - self.assertEqual(self.model.objective, eno_obj) - - with TimeMachine() as tm: - self.model.change_objective("PFK", tm) - self.assertEqual(self.model.objective, pfk_obj) - self.assertEqual(self.model.objective, eno_obj) - - def test_set_reaction_objective(self): - self.model.objective = self.model.reactions.ACALD - self.assertEqual(str(self.model.objective.expression), str( - 1.0 * self.model.reactions.ACALD.forward_variable - 1.0 * self.model.reactions.ACALD.reverse_variable)) - - def test_set_reaction_objective_str(self): - self.model.objective = self.model.reactions.ACALD.id - self.assertEqual(str(self.model.objective.expression), str( - 1.0 * self.model.reactions.ACALD.forward_variable - 1.0 * self.model.reactions.ACALD.reverse_variable)) - - def test_invalid_objective_raises(self): - self.assertRaises(ValueError, setattr, self.model, 'objective', 'This is not a valid objective!') - self.assertRaises(TypeError, setattr, self.model, 'objective', 3.) - - def test_solver_change(self): - solver_id = id(self.model.solver) - problem_id = id(self.model.solver.problem) - solution = self.model.solve().x_dict - self.model.solver = 'glpk' - self.assertNotEqual(id(self.model.solver), solver_id) - self.assertNotEqual(id(self.model.solver.problem), problem_id) - new_solution = self.model.solve() - for key in list(solution.keys()): - self.assertAlmostEqual(new_solution.x_dict[key], solution[key]) - - def test_solver_change_with_optlang_interface(self): - solver_id = id(self.model.solver) - problem_id = id(self.model.solver.problem) - solution = self.model.solve().x_dict - self.model.solver = optlang.glpk_interface - self.assertNotEqual(id(self.model.solver), solver_id) - self.assertNotEqual(id(self.model.solver.problem), problem_id) - new_solution = self.model.solve() - for key in list(solution.keys()): - self.assertAlmostEqual(new_solution.x_dict[key], solution[key]) - - def test_invalid_solver_change_raises(self): - self.assertRaises(ValueError, setattr, self.model, 'solver', [1, 2, 3]) - self.assertRaises(ValueError, setattr, self.model, 'solver', 'ThisIsDefinitelyNotAvalidSolver') - self.assertRaises(ValueError, setattr, self.model, 'solver', os) - - @unittest.skipIf('cplex' not in solvers, "No cplex interface available") - def test_change_solver_to_cplex_and_check_copy_works(self): - # First, load model from scratch - model = load_model(os.path.join(TESTDIR, 'data/EcoliCore.xml'), solver_interface='cplex') - self.assertAlmostEqual(model.optimize().f, 0.8739215069684306) - model_copy = model.copy() - self.assertAlmostEqual(model_copy.optimize().f, 0.8739215069684306) - # Second, change existing glpk based model to cplex - self.model.solver = 'cplex' - self.assertAlmostEqual(self.model.optimize().f, 0.8739215069684306) - model_copy = copy.copy(self.model) - self.assertAlmostEqual(model_copy.optimize().f, 0.8739215069684306) - - def test_copy_preserves_existing_solution(self): - self.model.solve() # TODO: not sure why the model has to be solved here because it is already in setUp - model_cp = copy.copy(self.model) - primals_original = [variable.primal for variable in self.model.solver.variables] - primals_copy = [variable.primal for variable in model_cp.solver.variables] - abs_diff = abs(numpy.array(primals_copy) - numpy.array(primals_original)) - self.assertFalse(any(abs_diff > 1e-6)) - - def test_essential_genes(self): - essential_genes = [g.id for g in self.model.essential_genes()] - self.assertTrue(sorted(essential_genes) == sorted(ESSENTIAL_GENES)) - with self.assertRaises(cameo.exceptions.SolveError): - self.model.reactions.Biomass_Ecoli_core_N_LPAREN_w_FSLASH_GAM_RPAREN__Nmet2.lower_bound = 999999. - self.model.essential_genes() - - def test_essential_reactions(self): - essential_reactions = [r.id for r in self.model.essential_reactions()] - self.assertTrue(sorted(essential_reactions) == sorted(ESSENTIAL_REACTIONS)) - with self.assertRaises(cameo.exceptions.SolveError): - self.model.reactions.Biomass_Ecoli_core_N_LPAREN_w_FSLASH_GAM_RPAREN__Nmet2.lower_bound = 999999. - self.model.essential_reactions() - - def test_essential_metabolites(self): - model = self.model.copy() - essential_metabolites_unbalanced = [m.id for m in model.essential_metabolites(force_steady_state=False)] - essential_metabolites_balanced = [m.id for m in model.essential_metabolites(force_steady_state=True)] - self.assertTrue(sorted(essential_metabolites_unbalanced) == sorted(ESSENTIAL_METABOLITES)) - self.assertTrue(sorted(essential_metabolites_balanced) == sorted(ESSENTIAL_METABOLITES)) - - with self.assertRaises(cameo.exceptions.SolveError): - self.model.reactions.Biomass_Ecoli_core_N_LPAREN_w_FSLASH_GAM_RPAREN__Nmet2.lower_bound = 999999. - self.model.essential_metabolites(force_steady_state=False) - - with self.assertRaises(cameo.exceptions.SolveError): - self.model.reactions.Biomass_Ecoli_core_N_LPAREN_w_FSLASH_GAM_RPAREN__Nmet2.lower_bound = 999999. - self.model.essential_metabolites(force_steady_state=True) - - def test_effective_bounds(self): - self.model.reactions.Biomass_Ecoli_core_N_LPAREN_w_FSLASH_GAM_RPAREN__Nmet2.lower_bound = 0.873921 - for reaction in self.model.reactions: - self.assertAlmostEqual(reaction.effective_lower_bound, - REFERENCE_FVA_SOLUTION_ECOLI_CORE['lower_bound'][reaction.id], delta=0.000001) - self.assertAlmostEqual(reaction.effective_upper_bound, - REFERENCE_FVA_SOLUTION_ECOLI_CORE['upper_bound'][reaction.id], delta=0.000001) - - def test_add_demand_for_non_existing_metabolite(self): - metabolite = Metabolite(id="a_metabolite") - self.model.add_demand(metabolite) - self.assertTrue(self.model.solver.constraints[metabolite.id].expression.has( - self.model.solver.variables["DM_" + metabolite.id])) - - def test_add_ratio_constraint(self): - solution = self.model.solve() - self.assertAlmostEqual(solution.f, 0.873921506968) - self.assertNotEqual(2 * solution.x_dict['PGI'], solution.x_dict['G6PDH2r']) - cp = self.model.copy() - ratio_constr = cp.add_ratio_constraint(cp.reactions.PGI, cp.reactions.G6PDH2r, 0.5) - self.assertEqual(ratio_constr.name, 'ratio_constraint_PGI_G6PDH2r') - solution = cp.solve() - self.assertAlmostEqual(solution.f, 0.870407873712) - self.assertAlmostEqual(2 * solution.x_dict['PGI'], solution.x_dict['G6PDH2r']) - cp = self.model.copy() - - ratio_constr = cp.add_ratio_constraint(cp.reactions.PGI, cp.reactions.G6PDH2r, 0.5) - self.assertEqual(ratio_constr.name, 'ratio_constraint_PGI_G6PDH2r') - solution = cp.solve() - self.assertAlmostEqual(solution.f, 0.870407873712) - self.assertAlmostEqual(2 * solution.x_dict['PGI'], solution.x_dict['G6PDH2r']) - - cp = self.model.copy() - ratio_constr = cp.add_ratio_constraint('PGI', 'G6PDH2r', 0.5) - self.assertEqual(ratio_constr.name, 'ratio_constraint_PGI_G6PDH2r') - solution = cp.solve() - self.assertAlmostEqual(solution.f, 0.870407873712) - self.assertAlmostEqual(2 * solution.x_dict['PGI'], solution.x_dict['G6PDH2r']) - - cp = self.model.copy() - ratio_constr = cp.add_ratio_constraint([cp.reactions.PGI, cp.reactions.ACALD], - [cp.reactions.G6PDH2r, cp.reactions.ACONTa], 0.5) - self.assertEqual(ratio_constr.name, 'ratio_constraint_PGI+ACALD_G6PDH2r+ACONTa') - solution = cp.solve() - self.assertAlmostEqual(solution.f, 0.8729595694565973) - self.assertAlmostEqual(2 * solution.x_dict['PGI'] + solution.x_dict['ACALD'], - solution.x_dict['G6PDH2r'] + solution.x_dict['ACONTa']) - - def test_fix_objective_as_constraint(self): - # with TimeMachine - with TimeMachine() as tm: - self.model.fix_objective_as_constraint(time_machine=tm) - constraint_name = self.model.solver.constraints[-1] - self.assertEqual(self.model.solver.constraints[-1].expression - self.model.objective.expression, 0) - self.assertNotIn(constraint_name, self.model.solver.constraints) - # without TimeMachine - self.model.fix_objective_as_constraint() - constraint_name = self.model.solver.constraints[-1] - self.assertEqual(self.model.solver.constraints[-1].expression - self.model.objective.expression, 0) - self.assertIn(constraint_name, self.model.solver.constraints) - - def test_reactions_for(self): - with TimeMachine() as tm: - for r in self.model.reactions: - self.assertIsInstance(self.model._reaction_for(r.id, time_machine=tm), Reaction) - self.assertIsInstance(self.model._reaction_for(r, time_machine=tm), Reaction) - for m in self.model.metabolites: - self.assertIsInstance(self.model._reaction_for(m.id, time_machine=tm), Reaction) - self.assertIsInstance(self.model._reaction_for(m, time_machine=tm), Reaction) - - self.assertRaises(KeyError, self.model._reaction_for, None) - self.assertRaises(KeyError, self.model._reaction_for, "blablabla") - self.assertRaises(KeyError, self.model._reaction_for, "accoa_lp_c_lp_", add=False) - - def test_stoichiometric_matrix(self): - stoichiometric_matrix = create_stoichiometric_array(self.model) - self.assertEqual(len(self.model.reactions), stoichiometric_matrix.shape[1]) - self.assertEqual(len(self.model.metabolites), stoichiometric_matrix.shape[0]) - - for i, reaction in enumerate(self.model.reactions): - for j, metabolite in enumerate(self.model.metabolites): - if metabolite in reaction.metabolites: - coefficient = reaction.metabolites[metabolite] - else: - coefficient = 0 - self.assertEqual(stoichiometric_matrix[j, i], coefficient) - - def test_set_medium(self): - medium = self.model.medium - - for reaction in self.model.exchanges: - if reaction.lower_bound == 0: - self.assertNotIn(reaction.id, medium.reaction_id.values) - if reaction.lower_bound < 0: - self.assertIn(reaction.id, medium.reaction_id.values) - - self.model.load_medium(medium) - - for rid in self.model.medium.reaction_id: - self.assertEqual(len(medium[medium.reaction_id == rid]), 1) - - -class TestSolverBasedModelGLPK(WrappedAbstractTestSolverBasedModel.AbstractTestSolverBasedModel): - def setUp(self): - super(TestSolverBasedModelGLPK, self).setUp() - self.model.solver = 'glpk' - - def test_cobrapy_attributes_not_in_dir(self): - self.assertNotIn('optimize', dir(self.model)) - - def test_solver_change_preserves_non_metabolic_constraints(self): - self.model.add_ratio_constraint(self.model.reactions.PGK, self.model.reactions.PFK, 1 / 2) - all_constraint_ids = self.model.solver.constraints.keys() - self.assertTrue(all_constraint_ids[-1], 'ratio_constraint_PGK_PFK') - resurrected = pickle.loads(pickle.dumps(self.model)) - self.assertEqual(resurrected.solver.constraints.keys(), all_constraint_ids) - - -@unittest.skipIf('cplex' not in solvers, "No cplex interface available") -class TestSolverBasedModelCPLEX(WrappedAbstractTestSolverBasedModel.AbstractTestSolverBasedModel): - def setUp(self): - super(TestSolverBasedModelCPLEX, self).setUp() - self.model.solver = 'cplex' - - -class WrappedAbstractTestMetabolite: - class AbstractTestMetabolite(unittest.TestCase): - def test_set_id(self): - met = Metabolite("test") - self.assertRaises(TypeError, setattr, met, 'id', 1) - g6p = self.model.metabolites.get_by_id("g6p_c") - self.model.add_metabolite(met) - self.assertRaises(ValueError, setattr, met, "id", 'g6p_c') - met.id = "test2" - self.assertIn("test2", self.model.metabolites) - self.assertNotIn("test", self.model.metabolites) - - def test_knock_out(self): - model = self.model.copy() - rxn = Reaction('rxn', upper_bound=10, lower_bound=-10) - metabolite_a = Metabolite('A') - metabolite_b = Metabolite('B') - rxn.add_metabolites({metabolite_a: -1, metabolite_b: 1}) - model.add_reaction(rxn) +@pytest.fixture(scope="function", params=list(solvers)) +def solved_model(request, data_directory): + core_model = load_model(os.path.join(data_directory, 'EcoliCore.xml'), sanitize=False) + core_model.solver = request.param + solution = core_model.solve() + return solution, core_model + + +@pytest.fixture(scope="module", params=list(solvers)) +def tiny_toy_model(request): + tiny = Model("Toy Model") + m1 = Metabolite("M1") + d1 = Reaction("ex1") + d1.add_metabolites({m1: -1}) + d1.upper_bound = 0 + d1.lower_bound = -1000 + tiny.add_reactions([d1]) + tiny.solver = request.param + return tiny + + +class TestLazySolution: + def test_self_invalidation(self, solved_model): + solution, model = solved_model + assert abs(solution.f - 0.873921506968431) < 0.000001 + model.optimize() + with pytest.raises(UndefinedSolution): + getattr(solution, 'f') + + def test_solution_contains_only_reaction_specific_values(self, solved_model): + solution, model = solved_model + reaction_ids = set([reaction.id for reaction in model.reactions]) + assert set(solution.fluxes.keys()).difference(reaction_ids) == set() + assert set(solution.reduced_costs.keys()).difference(reaction_ids) == set() + assert set(solution.reduced_costs.keys()).difference(reaction_ids) == set() + metabolite_ids = set([metabolite.id for metabolite in model.metabolites]) + assert set(solution.shadow_prices.keys()).difference(metabolite_ids) == set() + + +class TestReaction: + def test_clone_cobrapy_reaction(self): + model = cobra.test.create_test_model('textbook') + for reaction in model.reactions: + cloned_reaction = Reaction.clone(reaction) + assert cloned_reaction.objective_coefficient == reaction.objective_coefficient + assert cloned_reaction.gene_reaction_rule == reaction.gene_reaction_rule + assert set([gene.id for gene in cloned_reaction.genes]) == set([gene.id for gene in reaction.genes]) + assert all(isinstance(gene, cameo.core.Gene) for gene in list(cloned_reaction.genes)) + assert {metabolite.id for metabolite in cloned_reaction.metabolites} == {metabolite.id for metabolite in + reaction.metabolites} + assert all(isinstance(metabolite, cameo.core.Metabolite) for metabolite in cloned_reaction.metabolites) + assert {metabolite.id for metabolite in cloned_reaction.products} == {metabolite.id for metabolite in + reaction.products} + assert {metabolite.id for metabolite in cloned_reaction.reactants} == {metabolite.id for metabolite in + reaction.reactants} + assert reaction.id == cloned_reaction.id + assert reaction.name == cloned_reaction.name + assert reaction.upper_bound == cloned_reaction.upper_bound + assert reaction.lower_bound == cloned_reaction.lower_bound + + def test_gene_reaction_rule_setter(self, core_model): + rxn = Reaction('rxn') + rxn.add_metabolites({Metabolite('A'): -1, Metabolite('B'): 1}) + rxn.gene_reaction_rule = 'A2B1 or A2B2 and A2B3' + assert hasattr(list(rxn.genes)[0], 'knock_out') + core_model.add_reaction(rxn) + with cameo.util.TimeMachine() as tm: + core_model.genes.A2B1.knock_out(time_machine=tm) + assert not core_model.genes.A2B1.functional + core_model.genes.A2B3.knock_out(time_machine=tm) + assert not rxn.functional + assert core_model.genes.A2B3.functional + assert rxn.functional + core_model.genes.A2B1.knock_out() + assert not core_model.genes.A2B1.functional + assert core_model.reactions.rxn.functional + core_model.genes.A2B3.knock_out() + assert not core_model.reactions.rxn.functional + non_functional = [gene.id for gene in core_model.non_functional_genes] + assert all(gene in non_functional for gene in ['A2B3', 'A2B1']) + + def test_gene_reaction_rule_setter_reaction_already_added_to_model(self, core_model): + rxn = Reaction('rxn') + rxn.add_metabolites({Metabolite('A'): -1, Metabolite('B'): 1}) + core_model.add_reaction(rxn) + rxn.gene_reaction_rule = 'A2B' + assert hasattr(list(rxn.genes)[0], 'knock_out') + + def test_str(self, core_model): + assert core_model.reactions[0].__str__().startswith('ACALD') + + def test_add_metabolite(self, solved_model): + solution, model = solved_model + pgi_reaction = model.reactions.PGI + test_met = model.metabolites[0] + pgi_reaction.add_metabolites({test_met: 42}, combine=False) + constraints = model.solver.constraints + assert pgi_reaction.metabolites[test_met] == 42 + assert constraints[test_met.id].expression.as_coefficients_dict()[pgi_reaction.forward_variable] == 42 + assert constraints[test_met.id].expression.as_coefficients_dict()[pgi_reaction.reverse_variable] == -42 + + pgi_reaction.add_metabolites({test_met: -10}, combine=True) + assert pgi_reaction.metabolites[test_met] == 32 + assert constraints[test_met.id].expression.as_coefficients_dict()[pgi_reaction.forward_variable] == 32 + assert constraints[test_met.id].expression.as_coefficients_dict()[pgi_reaction.reverse_variable] == -32 + + pgi_reaction.add_metabolites({test_met: 0}, combine=False) + with pytest.raises(KeyError): + assert pgi_reaction.metabolites[test_met] + assert constraints[test_met.id].expression.as_coefficients_dict()[pgi_reaction.forward_variable] == 0 + assert constraints[test_met.id].expression.as_coefficients_dict()[pgi_reaction.reverse_variable] == 0 + + def test_removal_from_model_retains_bounds(self, core_model): + core_model_cp = core_model.copy() + reaction = core_model_cp.reactions.ACALD + assert reaction.model == core_model_cp + assert reaction.lower_bound == -1000.0 + assert reaction.upper_bound == 1000.0 + assert reaction._lower_bound == -1000.0 + assert reaction._upper_bound == 1000.0 + core_model_cp.remove_reactions([reaction]) + assert reaction.model is None + assert reaction.lower_bound == -1000.0 + assert reaction.upper_bound == 1000.0 + assert reaction._lower_bound == -1000.0 + assert reaction._upper_bound == 1000.0 + + def test_set_bounds_scenario_1(self, core_model): + acald_reaction = core_model.reactions.ACALD + assert acald_reaction.lower_bound == -1000. + assert acald_reaction.upper_bound == 1000. + assert acald_reaction.forward_variable.lb == 0. + assert acald_reaction.forward_variable.ub == 1000. + assert acald_reaction.reverse_variable.lb == 0 + assert acald_reaction.reverse_variable.ub == 1000. + acald_reaction.upper_bound = acald_reaction.lower_bound - 100 + assert acald_reaction.lower_bound == -1100.0 + assert acald_reaction.upper_bound == -1100.0 + assert acald_reaction.forward_variable.lb == 0 + assert acald_reaction.forward_variable.ub == 0 + assert acald_reaction.reverse_variable.lb == 1100. + assert acald_reaction.reverse_variable.ub == 1100. + acald_reaction.upper_bound = 100 + assert acald_reaction.lower_bound == -1100.0 + assert acald_reaction.upper_bound == 100 + assert acald_reaction.forward_variable.lb == 0 + assert acald_reaction.forward_variable.ub == 100 + assert acald_reaction.reverse_variable.lb == 0 + assert acald_reaction.reverse_variable.ub == 1100.0 + + def test_set_bounds_scenario_3(self, core_model): + reac = core_model.reactions.ACALD + reac.upper_bound = -10 + reac.lower_bound = -10 + assert reac.lower_bound == -10 + assert reac.upper_bound == -10 + reac.lower_bound = -9 + assert reac.lower_bound == -9 + assert reac.upper_bound == -9 + reac.lower_bound = 2 + assert reac.lower_bound == 2 + assert reac.upper_bound == 2 + reac.upper_bound = -10 + assert reac.lower_bound == -10 + assert reac.upper_bound == -10 + reac.upper_bound = -11 + assert reac.lower_bound == -11 + assert reac.upper_bound == -11 + reac.upper_bound = 2 + assert reac.lower_bound == -11 + assert reac.upper_bound == 2 + + def test_set_bounds_scenario_4(self, core_model): + reac = core_model.reactions.ACALD + reac.lower_bound = reac.upper_bound = 0 + reac.lower_bound = 2 + assert reac.lower_bound == 2 + assert reac.upper_bound == 2 + assert reac.forward_variable.lb == 2 + assert reac.forward_variable.ub == 2 + reac.knock_out() + reac.upper_bound = -2 + assert reac.lower_bound == -2 + assert reac.upper_bound == -2 + assert reac.reverse_variable.lb == 2 + assert reac.reverse_variable.ub == 2 + + def test_set_upper_before_lower_bound_to_0(self, core_model): + core_model.reactions.GAPD.upper_bound = 0 + core_model.reactions.GAPD.lower_bound = 0 + assert core_model.reactions.GAPD.lower_bound == 0 + assert core_model.reactions.GAPD.upper_bound == 0 + assert core_model.reactions.GAPD.forward_variable.lb == 0 + assert core_model.reactions.GAPD.forward_variable.ub == 0 + assert core_model.reactions.GAPD.reverse_variable.lb == 0 + assert core_model.reactions.GAPD.reverse_variable.ub == 0 + + def test_set_bounds_scenario_2(self, core_model): + acald_reaction = core_model.reactions.ACALD + assert acald_reaction.lower_bound == -1000. + assert acald_reaction.upper_bound == 1000. + assert acald_reaction.forward_variable.lb == 0. + assert acald_reaction.forward_variable.ub == 1000. + assert acald_reaction.reverse_variable.lb == 0 + assert acald_reaction.reverse_variable.ub == 1000. + acald_reaction.lower_bound = acald_reaction.upper_bound + 100 + assert acald_reaction.lower_bound == 1100.0 + assert acald_reaction.upper_bound == 1100.0 + assert acald_reaction.forward_variable.lb == 1100.0 + assert acald_reaction.forward_variable.ub == 1100.0 + assert acald_reaction.reverse_variable.lb == 0 + assert acald_reaction.reverse_variable.ub == 0 + acald_reaction.lower_bound = -100 + assert acald_reaction.lower_bound == -100. + assert acald_reaction.upper_bound == 1100. + assert acald_reaction.forward_variable.lb == 0 + assert acald_reaction.forward_variable.ub == 1100. + assert acald_reaction.reverse_variable.lb == 0 + assert acald_reaction.reverse_variable.ub == 100 + + def test_change_bounds(self, core_model): + reac = core_model.reactions.ACALD + reac.change_bounds(lb=2, ub=2) + assert reac.lower_bound == 2 + assert reac.upper_bound == 2 + with TimeMachine() as tm: + reac.change_bounds(lb=5, time_machine=tm) + assert reac.lower_bound == 5 + assert reac.upper_bound == 5 + assert reac.lower_bound == 2 + assert reac.upper_bound == 2 + + def test_make_irreversible(self, core_model): + acald_reaction = core_model.reactions.ACALD + assert acald_reaction.lower_bound == -1000. + assert acald_reaction.upper_bound == 1000. + assert acald_reaction.forward_variable.lb == 0. + assert acald_reaction.forward_variable.ub == 1000. + assert acald_reaction.reverse_variable.lb == 0 + assert acald_reaction.reverse_variable.ub == 1000. + acald_reaction.lower_bound = 0 + assert acald_reaction.lower_bound == 0 + assert acald_reaction.upper_bound == 1000. + assert acald_reaction.forward_variable.lb == 0 + assert acald_reaction.forward_variable.ub == 1000.0 + assert acald_reaction.reverse_variable.lb == 0 + assert acald_reaction.reverse_variable.ub == 0 + acald_reaction.lower_bound = -100 + assert acald_reaction.lower_bound == -100. + assert acald_reaction.upper_bound == 1000. + assert acald_reaction.forward_variable.lb == 0 + assert acald_reaction.forward_variable.ub == 1000. + assert acald_reaction.reverse_variable.lb == 0 + assert acald_reaction.reverse_variable.ub == 100 + + def test_make_reversible(self, core_model): + pfk_reaction = core_model.reactions.PFK + assert pfk_reaction.lower_bound == 0. + assert pfk_reaction.upper_bound == 1000. + assert pfk_reaction.forward_variable.lb == 0. + assert pfk_reaction.forward_variable.ub == 1000. + assert pfk_reaction.reverse_variable.lb == 0 + assert pfk_reaction.reverse_variable.ub == 0 + pfk_reaction.lower_bound = -100. + assert pfk_reaction.lower_bound == -100. + assert pfk_reaction.upper_bound == 1000. + assert pfk_reaction.forward_variable.lb == 0 + assert pfk_reaction.forward_variable.ub == 1000.0 + assert pfk_reaction.reverse_variable.lb == 0 + assert pfk_reaction.reverse_variable.ub == 100. + pfk_reaction.lower_bound = 0 + assert pfk_reaction.lower_bound == 0 + assert pfk_reaction.upper_bound == 1000. + assert pfk_reaction.forward_variable.lb == 0 + assert pfk_reaction.forward_variable.ub == 1000. + assert pfk_reaction.reverse_variable.lb == 0 + assert pfk_reaction.reverse_variable.ub == 0 + + def test_make_irreversible_irreversible_to_the_other_side(self, core_model): + pfk_reaction = core_model.reactions.PFK + assert pfk_reaction.lower_bound == 0. + assert pfk_reaction.upper_bound == 1000. + assert pfk_reaction.forward_variable.lb == 0. + assert pfk_reaction.forward_variable.ub == 1000. + assert pfk_reaction.reverse_variable.lb == 0 + assert pfk_reaction.reverse_variable.ub == 0 + pfk_reaction.upper_bound = -100. + assert pfk_reaction.forward_variable.lb == 0 + assert pfk_reaction.forward_variable.ub == 0 + assert pfk_reaction.reverse_variable.lb == 100 + assert pfk_reaction.reverse_variable.ub == 100 + pfk_reaction.lower_bound = -1000. + assert pfk_reaction.lower_bound == -1000. + assert pfk_reaction.upper_bound == -100. + assert pfk_reaction.forward_variable.lb == 0 + assert pfk_reaction.forward_variable.ub == 0 + assert pfk_reaction.reverse_variable.lb == 100 + assert pfk_reaction.reverse_variable.ub == 1000. + + def test_make_lhs_irreversible_reversible(self, core_model): + rxn = Reaction('test') + rxn.add_metabolites( + {core_model.metabolites[0]: -1., core_model.metabolites[1]: 1.}) + rxn.lower_bound = -1000. + rxn.upper_bound = -100 + core_model.add_reaction(rxn) + assert rxn.lower_bound == -1000. + assert rxn.upper_bound == -100. + assert rxn.forward_variable.lb == 0. + assert rxn.forward_variable.ub == 0. + assert rxn.reverse_variable.lb == 100. + assert rxn.reverse_variable.ub == 1000. + rxn.upper_bound = 666. + assert rxn.lower_bound == -1000. + assert rxn.upper_bound == 666. + assert rxn.forward_variable.lb == 0. + assert rxn.forward_variable.ub == 666 + assert rxn.reverse_variable.lb == 0. + assert rxn.reverse_variable.ub == 1000. + + def test_model_less_reaction(self, solved_model): + solution, model = solved_model + for reaction in model.reactions: + assert isinstance(reaction.flux, float) + assert isinstance(reaction.reduced_cost, float) + for reaction in model.reactions: + model.remove_reactions([reaction]) + assert reaction.flux is None + assert reaction.reduced_cost is None + + def test_knockout(self, core_model): + original_bounds = dict() + for reaction in core_model.reactions: + original_bounds[reaction.id] = ( + reaction.lower_bound, reaction.upper_bound) + reaction.knock_out() + assert reaction.lower_bound == 0 + assert reaction.upper_bound == 0 + for k, (lb, ub) in six.iteritems(original_bounds): + core_model.reactions.get_by_id(k).lower_bound = lb + core_model.reactions.get_by_id(k).upper_bound = ub + for reaction in core_model.reactions: + assert reaction.lower_bound == original_bounds[reaction.id][0] + assert reaction.upper_bound == original_bounds[reaction.id][1] + with TimeMachine() as tm: + for reaction in core_model.reactions: + original_bounds[reaction.id] = ( + reaction.lower_bound, reaction.upper_bound) + reaction.knock_out(time_machine=tm) + assert reaction.lower_bound == 0 + assert reaction.upper_bound == 0 + for reaction in core_model.reactions: + assert reaction.lower_bound == original_bounds[reaction.id][0] + assert reaction.upper_bound == original_bounds[reaction.id][1] + + def test_repr_html_(self, core_model): + assert '
' in core_model.reactions[0]._repr_html_() + + def test_reaction_without_model(self): + r = Reaction('blub') + assert r.flux_expression is None + assert r.forward_variable is None + assert r.reverse_variable is None + + def test_weird_left_to_right_reaction_issue(self, tiny_toy_model): + d1 = tiny_toy_model.reactions.get_by_id('ex1') + assert not d1.reversibility + assert d1.lower_bound == -1000 + assert d1._lower_bound == -1000 + assert d1.upper_bound == 0 + assert d1._upper_bound == 0 + with TimeMachine() as tm: + d1.knock_out(time_machine=tm) + assert d1.lower_bound == 0 + assert d1._lower_bound == 0 + assert d1.upper_bound == 0 + assert d1._upper_bound == 0 + assert d1.lower_bound == -1000 + assert d1._lower_bound == -1000 + assert d1.upper_bound == 0 + assert d1._upper_bound == 0 + + def test_one_left_to_right_reaction_set_positive_ub(self, tiny_toy_model): + d1 = tiny_toy_model.reactions.get_by_id('ex1') + assert d1.reverse_variable.lb == 0 + assert d1.reverse_variable.ub == 1000 + assert d1._lower_bound == -1000 + assert d1.lower_bound == -1000 + assert d1._upper_bound == 0 + assert d1.upper_bound == 0 + assert d1.forward_variable.lb == 0 + assert d1.forward_variable.ub == 0 + d1.upper_bound = .1 + assert d1.forward_variable.lb == 0 + assert d1.forward_variable.ub == .1 + assert d1.reverse_variable.lb == 0 + assert d1.reverse_variable.ub == 1000 + assert d1._lower_bound == -1000 + assert d1.upper_bound == .1 + assert d1._lower_bound == -1000 + assert d1.upper_bound == .1 + + def test_irrev_reaction_set_negative_lb(self, core_model): + assert not core_model.reactions.PFK.reversibility + assert core_model.reactions.PFK.lower_bound == 0 + assert core_model.reactions.PFK.upper_bound == 1000.0 + assert core_model.reactions.PFK.forward_variable.lb == 0 + assert core_model.reactions.PFK.forward_variable.ub == 1000.0 + assert core_model.reactions.PFK.reverse_variable.lb == 0 + assert core_model.reactions.PFK.reverse_variable.ub == 0 + core_model.reactions.PFK.lower_bound = -1000 + assert core_model.reactions.PFK.lower_bound == -1000 + assert core_model.reactions.PFK.upper_bound == 1000.0 + assert core_model.reactions.PFK.forward_variable.lb == 0 + assert core_model.reactions.PFK.forward_variable.ub == 1000.0 + assert core_model.reactions.PFK.reverse_variable.lb == 0 + assert core_model.reactions.PFK.reverse_variable.ub == 1000 + + def test_twist_irrev_right_to_left_reaction_to_left_to_right(self, core_model): + assert not core_model.reactions.PFK.reversibility + assert core_model.reactions.PFK.lower_bound == 0 + assert core_model.reactions.PFK.upper_bound == 1000.0 + assert core_model.reactions.PFK.forward_variable.lb == 0 + assert core_model.reactions.PFK.forward_variable.ub == 1000.0 + assert core_model.reactions.PFK.reverse_variable.lb == 0 + assert core_model.reactions.PFK.reverse_variable.ub == 0 + core_model.reactions.PFK.lower_bound = -1000 + core_model.reactions.PFK.upper_bound = 0 + assert core_model.reactions.PFK.lower_bound == -1000 + assert core_model.reactions.PFK.upper_bound == 0 + assert core_model.reactions.PFK.forward_variable.lb == 0 + assert core_model.reactions.PFK.forward_variable.ub == 0 + assert core_model.reactions.PFK.reverse_variable.lb == 0 + assert core_model.reactions.PFK.reverse_variable.ub == 1000 + + @pytest.mark.skipif(TRAVIS, reason='too slow for ci') + def test_imm904_4hglsdm_problem(self, imm904): + # set upper bound before lower bound after knockout + cp = imm904.copy() + rxn = cp.reactions.get_by_id('4HGLSDm') + prev_lb, prev_ub = rxn.lower_bound, rxn.upper_bound + rxn.lower_bound = 0 + rxn.upper_bound = 0 + rxn.upper_bound = prev_ub + rxn.lower_bound = prev_lb + assert rxn.lower_bound == prev_lb + assert rxn.upper_bound == prev_ub + # set lower bound before upper bound after knockout + cp = imm904.copy() + rxn = cp.reactions.get_by_id('4HGLSDm') + prev_lb, prev_ub = rxn.lower_bound, rxn.upper_bound + rxn.lower_bound = 0 + rxn.upper_bound = 0 + rxn.lower_bound = prev_lb + rxn.upper_bound = prev_ub + assert rxn.lower_bound == prev_lb + assert rxn.upper_bound == prev_ub + + def test_set_lb_higher_than_ub_sets_ub_to_new_lb(self, core_model): + for reaction in core_model.reactions: + assert reaction.lower_bound <= reaction.upper_bound + reaction.lower_bound = reaction.upper_bound + 100 + assert reaction.lower_bound == reaction.upper_bound + + def test_set_ub_lower_than_lb_sets_lb_to_new_ub(self, core_model): + for reaction in core_model.reactions: + assert reaction.lower_bound <= reaction.upper_bound + reaction.upper_bound = reaction.lower_bound - 100 + assert reaction.lower_bound == reaction.upper_bound + + def test_add_metabolites_combine_true(self, core_model): + test_metabolite = Metabolite('test') + for reaction in core_model.reactions: + reaction.add_metabolites({test_metabolite: -66}, combine=True) + assert reaction.metabolites[test_metabolite] == -66 + assert core_model.solver.constraints['test'].expression.has(-66. * reaction.forward_variable) + assert core_model.solver.constraints['test'].expression.has(66. * reaction.reverse_variable) + already_included_metabolite = list(reaction.metabolites.keys())[0] + previous_coefficient = reaction.get_coefficient( + already_included_metabolite.id) + reaction.add_metabolites({already_included_metabolite: 10}, + combine=True) + new_coefficient = previous_coefficient + 10 + assert reaction.metabolites[already_included_metabolite] == new_coefficient + assert core_model.solver.constraints[already_included_metabolite.id].expression.has( + new_coefficient * reaction.forward_variable) + assert core_model.solver.constraints[already_included_metabolite.id].expression.has( + -1 * new_coefficient * reaction.reverse_variable) + + @pytest.mark.skipif(TRAVIS, reason='non-deterministic') + def test_add_metabolites_combine_false(self, core_model): + test_metabolite = Metabolite('test') + for reaction in core_model.reactions: + reaction.add_metabolites({test_metabolite: -66}, combine=False) + assert reaction.metabolites[test_metabolite] == -66 + assert core_model.solver.constraints['test'].expression.has(-66. * reaction.forward_variable) + assert core_model.solver.constraints['test'].expression.has(66. * reaction.reverse_variable) + already_included_metabolite = list(reaction.metabolites.keys())[0] + reaction.add_metabolites({already_included_metabolite: 10}, combine=False) + assert reaction.metabolites[already_included_metabolite] == 10 + assert core_model.solver.constraints[already_included_metabolite.id].expression.has( + 10 * reaction.forward_variable) + assert core_model.solver.constraints[already_included_metabolite.id].expression.has( + -10 * reaction.reverse_variable) + + def test_pop(self, core_model): + pgi = core_model.reactions.PGI + g6p = core_model.metabolites.get_by_id("g6p_c") + f6p = core_model.metabolites.get_by_id("f6p_c") + g6p_expr = core_model.solver.constraints["g6p_c"].expression + g6p_coef = pgi.pop("g6p_c") + assert g6p not in pgi.metabolites + actual = core_model.solver.constraints["g6p_c"].expression.as_coefficients_dict() + expected = (g6p_expr - g6p_coef * pgi.flux_expression).as_coefficients_dict() + assert actual == expected + assert pgi.metabolites[f6p] == 1 + + f6p_expr = core_model.solver.constraints["f6p_c"].expression + f6p_coef = pgi.pop(f6p) + assert f6p not in pgi.metabolites + assert core_model.solver.constraints["f6p_c"].expression.as_coefficients_dict() == ( + f6p_expr - f6p_coef * pgi.flux_expression + ).as_coefficients_dict() + + def test_remove_from_model(self, core_model): + pgi = core_model.reactions.PGI + pgi.remove_from_model() + assert pgi.model is None + assert not ("PGI" in core_model.reactions) + assert not (pgi._get_forward_id() in core_model.solver.variables) + assert not (pgi._get_reverse_id() in core_model.solver.variables) + + def test_delete(self, core_model): + pgi = core_model.reactions.PGI + pgi.delete() + assert pgi.model is None + assert not ("PGI" in core_model.reactions) + assert not (pgi._get_forward_id() in core_model.solver.variables) + assert not (pgi._get_reverse_id() in core_model.solver.variables) + + def test_change_id_is_reflected_in_solver(self, core_model): + for i, reaction in enumerate(core_model.reactions): + old_reaction_id = reaction.id + assert core_model.solver.variables[old_reaction_id].name == old_reaction_id + assert old_reaction_id in core_model.solver.variables + new_reaction_id = reaction.id + '_' + str(i) + reaction.id = new_reaction_id + assert reaction.id == new_reaction_id + assert not (old_reaction_id in core_model.solver.variables) + assert reaction._get_forward_id() in core_model.solver.variables + assert reaction._get_reverse_id() in core_model.solver.variables + name = core_model.solver.variables[reaction._get_forward_id()].name + assert name == reaction._get_forward_id() + + +class TestSolverBasedModel: + def test_model_is_subclassed(self, core_model): + assert isinstance(core_model, cameo.core.SolverBasedModel) + for reac in core_model.reactions: + assert isinstance(reac, Reaction) + for met in reac.metabolites: + assert isinstance(met, Metabolite) + assert met in core_model.metabolites + assert met is core_model.metabolites.get_by_id(met.id) + for gene in reac.genes: + assert isinstance(gene, Gene) + assert gene in core_model.genes + assert gene is core_model.genes.get_by_id(gene.id) + + for gene in core_model.genes: + assert isinstance(gene, Gene) + for reac in gene.reactions: + assert isinstance(reac, Reaction) + assert reac in core_model.reactions + assert reac is core_model.reactions.get_by_id(reac.id) + + for met in core_model.metabolites: + assert isinstance(met, Metabolite) + for reac in met.reactions: + assert isinstance(reac, Reaction) + assert reac in core_model.reactions + assert reac is core_model.reactions.get_by_id(reac.id) + + def test_objective_coefficient_reflects_changed_objective(self, core_model): + biomass_r = core_model.reactions.get_by_id('Biomass_Ecoli_core_N_LPAREN_w_FSLASH_GAM_RPAREN__Nmet2') + assert biomass_r.objective_coefficient == 1 + core_model.objective = "PGI" + assert biomass_r.objective_coefficient == 0 + assert core_model.reactions.PGI.objective_coefficient == 1 + + def test_change_objective_through_objective_coefficient(self, core_model): + biomass_r = core_model.reactions.get_by_id('Biomass_Ecoli_core_N_LPAREN_w_FSLASH_GAM_RPAREN__Nmet2') + pgi = core_model.reactions.PGI + pgi.objective_coefficient = 2 + coef_dict = core_model.solver.objective.expression.as_coefficients_dict() + # Check that objective has been updated + assert coef_dict[pgi.forward_variable] == 2 + assert coef_dict[pgi.reverse_variable] == -2 + # Check that original objective is still in there + assert coef_dict[biomass_r.forward_variable] == 1 + assert coef_dict[biomass_r.reverse_variable] == -1 + + def test_model_from_other_model(self, core_model): + core_model = Model(description=core_model) + for reaction in core_model.reactions: + assert reaction == core_model.reactions.get_by_id(reaction.id) + + def test_add_reactions(self, core_model): + r1 = Reaction('r1') + r1.add_metabolites({Metabolite('A'): -1, Metabolite('B'): 1}) + r1.lower_bound, r1.upper_bound = -999999., 999999. + r2 = Reaction('r2') + r2.add_metabolites( + {Metabolite('A'): -1, Metabolite('C'): 1, Metabolite('D'): 1}) + r2.lower_bound, r2.upper_bound = 0., 999999. + r2.objective_coefficient = 3. + assert r2.objective_coefficient == 3. + core_model.add_reactions([r1, r2]) + assert core_model.reactions[-2] == r1 + assert core_model.reactions[-1] == r2 + assert isinstance(core_model.reactions[-2].reverse_variable, core_model.solver.interface.Variable) + coefficients_dict = core_model.solver.objective.expression.as_coefficients_dict() + biomass_r = core_model.reactions.get_by_id('Biomass_Ecoli_core_N_LPAREN_w_FSLASH_GAM_RPAREN__Nmet2') + assert coefficients_dict[biomass_r.forward_variable] == 1. + assert coefficients_dict[biomass_r.reverse_variable] == -1. + assert coefficients_dict[core_model.reactions.r2.forward_variable] == 3. + assert coefficients_dict[core_model.reactions.r2.reverse_variable] == -3. + + def test_remove_reactions_1(self, core_model): + core_model.remove_reactions([core_model.reactions.PGI, core_model.reactions.PGK], delete=False) + assert "PGI" not in core_model.reactions + assert "PGK" not in core_model.reactions + assert "PGI" not in core_model.reactions + assert "PGK" not in core_model.reactions + + def test_remove_reactions_2(self, core_model): + reactions_to_remove = core_model.reactions[10:30] + assert all([reaction.model is core_model for reaction in reactions_to_remove]) + assert all([core_model.reactions.get_by_id(reaction.id) == reaction for reaction in reactions_to_remove]) + + core_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(core_model.solver.variables.keys()) + + core_model.add_reactions(reactions_to_remove) + for reaction in reactions_to_remove: + assert reaction in core_model.reactions + + def test_remove_and_add_reactions(self, core_model): + model_copy = core_model.copy() + pgi, pgk = model_copy.reactions.PGI, model_copy.reactions.PGK + model_copy.remove_reactions([pgi, pgk], delete=False) + assert "PGI" not in model_copy.reactions + assert "PGK" not in model_copy.reactions + assert "PGI" in core_model.reactions + assert "PGK" in core_model.reactions + model_copy.add_reactions([pgi, pgk]) + assert "PGI" in core_model.reactions + assert "PGK" in core_model.reactions + assert "PGI" in model_copy.reactions + assert "PGK" in model_copy.reactions + + def test_add_cobra_reaction(self, core_model): + r = cobra.Reaction(id="c1") + core_model.add_reaction(r) + assert isinstance(core_model.reactions.c1, Reaction) + + def test_all_objects_point_to_all_other_correct_objects(self, core_model): + for reaction in core_model.reactions: + assert reaction.model == core_model + for gene in reaction.genes: + assert gene == core_model.genes.get_by_id(gene.id) + assert gene.model == core_model + for reaction2 in gene.reactions: + assert reaction2.model == core_model + assert reaction2 == core_model.reactions.get_by_id(reaction2.id) + + for metabolite in reaction.metabolites: + assert metabolite.model == core_model + assert metabolite == core_model.metabolites.get_by_id(metabolite.id) + for reaction2 in metabolite.reactions: + assert reaction2.model == core_model + assert reaction2 == core_model.reactions.get_by_id(reaction2.id) + + def test_objects_point_to_correct_other_after_copy(self, core_model): + for reaction in core_model.reactions: + assert reaction.model == core_model + for gene in reaction.genes: + assert gene == core_model.genes.get_by_id(gene.id) + assert gene.model == core_model + for reaction2 in gene.reactions: + assert reaction2.model == core_model + assert reaction2 == core_model.reactions.get_by_id(reaction2.id) + + for metabolite in reaction.metabolites: + assert metabolite.model == core_model + assert metabolite == core_model.metabolites.get_by_id(metabolite.id) + for reaction2 in metabolite.reactions: + assert reaction2.model == core_model + assert reaction2 == core_model.reactions.get_by_id(reaction2.id) + + def test_add_exchange(self, core_model): + for demand, prefix in {True: 'DemandReaction_', False: 'SupplyReaction_'}.items(): + for metabolite in core_model.metabolites: + demand_reaction = core_model.add_exchange(metabolite, demand=demand, prefix=prefix) + assert core_model.reactions.get_by_id(demand_reaction.id) == demand_reaction + assert demand_reaction.reactants == [metabolite] + assert core_model.solver.constraints[metabolite.id].expression.has( + core_model.solver.variables[prefix + metabolite.id]) + + def test_add_exchange_time_machine(self, core_model): + for demand, prefix in {True: 'DemandReaction_', False: 'SupplyReaction_'}.items(): with TimeMachine() as tm: - metabolite_a.knock_out(time_machine=tm) - self.assertEquals(rxn.upper_bound, 0) - metabolite_b.knock_out(time_machine=tm) - self.assertEquals(rxn.lower_bound, 0) - self.assertEquals(metabolite_a.constraint.lb, -1000 * len(metabolite_a.reactions)) - self.assertEquals(metabolite_a.constraint.ub, 1000 * len(metabolite_a.reactions)) - self.assertEquals(metabolite_a.constraint.lb, 0) - self.assertEquals(metabolite_a.constraint.ub, 0) - self.assertEquals(rxn.upper_bound, 10) - self.assertEquals(rxn.lower_bound, -10) - - def test_remove_from_model(self): - met = self.model.metabolites.get_by_id("g6p_c") - met.remove_from_model() - self.assertFalse(met.id in self.model.metabolites) - self.assertFalse(met.id in self.model.solver.constraints) - - def test_notebook_repr(self): - met = Metabolite(id="test", name="test metabolites", formula="CH4") - self.assertEqual(met._repr_html_(), """ + for metabolite in core_model.metabolites: + demand_reaction = core_model.add_exchange(metabolite, demand=demand, prefix=prefix, time_machine=tm) + assert core_model.reactions.get_by_id(demand_reaction.id) == demand_reaction + assert demand_reaction.reactants == [metabolite] + assert -core_model.solver.constraints[metabolite.id].expression.has( + core_model.solver.variables[prefix + metabolite.id]) + for metabolite in core_model.metabolites: + assert prefix + metabolite.id not in core_model.reactions + assert prefix + metabolite.id not in core_model.solver.variables.keys() + + def test_add_existing_exchange(self, core_model): + for metabolite in core_model.metabolites: + core_model.add_exchange(metabolite, prefix="test") + with pytest.raises(ValueError): + core_model.add_exchange(metabolite, prefix="test") + + def test_objective(self, core_model): + obj = core_model.objective + assert {var.name: coef for var, coef in obj.expression.as_coefficients_dict().items()} == \ + {'Biomass_Ecoli_core_N_LPAREN_w_FSLASH_GAM_RPAREN__Nmet2_reverse_9ebcd': -1, + 'Biomass_Ecoli_core_N_LPAREN_w_FSLASH_GAM_RPAREN__Nmet2': 1} + assert obj.direction == "max" + + def test_change_objective(self, core_model): + expression = 1.0 * core_model.solver.variables['ENO'] + 1.0 * core_model.solver.variables['PFK'] + core_model.solver.objective = core_model.solver.interface.Objective(expression) + assert core_model.solver.objective.expression == expression + + core_model.change_objective("ENO") + eno_obj = core_model.solver.interface.Objective( + core_model.reactions.ENO.flux_expression, direction="max") + pfk_obj = core_model.solver.interface.Objective( + core_model.reactions.PFK.flux_expression, direction="max") + assert core_model.solver.objective == eno_obj + + with TimeMachine() as tm: + core_model.change_objective("PFK", tm) + assert core_model.solver.objective == pfk_obj + assert core_model.solver.objective == eno_obj + + def test_set_reaction_objective(self, core_model): + core_model.objective = core_model.reactions.ACALD + assert str(core_model.solver.objective.expression) == str( + 1.0 * core_model.reactions.ACALD.forward_variable - + 1.0 * core_model.reactions.ACALD.reverse_variable) + + def test_set_reaction_objective_str(self, core_model): + core_model.objective = core_model.reactions.ACALD.id + assert str(core_model.solver.objective.expression) == str( + 1.0 * core_model.reactions.ACALD.forward_variable - + 1.0 * core_model.reactions.ACALD.reverse_variable) + + def test_invalid_objective_raises(self, core_model): + with pytest.raises(ValueError): + setattr(core_model, 'objective', 'This is not a valid objective!') + with pytest.raises(TypeError): + setattr(core_model, 'objective', 3.) + + def test_solver_change(self, core_model): + solver_id = id(core_model.solver) + problem_id = id(core_model.solver.problem) + solution = core_model.solve().x_dict + core_model.solver = 'glpk' + assert id(core_model.solver) != solver_id + assert id(core_model.solver.problem) != problem_id + new_solution = core_model.solve() + for key in list(solution.keys()): + assert round(abs(new_solution.x_dict[key] - solution[key]), + 7) == 0 + + def test_solver_change_with_optlang_interface(self, core_model): + solver_id = id(core_model.solver) + problem_id = id(core_model.solver.problem) + solution = core_model.solve().x_dict + core_model.solver = optlang.glpk_interface + assert id(core_model.solver) != solver_id + assert id(core_model.solver.problem) != problem_id + new_solution = core_model.solve() + for key in list(solution.keys()): + assert round(abs(new_solution.x_dict[key] - solution[key]), + 7) == 0 + + def test_invalid_solver_change_raises(self, core_model): + with pytest.raises(ValueError): + setattr(core_model, 'solver', [1, 2, 3]) + with pytest.raises(ValueError): + setattr(core_model, 'solver', + 'ThisIsDefinitelyNotAvalidSolver') + with pytest.raises(ValueError): + setattr(core_model, 'solver', os) + + @pytest.mark.skipif('cplex' not in solvers, reason='no cplex') + def test_change_solver_to_cplex_and_check_copy_works(self, core_model): + assert round(abs(core_model.optimize().f - 0.8739215069684306), 7) == 0 + core_model_copy = core_model.copy() + assert round(abs(core_model_copy.optimize().f - 0.8739215069684306), 7) == 0 + # Second, change existing glpk based model to cplex + core_model.solver = 'cplex' + assert round(abs(core_model.optimize().f - 0.8739215069684306), 7) == 0 + core_model_copy = copy.copy(core_model) + assert round(abs(core_model_copy.optimize().f - 0.8739215069684306), 7) == 0 + + def test_copy_preserves_existing_solution(self, solved_model): + solution, model = solved_model + model_cp = copy.copy(model) + primals_original = [variable.primal for variable in model.solver.variables] + primals_copy = [variable.primal for variable in model_cp.solver.variables] + abs_diff = abs(numpy.array(primals_copy) - numpy.array(primals_original)) + assert not any(abs_diff > 1e-6) + + def test_essential_genes(self, core_model): + observed_essential_genes = [g.id for g in core_model.essential_genes()] + assert sorted(observed_essential_genes) == sorted(ESSENTIAL_GENES) + with pytest.raises(cameo.exceptions.SolveError): + core_model.reactions.Biomass_Ecoli_core_N_LPAREN_w_FSLASH_GAM_RPAREN__Nmet2.lower_bound = 999999. + core_model.essential_genes() + + def test_essential_reactions(self, core_model): + observed_essential_reactions = [r.id for r in core_model.essential_reactions()] + assert sorted(observed_essential_reactions) == sorted(ESSENTIAL_REACTIONS) + with pytest.raises(cameo.exceptions.SolveError): + core_model.reactions.Biomass_Ecoli_core_N_LPAREN_w_FSLASH_GAM_RPAREN__Nmet2.lower_bound = 999999. + core_model.essential_reactions() + + def test_essential_metabolites(self, core_model): + essential_metabolites_unbalanced = [m.id for m in core_model.essential_metabolites(force_steady_state=False)] + essential_metabolites_balanced = [m.id for m in core_model.essential_metabolites(force_steady_state=True)] + assert sorted(essential_metabolites_unbalanced) == sorted(ESSENTIAL_METABOLITES) + assert sorted(essential_metabolites_balanced) == sorted(ESSENTIAL_METABOLITES) + + with pytest.raises(cameo.exceptions.SolveError): + core_model.reactions.Biomass_Ecoli_core_N_LPAREN_w_FSLASH_GAM_RPAREN__Nmet2.lower_bound = 999999. + core_model.essential_metabolites(force_steady_state=False) + + with pytest.raises(cameo.exceptions.SolveError): + core_model.reactions.Biomass_Ecoli_core_N_LPAREN_w_FSLASH_GAM_RPAREN__Nmet2.lower_bound = 999999. + core_model.essential_metabolites(force_steady_state=True) + + def test_effective_bounds(self, core_model): + core_model.reactions.Biomass_Ecoli_core_N_LPAREN_w_FSLASH_GAM_RPAREN__Nmet2.lower_bound = 0.873921 + for reaction in core_model.reactions: + assert abs(reaction.effective_lower_bound - REFERENCE_FVA_SOLUTION_ECOLI_CORE['lower_bound'][ + reaction.id]) < 0.000001 + assert abs(reaction.effective_upper_bound - REFERENCE_FVA_SOLUTION_ECOLI_CORE['upper_bound'][ + reaction.id]) < 0.000001 + + def test_add_demand_for_non_existing_metabolite(self, core_model): + metabolite = Metabolite(id="a_metabolite") + core_model.add_demand(metabolite) + assert core_model.solver.constraints[metabolite.id].expression.has( + core_model.solver.variables["DM_" + metabolite.id]) + + def test_add_ratio_constraint(self, solved_model): + solution, model = solved_model + assert round(abs(solution.f - 0.873921506968), 7) == 0 + assert 2 * solution.x_dict['PGI'] != solution.x_dict['G6PDH2r'] + cp = model.copy() + ratio_constr = cp.add_ratio_constraint(cp.reactions.PGI, cp.reactions.G6PDH2r, 0.5) + assert ratio_constr.name == 'ratio_constraint_PGI_G6PDH2r' + solution = cp.solve() + assert round(abs(solution.f - 0.870407873712), 7) == 0 + assert round(abs(2 * solution.x_dict['PGI'] - solution.x_dict['G6PDH2r']), 7) == 0 + cp = model.copy() + + ratio_constr = cp.add_ratio_constraint(cp.reactions.PGI, cp.reactions.G6PDH2r, 0.5) + assert ratio_constr.name == 'ratio_constraint_PGI_G6PDH2r' + solution = cp.solve() + assert round(abs(solution.f - 0.870407873712), 7) == 0 + assert round(abs(2 * solution.x_dict['PGI'] - solution.x_dict['G6PDH2r']), 7) == 0 + + cp = model.copy() + ratio_constr = cp.add_ratio_constraint('PGI', 'G6PDH2r', 0.5) + assert ratio_constr.name == 'ratio_constraint_PGI_G6PDH2r' + solution = cp.solve() + assert abs(solution.f - 0.870407) < 1e-6 + assert abs(2 * solution.x_dict['PGI'] - solution.x_dict['G6PDH2r']) < 1e-6 + + cp = model.copy() + ratio_constr = cp.add_ratio_constraint([cp.reactions.PGI, cp.reactions.ACALD], + [cp.reactions.G6PDH2r, cp.reactions.ACONTa], 0.5) + assert ratio_constr.name == 'ratio_constraint_PGI+ACALD_G6PDH2r+ACONTa' + solution = cp.solve() + assert abs(solution.f - 0.872959) < 1e-6 + assert abs((solution.x_dict['PGI'] + solution.x_dict['ACALD']) - + 0.5 * (solution.x_dict['G6PDH2r'] + solution.x_dict['ACONTa'])) < 1e-5 + + def test_fix_objective_as_constraint(self, core_model): + # with TimeMachine + with TimeMachine() as tm: + core_model.fix_objective_as_constraint(time_machine=tm) + constraint_name = core_model.solver.constraints[-1] + assert core_model.solver.constraints[-1].expression - core_model.objective.expression == 0 + assert constraint_name not in core_model.solver.constraints + # without TimeMachine + core_model.fix_objective_as_constraint() + constraint_name = core_model.solver.constraints[-1] + assert core_model.solver.constraints[-1].expression - core_model.objective.expression == 0 + assert constraint_name in core_model.solver.constraints + + def test_reactions_for(self, core_model): + with TimeMachine() as tm: + for r in core_model.reactions: + assert isinstance(core_model._reaction_for(r.id, time_machine=tm), Reaction) + assert isinstance(core_model._reaction_for(r, time_machine=tm), Reaction) + for m in core_model.metabolites: + assert isinstance(core_model._reaction_for(m.id, time_machine=tm), Reaction) + assert isinstance(core_model._reaction_for(m, time_machine=tm), Reaction) + + with pytest.raises(KeyError): + core_model._reaction_for(None) + with pytest.raises(KeyError): + core_model._reaction_for("blablabla") + with pytest.raises(KeyError): + core_model._reaction_for("accoa_lp_c_lp_", add=False) + + def test_stoichiometric_matrix(self, core_model): + stoichiometric_matrix = create_stoichiometric_array(core_model) + assert len(core_model.reactions) == stoichiometric_matrix.shape[1] + assert len(core_model.metabolites) == stoichiometric_matrix.shape[0] + + for i, reaction in enumerate(core_model.reactions): + for j, metabolite in enumerate(core_model.metabolites): + if metabolite in reaction.metabolites: + coefficient = reaction.metabolites[metabolite] + else: + coefficient = 0 + assert stoichiometric_matrix[j, i] == coefficient + + def test_set_medium(self, core_model): + medium = core_model.medium + for reaction in core_model.exchanges: + if reaction.lower_bound == 0: + assert reaction.id not in medium.reaction_id.values + if reaction.lower_bound < 0: + assert reaction.id in medium.reaction_id.values + core_model.load_medium(medium) + for rid in core_model.medium.reaction_id: + assert len(medium[medium.reaction_id == rid]) == 1 + + def test_solver_change_preserves_non_metabolic_constraints(self, core_model): + core_model.add_ratio_constraint(core_model.reactions.PGK, core_model.reactions.PFK, 1 / 2) + all_constraint_ids = core_model.solver.constraints.keys() + assert all_constraint_ids[-1], 'ratio_constraint_PGK_PFK' + resurrected = pickle.loads(pickle.dumps(core_model)) + assert resurrected.solver.constraints.keys() == all_constraint_ids + + +class TestMetabolite: + def test_set_id(self, core_model): + met = Metabolite("test") + with pytest.raises(TypeError): + setattr(met, 'id', 1) + core_model.add_metabolites([met]) + with pytest.raises(ValueError): + setattr(met, "id", 'g6p_c') + met.id = "test2" + assert "test2" in core_model.metabolites + assert "test" not in core_model.metabolites + + def test_knock_out(self, core_model): + rxn = Reaction('rxn', upper_bound=10, lower_bound=-10) + metabolite_a = Metabolite('A') + metabolite_b = Metabolite('B') + rxn.add_metabolites({metabolite_a: -1, metabolite_b: 1}) + core_model.add_reaction(rxn) + with TimeMachine() as tm: + metabolite_a.knock_out(time_machine=tm) + assert rxn.upper_bound == 0 + metabolite_b.knock_out(time_machine=tm) + assert rxn.lower_bound == 0 + assert metabolite_a.constraint.lb == -1000 * len(metabolite_a.reactions) + assert metabolite_a.constraint.ub == 1000 * len(metabolite_a.reactions) + assert metabolite_a.constraint.lb == 0 + assert metabolite_a.constraint.ub == 0 + assert rxn.upper_bound == 10 + assert rxn.lower_bound == -10 + + def test_remove_from_model(self, core_model): + met = core_model.metabolites.get_by_id("g6p_c") + met.remove_from_model() + assert not (met.id in core_model.metabolites) + assert not (met.id in core_model.solver.constraints) + + def test_notebook_repr(self): + met = Metabolite(id="test", name="test metabolites", formula="CH4") + expected = """
@@ -1175,26 +1060,6 @@ def test_notebook_repr(self): - -
Idtest
FormulaCH4
""") - - -class TestMetaboliteGLPK(WrappedAbstractTestMetabolite.AbstractTestMetabolite): - def setUp(self): - self.cobrapy_model = COBRAPYTESTMODEL.copy() - self.model = TESTMODEL.copy() - self.model.solver = 'glpk' - - -@unittest.skipIf('cplex' not in solvers, "No cplex interface available") -class TestMetaboliteCPLEX(WrappedAbstractTestMetabolite.AbstractTestMetabolite): - def setUp(self): - self.cobrapy_model = COBRAPYTESTMODEL.copy() - self.model = TESTMODEL.copy() - self.model.solver = 'cplex' - - -if __name__ == '__main__': - import nose - - nose.runmodule() + + """.replace(' ', '') + assert met._repr_html_().replace(' ', '') == expected diff --git a/tests/test_strain_design_deterministic.py b/tests/test_strain_design_deterministic.py index 04bfac100..edb36105f 100644 --- a/tests/test_strain_design_deterministic.py +++ b/tests/test_strain_design_deterministic.py @@ -15,110 +15,110 @@ from __future__ import absolute_import, print_function import os -import unittest import pandas +import pytest from pandas import DataFrame from pandas.util.testing import assert_frame_equal import cameo -from cameo import load_model +from cameo import fba from cameo.config import solvers -from cameo.strain_design.deterministic.flux_variability_based import FSEOF, FSEOFResult, DifferentialFVA +from cameo.strain_design.deterministic.flux_variability_based import (FSEOF, + DifferentialFVA, + FSEOFResult) +from cameo.exceptions import Infeasible from cameo.strain_design.deterministic.linear_programming import OptKnock +from cameo.util import TimeMachine -TRAVIS = os.getenv('TRAVIS', False) +TRAVIS = bool(os.getenv('TRAVIS', False)) TESTDIR = os.path.dirname(__file__) -ECOLICORE = load_model(os.path.join(TESTDIR, 'data/EcoliCore.xml')) -def assert_dataframes_equal(df, expected): - try: - assert_frame_equal(df, expected, check_names=False) - return True - except AssertionError: - return False +@pytest.fixture(scope='module') +def cplex_optknock(model): + cplex_core = model.copy() + cplex_core.reactions.Biomass_Ecoli_core_N_lp_w_fsh_GAM_rp__Nmet2.lower_bound = 0.1 + cplex_core.solver = "cplex" + return cplex_core, OptKnock(cplex_core) -class TestFSEOF(unittest.TestCase): - def setUp(self): - self.model = ECOLICORE.copy() - self.model.solver = 'glpk' - - def test_fseof(self): - objective = self.model.objective - fseof = FSEOF(self.model) +class TestFSEOF: + def test_fseof(self, model): + objective = model.objective + fseof = FSEOF(model) fseof_result = fseof.run(target="EX_succ_lp_e_rp_") - self.assertIsInstance(fseof_result, FSEOFResult) - self.assertIs(objective, self.model.objective) - - def test_fseof_result(self): - fseof = FSEOF(self.model) - fseof_result = fseof.run(target=self.model.reactions.EX_ac_lp_e_rp_) - self.assertIsInstance(fseof_result.data_frame, DataFrame) - self.assertIs(fseof_result.target, self.model.reactions.EX_ac_lp_e_rp_) - self.assertIs(fseof_result.model, self.model) - - -# if six.PY2: # Make these test cases work with PY3 as well -class TestDifferentialFVA(unittest.TestCase): - def setUp(self): - self.model = ECOLICORE.copy() - - def test_minimal_input(self): - result = DifferentialFVA(self.model, self.model.reactions.EX_succ_lp_e_rp_, points=5).run() - - # result.data_frame.iloc[0].to_csv(os.path.join(TESTDIR, 'data/REFERENCE_DiffFVA1.csv')) - ref_df = pandas.read_csv(os.path.join(TESTDIR, 'data/REFERENCE_DiffFVA1.csv'), index_col=0).astype( - "O").sort_index(axis=1) + assert isinstance(fseof_result, FSEOFResult) + assert objective is model.objective + + def test_fseof_result(self, model): + fseof = FSEOF(model) + fseof_result = fseof.run(target=model.reactions.EX_ac_lp_e_rp_) + assert isinstance(fseof_result.data_frame, DataFrame) + assert fseof_result.target is model.reactions.EX_ac_lp_e_rp_ + assert fseof_result.model is model + + +class TestDifferentialFVA: + def test_minimal_input(self, model): + result = DifferentialFVA(model, model.reactions.EX_succ_lp_e_rp_, points=5).run() + ref_df = (pandas.read_csv(os.path.join(TESTDIR, 'data/REFERENCE_DiffFVA1.csv'), index_col=0) + .astype("O") + .sort_index(axis=1)) pandas.util.testing.assert_frame_equal(result.data_frame.iloc[0].sort_index(axis=1), ref_df) - def test_with_reference_model(self): - reference_model = self.model.copy() + def test_apply_designs(self, model): + result = DifferentialFVA(model, model.reactions.EX_succ_lp_e_rp_, points=5).run() + works = [] + for strain_design in result: + with TimeMachine() as tm: + strain_design.apply(model, tm) + try: + solution = fba(model, objective="Biomass_Ecoli_core_N_lp_w_fsh_GAM_rp__Nmet2") + works.append(solution["EX_succ_lp_e_rp_"] > 1e-6 and solution.objective_value > 1e-6) + except Infeasible: + works.append(False) + assert any(works) + + def test_with_reference_model(self, model): + reference_model = model.copy() biomass_rxn = reference_model.reactions.Biomass_Ecoli_core_N_lp_w_fsh_GAM_rp__Nmet2 biomass_rxn.lower_bound = 0.3 target = reference_model.reactions.EX_succ_lp_e_rp_ target.lower_bound = 2 - result = DifferentialFVA(self.model, target, reference_model=reference_model, points=5).run() - # result.data_frame.iloc[0].to_csv(os.path.join(TESTDIR, 'data/REFERENCE_DiffFVA2.csv')) - ref_df = pandas.read_csv(os.path.join(TESTDIR, 'data/REFERENCE_DiffFVA2.csv'), index_col=0).astype( - "O").sort_index(axis=1) + result = DifferentialFVA(model, target, reference_model=reference_model, points=5).run() + ref_df = (pandas.read_csv(os.path.join(TESTDIR, 'data/REFERENCE_DiffFVA2.csv'), index_col=0) + .astype("O") + .sort_index(axis=1)) pandas.util.testing.assert_frame_equal(result.data_frame.iloc[0].sort_index(axis=1), ref_df) -@unittest.skipIf('cplex' not in solvers, "No cplex interface available") -class TestOptKnock(unittest.TestCase): - def setUp(self): - self.model = ECOLICORE.copy() - self.model.reactions.Biomass_Ecoli_core_N_lp_w_fsh_GAM_rp__Nmet2.lower_bound = 0.1 - self.model.solver = "cplex" - self.optknock = OptKnock(self.model) - - def test_optknock_runs(self): - result = self.optknock.run(max_knockouts=0, target="EX_ac_lp_e_rp_", - biomass="Biomass_Ecoli_core_N_lp_w_fsh_GAM_rp__Nmet2", max_results=1) - self.assertEqual(len(result), 1) - self.assertEqual(len(result.knockouts[0]), 0) - self.assertEqual(len(list(result)), 1) - self.assertIsInstance(result.data_frame, DataFrame) - - def test_result_is_correct(self): - result = self.optknock.run(max_knockouts=1, target="EX_ac_lp_e_rp_", - biomass="Biomass_Ecoli_core_N_lp_w_fsh_GAM_rp__Nmet2", max_results=1) +@pytest.mark.skipif('cplex' not in solvers, reason="No cplex interface available") +class TestOptKnock: + def test_optknock_runs(self, cplex_optknock): + _, optknock = cplex_optknock + result = optknock.run(max_knockouts=0, target="EX_ac_lp_e_rp_", + biomass="Biomass_Ecoli_core_N_lp_w_fsh_GAM_rp__Nmet2", max_results=1) + assert len(result) == 1 + assert len(result.knockouts[0]) == 0 + assert len(list(result)) == 1 + assert isinstance(result.data_frame, DataFrame) + + def test_result_is_correct(self, cplex_optknock): + model, optknock = cplex_optknock + result = optknock.run(max_knockouts=1, target="EX_ac_lp_e_rp_", + biomass="Biomass_Ecoli_core_N_lp_w_fsh_GAM_rp__Nmet2", max_results=1) production = result.production[0] knockouts = result.knockouts[0] for knockout in knockouts: - self.model.reactions.get_by_id(knockout).knock_out() - fva = cameo.flux_variability_analysis(self.model, fraction_of_optimum=1, remove_cycles=False, + model.reactions.get_by_id(knockout).knock_out() + fva = cameo.flux_variability_analysis(model, fraction_of_optimum=1, remove_cycles=False, reactions=["EX_ac_lp_e_rp_"]) - self.assertAlmostEqual(fva["upper_bound"][0], production, delta=1e-6) - - def test_invalid_input(self): - self.assertRaises(KeyError, self.optknock.run, target="EX_ac_lp_e_rp_") - self.assertRaises(KeyError, self.optknock.run, biomass="Biomass_Ecoli_core_N_lp_w_fsh_GAM_rp__Nmet2") - - -if __name__ == "__main__": - import nose - - nose.runmodule() + assert abs(fva["upper_bound"][0] - production) < 1e-6 + + def test_invalid_input(self, cplex_optknock): + _, optknock = cplex_optknock + with pytest.raises(KeyError): + optknock.run(target="EX_ac_lp_e_rp_") + with pytest.raises(KeyError): + optknock.run(biomass="Biomass_Ecoli_core_N_lp_w_fsh_GAM_rp__Nmet2") diff --git a/tests/test_strain_design_heuristics.py b/tests/test_strain_design_heuristics.py index 02ca5220f..c08c7f8f8 100644 --- a/tests/test_strain_design_heuristics.py +++ b/tests/test_strain_design_heuristics.py @@ -16,51 +16,65 @@ import os import pickle -import unittest from collections import namedtuple from math import sqrt from tempfile import mkstemp import inspyred import numpy +import pytest import six from inspyred.ec import Bounder from inspyred.ec.emo import Pareto from ordered_set import OrderedSet -from six.moves import range -from cameo import load_model, fba, config +from cameo import config, fba from cameo.core.manipulation import swap_cofactors from cameo.parallel import SequentialView +from cameo.strain_design.heuristic.evolutionary.archives import (BestSolutionArchive, + Individual) +from cameo.strain_design.heuristic.evolutionary.decoders import (GeneSetDecoder, + ReactionSetDecoder, + SetDecoder) +from cameo.strain_design.heuristic.evolutionary.evaluators import KnockoutEvaluator +from cameo.strain_design.heuristic.evolutionary.generators import (linear_set_generator, + multiple_chromosome_set_generator, + set_generator) +from cameo.strain_design.heuristic.evolutionary.genomes import MultipleChromosomeGenome +from cameo.strain_design.heuristic.evolutionary.metrics import (euclidean_distance, + manhattan_distance) +from cameo.strain_design.heuristic.evolutionary.multiprocess.migrators import MultiprocessingMigrator +from cameo.strain_design.heuristic.evolutionary.objective_functions import (MultiObjectiveFunction, + YieldFunction, + biomass_product_coupled_min_yield, + biomass_product_coupled_yield, + number_of_knockouts, + product_yield) +from cameo.strain_design.heuristic.evolutionary.optimization import (NADH_NADPH, + CofactorSwapOptimization, + EvaluatorWrapper, + HeuristicOptimization, + ReactionKnockoutOptimization, + SolutionSimplification, + TargetOptimizationResult, + set_distance_function, + GeneKnockoutOptimization) +from cameo.strain_design.heuristic.evolutionary.variators import (_do_set_n_point_crossover, + multiple_chromosome_set_indel, + multiple_chromosome_set_mutation, + set_indel, + set_mutation, + set_n_point_crossover) +from cameo.util import RandomGenerator as Random +from cameo.util import TimeMachine +from six.moves import range + try: from cameo.parallel import RedisQueue except ImportError: RedisQueue = None -from cameo.strain_design.heuristic.evolutionary.archives import Individual, BestSolutionArchive -from cameo.strain_design.heuristic.evolutionary.decoders import ReactionSetDecoder, SetDecoder, \ - GeneSetDecoder -from cameo.strain_design.heuristic.evolutionary.generators import set_generator, \ - multiple_chromosome_set_generator, linear_set_generator -from cameo.strain_design.heuristic.evolutionary.genomes import MultipleChromosomeGenome -from cameo.strain_design.heuristic.evolutionary.metrics import euclidean_distance -from cameo.strain_design.heuristic.evolutionary.metrics import manhattan_distance - -from cameo.strain_design.heuristic.evolutionary.multiprocess.migrators import MultiprocessingMigrator - -from cameo.strain_design.heuristic.evolutionary.objective_functions import biomass_product_coupled_yield, \ - product_yield, number_of_knockouts, biomass_product_coupled_min_yield, MultiObjectiveFunction, YieldFunction -from cameo.strain_design.heuristic.evolutionary.optimization import HeuristicOptimization, \ - ReactionKnockoutOptimization, set_distance_function, TargetOptimizationResult, EvaluatorWrapper, \ - CofactorSwapOptimization, SolutionSimplification, NADH_NADPH - -from cameo.strain_design.heuristic.evolutionary.evaluators import KnockoutEvaluator - -from cameo.strain_design.heuristic.evolutionary.variators import _do_set_n_point_crossover, set_n_point_crossover, \ - set_mutation, set_indel, multiple_chromosome_set_mutation, multiple_chromosome_set_indel -from cameo.util import RandomGenerator as Random, TimeMachine - -TRAVIS = os.getenv('TRAVIS', False) +TRAVIS = bool(os.getenv('TRAVIS', False)) if os.getenv('REDIS_PORT_6379_TCP_ADDR'): REDIS_HOST = os.getenv('REDIS_PORT_6379_TCP_ADDR') # wercker @@ -68,13 +82,7 @@ REDIS_HOST = 'localhost' SEED = 1234 - CURRENT_PATH = os.path.dirname(__file__) -CORE_MODEL_PATH = os.path.join(CURRENT_PATH, "data/EcoliCore.xml") -IAF1260_MODEL_PATH = os.path.join(CURRENT_PATH, "data/iAF1260.xml") - -TEST_MODEL = load_model(CORE_MODEL_PATH, sanitize=False) -IAF1260_MODEL = load_model(IAF1260_MODEL_PATH, sanitize=False) SOLUTIONS = [ [[1, 2, 3], 0.1], @@ -90,34 +98,43 @@ ] -class TestWithModel: - class TestWithEColiCore(unittest.TestCase): - def setUp(self): - self.model = load_model(CORE_MODEL_PATH, sanitize=False) +@pytest.fixture(scope='function') +def generators(model): + mockup_evolutionary_algorithm = namedtuple("EA", ["bounder"]) + args = {} + args.setdefault('representation', [r.id for r in model.reactions]) + random = Random() + return args, random, mockup_evolutionary_algorithm + - class TestWithiAF1260Model(unittest.TestCase): - def setUp(self): - self.model = load_model(IAF1260_MODEL_PATH, sanitize=False) +@pytest.fixture(scope="module") +def objectives(): + single_objective_function = product_yield('product', 'substrate') + multi_objective_function = MultiObjectiveFunction([ + product_yield('product', 'substrate'), + number_of_knockouts() + ]) + return single_objective_function, multi_objective_function -class TestMetrics(unittest.TestCase): +class TestMetrics: def test_euclidean_distance(self): distance = euclidean_distance({'a': 9}, {'a': 3}) - self.assertEqual(distance, sqrt((9 - 3) ** 2)) + assert distance == sqrt((9 - 3) ** 2) def test_manhattan_distance(self): distance = manhattan_distance({'a': 9}, {'a': 3}) - self.assertEqual(distance, abs(9 - 3)) + assert distance == abs(9 - 3) -class TestBestSolutionArchive(unittest.TestCase): +class TestBestSolutionArchive: def test_solution_string(self): sol1 = Individual(SOLUTIONS[0][0], SOLUTIONS[0][1]) sol2 = Individual(SOLUTIONS[1][0], SOLUTIONS[1][1]) sol3 = Individual(SOLUTIONS[2][0], SOLUTIONS[2][1]) - self.assertEqual(sol1.__str__(), "[1, 2, 3] - 0.1 sense: max") - self.assertEqual(sol2.__str__(), "[1, 2, 3, 4] - 0.1 sense: max") - self.assertEqual(sol3.__str__(), "[2, 3, 4] - 0.45 sense: max") + assert sol1.__str__() == "[1, 2, 3] - 0.1 sense: max" + assert sol2.__str__() == "[1, 2, 3, 4] - 0.1 sense: max" + assert sol3.__str__() == "[2, 3, 4] - 0.45 sense: max" def test_solution_comparison_maximization(self): sol1 = Individual(SOLUTIONS[0][0], SOLUTIONS[0][1]) @@ -125,43 +142,43 @@ def test_solution_comparison_maximization(self): sol3 = Individual(SOLUTIONS[2][0], SOLUTIONS[2][1]) # test ordering - self.assertEqual(sol1.__cmp__(sol2), -1) - self.assertEqual(sol1.__cmp__(sol1), 0) - self.assertEqual(sol1.__cmp__(sol3), 1) + assert sol1.__cmp__(sol2) == -1 + assert sol1.__cmp__(sol1) == 0 + assert sol1.__cmp__(sol3) == 1 - self.assertTrue(sol1 < sol2) - self.assertTrue(sol1 == sol1) - self.assertTrue(sol1 > sol3) + assert sol1 < sol2 + assert sol1 == sol1 + assert sol1 > sol3 # test gt and lt - self.assertTrue(sol1.__lt__(sol2)) - self.assertTrue(sol1.__gt__(sol3)) - self.assertFalse(sol1.__lt__(sol1)) - self.assertFalse(sol1.__gt__(sol1)) - self.assertFalse(sol2.__lt__(sol1)) - self.assertFalse(sol3.__gt__(sol1)) + assert sol1.__lt__(sol2) + assert sol1.__gt__(sol3) + assert not sol1.__lt__(sol1) + assert not sol1.__gt__(sol1) + assert not sol2.__lt__(sol1) + assert not sol3.__gt__(sol1) # testing issubset - self.assertTrue(sol1.issubset(sol2), msg="Solution 1 is subset of Solution 2") - self.assertFalse(sol2.issubset(sol1), msg="Solution 2 is not subset of Solution 1") - self.assertTrue(sol3.issubset(sol2), msg="Solution 3 is subset of Solution 2") - self.assertFalse(sol2.issubset(sol3), msg="Solution 2 is not subset of Solution 3") - self.assertFalse(sol1.issubset(sol3), msg="Solution 1 is subset of Solution 3") - self.assertFalse(sol2.issubset(sol3), msg="Solution 3 is not subset of Solution 1") + assert sol1.issubset(sol2), "Solution 1 is subset of Solution 2" + assert not sol2.issubset(sol1), "Solution 2 is not subset of Solution 1" + assert sol3.issubset(sol2), "Solution 3 is subset of Solution 2" + assert not sol2.issubset(sol3), "Solution 2 is not subset of Solution 3" + assert not sol1.issubset(sol3), "Solution 1 is subset of Solution 3" + assert not sol2.issubset(sol3), "Solution 3 is not subset of Solution 1" # test difference l = len(sol2.symmetric_difference(sol1)) - self.assertEqual(l, 1, msg="Difference between Solution 2 and 1 is (%s)" % sol2.symmetric_difference(sol1)) + assert l == 1, "Difference between Solution 2 and 1 is (%s)" % sol2.symmetric_difference(sol1) l = len(sol3.symmetric_difference(sol2)) - self.assertEqual(l, 1, msg="Difference between Solution 3 and 1 is (%s)" % sol3.symmetric_difference(sol2)) + assert l == 1, "Difference between Solution 3 and 1 is (%s)" % sol3.symmetric_difference(sol2) l = len(sol3.symmetric_difference(sol1)) - self.assertEqual(l, 2, msg="Difference between Solution 1 and 3 is (%s)" % sol3.symmetric_difference(sol1)) + assert l == 2, "Difference between Solution 1 and 3 is (%s)" % sol3.symmetric_difference(sol1) - self.assertTrue(sol1.improves(sol2), msg="Solution 1 is better than Solution 2") - self.assertTrue(sol3.improves(sol2), msg="Solution 3 is better than Solution 2") - self.assertFalse(sol3.improves(sol1), msg="Solution 3 does not improve Solution 1") - self.assertFalse(sol2.improves(sol1), msg="Solution 2 does not improve Solution 1") - self.assertFalse(sol2.improves(sol3), msg="Solution 2 does not improve Solution 3") + assert sol1.improves(sol2), "Solution 1 is better than Solution 2" + assert sol3.improves(sol2), "Solution 3 is better than Solution 2" + assert not sol3.improves(sol1), "Solution 3 does not improve Solution 1" + assert not sol2.improves(sol1), "Solution 2 does not improve Solution 1" + assert not sol2.improves(sol3), "Solution 2 does not improve Solution 3" def test_solution_comparison_minimization(self): sol1 = Individual(SOLUTIONS[0][0], SOLUTIONS[0][1], maximize=False) @@ -169,68 +186,68 @@ def test_solution_comparison_minimization(self): sol3 = Individual(SOLUTIONS[2][0], SOLUTIONS[2][1], maximize=False) # test ordering - self.assertEqual(sol1.__cmp__(sol2), -1) - self.assertEqual(sol1.__cmp__(sol1), 0) - self.assertEqual(sol1.__cmp__(sol3), -1) - self.assertEqual(sol3.__cmp__(sol1), 1) + assert sol1.__cmp__(sol2) == -1 + assert sol1.__cmp__(sol1) == 0 + assert sol1.__cmp__(sol3) == -1 + assert sol3.__cmp__(sol1) == 1 - self.assertTrue(sol1 < sol2) - self.assertTrue(sol1 == sol1) - self.assertTrue(sol1 < sol3) + assert sol1 < sol2 + assert sol1 == sol1 + assert sol1 < sol3 # test gt and lt - self.assertTrue(sol1.__lt__(sol2)) - self.assertTrue(sol1.__lt__(sol3)) - self.assertFalse(sol1.__gt__(sol1)) - self.assertFalse(sol1.__lt__(sol1)) - self.assertTrue(sol2.__gt__(sol1)) - self.assertFalse(sol3.__lt__(sol1)) + assert sol1.__lt__(sol2) + assert sol1.__lt__(sol3) + assert not sol1.__gt__(sol1) + assert not sol1.__lt__(sol1) + assert sol2.__gt__(sol1) + assert not sol3.__lt__(sol1) # testing issubset - self.assertTrue(sol1.issubset(sol2), msg="Solution 1 is subset of Solution 2") - self.assertFalse(sol2.issubset(sol1), msg="Solution 2 is not subset of Solution 1") - self.assertTrue(sol3.issubset(sol2), msg="Solution 3 is subset of Solution 2") - self.assertFalse(sol2.issubset(sol3), msg="Solution 2 is not subset of Solution 3") - self.assertFalse(sol1.issubset(sol3), msg="Solution 1 is subset of Solution 3") - self.assertFalse(sol2.issubset(sol3), msg="Solution 3 is not subset of Solution 1") + assert sol1.issubset(sol2), "Solution 1 is subset of Solution 2" + assert not sol2.issubset(sol1), "Solution 2 is not subset of Solution 1" + assert sol3.issubset(sol2), "Solution 3 is subset of Solution 2" + assert not sol2.issubset(sol3), "Solution 2 is not subset of Solution 3" + assert not sol1.issubset(sol3), "Solution 1 is subset of Solution 3" + assert not sol2.issubset(sol3), "Solution 3 is not subset of Solution 1" # test difference l = len(sol2.symmetric_difference(sol1)) - self.assertEqual(l, 1, msg="Difference between Solution 2 and 1 is (%s)" % sol2.symmetric_difference(sol1)) + assert l == 1, "Difference between Solution 2 and 1 is (%s)" % sol2.symmetric_difference(sol1) l = len(sol3.symmetric_difference(sol2)) - self.assertEqual(l, 1, msg="Difference between Solution 3 and 1 is (%s)" % sol3.symmetric_difference(sol2)) + assert l == 1, "Difference between Solution 3 and 1 is (%s)" % sol3.symmetric_difference(sol2) l = len(sol3.symmetric_difference(sol1)) - self.assertEqual(l, 2, msg="Difference between Solution 1 and 3 is (%s)" % sol3.symmetric_difference(sol1)) + assert l == 2, "Difference between Solution 1 and 3 is (%s)" % sol3.symmetric_difference(sol1) - self.assertTrue(sol1.improves(sol2), msg="Solution 1 is better than Solution 2") - self.assertFalse(sol3.improves(sol2), msg="Solution 3 is not better than Solution 2") - self.assertFalse(sol3.improves(sol1), msg="Solution 3 does not improve Solution 1") - self.assertFalse(sol2.improves(sol1), msg="Solution 2 does not improve Solution 1") - self.assertFalse(sol2.improves(sol3), msg="Solution 2 does not improve Solution 3") + assert sol1.improves(sol2), "Solution 1 is better than Solution 2" + assert not sol3.improves(sol2), "Solution 3 is not better than Solution 2" + assert not sol3.improves(sol1), "Solution 3 does not improve Solution 1" + assert not sol2.improves(sol1), "Solution 2 does not improve Solution 1" + assert not sol2.improves(sol3), "Solution 2 does not improve Solution 3" def test_add_greater_solution_with_same_fitness(self): size = 1 pool = BestSolutionArchive() pool.add(SOLUTIONS[0][0], SOLUTIONS[0][1], None, True, size) pool.add(SOLUTIONS[1][0], SOLUTIONS[1][1], None, True, size) - self.assertEqual(pool.length(), 1, msg="Pool must keep one solution (length=%s)" % pool.length()) + assert pool.length() == 1, "Pool must keep one solution (length=%s)" % pool.length() best_solution = set(SOLUTIONS[0][0]) best_fitness = SOLUTIONS[0][1] sol = pool.get(0) - self.assertEqual(sol.candidate, best_solution, msg="Best solution set must be the first") - self.assertEqual(sol.fitness, best_fitness, msg="Best solution fitness must be the first") + assert sol.candidate == best_solution, "Best solution set must be the first" + assert sol.fitness == best_fitness, "Best solution fitness must be the first" def test_add_smaller_solution_with_same_fitness(self): size = 1 pool = BestSolutionArchive() pool.add(SOLUTIONS[1][0], SOLUTIONS[1][1], None, True, size) pool.add(SOLUTIONS[0][0], SOLUTIONS[0][1], None, True, size) - self.assertEqual(pool.length(), 1, msg="Pool must keep one solution (length=%s)" % pool.length()) + assert pool.length() == 1, "Pool must keep one solution (length=%s)" % pool.length() solution = set(SOLUTIONS[0][0]) fitness = SOLUTIONS[0][1] sol = pool.get(0) - self.assertEqual(sol.candidate, solution, msg="Best solution must be the first (%s)" % sol.candidate) - self.assertEqual(sol.fitness, fitness, msg="Best fitness must be the first (%s)" % sol.fitness) + assert sol.candidate == solution, "Best solution must be the first (%s)" % sol.candidate + assert sol.fitness == fitness, "Best fitness must be the first (%s)" % sol.fitness def test_uniqueness_of_solutions(self): size = 2 @@ -238,7 +255,7 @@ def test_uniqueness_of_solutions(self): pool.add(SOLUTIONS[1][0], SOLUTIONS[1][1], None, True, size) pool.add(SOLUTIONS[1][0], SOLUTIONS[1][1], None, True, size) - self.assertEqual(pool.length(), 1, "Added repeated solution") + assert pool.length() == 1, "Added repeated solution" def test_pool_size_limit(self): size = 1 @@ -253,7 +270,7 @@ def test_pool_size_limit(self): pool.add(SOLUTIONS[7][0], SOLUTIONS[7][1], None, True, size) pool.add(SOLUTIONS[8][0], SOLUTIONS[8][1], None, True, size) pool.add(SOLUTIONS[9][0], SOLUTIONS[9][1], None, True, size) - self.assertLessEqual(pool.length(), 1, msg="Pool must keep one solution (length=%s)" % pool.length()) + assert pool.length() <= 1, "Pool must keep one solution (length=%s)" % pool.length() size = 2 pool = BestSolutionArchive() pool.add(SOLUTIONS[0][0], SOLUTIONS[0][1], None, True, size) @@ -266,7 +283,7 @@ def test_pool_size_limit(self): pool.add(SOLUTIONS[7][0], SOLUTIONS[7][1], None, True, size) pool.add(SOLUTIONS[8][0], SOLUTIONS[8][1], None, True, size) pool.add(SOLUTIONS[9][0], SOLUTIONS[9][1], None, True, size) - self.assertLessEqual(pool.length(), 2, msg="Pool must keep one solution (length=%s)" % pool.length()) + assert pool.length() <= 2, "Pool must keep one solution (length=%s)" % pool.length() size = 3 pool = BestSolutionArchive() pool.add(SOLUTIONS[0][0], SOLUTIONS[0][1], None, True, size) @@ -279,7 +296,7 @@ def test_pool_size_limit(self): pool.add(SOLUTIONS[7][0], SOLUTIONS[7][1], None, True, size) pool.add(SOLUTIONS[8][0], SOLUTIONS[8][1], None, True, size) pool.add(SOLUTIONS[9][0], SOLUTIONS[9][1], None, True, size) - self.assertLessEqual(pool.length(), 3, msg="Pool must keep one solution (length=%s)" % pool.length()) + assert pool.length() <= 3, "Pool must keep one solution (length=%s)" % pool.length() size = 4 pool = BestSolutionArchive() pool.add(SOLUTIONS[0][0], SOLUTIONS[0][1], None, True, size) @@ -292,13 +309,12 @@ def test_pool_size_limit(self): pool.add(SOLUTIONS[7][0], SOLUTIONS[7][1], None, True, size) pool.add(SOLUTIONS[8][0], SOLUTIONS[8][1], None, True, size) pool.add(SOLUTIONS[9][0], SOLUTIONS[9][1], None, True, size) - self.assertLessEqual(pool.length(), 4, msg="Pool must keep one solution (length=%s)" % pool.length()) + assert pool.length() <= 4, "Pool must keep one solution (length=%s)" % pool.length() def test_callable_pool(self): pool = BestSolutionArchive() size = 3 - args = {} - args['max_archive_size'] = size + args = {'max_archive_size': 3} population = [Individual(SOLUTIONS[0][0], SOLUTIONS[0][1]), Individual(SOLUTIONS[1][0], SOLUTIONS[1][1]), Individual(SOLUTIONS[2][0], SOLUTIONS[2][1]), @@ -307,14 +323,14 @@ def test_callable_pool(self): Individual(SOLUTIONS[5][0], SOLUTIONS[5][1]), Individual(SOLUTIONS[6][0], SOLUTIONS[6][1])] archive = pool(None, population, [], args) - self.assertEqual(pool.length(), size) + assert pool.length() == size for sol in pool: - self.assertTrue(sol in archive) + assert sol in archive -class TestObjectiveFunctions(TestWithModel.TestWithEColiCore): - class _MockupSolution(): +class TestObjectiveFunctions: + class _MockupSolution: def __init__(self): self._primal = {} @@ -329,24 +345,31 @@ def fluxes(self): return self._primal def _assert_is_pickable(self, of): - self.assertIsInstance(pickle.dumps(of), bytes) + assert isinstance(pickle.dumps(of), bytes) - def test_base_yield_function(self): + def test_base_yield_function(self, model): solution = self._MockupSolution() - solution.set_primal('EX_ac_LPAREN_e_RPAREN_', 2) - solution.set_primal('EX_glc_LPAREN_e_RPAREN_', -10) - - of = YieldFunction(TEST_MODEL.reactions.EX_ac_LPAREN_e_RPAREN_, TEST_MODEL.reactions.EX_glc_LPAREN_e_RPAREN_) + solution.set_primal('EX_ac_lp_e_rp_', 2) + solution.set_primal('EX_glc_lp_e_rp_', -10) + of = YieldFunction(model.reactions.EX_ac_lp_e_rp_, model.reactions.EX_glc_lp_e_rp_) self._assert_is_pickable(of) - self.assertRaises(ValueError, YieldFunction, {}, TEST_MODEL.reactions.EX_glc_LPAREN_e_RPAREN_) - self.assertRaises(ValueError, YieldFunction, None, TEST_MODEL.reactions.EX_glc_LPAREN_e_RPAREN_) - self.assertRaises(ValueError, YieldFunction, [], TEST_MODEL.reactions.EX_glc_LPAREN_e_RPAREN_) - self.assertRaises(ValueError, YieldFunction, 1, TEST_MODEL.reactions.EX_glc_LPAREN_e_RPAREN_) - self.assertRaises(ValueError, YieldFunction, TEST_MODEL.reactions.EX_ac_LPAREN_e_RPAREN_, []) - self.assertRaises(ValueError, YieldFunction, TEST_MODEL.reactions.EX_ac_LPAREN_e_RPAREN_, 1) - self.assertRaises(ValueError, YieldFunction, TEST_MODEL.reactions.EX_ac_LPAREN_e_RPAREN_, {}) - self.assertRaises(ValueError, YieldFunction, TEST_MODEL.reactions.EX_ac_LPAREN_e_RPAREN_, []) + with pytest.raises(ValueError): + YieldFunction({}, model.reactions.EX_glc_lp_e_rp_) + with pytest.raises(ValueError): + YieldFunction(None, model.reactions.EX_glc_lp_e_rp_) + with pytest.raises(ValueError): + YieldFunction([], model.reactions.EX_glc_lp_e_rp_) + with pytest.raises(ValueError): + YieldFunction(1, model.reactions.EX_glc_lp_e_rp_) + with pytest.raises(ValueError): + YieldFunction(model.reactions.EX_ac_lp_e_rp_, []) + with pytest.raises(ValueError): + YieldFunction(model.reactions.EX_ac_lp_e_rp_, 1) + with pytest.raises(ValueError): + YieldFunction(model.reactions.EX_ac_lp_e_rp_, {}) + with pytest.raises(ValueError): + YieldFunction(model.reactions.EX_ac_lp_e_rp_, []) def test_biomass_product_coupled_yield(self): solution = self._MockupSolution() @@ -355,29 +378,29 @@ def test_biomass_product_coupled_yield(self): solution.set_primal('substrate', -10) of = biomass_product_coupled_yield("biomass", "product", "substrate") - self.assertEqual(of.name, "bpcy = (biomass * product) / substrate") + assert of.name == "bpcy = (biomass * product) / substrate" self._assert_is_pickable(of) fitness = of(None, solution, None) - self.assertAlmostEqual((0.6 * 2) / 10, fitness) + assert round(abs((0.6 * 2) / 10 - fitness), 7) == 0 solution.set_primal('substrate', 0) fitness = of(None, solution, None) - self.assertEquals(0, fitness) + assert 0 == fitness solution.set_primal('substrate2', -5) solution.set_primal('substrate', -5) of2 = biomass_product_coupled_yield("biomass", "product", ["substrate", "substrate2"]) - self.assertEqual(of2.name, "bpcy = (biomass * product) / (substrate + substrate2)") + assert of2.name == "bpcy = (biomass * product) / (substrate + substrate2)" self._assert_is_pickable(of2) fitness = of2(None, solution, None) - self.assertAlmostEqual((0.6 * 2) / 10, fitness) + assert round(abs((0.6 * 2) / 10 - fitness), 7) == 0 - def test_biomass_product_coupled_min_yield(self): - biomass = "Biomass_Ecoli_core_N_LPAREN_w_FSLASH_GAM_RPAREN__Nmet2" - product = "EX_ac_LPAREN_e_RPAREN_" - substrate = "EX_glc_LPAREN_e_RPAREN_" + def test_biomass_product_coupled_min_yield(self, model): + biomass = "Biomass_Ecoli_core_N_lp_w_fsh_GAM_rp__Nmet2" + product = "EX_ac_lp_e_rp_" + substrate = "EX_glc_lp_e_rp_" solution = self._MockupSolution() solution.set_primal(biomass, 0.263136) solution.set_primal(product, 16.000731) @@ -385,13 +408,13 @@ def test_biomass_product_coupled_min_yield(self): of = biomass_product_coupled_min_yield(biomass, product, substrate) self._assert_is_pickable(of) - self.assertEqual(of.name, "bpcy = (%s * min(%s)) / %s" % (biomass, product, substrate)) - reactions = [self.model.reactions.get_by_id(r) for r in ['ATPS4r', 'CO2t', 'GLUDy', 'PPS', 'PYK']] + assert of.name == "bpcy = (%s * min(%s)) / %s" % (biomass, product, substrate) + reactions = [model.reactions.get_by_id(r) for r in ['ATPS4r', 'CO2t', 'GLUDy', 'PPS', 'PYK']] with TimeMachine() as tm: for r in reactions: r.knock_out(tm) - fitness = of(self.model, solution, reactions) - self.assertAlmostEqual(0.414851, fitness, places=5) + fitness = of(model, solution, reactions) + assert round(abs(0.414851 - fitness), 5) == 0 def test_product_yield(self): solution = self._MockupSolution() @@ -400,195 +423,200 @@ def test_product_yield(self): solution.set_primal('substrate', -10) of = product_yield("product", "substrate", carbon_yield=False) - self.assertEqual(of.name, "yield = (product / substrate)") + assert of.name == "yield = (product / substrate)" self._assert_is_pickable(of) fitness = of(None, solution, None) - self.assertAlmostEqual(2.0 / 10.0, fitness) + assert round(abs(2.0 / 10.0 - fitness), 7) == 0 solution.set_primal('substrate', 0) fitness = of(None, solution, None) - self.assertEquals(0, fitness) + assert 0 == fitness solution.set_primal('substrate', -5) solution.set_primal('substrate2', -5) of2 = product_yield('product', ['substrate', 'substrate2'], carbon_yield=False) - self.assertEqual(of2.name, "yield = (product / (substrate + substrate2))") + assert of2.name == "yield = (product / (substrate + substrate2))" self._assert_is_pickable(of2) fitness = of2(None, solution, None) - self.assertAlmostEqual(2.0 / 10.0, fitness) + assert round(abs(2.0 / 10.0 - fitness), 7) == 0 def test_number_of_knockouts(self): of_max = number_of_knockouts(sense='max') - self.assertEqual(of_max.name, "max knockouts") + assert of_max.name == "max knockouts" of_min = number_of_knockouts(sense='min') - self.assertEqual(of_min.name, "min knockouts") + assert of_min.name == "min knockouts" f1 = of_max(None, None, ['a', 'b']) f2 = of_max(None, None, ['a', 'b', 'c']) - self.assertGreater(f2, f1) + assert f2 > f1 f1 = of_min(None, None, ['a', 'b']) f2 = of_min(None, None, ['a', 'b', 'c']) - self.assertGreater(f1, f2) + assert f1 > f2 -class TestKnockoutEvaluator(TestWithModel.TestWithEColiCore): - def test_initializer(self): +class TestKnockoutEvaluator: + def test_initializer(self, model): objective1 = biomass_product_coupled_yield( - "Biomass_Ecoli_core_N_LPAREN_w_FSLASH_GAM_RPAREN__Nmet2", - "EX_ac_LPAREN_e_RPAREN_", - "EX_glc_LPAREN_e_RPAREN_") - decoder = ReactionSetDecoder(["PGI", "PDH", "FUM", "FBA", "G6PDH2r", "FRD7", "PGL", "PPC"], self.model) - evaluator = KnockoutEvaluator(self.model, decoder, objective1, fba, {}) - self.assertEquals(evaluator.decoder, decoder) - self.assertEquals(evaluator.objective_function, objective1) - self.assertTrue(hasattr(evaluator, "__call__")) - - objective2 = product_yield("EX_ac_LPAREN_e_RPAREN_", "EX_glc_LPAREN_e_RPAREN_") - evaluator = KnockoutEvaluator(self.model, decoder, MultiObjectiveFunction([objective1, objective2]), fba, {}) - self.assertEquals(evaluator.objective_function.objectives, [objective1, objective2]) - - def test_invalid_initializers(self): + "Biomass_Ecoli_core_N_lp_w_fsh_GAM_rp__Nmet2", + "EX_ac_lp_e_rp_", + "EX_glc_lp_e_rp_") + decoder = ReactionSetDecoder(["PGI", "PDH", "FUM", "FBA", "G6PDH2r", "FRD7", "PGL", "PPC"], model) + evaluator = KnockoutEvaluator(model, decoder, objective1, fba, {}) + assert evaluator.decoder == decoder + assert evaluator.objective_function == objective1 + assert hasattr(evaluator, "__call__") + + objective2 = product_yield("EX_ac_lp_e_rp_", "EX_glc_lp_e_rp_") + evaluator = KnockoutEvaluator(model, decoder, MultiObjectiveFunction([objective1, objective2]), fba, {}) + assert evaluator.objective_function.objectives == [objective1, objective2] + + def test_invalid_initializers(self, model): objective1 = biomass_product_coupled_yield( - "Biomass_Ecoli_core_N_LPAREN_w_FSLASH_GAM_RPAREN__Nmet2", - "EX_ac_LPAREN_e_RPAREN_", - "EX_glc_LPAREN_e_RPAREN_") - decoder = ReactionSetDecoder(["PGI", "PDH", "FUM", "FBA", "G6PDH2r", "FRD7", "PGL", "PPC"], self.model) - self.assertRaises(ValueError, KnockoutEvaluator, self.model, decoder, 1, fba, {}) - self.assertRaises(ValueError, KnockoutEvaluator, self.model, decoder, None, fba, {}) - self.assertRaises(ValueError, KnockoutEvaluator, self.model, decoder, [], fba, {}) - self.assertRaises(ValueError, KnockoutEvaluator, self.model, decoder, [2, 3], fba, {}) - self.assertRaises(ValueError, KnockoutEvaluator, self.model, decoder, [objective1], fba, {}) - self.assertRaises(ValueError, KnockoutEvaluator, self.model, None, [], fba, {}) - self.assertRaises(ValueError, KnockoutEvaluator, self.model, True, [], fba, {}) - - def test_evaluate_single_objective(self): + "Biomass_Ecoli_core_N_lp_w_fsh_GAM_rp__Nmet2", + "EX_ac_lp_e_rp_", + "EX_glc_lp_e_rp_") + decoder = ReactionSetDecoder(["PGI", "PDH", "FUM", "FBA", "G6PDH2r", "FRD7", "PGL", "PPC"], model) + with pytest.raises(ValueError): + KnockoutEvaluator(model, decoder, 1, fba, {}) + with pytest.raises(ValueError): + KnockoutEvaluator(model, decoder, None, fba, {}) + with pytest.raises(ValueError): + KnockoutEvaluator(model, decoder, [], fba, {}) + with pytest.raises(ValueError): + KnockoutEvaluator(model, decoder, [2, 3], fba, {}) + with pytest.raises(ValueError): + KnockoutEvaluator(model, decoder, [objective1], fba, {}) + with pytest.raises(ValueError): + KnockoutEvaluator(model, None, [], fba, {}) + with pytest.raises(ValueError): + KnockoutEvaluator(model, True, [], fba, {}) + + def test_evaluate_single_objective(self, model): representation = ["ATPS4r", "PYK", "GLUDy", "PPS", "CO2t", "PDH", "FUM", "FBA", "G6PDH2r", "FRD7", "PGL", "PPC"] - decoder = ReactionSetDecoder(representation, self.model) + decoder = ReactionSetDecoder(representation, model) objective1 = biomass_product_coupled_yield( - "Biomass_Ecoli_core_N_LPAREN_w_FSLASH_GAM_RPAREN__Nmet2", - "EX_ac_LPAREN_e_RPAREN_", - "EX_glc_LPAREN_e_RPAREN_") - evaluator = KnockoutEvaluator(self.model, decoder, objective1, fba, {}) + "Biomass_Ecoli_core_N_lp_w_fsh_GAM_rp__Nmet2", + "EX_ac_lp_e_rp_", + "EX_glc_lp_e_rp_") + evaluator = KnockoutEvaluator(model, decoder, objective1, fba, {}) fitness = evaluator([[0, 1, 2, 3, 4]])[0] - self.assertAlmostEqual(fitness, 0.41, delta=0.02) + assert abs(fitness - 0.41) < 0.02 - def test_evaluate_multiobjective(self): + def test_evaluate_multiobjective(self, model): representation = ["ATPS4r", "PYK", "GLUDy", "PPS", "CO2t", "PDH", "FUM", "FBA", "G6PDH2r", "FRD7", "PGL", "PPC"] - decoder = ReactionSetDecoder(representation, self.model) + decoder = ReactionSetDecoder(representation, model) objective1 = biomass_product_coupled_yield( - "Biomass_Ecoli_core_N_LPAREN_w_FSLASH_GAM_RPAREN__Nmet2", - "EX_ac_LPAREN_e_RPAREN_", - "EX_glc_LPAREN_e_RPAREN_") - objective2 = product_yield("EX_ac_LPAREN_e_RPAREN_", "EX_glc_LPAREN_e_RPAREN_", carbon_yield=False) + "Biomass_Ecoli_core_N_lp_w_fsh_GAM_rp__Nmet2", + "EX_ac_lp_e_rp_", + "EX_glc_lp_e_rp_") + objective2 = product_yield("EX_ac_lp_e_rp_", "EX_glc_lp_e_rp_", carbon_yield=False) objective = MultiObjectiveFunction([objective1, objective2]) - evaluator = KnockoutEvaluator(self.model, decoder, objective, fba, {}) + evaluator = KnockoutEvaluator(model, decoder, objective, fba, {}) fitness = evaluator([[0, 1, 2, 3, 4]])[0] - self.assertIsInstance(fitness, Pareto) - self.assertAlmostEqual(fitness[0], 0.41, delta=0.02) - self.assertAlmostEqual(fitness[1], 1.57, delta=0.035) + assert isinstance(fitness, Pareto) + assert abs(fitness[0] - 0.41) < 0.02 + assert abs(fitness[1] - 1.57) < 0.035 - def test_evaluate_infeasible_solution(self): + def test_evaluate_infeasible_solution(self, model): representation = ["ENO", "ATPS4r", "PYK", "GLUDy", "PPS", "CO2t", "PDH", "FUM", "FBA", "G6PDH2r", "FRD7", "PGL", "PPC"] - decoder = ReactionSetDecoder(representation, self.model) + decoder = ReactionSetDecoder(representation, model) objective1 = biomass_product_coupled_yield( - "Biomass_Ecoli_core_N_LPAREN_w_FSLASH_GAM_RPAREN__Nmet2", - "EX_ac_LPAREN_e_RPAREN_", - "EX_glc_LPAREN_e_RPAREN_") - evaluator = KnockoutEvaluator(self.model, decoder, objective1, fba, {}) + "Biomass_Ecoli_core_N_lp_w_fsh_GAM_rp__Nmet2", + "EX_ac_lp_e_rp_", + "EX_glc_lp_e_rp_") + evaluator = KnockoutEvaluator(model, decoder, objective1, fba, {}) fitness = evaluator([[0]])[0] - self.assertEquals(fitness, 0) + assert fitness == 0 -class TestWrappedEvaluator(unittest.TestCase): +class TestWrappedEvaluator: def test_initializer(self): def evaluation_function(x): return 1 + evaluator = EvaluatorWrapper(config.default_view, evaluation_function) - self.assertTrue(hasattr(evaluator, '__call__')) - self.assertTrue(hasattr(evaluator, 'view')) - self.assertTrue(hasattr(evaluator, 'evaluator')) - self.assertEquals(evaluator.view, config.default_view) - self.assertEquals(evaluator.evaluator, evaluation_function) + assert hasattr(evaluator, '__call__') + assert hasattr(evaluator, 'view') + assert hasattr(evaluator, 'evaluator') + assert evaluator.view == config.default_view + assert evaluator.evaluator == evaluation_function def test_invalid_initializer(self): - self.assertRaises(ValueError, EvaluatorWrapper, config.default_view, None) - self.assertRaises(ValueError, EvaluatorWrapper, config.default_view, 1) - self.assertRaises(ValueError, EvaluatorWrapper, config.default_view, [1, 2, 3]) - self.assertRaises(ValueError, EvaluatorWrapper, lambda x: 1, config.default_view) - self.assertRaises(ValueError, EvaluatorWrapper, None, lambda x: 1) - self.assertRaises(ValueError, EvaluatorWrapper, 123, lambda x: 1) - - -class TestSwapOptimization(TestWithModel.TestWithEColiCore): - - def test_swap_reaction_identification(self): + with pytest.raises(ValueError): + EvaluatorWrapper(config.default_view, None) + with pytest.raises(ValueError): + EvaluatorWrapper(config.default_view, 1) + with pytest.raises(ValueError): + EvaluatorWrapper(config.default_view, [1, 2, 3]) + with pytest.raises(ValueError): + EvaluatorWrapper(lambda x: 1, config.default_view) + with pytest.raises(ValueError): + EvaluatorWrapper(None, lambda x: 1) + with pytest.raises(ValueError): + EvaluatorWrapper(123, lambda x: 1) + + +class TestSwapOptimization: + def test_swap_reaction_identification(self, model): expected_reactions = ['ACALD', 'AKGDH', 'ALCD2x', 'G6PDH2r', 'GAPD', 'GLUDy', 'GLUSy', 'GND', 'ICDHyr', 'LDH_D', 'MDH', 'ME1', 'ME2', 'NADH16', 'PDH'] - swap_pairs = ([self.model.metabolites.get_by_id(m) for m in NADH_NADPH[0]], - [self.model.metabolites.get_by_id(m) for m in NADH_NADPH[1]]) - - representation = CofactorSwapOptimization.find_swappable_reactions(self.model, swap_pairs) - - self.assertEquals(expected_reactions, representation) - self.assertNotIn('PGI', representation) + swap_pairs = ([model.metabolites.get_by_id(m) for m in NADH_NADPH[0]], + [model.metabolites.get_by_id(m) for m in NADH_NADPH[1]]) - def test_evaluate_swap(self): - self.model.reactions.Biomass_Ecoli_core_N_LPAREN_w_FSLASH_GAM_RPAREN__Nmet2.lower_bound = 0.5 - py = product_yield(self.model.reactions.EX_etoh_LPAREN_e_RPAREN_, self.model.reactions.EX_glc_LPAREN_e_RPAREN_) - cofactors = ((self.model.metabolites.nad_c, self.model.metabolites.nadh_c), - (self.model.metabolites.nadp_c, self.model.metabolites.nadph_c)) - self.model.objective = self.model.reactions.EX_etoh_LPAREN_e_RPAREN_ + representation = CofactorSwapOptimization.find_swappable_reactions(model, swap_pairs) - swap_cofactors(self.model.reactions.ALCD2x, self.model, cofactors, inplace=True) + assert expected_reactions == representation + assert 'PGI' not in representation - reactions = ['GAPD', 'AKGDH', 'PDH', 'GLUDy', 'MDH'] - optimization = CofactorSwapOptimization(model=self.model, objective_function=py, candidate_reactions=reactions) - optimization_result = optimization.run(max_evaluations=10000, max_size=1, pop_size=100, variable_size=False, - mutation_rate=0.5, seed=1485441961) - fitness = optimization_result.data_frame.fitness.max() - print(fitness) - self.assertAlmostEqual(fitness, 0.322085, places=3) + def test_evaluate_swap(self, model): + model.reactions.Biomass_Ecoli_core_N_lp_w_fsh_GAM_rp__Nmet2.lower_bound = 0.5 + py = product_yield(model.reactions.EX_etoh_lp_e_rp_, model.reactions.EX_glc_lp_e_rp_) + cofactors = ((model.metabolites.nad_c, model.metabolites.nadh_c), + (model.metabolites.nadp_c, model.metabolites.nadph_c)) - -class TestDecoders(TestWithModel.TestWithEColiCore): - def test_set_decoder(self): + with TimeMachine() as tm: + model.change_objective(model.reactions.EX_etoh_lp_e_rp_, time_machine=tm) + swap_cofactors(model.reactions.ALCD2x, model, cofactors, inplace=True, time_machine=tm) + reactions = ['GAPD', 'AKGDH', 'PDH', 'GLUDy', 'MDH'] + optimization = CofactorSwapOptimization(model=model, objective_function=py, candidate_reactions=reactions) + optimization_result = optimization.run(max_evaluations=10000, max_size=1, pop_size=100, variable_size=False, + mutation_rate=0.5, seed=1485441961) + fitness = optimization_result.data_frame.fitness.max() + print(fitness) + assert round(abs(fitness - 0.322085), 3) == 0 + + +class TestDecoders: + def test_set_decoder(self, model): representation = [1, 2, 'a', 'b', None, '0'] - decoder = SetDecoder(representation, self.model) - self.assertEqual(decoder([]), []) + decoder = SetDecoder(representation, model) + assert decoder([]) == [] for i in range(len(representation)): - self.assertEqual(decoder([i]), [representation[i]]) + assert decoder([i]) == [representation[i]] - def test_reaction_set_decoder(self): - decoder = ReactionSetDecoder([r.id for r in self.model.reactions], self.model) + def test_reaction_set_decoder(self, model): + decoder = ReactionSetDecoder([r.id for r in model.reactions], model) reactions = decoder([1, 2, 3, 4]) for i in range(1, 5): - self.assertEqual(self.model.reactions[i], reactions[i-1]) + assert model.reactions[i] == reactions[i - 1] - def test_gene_set_decoder(self): - decoder = GeneSetDecoder([g.id for g in self.model.genes], self.model) + def test_gene_set_decoder(self, model): + decoder = GeneSetDecoder([g.id for g in model.genes], model) genes = decoder([1, 2, 3, 4]) for i in range(1, 5): - self.assertEqual(self.model.genes[i], genes[i-1]) - - -class TestGenerators(TestWithModel.TestWithEColiCore): - mockup_evolutionary_algorithm = namedtuple("EA", ["bounder"]) + assert model.genes[i] == genes[i - 1] - def setUp(self): - super(TestGenerators, self).setUp() - self.args = {} - self.args.setdefault('representation', [r.id for r in self.model.reactions]) - self.random = Random() +class TestGenerators: def test_set_generator(self): random = Random(SEED) representation = ["a", "b", "c", "d", "e", "f"] @@ -604,9 +632,9 @@ def test_set_generator(self): candidate = set_generator(random, dict(representation=representation, max_size=max_size, variable_size=variable_size)) - self.assertEqual(candidate, expected[i]) + assert candidate == expected[i] - def test_multiple_chromossome_set_generator(self): + def test_multiple_chromosome_set_generator(self): random = Random(SEED) args = dict(keys=["test_key_1", "test_key_2"], test_key_1_representation=["a1", "a2", "a3", "a4", "a5"], @@ -615,20 +643,21 @@ def test_multiple_chromossome_set_generator(self): test_key_2_max_size=5, variable_size=False) candidate = multiple_chromosome_set_generator(random, args) - self.assertEqual(len(candidate['test_key_1']), 3) - self.assertEqual(len(candidate['test_key_2']), 5) + assert len(candidate['test_key_1']) == 3 + assert len(candidate['test_key_2']) == 5 - def test_fixed_size_set_generator(self): + def test_fixed_size_set_generator(self, generators): + args, random, _ = generators candidates_file = os.path.join(CURRENT_PATH, "data", "fix_size_candidates.pkl") - self.random.seed(SEED) - self.args.setdefault('variable_size', False) + random.seed(SEED) + args.setdefault('variable_size', False) candidates = [] - self.args['max_size'] = 10 + args['max_size'] = 10 for _ in range(1000): - candidate = set_generator(self.random, self.args) - self.assertEqual(len(candidate), 10) + candidate = set_generator(random, args) + assert len(candidate) == 10 candidates.append(candidate) # with open(candidates_file, 'wb') as out_file: @@ -640,269 +669,255 @@ def test_fixed_size_set_generator(self): else: expected_candidates = pickle.load(in_file) - self.assertEqual(candidates, expected_candidates) + assert candidates == expected_candidates - self.args['max_size'] = 20 + args['max_size'] = 20 for _ in range(1000): - candidate = set_generator(self.random, self.args) - self.assertEqual(len(candidate), 20) + candidate = set_generator(random, args) + assert len(candidate) == 20 - def test_variable_size_set_generator(self): + def test_variable_size_set_generator(self, generators): + args, random, _ = generators candidates_file = os.path.join(CURRENT_PATH, "data", "variable_size_candidates.pkl") - self.args.setdefault('variable_size', True) - self.random.seed(SEED) + args.setdefault('variable_size', True) + random.seed(SEED) candidates = [] - self.args['max_size'] = 10 + args['max_size'] = 10 for _ in range(1000): - candidate = set_generator(self.random, self.args) - self.assertLessEqual(len(candidate), 10) + candidate = set_generator(random, args) + assert len(candidate) <= 10 candidates.append(candidate) - # with open(candidates_file, 'wb') as out_file: - # pickle.dump(candidates, out_file, protocol=2) - with open(candidates_file, 'rb') as in_file: if six.PY3: expected_candidates = pickle.load(in_file, encoding="latin1") else: expected_candidates = pickle.load(in_file) - self.assertEqual(candidates, expected_candidates) + assert candidates == expected_candidates - self.args['max_size'] = 20 + args['max_size'] = 20 for _ in range(1000): - candidate = set_generator(self.random, self.args) - self.assertLessEqual(len(candidate), 20) - - def test_fixed_size_linear_set_generator(self): - ec = self.mockup_evolutionary_algorithm(Bounder(-10, 10)) - self.args.setdefault('variable_size', False) - self.args['max_size'] = 10 - self.args['_ec'] = ec + candidate = set_generator(random, args) + assert len(candidate) <= 20 + + def test_fixed_size_linear_set_generator(self, generators): + args, random, mockup_evolutionary_algorithm = generators + ec = mockup_evolutionary_algorithm(Bounder(-10, 10)) + args.setdefault('variable_size', False) + args['max_size'] = 10 + args['_ec'] = ec for _ in range(1000): - candidate = linear_set_generator(self.random, self.args) + candidate = linear_set_generator(random, args) for i, v in six.iteritems(candidate): - self.assertIsInstance(i, (int, numpy.int64, numpy.int32)) - self.assertIsInstance(v, float) + assert isinstance(i, (int, numpy.int64, numpy.int32)) + assert isinstance(v, float) - self.assertLessEqual(len(candidate), 10) + assert len(candidate) <= 10 -class TestHeuristicOptimization(TestWithModel.TestWithEColiCore): - def setUp(self): - super(TestHeuristicOptimization, self).setUp() - self.single_objective_function = product_yield('product', 'substrate') - self.multiobjective_function = MultiObjectiveFunction([ - product_yield('product', 'substrate'), - number_of_knockouts() - ]) - - def test_default_initializer(self): +class TestHeuristicOptimization: + def test_default_initializer(self, model, objectives): + single_objective_function, multi_objective_function = objectives heuristic_optimization = HeuristicOptimization( - model=self.model, - objective_function=self.single_objective_function + model=model, + objective_function=single_objective_function ) - self.assertEqual(heuristic_optimization.model, self.model) - self.assertEqual(heuristic_optimization.objective_function, self.single_objective_function) + assert heuristic_optimization.model == model + assert heuristic_optimization.objective_function == single_objective_function heuristic_optimization = HeuristicOptimization( - model=self.model, - objective_function=self.single_objective_function, + model=model, + objective_function=single_objective_function, ) - self.assertEqual(heuristic_optimization.model, self.model) - self.assertEqual(heuristic_optimization.objective_function, self.single_objective_function) + assert heuristic_optimization.model == model + assert heuristic_optimization.objective_function == single_objective_function - def test_multiobjective_initializer(self): + def test_multi_objective_initializer(self, model, objectives): + single_objective_function, multi_objective_function = objectives heuristic_optimization = HeuristicOptimization( - model=self.model, - objective_function=self.multiobjective_function, + model=model, + objective_function=multi_objective_function, heuristic_method=inspyred.ec.emo.NSGA2 ) - self.assertEqual(heuristic_optimization.model, self.model) - self.assertEqual(len(heuristic_optimization.objective_function), 2) + assert heuristic_optimization.model == model + assert len(heuristic_optimization.objective_function) == 2 heuristic_optimization = HeuristicOptimization( - model=self.model, - objective_function=self.multiobjective_function, + model=model, + objective_function=multi_objective_function, heuristic_method=inspyred.ec.emo.NSGA2, ) - self.assertEqual(heuristic_optimization.model, self.model) - self.assertEqual(len(heuristic_optimization.objective_function), 2) + assert heuristic_optimization.model == model + assert len(heuristic_optimization.objective_function) == 2 - def test_invalid_initializer(self): - self.assertRaises(TypeError, HeuristicOptimization, - model=self.model, - objective_function=self.multiobjective_function, - heuristic_method=inspyred.ec.GA) + def test_invalid_initializer(self, model, objectives): + single_objective_function, multi_objective_function = objectives + with pytest.raises(TypeError): + HeuristicOptimization(model=model, + objective_function=multi_objective_function, + heuristic_method=inspyred.ec.GA) - def test_single_objective_function_with_multiobjective_initializer(self): + def test_single_objective_function_with_multiobjective_initializer(self, model, objectives): + single_objective_function, multi_objective_function = objectives heuristic_optimization = HeuristicOptimization( - model=self.model, - objective_function=self.single_objective_function, + model=model, + objective_function=single_objective_function, heuristic_method=inspyred.ec.emo.NSGA2 ) - self.assertEqual(len(heuristic_optimization.objective_function), 1) + assert len(heuristic_optimization.objective_function) == 1 - def test_change_objective_function(self): + def test_change_objective_function(self, model, objectives): + single_objective_function, multi_objective_function = objectives single_objective_heuristic = HeuristicOptimization( - model=self.model, - objective_function=self.single_objective_function, + model=model, + objective_function=single_objective_function, ) nok = number_of_knockouts() single_objective_heuristic.objective_function = nok - self.assertEqual(nok, single_objective_heuristic.objective_function) - self.assertRaises(TypeError, - single_objective_heuristic.objective_function, - self.multiobjective_function) + assert nok == single_objective_heuristic.objective_function + with pytest.raises(TypeError): + single_objective_heuristic.objective_function(multi_objective_function) - self.assertRaises(TypeError, single_objective_heuristic.objective_function, self.multiobjective_function) + with pytest.raises(TypeError): + single_objective_heuristic.objective_function(multi_objective_function) multiobjective_heuristic = HeuristicOptimization( - model=self.model, - objective_function=self.multiobjective_function, + model=model, + objective_function=multi_objective_function, heuristic_method=inspyred.ec.emo.NSGA2 ) multiobjective_heuristic.objective_function = nok - self.assertEqual(len(multiobjective_heuristic.objective_function), 1) - self.assertEqual(multiobjective_heuristic.objective_function, nok) + assert len(multiobjective_heuristic.objective_function) == 1 + assert multiobjective_heuristic.objective_function == nok - def test_change_heuristic_method(self): + def test_change_heuristic_method(self, model, objectives): + single_objective_function, multi_objective_function = objectives single_objective_heuristic = HeuristicOptimization( - model=self.model, - objective_function=self.single_objective_function, + model=model, + objective_function=single_objective_function, ) single_objective_heuristic.heuristic_method = inspyred.ec.emo.NSGA2 - self.assertEqual(len(single_objective_heuristic.objective_function), 1) + assert len(single_objective_heuristic.objective_function) == 1 multiobjective_heuristic = HeuristicOptimization( - model=self.model, - objective_function=self.multiobjective_function, + model=model, + objective_function=multi_objective_function, heuristic_method=inspyred.ec.emo.NSGA2 ) - self.assertRaises(TypeError, multiobjective_heuristic.heuristic_method, inspyred.ec.GA) - multiobjective_heuristic.objective_function = self.single_objective_function + with pytest.raises(TypeError): + multiobjective_heuristic.heuristic_method(inspyred.ec.GA) + multiobjective_heuristic.objective_function = single_objective_function multiobjective_heuristic.heuristic_method = inspyred.ec.GA def test_set_distance_function(self): s1 = {1, 2, 3} s2 = {1, 2, 3, 4} d = set_distance_function(s1, s2) - self.assertEqual(d, 1) + assert d == 1 s3 = {2, 3, 4} d = set_distance_function(s1, s3) - self.assertEqual(d, 2) + assert d == 2 d = set_distance_function(s3, s2) - self.assertEqual(d, 1) + assert d == 1 -class TestMigrators(unittest.TestCase): - def setUp(self): - self.population = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] - self.random = Random(SEED) - - @unittest.skipIf(RedisQueue is None, 'redis not available') +class TestMigrators: + @pytest.mark.skipif(RedisQueue is None, reason='redis not available') def test_migrator_constructor(self): migrator = MultiprocessingMigrator(max_migrants=1, host=REDIS_HOST) - self.assertIsInstance(migrator.migrants, RedisQueue) - self.assertEqual(migrator.max_migrants, 1) + assert isinstance(migrator.migrants, RedisQueue) + assert migrator.max_migrants == 1 migrator = MultiprocessingMigrator(max_migrants=2, host=REDIS_HOST) - self.assertIsInstance(migrator.migrants, RedisQueue) - self.assertEqual(migrator.max_migrants, 2) + assert isinstance(migrator.migrants, RedisQueue) + assert migrator.max_migrants == 2 migrator = MultiprocessingMigrator(max_migrants=3, host=REDIS_HOST) - self.assertIsInstance(migrator.migrants, RedisQueue) - self.assertEqual(migrator.max_migrants, 3) + assert isinstance(migrator.migrants, RedisQueue) + assert migrator.max_migrants == 3 - @unittest.skipIf(RedisQueue is None, 'redis not available') + @pytest.mark.skipif(RedisQueue is None, reason='redis not available') def test_migrate_individuals_without_evaluation(self): + population = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] + random = Random(SEED) migrator = MultiprocessingMigrator(max_migrants=1, host=REDIS_HOST) - self.assertIsInstance(migrator.migrants, RedisQueue) - self.assertEqual(migrator.max_migrants, 1) + assert isinstance(migrator.migrants, RedisQueue) + assert migrator.max_migrants == 1 - migrator(self.random, self.population, {}) - self.assertEqual(len(migrator.migrants), 1) + migrator(random, population, {}) + assert len(migrator.migrants) == 1 - migrator(self.random, self.population, {}) - self.assertEqual(len(migrator.migrants), 1) + migrator(random, population, {}) + assert len(migrator.migrants) == 1 -class TestOptimizationResult(TestWithModel.TestWithEColiCore): - def setUp(self): - super(TestOptimizationResult, self).setUp() - self.representation = [r.id for r in self.model.reactions] +class TestOptimizationResult: + def test_reaction_result(self, model): + representation = [r.id for r in model.reactions] random = Random(SEED) - args = {"representation": self.representation} + args = {"representation": representation} - self.solutions = BestSolutionArchive() + solutions = BestSolutionArchive() for _ in range(10000): - self.solutions.add(set_generator(random, args), random.random(), None, True, 100) + solutions.add(set_generator(random, args), random.random(), None, True, 100) - self.decoder = ReactionSetDecoder(self.representation, self.model) + decoder = ReactionSetDecoder(representation, model) - def test_reaction_result(self): result = TargetOptimizationResult( - model=self.model, + model=model, heuristic_method=None, simulation_method=fba, simulation_kwargs=None, - solutions=self.solutions, + solutions=solutions, objective_function=None, target_type="reaction", - decoder=self.decoder, + decoder=decoder, seed=SEED, simplify=False) - self.assertEqual(result.target_type, "reaction") + assert result.target_type == "reaction" individuals = [] for row in result: - encoded = set(self.representation.index(v) for v in row[0]) + encoded = set(representation.index(v) for v in row[0]) individual = Individual(encoded, row[1]) - self.assertNotIn(individual, individuals, msg="%s is repeated on result") + assert individual not in individuals, "%s is repeated on result" individuals.append(individual) - self.assertIn(individual, self.solutions.archive) - self.assertEqual(self.solutions.archive.count(individual), 1, msg="%s is unique in archive" % individual) - + assert individual in solutions.archive + assert solutions.archive.count(individual) == 1, "%s is unique in archive" % individual -class TestReactionKnockoutOptimization(TestWithModel.TestWithEColiCore): - def setUp(self): - super(TestReactionKnockoutOptimization, self).setUp() - self.essential_reactions = set([r.id for r in self.model.essential_reactions()]) - def test_initializer(self): +class TestReactionKnockoutOptimization: + def test_initializer(self, model): + essential_reactions = set([r.id for r in model.essential_reactions()]) objective = biomass_product_coupled_yield( - "Biomass_Ecoli_core_N_LPAREN_w_FSLASH_GAM_RPAREN__Nmet2", - "EX_ac_LPAREN_e_RPAREN_", - "EX_glc_LPAREN_e_RPAREN_") - rko = ReactionKnockoutOptimization(model=self.model, + "Biomass_Ecoli_core_N_lp_w_fsh_GAM_rp__Nmet2", "EX_ac_lp_e_rp_", "EX_glc_lp_e_rp_") + rko = ReactionKnockoutOptimization(model=model, simulation_method=fba, objective_function=objective) - self.assertTrue(sorted(self.essential_reactions) == sorted(rko.essential_reactions)) - self.assertEqual(rko._target_type, "reaction") - self.assertTrue(isinstance(rko._decoder, ReactionSetDecoder)) + assert sorted(essential_reactions) == sorted(rko.essential_reactions) + assert rko._target_type == "reaction" + assert isinstance(rko._decoder, ReactionSetDecoder) - # @unittest.skipIf(os.getenv('TRAVIS', False) or 'cplex' not in solvers, 'Missing cplex (or Travis)') - def test_run_single_objective(self): + def test_run_single_objective(self, model): # TODO: make optlang deterministic so this results can be permanently stored. _, result_file = mkstemp('.pkl') objective = biomass_product_coupled_yield( - "Biomass_Ecoli_core_N_LPAREN_w_FSLASH_GAM_RPAREN__Nmet2", - "EX_ac_LPAREN_e_RPAREN_", - "EX_glc_LPAREN_e_RPAREN_") + "Biomass_Ecoli_core_N_lp_w_fsh_GAM_rp__Nmet2", "EX_ac_lp_e_rp_", "EX_glc_lp_e_rp_") - rko = ReactionKnockoutOptimization(model=self.model, + rko = ReactionKnockoutOptimization(model=model, simulation_method=fba, objective_function=objective) @@ -917,21 +932,21 @@ def test_run_single_objective(self): else: expected_results = pickle.load(in_file) - self.assertEqual(results.seed, expected_results.seed) + assert results.seed == expected_results.seed - # @unittest.skipIf(os.getenv('TRAVIS', False) or 'cplex' not in solvers, 'Missing cplex (or Travis)') - def test_run_multiobjective(self): + def test_run_multi_objective(self, model): # TODO: make optlang deterministic so this results can be permanently stored. _, result_file = mkstemp('.pkl') + objective1 = biomass_product_coupled_yield( - "Biomass_Ecoli_core_N_LPAREN_w_FSLASH_GAM_RPAREN__Nmet2", - "EX_ac_LPAREN_e_RPAREN_", - "EX_glc_LPAREN_e_RPAREN_") + "Biomass_Ecoli_core_N_lp_w_fsh_GAM_rp__Nmet2", + "EX_ac_lp_e_rp_", + "EX_glc_lp_e_rp_") objective2 = number_of_knockouts() objective = MultiObjectiveFunction([objective1, objective2]) - rko = ReactionKnockoutOptimization(model=self.model, + rko = ReactionKnockoutOptimization(model=model, simulation_method=fba, objective_function=objective, heuristic_method=inspyred.ec.emo.NSGA2) @@ -947,12 +962,74 @@ def test_run_multiobjective(self): else: expected_results = pickle.load(in_file) - self.assertEqual(results.seed, expected_results.seed) + assert results.seed == expected_results.seed - # assert_frame_equal(results.data_frame, expected_results.data_frame) +class TestGeneKnockoutOptimization: + def test_initializer(self, model): + essential_genes = set([r.id for r in model.essential_genes()]) + objective = biomass_product_coupled_yield( + "Biomass_Ecoli_core_N_lp_w_fsh_GAM_rp__Nmet2", "EX_ac_lp_e_rp_", "EX_glc_lp_e_rp_") + rko = GeneKnockoutOptimization(model=model, + simulation_method=fba, + objective_function=objective) + + assert sorted(essential_genes) == sorted(rko.essential_genes) + assert rko._target_type == "gene" + assert isinstance(rko._decoder, GeneSetDecoder) + + def test_run_single_objective(self, model): + # TODO: make optlang deterministic so this results can be permanently stored. + _, result_file = mkstemp('.pkl') + objective = biomass_product_coupled_yield( + "Biomass_Ecoli_core_N_lp_w_fsh_GAM_rp__Nmet2", "EX_ac_lp_e_rp_", "EX_glc_lp_e_rp_") -class VariatorTestCase(unittest.TestCase): + rko = GeneKnockoutOptimization(model=model, + simulation_method=fba, + objective_function=objective) + + results = rko.run(max_evaluations=3000, pop_size=10, view=SequentialView(), seed=SEED) + + with open(result_file, 'wb') as in_file: + pickle.dump(results, in_file) + + with open(result_file, 'rb') as in_file: + if six.PY3: + expected_results = pickle.load(in_file, encoding="latin1") + else: + expected_results = pickle.load(in_file) + + assert results.seed == expected_results.seed + + def test_run_multi_objective(self, model): + # TODO: make optlang deterministic so this results can be permanently stored. + _, result_file = mkstemp('.pkl') + objective1 = biomass_product_coupled_yield( + "Biomass_Ecoli_core_N_lp_w_fsh_GAM_rp__Nmet2", "EX_ac_lp_e_rp_", "EX_glc_lp_e_rp_") + + objective2 = number_of_knockouts() + objective = MultiObjectiveFunction([objective1, objective2]) + + rko = GeneKnockoutOptimization(model=model, + simulation_method=fba, + objective_function=objective, + heuristic_method=inspyred.ec.emo.NSGA2) + + results = rko.run(max_evaluations=3000, pop_size=10, view=SequentialView(), seed=SEED) + + with open(result_file, 'wb') as in_file: + pickle.dump(results, in_file) + + with open(result_file, 'rb') as in_file: + if six.PY3: + expected_results = pickle.load(in_file, encoding="latin1") + else: + expected_results = pickle.load(in_file) + + assert results.seed == expected_results.seed + + +class TestVariator: def test_set_n_point_crossover(self): mom = OrderedSet([1, 3, 5, 9, 10]) dad = OrderedSet([2, 3, 7, 8]) @@ -964,8 +1041,8 @@ def test_set_n_point_crossover(self): children = set_n_point_crossover(Random(SEED), [mom, dad], args) bro = OrderedSet([1, 3, 5, 8]) sis = OrderedSet([2, 3, 7, 9, 10]) - self.assertEqual(bro, children[0]) - self.assertEqual(sis, children[1]) + assert bro == children[0] + assert sis == children[1] def test_do_not_set_n_point_crossover(self): mom = OrderedSet([1, 3, 5, 9, 10]) @@ -976,8 +1053,8 @@ def test_do_not_set_n_point_crossover(self): "candidate_size": 10 } children = set_n_point_crossover(Random(SEED), [mom, dad], args) - self.assertEqual(mom, children[0]) - self.assertEqual(dad, children[1]) + assert mom == children[0] + assert dad == children[1] def test_set_mutation(self): individual = OrderedSet([1, 3, 5, 9, 10]) @@ -987,9 +1064,9 @@ def test_set_mutation(self): "mutation_rate": 1.0 } new_individuals = set_mutation(Random(SEED), [individual], args) - self.assertEqual(len(new_individuals[0]), len(individual)) - self.assertNotEqual(new_individuals[0], individual) - self.assertEqual(new_individuals[0], [0, 2, 4, 6, 7]) + assert len(new_individuals[0]) == len(individual) + assert new_individuals[0] != individual + assert new_individuals[0] == [0, 2, 4, 6, 7] def test_do_not_set_mutation(self): individual = OrderedSet([1, 3, 5, 9, 10]) @@ -999,8 +1076,8 @@ def test_do_not_set_mutation(self): "mutation_rate": 0.0 } new_individuals = set_mutation(Random(SEED), [individual], args) - self.assertEqual(len(new_individuals[0]), len(individual)) - self.assertEqual(new_individuals[0], individual) + assert len(new_individuals[0]) == len(individual) + assert new_individuals[0] == individual def test_set_indel(self): individual = [1, 3, 5, 9, 10] @@ -1010,8 +1087,8 @@ def test_set_indel(self): "indel_rate": 1.0 } new_individuals = set_indel(Random(SEED), [individual], args) - self.assertNotEqual(len(new_individuals[0]), len(individual)) - self.assertEqual(new_individuals[0], [1, 3, 5, 6, 9, 10]) + assert len(new_individuals[0]) != len(individual) + assert new_individuals[0] == [1, 3, 5, 6, 9, 10] def test_do_not_set_indel(self): individual = [1, 3, 5, 9, 10] @@ -1021,8 +1098,8 @@ def test_do_not_set_indel(self): "indel_rate": 0.0 } new_individuals = set_indel(Random(SEED), [individual], args) - self.assertEqual(len(new_individuals[0]), len(individual)) - self.assertEqual(new_individuals[0], individual) + assert len(new_individuals[0]) == len(individual) + assert new_individuals[0] == individual args = { "representation": representation, @@ -1030,8 +1107,8 @@ def test_do_not_set_indel(self): "variable_size": False } new_individuals = set_indel(Random(SEED), [individual], args) - self.assertEqual(len(new_individuals[0]), len(individual)) - self.assertEqual(new_individuals[0], individual) + assert len(new_individuals[0]) == len(individual) + assert new_individuals[0] == individual def test_do_set_n_point_crossover(self): representation = ["A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N"] @@ -1042,8 +1119,8 @@ def test_do_set_n_point_crossover(self): children = _do_set_n_point_crossover(int_representation, mom, dad, points, Random(), len(mom)) bro = OrderedSet([0, 1, 8, 9, 10, 11]) sis = OrderedSet([0, 2, 4, 10, 11, 12]) - self.assertEqual(children[0], bro) - self.assertEqual(children[1], sis) + assert children[0] == bro + assert children[1] == sis def test_multiple_chromosome_set_mutation(self): genome = MultipleChromosomeGenome(["A", "B"]) @@ -1058,8 +1135,8 @@ def test_multiple_chromosome_set_mutation(self): } new_individuals = multiple_chromosome_set_mutation(Random(SEED), [genome], args) - self.assertEqual(new_individuals[0]["A"], OrderedSet([0, 6, 7, 8])) - self.assertEqual(new_individuals[0]["B"], OrderedSet([0, 6, 8, 9])) + assert new_individuals[0]["A"] == OrderedSet([0, 6, 7, 8]) + assert new_individuals[0]["B"] == OrderedSet([0, 6, 8, 9]) def test_multiple_chromosome_set_indel(self): genome = MultipleChromosomeGenome(["A", "B"]) @@ -1075,46 +1152,45 @@ def test_multiple_chromosome_set_indel(self): random = Random(SEED) new_individuals = multiple_chromosome_set_indel(random, [genome for _ in range(5)], args) - self.assertEqual(new_individuals[0]["A"], OrderedSet([1, 2, 3, 4, 7])) - self.assertEqual(new_individuals[0]["B"], OrderedSet([1, 5, 10])) - self.assertEqual(new_individuals[1]["A"], OrderedSet([2, 3, 4])) - self.assertEqual(new_individuals[1]["B"], OrderedSet([1, 5, 7, 8, 10])) - self.assertEqual(new_individuals[2]["A"], OrderedSet([1, 2, 3, 4, 6])) - self.assertEqual(new_individuals[2]["B"], OrderedSet([1, 5, 7])) - self.assertEqual(new_individuals[3]["A"], OrderedSet([1, 2, 3, 4, 8])) - self.assertEqual(new_individuals[3]["B"], OrderedSet([0, 1, 5, 7, 10])) - self.assertEqual(new_individuals[4]["A"], OrderedSet([1, 2, 3, 4, 7])) - self.assertEqual(new_individuals[4]["B"], OrderedSet([1, 5, 7, 8, 10])) - - -class GenomesTestCase(unittest.TestCase): + assert new_individuals[0]["A"] == OrderedSet([1, 2, 3, 4, 7]) + assert new_individuals[0]["B"] == OrderedSet([1, 5, 10]) + assert new_individuals[1]["A"] == OrderedSet([2, 3, 4]) + assert new_individuals[1]["B"] == OrderedSet([1, 5, 7, 8, 10]) + assert new_individuals[2]["A"] == OrderedSet([1, 2, 3, 4, 6]) + assert new_individuals[2]["B"] == OrderedSet([1, 5, 7]) + assert new_individuals[3]["A"] == OrderedSet([1, 2, 3, 4, 8]) + assert new_individuals[3]["B"] == OrderedSet([0, 1, 5, 7, 10]) + assert new_individuals[4]["A"] == OrderedSet([1, 2, 3, 4, 7]) + assert new_individuals[4]["B"] == OrderedSet([1, 5, 7, 8, 10]) + + +class TestGenomes: def test_two_chromosomes(self): genome = MultipleChromosomeGenome(["A", "B"]) - self.assertIsInstance(genome["A"], list) - self.assertIsInstance(genome["B"], list) + assert isinstance(genome["A"], list) + assert isinstance(genome["B"], list) genome["A"] = [1, 2, 3, 4] genome["B"] = ["A", "B", "C"] - self.assertEqual(genome["A"], OrderedSet([1, 2, 3, 4])) - self.assertEqual(genome["B"], OrderedSet(["A", "B", "C"])) + assert genome["A"] == OrderedSet([1, 2, 3, 4]) + assert genome["B"] == OrderedSet(["A", "B", "C"]) del genome["A"] - self.assertRaises(KeyError, genome.__getitem__, "A") - + with pytest.raises(KeyError): + genome.__getitem__("A") -class SimplificationTestCase(TestWithModel.TestWithiAF1260Model): - def simplify_knockout_solutions_for_succ(self): - representation = ["FUM", "SFGTHi", "DHACOAH", "ASPTRS"] - solution = [0, 1, 2, 3] +def simplify_knockout_solutions_for_succ(iaf1260): + representation = ["FUM", "SFGTHi", "DHACOAH", "ASPTRS"] + solution = [0, 1, 2, 3] - bpcy = biomass_product_coupled_min_yield("Ec_biomass_iAF1260_core_59p81M", - "EX_succ_lp_e_rp_", - "EX_glc_lp_e_rp_") + bpcy = biomass_product_coupled_min_yield("Ec_biomass_iAF1260_core_59p81M", + "EX_succ_lp_e_rp_", + "EX_glc_lp_e_rp_") - decoder = ReactionSetDecoder(representation, self.model) - evaluator = KnockoutEvaluator(self.model, decoder, bpcy, fba, {}) - simplification = SolutionSimplification(evaluator) + decoder = ReactionSetDecoder(representation, iaf1260) + evaluator = KnockoutEvaluator(iaf1260, decoder, bpcy, fba, {}) + simplification = SolutionSimplification(evaluator) - new_solution = simplification(solution) - self.assertEqual([0], new_solution) + new_solution = simplification(solution) + assert [0] == new_solution diff --git a/tests/test_targets.py b/tests/test_targets.py index bd63fd5ed..bf0b79d7f 100644 --- a/tests/test_targets.py +++ b/tests/test_targets.py @@ -7,42 +7,45 @@ # http://www.apache.org/licenses/LICENSE-2.0 # Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + + # See the License for the specific language governing permissions and # limitations under the License. -import os -import unittest - +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +import pytest import six -from cameo import load_model, Reaction, Metabolite -from cameo.core.target import * -from cameo.core.target import Target, FluxModulationTarget, EnsembleTarget, ReactionInversionTarget +from cameo import Metabolite, Reaction +from cameo.core.target import (ReactionKnockoutTarget, ReactionModulationTarget, GeneKnockoutTarget, + ReactionCofactorSwapTarget, ReactionKnockinTarget) +from cameo.core.target import (EnsembleTarget, FluxModulationTarget, + ReactionInversionTarget, Target) from cameo.exceptions import IncompatibleTargets from cameo.util import TimeMachine -TESTDIR = os.path.dirname(__file__) +class TestTargets: + def test_hashable(self): + knockout_target1 = ReactionKnockoutTarget("ACALD") + knockout_target2 = ReactionKnockoutTarget("ACALD") -class TargetsTestCase(unittest.TestCase): - model = None + assert knockout_target1 == knockout_target2 + assert hash(knockout_target1) == hash(knockout_target2) - @classmethod - def setUpClass(cls): - cls.model = load_model(os.path.join(TESTDIR, 'data', 'EcoliCore.xml')) + assert len({knockout_target1, knockout_target2}) == 1 - def test_reaction_knockout_target(self): + def test_reaction_knockout_target(self, model): knockout_target = ReactionKnockoutTarget("ACALD") with TimeMachine() as tm: - knockout_target.apply(self.model, time_machine=tm) - self.assertEqual(self.model.reactions.ACALD.lower_bound, 0) - self.assertEqual(self.model.reactions.ACALD.upper_bound, 0) + knockout_target.apply(model, time_machine=tm) + assert model.reactions.ACALD.lower_bound == 0 + assert model.reactions.ACALD.upper_bound == 0 - self.assertEqual(self.model.reactions.ACALD.lower_bound, -1000) - self.assertEqual(self.model.reactions.ACALD.upper_bound, 1000) + assert model.reactions.ACALD.lower_bound == -1000 + assert model.reactions.ACALD.upper_bound == 1000 - def test_reaction_down_regulation_target(self): + def test_reaction_down_regulation_target(self, model): reaction_id = "PGI" ref_val = 4.86 value = 3.4 @@ -51,15 +54,15 @@ def test_reaction_down_regulation_target(self): fold_change = -0.30041 down_reg_target = ReactionModulationTarget(reaction_id, value, ref_val) - self.assertAlmostEqual(down_reg_target.fold_change, fold_change, places=5) + assert round(abs(down_reg_target.fold_change - fold_change), 5) == 0 with TimeMachine() as tm: - down_reg_target.apply(self.model, time_machine=tm) - self.assertEqual(self.model.reactions.PGI.upper_bound, 3.4) - self.assertEqual(self.model.reactions.PGI.lower_bound, -1000) - self.assertAlmostEqual(self.model.solve().f, 0.8706, delta=0.0001) + down_reg_target.apply(model, time_machine=tm) + assert model.reactions.PGI.upper_bound == 3.4 + assert model.reactions.PGI.lower_bound == -1000 + assert abs(model.solve().f - 0.8706) < 0.0001 - self.assertEqual(self.model.reactions.PGI.upper_bound, 1000) - self.assertEqual(self.model.reactions.PGI.lower_bound, -1000) + assert model.reactions.PGI.upper_bound == 1000 + assert model.reactions.PGI.lower_bound == -1000 reaction_id = "RPI" ref_val = -2.28150 @@ -68,85 +71,86 @@ def test_reaction_down_regulation_target(self): fold_change = -0.342537 down_reg_target = ReactionModulationTarget(reaction_id, value, ref_val) - self.assertAlmostEqual(down_reg_target.fold_change, fold_change, places=5) + assert round(abs(down_reg_target.fold_change - fold_change), 5) == 0 with TimeMachine() as tm: - down_reg_target.apply(self.model, time_machine=tm) - self.assertEqual(self.model.reactions.RPI.lower_bound, -1.5) - self.assertEqual(self.model.reactions.RPI.upper_bound, 1000) - self.assertAlmostEqual(self.model.solve().f, 0.8691, delta=0.0001) + down_reg_target.apply(model, time_machine=tm) + assert model.reactions.RPI.lower_bound == -1.5 + assert model.reactions.RPI.upper_bound == 1000 + assert abs(model.solve().f - 0.8691) < 0.0001 - self.assertEqual(self.model.reactions.RPI.lower_bound, -1000) - self.assertEqual(self.model.reactions.RPI.upper_bound, 1000) + assert model.reactions.RPI.lower_bound == -1000 + assert model.reactions.RPI.upper_bound == 1000 - def test_reaction_knock_in_target(self): + def test_reaction_knock_in_target(self, model): reaction = Reaction(id="atpzase", name="Cosmic ATP generator") atp_z = Metabolite(id="atp_z", name="Cosmic ATP", compartment="c") - reaction.add_metabolites({self.model.metabolites.atp_c: 1, atp_z: -1}) + reaction.add_metabolites({model.metabolites.atp_c: 1, atp_z: -1}) knockin_target = ReactionKnockinTarget("atpzase", reaction) with TimeMachine() as tm: - knockin_target.apply(self.model, time_machine=tm) - self.assertIn(atp_z, self.model.metabolites) - self.assertIn(reaction, self.model.reactions) + knockin_target.apply(model, time_machine=tm) + assert atp_z in model.metabolites + assert reaction in model.reactions - self.assertNotIn(atp_z, self.model.metabolites) - self.assertNotIn(reaction, self.model.reactions) + assert atp_z not in model.metabolites + assert reaction not in model.reactions - def test_reaction_cofactor_swap_target(self): + def test_reaction_cofactor_swap_target(self, model): cofactor_id_swaps = [("nad_c", "nadh_c"), ("nadp_c", "nadph_c")] - swap_pairs = ([self.model.metabolites.get_by_id(m) for m in cofactor_id_swaps[0]], - [self.model.metabolites.get_by_id(m) for m in cofactor_id_swaps[1]]) + swap_pairs = ([model.metabolites.get_by_id(m) for m in cofactor_id_swaps[0]], + [model.metabolites.get_by_id(m) for m in cofactor_id_swaps[1]]) swap_target = ReactionCofactorSwapTarget("GAPD", swap_pairs) with TimeMachine() as tm: - swap_target.apply(self.model, time_machine=tm) - self.assertNotIn(self.model.metabolites.nad_c, self.model.reactions.GAPD.metabolites) - self.assertNotIn(self.model.metabolites.nadh_c, self.model.reactions.GAPD.metabolites) - self.assertIn(self.model.metabolites.nadp_c, self.model.reactions.GAPD.metabolites) - self.assertIn(self.model.metabolites.nadph_c, self.model.reactions.GAPD.metabolites) + swap_target.apply(model, time_machine=tm) + assert model.metabolites.nad_c not in model.reactions.GAPD.metabolites + assert model.metabolites.nadh_c not in model.reactions.GAPD.metabolites + assert model.metabolites.nadp_c in model.reactions.GAPD.metabolites + assert model.metabolites.nadph_c in model.reactions.GAPD.metabolites - self.assertNotIn(self.model.metabolites.nadp_c, self.model.reactions.GAPD.metabolites) - self.assertNotIn(self.model.metabolites.nadph_c, self.model.reactions.GAPD.metabolites) - self.assertIn(self.model.metabolites.nad_c, self.model.reactions.GAPD.metabolites) - self.assertIn(self.model.metabolites.nadh_c, self.model.reactions.GAPD.metabolites) + assert model.metabolites.nadp_c not in model.reactions.GAPD.metabolites + assert model.metabolites.nadph_c not in model.reactions.GAPD.metabolites + assert model.metabolites.nad_c in model.reactions.GAPD.metabolites + assert model.metabolites.nadh_c in model.reactions.GAPD.metabolites swap_target = ReactionCofactorSwapTarget("GND", swap_pairs) with TimeMachine() as tm: - swap_target.apply(self.model, time_machine=tm) - self.assertIn(self.model.metabolites.nad_c, self.model.reactions.GND.metabolites) - self.assertIn(self.model.metabolites.nadh_c, self.model.reactions.GND.metabolites) - self.assertNotIn(self.model.metabolites.nadp_c, self.model.reactions.GND.metabolites) - self.assertNotIn(self.model.metabolites.nadph_c, self.model.reactions.GND.metabolites) - - self.assertIn(self.model.metabolites.nadp_c, self.model.reactions.GND.metabolites) - self.assertIn(self.model.metabolites.nadph_c, self.model.reactions.GND.metabolites) - self.assertNotIn(self.model.metabolites.nad_c, self.model.reactions.GND.metabolites) - self.assertNotIn(self.model.metabolites.nadh_c, self.model.reactions.GND.metabolites) - - def test_reaction_inversion_target(self): + swap_target.apply(model, time_machine=tm) + assert model.metabolites.nad_c in model.reactions.GND.metabolites + assert model.metabolites.nadh_c in model.reactions.GND.metabolites + assert model.metabolites.nadp_c not in model.reactions.GND.metabolites + assert model.metabolites.nadph_c not in model.reactions.GND.metabolites + + assert model.metabolites.nadp_c in model.reactions.GND.metabolites + assert model.metabolites.nadph_c in model.reactions.GND.metabolites + assert model.metabolites.nad_c not in model.reactions.GND.metabolites + assert model.metabolites.nadh_c not in model.reactions.GND.metabolites + + def test_reaction_inversion_target(self, model): inversion_target = ReactionInversionTarget("GND", value=-10, reference_value=10) - self.assertEqual(inversion_target.fold_change, 0) - lower_bound = self.model.reactions.GND.lower_bound + assert inversion_target.fold_change == 0 + lower_bound = model.reactions.GND.lower_bound with TimeMachine() as tm: - inversion_target.apply(self.model, time_machine=tm) - self.assertEqual(self.model.reactions.GND.lower_bound, -10) - self.assertEqual(self.model.reactions.GND.lower_bound, lower_bound) + inversion_target.apply(model, time_machine=tm) + assert model.reactions.GND.lower_bound == -10 + assert model.reactions.GND.lower_bound == lower_bound - def test_invalid_reaction_knockout_target(self): + def test_invalid_reaction_knockout_target(self, model): knockout_target = ReactionKnockoutTarget("ACALDXYZ") - self.assertRaises(KeyError, knockout_target.apply, self.model) - - def test_invalid_reaction_modulation_target(self): + with pytest.raises(KeyError): + knockout_target.apply(model) + def test_invalid_reaction_modulation_target(self, model): reaction_id = "PGI_XY" ref_val = 4.86 value = 4 down_reg_target = ReactionModulationTarget(reaction_id, value, ref_val) - self.assertRaises(KeyError, down_reg_target.apply, self.model) + with pytest.raises(KeyError): + down_reg_target.apply(model) reaction_id = "RPI_Z" ref_val = -2.28150 @@ -154,125 +158,132 @@ def test_invalid_reaction_modulation_target(self): down_reg_target = ReactionModulationTarget(reaction_id, value, ref_val) - self.assertRaises(KeyError, down_reg_target.apply, self.model) + with pytest.raises(KeyError): + down_reg_target.apply(model) - def test_invalid_reaction_cofactor_swap_target(self): + def test_invalid_reaction_cofactor_swap_target(self, model): cofactor_id_swaps = [("nad_c", "nadh_c"), ("nadp_c", "nadph_c")] - swap_pairs = ([self.model.metabolites.get_by_id(m) for m in cofactor_id_swaps[0]], - [self.model.metabolites.get_by_id(m) for m in cofactor_id_swaps[1]]) + swap_pairs = ([model.metabolites.get_by_id(m) for m in cofactor_id_swaps[0]], + [model.metabolites.get_by_id(m) for m in cofactor_id_swaps[1]]) swap_target = ReactionCofactorSwapTarget("GAPD_124", swap_pairs) - self.assertRaises(KeyError, swap_target.apply, self.model) + with pytest.raises(KeyError): + swap_target.apply(model) swap_target = ReactionCofactorSwapTarget("ACKr", swap_pairs) - self.assertRaises(ValueError, swap_target.apply, self.model) + with pytest.raises(ValueError): + swap_target.apply(model) - def test_gene_knockout_target(self): + def test_gene_knockout_target(self, model): gene = "b4025" knockout_target = GeneKnockoutTarget(gene) with TimeMachine() as tm: - knockout_target.apply(self.model, time_machine=tm) - self.assertEqual(self.model.reactions.PGI.lower_bound, 0) - self.assertEqual(self.model.reactions.PGI.upper_bound, 0) - self.assertAlmostEqual(self.model.solve().f, 0.8631, delta=0.0001) + knockout_target.apply(model, time_machine=tm) + assert model.reactions.PGI.lower_bound == 0 + assert model.reactions.PGI.upper_bound == 0 + assert abs(model.solve().f - 0.8631) < 0.0001 - self.assertEqual(self.model.reactions.PGI.lower_bound, -1000) - self.assertEqual(self.model.reactions.PGI.upper_bound, 1000) + assert model.reactions.PGI.lower_bound == -1000 + assert model.reactions.PGI.upper_bound == 1000 - @unittest.skip("Gene Overexpression not implemented yet") + @pytest.mark.skip("Gene Overexpression not implemented yet") def test_gene_over_express_target(self): raise NotImplementedError - @unittest.skip("Gene Downregulation not implemented yet") + @pytest.mark.skip("Gene Downregulation not implemented yet") def test_gene_down_regulation_target(self): raise NotImplementedError - @unittest.skipIf(six.PY2, 'gnomic is not compatible with python 2') - def test_gnomic_integration(self): + @pytest.mark.skikpif(six.PY2, reason='gnomic is not compatible with python 2') + def test_gnomic_integration(self, model): from gnomic.models import Accession, Feature, Mutation, FeatureTree abstract_target = Target("test") abstract_target_gnomic = abstract_target.to_gnomic() - self.assertIsInstance(abstract_target_gnomic, Accession) - self.assertEqual(abstract_target_gnomic.identifier, abstract_target.id) + assert isinstance(abstract_target_gnomic, Accession) + assert abstract_target_gnomic.identifier == abstract_target.id flux_modulation_target = FluxModulationTarget("test", 1, 0) flux_modulation_target_gnomic = flux_modulation_target.to_gnomic() - self.assertIsInstance(flux_modulation_target_gnomic, Mutation) - self.assertIsInstance(flux_modulation_target_gnomic.old, FeatureTree) - self.assertIsInstance(flux_modulation_target_gnomic.old[0], Feature) - self.assertEqual(flux_modulation_target_gnomic.old[0].accession.identifier, flux_modulation_target.id) - self.assertEqual(flux_modulation_target_gnomic.old[0].variant, None) - self.assertEqual(flux_modulation_target_gnomic.old[0].type, 'flux') - self.assertIsInstance(flux_modulation_target_gnomic.new, FeatureTree) - self.assertIsInstance(flux_modulation_target_gnomic.new[0], Feature) - self.assertEqual(flux_modulation_target_gnomic.new[0].accession.identifier, flux_modulation_target.id) - self.assertEqual(flux_modulation_target_gnomic.new[0].type, 'flux') - self.assertEqual(flux_modulation_target_gnomic.new[0].variant, "over-expression(%.3f)" % flux_modulation_target.fold_change) + flux_mod_new = flux_modulation_target_gnomic.new + flux_mod_old = flux_modulation_target_gnomic.old + assert isinstance(flux_modulation_target_gnomic, Mutation) + assert isinstance(flux_mod_old, FeatureTree) + assert isinstance(flux_mod_old[0], Feature) + assert flux_mod_old[0].accession.identifier == flux_modulation_target.id + assert flux_mod_old[0].variant is None + assert flux_mod_old[0].type == 'flux' + assert isinstance(flux_mod_new, FeatureTree) + assert isinstance(flux_mod_new[0], Feature) + assert flux_mod_new[0].accession.identifier == flux_modulation_target.id + assert flux_mod_new[0].type == 'flux' + assert flux_mod_new[0].variant == "over-expression(%.3f)" % flux_modulation_target.fold_change flux_modulation_target = FluxModulationTarget("test", 0.5, 1) flux_modulation_target_gnomic = flux_modulation_target.to_gnomic() - self.assertIsInstance(flux_modulation_target_gnomic, Mutation) - self.assertIsInstance(flux_modulation_target_gnomic.old, FeatureTree) - self.assertIsInstance(flux_modulation_target_gnomic.old[0], Feature) - self.assertEqual(flux_modulation_target_gnomic.old[0].accession.identifier, flux_modulation_target.id) - self.assertEqual(flux_modulation_target_gnomic.old[0].variant, None) - self.assertEqual(flux_modulation_target_gnomic.old[0].type, 'flux') - self.assertIsInstance(flux_modulation_target_gnomic.new, FeatureTree) - self.assertIsInstance(flux_modulation_target_gnomic.new[0], Feature) - self.assertEqual(flux_modulation_target_gnomic.new[0].accession.identifier, flux_modulation_target.id) - self.assertEqual(flux_modulation_target_gnomic.new[0].type, 'flux') - self.assertEqual(flux_modulation_target_gnomic.new[0].variant, "down-regulation(%.3f)" % flux_modulation_target.fold_change) + flux_mod_new = flux_modulation_target_gnomic.new + flux_mod_old = flux_modulation_target_gnomic.old + assert isinstance(flux_modulation_target_gnomic, Mutation) + assert isinstance(flux_mod_old, FeatureTree) + assert isinstance(flux_mod_old[0], Feature) + assert flux_mod_old[0].accession.identifier == flux_modulation_target.id + assert flux_mod_old[0].variant is None + assert flux_mod_old[0].type == 'flux' + assert isinstance(flux_mod_new, FeatureTree) + assert isinstance(flux_mod_new[0], Feature) + assert flux_mod_new[0].accession.identifier == flux_modulation_target.id + assert flux_mod_new[0].type == 'flux' + assert flux_mod_new[0].variant == "down-regulation(%.3f)" % flux_modulation_target.fold_change flux_modulation_target = FluxModulationTarget("test", 0, 1) flux_modulation_target_gnomic = flux_modulation_target.to_gnomic() - self.assertIsInstance(flux_modulation_target_gnomic, Mutation) - self.assertIsInstance(flux_modulation_target_gnomic.old, FeatureTree) - self.assertIsInstance(flux_modulation_target_gnomic.old[0], Feature) - self.assertEqual(flux_modulation_target_gnomic.old[0].accession.identifier, flux_modulation_target.id) - self.assertEqual(flux_modulation_target_gnomic.old[0].variant, None) - self.assertEqual(flux_modulation_target_gnomic.old[0].type, 'flux') - self.assertIs(flux_modulation_target_gnomic.new, None) + assert isinstance(flux_modulation_target_gnomic, Mutation) + assert isinstance(flux_modulation_target_gnomic.old, FeatureTree) + assert isinstance(flux_modulation_target_gnomic.old[0], Feature) + assert flux_modulation_target_gnomic.old[0].accession.identifier == flux_modulation_target.id + assert flux_modulation_target_gnomic.old[0].variant is None + assert flux_modulation_target_gnomic.old[0].type == 'flux' + assert flux_modulation_target_gnomic.new is None reaction = Reaction(id="atpzase", name="Cosmic ATP generator") atp_z = Metabolite(id="atp_z", name="Cosmic ATP", compartment="c") - reaction.add_metabolites({self.model.metabolites.atp_c: 1, atp_z: -1}) + reaction.add_metabolites({model.metabolites.atp_c: 1, atp_z: -1}) knockin_target = ReactionKnockinTarget("atpzase", reaction) knockin_target_gnomic = knockin_target.to_gnomic() - self.assertIsInstance(knockin_target_gnomic, Mutation) - self.assertIsInstance(knockin_target_gnomic.new, FeatureTree) - self.assertIsInstance(knockin_target_gnomic.new[0], Feature) - self.assertEqual(knockin_target_gnomic.new[0].accession.identifier, knockin_target.id) - self.assertEqual(knockin_target_gnomic.new[0].variant, None) - self.assertEqual(knockin_target_gnomic.new[0].type, 'reaction') - self.assertIs(knockin_target_gnomic.old, None) + assert isinstance(knockin_target_gnomic, Mutation) + assert isinstance(knockin_target_gnomic.new, FeatureTree) + assert isinstance(knockin_target_gnomic.new[0], Feature) + assert knockin_target_gnomic.new[0].accession.identifier == knockin_target.id + assert knockin_target_gnomic.new[0].variant is None + assert knockin_target_gnomic.new[0].type == 'reaction' + assert knockin_target_gnomic.old is None cofactor_id_swaps = [("nad_c", "nadh_c"), ("nadp_c", "nadph_c")] - swap_pairs = ([self.model.metabolites.get_by_id(m) for m in cofactor_id_swaps[0]], - [self.model.metabolites.get_by_id(m) for m in cofactor_id_swaps[1]]) + swap_pairs = ([model.metabolites.get_by_id(m) for m in cofactor_id_swaps[0]], + [model.metabolites.get_by_id(m) for m in cofactor_id_swaps[1]]) swap_target = ReactionCofactorSwapTarget("GAPD", swap_pairs) swap_target_gnomic = swap_target.to_gnomic() - self.assertIsInstance(swap_target_gnomic, Mutation) - self.assertIsInstance(swap_target_gnomic.old, FeatureTree) - self.assertIsInstance(swap_target_gnomic.old[0], Feature) - self.assertEqual(swap_target_gnomic.old[0].accession.identifier, swap_target.id) - self.assertEqual(swap_target_gnomic.old[0].variant, None) - self.assertEqual(swap_target_gnomic.old[0].type, 'reaction') - self.assertIsInstance(swap_target_gnomic.new, FeatureTree) - self.assertIsInstance(swap_target_gnomic.new[0], Feature) - self.assertEqual(swap_target_gnomic.new[0].accession.identifier, swap_target.id + swap_target.swap_str) - self.assertEqual(swap_target_gnomic.new[0].variant, None) - self.assertEqual(swap_target_gnomic.new[0].type, 'reaction') + assert isinstance(swap_target_gnomic, Mutation) + assert isinstance(swap_target_gnomic.old, FeatureTree) + assert isinstance(swap_target_gnomic.old[0], Feature) + assert swap_target_gnomic.old[0].accession.identifier == swap_target.id + assert swap_target_gnomic.old[0].variant is None + assert swap_target_gnomic.old[0].type == 'reaction' + assert isinstance(swap_target_gnomic.new, FeatureTree) + assert isinstance(swap_target_gnomic.new[0], Feature) + assert swap_target_gnomic.new[0].accession.identifier == swap_target.id + swap_target.swap_str + assert swap_target_gnomic.new[0].variant is None + assert swap_target_gnomic.new[0].type == 'reaction' -class EnsembleTargetsTestCase(unittest.TestCase): +class TestEnsembleTargets: def test_compatible_targets(self): modulation_target = ReactionModulationTarget("a", 1, 0) ki_target = ReactionKnockinTarget("a", None) @@ -280,7 +291,7 @@ def test_compatible_targets(self): ensemble_1 = EnsembleTarget("a", [modulation_target, ki_target]) ensemble_2 = EnsembleTarget("a", [ki_target, modulation_target]) - self.assertEqual(ensemble_1.targets, ensemble_2.targets) + assert ensemble_1.targets == ensemble_2.targets modulation_target = ReactionModulationTarget("b", 1, 0) swap_target = ReactionCofactorSwapTarget("b", [("nad_c", "nadh_c"), ("nadp_c", "nadph_c")]) @@ -288,33 +299,38 @@ def test_compatible_targets(self): ensemble_1 = EnsembleTarget("b", [modulation_target, swap_target]) ensemble_2 = EnsembleTarget("b", [swap_target, modulation_target]) - self.assertEqual(ensemble_1.targets, ensemble_2.targets) + assert ensemble_1.targets == ensemble_2.targets ki_target = ReactionKnockinTarget("c", None) modulation_target = ReactionModulationTarget("c", 1, 0) swap_target = ReactionCofactorSwapTarget("c", [("nad_c", "nadh_c"), ("nadp_c", "nadph_c")]) ensemble = EnsembleTarget("c", [modulation_target, swap_target, ki_target]) - self.assertEqual(ensemble.targets[0], ki_target) - self.assertEqual(ensemble.targets[1], swap_target) - self.assertEqual(ensemble.targets[2], modulation_target) + assert ensemble.targets[0] == ki_target + assert ensemble.targets[1] == swap_target + assert ensemble.targets[2] == modulation_target def test_incompatible_targets(self): ko_target = ReactionKnockoutTarget("a") ki_target = ReactionKnockinTarget("a", None) - self.assertRaises(IncompatibleTargets, EnsembleTarget, "a", [ko_target, ki_target]) - self.assertRaises(IncompatibleTargets, EnsembleTarget, "a", [ki_target, ko_target]) + with pytest.raises(IncompatibleTargets): + EnsembleTarget("a", [ko_target, ki_target]) + with pytest.raises(IncompatibleTargets): + EnsembleTarget("a", [ki_target, ko_target]) ko_target = ReactionKnockoutTarget("b") swap_target = ReactionCofactorSwapTarget("b", [("nad_c", "nadh_c"), ("nadp_c", "nadph_c")]) - self.assertRaises(IncompatibleTargets, EnsembleTarget, "b", [ko_target, swap_target]) - self.assertRaises(IncompatibleTargets, EnsembleTarget, "b", [swap_target, ko_target]) + with pytest.raises(IncompatibleTargets): + EnsembleTarget("b", [ko_target, swap_target]) + with pytest.raises(IncompatibleTargets): + EnsembleTarget("b", [swap_target, ko_target]) modulation_target = ReactionModulationTarget("c", 0, 0) ki_target = ReactionKnockinTarget("c", None) - self.assertRaises(IncompatibleTargets, EnsembleTarget, "c", [modulation_target, ki_target]) - self.assertRaises(IncompatibleTargets, EnsembleTarget, "c", [ki_target, modulation_target]) - + with pytest.raises(IncompatibleTargets): + EnsembleTarget("c", [modulation_target, ki_target]) + with pytest.raises(IncompatibleTargets): + EnsembleTarget("c", [ki_target, modulation_target]) diff --git a/tests/test_util.py b/tests/test_util.py index 02806f259..06579426f 100644 --- a/tests/test_util.py +++ b/tests/test_util.py @@ -14,42 +14,47 @@ from __future__ import absolute_import, print_function -import os -import unittest from functools import partial from itertools import chain +import pytest from cobra import Metabolite -from six.moves import range -from cameo.io import load_model from cameo.network_analysis.util import distance_based_on_molecular_formula -from cameo.util import TimeMachine, generate_colors, Singleton, partition, RandomGenerator, frozendict, ProblemCache, \ - float_floor, float_ceil +from cameo.util import (ProblemCache, RandomGenerator, Singleton, TimeMachine, + float_ceil, float_floor, frozendict, generate_colors, + partition) +from six.moves import range + +SEED = 1234 -TESTDIR = os.path.dirname(__file__) -TESTMODEL = load_model(os.path.join(TESTDIR, 'data/EcoliCore.xml'), sanitize=False) +@pytest.fixture(scope="function") +def problem_cache_trial(core_model): + reference = core_model.solve().fluxes + n_constraints = len(core_model.solver.constraints) + n_variables = len(core_model.solver.variables) + return core_model, reference, n_constraints, n_variables -class TimeMachineTestCase(unittest.TestCase): - def setUp(self): - self.tm = TimeMachine() +class TestTimeMachine: def test_one_change_list(self): + tm = TimeMachine() l = [1, 2, 3, 4] - self.tm(do=partial(l.append, 5), undo=l.pop) - self.assertEqual(l, [1, 2, 3, 4, 5]) - self.tm.reset() - self.assertEqual(l, [1, 2, 3, 4]) + tm(do=partial(l.append, 5), undo=l.pop) + assert l == [1, 2, 3, 4, 5] + tm.reset() + assert l == [1, 2, 3, 4] def test_str_handles_different_types_of_stored_operations(self): + tm = TimeMachine() + def normal_function(): pass partial_function = partial(str, 1) - self.tm(do=normal_function, undo=partial_function) - self.assertEqual(self.tm.__str__().split('\n')[2:-1], - ["undo: " + str(str) + " (1,) {}", 'redo: normal_function']) + tm(do=normal_function, undo=partial_function) + assert tm.__str__().split('\n')[2:-1] == ["undo: " + str(str) + " (1,) {}", 'redo: normal_function'] def test_with_statement(self): l = [1, 2, 3, 4] @@ -57,7 +62,7 @@ def test_with_statement(self): tm(do=partial(l.append, 33), undo=partial(l.pop)) tm(do=partial(l.append, 66), undo=partial(l.pop)) tm(do=partial(l.append, 99), undo=partial(l.pop)) - self.assertEqual(l, [1, 2, 3, 4]) + assert l == [1, 2, 3, 4] def some_method_that_adds_stuff(model, cache): @@ -78,174 +83,180 @@ def update_constraint(model, constraint, vars, lb, ub): constraint.ub = ub for i in range(10): - cache.add_variable("var_%i" % (i+1), create_variable, update_variable, 10, 15) + cache.add_variable("var_%i" % (i + 1), create_variable, update_variable, 10, 15) for i in range(9): - v1 = cache.variables["var_%i" % (i+1)] - v2 = cache.variables["var_%i" % (i+2)] - cache.add_constraint("c_%i" % (i+1), create_constraint, update_constraint, [v1, v2], -20, 100) + v1 = cache.variables["var_%i" % (i + 1)] + v2 = cache.variables["var_%i" % (i + 2)] + cache.add_constraint("c_%i" % (i + 1), create_constraint, update_constraint, [v1, v2], -20, 100) + +class TestProblemCache: + def test_add_variable(self, core_model): + cache = ProblemCache(core_model) -class TestProblemCache(unittest.TestCase): - def setUp(self): - self.reference = TESTMODEL.solve().fluxes - self.n_constraints = len(TESTMODEL.solver.constraints) - self.n_variables = len(TESTMODEL.solver.variables) + def add_var(model, var_id): + return model.solver.interface.Variable(var_id, ub=0) - def test_add_variable(self): - cache = ProblemCache(TESTMODEL) - add_var = lambda model, var_id: model.solver.interface.Variable(var_id, ub=0) - update_var = lambda model, var: setattr(var, "ub", 1000) + def update_var(model, var): + return setattr(var, "ub", 1000) for i in range(10): cache.add_variable("%i" % i, add_var, update_var) for i in range(10): - self.assertIn(cache.variables["%i" % i], TESTMODEL.solver.variables) - self.assertEqual(cache.variables["%i" % i].ub, 0) - self.assertEqual(TESTMODEL.solver.variables["%i" % i].ub, 0) + assert cache.variables["%i" % i] in core_model.solver.variables + assert cache.variables["%i" % i].ub == 0 + assert core_model.solver.variables["%i" % i].ub == 0 for i in range(10): cache.add_variable("%i" % i, add_var, update_var) - self.assertEqual(cache.variables["%i" % i].ub, 1000) - self.assertEqual(TESTMODEL.solver.variables["%i" % i].ub, 1000) + assert cache.variables["%i" % i].ub == 1000 + assert core_model.solver.variables["%i" % i].ub == 1000 cache.reset() for i in range(10): - self.assertRaises(KeyError, TESTMODEL.solver.variables.__getitem__, "%i" % i) + with pytest.raises(KeyError): + core_model.solver.variables.__getitem__("%i" % i) + + def test_add_constraint(self, core_model): + cache = ProblemCache(core_model) - def test_add_constraint(self): - cache = ProblemCache(TESTMODEL) + def add_var(model, var_id): + return model.solver.interface.Variable(var_id, ub=0) - add_var = lambda model, var_id: model.solver.interface.Variable(var_id, ub=0) - add_constraint = lambda m, const_id, var: m.solver.interface.Constraint(var, lb=-10, ub=10, name=const_id) - update_constraint = lambda model, const, var: setattr(const, "ub", 1000) + def add_constraint(m, const_id, var): + return m.solver.interface.Constraint(var, lb=-10, ub=10, name=const_id) + + def update_constraint(model, const, var): + return setattr(const, "ub", 1000) for i in range(10): cache.add_variable("%i" % i, add_var, None) cache.add_constraint("c%i" % i, add_constraint, update_constraint, cache.variables["%i" % i]) for i in range(10): - self.assertIn(cache.constraints["c%i" % i], TESTMODEL.solver.constraints) - self.assertEqual(cache.constraints["c%i" % i].ub, 10) - self.assertEqual(cache.constraints["c%i" % i].lb, -10) - self.assertEqual(TESTMODEL.solver.constraints["c%i" % i].ub, 10) - self.assertEqual(TESTMODEL.solver.constraints["c%i" % i].lb, -10) + assert cache.constraints["c%i" % i] in core_model.solver.constraints + assert cache.constraints["c%i" % i].ub == 10 + assert cache.constraints["c%i" % i].lb == -10 + assert core_model.solver.constraints["c%i" % i].ub == 10 + assert core_model.solver.constraints["c%i" % i].lb == -10 for i in range(10): cache.add_constraint("c%i" % i, add_constraint, update_constraint, cache.variables["%i" % i]) - self.assertEqual(TESTMODEL.solver.constraints["c%i" % i].ub, 1000) + assert core_model.solver.constraints["c%i" % i].ub == 1000 cache.reset() for i in range(10): - self.assertRaises(KeyError, TESTMODEL.solver.variables.__getitem__, "%i" % i) - self.assertRaises(KeyError, TESTMODEL.solver.constraints.__getitem__, "c%i" % i) + with pytest.raises(KeyError): + core_model.solver.variables.__getitem__("%i" % i) + with pytest.raises(KeyError): + core_model.solver.constraints.__getitem__("c%i" % i) - def test_cache_problem(self): + def test_cache_problem(self, problem_cache_trial): + core_model, reference, n_constraints, n_variables = problem_cache_trial # After the number of variables and constraints remains the same if nothing happens - self.assertEqual(self.n_constraints, len(TESTMODEL.solver.constraints)) - self.assertEqual(self.n_variables, len(TESTMODEL.solver.variables)) + assert n_constraints == len(core_model.solver.constraints) + assert n_variables == len(core_model.solver.variables) - cache = ProblemCache(TESTMODEL) - some_method_that_adds_stuff(TESTMODEL, cache) + cache = ProblemCache(core_model) + some_method_that_adds_stuff(core_model, cache) # After running some_method_that_adds_stuff with cache, problem has 10 more variables - self.assertEqual(self.n_variables+10, len(TESTMODEL.solver.variables)) + assert n_variables + 10 == len(core_model.solver.variables) # And has 9 more more constraints - self.assertEqual(self.n_constraints+9, len(TESTMODEL.solver.constraints)) + assert n_constraints + 9 == len(core_model.solver.constraints) cache.reset() # After reset cache, the problem should return to its original size - self.assertEqual(self.n_constraints, len(TESTMODEL.solver.constraints)) - self.assertEqual(self.n_variables, len(TESTMODEL.solver.variables)) + assert n_constraints == len(core_model.solver.constraints) + assert n_variables == len(core_model.solver.variables) - def test_with(self): - with ProblemCache(TESTMODEL) as cache: - some_method_that_adds_stuff(TESTMODEL, cache) + def test_with(self, problem_cache_trial): + core_model, reference, n_constraints, n_variables = problem_cache_trial + with ProblemCache(core_model) as cache: + some_method_that_adds_stuff(core_model, cache) # After running some_method_that_adds_stuff with cache, problem has 10 more variables - self.assertEqual(self.n_variables+10, len(TESTMODEL.solver.variables)) + assert n_variables + 10 == len(core_model.solver.variables) # And has 9 more more constraints - self.assertEqual(self.n_constraints+9, len(TESTMODEL.solver.constraints)) + assert n_constraints + 9 == len(core_model.solver.constraints) # If the method runs again, it does not add repeated variables - some_method_that_adds_stuff(TESTMODEL, cache) + some_method_that_adds_stuff(core_model, cache) # After running some_method_that_adds_stuff with cache, problem has 10 more variables - self.assertEqual(self.n_variables+10, len(TESTMODEL.solver.variables)) + assert n_variables + 10 == len(core_model.solver.variables) # And has 9 more more constraints - self.assertEqual(self.n_constraints+9, len(TESTMODEL.solver.constraints)) + assert n_constraints + 9 == len(core_model.solver.constraints) # After reset cache, the problem should return to its original size - self.assertEqual(self.n_constraints, len(TESTMODEL.solver.constraints)) - self.assertEqual(self.n_variables, len(TESTMODEL.solver.variables)) - + assert n_constraints == len(core_model.solver.constraints) + assert n_variables == len(core_model.solver.variables) -class TestRandomGenerator(unittest.TestCase): - def setUp(self): - self.seed = 1234 +class TestRandomGenerator: def test_random(self): random = RandomGenerator() for _ in range(1000): - self.assertGreaterEqual(random.random(), 0) - self.assertLessEqual(random.random(), 1) + assert random.random() >= 0 + assert random.random() <= 1 def test_randint(self): random = RandomGenerator() lower = 0 upper = 10 for _ in range(10000): - self.assertGreaterEqual(random.randint(lower, upper), lower) - self.assertLessEqual(random.randint(lower, upper), upper) + assert random.randint(lower, upper) >= lower + assert random.randint(lower, upper) <= upper lower = -10 upper = 100 for _ in range(10000): - self.assertGreaterEqual(random.randint(lower, upper), lower) - self.assertLessEqual(random.randint(lower, upper), upper) + assert random.randint(lower, upper) >= lower + assert random.randint(lower, upper) <= upper lower = 5 upper = 21 for _ in range(10000): - self.assertGreaterEqual(random.randint(lower, upper), lower) - self.assertLessEqual(random.randint(lower, upper), upper) + assert random.randint(lower, upper) >= lower + assert random.randint(lower, upper) <= upper lower = -5 upper = 5 for _ in range(10000): - self.assertGreaterEqual(random.randint(lower, upper), lower) - self.assertLessEqual(random.randint(lower, upper), upper) + assert random.randint(lower, upper) >= lower + assert random.randint(lower, upper) <= upper def test_seeded_methods(self): random = RandomGenerator() - random.seed(self.seed) + random.seed(SEED) value = random.random() - random.seed(self.seed) - self.assertEqual(value, random.random()) + random.seed(SEED) + assert value == random.random() - random.seed(self.seed) + random.seed(SEED) value = random.randint(1, 10) - random.seed(self.seed) - self.assertEqual(value, random.randint(1, 10)) + random.seed(SEED) + assert value == random.randint(1, 10) - random.seed(self.seed) + random.seed(SEED) population = [1, 2, 3, 4, 5] value = random.sample(population, 2) - random.seed(self.seed) - self.assertEqual(value, random.sample(population, 2)) + random.seed(SEED) + assert value == random.sample(population, 2) - random.seed(self.seed) + random.seed(SEED) value = random.uniform() - random.seed(self.seed) - self.assertEqual(value, random.uniform()) + random.seed(SEED) + assert value == random.uniform() -class TestUtils(unittest.TestCase): +class TestUtils: def test_color_generation(self): for i in range(1, 100): color_map = generate_colors(i) - self.assertEqual(len(color_map), i) - self.assertEqual(len(color_map), len(set(color_map.values()))) + assert len(color_map) == i + assert len(color_map) == len(set(color_map.values())) def test_partition(self): chunks = 3 @@ -256,83 +267,73 @@ def test_partition(self): ] for fixture in iterables: test_output = partition(fixture, chunks) - self.assertEqual(len(fixture), sum(map(len, test_output))) - self.assertEqual(len(test_output), chunks) - self.assertEqual(list(fixture), list(chain(*test_output))) + assert len(fixture) == sum(map(len, test_output)) + assert len(test_output) == chunks + assert list(fixture) == list(chain(*test_output)) for out_chunk in test_output: - self.assertTrue(set(out_chunk).issubset(set(fixture))) + assert set(out_chunk).issubset(set(fixture)) bad_input = 5 - self.assertRaises(TypeError, partition, bad_input, chunks) + with pytest.raises(TypeError): + partition(bad_input, chunks) def test_distance_based_on_molecular_formula(self): # from network_analysis.util met1 = Metabolite("H2O", formula="H2O") met2 = Metabolite("H2O2", formula="H2O2") met3 = Metabolite("C6H12O6", formula="C6H12O6") - self.assertEqual(distance_based_on_molecular_formula(met1, met2, normalize=False), 1) - self.assertEqual(distance_based_on_molecular_formula(met1, met2, normalize=True), 1. / 7) + assert distance_based_on_molecular_formula(met1, met2, normalize=False) == 1 + assert distance_based_on_molecular_formula(met1, met2, normalize=True) == 1. / 7 - self.assertEqual(distance_based_on_molecular_formula(met2, met3, normalize=False), 20) - self.assertEqual(distance_based_on_molecular_formula(met2, met3, normalize=True), 20. / 28) + assert distance_based_on_molecular_formula(met2, met3, normalize=False) == 20 + assert distance_based_on_molecular_formula(met2, met3, normalize=True) == 20. / 28 - self.assertEqual(distance_based_on_molecular_formula(met1, met3, normalize=False), 21) - self.assertEqual(distance_based_on_molecular_formula(met1, met3, normalize=True), 21. / 27) + assert distance_based_on_molecular_formula(met1, met3, normalize=False) == 21 + assert distance_based_on_molecular_formula(met1, met3, normalize=True) == 21. / 27 def test_float_conversions(self): val = 1.3456 - new_value = float_floor(val, 2) - self.assertEqual(new_value, 1.34) - + assert new_value == 1.34 new_value = float_ceil(val, 2) - self.assertEqual(new_value, 1.35) - + assert new_value == 1.35 new_value = float_floor(val, 1) - self.assertEqual(new_value, 1.3) - + assert new_value == 1.3 new_value = float_ceil(val, 1) - self.assertEqual(new_value, 1.4) - + assert new_value == 1.4 new_value = float_floor(val) - self.assertEqual(new_value, 1) - + assert new_value == 1 new_value = float_ceil(val) - self.assertEqual(new_value, 2) - + assert new_value == 2 val = 0.00000 for i in range(1, 10): new_value = float_floor(val, i) - self.assertEqual(new_value, 0) + assert new_value == 0 new_value = float_ceil(val, i) - self.assertEqual(new_value, 0) - - - + assert new_value == 0 -class FrozendictTestCase(unittest.TestCase): - def setUp(self): - self.frozen_dict = frozendict({"A": 1, "B": 2, "C": 3, "D": 4, "E": [2, 3, 4, 5]}) +class TestFrozendict: def test_frozen_attributes(self): - self.assertRaises(AttributeError, self.frozen_dict.popitem) - self.assertRaises(AttributeError, self.frozen_dict.pop, "A") - self.assertRaises(AttributeError, self.frozen_dict.__setitem__, "C", 1) - self.assertRaises(AttributeError, self.frozen_dict.setdefault, "K") - self.assertRaises(AttributeError, self.frozen_dict.__delitem__, "A") - self.assertRaises(AttributeError, self.frozen_dict.update) - - self.assertTrue(hasattr(self.frozen_dict, "__hash__")) - - -class TestSingleton(unittest.TestCase): + frozen_dict = frozendict({"A": 1, "B": 2, "C": 3, "D": 4, "E": [2, 3, 4, 5]}) + with pytest.raises(AttributeError): + frozen_dict.popitem() + with pytest.raises(AttributeError): + frozen_dict.pop("A") + with pytest.raises(AttributeError): + frozen_dict.__setitem__("C", 1) + with pytest.raises(AttributeError): + frozen_dict.setdefault("K") + with pytest.raises(AttributeError): + frozen_dict.__delitem__("A") + with pytest.raises(AttributeError): + frozen_dict.update() + + assert hasattr(frozen_dict, "__hash__") + + +class TestSingleton: def test_singleton(self): s1 = Singleton() s2 = Singleton() - self.assertIs(s1, s2) - - -if __name__ == "__main__": - import nose - - nose.runmodule() + assert s1 is s2 diff --git a/tests/test_webmodels.py b/tests/test_webmodels.py index 057cf650e..60c4a1cd2 100644 --- a/tests/test_webmodels.py +++ b/tests/test_webmodels.py @@ -7,37 +7,45 @@ # http://www.apache.org/licenses/LICENSE-2.0 # Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + + # See the License for the specific language governing permissions and # limitations under the License. from tempfile import _TemporaryFileWrapper -from unittest import TestCase +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +import pytest import requests from pandas import DataFrame -from nose.plugins.skip import SkipTest -from cameo.models.webmodels import index_models_minho, get_sbml_file, NotFoundException + +from cameo.models.webmodels import (NotFoundException, get_sbml_file, + index_models_minho) -class WebmodelsTestCase(TestCase): +class TestWebModels: def test_invalid_host(self): - self.assertRaises(requests.ConnectionError, index_models_minho, host="http://blabla") - self.assertRaises(requests.ConnectionError, get_sbml_file, 1, host="http://blabla") + with pytest.raises(requests.ConnectionError): + index_models_minho(host="http://blabla") + with pytest.raises(requests.ConnectionError): + get_sbml_file(1, host="http://blabla") def test_index(self): try: index = index_models_minho() except requests.ConnectionError: - raise SkipTest('skipping web test due to connection error') - self.assertIsInstance(index, DataFrame) - self.assertListEqual(list(index.columns), - ["id", "name", "doi", "author", "year", "formats", "organism", "taxonomy", "validated"]) + pytest.skip('skipping test due to connection error') + else: + assert isinstance(index, DataFrame) + assert list(index.columns) == ["id", "name", "doi", "author", "year", "formats", "organism", "taxonomy", + "validated"] def test_get_sbml(self): try: tmp = get_sbml_file(1) except requests.ConnectionError: - raise SkipTest('skipping web test due to connection error') - self.assertIsInstance(tmp, _TemporaryFileWrapper) - self.assertRaises(NotFoundException, get_sbml_file, -1) + pytest.skip('skipping test due to connection error') + else: + assert isinstance(tmp, _TemporaryFileWrapper) + with pytest.raises(NotFoundException): + get_sbml_file(-1) diff --git a/tox.ini b/tox.ini index 34b09f6f4..e1546dff9 100644 --- a/tox.ini +++ b/tox.ini @@ -1,5 +1,5 @@ [tox] -envlist = flake8, py27, py34, py35 +envlist = flake8, py27, py34, py35, py36 [testenv:flake8] basepython=python @@ -10,5 +10,4 @@ commands=flake8 cameo deps= -r{toxinidir}/requirements_dev.txt commands = - pip install -U pip - python setup.py nosetests + pytest