コード例 #1
0
def pytfa_relax_dgo(tmodel: ThermoModel, reactions_to_ignore=None):
    """
    Uses the pytfa algorithm to relax the standard Gibbs free energy bounds.

    Parameters
    ----------
    tmodel : pytfa.ThermoModel
        A cobra model with thermodynamics information.
    reactions_to_ignore : list
        A list of reaction IDs that will be ignored by the relaxation
        algorithm.

    Returns
    -------
    relaxed_model : pytfa.ThermoModel
        The model with the relaxations applied to it.
    relax_table : pandas.DataFrame
        A 2-column table containing bound violation magnitudes.

    Raises
    ------
    Infeasible
        If the feasibility relaxation fails.
    """
    from pytfa.optim.relaxation import relax_dgo

    if reactions_to_ignore is None:
        reactions_to_ignore = []
    relaxed_model, _, relax_table = relax_dgo(tmodel, reactions_to_ignore)
    if relax_table is None:
        raise Infeasible("Failed to create the feasibility relaxation!")
    relaxed_model.optimize()
    relax_table = get_dgo_bound_change(relaxed_model, relax_table)
    return relaxed_model, relax_table
コード例 #2
0
def find_reactions_with_unbounded_flux_default_condition(model):
    """
    Return list of reactions whose flux is unbounded in the default condition.

    Parameters
    ----------
    model : cobra.Model
        The metabolic model under investigation.

    Returns
    -------
    tuple
        list
            A list of reactions that in default modeling conditions are able to
            carry flux as high/low as the systems maximal and minimal bounds.
        float
            The fraction of the amount of unbounded reactions to the amount of
            non-blocked reactions.
        list
            A list of reactions that in default modeling conditions are not able
            to carry flux at all.

    """
    try:
        fva_result = flux_variability_analysis(model, fraction_of_optimum=1.0)
    except Infeasible as err:
        LOGGER.error("Failed to find reactions with unbounded flux "
                     "because '{}'. This may be a bug.".format(err))
        raise Infeasible("It was not possible to run flux variability "
                         "analysis on the model. Make sure that the model "
                         "can be solved! Check if the constraints are not "
                         "too strict.")
    # Per reaction (row) the flux is below threshold (close to zero).
    conditionally_blocked = fva_result.loc[fva_result.abs().max(
        axis=1) < TOLERANCE_THRESHOLD].index.tolist()
    small, large = helpers.find_bounds(model)
    # Find those reactions whose flux is close to or outside of the median
    # upper or lower bound, i.e., appears unconstrained.
    unlimited_flux = fva_result.loc[
        np.isclose(fva_result["maximum"], large, atol=TOLERANCE_THRESHOLD) |
        (fva_result["maximum"] > large)
        | np.isclose(fva_result["minimum"], small, atol=TOLERANCE_THRESHOLD) |
        (fva_result["minimum"] < small)].index.tolist()
    try:
        fraction = len(unlimited_flux) / \
            (len(model.reactions) - len(conditionally_blocked))
    except ZeroDivisionError:
        LOGGER.error("Division by Zero! Failed to calculate the "
                     "fraction of unbounded reactions. Does this model "
                     "have any reactions at all?")
        raise ZeroDivisionError("It was not possible to calculate the "
                                "fraction of unbounded reactions to "
                                "un-blocked reactions. This may be because"
                                "the model doesn't have any reactions at "
                                "all or that none of the reactions can "
                                "carry a flux larger than zero!")

    return unlimited_flux, fraction, conditionally_blocked
コード例 #3
0
def find_reactions_with_unbounded_flux_default_condition(model):
    """
    Return list of reactions whose flux is unbounded in the default condition.

    Parameters
    ----------
    model : cobra.Model
        The metabolic model under investigation.

    Returns
    -------
    unlimited_reactions: list
        A list of reactions that in default modeling conditions are able to
        carry flux as high/low as the systems maximal and minimal bounds.
    fraction: float
        The fraction of the amount of unbounded reactions to the amount of
        non-blocked reactions.
    conditionally_blocked_reactions: list
        A list of reactions that in default modeling conditions are not able
        to carry flux at all.

    """
    with model:
        try:
            fva_result = flux_variability_analysis(model)
        except Infeasible as err:
            LOGGER.error("Failed to find reactions with unbounded flux "
                         "because '{}'. This may be a bug.".format(err))
            raise Infeasible("It was not possible to run flux variability "
                             "analysis on the model. Make sure that the model "
                             "can be solved! Check if the constraints are not "
                             "too strict.")
        conditionally_blocked = fva_result.loc[(fva_result["maximum"] == 0.0) &
                                               (fva_result["minimum"] == 0.0)]
        max_bound = max([rxn.upper_bound for rxn in model.reactions])
        min_bound = min([rxn.lower_bound for rxn in model.reactions])
        unlimited_flux = fva_result.loc[(fva_result["maximum"] == max_bound) |
                                        (fva_result["minimum"] == min_bound)]
        conditionally_blocked_reactions = [
            model.reactions.get_by_id(met_id)
            for met_id in conditionally_blocked.index
        ]
        unlimited_reactions = [
            model.reactions.get_by_id(met_id)
            for met_id in unlimited_flux.index
        ]
        try:
            fraction = len(unlimited_reactions) / float(
                (len(model.reactions) - len(conditionally_blocked_reactions)))
        except ZeroDivisionError:
            LOGGER.error("Division by Zero! Failed to calculate the "
                         "fraction of unbounded reactions. Does this model "
                         "have any reactions at all?")
            raise ZeroDivisionError("It was not possible to calculate the "
                                    "fraction of unbounded reactions to "
                                    "un-blocked reactions. This may be because"
                                    "the model doesn't have any reactions at "
                                    "all or that none of the reactions can "
                                    "carry a flux larger than zero!")

        return unlimited_reactions, fraction, conditionally_blocked_reactions
コード例 #4
0
def relax(tmodel: ThermoModel,
          reactions_to_relax,
          metabolites_to_relax,
          destructive=True):
    """
    Uses the Gurobi subroutines to relax the standard Gibbs free energy and log concentration bounds.

    Parameters
    ----------
    tmodel : pytfa.ThermoModel
        A cobra model with thermodynamics information.
    reactions_to_relax : list
        A list of reaction IDs whose standard Gibbs free energy bounds are allowed to be violated.
    metabolites_to_relax : list
        A list of metabolite IDs whose log concentration bounds are allowed to be violated.
    destructive : bool
        If True, apply bound changes to input model. If False, leave input model intact.

    Returns
    -------
    cons_table : pandas.DataFrame
        A 2-column table containing bound violation magnitudes of standard Gibbs free energy variables.
    vars_table : pandas.DataFrame
        A 2-column table containing bound violation magnitudes of log concentration variables.

    Raises
    ------
    ModuleNotFoundError
        If Gurobi is not the solver of the input model.
    Infeasible
        If the feasibility relaxation fails.
    """
    if tmodel.solver.interface.__name__ != "optlang.gurobi_interface":
        raise ModuleNotFoundError("Requires Gurobi.")

    # copy Gurobi model
    grb_model: GRBModel = tmodel.solver.problem.copy()

    relax_cons = [
        grb_model.getConstrByName(con.name) for con in tmodel.negative_delta_g
        if con.id in reactions_to_relax
    ]
    relax_vars = [
        grb_model.getVarByName(var.name) for var in tmodel.log_concentration
        if var.id in metabolites_to_relax
    ]
    cons_penalties = [1] * len(relax_cons)
    vars_penalties = [1] * len(relax_vars)

    # perform relaxation of variable bounds
    relax_obj = grb_model.feasRelax(
        relaxobjtype=0,
        minrelax=True,
        vars=relax_vars,
        lbpen=vars_penalties,
        ubpen=vars_penalties,
        constrs=relax_cons,
        rhspen=cons_penalties,
    )

    # check if relaxation was successful
    if relax_obj < 0:
        raise Infeasible("Failed to create the feasibility relaxation!")

    grb_model.optimize()

    # transfer/record the standard dG changes to the ThermoModel
    if relax_cons:
        rows = []
        violations = _extract_bound_violations(
            grb_model, tmodel.delta_gstd.list_attr("id"), "ArtN_G_", "ArtP_G_")
        for rxn_id, lb_change, ub_change in violations:
            if destructive:
                var = tmodel.delta_gstd.get_by_id(rxn_id).variable
                var.set_bounds(lb=var.lb + lb_change, ub=var.lb + ub_change)
            rows.append({
                "reaction":
                rxn_id,
                "bound_change":
                lb_change + ub_change,
                "subsystem":
                tmodel.reactions.get_by_id(rxn_id).subsystem,
            })
        cons_table = pd.DataFrame.from_records(rows, index="reaction")
    else:
        cons_table = None

    # transfer/record the log concentration changes to the ThermoModel
    if relax_vars:
        rows = []
        violations = _extract_bound_violations(
            grb_model, tmodel.log_concentration.list_attr("id"), "ArtL_LC_",
            "ArtU_LC_")
        for met_id, lb_change, ub_change in violations:
            if destructive:
                var = tmodel.log_concentration.get_by_id(met_id).variable
                var.set_bounds(lb=var.lb + lb_change, ub=var.lb + ub_change)
            rows.append({
                "metabolite":
                met_id,
                "bound_change":
                lb_change + ub_change,
                "compartment":
                tmodel.compartments[tmodel.metabolites.get_by_id(
                    met_id).compartment]["name"],
            })
        vars_table = pd.DataFrame.from_records(rows, index="metabolite")
    else:
        vars_table = None

    # solve relaxed model
    if destructive:
        tmodel.optimize()

    return cons_table, vars_table
コード例 #5
0
    def save_conditions(self,
                        model,
                        conditions_id,
                        carbon_source=None,
                        apply_to=None,
                        observe_growth=True):
        """
        Add media conditions that a given model has to the project. Essentially the lower bounds on transport reactions.
        All other trnasport reactions will be switched off.

        In some cases, one may wish to set conditions under which a model should not grow.
        observe_growth allows this to be configured. If using a single model or if the condition should not grow under
        any circumstances, observe_growth can be set to false.

        If certain models should grow, specify this with a a tuple where the entries refer to the model files tracked by
        the project. All models specified must be contained within the project.

        :param model: cobrapy model
        :param conditions_id: identifier for the conditions should be unique
        :param carbon_source: name of carbon source in the media that has a fixed uptake rate
        :param apply_to: iterable of models that this set of designs applies to
        :param observe_growth: bool or list.
        :return:
        """
        # If it can't get a solution then this will raise errors.
        status = model.solver.optimize()
        if not observe_growth and status != 'infeasible':
            raise AssertionError('Model should not grow')
        elif observe_growth and status == 'infeasible':
            raise Infeasible(
                'Cannot find valid solution - should conditions result in growth?'
            )

        if apply_to is None:
            apply_to = []
        elif not isinstance(apply_to, list):
            apply_to = [apply_to]

        for mdl_path in apply_to:
            if mdl_path not in self.config.models:
                raise KeyError(
                    "Model {} not in current project".format(mdl_path))

        # List all transport reactions in to the cell
        def is_exchange(rr):
            return (len(rr.reactants) == 0 or len(rr.products) == 0) and len(
                rr.metabolites) == 1

        def is_media(rr):
            if rr.lower_bound < 0 and is_exchange(rr):
                return True
            return False

        media = {}
        for r in model.reactions:
            if is_media(r):
                media[r.id] = r.lower_bound

        # save to conditions file
        conditions_store = self.get_conditions(update=True)

        if carbon_source is not None and carbon_source not in media:
            raise KeyError("carbon source not valid")

        objective_reactions = []
        for reaction in model.reactions:
            if reaction.objective_coefficient:
                objective_reactions.append(reaction.id)

        objective_direction = model.objective_direction

        conditions_store['growth_conditions'][conditions_id] = dict(
            media=media,
            models=apply_to,
            observe_growth=observe_growth,
            carbon_source=carbon_source,
            objective_reactions=objective_reactions,
            objective_direction=objective_direction)
        self._write_conditions(conditions_store)
コード例 #6
0
    def save_design(self,
                    model,
                    did,
                    name,
                    description='',
                    conditions=None,
                    base_model=None,
                    parent=None,
                    overwrite=False):
        """
        Creates a design from a diff of model_a and model_b
        
        id should be a string with no spaces (conversion handled)
        
        Returns the saved design diff

        :param model: cobrapy model
        :param did: design identifier
        :param name: name of the design
        :param description: text description of what it does
        :param conditions: conditions that should be applied for the design
        :param base_model: Model that the design should be derived from - specified model included in project
        :param parent: string for parent design that this design is a diff from
        :param overwrite: overwrite and existing design (only applies if the id is already in use)
        """
        # Test, infeasible designs should not be added
        status = model.solver.optimize()
        if status == 'infeasible':
            raise Infeasible('Could not find valid solution')

        if parent is not None:
            if isinstance(parent, string_types):
                parent = self.get_design(parent)
            elif not isinstance(
                    parent,
                    StrainDesign) or parent.id not in self.list_designs:
                raise DesignError(
                    'Parent relate a valid project strain design')

        did = str(did).replace(' ', '_')
        design_save_path = os.path.join(self.design_path,
                                        '{}.json'.format(did))

        if os.path.exists(design_save_path) and not overwrite:
            raise IOError('File {} exists'.format(design_save_path))

        if base_model is None and parent is None:
            base_model = self.config.default_model
        elif parent is not None:
            # If a parent design is specified this model is loaded first
            base_model = parent.base_model
        elif base_model is not None and base_model not in self.config.models:
            raise KeyError('Base model not found should be one of {}'.format(
                " ".join(self.config.models)))

        if parent is None:
            lmodel = self.load_model(base_model)
        else:
            lmodel = parent.load()

        if conditions is not None:
            self.load_conditions(conditions, lmodel)

        # Find all the differences between the models
        diff = model_diff(lmodel, model)

        if parent is not None:
            parent = parent.id

        diff['description'] = description
        diff['id'] = did
        diff['name'] = name
        diff['conditions'] = conditions
        diff['base_model'] = base_model
        diff['parent'] = parent

        des = StrainDesign.from_dict(did, diff, self)
        des.to_json(design_save_path, overwrite=overwrite)

        return des
コード例 #7
0
def bound_relaxation(
    infeasible_model, fittedFluxes, destructive=True, fluxes_to_ignore=[]
):
    """
    Relaxation function for cobra metabolic models. By making use of the
    Gurobi solver, this functions figures out which bounds have to be
    relaxed by how much in order to make the model solution feasible.
    If `destructive=True`, then these changes are automatically applied.

    Parameters
    ----------
    infeasible_model : cobra.Model
        A metabolic model with constraints that lead to an infeasible
        solution.
    fittedFluxes : pandas.DataFrame
        Dataframe (reimported output of an INCA simulation)
        that contains the confidence intervals predicted for
        the model.
    destructive : bool
        Preset to `True`. If True, then the calculated fluxes will be
        applied to the model.
    fluxes_to_ignore : list
        A list of fluxes that should not be relaxed. Preset to `[]`.

    Returns
    -------
    cons_table : pandas.DataFrame
        A dataframe listing the reactions that have to be relaxed and
        the changes that need to be applied.

    Raises
    ------
    ModuleNotFoundError
        If Gurobi is not the solver of the input model.
    Infeasible
        If the feasibility relaxation fails.
    """
    model = infeasible_model
    if model.solver.interface.__name__ != "optlang.gurobi_interface":
        raise ModuleNotFoundError("Requires Gurobi solver.")

    # copy Gurobi model
    grb_model: GRBModel = model.solver.problem.copy()
    MFA_fluxes = _reshape_fluxes(fittedFluxes)
    reactions_to_relax = list(MFA_fluxes.keys())

    # remove fluxes to ignore
    if fluxes_to_ignore:
        for flux_to_ignore in fluxes_to_ignore:
            reactions_to_relax.remove(flux_to_ignore)

    # There are no metabolites to relax to we leave relax_cons empty
    relax_cons = []

    # Get forward and reverse reaction separately
    relax_vars = [
        grb_model.getVarByName(con.id)
        for con in model.reactions
        if con.id in reactions_to_relax
    ]
    relax_vars_reverse = [
        grb_model.getVarByName(con.reverse_id)
        for con in model.reactions
        if con.id in reactions_to_relax
    ]

    # Combine the lists
    relax_vars = relax_vars + relax_vars_reverse

    cons_penalties = relax_cons
    vars_penalties = [1] * len(relax_vars)

    # perform relaxation of variable bounds
    relax_obj = grb_model.feasRelax(
        relaxobjtype=0,
        minrelax=True,
        vars=relax_vars,
        lbpen=vars_penalties,
        ubpen=vars_penalties,
        constrs=relax_cons,
        rhspen=cons_penalties,
    )

    # check if relaxation was successful
    if relax_obj <= 0:
        raise Infeasible("Failed to create the feasibility relaxation!")
    grb_model.optimize()

    # transfer/record the lower/upper bound changes to the cobra model
    if relax_vars:
        variables = [var.VarName for var in relax_vars]
        rows = []
        violations = _extract_bound_violations(
            grb_model, variables, "ArtL_", "ArtU_"
        )
        for rxn_id, lb_change, ub_change in violations:
            try:
                subsystem = model.reactions.get_by_id(rxn_id).subsystem
            except KeyError:
                subsystem = model.reactions.get_by_id(
                    re.match(".+?(?=_reverse_)", rxn_id)[0]
                ).subsystem
            rows.append(
                {
                    "reaction": rxn_id,
                    "lb_change": lb_change,
                    "ub_change": ub_change,
                    "subsystem": subsystem,
                }
            )
            if destructive:
                if "_reverse_" in rxn_id:
                    # reverse reactions are only temporary for optimization,
                    # so we have to adjust the corresponding original reaction
                    rxn_id = re.match(".+?(?=_reverse_)", rxn_id)[0]
                    lb_change, ub_change = -ub_change, -lb_change
                lb = model.reactions.get_by_id(rxn_id).lower_bound
                ub = model.reactions.get_by_id(rxn_id).upper_bound
                model.reactions.get_by_id(rxn_id).lower_bound = lb + lb_change
                model.reactions.get_by_id(rxn_id).upper_bound = ub + ub_change

        cons_table = pd.DataFrame.from_records(rows, index="reaction")
    else:
        cons_table = None

    return cons_table
コード例 #8
0
    def _fexec(self, model=None):
        """
        broken up code for testing individual entries
        """
        if self._override_model is not None:
            model = self._override_model
        elif model is None and self._model_loader is None:
            model = self.project.load_model()
        elif self._model_loader is not None:
            try:
                model = self._model_loader.load(self.log)
            except Exception as ex:
                self.log.add_error("Error loading model {}".format(ex))
                return self.log

        elif not isinstance(model, cobra.Model):
            raise TypeError("Expected gsmodutils or cobra model")

        try:
            status = model.solver.optimize()

            if status == 'infeasible':
                raise Infeasible('Cannot find solution')

            # Test entries that require non-zero fluxes
            for rid in self.entry['required_reactions']:

                try:
                    reac = model.reactions.get_by_id(rid)

                    self.log.assertion(
                        reac.flux == 0,
                        success_msg='required reaction {} not active'.format(
                            rid),
                        error_msg='required reaction {} present at steady state'
                        .format(rid),
                        desc='.required_reaction')

                except KeyError:
                    self.log.assertion(
                        False,
                        success_msg='',
                        error_msg="required reaction {} not found in model".
                        format(rid),
                        desc='.required_reaction .reaction_not_found')
                    continue

            # tests for specific reaction flux ranges
            for rid, (lb, ub) in self.entry['reaction_fluxes'].items():
                try:
                    reac = model.reactions.get_by_id(rid)
                    if reac.flux < lb or reac.flux > ub:
                        err = 'reaction {} outside of flux bounds {}, {}'.format(
                            rid, lb, ub)
                        self.log.error.append((err, '.reaction_flux'))
                    else:
                        msg = 'reaction {} inside flux bounds {}, {}'.format(
                            rid, lb, ub)
                        self.log.success.append((msg, '.reaction_flux'))
                except KeyError:
                    # Error log of reaction not found
                    self.log.assertion(
                        False,
                        success_msg='',
                        error_msg="required reaction {} not found in model".
                        format(rid),
                        desc='.reaction_flux .reaction_not_found')
                    continue

        except Infeasible:
            # This is a full test failure (i.e. the model does not work)
            # not a conditional assertion
            self.log.add_error("No solution found with model configuration",
                               '.no_solution')

        return self.log