diff --git a/README.md b/README.md index 1dbe7733..7d138bf3 100644 --- a/README.md +++ b/README.md @@ -218,6 +218,27 @@ max_biomass_objective = fva_output[3] The output of FVA method is tuple that contains `numpy` arrays. The vectors `min_fluxes` and `max_fluxes` contains the minimum and the maximum values of each flux. The vector `max_biomass_flux_vector` is the optimal flux vector according to the biomass objective function and `max_biomass_objective` is the value of that optimal solution. +```python +fva_output_df = model.fva_to_df() +``` + +returns the solution is an easier to query dataframe with the model's reactions as indices: +```python +>>> fva_output_df + minimum maximum +PFK 7.477306 7.477485 +PFL 0.000000 0.000066 +PGI 4.860632 4.861110 +PGK -16.023610 -16.023451 +PGL 4.959736 4.960214 +>>> +>>> df.loc["NH4t"] +minimum 4.765316 +maximum 4.765324 +Name: NH4t, dtype: float64 +``` + + To apply FBA method, ```python @@ -229,6 +250,16 @@ max_biomass_objective = fba_output[1] while the output vectors are the same with the previous example. +Again, one may use the `fba_to_df()` to get the solution as a pandas dataframe with model's reactions as indices. +```python +>>> model.fba_to_df() + fluxes +PFK 7.477382 +PFL 0.000000 +PGI 4.860861 +PGK -16.023526 +PGL 4.959985 +``` ### Set the restriction in the flux space @@ -266,6 +297,46 @@ sampler = polytope_sampler(model) steady_states = sampler.generate_steady_states() ``` +### Change the medium on your model + +To do that, you need to first describe the new medium and then assign it on your `model`. +For example: + +```python +initial_medium = model.medium +model.medium +# {'EX_co2_e': 1000.0, 'EX_glc__D_e': 10.0, 'EX_h_e': 1000.0, 'EX_h2o_e': 1000.0, 'EX_nh4_e': 1000.0, 'EX_o2_e': 1000.0, 'EX_pi_e': 1000.0} +model.fba()[-1] +# 0.8739215069684305 +new_medium = initial_medium.copy() + +# Set anoxygenic conditions +new_medium['EX_o2_e'] = 0 +model.medium = new_medium +model.fba()[-1] + +# Check the difference in the optimal value +# 0.21166294973531055 +``` + +### Who-is-who + +Models may use ids for metabolites and reactions hard to interpret. +You may use the `reactions_map` and the `metabolites_map` that return the reactions/metabolites ids along with their corresponding names. +For example: + +```python +>>> model.reactions_map + reaction_name +PFK Phosphofructokinase +PFL Pyruvate formate lyase +PGI Glucose-6-phosphate isomerase +PGK Phosphoglycerate kinase +PGL 6-phosphogluconolactonase +``` + + + ### Plot flux marginals @@ -282,8 +353,8 @@ steady_states = sampler.generate_steady_states(ess = 3000) # plot the histogram for the 14th reaction in e-coli (ACONTa) reactions = model.reactions plot_histogram( - steady_states[13], - reactions[13], + steady_states.loc["ACONTa"], + "ACONTa", n_bins = 60, ) ``` @@ -306,8 +377,8 @@ steady_states = sampler.generate_steady_states(ess = 3000) # plot the copula between the 13th (PPC) and the 14th (ACONTa) reaction in e-coli reactions = model.reactions -data_flux2=[steady_states[12],reactions[12]] -data_flux1=[steady_states[13],reactions[13]] +data_flux2=[steady_states.loc["ACONTa"], "ACONTa"] +data_flux1=[steady_states.loc["PPC"], "PPC"] plot_copula(data_flux1, data_flux2, n=10) ``` diff --git a/dingo/MetabolicNetwork.py b/dingo/MetabolicNetwork.py index eec1a8b5..37d2ef6a 100644 --- a/dingo/MetabolicNetwork.py +++ b/dingo/MetabolicNetwork.py @@ -7,12 +7,15 @@ # Licensed under GNU LGPL.3, see LICENCE file import numpy as np +import pandas as pd import sys from typing import Dict import cobra from dingo.loading_models import read_json_file, read_mat_file, read_sbml_file, parse_cobra_model from dingo.fva import slow_fva from dingo.fba import slow_fba +import logging +logger = logging.getLogger(__name__) try: import gurobipy @@ -33,10 +36,12 @@ def __init__(self, tuple_args): import gurobipy self._parameters["fast_computations"] = True + self._parameters["tol"] = 1e-06 except ImportError as e: self._parameters["fast_computations"] = False + self._parameters["tol"] = 1e-03 - if len(tuple_args) != 10: + if len(tuple_args) != 12: raise Exception( "An unknown input format given to initialize a metabolic network object." ) @@ -51,6 +56,8 @@ def __init__(self, tuple_args): self._medium = tuple_args[7] self._medium_indices = tuple_args[8] self._exchanges = tuple_args[9] + self._reactions_map = tuple_args[10] + self._metabolites_map = tuple_args[11] try: if self._biomass_index is not None and ( @@ -104,11 +111,11 @@ def from_cobra_model(cls, arg): return cls(parse_cobra_model(arg)) - def fva(self): + def _fva(self): """A member function to apply the FVA method on the metabolic network.""" if self._parameters["fast_computations"]: - return fast_fva( + min_fluxes, max_fluxes, max_biomass_flux_vector, max_biomass_objective = fast_fva( self._lb, self._ub, self._S, @@ -116,22 +123,43 @@ def fva(self): self._parameters["opt_percentage"], ) else: - return slow_fva( + min_fluxes, max_fluxes, max_biomass_flux_vector, max_biomass_objective = slow_fva( self._lb, self._ub, self._S, self._biomass_function, self._parameters["opt_percentage"], ) + self._min_fluxes = min_fluxes + self._max_fluxes = max_fluxes + self._opt_vector = max_biomass_flux_vector + self._opt_value = max_biomass_objective + return min_fluxes, max_fluxes, max_biomass_flux_vector, max_biomass_objective - def fba(self): + def _fba(self): """A member function to apply the FBA method on the metabolic network.""" if self._parameters["fast_computations"]: - return fast_fba(self._lb, self._ub, self._S, self._biomass_function) + opt_vector, opt_value = fast_fba(self._lb, self._ub, self._S, self._biomass_function) else: - return slow_fba(self._lb, self._ub, self._S, self._biomass_function) + opt_vector, opt_value = slow_fba(self._lb, self._ub, self._S, self._biomass_function) + self._opt_vector = opt_vector + self._opt_value = opt_value + return opt_vector, opt_value + + def fba(self): + if not hasattr(self, '_opt_vector'): + self._fba() + fba_df = pd.DataFrame({'fluxes': self._opt_vector}, index=self._reactions) + return fba_df + + def fva(self): + if not hasattr(self, '_min_fluxes'): + self._fva() + fva_df = pd.DataFrame({'minimum': self._min_fluxes, 'maximum': self._max_fluxes}, index=self._reactions) + return fva_df + # Descriptors @property def lb(self): return self._lb @@ -172,6 +200,30 @@ def exchanges(self): def parameters(self): return self._parameters + @property + def reactions_map(self): + return self._reactions_map + + @property + def metabolites_map(self): + return self._metabolites_map + + @property + def opt_value(self): + return self._opt_value + + @property + def opt_vector(self): + return self._opt_vector + + @property + def min_fluxes(self): + return self._min_fluxes + + @property + def max_fluxes(self): + return self._max_fluxes + @property def get_as_tuple(self): return ( @@ -183,16 +235,11 @@ def get_as_tuple(self): self._biomass_index, self._biomass_function, self._medium, - self._inter_medium, - self._exchanges + self._exchanges, + self._reactions_map, + self._metabolites_map ) - def num_of_reactions(self): - return len(self._reactions) - - def num_of_metabolites(self): - return len(self._metabolites) - @lb.setter def lb(self, value): self._lb = value @@ -221,7 +268,6 @@ def biomass_index(self, value): def biomass_function(self, value): self._biomass_function = value - @medium.setter def medium(self, medium: Dict[str, float]) -> None: """Set the constraints on the model exchanges. @@ -275,17 +321,51 @@ def set_active_bound(reaction: str, reac_index: int, bound: float) -> None: # Turn off reactions not present in media for rxn_id in exchange_rxns - frozen_media_rxns: """ - is_export for us, needs to check on the S - order reactions to their lb and ub + is_export for us, needs to check on the S + order reactions to their lb and ub """ # is_export = rxn.reactants and not rxn.products reac_index = self._reactions.index(rxn_id) - products = np.any(self._S[:,reac_index] > 0) + products = np.any(self._S[:,reac_index] > 0) reactants_exist = np.any(self._S[:,reac_index] < 0) is_export = True if not products and reactants_exist else False set_active_bound( rxn_id, reac_index, min(0.0, -self._lb[reac_index] if is_export else self._ub[reac_index]) ) + self._medium = medium + self._opt_vector = None + + @reactions_map.setter + def reactions_map(self, value): + self._reactions_map = value + + @metabolites_map.setter + def metabolites_map(self, value): + self._metabolites_map = value + + @min_fluxes.setter + def min_fluxes(self, value): + self._min_fluxes = value + + @max_fluxes.setter + def max_fluxes(self, value): + self._max_fluxes = value + + @opt_value.setter + def opt_value(self, value): + self._opt_value = value + + @opt_vector.setter + def opt_vector(self, value): + self._opt_vector = value + + + # Attributes + def num_of_reactions(self): + return len(self._reactions) + + def num_of_metabolites(self): + return len(self._metabolites) def set_fast_mode(self): diff --git a/dingo/PolytopeSampler.py b/dingo/PolytopeSampler.py index 4fe9dca7..e3ba537a 100644 --- a/dingo/PolytopeSampler.py +++ b/dingo/PolytopeSampler.py @@ -6,6 +6,7 @@ # Licensed under GNU LGPL.3, see LICENCE file +import copy import numpy as np import warnings import math @@ -16,6 +17,7 @@ get_matrices_of_low_dim_polytope, get_matrices_of_full_dim_polytope, ) +import pandas as pd try: import gurobipy @@ -37,13 +39,13 @@ def __init__(self, metabol_net): if not isinstance(metabol_net, MetabolicNetwork): raise Exception("An unknown input object given for initialization.") - self._metabolic_network = metabol_net - self._A = [] - self._b = [] - self._N = [] - self._N_shift = [] - self._T = [] - self._T_shift = [] + self._metabolic_network = copy.deepcopy(metabol_net) + self._A = np.empty( shape=(0, 0) ) + self._b = np.empty( shape=(0, 0) ) + self._N = np.empty( shape=(0, 0) ) + self._N_shift = np.empty( shape=(0, 0) ) + self._T = np.empty( shape=(0, 0) ) + self._T_shift = np.empty( shape=(0, 0) ) self._parameters = {} self._parameters["nullspace_method"] = "sparseQR" self._parameters["opt_percentage"] = self.metabolic_network.parameters[ @@ -68,18 +70,18 @@ def get_polytope(self): """ if ( - self._A == [] - or self._b == [] - or self._N == [] - or self._N_shift == [] - or self._T == [] - or self._T_shift == [] + self._A.size == 0 + or self._b.size == 0 + or self._N.size == 0 + or self._N_shift.size == 0 + or self._T.size == 0 + or self._T_shift.size == 0 ): ( max_biomass_flux_vector, max_biomass_objective, - ) = self._metabolic_network.fba() + ) = self._metabolic_network._fba() if ( self._parameters["fast_computations"] @@ -106,7 +108,7 @@ def get_polytope(self): max_fluxes, max_biomass_flux_vector, max_biomass_objective, - ) = self._metabolic_network.fva() + ) = self._metabolic_network._fva() A, b, Aeq, beq = get_matrices_of_low_dim_polytope( self._metabolic_network.S, @@ -166,6 +168,7 @@ def generate_steady_states( self._A, self._b, Tr, Tr_shift, samples = P.fast_mmcs( ess, psrf, parallel_mmcs, num_threads ) + else: self._A, self._b, Tr, Tr_shift, samples = P.slow_mmcs( ess, psrf, parallel_mmcs, num_threads @@ -184,8 +187,10 @@ def generate_steady_states( self._T = np.dot(self._T, Tr) self._T_shift = np.add(self._T_shift, Tr_shift) - return steady_states - + steady_states_df = pd.DataFrame(steady_states, index = self._metabolic_network.reactions) + + return steady_states_df + def generate_steady_states_no_multiphase( self, method = 'billiard_walk', n=1000, burn_in=0, thinning=1, variance=1.0, bias_vector=None ): @@ -197,11 +202,11 @@ def generate_steady_states_no_multiphase( burn_in -- the number of points to burn before sampling thinning -- the walk length of the chain """ - + self.get_polytope() P = HPolytope(self._A, self._b) - + if bias_vector is None: bias_vector = np.ones(self._A.shape[1], dtype=np.float64) else: @@ -213,8 +218,9 @@ def generate_steady_states_no_multiphase( steady_states = map_samples_to_steady_states( samples_T, self._N, self._N_shift ) + steady_states_df = pd.DataFrame(steady_states, index = self._metabolic_network.reactions) - return steady_states + return steady_states_df @staticmethod def sample_from_polytope( @@ -245,7 +251,7 @@ def sample_from_polytope( ) return samples - + @staticmethod def sample_from_polytope_no_multiphase( A, b, method = 'billiard_walk', n=1000, burn_in=0, thinning=1, variance=1.0, bias_vector=None @@ -264,7 +270,7 @@ def sample_from_polytope_no_multiphase( bias_vector = np.ones(A.shape[1], dtype=np.float64) else: bias_vector = bias_vector.astype('float64') - + P = HPolytope(A, b) try: @@ -286,16 +292,13 @@ def round_polytope( A, b, Tr, Tr_shift, round_value = P.rounding(method, True) except ImportError as e: A, b, Tr, Tr_shift, round_value = P.rounding(method, False) - + return A, b, Tr, Tr_shift + @staticmethod def sample_from_fva_output( - min_fluxes, - max_fluxes, - biomass_function, - max_biomass_objective, - S, + model, opt_percentage=100, ess=1000, psrf=False, @@ -305,11 +308,7 @@ def sample_from_fva_output( """A static function to sample steady states when the output of FVA is given. Keyword arguments: - min_fluxes -- minimum values of the fluxes, i.e., a n-dimensional vector - max_fluxes -- maximum values for the fluxes, i.e., a n-dimensional vector - biomass_function -- the biomass objective function - max_biomass_objective -- the maximum value of the biomass objective function - S -- stoichiometric matrix + model -- a dingo.MetabolicNetwork() object opt_percentage -- consider solutions that give you at least a certain percentage of the optimal solution (default is to consider optimal solutions only) @@ -319,16 +318,18 @@ def sample_from_fva_output( num_threads -- the number of threads to use for parallel mmcs """ + min_fluxes, max_fluxes, opt_vector, opt_value = model._fva() + A, b, Aeq, beq = get_matrices_of_low_dim_polytope( - S, min_fluxes, max_fluxes, opt_percentage, tol + model.S, min_fluxes, max_fluxes, opt_percentage, model._parameters["tol"] ) - A = np.vstack((A, -biomass_function)) + A = np.vstack((A, -model.biomass_function)) b = np.append( b, -(opt_percentage / 100) - * self._parameters["tol"] - * math.floor(max_biomass_objective / self._parameters["tol"]), + * model._parameters["tol"] + * math.floor(opt_value / model._parameters["tol"]), ) A, b, N, N_shift = get_matrices_of_full_dim_polytope(A, b, Aeq, beq) @@ -350,6 +351,17 @@ def sample_from_fva_output( return steady_states + @staticmethod + def samples_as_df(model, samples): + """A static function to convert the samples numpy ndarray to a pandas DataFrame with model's reactions as indices + + Keyword arguments: + model -- + samples -- + """ + samples_df = pd.DataFrame(samples, index = model.reactions) + return samples_df + @property def A(self): return self._A @@ -411,3 +423,4 @@ def set_tol(self, value): def set_opt_percentage(self, value): self._parameters["opt_percentage"] = value + diff --git a/dingo/__init__.py b/dingo/__init__.py index 49f7d043..b2986eee 100644 --- a/dingo/__init__.py +++ b/dingo/__init__.py @@ -169,7 +169,7 @@ def dingo_main(): else: raise Exception("An unknown format file given.") - result_obj = model.fba() + result_obj = model._fba() with open("dingo_fba_" + name + ".pckl", "wb") as dingo_fba_file: pickle.dump(result_obj, dingo_fba_file) diff --git a/dingo/loading_models.py b/dingo/loading_models.py index 84efceb5..d1d714d9 100644 --- a/dingo/loading_models.py +++ b/dingo/loading_models.py @@ -9,6 +9,7 @@ import json import numpy as np import cobra +import pandas as pd def read_json_file(input_file): """A Python function to Read a Bigg json file and returns, @@ -23,7 +24,7 @@ def read_json_file(input_file): input_file -- a json file that contains the information about a mettabolic network, for example see http://bigg.ucsd.edu/models """ - try: + try: cobra.io.load_matlab_model( input_file ) except: cobra_config = cobra.Configuration() @@ -45,7 +46,7 @@ def read_mat_file(input_file): Keyword arguments: input_file -- a mat file that contains a MATLAB structure with the information about a mettabolic network, for example see http://bigg.ucsd.edu/models """ - try: + try: cobra.io.load_matlab_model( input_file ) except: cobra_config = cobra.Configuration() @@ -56,8 +57,8 @@ def read_mat_file(input_file): return (parse_cobra_model( model )) def read_sbml_file(input_file): - """A Python function, based on the cobra.io.read_sbml_model() function of cabrapy - and the extract_polytope() function of PolyRound + """A Python function, based on the cobra.io.read_sbml_model() function of cobrapy + and the extract_polytope() function of PolyRound (https://gitlab.com/csb.ethz/PolyRound/-/blob/master/PolyRound/static_classes/parse_sbml_stoichiometry.py) to read an SBML file (.xml) and return: (a) lower/upper flux bounds @@ -68,10 +69,10 @@ def read_sbml_file(input_file): (f) the objective function to maximize the biomass pseudoreaction Keyword arguments: - input_file -- a xml file that contains an SBML model with the information about a mettabolic network, for example see: + input_file -- a xml file that contains an SBML model with the information about a mettabolic network, for example see: https://github.com/VirtualMetabolicHuman/AGORA/blob/master/CurrentVersion/AGORA_1_03/AGORA_1_03_sbml/Abiotrophia_defectiva_ATCC_49176.xml """ - try: + try: cobra.io.read_sbml_model( input_file ) except: cobra_config = cobra.Configuration() @@ -137,9 +138,11 @@ def parse_cobra_model(cobra_model): exchanges.append(reac.id) - return lb, ub, S, metabolites, reactions, biomass_index, biomass_function, medium, inter_medium, exchanges - - - - + # Map ids to complete names for convenience + reactions_map = pd.DataFrame( [x.name for x in cobra_model.reactions], [x.id for x in cobra_model.reactions]) + reactions_map.columns = ["reaction_name"] + metabolites_map = pd.DataFrame( [x.name for x in cobra_model.metabolites], [x.id for x in cobra_model.metabolites]) + metabolites_map.columns = ["metabolite_name"] + return lb, ub, S, metabolites, reactions, biomass_index, biomass_function, \ + medium, inter_medium, exchanges, reactions_map, metabolites_map diff --git a/dingo/utils.py b/dingo/utils.py index 8074b155..546774e9 100644 --- a/dingo/utils.py +++ b/dingo/utils.py @@ -34,14 +34,14 @@ def compute_copula(flux1, flux2, n): rng = range((j*math.floor(N/n)),((j+1)*math.floor(N/n))) grouped_flux1[I1[rng]] = j grouped_flux2[I2[rng]] = j - + for i in range(n): for j in range(n): copula[i,j] = sum((grouped_flux1==i) *( grouped_flux2==j)) - + copula = copula / N return copula - + def apply_scaling(A, b, cs, rs): """A Python function to apply the scaling computed by the function `gmscale` to a convex polytope diff --git a/tests/fast_implementation_test.py b/tests/fast_implementation_test.py index a4b1995f..ec2992a8 100644 --- a/tests/fast_implementation_test.py +++ b/tests/fast_implementation_test.py @@ -12,6 +12,7 @@ from dingo.gurobi_based_implementations import fast_inner_ball class TestFastMethods(unittest.TestCase): + def test_fast_max_bal_computation(self): m = 2 @@ -26,6 +27,7 @@ def test_fast_max_bal_computation(self): self.assertTrue(abs(max_ball[1] - 1) < 1e-08) + def test_fast_fva(self): current_directory = os.getcwd() @@ -34,12 +36,18 @@ def test_fast_fva(self): model = MetabolicNetwork.from_json(input_file_json) model.set_fast_mode() - res = model.fva() + res = model._fva() self.assertTrue(abs(res[3] - 0.8739215069684305) < 1e-08) self.assertEqual(res[0].size, 95) self.assertEqual(res[1].size, 95) + fva_df = model.fva() + biomass_function = model.reactions[model.biomass_index] + self.assertTrue(fva_df.loc[biomass_function]["maximum"] - fva_df.loc[biomass_function]["minimum"] <= 1e-03) + self.assertEqual(fva_df.shape, (95,2)) + + def test_ecoli_to_full_dimensional_polytope(self): current_directory = os.getcwd() @@ -55,9 +63,9 @@ def test_ecoli_to_full_dimensional_polytope(self): self.assertEqual(sampler.A.shape[0], 26) self.assertEqual(sampler.A.shape[1], 24) - self.assertEqual(steady_states.shape[0], 95) + def test_fast_fba(self): current_directory = os.getcwd() @@ -65,11 +73,13 @@ def test_fast_fba(self): model = MetabolicNetwork.from_json(input_file_json) model.set_fast_mode() - - res = model.fba() - + res = model._fba() self.assertTrue(abs(res[1] - 0.8739215069684305) < 1e-08) + fba_df = model.fba() + biomass_function = model.reactions[model.biomass_index] + self.assertTrue(abs(fba_df.loc[biomass_function]["fluxes"] - 0.8739215069684305) < 1e-08) + if __name__ == "__main__": unittest.main() diff --git a/tests/fba.py b/tests/fba.py index 94dc5b6f..1f517f66 100644 --- a/tests/fba.py +++ b/tests/fba.py @@ -16,39 +16,51 @@ def test_fba_json(self): input_file_json = os.getcwd() + "/ext_data/e_coli_core.json" model = MetabolicNetwork.from_json(input_file_json) model.set_slow_mode() - res = model.fba() - + res = model._fba() self.assertTrue(abs(res[1] - 0.8739215067486387) < 1e-03) + biomass_function = model.reactions[model.biomass_index] + fba_df = model.fba() + self.assertTrue(abs(fba_df.loc[biomass_function]["fluxes"] - 0.8739215067486387) < 1e-03) + def test_fba_mat(self): input_file_mat = os.getcwd() + "/ext_data/e_coli_core.mat" model = MetabolicNetwork.from_mat(input_file_mat) model.set_slow_mode() - - res = model.fba() - + res = model._fba() self.assertTrue(abs(res[1] - 0.8739215067486387) < 1e-03) + biomass_function = model.reactions[model.biomass_index] + fba_df = model.fba() + self.assertTrue(abs(fba_df.loc[biomass_function]["fluxes"] - 0.8739215067486387) < 1e-03) + def test_fba_sbml(self): input_file_sbml = os.getcwd() + "/ext_data/e_coli_core.xml" model = MetabolicNetwork.from_sbml(input_file_sbml) model.set_slow_mode() - - res = model.fba() - + res = model._fba() self.assertTrue(abs(res[1] - 0.8739215067486387) < 1e-03) + biomass_function = model.reactions[model.biomass_index] + fba_df = model.fba() + self.assertTrue(abs(fba_df.loc[biomass_function]["fluxes"] - 0.8739215067486387) < 1e-03) + def test_modify_medium(self): input_file_sbml = os.getcwd() + "/ext_data/e_coli_core.xml" model = MetabolicNetwork.from_sbml(input_file_sbml) - model.set_slow_mode() + + # Check if script is running in GitHub action + if os.getenv('CI', 'false').lower() == 'true': + model.set_slow_mode() initial_medium = model.medium - initial_fba = model.fba()[-1] + initial_fba = model._fba()[-1] + initial_fba_df = model.fba() + # Original indices of the exchange reactions e_coli_core_medium_compound_indices = { "EX_co2_e" : 46, "EX_glc__D_e" : 51, @@ -59,25 +71,35 @@ def test_modify_medium(self): "EX_pi_e" : 60 } - glc_index = model.reactions.index("EX_glc__D_e") - o2_index = model.reactions.index("EX_o2_e") - + # Edit and assign new medium to model new_media = initial_medium.copy() - new_media["EX_glc__D_e"] = 1.5 - new_media["EX_o2_e"] = -0.5 - + new_media["EX_glc__D_e"] = 35 + new_media["EX_o2_e"] = 0.5 model.medium = new_media + # Check if indices are affected updated_media = model.medium updated_medium_indices = {} for reac in updated_media: updated_medium_indices[reac] = model.reactions.index(reac) + glc_index = model.reactions.index("EX_glc__D_e") + o2_index = model.reactions.index("EX_o2_e") + self.assertTrue(updated_medium_indices == e_coli_core_medium_compound_indices) + self.assertTrue(model.lb[glc_index] == -35 and model.lb[o2_index] == -0.5) + + # Check if optimal value is affected + self.assertTrue( + abs((model._fba()[-1] - initial_fba) - 0.1172) <= 1e-03 + ) - self.assertTrue(model.lb[glc_index] == -1.5 and model.lb[o2_index] == 0.5) - self.assertTrue(initial_fba - model.fba()[-1] > 0) + biomass_function = model.reactions[model.biomass_index] + fba_df = model.fba() + self.assertTrue( + abs((fba_df.loc[biomass_function]["fluxes"] - initial_fba_df.loc[biomass_function]["fluxes"]) - 0.1172) <= 1e-03 + ) if __name__ == "__main__": diff --git a/tests/sampling.py b/tests/sampling.py index bf731715..39302a36 100644 --- a/tests/sampling.py +++ b/tests/sampling.py @@ -23,7 +23,7 @@ def test_sample_json(self): steady_states = sampler.generate_steady_states(ess = 20000, psrf = True) self.assertTrue( steady_states.shape[0] == 95 ) - self.assertTrue( abs( steady_states[12].mean() - 2.504 ) < 1e-02 ) + self.assertTrue( abs( steady_states.loc["PPC"].mean() - 2.504 ) < 1e-02 ) def test_sample_mat(self): @@ -32,10 +32,10 @@ def test_sample_mat(self): model = MetabolicNetwork.from_mat(input_file_mat) sampler = PolytopeSampler(model) - steady_states = sampler.generate_steady_states(ess = 20000, psrf = True) + steady_states = sampler.generate_steady_states(ess = 20000, psrf = True) self.assertTrue( steady_states.shape[0] == 95 ) - self.assertTrue( abs( steady_states[12].mean() - 2.504 ) < 1e-02 ) + self.assertTrue( abs( steady_states.loc["PPC"].mean() - 2.504 ) < 1e-02 ) def test_sample_sbml(self): @@ -47,7 +47,7 @@ def test_sample_sbml(self): steady_states = sampler.generate_steady_states(ess = 20000, psrf = True) self.assertTrue( steady_states.shape[0] == 95 ) - self.assertTrue( abs( steady_states[12].mean() - 2.504 ) < 1e-02 ) + self.assertTrue( abs( steady_states.loc["PPC"].mean() - 2.504 ) < 1e-02 ) diff --git a/tutorials/dingo_tutorial.ipynb b/tutorials/dingo_tutorial.ipynb index 1b1b7e5a..5c7799ee 100644 --- a/tutorials/dingo_tutorial.ipynb +++ b/tutorials/dingo_tutorial.ipynb @@ -689,7 +689,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 3, "metadata": { "colab": { "base_uri": "https://localhost:8080/" @@ -714,7 +714,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 2, "metadata": { "id": "FahzUpQFommK" }, @@ -735,13 +735,13 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 4, "metadata": { "id": "NNSJM_Z3McSs" }, "outputs": [], "source": [ - "model = MetabolicNetwork.from_json('ext_data/e_coli_core.json')" + "model = MetabolicNetwork.from_json('../ext_data/e_coli_core.json')" ] }, { @@ -1043,7 +1043,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 5, "metadata": { "id": "kWFbcJumShgh" }, @@ -1053,6 +1053,120 @@ "fba_output = model.fba()" ] }, + { + "cell_type": "code", + "execution_count": 9, + "metadata": {}, + "outputs": [], + "source": [ + "fba_output_df = model.fba_to_df()" + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
fluxes
PFK7.477382
PFL0.000000
PGI4.860861
PGK-16.023526
PGL4.959985
......
NADH1638.534610
NADTRHD0.000000
NH4t4.765319
O2t21.799493
PDH9.282533
\n", + "

95 rows × 1 columns

\n", + "
" + ], + "text/plain": [ + " fluxes\n", + "PFK 7.477382\n", + "PFL 0.000000\n", + "PGI 4.860861\n", + "PGK -16.023526\n", + "PGL 4.959985\n", + "... ...\n", + "NADH16 38.534610\n", + "NADTRHD 0.000000\n", + "NH4t 4.765319\n", + "O2t 21.799493\n", + "PDH 9.282533\n", + "\n", + "[95 rows x 1 columns]" + ] + }, + "execution_count": 8, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "fba_output_df" + ] + }, { "cell_type": "markdown", "metadata": { @@ -1101,7 +1215,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 11, "metadata": { "id": "fdLlAbhA0Ylg" }, @@ -1123,7 +1237,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 12, "metadata": { "id": "jrASE_SS2Nst" }, @@ -1252,9 +1366,146 @@ "In the previous step, we replaced the `biomass_function` of our model. So if we run FVA in our current model, then the first reaction of our model " ] }, + { + "cell_type": "code", + "execution_count": 28, + "metadata": {}, + "outputs": [], + "source": [ + "fva_output_df = model.fva_to_df()" + ] + }, + { + "cell_type": "code", + "execution_count": 29, + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
minimummaximum
PFK176.6099991.766100e+02
PFL0.0000006.666667e-07
PGI9.9999991.000000e+01
PGK-20.000000-2.000000e+01
PGL0.0000001.333333e-06
.........
NADH1699.9999991.000000e+02
NADTRHD19.9999992.000000e+01
NH4t0.0000001.777778e-07
O2t60.0000006.000000e+01
PDH19.9999992.000000e+01
\n", + "

95 rows × 2 columns

\n", + "
" + ], + "text/plain": [ + " minimum maximum\n", + "PFK 176.609999 1.766100e+02\n", + "PFL 0.000000 6.666667e-07\n", + "PGI 9.999999 1.000000e+01\n", + "PGK -20.000000 -2.000000e+01\n", + "PGL 0.000000 1.333333e-06\n", + "... ... ...\n", + "NADH16 99.999999 1.000000e+02\n", + "NADTRHD 19.999999 2.000000e+01\n", + "NH4t 0.000000 1.777778e-07\n", + "O2t 60.000000 6.000000e+01\n", + "PDH 19.999999 2.000000e+01\n", + "\n", + "[95 rows x 2 columns]" + ] + }, + "execution_count": 29, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "fva_output_df" + ] + }, { "cell_type": "code", "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Get min and max flux values\n", + "pfk_min_fluxes = fva_output_df.loc[\"PFK\"].minimum\n", + "pfk_max_fluxes = fva_output_df.loc[\"PFK\"].maximum" + ] + }, + { + "cell_type": "code", + "execution_count": 30, "metadata": { "id": "AK5OdFU4yZZL" }, @@ -1264,10 +1515,6 @@ "# Run FVA\n", "fva_output = model.fva()\n", "\n", - "# Get min and max flux values\n", - "pfk_min_fluxes = fva_output[0]\n", - "pfk_max_fluxes = fva_output[1]\n", - "\n", "# Get the max flux distribution and the max biomass value when the objective function is maximum\n", "pfk_max_biomass_flux_vector = fva_output[2]\n", "pfk_max_biomass_objective = fva_output[3]" @@ -1369,7 +1616,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 5, "metadata": { "id": "HHXSrEelXh4o" }, @@ -1415,6 +1662,390 @@ "steady_states = sampler.generate_steady_states(ess = 1000, psrf = True) # this took a little bit more than 5 minutes" ] }, + { + "cell_type": "code", + "execution_count": 7, + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
0123456789...4990499149924993499449954996499749984999
PFK7.4773827.4773807.4773817.4773857.4773837.4773837.4773877.477399e+007.4773897.477392e+00...7.4773767.4773847.477380e+007.477389e+007.4773887.4773927.4773887.4773847.477385e+007.477379
PFL0.0000020.0000020.0000020.0000030.0000020.0000050.0000099.758857e-070.0000014.354554e-06...0.0000030.0000011.003201e-072.492911e-060.0000040.0000040.0000040.0000014.600969e-070.000002
PGI4.8608484.8608384.8608424.8608584.8608584.8608574.8608594.860871e+004.8608684.860880e+00...4.8608394.8608614.860854e+004.860868e+004.8608724.8608894.8608764.8608654.860866e+004.860845
PGK-16.023522-16.023519-16.023521-16.023526-16.023526-16.023526-16.023527-1.602353e+01-16.023529-1.602353e+01...-16.023520-16.023527-1.602352e+01-1.602353e+01-16.023531-16.023536-16.023532-16.023528-1.602353e+01-16.023521
PGL4.9599984.9600084.9600044.9599884.9599884.9599894.9599874.959975e+004.9599784.959966e+00...4.9600074.9599854.959992e+004.959978e+004.9599744.9599574.9599704.9599814.959980e+004.960000
..................................................................
NADH1638.53462438.53463038.53462838.53462238.53462238.53461438.5346273.853460e+0138.5346113.853461e+01...38.53462338.5346183.853463e+013.853462e+0138.53462238.53461338.53461638.5346113.853461e+0138.534620
NADTRHD0.0000240.0000310.0000210.0000070.0000390.0000130.0000257.147399e-060.0000106.149728e-07...0.0000230.0000082.870932e-059.178589e-070.0000150.0000220.0000280.0000114.563802e-060.000014
NH4t4.7653174.7653174.7653174.7653174.7653174.7653174.7653174.765317e+004.7653184.765317e+00...4.7653174.7653174.765317e+004.765317e+004.7653174.7653174.7653174.7653174.765317e+004.765317
O2t21.79949821.79950021.79950021.79949921.79949921.79949421.7995042.179949e+0121.7994942.179950e+01...21.79949621.7994972.179950e+012.179950e+0121.79950221.79950021.79950021.7994942.179950e+0121.799495
PDH9.2825439.2825539.2825589.2825559.2825329.2825469.2825329.282541e+009.2825529.282542e+00...9.2825559.2825439.282538e+009.282542e+009.2825449.2825529.2825639.2825359.282538e+009.282569
\n", + "

95 rows × 5000 columns

\n", + "
" + ], + "text/plain": [ + " 0 1 2 3 4 5 \\\n", + "PFK 7.477382 7.477380 7.477381 7.477385 7.477383 7.477383 \n", + "PFL 0.000002 0.000002 0.000002 0.000003 0.000002 0.000005 \n", + "PGI 4.860848 4.860838 4.860842 4.860858 4.860858 4.860857 \n", + "PGK -16.023522 -16.023519 -16.023521 -16.023526 -16.023526 -16.023526 \n", + "PGL 4.959998 4.960008 4.960004 4.959988 4.959988 4.959989 \n", + "... ... ... ... ... ... ... \n", + "NADH16 38.534624 38.534630 38.534628 38.534622 38.534622 38.534614 \n", + "NADTRHD 0.000024 0.000031 0.000021 0.000007 0.000039 0.000013 \n", + "NH4t 4.765317 4.765317 4.765317 4.765317 4.765317 4.765317 \n", + "O2t 21.799498 21.799500 21.799500 21.799499 21.799499 21.799494 \n", + "PDH 9.282543 9.282553 9.282558 9.282555 9.282532 9.282546 \n", + "\n", + " 6 7 8 9 ... 4990 \\\n", + "PFK 7.477387 7.477399e+00 7.477389 7.477392e+00 ... 7.477376 \n", + "PFL 0.000009 9.758857e-07 0.000001 4.354554e-06 ... 0.000003 \n", + "PGI 4.860859 4.860871e+00 4.860868 4.860880e+00 ... 4.860839 \n", + "PGK -16.023527 -1.602353e+01 -16.023529 -1.602353e+01 ... -16.023520 \n", + "PGL 4.959987 4.959975e+00 4.959978 4.959966e+00 ... 4.960007 \n", + "... ... ... ... ... ... ... \n", + "NADH16 38.534627 3.853460e+01 38.534611 3.853461e+01 ... 38.534623 \n", + "NADTRHD 0.000025 7.147399e-06 0.000010 6.149728e-07 ... 0.000023 \n", + "NH4t 4.765317 4.765317e+00 4.765318 4.765317e+00 ... 4.765317 \n", + "O2t 21.799504 2.179949e+01 21.799494 2.179950e+01 ... 21.799496 \n", + "PDH 9.282532 9.282541e+00 9.282552 9.282542e+00 ... 9.282555 \n", + "\n", + " 4991 4992 4993 4994 4995 \\\n", + "PFK 7.477384 7.477380e+00 7.477389e+00 7.477388 7.477392 \n", + "PFL 0.000001 1.003201e-07 2.492911e-06 0.000004 0.000004 \n", + "PGI 4.860861 4.860854e+00 4.860868e+00 4.860872 4.860889 \n", + "PGK -16.023527 -1.602352e+01 -1.602353e+01 -16.023531 -16.023536 \n", + "PGL 4.959985 4.959992e+00 4.959978e+00 4.959974 4.959957 \n", + "... ... ... ... ... ... \n", + "NADH16 38.534618 3.853463e+01 3.853462e+01 38.534622 38.534613 \n", + "NADTRHD 0.000008 2.870932e-05 9.178589e-07 0.000015 0.000022 \n", + "NH4t 4.765317 4.765317e+00 4.765317e+00 4.765317 4.765317 \n", + "O2t 21.799497 2.179950e+01 2.179950e+01 21.799502 21.799500 \n", + "PDH 9.282543 9.282538e+00 9.282542e+00 9.282544 9.282552 \n", + "\n", + " 4996 4997 4998 4999 \n", + "PFK 7.477388 7.477384 7.477385e+00 7.477379 \n", + "PFL 0.000004 0.000001 4.600969e-07 0.000002 \n", + "PGI 4.860876 4.860865 4.860866e+00 4.860845 \n", + "PGK -16.023532 -16.023528 -1.602353e+01 -16.023521 \n", + "PGL 4.959970 4.959981 4.959980e+00 4.960000 \n", + "... ... ... ... ... \n", + "NADH16 38.534616 38.534611 3.853461e+01 38.534620 \n", + "NADTRHD 0.000028 0.000011 4.563802e-06 0.000014 \n", + "NH4t 4.765317 4.765317 4.765317e+00 4.765317 \n", + "O2t 21.799500 21.799494 2.179950e+01 21.799495 \n", + "PDH 9.282563 9.282535 9.282538e+00 9.282569 \n", + "\n", + "[95 rows x 5000 columns]" + ] + }, + "execution_count": 7, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "steady_states" + ] + }, { "cell_type": "markdown", "metadata": { @@ -1488,17 +2119,33 @@ }, { "cell_type": "code", - "execution_count": null, - "metadata": { - "colab": { - "base_uri": "https://localhost:8080/" - }, - "id": "-im2Cgq5b3oS", - "outputId": "f0f9948a-99ef-46de-d05a-e21e49164011" - }, - "outputs": [], - "source": [ - "model.reactions.index('PYK')" + "execution_count": 10, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "0 1.758176\n", + "1 1.758188\n", + "2 1.758201\n", + "3 1.758194\n", + "4 1.758174\n", + " ... \n", + "4995 1.758186\n", + "4996 1.758197\n", + "4997 1.758173\n", + "4998 1.758185\n", + "4999 1.758213\n", + "Name: PYK, Length: 5000, dtype: float64" + ] + }, + "execution_count": 10, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "steady_states.loc[\"PYK\"]" ] }, { @@ -1512,7 +2159,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 13, "metadata": { "colab": { "base_uri": "https://localhost:8080/", @@ -1521,14 +2168,25 @@ "id": "-n2HKr82yKyh", "outputId": "2d924fe9-975b-482c-9da7-016c002de61c" }, - "outputs": [], + "outputs": [ + { + "data": { + "image/png": "iVBORw0KGgoAAAANSUhEUgAAAm8AAAJ7CAYAAAC1cXYFAAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjguNCwgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy8fJSN1AAAACXBIWXMAAA9hAAAPYQGoP6dpAABxMElEQVR4nO3dd3gUVd/G8XvTQyB0Qg0gzYAU6QGpUgQeRcWCAgKiCIIFsKDSRUXsBeURpPgComIBBCkiVXoERMBINaK0UEJJISTn/QOzD8tuks1mQzLk+7muvXRnzsz+5mQJN1POsRljjAAAAGAJPrldAAAAANxHeAMAALAQwhsAAICFEN4AAAAshPAGAABgIYQ3AAAACyG8AQAAWAjhDQAAwEIIbwAAABZCeANwXWrdurVsNpv91adPn9wuCXnMoUOHHL4jNptNq1atyu2ygEwR3oA86urwceXL19dXhQoVUtWqVdW1a1d98sknSkxMzO2Sc9T27ds1ZswYhxecrVq1Kt3vjc1mk4+Pj0JDQ1WjRg09+OCD+vbbb5WammrfPjo6WsHBwQ7blChRQsePH0/3M5cuXer0OTVr1lRSUtK1OGQg3yG8ARaUmpqq8+fPa//+/VqwYIEee+wxRURE6Pfff8/t0nLM9u3bNXbsWIcXss4Yo3PnzumPP/7Q559/rrvvvluRkZH6559/JEk1atTQyy+/7LDNyZMnNXjwYJf7O3funPr37++wzNfXV9OnT1dgYGDOHASQzxHegOvEoUOHdP/998sYk9ul5Alz587VwYMH7a8333wzt0vKszZv3qwOHTooOTlZkjR06FBFRkY6tPnqq6/07bffOm07fPhwxcTEOCx75pln1KRJk5wrGMjnCG+AhaQFkW3btum9995TcHCww/pff/1V27Zty6Xq8pbSpUurUqVK9leJEiVyu6Rc89RTT9m/O9u3b9d7772n0NBQhza7du3S7NmzJUk+Pj6aPn26goKCHNo8/vjjOn36tP392rVr9fHHHzu0iYiI4KwokMMIb4CFpAWRevXq6cknn9TAgQOd2uzbty/d7RMTEzV16lTdcccdqlChgoKDg1WwYEFVr15d/fr10+bNm9Pd9tChQ3r//ffVt29fNWrUSJUqVVJoaKj8/f1VrFgx1a9fX48//ri2bNmS6XFcunRJX3zxhbp3766qVasqNDRUQUFBqlChglq0aKExY8bowIEDkqQxY8bIZrOpb9++Tvu5+j6rK++Dy8oDC9u2bdOgQYNUt25dFS1aVP7+/ipRooSaNGmiF154QX/++We621aqVMmphpSUFP33v/9Vs2bNVLhwYYWEhOjmm2/W+++/73B/2ZVmzJjhdDzeUqRIEft3p27dunryySf19ttvO7X78ccf7f/v6vLp0aNHNWTIEEmXv0uPPPKIw5leX19fzZgxw3659Ntvv9WLL76o2267TREREQoLC1NAQIBCQkIUHh6uzp0766OPPtL58+fTrf306dN69dVX1bJlS4WFhSkwMFAFChRQxYoV1bhxY/Xv319Tp07V4cOHXW6/e/du9ejRQ2XKlFFQUJBuuOEGPfnkkzp27Jj7HQjkNQZAntSqVSsjyeF1tY8++sipzeLFi13ub8OGDaZixYpO7a9+DRgwwFy8eNFp+3feeSfTbSUZm81mhg4dmu5x/fLLL+bGG2/MdD/vvPOOMcaY0aNHu/W5kszo0aPT7b/evXs71ZKQkGAeffTRTPfr5+dnXn/9dZfHc3WfPvnkk+aWW25Jd1+u6jDGmOnTp2f683bHypUrM+yXNL/99ptTuw4dOji0SUlJMZGRkU7tfvjhB/Pcc885LX/++ecdti9cuLBbP7eKFSuanTt3OtUYHR1typQp49Y+XnvtNaftv/32WxMQEOCyfYkSJcy3337rtHzlypUe9TtwLXHmDbCwqx9Q8PX1Ve3atZ3abdu2Te3atcvwDFKayZMna8CAAR7XZIzR22+/rU8//dRp3c6dO9WmTZs88WCFMUa9evXSlClTMm176dIlPf/883r11VczbfvBBx9o3bp16a6fOXOmVqxYkaVac8KuXbuclhUtWtThfXqXT/v06aO33nrLYVl2Lpf++eef6tq1q/2euzTDhg3TkSNHPNrn77//rgceeEAXL150uT42NlY9evTwaN9AbiO8ARZy6NAhHTp0SDt27NC7776r//73vw7re/furfLlyzssM8aoX79+unDhgn1ZjRo1NHv2bP3222/aunWrXnjhBYfLdNOmTdNPP/3ksJ+AgAC1bdtWb7/9thYtWqTNmzdr7969+uWXX/TZZ5+pVq1aDu3feOMNpzoefvhhxcXFOSxv27atFixYoOjoaO3YsUOffvqpbrnlFvv6p59+WgcPHnTanySHBxIOHjyo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IL2tWrXSrbfeKl9f3xwpGAAAID/L8gwLvr6+zGsKAACQS5jbFAAAwEKyfObNldTUVO3Zs0cXLlxQxYoVXQ7gCwAAgOxz+8zbmjVrFBMT47R80qRJCgsLU506dRQZGamyZcuqffv2OnTokDfrBAAAgLIQ3tq0aaMZM2Y4LJs4caKefPJJXbhwQbfeeqvuv/9+ValSRStWrFDr1q3tDzMAAADAO9wOb8YYh/enTp3S2LFjVa5cOW3dutU+vtvvv/+uYcOGKSYmRu+8847XCwYAAMjPPH5g4ccff1RCQoJee+011axZ83879PHR66+/rurVq2vBggVeKRIAAACXeRzeDh06JJvNprZt2zrv1MdHrVq10r59+7JVHAAAABx5HN78/C4/qFqsWDGX64sVK6aLFy96unsAAAC4kKWhQrZv367PPvtMkuxPnh4+fFhVqlRxavv333+nG+wAAADgmSyFt++++07z58+X9L8HGJYuXarHH3/cqe2vv/6qqlWreqFEAAAApHE7vE2fPt3l8sqVKzst++WXX/Trr7/qmWee8bwyAAAAOHE7vPXu3dvtndavX1+pqakeFQQAAID0MbcpAACAhRDeAAAALITwBgAAYCGENwAAAAshvAEAAFgI4Q0AAMBCCG8AAAAWQngDAACwEMIbAACAhXgc3nx9feXn56c//vjDaV10dLR9PQAAALzH43RljLFPTu/JegAAAGSdx+Eto7lLa9SowdymAAAAOSDP3fP28ccfq06dOgoNDVVoaKgiIyP1ww8/2NcnJiZq0KBBKl68uAoWLKhu3brp2LFjDvuIiYlRly5dVKBAAZUqVUrPPvusLl26dK0PBQAAwOvyXHgrX768JkyYoKioKG3dulVt27ZV165dtWvXLknSkCFDtHDhQn311VdavXq1/vnnH91999327VNSUtSlSxddvHhR69ev18yZMzVjxgyNGjUqtw4JAADAazwKb3/99Zd++uknxcfH25elpqbq9ddfV/PmzdWuXTstWrTIo4Juv/12de7cWdWqVVP16tX1yiuvqGDBgtq4caPi4uL06aef6u2331bbtm3VoEEDTZ8+XevXr9fGjRslScuWLdPu3bs1a9Ys1atXT506ddLLL7+sSZMm6eLFix7VBAAAkFd4dM/byJEjtXDhQh09etS+7JVXXtHo0aPt71evXq3169erUaNG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pFmSItJps0dSz8RExcbG8jsAALzM4/AWFRWlF198UWvWrNHFixddtrHZbLp06ZLHxQHIuyIk1c/tIgAgH/IovG3fvl0tWrSQn5+fOnTooIULF6pu3boqXbq0fvnlF504cUKtW7dWxYoVvV0vAABAvubRIL0vv/yyJGnTpk2aP3++JOmuu+7SDz/8oEOHDmnAgAH67bffNHr0aO9VCgAAAM/C27p163THHXcoIuJ/d7wYYyRJwcHB+vDDD1W2bFm9+OKL3qkSAAAAkjwMb3Fxcbrhhhvs7/39/XX+/Pn/7dTHR61bt9aKFSuyXyEAAADsPApvpUqV0unTp+3vS5curb179zq0SUxMVHx8fPaqAwAAgAOPwlvNmjUVHR1tf9+8eXMtW7ZMGzZskHR58M4vv/xSN954o3eqBAAAgCQPw1uXLl20Zs0aHTlyRJL0/PPPyxijW265RSVLllTt2rV15swZ7nkDAADwMo/C24ABA/T333+rePHikqS6detqxYoVuu2221SiRAm1a9dOCxcu1F133eXVYgEAAPI7j8Z58/f3V1hYmMOyZs2aadGiRV4pCgAAAK55dOYtI0lJSUpOTvb2bgEAACAPw9uaNWs0atQonTlzxr7s5MmT6tSpkwoWLKjChQtr+PDh3qoRAAAA//IovL355puaM2eOihQpYl82bNgwLV26VJUrV1aRIkX0xhtv6Msvv/RWnQAAAJCH97xt27ZNt956q/19YmKivvzyS3Xo0EFLlizRuXPnVKdOHX388ce67777vFYskF0xMTGKjY1Nd/2ePXuuYTUAAGSdR+Ht5MmTKleunP39hg0blJiYqL59+0qSChUqpP/85z/6+uuvvVMl4AUxMTGKqFFD8YmJuV0KAAAe8yi8BQcH69y5c/b3K1eulM1mU6tWrezLChYs6DALA5DbYmNjFZ+YqFmSItJps1jSyGtYEwAAWeVReKtataqWLFmipKQk2Ww2zZ07VzVr1lTp0qXtbWJiYlSqVCmvFQp4S4Sk+ums46IpACCv8+iBhUcffVT79u1T1apVFRERof3799svmaaJiopSzZo1vVIkAAAALvMovPXr10/PPvusEhISFBcXp4EDB+rpp5+2r9+wYYP++OMPh4caAAAAkH0eXTa12Wx6/fXX9frrr7tc36BBA50+fVohISHZKg4AAACOPApvmQkICFBA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d+7U9u3b7a877rhDbdq00fbt21WhQgUPe+V/rNw/0uXf0/v27XMIIX/88YfKlCmjgICArHRFuqzeRzn9e9qlHHsUAtfE3LlzTWBgoJkxY4bZvXu36d+/vylSpIg5evSoMcaYXr16meHDh9vb//zzz8bPz8+8+eabZs+ePWb06NEuH68uUqSImT9/vvn1119N165dXT5effPNN5tNmzaZdevWmWrVqjk8Xm2MMbt27TLbtm0zt99+u2ndurXZtm2b2bZtm339uXPnTPny5c0999xjdu3aZVavXm2qVatmHnnkEfroX9OnTzd+fn7mo48+Mvv37zfr1q0zDRs2NI0bN77u++enn34yBQoUMC+88II5cuSI/XXl08nu1JLf+2jChAkmICDAzJs3z6HNuXPn6B8XcuJpUyv3T0xMjClUqJAZPHiwiY6ONt9//70pVaqUGT9+PH30r9GjR5tChQqZzz//3Bw4cMAsW7bMVKlSxdx3331e7aMrEd6uAx988IEJDw83AQEBpnHjxmbjxo32da1atTK9e/d2aP/ll1+a6tWrm4CAAFOrVi2zaNEih/Wpqalm5MiRJiwszAQGBppbb73VREdHO7Q5efKkeeCBB0zBggVNaGio6du3r9NfBhUrVjSSnF5X2rNnj2nXrp0JDg425cuXN0OHDjXx8fFe6BVHVu6j999/39SsWdMEBwebMmXKmB49enh1jC5j8mb/9O7d22XftGrVKku1eItV+yi979jo0aO91jfGWLd/rpYT4c0Ya/fP+vXrTZMmTUxgYKC54YYbzCuvvGIuXbrknY65glX7KDk52YwZM8ZUqVLFBAUFmQoVKpjHH3/cnD592mt9czWbMTl1Tg8AAADexj1vAAAAFkJ4AwAAsBDCGwAAgIUQ3gAAACyE8AYAAGAhhDcAAAALIbwBAABYCOENAADkG6+88oqaNWumAgUKqEiRIm5tY7PZXL7eeOMNe5tKlSo5rZ8wYYLDfpYuXaqmTZuqUKFCKlmypLp166ZDhw5l+RgIb4CFuPrlcPXr3Xfftbc/dOiQbDabKlWqlGs1uys1NVUNGzZU6dKldeHChdwu55pL+/llZPr06bLZbJo4cWK2Pivte3Hly9fXV0WKFNENN9yg22+/Xa+++qr+/PNPl9uvWLFCNptNRYoUUUpKiss248ePt+973bp1Ltts3bpVNptN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dZB2v1urVq3s7Vq1aqVNmzZp48aNatWqlbZu3arz58+rQIECaty4sdPnbd68WYcPH1bv3r3l5+f6r6n//Oc/Lr9rERERki7fp+hKQECAChYsqPPnz+vYsWMKDQ3N5OhxvRk1apRmzpxpf3/zzTdLuvzwTdr9odHR0YqLi3PYbu7cuTLG6IEHHnDaZ2BgoObOnasxY8YoKSlJlStX1pAhQzR06FB7m7Zt22rOnDmaOHGiJk6cqAIFCigyMlJLlixxOVRORghvgAU98sgj6d7cf6088cQT+v7777Vs2TLZbDbNnTtXRYsWzfJ+0n5BuvOXaHh4uNOytPHHypQp4/Iv+kKFCkmSEhMT3d7nlftNb72r/aYFhsqVK7vcRpKqVKni0DYzP/74o+Li4tSvXz+H5bGxsfbPTu/BE28MzpySkqIzZ85IkooVK+awrnHjxgoJCdGFCxe0ZcsWNWvWTKtWrZKPj49atmxpb9eqVStNnDhRq1atUqtWrewBr3nz5goICHD6THfGs0vv55L2PUrv553W5vz58x4/PIK8L+075sqMGTMyHePNuHiAoH///urfv7/L9vXr19fGjRszrat79+7q3r17pu0yw9OmAFxKO9uSnr1792rDhg2SLv+iSxsBP6vSpqc5e/Zspm3TBrbM6jpP95md/XpLdgbmzepZUFd+++03+zAftWvXdljn7++v5s2bS7p81iIpKUkbN25UvXr1VLhwYXu7W265Rb6+vvZhRdL+m9EQIYUKFXJ5JjVNdn4uaf9g8OQfG0BeQHgD8qG0sx2uhvuQLo9sf+TIkXS3T0xM1H333adz586pR48eCgwM1LPPPqutW7dmuZa0S5YnT57M8rZ5Tbly5SRJBw4cSLdN2rq0thlJSUnR/PnzVaZMGafhNIoXL67AwEBJSncaK0/mTLzarFmz7J935WXbNFeO95Y2vtuVl0yly2e66tWrp40bN+rcuXP6+eefHba90vbt23XgwAF17tzZfnzelJSUZJ9+LSwszOv7B64FwhuQD5UsWVIBAQE6deqUyzHQli5d6jR215Weeuopbd++XW3atNFnn32mt956SxcvXtR9991nv8Tmrvr160uSdu/enaXt8qKWLVvKx8dH27dv144dO5zWHzlyxD6+WHpnna60evVqnTx5UnfddZfTWTR/f397oJszZ47L7T///POsHoKDX375xT4G39ChQ+Xr6+vUJu041q9fr2XLlkmSy3HlWrVqpaSkJL3//vu6cOGCChYs6HJarfTGs/OW3377TZJUtWpV7neDZRHegHzI39/ffk/SiBEjHC6R7tixQ4MHD0532zlz5uiTTz5RWFiY5syZIx8fHw0aNEj33HOPDh48qIcffjhLtaT95Z92CdbKwsPDde+998oYo8cee8zhbOKFCxfUv39/JSYmqlmzZm49rJDZJdMnn3xSkvT+++873W/z3nvvOQ2M666EhAR9/PHHat26tRITE9W6dWs988wzLts2aNBAhQoVsm9z9f1uadLOxr399tuSpBYtWri8R/Hrr79WcHCwy4dTvCHtIY+2bdvmyP6Ba4EHFoB8avz48VqzZo2mTJmi1atXq06dOvr777+1detWPfjgg1q1apXT5bjo6Gg99thj8vHx0Zw5cxwmYJ46dap++eUXffvtt3rvvff01FNPuVVH5cqVVadOHf3666/as2eP/WlBq5o0aZJ+//13bdq0SVWqVFGbNm3k5+en1atX68SJE6pcubJmz56d6X6MMfr2229VokQJp8uQae666y71799fn3zyiW655Ra1aNFCZcqU0c6dO7Vnzx4NGTJE77zzjsuHAtI888wz9oczLly4oH/++Ue//PKLEhMT5ePjowEDBujNN99Mdx9+fn5q0aKFFi9erFOnTqlevXr2+xiv1KJFC/n4+OjUqVOSXJ95/P3337V7927deeedCgkJyayLPPLjjz9Kku68884c2T9wLXDmDcinmjRpotWrV6tDhw46evSoFi1apPj4eL333nuaPn26U/uEhATde++9On/+vEaOHOl05qJw4cL68ssvFRgYqOeeey5LQ1SknenL7AkwKyhevLjWr1+v1157zT6X4ffff68SJUroxRdfVFRUlFtPga5fv15HjhzRHXfc4fJyZZrJkydrypQpqlu3rjZu3KgffvhBZcuW1cqVK+1DIJQoUSLd7b/++mvNnDlT//d//6dly5bpn3/+Ubt27fTKK6/o4MGD+vjjjzMNUlcGsfSm4ipatKjDAw+uwpu35zK92okTJ/TDDz+oSpUquu2223LkM4BrwWZcPQ8LANdQfHy8KlWqJD8/Px06dCjDM0X5xbBhw/T2229r0aJFHl9CfPjhhzV9+nS99dZbDuNN5VUNGjTQzp07dfz4cZdn77Lrrbfe0jPPPKP33nvPfskZsCLOvAHIdQUKFNArr7yiI0eO6JNPPsntcvKEG2+8UWPGjFG7du0ybLdr1y7705NpUlNTNWXKFM2YMUNBQUEuBxXNay5evKg77rhDH374YY4EtwsXLmjixImqXr26Bg4c6PX9A9cSZ94A5Ampqalq3LixDh8+rP379+fYPU/Xmz59+ujLL7/UzTffrHLlyunChQvavXu3Dh06JF9fX02ZMkV9+/bN7TJz3fjx4zVy5MhsnckE8grCGwBY2A8//KApU6YoKipKsbGxunTpkkqVKqXmzZvr6aefVtOmTXO7RABeRngDAACwEO55AwAAsBDCGwAAgIUQ3gAAACyE8AYAAGAhhDcAAAALIbwBAABYCOENAADAQghvAAAAFvL/DB5/cNi621cAAAAASUVORK5CYII=", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], "source": [ "from dingo import plot_histogram\n", "\n", "reactions = model.reactions\n", "plot_histogram(\n", - " steady_states[23], # here we set which reaction's flux we need to get \n", - " reactions[23], # here we provide the name of the reaction\n", + " steady_states.loc[\"PYK\"], # here we set which reaction's flux we need to get \n", + " \"PYK\", # here we provide the name of the reaction\n", " n_bins = 60,\n", ")" ] @@ -1544,7 +2202,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 18, "metadata": { "colab": { "base_uri": "https://localhost:8080/", @@ -1553,12 +2211,192 @@ "id": "pQskY7EeWPdv", "outputId": "715833d7-fc8f-452e-e0b2-3fdcf2532848" }, - "outputs": [], + "outputs": [ + { + "data": { + "application/vnd.plotly.v1+json": { + "config": { + "plotlyServerURL": "https://plot.ly" + }, + "data": [ + { + "type": "surface", + "z": [ + [ + 0.0194, + 0.0398, + 0.047, + 0.0446, + 0.0492 + ], + [ + 0.0532, + 0.0474, + 0.0372, + 0.0372, + 0.025 + ], + [ + 0.0524, + 0.0458, + 0.044, + 0.032, + 0.0258 + ], + [ + 0.0472, + 0.0374, + 0.0378, + 0.0406, + 0.037 + ], + [ + 0.0278, + 0.0296, + 0.034, + 0.0456, + 0.063 + ] + ] + } + ], + "layout": { + "height": 600, + "margin": { + "b": 30, + "l": 30, + "r": 30, + "t": 50 + }, + "scene": { + "xaxis": { + "title": { + "text": "PYK" + } + }, + "yaxis": { + "title": { + "text": "ATPS4r" + } + }, + "zaxis": { + "title": { + "text": "prob, mass" + } + } + }, + "title": { + "text": "Copula between PYK and ATPS4r" + }, + "width": 900 + } + } + }, + "metadata": {}, + "output_type": "display_data" + } + ], "source": [ "from dingo.illustrations import plot_copula\n", "\n", - "reaction_1 = [steady_states[23], reactions[23]]\n", - "reaction_2 = [steady_states[21], reactions[21]]\n", + "reaction_1 = [steady_states.loc[\"PYK\"], \"PYK\"] #model.reactions.index('PYK')\n", + "reaction_2 = [steady_states.loc[\"ATPS4r\"], \"ATPS4r\"]\n", + "\n", + "plot_copula(reaction_1, reaction_2, n = 5, width = 900 , height = 600, export_format = \"svg\")\n" + ] + }, + { + "cell_type": "code", + "execution_count": 20, + "metadata": {}, + "outputs": [ + { + "data": { + "application/vnd.plotly.v1+json": { + "config": { + "plotlyServerURL": "https://plot.ly" + }, + "data": [ + { + "type": "surface", + "z": [ + [ + 0.1092, + 0.0404, + 0.0236, + 0.0166, + 0.0102 + ], + [ + 0.0522, + 0.0554, + 0.0444, + 0.0296, + 0.0184 + ], + [ + 0.0216, + 0.0482, + 0.0504, + 0.0444, + 0.0354 + ], + [ + 0.0098, + 0.033, + 0.0444, + 0.0576, + 0.0552 + ], + [ + 0.0072, + 0.023, + 0.0372, + 0.0518, + 0.0808 + ] + ] + } + ], + "layout": { + "height": 600, + "margin": { + "b": 30, + "l": 30, + "r": 30, + "t": 50 + }, + "scene": { + "xaxis": { + "title": { + "text": "ACONTa" + } + }, + "yaxis": { + "title": { + "text": "PPC" + } + }, + "zaxis": { + "title": { + "text": "prob, mass" + } + } + }, + "title": { + "text": "Copula between ACONTa and PPC" + }, + "width": 900 + } + } + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "reaction_1 = [steady_states.loc[\"ACONTa\"], \"ACONTa\"] #model.reactions.index('PYK')\n", + "reaction_2 = [steady_states.loc[\"PPC\"], \"PPC\"]\n", "\n", "plot_copula(reaction_1, reaction_2, n = 5, width = 900 , height = 600, export_format = \"svg\")\n" ] @@ -1676,7 +2514,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.10.6" + "version": "3.10.12" }, "vscode": { "interpreter": {