def objective(self, value): if isinstance(value, sympy.Basic): value = self.problem.Objective(value, sloppy=False) if not isinstance(value, (dict, optlang.interface.Objective)): try: reactions = self.reactions.get_by_any(value) except KeyError: raise ValueError('invalid objective') value = {rxn: 1 for rxn in reactions} set_objective(self, value, additive=False)
def objective(self, value): if isinstance(value, Basic): value = self.problem.Objective(value, sloppy=False) if not isinstance(value, (dict, optlang.interface.Objective)): try: reactions = self.reactions.get_by_any(value) except KeyError: raise ValueError('invalid objective') value = {rxn: 1 for rxn in reactions} set_objective(self, value, additive=False)
def test_fix_objective_as_constraint_minimize(self, solver, model): model.reactions.Biomass_Ecoli_core.bounds = (0.1, 0.1) minimize_glucose = model.problem.Objective( model.reactions.EX_glc__D_e.flux_expression, direction='min') su.set_objective(model, minimize_glucose) su.fix_objective_as_constraint(model) fx_name = 'Fixed_objective_{}'.format(model.objective.name) constr = model.constraints assert (constr[fx_name].lb, constr[fx_name].ub) == (None, model.solver.objective.value)
def objective(self, objective): if isinstance(objective, str): self.model.objective = objective elif isinstance(objective, dict): from cobra.util.solver import set_objective linear_coef = {self.model.reactions.get_by_id(r_id): v for r_id, v in objective.items()} set_objective(self.model, linear_coef) else: raise ValueError( 'The objective must be a reaction identifier or a dictionary of \ reaction identifier with respective coeficients.')
def test_fix_objective_as_constraint_minimize(model, solver): model.solver = solver model.reactions.Biomass_Ecoli_core.bounds = (0.1, 0.1) minimize_glucose = model.problem.Objective( model.reactions.EX_glc__D_e.flux_expression, direction='min') su.set_objective(model, minimize_glucose) su.fix_objective_as_constraint(model) fx_name = 'fixed_objective_{}'.format(model.objective.name) constr = model.constraints # Ensure that a solution exists on non-GLPK solvers. model.slim_optimize() assert (constr[fx_name].lb, constr[fx_name].ub) == (None, model.solver.objective.value)
def optimize_for_species(model_file_name, species_id, medium, time_point_folder): """ Knock out the other species from a two species model, optimize, and save results. """ LOGGER.info('Loading model {0} to optimize for {1}'.format( model_file_name, species_id)) pair_model = load_model_from_file(model_file_name) # Figure out the species to knock out in the pair community model. species_index = -1 for index in range(len(pair_model.notes['species'])): if pair_model.notes['species'][index]['id'] == species_id: species_index = index if species_index < 0: raise Exception( 'Species {0} is not a member of the community'.format(species_id)) if species_index == 0: knockout_index = 1 else: knockout_index = 0 knockout_id = pair_model.notes['species'][knockout_index]['id'] LOGGER.info('Going to knock out {0} from index {1}'.format( knockout_id, knockout_index)) with pair_model: # Apply the medium. apply_medium(pair_model, medium) # Knock out all of the reactions for the specified species. knockout_reactions = pair_model.reactions.query( lambda r: r.startswith(knockout_id), 'id') for reaction in knockout_reactions: reaction.knock_out() # Remove the species objective from the community model objective. knockout_objective = pair_model.reactions.get_by_id( pair_model.notes['species'][knockout_index]['objective']) linear_coefficients = linear_reaction_coefficients(pair_model) del linear_coefficients[knockout_objective] set_objective(pair_model, linear_coefficients) save_model_to_file(pair_model, join(time_point_folder, pair_model.id + '.json')) # Optimize the community model with the specified species knocked out. solution = pair_model.optimize() solution.fluxes.to_json( join(time_point_folder, pair_model.id + '-solution.json')) # , orient='records', lines=True return solution
def test_fix_objective_as_constraint_minimize(self, model, solver): model.solver = solver model.reactions.Biomass_Ecoli_core.bounds = (0.1, 0.1) minimize_glucose = model.problem.Objective( model.reactions.EX_glc__D_e.flux_expression, direction='min') su.set_objective(model, minimize_glucose) su.fix_objective_as_constraint(model) fx_name = 'fixed_objective_{}'.format(model.objective.name) constr = model.constraints # Ensure that a solution exists on non-GLPK solvers. model.slim_optimize() assert (constr[fx_name].lb, constr[fx_name].ub) == ( None, model.solver.objective.value)
def test_fix_objective_as_constraint_minimize(model: "Model", solver: str) -> None: """Test fixing present objective as a constraint but as a minimization.""" model.solver = solver model.reactions.Biomass_Ecoli_core.bounds = (0.1, 0.1) minimize_glucose = model.problem.Objective( model.reactions.EX_glc__D_e.flux_expression, direction="min") su.set_objective(model, minimize_glucose) su.fix_objective_as_constraint(model) fx_name = f"fixed_objective_{model.objective.name}" constr = model.constraints # Ensure that a solution exists on non-GLPK solvers. model.slim_optimize() assert (constr[fx_name].lb, constr[fx_name].ub) == ( None, model.solver.objective.value, )
def model_from_dict(obj): """Build a model from a dict. Models stored in json are first formulated as a dict that can be read to cobra model using this function. Parameters ---------- obj : dict A dictionary with elements, 'genes', 'compartments', 'id', 'metabolites', 'notes' and 'reqctions' where 'metabolites', 'genes' and 'metabolites' are in turn lists with dictionaries holding all attributes to form the corresponding object. Returns ------- cora.core.Model The generated model. See Also -------- cobra.io.json.model_to_dict """ if 'reactions' not in obj: raise Exception('JSON object has no reactions attribute. Cannot load.') model = Model() model.add_metabolites([ metabolite_from_dict(metabolite) for metabolite in obj['metabolites'] ]) model.genes.extend([gene_from_dict(gene) for gene in obj['genes']]) model.add_reactions( [reaction_from_dict(reaction, model) for reaction in obj['reactions']]) objective_reactions = [ rxn for rxn in obj['reactions'] if rxn.get('objective_coefficient', 0) != 0 ] coefficients = { model.reactions.get_by_id(rxn['id']): rxn['objective_coefficient'] for rxn in objective_reactions } set_objective(model, coefficients) for k, v in iteritems(obj): if k in {'id', 'name', 'notes', 'compartments', 'annotation'}: setattr(model, k, v) return model
def fix_reaction_to_min(model: cobra.Model, reac: cobra.Reaction) -> Tuple[str, float]: """Fix a reaction to its minimum.""" rev_id = _apply_rev(reac.id, model) rev_reac = model.reactions.get_by_id(rev_id) if rev_id else None with model: if rev_reac is not None: prev_bounds = reac.bounds set_objective(model, {rev_reac: 1}) reac.bounds = 0, 0 lb = model.slim_optimize() reac.bounds = prev_bounds reac_to_fix = rev_id else: set_objective(model, {reac: -1}) lb = model.slim_optimize() reac_to_fix = reac.id return reac_to_fix, lb
def convert_to_irreversible(model): """Split reversible reactions into two irreversible reactions These two reactions will proceed in opposite directions. This guarentees that all reactions in the model will only allow positive flux values, which is useful for some modeling problems. Arguments ---------- * model: cobra.Model ~ A Model object which will be modified in place. """ #warn("deprecated, not applicable for optlang solvers", DeprecationWarning) reactions_to_add = [] coefficients = {} for reaction in model.reactions: # If a reaction is reverse only, the forward reaction (which # will be constrained to 0) will be left in the model. if reaction.lower_bound < 0 and reaction.upper_bound > 0: reverse_reaction = Reaction(reaction.id + "_reverse") reverse_reaction.lower_bound = max(0, -reaction.upper_bound) reverse_reaction.upper_bound = -reaction.lower_bound coefficients[ reverse_reaction] = reaction.objective_coefficient * -1 reaction.lower_bound = max(0, reaction.lower_bound) reaction.upper_bound = max(0, reaction.upper_bound) # Make the directions aware of each other reaction.notes["reflection"] = reverse_reaction.id reverse_reaction.notes["reflection"] = reaction.id reaction_dict = { k: v * -1 for k, v in reaction._metabolites.items() } reverse_reaction.add_metabolites(reaction_dict) reverse_reaction._model = reaction._model reverse_reaction._genes = reaction._genes for gene in reaction._genes: gene._reaction.add(reverse_reaction) reverse_reaction.subsystem = reaction.subsystem reverse_reaction._gene_reaction_rule = reaction._gene_reaction_rule reactions_to_add.append(reverse_reaction) model.add_reactions(reactions_to_add) set_objective(model, coefficients, additive=True)
def model_from_dict(obj): """Build a model from a dict. Models stored in json are first formulated as a dict that can be read to cobra model using this function. Parameters ---------- obj : dict A dictionary with elements, 'genes', 'compartments', 'id', 'metabolites', 'notes' and 'reactions'; where 'metabolites', 'genes' and 'metabolites' are in turn lists with dictionaries holding all attributes to form the corresponding object. Returns ------- cora.core.Model The generated model. See Also -------- cobra.io.model_to_dict """ if 'reactions' not in obj: raise ValueError('Object has no reactions attribute. Cannot load.') model = Model() model.add_metabolites( [metabolite_from_dict(metabolite) for metabolite in obj['metabolites']] ) model.genes.extend([gene_from_dict(gene) for gene in obj['genes']]) model.add_reactions( [reaction_from_dict(reaction, model) for reaction in obj['reactions']] ) objective_reactions = [rxn for rxn in obj['reactions'] if rxn.get('objective_coefficient', 0) != 0] coefficients = { model.reactions.get_by_id(rxn['id']): rxn['objective_coefficient'] for rxn in objective_reactions} set_objective(model, coefficients) for k, v in iteritems(obj): if k in {'id', 'name', 'notes', 'compartments', 'annotation'}: setattr(model, k, v) return model
def test_change_objective(self, model): expression = 1.0 * model.variables['ENO'] + \ 1.0 * model.variables['PFK'] model.objective = model.problem.Objective( expression) assert model.objective.expression == expression model.objective = "ENO" eno_obj = model.problem.Objective( model.reactions.ENO.flux_expression, direction="max") pfk_obj = model.problem.Objective( model.reactions.PFK.flux_expression, direction="max") assert model.objective == eno_obj with model: model.objective = "PFK" assert model.objective == pfk_obj assert model.objective == eno_obj expression = model.objective.expression atpm = model.reactions.get_by_id("ATPM") biomass = model.reactions.get_by_id("Biomass_Ecoli_core") with model: model.objective = atpm assert model.objective.expression == expression with model: atpm.objective_coefficient = 1 biomass.objective_coefficient = 2 assert model.objective.expression == expression with model: set_objective(model, model.problem.Objective( atpm.flux_expression)) assert model.objective.expression == atpm.flux_expression assert model.objective.expression == expression expression = model.objective.expression with model: with model: # Test to make sure nested contexts are OK set_objective(model, atpm.flux_expression, additive=True) assert (model.objective.expression == expression + atpm.flux_expression) assert model.objective.expression == expression
def test_change_objective(self, model): expression = 1.0 * model.variables['ENO'] + \ 1.0 * model.variables['PFK'] model.objective = model.problem.Objective( expression) assert same_ex(model.objective.expression, expression) model.objective = "ENO" eno_obj = model.problem.Objective( model.reactions.ENO.flux_expression, direction="max") pfk_obj = model.problem.Objective( model.reactions.PFK.flux_expression, direction="max") assert same_ex(model.objective.expression, eno_obj.expression) with model: model.objective = "PFK" assert same_ex(model.objective.expression, pfk_obj.expression) assert same_ex(model.objective.expression, eno_obj.expression) expression = model.objective.expression atpm = model.reactions.get_by_id("ATPM") biomass = model.reactions.get_by_id("Biomass_Ecoli_core") with model: model.objective = atpm assert same_ex(model.objective.expression, expression) with model: atpm.objective_coefficient = 1 biomass.objective_coefficient = 2 assert same_ex(model.objective.expression, expression) with model: set_objective(model, model.problem.Objective( atpm.flux_expression)) assert same_ex(model.objective.expression, atpm.flux_expression) assert same_ex(model.objective.expression, expression) expression = model.objective.expression with model: with model: # Test to make sure nested contexts are OK set_objective(model, atpm.flux_expression, additive=True) assert same_ex(model.objective.expression, expression + atpm.flux_expression) assert same_ex(model.objective.expression, expression)
def convert_to_irreversible(cobra_model): """Split reversible reactions into two irreversible reactions These two reactions will proceed in opposite directions. This guarentees that all reactions in the model will only allow positive flux values, which is useful for some modeling problems. cobra_model: A Model object which will be modified in place. """ warn("deprecated, not applicable for optlang solvers", DeprecationWarning) reactions_to_add = [] coefficients = {} for reaction in cobra_model.reactions: # If a reaction is reverse only, the forward reaction (which # will be constrained to 0) will be left in the model. if reaction.lower_bound < 0: reverse_reaction = Reaction(reaction.id + "_reverse") reverse_reaction.lower_bound = max(0, -reaction.upper_bound) reverse_reaction.upper_bound = -reaction.lower_bound coefficients[ reverse_reaction] = reaction.objective_coefficient * -1 reaction.lower_bound = max(0, reaction.lower_bound) reaction.upper_bound = max(0, reaction.upper_bound) # Make the directions aware of each other reaction.notes["reflection"] = reverse_reaction.id reverse_reaction.notes["reflection"] = reaction.id reaction_dict = {k: v * -1 for k, v in iteritems(reaction._metabolites)} reverse_reaction.add_metabolites(reaction_dict) reverse_reaction._model = reaction._model reverse_reaction._genes = reaction._genes for gene in reaction._genes: gene._reaction.add(reverse_reaction) reverse_reaction.subsystem = reaction.subsystem reverse_reaction._gene_reaction_rule = reaction._gene_reaction_rule reactions_to_add.append(reverse_reaction) cobra_model.add_reactions(reactions_to_add) set_objective(cobra_model, coefficients, additive=True)
def _from_dict(obj): """build a model from a dict""" if 'reactions' not in obj: raise Exception('JSON object has no reactions attribute. Cannot load.') model = Model() model.add_metabolites([ metabolite_from_dict(metabolite) for metabolite in obj['metabolites'] ]) model.genes.extend([gene_from_dict(gene) for gene in obj['genes']]) model.add_reactions( [reaction_from_dict(reaction, model) for reaction in obj['reactions']]) objective_reactions = [ rxn for rxn in obj['reactions'] if rxn.get('objective_coefficient', 0) != 0 ] coefficients = { model.reactions.get_by_id(rxn['id']): rxn['objective_coefficient'] for rxn in objective_reactions } set_objective(model, coefficients) for k, v in iteritems(obj): if k in {'id', 'name', 'notes', 'compartments', 'annotation'}: setattr(model, k, v) return model
def _fva_step(reaction_id): """Run a maximization and a minization. Modified from cobrapy to account for possibly duplicated (`_REV`) reactions in the EC model. """ global _model global _target target = _model.__getattribute__(_target) reac = target.get_by_id(reaction_id) rev_id = _apply_rev(reaction_id, _model) rev_reac = target.get_by_id(rev_id) if rev_id else None set_objective(_model, {reac: 1}) if rev_reac is not None: # if reac with duplicate is optimized, block counterpart prev_bounds = rev_reac.bounds rev_reac.bounds = 0, 0 ub = _model.slim_optimize() rev_reac.bounds = prev_bounds # and use the reverse for the minimum (blocking forward) prev_bounds = reac.bounds set_objective(_model, {rev_reac: 1}, False) reac.bounds = 0, 0 lb = -_model.slim_optimize() reac.bounds = prev_bounds else: ub = _model.slim_optimize() set_objective(_model, {reac: -1}, False) lb = _model.slim_optimize() # handle infeasible case if isnan_none(lb) or isnan_none(ub): lb = 0.0 if isnan_none(lb) else lb ub = 0.0 if isnan_none(ub) else ub LOGGER.warning( "Could not get flux for reaction %s, setting " "it to 0. This is usually due to numerical instability.", reaction_id, ) return reaction_id, lb, ub
def single_species_knockout(community, species_id): """ Knockout a species from a community model. Parameters ---------- community : cobra.core.Model Community model species_id : str ID of species to knockout from community model Returns ------- cobra.Solution Solution after optimizing model with species knocked out Raises ------ Exception If specified species is not a member of the community. """ # Make sure the species is in the community model. species_index = -1 for index in range(len(community.notes['species'])): if community.notes['species'][index]['id'] == species_id: species_index = index if species_index < 0: raise Exception( 'Species {0} is not a member of the community'.format(species_id)) if cobra06: with community: # Knock out all of the reactions for the specified species. species_reactions = community.reactions.query( lambda r: r.startswith(species_id), 'id') for reaction in species_reactions: reaction.knock_out() # Remove the species objective from the community model objective. species_objective = community.reactions.get_by_id( community.notes['species'][species_index]['objective']) linear_coefficients = linear_reaction_coefficients(community) del linear_coefficients[species_objective] set_objective(community, linear_coefficients) # Optimize the community model with the specified species knocked out. solution = community.optimize() else: # Knock out all of the reactions for the specified species. species_reactions = community.reactions.query( lambda r: r.startswith(species_id), 'id') saved_bounds = dict() for reaction in species_reactions: saved_bounds[reaction.id] = reaction.bounds reaction.knock_out() # Remove the species objective from the community model objective. species_objectives = dict() for reaction in community.objective: if reaction.id.startswith(species_id): species_objectives[reaction] = reaction.objective_coefficient reaction.objective_coefficient = 0. # Optimize the community model with the specified species knocked out. solution = community.optimize() # Restore all species reactions to original values. for reaction in species_reactions: reaction.bounds = saved_bounds[reaction.id] for reaction in species_objectives: reaction.objective_coefficient = species_objectives[reaction] return solution
def create_community_model(source_models): """ Create a community model from a list of source models. A community model contains all of the reactions and metabolites from the source models. As a source model for a species is added to the community model, a model ID prefix is added to the IDs of reactions and metabolites. This is the same as assigning each species to a different compartment, which is required because cells are closed compartments that do not share metabolites. A shared compartment is added to the community model and all unique exchange reactions from the source models are added to the community model to exchange metabolites between the shared compartment and the system boundary. For each source model, all exchange reactions are converted to transport reactions to move the metabolite between the shared compartment and the extracellular compartment of the source model. Parameters ---------- source_models : list of str List of path names to model files Returns ------- cobra.core.Model Community model Raises ------ Exception If there are less than two models in the list of source models. If there is a duplicate model ID in the list of source models. If there is more than one objective If the type of IDs in the model metabolites could not be determined. """ # There must be at least two source models to make a community model. if len(source_models) < 2: raise Exception( 'There must be at least two species in a community model') # Start with an empty model. community = Model('Community') community.compartments['u'] = 'Lumen' # Keep track of the source models. Each element is a tuple with ID and source file name. community.notes['species'] = list() # IDs of all exchange reactions in the community. exchange_reaction_ids = set() # IDs of all models in the community (duplicates are not allowed). model_ids = set() # Add of the source models to the community model. for model_filename in source_models: # Load the model from a file. model = load_model_from_file(model_filename) # Since the model ID is used as a prefix for reactions and metabolites it must be unique. if model.id in model_ids: raise Exception( 'Model ID {0} from {1} is a duplicate in the community'.format( model.id, model_filename)) model_ids.add(model.id) # Check for multiple objectives in the model (which changed significantly in cobra 0.6). if cobra06: linear_coefficients = linear_reaction_coefficients(model) if len(linear_coefficients) != 1: raise Exception( 'Wrong number of objectives for model {0}, only one growth objective is allowed' .format(model_filename)) objective_id = '{0}_{1}'.format( model.id, six.next(six.iterkeys(linear_coefficients)).id) objective_value = six.next(six.itervalues(linear_coefficients)) else: if len(model.objective) != 1: raise Exception( 'Wrong number of objectives for model {0}, only one growth objective is allowed' .format(model_filename)) objective_id = '{0}_{1}'.format( model.id, six.next(six.iterkeys(model.objective)).id) objective_value = six.next(six.itervalues(model.objective)) # All metabolites need to have a compartment suffix. for metabolite in model.metabolites: metabolite.notes['type'] = _id_type(metabolite.id) unknown = model.metabolites.query(lambda x: 'unknown' in x['type'], 'notes') if len(unknown) > 0: raise Exception( 'Unknown compartment suffixes found in metabolites for {0}'. format(model_filename)) # Get the exchange reactions from the species model. exchange_reactions = model.reactions.query( lambda x: x.startswith('EX_'), 'id') # Add any exchange reactions that are not already in the community model. for reaction in exchange_reactions: if reaction.id not in exchange_reaction_ids: exchange_reaction_ids.add(reaction.id) if cobra06: metabolite = six.next(six.iterkeys( reaction.metabolites)).copy() metabolite.compartment = community_compartment metabolite.id = _change_compartment( metabolite.id, community_compartment, metabolite.notes['type']) rxn = community.add_boundary( metabolite) # This is slow on cobra06 rxn.id = reaction.id # Keep same ID as species model else: community.add_reactions([copy_exchange_reaction(reaction)]) # Update the reaction IDs with species prefix and convert exchange reactions to transport reactions. for reaction in model.reactions: # Change the exchange reactions to transport reactions to move the metabolite between the # community compartment and the extracellular compartment of this organism. if reaction in exchange_reactions: e_metabolite = six.next(six.iterkeys(reaction.metabolites)) u_metabolite = community.metabolites.get_by_id( _change_compartment(e_metabolite.id, community_compartment, e_metabolite.notes['type'])) reaction.add_metabolites({u_metabolite: 1.}) reaction.id = '{0}_TR_{1}'.format( model.id, reaction.id[3:]) # Strip "EX_" prefix reaction.bounds = (-1000., 1000.) else: reaction.id = '{0}_{1}'.format(model.id, reaction.id) # Update the metabolite IDs with species prefix. for metabolite in model.metabolites: if metabolite.compartment != community_compartment: metabolite.id = '{0}_{1}'.format(model.id, metabolite.id) # Add the species model to the community model. community += model if cobra06: # Workaround until agreement reached on issue #505. objective_reaction = community.reactions.get_by_id(objective_id) set_objective(community, {objective_reaction: objective_value}, additive=True) species = { 'id': model.id, 'objective': objective_id, 'filename': model_filename } community.notes['species'].append(species) # Update the community ID to include all of the species in the community. # Note that the community ID is used as the file name when saving the # community model to a JSON file. community.id = 'x'.join(model_ids) return community
def create_cobra_model_from_sbml_file(sbml_filename, old_sbml=False, legacy_metabolite=False, print_time=False, use_hyphens=False): """convert an SBML XML file into a cobra.Model object. Supports SBML Level 2 Versions 1 and 4. The function will detect if the SBML fbc package is used in the file and run the converter if the fbc package is used. Parameters ---------- sbml_filename: string old_sbml: bool Set to True if the XML file has metabolite formula appended to metabolite names. This was a poorly designed artifact that persists in some models. legacy_metabolite: bool If True then assume that the metabolite id has the compartment id appended after an underscore (e.g. _c for cytosol). This has not been implemented but will be soon. print_time: bool deprecated use_hyphens: bool If True, double underscores (__) in an SBML ID will be converted to hyphens Returns ------- Model : The parsed cobra model """ if not libsbml: raise ImportError('create_cobra_model_from_sbml_file ' 'requires python-libsbml') __default_lower_bound = -1000 __default_upper_bound = 1000 __default_objective_coefficient = 0 # Ensure that the file exists if not isfile(sbml_filename): raise IOError('Your SBML file is not found: %s' % sbml_filename) # Expressions to change SBML Ids to Palsson Lab Ids metabolite_re = re.compile('^M_') reaction_re = re.compile('^R_') compartment_re = re.compile('^C_') if print_time: warn("print_time is deprecated", DeprecationWarning) model_doc = libsbml.readSBML(sbml_filename) if model_doc.getPlugin("fbc") is not None: from libsbml import ConversionProperties, LIBSBML_OPERATION_SUCCESS conversion_properties = ConversionProperties() conversion_properties.addOption( "convert fbc to cobra", True, "Convert FBC model to Cobra model") result = model_doc.convert(conversion_properties) if result != LIBSBML_OPERATION_SUCCESS: raise Exception("Conversion of SBML+fbc to COBRA failed") sbml_model = model_doc.getModel() sbml_model_id = sbml_model.getId() sbml_species = sbml_model.getListOfSpecies() sbml_reactions = sbml_model.getListOfReactions() sbml_compartments = sbml_model.getListOfCompartments() compartment_dict = dict([(compartment_re.split(x.getId())[-1], x.getName()) for x in sbml_compartments]) if legacy_metabolite: # Deal with the palsson lab appending the compartment id to the # metabolite id new_dict = {} for the_id, the_name in compartment_dict.items(): if the_name == '': new_dict[the_id[0].lower()] = the_id else: new_dict[the_id] = the_name compartment_dict = new_dict legacy_compartment_converter = dict( [(v, k) for k, v in iteritems(compartment_dict)]) cobra_model = Model(sbml_model_id) metabolites = [] metabolite_dict = {} # Convert sbml_metabolites to cobra.Metabolites for sbml_metabolite in sbml_species: # Skip sbml boundary species if sbml_metabolite.getBoundaryCondition(): continue if (old_sbml or legacy_metabolite) and \ sbml_metabolite.getId().endswith('_b'): # Deal with incorrect sbml from bigg.ucsd.edu continue tmp_metabolite = Metabolite() metabolite_id = tmp_metabolite.id = sbml_metabolite.getId() tmp_metabolite.compartment = compartment_re.split( sbml_metabolite.getCompartment())[-1] if legacy_metabolite: if tmp_metabolite.compartment not in compartment_dict: tmp_metabolite.compartment = legacy_compartment_converter[ tmp_metabolite.compartment] tmp_metabolite.id = parse_legacy_id( tmp_metabolite.id, tmp_metabolite.compartment, use_hyphens=use_hyphens) if use_hyphens: tmp_metabolite.id = metabolite_re.split( tmp_metabolite.id)[-1].replace('__', '-') else: # Just in case the SBML ids are ill-formed and use - tmp_metabolite.id = metabolite_re.split( tmp_metabolite.id)[-1].replace('-', '__') tmp_metabolite.name = sbml_metabolite.getName() tmp_formula = '' tmp_metabolite.notes = parse_legacy_sbml_notes( sbml_metabolite.getNotesString()) if sbml_metabolite.isSetCharge(): tmp_metabolite.charge = sbml_metabolite.getCharge() if "CHARGE" in tmp_metabolite.notes: note_charge = tmp_metabolite.notes["CHARGE"][0] try: note_charge = float(note_charge) if note_charge == int(note_charge): note_charge = int(note_charge) except: warn("charge of %s is not a number (%s)" % (tmp_metabolite.id, str(note_charge))) else: if ((tmp_metabolite.charge is None) or (tmp_metabolite.charge == note_charge)): tmp_metabolite.notes.pop("CHARGE") # set charge to the one from notes if not assigend before # the same tmp_metabolite.charge = note_charge else: # tmp_metabolite.charge != note_charge msg = "different charges specified for %s (%d and %d)" msg = msg % (tmp_metabolite.id, tmp_metabolite.charge, note_charge) warn(msg) # Chances are a 0 note charge was written by mistake. We # will default to the note_charge in this case. if tmp_metabolite.charge == 0: tmp_metabolite.charge = note_charge for the_key in tmp_metabolite.notes.keys(): if the_key.lower() == 'formula': tmp_formula = tmp_metabolite.notes.pop(the_key)[0] break if tmp_formula == '' and old_sbml: tmp_formula = tmp_metabolite.name.split('_')[-1] tmp_metabolite.name = tmp_metabolite.name[:-len(tmp_formula) - 1] tmp_metabolite.formula = tmp_formula metabolite_dict.update({metabolite_id: tmp_metabolite}) metabolites.append(tmp_metabolite) cobra_model.add_metabolites(metabolites) # Construct the vectors and matrices for holding connectivity and numerical # info to feed to the cobra toolbox. # Always assume steady state simulations so b is set to 0 cobra_reaction_list = [] coefficients = {} for sbml_reaction in sbml_reactions: if use_hyphens: # Change the ids to match conventions used by the Palsson lab. reaction = Reaction(reaction_re.split( sbml_reaction.getId())[-1].replace('__', '-')) else: # Just in case the SBML ids are ill-formed and use - reaction = Reaction(reaction_re.split( sbml_reaction.getId())[-1].replace('-', '__')) cobra_reaction_list.append(reaction) # reaction.exchange_reaction = 0 reaction.name = sbml_reaction.getName() cobra_metabolites = {} # Use the cobra.Metabolite class here for sbml_metabolite in sbml_reaction.getListOfReactants(): tmp_metabolite_id = sbml_metabolite.getSpecies() # This deals with boundary metabolites if tmp_metabolite_id in metabolite_dict: tmp_metabolite = metabolite_dict[tmp_metabolite_id] cobra_metabolites[tmp_metabolite] = - \ sbml_metabolite.getStoichiometry() for sbml_metabolite in sbml_reaction.getListOfProducts(): tmp_metabolite_id = sbml_metabolite.getSpecies() # This deals with boundary metabolites if tmp_metabolite_id in metabolite_dict: tmp_metabolite = metabolite_dict[tmp_metabolite_id] # Handle the case where the metabolite was specified both # as a reactant and as a product. if tmp_metabolite in cobra_metabolites: warn("%s appears as a reactant and product %s" % (tmp_metabolite_id, reaction.id)) cobra_metabolites[ tmp_metabolite] += sbml_metabolite.getStoichiometry() # if the combined stoichiometry is 0, remove the metabolite if cobra_metabolites[tmp_metabolite] == 0: cobra_metabolites.pop(tmp_metabolite) else: cobra_metabolites[ tmp_metabolite] = sbml_metabolite.getStoichiometry() # check for nan for met, v in iteritems(cobra_metabolites): if isnan(v) or isinf(v): warn("invalid value %s for metabolite '%s' in reaction '%s'" % (str(v), met.id, reaction.id)) reaction.add_metabolites(cobra_metabolites) # Parse the kinetic law info here. parameter_dict = {} # If lower and upper bounds are specified in the Kinetic Law then # they override the sbml reversible attribute. If they are not # specified then the bounds are determined by getReversible. if not sbml_reaction.getKineticLaw(): if sbml_reaction.getReversible(): parameter_dict['lower_bound'] = __default_lower_bound parameter_dict['upper_bound'] = __default_upper_bound else: # Assume that irreversible reactions only proceed from left to # right. parameter_dict['lower_bound'] = 0 parameter_dict['upper_bound'] = __default_upper_bound parameter_dict[ 'objective_coefficient'] = __default_objective_coefficient else: for sbml_parameter in \ sbml_reaction.getKineticLaw().getListOfParameters(): parameter_dict[ sbml_parameter.getId().lower()] = sbml_parameter.getValue() if 'lower_bound' in parameter_dict: reaction.lower_bound = parameter_dict['lower_bound'] elif 'lower bound' in parameter_dict: reaction.lower_bound = parameter_dict['lower bound'] elif sbml_reaction.getReversible(): reaction.lower_bound = __default_lower_bound else: reaction.lower_bound = 0 if 'upper_bound' in parameter_dict: reaction.upper_bound = parameter_dict['upper_bound'] elif 'upper bound' in parameter_dict: reaction.upper_bound = parameter_dict['upper bound'] else: reaction.upper_bound = __default_upper_bound objective_coefficient = parameter_dict.get( 'objective_coefficient', parameter_dict.get( 'objective_coefficient', __default_objective_coefficient)) if objective_coefficient != 0: coefficients[reaction] = objective_coefficient # ensure values are not set to nan or inf if isnan(reaction.lower_bound) or isinf(reaction.lower_bound): reaction.lower_bound = __default_lower_bound if isnan(reaction.upper_bound) or isinf(reaction.upper_bound): reaction.upper_bound = __default_upper_bound reaction_note_dict = parse_legacy_sbml_notes( sbml_reaction.getNotesString()) # Parse the reaction notes. # POTENTIAL BUG: DEALING WITH LEGACY 'SBML' THAT IS NOT IN A # STANDARD FORMAT # TODO: READ IN OTHER NOTES AND GIVE THEM A reaction_ prefix. # TODO: Make sure genes get added as objects if 'GENE ASSOCIATION' in reaction_note_dict: rule = reaction_note_dict['GENE ASSOCIATION'][0] try: rule.encode('ascii') except (UnicodeEncodeError, UnicodeDecodeError): warn("gene_reaction_rule '%s' is not ascii compliant" % rule) if rule.startswith(""") and rule.endswith("""): rule = rule[6:-6] reaction.gene_reaction_rule = rule if 'GENE LIST' in reaction_note_dict: reaction.systematic_names = reaction_note_dict['GENE LIST'][0] elif ('GENES' in reaction_note_dict and reaction_note_dict['GENES'] != ['']): reaction.systematic_names = reaction_note_dict['GENES'][0] elif 'LOCUS' in reaction_note_dict: gene_id_to_object = dict([(x.id, x) for x in reaction._genes]) for the_row in reaction_note_dict['LOCUS']: tmp_row_dict = {} the_row = 'LOCUS:' + the_row.lstrip('_').rstrip('#') for the_item in the_row.split('#'): k, v = the_item.split(':') tmp_row_dict[k] = v tmp_locus_id = tmp_row_dict['LOCUS'] if 'TRANSCRIPT' in tmp_row_dict: tmp_locus_id = tmp_locus_id + \ '.' + tmp_row_dict['TRANSCRIPT'] if 'ABBREVIATION' in tmp_row_dict: gene_id_to_object[tmp_locus_id].name = tmp_row_dict[ 'ABBREVIATION'] if 'SUBSYSTEM' in reaction_note_dict: reaction.subsystem = reaction_note_dict.pop('SUBSYSTEM')[0] reaction.notes = reaction_note_dict # Now, add all of the reactions to the model. cobra_model.id = sbml_model.getId() # Populate the compartment list - This will be done based on # cobra.Metabolites in cobra.Reactions in the future. cobra_model.compartments = compartment_dict cobra_model.add_reactions(cobra_reaction_list) set_objective(cobra_model, coefficients) return cobra_model
def from_mat_struct(mat_struct, model_id=None, inf=inf): """create a model from the COBRA toolbox struct The struct will be a dict read in by scipy.io.loadmat """ m = mat_struct if m.dtype.names is None: raise ValueError("not a valid mat struct") if not {"rxns", "mets", "S", "lb", "ub"} <= set(m.dtype.names): raise ValueError("not a valid mat struct") if "c" in m.dtype.names: c_vec = m["c"][0, 0] else: c_vec = None warn("objective vector 'c' not found") model = Model() if model_id is not None: model.id = model_id elif "description" in m.dtype.names: description = m["description"][0, 0][0] if not isinstance(description, string_types) and len(description) > 1: model.id = description[0] warn("Several IDs detected, only using the first.") else: model.id = description else: model.id = "imported_model" for i, name in enumerate(m["mets"][0, 0]): new_metabolite = Metabolite() new_metabolite.id = str(name[0][0]) if all(var in m.dtype.names for var in ['metComps', 'comps', 'compNames']): comp_index = m["metComps"][0, 0][i][0] - 1 new_metabolite.compartment = m['comps'][0, 0][comp_index][0][0] if new_metabolite.compartment not in model.compartments: comp_name = m['compNames'][0, 0][comp_index][0][0] model.compartments[new_metabolite.compartment] = comp_name else: new_metabolite.compartment = _get_id_compartment(new_metabolite.id) if new_metabolite.compartment not in model.compartments: model.compartments[ new_metabolite.compartment] = new_metabolite.compartment try: new_metabolite.name = str(m["metNames"][0, 0][i][0][0]) except (IndexError, ValueError): pass try: new_metabolite.formula = str(m["metFormulas"][0][0][i][0][0]) except (IndexError, ValueError): pass try: new_metabolite.charge = float(m["metCharge"][0, 0][i][0]) int_charge = int(new_metabolite.charge) if new_metabolite.charge == int_charge: new_metabolite.charge = int_charge except (IndexError, ValueError): pass model.add_metabolites([new_metabolite]) new_reactions = [] coefficients = {} for i, name in enumerate(m["rxns"][0, 0]): new_reaction = Reaction() new_reaction.id = str(name[0][0]) new_reaction.lower_bound = float(m["lb"][0, 0][i][0]) new_reaction.upper_bound = float(m["ub"][0, 0][i][0]) if isinf(new_reaction.lower_bound) and new_reaction.lower_bound < 0: new_reaction.lower_bound = -inf if isinf(new_reaction.upper_bound) and new_reaction.upper_bound > 0: new_reaction.upper_bound = inf if c_vec is not None: coefficients[new_reaction] = float(c_vec[i][0]) try: new_reaction.gene_reaction_rule = str(m['grRules'][0, 0][i][0][0]) except (IndexError, ValueError): pass try: new_reaction.name = str(m["rxnNames"][0, 0][i][0][0]) except (IndexError, ValueError): pass try: new_reaction.subsystem = str(m['subSystems'][0, 0][i][0][0]) except (IndexError, ValueError): pass new_reactions.append(new_reaction) model.add_reactions(new_reactions) set_objective(model, coefficients) coo = scipy_sparse.coo_matrix(m["S"][0, 0]) for i, j, v in zip(coo.row, coo.col, coo.data): model.reactions[j].add_metabolites({model.metabolites[i]: v}) return model
def objective_coefficient(self, value): if self.model is None: raise AttributeError('cannot assign objective to a missing model') if self.flux_expression is not None: set_objective(self.model, {self: value}, additive=True)
def convert_to_irreversible(cobra_model): """ Split reversible reactions into two irreversible reactions: one going in the forward direction, the other in the backward direction. In this manner, all reactions in the model carry non-negative flux values. Forward reactions are tagged as "forward" while backward reactions as "backward". cobra_model: A Model object which will be modified in place. Modified from the deprecated cobrapy version by Semidan Robaina, February 2019. """ reactions_to_add = [] coefficients = {} def onlyBackward(reaction): return reaction.lower_bound < 0 and reaction.upper_bound <= 0 def backwardAndForward(reaction): return reaction.lower_bound < 0 and reaction.upper_bound > 0 def changeDirection(reaction): def swapSign(number): return -number lb = swapSign(reaction.upper_bound) ub = swapSign(reaction.lower_bound) reaction.lower_bound = lb reaction.upper_bound = ub reaction.objective_coefficient * -1 reaction.notes["reflection"] = 'only reverse' reaction.id += '_reverse' reaction.name += '_reverse' for met in reaction._metabolites.keys(): reaction._metabolites[met] *= -1 def createBackwardReaction(reaction): backward_reaction = cobraReaction(reaction.id + '_reverse') backward_reaction.lower_bound = 0 backward_reaction.upper_bound = -reaction.lower_bound reaction_dict = { k: v * -1 for k, v in iteritems(reaction._metabolites) } backward_reaction.add_metabolites(reaction_dict) backward_reaction._model = reaction._model backward_reaction._genes = reaction._genes for gene in reaction._genes: gene._reaction.add(backward_reaction) backward_reaction.subsystem = reaction.subsystem backward_reaction.name = reaction.name + '_reverse' backward_reaction._gene_reaction_rule = reaction._gene_reaction_rule coefficients[backward_reaction] = reaction.objective_coefficient * -1 return backward_reaction for reaction in cobra_model.reactions: if onlyBackward(reaction): changeDirection(reaction) elif backwardAndForward(reaction): backward_reaction = createBackwardReaction(reaction) reactions_to_add.append(backward_reaction) reaction.lower_bound = 0 cobra_model.add_reactions(reactions_to_add) set_objective(cobra_model, coefficients, additive=True)
def min_incon_parsi(self, **kwargs): '''The main step called by compute_met_form to solve the MIP problem. Return a min_incon_parsi_info object summarizing the results of solving MIP. formulae: result formulae infeas: infeasibility of each solve bound: bound used for total inconsistency or the relaxation value eps0 for each solve obj: objective function value for each solve solution: solution values for each type of variables (m, xp, xn, Ap, An) met_model: the MIP problems solved for each connected set of elements as individual cobra models final: the final status of the solution sol_stat: solution status for each connected set of elements sol_constrain: 'minIncon' or 'minFill' indicating the solution used to constrain the minimal formulae problem. ''' inf, neg_inf = self.infinity, self.negative_infinity pre = self.pre metK, metU, metF, rxnK, ele, ele_connect, met_fill_connect, feasTol, digitRounded \ = pre.met_known, pre.met_unknown, pre.met_fill, pre.rxn_known, \ pre.ele, pre.ele_connect, pre.met_fill_connect, pre.feasTol, pre.digitRounded model = self.model kwargs['objective_sense'] = 'minimize' #always minimize #data to be stored infeas, bound, solution, met_model, sol_constrain, sol_stat, obj = ({} for i in range(7)) for k in ['minIncon', 'minFill', 'minForm']: #pre-assignment solution[k] = {'m': {}, 'xp': {}, 'xn': {}, 'Ap': {}, 'An': {}} #Optimize for each connected componenet in ele_connect ct = 0 for eCC in ele_connect: ct += 1 print("Optimizing for %d / %d set of connected elements ... %s" %(ct, len(ele_connect), datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S"))) metModelJ = Model(', '.join(eCC)) metModelJ.solver = self.__solver infeasJ, boundJ, objJ = {}, {}, {} constraint = {e: {j: Metabolite(j.id + ',' + e) for j in rxnK} for e in eCC} m = {e: {i: Reaction('m_' + i.id + ',' + e) for i in metU} for e in eCC} xp = {e: {j: Reaction('xp_' + j.id + ',' + e) for j in rxnK} for e in eCC} xn = {e: {j: Reaction('xn_' + j.id + ',' + e) for j in rxnK} for e in eCC} #add adjustment variable if filling mets associated with the current connected elements Ap = {i: {j: Reaction('Ap_' + metF[i].formula + ',' + j.id) for j in rxnK} for i in met_fill_connect[eCC]} An = {i: {j: Reaction('An_' + metF[i].formula + ',' + j.id) for j in rxnK} for i in met_fill_connect[eCC]} for e in eCC: for j in rxnK: #RHS for each constraint: -sum(S_ij * m^known_ie) (obsolete) constraint[e][j]._bound = -sum([S_ij * metK[i].elements[e] for i, S_ij in j._metabolites.items() if i in metK and e in metK[i].elements]) constraint[e][j]._constraint_sense = 'E' #x_pos - x_neg for each constraint xp[e][j].add_metabolites({constraint[e][j]: 1}) xn[e][j].add_metabolites({constraint[e][j]: -1}) xp[e][j].lower_bound, xn[e][j].lower_bound, xp[e][j].upper_bound, xn[e][j].upper_bound = 0, 0, inf, inf #m * A_pos - m * A_neg for each constraint for i in met_fill_connect[eCC]: if e in metF[i].elements: Ap[i][j].add_metabolites({constraint[e][j]: metF[i].elements[e]}) An[i][j].add_metabolites({constraint[e][j]: -metF[i].elements[e]}) for i in metU: # S_ij x m_ie for each i m[e][i].add_metabolites({constraint[e][j]: j._metabolites[i] for j in list(i._reaction) if j in rxnK}) m[e][i].upper_bound = inf m[e][i].lower_bound = neg_inf if e == 'Charge' else 0 for i in met_fill_connect[eCC]: for j in rxnK: Ap[i][j].lower_bound, An[i][j].lower_bound, Ap[i][j].upper_bound, An[i][j].upper_bound = 0, 0, inf, inf #add reactions into the model metModelJ.add_reactions([m[e][i] for e in eCC for i in metU]) metModelJ.add_reactions([xp[e][j] for e in eCC for j in rxnK]) metModelJ.add_reactions([xn[e][j] for e in eCC for j in rxnK]) if bool(met_fill_connect[eCC]): metModelJ.add_reactions([Ap[i][j] for i in met_fill_connect[eCC] for j in rxnK]) metModelJ.add_reactions([An[i][j] for i in met_fill_connect[eCC] for j in rxnK]) # the objective can be set as a dictionary of rxns {rxn_id: 1,...} objective_dict = {xp[e][j]: 1 for e in eCC for j in rxnK} objective_dict.update({xn[e][j]: 1 for e in eCC for j in rxnK}) set_objective(metModelJ, objective_dict) #set RHS: -sum(S_ij * m^known_ie) for e in eCC: for j in rxnK: metModelJ.constraints[constraint[e][j].id].ub = inf #ub must be set to be > lb to avoid error metModelJ.constraints[constraint[e][j].id].lb, metModelJ.constraints[constraint[e][j].id].ub = constraint[e][j]._bound, constraint[e][j]._bound #Solve for minimum inconsistency sol = metModelJ.optimize(**kwargs) solStatJ = 'minIncon' infeasJ[solStatJ] = solution_infeasibility(metModelJ, sol) if sol.fluxes is None: boundJ[solStatJ] = {e: float('nan') for e in eCC} else: boundJ[solStatJ] = {e: sum([sol.fluxes[j.id] for j in chain(xp[e].values(), xn[e].values())]) for e in eCC} if not infeasJ[solStatJ] <= feasTol: #infeasible (should not happen) infeasJ['minFill'], infeasJ['minForm'] = inf, inf solConstrainJ = 'infeasible' solStatJ = 'infeasible' objJ['minIncon'], objJ['minFill'], objJ['minForm'] = (float('nan') for i in range(3)) else: #Feasible. Store the solution solution[solStatJ]['m'].update({e: {i: sol.fluxes[m[e][i].id] for i in metU} for e in eCC}) solution[solStatJ]['xp'].update({e: {j: sol.fluxes[xp[e][j].id] for j in rxnK} for e in eCC}) solution[solStatJ]['xn'].update({e: {j: sol.fluxes[xn[e][j].id] for j in rxnK} for e in eCC}) solution[solStatJ]['Ap'].update({i: {j: sol.fluxes[Ap[i][j].id] for j in rxnK} for i in met_fill_connect[eCC]}) solution[solStatJ]['An'].update({i: {j: sol.fluxes[An[i][j].id] for j in rxnK} for i in met_fill_connect[eCC]}) objJ[solStatJ] = sol.objective_value # .f deprecated since 2018 Oct #solution used to constrain the minimal formula problem solConstrainJ = 'minIncon' #minimize total adjustment if filling metabolites exist if bool(met_fill_connect[eCC]): #Add constraint to fix the total inconsistency for each element constraint_minIncon = {e: Metabolite('minIncon_'+e) for e in eCC} for e in eCC: #rounding to avoid infeasibility due to numerical issues constraint_minIncon[e]._bound = round(boundJ[solStatJ][e], digitRounded) constraint_minIncon[e]._constraint_sense = 'L' #sum(xp) + sum(xn) <= total inconsistency for j in rxnK: xp[e][j].add_metabolites({constraint_minIncon[e]: 1}) xn[e][j].add_metabolites({constraint_minIncon[e]: 1}) metModelJ.constraints[constraint_minIncon[e].id].lb = neg_inf metModelJ.constraints[constraint_minIncon[e].id].ub = round(boundJ[solStatJ][e], digitRounded) #reset the objective function objective_dict = {Ap[i][j]: 1 for i in met_fill_connect[eCC] for j in rxnK} objective_dict.update({An[i][j]: 1 for i in met_fill_connect[eCC] for j in rxnK}) set_objective(metModelJ, objective_dict) solStatJ = 'minFill' eps0 = 1e-6 while True: sol = metModelJ.optimize(**kwargs) infeasJ[solStatJ] = solution_infeasibility(metModelJ, sol) if infeasJ[solStatJ] <= feasTol or eps0 > 1e-4 + 1e-8: break eps0 *= 10 for e in eCC: #rounding to avoid infeasibility due to numerical issues metModelJ.constraints[constraint_minIncon[e].id].ub = round(boundJ['minIncon'][e] * (1 + eps0), digitRounded) boundJ[solStatJ] = eps0 if infeasJ[solStatJ] <= feasTol: #Feasible. Use this as the solution for constraining the minimal formula problem solConstrainJ = 'minFill' #Store the solution solution[solStatJ]['m'].update({e: {i: sol.fluxes[m[e][i].id] for i in metU} for e in eCC}) solution[solStatJ]['xp'].update({e: {j: sol.fluxes[xp[e][j].id] for j in rxnK} for e in eCC}) solution[solStatJ]['xn'].update({e: {j: sol.fluxes[xn[e][j].id] for j in rxnK} for e in eCC}) solution[solStatJ]['Ap'].update({i: {j: sol.fluxes[Ap[i][j].id] for j in rxnK} for i in met_fill_connect[eCC]}) solution[solStatJ]['An'].update({i: {j: sol.fluxes[An[i][j].id] for j in rxnK} for i in met_fill_connect[eCC]}) objJ[solStatJ] = sol.objective_value else: #infeasible, should not happen objJ[solStatJ] = float('nan') #prepare to compute minimal formulae eps0 = 1e-10 for j in rxnK: for i in met_fill_connect[eCC]: #Fix the adjustment Ap[i][j].lower_bound = round(solution[solConstrainJ]['Ap'][i][j] * (1 - eps0), digitRounded) Ap[i][j].upper_bound = round(solution[solConstrainJ]['Ap'][i][j] * (1 + eps0), digitRounded) An[i][j].lower_bound = round(solution[solConstrainJ]['An'][i][j] * (1 - eps0), digitRounded) An[i][j].upper_bound = round(solution[solConstrainJ]['An'][i][j] * (1 + eps0), digitRounded) for e in eCC: #remove all coefficients on the constraints for total inconsistency tmp = xp[e][j]._metabolites.pop(constraint_minIncon[e]) tmp = xn[e][j]._metabolites.pop(constraint_minIncon[e]) for e in eCC: #then remove the constraints metModelJ.metabolites.remove(constraint_minIncon[e]) else: #no filling metabolites infeasJ['minFill'], boundJ['minFill'] = 0, 0 #compute minimal formulae eps0 = 1e-10 for e in eCC: for j in rxnK: #fix the inconsistency xp[e][j].lower_bound = round(solution[solConstrainJ]['xp'][e][j] * (1 - eps0), digitRounded) xp[e][j].upper_bound = round(solution[solConstrainJ]['xp'][e][j] * (1 + eps0), digitRounded) xn[e][j].lower_bound = round(solution[solConstrainJ]['xn'][e][j] * (1 - eps0), digitRounded) xn[e][j].upper_bound = round(solution[solConstrainJ]['xn'][e][j] * (1 + eps0), digitRounded) if e == 'Charge': #add variables for the positive and negative part of charges chargePos = {i: Reaction('chargePos_' + i.id) for i in metU} chargeNeg = {i: Reaction('chargeNeg_' + i.id) for i in metU} constraint_chargeDecomp = {i: Metabolite('chargeDecomp_' + i.id) for i in metU} for i in metU: #constraint_chargeDecomp[i]: m_charge,i - chargePos_i + chargeNeg_i = 0 m[e][i].add_metabolites({constraint_chargeDecomp[i]: 1}) chargePos[i].add_metabolites({constraint_chargeDecomp[i]: -1}) chargeNeg[i].add_metabolites({constraint_chargeDecomp[i]: 1}) chargePos[i].lower_bound, chargePos[i].upper_bound = 0, inf chargeNeg[i].lower_bound, chargeNeg[i].upper_bound = 0, inf constraint_chargeDecomp[i]._bound, constraint_chargeDecomp[i]._constraint_sense = 0, 'E' metModelJ.add_metabolites(constraint_chargeDecomp.values()) metModelJ.add_reactions(list(chargePos.values()) + list(chargeNeg.values())) #reset the objective function objective_dict = {m[e][i]: 1 for e in eCC if e != 'Charge' for i in metU} if 'Charge' in eCC: objective_dict.update({chargePos[i]: 1 for i in metU}) objective_dict.update({chargeNeg[i]: 1 for i in metU}) set_objective(metModelJ, objective_dict) #Solve for minimum formulae solStatPrev, solStatJ =solStatJ, 'minForm' while True: sol = metModelJ.optimize(**kwargs) infeasJ[solStatJ] = solution_infeasibility(metModelJ, sol) if infeasJ[solStatJ] <= feasTol or eps0 > 1e-5 + 1e-8: break eps0 *= 10 #relax bounds, rounding to avoid infeasibility due to numerical issues for j in rxnK: for i in met_fill_connect[eCC]: Ap[i][j].lower_bound = round(solution[solConstrainJ]['Ap'][i][j] * (1 - eps0), digitRounded) Ap[i][j].upper_bound = round(solution[solConstrainJ]['Ap'][i][j] * (1 + eps0), digitRounded) An[i][j].lower_bound = round(solution[solConstrainJ]['An'][i][j] * (1 - eps0), digitRounded) An[i][j].upper_bound = round(solution[solConstrainJ]['An'][i][j] * (1 + eps0), digitRounded) for e in eCC: xp[e][j].lower_bound = round(solution[solConstrainJ]['xp'][e][j] * (1 - eps0), digitRounded) xp[e][j].upper_bound = round(solution[solConstrainJ]['xp'][e][j] * (1 + eps0), digitRounded) xn[e][j].lower_bound = round(solution[solConstrainJ]['xn'][e][j] * (1 - eps0), digitRounded) xn[e][j].upper_bound = round(solution[solConstrainJ]['xn'][e][j] * (1 + eps0), digitRounded) boundJ[solStatJ] = eps0 if infeasJ[solStatJ] <= feasTol: #Feasible. Store the solution solution[solStatJ]['m'].update({e: {i: sol.fluxes[m[e][i].id] for i in metU} for e in eCC}) solution[solStatJ]['xp'].update({e: {j: sol.fluxes[xp[e][j].id] for j in rxnK} for e in eCC}) solution[solStatJ]['xn'].update({e: {j: sol.fluxes[xn[e][j].id] for j in rxnK} for e in eCC}) solution[solStatJ]['Ap'].update({i: {j: sol.fluxes[Ap[i][j].id] for j in rxnK} for i in met_fill_connect[eCC]}) solution[solStatJ]['An'].update({i: {j: sol.fluxes[An[i][j].id] for j in rxnK} for i in met_fill_connect[eCC]}) objJ[solStatJ] = sol.objective_value else: #infeasible, should not happen solStatJ = solStatPrev objJ[solStatJ] = float('nan') #store data infeas[eCC] = infeasJ bound[eCC] = boundJ obj[eCC] = objJ met_model[eCC] = metModelJ sol_constrain[eCC] = solConstrainJ sol_stat[eCC] = solStatJ #summarize the final solution state if any([k == 'infeasible' for k in sol_stat.values()]): print('Failure: no feasible solution can be found.') solFinal = 'infeasible' else: solMixed = True for stat in ['minForm', 'minFill', 'minIncon']: if all([k == stat for k in sol_stat.values()]): solFinal, solMixed = stat, False break if solMixed: solFinal = 'mixed' #Get the resultant set of formulae. For each set of elements in ele_connect, choose the latest solution (minForm > minFill > minIncon), recorded in sol_stat. if solFinal != 'infeasible': formulae = {i: Formula('Mass0') for i in metU} for i in metU: for eCC in ele_connect: formulae[i].update_elements({e: round(solution[sol_stat[eCC]]['m'][e][i], digitRounded) for e in eCC}) else: formulae = {i: Formula() for i in metU} formulae.update(metK) mip_info = min_incon_parsi_info() mip_info.formulae, mip_info.infeas, mip_info.bound, mip_info.obj, mip_info.solution, \ mip_info.met_model, mip_info.final, mip_info.sol_constrain, mip_info.sol_stat \ = formulae, infeas, bound, obj, solution, met_model, solFinal, sol_constrain, sol_stat return mip_info
def min_incon_parsi_mwRange(self, metInterest, **kwargs): '''The main step called by compute_met_range to find the range for the molecular weight Return min_incon_parsi_info object summarizing the results of solving MIP. formulae: (formula for min MW, formula max MW) mw_range: (min MW, max MW) rhs: {e: {i: RHS[i] for all metabolite i}} the RHS value in the MIP problem for each element solved. (compute_met_range only) infeas: infeasibility of each solve bound: bound used for total inconsistency or the relaxation value eps0 for each solve obj: objective function value for each solve solution: solution values for each type of variables (m, xp, xn) met_model: the MIP problem solved for each element as a cobra model. Same model but different rhs for different elements. final: the final status of the solution sol_stat: solution status for each element/connected set of elements ''' inf, neg_inf = self.infinity, self.negative_infinity pre = self.pre metK, metU, rxnK, ele, feasTol, digitRounded \ = pre.met_known, pre.met_unknown, pre.rxn_known, pre.ele, pre.feasTol, pre.digitRounded model = self.model #handle metInterest if isinstance(metInterest,type(model.metabolites[0])): metI = metInterest elif isinstance(metInterest, str): metI = model.metabolites.get_by_id(metInterest) if metI in metK: print("%s in the input is already known." %metI.id) mip_info = min_incon_parsi_info() mip_info.mw_range = (metK[metI].mw, metK[metI].mw) return mip_info print('Find the range for the molecular weight of %s ... %s' %(metI.id, datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S"))) infeas, bound, solution, sol_stat, obj, rhs = ({} for i in range(6)) for k in ['minIncon', 'minMw', 'maxMw']: #pre-assignment solution[k] = {'m': {}, 'xp': {}, 'xn': {}} ct = 0 #The optimization problem for each element is the same except the RHS met_model = Model('min/max Mw of ' + metI.id) met_model.solver = self.__solver constraint = {j: Metabolite(j.id) for j in rxnK} m = {i: Reaction('m_' + i.id) for i in metU} xp = {j: Reaction('xp_' + j.id) for j in rxnK} xn = {j: Reaction('xn_' + j.id) for j in rxnK} for j in rxnK: xp[j].add_metabolites({constraint[j]: 1}) xn[j].add_metabolites({constraint[j]: -1}) xp[j].lower_bound, xn[j].lower_bound, xp[j].upper_bound, xn[j].upper_bound = 0, 0, inf, inf constraint[j]._constraint_sense = 'E' for i in metU: # S_ij x m_ie for each i m[i].add_metabolites({constraint[j]: j._metabolites[i] for j in list(i._reaction) if j in rxnK}) m[i].upper_bound = inf #Add constraint to fix the total inconsistency for each element constraint_minIncon = Metabolite('minIncon') constraint_minIncon._bound = inf constraint_minIncon._constraint_sense = 'L' for j in rxnK: xp[j].add_metabolites({constraint_minIncon: 1}) xn[j].add_metabolites({constraint_minIncon: 1}) met_model.add_reactions([m[i] for i in metU]) met_model.add_reactions([xp[j] for j in rxnK]) met_model.add_reactions([xn[j] for j in rxnK]) met_model.constraints[constraint_minIncon.id].lb = neg_inf #to avoid error objective_dict_minIncon = {xp[j]: 1 for j in rxnK} objective_dict_minIncon.update({xn[j]: 1 for j in rxnK}) for e in ele: ct += 1 print("Optimizing for %d / %d element ... %s" %(ct, len(ele), datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S"))) # metModelJ = Model('min/max ' + e) # metModelJ.solver = self.__solver infeasJ, boundJ, objJ, rhsJ = {}, {}, {}, {} # constraint = {j: Metabolite(j.id + ',' + e) for j in rxnK} # m = {i: Reaction('m_' + i.id + ',' + e) for i in metU} # xp = {j: Reaction('xp_' + j.id + ',' + e) for j in rxnK} # xn = {j: Reaction('xn_' + j.id + ',' + e) for j in rxnK} # for j in rxnK: #RHS for each constraint: -sum(S_ij * m^known_ie) (obsolete) # constraint[j]._bound = -sum([S_ij * metK[i].elements[e] for i, S_ij in j._metabolites.items() if i in metK and e in metK[i].elements]) # constraint[j]._constraint_sense = 'E' #x_pos - x_neg for each constraint # xp[j].add_metabolites({constraint[j]: 1}) # xn[j].add_metabolites({constraint[j]: -1}) # xp[j].lower_bound, xn[j].lower_bound, xp[j].upper_bound, xn[j].upper_bound = 0, 0, inf, inf for i in metU: # # S_ij x m_ie for each i # m[i].add_metabolites({constraint[j]: j._metabolites[i] for j in list(i._reaction) if j in rxnK}) # m[i].upper_bound = inf m[i].lower_bound = neg_inf if e == 'Charge' else 0 #add reactions into the model # metModelJ.add_reactions([m[i] for i in metU]) # metModelJ.add_reactions([xp[j] for j in rxnK]) # metModelJ.add_reactions([xn[j] for j in rxnK]) #set the objective function # objective_dict = {xp[j]: 1 for j in rxnK} # objective_dict.update({xn[j]: 1 for j in rxnK}) met_model.constraints[constraint_minIncon.id].ub = inf set_objective(met_model, objective_dict_minIncon) #set RHS: -sum(S_ij * m^known_ie) for j in rxnK: met_model.constraints[constraint[j].id].ub = inf #ub must be set to be > lb to avoid error rhsJ[j] = -sum([S_ij * metK[i].elements[e] for i, S_ij in j._metabolites.items() if i in metK and e in metK[i].elements]) met_model.constraints[constraint[j].id].lb, met_model.constraints[constraint[j].id].ub = rhsJ[j], rhsJ[j] constraint[j]._bound = rhsJ[j] #Solve for minimum inconsistency kwargs['objective_sense'] = 'minimize' sol = met_model.optimize(**kwargs) solStatJ = 'minIncon' infeasJ[solStatJ] = solution_infeasibility(met_model, sol) if sol.fluxes is None: boundJ[solStatJ] = float('nan') else: boundJ[solStatJ] = sum([sol.fluxes[j.id] for j in chain(xp.values(), xn.values())]) if not infeasJ[solStatJ] <= feasTol: #infeasible (should not happen) infeasJ['minMw'], infeasJ['maxMw'] = inf, inf solStatJ = 'infeasible' objJ['minIncon'], objJ['minMw'], objJ['maxMw'] = (float('nan') for i in range(3)) for s in ['minIncon','minMw','maxMw']: solution[s]['m'][e] = {i: float('nan') for i in metU} solution[s]['xp'][e] = {j: float('nan') for j in rxnK} solution[s]['xn'][e] = {j: float('nan') for j in rxnK} else: #Feasible. Store the solution solution[solStatJ]['m'][e] = {i: sol.fluxes[m[i].id] for i in metU} solution[solStatJ]['xp'][e] = {j: sol.fluxes[xp[j].id] for j in rxnK} solution[solStatJ]['xn'][e] = {j: sol.fluxes[xn[j].id] for j in rxnK} objJ[solStatJ] = sol.objective_value # #Add constraint to fix the total inconsistency for each element # constraint_minIncon = Metabolite('minIncon_'+e) # constraint_minIncon._bound = round(boundJ['minIncon'], digitRounded) # constraint_minIncon._constraint_sense = 'L' # for j in rxnK: # xp[j].add_metabolites({constraint_minIncon: 1}) # xn[j].add_metabolites({constraint_minIncon: 1}) # met_model.constraints[constraint_minIncon.id].lb = neg_inf #to avoid error met_model.constraints[constraint_minIncon.id].ub = round(boundJ['minIncon'], digitRounded) #reset the objective function to minimize molecular weight objective_dict = {m[metI]: Formula(formula=e).mw} set_objective(met_model, objective_dict) solStatJ = 'minMw' kwargs['objective_sense'] = 'minimize' eps0 = 1e-6 while True: sol = met_model.optimize(**kwargs) infeasJ[solStatJ] = solution_infeasibility(met_model, sol) if infeasJ[solStatJ] <= feasTol or eps0 > 1e-4 + 1e-8: break eps0 *= 10 #rounding to avoid infeasibility due to numerical issues met_model.constraints[constraint_minIncon.id].ub = round(boundJ['minIncon'] * (1 + eps0), digitRounded) boundJ[solStatJ] = eps0 if infeasJ[solStatJ] <= feasTol: #Feasible. Store the solution solution[solStatJ]['m'].update({e: {i: sol.fluxes[m[i].id] for i in metU}}) solution[solStatJ]['xp'].update({e: {j: sol.fluxes[xp[j].id] for j in rxnK}}) solution[solStatJ]['xn'].update({e: {j: sol.fluxes[xn[j].id] for j in rxnK}}) objJ[solStatJ] = sol.objective_value else: #infeasible, should not happen objJ[solStatJ] = float('nan') solution['minMw']['m'][e] = {i: float('nan') for i in metU} solution['minMw']['xp'][e] = {j: float('nan') for j in rxnK} solution['minMw']['xn'][e] = {j: float('nan') for j in rxnK} #maximize molecular weight solStatJ = 'maxMw' #reset the bound for total inconsistency met_model.constraints[constraint_minIncon.id].ub = round(boundJ['minIncon'], digitRounded) kwargs['objective_sense'] = 'maximize' eps0 = 1e-6 while True: sol = met_model.optimize(**kwargs) infeasJ[solStatJ] = solution_infeasibility(met_model, sol) if infeasJ[solStatJ] <= feasTol or eps0 > 1e-4 + 1e-8: break eps0 *= 10 #rounding to avoid infeasibility due to numerical issues met_model.constraints[constraint_minIncon.id].ub = round(boundJ['minIncon'] * (1 + eps0), digitRounded) boundJ[solStatJ] = eps0 if infeasJ[solStatJ] <= feasTol: #Feasible. Store the solution solution[solStatJ]['m'].update({e: {i: sol.fluxes[m[i].id] for i in metU}}) solution[solStatJ]['xp'].update({e: {j: sol.fluxes[xp[j].id] for j in rxnK}}) solution[solStatJ]['xn'].update({e: {j: sol.fluxes[xn[j].id] for j in rxnK}}) objJ[solStatJ] = sol.objective_value else: #infeasible, should not happen objJ[solStatJ] = float('nan') solution['maxMw']['m'][e] = {i: float('nan') for i in metU} solution['maxMw']['xp'][e] = {j: float('nan') for j in rxnK} solution['maxMw']['xn'][e] = {j: float('nan') for j in rxnK} #store data infeas[e] = infeasJ bound[e] = boundJ obj[e] = objJ # met_model[e] = met_model sol_stat[e] = solStatJ rhs[e] = rhsJ #summarize the final solution state if any([k == 'infeasible' for k in sol_stat.values()]): print('Failure: no feasible solution can be found.') solFinal = 'infeasible' else: if all([obj[e]['minMw'] == obj[e]['minMw'] for e in ele]): if all([obj[e]['maxMw'] == obj[e]['maxMw'] for e in ele]): solFinal = 'minMw + maxMw' else: solFinal = 'minMw only' else: if all([obj[e]['maxMw'] == obj[e]['maxMw'] for e in ele]): solFinal = 'maxMw only' else: solFinal = 'minIncon' mip_info = min_incon_parsi_info() mip_info.infeas, mip_info.bound, mip_info.obj, mip_info.solution, \ mip_info.met_model, mip_info.final, mip_info.sol_stat \ = infeas, bound, obj, solution, met_model, solFinal, sol_stat mip_info.rhs = rhs mip_info.mw_range = (sum([obj[e]['minMw'] for e in ele]), sum([obj[e]['maxMw'] for e in ele])) mip_info.formulae = tuple([formula_dict2str({e: solution[s]['m'][e][metI] for e in ele}) for s in ['minMw', 'maxMw']]) return mip_info
def relax_dgo(tmodel, reactions_to_ignore=(), solver=None): """ :param t_tmodel: :type t_tmodel: pytfa.thermo.ThermoModel: :param reactions_to_ignore: Iterable of reactions that should not be relaxed :param solver: solver to use (e.g. 'optlang-glpk', 'optlang-cplex', 'optlang-gurobi' :return: a cobra_model with relaxed bounds on standard Gibbs free energy """ if solver is None: solver = tmodel.solver.interface # Create a copy of the cobra_model on which we will perform the slack addition slack_model = deepcopy(tmodel) slack_model.solver = solver slack_model.name = 'SlackModel ' + tmodel.name slack_model.id = 'SlackModel_' + tmodel.id # Create a copy that will receive the relaxation relaxed_model = deepcopy(tmodel) relaxed_model.solver = solver relaxed_model.name = 'RelaxedModel ' + tmodel.name relaxed_model.id = 'RelaxedModel_' + tmodel.id # Ensure the lazy updates are all done slack_model.repair() relaxed_model.repair() # Do not relax if cobra_model is already optimal # try: # solution = tmodel.optimize() # except SolverError as SE: # status = tmodel.solver.status # tmodel.logger.error(SE) # tmodel.logger.warning('Solver status: {}'.format(status)) # if tmodel.solver.status == OPTIMAL: # raise Exception('Model is already optimal') # Find variables that represent standard Gibbs Energy change my_dgo = relaxed_model.get_variables_of_type(DeltaGstd) # Find constraints that represent negativity of Gibbs Energy change my_neg_dg = slack_model.get_constraints_of_type(NegativeDeltaG) changes = OrderedDict() objective_symbols = [] slack_model.logger.info('Adding slack constraints') hooks = dict() for this_neg_dg in my_neg_dg: # If there is no thermo, or relaxation forbidden, pass if this_neg_dg.id in reactions_to_ignore or this_neg_dg.id not in my_dgo: continue # If there is no thermo, or relaxation forbidden, pass if this_neg_dg.id in reactions_to_ignore or this_neg_dg.id not in my_dgo: continue # Create the negative and positive slack variables neg_slack = slack_model.add_variable(NegSlackVariable, this_neg_dg.reaction, lb=0, ub=BIGM_DG) pos_slack = slack_model.add_variable(PosSlackVariable, this_neg_dg.reaction, lb=0, ub=BIGM_DG) subs_dict = { k: slack_model.variables.get(k.name) for k in this_neg_dg.constraint.variables } # Create the new constraint by adding the slack variables to the # negative delta G constraint (from the initial cobra_model) new_expr = this_neg_dg.constraint.expression.subs(subs_dict) new_expr += (pos_slack - neg_slack) this_reaction = this_neg_dg.reaction # Remove former constraint to override it slack_model.remove_constraint(slack_model._cons_dict[this_neg_dg.name]) # Add the new variant slack_model.add_constraint(NegativeDeltaG, this_reaction, expr=new_expr, lb=0, ub=0) # Update the objective with the new variables objective_symbols += [neg_slack, pos_slack] # objective = chunk_sum(objective_symbols) objective = symbol_sum(objective_symbols) # Change the objective to minimize slack set_objective(slack_model, objective) # Update variables and constraints references slack_model.repair() slack_model.logger.info('Optimizing slack model') # Relax slack_model.objective.direction = 'min' relaxation = slack_model.optimize() # Extract the relaxation values from the solution, by type relaxed_model.logger.info('Extracting relaxation') my_neg_slacks = slack_model.get_variables_of_type(NegSlackVariable) my_pos_slacks = slack_model.get_variables_of_type(PosSlackVariable) neg_slack_values = get_solution_value_for_variables( relaxation, my_neg_slacks) pos_slack_values = get_solution_value_for_variables( relaxation, my_pos_slacks) epsilon = relaxed_model.solver.configuration.tolerances.feasibility for this_reaction in relaxed_model.reactions: # No thermo, or relaxation forbidden if this_reaction.id in reactions_to_ignore or this_reaction.id not in my_dgo: continue # Get the standard delta G variable the_dgo = my_dgo.get_by_id(this_reaction.id) # Get the relaxation dgo_delta_lb = \ neg_slack_values[my_neg_slacks \ .get_by_id(this_reaction.id).name] dgo_delta_ub = \ pos_slack_values[my_pos_slacks \ .get_by_id(this_reaction.id).name] # Apply reaction delta G standard bound change if dgo_delta_lb > 0 or dgo_delta_ub > 0: # Store previous values previous_dgo_lb = the_dgo.variable.lb previous_dgo_ub = the_dgo.variable.ub # Apply change the_dgo.variable.lb -= (dgo_delta_lb + epsilon) the_dgo.variable.ub += (dgo_delta_ub + epsilon) # If needed, store that in a report table changes[this_reaction.id] = [ previous_dgo_lb, previous_dgo_ub, dgo_delta_lb, dgo_delta_ub, the_dgo.variable.lb, the_dgo.variable.ub ] relaxed_model.logger.info('Testing relaxation') relaxed_model.optimize() # Format relaxation relax_table = pd.DataFrame.from_dict(changes, orient='index') relax_table.columns = [ 'lb_in', 'ub_in', 'lb_change', 'ub_change', 'lb_out', 'ub_out' ] relaxed_model.logger.info('\n' + relax_table.__str__()) return relaxed_model, slack_model, relax_table
def parse_xml_into_model(xml, number=float): xml_model = xml.find(ns("sbml:model")) if get_attrib(xml_model, "fbc:strict") != "true": warn('loading SBML model without fbc:strict="true"') model_id = get_attrib(xml_model, "id") model = Model(model_id) model.name = xml_model.get("name") model.compartments = {c.get("id"): c.get("name") for c in xml_model.findall(COMPARTMENT_XPATH)} # add metabolites for species in xml_model.findall(SPECIES_XPATH % 'false'): met = get_attrib(species, "id", require=True) met = Metabolite(clip(met, "M_")) met.name = species.get("name") annotate_cobra_from_sbml(met, species) met.compartment = species.get("compartment") met.charge = get_attrib(species, "fbc:charge", int) met.formula = get_attrib(species, "fbc:chemicalFormula") model.add_metabolites([met]) # Detect boundary metabolites - In case they have been mistakenly # added. They should not actually appear in a model boundary_metabolites = {clip(i.get("id"), "M_") for i in xml_model.findall(SPECIES_XPATH % 'true')} # add genes for sbml_gene in xml_model.iterfind(GENES_XPATH): gene_id = get_attrib(sbml_gene, "fbc:id").replace(SBML_DOT, ".") gene = Gene(clip(gene_id, "G_")) gene.name = get_attrib(sbml_gene, "fbc:name") if gene.name is None: gene.name = get_attrib(sbml_gene, "fbc:label") annotate_cobra_from_sbml(gene, sbml_gene) model.genes.append(gene) def process_gpr(sub_xml): """recursively convert gpr xml to a gpr string""" if sub_xml.tag == OR_TAG: return "( " + ' or '.join(process_gpr(i) for i in sub_xml) + " )" elif sub_xml.tag == AND_TAG: return "( " + ' and '.join(process_gpr(i) for i in sub_xml) + " )" elif sub_xml.tag == GENEREF_TAG: gene_id = get_attrib(sub_xml, "fbc:geneProduct", require=True) return clip(gene_id, "G_") else: raise Exception("unsupported tag " + sub_xml.tag) bounds = {bound.get("id"): get_attrib(bound, "value", type=number) for bound in xml_model.iterfind(BOUND_XPATH)} # add reactions reactions = [] for sbml_reaction in xml_model.iterfind( ns("sbml:listOfReactions/sbml:reaction")): reaction = get_attrib(sbml_reaction, "id", require=True) reaction = Reaction(clip(reaction, "R_")) reaction.name = sbml_reaction.get("name") annotate_cobra_from_sbml(reaction, sbml_reaction) lb_id = get_attrib(sbml_reaction, "fbc:lowerFluxBound", require=True) ub_id = get_attrib(sbml_reaction, "fbc:upperFluxBound", require=True) try: reaction.upper_bound = bounds[ub_id] reaction.lower_bound = bounds[lb_id] except KeyError as e: raise CobraSBMLError("No constant bound with id '%s'" % str(e)) reactions.append(reaction) stoichiometry = defaultdict(lambda: 0) for species_reference in sbml_reaction.findall( ns("sbml:listOfReactants/sbml:speciesReference")): met_name = clip(species_reference.get("species"), "M_") stoichiometry[met_name] -= \ number(species_reference.get("stoichiometry")) for species_reference in sbml_reaction.findall( ns("sbml:listOfProducts/sbml:speciesReference")): met_name = clip(species_reference.get("species"), "M_") stoichiometry[met_name] += \ get_attrib(species_reference, "stoichiometry", type=number, require=True) # needs to have keys of metabolite objects, not ids object_stoichiometry = {} for met_id in stoichiometry: if met_id in boundary_metabolites: warn("Boundary metabolite '%s' used in reaction '%s'" % (met_id, reaction.id)) continue try: metabolite = model.metabolites.get_by_id(met_id) except KeyError: warn("ignoring unknown metabolite '%s' in reaction %s" % (met_id, reaction.id)) continue object_stoichiometry[metabolite] = stoichiometry[met_id] reaction.add_metabolites(object_stoichiometry) # set gene reaction rule gpr_xml = sbml_reaction.find(GPR_TAG) if gpr_xml is not None and len(gpr_xml) != 1: warn("ignoring invalid geneAssociation for " + repr(reaction)) gpr_xml = None gpr = process_gpr(gpr_xml[0]) if gpr_xml is not None else '' # remove outside parenthesis, if any if gpr.startswith("(") and gpr.endswith(")"): gpr = gpr[1:-1].strip() gpr = gpr.replace(SBML_DOT, ".") reaction.gene_reaction_rule = gpr try: model.add_reactions(reactions) except ValueError as e: warn(str(e)) # objective coefficients are handled after all reactions are added obj_list = xml_model.find(ns("fbc:listOfObjectives")) if obj_list is None: warn("listOfObjectives element not found") return model target_objective_id = get_attrib(obj_list, "fbc:activeObjective") target_objective = obj_list.find( ns("fbc:objective[@fbc:id='{}']".format(target_objective_id))) obj_direction_long = get_attrib(target_objective, "fbc:type") obj_direction = LONG_SHORT_DIRECTION[obj_direction_long] obj_query = OBJECTIVES_XPATH % target_objective_id coefficients = {} for sbml_objective in obj_list.findall(obj_query): rxn_id = clip(get_attrib(sbml_objective, "fbc:reaction"), "R_") try: objective_reaction = model.reactions.get_by_id(rxn_id) except KeyError: raise CobraSBMLError("Objective reaction '%s' not found" % rxn_id) try: coefficients[objective_reaction] = get_attrib( sbml_objective, "fbc:coefficient", type=number) except ValueError as e: warn(str(e)) set_objective(model, coefficients) model.solver.objective.direction = obj_direction return model
def relax_lc(tmodel, metabolites_to_ignore=(), solver=None): """ :param metabolites_to_ignore: :param in_tmodel: :type in_tmodel: pytfa.thermo.ThermoModel: :param min_objective_value: :return: """ if solver is None: solver = tmodel.solver # Create a copy of the cobra_model on which we will perform the slack addition slack_model = deepcopy(tmodel) slack_model.solver = solver # Create a copy that will receive the relaxation relaxed_model = deepcopy(tmodel) relaxed_model.solver = solver # Do not relax if cobra_model is already optimal try: solution = tmodel.optimize() except SolverError as SE: status = tmodel.solver.status tmodel.logger.error(SE) tmodel.logger.warning('Solver status: {}'.format(status)) # if tmodel.solver.status == OPTIMAL: # raise Exception('Model is already optimal') # Find variables that represent standard Gibbs Energy change my_lc = relaxed_model.get_variables_of_type(LogConcentration) # Find constraints that represent negativity of Gibbs Energy change my_neg_dg = slack_model.get_constraints_of_type(NegativeDeltaG) changes = OrderedDict() objective = 0 pos_slack = dict() neg_slack = dict() for this_lc in my_lc: if this_lc.name in metabolites_to_ignore: continue neg_slack[this_lc.name] = slack_model.add_variable(NegSlackLC, this_lc, lb=0, ub=BIGM_DG) pos_slack[this_lc.name] = slack_model.add_variable(PosSlackLC, this_lc, lb=0, ub=BIGM_DG) # Update the objective with the new variables objective += (neg_slack[this_lc.name] + pos_slack[this_lc.name]) for this_neg_dg in my_neg_dg: # If there is no thermo, or relaxation forbidden, pass if this_neg_dg.id not in my_neg_dg: continue subs_dict = {k: slack_model.variables.get(k.name) \ for k in this_neg_dg.constraint.variables} # Create the new constraint by adding the slack variables to the # negative delta G constraint (from the initial cobra_model) new_expr = this_neg_dg.constraint.expression.subs(subs_dict) for this_var in this_neg_dg.constraint.variables: if not this_var.name in neg_slack: continue met_id = pos_slack[this_var.name].id the_met = slack_model.metabolites.get_by_id(met_id) stoich = this_neg_dg.reaction.metabolites[the_met] new_expr += slack_model.RT * stoich \ * \ (pos_slack[this_var.name] - neg_slack[this_var.name]) # Remove former constraint to override it slack_model.remove_constraint(this_neg_dg) # Add the new variant slack_model.add_constraint(NegativeDeltaG, this_neg_dg.reaction, expr=new_expr, lb=0, ub=0) # Change the objective to minimize slack set_objective(slack_model, objective) # Update variables and constraints references slack_model.repair() # Relax slack_model.objective.direction = 'min' relaxation = slack_model.optimize() # Extract the relaxation values from the solution, by type my_neg_slacks = slack_model.get_variables_of_type(NegSlackLC) my_pos_slacks = slack_model.get_variables_of_type(PosSlackLC) neg_slack_values = get_solution_value_for_variables( relaxation, my_neg_slacks) pos_slack_values = get_solution_value_for_variables( relaxation, my_pos_slacks) epsilon = relaxed_model.solver.configuration.tolerances.feasibility for this_met in relaxed_model.metabolites: # No thermo, or relaxation forbidden if this_met.id in metabolites_to_ignore or this_met.id not in my_lc: continue # Get the standard delta G variable the_lc = my_lc.get_by_id(this_met.id) # Get the relaxation lc_delta_lb = neg_slack_values[ my_neg_slacks \ .get_by_id(this_met.id).name] lc_delta_ub = \ pos_slack_values[my_pos_slacks \ .get_by_id(this_met.id).name] # Apply reaction delta G standard bound change if lc_delta_lb > 0 or lc_delta_ub > 0: # Store previous values previous_lc_lb = the_lc.variable.lb previous_lc_ub = the_lc.variable.ub # Apply change the_lc.variable.lb -= (lc_delta_lb + epsilon) the_lc.variable.ub += (lc_delta_ub + epsilon) # If needed, store that in a report table changes[this_met.id] = [ previous_lc_lb, previous_lc_ub, lc_delta_lb, lc_delta_ub, the_lc.variable.lb, the_lc.variable.ub ] # Obtain relaxation relaxed_model.optimize() # Format relaxation relax_table = pd.DataFrame.from_dict(changes, orient='index') relax_table.columns = [ 'lb_in', 'ub_in', 'lb_change', 'ub_change', 'lb_out', 'ub_out' ] tmodel.logger.info('\n' + relax_table.__str__()) relaxed_model.relaxation = relax_table return relaxed_model, slack_model, relax_table
def objective(self, value): if isinstance(value, sympy.Basic): value = self.problem.Objective(value, sloppy=False) if not isinstance(value, (dict, optlang.interface.Objective)): value = {rxn: 1 for rxn in self.reactions.get_by_any(value)} set_objective(self, value, additive=False)
def relax_dgo(tmodel, reactions_to_ignore=(), solver=None, in_place=False): """ :param t_tmodel: :type t_tmodel: pytfa.thermo.ThermoModel: :param reactions_to_ignore: Iterable of reactions that should not be relaxed :param solver: solver to use (e.g. 'optlang-glpk', 'optlang-cplex', 'optlang-gurobi' :return: a cobra_model with relaxed bounds on standard Gibbs free energy """ if solver is None: solver = tmodel.solver.interface # Create a copy of the cobra_model on which we will perform the slack addition slack_model = deepcopy(tmodel) slack_model.solver = solver slack_model.name = 'SlackModel ' + str(tmodel.name) slack_model.id = 'SlackModel_' + tmodel.id # Ensure the lazy updates are all done slack_model.repair() if not in_place: # Create a copy that will receive the relaxation relaxed_model = deepcopy(tmodel) relaxed_model.solver = solver relaxed_model.name = 'RelaxedModel ' + str(tmodel.name) relaxed_model.id = 'RelaxedModel_' + tmodel.id relaxed_model.repair() else: relaxed_model = slack_model dg_relax_config(slack_model) original_objective = relaxed_model.objective # Do not relax if cobra_model is already optimal # try: # solution = tmodel.optimize() # except SolverError as SE: # status = tmodel.solver.status # tmodel.logger.error(SE) # tmodel.logger.warning('Solver status: {}'.format(status)) # if tmodel.solver.status == OPTIMAL: # raise Exception('Model is already optimal') # Find variables that represent standard Gibbs Energy change my_dgo = relaxed_model.get_variables_of_type(DeltaGstd) # Find constraints that represent negativity of Gibbs Energy change my_neg_dg = slack_model.get_constraints_of_type(NegativeDeltaG) changes = OrderedDict() objective_symbols = [] slack_model.logger.info('Adding slack constraints') slack_model.solver.update() for this_neg_dg in tqdm(my_neg_dg, desc='adding slacks'): # If there is no thermo, or relaxation forbidden, pass if this_neg_dg.id in reactions_to_ignore or this_neg_dg.id not in my_dgo: continue # Create the negative and positive slack variables # We can't queue them because they will be in an expression to declare # the constraint neg_slack = slack_model.add_variable(NegSlackVariable, this_neg_dg.reaction, lb=0, ub=BIGM_DG, queue=False) pos_slack = slack_model.add_variable(PosSlackVariable, this_neg_dg.reaction, lb=0, ub=BIGM_DG, queue=False) # Create the new constraint by adding the slack variables to the # negative delta G constraint (from the initial cobra_model) new_expr = this_neg_dg.constraint.expression new_expr += (pos_slack - neg_slack) this_reaction = this_neg_dg.reaction # # Remove former constraint to override it # slack_model.remove_constraint(slack_model._cons_dict[this_neg_dg.name]) # # # Add the new variant # slack_model.add_constraint(NegativeDeltaG, # this_reaction, # expr=new_expr, # lb=0, # ub=0, # queue=True) this_neg_dg.change_expr(new_expr) # Update the objective with the new variables objective_symbols += [neg_slack, pos_slack] # objective = chunk_sum(objective_symbols) objective = symbol_sum(objective_symbols) # Change the objective to minimize slack set_objective(slack_model, objective) # Update variables and constraints references slack_model.repair() slack_model.logger.info('Optimizing slack model') # Relax slack_model.objective.direction = 'min' relaxation = slack_model.optimize() # Extract the relaxation values from the solution, by type relaxed_model.logger.info('Extracting relaxation') my_neg_slacks = slack_model.get_variables_of_type(NegSlackVariable) my_pos_slacks = slack_model.get_variables_of_type(PosSlackVariable) neg_slack_values = get_solution_value_for_variables( relaxation, my_neg_slacks) pos_slack_values = get_solution_value_for_variables( relaxation, my_pos_slacks) epsilon = relaxed_model.solver.configuration.tolerances.feasibility relaxed_model.repair() relaxed_model.solver.update() # Apply reaction delta G standard bound change for this_reaction in tqdm(relaxed_model.reactions, desc='applying slack'): # No thermo, or relaxation forbidden if this_reaction.id in reactions_to_ignore or this_reaction.id not in my_dgo: continue # Get the standard delta G variable the_dgo = my_dgo.get_by_id(this_reaction.id) # Get the relaxation dgo_delta_lb = \ neg_slack_values[my_neg_slacks \ .get_by_id(this_reaction.id).name] dgo_delta_ub = \ pos_slack_values[my_pos_slacks \ .get_by_id(this_reaction.id).name] if in_place: the_neg_slack = my_neg_slacks.get_by_id(this_reaction.id) the_neg_slack_value = slack_model.solution.raw[the_neg_slack.name] the_neg_slack.variable.lb = the_neg_slack_value - epsilon the_neg_slack.variable.ub = the_neg_slack_value + epsilon the_pos_slack = my_pos_slacks.get_by_id(this_reaction.id) the_pos_slack_value = slack_model.solution.raw[the_pos_slack.name] the_pos_slack.variable.lb = the_pos_slack_value - epsilon the_pos_slack.variable.ub = the_pos_slack_value + epsilon # Apply reaction delta G standard bound change if dgo_delta_lb > 0 or dgo_delta_ub > 0: # Store previous values previous_dgo_lb = the_dgo.variable.lb previous_dgo_ub = the_dgo.variable.ub if not in_place: # Apply change the_dgo.variable.lb -= (dgo_delta_lb + epsilon) the_dgo.variable.ub += (dgo_delta_ub + epsilon) # If needed, store that in a report table changes[this_reaction.id] = [ previous_dgo_lb, previous_dgo_ub, dgo_delta_lb, dgo_delta_ub, the_dgo.variable.lb, the_dgo.variable.ub ] relaxed_model.repair() relaxed_model.logger.info('Testing relaxation') relaxed_model.objective = original_objective relaxed_model.objective.direction = 'max' relaxed_model.optimize() if len(changes) == 0: # The model is infeasible or something went wrong tmodel.logger.error('Relaxation could not complete ' '(no DeltaG relaxation found)') return relaxed_model, slack_model, None # Format relaxation relax_table = pd.DataFrame.from_dict(changes, orient='index') relax_table.columns = [ 'lb_in', 'ub_in', 'lb_change', 'ub_change', 'lb_out', 'ub_out' ] return relaxed_model, slack_model, relax_table
def objective_coefficient(self, value): if self.model is None: raise AttributeError("cannot assign objective to a missing model") if self.flux_expression is not None: set_objective(self.model, {self: value}, additive=True)
def read_sbml_ec_model( filename: str, number: float = float, # re.Pattern does not exist in py36 so this type hint cannot be added now f_replace=F_REPLACE, set_missing_bounds: bool = False, hardcoded_rev_reactions: bool = True, **kwargs, ) -> Model: """Create `geckopy.Model` from SBMLDocument. Parameters ---------- filename: str number: data type of stoichiometry: {float, int} In which data type should the stoichiometry be parsed. f_replace : dict of replacement functions for id replacement set_missing_bounds : flag to set missing bounds hardcoded_rev_reactions: bool if reversible reaction to account for proteins being consumed on both directions are written explicitly for in the SBML Returns ------- cobra.core.Model """ try: fsanitized = str(filename) if isinstance(filename, Path) else filename doc = _get_doc_from_filename(fsanitized) except IOError as e: raise e except Exception as original_error: raise CobraSBMLError( "Something went wrong reading the SBML model. Most likely the SBML" " model is not valid. Please check that your model is valid using " "the `cobra.io.sbml.validate_sbml_model` function or via the " "online validator at http://sbml.org/validator .\n" "\t`(model, errors) = validate_sbml_model(filename)`" "\nIf the model is valid and cannot be read please open an issue " f"at https://github.com/opencobra/cobrapy/issues: {original_error}" ) if f_replace is None: f_replace = {} # SBML model model: libsbml.Model = doc.getModel() if model is None: raise CobraSBMLError("No SBML model detected in file.") model_fbc: libsbml.FbcModelPlugin = model.getPlugin("fbc") if not model_fbc: LOGGER.warning("Model does not contain SBML fbc package information.") else: if not model_fbc.isSetStrict(): LOGGER.warning('Loading SBML model without fbc:strict="true"') # fbc-v1 (legacy) doc_fbc = doc.getPlugin("fbc") # type: libsbml.FbcSBMLDocumentPlugin fbc_version = doc_fbc.getPackageVersion() if fbc_version == 1: LOGGER.warning("Loading SBML with fbc-v1 (models should be encoded" " using fbc-v2)") conversion_properties = libsbml.ConversionProperties() conversion_properties.addOption("convert fbc v1 to fbc v2", True, "Convert FBC-v1 model to FBC-v2") result = doc.convert(conversion_properties) if result != libsbml.LIBSBML_OPERATION_SUCCESS: raise Exception("Conversion of SBML fbc v1 to fbc v2 failed") # Model model_id = model.getIdAttribute() if not libsbml.SyntaxChecker.isValidSBMLSId(model_id): LOGGER.error("'%s' is not a valid SBML 'SId'." % model_id) geckopy_model = Model(model_id, hardcoded_rev_reactions=hardcoded_rev_reactions) geckopy_model.name = model.getName() # meta information meta = { "model.id": model_id, "level": model.getLevel(), "version": model.getVersion(), "packages": [], } # History creators = [] created = None if model.isSetModelHistory(): history = model.getModelHistory() # type: libsbml.ModelHistory if history.isSetCreatedDate(): created = history.getCreatedDate() for c in history.getListCreators(): # type: libsbml.ModelCreator creators.append({ "familyName": c.getFamilyName() if c.isSetFamilyName() else None, "givenName": c.getGivenName() if c.isSetGivenName() else None, "organisation": c.getOrganisation() if c.isSetOrganisation() else None, "email": c.getEmail() if c.isSetEmail() else None, }) meta["creators"] = creators meta["created"] = created meta["notes"] = _parse_notes_dict(doc) meta["annotation"] = _parse_annotations(doc) info = "<{}> SBML L{}V{}".format(model_id, model.getLevel(), model.getVersion()) packages = {} for k in range(doc.getNumPlugins()): plugin = doc.getPlugin(k) # type:libsbml.SBasePlugin key, value = plugin.getPackageName(), plugin.getPackageVersion() packages[key] = value info += ", {}-v{}".format(key, value) if key not in ["fbc", "groups", "l3v2extendedmath"]: LOGGER.warning( "SBML package '%s' not supported by cobrapy, " "information is not parsed", key, ) meta["info"] = info meta["packages"] = packages geckopy_model._sbml = meta # notes and annotations geckopy_model.notes = _parse_notes_dict(model) geckopy_model.annotation = _parse_annotations(model) # Compartments # FIXME: update with new compartments compartments = {} for (compartment) in model.getListOfCompartments( ): # noqa: E501 type: libsbml.Compartment cid = _check_required(compartment, compartment.getIdAttribute(), "id") compartments[cid] = compartment.getName() geckopy_model.compartments = compartments # Species metabolites = [] # proteins that rely on the naming convention "prot_UNIPROT_ID" will be # catched here. Those who are annotated by groups membership will be parsed # after the groups are processed. proteins = [] boundary_metabolites = [] if model.getNumSpecies() == 0: LOGGER.warning("No metabolites in model") for specie in model.getListOfSpecies(): # type: libsbml.Species sid = _check_required(specie, specie.getIdAttribute(), "id") if f_replace and F_SPECIE in f_replace: sid = f_replace[F_SPECIE](sid) met = Metabolite(sid) met.name = specie.getName() met.notes = _parse_notes_dict(specie) met.annotation = _parse_annotations(specie) met.compartment = specie.getCompartment() initial_amount = specie.getInitialAmount() specie_fbc = specie.getPlugin("fbc") # type: libsbml.FbcSpeciesPlugin if specie_fbc: met.charge = specie_fbc.getCharge() met.formula = specie_fbc.getChemicalFormula() else: if specie.isSetCharge(): LOGGER.warning( "Use of the species charge attribute is " "discouraged, use fbc:charge " "instead: %s", specie, ) met.charge = specie.getCharge() else: if "CHARGE" in met.notes: LOGGER.warning( "Use of CHARGE in the notes element is " "discouraged, use fbc:charge " "instead: %s", specie, ) try: met.charge = int(met.notes["CHARGE"]) except ValueError: # handle nan, na, NA, ... pass if "FORMULA" in met.notes: LOGGER.warning( "Use of FORMULA in the notes element is " "discouraged, use fbc:chemicalFormula " "instead: %s", specie, ) met.formula = met.notes["FORMULA"] # Detect boundary metabolites if specie.getBoundaryCondition() is True: boundary_metabolites.append(met) if not PROT_PATTERN.match(met.id): metabolites.append(met) else: proteins.append(Protein(met, concentration=initial_amount)) geckopy_model.add_metabolites(metabolites) geckopy_model.add_proteins(proteins) # Add exchange reactions for boundary metabolites ex_reactions = [] for met in boundary_metabolites: ex_rid = "EX_{}".format(met.id) ex_reaction = Reaction(ex_rid) ex_reaction.name = ex_rid ex_reaction.annotation = {"sbo": SBO_EXCHANGE_REACTION} ex_reaction.lower_bound = config.lower_bound ex_reaction.upper_bound = config.upper_bound LOGGER.warning("Adding exchange reaction %s with default bounds " "for boundary metabolite: %s." % (ex_reaction.id, met.id)) # species is reactant ex_reaction.add_metabolites({met: -1}) ex_reactions.append(ex_reaction) geckopy_model.add_reactions(ex_reactions) # Genes if model_fbc: for (gp) in model_fbc.getListOfGeneProducts( ): # noqa: E501 type: libsbml.GeneProduct gid = _check_required(gp, gp.getIdAttribute(), "id") if f_replace and F_GENE in f_replace: gid = f_replace[F_GENE](gid) cobra_gene = Gene(gid) cobra_gene.name = gp.getName() if cobra_gene.name is None: cobra_gene.name = gid cobra_gene.annotation = _parse_annotations(gp) cobra_gene.notes = _parse_notes_dict(gp) geckopy_model.genes.append(cobra_gene) else: for (cobra_reaction) in model.getListOfReactions( ): # noqa: E501 type: libsbml.Reaction # fallback to notes information notes = _parse_notes_dict(cobra_reaction) if "GENE ASSOCIATION" in notes: gpr = notes["GENE ASSOCIATION"] elif "GENE_ASSOCIATION" in notes: gpr = notes["GENE_ASSOCIATION"] else: gpr = "" if len(gpr) > 0: gpr = gpr.replace("(", ";") gpr = gpr.replace(")", ";") gpr = gpr.replace("or", ";") gpr = gpr.replace("and", ";") # Interaction of the above replacements can lead to multiple # ;, which results in empty gids gids = [t.strip() for t in gpr.split(";")] gids = set(gids).difference({""}) # create missing genes for gid in gids: if f_replace and F_GENE in f_replace: gid = f_replace[F_GENE](gid) if gid not in geckopy_model.genes: cobra_gene = Gene(gid) cobra_gene.name = gid geckopy_model.genes.append(cobra_gene) # GPR rules def process_association(ass): """Recursively convert gpr association to a gpr string. Defined as inline functions to not pass the replacement dict around. """ if ass.isFbcOr(): return " ".join([ "(", " or ".join( process_association(c) for c in ass.getListOfAssociations()), ")", ]) elif ass.isFbcAnd(): return " ".join([ "(", " and ".join( process_association(c) for c in ass.getListOfAssociations()), ")", ]) elif ass.isGeneProductRef(): gid = ass.getGeneProduct() if f_replace and F_GENE in f_replace: return f_replace[F_GENE](gid) else: return gid # Reactions missing_bounds = False reactions = [] if model.getNumReactions() == 0: LOGGER.warning("No reactions in model") for reaction in model.getListOfReactions(): # type: libsbml.Reaction rid = _check_required(reaction, reaction.getIdAttribute(), "id") # proteins are parsed based on Species, prot exchanges are ignored if PROT_EX_PATTERN.search(rid): continue if f_replace and F_REACTION in f_replace: rid = f_replace[F_REACTION](rid) cobra_reaction = Reaction(rid) cobra_reaction.name = reaction.getName() cobra_reaction.annotation = _parse_annotations(reaction) cobra_reaction.notes = _parse_notes_dict(reaction) # set bounds p_ub, p_lb = None, None r_fbc = reaction.getPlugin("fbc") # type: libsbml.FbcReactionPlugin if r_fbc: # bounds in fbc lb_id = r_fbc.getLowerFluxBound() if lb_id: p_lb = model.getParameter(lb_id) # type: libsbml.Parameter if p_lb and p_lb.getConstant() and (p_lb.getValue() is not None): cobra_reaction.lower_bound = p_lb.getValue() else: raise CobraSBMLError("No constant bound '%s' for " "reaction: %s" % (p_lb, reaction)) ub_id = r_fbc.getUpperFluxBound() if ub_id: p_ub = model.getParameter(ub_id) # type: libsbml.Parameter if p_ub and p_ub.getConstant() and (p_ub.getValue() is not None): cobra_reaction.upper_bound = p_ub.getValue() else: raise CobraSBMLError("No constant bound '%s' for " "reaction: %s" % (p_ub, reaction)) elif reaction.isSetKineticLaw(): # some legacy models encode bounds in kinetic laws klaw = reaction.getKineticLaw() # type: libsbml.KineticLaw p_lb = klaw.getParameter( "LOWER_BOUND") # noqa: E501 type: libsbml.LocalParameter if p_lb: cobra_reaction.lower_bound = p_lb.getValue() p_ub = klaw.getParameter( "UPPER_BOUND") # noqa: E501 type: libsbml.LocalParameter if p_ub: cobra_reaction.upper_bound = p_ub.getValue() if p_ub is not None or p_lb is not None: LOGGER.warning( "Encoding LOWER_BOUND and UPPER_BOUND in " "KineticLaw is discouraged, " "use fbc:fluxBounds instead: %s", reaction, ) if p_lb is None: missing_bounds = True lower_bound = config.lower_bound cobra_reaction.lower_bound = lower_bound LOGGER.warning( "Missing lower flux bound set to '%s' for " " reaction: '%s'", lower_bound, reaction, ) if p_ub is None: missing_bounds = True upper_bound = config.upper_bound cobra_reaction.upper_bound = upper_bound LOGGER.warning( "Missing upper flux bound set to '%s' for " " reaction: '%s'", upper_bound, reaction, ) # add reaction reactions.append(cobra_reaction) # parse equation stoichiometry = defaultdict(lambda: 0) for (sref) in reaction.getListOfReactants( ): # noqa: E501 type: libsbml.SpeciesReference sid = _check_required(sref, sref.getSpecies(), "species") if f_replace and F_SPECIE in f_replace: sid = f_replace[F_SPECIE](sid) stoichiometry[sid] -= number( _check_required(sref, sref.getStoichiometry(), "stoichiometry")) for (sref) in reaction.getListOfProducts( ): # noqa: E501 type: libsbml.SpeciesReference sid = _check_required(sref, sref.getSpecies(), "species") if f_replace and F_SPECIE in f_replace: sid = f_replace[F_SPECIE](sid) stoichiometry[sid] += number( _check_required(sref, sref.getStoichiometry(), "stoichiometry")) # convert to metabolite objects object_stoichiometry = {} for met_id in stoichiometry: target_set = (geckopy_model.proteins if met_id in geckopy_model.proteins else geckopy_model.metabolites) metabolite = target_set.get_by_id(met_id) object_stoichiometry[metabolite] = stoichiometry[met_id] cobra_reaction.add_metabolites(object_stoichiometry) # GPR if r_fbc: gpr = "" gpa = (r_fbc.getGeneProductAssociation() ) # noqa: E501 type: libsbml.GeneProductAssociation if gpa is not None: association = (gpa.getAssociation() ) # noqa: E501 type: libsbml.FbcAssociation gpr = process_association(association) else: # fallback to notes information notes = cobra_reaction.notes if "GENE ASSOCIATION" in notes: gpr = notes["GENE ASSOCIATION"] elif "GENE_ASSOCIATION" in notes: gpr = notes["GENE_ASSOCIATION"] else: gpr = "" if len(gpr) > 0: LOGGER.warning( "Use of GENE ASSOCIATION or GENE_ASSOCIATION " "in the notes element is discouraged, use " "fbc:gpr instead: %s", reaction, ) if f_replace and F_GENE in f_replace: gpr = " ".join(f_replace[F_GENE](t) for t in gpr.split(" ")) # remove outside parenthesis, if any if gpr.startswith("(") and gpr.endswith(")"): try: parse_gpr(gpr[1:-1].strip()) gpr = gpr[1:-1].strip() except (SyntaxError, TypeError) as e: LOGGER.warning( f"Removing parenthesis from gpr {gpr} leads to " f"an error, so keeping parenthesis, error: {e}", ) cobra_reaction.gene_reaction_rule = gpr geckopy_model.add_reactions(reactions) # Objective obj_direction = "max" coefficients = {} if model_fbc: obj_list = (model_fbc.getListOfObjectives() ) # noqa: E501 type: libsbml.ListOfObjectives if obj_list is None: LOGGER.warning("listOfObjectives element not found") elif obj_list.size() == 0: LOGGER.warning("No objective in listOfObjectives") elif not obj_list.getActiveObjective(): LOGGER.warning("No active objective in listOfObjectives") else: obj_id = obj_list.getActiveObjective() obj = model_fbc.getObjective(obj_id) # type: libsbml.Objective obj_direction = LONG_SHORT_DIRECTION[obj.getType()] for (flux_obj) in (obj.getListOfFluxObjectives() ): # noqa: E501 type: libsbml.FluxObjective rid = flux_obj.getReaction() if f_replace and F_REACTION in f_replace: rid = f_replace[F_REACTION](rid) try: objective_reaction = geckopy_model.reactions.get_by_id(rid) except KeyError: raise CobraSBMLError("Objective reaction '%s' " "not found" % rid) try: coefficients[objective_reaction] = number( flux_obj.getCoefficient()) except ValueError as e: LOGGER.warning(str(e)) else: # some legacy models encode objective coefficients in kinetic laws for reaction in model.getListOfReactions(): # type: libsbml.Reaction if reaction.isSetKineticLaw(): klaw = reaction.getKineticLaw() # type: libsbml.KineticLaw p_oc = klaw.getParameter( "OBJECTIVE_COEFFICIENT") # type: libsbml.LocalParameter if p_oc: rid = _check_required(reaction, reaction.getIdAttribute(), "id") if f_replace and F_REACTION in f_replace: rid = f_replace[F_REACTION](rid) try: objective_reaction = geckopy_model.reactions.get_by_id( rid) except KeyError: raise CobraSBMLError( "Objective reaction '%s' " "not found", rid) try: coefficients[objective_reaction] = number( p_oc.getValue()) except ValueError as e: LOGGER.warning(str(e)) LOGGER.warning( "Encoding OBJECTIVE_COEFFICIENT in " "KineticLaw is discouraged, " "use fbc:fluxObjective " "instead: %s", reaction, ) if len(coefficients) == 0: LOGGER.error("No objective coefficients in model. Unclear what should " "be optimized") set_objective(geckopy_model, coefficients) geckopy_model.solver.objective.direction = obj_direction # parse groups model_groups = model.getPlugin("groups") # type: libsbml.GroupsModelPlugin groups = [] if model_groups: # calculate hashmaps to lookup objects in O(1) sid_map = {} metaid_map = {} for obj_list in [ model.getListOfCompartments(), model.getListOfSpecies(), model.getListOfReactions(), model_groups.getListOfGroups(), ]: for sbase in obj_list: # type: libsbml.SBase if sbase.isSetId(): sid_map[sbase.getIdAttribute()] = sbase if sbase.isSetMetaId(): metaid_map[sbase.getMetaId()] = sbase # create groups for group in model_groups.getListOfGroups(): # type: libsbml.Group gid = _check_required(group, group.getIdAttribute(), "id") if f_replace and F_GROUP in f_replace: gid = f_replace[F_GROUP](gid) cobra_group = Group(gid) cobra_group.name = group.getName() if group.isSetKind(): cobra_group.kind = group.getKindAsString() cobra_group.annotation = _parse_annotations(group) cobra_group.notes = _parse_notes_dict(group) cobra_members = [] for member in group.getListOfMembers(): # type: libsbml.Member if member.isSetIdRef(): obj = sid_map[member.getIdRef()] elif member.isSetMetaIdRef(): obj = metaid_map[member.getMetaIdRef()] typecode = obj.getTypeCode() obj_id = _check_required(obj, obj.getIdAttribute(), "id") # id replacements cobra_member = None if typecode == libsbml.SBML_SPECIES: if f_replace and F_SPECIE in f_replace: obj_id = f_replace[F_SPECIE](obj_id) try: cobra_member = geckopy_model.metabolites.get_by_id( obj_id) except KeyError: cobra_member = geckopy_model.proteins.get_by_id(obj_id) elif typecode == libsbml.SBML_REACTION: if f_replace and F_REACTION in f_replace: obj_id = f_replace[F_REACTION](obj_id) cobra_member = geckopy_model.reactions.get_by_id(obj_id) elif typecode == libsbml.SBML_FBC_GENEPRODUCT: if f_replace and F_GENE in f_replace: obj_id = f_replace[F_GENE](obj_id) cobra_member = geckopy_model.genes.get_by_id(obj_id) else: LOGGER.warning("Member %s could not be added to group %s." "unsupported type code: " "%s" % (member, group, typecode)) if cobra_member: cobra_members.append(cobra_member) cobra_group.add_members(cobra_members) groups.append(cobra_group) else: # parse deprecated subsystems on reactions groups_dict = {} for cobra_reaction in geckopy_model.reactions: if "SUBSYSTEM" in cobra_reaction.notes: g_name = cobra_reaction.notes["SUBSYSTEM"] if g_name in groups_dict: groups_dict[g_name].append(cobra_reaction) else: groups_dict[g_name] = [cobra_reaction] for gid, cobra_members in groups_dict.items(): if f_replace and F_GROUP in f_replace: gid = f_replace[F_GROUP](gid) cobra_group = Group(gid, name=gid, kind="collection") cobra_group.add_members(cobra_members) groups.append(cobra_group) geckopy_model.add_groups(groups) # now add everything under group Proteins to model.proteins if it was not # already added based on naming conventions if geckopy_model.groups.query("Protein"): g_proteins = geckopy_model.groups.Protein.members.copy() g_proteins = { prot: {reac: reac.metabolites[prot] for reac in prot.reactions} for prot in g_proteins if prot not in geckopy_model.proteins } if g_proteins: geckopy_model.remove_metabolites(g_proteins.keys()) geckopy_model.add_proteins([Protein(prot) for prot in g_proteins]) for prot, reactions in g_proteins.items(): for reac, stoich in reactions.items(): # reverse the (negative) stoichiometry coefficient to kcat # we expect that Proteins that are identified only by their # group are respecting the specification and do form part # of reactions # TODO: provide options to tune this behvior reac.add_protein(prot.id, -1 / (stoich * 3600)) # general hint for missing flux bounds if missing_bounds: LOGGER.warning( "Missing flux bounds on reactions set to default bounds." "As best practise and to avoid confusion flux bounds " "should be set explicitly on all reactions.") return geckopy_model