Exemple #1
0
    def adjust_pool_bounds(self,
                           min_objective=0.05,
                           inplace=False,
                           tolerance=1e-9):
        """Adjust protein pool bounds minimally to make model feasible.

        Bounds from measurements can make the model non-viable or even infeasible. Adjust these minimally by minimizing
        the positive deviation from the measured values.

        :param min_objective: float, The minimum value of for the ojective for calling the model viable.
        :param inplace: bool, Apply the adjustments to the model.
        :param tolerance: float, Minimum non-zero value. Solver specific value.
        :returns: pd.DataFrame, Data frame with the series 'original' bounds and the new 'adjusted' bound, \
            and the optimized 'addition'.

        """
        from reframed.solvers import solver_instance
        solver = solver_instance(self)
        solver.add_constraint('constraint_objective',
                              self.get_objective,
                              sense='>',
                              rhs=min_objective)
        for pool in self.individual_protein_exchanges:
            solver.add_variable('pool_diff_' + pool.id, lb=0)
            solver.add_variable('measured_bound_' + pool.id,
                                lb=pool.upper_bound,
                                ub=pool.upper_bound)
Exemple #2
0
def build_ensemble(model, reaction_scores, size, init_env=None):
    """ Reconstruct a model ensemble using the CarveMe approach.

    Args:
        model (CBModel): universal model
        reaction_scores (dict): reaction scores
        size (int): ensemble size
        outputfile (str): write model to SBML file (optional)
        flavor (str): SBML flavor ('cobra' or 'fbc2', optional)
        init_env (Environment): initialize final model with given Environment (optional)

    Returns:
        EnsembleModel: reconstructed ensemble
    """

    scores = dict(reaction_scores[['reaction', 'normalized_score']].values)
    unscored = [
        r_id for r_id in model.reactions
        if r_id not in scores and not r_id.startswith('R_EX')
    ]
    logstd = np.std(np.log([x for x in scores.values() if x > 0]))

    reaction_status = {r_id: [] for r_id in model.reactions}
    solver = solver_instance(model)
    failed = 0

    for i in range(size):
        random_scores = -np.exp(logstd * np.random.randn(len(unscored)))
        all_scores = dict(zip(unscored, random_scores))
        all_scores.update(scores)

        sol = minmax_reduction(model, all_scores, solver=solver)

        if sol.status == Status.OPTIMAL:
            for r_id in model.reactions:
                active = (abs(sol.values[r_id]) >= 1e-6
                          or (sol.values.get('yf_' + r_id, 0) > 0.5)
                          or (sol.values.get('yr_' + r_id, 0) > 0.5))
                reaction_status[r_id].append(active)
        else:
            failed += 1

    ensemble_size = size - failed
    ensemble = EnsembleModel(model, ensemble_size, reaction_status)
    ensemble.simplify()

    for i, row in reaction_scores.iterrows():
        r_id = row['reaction']
        if r_id in ensemble.model.reactions:
            gpr = parse_gpr_rule(row['GPR'])
            ensemble.model.reactions[r_id].set_gpr_association(gpr)

    if init_env:
        init_env.apply(ensemble.model, inplace=True, warning=False)
Exemple #3
0
def gapFill(model,
            universe,
            constraints=None,
            min_growth=0.1,
            scores=None,
            inplace=True,
            bigM=1e3,
            abstol=1e-9,
            solver=None,
            tag=None):
    """ Gap Fill a metabolic model by adding reactions from a reaction universe

    Args:
        model (CBModel): original model
        universe (CBModel): universe model
        constraints (dict): additional constraints (optional)
        min_growth (float): minimum growth rate (default: 0.1)
        scores (dict): reaction scores (optional, see notes)
        inplace (bool): modify given model in place (default: True)
        bigM (float): maximal reaction flux (default: 1000)
        abstol (float): minimum threshold to consider a reaction active (default: 1e-9)
        solver (Solver): solver instance (optional)
        tag (str): add a metadata tag to gapfilled reactions (optional)

    Returns:
        CBModel: gap filled model (if inplace=False)

    Notes:
        Scores can be used to make some reactions more likely to be included.
        Scored reactions have a penalty of 1/(1+score), which varies between [0, 1].
        Unscored reactions have a penalty of 1.
    """

    new_reactions = set(universe.reactions) - set(model.reactions)

    model = merge_models(model, universe, inplace, tag=tag)

    for r_id in new_reactions:
        if r_id.startswith('R_EX'):
            model.set_flux_bounds(r_id, lb=0)

    if not solver:
        solver = solver_instance(model)

    if not scores:
        scores = {}

    if not hasattr(solver, '_gapfill_flag'):
        solver._gapfill_flag = True

        for r_id in new_reactions:
            solver.add_variable('y_' + r_id,
                                0,
                                1,
                                vartype=VarType.BINARY,
                                update=False)

        solver.update()

        for r_id in new_reactions:
            solver.add_constraint('lb_' + r_id, {
                r_id: 1,
                'y_' + r_id: bigM
            },
                                  '>',
                                  0,
                                  update=False)
            solver.add_constraint('ub_' + r_id, {
                r_id: 1,
                'y_' + r_id: -bigM
            },
                                  '<',
                                  0,
                                  update=False)

        biomass = model.biomass_reaction
        solver.add_constraint('min_growth', {biomass: 1},
                              '>',
                              min_growth,
                              update=False)

        solver.update()

    objective = {
        'y_' + r_id: 1.0 / (1.0 + scores.get(r_id, 0.0))
        for r_id in new_reactions
    }

    solution = solver.solve(linear=objective,
                            minimize=True,
                            constraints=constraints)

    if solution.status == Status.OPTIMAL:

        inactive = [
            r_id for r_id in new_reactions
            if abs(solution.values[r_id]) < abstol
        ]

    else:
        raise RuntimeError('Failed to gapfill model for medium {}'.format(tag))

    model.remove_reactions(inactive)
    del_metabolites = disconnected_metabolites(model)
    model.remove_metabolites(del_metabolites)

    if not inplace:
        return model
Exemple #4
0
def multiGapFill(model,
                 universe,
                 media,
                 media_db,
                 min_growth=0.1,
                 max_uptake=10,
                 scores=None,
                 inplace=True,
                 bigM=1e3,
                 spent_model=None):
    """ Gap Fill a metabolic model for multiple environmental conditions

    Args:
        model (CBModel): original model
        universe (CBModel): universe model
        media (list): list of growth media ids
        media_db (dict): growth media database (see notes)
        min_growth (float): minimum growth rate (default: 0.1)
        max_uptake (float): maximum uptake rate (default: 10)
        scores (dict): reaction scores (optional, see *gapFill* for details)
        inplace (bool): modify given model in place (default: True)
        bigM (float): maximal reaction flux (default: 1000)
        spent_model (CBModel): additional species to generate spent medium compounds

    Returns:
        CBModel: gap filled model (if inplace=False)

    Notes:
        *media_db* is a dict from medium name to the list of respective compounds.
    """

    ABSTOL = 1e-6

    if not inplace:
        model = model.copy()

    new_reactions = set(universe.reactions) - set(model.reactions)

    for r_id in new_reactions:
        if r_id.startswith('R_EX'):
            universe.set_flux_bounds(r_id, lb=0)

    merged_model = merge_models(model, universe, inplace=False)
    solver = solver_instance(merged_model)

    if spent_model:
        solver0 = solver_instance(spent_model)

    for medium_name in media:
        if medium_name in media_db:
            compounds = set(media_db[medium_name])

            constraints = medium_to_constraints(merged_model,
                                                compounds,
                                                max_uptake=max_uptake,
                                                inplace=False,
                                                verbose=False)

            if spent_model:
                constraints0 = medium_to_constraints(spent_model,
                                                     compounds,
                                                     max_uptake=max_uptake,
                                                     inplace=False,
                                                     verbose=False)
                for r_id in spent_model.get_exchange_reactions():
                    if r_id in constraints:
                        sol = FBA(spent_model,
                                  objective={r_id: 1},
                                  constraints=constraints0,
                                  solver=solver0,
                                  get_values=False)
                        if sol.fobj > ABSTOL:
                            constraints[r_id] = (-max_uptake, None)
                            print("added", r_id[5:-2], "to", medium_name)

            gapFill(model,
                    universe,
                    constraints=constraints,
                    min_growth=min_growth,
                    scores=scores,
                    inplace=True,
                    bigM=bigM,
                    solver=solver,
                    tag=medium_name)
        else:
            print('Medium {} not in database, ignored.'.format(medium_name))

    return model
Exemple #5
0
    def simulate(self,
                 objective=None,
                 method=SimulationMethod.FBA,
                 maximize=True,
                 constraints=None,
                 reference=None,
                 scalefactor=None,
                 solver=None):
        '''
        Simulates a phenotype when applying a set constraints using the specified method.

        :param dic objective: The simulation objective. If none, the model objective is considered.
        :param method: The SimulationMethod (FBA, pFBA, lMOMA, etc ...)
        :param boolean maximize: The optimization direction
        :param dic constraints: A dictionary of constraints to be applied to the model.
        :param dic reference: A dictionary of reaction flux values.
        :param float scalefactor: A positive scaling factor for the solver. Default None.
        :param solver: An instance of the solver.
        '''

        if not objective:
            objective = self.model.get_objective()

        simul_constraints = OrderedDict()
        if constraints:
            simul_constraints.update(constraints)
        if self.constraints:
            simul_constraints.update(self.constraints)
        if self.environmental_conditions:
            simul_constraints.update(self.environmental_conditions)

        a_solver = solver
        if not self._reset_solver and not a_solver:
            if self.solver is None:
                self.solver = solver_instance(self.model)
            a_solver = self.solver

        # scales the model if a scalling factor is defined.
        # ... scalling should be implemented at the solver level.
        if scalefactor:
            for _, rxn in self.model.reactions.items():
                rxn.lb = rxn.lb * scalefactor
                rxn.ub = rxn.ub * scalefactor
            if simul_constraints:
                for idx, constraint in simul_constraints.items():
                    if isinstance(constraint, (int, float)):
                        simul_constraints[idx] = constraint * scalefactor
                    elif isinstance(constraint, tuple):
                        simul_constraints[idx] = tuple(x * scalefactor
                                                       for x in constraint)
                    else:
                        raise ValueError("Could not scale the model")

        # TODO: simplifly ...
        if method in [
                SimulationMethod.lMOMA, SimulationMethod.MOMA,
                SimulationMethod.ROOM
        ] and reference is None:
            reference = self.reference

        if method == SimulationMethod.FBA:
            solution = FBA(self.model,
                           objective=objective,
                           minimize=not maximize,
                           constraints=simul_constraints,
                           solver=a_solver)
        elif method == SimulationMethod.pFBA:
            solution = pFBA(self.model,
                            objective=objective,
                            minimize=not maximize,
                            constraints=simul_constraints,
                            solver=a_solver,
                            obj_frac=0.999)
        elif method == SimulationMethod.lMOMA:
            solution = lMOMA(self.model,
                             constraints=simul_constraints,
                             reference=reference,
                             solver=a_solver)
        elif method == SimulationMethod.MOMA:
            solution = MOMA(self.model,
                            constraints=simul_constraints,
                            reference=reference,
                            solver=a_solver)
        elif method == SimulationMethod.ROOM:
            solution = ROOM(self.model,
                            constraints=simul_constraints,
                            reference=reference,
                            solver=a_solver)
        # Special case in which only the simulation context is required without any simulatin result
        elif method == SimulationMethod.NONE:
            solution = Solution(status=s_status.UNKNOWN,
                                message=None,
                                fobj=None,
                                values=None)
        else:
            raise Exception("Unknown method to perform the simulation.")

        # undoes the model scaling
        if scalefactor:
            for _, rxn in self.model.reactions.items():
                rxn.lb = rxn.lb / scalefactor
                rxn.ub = rxn.ub / scalefactor
            if solution.status in (s_status.OPTIMAL, s_status.SUBOPTIMAL):
                solution.fobj = solution.fobj / scalefactor
                for x, y in solution.values.items():
                    solution.values[x] = y / scalefactor

        status = self.__status_mapping[solution.status]

        result = SimulationResult(self.model,
                                  solution.fobj,
                                  fluxes=solution.values,
                                  status=status,
                                  envcond=self.environmental_conditions,
                                  model_constraints=self.constraints,
                                  simul_constraints=constraints,
                                  maximize=maximize)
        return result
Exemple #6
0
def minmax_reduction(model,
                     scores,
                     min_growth=0.1,
                     min_atpm=0.1,
                     eps=1e-3,
                     bigM=1e3,
                     default_score=-1.0,
                     uptake_score=0.0,
                     soft_score=1.0,
                     soft_constraints=None,
                     hard_constraints=None,
                     ref_reactions=None,
                     ref_score=0.0,
                     solver=None,
                     debug_output=None):
    """ Apply minmax reduction algorithm (MILP).

    Computes a binary reaction vector that optimizes the agreement with reaction scores (maximizes positive scores,
    and minimizes negative scores). It generates a fully connected reaction network (i.e. all reactions must be able
    to carry some flux).

    Args:
        model (CBModel): universal model
        scores (dict): reaction scores
        min_growth (float): minimal growth constraint
        min_atpm (float): minimal maintenance ATP constraint
        eps (float): minimal flux required to consider leaving the reaction in the model
        bigM (float): maximal reaction flux
        default_score (float): penalty score for reactions without an annotation score (default: -1.0).
        uptake_score (float): penalty score for using uptake reactions (default: 0.0).
        soft_score (float): score for soft constraints (default: 1.0)
        soft_constraints (dict): dictionary from reaction id to expected flux direction (-1, 1, 0)
        hard_constraints (dict): dictionary of flux bounds
        solver (Solver): solver instance (optional)

    Returns:
        Solution: optimization result
    """

    if not solver:
        solver = solver_instance(model)

    objective = {}

    scores = scores.copy()
    reactions = list(scores.keys())

    if soft_constraints:
        reactions += [
            r_id for r_id in soft_constraints if r_id not in reactions
        ]
    else:
        soft_constraints = {}

    if not ref_reactions:
        ref_reactions = {}

    if hard_constraints:
        solver.set_bounds(hard_constraints)

    if default_score != 0:
        for r_id in model.reactions:
            if r_id not in reactions and r_id not in ref_reactions and not r_id.startswith(
                    'R_EX') and r_id != 'R_ATPM':
                scores[r_id] = default_score
                reactions.append(r_id)

    if ref_score != 0:
        for r_id in ref_reactions:
            if r_id not in reactions and r_id != 'R_ATPM':
                scores[r_id] = ref_score
                reactions.append(r_id)

    if not hasattr(solver, '_carveme_flag'):
        solver._carveme_flag = True

        biomass = model.biomass_reaction
        solver.add_constraint('min_growth', {biomass: 1},
                              '>',
                              min_growth,
                              update=False)
        solver.add_constraint('min_atpm', {'R_ATPM': 1},
                              '>',
                              min_atpm,
                              update=False)

        solver.neg_vars = []
        solver.pos_vars = []

        for r_id in reactions:
            if model.reactions[r_id].lb is None or model.reactions[r_id].lb < 0:
                y_r = 'yr_' + r_id
                solver.add_variable(y_r,
                                    0,
                                    1,
                                    vartype=VarType.BINARY,
                                    update=False)
                solver.neg_vars.append(y_r)
            if model.reactions[r_id].ub is None or model.reactions[r_id].ub > 0:
                y_f = 'yf_' + r_id
                solver.add_variable(y_f,
                                    0,
                                    1,
                                    vartype=VarType.BINARY,
                                    update=False)
                solver.pos_vars.append(y_f)

        if uptake_score != 0:
            for r_id in model.reactions:
                if r_id.startswith('R_EX'):
                    solver.add_variable('y_' + r_id,
                                        0,
                                        1,
                                        vartype=VarType.BINARY,
                                        update=False)

        solver.update()

        for r_id in reactions:
            y_r, y_f = 'yr_' + r_id, 'yf_' + r_id
            if y_r in solver.neg_vars and y_f in solver.pos_vars:
                solver.add_constraint('lb_' + r_id, {
                    r_id: 1,
                    y_f: -eps,
                    y_r: bigM
                },
                                      '>',
                                      0,
                                      update=False)
                solver.add_constraint('ub_' + r_id, {
                    r_id: 1,
                    y_f: -bigM,
                    y_r: eps
                },
                                      '<',
                                      0,
                                      update=False)
                solver.add_constraint('rev_' + r_id, {
                    y_f: 1,
                    y_r: 1
                },
                                      '<',
                                      1,
                                      update=False)
            elif y_f in solver.pos_vars:
                solver.add_constraint('lb_' + r_id, {
                    r_id: 1,
                    y_f: -eps
                },
                                      '>',
                                      0,
                                      update=False)
                solver.add_constraint('ub_' + r_id, {
                    r_id: 1,
                    y_f: -bigM
                },
                                      '<',
                                      0,
                                      update=False)
            elif y_r in solver.neg_vars:
                solver.add_constraint('lb_' + r_id, {
                    r_id: 1,
                    y_r: bigM
                },
                                      '>',
                                      0,
                                      update=False)
                solver.add_constraint('ub_' + r_id, {
                    r_id: 1,
                    y_r: eps
                },
                                      '<',
                                      0,
                                      update=False)

        if uptake_score != 0:
            for r_id in model.reactions:
                if r_id.startswith('R_EX'):
                    solver.add_constraint('lb_' + r_id, {
                        r_id: 1,
                        'y_' + r_id: bigM
                    },
                                          '>',
                                          0,
                                          update=False)

        solver.update()

    for r_id in reactions:
        y_r, y_f = 'yr_' + r_id, 'yf_' + r_id

        if r_id in soft_constraints:
            sign = soft_constraints[r_id]

            if sign > 0:
                w_f, w_r = soft_score, 0
            elif sign < 0:
                w_f, w_r = 0, soft_score
            else:
                w_f, w_r = -soft_score, -soft_score

        if y_f in solver.pos_vars:
            if r_id in soft_constraints:
                objective[y_f] = w_f
            elif r_id in ref_reactions:
                objective[y_f] = 2 * scores[r_id] + ref_score
            else:
                objective[y_f] = scores[r_id]

        if y_r in solver.neg_vars:
            if r_id in soft_constraints:
                objective[y_r] = w_r
            elif r_id in ref_reactions:
                objective[y_r] = 2 * scores[r_id] + ref_score
            else:
                objective[y_r] = scores[r_id]

    if uptake_score != 0:
        for r_id in model.reactions:
            if r_id.startswith('R_EX') and r_id not in soft_constraints:
                objective['y_' + r_id] = uptake_score

    solver.set_objective(linear=objective, minimize=False)

    if debug_output:
        solver.write_to_file(debug_output + "_milp_problem.lp")

    solution = solver.solve()

    return solution