def test_optstoic_glycolysis(): logger = create_logger(name='optstoicpy.script.optstoic_glycolysis.main') #logger.debug('Testing optstoic output filepath: %s', res_dir) logger.info("Test optstoic_glycolysis") pulp_solver = load_pulp_solver( solver_names=[ 'SCIP_CMD', 'GUROBI', 'GUROBI_CMD', 'CPLEX_CMD', 'GLPK_CMD'], logger=logger) test = OptStoicGlycolysis( objective='MinFlux', nATP=1, zlb=10, # setting this may slow down the optimization, but integer cut constraints will work max_iteration=1, pulp_solver=pulp_solver, result_filepath='./result/', M=1000, logger=logger) if sys.platform == 'cygwin': lp_prob, pathways = test.solve_gurobi_cl( outputfile='test_optstoic_cyg.txt', cleanup=False) #test.max_iteration = test.max_iteration + 2 #lp_prob, pathways = test.solve_gurobi_cl(outputfile='test_optstoic_cyg.txt', exclude_existing_solution=True, cleanup=False) else: lp_prob, pathways = test.solve(outputfile='test_optstoic.txt') #test.max_iteration = test.max_iteration + 1 #lp_prob, pathways = test.solve(outputfile='test_optstoic.txt', exclude_existing_solution=True) return lp_prob, pathways
def test_all_optimization_scripts(): """TODO: To be deprecated or convert to example in Jupyter notebook. """ logger = create_logger(name="optstoicpy.test.testAll") logger.info("Test optstoic") lp_prob1, pathways1 = optstoic.test_optstoic() logger.info( "Test optstoic glycolysis. Runtime depends on the solver used.") lp_prob2, pathways2 = optstoic_gly.test_optstoic_glycolysis() logger.info("Generate kegg_model and draw pathway") res_dir = os.path.join(current_dir, 'result/') if not os.path.exists(res_dir): os.makedirs(res_dir) f = open(os.path.join(res_dir, 'test_KeggModel.txt'), 'w+') for ind, p in pathways2.items(): p.rearrange_reaction_order() generate_kegg_model(p, filehandle=f) graph_title = "{0}_{1}ATP_P{2}".format(p.name, p.nATP, p.id) drawpathway.draw_pathway(p, os.path.join(res_dir + '/pathway_{0:03d}'.format(p.id)), imageFormat='png', graphTitle=graph_title) f.close() logger.info("optstoicpy.test.testAll completed!")
def load_pulp_solver(solver_names=ORDERED_SOLVERS, logger=None): """Load a pulp solver based on what is available. Args: solver_name (`list` of `str`, optional): A list of solver names in the order of loading preferences. logger (None, optional): A logging.Logger object Returns: pulp.apis.core.LpSolver: A pulp solver instance. """ if logger is None: logger = create_logger('optstoic.load_pulp_solver') if isinstance(solver_names, str): solver_names = [solver_names] elif not isinstance(solver_names, list): raise Exception("Argument solver_names must be a list!") # Load solvers in the order of preferences for solver_name in solver_names: kwargs = SOLVER_KWARGS.get(solver_name, None) pulp_solver = pulp.get_solver(**kwargs) if pulp_solver.available(): logger.warning("Pulp solver set to %s." % solver_name) if hasattr(pulp_solver, 'tmpDir'): pulp_solver.tmpDir = './' if solver_name == 'SCIP_CMD': scip_parameter_filepath = create_scip_parameter_file( parameters=SCIP_CMD_PARAMETERS, filepath=pulp_solver.tmpDir) pulp_solver.options = [ "-s", "{}".format(scip_parameter_filepath) ] if solver_name == 'GLPK_CMD': logger.warning( "GLPK takes a significantly longer time to solve " "OptStoic. Please be patient.") return pulp_solver logger.warning("No solver is available!") return None
def test_internal_loop_analysis(): logger = create_logger( name="optstoicpy.script.database_preprocessing.test_internal_loop_analysis") # Load the base database without exchange reactions db = load_base_reaction_db( user_defined_export_rxns_Sji=None ) # Load cofactors CURRENT_DIR = os.path.dirname(os.path.abspath(__file__)) DATA_DIR = os.path.normpath( os.path.join(CURRENT_DIR, '../data/')) cofactors_df = pd.read_csv( os.path.join( DATA_DIR, 'cofactors_to_exclude.csv')) cofactors = cofactors_df['KEGG_ID'].tolist() # Remove blocked reaction blocked_reactions = json.load( open( os.path.join( DATA_DIR, 'optstoic_db_v3', 'optstoic_v3_blocked_reactions_0to5ATP.json'), 'r+')) for rxn in blocked_reactions: db.remove_reaction(rxn, refresh_database=False) db.refresh_database() # Remove cofactors S_df = copy.deepcopy(db.S_df) S_df_no_cofactor = remove_cofactors_from_Sij(S_df, cofactors) assert_equal(S_df_no_cofactor.shape, (1844, 3256)) # Method 1: MATLAB # sparseMat, reactionList = write_matfile(S_df_no_cofactor) # run find_null_space.m to obtain all the loops # Method 2: This function is not yet implemented in Python as it is too slow # internal_loop_analysis(S_df_no_cofactor) return S_df_no_cofactor
def __init__( self, description='', data_filepath='../data/', dbdict_json=None, dbdict_gams=None, blocked_rxns=None, excluded_reactions=None, reduce_model_size=True, logger=None): """Summary Args: description (str, optional): Description of the Database data_filepath (str, optional): Description dbdict_json (None, optional): filename for json dbdict_gams (None, optional): filename for gams input blocked_rxns (None, optional): A list of blocked reactions excluded_reactions (None, optional): A list of reactions to be excluded from optstoic result reduce_model_size (bool, optional): If True, remove blocked_rxns from the S matrix to speed up optstoic analysis logger (None, optional): Description """ if logger is None: logger = create_logger('core.Database') self.description = description super(Database, self).__init__( data_filepath=data_filepath, dbdict_json=dbdict_json, dbdict_gams=dbdict_gams, logger=logger) # Initalize child-only attributes self.loops = [] self.Ninternal = {} self.all_excluded_reactions = [] self.excluded_reactions = excluded_reactions if blocked_rxns is not None: assert isinstance( blocked_rxns, list), "blocked_rxns must be a list" self.blocked_rxns = blocked_rxns self.reduce_model_size = reduce_model_size
def __init__(self, rid=None, flux=1, metabolites={}, equation='', reversible=True, logger=None): if logger is None: self.logger = create_logger('core.Reaction') else: self.logger = logger self.rid = rid self.flux = flux self.metabolites = metabolites self.equation = equation self.reversible = reversible
def setUp(self): self.logger = create_logger(name='Test core.drawpathway') self.pathway_fixture = { 'flux': [ -1.0, 1.0, 1.0, 1.0, 1.0, -1.0, -1.0, 1.0, 1.0, 1.0, -1.0, -1.0, -1.0, -1.0, 2.0, 1.0, 1.0, 1.0, -1.0, 1.0 ], 'iteration': 1, 'reaction_id': [ 'R00200', 'R00300', 'R00658', 'R01059', 'R01063', 'R01512', 'R01518', 'R01519', 'R01538', 'R08570', 'EX_glc', 'EX_nad', 'EX_adp', 'EX_phosphate', 'EX_pyruvate', 'EX_nadh', 'EX_atp', 'EX_h2o', 'EX_nadp', 'EX_nadph' ] } self.p1 = Pathway(id=1, name='OptStoic_pathway', reaction_ids=self.pathway_fixture['reaction_id'], fluxes=self.pathway_fixture['flux'])
def __init__(self, data_filepath='../data/', dbdict_json=None, dbdict_gams=None, logger=None): if logger is None: self.logger = create_logger('core.BaseDatabase') else: self.logger = logger self.data_filepath = data_filepath self.dbdict_json = dbdict_json self.dbdict_gams = dbdict_gams # initalize self.reactions = [] self.metabolites = [] self.S = {} self.Sji = {} self.rxntype = [] self.user_defined_export_rxns = [] self._S_df = None
def internal_loop_analysis(S_df, logger=None): """ Identifies the "rational" basis for the null space of S_df matrix, and convert them to internal loops. This is an alternative to the Matlab function null(S_df, 'r') (which is significantly faster). Warning: This is extremely time consuming. Please use the MATLAB version. M = Matrix([[16, 2, 3,13], [5,11,10, 8], [9, 7, 6,12], [4,14,15, 1]]) print(nsimplify(M, rational=True).nullspace()) """ if logger is None: logger = create_logger( name="optstoicpy.script.database_preprocessing.internal_loop_analysis") raise NotImplementedError("Use the Matlab version as this is too slow.") reactions = S_df_no_cofactor.columns.tolist() metabolites = S_df_no_cofactor.index.tolist() Sint = S_df_no_cofactor.as_matrix() # Sint_mat = Matrix(Sint) # too slow Smat = SparseMatrix(Sint.astype(int)) # Get the rational basis of the null space of Sint Nint_mat = nsimplify(Smat, rational=True).nullspace() # Convert back to numpy array Nint = np.array(Nint_mat).astype(np.float64) eps = 1e-9 Nint[Nint < eps] = 0
def test_optstoic(): """An alternative to the nosetest due to issue with PULP/SCIP_CMD """ logger = create_logger(name='optstoicpy.script.optstoic.main') logger.info("Test generalized optstoic") # Set the following reactions as allowable export reactions db3 = load_db_v3(reduce_model_size=True, user_defined_export_rxns_Sji={ 'EX_glc': { 'C00031': -1.0 }, 'EX_nad': { 'C00003': -1.0 }, 'EX_adp': { 'C00008': -1.0 }, 'EX_phosphate': { 'C00009': -1.0 }, 'EX_pyruvate': { 'C00022': -1.0 }, 'EX_nadh': { 'C00004': -1.0 }, 'EX_atp': { 'C00002': -1.0 }, 'EX_h2o': { 'C00001': -1.0 }, 'EX_hplus': { 'C00080': -1.0 }, 'EX_nadp': { 'C00006': -1.0 }, 'EX_nadph': { 'C00005': -1.0 } }) #logger.debug('Testing optstoic output filepath: %s', res_dir) pulp_solver = load_pulp_solver(solver_names=[ 'SCIP_CMD', 'GUROBI', 'GUROBI_CMD', 'CPLEX_CMD', 'GLPK_CMD' ], logger=logger) # How to add custom flux constraints: # E.g., # v('EX_nadph') + v('EX_nadh') = 2; # v('EX_nadp') + v('EX_nad') = -2; # v('EX_nadh') + v('EX_nad') = 0; # v('EX_nadph') + v('EX_nadp') = 0; # became # lp_prob += v['EX_nadph'] + v['EX_nadh'] == 2, 'nadphcons1' # lp_prob += v['EX_nadp'] + v['EX_nad'] == -2, 'nadphcons2' # lp_prob += v['EX_nadh'] + v['EX_nad'] == 0, 'nadphcons3' # lp_prob += v['EX_nadph'] + v['EX_nadp'] == 0, 'nadphcons4' custom_flux_constraints = [{ 'constraint_name': 'nadphcons1', 'reactions': ['EX_nadph', 'EX_nadh'], 'UB': 2, 'LB': 2 }, { 'constraint_name': 'nadphcons2', 'reactions': ['EX_nadp', 'EX_nad'], 'UB': -2, 'LB': -2 }, { 'constraint_name': 'nadphcons3', 'reactions': ['EX_nadh', 'EX_nad'], 'UB': 0, 'LB': 0 }, { 'constraint_name': 'nadphcons4', 'reactions': ['EX_nadph', 'EX_nadp'], 'UB': 0, 'LB': 0 }] specific_bounds = { 'EX_glc': { 'LB': -1, 'UB': -1 }, 'EX_pyruvate': { 'LB': 2, 'UB': 2 }, 'EX_nad': { 'LB': -2, 'UB': 0 }, 'EX_nadh': { 'LB': 0, 'UB': 2 }, 'EX_nadp': { 'LB': -2, 'UB': 0 }, 'EX_nadph': { 'LB': 0, 'UB': 2 }, 'EX_adp': { 'LB': -1, 'UB': -1 }, 'EX_phosphate': { 'LB': -1, 'UB': -1 }, 'EX_atp': { 'LB': 1, 'UB': 1 }, 'EX_h2o': { 'LB': 1, 'UB': 1 }, 'EX_hplus': { 'LB': -10, 'UB': 10 } } # pulp/gurobi has issue with "h+" test = OptStoic(database=db3, objective='MinFlux', zlb=None, specific_bounds=specific_bounds, custom_flux_constraints=custom_flux_constraints, add_loopless_constraints=True, max_iteration=2, pulp_solver=pulp_solver, result_filepath='./result/', M=1000, logger=logger) if sys.platform == 'cygwin': lp_prob, pathways = test.solve_gurobi_cl( outputfile='test_optstoic_cyg.txt', cleanup=False) #test.max_iteration = test.max_iteration + 2 #lp_prob, pathways = test.solve_gurobi_cl(outputfile='test_optstoic_cyg.txt', exclude_existing_solution=True, cleanup=False) else: lp_prob, pathways = test.solve(outputfile='test_optstoic.txt') #test.max_iteration = test.max_iteration + 1 #lp_prob, pathways = test.solve(outputfile='test_optstoic.txt', exclude_existing_solution=True) return lp_prob, pathways
def __init__(self, database, objective='MinFlux', zlb=None, specific_bounds=None, custom_flux_constraints=None, add_loopless_constraints=True, max_iteration=2, pulp_solver=None, result_filepath=None, M=1000, logger=None): """ Args: database (TYPE): An optStoic Database object (equivalent to GSM model) objective (str, optional): The mode for optStoic pathway prospecting. Options available are: ['MinFlux', 'MinRxn'] zlb (int, optional): The lowerbound on objective value z. If not provided, the integer cut constraints may not work properly when the objective value increases. specific_bounds (dict, optional): LB and UB for exchange reactions which defined the overall pathway equations. E.g. {'Ex_glc': {'LB': -1, 'UB':-1}} custom_flux_constraints (dict, optional): The custom constraints that need to be added to the model formulation. add_loopless_constraints (bool, optional): If True, use loopless constraints. If False, run optStoic without loopless constraint (faster, but the pathway may contain loops). max_iteration (int, optional): The default maximum number of iteration pulp_solver (None, optional): A pulp.solvers object (load any of the user-defined solver) result_filepath (str, optional): Filepath for result M (int, optional): The maximum flux bound (default 1000) logger (:obj:`logging.Logger`, optional): A logging.Logger object Raises: Exception: Description """ if logger is None: self.logger = create_logger(name='optstoic.OptStoic') else: self.logger = logger self.objective = objective self.zlb = zlb if specific_bounds is None: raise Exception("specific_bounds must be specified!") self.specific_bounds = specific_bounds self.add_loopless_constraints = add_loopless_constraints self.custom_flux_constraints = custom_flux_constraints self.M = M self._varCat = 'Integer' # self._varCat = 'Continuous' self.max_iteration = max_iteration if result_filepath is None: result_filepath = './result' self.result_filepath = result_filepath if not os.path.exists(self.result_filepath): self.logger.warning("A folder %s is created!" % self.result_filepath) os.makedirs(self.result_filepath) self.database = database self.pathways = {} self.iteration = 1 self.lp_prob = None self.pulp_solver = pulp_solver self.lp_prob_fname = "OptStoic_{0}".format( self.generate_random_string(6))
def setUp(self): self.logger = create_logger(name='Test generalized optstoic') self.pulp_solver = load_pulp_solver(solver_names=ORDERED_SOLVERS) if self.pulp_solver.name not in ['GUROBI', 'GUROBI_CMD', 'CPLEX_CMD']: self.skipTest("Skip because it will take a long time to solve.")
def __init__(self, id=None, name=None, reaction_ids=[], fluxes=[], reactions=None, sourceSubstrateID='C00031', endSubstrateID='C00022', total_flux_no_exchange=None, note={}, logger=None): """ A Pathway instance can be initialized by either (a) List of reaction_ids and fluxes (Let reactions = None) (b) List of reaction instances (reaction_ids and fluxes will be populated) Args: id (None, optional): Pathway id name (None, optional): Pathway name reaction_ids (list, optional): A list of reaction IDs (kegg_id) in the pathway fluxes (list, optional): A list of reaction fluxes corresponding to the reaction_ids reactions (None, optional): Description sourceSubstrateID (str, optional): Kegg compound ID of the source metabolite of the pathway endSubstrateID (str, optional): Kegg compound ID of the end metabolite of the pathway total_flux_no_exchange (None, optional): Sum of absolute flux through the pathway (Exclude export reactions) note (dict, optional): (For debugging purpose) modelstat and solvestat can be added """ if logger is None: self.logger = create_logger('core.Pathway') else: self.logger = logger self.id = id self.name = name self.note = note # Iniatilize pathway object using list of reaction_ids and fluxes if reactions is None: # Check if both reaction_ids and fluxes are list and # contain the same number of item assert (isinstance(reaction_ids, list) == 1) assert (isinstance(fluxes, list) == 1) assert len(reaction_ids) == len( fluxes), "number of reactions must equal number of fluxes!" # Change EX_h+ to EX_hplus as optstoic pulp fail to read "+" symbol self.reaction_ids = [ "EX_hplus" if x == "EX_h+" else x for x in reaction_ids ] self.fluxes = fluxes # Create list of reaction objects self.reactions = Reaction.create_Reaction_list_from_dict( { 'reaction_id': self.reaction_ids, 'flux': self.fluxes }, excludeExchangeRxn=True) # Iniatilize pathway object using list of reaction objects else: self.reactions = reactions self.fluxes = [r.flux for r in reactions] self.reaction_ids = [r.rid for r in reactions] self.reaction_ids_no_exchange = [ r for r in reaction_ids if 'EX_' not in r ] if not total_flux_no_exchange: self.total_flux_no_exchange = sum( map(abs, [r.flux for r in self.reactions])) else: self.total_flux_no_exchange = total_flux_no_exchange self.rxn_flux_dict = dict(list(zip(self.reaction_ids, self.fluxes))) try: self.nATP = self.rxn_flux_dict['EX_atp'] except BaseException: self.nATP = None self.sourceSubstrateID = sourceSubstrateID self.endSubstrateID = endSubstrateID
def load_db_v3_toy_model(user_defined_export_rxns_Sji, reduce_model_size=True, logger=None): """Load OptStoic database v3 Returns: TYPE: Description Args: reduce_model_size (bool, optional): True if you want to reduce the size of the model by removing blocked reactions from the S matrix. user_defined_export_rxns_Sji (dict, optional): The list of export reactions that need to be added to the model for metabolite exchange (i.e., any metabolite that participate in the design equation) """ if logger is None: logger = create_logger(name="optstoicpy.core.database.load_db_v3") # get reactions that are manually curated to be excluded for glycolysis # study excluded_reactions = load_custom_reactions_to_be_excluded() dbdict_json = { 'Sji': 'toy_model_Sji.json', #'Nint': 'Nint_ecolicore.json', 'reactiontype': 'toy_model_reactiontype.json' } dbdict_gams = { 'Sji': 'optstoic_v3_Sij.txt', 'reaction': 'optstoic_v3_reactions.txt', 'metabolite': 'optstoic_v3_metabolites.txt', 'reactiontype': 'optstoic_v3_reactiontype.txt', 'loops': 'optstoic_v3_loops_nocofactor.txt', 'Nint': 'optstoic_v3_null_sij_nocofactor.txt', 'blocked_rxns': 'optstoic_v3_blocked_reactions_0to5ATP.txt', } logger.debug('Reading blocked reactions file...') if 'blocked_rxns' in dbdict_gams: blocked_rxns = gams_parser.convert_set_to_list( os.path.join(DATA_DIR, dbdict_gams['blocked_rxns'])) else: blocked_rxns = None DB = Database(description='toy_model_v3', data_filepath=DATA_DIR, dbdict_json=dbdict_json, dbdict_gams=dbdict_gams, blocked_rxns=[], excluded_reactions=excluded_reactions, reduce_model_size=reduce_model_size) DB.load_toy_model() # Update reaction type # Update reaction type = 0 irreversible_fwd_rxns = gams_parser.convert_set_to_list( os.path.join(DATA_DIR, 'optstoic_v3_ATP_irreversible_forward_rxns.txt')) new_reaction_type_dict = dict( list(zip(irreversible_fwd_rxns, [0] * len(irreversible_fwd_rxns)))) # Update reaction type = 2 irreversible_bwd_rxns = gams_parser.convert_set_to_list( os.path.join(DATA_DIR, 'optstoic_v3_ATP_irreversible_backward_rxns.txt')) new_reaction_type_dict.update( dict(list(zip(irreversible_bwd_rxns, [2] * len(irreversible_bwd_rxns))))) DB.update_rxntype(new_reaction_type_dict) # user_defined_export_rxns = ['EX_glc', 'EX_nad', 'EX_adp', # 'EX_phosphate', 'EX_pyruvate', 'EX_nadh', # 'EX_atp', 'EX_h2o', 'EX_hplus', 'EX_nadp', # 'EX_nadph'] # Add a list of export reactions and the metabolites #if user_defined_export_rxns_Sji is not None: user_defined_export_rxns_Sij = Database.transpose_S( user_defined_export_rxns_Sji) DB.set_database_export_reaction(user_defined_export_rxns_Sij) return DB
def load_base_reaction_db_ecolicore(user_defined_export_rxns_Sji=None, logger=None): """Load the base reaction database with all reactions (i.e., no blocked reactions and no loops) Args: user_defined_export_rxns_Sji (None, optional): Description Returns: :obj:`BaseReactionDatabase`: """ if logger is None: logger = create_logger( name="optstoicpy.core.database.load_base_reaction_db") # get reactions that are manually curated to be excluded for glycolysis # study excluded_reactions = load_custom_reactions_to_be_excluded() dbdict_json = { 'Sji': 'Sji_dict_ecolicore.json', 'reactiontype': 'ecolicore_reactiontype.json' } dbdict_gams = { 'Sji': 'optstoic_v3_Sij.txt', 'reaction': 'optstoic_v3_reactions.txt', 'metabolite': 'optstoic_v3_metabolites.txt', 'reactiontype': 'optstoic_v3_reactiontype.txt' } DB = BaseReactionDatabase(data_filepath=DATA_DIR, dbdict_json=dbdict_json, dbdict_gams=dbdict_gams) DB.load_ecolicore() # Update reaction type # Update reaction type = 0 irreversible_fwd_rxns = gams_parser.convert_set_to_list( os.path.join(DATA_DIR, 'optstoic_v3_ATP_irreversible_forward_rxns.txt')) new_reaction_type_dict = dict( list(zip(irreversible_fwd_rxns, [0] * len(irreversible_fwd_rxns)))) # Update reaction type = 2 irreversible_bwd_rxns = gams_parser.convert_set_to_list( os.path.join(DATA_DIR, 'optstoic_v3_ATP_irreversible_backward_rxns.txt')) new_reaction_type_dict.update( dict(list(zip(irreversible_bwd_rxns, [2] * len(irreversible_bwd_rxns))))) DB.update_rxntype(new_reaction_type_dict) # Add a list of export reactions and the metabolites if user_defined_export_rxns_Sji is not None: user_defined_export_rxns_Sij = Database.transpose_S( user_defined_export_rxns_Sji) DB.set_database_export_reaction(user_defined_export_rxns_Sij) return DB
def blocked_reactions_analysis( database, pulp_solver, specific_bounds, custom_flux_constraints, excluded_reactions=None, target_reactions_list=None, logger=None): """ Perform flux variability analysis on the database, based on the overall reaction equation of optstoic. If a reaction cannot carry flux (i.e., -eps <= v(j) <= eps, where eps = 1e-8), then the reaction is considered as a blocked reaction. The blocked reactions are then eliminated from the database S matrix. Next, the internal loops (excluding cofactors) are identified. Then, optStoic analysis can be performed for pathway prospecting. max/min v(j) subject to: sum(j, S(i,j) * v(j)) = 0, for all i custom_flux_constraints Note: The glycolysis study was done using the GAMS version of this code. This is written in attempt to port find_blocked_reactions.gms from GAMS to Python, as a part of effort to generalize optstoic analysis. Args: database (:obj:`BaseReactionDatabase`): The default reaction database without blocked reactions/loops. pulp_solver (TYPE): The solver for PuLP. specific_bounds (dict): LB and UB for exchange reactions which defined the overall design equations. E.g. {'Ex_glc': {'LB': -1, 'UB':-1}} custom_flux_constraints (TYPE): The custom constraints that need to be added to the model formulation. excluded_reactions (None, optional): The list of reactions that are manually selected to be excluded from optstoic solution. target_reactions_list (None, optional): If provided, the blocked reaction analysis is performed only on a subset of the reaction provided. If None, the blocked reaction analysis will be performed on all reactions in the database. The excluded_reactions set can be subtracted(e.g., set(database.reactions) - excluded_reactions), since they are blocked reactions. logger (:obj:`logging.logger`, optional): The logging instance Returns: TYPE: Description Raises: ValueError: Description Deleted Parameters: user_defined_export_rxns_Sji (dict): The list of export reactions that need to be added to the model for metabolite exchange (i.e., any metabolite that participate in the design equation) """ if logger is None: logger = create_logger( name="optstoicpy.script.database_preprocessing.blocked_reactions_analysis") logger.warning( "This process may take a long time to run. It is recommended to be run in a batch script.") M = 1000 EPS = 1e-8 # Initialize variables v = pulp.LpVariable.dicts("v", database.reactions, lowBound=-M, upBound=M, cat='Continuous') for j in database.reactions: if database.rxntype[j] == 0: # Forward irreversible v[j].lowBound = 0 v[j].upBound = M elif database.rxntype[j] == 1: # Reversible v[j].lowBound = -M v[j].upBound = M elif database.rxntype[j] == 2: # Reverse irreversible v[j].lowBound = -M v[j].upBound = 0 elif database.rxntype[j] == 4: v[j].lowBound = 0 v[j].upBound = 0 else: raise ValueError("Reaction type for reaction %s is unknown." % j) if excluded_reactions is not None: for j in excluded_reactions: v[j].lowBound = 0 v[j].upBound = 0 # Fix stoichiometry of source/sink metabolites for j, bounds in specific_bounds.items(): v[j].lowBound = bounds['LB'] v[j].upBound = bounds['UB'] FVA_res = {} blocked_reactions = [] lp_prob = None if target_reactions_list is None: target_reactions_list = database.reactions num_rxn = len(target_reactions_list) for ind, j1 in enumerate(target_reactions_list): logger.debug("%s/%s" % (ind, num_rxn)) FVA_res[j1] = {} for obj in ['min', 'max']: # Variables (make a copy) vt = copy.deepcopy(v) del lp_prob # Objective function if obj == 'min': lp_prob = pulp.LpProblem("FVA%s" % obj, pulp.LpMinimize) lp_prob += vt[j1], "FVA_min" elif obj == 'max': lp_prob = pulp.LpProblem("FVA%s" % obj, pulp.LpMaximize) lp_prob += vt[j1], "FVA_max" # Constraints # Mass_balance for i in database.metabolites: # If metabolites not involve in any reactions if i not in database.S: continue label = "mass_balance_%s" % i dot_S_v = pulp.lpSum([database.S[i][j] * vt[j] for j in list(database.S[i].keys())]) condition = dot_S_v == 0 lp_prob += condition, label if custom_flux_constraints is not None: logger.info("Adding custom constraints...") for group in custom_flux_constraints: lp_prob += pulp.lpSum(vt[rxn] for rxn in group['reactions'] ) <= group['UB'], "%s_UB" % group['constraint_name'] lp_prob += pulp.lpSum(vt[rxn] for rxn in group['reactions'] ) >= group['LB'], "%s_LB" % group['constraint_name'] lp_prob.solve(solver=pulp_solver) FVA_res[j1][obj] = pulp.value(lp_prob.objective) if (FVA_res[j1]['max'] < EPS) and (FVA_res[j1]['min'] > -EPS): blocked_reactions.append(j1) json.dump(FVA_res, open("temp_FVA_result.json", 'w+'), sort_keys=True, indent=4) return blocked_reactions, FVA_res