def validate_model(model): """ Cobra model validator Modified from https://github.com/aebrahim/cobra_sbml_validator (MIT LICENCE) """ errors = [] warnings = [] # test mass balance for reaction in model.reactions: balance = reaction.check_mass_balance() if len(balance): warnings.append("reaction '%s' is not balanced for %s" % (reaction.id, ", ".join(sorted(balance)))) # try solving model.optimize() solution = get_solution(model) if solution.status != "optimal": errors.append("model can not be solved (status '%s')" % solution.status) return {"errors": errors, "warnings": warnings} # if there is no objective, then we know why the objective was low if solution.objective_value <= 0: errors.append("model can not produce nonzero biomass") if solution.objective_value <= 1e-3: warnings.append("biomass flux %s too low" % str(solution.objective_value)) return {"errors": errors, "warnings": warnings, "objective": solution.objective_value}
def find_blocked_reactions(model, reaction_list=None, zero_cutoff=1e-9, open_exchanges=False, processes=None): """ Find reactions that cannot carry any flux. The question whether or not a reaction is blocked is highly dependent on the current exchange reaction settings for a COBRA model. Hence an argument is provided to open all exchange reactions. Notes ----- Sink and demand reactions are left untouched. Please modify them manually. Parameters ---------- model : cobra.Model The model to analyze. reaction_list : list, optional List of reactions to consider, the default includes all model reactions. zero_cutoff : float, optional Flux value which is considered to effectively be zero. open_exchanges : bool, optional Whether or not to open all exchange reactions to very high flux ranges. processes : int, optional The number of parallel processes to run. Can speed up the computations if the number of reactions is large. If not explicitly passed, it will be set from the global configuration singleton. Returns ------- list List with the identifiers of blocked reactions. """ with model: if open_exchanges: for reaction in model.exchanges: reaction.bounds = (min(reaction.lower_bound, -1000), max(reaction.upper_bound, 1000)) if reaction_list is None: reaction_list = model.reactions # Limit the search space to reactions which have zero flux. If the # reactions already carry flux in this solution, # then they cannot be blocked. model.slim_optimize() solution = get_solution(model, reactions=reaction_list) reaction_list = solution.fluxes[ solution.fluxes.abs() < zero_cutoff].index.tolist() # Run FVA to find reactions where both the minimal and maximal flux # are zero (below the cut off). flux_span = flux_variability_analysis(model, fraction_of_optimum=0., reaction_list=reaction_list, processes=processes) return flux_span[flux_span.abs().max( axis=1) < zero_cutoff].index.tolist()
def _model_check(model): """ Check a model produces a steady state flux solution :return: bool """ status = model.solver.optimize() if status == 'infeasible': return False solution = get_solution(model) if solution.objective_value != 0: return True return False
def find_blocked_reactions(model, reaction_list=None, zero_cutoff=1e-9, open_exchanges=False): """Finds reactions that cannot carry a flux with the current exchange reaction settings for a cobra model, using flux variability analysis. Parameters ---------- model : cobra.Model The model to analyze reaction_list : list List of reactions to consider, use all if left missing zero_cutoff : float Flux value which is considered to effectively be zero. open_exchanges : bool If true, set bounds on exchange reactions to very high values to avoid that being the bottle-neck. Returns ------- list List with the blocked reactions """ with model: if open_exchanges: for reaction in model.exchanges: reaction.bounds = (min(reaction.lower_bound, -1000), max(reaction.upper_bound, 1000)) if reaction_list is None: reaction_list = model.reactions # limit to reactions which are already 0. If the reactions already # carry flux in this solution, then they can not be blocked. model.slim_optimize() solution = get_solution(model, reactions=reaction_list) reaction_list = [ rxn for rxn in reaction_list if abs(solution.fluxes[rxn.id]) < zero_cutoff ] # run fva to find reactions where both max and min are 0 flux_span = flux_variability_analysis(model, fraction_of_optimum=0., reaction_list=reaction_list) return [ rxn_id for rxn_id, min_max in flux_span.iterrows() if max(abs(min_max)) < zero_cutoff ]
def find_blocked_reactions(model, reaction_list=None, zero_cutoff=1e-9, open_exchanges=False): """Finds reactions that cannot carry a flux with the current exchange reaction settings for a cobra model, using flux variability analysis. Parameters ---------- model : cobra.Model The model to analyze reaction_list : list List of reactions to consider, use all if left missing zero_cutoff : float Flux value which is considered to effectively be zero. open_exchanges : bool If true, set bounds on exchange reactions to very high values to avoid that being the bottle-neck. Returns ------- list List with the blocked reactions """ with model: if open_exchanges: for reaction in model.exchanges: reaction.bounds = (min(reaction.lower_bound, -1000), max(reaction.upper_bound, 1000)) if reaction_list is None: reaction_list = model.reactions # limit to reactions which are already 0. If the reactions already # carry flux in this solution, then they can not be blocked. model.slim_optimize() solution = get_solution(model, reactions=reaction_list) reaction_list = [rxn for rxn in reaction_list if abs(solution.fluxes[rxn.id]) < zero_cutoff] # run fva to find reactions where both max and min are 0 flux_span = flux_variability_analysis( model, fraction_of_optimum=0., reaction_list=reaction_list) return [rxn_id for rxn_id, min_max in flux_span.iterrows() if max(abs(min_max)) < zero_cutoff]
def loopless_fva_iter(model, reaction, all_fluxes=False, zero_cutoff=1e-9): """Plugin to get a loopless FVA solution from single FVA iteration. Assumes the following about `model` and `reaction`: 1. the model objective is set to be `reaction` 2. the model has been optimized and contains the minimum/maximum flux for `reaction` 3. the model contains an auxiliary variable called "fva_old_objective" denoting the previous objective Parameters ---------- model : cobra.Model The model to be used. reaction : cobra.Reaction The reaction currently minimized/maximized. all_fluxes : boolean, optional Whether to return all fluxes or only the minimum/maximum for `reaction`. zero_cutoff : positive float, optional Cutoff used for loop removal. Fluxes with an absolute value smaller than `zero_cutoff` are considered to be zero. Returns ------- single float or dict Returns the minimized/maximized flux through `reaction` if all_fluxes == False (default). Otherwise returns a loopless flux solution containing the minimum/maximum flux for `reaction`. """ current = model.solver.objective.value # boundary reactions can not be part of cycles if reaction.boundary: if all_fluxes: return get_solution(model).fluxes else: return current # Reset objective to original one and get a loopless solution model.solver.objective.set_linear_coefficients({ model.variables.fva_old_objective: 1, reaction.forward_variable: 0, reaction.reverse_variable: 0 }) loopless = loopless_solution(model) # If the previous optimum is maintained in the loopless solution it was # loopless and we are done if abs(loopless[reaction.id] - current) < zero_cutoff: # Reset the objective to the one used in the iteration, walk around # the context manager for speed model.solver.objective.set_linear_coefficients({ model.variables.fva_old_objective: 0, reaction.forward_variable: 1, reaction.reverse_variable: -1 }) if all_fluxes: current = loopless return current # If previous optimum was not in the loopless solution create a new # almost loopless solution containing only loops including the current # reaction. Than remove all of those loops. with model: reaction.bounds = (current, current) almost_loopless = loopless_solution(model) # find the reactions with loops using the current reaction and remove # the loops for rxn in model.reactions: if ((abs(loopless[rxn.id]) < zero_cutoff) and (abs(almost_loopless[rxn.id]) > zero_cutoff)): rxn.bounds = (0, 0) # Globally reset the objective to the one used in the FVA iteration model.solver.objective.set_linear_coefficients({ model.variables.fva_old_objective: 0, reaction.forward_variable: 1, reaction.reverse_variable: -1 }) solution = model.optimize(objective_sense=None) if all_fluxes: best = solution.fluxes else: best = reaction.flux return best
def metabolite_summary(met, solution=None, threshold=0.01, fva=False, names=False, floatfmt='.3g'): """ Print a summary of the production and consumption fluxes. This method requires the model for which this metabolite is a part to be solved. Parameters ---------- solution : cobra.Solution, optional 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, optional Threshold below which fluxes are not reported. fva : pandas.DataFrame, float or None, 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. names : bool, optional Emit reaction and metabolite names rather than identifiers (default False). floatfmt : string, optional Format string for floats (default '.3g'). """ if names: emit = attrgetter('name') else: emit = attrgetter('id') if solution is None: met.model.slim_optimize(error_value=None) solution = get_solution(met.model, reactions=met.reactions) rxns = sorted(met.reactions, key=attrgetter("id")) rxn_id = list() rxn_name = list() flux = list() reaction = list() for rxn in rxns: rxn_id.append(rxn.id) rxn_name.append(format_long_string(emit(rxn), 10)) flux.append(solution[rxn.id] * rxn.metabolites[met]) txt = rxn.build_reaction_string(use_metabolite_names=names) reaction.append(format_long_string(txt, 40 if fva is not None else 50)) flux_summary = pd.DataFrame( { "id": rxn_name, "flux": flux, "reaction": reaction }, index=rxn_id) if fva is not None: if hasattr(fva, 'columns'): fva_results = fva else: fva_results = flux_variability_analysis(met.model, list(met.reactions), fraction_of_optimum=fva) flux_summary["maximum"] = zeros(len(rxn_id), dtype=float) flux_summary["minimum"] = zeros(len(rxn_id), dtype=float) for rxn in rxns: fmax = rxn.metabolites[met] * fva_results.at[rxn.id, "maximum"] fmin = rxn.metabolites[met] * fva_results.at[rxn.id, "minimum"] if abs(fmin) <= abs(fmax): flux_summary.at[rxn.id, "fmax"] = fmax flux_summary.at[rxn.id, "fmin"] = fmin else: # Reverse fluxes. flux_summary.at[rxn.id, "fmax"] = fmin flux_summary.at[rxn.id, "fmin"] = fmax assert flux_summary["flux"].sum() < 1E-6, "Error in flux balance" flux_summary = _process_flux_dataframe(flux_summary, fva, threshold, floatfmt) flux_summary['percent'] = 0 total_flux = flux_summary.loc[flux_summary.is_input, "flux"].sum() flux_summary.loc[flux_summary.is_input, 'percent'] = \ flux_summary.loc[flux_summary.is_input, 'flux'] / total_flux flux_summary.loc[~flux_summary.is_input, 'percent'] = \ flux_summary.loc[~flux_summary.is_input, 'flux'] / total_flux flux_summary['percent'] = flux_summary.percent.apply( lambda x: '{:.0%}'.format(x)) if fva is not None: flux_table = tabulate( flux_summary. loc[:, ['percent', 'flux', 'fva_fmt', 'id', 'reaction']].values, floatfmt=floatfmt, headers=['%', 'FLUX', 'RANGE', 'RXN ID', 'REACTION']).split('\n') else: flux_table = tabulate( flux_summary.loc[:, ['percent', 'flux', 'id', 'reaction']].values, floatfmt=floatfmt, headers=['%', 'FLUX', 'RXN ID', 'REACTION']).split('\n') flux_table_head = flux_table[:2] met_tag = "{0} ({1})".format(format_long_string(met.name, 45), format_long_string(met.id, 10)) head = "PRODUCING REACTIONS -- " + met_tag print_(head) print_("-" * len(head)) print_('\n'.join(flux_table_head)) print_('\n'.join( pd.np.array(flux_table[2:])[flux_summary.is_input.values])) print_() print_("CONSUMING REACTIONS -- " + met_tag) print_("-" * len(head)) print_('\n'.join(flux_table_head)) print_('\n'.join( pd.np.array(flux_table[2:])[~flux_summary.is_input.values]))
def metabolite_summary(met, solution=None, threshold=0.01, fva=False, floatfmt='.3g'): """Print a summary of the reactions which produce and consume this metabolite 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 a percentage value of the maximum flux below which to ignore reaction fluxes fva : float (0->1), or None Whether or not to include flux variability analysis in the output. If given, fva should be a float between 0 and 1, representing the fraction of the optimum objective to be searched. floatfmt : string format method for floats, passed to tabulate. Default is '.3g'. """ if solution is None: met.model.slim_optimize(error_value=None) solution = get_solution(met.model, reactions=met.reactions) rxn_id = list() flux = list() reaction = list() for rxn in met.reactions: rxn_id.append(format_long_string(rxn.id, 10)) flux.append(solution.fluxes[rxn.id] * rxn.metabolites[met]) reaction.append(format_long_string(rxn.reaction, 40 if fva else 50)) flux_summary = pd.DataFrame(data={ "id": rxn_id, "flux": flux, "reaction": reaction}) if fva: fva_results = flux_variability_analysis( met.model, met.reactions, fraction_of_optimum=fva) flux_summary.index = flux_summary["id"] flux_summary["maximum"] = zeros(len(rxn_id)) flux_summary["minimum"] = zeros(len(rxn_id)) for rid, rxn in zip(rxn_id, met.reactions): imax = rxn.metabolites[met] * fva_results.loc[rxn.id, "maximum"] imin = rxn.metabolites[met] * fva_results.loc[rxn.id, "minimum"] flux_summary.loc[rid, "fmax"] = (imax if abs(imin) <= abs(imax) else imin) flux_summary.loc[rid, "fmin"] = (imin if abs(imin) <= abs(imax) else imax) assert flux_summary.flux.sum() < 1E-6, "Error in flux balance" flux_summary = _process_flux_dataframe(flux_summary, fva, threshold, floatfmt) flux_summary['percent'] = 0 total_flux = flux_summary[flux_summary.is_input].flux.sum() flux_summary.loc[flux_summary.is_input, 'percent'] = \ flux_summary.loc[flux_summary.is_input, 'flux'] / total_flux flux_summary.loc[~flux_summary.is_input, 'percent'] = \ flux_summary.loc[~flux_summary.is_input, 'flux'] / total_flux flux_summary['percent'] = flux_summary.percent.apply( lambda x: '{:.0%}'.format(x)) if fva: flux_table = tabulate( flux_summary.loc[:, ['percent', 'flux', 'fva_fmt', 'id', 'reaction']].values, floatfmt=floatfmt, headers=['%', 'FLUX', 'RANGE', 'RXN ID', 'REACTION']).split('\n') else: flux_table = tabulate( flux_summary.loc[:, ['percent', 'flux', 'id', 'reaction']].values, floatfmt=floatfmt, headers=['%', 'FLUX', 'RXN ID', 'REACTION'] ).split('\n') flux_table_head = flux_table[:2] met_tag = "{0} ({1})".format(format_long_string(met.name, 45), format_long_string(met.id, 10)) head = "PRODUCING REACTIONS -- " + met_tag print_(head) print_("-" * len(head)) print_('\n'.join(flux_table_head)) print_('\n'.join( pd.np.array(flux_table[2:])[flux_summary.is_input.values])) print_() print_("CONSUMING REACTIONS -- " + met_tag) print_("-" * len(head)) print_('\n'.join(flux_table_head)) print_('\n'.join( pd.np.array(flux_table[2:])[~flux_summary.is_input.values]))
def loopless_fva_iter(model, reaction, solution=False, zero_cutoff=1e-6): """Plugin to get a loopless FVA solution from single FVA iteration. Assumes the following about `model` and `reaction`: 1. the model objective is set to be `reaction` 2. the model has been optimized and contains the minimum/maximum flux for `reaction` 3. the model contains an auxiliary variable called "fva_old_objective" denoting the previous objective Parameters ---------- model : cobra.Model The model to be used. reaction : cobra.Reaction The reaction currently minimized/maximized. solution : boolean, optional Whether to return the entire solution or only the minimum/maximum for `reaction`. zero_cutoff : positive float, optional Cutoff used for loop removal. Fluxes with an absolute value smaller than `zero_cutoff` are considered to be zero. Returns ------- single float or dict Returns the minimized/maximized flux through `reaction` if all_fluxes == False (default). Otherwise returns a loopless flux solution containing the minimum/maximum flux for `reaction`. """ current = model.objective.value sol = get_solution(model) objective_dir = model.objective.direction # boundary reactions can not be part of cycles if reaction.boundary: if solution: return sol else: return current with model: _add_cycle_free(model, sol.fluxes) flux = model.slim_optimize() # If the previous optimum is maintained in the loopless solution it was # loopless and we are done if abs(flux - current) < zero_cutoff: if solution: return sol return current # If previous optimum was not in the loopless solution create a new # almost loopless solution containing only loops including the current # reaction. Than remove all of those loops. ll_sol = get_solution(model).fluxes bounds = reaction.bounds reaction.bounds = (current, current) model.slim_optimize() almost_ll_sol = get_solution(model).fluxes reaction.bounds = bounds # find the reactions with loops using the current reaction and remove # the loops for rxn in model.reactions: rid = rxn.id if ((abs(ll_sol[rid]) < zero_cutoff) and (abs(almost_ll_sol[rid]) > zero_cutoff)): rxn.bounds = max(0, rxn.lower_bound), min(0, rxn.upper_bound) if solution: best = model.optimize() else: model.slim_optimize() best = reaction.flux model.objective.direction = objective_dir return best
def _generate(self): """ Returns ------- flux_summary: pandas.DataFrame The DataFrame of flux summary data. """ if self.names: emit = attrgetter('name') else: emit = attrgetter('id') if self.solution is None: self.metabolite.model.slim_optimize(error_value=None) self.solution = get_solution(self.metabolite.model, reactions=self.metabolite.reactions) rxns = sorted(self.metabolite.reactions, key=attrgetter('id')) data = [ (emit(rxn), self.solution[rxn.id] * rxn.metabolites[self.metabolite], rxn.build_reaction_string(use_metabolite_names=self.names), rxn) for rxn in rxns ] flux_summary = pd.DataFrame.from_records( data=data, index=[rxn.id for rxn in rxns], columns=['id', 'flux', 'reaction_string', 'reaction']) assert flux_summary['flux'].sum() < self.metabolite.model.tolerance, \ 'Error in flux balance' if self.fva is not None: if hasattr(self.fva, 'columns'): fva_results = self.fva else: fva_results = flux_variability_analysis( self.metabolite.model, list(self.metabolite.reactions), fraction_of_optimum=self.fva) flux_summary = pd.concat([flux_summary, fva_results], axis=1, sort=False) flux_summary.rename(columns={ 'maximum': 'fmax', 'minimum': 'fmin' }, inplace=True) def set_min_and_max(row): """Scale and set proper min and max values for flux.""" fmax = row.reaction.metabolites[self.metabolite] * row.fmax fmin = row.reaction.metabolites[self.metabolite] * row.fmin if abs(fmin) <= abs(fmax): row.fmax = fmax row.fmin = fmin else: # Reverse fluxes row.fmax = fmin row.fmin = fmax return row flux_summary = flux_summary.apply(set_min_and_max, axis=1) flux_summary = self._process_flux_dataframe(flux_summary) total_flux = flux_summary.loc[flux_summary.is_input, 'flux'].sum() # Calculate flux percentage flux_summary['percent'] = (flux_summary['flux'] / total_flux) * 100 return flux_summary
def model_summary(model, solution=None, threshold=0.01, fva=None, floatfmt='.3g'): """Print a summary of the input and output fluxes of the model. 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 a percentage value of the maximum flux below which to ignore reaction fluxes 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'. """ objective_reactions = linear_reaction_coefficients(model) boundary_reactions = model.exchanges summary_rxns = set(objective_reactions.keys()).union(boundary_reactions) if solution is None: model.slim_optimize(error_value=None) solution = get_solution(model, reactions=summary_rxns) # Create a dataframe of objective fluxes obj_fluxes = pd.DataFrame({key: solution.fluxes[key.id] * value for key, value in iteritems(objective_reactions)}, index=['flux']).T obj_fluxes['id'] = obj_fluxes.apply( lambda x: format_long_string(x.name.id, 15), 1) # Build a dictionary of metabolite production from the boundary reactions metabolite_fluxes = {} for rxn in boundary_reactions: for met, stoich in iteritems(rxn.metabolites): metabolite_fluxes[met] = { 'id': format_long_string(met.id, 15), 'flux': stoich * solution.fluxes[rxn.id]} # Calculate FVA results if requested if fva: fva_results = flux_variability_analysis( model, reaction_list=boundary_reactions, fraction_of_optimum=fva) for rxn in boundary_reactions: for met, stoich in iteritems(rxn.metabolites): imin = stoich * fva_results.loc[rxn.id]['minimum'] imax = stoich * fva_results.loc[rxn.id]['maximum'] # Correct 'max' and 'min' for negative values metabolite_fluxes[met].update({ 'fmin': imin if abs(imin) <= abs(imax) else imax, 'fmax': imax if abs(imin) <= abs(imax) else imin, }) # Generate a dataframe of boundary fluxes metabolite_fluxes = pd.DataFrame(metabolite_fluxes).T metabolite_fluxes = _process_flux_dataframe( metabolite_fluxes, fva, threshold, floatfmt) # Begin building string output table def get_str_table(species_df, fva=False): """Formats a string table for each column""" if not fva: return tabulate(species_df.loc[:, ['id', 'flux']].values, floatfmt=floatfmt, tablefmt='plain').split('\n') else: return tabulate( species_df.loc[:, ['id', 'flux', 'fva_fmt']].values, floatfmt=floatfmt, tablefmt='simple', headers=['id', 'Flux', 'Range']).split('\n') in_table = get_str_table( metabolite_fluxes[metabolite_fluxes.is_input], fva=fva) out_table = get_str_table( metabolite_fluxes[~metabolite_fluxes.is_input], fva=fva) obj_table = get_str_table(obj_fluxes, fva=False) # Print nested output table print_(tabulate( [entries for entries in zip_longest(in_table, out_table, obj_table)], headers=['IN FLUXES', 'OUT FLUXES', 'OBJECTIVES'], tablefmt='simple'))
def model_summary(model, solution=None, threshold=0.01, fva=None, names=False, floatfmt='.3g'): """ Print a summary of the input and output fluxes of the model. Parameters ---------- solution: cobra.Solution, optional 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, optional Threshold below which fluxes are not reported. fva : pandas.DataFrame, float or None, 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. names : bool, optional Emit reaction and metabolite names rather than identifiers (default False). floatfmt : string, optional Format string for floats (default '.3g'). """ if names: emit = attrgetter('name') else: emit = attrgetter('id') objective_reactions = linear_reaction_coefficients(model) boundary_reactions = model.exchanges summary_rxns = set(objective_reactions.keys()).union(boundary_reactions) if solution is None: model.slim_optimize(error_value=None) solution = get_solution(model, reactions=summary_rxns) # Create a dataframe of objective fluxes obj_fluxes = pd.DataFrame({key: solution[key.id] * value for key, value in iteritems(objective_reactions)}, index=['flux']).T obj_fluxes['id'] = obj_fluxes.apply( lambda x: format_long_string(x.name.id, 15), 1) # Build a dictionary of metabolite production from the boundary reactions metabolites = {m for r in boundary_reactions for m in r.metabolites} index = sorted(metabolites, key=attrgetter('id')) metabolite_fluxes = pd.DataFrame({ 'id': [format_long_string(emit(m), 15) for m in index], 'flux': zeros(len(index), dtype=float) }, index=[m.id for m in index]) for rxn in boundary_reactions: for met, stoich in iteritems(rxn.metabolites): metabolite_fluxes.at[met.id, 'flux'] += stoich * solution[rxn.id] # Calculate FVA results if requested if fva is not None: if len(index) != len(boundary_reactions): LOGGER.warning( "There exists more than one boundary reaction per metabolite. " "Please be careful when evaluating flux ranges.") metabolite_fluxes['fmin'] = zeros(len(index), dtype=float) metabolite_fluxes['fmax'] = zeros(len(index), dtype=float) if hasattr(fva, 'columns'): fva_results = fva else: fva_results = flux_variability_analysis( model, reaction_list=boundary_reactions, fraction_of_optimum=fva) for rxn in boundary_reactions: for met, stoich in iteritems(rxn.metabolites): fmin = stoich * fva_results.at[rxn.id, 'minimum'] fmax = stoich * fva_results.at[rxn.id, 'maximum'] # Correct 'max' and 'min' for negative values if abs(fmin) <= abs(fmax): metabolite_fluxes.at[met.id, 'fmin'] += fmin metabolite_fluxes.at[met.id, 'fmax'] += fmax else: metabolite_fluxes.at[met.id, 'fmin'] += fmax metabolite_fluxes.at[met.id, 'fmax'] += fmin # Generate a dataframe of boundary fluxes metabolite_fluxes = _process_flux_dataframe( metabolite_fluxes, fva, threshold, floatfmt) # Begin building string output table def get_str_table(species_df, fva=False): """Formats a string table for each column""" if fva: return tabulate( species_df.loc[:, ['id', 'flux', 'fva_fmt']].values, floatfmt=floatfmt, tablefmt='simple', headers=['id', 'Flux', 'Range']).split('\n') else: return tabulate(species_df.loc[:, ['id', 'flux']].values, floatfmt=floatfmt, tablefmt='plain').split('\n') in_table = get_str_table( metabolite_fluxes[metabolite_fluxes['is_input']], fva=fva is not None) out_table = get_str_table( metabolite_fluxes[~metabolite_fluxes['is_input']], fva=fva is not None) obj_table = get_str_table(obj_fluxes, fva=False) # Print nested output table print_(tabulate( [entries for entries in zip_longest(in_table, out_table, obj_table)], headers=['IN FLUXES', 'OUT FLUXES', 'OBJECTIVES'], tablefmt='simple'))
def _cycle_free_fva(model, reactions=None, sloppy=True, sloppy_bound=666): """Cycle free flux-variability analysis. (http://cran.r-project.org/web/packages/sybilcycleFreeFlux/index.html) Parameters ---------- model : cobra.Model reactions : list List of reactions whose flux-ranges should be determined. sloppy : boolean, optional If true, only fluxes v with abs(v) > sloppy_bound are checked to be futile cycles (defaults to True). sloppy_bound : int, optional The threshold bound used by sloppy (defaults to the number of the beast). """ cycle_count = 0 if reactions is None: reactions = model.reactions else: reactions = model.reactions.get_by_any(reactions) fva_sol = OrderedDict() for reaction in reactions: fva_sol[reaction.id] = dict() model.objective = reaction model.objective.direction = 'min' model.solver.optimize() if model.solver.status == UNBOUNDED: fva_sol[reaction.id]['lower_bound'] = -numpy.inf continue elif model.solver.status != OPTIMAL: fva_sol[reaction.id]['lower_bound'] = 0 continue bound = model.objective.value if sloppy and abs(bound) < sloppy_bound: fva_sol[reaction.id]['lower_bound'] = bound else: logger.debug('Determine if {} with bound {} is a cycle'.format( reaction.id, bound)) solution = get_solution(model) v0_fluxes = solution.fluxes v1_cycle_free_fluxes = remove_infeasible_cycles(model, v0_fluxes) if abs(v1_cycle_free_fluxes[reaction.id] - bound) < 10**-6: fva_sol[reaction.id]['lower_bound'] = bound else: logger.debug('Cycle detected: {}'.format(reaction.id)) cycle_count += 1 v2_one_cycle_fluxes = remove_infeasible_cycles( model, v0_fluxes, fix=[reaction.id]) with model: for key, v1_flux in six.iteritems(v1_cycle_free_fluxes): if round(v1_flux, config.ndecimals) == 0 and round( v2_one_cycle_fluxes[key], config.ndecimals) != 0: knockout_reaction = model.reactions.get_by_id(key) knockout_reaction.knock_out() model.objective.direction = 'min' model.solver.optimize() if model.solver.status == OPTIMAL: fva_sol[ reaction.id]['lower_bound'] = model.objective.value elif model.solver.status == UNBOUNDED: fva_sol[reaction.id]['lower_bound'] = -numpy.inf else: fva_sol[reaction.id]['lower_bound'] = 0 for reaction in reactions: model.objective = reaction model.objective.direction = 'max' model.solver.optimize() if model.solver.status == UNBOUNDED: fva_sol[reaction.id]['upper_bound'] = numpy.inf continue elif model.solver.status != OPTIMAL: fva_sol[reaction.id]['upper_bound'] = 0 continue bound = model.objective.value if sloppy and abs(bound) < sloppy_bound: fva_sol[reaction.id]['upper_bound'] = bound else: logger.debug('Determine if {} with bound {} is a cycle'.format( reaction.id, bound)) solution = get_solution(model) v0_fluxes = solution.fluxes v1_cycle_free_fluxes = remove_infeasible_cycles(model, v0_fluxes) if abs(v1_cycle_free_fluxes[reaction.id] - bound) < 1e-6: fva_sol[reaction.id]['upper_bound'] = v0_fluxes[reaction.id] else: logger.debug('Cycle detected: {}'.format(reaction.id)) cycle_count += 1 v2_one_cycle_fluxes = remove_infeasible_cycles( model, v0_fluxes, fix=[reaction.id]) with model: for key, v1_flux in six.iteritems(v1_cycle_free_fluxes): if round(v1_flux, config.ndecimals) == 0 and round( v2_one_cycle_fluxes[key], config.ndecimals) != 0: knockout_reaction = model.reactions.get_by_id(key) knockout_reaction.knock_out() model.objective.direction = 'max' model.solver.optimize() if model.solver.status == OPTIMAL: fva_sol[ reaction.id]['upper_bound'] = model.objective.value elif model.solver.status == UNBOUNDED: fva_sol[reaction.id]['upper_bound'] = numpy.inf else: fva_sol[reaction.id]['upper_bound'] = 0 df = pandas.DataFrame.from_dict(fva_sol, orient='index') lb_higher_ub = df[df.lower_bound > df.upper_bound] # Assert that these cases really only numerical artifacts assert ((lb_higher_ub.lower_bound - lb_higher_ub.upper_bound) < 1e-6).all() df.lower_bound[lb_higher_ub.index] = df.upper_bound[lb_higher_ub.index] return df
def metabolite_summary(met, solution=None, threshold=0.01, fva=False, names=False, floatfmt='.3g'): """ Print a summary of the production and consumption fluxes. This method requires the model for which this metabolite is a part to be solved. Parameters ---------- solution : cobra.Solution, optional 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, optional Threshold below which fluxes are not reported. fva : pandas.DataFrame, float or None, 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. names : bool, optional Emit reaction and metabolite names rather than identifiers (default False). floatfmt : string, optional Format string for floats (default '.3g'). """ if names: emit = attrgetter('name') else: emit = attrgetter('id') if solution is None: met.model.slim_optimize(error_value=None) solution = get_solution(met.model, reactions=met.reactions) rxns = sorted(met.reactions, key=attrgetter("id")) rxn_id = list() rxn_name = list() flux = list() reaction = list() for rxn in rxns: rxn_id.append(rxn.id) rxn_name.append(format_long_string(emit(rxn), 10)) flux.append(solution[rxn.id] * rxn.metabolites[met]) txt = rxn.build_reaction_string(use_metabolite_names=names) reaction.append(format_long_string(txt, 40 if fva is not None else 50)) flux_summary = pd.DataFrame({ "id": rxn_name, "flux": flux, "reaction": reaction }, index=rxn_id) if fva is not None: if hasattr(fva, 'columns'): fva_results = fva else: fva_results = flux_variability_analysis( met.model, list(met.reactions), fraction_of_optimum=fva) flux_summary["maximum"] = zeros(len(rxn_id), dtype=float) flux_summary["minimum"] = zeros(len(rxn_id), dtype=float) for rxn in rxns: fmax = rxn.metabolites[met] * fva_results.at[rxn.id, "maximum"] fmin = rxn.metabolites[met] * fva_results.at[rxn.id, "minimum"] if abs(fmin) <= abs(fmax): flux_summary.at[rxn.id, "fmax"] = fmax flux_summary.at[rxn.id, "fmin"] = fmin else: # Reverse fluxes. flux_summary.at[rxn.id, "fmax"] = fmin flux_summary.at[rxn.id, "fmin"] = fmax assert flux_summary["flux"].sum() < 1E-6, "Error in flux balance" flux_summary = _process_flux_dataframe(flux_summary, fva, threshold, floatfmt) flux_summary['percent'] = 0 total_flux = flux_summary.loc[flux_summary.is_input, "flux"].sum() flux_summary.loc[flux_summary.is_input, 'percent'] = \ flux_summary.loc[flux_summary.is_input, 'flux'] / total_flux flux_summary.loc[~flux_summary.is_input, 'percent'] = \ flux_summary.loc[~flux_summary.is_input, 'flux'] / total_flux flux_summary['percent'] = flux_summary.percent.apply( lambda x: '{:.0%}'.format(x)) if fva is not None: flux_table = tabulate( flux_summary.loc[:, ['percent', 'flux', 'fva_fmt', 'id', 'reaction']].values, floatfmt=floatfmt, headers=['%', 'FLUX', 'RANGE', 'RXN ID', 'REACTION']).split('\n') else: flux_table = tabulate( flux_summary.loc[:, ['percent', 'flux', 'id', 'reaction']].values, floatfmt=floatfmt, headers=['%', 'FLUX', 'RXN ID', 'REACTION'] ).split('\n') flux_table_head = flux_table[:2] met_tag = "{0} ({1})".format(format_long_string(met.name, 45), format_long_string(met.id, 10)) head = "PRODUCING REACTIONS -- " + met_tag print_(head) print_("-" * len(head)) print_('\n'.join(flux_table_head)) print_('\n'.join( pd.np.array(flux_table[2:])[flux_summary.is_input.values])) print_() print_("CONSUMING REACTIONS -- " + met_tag) print_("-" * len(head)) print_('\n'.join(flux_table_head)) print_('\n'.join( pd.np.array(flux_table[2:])[~flux_summary.is_input.values]))
def model_summary(model, solution=None, threshold=0.01, fva=None, names=False, floatfmt='.3g'): """ Print a summary of the input and output fluxes of the model. Parameters ---------- solution: cobra.Solution, optional 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, optional Threshold below which fluxes are not reported. fva : pandas.DataFrame, float or None, 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. names : bool, optional Emit reaction and metabolite names rather than identifiers (default False). floatfmt : string, optional Format string for floats (default '.3g'). """ if names: emit = attrgetter('name') else: emit = attrgetter('id') objective_reactions = linear_reaction_coefficients(model) boundary_reactions = model.exchanges summary_rxns = set(objective_reactions.keys()).union(boundary_reactions) if solution is None: model.slim_optimize(error_value=None) solution = get_solution(model, reactions=summary_rxns) # Create a dataframe of objective fluxes obj_fluxes = pd.DataFrame( { key: solution[key.id] * value for key, value in iteritems(objective_reactions) }, index=['flux']).T obj_fluxes['id'] = obj_fluxes.apply( lambda x: format_long_string(x.name.id, 15), 1) # Build a dictionary of metabolite production from the boundary reactions metabolites = {m for r in boundary_reactions for m in r.metabolites} index = sorted(metabolites, key=attrgetter('id')) metabolite_fluxes = pd.DataFrame( { 'id': [format_long_string(emit(m), 15) for m in index], 'flux': zeros(len(index), dtype=float) }, index=[m.id for m in index]) for rxn in boundary_reactions: for met, stoich in iteritems(rxn.metabolites): metabolite_fluxes.at[met.id, 'flux'] += stoich * solution[rxn.id] # Calculate FVA results if requested if fva is not None: if len(index) != len(boundary_reactions): LOGGER.warning( "There exists more than one boundary reaction per metabolite. " "Please be careful when evaluating flux ranges.") metabolite_fluxes['fmin'] = zeros(len(index), dtype=float) metabolite_fluxes['fmax'] = zeros(len(index), dtype=float) if hasattr(fva, 'columns'): fva_results = fva else: fva_results = flux_variability_analysis( model, reaction_list=boundary_reactions, fraction_of_optimum=fva) for rxn in boundary_reactions: for met, stoich in iteritems(rxn.metabolites): fmin = stoich * fva_results.at[rxn.id, 'minimum'] fmax = stoich * fva_results.at[rxn.id, 'maximum'] # Correct 'max' and 'min' for negative values if abs(fmin) <= abs(fmax): metabolite_fluxes.at[met.id, 'fmin'] += fmin metabolite_fluxes.at[met.id, 'fmax'] += fmax else: metabolite_fluxes.at[met.id, 'fmin'] += fmax metabolite_fluxes.at[met.id, 'fmax'] += fmin # Generate a dataframe of boundary fluxes metabolite_fluxes = _process_flux_dataframe(metabolite_fluxes, fva, threshold, floatfmt) # Begin building string output table def get_str_table(species_df, fva=False): """Formats a string table for each column""" if fva: return tabulate(species_df.loc[:, ['id', 'flux', 'fva_fmt']].values, floatfmt=floatfmt, tablefmt='simple', headers=['id', 'Flux', 'Range']).split('\n') else: return tabulate(species_df.loc[:, ['id', 'flux']].values, floatfmt=floatfmt, tablefmt='plain').split('\n') in_table = get_str_table(metabolite_fluxes[metabolite_fluxes['is_input']], fva=fva is not None) out_table = get_str_table( metabolite_fluxes[~metabolite_fluxes['is_input']], fva=fva is not None) obj_table = get_str_table(obj_fluxes, fva=False) # Print nested output table print_( tabulate([ entries for entries in zip_longest(in_table, out_table, obj_table) ], headers=['IN FLUXES', 'OUT FLUXES', 'OBJECTIVES'], tablefmt='simple'))
def loopless_fva_iter(model, reaction, solution=False, zero_cutoff=None): """Plugin to get a loopless FVA solution from single FVA iteration. Assumes the following about `model` and `reaction`: 1. the model objective is set to be `reaction` 2. the model has been optimized and contains the minimum/maximum flux for `reaction` 3. the model contains an auxiliary variable called "fva_old_objective" denoting the previous objective Parameters ---------- model : cobra.Model The model to be used. reaction : cobra.Reaction The reaction currently minimized/maximized. solution : boolean, optional Whether to return the entire solution or only the minimum/maximum for `reaction`. zero_cutoff : positive float, optional Cutoff used for loop removal. Fluxes with an absolute value smaller than `zero_cutoff` are considered to be zero (default model.tolerance). Returns ------- single float or dict Returns the minimized/maximized flux through `reaction` if all_fluxes == False (default). Otherwise returns a loopless flux solution containing the minimum/maximum flux for `reaction`. """ zero_cutoff = normalize_cutoff(model, zero_cutoff) current = model.objective.value sol = get_solution(model) objective_dir = model.objective.direction # boundary reactions can not be part of cycles if reaction.boundary: if solution: return sol else: return current with model: _add_cycle_free(model, sol.fluxes) model.slim_optimize() # If the previous optimum is maintained in the loopless solution it was # loopless and we are done if abs(reaction.flux - current) < zero_cutoff: if solution: return sol return current # If previous optimum was not in the loopless solution create a new # almost loopless solution containing only loops including the current # reaction. Than remove all of those loops. ll_sol = get_solution(model).fluxes reaction.bounds = (current, current) model.slim_optimize() almost_ll_sol = get_solution(model).fluxes with model: # find the reactions with loops using the current reaction and remove # the loops for rxn in model.reactions: rid = rxn.id if ((abs(ll_sol[rid]) < zero_cutoff) and (abs(almost_ll_sol[rid]) > zero_cutoff)): rxn.bounds = max(0, rxn.lower_bound), min(0, rxn.upper_bound) if solution: best = model.optimize() else: model.slim_optimize() best = reaction.flux model.objective.direction = objective_dir return best
def geometric_fba(model, epsilon=1E-06, max_tries=200): """Perform geometric FBA to obtain a unique, centered flux distribution. Geometric FBA [1]_ formulates the problem as a polyhedron and then solves it by bounding the convex hull of the polyhedron. The bounding forms a box around the convex hull which reduces with every iteration and extracts a unique solution in this way. Parameters ---------- model: cobra.Model The model to perform geometric FBA on. epsilon: float, optional The convergence tolerance of the model (default 1E-06). max_tries: int, optional Maximum number of iterations (default 200). Returns ------- cobra.Solution The solution object containing all the constraints required for geometric FBA. References ---------- .. [1] Smallbone, Kieran & Simeonidis, Vangelis. (2009). Flux balance analysis: A geometric perspective. Journal of theoretical biology.258. 311-5. 10.1016/j.jtbi.2009.01.027. """ with model: # iteration parameters delta = 1.0 # initialize at 1.0 to enter while loop count = 2 # iteration #1 happens out of the loop # vars and consts storage variables consts = [] obj_vars = [] updating_vars_cons = [] # first iteration prob = model.problem add_pfba(model) # minimizes the solution space to convex hull model.optimize() fva_sol = flux_variability_analysis(model) mean_flux = (fva_sol["maximum"] + fva_sol["minimum"]).abs() / 2 # set gFBA constraints for rxn in model.reactions: var = prob.Variable("geometric_fba_" + rxn.id, lb=0, ub=mean_flux[rxn.id]) upper_const = prob.Constraint(rxn.flux_expression - var, ub=mean_flux[rxn.id], name="geometric_fba_upper_const_" + rxn.id) lower_const = prob.Constraint(rxn.flux_expression + var, lb=fva_sol.at[rxn.id, "minimum"], name="geometric_fba_lower_const_" + rxn.id) updating_vars_cons.append((rxn.id, var, upper_const, lower_const)) consts.extend([var, upper_const, lower_const]) obj_vars.append(var) model.add_cons_vars(consts) # minimize distance between flux and centre model.objective = prob.Objective(Zero, sloppy=True, direction="min") model.objective.set_linear_coefficients({v: 1.0 for v in obj_vars}) model.optimize() # further iterations while delta > epsilon and count <= max_tries: fva_sol = flux_variability_analysis(model) mean_flux = (fva_sol["maximum"] + fva_sol["minimum"]).abs() / 2 for rxn_id, var, u_c, l_c in updating_vars_cons: var.ub = mean_flux[rxn_id] u_c.ub = mean_flux[rxn_id] l_c.lb = fva_sol.at[rxn_id, "minimum"] model.optimize() delta = (fva_sol["maximum"] - fva_sol["minimum"]).max() count += 1 if count == max_tries: raise RuntimeError( "The iterations have exceeded the maximum value of {}. " "This is probably due to the increased complexity of the " "model and can lead to inaccurate results. Please set a " "different convergence tolerance and/or increase the " "maximum iterations".format(max_tries)) return get_solution(model)
def geometric_fba(model, epsilon=1E-06, max_tries=200): """Perform geometric FBA to obtain a unique, centered flux distribution. Geometric FBA [1]_ formulates the problem as a polyhedron and then solves it by bounding the convex hull of the polyhedron. The bounding forms a box around the convex hull which reduces with every iteration and extracts a unique solution in this way. Parameters ---------- model: cobra.Model The model to perform geometric FBA on. epsilon: float, optional The convergence tolerance of the model (default 1E-06). max_tries: int, optional Maximum number of iterations (default 200). Returns ------- cobra.Solution The solution object containing all the constraints required for geometric FBA. References ---------- .. [1] Smallbone, Kieran & Simeonidis, Vangelis. (2009). Flux balance analysis: A geometric perspective. Journal of theoretical biology.258. 311-5. 10.1016/j.jtbi.2009.01.027. """ with model: # iteration parameters delta = 1.0 # initialize at 1.0 to enter while loop count = 2 # iteration #1 happens out of the loop # vars and consts storage variables consts = [] obj_vars = [] updating_vars_cons = [] # first iteration prob = model.problem add_pfba(model) # minimizes the solution space to convex hull model.optimize() fva_sol = flux_variability_analysis(model) mean_flux = (fva_sol["maximum"] + fva_sol["minimum"]).abs() / 2 # set gFBA constraints for rxn in model.reactions: var = prob.Variable("geometric_fba_" + rxn.id, lb=0, ub=mean_flux[rxn.id]) upper_const = prob.Constraint(rxn.flux_expression - var, ub=mean_flux[rxn.id], name="geometric_fba_upper_const_" + rxn.id) lower_const = prob.Constraint(rxn.flux_expression + var, lb=fva_sol.at[rxn.id, "minimum"], name="geometric_fba_lower_const_" + rxn.id) updating_vars_cons.append((rxn.id, var, upper_const, lower_const)) consts.extend([var, upper_const, lower_const]) obj_vars.append(var) model.add_cons_vars(consts) # minimize distance between flux and centre model.objective = prob.Objective(Zero, sloppy=True, direction="min") model.objective.set_linear_coefficients({v: 1.0 for v in obj_vars}) model.optimize() # further iterations while delta > epsilon and count <= max_tries: fva_sol = flux_variability_analysis(model) mean_flux = (fva_sol["maximum"] + fva_sol["minimum"]).abs() / 2 for rxn_id, var, u_c, l_c in updating_vars_cons: var.ub = mean_flux[rxn_id] u_c.ub = mean_flux[rxn_id] l_c.lb = fva_sol.at[rxn_id, "minimum"] model.optimize() delta = (fva_sol["maximum"] - fva_sol["minimum"]).max() count += 1 if count == max_tries: raise RuntimeError( "The iterations have exceeded the maximum value of {}. " "This is probably due to the increased complexity of the " "model and can lead to inaccurate results. Please set a " "different convergence tolerance and/or increase the " "maximum iterations".format(max_tries) ) return get_solution(model)
def find_blocked_reactions(model, reaction_list=None, solver=None, zero_cutoff=1e-9, open_exchanges=False, **solver_args): """Finds reactions that cannot carry a flux with the current exchange reaction settings for a cobra model, using flux variability analysis. Parameters ---------- model : cobra.Model The model to analyze reaction_list : list List of reactions to consider, use all if left missing solver : string The name of the solver to use zero_cutoff : float Flux value which is considered to effectively be zero. open_exchanges : bool If true, set bounds on exchange reactions to very high values to avoid that being the bottle-neck. **solver_args : Additional arguments to the solver. Ignored for optlang based solvers. Returns ------- list List with the blocked reactions """ legacy, solver_interface = sutil.choose_solver(model, solver) with model: if open_exchanges: for reaction in model.exchanges: reaction.bounds = (min(reaction.lower_bound, -1000), max(reaction.upper_bound, 1000)) if reaction_list is None: reaction_list = model.reactions # limit to reactions which are already 0. If the reactions already # carry flux in this solution, then they can not be blocked. if legacy: solution = solver_interface.solve(model, **solver_args) reaction_list = [ i for i in reaction_list if abs(solution.x_dict[i.id]) < zero_cutoff ] else: model.solver = solver_interface model.solver.optimize() solution = get_solution(model, reactions=reaction_list) reaction_list = [ rxn for rxn in reaction_list if abs(solution.fluxes[rxn.id]) < zero_cutoff ] # run fva to find reactions where both max and min are 0 flux_span = flux_variability_analysis(model, fraction_of_optimum=0., reaction_list=reaction_list, solver=solver, **solver_args) return [ rxn_id for rxn_id, min_max in flux_span.iterrows() if max(abs(min_max)) < zero_cutoff ]
def _generate(self): """ Returns ------- flux_summary: pandas.DataFrame The DataFrame of flux summary data. """ if self.names: emit = attrgetter('name') else: emit = attrgetter('id') obj_rxns = linear_reaction_coefficients(self.model) boundary_rxns = self.model.exchanges summary_rxns = set(obj_rxns.keys()).union(boundary_rxns) if self.solution is None: self.model.slim_optimize(error_value=None) self.solution = get_solution(self.model, reactions=summary_rxns) # the order needs to be maintained ord_obj_rxns = OrderedDict(obj_rxns) obj_data = [(emit(rxn), self.solution[rxn.id] * stoich, np.nan, rxn, np.nan, 1.0) for rxn, stoich in iteritems(ord_obj_rxns)] # create a DataFrame of objective reactions obj_df = pd.DataFrame.from_records( data=obj_data, index=[rxn.id for rxn in iterkeys(ord_obj_rxns)], columns=[ 'id', 'flux', 'metabolite', 'reaction', 'is_input', 'is_obj_rxn' ]) # create a collection of metabolites from the boundary reactions mets = OrderedDict( (met, rxn) for rxn in boundary_rxns for met in sorted(rxn.metabolites, key=attrgetter('id'))) index = [met.id for met in iterkeys(mets)] met_data = [(emit(met), self.solution[rxn.id] * rxn.metabolites[met], met, rxn) for met, rxn in iteritems(mets)] # create a DataFrame of metabolites flux_summary = pd.DataFrame.from_records( data=met_data, index=index, columns=['id', 'flux', 'metabolite', 'reaction']) # Calculate FVA results if requested if self.fva is not None: if len(index) != len(boundary_rxns): logger.warning( 'There exists more than one boundary reaction per ' 'metabolite. Please be careful when evaluating flux ' 'ranges.') if hasattr(self.fva, 'columns'): fva_results = self.fva else: fva_results = flux_variability_analysis( self.model, reaction_list=boundary_rxns, fraction_of_optimum=self.fva) # save old index old_index = flux_summary.index # generate new index for consistency with fva_results flux_summary['rxn_id'] = flux_summary.apply( lambda x: x.reaction.id, axis=1) # change to new index flux_summary.set_index(['rxn_id'], inplace=True) flux_summary = pd.concat([flux_summary, fva_results], axis=1, sort=False) flux_summary.rename(columns={ 'maximum': 'fmax', 'minimum': 'fmin' }, inplace=True) # revert to old index flux_summary.set_index(old_index) def set_min_and_max(row): """Scale and set proper min and max values for flux.""" fmax = row.reaction.metabolites[row.metabolite] * row.fmax fmin = row.reaction.metabolites[row.metabolite] * row.fmin if abs(fmin) <= abs(fmax): row.fmax = fmax row.fmin = fmin else: # Reverse fluxes row.fmax = fmin row.fmin = fmax return row flux_summary = flux_summary.apply(set_min_and_max, axis=1) flux_summary = self._process_flux_dataframe(flux_summary) flux_summary = pd.concat([flux_summary, obj_df], sort=False) return flux_summary