def list_model_reactions(model_id): """ List all reactions in a model. Parameters ---------- model_id : str A valid id for a model in BiGG. Returns ------- reactions : DictList All model reactions. Raises ------ requests.HTTPError If the request has failed. """ data = _get("reactions", None, model_id) LOGGER.info("Found %i reactions", data[RESULTS_COUNT]) reactions = DictList() for reaction_data in data[RESULTS]: reaction_id = reaction_data[BIGG_ID] if reaction_id in reactions: continue reaction = Reaction(id=reaction_id, name=reaction_data[NAME]) reactions.append(reaction) return reactions
def list_model_metabolites(model_id): """ List all metabolites in a model. Parameters ---------- model_id : str A model id present in BiGG. Returns ------- metabolites : DictList A list of metabolites. Raises ------ requests.HTTPError If the request has failed. """ data = _get("metabolites", None, model_id) LOGGER.info("Found %i reactions", data[RESULTS_COUNT]) metabolites = DictList() for metabolites_data in data[RESULTS]: metabolite_id = metabolites_data[BIGG_ID] + "_" + metabolites_data[ COMPARTMENT_BIGG_ID] if metabolite_id in metabolites: continue metabolite = Metabolite(id=metabolite_id, name=metabolites_data[NAME]) metabolites.append(metabolite) return metabolites
def __init__(self, id_or_model=None, name=None): if isinstance(id_or_model, Model): Object.__init__(self, name=name) self.__setstate__(id_or_model.__dict__) if not hasattr(self, "name"): self.name = None self._solver = id_or_model.solver else: Object.__init__(self, id_or_model, name=name) self._trimmed = False self._trimmed_genes = [] self._trimmed_reactions = {} self.genes = DictList() self.reactions = DictList() # A list of cobra.Reactions self.metabolites = DictList() # A list of cobra.Metabolites # genes based on their ids {Gene.id: Gene} self._compartments = dict() self._contexts = [] # from cameo ... # if not hasattr(self, '_solver'): # backwards compatibility # with older cobrapy pickles? interface = solvers[get_solver_name()] self._solver = interface.Model() self._solver.objective = interface.Objective(Zero) self._populate_solver(self.reactions, self.metabolites)
def list_reactions(): """ List all reactions available in BiGG. The reactions do not contain stoichiometry. To retrieve the full reaction use *get_reaction*. Returns ------- reactions : list A list of Reaction. Raises ------ requests.HTTPError If the request has failed. """ data = _get("reactions", None, None) LOGGER.info("Found %i reactions", data[RESULTS_COUNT]) reactions = DictList() for reaction_data in data[RESULTS]: reaction_id = reaction_data[BIGG_ID] if reaction_id in reactions: continue reaction = Reaction(id=reaction_data[BIGG_ID], name=reaction_data[NAME]) reactions.append(reaction) return reactions
def __init__(self, id, name="", members=None, kind=None): Object.__init__(self, id, name) self._members = DictList() if members is None else DictList(members) self._kind = None self.kind = "collection" if kind is None else kind # self.model is None or refers to the cobra.Model that # contains self self._model = None
def __init__(self, id_or_model=None, name=None): if isinstance(id_or_model, Model): Object.__init__(self, name=name) self.__setstate__(id_or_model.__dict__) if not hasattr(self, "name"): self.name = None self._solver = id_or_model.solver else: Object.__init__(self, id_or_model, name=name) self._trimmed = False self._trimmed_genes = [] self._trimmed_reactions = {} self.genes = DictList() self.reactions = DictList() # A list of cobra.Reactions self.metabolites = DictList() # A list of cobra.Metabolites self.groups = DictList() # A list of cobra.Groups # genes based on their ids {Gene.id: Gene} self._compartments = {} self._contexts = [] # from cameo ... # if not hasattr(self, '_solver'): # backwards compatibility # with older cobrapy pickles? interface = configuration.solver self._solver = interface.Model() self._solver.objective = interface.Objective(Zero) self._populate_solver(self.reactions, self.metabolites) self._tolerance = None self.tolerance = configuration.tolerance
def __init__( self, id_or_model: Union[str, cobra.Model] = None, name: str = None, hardcoded_rev_reactions: bool = True, ): """Initialize model.""" # TODO: refactor from cobra.Model so that internal matrix does not get # repopulated 100 times self.proteins = DictList() self.hardcoded_rev_reactions = hardcoded_rev_reactions if isinstance(id_or_model, cobra.Model) and not hasattr(id_or_model, "proteins"): self.from_cobra(id_or_model, name) else: super().__init__(id_or_model, name)
def __init__(self, data=None, info=None, args=None): super().__init__(data, info, args, 'KBaseFBA.FBAModel') self.metabolites = DictList() model_compounds = [] ids = set() for o in self.data['modelcompounds']: if o['id'] not in ids: ids.add(o['id']) model_compounds.append(o) self.data['modelcompounds'] = model_compounds self.update_indexes()
def __init__( self, model, Exclude_list=[], tolerance_integral=1e-9, compartment_info=None, membrane_potential=None, ): self.compartment_info = compartment_info self.membrane_potential = membrane_potential do_not_copy_by_ref = { "metabolites", "reactions", "genes", "notes", "annotation", } for attr in model.__dict__: if attr not in do_not_copy_by_ref: self.__dict__[attr] = model.__dict__[attr] self.metabolites = DictList() do_not_copy_by_ref = {"_reaction", "_model"} for metabolite in model.metabolites: new_met = Thermo_met( metabolite=metabolite, updated_model=self, ) self.metabolites.append(new_met) self.genes = DictList() for gene in model.genes: new_gene = gene.__class__(None) for attr, value in iteritems(gene.__dict__): if attr not in do_not_copy_by_ref: new_gene.__dict__[attr] = (copy(value) if attr == "formula" else value) new_gene._model = self self.genes.append(new_gene) self.reactions = DictList() do_not_copy_by_ref = {"_model", "_metabolites", "_genes"} for reaction in model.reactions: new_reaction = thermo_reaction( cobra_rxn=reaction, updated_model=self, ) self.reactions.append(new_reaction) try: self._solver = deepcopy(model.solver) # Cplex has an issue with deep copies except Exception: # pragma: no cover self._solver = copy(model.solver) # pragma: no cover self.Exclude_list = Exclude_list self.solver.configuration.tolerances.integrality = tolerance_integral self._var_update = False
def list_metabolites(): """ List all metabolites present in BiGG. Returns ------- metabolites : DictList A list of metabolites. Raises ------ requests.HTTPError If the request has failed. """ data = _get("metabolites", None, None) LOGGER.info("Found %i metabolites", data[RESULTS_COUNT]) metabolites = DictList() for metabolites_data in data[RESULTS]: metabolite = Metabolite(id=metabolites_data[BIGG_ID], name=metabolites_data[NAME]) metabolites.append(metabolite) return metabolites
def _from_json(self, data): # Should find better solution for this ... # Converting every list of hashes containing objects with id fields into DictList for field in data.keys(): if isinstance(data[field], list) and len(data[field]) > 0: if field == "mediacompounds": for item in data[field]: if not "id" in item: item["id"] = item["compound_ref"].split("/").pop() if item["id"] == "cpd00000": item["id"] = item["name"] if isinstance(data[field][0], dict) and "id" in data[field][0]: data[field] = DictList(map(lambda x: self._to_object(field, x), data[field])) return data
def add_groups(self, group_list): """Add groups to the model. Groups with identifiers identical to a group already in the model are ignored. If any group contains members that are not in the model, these members are added to the model as well. Only metabolites, reactions, and genes can have groups. Parameters ---------- group_list : list A list of `cobra.Group` objects to add to the model. """ def existing_filter(group): if group.id in self.groups: logger.warning("Ignoring group '%s' since it already exists.", group.id) return False return True if isinstance(group_list, string_types) or hasattr(group_list, "id"): warn("need to pass in a list") group_list = [group_list] pruned = DictList(filter(existing_filter, group_list)) for group in pruned: group._model = self for member in group.members: # If the member is not associated with the model, add it if isinstance(member, Metabolite): if member not in self.metabolites: self.add_metabolites([member]) if isinstance(member, Reaction): if member not in self.reactions: self.add_reactions([member]) # TODO(midnighter): `add_genes` method does not exist. # if isinstance(member, Gene): # if member not in self.genes: # self.add_genes([member]) self.groups += [group]
class Model(cobra.Model): """Extension of cobra.Model providing an API for proteins in EC models. Attributes ---------- reactions : DictList A DictList where the key is the reaction identifier and the value a Reaction. metabolites : DictList A DictList where the key is the metabolite identifier and the value a Metabolite. proteins : DictList A DictList where the key is the metabolite identifier and the value a Protein. genes : DictList A DictList where the key is the gene identifier and the value a Gene. groups : DictList A DictList where the key is the group identifier and the value a Group. solution : Solution # TODO: separate proteins from cobra.Reactions The last obtained solution from optimizing the model. """ def __setstate__(self, state: Dict): """Make sure all cobra.Objects in the model point to the model.""" self.__dict__.update(state) for y in ["reactions", "genes", "metabolites", "proteins"]: for x in getattr(self, y): x._model = self if not hasattr(self, "name"): self.name = None def __init__( self, id_or_model: Union[str, cobra.Model] = None, name: str = None, hardcoded_rev_reactions: bool = True, ): """Initialize model.""" # TODO: refactor from cobra.Model so that internal matrix does not get # repopulated 100 times self.proteins = DictList() self.hardcoded_rev_reactions = hardcoded_rev_reactions if isinstance(id_or_model, cobra.Model) and not hasattr(id_or_model, "proteins"): self.from_cobra(id_or_model, name) else: super().__init__(id_or_model, name) def from_cobra(self, model: cobra.Model, name: str): """Initialize from cobra model.""" # gather prots from by naming convention super().__init__(model.id, name) g_proteins = [ met for met in model.metabolites if met.id.startswith("prot_") ] # gather prots from by SBML group if model.groups.query("Protein"): g_proteins += model.groups.Protein.members g_proteins = set(g_proteins) model.remove_metabolites(g_proteins) self.add_metabolites( [met for met in model.metabolites if met not in g_proteins]) self.add_reactions([ reac for reac in model.reactions if not reac.id.startswith("prot_") ]) self.add_proteins([Protein(prot) for prot in g_proteins]) # point every previous protein Metabolite to an actual Protein object for reac in self.reactions: reac._metabolites = { met if met not in g_proteins else self.proteins.get_by_id( met.id): val for met, val in reac.metabolites.items() } self.__setstate__(self.__dict__) self._populate_solver(self.reactions, self.metabolites, self.proteins) def get_total_measured_proteins(self) -> float: """Sum of all `Proteins` in the model that has a concentration.""" return sum( # nan + number = nan! prot.concentration if not isnan(prot.concentration) else 0 for prot in self.proteins # guard against None if prot.concentration) def constrain_pool( self, p_total: float, sigma_saturation_factor: float, fn_mass_fraction_unmeasured_matched: float, protein_list: List[str] = None, ): """Constrain the draw reactions for the unmeasured (common protein pool) proteins. Adapted from [geckopy] (https://github.com/SysBioChalmers/GECKO/blob/master/geckopy/geckopy/gecko.py#L184) Proteins without their own protein pool are collectively constrained by the common protein pool. Remove protein pools for all proteins that don't have measurements, along with corresponding draw reactions, and add these to the common protein pool and reaction. Parameters ---------- p_total: float measured total protein fraction in cell in g protein / gDW sigma_saturation_factor: float part of proteome that can be used by metabolism fn_mass_fraction_unmeasured_matched: float TODO: add convenience function to handle this sum of the product of average abundances of unmesured proteins (from, e.g., paxDB) times their molecular weight. protein_list: List[str] list of ids of proteins to constrain. This is useful for inspecting the protein utilization of, e.g., a heterologous pathway. """ proteins = ( [self.proteins.get_by_id(prot_id) for prot_id in protein_list] if protein_list else self.proteins) unmeasured_prots = [ prot for prot in proteins if prot.concentration is None or isnan(prot.concentration) ] if not unmeasured_prots: return self.add_pool() # if there are no proteins measured, the calculations are simplified fs_matched_adjusted = p_total * sigma_saturation_factor if len(unmeasured_prots) != len(proteins): p_measured = self.get_total_measured_proteins() # * section 2.5.1 # 1. and 2. introduce `prot_pool` and exchange reaction done in __init__ # 3. limiting total usage with the unmeasured amount of protein f_mass_fraction_measured_matched_to_total = ( fn_mass_fraction_unmeasured_matched / (1 - p_measured / p_total)) fs_matched_adjusted = ((p_total - p_measured) * f_mass_fraction_measured_matched_to_total * sigma_saturation_factor) self.protein_pool_exchange.bounds = 0, fs_matched_adjusted # Mw guarding against None and nan m_weigths = [ prot.mw if not isnan(prot.mw) else 0 for prot in proteins if prot.mw ] average_mmw = sum(m_weigths) / len(m_weigths) / 1000.0 for protein in unmeasured_prots: mmw = protein.mw / 1000 if protein.mw else average_mmw protein.suscribe_to_pool(mmw) def add_pool(self, reac_id: str = "prot_pool_exchange", met_id: str = "prot_pool"): """Add the reaction and metabolite to constraint the common pool of proteins. Parameters ---------- read_id: str id of the common protein pool pseudorreaction to add. met_id: str id of the common protein pool metabolite to add. """ try: self.common_protein_pool = self.metabolites.get_by_id(met_id) except KeyError: self.common_protein_pool = Metabolite(met_id) try: self.protein_pool_exchange = self.reactions.get_by_id(reac_id) except KeyError: self.protein_pool_exchange = Reaction(reac_id) self.protein_pool_exchange.add_metabolites( {self.common_protein_pool: 1.0}) self.add_reactions([self.protein_pool_exchange]) def copy(self): """Provide a partial 'deepcopy' of the Model. All of the Metabolite, Gene, and Reaction objects are created anew but in a faster fashion than deepcopy. Enzyme constrained changes: also deepcopy proteins. """ new = self.__class__() do_not_copy_by_ref = { "metabolites", "reactions", "proteins", "genes", "notes", "annotation", "groups", } for attr in self.__dict__: if attr not in do_not_copy_by_ref: new.__dict__[attr] = self.__dict__[attr] new.notes = deepcopy(self.notes) new.annotation = deepcopy(self.annotation) new.metabolites = DictList() do_not_copy_by_ref = {"_reaction", "_model"} for metabolite in self.metabolites: new_met = metabolite.__class__() for attr, value in metabolite.__dict__.items(): if attr not in do_not_copy_by_ref: new_met.__dict__[attr] = copy( value) if attr == "formula" else value new_met._model = new new.metabolites.append(new_met) new.proteins = DictList() for protein in self.proteins: new_protein = protein.__class__() for attr, value in protein.__dict__.items(): if attr not in do_not_copy_by_ref: new_protein.__dict__[attr] = (copy(value) if attr == "formula" else value) new_protein._model = new for attr, value in protein.__dict__.items(): if attr not in do_not_copy_by_ref: new_protein.__dict__[attr] = copy(value) new_protein._model = new new.proteins.append(new_protein) # update awareness for metabolite, stoic in protein._metabolites.items(): if metabolite not in new.proteins: new_met = new.metabolites.get_by_id(metabolite.id) new_protein._metabolites[new_met] = stoic new_met._reaction.add(new_protein) new_protein.kcats._protein = new_protein new.genes = DictList() for gene in self.genes: new_gene = gene.__class__(None) for attr, value in gene.__dict__.items(): if attr not in do_not_copy_by_ref: new_gene.__dict__[attr] = (copy(value) if attr == "formula" else value) new_gene._model = new new.genes.append(new_gene) new.reactions = DictList() do_not_copy_by_ref = {"_model", "_metabolites", "_genes"} for reaction in self.reactions: new_reaction = reaction.__class__() for attr, value in reaction.__dict__.items(): if attr not in do_not_copy_by_ref: new_reaction.__dict__[attr] = copy(value) new_reaction._model = new new.reactions.append(new_reaction) # update awareness for metabolite, stoic in reaction._metabolites.items(): if metabolite in new.proteins: new_met = new.proteins.get_by_id(metabolite.id) new_reaction._metabolites[new_met] = stoic new_met._reaction.add(new_reaction) else: # regular met new_met = new.metabolites.get_by_id(metabolite.id) new_reaction._metabolites[new_met] = stoic new_met._reaction.add(new_reaction) for gene in reaction._genes: new_gene = new.genes.get_by_id(gene.id) new_reaction._genes.add(new_gene) new_gene._reaction.add(new_reaction) new.groups = DictList() do_not_copy_by_ref = {"_model", "_members"} # Groups can be members of other groups. We initialize them first and # then update their members. for group in self.groups: new_group = group.__class__(group.id) for attr, value in group.__dict__.items(): if attr not in do_not_copy_by_ref: new_group.__dict__[attr] = copy(value) new_group._model = new new.groups.append(new_group) for group in self.groups: new_group = new.groups.get_by_id(group.id) # update awareness, as in the reaction copies new_objects = [] for member in group.members: if isinstance(member, Metabolite): new_object = new.metabolites.get_by_id(member.id) elif isinstance(member, Reaction): new_object = new.reactions.get_by_id(member.id) elif isinstance(member, cobra.Gene): new_object = new.genes.get_by_id(member.id) elif isinstance(member, Protein): new_object = new.proteins.get_by_id(member.id) elif isinstance(member, cobra.core.Group): new_object = new.genes.get_by_id(member.id) else: raise TypeError( "The group member {!r} is unexpectedly not a " "metabolite, reaction, gene, nor another " "group.".format(member)) new_objects.append(new_object) new_group.add_members(new_objects) try: new._solver = deepcopy(self.solver) # Cplex has an issue with deep copies except Exception: # pragma: no cover new._solver = copy(self.solver) # pragma: no cover # it doesn't make sense to retain the context of a copied model so # assign a new empty context new._contexts = list() return new def _populate_solver( self, reaction_list: Iterator[Reaction], metabolite_list: Iterator[Metabolite] = None, protein_list: Iterator[Protein] = None, ): """Populate attached solver with LP problem given reactions + proteins. Note that proteins are added both as Constraints and Variables. """ constraint_terms = defaultdict(lambda: defaultdict(float)) to_add = [] metabolite_list = metabolite_list if metabolite_list is not None else [] protein_list = protein_list if protein_list is not None else [] if metabolite_list or protein_list: for met in metabolite_list + protein_list: if met.id not in self.constraints: # this condition only applies when passing `protein_list` to_add += [ self.problem.Constraint(Zero, name=met.id, lb=0, ub=0) ] self.add_cons_vars(to_add) for reaction in reaction_list + protein_list: reaction_id = reaction.id if reaction_id not in self.variables: forward_variable = self.problem.Variable(reaction_id) reverse_variable = self.problem.Variable(reaction.reverse_id) self.add_cons_vars([forward_variable, reverse_variable]) else: try: reaction = self.reactions.get_by_id(reaction_id) except KeyError: reaction = self.proteins.get_by_id(reaction_id) forward_variable = reaction.forward_variable reverse_variable = reaction.reverse_variable for metabolite, coeff in reaction.metabolites.items(): if metabolite.id in self.constraints: constraint = self.constraints[metabolite.id] else: constraint = self.problem.Constraint(Zero, name=metabolite.id, lb=0, ub=0) self.add_cons_vars(constraint, sloppy=True) constraint_terms[constraint][forward_variable] = coeff # the protein is used on both directions (if no legacy model) constraint_terms[constraint][reverse_variable] = ( coeff if not self.hardcoded_rev_reactions and isinstance(metabolite, Protein) # proteins' pseudoexchanges should have normal coefficients and not isinstance(reaction, Protein) else -coeff) self.solver.update() for reaction in reaction_list: self.reactions.get_by_id(reaction.id).update_variable_bounds() for protein in protein_list: self.proteins.get_by_id(protein.id).update_variable_bounds() for constraint, terms in constraint_terms.items(): constraint.set_linear_coefficients(terms) def add_proteins(self, protein_list: Iterator[Protein]): """Add proteins to the model, in the same fashion as `.add_metabollites`.""" if not hasattr(protein_list, "__iter__"): protein_list = [protein_list] if len(protein_list) == 0: return None # Take left difference pruned = [x for x in protein_list if x.id not in self.proteins] for prot in pruned: prot._model = self to_add = [] for prot in pruned: if prot.id not in self.constraints: constraint = self.problem.Constraint(Zero, name=prot.id, lb=0, ub=0) to_add += [constraint] self.add_cons_vars(to_add) context = get_context(self) if context: context(partial(self.proteins.__isub__, pruned)) for x in pruned: context(partial(setattr, x, "_model", None)) self.proteins += pruned self._populate_solver([], None, pruned) def remove_proteins(self, protein_list: List[Protein]): """Remove a list of proteins from self. Parameters ---------- protein_list : list A list with `cobra.Metabolite` objects as elements. """ if not hasattr(protein_list, "__iter__"): protein_list = [protein_list] # Make sure proteins exist in model protein_list = [x for x in protein_list if x.id in self.proteins] for x in protein_list: # remove reference to the protein in all groups associated_groups = self.get_associated_groups(x) for group in associated_groups: group.remove_members(x) for the_reaction in list(x._reaction): if isinstance(the_reaction, Reaction): the_reaction.remove_protein(x) else: # the protein itself self.remove_cons_vars( [x.forward_variable, x.reverse_variable]) x._model = None self.proteins -= protein_list def add_reactions(self, reaction_list: Iterator[Reaction]): """Add reactions to the model. Reactions with identifiers identical to a reaction already in the model are ignored. The change is reverted upon exit when using the model as a context. Enzyme Constrained changes: avoid adding proteins as metabolites. Parameters ---------- reaction_list : list A list of `cobra.Reaction` objects """ def existing_filter(rxn): if rxn.id in self.reactions: LOGGER.warning( f"Ignoring reaction '{rxn.id}' since it already exists.") return False return True # First check whether the reactions exist in the model. pruned = DictList(filter(existing_filter, reaction_list)) context = get_context(self) # Add reactions. Also take care of genes and metabolites in the loop. for reaction in pruned: reaction._model = self # Build a `list()` because the dict will be modified in the loop. for metabolite in list(reaction.metabolites): is_prot = isinstance(metabolite, Protein) target = self.proteins if is_prot else self.metabolites if metabolite not in target: if is_prot: self.add_proteins([metabolite]) else: self.add_metabolites(metabolite) # A copy of the metabolite exists in the model, the reaction # needs to point to the metabolite in the model. else: stoichiometry = reaction._metabolites.pop(metabolite) model_metabolite = target.get_by_id(metabolite.id) reaction._metabolites[model_metabolite] = stoichiometry model_metabolite._reaction.add(reaction) if context: context( partial(model_metabolite._reaction.remove, reaction)) for gene in list(reaction._genes): # If the gene is not in the model, add it if not self.genes.has_id(gene.id): self.genes += [gene] gene._model = self if context: # Remove the gene later context(partial(self.genes.__isub__, [gene])) context(partial(setattr, gene, "_model", None)) # Otherwise, make the gene point to the one in the model else: model_gene = self.genes.get_by_id(gene.id) if model_gene is not gene: reaction._dissociate_gene(gene) reaction._associate_gene(model_gene) self.reactions += pruned if context: context(partial(self.reactions.__isub__, pruned)) # from cameo ... self._populate_solver(pruned) def optimize(self, objective_sense: Optional[str] = None, raise_error: bool = False ) -> Tuple[cobra.Solution, cobra.Solution]: """Optimize the model using flux balance analysis. Parameters ---------- objective_sense : {None, 'maximize' 'minimize'}, optional Whether fluxes should be maximized or minimized. In case of None, the previous direction is used. raise_error : bool If true, raise an OptimizationError if solver status is not optimal. Notes ----- Only the most commonly used parameters are presented here. Additional parameters for cobra.solvers may be available and specified with the appropriate keyword argument. """ solution = super().optimize(objective_sense, raise_error) solution_prot = cobra.core.get_solution(self, reactions=self.proteins, metabolites=self.proteins) solution_prot.contributions = solution_prot.fluxes if all(solution_prot.shadow_prices == 0): # workaround for wrong shadow prices of proteins try: prot_index = [prot.id for prot in self.proteins] solution_prot.shadow_prices = pd.Series( index=prot_index, data=[ -self.variables[prot.id].dual + self.variables[prot.reverse_id].dual for prot in prot_index ], name="shadow_prices", ) except Exception: pass return solution, solution_prot def add_boundary( self, metabolite: Union[cobra.Metabolite, Protein], type: str = "exchange", reaction_id: Optional[str] = None, lb: Optional[float] = None, ub: Optional[float] = None, sbo_term: Optional[str] = None, ) -> Reaction: """Add a boundary reaction for a given metabolite. Enzyme constraint changes: return an geckopy.Reaction. """ rxn = super().add_boundary(metabolite, type, reaction_id, lb, ub, sbo_term) return Reaction(rxn)
def add_reactions(self, reaction_list): """Will add a cobra.Reaction object to the model, if reaction.id is not in self.reactions. The change is reverted upon exit when using the model as a context. Parameters ---------- reaction_list : list A list of `cobra.Reaction` objects """ try: reaction_list = DictList(reaction_list) except TypeError: reaction_list = DictList([reaction_list]) # Only add the reaction if one with the same ID is not already # present in the model. reactions_in_model = [ i.id for i in reaction_list if i.id in self.reactions ] if len(reactions_in_model) > 0: raise Exception("Reactions already in the model: " + ", ".join(reactions_in_model)) context = get_context(self) # Add reactions. Also take care of genes and metabolites in the loop for reaction in reaction_list: reaction._reset_var_cache() reaction._model = self # the reaction now points to the model # keys() is necessary because the dict will be modified during # the loop for metabolite in list(reaction._metabolites.keys()): # if the metabolite is not in the model, add it # should we be adding a copy instead. if metabolite not in self.metabolites: self.add_metabolites(metabolite) # A copy of the metabolite exists in the model, the reaction # needs to point to the metabolite in the model. else: stoichiometry = reaction._metabolites.pop(metabolite) model_metabolite = self.metabolites.get_by_id( metabolite.id) reaction._metabolites[model_metabolite] = stoichiometry model_metabolite._reaction.add(reaction) if context: context( partial(model_metabolite._reaction.remove, reaction)) for gene in list(reaction._genes): # If the gene is not in the model, add it if not self.genes.has_id(gene.id): self.genes += [gene] gene._model = self if context: # Remove the gene later context(partial(self.genes.__isub__, [gene])) context(partial(setattr, gene, '_model', None)) # Otherwise, make the gene point to the one in the model else: model_gene = self.genes.get_by_id(gene.id) if model_gene is not gene: reaction._dissociate_gene(gene) reaction._associate_gene(model_gene) self.reactions += reaction_list if context: context(partial(self.reactions.__isub__, reaction_list)) # from cameo ... self._populate_solver(reaction_list)
def copy(self): """Provide a partial 'deepcopy' of the Model. All of the Metabolite, Gene, and Reaction objects are created anew but in a faster fashion than deepcopy. Enzyme constrained changes: also deepcopy proteins. """ new = self.__class__() do_not_copy_by_ref = { "metabolites", "reactions", "proteins", "genes", "notes", "annotation", "groups", } for attr in self.__dict__: if attr not in do_not_copy_by_ref: new.__dict__[attr] = self.__dict__[attr] new.notes = deepcopy(self.notes) new.annotation = deepcopy(self.annotation) new.metabolites = DictList() do_not_copy_by_ref = {"_reaction", "_model"} for metabolite in self.metabolites: new_met = metabolite.__class__() for attr, value in metabolite.__dict__.items(): if attr not in do_not_copy_by_ref: new_met.__dict__[attr] = copy( value) if attr == "formula" else value new_met._model = new new.metabolites.append(new_met) new.proteins = DictList() for protein in self.proteins: new_protein = protein.__class__() for attr, value in protein.__dict__.items(): if attr not in do_not_copy_by_ref: new_protein.__dict__[attr] = (copy(value) if attr == "formula" else value) new_protein._model = new for attr, value in protein.__dict__.items(): if attr not in do_not_copy_by_ref: new_protein.__dict__[attr] = copy(value) new_protein._model = new new.proteins.append(new_protein) # update awareness for metabolite, stoic in protein._metabolites.items(): if metabolite not in new.proteins: new_met = new.metabolites.get_by_id(metabolite.id) new_protein._metabolites[new_met] = stoic new_met._reaction.add(new_protein) new_protein.kcats._protein = new_protein new.genes = DictList() for gene in self.genes: new_gene = gene.__class__(None) for attr, value in gene.__dict__.items(): if attr not in do_not_copy_by_ref: new_gene.__dict__[attr] = (copy(value) if attr == "formula" else value) new_gene._model = new new.genes.append(new_gene) new.reactions = DictList() do_not_copy_by_ref = {"_model", "_metabolites", "_genes"} for reaction in self.reactions: new_reaction = reaction.__class__() for attr, value in reaction.__dict__.items(): if attr not in do_not_copy_by_ref: new_reaction.__dict__[attr] = copy(value) new_reaction._model = new new.reactions.append(new_reaction) # update awareness for metabolite, stoic in reaction._metabolites.items(): if metabolite in new.proteins: new_met = new.proteins.get_by_id(metabolite.id) new_reaction._metabolites[new_met] = stoic new_met._reaction.add(new_reaction) else: # regular met new_met = new.metabolites.get_by_id(metabolite.id) new_reaction._metabolites[new_met] = stoic new_met._reaction.add(new_reaction) for gene in reaction._genes: new_gene = new.genes.get_by_id(gene.id) new_reaction._genes.add(new_gene) new_gene._reaction.add(new_reaction) new.groups = DictList() do_not_copy_by_ref = {"_model", "_members"} # Groups can be members of other groups. We initialize them first and # then update their members. for group in self.groups: new_group = group.__class__(group.id) for attr, value in group.__dict__.items(): if attr not in do_not_copy_by_ref: new_group.__dict__[attr] = copy(value) new_group._model = new new.groups.append(new_group) for group in self.groups: new_group = new.groups.get_by_id(group.id) # update awareness, as in the reaction copies new_objects = [] for member in group.members: if isinstance(member, Metabolite): new_object = new.metabolites.get_by_id(member.id) elif isinstance(member, Reaction): new_object = new.reactions.get_by_id(member.id) elif isinstance(member, cobra.Gene): new_object = new.genes.get_by_id(member.id) elif isinstance(member, Protein): new_object = new.proteins.get_by_id(member.id) elif isinstance(member, cobra.core.Group): new_object = new.genes.get_by_id(member.id) else: raise TypeError( "The group member {!r} is unexpectedly not a " "metabolite, reaction, gene, nor another " "group.".format(member)) new_objects.append(new_object) new_group.add_members(new_objects) try: new._solver = deepcopy(self.solver) # Cplex has an issue with deep copies except Exception: # pragma: no cover new._solver = copy(self.solver) # pragma: no cover # it doesn't make sense to retain the context of a copied model so # assign a new empty context new._contexts = list() return new
def copy(self): """Provides a partial 'deepcopy' of the Model. All of the Metabolite, Gene, and Reaction objects are created anew but in a faster fashion than deepcopy """ new = self.__class__() do_not_copy_by_ref = { "metabolites", "reactions", "genes", "notes", "annotation" } for attr in self.__dict__: if attr not in do_not_copy_by_ref: new.__dict__[attr] = self.__dict__[attr] new.notes = deepcopy(self.notes) new.annotation = deepcopy(self.annotation) new.metabolites = DictList() do_not_copy_by_ref = {"_reaction", "_model"} for metabolite in self.metabolites: new_met = metabolite.__class__() for attr, value in iteritems(metabolite.__dict__): if attr not in do_not_copy_by_ref: new_met.__dict__[attr] = copy( value) if attr == "formula" else value new_met._model = new new.metabolites.append(new_met) new.genes = DictList() for gene in self.genes: new_gene = gene.__class__(None) for attr, value in iteritems(gene.__dict__): if attr not in do_not_copy_by_ref: new_gene.__dict__[attr] = copy( value) if attr == "formula" else value new_gene._model = new new.genes.append(new_gene) new.reactions = DictList() do_not_copy_by_ref = {"_model", "_metabolites", "_genes"} for reaction in self.reactions: new_reaction = reaction.__class__() for attr, value in iteritems(reaction.__dict__): if attr not in do_not_copy_by_ref: new_reaction.__dict__[attr] = copy(value) new_reaction._model = new new.reactions.append(new_reaction) # update awareness for metabolite, stoic in iteritems(reaction._metabolites): new_met = new.metabolites.get_by_id(metabolite.id) new_reaction._metabolites[new_met] = stoic new_met._reaction.add(new_reaction) for gene in reaction._genes: new_gene = new.genes.get_by_id(gene.id) new_reaction._genes.add(new_gene) new_gene._reaction.add(new_reaction) for reaction in new.reactions: reaction._reset_var_cache() try: new._solver = deepcopy(self.solver) # Cplex has an issue with deep copies except Exception: # pragma: no cover new._solver = copy(self.solver) # pragma: no cover return new
class Model(Object): """Class representation for a cobra model Parameters ---------- id_or_model : Model, string Either an existing Model object in which case a new model object is instantiated with the same properties as the original model, or a the identifier to associate with the model as a string. name : string Human readable name for the model Attributes ---------- reactions : DictList A DictList where the key is the reaction identifier and the value a Reaction metabolites : DictList A DictList where the key is the metabolite identifier and the value a Metabolite genes : DictList A DictList where the key is the gene identifier and the value a Gene solution : Solution The last obtained solution from optimizing the model. """ def __setstate__(self, state): """Make sure all cobra.Objects in the model point to the model""" self.__dict__.update(state) for y in ['reactions', 'genes', 'metabolites']: for x in getattr(self, y): x._model = self if y == 'reactions': x._reset_var_cache() if not hasattr(self, "name"): self.name = None def __init__(self, id_or_model=None, name=None): if isinstance(id_or_model, Model): Object.__init__(self, name=name) self.__setstate__(id_or_model.__dict__) if not hasattr(self, "name"): self.name = None self._solver = id_or_model.solver else: Object.__init__(self, id_or_model, name=name) self._trimmed = False self._trimmed_genes = [] self._trimmed_reactions = {} self.genes = DictList() self.reactions = DictList() # A list of cobra.Reactions self.metabolites = DictList() # A list of cobra.Metabolites # genes based on their ids {Gene.id: Gene} self.compartments = dict() self._contexts = [] # from cameo ... # if not hasattr(self, '_solver'): # backwards compatibility # with older cobrapy pickles? interface = solvers[get_solver_name()] self._solver = interface.Model() self._solver.objective = interface.Objective(S.Zero) self._populate_solver(self.reactions, self.metabolites) @property def solver(self): """Get or set the attached solver instance. The associated the solver object, which manages the interaction with the associated solver, e.g. glpk. This property is useful for accessing the optimization problem directly and to define additional non-metabolic constraints. Examples -------- >>> import cobra.test >>> model = cobra.test.create_test_model("textbook") >>> new = model.problem.Constraint(model.objective.expression, >>> lb=0.99) >>> model.solver.add(new) """ return self._solver @solver.setter @resettable def solver(self, value): not_valid_interface = SolverNotFound( '%s is not a valid solver interface. Pick from %s, or specify an ' 'optlang interface (e.g. optlang.glpk_interface).' % (value, list(solvers.keys()))) if isinstance(value, six.string_types): try: interface = solvers[interface_to_str(value)] except KeyError: raise not_valid_interface elif isinstance(value, types.ModuleType) and hasattr(value, 'Model'): interface = value elif isinstance(value, optlang.interface.Model): interface = value.interface else: raise not_valid_interface # Do nothing if the solver did not change if self.problem == interface: return for reaction in self.reactions: reaction._reset_var_cache() self._solver = interface.Model.clone(self._solver) @property def description(self): warn("description deprecated", DeprecationWarning) return self.name if self.name is not None else "" @description.setter def description(self, value): self.name = value warn("description deprecated", DeprecationWarning) def get_metabolite_compartments(self): """Return all metabolites' compartments.""" return { met.compartment for met in self.metabolites if met.compartment is not None } @property def medium(self): def is_active(reaction): """Determine if a boundary reaction permits flux towards creating metabolites """ return ((bool(reaction.products) and (reaction.upper_bound > 0)) or (bool(reaction.reactants) and (reaction.lower_bound < 0))) def get_active_bound(reaction): """For an active boundary reaction, return the relevant bound""" if reaction.reactants: return -reaction.lower_bound elif reaction.products: return reaction.upper_bound return { rxn.id: get_active_bound(rxn) for rxn in self.exchanges if is_active(rxn) } @medium.setter def medium(self, medium): """Get or set the constraints on the model exchanges. `model.medium` returns a dictionary of the bounds for each of the boundary reactions, in the form of `{rxn_id: bound}`, where `bound` specifies the absolute value of the bound in direction of metabolite creation (i.e., lower_bound for `met <--`, upper_bound for `met -->`) Parameters ---------- medium: dictionary-like The medium to initialize. medium should be a dictionary defining `{rxn_id: bound}` pairs. """ def set_active_bound(reaction, bound): if reaction.reactants: reaction.lower_bound = -bound elif reaction.products: reaction.upper_bound = bound # Set the given media bounds media_rxns = list() for rxn_id, bound in iteritems(medium): rxn = self.reactions.get_by_id(rxn_id) media_rxns.append(rxn) set_active_bound(rxn, bound) boundary_rxns = set(self.exchanges) media_rxns = set(media_rxns) # Turn off reactions not present in media for rxn in (boundary_rxns - media_rxns): set_active_bound(rxn, 0) def __add__(self, other_model): """Adds two models. + The issue of reactions being able to exists in multiple Models now arises, the same for metabolites and such. This might be a little difficult as a reaction with the same name / id in two models might have different coefficients for their metabolites due to pH and whatnot making them different reactions. """ new_model = self.copy() new_reactions = deepcopy(other_model.reactions) new_model.add_reactions(new_reactions) new_model.id = self.id + '_' + other_model.id return new_model def __iadd__(self, other_model): """Adds a Model to this model += The issue of reactions being able to exists in multiple Models now arises, the same for metabolites and such. This might be a little difficult as a reaction with the same name / id in two models might have different coefficients for their metabolites due to pH and whatnot making them different reactions. """ new_reactions = deepcopy(other_model.reactions) self.add_reactions(new_reactions) self.id = self.id + '_' + other_model.id return self def copy(self): """Provides a partial 'deepcopy' of the Model. All of the Metabolite, Gene, and Reaction objects are created anew but in a faster fashion than deepcopy """ new = self.__class__() do_not_copy_by_ref = { "metabolites", "reactions", "genes", "notes", "annotation" } for attr in self.__dict__: if attr not in do_not_copy_by_ref: new.__dict__[attr] = self.__dict__[attr] new.notes = deepcopy(self.notes) new.annotation = deepcopy(self.annotation) new.metabolites = DictList() do_not_copy_by_ref = {"_reaction", "_model"} for metabolite in self.metabolites: new_met = metabolite.__class__() for attr, value in iteritems(metabolite.__dict__): if attr not in do_not_copy_by_ref: new_met.__dict__[attr] = copy( value) if attr == "formula" else value new_met._model = new new.metabolites.append(new_met) new.genes = DictList() for gene in self.genes: new_gene = gene.__class__(None) for attr, value in iteritems(gene.__dict__): if attr not in do_not_copy_by_ref: new_gene.__dict__[attr] = copy( value) if attr == "formula" else value new_gene._model = new new.genes.append(new_gene) new.reactions = DictList() do_not_copy_by_ref = {"_model", "_metabolites", "_genes"} for reaction in self.reactions: new_reaction = reaction.__class__() for attr, value in iteritems(reaction.__dict__): if attr not in do_not_copy_by_ref: new_reaction.__dict__[attr] = copy(value) new_reaction._model = new new.reactions.append(new_reaction) # update awareness for metabolite, stoic in iteritems(reaction._metabolites): new_met = new.metabolites.get_by_id(metabolite.id) new_reaction._metabolites[new_met] = stoic new_met._reaction.add(new_reaction) for gene in reaction._genes: new_gene = new.genes.get_by_id(gene.id) new_reaction._genes.add(new_gene) new_gene._reaction.add(new_reaction) for reaction in new.reactions: reaction._reset_var_cache() try: new._solver = deepcopy(self.solver) # Cplex has an issue with deep copies except Exception: # pragma: no cover new._solver = copy(self.solver) # pragma: no cover return new def add_metabolites(self, metabolite_list): """Will add a list of metabolites to the model object and add new constraints accordingly. The change is reverted upon exit when using the model as a context. Parameters ---------- metabolite_list : A list of `cobra.core.Metabolite` objects """ if not hasattr(metabolite_list, '__iter__'): metabolite_list = [metabolite_list] # First check whether the metabolites exist in the model metabolite_list = [ x for x in metabolite_list if x.id not in self.metabolites ] for x in metabolite_list: x._model = self self.metabolites += metabolite_list # from cameo ... to_add = [] for met in metabolite_list: if met.id not in self.constraints: constraint = self.problem.Constraint(S.Zero, name=met.id, lb=0, ub=0) to_add += [constraint] self.add_cons_vars(to_add) context = get_context(self) if context: context(partial(self.metabolites.__isub__, metabolite_list)) for x in metabolite_list: # Do we care? context(partial(setattr, x, '_model', None)) def remove_metabolites(self, metabolite_list, destructive=False): """Remove a list of metabolites from the the object. The change is reverted upon exit when using the model as a context. Parameters ---------- metabolite_list : list A list with `cobra.Metabolite` objects as elements. destructive : bool If False then the metabolite is removed from all associated reactions. If True then all associated reactions are removed from the Model. """ if not hasattr(metabolite_list, '__iter__'): metabolite_list = [metabolite_list] # Make sure metabolites exist in model metabolite_list = [ x for x in metabolite_list if x.id in self.metabolites ] for x in metabolite_list: x._model = None if not destructive: for the_reaction in list(x._reaction): the_coefficient = the_reaction._metabolites[x] the_reaction.subtract_metabolites({x: the_coefficient}) else: for x in list(x._reaction): x.remove_from_model() self.metabolites -= metabolite_list to_remove = [self.solver.constraints[m.id] for m in metabolite_list] self.remove_cons_vars(to_remove) context = get_context(self) if context: context(partial(self.metabolites.__iadd__, metabolite_list)) for x in metabolite_list: context(partial(setattr, x, '_model', self)) def add_reaction(self, reaction): """Will add a cobra.Reaction object to the model, if reaction.id is not in self.reactions. Parameters ---------- reaction : cobra.Reaction The reaction to add Deprecated (0.6). Use `~cobra.Model.add_reactions` instead """ warn("add_reaction deprecated. Use add_reactions instead", DeprecationWarning) self.add_reactions([reaction]) def add_reactions(self, reaction_list): """Will add a cobra.Reaction object to the model, if reaction.id is not in self.reactions. The change is reverted upon exit when using the model as a context. Parameters ---------- reaction_list : list A list of `cobra.Reaction` objects """ try: reaction_list = DictList(reaction_list) except TypeError: reaction_list = DictList([reaction_list]) # Only add the reaction if one with the same ID is not already # present in the model. reactions_in_model = [ i.id for i in reaction_list if i.id in self.reactions ] if len(reactions_in_model) > 0: raise Exception("Reactions already in the model: " + ", ".join(reactions_in_model)) context = get_context(self) # Add reactions. Also take care of genes and metabolites in the loop for reaction in reaction_list: reaction._reset_var_cache() reaction._model = self # the reaction now points to the model # keys() is necessary because the dict will be modified during # the loop for metabolite in list(reaction._metabolites.keys()): # if the metabolite is not in the model, add it # should we be adding a copy instead. if metabolite not in self.metabolites: self.add_metabolites(metabolite) # A copy of the metabolite exists in the model, the reaction # needs to point to the metabolite in the model. else: stoichiometry = reaction._metabolites.pop(metabolite) model_metabolite = self.metabolites.get_by_id( metabolite.id) reaction._metabolites[model_metabolite] = stoichiometry model_metabolite._reaction.add(reaction) if context: context( partial(model_metabolite._reaction.remove, reaction)) for gene in list(reaction._genes): # If the gene is not in the model, add it if not self.genes.has_id(gene.id): self.genes += [gene] gene._model = self if context: # Remove the gene later context(partial(self.genes.__isub__, [gene])) context(partial(setattr, gene, '_model', None)) # Otherwise, make the gene point to the one in the model else: model_gene = self.genes.get_by_id(gene.id) if model_gene is not gene: reaction._dissociate_gene(gene) reaction._associate_gene(model_gene) self.reactions += reaction_list if context: context(partial(self.reactions.__isub__, reaction_list)) # from cameo ... self._populate_solver(reaction_list) def remove_reactions(self, reactions, delete=True, remove_orphans=False): """Remove reactions from the model. The change is reverted upon exit when using the model as a context. Parameters ---------- reactions : list A list with reactions (`cobra.Reaction`), or their id's, to remove delete : bool Whether or not the reactions should be deleted after removal. If the reactions are not deleted, those objects will be recreated with new metabolite and gene objects. remove_orphans : bool Remove orphaned genes and metabolites from the model as well """ if isinstance(reactions, string_types) or hasattr(reactions, "id"): warn("need to pass in a list") reactions = [reactions] context = get_context(self) for reaction in reactions: try: reaction = self.reactions[self.reactions.index(reaction)] except ValueError: warn('%s not in %s' % (reaction, self)) else: forward = reaction.forward_variable reverse = reaction.reverse_variable self.remove_cons_vars([forward, reverse]) self.reactions.remove(reaction) reaction._model = None if context: context(partial(setattr, reaction, '_model', self)) context(partial(self.reactions.add, reaction)) for x in reaction._metabolites: if reaction in x._reaction: x._reaction.remove(reaction) if context: context(partial(x._reaction.add, reaction)) if remove_orphans and len(x._reaction) == 0: self.remove_metabolites(x) for x in reaction._genes: if reaction in x._reaction: x._reaction.remove(reaction) if context: context(partial(x._reaction.add, reaction)) if remove_orphans and len(x._reaction) == 0: self.genes.remove(x) if context: context(partial(self.genes.add, x)) reaction._metabolites = {} reaction._genes = set() def add_cons_vars(self, what, **kwargs): """Add constraints and variables to the model's mathematical problem. Useful for variables and constraints that can not be expressed with reactions and simple lower and upper bounds. Additions are reversed upon exit if the model itself is used as context. Parameters ---------- what : list or tuple of optlang variables or constraints. The variables or constraints to add to the model. Must be of class `optlang.interface.Variable` or `optlang.interface.Constraint`. **kwargs : keyword arguments Passed to solver.add() """ add_cons_vars_to_problem(self, what, **kwargs) def remove_cons_vars(self, what): """Remove variables and constraints from the model's mathematical problem. Remove variables and constraints that were added directly to the model's underlying mathematical problem. Removals are reversed upon exit if the model itself is used as context. Parameters ---------- what : list or tuple of optlang variables or constraints. The variables or constraints to add to the model. Must be of class `optlang.interface.Variable` or `optlang.interface.Constraint`. """ remove_cons_vars_from_problem(self, what) @property def problem(self): """The interface to the model's underlying mathematical problem. Solutions to cobra models are obtained by formulating a mathematical problem and solving it. Cobrapy uses the optlang package to accomplish that and with this property you can get access to the problem interface directly. Returns ------- optlang.interface The problem interface that defines methods for interacting with the problem and associated solver directly. """ return self.solver.interface @property def variables(self): """The mathematical variables in the cobra model. In a cobra model, most variables are reactions. However, for specific use cases, it may also be useful to have other types of variables. This property defines all variables currently associated with the model's problem. Returns ------- optlang.container.Container A container with all associated variables. """ return self.solver.variables @property def constraints(self): """The constraints in the cobra model. In a cobra model, most constraints are metabolites and their stoichiometries. However, for specific use cases, it may also be useful to have other types of constraints. This property defines all constraints currently associated with the model's problem. Returns ------- optlang.container.Container A container with all associated constraints. """ return self.solver.constraints @property def exchanges(self): """Exchange reactions in model. Reactions that either don't have products or substrates. """ return [rxn for rxn in self.reactions if rxn.boundary] def _populate_solver(self, reaction_list, metabolite_list=None): """Populate attached solver with constraints and variables that model the provided reactions. """ constraint_terms = AutoVivification() to_add = [] if metabolite_list is not None: for met in metabolite_list: to_add += [ self.problem.Constraint(S.Zero, name=met.id, lb=0, ub=0) ] self.add_cons_vars(to_add) for reaction in reaction_list: reverse_lb, reverse_ub, forward_lb, forward_ub = \ separate_forward_and_reverse_bounds(*reaction.bounds) forward_variable = self.problem.Variable(reaction.id, lb=forward_lb, ub=forward_ub) reverse_variable = self.problem.Variable(reaction.reverse_id, lb=reverse_lb, ub=reverse_ub) self.add_cons_vars([forward_variable, reverse_variable]) self.solver.update() for metabolite, coeff in six.iteritems(reaction.metabolites): if metabolite.id in self.constraints: constraint = self.constraints[metabolite.id] else: constraint = self.problem.Constraint(S.Zero, name=metabolite.id, lb=0, ub=0) self.add_cons_vars(constraint, sloppy=True) constraint_terms[constraint][forward_variable] = coeff constraint_terms[constraint][reverse_variable] = -coeff self.solver.update() for constraint, terms in six.iteritems(constraint_terms): constraint.set_linear_coefficients(terms) def to_array_based_model(self, deepcopy_model=False, **kwargs): """Makes a `cobra.core.ArrayBasedModel` from a cobra.Model which may be used to perform linear algebra operations with the stoichiometric matrix. Deprecated (0.6). Use `cobra.util.array.create_stoichiometric_array` instead. Parameters ---------- deepcopy_model : bool If False then the ArrayBasedModel points to the Model """ warn( "to_array_based_model is deprecated. " "use cobra.util.array.create_stoichiometric_array instead", DeprecationWarning) from cobra.core.arraybasedmodel import ArrayBasedModel return ArrayBasedModel(self, deepcopy_model=deepcopy_model, **kwargs) def optimize(self, objective_sense=None, **kwargs): """ Optimize the model using flux balance analysis. Parameters ---------- objective_sense : {None, 'maximize' 'minimize'}, optional Whether fluxes should be maximized or minimized. In case of None, the previous direction is used. solver : {None, 'glpk', 'cglpk', 'gurobi', 'cplex'}, optional If unspecified will use the currently defined `self.solver` otherwise it will use the given solver and update the attribute. quadratic_component : {None, scipy.sparse.dok_matrix}, optional The dimensions should be (n, n) where n is the number of reactions. This sets the quadratic component (Q) of the objective coefficient, adding :math:`\\frac{1}{2} v^T \cdot Q \cdot v` to the objective. Ignored for optlang based solvers. tolerance_feasibility : float Solver tolerance for feasibility. Ignored for optlang based solvers tolerance_markowitz : float Solver threshold during pivot. Ignored for optlang based solvers time_limit : float Maximum solver time (in seconds). Ignored for optlang based solvers Notes ----- Only the most commonly used parameters are presented here. Additional parameters for cobra.solvers may be available and specified with the appropriate keyword argument. """ legacy, solver = choose_solver(self, solver=kwargs.get("solver")) original_direction = self.objective.direction if legacy: if objective_sense is None: objective_sense = { "max": "maximize", "min": "minimize" }[original_direction] solution = optimize(self, objective_sense=objective_sense, **kwargs) check_solver_status(solution.status) return solution self.solver = solver self.objective.direction = \ {"maximize": "max", "minimize": "min"}.get( objective_sense, original_direction) self.solver.optimize() solution = get_solution(self) self.objective.direction = original_direction return solution def repair(self, rebuild_index=True, rebuild_relationships=True): """Update all indexes and pointers in a model Parameters ---------- rebuild_index : bool rebuild the indices kept in reactions, metabolites and genes rebuild_relationships : bool reset all associations between genes, metabolites, model and then re-add them. """ if rebuild_index: # DictList indexes self.reactions._generate_index() self.metabolites._generate_index() self.genes._generate_index() if rebuild_relationships: for met in self.metabolites: met._reaction.clear() for gene in self.genes: gene._reaction.clear() for rxn in self.reactions: for met in rxn._metabolites: met._reaction.add(rxn) for gene in rxn._genes: gene._reaction.add(rxn) # point _model to self for l in (self.reactions, self.genes, self.metabolites): for e in l: e._model = self @property def objective(self): """Get or set the solver objective Before introduction of the optlang based problems, this function returned the objective reactions as a list. With optlang, the objective is not limited a simple linear summation of individual reaction fluxes, making that return value ambiguous. Henceforth, use `cobra.util.solver.linear_reaction_coefficients` to get a dictionary of reactions with their linear coefficients (empty if there are none) The set value can be dictionary (reactions as keys, linear coefficients as values), string (reaction identifier), int (reaction index), Reaction or problem.Objective or sympy expression directly interpreted as objectives. When using a `HistoryManager` context, this attribute can be set temporarily, reversed when the exiting the context. """ return self.solver.objective @objective.setter 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 summary(self, threshold=1E-8, fva=None, floatfmt='.3g', **kwargs): """Print a summary of the input and output fluxes of the model. This method requires the model to have been previously solved. Parameters ---------- threshold : float tolerance for determining if a flux is zero (not printed) fva : int or None Whether or not to calculate and report flux variability in the output summary floatfmt : string format method for floats, passed to tabulate. Default is '.3g'. """ from cobra.flux_analysis.summary import model_summary return model_summary(self, threshold=threshold, fva=fva, floatfmt=floatfmt, **kwargs) def __enter__(self): """Record all future changes to the model, undoing them when a call to __exit__ is received""" # Create a new context and add it to the stack try: self._contexts.append(HistoryManager()) except AttributeError: self._contexts = [HistoryManager()] return self def __exit__(self, type, value, traceback): """Pop the top context manager and trigger the undo functions""" context = self._contexts.pop() context.reset()
class tmodel(Model): """tmodel is Class representation of thermodynamic metabolic flux analysis model. This class adds attributes and methods required for thermodynamic analysis of a COBRA model. Parameters ---------- Model : cobra.core.model A Cobra model class model : instance of cobra.core.model tmodel requires a cobra model instance as input. Thermodynamic properties are added based on the stoichiometry of the underlying Cobra model Exclude_list : list, optional List of reactions user wants to exclude from thermodynamic analysis, For example, Exchange/Demand reactions, by default [] tolerance_integral : float, optional integrality tolerance of for the model , by default 1e-9 compartment_info : pd.Dataframe, optional a pandas Dataframe containing the compartment information like pH, ionic strength, magnesium concentration etc. Row indices should be the compartment symbol and column indices should be property, by default None membrane_potential : pd.Dataframe, optional a pandas Dataframe containing membrane electrostatic potential information to calculate the delG of muli compartment transport. Values are read in a sequence that column represent the first compartment and row represent the compartment being transported to. Row & column indices should be the compartment symbols , by default None """ def __init__( self, model, Exclude_list=[], tolerance_integral=1e-9, compartment_info=None, membrane_potential=None, ): self.compartment_info = compartment_info self.membrane_potential = membrane_potential do_not_copy_by_ref = { "metabolites", "reactions", "genes", "notes", "annotation", } for attr in model.__dict__: if attr not in do_not_copy_by_ref: self.__dict__[attr] = model.__dict__[attr] self.metabolites = DictList() do_not_copy_by_ref = {"_reaction", "_model"} for metabolite in model.metabolites: new_met = Thermo_met( metabolite=metabolite, updated_model=self, ) self.metabolites.append(new_met) self.genes = DictList() for gene in model.genes: new_gene = gene.__class__(None) for attr, value in iteritems(gene.__dict__): if attr not in do_not_copy_by_ref: new_gene.__dict__[attr] = (copy(value) if attr == "formula" else value) new_gene._model = self self.genes.append(new_gene) self.reactions = DictList() do_not_copy_by_ref = {"_model", "_metabolites", "_genes"} for reaction in model.reactions: new_reaction = thermo_reaction( cobra_rxn=reaction, updated_model=self, ) self.reactions.append(new_reaction) try: self._solver = deepcopy(model.solver) # Cplex has an issue with deep copies except Exception: # pragma: no cover self._solver = copy(model.solver) # pragma: no cover self.Exclude_list = Exclude_list self.solver.configuration.tolerances.integrality = tolerance_integral self._var_update = False @property def gurobi_interface(self): """multiTFA at the moment supports two solvers Gurobi/Cplex for solving quadratic constraint problems. Optlang doesn't support adding QC, so we chose to add two separate solver interafaces to tmodel. This is gurobi solver interface. In addition to the linear constraints, this interface contain one extra constraint to represent sphere Returns ------- gurobi model object Gurobi model containing the QC """ try: return self._gurobi_interface except AttributeError: if self.solver.__class__.__module__ == "optlang.gurobi_interface": # self._gurobi_interface = self.solver.problem.copy() self._gurobi_interface = self.Quadratic_constraint() return self._gurobi_interface @property def cplex_interface(self): """Cplex interface to support QC Returns ------- Cplex model Cplex model containing QC """ try: return self._cplex_interface except AttributeError: if self.solver.__class__.__module__ == "optlang.cplex_interface": self._cplex_interface = self.Quadratic_constraint() return self._cplex_interface @property def problem_metabolites(self): """Metabolites for which we can't calculate the Gibbs free energy of formation using component contribution method. If the metabolite is not covered by reactant/group contribution method, then we have to write the metabolite and corresponding reactions from tMFA analysis or have to lump the reactions. Returns: List List of metabolite not covered by component contribution """ problematic_metabolites = [] for met in self.metabolites: if met.is_proton: continue if ~met.compound_vector.any(): problematic_metabolites.append(met) return problematic_metabolites @property def Exclude_reactions(self): """Reactions that needs to be excluded from tMFA. This list includes both user excluded reactions and reactions involving non-coverage metabolites Returns ------- List List of reactions Excluded from thermo analysis """ try: return self._Exclude_reactions except AttributeError: self._Exclude_reactions = list( set(self.Exclude_list + self.problematic_rxns)) return self._Exclude_reactions @property def problematic_rxns(self): """List of reactions containing non-covered metabolites. These can either be written out or lumped Returns ------- List List of non-covered reactions """ try: return self._problematic_rxns except AttributeError: self._problematic_rxns = self.cal_problematic_rxns() return self._problematic_rxns @property def component_variables(self): return np.array([ var for var in self.variables if var.name.startswith("component_") ]) @property def metabolite_equilibrator_accessions(self): try: return self._metabolite_equilibrator_accessions except AttributeError: self._metabolite_equilibrator_accessions = ( self.populate_metabolite_properties()) return self._metabolite_equilibrator_accessions def populate_metabolite_properties(self): """Local cache file for equilibrator-api data. This is a temporary fix till equilibrator's cache Returns ------- dict Dictionary of metabolite id to corresponding equilibrator compound object """ if os.path.isfile(cache_file): with open(cache_file, "rb") as handle: metabolite_accessions, microspecies, mg_dissociation_data = pickle.load( handle) else: metabolite_accessions, microspecies, mg_dissociation_data = ({}, {}, {}) accessions = {} for metabolite in self.metabolites: if metabolite.Kegg_id in metabolite_accessions: accessions[metabolite.id] = metabolite_accessions[ metabolite.Kegg_id] logging.debug("{} fetched from cache data".format( metabolite.id)) else: if metabolite.Kegg_id == "NA": eq_accession = None logging.debug( "Database identifier not available for {}, ignoring from thermodynamic analysis" .format(metabolite.id)) else: try: eq_accession = api.get_compound(metabolite.Kegg_id) except: eq_accession = None logging.debug( "Unable to fetch data from eQuilibrator for the metabolite {}, ignoring from thermodynamic analysis" .format(metabolite.id)) accessions[metabolite.id] = eq_accession # update the cache file if eq_accession is not None: metabolite_accessions[metabolite.Kegg_id] = eq_accession microspecies[ metabolite.Kegg_id] = eq_accession.microspecies mg_dissociation_data[ metabolite. Kegg_id] = eq_accession.magnesium_dissociation_constants # Try and update the cache file with updated values try: with open(cache_file, "wb") as handle: pickle.dump([ metabolite_accessions, microspecies, mg_dissociation_data ], handle) except: pass return accessions @property def compound_vector_matrix(self): try: return self._compound_vector_matrix except AttributeError: # Initialize the matrix with zeros comp_vector = np.zeros((len(self.metabolites), Nc + Ng)) for metabolite in self.metabolites: met_index = self.metabolites.index(metabolite) comp_vector[met_index, :] = metabolite.compound_vector self._compound_vector_matrix = comp_vector return self._compound_vector_matrix def core_stoichiometry(self): n_core_rxn = len(self.reactions) - len(self.Exclude_reactions) stoichiometry_core = np.zeros((n_core_rxn, len(self.metabolites))) i = 0 rxn_var_name = [] for reaction in self.reactions: if reaction.id in self.Exclude_reactions: continue rxn_stoichiometry = reaction.cal_stoichiometric_matrix() stoichiometry_core[i, :] = rxn_stoichiometry i = i + 2 rxn_var_name.extend([ reaction.forward_variable.name, reaction.reverse_variable.name ]) return (rxn_var_name, stoichiometry_core) def update_thermo_variables(self): """Generates reaction and metabolite variables required for thermodynamic analysis and adds to the model. We use two different methods to solve the tMFA problem. Traditional 'box' method employs MILP problem where components are allowed to vary between some s.d from mean. The other method uses MIQCP structure to use covariance matrix to capture covariance. Two methods share some common variables, where as MIQCP method requires independent variables to sample from original solution space. Common Variables: delG_reaction, indicator_reaction, concentration_metabolite MILP variables: metabolite error MIQCP variables: independent component variables """ self._var_update = False # Add metabolite concentration variable and error variable for the metabolite conc_variables, dG_err_vars = ([], []) for metabolite in self.metabolites: conc_variable = self.problem.Variable( "lnc_{}".format(metabolite.id), lb=np.log(metabolite.concentration_min), ub=np.log(metabolite.concentration_max), ) delG_err_variable = self.problem.Variable( "dG_err_{}".format(metabolite.id), lb=-1.96 * np.sqrt(metabolite.std_dev), ub=1.96 * np.sqrt(metabolite.std_dev), ) conc_variables.append(conc_variable) dG_err_vars.append(delG_err_variable) self.add_cons_vars(conc_variables + dG_err_vars) # Adding the thermo variables for reactions, delG_reaction and indicator (binary) rxn_variables = [] for rxn in self.reactions: if rxn.id in self.Exclude_reactions: continue delG_forward = self.problem.Variable("dG_{}".format( rxn.forward_variable.name), lb=-1e6, ub=1e5) delG_reverse = self.problem.Variable("dG_{}".format( rxn.reverse_variable.name), lb=-1e6, ub=1e5) indicator_forward = self.problem.Variable( "indicator_{}".format(rxn.forward_variable.name), lb=0, ub=1, type="binary", ) indicator_reverse = self.problem.Variable( "indicator_{}".format(rxn.reverse_variable.name), lb=0, ub=1, type="binary", ) rxn_variables.extend([ delG_forward, delG_reverse, indicator_forward, indicator_reverse ]) self.add_cons_vars(rxn_variables) self._var_update = True def _generate_constraints(self): """Generates thermodynamic constraints for the model. See util/constraints.py for detailed explanation of constraints Vi - Vmax * Zi <= 0 delGr - K + K * Zi <= 0 delGr - RT * S.T * ln(x) - S.T @ delGf - delGtransport = 0 Returns: List -- List of themrodynamic constraints """ # First check if thermovariables are added to the model if not self._var_update: self.update_thermo_variables() rxn_constraints = [] # Now add reaction variables and generate remaining constraints for rxn in self.reactions: if rxn.id in self.Exclude_reactions: logging.debug( "Reaction {} is excluded from thermodyanmic analysis". format(rxn.id)) continue # Directionality constraint dir_f, dir_r = directionality(rxn) ind_f, ind_r = delG_indicator(rxn) rxn_constraints.extend([dir_f, dir_r, ind_f, ind_r]) # Create two different constraints for box method and MIQC method # delG constraint for box concentration_term = sum( stoic * metabolite.concentration_variable for metabolite, stoic in iteritems(rxn.metabolites) if metabolite.equilibrator_accession.inchi_key != PROTON_INCHI_KEY) err_term = sum(stoic * metabolite.delG_err_variable for metabolite, stoic in iteritems(rxn.metabolites) if metabolite.equilibrator_accession.inchi_key != PROTON_INCHI_KEY) lhs_forward = rxn.delG_forward - RT * concentration_term - err_term lhs_reverse = rxn.delG_reverse + RT * concentration_term + err_term rhs = rxn.delG_prime + rxn.delG_transport delG_f = self.problem.Constraint( lhs_forward, lb=rhs, ub=rhs, name="delG_{}".format(rxn.forward_variable.name), ) delG_r = self.problem.Constraint( lhs_reverse, lb=-rhs, ub=-rhs, name="delG_{}".format(rxn.reverse_variable.name), ) rxn_constraints.extend([delG_f, delG_r]) return rxn_constraints def update(self): """Adds the generated thermo constaints to model. Checks for duplication""" thermo_constraints = self._generate_constraints() for cons in thermo_constraints: if cons.name not in self.constraints: self.add_cons_vars([cons]) logging.debug("Constraint {} added to the model".format( cons.name)) else: logging.warning( "Constraint {} already in the model, removing previous entry" .format(cons.name)) self.solver.remove(cons.name) self.add_cons_vars([cons]) def optimize(self, solve_method="QC", raise_error=False): """solves the model with given constraints. By default, we try to solve the model with quadratic constraints. Note: Quadratic constraints are supported by Gurobi/Cplex currently. if either of two solvers are not found, one can solve 'box' type MILP problem. :param solve_method: Method to solve the problem, defaults to "MIQC", any other string input leades to solving with box MILP :type solve_method: str, optional :param raise_error: , defaults to False :type raise_error: bool, optional :return: returns solution object :rtype: solution object (refer to Solution class) """ if solve_method.lower() == "qc": if not (optlang.available_solvers["GUROBI"] or optlang.available_solvers["CPLEX"]): logging.warning( "GUROBI/CPLEX not available, Quadratic constraints are not supported by current solver" ) print( "GUROBI/CPLEX not available, Quadratic constraints are not supported by current solver, solving MIP problem instead." ) self.slim_optimize() solution = get_solution(self, raise_error=raise_error) return solution if self.solver.__class__.__module__ == "optlang.gurobi_interface": self.gurobi_interface.optimize() solution = get_legacy_solution(self, solver="gurobi") return solution elif self.solver.__class__.__module__ == "optlang.cplex_interface": solution = self.cplex_interface.solve() solution = get_legacy_solution(self, solver="cplex") return solution elif solve_method.lower() == "mip": self.slim_optimize() solution = get_solution(self, raise_error=raise_error) return solution else: raise ValueError("Solver not understood") def Quadratic_constraint(self): """Adds Quadratic constraint to the model's Gurobi/Cplex Interface. (x-mu).T @ inv(cov) @ (x-mu) <= chi-square Note: This one creates one ellipsoidal constraint for all the metabolites that has non zero or non 'nan' formation energy, irrespective of the magnitude of variance. if the model is infeasible after adding this constraint, refer to util_func.py, find_correlated metabolites to add different ellipsoidal constraints to high variance and normal compounds to avoid possible numerical issues. Unable to retrieve quadratic constraints in Gurobi model, can see the QC when printed. :raises NotImplementedError: Implemented only for Gurobi/Cplex interfaces. :return: [description] :rtype: [type] """ # Pick indices of components present in the current model model_component_indices = [ i for i in range(self.compound_vector_matrix.shape[1]) if np.any(self.compound_vector_matrix[:, i]) ] # Reduced the compound_vector to contain only the non zero entries model_compound_vector = self.compound_vector_matrix[:, model_component_indices] # Now extract the sub covariance matrix containing only the components present in the model component_model_covariance = covariance[:, model_component_indices][ model_component_indices, :] # Now separate the compounds that have variance > 1000 and others to avoid numerical issues high_variance_indices = np.where( np.diag(component_model_covariance) > 1000)[0] low_variance_indices = np.where( np.diag(component_model_covariance) < 1000)[0] # Calculate cholesky matrix for two different covariance matrices if len(low_variance_indices) > 0: small_component_covariance = component_model_covariance[:, low_variance_indices][ low_variance_indices, :] cholesky_small_variance = matrix_decomposition( small_component_covariance) chi2_value_small = stats.chi2.isf( q=0.05, df=cholesky_small_variance.shape[1] ) # Chi-square value to map confidence interval for i in high_variance_indices: zeros_axis = np.zeros((cholesky_small_variance.shape[1], )) cholesky_small_variance = np.insert(cholesky_small_variance, i, zeros_axis, axis=0) metabolite_sphere_small = ( model_compound_vector @ cholesky_small_variance ) # This is a fixed term compound_vector @ cholesky if len(high_variance_indices) > 0: large_component_covariance = component_model_covariance[:, high_variance_indices][ high_variance_indices, :] # Covariance matrix for the high variance components cholesky_large_variance = matrix_decomposition( large_component_covariance) chi2_value_high = stats.chi2.isf( q=0.05, df=cholesky_large_variance.shape[1]) # Insert empty rows for the low_variance_components for i in low_variance_indices: zeros_axis = np.zeros((cholesky_large_variance.shape[1], )) cholesky_large_variance = np.insert(cholesky_large_variance, i, zeros_axis, axis=0) metabolite_sphere_large = ( model_compound_vector @ cholesky_large_variance ) # This is a fixed term compound_vector @ cholesky proton_indices = [ self.metabolites.index(metabolite) for metabolite in self.metabolites if metabolite.equilibrator_accession is not None if metabolite.equilibrator_accession.inchi_key == PROTON_INCHI_KEY ] # Get indices of protons in metabolite list to avoid double correcting them for concentrations if self.solver.__class__.__module__ == "optlang.cplex_interface": from cplex import Cplex, SparsePair, SparseTriple # Instantiate Cplex model cplex_model = Cplex() rand_str = "".join( choices(string.ascii_lowercase + string.digits, k=6)) # write cplex model to mps file in random directory and re read with tempfile.TemporaryDirectory() as td: temp_filename = os.path.join(td, rand_str + ".mps") self.solver.problem.write(temp_filename) cplex_model.read(temp_filename) # Stop printing output in cplex cplex_model.set_log_stream(None) cplex_model.set_error_stream(None) cplex_model.set_warning_stream(None) cplex_model.set_results_stream(None) # Remove the unnecessary variables and constraints remove_vars = [ var for var in cplex_model.variables.get_names() if var.startswith("component_") or var.startswith("dG_err_") ] # Remove error variables remove_constrs = [ cons for cons in cplex_model.linear_constraints.get_names() if cons.startswith("delG_") or cons.startswith("std_dev_") ] # Remove delG constraint and re-add with component variables cplex_model.linear_constraints.delete( remove_constrs) # Removing constr cplex_model.variables.delete(remove_vars) # Removing Vars # QC for small variance components if len(low_variance_indices) > 0: indices_sphere1 = cplex_model.variables.add( names=[ "Sphere1_{}".format(i) for i in range(cholesky_small_variance.shape[1]) ], lb=[-1] * cholesky_small_variance.shape[1], ub=[1] * cholesky_small_variance.shape[1], ) # Adding independent component variables to the model, store the variable indices # Add the Sphere constraint cplex_model.quadratic_constraints.add( quad_expr=SparseTriple( ind1=indices_sphere1, ind2=indices_sphere1, val=len(indices_sphere1) * [1], ), sense="L", rhs=1, name="unit_normal_small_variance", ) else: indices_sphere1 = [ ] # Just to adjust the matrix dimensions later # QC for large variance components if len(high_variance_indices) > 0: indices_sphere2 = cplex_model.variables.add( names=[ "Sphere2_{}".format(i) for i in range(cholesky_large_variance.shape[1]) ], lb=[-1] * cholesky_large_variance.shape[1], ub=[1] * cholesky_large_variance.shape[1], ) # Independent large variance components cplex_model.quadratic_constraints.add( quad_expr=SparseTriple( ind1=indices_sphere2, ind2=indices_sphere2, val=len(indices_sphere2) * [1], ), rhs=1, sense="L", name="unit_normal_high_variance", ) else: indices_sphere2 = [] # Balancing matrix dimensions concentration_variables = [ "lnc_{}".format(metabolite.id) for metabolite in self.metabolites ] # Add the delG constraints for reaction in self.reactions: if reaction.id in self.Exclude_reactions: continue rxn_stoichiometry = reaction.cal_stoichiometric_matrix() rxn_stoichiometry = rxn_stoichiometry[np.newaxis, :] if len(low_variance_indices) > 0: coefficient_matrix_small_variance = ( np.sqrt(chi2_value_small) * rxn_stoichiometry @ metabolite_sphere_small ) # Coefficient array for small variance ellipsoid else: coefficient_matrix_small_variance = np.array(()) if len(high_variance_indices) > 0: coefficient_matrix_large_variance = ( np.sqrt(chi2_value_high) * rxn_stoichiometry @ metabolite_sphere_large ) # Coefficient array for large variance ellipsoid else: coefficient_matrix_large_variance = np.array(()) concentration_coefficients = RT * rxn_stoichiometry concentration_coefficients[0, proton_indices] = 0 coefficients_forward = np.hstack(( np.array((1)), -1 * concentration_coefficients.flatten(), -1 * coefficient_matrix_small_variance.flatten(), -1 * coefficient_matrix_large_variance.flatten(), )) coefficients_reverse = np.hstack(( np.array((1)), concentration_coefficients.flatten(), coefficient_matrix_small_variance.flatten(), coefficient_matrix_large_variance.flatten(), )) variable_order_forward = ( ["dG_{}".format(reaction.forward_variable.name)] + concentration_variables + list(indices_sphere1) + list(indices_sphere2)) variable_order_reverse = ( ["dG_{}".format(reaction.reverse_variable.name)] + concentration_variables + list(indices_sphere1) + list(indices_sphere2)) rhs = reaction.delG_prime + reaction.delG_transport cplex_model.linear_constraints.add( lin_expr=[ SparsePair( ind=variable_order_forward, val=coefficients_forward.tolist(), ) ], senses=["E"], rhs=[rhs], names=["delG_{}".format(reaction.forward_variable.name)], ) # delG constraint for forward reaction cplex_model.linear_constraints.add( lin_expr=[ SparsePair( ind=variable_order_reverse, val=coefficients_reverse.tolist(), ) ], senses=["E"], rhs=[-rhs], names=["delG_{}".format(reaction.reverse_variable.name)], ) # delG constraint for reverse reaction return cplex_model elif self.solver.__class__.__module__ == "optlang.gurobi_interface": from gurobipy import GRB, LinExpr gurobi_model = self.solver.problem.copy() # Remove unnecessary variables and constraints and rebuild appropriate ones remove_vars = [ var for var in gurobi_model.getVars() if var.VarName.startswith("component_") or var.VarName.startswith("dG_err_") ] remove_constrs = [ cons for cons in gurobi_model.getConstrs() if cons.ConstrName.startswith("delG_") or cons.ConstrName.startswith("std_dev_") ] gurobi_model.remove(remove_constrs + remove_vars) # Add sphere variables for smaller set and larger set separately if len(low_variance_indices) > 0: for i in range(cholesky_small_variance.shape[1]): gurobi_model.addVar(lb=-1, ub=1, name="Sphere1_{}".format(i)) gurobi_model.update() sphere1_variables = [ var for var in gurobi_model.getVars() if var.VarName.startswith("Sphere1_") ] gurobi_model.addQConstr( np.sum(np.square(np.array(sphere1_variables))) <= 1, name="unit_normal_small_variance", ) gurobi_model.update() else: sphere1_variables = [] # QC for large variance components if len(high_variance_indices) > 0: for i in range(cholesky_large_variance.shape[1]): gurobi_model.addVar(lb=-1, ub=1, name="Sphere2_{}".format(i)) gurobi_model.update() sphere2_variables = [ var for var in gurobi_model.getVars() if var.VarName.startswith("Sphere2_") ] gurobi_model.addQConstr( np.sum(np.square(np.array(sphere2_variables))) <= 1, name="unit_normal_high_variance", ) gurobi_model.update() else: sphere2_variables = [] # Create a list of metabolite concentration variables concentration_variables = [] for metabolite in self.metabolites: varname = "lnc_{}".format(metabolite.id) conc_var = gurobi_model.getVarByName(varname) concentration_variables.append(conc_var) # Add the delG constraints for reaction in self.reactions: if reaction.id in self.Exclude_reactions: continue rxn_stoichiometry = reaction.cal_stoichiometric_matrix() rxn_stoichiometry = rxn_stoichiometry[np.newaxis, :] if len(low_variance_indices) > 0: coefficient_matrix_small_variance = ( np.sqrt(chi2_value_small) * rxn_stoichiometry @ metabolite_sphere_small ) # Coefficient array for small variance ellipsoid else: coefficient_matrix_small_variance = np.array(()) if len(high_variance_indices) > 0: coefficient_matrix_large_variance = ( np.sqrt(chi2_value_high) * rxn_stoichiometry @ metabolite_sphere_large ) # Coefficient array for large variance ellipsoid else: coefficient_matrix_large_variance = np.array(()) concentration_coefficients = RT * rxn_stoichiometry concentration_coefficients[0, proton_indices] = 0 coefficients_forward = np.hstack(( -1 * concentration_coefficients.flatten(), -1 * coefficient_matrix_small_variance.flatten(), -1 * coefficient_matrix_large_variance.flatten(), )) coefficients_reverse = np.hstack(( concentration_coefficients.flatten(), coefficient_matrix_small_variance.flatten(), coefficient_matrix_large_variance.flatten(), )) variable_order = (concentration_variables + sphere1_variables + sphere2_variables) delG_err_forward = LinExpr(coefficients_forward.tolist(), variable_order) delG_err_reverse = LinExpr(coefficients_reverse.tolist(), variable_order) delG_for_var = gurobi_model.getVarByName("dG_{}".format( reaction.forward_variable.name)) delG_rev_var = gurobi_model.getVarByName("dG_{}".format( reaction.reverse_variable.name)) rhs = reaction.delG_prime + reaction.delG_transport gurobi_model.addConstr( delG_for_var + delG_err_forward, GRB.EQUAL, rhs, name="delG_{}".format(reaction.forward_variable.name), ) gurobi_model.addConstr( delG_rev_var + delG_err_reverse, GRB.EQUAL, -rhs, name="delG_{}".format(reaction.reverse_variable.name), ) gurobi_model.update() return gurobi_model else: raise NotImplementedError("Current solver doesn't support QC") logging.error( "Current solver doesnt support problesm of type MIQC") def calculate_S_matrix(self): """Calculates the stoichiometric matrix (metabolites * Reactions) Returns: Tuple -- Tuple of reaction order, np.ndarray of stoichiometric matrix """ n_reactions = len(self.reactions) n_metabolites = len(self.metabolites) S_matrix = np.zeros((2 * n_reactions, n_metabolites)) reaction_index = 0 rxn_order = [] for reaction in self.reactions: rxn_order.append(reaction.forward_variable.name) rxn_order.append(reaction.reverse_variable.name) for metabolite, stoic in iteritems(reaction.metabolites): S_matrix[reaction_index, self.metabolites.index(metabolite)] = stoic S_matrix[reaction_index + 1, self.metabolites.index(metabolite)] = -stoic reaction_index = reaction_index + 2 S = np.transpose(S_matrix) return rxn_order, S def concentration_ratio_constraints(self, ratio_metabolites, ratio_lb, ratio_ub): """Function to add metabolite concentration ratio constraints to the model. E.g. ratio of redox pairs Arguments: ratio_metabolites {Tuple} -- Tuple of metabolite names to which we have concentration ratios in the same order ratio_lb {List} -- Lower bound of the ratio ratio_ub {List} -- Upper bound on the ratio """ for i in range(len(ratio_metabolites)): ratio_met1 = self.metabolites.get_by_id(ratio_metabolites[i][0]) ratio_met2 = self.metabolites.get_by_id(ratio_metabolites[i][1]) ratio_constraint = self.problem.Constraint( 1 * ratio_met1.concentration_variable - 1 * ratio_met2.concentration_variable, lb=ratio_lb[i], ub=ratio_ub[i], ) self.add_cons_vars(ratio_constraint) def cal_problematic_rxns(self): """Reactions which can't be included in thermodynamic analysis reactions involving problematic metabolites Returns: List -- List of reactions excluded, this combined with 'Exclude_list' gives us 'Exclude_reactions' """ problematic_rxns = [] for met in self.metabolites: if met.is_exclude: problematic_rxns.append(met.reactions) if len(problematic_rxns) > 0: problematic_rxns = frozenset.union(*problematic_rxns) problems = [i.id for i in problematic_rxns] return problems else: return [] def export_MIP_matrix(self): """Creates matrices structure of the MILP problem. Quadratic constraint is not exported. :return: lhs- lhs matrix representing all constraints rhs - rhs matrix var_names - variable name lb, ub- lower, upper bounds of variables cons_sense - constraint sense (eg: equal, less equal etc) :rtype: Tuple """ rxn_var, S = self.calculate_S_matrix() n_mets, rxn_tot = np.shape(S) n_rxn_Excl = len(self.Exclude_reactions) n_core_rxns = rxn_tot - n_rxn_Excl # Create S.v = 0 and expand for other constraints mass_balance = np.concatenate( (S, np.zeros((n_mets, 2 * n_core_rxns + 2 * n_mets))), axis=1) rhs_mass_bal = [0] * n_mets sense_mass_bal = ["E"] * n_mets indicators, delGr, concentration, formation, rhs_delG, sense = ( [], [], [], [], [], [], ) # Intialize 6 constraints delG_cons_matrix = np.zeros( (2 * n_core_rxns, 3 * n_core_rxns + 2 * n_mets)) for reaction in self.reactions: if reaction.id in self.Exclude_reactions: continue S_rxn = reaction.S_matrix rxn_index = rxn_var.index(reaction.id) # vi-vmax*zi <= 0 delG_cons_matrix[rxn_index, rxn_index] = 1 delG_cons_matrix[rxn_index + 1, rxn_index] = 1 delG_cons_matrix[rxn_index, rxn_index + n_core_rxns] = -Vmax delG_cons_matrix[rxn_index + 1, rxn_index + n_core_rxns] = -Vmax # delG - k*zi <= k delG_cons_matrix[rxn_index + 2, 2 * n_core_rxns + rxn_index] = 1 delG_cons_matrix[rxn_index + 3, 2 * n_core_rxns + rxn_index] = 1 delG_cons_matrix[rxn_index + 2, n_core_rxns + rxn_index] = -K delG_cons_matrix[rxn_index + 3, n_core_rxns + rxn_index] = -K # delG - s.T RT ln(x) - S.T delGf = 0 delG_cons_matrix[rxn_index + 4, 2 * n_core_rxns + rxn_index] = 1 delG_cons_matrix[rxn_index + 5, 2 * n_core_rxns + rxn_index] = 1 delG_cons_matrix[rxn_index + 4, 3 * n_core_rxns:3 * n_core_rxns + n_mets] = -S_rxn.T delG_cons_matrix[rxn_index + 5, 3 * n_core_rxns:3 * n_core_rxns + n_mets] = S_rxn.T delG_cons_matrix[rxn_index + 4, 3 * n_core_rxns + n_mets:3 * n_core_rxns + 2 * n_mets] = -S_rxn.T delG_cons_matrix[rxn_index + 5, 3 * n_core_rxns + n_mets:3 * n_core_rxns + 2 * n_mets] = S_rxn.T indicators.extend([ reaction.indicator_forward.name, reaction.indicator_reverse.name ]) delGr.extend( [reaction.delG_forward.name, reaction.delG_reverse.name]) rhs_delG.extend([0, K, 0]) sense.extend(["L", "L", "E"]) lb_conc, lb_formation, ub_conc, ub_formation = ([], [], [], []) for met in self.metabolites: concentration.append(met.concentration_variable.name) formation.append(met.compound_variable.name) lb_conc.append(met.concentration_variable.lb) ub_conc.append(met.concentration_variable.ub) lb_formation.append(met.compound_variable.lb) ub_formation.append(met.compound_variable.ub) var_names = rxn_var + indicators + delGr + concentration + formation lhs = np.concatenate((mass_balance, delG_cons_matrix), axis=0) rhs = rhs_mass_bal + rhs_delG cons_sense = sense_mass_bal + sense lb = ([-1000] * rxn_tot + [0] * n_core_rxns + [-1e5] * n_core_rxns + lb_conc + lb_formation) ub = ([1000] * rxn_tot + [1] * n_core_rxns + [1e5] * n_core_rxns + ub_conc + ub_formation) return (lhs, rhs, var_names, np.array(lb), np.array(ub), cons_sense)
class Model(Object): """Class representation for a cobra model Parameters ---------- id_or_model : Model, string Either an existing Model object in which case a new model object is instantiated with the same properties as the original model, or a the identifier to associate with the model as a string. name : string Human readable name for the model Attributes ---------- reactions : DictList A DictList where the key is the reaction identifier and the value a Reaction metabolites : DictList A DictList where the key is the metabolite identifier and the value a Metabolite genes : DictList A DictList where the key is the gene identifier and the value a Gene solution : Solution The last obtained solution from optimizing the model. """ def __setstate__(self, state): """Make sure all cobra.Objects in the model point to the model. """ self.__dict__.update(state) for y in ['reactions', 'genes', 'metabolites']: for x in getattr(self, y): x._model = self if y == 'reactions': x._reset_var_cache() if not hasattr(self, "name"): self.name = None def __getstate__(self): """Get state for serialization. Ensures that the context stack is cleared prior to serialization, since partial functions cannot be pickled reliably. """ odict = self.__dict__.copy() odict['_contexts'] = [] return odict def __init__(self, id_or_model=None, name=None): if isinstance(id_or_model, Model): Object.__init__(self, name=name) self.__setstate__(id_or_model.__dict__) if not hasattr(self, "name"): self.name = None self._solver = id_or_model.solver else: Object.__init__(self, id_or_model, name=name) self._trimmed = False self._trimmed_genes = [] self._trimmed_reactions = {} self.genes = DictList() self.reactions = DictList() # A list of cobra.Reactions self.metabolites = DictList() # A list of cobra.Metabolites # genes based on their ids {Gene.id: Gene} self.compartments = dict() self._contexts = [] # from cameo ... # if not hasattr(self, '_solver'): # backwards compatibility # with older cobrapy pickles? interface = solvers[get_solver_name()] self._solver = interface.Model() self._solver.objective = interface.Objective(S.Zero) self._populate_solver(self.reactions, self.metabolites) @property def solver(self): """Get or set the attached solver instance. The associated the solver object, which manages the interaction with the associated solver, e.g. glpk. This property is useful for accessing the optimization problem directly and to define additional non-metabolic constraints. Examples -------- >>> import cobra.test >>> model = cobra.test.create_test_model("textbook") >>> new = model.problem.Constraint(model.objective.expression, >>> lb=0.99) >>> model.solver.add(new) """ return self._solver @solver.setter @resettable def solver(self, value): not_valid_interface = SolverNotFound( '%s is not a valid solver interface. Pick from %s, or specify an ' 'optlang interface (e.g. optlang.glpk_interface).' % (value, list(solvers.keys()))) if isinstance(value, six.string_types): try: interface = solvers[interface_to_str(value)] except KeyError: raise not_valid_interface elif isinstance(value, types.ModuleType) and hasattr(value, 'Model'): interface = value elif isinstance(value, optlang.interface.Model): interface = value.interface else: raise not_valid_interface # Do nothing if the solver did not change if self.problem == interface: return for reaction in self.reactions: reaction._reset_var_cache() self._solver = interface.Model.clone(self._solver) @property def description(self): warn("description deprecated", DeprecationWarning) return self.name if self.name is not None else "" @description.setter def description(self, value): self.name = value warn("description deprecated", DeprecationWarning) def get_metabolite_compartments(self): """Return all metabolites' compartments.""" return { met.compartment for met in self.metabolites if met.compartment is not None } @property def medium(self): def is_active(reaction): """Determine if a boundary reaction permits flux towards creating metabolites """ return ((bool(reaction.products) and (reaction.upper_bound > 0)) or (bool(reaction.reactants) and (reaction.lower_bound < 0))) def get_active_bound(reaction): """For an active boundary reaction, return the relevant bound""" if reaction.reactants: return -reaction.lower_bound elif reaction.products: return reaction.upper_bound return { rxn.id: get_active_bound(rxn) for rxn in self.exchanges if is_active(rxn) } @medium.setter def medium(self, medium): """Get or set the constraints on the model exchanges. `model.medium` returns a dictionary of the bounds for each of the boundary reactions, in the form of `{rxn_id: bound}`, where `bound` specifies the absolute value of the bound in direction of metabolite creation (i.e., lower_bound for `met <--`, upper_bound for `met -->`) Parameters ---------- medium: dictionary-like The medium to initialize. medium should be a dictionary defining `{rxn_id: bound}` pairs. """ def set_active_bound(reaction, bound): if reaction.reactants: reaction.lower_bound = -bound elif reaction.products: reaction.upper_bound = bound # Set the given media bounds media_rxns = list() for rxn_id, bound in iteritems(medium): rxn = self.reactions.get_by_id(rxn_id) media_rxns.append(rxn) set_active_bound(rxn, bound) boundary_rxns = set(self.exchanges) media_rxns = set(media_rxns) # Turn off reactions not present in media for rxn in (boundary_rxns - media_rxns): set_active_bound(rxn, 0) def __add__(self, other_model): """Add the content of another model to this model (+). The model is copied as a new object, with a new model identifier, and copies of all the reactions in the other model are added to this model. The objective is the sum of the objective expressions for the two models. """ warn('use model.merge instead', DeprecationWarning) return self.merge(other_model, objective='sum', inplace=False) def __iadd__(self, other_model): """Incrementally add the content of another model to this model (+=). Copies of all the reactions in the other model are added to this model. The objective is the sum of the objective expressions for the two models. """ warn('use model.merge instead', DeprecationWarning) return self.merge(other_model, objective='sum', inplace=True) def copy(self): """Provides a partial 'deepcopy' of the Model. All of the Metabolite, Gene, and Reaction objects are created anew but in a faster fashion than deepcopy """ new = self.__class__() do_not_copy_by_ref = { "metabolites", "reactions", "genes", "notes", "annotation" } for attr in self.__dict__: if attr not in do_not_copy_by_ref: new.__dict__[attr] = self.__dict__[attr] new.notes = deepcopy(self.notes) new.annotation = deepcopy(self.annotation) new.metabolites = DictList() do_not_copy_by_ref = {"_reaction", "_model"} for metabolite in self.metabolites: new_met = metabolite.__class__() for attr, value in iteritems(metabolite.__dict__): if attr not in do_not_copy_by_ref: new_met.__dict__[attr] = copy( value) if attr == "formula" else value new_met._model = new new.metabolites.append(new_met) new.genes = DictList() for gene in self.genes: new_gene = gene.__class__(None) for attr, value in iteritems(gene.__dict__): if attr not in do_not_copy_by_ref: new_gene.__dict__[attr] = copy( value) if attr == "formula" else value new_gene._model = new new.genes.append(new_gene) new.reactions = DictList() do_not_copy_by_ref = {"_model", "_metabolites", "_genes"} for reaction in self.reactions: new_reaction = reaction.__class__() for attr, value in iteritems(reaction.__dict__): if attr not in do_not_copy_by_ref: new_reaction.__dict__[attr] = copy(value) new_reaction._model = new new.reactions.append(new_reaction) # update awareness for metabolite, stoic in iteritems(reaction._metabolites): new_met = new.metabolites.get_by_id(metabolite.id) new_reaction._metabolites[new_met] = stoic new_met._reaction.add(new_reaction) for gene in reaction._genes: new_gene = new.genes.get_by_id(gene.id) new_reaction._genes.add(new_gene) new_gene._reaction.add(new_reaction) for reaction in new.reactions: reaction._reset_var_cache() try: new._solver = deepcopy(self.solver) # Cplex has an issue with deep copies except Exception: # pragma: no cover new._solver = copy(self.solver) # pragma: no cover # it doesn't make sense to retain the context of a copied model so # assign a new empty context new._contexts = list() return new def add_metabolites(self, metabolite_list): """Will add a list of metabolites to the model object and add new constraints accordingly. The change is reverted upon exit when using the model as a context. Parameters ---------- metabolite_list : A list of `cobra.core.Metabolite` objects """ if not hasattr(metabolite_list, '__iter__'): metabolite_list = [metabolite_list] if len(metabolite_list) == 0: return None # First check whether the metabolites exist in the model metabolite_list = [ x for x in metabolite_list if x.id not in self.metabolites ] bad_ids = [ m for m in metabolite_list if not isinstance(m.id, string_types) or len(m.id) < 1 ] if len(bad_ids) != 0: raise ValueError('invalid identifiers in {}'.format(repr(bad_ids))) for x in metabolite_list: x._model = self self.metabolites += metabolite_list # from cameo ... to_add = [] for met in metabolite_list: if met.id not in self.constraints: constraint = self.problem.Constraint(S.Zero, name=met.id, lb=0, ub=0) to_add += [constraint] self.add_cons_vars(to_add) context = get_context(self) if context: context(partial(self.metabolites.__isub__, metabolite_list)) for x in metabolite_list: # Do we care? context(partial(setattr, x, '_model', None)) def remove_metabolites(self, metabolite_list, destructive=False): """Remove a list of metabolites from the the object. The change is reverted upon exit when using the model as a context. Parameters ---------- metabolite_list : list A list with `cobra.Metabolite` objects as elements. destructive : bool If False then the metabolite is removed from all associated reactions. If True then all associated reactions are removed from the Model. """ if not hasattr(metabolite_list, '__iter__'): metabolite_list = [metabolite_list] # Make sure metabolites exist in model metabolite_list = [ x for x in metabolite_list if x.id in self.metabolites ] for x in metabolite_list: x._model = None if not destructive: for the_reaction in list(x._reaction): the_coefficient = the_reaction._metabolites[x] the_reaction.subtract_metabolites({x: the_coefficient}) else: for x in list(x._reaction): x.remove_from_model() self.metabolites -= metabolite_list to_remove = [self.solver.constraints[m.id] for m in metabolite_list] self.remove_cons_vars(to_remove) context = get_context(self) if context: context(partial(self.metabolites.__iadd__, metabolite_list)) for x in metabolite_list: context(partial(setattr, x, '_model', self)) def add_reaction(self, reaction): """Will add a cobra.Reaction object to the model, if reaction.id is not in self.reactions. Parameters ---------- reaction : cobra.Reaction The reaction to add Deprecated (0.6). Use `~cobra.Model.add_reactions` instead """ warn("add_reaction deprecated. Use add_reactions instead", DeprecationWarning) self.add_reactions([reaction]) def add_boundary(self, metabolite, type="exchange", reaction_id=None, lb=None, ub=1000.0): """Add a boundary reaction for a given metabolite. There are three different types of pre-defined boundary reactions: exchange, demand, and sink reactions. An exchange reaction is a reversible, imbalanced reaction that adds to or removes an extracellular metabolite from the extracellular compartment. A demand reaction is an irreversible reaction that consumes an intracellular metabolite. A sink is similar to an exchange but specifically for intracellular metabolites. If you set the reaction `type` to something else, you must specify the desired identifier of the created reaction along with its upper and lower bound. The name will be given by the metabolite name and the given `type`. Parameters ---------- metabolite : cobra.Metabolite Any given metabolite. The compartment is not checked but you are encouraged to stick to the definition of exchanges and sinks. type : str, {"exchange", "demand", "sink"} Using one of the pre-defined reaction types is easiest. If you want to create your own kind of boundary reaction choose any other string, e.g., 'my-boundary'. reaction_id : str, optional The ID of the resulting reaction. Only used for custom reactions. lb : float, optional The lower bound of the resulting reaction. Only used for custom reactions. ub : float, optional The upper bound of the resulting reaction. For the pre-defined reactions this default value determines all bounds. Returns ------- cobra.Reaction The created boundary reaction. Examples -------- >>> import cobra.test >>> model = cobra.test.create_test_model("textbook") >>> demand = model.add_boundary(model.metabolites.atp_c, type="demand") >>> demand.id 'DM_atp_c' >>> demand.name 'ATP demand' >>> demand.bounds (0, 1000.0) >>> demand.build_reaction_string() 'atp_c --> ' """ types = dict(exchange=("EX", -ub, ub), demand=("DM", 0, ub), sink=("SK", -ub, ub)) if type in types: prefix, lb, ub = types[type] reaction_id = "{}_{}".format(prefix, metabolite.id) if reaction_id in self.reactions: raise ValueError('boundary %s already exists' % reaction_id) name = "{} {}".format(metabolite.name, type) rxn = Reaction(id=reaction_id, name=name, lower_bound=lb, upper_bound=ub) rxn.add_metabolites({metabolite: -1}) self.add_reactions([rxn]) return rxn def add_reactions(self, reaction_list): """Add reactions to the model. Reactions with identifiers identical to a reaction already in the model are ignored. The change is reverted upon exit when using the model as a context. Parameters ---------- reaction_list : list A list of `cobra.Reaction` objects """ try: reaction_list = DictList(reaction_list) except TypeError: reaction_list = DictList([reaction_list]) # First check whether the metabolites exist in the model existing = [rxn for rxn in reaction_list if rxn.id in self.reactions] for rxn in existing: LOGGER.info('skip adding reaction %s as already existing', rxn.id) reaction_list = [ rxn for rxn in reaction_list if rxn.id not in existing ] context = get_context(self) # Add reactions. Also take care of genes and metabolites in the loop for reaction in reaction_list: reaction._reset_var_cache() reaction._model = self # the reaction now points to the model # keys() is necessary because the dict will be modified during # the loop for metabolite in list(reaction._metabolites.keys()): # if the metabolite is not in the model, add it # should we be adding a copy instead. if metabolite not in self.metabolites: self.add_metabolites(metabolite) # A copy of the metabolite exists in the model, the reaction # needs to point to the metabolite in the model. else: stoichiometry = reaction._metabolites.pop(metabolite) model_metabolite = self.metabolites.get_by_id( metabolite.id) reaction._metabolites[model_metabolite] = stoichiometry model_metabolite._reaction.add(reaction) if context: context( partial(model_metabolite._reaction.remove, reaction)) for gene in list(reaction._genes): # If the gene is not in the model, add it if not self.genes.has_id(gene.id): self.genes += [gene] gene._model = self if context: # Remove the gene later context(partial(self.genes.__isub__, [gene])) context(partial(setattr, gene, '_model', None)) # Otherwise, make the gene point to the one in the model else: model_gene = self.genes.get_by_id(gene.id) if model_gene is not gene: reaction._dissociate_gene(gene) reaction._associate_gene(model_gene) self.reactions += reaction_list if context: context(partial(self.reactions.__isub__, reaction_list)) # from cameo ... self._populate_solver(reaction_list) def remove_reactions(self, reactions, remove_orphans=False): """Remove reactions from the model. The change is reverted upon exit when using the model as a context. Parameters ---------- reactions : list A list with reactions (`cobra.Reaction`), or their id's, to remove remove_orphans : bool Remove orphaned genes and metabolites from the model as well """ if isinstance(reactions, string_types) or hasattr(reactions, "id"): warn("need to pass in a list") reactions = [reactions] context = get_context(self) for reaction in reactions: # Make sure the reaction is in the model try: reaction = self.reactions[self.reactions.index(reaction)] except ValueError: warn('%s not in %s' % (reaction, self)) else: forward = reaction.forward_variable reverse = reaction.reverse_variable self.remove_cons_vars([forward, reverse]) self.reactions.remove(reaction) reaction._model = None if context: context(reaction._reset_var_cache) context(partial(setattr, reaction, '_model', self)) context(partial(self.reactions.add, reaction)) for met in reaction._metabolites: if reaction in met._reaction: met._reaction.remove(reaction) if context: context(partial(met._reaction.add, reaction)) if remove_orphans and len(met._reaction) == 0: self.remove_metabolites(met) for gene in reaction._genes: if reaction in gene._reaction: gene._reaction.remove(reaction) if context: context(partial(gene._reaction.add, reaction)) if remove_orphans and len(gene._reaction) == 0: self.genes.remove(gene) if context: context(partial(self.genes.add, gene)) def add_cons_vars(self, what, **kwargs): """Add constraints and variables to the model's mathematical problem. Useful for variables and constraints that can not be expressed with reactions and simple lower and upper bounds. Additions are reversed upon exit if the model itself is used as context. Parameters ---------- what : list or tuple of optlang variables or constraints. The variables or constraints to add to the model. Must be of class `optlang.interface.Variable` or `optlang.interface.Constraint`. **kwargs : keyword arguments Passed to solver.add() """ add_cons_vars_to_problem(self, what, **kwargs) def remove_cons_vars(self, what): """Remove variables and constraints from the model's mathematical problem. Remove variables and constraints that were added directly to the model's underlying mathematical problem. Removals are reversed upon exit if the model itself is used as context. Parameters ---------- what : list or tuple of optlang variables or constraints. The variables or constraints to add to the model. Must be of class `optlang.interface.Variable` or `optlang.interface.Constraint`. """ remove_cons_vars_from_problem(self, what) @property def problem(self): """The interface to the model's underlying mathematical problem. Solutions to cobra models are obtained by formulating a mathematical problem and solving it. Cobrapy uses the optlang package to accomplish that and with this property you can get access to the problem interface directly. Returns ------- optlang.interface The problem interface that defines methods for interacting with the problem and associated solver directly. """ return self.solver.interface @property def variables(self): """The mathematical variables in the cobra model. In a cobra model, most variables are reactions. However, for specific use cases, it may also be useful to have other types of variables. This property defines all variables currently associated with the model's problem. Returns ------- optlang.container.Container A container with all associated variables. """ return self.solver.variables @property def constraints(self): """The constraints in the cobra model. In a cobra model, most constraints are metabolites and their stoichiometries. However, for specific use cases, it may also be useful to have other types of constraints. This property defines all constraints currently associated with the model's problem. Returns ------- optlang.container.Container A container with all associated constraints. """ return self.solver.constraints @property def exchanges(self): """Exchange reactions in model. Reactions that either don't have products or substrates. """ return [rxn for rxn in self.reactions if rxn.boundary] def _populate_solver(self, reaction_list, metabolite_list=None): """Populate attached solver with constraints and variables that model the provided reactions. """ constraint_terms = AutoVivification() to_add = [] if metabolite_list is not None: for met in metabolite_list: to_add += [ self.problem.Constraint(S.Zero, name=met.id, lb=0, ub=0) ] self.add_cons_vars(to_add) for reaction in reaction_list: reverse_lb, reverse_ub, forward_lb, forward_ub = \ separate_forward_and_reverse_bounds(*reaction.bounds) forward_variable = self.problem.Variable(reaction.id, lb=forward_lb, ub=forward_ub) reverse_variable = self.problem.Variable(reaction.reverse_id, lb=reverse_lb, ub=reverse_ub) self.add_cons_vars([forward_variable, reverse_variable]) self.solver.update() for metabolite, coeff in six.iteritems(reaction.metabolites): if metabolite.id in self.constraints: constraint = self.constraints[metabolite.id] else: constraint = self.problem.Constraint(S.Zero, name=metabolite.id, lb=0, ub=0) self.add_cons_vars(constraint, sloppy=True) constraint_terms[constraint][forward_variable] = coeff constraint_terms[constraint][reverse_variable] = -coeff self.solver.update() for constraint, terms in six.iteritems(constraint_terms): constraint.set_linear_coefficients(terms) def to_array_based_model(self, deepcopy_model=False, **kwargs): """Makes a `cobra.core.ArrayBasedModel` from a cobra.Model which may be used to perform linear algebra operations with the stoichiometric matrix. Deprecated (0.6). Use `cobra.util.array.create_stoichiometric_matrix` instead. Parameters ---------- deepcopy_model : bool If False then the ArrayBasedModel points to the Model """ warn( "to_array_based_model is deprecated. " "use cobra.util.array.create_stoichiometric_matrix instead", DeprecationWarning) from cobra.core.arraybasedmodel import ArrayBasedModel return ArrayBasedModel(self, deepcopy_model=deepcopy_model, **kwargs) def slim_optimize(self, error_value=float('nan'), message=None): """Optimize model without creating a solution object. Creating a full solution object implies fetching shadow prices and flux values for all reactions and metabolites from the solver object. This necessarily takes some time and in cases where only one or two values are of interest, it is recommended to instead use this function which does not create a solution object returning only the value of the objective. Note however that the `optimize()` function uses efficient means to fetch values so if you need fluxes/shadow prices for more than say 4 reactions/metabolites, then the total speed increase of `slim_optimize` versus `optimize` is expected to be small or even negative depending on how you fetch the values after optimization. Parameters ---------- error_value : float, None The value to return if optimization failed due to e.g. infeasibility. If None, raise `OptimizationError` if the optimization fails. message : string Error message to use if the model optimization did not succeed. Returns ------- float The objective value. """ self.solver.optimize() if self.solver.status == optlang.interface.OPTIMAL: return self.solver.objective.value elif error_value is not None: return error_value else: assert_optimal(self, message) def optimize(self, objective_sense=None, raise_error=False, **kwargs): """ Optimize the model using flux balance analysis. Parameters ---------- objective_sense : {None, 'maximize' 'minimize'}, optional Whether fluxes should be maximized or minimized. In case of None, the previous direction is used. raise_error : bool If true, raise an OptimizationError if solver status is not optimal. solver : {None, 'glpk', 'cglpk', 'gurobi', 'cplex'}, optional If unspecified will use the currently defined `self.solver` otherwise it will use the given solver and update the attribute. quadratic_component : {None, scipy.sparse.dok_matrix}, optional The dimensions should be (n, n) where n is the number of reactions. This sets the quadratic component (Q) of the objective coefficient, adding :math:`\\frac{1}{2} v^T \cdot Q \cdot v` to the objective. Ignored for optlang based solvers. tolerance_feasibility : float Solver tolerance for feasibility. Ignored for optlang based solvers tolerance_markowitz : float Solver threshold during pivot. Ignored for optlang based solvers time_limit : float Maximum solver time (in seconds). Ignored for optlang based solvers Notes ----- Only the most commonly used parameters are presented here. Additional parameters for cobra.solvers may be available and specified with the appropriate keyword argument. """ legacy, solver = choose_solver(self, solver=kwargs.get("solver")) original_direction = self.objective.direction if legacy: if objective_sense is None: objective_sense = { "max": "maximize", "min": "minimize" }[original_direction] solution = optimize(self, objective_sense=objective_sense, **kwargs) check_solver_status(solution.status, raise_error=raise_error) return solution self.solver = solver self.objective.direction = \ {"maximize": "max", "minimize": "min"}.get( objective_sense, original_direction) self.slim_optimize() solution = get_solution(self, raise_error=raise_error) self.objective.direction = original_direction return solution def repair(self, rebuild_index=True, rebuild_relationships=True): """Update all indexes and pointers in a model Parameters ---------- rebuild_index : bool rebuild the indices kept in reactions, metabolites and genes rebuild_relationships : bool reset all associations between genes, metabolites, model and then re-add them. """ if rebuild_index: # DictList indexes self.reactions._generate_index() self.metabolites._generate_index() self.genes._generate_index() if rebuild_relationships: for met in self.metabolites: met._reaction.clear() for gene in self.genes: gene._reaction.clear() for rxn in self.reactions: for met in rxn._metabolites: met._reaction.add(rxn) for gene in rxn._genes: gene._reaction.add(rxn) # point _model to self for l in (self.reactions, self.genes, self.metabolites): for e in l: e._model = self @property def objective(self): """Get or set the solver objective Before introduction of the optlang based problems, this function returned the objective reactions as a list. With optlang, the objective is not limited a simple linear summation of individual reaction fluxes, making that return value ambiguous. Henceforth, use `cobra.util.solver.linear_reaction_coefficients` to get a dictionary of reactions with their linear coefficients (empty if there are none) The set value can be dictionary (reactions as keys, linear coefficients as values), string (reaction identifier), int (reaction index), Reaction or problem.Objective or sympy expression directly interpreted as objectives. When using a `HistoryManager` context, this attribute can be set temporarily, reversed when the exiting the context. """ return self.solver.objective @objective.setter 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 summary(self, solution=None, threshold=1E-8, fva=None, floatfmt='.3g'): """Print a summary of the input and output fluxes of the model. This method requires the model to have been previously solved. Parameters ---------- solution: cobra.core.Solution A previously solved model solution to use for generating the summary. If none provided (default), the summary method will resolve the model. Note that the solution object must match the model, i.e., changes to the model such as changed bounds, added or removed reactions are not taken into account by this method. threshold : float tolerance for determining if a flux is zero (not printed) fva : int or None Whether or not to calculate and report flux variability in the output summary floatfmt : string format method for floats, passed to tabulate. Default is '.3g'. """ from cobra.flux_analysis.summary import model_summary return model_summary(self, solution=solution, threshold=threshold, fva=fva, floatfmt=floatfmt) def __enter__(self): """Record all future changes to the model, undoing them when a call to __exit__ is received""" # Create a new context and add it to the stack try: self._contexts.append(HistoryManager()) except AttributeError: self._contexts = [HistoryManager()] return self def __exit__(self, type, value, traceback): """Pop the top context manager and trigger the undo functions""" context = self._contexts.pop() context.reset() def merge(self, right, prefix_existing=None, inplace=True, objective='left'): """Merge two models to create a model with the reactions from both models. Custom constraints and variables from right models are also copied to left model, however note that, constraints and variables are assumed to be the same if they have the same name. right : cobra.Model The model to add reactions from prefix_existing : string Prefix the reaction identifier in the right that already exist in the left model with this string. inplace : bool Add reactions from right directly to left model object. Otherwise, create a new model leaving the left model untouched. When done within the model as context, changes to the models are reverted upon exit. objective : string One of 'left', 'right' or 'sum' for setting the objective of the resulting model to that of the corresponding model or the sum of both. """ if inplace: new_model = self else: new_model = self.copy() new_model.id = '{}_{}'.format(self.id, right.id) new_reactions = deepcopy(right.reactions) if prefix_existing is not None: existing = new_reactions.query( lambda rxn: rxn.id in self.reactions) for reaction in existing: reaction.id = '{}{}'.format(prefix_existing, reaction.id) new_model.add_reactions(new_reactions) interface = new_model.problem new_vars = [ interface.Variable.clone(v) for v in right.variables if v.name not in new_model.variables ] new_model.add_cons_vars(new_vars) new_cons = [ interface.Constraint.clone(c, model=new_model.solver) for c in right.constraints if c.name not in new_model.constraints ] new_model.add_cons_vars(new_cons, sloppy=True) new_model.objective = dict(left=self.objective, right=right.objective, sum=self.objective.expression + right.objective.expression)[objective] return new_model def _repr_html_(self): return """ <table> <tr> <td><strong>Name</strong></td> <td>{name}</td> </tr><tr> <td><strong>Memory address</strong></td> <td>{address}</td> </tr><tr> <td><strong>Number of metabolites</strong></td> <td>{num_metabolites}</td> </tr><tr> <td><strong>Number of reactions</strong></td> <td>{num_reactions}</td> </tr><tr> <td><strong>Objective expression</strong></td> <td>{objective}</td> </tr><tr> <td><strong>Compartments</strong></td> <td>{compartments}</td> </tr> </table>""".format(name=self.id, address='0x0%x' % id(self), num_metabolites=len(self.metabolites), num_reactions=len(self.reactions), objective=str(self.objective.expression), compartments=", ".join( v if v else k for k, v in iteritems(self.compartments)))
class Model(Object): """Class representation for a cobra model Parameters ---------- id_or_model : Model, string Either an existing Model object in which case a new model object is instantiated with the same properties as the original model, or a the identifier to associate with the model as a string. name : string Human readable name for the model Attributes ---------- reactions : DictList A DictList where the key is the reaction identifier and the value a Reaction metabolites : DictList A DictList where the key is the metabolite identifier and the value a Metabolite genes : DictList A DictList where the key is the gene identifier and the value a Gene solution : Solution The last obtained solution from optimizing the model. """ def __setstate__(self, state): """Make sure all cobra.Objects in the model point to the model. """ self.__dict__.update(state) for y in ['reactions', 'genes', 'metabolites']: for x in getattr(self, y): x._model = self if not hasattr(self, "name"): self.name = None def __getstate__(self): """Get state for serialization. Ensures that the context stack is cleared prior to serialization, since partial functions cannot be pickled reliably. """ odict = self.__dict__.copy() odict['_contexts'] = [] return odict def __init__(self, id_or_model=None, name=None): if isinstance(id_or_model, Model): Object.__init__(self, name=name) self.__setstate__(id_or_model.__dict__) if not hasattr(self, "name"): self.name = None self._solver = id_or_model.solver else: Object.__init__(self, id_or_model, name=name) self._trimmed = False self._trimmed_genes = [] self._trimmed_reactions = {} self.genes = DictList() self.reactions = DictList() # A list of cobra.Reactions self.metabolites = DictList() # A list of cobra.Metabolites # genes based on their ids {Gene.id: Gene} self._compartments = dict() self._contexts = [] # from cameo ... # if not hasattr(self, '_solver'): # backwards compatibility # with older cobrapy pickles? interface = solvers[get_solver_name()] self._solver = interface.Model() self._solver.objective = interface.Objective(Zero) self._populate_solver(self.reactions, self.metabolites) @property def solver(self): """Get or set the attached solver instance. The associated the solver object, which manages the interaction with the associated solver, e.g. glpk. This property is useful for accessing the optimization problem directly and to define additional non-metabolic constraints. Examples -------- >>> import cobra.test >>> model = cobra.test.create_test_model("textbook") >>> new = model.problem.Constraint(model.objective.expression, >>> lb=0.99) >>> model.solver.add(new) """ return self._solver @solver.setter @resettable def solver(self, value): not_valid_interface = SolverNotFound( '%s is not a valid solver interface. Pick from %s.' % ( value, list(solvers))) if isinstance(value, six.string_types): try: interface = solvers[interface_to_str(value)] except KeyError: raise not_valid_interface elif isinstance(value, types.ModuleType) and hasattr(value, 'Model'): interface = value elif isinstance(value, optlang.interface.Model): interface = value.interface else: raise not_valid_interface # Do nothing if the solver did not change if self.problem == interface: return self._solver = interface.Model.clone(self._solver) @property def description(self): warn("description deprecated", DeprecationWarning) return self.name if self.name is not None else "" @description.setter def description(self, value): self.name = value warn("description deprecated", DeprecationWarning) def get_metabolite_compartments(self): """Return all metabolites' compartments.""" warn('use Model.compartments instead', DeprecationWarning) return {met.compartment for met in self.metabolites if met.compartment is not None} @property def compartments(self): return {met.compartment: self._compartments.get(met.compartment, '') for met in self.metabolites if met.compartment is not None} @compartments.setter def compartments(self, value): """Get or set the dictionary of current compartment descriptions. Assigning a dictionary to this property updates the model's dictionary of compartment descriptions with the new values. Parameters ---------- value : dict Dictionary mapping compartments abbreviations to full names. Examples -------- >>> import cobra.test >>> model = cobra.test.create_test_model("textbook") >>> model.compartments = {'c': 'the cytosol'} {'c': 'the cytosol', 'e': 'extracellular'} """ self._compartments.update(value) @property def medium(self): def is_active(reaction): """Determine if a boundary reaction permits flux towards creating metabolites """ return ((bool(reaction.products) and (reaction.upper_bound > 0)) or (bool(reaction.reactants) and (reaction.lower_bound < 0))) def get_active_bound(reaction): """For an active boundary reaction, return the relevant bound""" if reaction.reactants: return -reaction.lower_bound elif reaction.products: return reaction.upper_bound return {rxn.id: get_active_bound(rxn) for rxn in self.exchanges if is_active(rxn)} @medium.setter def medium(self, medium): """Get or set the constraints on the model exchanges. `model.medium` returns a dictionary of the bounds for each of the boundary reactions, in the form of `{rxn_id: bound}`, where `bound` specifies the absolute value of the bound in direction of metabolite creation (i.e., lower_bound for `met <--`, upper_bound for `met -->`) Parameters ---------- medium: dictionary-like The medium to initialize. medium should be a dictionary defining `{rxn_id: bound}` pairs. """ def set_active_bound(reaction, bound): if reaction.reactants: reaction.lower_bound = -bound elif reaction.products: reaction.upper_bound = bound # Set the given media bounds media_rxns = list() for rxn_id, bound in iteritems(medium): rxn = self.reactions.get_by_id(rxn_id) media_rxns.append(rxn) set_active_bound(rxn, bound) boundary_rxns = set(self.exchanges) media_rxns = set(media_rxns) # Turn off reactions not present in media for rxn in (boundary_rxns - media_rxns): set_active_bound(rxn, 0) def __add__(self, other_model): """Add the content of another model to this model (+). The model is copied as a new object, with a new model identifier, and copies of all the reactions in the other model are added to this model. The objective is the sum of the objective expressions for the two models. """ warn('use model.merge instead', DeprecationWarning) return self.merge(other_model, objective='sum', inplace=False) def __iadd__(self, other_model): """Incrementally add the content of another model to this model (+=). Copies of all the reactions in the other model are added to this model. The objective is the sum of the objective expressions for the two models. """ warn('use model.merge instead', DeprecationWarning) return self.merge(other_model, objective='sum', inplace=True) def copy(self): """Provides a partial 'deepcopy' of the Model. All of the Metabolite, Gene, and Reaction objects are created anew but in a faster fashion than deepcopy """ new = self.__class__() do_not_copy_by_ref = {"metabolites", "reactions", "genes", "notes", "annotation"} for attr in self.__dict__: if attr not in do_not_copy_by_ref: new.__dict__[attr] = self.__dict__[attr] new.notes = deepcopy(self.notes) new.annotation = deepcopy(self.annotation) new.metabolites = DictList() do_not_copy_by_ref = {"_reaction", "_model"} for metabolite in self.metabolites: new_met = metabolite.__class__() for attr, value in iteritems(metabolite.__dict__): if attr not in do_not_copy_by_ref: new_met.__dict__[attr] = copy( value) if attr == "formula" else value new_met._model = new new.metabolites.append(new_met) new.genes = DictList() for gene in self.genes: new_gene = gene.__class__(None) for attr, value in iteritems(gene.__dict__): if attr not in do_not_copy_by_ref: new_gene.__dict__[attr] = copy( value) if attr == "formula" else value new_gene._model = new new.genes.append(new_gene) new.reactions = DictList() do_not_copy_by_ref = {"_model", "_metabolites", "_genes"} for reaction in self.reactions: new_reaction = reaction.__class__() for attr, value in iteritems(reaction.__dict__): if attr not in do_not_copy_by_ref: new_reaction.__dict__[attr] = copy(value) new_reaction._model = new new.reactions.append(new_reaction) # update awareness for metabolite, stoic in iteritems(reaction._metabolites): new_met = new.metabolites.get_by_id(metabolite.id) new_reaction._metabolites[new_met] = stoic new_met._reaction.add(new_reaction) for gene in reaction._genes: new_gene = new.genes.get_by_id(gene.id) new_reaction._genes.add(new_gene) new_gene._reaction.add(new_reaction) try: new._solver = deepcopy(self.solver) # Cplex has an issue with deep copies except Exception: # pragma: no cover new._solver = copy(self.solver) # pragma: no cover # it doesn't make sense to retain the context of a copied model so # assign a new empty context new._contexts = list() return new def add_metabolites(self, metabolite_list): """Will add a list of metabolites to the model object and add new constraints accordingly. The change is reverted upon exit when using the model as a context. Parameters ---------- metabolite_list : A list of `cobra.core.Metabolite` objects """ if not hasattr(metabolite_list, '__iter__'): metabolite_list = [metabolite_list] if len(metabolite_list) == 0: return None # First check whether the metabolites exist in the model metabolite_list = [x for x in metabolite_list if x.id not in self.metabolites] bad_ids = [m for m in metabolite_list if not isinstance(m.id, string_types) or len(m.id) < 1] if len(bad_ids) != 0: raise ValueError('invalid identifiers in {}'.format(repr(bad_ids))) for x in metabolite_list: x._model = self self.metabolites += metabolite_list # from cameo ... to_add = [] for met in metabolite_list: if met.id not in self.constraints: constraint = self.problem.Constraint( Zero, name=met.id, lb=0, ub=0) to_add += [constraint] self.add_cons_vars(to_add) context = get_context(self) if context: context(partial(self.metabolites.__isub__, metabolite_list)) for x in metabolite_list: # Do we care? context(partial(setattr, x, '_model', None)) def remove_metabolites(self, metabolite_list, destructive=False): """Remove a list of metabolites from the the object. The change is reverted upon exit when using the model as a context. Parameters ---------- metabolite_list : list A list with `cobra.Metabolite` objects as elements. destructive : bool If False then the metabolite is removed from all associated reactions. If True then all associated reactions are removed from the Model. """ if not hasattr(metabolite_list, '__iter__'): metabolite_list = [metabolite_list] # Make sure metabolites exist in model metabolite_list = [x for x in metabolite_list if x.id in self.metabolites] for x in metabolite_list: x._model = None if not destructive: for the_reaction in list(x._reaction): the_coefficient = the_reaction._metabolites[x] the_reaction.subtract_metabolites({x: the_coefficient}) else: for x in list(x._reaction): x.remove_from_model() self.metabolites -= metabolite_list to_remove = [self.solver.constraints[m.id] for m in metabolite_list] self.remove_cons_vars(to_remove) context = get_context(self) if context: context(partial(self.metabolites.__iadd__, metabolite_list)) for x in metabolite_list: context(partial(setattr, x, '_model', self)) def add_reaction(self, reaction): """Will add a cobra.Reaction object to the model, if reaction.id is not in self.reactions. Parameters ---------- reaction : cobra.Reaction The reaction to add Deprecated (0.6). Use `~cobra.Model.add_reactions` instead """ warn("add_reaction deprecated. Use add_reactions instead", DeprecationWarning) self.add_reactions([reaction]) def add_boundary(self, metabolite, type="exchange", reaction_id=None, lb=None, ub=1000.0): """Add a boundary reaction for a given metabolite. There are three different types of pre-defined boundary reactions: exchange, demand, and sink reactions. An exchange reaction is a reversible, imbalanced reaction that adds to or removes an extracellular metabolite from the extracellular compartment. A demand reaction is an irreversible reaction that consumes an intracellular metabolite. A sink is similar to an exchange but specifically for intracellular metabolites. If you set the reaction `type` to something else, you must specify the desired identifier of the created reaction along with its upper and lower bound. The name will be given by the metabolite name and the given `type`. Parameters ---------- metabolite : cobra.Metabolite Any given metabolite. The compartment is not checked but you are encouraged to stick to the definition of exchanges and sinks. type : str, {"exchange", "demand", "sink"} Using one of the pre-defined reaction types is easiest. If you want to create your own kind of boundary reaction choose any other string, e.g., 'my-boundary'. reaction_id : str, optional The ID of the resulting reaction. Only used for custom reactions. lb : float, optional The lower bound of the resulting reaction. Only used for custom reactions. ub : float, optional The upper bound of the resulting reaction. For the pre-defined reactions this default value determines all bounds. Returns ------- cobra.Reaction The created boundary reaction. Examples -------- >>> import cobra.test >>> model = cobra.test.create_test_model("textbook") >>> demand = model.add_boundary(model.metabolites.atp_c, type="demand") >>> demand.id 'DM_atp_c' >>> demand.name 'ATP demand' >>> demand.bounds (0, 1000.0) >>> demand.build_reaction_string() 'atp_c --> ' """ types = dict(exchange=("EX", -ub, ub), demand=("DM", 0, ub), sink=("SK", -ub, ub)) if type in types: prefix, lb, ub = types[type] reaction_id = "{}_{}".format(prefix, metabolite.id) if reaction_id in self.reactions: raise ValueError('boundary %s already exists' % reaction_id) name = "{} {}".format(metabolite.name, type) rxn = Reaction(id=reaction_id, name=name, lower_bound=lb, upper_bound=ub) rxn.add_metabolites({metabolite: -1}) self.add_reactions([rxn]) return rxn def add_reactions(self, reaction_list): """Add reactions to the model. Reactions with identifiers identical to a reaction already in the model are ignored. The change is reverted upon exit when using the model as a context. Parameters ---------- reaction_list : list A list of `cobra.Reaction` objects """ def existing_filter(rxn): if rxn.id in self.reactions: LOGGER.warning( "Ignoring reaction '%s' since it already exists.", rxn.id) return False return True # First check whether the reactions exist in the model. pruned = DictList(filter(existing_filter, reaction_list)) context = get_context(self) # Add reactions. Also take care of genes and metabolites in the loop. for reaction in pruned: reaction._model = self # Build a `list()` because the dict will be modified in the loop. for metabolite in list(reaction.metabolites): # TODO: Should we add a copy of the metabolite instead? if metabolite not in self.metabolites: self.add_metabolites(metabolite) # A copy of the metabolite exists in the model, the reaction # needs to point to the metabolite in the model. else: # FIXME: Modifying 'private' attributes is horrible. stoichiometry = reaction._metabolites.pop(metabolite) model_metabolite = self.metabolites.get_by_id( metabolite.id) reaction._metabolites[model_metabolite] = stoichiometry model_metabolite._reaction.add(reaction) if context: context(partial( model_metabolite._reaction.remove, reaction)) for gene in list(reaction._genes): # If the gene is not in the model, add it if not self.genes.has_id(gene.id): self.genes += [gene] gene._model = self if context: # Remove the gene later context(partial(self.genes.__isub__, [gene])) context(partial(setattr, gene, '_model', None)) # Otherwise, make the gene point to the one in the model else: model_gene = self.genes.get_by_id(gene.id) if model_gene is not gene: reaction._dissociate_gene(gene) reaction._associate_gene(model_gene) self.reactions += pruned if context: context(partial(self.reactions.__isub__, pruned)) # from cameo ... self._populate_solver(pruned) def remove_reactions(self, reactions, remove_orphans=False): """Remove reactions from the model. The change is reverted upon exit when using the model as a context. Parameters ---------- reactions : list A list with reactions (`cobra.Reaction`), or their id's, to remove remove_orphans : bool Remove orphaned genes and metabolites from the model as well """ if isinstance(reactions, string_types) or hasattr(reactions, "id"): warn("need to pass in a list") reactions = [reactions] context = get_context(self) for reaction in reactions: # Make sure the reaction is in the model try: reaction = self.reactions[self.reactions.index(reaction)] except ValueError: warn('%s not in %s' % (reaction, self)) else: forward = reaction.forward_variable reverse = reaction.reverse_variable self.remove_cons_vars([forward, reverse]) self.reactions.remove(reaction) reaction._model = None if context: context(partial(setattr, reaction, '_model', self)) context(partial(self.reactions.add, reaction)) for met in reaction._metabolites: if reaction in met._reaction: met._reaction.remove(reaction) if context: context(partial(met._reaction.add, reaction)) if remove_orphans and len(met._reaction) == 0: self.remove_metabolites(met) for gene in reaction._genes: if reaction in gene._reaction: gene._reaction.remove(reaction) if context: context(partial(gene._reaction.add, reaction)) if remove_orphans and len(gene._reaction) == 0: self.genes.remove(gene) if context: context(partial(self.genes.add, gene)) def add_cons_vars(self, what, **kwargs): """Add constraints and variables to the model's mathematical problem. Useful for variables and constraints that can not be expressed with reactions and simple lower and upper bounds. Additions are reversed upon exit if the model itself is used as context. Parameters ---------- what : list or tuple of optlang variables or constraints. The variables or constraints to add to the model. Must be of class `optlang.interface.Variable` or `optlang.interface.Constraint`. **kwargs : keyword arguments Passed to solver.add() """ add_cons_vars_to_problem(self, what, **kwargs) def remove_cons_vars(self, what): """Remove variables and constraints from the model's mathematical problem. Remove variables and constraints that were added directly to the model's underlying mathematical problem. Removals are reversed upon exit if the model itself is used as context. Parameters ---------- what : list or tuple of optlang variables or constraints. The variables or constraints to add to the model. Must be of class `optlang.interface.Variable` or `optlang.interface.Constraint`. """ remove_cons_vars_from_problem(self, what) @property def problem(self): """The interface to the model's underlying mathematical problem. Solutions to cobra models are obtained by formulating a mathematical problem and solving it. Cobrapy uses the optlang package to accomplish that and with this property you can get access to the problem interface directly. Returns ------- optlang.interface The problem interface that defines methods for interacting with the problem and associated solver directly. """ return self.solver.interface @property def variables(self): """The mathematical variables in the cobra model. In a cobra model, most variables are reactions. However, for specific use cases, it may also be useful to have other types of variables. This property defines all variables currently associated with the model's problem. Returns ------- optlang.container.Container A container with all associated variables. """ return self.solver.variables @property def constraints(self): """The constraints in the cobra model. In a cobra model, most constraints are metabolites and their stoichiometries. However, for specific use cases, it may also be useful to have other types of constraints. This property defines all constraints currently associated with the model's problem. Returns ------- optlang.container.Container A container with all associated constraints. """ return self.solver.constraints @property def exchanges(self): """Exchange reactions in model. Reactions that either don't have products or substrates. """ return [rxn for rxn in self.reactions if rxn.boundary] def _populate_solver(self, reaction_list, metabolite_list=None): """Populate attached solver with constraints and variables that model the provided reactions. """ constraint_terms = AutoVivification() to_add = [] if metabolite_list is not None: for met in metabolite_list: to_add += [self.problem.Constraint( Zero, name=met.id, lb=0, ub=0)] self.add_cons_vars(to_add) for reaction in reaction_list: reverse_lb, reverse_ub, forward_lb, forward_ub = \ separate_forward_and_reverse_bounds(*reaction.bounds) forward_variable = self.problem.Variable( reaction.id, lb=forward_lb, ub=forward_ub) reverse_variable = self.problem.Variable( reaction.reverse_id, lb=reverse_lb, ub=reverse_ub) self.add_cons_vars([forward_variable, reverse_variable]) self.solver.update() for metabolite, coeff in six.iteritems(reaction.metabolites): if metabolite.id in self.constraints: constraint = self.constraints[metabolite.id] else: constraint = self.problem.Constraint( Zero, name=metabolite.id, lb=0, ub=0) self.add_cons_vars(constraint, sloppy=True) constraint_terms[constraint][forward_variable] = coeff constraint_terms[constraint][reverse_variable] = -coeff self.solver.update() for constraint, terms in six.iteritems(constraint_terms): constraint.set_linear_coefficients(terms) def slim_optimize(self, error_value=float('nan'), message=None): """Optimize model without creating a solution object. Creating a full solution object implies fetching shadow prices and flux values for all reactions and metabolites from the solver object. This necessarily takes some time and in cases where only one or two values are of interest, it is recommended to instead use this function which does not create a solution object returning only the value of the objective. Note however that the `optimize()` function uses efficient means to fetch values so if you need fluxes/shadow prices for more than say 4 reactions/metabolites, then the total speed increase of `slim_optimize` versus `optimize` is expected to be small or even negative depending on how you fetch the values after optimization. Parameters ---------- error_value : float, None The value to return if optimization failed due to e.g. infeasibility. If None, raise `OptimizationError` if the optimization fails. message : string Error message to use if the model optimization did not succeed. Returns ------- float The objective value. """ self.solver.optimize() if self.solver.status == optlang.interface.OPTIMAL: return self.solver.objective.value elif error_value is not None: return error_value else: assert_optimal(self, message) def optimize(self, objective_sense=None, raise_error=False): """ Optimize the model using flux balance analysis. Parameters ---------- objective_sense : {None, 'maximize' 'minimize'}, optional Whether fluxes should be maximized or minimized. In case of None, the previous direction is used. raise_error : bool If true, raise an OptimizationError if solver status is not optimal. Notes ----- Only the most commonly used parameters are presented here. Additional parameters for cobra.solvers may be available and specified with the appropriate keyword argument. """ original_direction = self.objective.direction self.objective.direction = \ {"maximize": "max", "minimize": "min"}.get( objective_sense, original_direction) self.slim_optimize() solution = get_solution(self, raise_error=raise_error) self.objective.direction = original_direction return solution def repair(self, rebuild_index=True, rebuild_relationships=True): """Update all indexes and pointers in a model Parameters ---------- rebuild_index : bool rebuild the indices kept in reactions, metabolites and genes rebuild_relationships : bool reset all associations between genes, metabolites, model and then re-add them. """ if rebuild_index: # DictList indexes self.reactions._generate_index() self.metabolites._generate_index() self.genes._generate_index() if rebuild_relationships: for met in self.metabolites: met._reaction.clear() for gene in self.genes: gene._reaction.clear() for rxn in self.reactions: for met in rxn._metabolites: met._reaction.add(rxn) for gene in rxn._genes: gene._reaction.add(rxn) # point _model to self for l in (self.reactions, self.genes, self.metabolites): for e in l: e._model = self @property def objective(self): """Get or set the solver objective Before introduction of the optlang based problems, this function returned the objective reactions as a list. With optlang, the objective is not limited a simple linear summation of individual reaction fluxes, making that return value ambiguous. Henceforth, use `cobra.util.solver.linear_reaction_coefficients` to get a dictionary of reactions with their linear coefficients (empty if there are none) The set value can be dictionary (reactions as keys, linear coefficients as values), string (reaction identifier), int (reaction index), Reaction or problem.Objective or sympy expression directly interpreted as objectives. When using a `HistoryManager` context, this attribute can be set temporarily, reversed when the exiting the context. """ return self.solver.objective @objective.setter 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) @property def objective_direction(self): """ Get or set the objective direction. When using a `HistoryManager` context, this attribute can be set temporarily, reversed when exiting the context. """ return self.solver.objective.direction @objective_direction.setter @resettable def objective_direction(self, value): value = value.lower() if value.startswith("max"): self.solver.objective.direction = "max" elif value.startswith("min"): self.solver.objective.direction = "min" else: raise ValueError("Unknown objective direction '{}'.".format(value)) def summary(self, solution=None, threshold=1E-8, fva=None, floatfmt='.3g'): """Print a summary of the input and output fluxes of the model. This method requires the model to have been previously solved. Parameters ---------- solution: cobra.core.Solution A previously solved model solution to use for generating the summary. If none provided (default), the summary method will resolve the model. Note that the solution object must match the model, i.e., changes to the model such as changed bounds, added or removed reactions are not taken into account by this method. threshold : float tolerance for determining if a flux is zero (not printed) fva : int or None Whether or not to calculate and report flux variability in the output summary floatfmt : string format method for floats, passed to tabulate. Default is '.3g'. """ from cobra.flux_analysis.summary import model_summary return model_summary(self, solution=solution, threshold=threshold, fva=fva, floatfmt=floatfmt) def __enter__(self): """Record all future changes to the model, undoing them when a call to __exit__ is received""" # Create a new context and add it to the stack try: self._contexts.append(HistoryManager()) except AttributeError: self._contexts = [HistoryManager()] return self def __exit__(self, type, value, traceback): """Pop the top context manager and trigger the undo functions""" context = self._contexts.pop() context.reset() def merge(self, right, prefix_existing=None, inplace=True, objective='left'): """Merge two models to create a model with the reactions from both models. Custom constraints and variables from right models are also copied to left model, however note that, constraints and variables are assumed to be the same if they have the same name. right : cobra.Model The model to add reactions from prefix_existing : string Prefix the reaction identifier in the right that already exist in the left model with this string. inplace : bool Add reactions from right directly to left model object. Otherwise, create a new model leaving the left model untouched. When done within the model as context, changes to the models are reverted upon exit. objective : string One of 'left', 'right' or 'sum' for setting the objective of the resulting model to that of the corresponding model or the sum of both. """ if inplace: new_model = self else: new_model = self.copy() new_model.id = '{}_{}'.format(self.id, right.id) new_reactions = deepcopy(right.reactions) if prefix_existing is not None: existing = new_reactions.query( lambda rxn: rxn.id in self.reactions) for reaction in existing: reaction.id = '{}{}'.format(prefix_existing, reaction.id) new_model.add_reactions(new_reactions) interface = new_model.problem new_vars = [interface.Variable.clone(v) for v in right.variables if v.name not in new_model.variables] new_model.add_cons_vars(new_vars) new_cons = [interface.Constraint.clone(c, model=new_model.solver) for c in right.constraints if c.name not in new_model.constraints] new_model.add_cons_vars(new_cons, sloppy=True) new_model.objective = dict( left=self.objective, right=right.objective, sum=self.objective.expression + right.objective.expression )[objective] return new_model def _repr_html_(self): return """ <table> <tr> <td><strong>Name</strong></td> <td>{name}</td> </tr><tr> <td><strong>Memory address</strong></td> <td>{address}</td> </tr><tr> <td><strong>Number of metabolites</strong></td> <td>{num_metabolites}</td> </tr><tr> <td><strong>Number of reactions</strong></td> <td>{num_reactions}</td> </tr><tr> <td><strong>Objective expression</strong></td> <td>{objective}</td> </tr><tr> <td><strong>Compartments</strong></td> <td>{compartments}</td> </tr> </table>""".format( name=self.id, address='0x0%x' % id(self), num_metabolites=len(self.metabolites), num_reactions=len(self.reactions), objective=format_long_string(str(self.objective.expression), 100), compartments=", ".join( v if v else k for k, v in iteritems(self.compartments) ))
def add_reactions(self, reaction_list: Iterator[Reaction]): """Add reactions to the model. Reactions with identifiers identical to a reaction already in the model are ignored. The change is reverted upon exit when using the model as a context. Enzyme Constrained changes: avoid adding proteins as metabolites. Parameters ---------- reaction_list : list A list of `cobra.Reaction` objects """ def existing_filter(rxn): if rxn.id in self.reactions: LOGGER.warning( f"Ignoring reaction '{rxn.id}' since it already exists.") return False return True # First check whether the reactions exist in the model. pruned = DictList(filter(existing_filter, reaction_list)) context = get_context(self) # Add reactions. Also take care of genes and metabolites in the loop. for reaction in pruned: reaction._model = self # Build a `list()` because the dict will be modified in the loop. for metabolite in list(reaction.metabolites): is_prot = isinstance(metabolite, Protein) target = self.proteins if is_prot else self.metabolites if metabolite not in target: if is_prot: self.add_proteins([metabolite]) else: self.add_metabolites(metabolite) # A copy of the metabolite exists in the model, the reaction # needs to point to the metabolite in the model. else: stoichiometry = reaction._metabolites.pop(metabolite) model_metabolite = target.get_by_id(metabolite.id) reaction._metabolites[model_metabolite] = stoichiometry model_metabolite._reaction.add(reaction) if context: context( partial(model_metabolite._reaction.remove, reaction)) for gene in list(reaction._genes): # If the gene is not in the model, add it if not self.genes.has_id(gene.id): self.genes += [gene] gene._model = self if context: # Remove the gene later context(partial(self.genes.__isub__, [gene])) context(partial(setattr, gene, "_model", None)) # Otherwise, make the gene point to the one in the model else: model_gene = self.genes.get_by_id(gene.id) if model_gene is not gene: reaction._dissociate_gene(gene) reaction._associate_gene(model_gene) self.reactions += pruned if context: context(partial(self.reactions.__isub__, pruned)) # from cameo ... self._populate_solver(pruned)
def add_reactions(self, reaction_list): """Add reactions to the model. Reactions with identifiers identical to a reaction already in the model are ignored. The change is reverted upon exit when using the model as a context. Parameters ---------- reaction_list : list A list of `cobra.Reaction` objects """ def existing_filter(rxn): if rxn.id in self.reactions: logger.warning( "Ignoring reaction '%s' since it already exists.", rxn.id) return False return True # First check whether the reactions exist in the model. pruned = DictList(filter(existing_filter, reaction_list)) context = get_context(self) # Add reactions. Also take care of genes and metabolites in the loop. for reaction in pruned: reaction._model = self # Build a `list()` because the dict will be modified in the loop. for metabolite in list(reaction.metabolites): # TODO: Should we add a copy of the metabolite instead? if metabolite not in self.metabolites: self.add_metabolites(metabolite) # A copy of the metabolite exists in the model, the reaction # needs to point to the metabolite in the model. else: # FIXME: Modifying 'private' attributes is horrible. stoichiometry = reaction._metabolites.pop(metabolite) model_metabolite = self.metabolites.get_by_id( metabolite.id) reaction._metabolites[model_metabolite] = stoichiometry model_metabolite._reaction.add(reaction) if context: context( partial(model_metabolite._reaction.remove, reaction)) for gene in list(reaction._genes): # If the gene is not in the model, add it if not self.genes.has_id(gene.id): self.genes += [gene] gene._model = self if context: # Remove the gene later context(partial(self.genes.__isub__, [gene])) context(partial(setattr, gene, "_model", None)) # Otherwise, make the gene point to the one in the model else: model_gene = self.genes.get_by_id(gene.id) if model_gene is not gene: reaction._dissociate_gene(gene) reaction._associate_gene(model_gene) self.reactions += pruned if context: context(partial(self.reactions.__isub__, pruned))
def copy(self): """ Returns a deep copy of the model. Overides default behaviour of cobra.Model copy which makes a breaking call to self.__class__() Does not copy the project or design :return: GSModutilsModel """ new = self.__class__(self.project, design=self.design, mpath=self.mpath) do_not_copy_by_ref = { "metabolites", "reactions", "genes", "notes", "annotation" } for attr in self.__dict__: if attr not in do_not_copy_by_ref: new.__dict__[attr] = self.__dict__[attr] new.notes = deepcopy(self.notes) new.annotation = deepcopy(self.annotation) new.metabolites = DictList() do_not_copy_by_ref = {"_reaction", "_model"} for metabolite in self.metabolites: new_met = metabolite.__class__() for attr, value in iteritems(metabolite.__dict__): if attr not in do_not_copy_by_ref: new_met.__dict__[attr] = copy( value) if attr == "formula" else value new_met._model = new new.metabolites.append(new_met) new.genes = DictList() for gene in self.genes: new_gene = gene.__class__(None) for attr, value in iteritems(gene.__dict__): if attr not in do_not_copy_by_ref: new_gene.__dict__[attr] = copy( value) if attr == "formula" else value new_gene._model = new new.genes.append(new_gene) new.reactions = DictList() do_not_copy_by_ref = {"_model", "_metabolites", "_genes"} for reaction in self.reactions: new_reaction = reaction.__class__() for attr, value in iteritems(reaction.__dict__): if attr not in do_not_copy_by_ref: new_reaction.__dict__[attr] = copy(value) new_reaction._model = new new.reactions.append(new_reaction) # update awareness for metabolite, stoic in iteritems(reaction._metabolites): new_met = new.metabolites.get_by_id(metabolite.id) new_reaction._metabolites[new_met] = stoic new_met._reaction.add(new_reaction) for gene in reaction._genes: new_gene = new.genes.get_by_id(gene.id) new_reaction._genes.add(new_gene) new_gene._reaction.add(new_reaction) try: new._solver = deepcopy(self.solver) # Cplex has an issue with deep copies except Exception: # pragma: no cover new._solver = copy(self.solver) # pragma: no cover # it doesn't make sense to retain the context of a copied model so # assign a new empty context new._contexts = list() return new
class Group(Object): """ Manage groups via this implementation of the SBML group specification. `Group` is a class for holding information regarding a pathways, subsystems, or other custom groupings of objects within a cobra.Model object. Parameters ---------- id : str The identifier to associate with this group name : str, optional A human readable name for the group members : iterable, optional A DictList containing references to cobra.Model-associated objects that belong to the group. kind : {"collection", "classification", "partonomy"}, optional The kind of group, as specified for the Groups feature in the SBML level 3 package specification. Can be any of "classification", "partonomy", or "collection". The default is "collection". Please consult the SBML level 3 package specification to ensure you are using the proper value for kind. In short, members of a "classification" group should have an "is-a" relationship to the group (e.g. member is-a polar compound, or member is-a transporter). Members of a "partonomy" group should have a "part-of" relationship (e.g. member is part-of glycolysis). Members of a "collection" group do not have an implied relationship between the members, so use this value for kind when in doubt (e.g. member is a gap-filled reaction, or member is involved in a disease phenotype). """ KIND_TYPES = ("collection", "classification", "partonomy") def __init__(self, id, name="", members=None, kind=None): Object.__init__(self, id, name) self._members = DictList() if members is None else DictList(members) self._kind = None self.kind = "collection" if kind is None else kind # self.model is None or refers to the cobra.Model that # contains self self._model = None def __len__(self): return len(self._members) # read-only @property def members(self): return self._members @property def kind(self): return self._kind @kind.setter def kind(self, kind): kind = kind.lower() if kind in self.KIND_TYPES: self._kind = kind else: raise ValueError("Kind can only by one of: {}.".format(", ".join( self.KIND_TYPES))) def add_members(self, new_members): """ Add objects to the group. Parameters ---------- new_members : list A list of cobrapy objects to add to the group. """ if isinstance(new_members, str) or hasattr(new_members, "id"): warn("need to pass in a list") new_members = [new_members] self._members.union(new_members) def remove_members(self, to_remove): """ Remove objects from the group. Parameters ---------- to_remove : list A list of cobra objects to remove from the group """ if isinstance(to_remove, str) or hasattr(to_remove, "id"): warn("need to pass in a list") to_remove = [to_remove] for member_to_remove in to_remove: self._members.remove(member_to_remove)
class Model(Object): """Class representation for a cobra model Parameters ---------- id_or_model : Model, string Either an existing Model object in which case a new model object is instantiated with the same properties as the original model, or an identifier to associate with the model as a string. name : string Human readable name for the model Attributes ---------- reactions : DictList A DictList where the key is the reaction identifier and the value a Reaction metabolites : DictList A DictList where the key is the metabolite identifier and the value a Metabolite genes : DictList A DictList where the key is the gene identifier and the value a Gene groups : DictList A DictList where the key is the group identifier and the value a Group solution : Solution The last obtained solution from optimizing the model. """ def __setstate__(self, state): """Make sure all cobra.Objects in the model point to the model.""" self.__dict__.update(state) for y in ["reactions", "genes", "metabolites"]: for x in getattr(self, y): x._model = self if not hasattr(self, "name"): self.name = None def __getstate__(self): """Get state for serialization. Ensures that the context stack is cleared prior to serialization, since partial functions cannot be pickled reliably. """ odict = self.__dict__.copy() odict["_contexts"] = [] return odict def __init__(self, id_or_model=None, name=None): if isinstance(id_or_model, Model): Object.__init__(self, name=name) self.__setstate__(id_or_model.__dict__) if not hasattr(self, "name"): self.name = None self._solver = id_or_model.solver else: Object.__init__(self, id_or_model, name=name) self._trimmed = False self._trimmed_genes = [] self._trimmed_reactions = {} self.genes = DictList() self.reactions = DictList() # A list of cobra.Reactions self.metabolites = DictList() # A list of cobra.Metabolites self.groups = DictList() # A list of cobra.Groups # genes based on their ids {Gene.id: Gene} self._compartments = {} self._contexts = [] # from cameo ... # if not hasattr(self, '_solver'): # backwards compatibility # with older cobrapy pickles? interface = configuration.solver self._solver = interface.Model() self._solver.objective = interface.Objective(Zero) self._populate_solver(self.reactions, self.metabolites) self._tolerance = None self.tolerance = configuration.tolerance @property def solver(self): """Get or set the attached solver instance. The associated the solver object, which manages the interaction with the associated solver, e.g. glpk. This property is useful for accessing the optimization problem directly and to define additional non-metabolic constraints. Examples -------- >>> import cobra.test >>> model = cobra.test.create_test_model("textbook") >>> new = model.problem.Constraint(model.objective.expression, >>> lb=0.99) >>> model.solver.add(new) """ return self._solver @solver.setter @resettable def solver(self, value): not_valid_interface = SolverNotFound( "%s is not a valid solver interface. Pick from %s." % (value, list(solvers))) if isinstance(value, six.string_types): try: interface = solvers[interface_to_str(value)] except KeyError: raise not_valid_interface elif isinstance(value, types.ModuleType) and hasattr(value, "Model"): interface = value elif isinstance(value, optlang.interface.Model): interface = value.interface else: raise not_valid_interface # Do nothing if the solver did not change if self.problem == interface: return self._solver = interface.Model.clone(self._solver) @property def tolerance(self): return self._tolerance @tolerance.setter def tolerance(self, value): solver_tolerances = self._solver.configuration.tolerances try: solver_tolerances.feasibility = value except AttributeError: logger.info("The current solver doesn't allow setting" "feasibility tolerance.") try: solver_tolerances.optimality = value except AttributeError: logger.info("The current solver doesn't allow setting" "optimality tolerance.") try: solver_tolerances.integrality = value except AttributeError: logger.info("The current solver doesn't allow setting" "integrality tolerance.") self._tolerance = value @property def description(self): warn("description deprecated", DeprecationWarning) return self.name if self.name is not None else "" @description.setter def description(self, value): self.name = value warn("description deprecated", DeprecationWarning) def get_metabolite_compartments(self): """Return all metabolites' compartments.""" warn("use Model.compartments instead", DeprecationWarning) return { met.compartment for met in self.metabolites if met.compartment is not None } @property def compartments(self): return { met.compartment: self._compartments.get(met.compartment, "") for met in self.metabolites if met.compartment is not None } @compartments.setter def compartments(self, value): """Get or set the dictionary of current compartment descriptions. Assigning a dictionary to this property updates the model's dictionary of compartment descriptions with the new values. Parameters ---------- value : dict Dictionary mapping compartments abbreviations to full names. Examples -------- >>> import cobra.test >>> model = cobra.test.create_test_model("textbook") >>> model.compartments = {'c': 'the cytosol'} {'c': 'the cytosol', 'e': 'extracellular'} """ self._compartments.update(value) @property def medium(self): def is_active(reaction): """Determine if a boundary reaction permits flux towards creating metabolites """ return (bool(reaction.products) and (reaction.upper_bound > 0)) or (bool(reaction.reactants) and (reaction.lower_bound < 0)) def get_active_bound(reaction): """For an active boundary reaction, return the relevant bound""" if reaction.reactants: return -reaction.lower_bound elif reaction.products: return reaction.upper_bound return { rxn.id: get_active_bound(rxn) for rxn in self.exchanges if is_active(rxn) } @medium.setter def medium(self, medium): """Get or set the constraints on the model exchanges. `model.medium` returns a dictionary of the bounds for each of the boundary reactions, in the form of `{rxn_id: bound}`, where `bound` specifies the absolute value of the bound in direction of metabolite creation (i.e., lower_bound for `met <--`, upper_bound for `met -->`) Parameters ---------- medium: dictionary-like The medium to initialize. medium should be a dictionary defining `{rxn_id: bound}` pairs. """ def set_active_bound(reaction, bound): if reaction.reactants: reaction.lower_bound = -bound elif reaction.products: reaction.upper_bound = bound # Set the given media bounds media_rxns = list() exchange_rxns = frozenset(self.exchanges) for rxn_id, bound in iteritems(medium): rxn = self.reactions.get_by_id(rxn_id) if rxn not in exchange_rxns: logger.warn( "%s does not seem to be an" " an exchange reaction. Applying bounds anyway.", rxn.id, ) media_rxns.append(rxn) set_active_bound(rxn, bound) media_rxns = frozenset(media_rxns) # Turn off reactions not present in media for rxn in exchange_rxns - media_rxns: set_active_bound(rxn, 0) def __add__(self, other_model): """Add the content of another model to this model (+). The model is copied as a new object, with a new model identifier, and copies of all the reactions in the other model are added to this model. The objective is the sum of the objective expressions for the two models. """ warn("use model.merge instead", DeprecationWarning) return self.merge(other_model, objective="sum", inplace=False) def __iadd__(self, other_model): """Incrementally add the content of another model to this model (+=). Copies of all the reactions in the other model are added to this model. The objective is the sum of the objective expressions for the two models. """ warn("use model.merge instead", DeprecationWarning) return self.merge(other_model, objective="sum", inplace=True) def copy(self): """Provides a partial 'deepcopy' of the Model. All of the Metabolite, Gene, and Reaction objects are created anew but in a faster fashion than deepcopy """ new = self.__class__() do_not_copy_by_ref = { "metabolites", "reactions", "genes", "notes", "annotation", "groups", } for attr in self.__dict__: if attr not in do_not_copy_by_ref: new.__dict__[attr] = self.__dict__[attr] new.notes = deepcopy(self.notes) new.annotation = deepcopy(self.annotation) new.metabolites = DictList() do_not_copy_by_ref = {"_reaction", "_model"} for metabolite in self.metabolites: new_met = metabolite.__class__() for attr, value in iteritems(metabolite.__dict__): if attr not in do_not_copy_by_ref: new_met.__dict__[attr] = copy( value) if attr == "formula" else value new_met._model = new new.metabolites.append(new_met) new.genes = DictList() for gene in self.genes: new_gene = gene.__class__(None) for attr, value in iteritems(gene.__dict__): if attr not in do_not_copy_by_ref: new_gene.__dict__[attr] = (copy(value) if attr == "formula" else value) new_gene._model = new new.genes.append(new_gene) new.reactions = DictList() do_not_copy_by_ref = {"_model", "_metabolites", "_genes"} for reaction in self.reactions: new_reaction = reaction.__class__() for attr, value in iteritems(reaction.__dict__): if attr not in do_not_copy_by_ref: new_reaction.__dict__[attr] = copy(value) new_reaction._model = new new.reactions.append(new_reaction) # update awareness for metabolite, stoic in iteritems(reaction._metabolites): new_met = new.metabolites.get_by_id(metabolite.id) new_reaction._metabolites[new_met] = stoic new_met._reaction.add(new_reaction) for gene in reaction._genes: new_gene = new.genes.get_by_id(gene.id) new_reaction._genes.add(new_gene) new_gene._reaction.add(new_reaction) new.groups = DictList() do_not_copy_by_ref = {"_model", "_members"} # Groups can be members of other groups. We initialize them first and # then update their members. for group in self.groups: new_group = group.__class__(group.id) for attr, value in iteritems(group.__dict__): if attr not in do_not_copy_by_ref: new_group.__dict__[attr] = copy(value) new_group._model = new new.groups.append(new_group) for group in self.groups: new_group = new.groups.get_by_id(group.id) # update awareness, as in the reaction copies new_objects = [] for member in group.members: if isinstance(member, Metabolite): new_object = new.metabolites.get_by_id(member.id) elif isinstance(member, Reaction): new_object = new.reactions.get_by_id(member.id) elif isinstance(member, Gene): new_object = new.genes.get_by_id(member.id) elif isinstance(member, Group): new_object = new.genes.get_by_id(member.id) else: raise TypeError( "The group member {!r} is unexpectedly not a " "metabolite, reaction, gene, nor another " "group.".format(member)) new_objects.append(new_object) new_group.add_members(new_objects) try: new._solver = deepcopy(self.solver) # Cplex has an issue with deep copies except Exception: # pragma: no cover new._solver = copy(self.solver) # pragma: no cover # it doesn't make sense to retain the context of a copied model so # assign a new empty context new._contexts = list() return new def add_metabolites(self, metabolite_list): """Will add a list of metabolites to the model object and add new constraints accordingly. The change is reverted upon exit when using the model as a context. Parameters ---------- metabolite_list : A list of `cobra.core.Metabolite` objects """ if not hasattr(metabolite_list, "__iter__"): metabolite_list = [metabolite_list] if len(metabolite_list) == 0: return None # First check whether the metabolites exist in the model metabolite_list = [ x for x in metabolite_list if x.id not in self.metabolites ] bad_ids = [ m for m in metabolite_list if not isinstance(m.id, string_types) or len(m.id) < 1 ] if len(bad_ids) != 0: raise ValueError("invalid identifiers in {}".format(repr(bad_ids))) for x in metabolite_list: x._model = self self.metabolites += metabolite_list # from cameo ... to_add = [] for met in metabolite_list: if met.id not in self.constraints: constraint = self.problem.Constraint(Zero, name=met.id, lb=0, ub=0) to_add += [constraint] self.add_cons_vars(to_add) context = get_context(self) if context: context(partial(self.metabolites.__isub__, metabolite_list)) for x in metabolite_list: # Do we care? context(partial(setattr, x, "_model", None)) def remove_metabolites(self, metabolite_list, destructive=False): """Remove a list of metabolites from the the object. The change is reverted upon exit when using the model as a context. Parameters ---------- metabolite_list : list A list with `cobra.Metabolite` objects as elements. destructive : bool If False then the metabolite is removed from all associated reactions. If True then all associated reactions are removed from the Model. """ if not hasattr(metabolite_list, "__iter__"): metabolite_list = [metabolite_list] # Make sure metabolites exist in model metabolite_list = [ x for x in metabolite_list if x.id in self.metabolites ] for x in metabolite_list: x._model = None # remove reference to the metabolite in all groups associated_groups = self.get_associated_groups(x) for group in associated_groups: group.remove_members(x) if not destructive: for the_reaction in list(x._reaction): the_coefficient = the_reaction._metabolites[x] the_reaction.subtract_metabolites({x: the_coefficient}) else: for x in list(x._reaction): x.remove_from_model() self.metabolites -= metabolite_list to_remove = [self.solver.constraints[m.id] for m in metabolite_list] self.remove_cons_vars(to_remove) context = get_context(self) if context: context(partial(self.metabolites.__iadd__, metabolite_list)) for x in metabolite_list: context(partial(setattr, x, "_model", self)) def add_reaction(self, reaction): """Will add a cobra.Reaction object to the model, if reaction.id is not in self.reactions. Parameters ---------- reaction : cobra.Reaction The reaction to add Deprecated (0.6). Use `~cobra.Model.add_reactions` instead """ warn("add_reaction deprecated. Use add_reactions instead", DeprecationWarning) self.add_reactions([reaction]) def add_boundary( self, metabolite, type="exchange", reaction_id=None, lb=None, ub=None, sbo_term=None, ): """ Add a boundary reaction for a given metabolite. There are three different types of pre-defined boundary reactions: exchange, demand, and sink reactions. An exchange reaction is a reversible, unbalanced reaction that adds to or removes an extracellular metabolite from the extracellular compartment. A demand reaction is an irreversible reaction that consumes an intracellular metabolite. A sink is similar to an exchange but specifically for intracellular metabolites. If you set the reaction `type` to something else, you must specify the desired identifier of the created reaction along with its upper and lower bound. The name will be given by the metabolite name and the given `type`. Parameters ---------- metabolite : cobra.Metabolite Any given metabolite. The compartment is not checked but you are encouraged to stick to the definition of exchanges and sinks. type : str, {"exchange", "demand", "sink"} Using one of the pre-defined reaction types is easiest. If you want to create your own kind of boundary reaction choose any other string, e.g., 'my-boundary'. reaction_id : str, optional The ID of the resulting reaction. This takes precedence over the auto-generated identifiers but beware that it might make boundary reactions harder to identify afterwards when using `model.boundary` or specifically `model.exchanges` etc. lb : float, optional The lower bound of the resulting reaction. ub : float, optional The upper bound of the resulting reaction. sbo_term : str, optional A correct SBO term is set for the available types. If a custom type is chosen, a suitable SBO term should also be set. Returns ------- cobra.Reaction The created boundary reaction. Examples -------- >>> import cobra.test >>> model = cobra.test.create_test_model("textbook") >>> demand = model.add_boundary(model.metabolites.atp_c, type="demand") >>> demand.id 'DM_atp_c' >>> demand.name 'ATP demand' >>> demand.bounds (0, 1000.0) >>> demand.build_reaction_string() 'atp_c --> ' """ ub = configuration.upper_bound if ub is None else ub lb = configuration.lower_bound if lb is None else lb types = { "exchange": ("EX", lb, ub, sbo_terms["exchange"]), "demand": ("DM", 0, ub, sbo_terms["demand"]), "sink": ("SK", lb, ub, sbo_terms["sink"]), } if type == "exchange": external = find_external_compartment(self) if metabolite.compartment != external: raise ValueError("The metabolite is not an external metabolite" " (compartment is `%s` but should be `%s`). " "Did you mean to add a demand or sink? " "If not, either change its compartment or " "rename the model compartments to fix this." % (metabolite.compartment, external)) if type in types: prefix, lb, ub, default_term = types[type] if reaction_id is None: reaction_id = "{}_{}".format(prefix, metabolite.id) if sbo_term is None: sbo_term = default_term if reaction_id is None: raise ValueError( "Custom types of boundary reactions require a custom " "identifier. Please set the `reaction_id`.") if reaction_id in self.reactions: raise ValueError( "Boundary reaction '{}' already exists.".format(reaction_id)) name = "{} {}".format(metabolite.name, type) rxn = Reaction(id=reaction_id, name=name, lower_bound=lb, upper_bound=ub) rxn.add_metabolites({metabolite: -1}) if sbo_term: rxn.annotation["sbo"] = sbo_term self.add_reactions([rxn]) return rxn def add_reactions(self, reaction_list): """Add reactions to the model. Reactions with identifiers identical to a reaction already in the model are ignored. The change is reverted upon exit when using the model as a context. Parameters ---------- reaction_list : list A list of `cobra.Reaction` objects """ def existing_filter(rxn): if rxn.id in self.reactions: logger.warning( "Ignoring reaction '%s' since it already exists.", rxn.id) return False return True # First check whether the reactions exist in the model. pruned = DictList(filter(existing_filter, reaction_list)) context = get_context(self) # Add reactions. Also take care of genes and metabolites in the loop. for reaction in pruned: reaction._model = self # Build a `list()` because the dict will be modified in the loop. for metabolite in list(reaction.metabolites): # TODO: Should we add a copy of the metabolite instead? if metabolite not in self.metabolites: self.add_metabolites(metabolite) # A copy of the metabolite exists in the model, the reaction # needs to point to the metabolite in the model. else: # FIXME: Modifying 'private' attributes is horrible. stoichiometry = reaction._metabolites.pop(metabolite) model_metabolite = self.metabolites.get_by_id( metabolite.id) reaction._metabolites[model_metabolite] = stoichiometry model_metabolite._reaction.add(reaction) if context: context( partial(model_metabolite._reaction.remove, reaction)) for gene in list(reaction._genes): # If the gene is not in the model, add it if not self.genes.has_id(gene.id): self.genes += [gene] gene._model = self if context: # Remove the gene later context(partial(self.genes.__isub__, [gene])) context(partial(setattr, gene, "_model", None)) # Otherwise, make the gene point to the one in the model else: model_gene = self.genes.get_by_id(gene.id) if model_gene is not gene: reaction._dissociate_gene(gene) reaction._associate_gene(model_gene) self.reactions += pruned if context: context(partial(self.reactions.__isub__, pruned)) # from cameo ... #self._populate_solver(pruned) def remove_reactions(self, reactions, remove_orphans=False): """Remove reactions from the model. The change is reverted upon exit when using the model as a context. Parameters ---------- reactions : list A list with reactions (`cobra.Reaction`), or their id's, to remove remove_orphans : bool Remove orphaned genes and metabolites from the model as well """ if isinstance(reactions, string_types) or hasattr(reactions, "id"): warn("need to pass in a list") reactions = [reactions] context = get_context(self) for reaction in reactions: # Make sure the reaction is in the model try: reaction = self.reactions[self.reactions.index(reaction)] except ValueError: warn("%s not in %s" % (reaction, self)) else: forward = reaction.forward_variable reverse = reaction.reverse_variable if context: obj_coef = reaction.objective_coefficient if obj_coef != 0: context( partial( self.solver.objective.set_linear_coefficients, { forward: obj_coef, reverse: -obj_coef }, )) context(partial(self._populate_solver, [reaction])) context(partial(setattr, reaction, "_model", self)) context(partial(self.reactions.add, reaction)) self.remove_cons_vars([forward, reverse]) self.reactions.remove(reaction) reaction._model = None for met in reaction._metabolites: if reaction in met._reaction: met._reaction.remove(reaction) if context: context(partial(met._reaction.add, reaction)) if remove_orphans and len(met._reaction) == 0: self.remove_metabolites(met) for gene in reaction._genes: if reaction in gene._reaction: gene._reaction.remove(reaction) if context: context(partial(gene._reaction.add, reaction)) if remove_orphans and len(gene._reaction) == 0: self.genes.remove(gene) if context: context(partial(self.genes.add, gene)) # remove reference to the reaction in all groups associated_groups = self.get_associated_groups(reaction) for group in associated_groups: group.remove_members(reaction) def add_groups(self, group_list): """Add groups to the model. Groups with identifiers identical to a group already in the model are ignored. If any group contains members that are not in the model, these members are added to the model as well. Only metabolites, reactions, and genes can have groups. Parameters ---------- group_list : list A list of `cobra.Group` objects to add to the model. """ def existing_filter(group): if group.id in self.groups: logger.warning("Ignoring group '%s' since it already exists.", group.id) return False return True if isinstance(group_list, string_types) or hasattr(group_list, "id"): warn("need to pass in a list") group_list = [group_list] pruned = DictList(filter(existing_filter, group_list)) for group in pruned: group._model = self for member in group.members: # If the member is not associated with the model, add it if isinstance(member, Metabolite): if member not in self.metabolites: self.add_metabolites([member]) if isinstance(member, Reaction): if member not in self.reactions: self.add_reactions([member]) # TODO(midnighter): `add_genes` method does not exist. # if isinstance(member, Gene): # if member not in self.genes: # self.add_genes([member]) self.groups += [group] def remove_groups(self, group_list): """Remove groups from the model. Members of each group are not removed from the model (i.e. metabolites, reactions, and genes in the group stay in the model after any groups containing them are removed). Parameters ---------- group_list : list A list of `cobra.Group` objects to remove from the model. """ if isinstance(group_list, string_types) or hasattr(group_list, "id"): warn("need to pass in a list") group_list = [group_list] for group in group_list: # make sure the group is in the model if group.id not in self.groups: logger.warning("%r not in %r. Ignored.", group, self) else: self.groups.remove(group) group._model = None def get_associated_groups(self, element): """Returns a list of groups that an element (reaction, metabolite, gene) is associated with. Parameters ---------- element: `cobra.Reaction`, `cobra.Metabolite`, or `cobra.Gene` Returns ------- list of `cobra.Group` All groups that the provided object is a member of """ # check whether the element is associated with the model return [g for g in self.groups if element in g.members] def add_cons_vars(self, what, **kwargs): """Add constraints and variables to the model's mathematical problem. Useful for variables and constraints that can not be expressed with reactions and simple lower and upper bounds. Additions are reversed upon exit if the model itself is used as context. Parameters ---------- what : list or tuple of optlang variables or constraints. The variables or constraints to add to the model. Must be of class `optlang.interface.Variable` or `optlang.interface.Constraint`. **kwargs : keyword arguments Passed to solver.add() """ add_cons_vars_to_problem(self, what, **kwargs) def remove_cons_vars(self, what): """Remove variables and constraints from the model's mathematical problem. Remove variables and constraints that were added directly to the model's underlying mathematical problem. Removals are reversed upon exit if the model itself is used as context. Parameters ---------- what : list or tuple of optlang variables or constraints. The variables or constraints to add to the model. Must be of class `optlang.interface.Variable` or `optlang.interface.Constraint`. """ remove_cons_vars_from_problem(self, what) @property def problem(self): """The interface to the model's underlying mathematical problem. Solutions to cobra models are obtained by formulating a mathematical problem and solving it. Cobrapy uses the optlang package to accomplish that and with this property you can get access to the problem interface directly. Returns ------- optlang.interface The problem interface that defines methods for interacting with the problem and associated solver directly. """ return self.solver.interface @property def variables(self): """The mathematical variables in the cobra model. In a cobra model, most variables are reactions. However, for specific use cases, it may also be useful to have other types of variables. This property defines all variables currently associated with the model's problem. Returns ------- optlang.container.Container A container with all associated variables. """ return self.solver.variables @property def constraints(self): """The constraints in the cobra model. In a cobra model, most constraints are metabolites and their stoichiometries. However, for specific use cases, it may also be useful to have other types of constraints. This property defines all constraints currently associated with the model's problem. Returns ------- optlang.container.Container A container with all associated constraints. """ return self.solver.constraints @property def boundary(self): """Boundary reactions in the model. Reactions that either have no substrate or product. """ return [rxn for rxn in self.reactions if rxn.boundary] @property def exchanges(self): """Exchange reactions in model. Reactions that exchange mass with the exterior. Uses annotations and heuristics to exclude non-exchanges such as sink reactions. """ return find_boundary_types(self, "exchange", None) @property def demands(self): """Demand reactions in model. Irreversible reactions that accumulate or consume a metabolite in the inside of the model. """ return find_boundary_types(self, "demand", None) @property def sinks(self): """Sink reactions in model. Reversible reactions that accumulate or consume a metabolite in the inside of the model. """ return find_boundary_types(self, "sink", None) def _populate_solver(self, reaction_list, metabolite_list=None): """Populate attached solver with constraints and variables that model the provided reactions. """ constraint_terms = AutoVivification() to_add = [] if metabolite_list is not None: for met in metabolite_list: to_add += [ self.problem.Constraint(Zero, name=met.id, lb=0, ub=0) ] self.add_cons_vars(to_add) for reaction in reaction_list: if reaction.id not in self.variables: forward_variable = self.problem.Variable(reaction.id) reverse_variable = self.problem.Variable(reaction.reverse_id) self.add_cons_vars([forward_variable, reverse_variable]) else: reaction = self.reactions.get_by_id(reaction.id) forward_variable = reaction.forward_variable reverse_variable = reaction.reverse_variable for metabolite, coeff in six.iteritems(reaction.metabolites): if metabolite.id in self.constraints: constraint = self.constraints[metabolite.id] else: constraint = self.problem.Constraint(Zero, name=metabolite.id, lb=0, ub=0) self.add_cons_vars(constraint, sloppy=True) constraint_terms[constraint][forward_variable] = coeff constraint_terms[constraint][reverse_variable] = -coeff self.solver.update() for reaction in reaction_list: reaction = self.reactions.get_by_id(reaction.id) reaction.update_variable_bounds() for constraint, terms in six.iteritems(constraint_terms): constraint.set_linear_coefficients(terms) def slim_optimize(self, error_value=float("nan"), message=None): """Optimize model without creating a solution object. Creating a full solution object implies fetching shadow prices and flux values for all reactions and metabolites from the solver object. This necessarily takes some time and in cases where only one or two values are of interest, it is recommended to instead use this function which does not create a solution object returning only the value of the objective. Note however that the `optimize()` function uses efficient means to fetch values so if you need fluxes/shadow prices for more than say 4 reactions/metabolites, then the total speed increase of `slim_optimize` versus `optimize` is expected to be small or even negative depending on how you fetch the values after optimization. Parameters ---------- error_value : float, None The value to return if optimization failed due to e.g. infeasibility. If None, raise `OptimizationError` if the optimization fails. message : string Error message to use if the model optimization did not succeed. Returns ------- float The objective value. """ self.solver.optimize() if self.solver.status == optlang.interface.OPTIMAL: return self.solver.objective.value elif error_value is not None: return error_value else: assert_optimal(self, message) def optimize(self, objective_sense=None, raise_error=False): """ Optimize the model using flux balance analysis. Parameters ---------- objective_sense : {None, 'maximize' 'minimize'}, optional Whether fluxes should be maximized or minimized. In case of None, the previous direction is used. raise_error : bool If true, raise an OptimizationError if solver status is not optimal. Notes ----- Only the most commonly used parameters are presented here. Additional parameters for cobra.solvers may be available and specified with the appropriate keyword argument. """ original_direction = self.objective.direction self.objective.direction = { "maximize": "max", "minimize": "min" }.get(objective_sense, original_direction) self.slim_optimize() solution = get_solution(self, raise_error=raise_error) self.objective.direction = original_direction return solution def repair(self, rebuild_index=True, rebuild_relationships=True): """Update all indexes and pointers in a model Parameters ---------- rebuild_index : bool rebuild the indices kept in reactions, metabolites and genes rebuild_relationships : bool reset all associations between genes, metabolites, model and then re-add them. """ if rebuild_index: # DictList indexes self.reactions._generate_index() self.metabolites._generate_index() self.genes._generate_index() self.groups._generate_index() if rebuild_relationships: for met in self.metabolites: met._reaction.clear() for gene in self.genes: gene._reaction.clear() for rxn in self.reactions: for met in rxn._metabolites: met._reaction.add(rxn) for gene in rxn._genes: gene._reaction.add(rxn) # point _model to self for l in (self.reactions, self.genes, self.metabolites, self.groups): for e in l: e._model = self @property def objective(self): """Get or set the solver objective Before introduction of the optlang based problems, this function returned the objective reactions as a list. With optlang, the objective is not limited a simple linear summation of individual reaction fluxes, making that return value ambiguous. Henceforth, use `cobra.util.solver.linear_reaction_coefficients` to get a dictionary of reactions with their linear coefficients (empty if there are none) The set value can be dictionary (reactions as keys, linear coefficients as values), string (reaction identifier), int (reaction index), Reaction or problem.Objective or sympy expression directly interpreted as objectives. When using a `HistoryManager` context, this attribute can be set temporarily, reversed when the exiting the context. """ return self.solver.objective @objective.setter 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) @property def objective_direction(self): """ Get or set the objective direction. When using a `HistoryManager` context, this attribute can be set temporarily, reversed when exiting the context. """ return self.solver.objective.direction @objective_direction.setter @resettable def objective_direction(self, value): value = value.lower() if value.startswith("max"): self.solver.objective.direction = "max" elif value.startswith("min"): self.solver.objective.direction = "min" else: raise ValueError("Unknown objective direction '{}'.".format(value)) def summary(self, solution=None, fva=None): """ Create a summary of the exchange fluxes of the model. Parameters ---------- solution : cobra.Solution, optional A previous model solution to use for generating the summary. If ``None``, the summary method will generate a parsimonious flux distribution (default None). fva : pandas.DataFrame or float, optional Whether or not to include flux variability analysis in the output. If given, `fva` should either be a previous FVA solution matching the model or a float between 0 and 1 representing the fraction of the optimum objective to be searched (default None). Returns ------- cobra.ModelSummary See Also -------- Reaction.summary Metabolite.summary """ from cobra.summary import ModelSummary return ModelSummary(model=self, solution=solution, fva=fva) def __enter__(self): """Record all future changes to the model, undoing them when a call to __exit__ is received""" # Create a new context and add it to the stack try: self._contexts.append(HistoryManager()) except AttributeError: self._contexts = [HistoryManager()] return self def __exit__(self, type, value, traceback): """Pop the top context manager and trigger the undo functions""" context = self._contexts.pop() context.reset() def merge(self, right, prefix_existing=None, inplace=True, objective="left"): """Merge two models to create a model with the reactions from both models. Custom constraints and variables from right models are also copied to left model, however note that, constraints and variables are assumed to be the same if they have the same name. right : cobra.Model The model to add reactions from prefix_existing : string Prefix the reaction identifier in the right that already exist in the left model with this string. inplace : bool Add reactions from right directly to left model object. Otherwise, create a new model leaving the left model untouched. When done within the model as context, changes to the models are reverted upon exit. objective : string One of 'left', 'right' or 'sum' for setting the objective of the resulting model to that of the corresponding model or the sum of both. """ if inplace: new_model = self else: new_model = self.copy() new_model.id = "{}_{}".format(self.id, right.id) new_reactions = deepcopy(right.reactions) if prefix_existing is not None: existing = new_reactions.query( lambda rxn: rxn.id in self.reactions) for reaction in existing: reaction.id = "{}{}".format(prefix_existing, reaction.id) new_model.add_reactions(new_reactions) interface = new_model.problem new_vars = [ interface.Variable.clone(v) for v in right.variables if v.name not in new_model.variables ] new_model.add_cons_vars(new_vars) new_cons = [ interface.Constraint.clone(c, model=new_model.solver) for c in right.constraints if c.name not in new_model.constraints ] new_model.add_cons_vars(new_cons, sloppy=True) new_model.objective = dict( left=self.objective, right=right.objective, sum=self.objective.expression + right.objective.expression, )[objective] return new_model def _repr_html_(self): return """ <table> <tr> <td><strong>Name</strong></td> <td>{name}</td> </tr><tr> <td><strong>Memory address</strong></td> <td>{address}</td> </tr><tr> <td><strong>Number of metabolites</strong></td> <td>{num_metabolites}</td> </tr><tr> <td><strong>Number of reactions</strong></td> <td>{num_reactions}</td> </tr><tr> <td><strong>Number of groups</strong></td> <td>{num_groups}</td> </tr><tr> <td><strong>Objective expression</strong></td> <td>{objective}</td> </tr><tr> <td><strong>Compartments</strong></td> <td>{compartments}</td> </tr> </table>""".format( name=self.id, address="0x0%x" % id(self), num_metabolites=len(self.metabolites), num_reactions=len(self.reactions), num_groups=len(self.groups), objective=format_long_string(str(self.objective.expression), 100), compartments=", ".join(v if v else k for k, v in iteritems(self.compartments)), )
def add_reactions(self, reaction_list): """Add reactions to the model. Reactions with identifiers identical to a reaction already in the model are ignored. The change is reverted upon exit when using the model as a context. Parameters ---------- reaction_list : list A list of `cobra.Reaction` objects """ try: reaction_list = DictList(reaction_list) except TypeError: reaction_list = DictList([reaction_list]) # First check whether the metabolites exist in the model existing = [rxn for rxn in reaction_list if rxn.id in self.reactions] for rxn in existing: LOGGER.info('skip adding reaction %s as already existing', rxn.id) reaction_list = [ rxn for rxn in reaction_list if rxn.id not in existing ] context = get_context(self) # Add reactions. Also take care of genes and metabolites in the loop for reaction in reaction_list: reaction._reset_var_cache() reaction._model = self # the reaction now points to the model # keys() is necessary because the dict will be modified during # the loop for metabolite in list(reaction._metabolites.keys()): # if the metabolite is not in the model, add it # should we be adding a copy instead. if metabolite not in self.metabolites: self.add_metabolites(metabolite) # A copy of the metabolite exists in the model, the reaction # needs to point to the metabolite in the model. else: stoichiometry = reaction._metabolites.pop(metabolite) model_metabolite = self.metabolites.get_by_id( metabolite.id) reaction._metabolites[model_metabolite] = stoichiometry model_metabolite._reaction.add(reaction) if context: context( partial(model_metabolite._reaction.remove, reaction)) for gene in list(reaction._genes): # If the gene is not in the model, add it if not self.genes.has_id(gene.id): self.genes += [gene] gene._model = self if context: # Remove the gene later context(partial(self.genes.__isub__, [gene])) context(partial(setattr, gene, '_model', None)) # Otherwise, make the gene point to the one in the model else: model_gene = self.genes.get_by_id(gene.id) if model_gene is not gene: reaction._dissociate_gene(gene) reaction._associate_gene(model_gene) self.reactions += reaction_list if context: context(partial(self.reactions.__isub__, reaction_list)) # from cameo ... self._populate_solver(reaction_list)