def stoichiometric_matrix(model, path):
    """
    Extracts the model's stoichiometric matrix in a format compatible with TreeEFM [Pey et al., 2014],
    i.e. one cell per line as follows: row,column,value.
    :param model: libsbml model
    :param path: path to file where to save the matrix
    :return m_id2i, r_id2i, rev_r_id2i: a tuple of three dictionaries: species id to its index,
    reactions id to its indices; reversible reaction id to its index (for the opposite direction).
    """
    internal_s_ids = [s.id for s in model.getListOfSpecies() if not s.getBoundaryCondition()]
    m_id2i = dict(zip(internal_s_ids, range(1, len(internal_s_ids) + 1)))
    r_id2i, rev_r_id2i = {}, {}

    def add_reaction_data(f, reaction_number, reaction, rev=False):
        for (m_id, st) in get_reactants(reaction, True):
            if m_id in m_id2i:
                f.write("%d,%d,%d\n" % (m_id2i[m_id], reaction_number, st if rev else -st))
        for (m_id, st) in get_products(reaction, True):
            if m_id in m_id2i:
                f.write("%d,%d,%d\n" % (m_id2i[m_id], reaction_number, -st if rev else st))

    i = 1
    with open(path, 'w+') as f:
        for r in sorted(model.getListOfReactions(), key=lambda r: r.id):
            l_b, u_b = get_bounds(r)
            if u_b > 0:
                add_reaction_data(f, i, r)
                r_id2i[r.id] = i
                i += 1
            if l_b < 0:
                add_reaction_data(f, i, r, rev=True)
                rev_r_id2i[r.id] = i
                i += 1
    return m_id2i, r_id2i, rev_r_id2i
def constraint_exchange_reactions(model, forsed_r_id2rev, prohibited_r_id2rev=None, cofactors=None, min_flux=0.01):
    logging.info("Constraining input reactions...")

    for r in model.getListOfReactions():
        r_id = r.getId()
        r_l, r_u = get_bounds(r)

        if forsed_r_id2rev and r_id in forsed_r_id2rev:
            rev = forsed_r_id2rev[r_id]
            if rev:
                # reverse the reaction and set positive bounds,
                # as if both bounds are negative, the glp solver produces an error
                reverse_reaction(r)
                set_bounds(r, max(min_flux, -r_u), max(min_flux, -r_l))
                forsed_r_id2rev[r_id] = not rev
            else:
                set_bounds(r, max(min_flux, r_l), max(min_flux, r_u))
            r.setReversible(False)
            continue

        if prohibited_r_id2rev and r_id in prohibited_r_id2rev:
            rev = prohibited_r_id2rev[r_id]
            if not rev:
                reverse_reaction(r)
                set_bounds(r, 0, max(0, -r_l))
                prohibited_r_id2rev[r_id] = not rev
            else:
                set_bounds(r, 0, max(0, r_u))
            r.setReversible(False)
            continue

        if not cofactors:
            continue

        rs, ps = set(get_reactants(r)), set(get_products(r))

        boundary_s_ids = {s_id for s_id in rs if model.getSpecies(s_id).getBoundaryCondition()}
        if boundary_s_ids or not rs:
            if ps - cofactors:
                reverse_reaction(r)
                set_bounds(r, 0, max(-r_l, 0))
                r.setReversible(False)
            continue
        boundary_s_ids = {s_id for s_id in ps if model.getSpecies(s_id).getBoundaryCondition()}
        if boundary_s_ids or not ps:
            if rs - cofactors:
                set_bounds(r, 0, max(r_u, 0))
                r.setReversible(False)