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)
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)
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
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
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
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