Case study iMM904 curation

Using the iMM904 model as a case study, we have used Thermo-Flux and did the few manual curation steps to show how models can be converted into a combined thermodynamic-stoichiometric model. Here, we present the steps that required manual attention when following the protocol described in the publication. “

[ ]:
import cobra
from cobra import Model, Reaction, Metabolite
from thermo_flux.core.metabolite import ThermoMetabolite
import thermo_flux
from thermo_flux.io import load_excel as ex
from thermo_flux.core.model import ThermoModel, ThermoReaction
from equilibrator_api import  Q_
import pandas as pd
from thermo_flux.io import load_gams as gs
from thermo_flux.io import helper_load as hl
from thermo_flux.utils.vis import compare_met
import numpy as np
import gurobipy as  gp
from gurobipy import GRB


import seaborn as sns
import matplotlib.pyplot as plt

Import sbml model from BiGG

[2]:
from cobra.io import load_json_model
model=load_json_model('iMM904.json')

#set objective to biomass
model.objective = model.reactions.BIOMASS_SC5_notrace
model.reactions.EX_co2_e.id='co2_EX'
model.reactions.EX_o2_e.id='o2_EX'
model.reactions.EX_etoh_e.id='etoh_EX'
model.repair()

for rxn in model.reactions:
    if 'EX_' not in rxn.id:
        rxn.lower_bound=-500 if rxn.lower_bound==-999999.0 else rxn.lower_bound
        rxn.upper_bound=500 if rxn.upper_bound==999999.0 else rxn.upper_bound
    else:
        rxn.lower_bound=-500 if rxn.lower_bound==-999999.0 else rxn.lower_bound
        rxn.upper_bound=500 if rxn.upper_bound==999999.0 else rxn.upper_bound
       # print(rxn.id,rxn.lower_bound,rxn.upper_bound)

#reset O2 exchange bound
model.optimize().to_frame().loc['BIOMASS_SC5_notrace']['fluxes']
model.reactions.o2_EX.bounds=(-500,0)

print(model.optimize().to_frame().loc['BIOMASS_SC5_notrace']['fluxes'])
model.optimize().to_frame().loc[[rxn.id for rxn in model.boundary]]


Set parameter Username
Academic license - for non-commercial use only - expires 2025-02-26
0.9748814206733495
[2]:
fluxes reduced_costs
EX_epistest_SC_e 0.0 -96.275549
EX_epist_e 0.0 -1.676719
EX_ergst_e 0.0 -1.722387
EX_ergstest_SC_e 0.0 -96.321217
EX_13BDglcn_e 0.0 -0.196130
... ... ...
EX_xyl__D_e 0.0 -0.163442
EX_xylt_e 0.0 -0.176421
EX_rib__D_e 0.0 -0.163442
EX_zymst_e 0.0 -1.620476
EX_zymstest_SC_e 0.0 -96.219306

164 rows × 2 columns

[3]:
#plot biomass yield for stoichiometric model
#GUR_range=[-0.25, -0.57, -0.6 , -1.1 , -1.67, -2.14, -2.22, -2.83, -3.24, -3.7, -4.67, -5.44, -7.62, -8.09, -8.33, -10.23, -13.18, -15.7, -22.42]
GUR_range = np.linspace(-0, -23, 50)

GUR_plot_range=[-x for x in GUR_range]
biomass_flux=[]
etoh_flux=[]
o2_flux=[]
co2_flux=[]
for GUR in GUR_range:
    model.reactions.EX_glc__D_e.upper_bound=0
    model.reactions.EX_glc__D_e.lower_bound=GUR
    sol = model.optimize()
    #add values to list
    biomass_flux.append(sol.fluxes.loc['BIOMASS_SC5_notrace'])
    etoh_flux.append(sol.fluxes.loc['etoh_EX'])
    o2_flux.append(sol.fluxes.loc['o2_EX'])
    co2_flux.append(sol.fluxes.loc['co2_EX'])
#plot biomass vs GUR in a scatter plot
sns.scatterplot(x=GUR_plot_range,y=biomass_flux)
plt.xlabel('Glucose uptake rate (mmol/(gDW.h))')
plt.ylabel('Biomass production (mmol/(gDW.h))')
fba_flux=pd.DataFrame({'biomass_EX':biomass_flux,'etoh_EX':etoh_flux,'o2_EX':o2_flux,'co2_EX':co2_flux},index=GUR_range)
# plt.savefig('Figures/growth_stoichio_only.png',dpi=300)
# model.optimize().to_frame().loc['BIOMASS_SC5_notrace']/model.optimize().to_frame().loc['EX_glc__D_e']
c:\Users\tedns\anaconda3\envs\thermoflux\lib\site-packages\cobra\util\solver.py:554: UserWarning: Solver status is 'infeasible'.
  warn(f"Solver status is '{status}'.", UserWarning)
_images/thermoflux_iMM904_ENS_4_1.png

Step 1 - Physical and Biochemical parameters

[ ]:
tmodel=ThermoModel(model,split_biomass=True, add_charge_exchange= True)
Initializing component contribution object...
No valid license for cxcalc installed, operating in read-only mode. A local cache may be loaded, but no compounds can be created. Please obtain a ChemAxon license to enable compound creation.
Loading compounds from iMM904_compound.sqlite
added reaction:  biomass_ce: biomass_c <=> biomass_e
added reaction:  biomass_EX: biomass_e <=>
added reaction:  charge_ce: charge_e <=> charge_c
added reaction:  EX_charge: charge_e <=>
added reaction:  charge_cm: charge_m <=> charge_c
added reaction:  charge_cx: charge_x <=> charge_c
added reaction:  charge_cr: charge_r <=> charge_c
added reaction:  charge_cv: charge_v <=> charge_c
added reaction:  charge_cg: charge_g <=> charge_c
added reaction:  charge_cn: charge_n <=> charge_c
[ ]:
#define the biochemical parameters of the model

tmodel.phi={'ec': Q_(-0.06,'volt'), 'cm': Q_(-0.16,'volt'),'cx':Q_(0,'volt'),'cr':Q_(0,'volt'),'cv':Q_(0,'volt'),'cg':Q_(0,'volt'),'cn':Q_(0,'volt')}
tmodel.pH={'c':Q_(7),'e':Q_(5),'m':Q_(7.4),'r':Q_(7),'v':Q_(5.5),'g':Q_(7),'x':Q_(7),'n':Q_(7)} #On the intracellular pH of baker’s yeast(r), Martinez-Munoz et al. 2008(v), Juan Llopis et al 1998 (g), 7 is default pH

#change max drGcalc_drGt
tmodel._max_drG=Q_(1e6,'kJ/mol') #max is already 1e8 shouldnt need to change

Step 2 - Definition of metabolites

[ ]:
tmodel.get_compounds(search = True)
[██████████████████......................] 568/1236 gsn_m
C:\Users\tedns\AppData\Local\Temp\ipykernel_1896\2562127799.py:1: UserWarning: dolichol_c has inconsistent formula with eQuilibrator identified metabolite. COBRA:{'C': 15, 'H': 28, 'O': 1}, eQuilibrator:{'C': 25, 'O': 1}. Defining as unknown compound
  tmodel.get_compounds(search = True)
C:\Users\tedns\AppData\Local\Temp\ipykernel_1896\2562127799.py:1: UserWarning: dolmanp_r has inconsistent formula with eQuilibrator identified metabolite. COBRA:{'C': 21, 'H': 38, 'O': 9, 'P': 1}, eQuilibrator:{'C': 31, 'O': 9, 'P': 1}. Defining as unknown compound
  tmodel.get_compounds(search = True)
C:\Users\tedns\AppData\Local\Temp\ipykernel_1896\2562127799.py:1: UserWarning: glycogen_c has inconsistent formula with eQuilibrator identified metabolite. COBRA:{'C': 6, 'H': 10, 'O': 5}, eQuilibrator:{'O': 21, 'C': 24}. Defining as unknown compound
  tmodel.get_compounds(search = True)
C:\Users\tedns\AppData\Local\Temp\ipykernel_1896\2562127799.py:1: UserWarning: glycogen_v has inconsistent formula with eQuilibrator identified metabolite. COBRA:{'C': 6, 'H': 10, 'O': 5}, eQuilibrator:{'O': 21, 'C': 24}. Defining as unknown compound
  tmodel.get_compounds(search = True)
[███████████████████████████.............] 843/1236 pmtcoa_c
C:\Users\tedns\AppData\Local\Temp\ipykernel_1896\2562127799.py:1: UserWarning: mhpglu_c has inconsistent formula with eQuilibrator identified metabolite. COBRA:{'C': 25, 'H': 36, 'N': 8, 'O': 12}, eQuilibrator:{'C': 30, 'N': 9, 'O': 12}. Defining as unknown compound
  tmodel.get_compounds(search = True)
C:\Users\tedns\AppData\Local\Temp\ipykernel_1896\2562127799.py:1: UserWarning: macchitppdol_g has inconsistent formula with eQuilibrator identified metabolite. COBRA:{'C': 37, 'H': 64, 'N': 2, 'O': 22, 'P': 2}, eQuilibrator:{'C': 47, 'O': 22, 'P': 2, 'N': 2}. Defining as unknown compound
  tmodel.get_compounds(search = True)
C:\Users\tedns\AppData\Local\Temp\ipykernel_1896\2562127799.py:1: UserWarning: hpglu_c has inconsistent formula with eQuilibrator identified metabolite. COBRA:{'C': 24, 'H': 34, 'N': 8, 'O': 12}, eQuilibrator:{'N': 9, 'C': 29, 'O': 12}. Defining as unknown compound
  tmodel.get_compounds(search = True)
[███████████████████████████████████████.] 1221/1236 zym_int1_c
C:\Users\tedns\AppData\Local\Temp\ipykernel_1896\2562127799.py:1: UserWarning: trdox_c has inconsistent formula with eQuilibrator identified metabolite. COBRA:{'X': 1}, eQuilibrator:{'N': 4, 'C': 10, 'S': 2, 'O': 4}. Defining as unknown compound
  tmodel.get_compounds(search = True)
C:\Users\tedns\AppData\Local\Temp\ipykernel_1896\2562127799.py:1: UserWarning: trdox_m has inconsistent formula with eQuilibrator identified metabolite. COBRA:{'X': 1}, eQuilibrator:{'N': 4, 'C': 10, 'S': 2, 'O': 4}. Defining as unknown compound
  tmodel.get_compounds(search = True)
C:\Users\tedns\AppData\Local\Temp\ipykernel_1896\2562127799.py:1: UserWarning: trdox_n has inconsistent formula with eQuilibrator identified metabolite. COBRA:{'X': 1}, eQuilibrator:{'N': 4, 'C': 10, 'S': 2, 'O': 4}. Defining as unknown compound
  tmodel.get_compounds(search = True)
C:\Users\tedns\AppData\Local\Temp\ipykernel_1896\2562127799.py:1: UserWarning: trdox_x has inconsistent formula with eQuilibrator identified metabolite. COBRA:{'X': 1}, eQuilibrator:{'N': 4, 'C': 10, 'S': 2, 'O': 4}. Defining as unknown compound
  tmodel.get_compounds(search = True)
C:\Users\tedns\AppData\Local\Temp\ipykernel_1896\2562127799.py:1: UserWarning: trdrd_c has inconsistent formula with eQuilibrator identified metabolite. COBRA:{'X': 1, 'H': 2}, eQuilibrator:{'N': 4, 'C': 10, 'S': 2, 'O': 4}. Defining as unknown compound
  tmodel.get_compounds(search = True)
C:\Users\tedns\AppData\Local\Temp\ipykernel_1896\2562127799.py:1: UserWarning: trdrd_m has inconsistent formula with eQuilibrator identified metabolite. COBRA:{'X': 1, 'H': 2}, eQuilibrator:{'N': 4, 'C': 10, 'S': 2, 'O': 4}. Defining as unknown compound
  tmodel.get_compounds(search = True)
C:\Users\tedns\AppData\Local\Temp\ipykernel_1896\2562127799.py:1: UserWarning: trdrd_n has inconsistent formula with eQuilibrator identified metabolite. COBRA:{'X': 1, 'H': 2}, eQuilibrator:{'N': 4, 'C': 10, 'S': 2, 'O': 4}. Defining as unknown compound
  tmodel.get_compounds(search = True)
C:\Users\tedns\AppData\Local\Temp\ipykernel_1896\2562127799.py:1: UserWarning: trdrd_x has inconsistent formula with eQuilibrator identified metabolite. COBRA:{'X': 1, 'H': 2}, eQuilibrator:{'N': 4, 'C': 10, 'S': 2, 'O': 4}. Defining as unknown compound
  tmodel.get_compounds(search = True)
[████████████████████████████████████████] 1236/1236 charge_n

[]
[ ]:
#define biomass parameters
#if the biomass formula is not automatically calcualted it's important to specifiy the hydrogen atoms in biomass to ensure correct proton balancing
tmodel.metabolites.biomass_c.formula = 'H67'
tmodel.metabolites.biomass_e.formula = 'H67'

#manually define charge of cytochrome C so electron movement is correctly calcualted
tmodel.metabolites.focytc_m.charge = 2
tmodel.metabolites.focytc_m.redox= True #define as redox metabolite
tmodel.metabolites.ficytc_m.charge = 3
tmodel.metabolites.ficytc_m.redox = True

Step 3 - metabolites formation energies

When specifying the value of dfGprime for the biomass metabolite, we should plug in the values in units of J/gDW. This differs from all the other reactions in the model, for which values of kJ/mol are used.

The reason for this discrepancy has to do with the units of the fluxes.

For a normal reaction: \(g_{diss, rxn} = \Delta_rG \times v = (kJ/mol) \times (mmol/gDW/h) = (10^3 J/mol) \times (10^{-3} mol/gDW/h) = J/gDW/h\)

For the biomass reaction: $g_{diss, bio} = \DeltafG{bio} \times `:nbsphinx-math:mu = (J/gDW) :nbsphinx-math:times `(gDW/gDW/h) = J/gDW/h $

Since thermoflux handles all reactions the same when determining the gibbs energy dissipation rate (multiplying \(v\) by \(\Delta_rG\)), we have to pretend that the biomass formation energy has units of kJ/mol. E.g: tmodel.metabolites.biomass_c.dfGprime = Q_(701.767, "kJ/mol") (where 701.767 is the formation energy of E.coli biomass at cytosolic pH, in units of J/gDW)

[8]:
#define biomass properties
tmodel.reactions.BIOMASS_SC5_notrace.id='biomass_c' #update biomass reaction name for convenience

tmodel.metabolites.biomass_c.dfG0 = Q_(-3.04,'kJ/mol')*1000 #From Battley 1991, *1000 for mmol flux units (actual units here are J/gDW)
tmodel.metabolites.biomass_e.dfG0 = Q_(-3.04,'kJ/mol')*1000

Step 4 - Transporters

[9]:
#idenfity transporters where the transported metabolite is empty
#these could either be redox reactions or proton pumps just moving electrons (charge) and protons
#or they could be transporters with chemcial transformation during the transport process
for reaction in tmodel.reactions:
    if len(reaction.compartments) > 1:
        if thermo_flux.tools.drg_tools.calc_transported_mets(reaction) == {}:
            print(reaction.id, reaction, thermo_flux.tools.drg_tools.calc_transported_mets(reaction))

ASPOcm ASPOcm: asp__L_c + fad_m --> fadh2_m + h_c + iasp_c {}
ATPS ATPS: atp_c + h2o_c --> adp_c + h_e + pi_c {}
DHORD4i DHORD4i: dhor__S_c + q6_m --> orot_c + q6h2_m {}
DOLPMTcer DOLPMTcer: dolp_c + gdpmann_c --> dolmanp_r + gdp_c {}
DXHPScm DXHPScm: h2o_c + q6_m + spmd_c --> 13dampp_c + 4abutn_c + q6h2_m {}
D_LACDcm D_LACDcm: 2.0 ficytc_m + lac__D_c --> 2.0 focytc_m + pyr_c {}
FDNG FDNG: for_c + h_c + q6_m --> co2_c + q6h2_m {}
FRDcm FRDcm: fadh2_m + fum_c --> fad_m + succ_c {}
L_LACD2cm L_LACD2cm: 2.0 ficytc_m + lac__L_c --> 2.0 focytc_m + pyr_c {}
NADH2_u6cm NADH2_u6cm: h_c + nadh_c + q6_m --> nad_c + q6h2_m {}
[10]:
#DOLPMTcer involved chemcial transformation of dolp. We assume transport of dolmanp for thermodynamic calcualtion
tmodel.reactions.DOLPMTcer.transported_mets = {tmodel.metabolites.dolmanp_r: -1}

Step 5 - charge and proton balancing

[11]:
for rxn in tmodel.reactions:
    thermo_flux.tools.drg_tools.reaction_balance(rxn, balance_charge=True, balance_mg=False)
C:\Users\tedns\AppData\Local\Temp\ipykernel_1896\1541778503.py:2: UserWarning: ATPS3v is not balanced and could not be automatically balanced, please check reaction stoichiometry or define reaction.transported_h
  thermo_flux.tools.drg_tools.reaction_balance(rxn, balance_charge=True, balance_mg=False)
[12]:
#ATPS3v is vacuolar ATP synthase transporting three protons per ATP
tmodel.reactions.ATPS3v
[12]:
Reaction identifierATPS3v
NameATP synthase vacuole
Memory address 0x16d19f17f40
Stoichiometry

adp_v + 3.0 h_c + pi_v --> atp_v + h2o_v + 2.0 h_v

ADP C10H12N5O10P2 + 3.0 H+ + Phosphate --> ATP C10H12N5O13P3 + H2O H2O + 2.0 H+

GPRYBR127C and YDL185W and YEL027W and YEL051W and YGR020C and YHR026W and YHR039C_A and YKL080W and...
Lower bound0.0
Upper bound500
[13]:
tmodel.reactions.ATPS3v.transported_h = {'v': 3.0, 'c': -3.0}
thermo_flux.tools.drg_tools.reaction_balance(tmodel.reactions.ATPS3v, balance_charge=True, balance_mg=False)
tmodel.reactions.ATPS3v
[13]:
Reaction identifierATPS3v
NameATP synthase vacuole
Memory address 0x16d19f17f40
Stoichiometry

adp_v + 3.0 charge_c + 3.0 h_c + pi_v --> atp_v + 3.0 charge_v + h2o_v + 3.021815 h_v

ADP C10H12N5O10P2 + 3.0 charge + 3.0 H+ + Phosphate --> ATP C10H12N5O13P3 + 3.0 charge + H2O H2O + 3.021815 H+

GPRYBR127C and YDL185W and YEL027W and YEL051W and YGR020C and YHR026W and YHR039C_A and YKL080W and...
Lower bound0.0
Upper bound500

Step 6 - Calculation of Gibbs energy of reactions

Add thermodynamic constraints and parameters

[14]:
#change max flux to 500 for all reactions

tmodel.reactions.get_by_id("biomass_EX").bounds = (0,500)
tmodel.reactions.get_by_id("biomass_c").bounds = (0,500)
tmodel.reactions.get_by_id("biomass_ce").bounds = (0,500)


for rxn in tmodel.reactions:
    rxn.lower_bound = -500 if rxn.lower_bound == -1000 else rxn.lower_bound
    rxn.upper_bound = 500 if rxn.upper_bound == 1000 else rxn.upper_bound
tmodel.objective = tmodel.reactions.biomass_EX
[15]:
tmodel.update_thermo_info(fit_unknown_dfG0=False,search=True)

Identifying compounds...
[████████████████████████████████████████] 1244/1244 Mg_n

Estimating dfG0'...
ficytc_m 0 kilojoule / mole
focytc_m 0 kilojoule / mole
biomass_c 0 kilojoule / mole
biomass_e 0 kilojoule / mole
[████████████████████████████████████████] 1244/1244 Mg_n

Estimating drG0'...
[████████████████████████████████████████] 1587/1587 charge_cn

[16]:
for met in tmodel.metabolites:
    if met.ignore_conc:
        print(met.id,met.ignore_conc)
h2o_c True
h2o_e True
h2o_g True
h2o_m True
h2o_n True
h2o_r True
h2o_v True
h2o_x True
h_c True
h_e True
h_g True
h_m True
h_n True
h_r True
h_v True
h_x True
oh1_c True
oh1_m True
biomass_c True
biomass_e True
charge_c True
charge_e True
charge_m True
charge_x True
charge_r True
charge_v True
charge_g True
charge_n True
[17]:
for rxn in tmodel.reactions:
    if rxn.ignore_snd:
        print(rxn.id,rxn.ignore_snd)
EX_epistest_SC_e True
EX_epist_e True
EX_ergst_e True
EX_ergstest_SC_e True
EX_13BDglcn_e True
EX_etha_e True
EX_2hb_e True
etoh_EX True
EX_fe2_e True
EX_fecost_e True
EX_2mbac_e True
EX_2mbald_e True
EX_2mbtoh_e True
EX_2mppal_e True
EX_2phetoh_e True
EX_fecostest_SC_e True
EX_fmn_e True
EX_3c3hmp_e True
EX_3mbald_e True
EX_for_e True
EX_fru_e True
EX_3mop_e True
EX_4abut_e True
EX_fum_e True
EX_g3pc_e True
EX_4abz_e True
EX_5aop_e True
EX_g3pi_e True
EX_gal_e True
EX_8aonn_e True
EX_Nbfortyr_e True
EX_abt_e True
EX_ac_e True
EX_acald_e True
EX_aces_e True
EX_galur_e True
EX_gam6p_e True
EX_gcald_e True
EX_glc__D_e True
EX_gln__L_e True
EX_glu__L_e True
EX_glx_e True
EX_ade_e True
EX_adn_e True
EX_akg_e True
EX_ala__L_e True
EX_gly_e True
EX_glyc_e True
EX_gsn_e True
EX_gthox_e True
EX_gthrd_e True
EX_alltn_e True
EX_alltt_e True
EX_amet_e True
EX_arab__D_e True
EX_arab__L_e True
EX_gua_e True
EX_arg__L_e True
EX_h2o_e True
EX_h_e True
EX_hdca_e True
EX_hdcea_e True
EX_asn__L_e True
EX_asp__L_e True
EX_btd_RR_e True
EX_btn_e True
EX_hexc_e True
EX_his__L_e True
EX_hxan_e True
EX_iamac_e True
EX_iamoh_e True
EX_ibutac_e True
EX_chol_e True
EX_cit_e True
co2_EX True
EX_crn_e True
EX_csn_e True
EX_cys__L_e True
EX_cytd_e True
EX_dad_2_e True
EX_dann_e True
EX_ibutoh_e True
EX_id3acald_e True
EX_ile__L_e True
EX_ind3eth_e True
EX_inost_e True
EX_dca_e True
EX_dcyt_e True
EX_ddca_e True
EX_ins_e True
EX_k_e True
EX_dgsn_e True
EX_din_e True
EX_lac__D_e True
EX_lac__L_e True
EX_lanost_e True
EX_lanostest_SC_e True
EX_dttp_e True
EX_duri_e True
EX_leu__L_e True
EX_lys__L_e True
EX_mal__L_e True
EX_malt_e True
EX_ribflv_e True
EX_sbt__D_e True
EX_man_e True
EX_melib_e True
EX_met__L_e True
EX_mmet_e True
EX_na1_e True
EX_nac_e True
EX_sbt__L_e True
EX_ser__L_e True
EX_so3_e True
EX_so4_e True
EX_spmd_e True
EX_sprm_e True
EX_srb__L_e True
EX_succ_e True
EX_nadp_e True
EX_nh4_e True
EX_nmn_e True
EX_sucr_e True
o2_EX True
EX_taur_e True
EX_thm_e True
EX_oaa_e True
EX_ocdca_e True
EX_ocdcea_e True
EX_ocdcya_e True
EX_orn_e True
EX_thmmp_e True
EX_thmpp_e True
EX_thr__L_e True
EX_thym_e True
EX_pacald_e True
EX_pap_e True
EX_thymd_e True
EX_tre_e True
EX_trp__L_e True
EX_pc_SC_e True
EX_pectin_e True
EX_pepd_e True
EX_phe__L_e True
EX_pheac_e True
EX_pi_e True
EX_pnto__R_e True
EX_ttdca_e True
EX_tyr__L_e True
EX_ura_e True
EX_urea_e True
EX_uri_e True
EX_val__L_e True
EX_xan_e True
EX_xtsn_e True
EX_pro__L_e True
EX_ptd1ino_SC_e True
EX_ptrc_e True
EX_pyr_e True
EX_xyl__D_e True
EX_xylt_e True
EX_rib__D_e True
EX_zymst_e True
EX_zymstest_SC_e True
H2Ot True
H2Oter True
H2Otm True
H2Otn True
H2Otp True
H2Otv True
biomass_c True
biomass_ce True
biomass_EX True
EX_charge True
[18]:
#ignore snd for charge ?
for rxn in tmodel.reactions:
    if 'charge' in rxn.id and 'EX' not in rxn.id:#charge reactions are constrained by 2nd law
        print(rxn.id,rxn.ignore_snd)
        rxn.ignore_snd=True
        print(rxn.id,rxn.ignore_snd)


#tmodel.reactions.EX_charge.ignore_snd=True
charge_ce False
charge_ce True
charge_cm False
charge_cm True
charge_cx False
charge_cx True
charge_cr False
charge_cr True
charge_cv False
charge_cv True
charge_cg False
charge_cg True
charge_cn False
charge_cn True

Step 7 - Establishment of the thermodynamic-stoichiometric solution space

Note : In our curation of the iMM904 model, due to the limited information available for the membrane potential of organelles, we chose to allow the free leakage of ions (charges) across the membrane. Allowing free movement of charges between compartments prevents over-constraining the model to potentially incorrect information. For more detailed analysis of charge transport or models where more information is available on membrane physiology then a constraint on the second law could be added for these charge transport reactions.

[25]:
# add the TFBA variables to the model
tmodel.m = None #reset the gurobi model object in case you're re-running this cell
tmodel.add_TFBA_variables(gdiss_constraint = False, sigmac_limit = (5000/tmodel.T.m), error_type = 'covariance',qnorm=1,alpha=0.95)
tmodel.m.update()
Set parameter NonConvex to value 2
Set parameter TimeLimit to value 10
[26]:
thermo_flux.solver.gurobi.model_start(tmodel,'fixed_dir_firstTFBA_ENS_8.sol', ignore_vars=['all'],fix_vars=['qm','fluxes'],fix='start')

[27]:
tmodel.m.params.NonConvex=2
tmodel.m.params.IntFeasTol=1e-6
tmodel.m.params.OptimalityTol=1e-6
tmodel.m.params.TimeLimit=60*1
tmodel.m.optimize()

Set parameter IntFeasTol to value 1e-06
Set parameter TimeLimit to value 60
Gurobi Optimizer version 11.0.0 build v11.0.0rc2 (win64 - Windows 11+.0 (22631.2))

CPU model: Intel(R) Core(TM) i5-8250U CPU @ 1.60GHz, instruction set [SSE2|AVX|AVX2]
Thread count: 4 physical cores, 8 logical processors, using up to 8 threads

Optimize a model with 15685 rows, 12850 columns and 350518 nonzeros
Model fingerprint: 0x1b1ad44f
Model has 1 general constraint
Variable types: 11263 continuous, 1587 integer (1587 binary)
Coefficient statistics:
  Matrix range     [6e-06, 1e+06]
  Objective range  [1e+00, 1e+00]
  Bounds range     [1e+00, 1e+06]
  RHS range        [1e-05, 1e+06]

Warning: Completing partial solution with 1587 unfixed non-continuous variables out of 1587
User MIP start produced solution with objective 2.14592 (0.30s)
Loaded user MIP start with objective 2.14592

Presolve removed 8574 rows and 6443 columns
Presolve time: 1.61s
Presolved: 7111 rows, 6407 columns, 277793 nonzeros
Variable types: 5169 continuous, 1238 integer (1238 binary)

Root simplex log...

Iteration    Objective       Primal Inf.    Dual Inf.      Time
    6208    1.2993797e+00   3.446101e+05   0.000000e+00      5s
   12785    2.1805119e+00   0.000000e+00   0.000000e+00      8s

Root relaxation: objective 2.180512e+00, 12785 iterations, 5.59 seconds (7.85 work units)
Total elapsed time = 31.75s (DegenMoves)

    Nodes    |    Current Node    |     Objective Bounds      |     Work
 Expl Unexpl |  Obj  Depth IntInf | Incumbent    BestBd   Gap | It/Node Time

     0     0    2.18051    0   43    2.14592    2.18051  1.61%     -   32s
     0     0    2.18051    0   47    2.14592    2.18051  1.61%     -   32s
     0     0    2.18051    0   46    2.14592    2.18051  1.61%     -   33s
     0     0    2.18051    0   55    2.14592    2.18051  1.61%     -   36s
     0     0    2.18051    0   49    2.14592    2.18051  1.61%     -   39s
     0     0    2.18051    0   19    2.14592    2.18051  1.61%     -   44s
     0     0    2.18051    0   18    2.14592    2.18051  1.61%     -   45s
     0     0    2.18051    0   14    2.14592    2.18051  1.61%     -   48s
     0     0    2.18051    0   14    2.14592    2.18051  1.61%     -   49s
     0     0    2.18051    0   19    2.14592    2.18051  1.61%     -   52s
     0     0    2.18043    0   20    2.14592    2.18043  1.61%     -   53s
     0     0    2.18043    0   19    2.14592    2.18043  1.61%     -   53s
     0     0    2.18043    0   14    2.14592    2.18043  1.61%     -   55s
     0     0    2.18042    0   10    2.14592    2.18042  1.61%     -   56s
     0     0    2.18039    0   11    2.14592    2.18039  1.61%     -   57s
     0     0    2.18039    0    9    2.14592    2.18039  1.61%     -   58s
     0     0    2.18039    0    9    2.14592    2.18039  1.61%     -   58s
     0     0    2.18038    0    9    2.14592    2.18038  1.61%     -   58s

Cutting planes:
  Learned: 60
  Cover: 3
  Implied bound: 10
  MIR: 13
  Flow cover: 33
  Relax-and-lift: 4

Explored 1 nodes (49500 simplex iterations) in 60.25 seconds (53.86 work units)
Thread count was 8 (of 8 available processors)

Solution count 1: 2.14592

Time limit reached
Best objective 2.145918254336e+00, best bound 2.180382715743e+00, gap 1.6060%
[28]:
tmodel.m.write('fixed_dir_firstTFBA_ENS_9.mps')
tmodel.m.write('fixed_dir_firstTFBA_ENS_9.sol')
[45]:
thermo_flux.solver.gurobi.gdiss_model(tmodel)
drG error term
Ex: 143.54648665524567
Int: -143.54620102128138
Diff: 0.00028563396429603927


RTlnC concentration term
Ex: -257.1979974465446
Int: 257.19799744655256
Diff: 7.958078640513122e-12


drG0' term
Ex: 28160.88767254069
Int: -28160.88767254047
Diff: 2.219167072325945e-10


drG0 term non-transformed
Ex: 27952.820538559
Int: -27952.820538558746
Diff: 2.546585164964199e-10


drG0' transform term
Ex: 0.0
Int: 0.0
Diff: 0.0


drG0' total transport term
Ex: 0.0
Int: -0.0007508681337640155
Diff: -0.0007508681337640155


drG0' charge transport term
Ex: 0.0
Int: 7.466136594302952e-05
Diff: 7.466136594302952e-05


drG0'proton transport term
Ex: 0.0
Int: -0.0008255294998775753
Diff: -0.0008255294998775753
[29]:
# add the TFBA variables to the model
tmodel.m = None #reset the gurobi model object in case you're re-running this cell
tmodel.add_TFBA_variables(gdiss_constraint = True, sigmac_limit = (3700/tmodel.T.m), error_type = 'covariance',qnorm=1,alpha=0.95)
tmodel.m.update()
tmodel.m.params.timelimit=60*5

GUR_range = np.linspace(-0, -23, 50)

thermo_flux.solver.gurobi.variable_scan(tmodel, GUR_range, var = tmodel.mvars['v'][0][tmodel.reactions.index(tmodel.reactions.get_by_id('EX_glc__D_e'))]) # here we get the index for the glucose uptake reaction and use this to index the v variable in the gurobi model
thermo_flux.solver.gurobi.model_start(tmodel,'fixed_dir_firstTFBA_ENS_9.sol',ignore_vars=['all'],fix_vars=['qm'],fix='bound') #fix the drG error term to that from the initial TFBA to speed up optimisations

tmodel.m.optimize()

fluxes_gdiss = thermo_flux.solver.gurobi.multi_scenario_sol(tmodel,var = 'v')
fluxes_gdiss= pd.DataFrame(fluxes_gdiss[0].T, index = [rxn.id for rxn in tmodel.reactions])
Set parameter NonConvex to value 2
Set parameter TimeLimit to value 10
Set parameter TimeLimit to value 300
Gurobi Optimizer version 11.0.0 build v11.0.0rc2 (win64 - Windows 11+.0 (22631.2))

CPU model: Intel(R) Core(TM) i5-8250U CPU @ 1.60GHz, instruction set [SSE2|AVX|AVX2]
Thread count: 4 physical cores, 8 logical processors, using up to 8 threads

Optimize a model with 15686 rows, 13017 columns and 350685 nonzeros
Model fingerprint: 0x659902df
Model has 166 quadratic constraints
Model has 1 general constraint
Variable types: 11430 continuous, 1587 integer (1587 binary)
Coefficient statistics:
  Matrix range     [6e-06, 1e+06]
  QMatrix range    [1e+00, 1e+00]
  QLMatrix range   [1e+00, 1e+00]
  Objective range  [1e+00, 1e+00]
  Bounds range     [5e-05, 1e+06]
  RHS range        [1e-05, 1e+06]

Solving a multi-scenario model with 50 scenarios...

Presolve removed 11125 rows and 8819 columns
Presolve time: 0.28s
Presolved: 4888 rows, 4248 columns, 15024 nonzeros
Presolved model has 81 bilinear constraint(s)
Presolved model has 50 scenario(s)

Solving non-convex MIQCP

Variable types: 3286 continuous, 962 integer (962 binary)

Root relaxation: objective 1.202063e+00, 2935 iterations, 0.20 seconds (0.10 work units)

    Nodes    |    Current Node    |     Objective Bounds      |     Work
 Expl Unexpl |  Obj  Depth IntInf | Incumbent    BestBd   Gap | It/Node Time

     0     0    1.20206    0   50          -    1.20206      -     -    0s
     0     0    1.20206    0   54          -    1.20206      -     -    1s
     0     0    1.20206    0   54          -    1.20206      -     -    1s
     0     0    1.19820    0   30          -    1.19820      -     -    1s
     0     0    1.19820    0   30          -    1.19820      -     -    1s
     0     0    1.19820    0   31          -    1.19820      -     -    1s
     0     0    1.19820    0   32          -    1.19820      -     -    1s
     0     0    1.19820    0   30          -    1.19820      -     -    1s
     0     0    1.19820    0   30          -    1.19820      -     -    1s
     0     0    1.19820    0   27          -    1.19820      -     -    2s
     0     0    1.19820    0   28          -    1.19820      -     -    2s
     0     0    1.19820    0   27          -    1.19820      -     -    2s
     0     0    1.02710    0   27          -    1.02710      -     -    3s
H    0     0                      -0.0000000    1.02710      -     -    3s
     0     2    1.02710    0   27   -0.00000    1.02710      -     -    4s
    38    45   -0.00000   11    8   -0.00000    1.02700      -  23.6    5s
H  839   783                       0.0000000    1.02570      -  12.8   14s
H  840   783                       0.0000000    1.02570      -  12.8   14s
H  849   811                       0.1766679    1.02570   481%  12.9   14s
H  867   811                       0.3850917    1.02570   166%  12.9   14s
   871   853    0.51994   72   11    0.38509    1.02570   166%  13.0   16s
H 1131  1054                       0.3914907    1.02570   162%  13.1   18s
  1135   421    0.39149   31   35    0.39149    1.02570   162%  13.1   20s
  1145   172    0.40662  149   42    0.39149    0.40886  4.44%  17.3   26s
  1152    72    0.39149   66   40    0.39149    0.40886  4.44%  22.1   30s
Scenario 3 has been solved (gap 0.0020%). 48/50 scenarios left.
  1154    49   -0.00000   38   25    0.39149    0.40886  4.44%  27.2   38s
  1322   169   -0.00000   62   19    0.39149    0.40885  4.43%  25.9   40s
  2838  1705    0.25606  150   32    0.39149    0.40690  3.94%  19.7   45s
H 3486  2294                       0.4003544    0.40690  1.64%  18.6   47s
H 3731  2466                       0.4018819    0.40689  1.25%  18.6   48s
H 3835  2560                       0.4018852    0.40687  1.24%  18.6   48s
H 3954  2693                       0.4018853    0.40687  1.24%  18.4   49s
  4185  3072    0.39547  100   40    0.40189    0.40687  1.24%  17.9   50s
H 5709  4341                       0.4019121    0.40682  1.22%  16.5   54s
  5795  4614    0.38552  108   54    0.40191    0.40682  1.22%  16.3   55s
  7998  6722    0.03982  195   20    0.40191    0.40678  1.21%  15.4   60s
  9842  5587    0.40261  122   12    0.40191    0.40675  1.20%  16.7   65s
*10580  5888             290       0.4019171    0.40675  1.20%  16.4   66s
 12029  7125    0.27138  116   38    0.40192    0.40666  1.18%  16.3   70s
H12935  7608                       0.4022758    0.40657  1.07%  16.8   72s
 14248  8911 infeasible  200         0.40228    0.40652  1.06%  16.8   76s
H15435  9540                       0.4022806    0.40617  0.97%  17.1   80s
H15452  9540                       0.4024703    0.40617  0.92%  17.1   80s
 15506  9577    0.39308  118   29    0.40247    0.40617  0.92%  17.1   85s
Scenario 1 has been solved. 47/50 scenarios left.
Scenario 2 has been solved. 46/50 scenarios left.
Scenario 5 has been solved. 45/50 scenarios left.
Scenario 6 has been solved. 44/50 scenarios left.
 15548  9579    0.39308  119   29    0.40247    0.40617  0.92%  17.1  120s
Scenario 4 has been solved. 43/50 scenarios left.
Scenario 7 has been solved (gap 0.0018%). 42/50 scenarios left.
Scenario 8 has been solved (gap 0.0069%). 41/50 scenarios left.
Scenario 9 has been solved. 40/50 scenarios left.
 16277 10992    0.38948   98   35    0.40247    0.40607  0.89%  17.0  127s
H17122 11379                       0.4024815    0.40567  0.79%  16.6  129s
 17580 11996    0.37760  141   24    0.40248    0.40557  0.77%  16.7  132s
 18324 12778    0.38603  109   43    0.40248    0.40442  0.48%  16.7  135s
 20342 14646    0.35600  143   41    0.40248    0.40355  0.27%  16.3  141s
H21366 14798                       0.4026757    0.40350  0.21%  16.3  143s
H21371 14798                       0.4026888    0.40350  0.20%  16.4  143s
 21587 15652    0.40350   80   36    0.40269    0.40350  0.20%  16.4  146s
 23746 17593    0.35200  129   34    0.40269    0.40350  0.20%  16.3  152s
*24067 17593             171       0.4027681    0.40350  0.18%  16.2  152s
*24759 17593             165       0.4028130    0.40350  0.17%  16.1  152s
 24904 18017    0.36228  164   43    0.40281    0.40350  0.17%  16.1  155s
 26619 19080    0.39076  120   49    0.40281    0.40350  0.17%  16.2  160s
H26693 19080                       0.4029112    0.40350  0.14%  16.2  160s
 27890 20560     cutoff  105         0.40291    0.40345  0.13%  16.3  166s
 28939 21573    0.39425  150   30    0.40291    0.40345  0.13%  16.0  170s
 31013 23127    0.39472  195    4    0.40291    0.40345  0.13%  15.7  175s
 32841 24688    0.40336   86   35    0.40291    0.40341  0.12%  15.6  182s
 33978 25996    0.39189  185   23    0.40291    0.40341  0.12%  15.6  186s
 35466 26987 infeasible  105         0.40291    0.40340  0.12%  15.3  190s
 37764 28344    0.38396  168   25    0.40291    0.40340  0.12%  15.3  195s
 39656 29782 infeasible   95         0.40291    0.40338  0.12%  15.5  202s
 40905 30530    0.40155   87   52    0.40291    0.40338  0.12%  15.5  207s
 41838 30924    0.38574  151   28    0.40291    0.40338  0.12%  15.4  210s
 43650 32430    0.39840  118   40    0.40291    0.40337  0.11%  15.7  216s
 44490 32417    0.40259  133   34    0.40291    0.40337  0.11%  15.7  224s
Scenario 12 has been solved (gap 0.0079%). 39/50 scenarios left.
 44498 33271    0.40259  134   33    0.40291    0.40336  0.11%  15.7  227s
 45548 33460 infeasible  160         0.40291    0.40335  0.11%  15.7  230s
 47272 33928     cutoff  113         0.40291    0.40333  0.10%  16.2  236s
 48281 33922    0.40323  146   39    0.40291    0.40333  0.10%  16.5  241s
 49414 34994    0.40329  139   32    0.40291    0.40332  0.10%  16.6  247s
H49845 34994                       0.4029574    0.40332  0.09%  16.6  247s
 50165 35613    0.39175  104   40    0.40296    0.40332  0.09%  16.6  250s
 52053 36182    0.40326  105   36    0.40296    0.40328  0.08%  16.8  268s
Scenario 11 has been solved (gap 0.0056%). 38/50 scenarios left.
 52092 36781    0.40326  106   37    0.40296    0.40328  0.08%  16.8  272s
 52989 37366     cutoff  113         0.40296    0.40327  0.08%  16.8  276s
 53864 38129    0.39532  203   22    0.40296    0.40324  0.07%  16.8  284s
 55063 38537    0.40320  150   41    0.40296    0.40323  0.07%  16.8  291s
 56072 39425 infeasible  110         0.40296    0.40321  0.06%  16.9  298s

Cutting planes:
  Learned: 18
  Flow cover: 1
  RLT: 1

Explored 57432 nodes (975891 simplex iterations) in 300.49 seconds (56.02 work units)
Thread count was 8 (of 8 available processors)

Solution count 10: 0.402957 0.402957 0.402957 ... 0.402911

Time limit reached
Best objective 4.029573999817e-01, best bound 4.032086474970e-01, gap 0.0624%
[25]:
#plot biomass yield for stoichiometric model with balanced reactions and pH specified
df_data2 = pd.read_csv('plotting_Yeast_ALL_experimental_data.csv',index_col=0)
df_data2['regression'].fillna('Not fitted',inplace=True)
df_data2['regression'].replace('yes', 'Fitted', inplace=True)
df_data2.rename(columns={'regression':'Exp. data'}, inplace=True)
#rename index in Condition
df_data2.index.rename('Condition', inplace=True)

GUR_plot_range=[-x for x in GUR_range]
#plot biomass vs GUR in a scatter plot

sns.scatterplot(x=GUR_plot_range,y=fluxes_gdiss.loc['biomass_EX'],color='b',label='TFBA')
# sns.scatterplot(x=GUR_plot_range,y=tbiomass_flux,color='b',label='FBA and balanced')
# sns.scatterplot(x=GUR_plot_range,y=biomass_flux,color='g',label='FBA')
sns.scatterplot(x=df_data2['glc-D_EX'], y=df_data2['biomass_EX'], color=['red'],label='Experimental data')
plt.xlabel('Glucose uptake rate (mmol/(gDW.h))',fontsize=14)
plt.ylabel('Biomass production rate (mmol/(gDW.h))',fontsize=14)

plt.savefig('Figures/growth_all_TFBA_gdissconstraint.png',dpi=300)


_images/thermoflux_iMM904_ENS_34_0.png