예제 #1
0
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
예제 #2
0
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
예제 #3
0
    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)
예제 #4
0
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
예제 #5
0
    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
예제 #6
0
    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
예제 #7
0
 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)
예제 #8
0
파일: model.py 프로젝트: Fxe/cobrakbase
 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()
예제 #9
0
    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
예제 #10
0
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
예제 #11
0
    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
예제 #12
0
    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]
예제 #13
0
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)
예제 #14
0
파일: model.py 프로젝트: wbryant/cobrapy
    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)
예제 #15
0
    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
예제 #16
0
파일: model.py 프로젝트: wbryant/cobrapy
    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
예제 #17
0
파일: model.py 프로젝트: wbryant/cobrapy
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()
예제 #18
0
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)
예제 #19
0
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)))
예제 #20
0
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)
            ))
예제 #21
0
    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)
예제 #22
0
    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))
예제 #23
0
    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
예제 #24
0
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)
예제 #25
0
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)),
        )
예제 #26
0
    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)