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
예제 #2
0
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!")
예제 #3
0
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
예제 #5
0
    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
예제 #6
0
    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
예제 #7
0
    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'])
예제 #8
0
    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
예제 #10
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
예제 #11
0
    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))
예제 #12
0
 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.")
예제 #13
0
    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
예제 #14
0
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
예제 #15
0
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