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",
+ " fluxes | \n",
+ "
\n",
+ " \n",
+ " \n",
+ " \n",
+ " PFK | \n",
+ " 7.477382 | \n",
+ "
\n",
+ " \n",
+ " PFL | \n",
+ " 0.000000 | \n",
+ "
\n",
+ " \n",
+ " PGI | \n",
+ " 4.860861 | \n",
+ "
\n",
+ " \n",
+ " PGK | \n",
+ " -16.023526 | \n",
+ "
\n",
+ " \n",
+ " PGL | \n",
+ " 4.959985 | \n",
+ "
\n",
+ " \n",
+ " ... | \n",
+ " ... | \n",
+ "
\n",
+ " \n",
+ " NADH16 | \n",
+ " 38.534610 | \n",
+ "
\n",
+ " \n",
+ " NADTRHD | \n",
+ " 0.000000 | \n",
+ "
\n",
+ " \n",
+ " NH4t | \n",
+ " 4.765319 | \n",
+ "
\n",
+ " \n",
+ " O2t | \n",
+ " 21.799493 | \n",
+ "
\n",
+ " \n",
+ " PDH | \n",
+ " 9.282533 | \n",
+ "
\n",
+ " \n",
+ "
\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",
+ " minimum | \n",
+ " maximum | \n",
+ "
\n",
+ " \n",
+ " \n",
+ " \n",
+ " PFK | \n",
+ " 176.609999 | \n",
+ " 1.766100e+02 | \n",
+ "
\n",
+ " \n",
+ " PFL | \n",
+ " 0.000000 | \n",
+ " 6.666667e-07 | \n",
+ "
\n",
+ " \n",
+ " PGI | \n",
+ " 9.999999 | \n",
+ " 1.000000e+01 | \n",
+ "
\n",
+ " \n",
+ " PGK | \n",
+ " -20.000000 | \n",
+ " -2.000000e+01 | \n",
+ "
\n",
+ " \n",
+ " PGL | \n",
+ " 0.000000 | \n",
+ " 1.333333e-06 | \n",
+ "
\n",
+ " \n",
+ " ... | \n",
+ " ... | \n",
+ " ... | \n",
+ "
\n",
+ " \n",
+ " NADH16 | \n",
+ " 99.999999 | \n",
+ " 1.000000e+02 | \n",
+ "
\n",
+ " \n",
+ " NADTRHD | \n",
+ " 19.999999 | \n",
+ " 2.000000e+01 | \n",
+ "
\n",
+ " \n",
+ " NH4t | \n",
+ " 0.000000 | \n",
+ " 1.777778e-07 | \n",
+ "
\n",
+ " \n",
+ " O2t | \n",
+ " 60.000000 | \n",
+ " 6.000000e+01 | \n",
+ "
\n",
+ " \n",
+ " PDH | \n",
+ " 19.999999 | \n",
+ " 2.000000e+01 | \n",
+ "
\n",
+ " \n",
+ "
\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",
+ " 0 | \n",
+ " 1 | \n",
+ " 2 | \n",
+ " 3 | \n",
+ " 4 | \n",
+ " 5 | \n",
+ " 6 | \n",
+ " 7 | \n",
+ " 8 | \n",
+ " 9 | \n",
+ " ... | \n",
+ " 4990 | \n",
+ " 4991 | \n",
+ " 4992 | \n",
+ " 4993 | \n",
+ " 4994 | \n",
+ " 4995 | \n",
+ " 4996 | \n",
+ " 4997 | \n",
+ " 4998 | \n",
+ " 4999 | \n",
+ "
\n",
+ " \n",
+ " \n",
+ " \n",
+ " PFK | \n",
+ " 7.477382 | \n",
+ " 7.477380 | \n",
+ " 7.477381 | \n",
+ " 7.477385 | \n",
+ " 7.477383 | \n",
+ " 7.477383 | \n",
+ " 7.477387 | \n",
+ " 7.477399e+00 | \n",
+ " 7.477389 | \n",
+ " 7.477392e+00 | \n",
+ " ... | \n",
+ " 7.477376 | \n",
+ " 7.477384 | \n",
+ " 7.477380e+00 | \n",
+ " 7.477389e+00 | \n",
+ " 7.477388 | \n",
+ " 7.477392 | \n",
+ " 7.477388 | \n",
+ " 7.477384 | \n",
+ " 7.477385e+00 | \n",
+ " 7.477379 | \n",
+ "
\n",
+ " \n",
+ " PFL | \n",
+ " 0.000002 | \n",
+ " 0.000002 | \n",
+ " 0.000002 | \n",
+ " 0.000003 | \n",
+ " 0.000002 | \n",
+ " 0.000005 | \n",
+ " 0.000009 | \n",
+ " 9.758857e-07 | \n",
+ " 0.000001 | \n",
+ " 4.354554e-06 | \n",
+ " ... | \n",
+ " 0.000003 | \n",
+ " 0.000001 | \n",
+ " 1.003201e-07 | \n",
+ " 2.492911e-06 | \n",
+ " 0.000004 | \n",
+ " 0.000004 | \n",
+ " 0.000004 | \n",
+ " 0.000001 | \n",
+ " 4.600969e-07 | \n",
+ " 0.000002 | \n",
+ "
\n",
+ " \n",
+ " PGI | \n",
+ " 4.860848 | \n",
+ " 4.860838 | \n",
+ " 4.860842 | \n",
+ " 4.860858 | \n",
+ " 4.860858 | \n",
+ " 4.860857 | \n",
+ " 4.860859 | \n",
+ " 4.860871e+00 | \n",
+ " 4.860868 | \n",
+ " 4.860880e+00 | \n",
+ " ... | \n",
+ " 4.860839 | \n",
+ " 4.860861 | \n",
+ " 4.860854e+00 | \n",
+ " 4.860868e+00 | \n",
+ " 4.860872 | \n",
+ " 4.860889 | \n",
+ " 4.860876 | \n",
+ " 4.860865 | \n",
+ " 4.860866e+00 | \n",
+ " 4.860845 | \n",
+ "
\n",
+ " \n",
+ " PGK | \n",
+ " -16.023522 | \n",
+ " -16.023519 | \n",
+ " -16.023521 | \n",
+ " -16.023526 | \n",
+ " -16.023526 | \n",
+ " -16.023526 | \n",
+ " -16.023527 | \n",
+ " -1.602353e+01 | \n",
+ " -16.023529 | \n",
+ " -1.602353e+01 | \n",
+ " ... | \n",
+ " -16.023520 | \n",
+ " -16.023527 | \n",
+ " -1.602352e+01 | \n",
+ " -1.602353e+01 | \n",
+ " -16.023531 | \n",
+ " -16.023536 | \n",
+ " -16.023532 | \n",
+ " -16.023528 | \n",
+ " -1.602353e+01 | \n",
+ " -16.023521 | \n",
+ "
\n",
+ " \n",
+ " PGL | \n",
+ " 4.959998 | \n",
+ " 4.960008 | \n",
+ " 4.960004 | \n",
+ " 4.959988 | \n",
+ " 4.959988 | \n",
+ " 4.959989 | \n",
+ " 4.959987 | \n",
+ " 4.959975e+00 | \n",
+ " 4.959978 | \n",
+ " 4.959966e+00 | \n",
+ " ... | \n",
+ " 4.960007 | \n",
+ " 4.959985 | \n",
+ " 4.959992e+00 | \n",
+ " 4.959978e+00 | \n",
+ " 4.959974 | \n",
+ " 4.959957 | \n",
+ " 4.959970 | \n",
+ " 4.959981 | \n",
+ " 4.959980e+00 | \n",
+ " 4.960000 | \n",
+ "
\n",
+ " \n",
+ " ... | \n",
+ " ... | \n",
+ " ... | \n",
+ " ... | \n",
+ " ... | \n",
+ " ... | \n",
+ " ... | \n",
+ " ... | \n",
+ " ... | \n",
+ " ... | \n",
+ " ... | \n",
+ " ... | \n",
+ " ... | \n",
+ " ... | \n",
+ " ... | \n",
+ " ... | \n",
+ " ... | \n",
+ " ... | \n",
+ " ... | \n",
+ " ... | \n",
+ " ... | \n",
+ " ... | \n",
+ "
\n",
+ " \n",
+ " NADH16 | \n",
+ " 38.534624 | \n",
+ " 38.534630 | \n",
+ " 38.534628 | \n",
+ " 38.534622 | \n",
+ " 38.534622 | \n",
+ " 38.534614 | \n",
+ " 38.534627 | \n",
+ " 3.853460e+01 | \n",
+ " 38.534611 | \n",
+ " 3.853461e+01 | \n",
+ " ... | \n",
+ " 38.534623 | \n",
+ " 38.534618 | \n",
+ " 3.853463e+01 | \n",
+ " 3.853462e+01 | \n",
+ " 38.534622 | \n",
+ " 38.534613 | \n",
+ " 38.534616 | \n",
+ " 38.534611 | \n",
+ " 3.853461e+01 | \n",
+ " 38.534620 | \n",
+ "
\n",
+ " \n",
+ " NADTRHD | \n",
+ " 0.000024 | \n",
+ " 0.000031 | \n",
+ " 0.000021 | \n",
+ " 0.000007 | \n",
+ " 0.000039 | \n",
+ " 0.000013 | \n",
+ " 0.000025 | \n",
+ " 7.147399e-06 | \n",
+ " 0.000010 | \n",
+ " 6.149728e-07 | \n",
+ " ... | \n",
+ " 0.000023 | \n",
+ " 0.000008 | \n",
+ " 2.870932e-05 | \n",
+ " 9.178589e-07 | \n",
+ " 0.000015 | \n",
+ " 0.000022 | \n",
+ " 0.000028 | \n",
+ " 0.000011 | \n",
+ " 4.563802e-06 | \n",
+ " 0.000014 | \n",
+ "
\n",
+ " \n",
+ " NH4t | \n",
+ " 4.765317 | \n",
+ " 4.765317 | \n",
+ " 4.765317 | \n",
+ " 4.765317 | \n",
+ " 4.765317 | \n",
+ " 4.765317 | \n",
+ " 4.765317 | \n",
+ " 4.765317e+00 | \n",
+ " 4.765318 | \n",
+ " 4.765317e+00 | \n",
+ " ... | \n",
+ " 4.765317 | \n",
+ " 4.765317 | \n",
+ " 4.765317e+00 | \n",
+ " 4.765317e+00 | \n",
+ " 4.765317 | \n",
+ " 4.765317 | \n",
+ " 4.765317 | \n",
+ " 4.765317 | \n",
+ " 4.765317e+00 | \n",
+ " 4.765317 | \n",
+ "
\n",
+ " \n",
+ " O2t | \n",
+ " 21.799498 | \n",
+ " 21.799500 | \n",
+ " 21.799500 | \n",
+ " 21.799499 | \n",
+ " 21.799499 | \n",
+ " 21.799494 | \n",
+ " 21.799504 | \n",
+ " 2.179949e+01 | \n",
+ " 21.799494 | \n",
+ " 2.179950e+01 | \n",
+ " ... | \n",
+ " 21.799496 | \n",
+ " 21.799497 | \n",
+ " 2.179950e+01 | \n",
+ " 2.179950e+01 | \n",
+ " 21.799502 | \n",
+ " 21.799500 | \n",
+ " 21.799500 | \n",
+ " 21.799494 | \n",
+ " 2.179950e+01 | \n",
+ " 21.799495 | \n",
+ "
\n",
+ " \n",
+ " PDH | \n",
+ " 9.282543 | \n",
+ " 9.282553 | \n",
+ " 9.282558 | \n",
+ " 9.282555 | \n",
+ " 9.282532 | \n",
+ " 9.282546 | \n",
+ " 9.282532 | \n",
+ " 9.282541e+00 | \n",
+ " 9.282552 | \n",
+ " 9.282542e+00 | \n",
+ " ... | \n",
+ " 9.282555 | \n",
+ " 9.282543 | \n",
+ " 9.282538e+00 | \n",
+ " 9.282542e+00 | \n",
+ " 9.282544 | \n",
+ " 9.282552 | \n",
+ " 9.282563 | \n",
+ " 9.282535 | \n",
+ " 9.282538e+00 | \n",
+ " 9.282569 | \n",
+ "
\n",
+ " \n",
+ "
\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+fVohISHZKg4AAACOPApvmQkICFBAQEBO7BoAACBf8+iy6fnz5/XZZ5/pr7/+8nY9AAAAyIBHZ96OHTumvn376quvvlKFChUkXX669NChQ2rZsqVXCwRgXVcOepyamipJ2rFjh3x8Lv+7sUSJEgoPD8+V2gDAqtwObxs2bFDjxo3l6+srSTLGOKyfPn26xo0bp5SUFO9WCMByjujyaf2ePXvalwUHB+vzzz9Xy5YtlZCQIEkqEBSkPdHRBDgAyAK3w1vz5s1VsGBB3XLLLapZs6ZsNptsNltO1gbAos5ISpUcBkROlfS3pDW6HOz2SOqZmKjY2FjCGwBkgdvhbdGiRVqxYoVWrlypZcuWSZJ69+6tTz75RK1atdLevXtzrEgA1nTlgMjJuhze6kryz7WKAMD63A5vnTp1UqdOnSRJ27dvV/369dW0aVMdP35cI0aMUGpqqmw2mzp06KBWrVqpVatWatKkifz9+TUNAADgLR49bVqoUCFJ0oABAxQVFaWTJ0/aZ1g4c+aMxowZo5YtW6po0aLeqxQAAADun3krWbKk2rRpozZt2jjdn1K4cGH7ss2bN+vChQtat26d1q5d691qAQAA8jm3w1vXrl21atUqzZs3z/6wwocffqhjx46pVatWDk+fhoSEqGPHjurYsWOOFA0AAJBfuR3epk6dKkk6fPiw5s2bp6FDh2rHjh1atWqVbDabfQiRjz76SC1atFDt2rVzpmIAAIB8LMv3vJUvX1633367JGnKlCn666+/NHPmTDVq1EjGGA0ePFj16tVT8eLFdeedd3q7XgAAgHwt23OblitXTj179tT+/fu1ceNGHTlyRKtWrdLq1au1Zs0ab9QIAACAf3kU3vz9/VWxYkWFhIQ4rStVqpTuu+8+3XfffdkuDgAAAI48Cm/h4eE6ePCgwzJjjNOUWQAAAPAuj8Z5c2XMmDH2iacBAACQM7wW3gAAAJDzCG8AAAAW4lZ4u+2227RlyxaPPuDChQuaMGGCJk2a5NH2AAAA+B+3wtuJEyfUtGlTtWnTRtOnT1dcXFym22zcuFGDBw9WxYoV9fLLLyssLCzbxQIAAOR3bj1tGhUVpZkzZ2rs2LHq16+fHn30UdWoUUMNGjRQWFiYihQposTERJ06dUrR0dHaunWrzp07J19fX3Xv3l3jx493mg8VAAAAWef2UCG9e/fWQw89pMWLF2v69OlatWqVZs2a5dTOx8dHderU0V133aVHHnlEZcqU8WrBAAAA+VmWxnmz2Wzq0qWLunTpIknas2ePDh8+rJMnTyo4OFglS5ZUrVq1VLhw4RwpFgAAIL/L1vRYERERioiI8FYtAAAAyARDhQAAAFgI4Q0AAMBCCG8AAAAWQngDAACwEMIbAACAhRDeAAAALMSj8JaUlOTtOuxee+01NWrUSIUKFVKpUqV05513Kjo62qFNYmKiBg0apOLFi6tgwYLq1q2bjh075tAmJiZGXbp0UYECBVSqVCk9++yzunTpUo7VDQAAcC14FN7Kli2rp556Sjt37vR2PVq9erUGDRqkjRs3avny5UpOTlaHDh104cIFe5shQ4Zo4cKF+uqrr7R69Wr9888/uvvuu+3rU1JS1KVLF128eFHr16/XzJkzNWPGDI0aNcrr9QIAAFxLHoW3QoUK6YMPPlC9evUUGRmpadOmKT4+3isFLVmyRH369FGtWrVUt25dzZgxQzExMYqKipIkxcXF6dNPP9Xbb7+ttm3bqkGDBpo+fbrWr1+vjRs3SpKWLVum3bt3a9asWapXr546deqkl19+WZMmTdLFixe9UicAAEBu8GiGhYMHD2rZsmWaOnWqFi5cqEcffVRDhgzRAw88oEceeUQNGzb0WoFxcXGSpGLFikmSoqKilJycrHbt2tnb3HjjjQoPD9eGDRvUtGlTbdiwQbVr11ZYWJi9TceOHTVw4EDt2rVLN998s9PnJCUlOVwOPnv2rCQpOTlZycnJXjseq0rrAyv3RWpqqoKDg5UqKaOjCJay3CY5ONjhv57uJ7fb5OTnXd1HqWltUlMt/b3yluvhz1hOo48yRv9kLK/3T1bqshljTHY+LDY2VjNnztSnn36q33//XTabTXXq1FH//v3Vo0cPhYaGerzv1NRU3XHHHTpz5ozWrVsnSZozZ4769u3rdN9d48aN1aZNG73++uvq37+//vzzTy1dutS+Pj4+XiEhIVq8eLE6derk9FljxozR2LFjnZbPmTNHBQoU8PgYAAAAMhMfH68HH3xQcXFxmWanbM1tKkklSpTQsGHDNGzYMP3888/69NNP9dVXX2nw4MF69tlnde+992rgwIFq3Lhxlvc9aNAg/fbbb/bglpNeeOEFDR061P7+7NmzqlChgjp06JCtAHq9SE5O1vLly9W+fXv5+/vndjke2bFjh1q2bKk1kuqm0+ZLSY9KWW6THBys5dOmqf3DD8s/IcHj/eRmm5z+vKv7aIeklpLWrFmjunUzqip/uB7+jOU0+ihj9E/G8nr/pF3xc0e2w9uVChUqpAIFCsjPz0/GGKWkpGjmzJn67LPPdNttt2n69OkqVaqUW/saPHiwvv/+e61Zs0bly5e3Ly9durQuXryoM2fOqEiRIvblx44dU+nSpe1tNm/e7LC/tKdR09pcLTAwUIGBgU7L/f398+QPObdYuT98fHyUkJAgH0kZHUGC5HEb/4QE+SckZHs/udXmWnxeWh/5pLXx8bHsdyonWPnP2LVCH2WM/slYXu2frNSU7XHezp8/r08++USNGzfWzTffrI8++kjVq1fXp59+qlOnTmnz5s2655579MMPP+ixxx7LdH/GGA0ePFjffvutfvrpJ1WuXNlhfYMGDeTv768VK1bYl0VHRysmJkaRkZGSpMjISO3cuVPHjx+3t1m+fLlCQ0NVs2bN7B4yAABArvH4zNvGjRs1ZcoUffXVVzp//rwKFiyo/v3767HHHlO9evXs7Ro2bKgvvvhCAQEBWrBgQab7HTRokObMmaP58+erUKFCOnr0qCSpcOHCCg4OVuHChdWvXz8NHTpUxYoVU2hoqJ544glFRkaqadOmkqQOHTqoZs2a6tWrlyZOnKijR49qxIgRGjRokMuzawAAAFbhUXirXbu2du/eLWOMbr75Zj322GN68MEHVbBgwXS3qVWrlmbPnp3pvj/++GNJUuvWrR2WT58+XX369JEkvfPOO/Lx8VG3bt2UlJSkjh076qOPPrK39fX11ffff6+BAwcqMjJSISEh6t27t8aNG5f1gwUAAMhDPApvBw4cUN++ffXYY4+pUaNGbm3To0cP+2XNjLjz8GtQUJAmTZqkSZMmpdumYsWKWrx4sVu1AQAAWIVH4e3IkSNZfgKzQoUKqlChgicfBwAAgH959MBCSEiIzp49q9TUVJfrU1NTdfbsWaWkpGSrOAAAADjyKLyNHTtWpUqV0smTJ12uP3nypMLCwvTKK69kqzgAAAA48ii8ff/997r11ltVsmRJl+tLliypdu3aaf78+dkqDgAAAI48Cm8HDhzQjTfemGGbGjVq6ODBgx4VBQAAANc8Cm/Jycny8cl4U5vNpsTERI+KAgAAgGsehbeqVavqp59+yrCNq9kRAAAAkD0ehbe7775b27dv16hRo5yeKE1JSdHIkSO1fft23XvvvV4pEgAAAJd5NM7bsGHDNHfuXL3yyiuaO3eu2rRpo3Llyunvv//WypUrtX//fkVEROiZZ57xdr0AAAD5mkfhrWDBglqzZo0GDhyob7/9Vvv27bOv8/Hx0T333KOPPvoow+myAAAAkHUeT0xfsmRJzZs3T8eOHdPWrVsVFxenIkWKqGHDhipVqpQ3awQAAMC/PA5vacLCwtSlSxdv1AIAAIBMePTAAgAAAHKHx2fedu/erQ8//FBbtmzRmTNnXM5jarPZtH///mwVCAAAgP/xKLytXr1at912m5KSkuTn56ewsDD5+TnvyhiT7QIBAADwPx6Ft+HDh+vSpUuaOnWqevfuLV9fX2/XBWRZTEyMYmNj012/Z8+ea1gNAAA5w6PwtmPHDnXv3l0PP/ywt+sBPBITE6OIGjUUz5RsAIDrnEfhLSQkhOFAkKfExsYqPjFRsyRFpNNmsaSR17AmAABygkfhrXPnzlq7dq23awGyLUJS/XTWcdEUAHA98GiokDfeeENnzpzRk08+qfj4eG/XBAAAgHR4dOate/fuKliwoCZNmqQZM2aoevXqCg0NdWpns9m0YsWKbBcJAACAyzwKb6tWrbL///nz5/XLL7+4bGez2TwqCgAAAK55FN5SU1O9XQcAAADcwPRYAAAAFpLtienPnz+vP/74QxcuXFCLFi28URMAAADS4fGZt0OHDqlr164qWrSoGjVqpDZt2tjX/fzzz6pZs6bDvXEAAADIPo/CW0xMjJo2barFixera9euioyMdJjHtEmTJoqNjdXnn3/utUIBAADgYXgbPXq0Tp8+rdWrV2vevHlq3769w3o/Pz+1aNFCP//8s1eKBAAAwGUehbelS5fqrrvuUrNmzdJtU7FiRf39998eFwYAAABnHoW3U6dOqVKlShm2McYoKSnJk90DAAAgHR6Ft7CwMO3duzfDNjt37lR4eLhHRQEAAMA1j4YKad++vf7v//5Pv/76q+rUqeO0fu3atfrpp5/09NNPZ7c+ANe5PXv2ZLi+RIkS/EMQAK7gUXgbMWKE5s2bp5YtW+rZZ5/Vvn37JEk//PCD1q9fr7ffflslSpTQs88+69ViAVw/jujyqf+ePXtm2K5AUJD2REcT4ADgXx6Ft0qVKmnp0qXq3r27Ro4cKZvNJmOM/vOf/8gYo/DwcM2bN09lypTxdr0ArhNnJKVKmiUpIp02eyT1TExUbGws4Q0A/uXxDAtNmjTR3r17tXDhQm3atEmnTp1SaGiomjRpoq5duyogIMCbdQK4TkVIqp/bRQCAhWRreiw/Pz/ddddduuuuu7xVDwAAADLAxPQAAAAW4tGZt3HjxrnVzmazaeTIkZ58BAAAAFzwKLyNGTMmw/VpDzAQ3gAAALzLo/C2cuVKl8vj4uL0yy+/6P3331e7du00aNCgbBUHAAAARx6Ft1atWqW77o477lCPHj1Uv359devWzePCAAAA4CxHHlioVq2a7rrrLk2YMCEndg8AAJBv5djTpqVKlVJ0dHRO7R4AACBfypHwlpSUpCVLlqhIkSI5sXsAAIB8y6N73j777DOXyy9duqS///5bc+fO1e+//64nn3wyW8UBAADAkUfhrU+fPrLZbE7LjTGSLg8V8sADD3DPGwAAgJd5FN6mT5/ucrmPj4+KFi2qBg0aMCk9AABADvAovPXu3dvbdQCAx2JiYhQbG5thmxIlSig8PPwaVQQAOSdbE9MDQG6LiYlRRI0aik9MzLBdgaAg7YmOJsABsDyPwtuaNWs8/sCWLVt6vC0AXC02NlbxiYmaJSkinTZ7JPVMTFRsbCzhDYDleRTeWrdu7fKBBXekpKR4tB0AZCRCUv3cLgIArgGPwtuoUaO0adMmLV26VNWqVVPz5s0VFhamY8eOaf369frjjz/UsWNHNW3a1Nv1AgAA5Gsehbdbb71VEyZM0CeffKJ+/fo5nIUzxmjKlCl66qmn9NJLL+mWW27xWrEAAAD5nUczLIwcOVJdunTRI4884nT51GazqX///urUqZNGjhzplSIBAABwmUfhLSoqShER6d0afFlERIS2bt3qUVEAAABwzaPwFhAQoG3btmXYZtu2bQoICPCoKAAAALjmUXjr0KGDlixZogkTJujixYsO6y5evKjXXntNS5cuVceOHb1SJAAAAC7z6IGFN954Q2vXrtVLL72k9957Tw0bNlSpUqV0/Phxbd26VcePH1fZsmU1ceJEb9cLAACQr3kU3sqXL6+tW7dq+PDh+vLLL7Vo0SL7uqCgIPXq1UsTJkxQ6dKlvVYoAAAAsjE9VunSpTVjxgxNmTJF0dHRiouLU+HChVW9enXudQMAAMgh2Z7b1N/fXzfddJM3agEAl/bs2ePROgC4HmUrvB09elTffPONfv/9d8XHx2vq1KmSpBMnTujgwYOqXbu2goODvVIogPzniC4/VdWzZ8/cLgUA8gyPw9tHH32kYcOGKSkpSdLlwXnTwtvx48cVGRmpyZMn69FHH/VOpQDynTOSUqUMJ51fLInhwAHkJx4NFbJw4UINHjxYtWvX1oIFCzRw4ECH9bVq1VKdOnX03XffeaNGAPlc2qTzrl6Vc7EuAMgNHg8VEh4erpUrVyokJERRUVFObWrXrq21a9dmu0AAAAD8j0dn3rZv364uXbooJCQk3TblypXTsWPHPC4MAAAAzjwKb6mpqfL398+wzfHjxxUYGOhRUQAAAHDNo/BWo0aNDC+JXrp0SWvWrFHt2rU9LgwAAADOPApvPXr00LZt2zR27FindSkpKXrmmWd04MABPfTQQ9kuEAAAAP/j0QMLTzzxhBYuXKhx48Zp9uzZCgoKkiTdd9992rp1qw4dOqQOHTqoX79+Xi0WAAAgv/PozJu/v7+WLl2q4cOH6+TJk/rtt99kjNG8efN06tQpPf/881qwYIFsNpu36wUAAMjXPB6kNyAgQK+88orGjx+v6OhonTp1SqGhoYqIiJCvr683awQAAMC/PApvN9xwgzp16qRJkybJZrPpxhtv9HZdAAAAcMGjy6axsbEKDQ31di0AAADIhEfhrU6dOvrjjz+8XQsAAAAy4dFl0+eff17dunXTypUr1aZNG2/XBDiJiYlRbGxsuuv37NlzDasBACD3eBTeTp8+rQ4dOqhDhw6688471ahRI4WFhbl8upSx3pBdMTExiqhRQ/GJibldCgAAuc6j8NanTx/ZbDYZY/T111/r66+/liSH8GaMkc1mI7wh22JjYxWfmKhZkiLSabNY0shrWBMAALnFo/A2ffp0b9cBZCpCUv101nHRFACQX7gd3s6ePaugoCAFBASod+/eOVkTAAAA0uH206ZFixbV66+/7rBs8+bNev/9971eFAAAAFxz+8ybMUbGGIdlP/zwg8aNG6cnn3zS64UBgLdl9lRyiRIlFB4efo2qAQDPeDw9FgBYxRFdvszQs2fPDNsVCArSnuhoAhyAPI3wBuC6d0ZSqpThE8t7JPVMTFRsbCzhDUCeRngDkG9k9MQyAFiFR9NjAQAAIHdk6czbrFmztHHjRvv7ffv2SZI6d+7ssr3NZtOiRYuyUR4AAACulKXwtm/fPntgu9KSJUtctnc1XRYAAAA853Z4O3jwYE7WAQAAADe4Hd4qVqyYk3UAAADADTywAAAAYCGENwAAAAshvAEAAFgI4Q0AAMBCCG8AAAAWQngDAACwEMIbAACAhRDeAAAALITwBgAAYCGENwAAAAshvAEAAFhIngtva9as0e23366yZcvKZrPpu+++c1hvjNGoUaNUpkwZBQcHq127dtq7d69Dm1OnTqlHjx4KDQ1VkSJF1K9fP50/f/4aHgUAAEDOyHPh7cKFC6pbt64mTZrkcv3EiRP1/vvva/Lkydq0aZNCQkLUsWNHJSYm2tv06NFDu3bt0vLly/X9999rzZo16t+//7U6BAAAgBzjl9sFXK1Tp07q1KmTy3XGGL377rsaMWKEunbtKkn67LPPFBYWpu+++07du3fXnj17tGTJEm3ZskUNGzaUJH3wwQfq3Lmz3nzzTZUtW/aaHQsAAIC35bnwlpGDBw/q6NGjateunX1Z4cKF1aRJE23YsEHdu3fXhg0bVKRIEXtwk6R27drJx8dHmzZt0l133eVy30lJSUpKSrK/P3v2rCQpOTlZycnJOXRE1pHWB7nRF6mpqQoODlaqpIw+PVjKtTbJwcEO/83tejxtk5Ofl9f7KDWtTWpqrnzPc/PPmFXQRxmjfzKW1/snK3XZjDEmB2vJFpvNpm+//VZ33nmnJGn9+vVq3ry5/vnnH5UpU8be7r777pPNZtMXX3yhV199VTNnzlR0dLTDvkqVKqWxY8dq4MCBLj9rzJgxGjt2rNPyOXPmqECBAt47KAAAgKvEx8frwQcfVFxcnEJDQzNsa6kzbznphRde0NChQ+3vz549qwoVKqhDhw6ZdmJ+kJycrOXLl6t9+/by9/e/pp+9Y8cOtWzZUmsk1U2nzZeSHpVyrU1ycLCWT5um9g8/LP+EhFyvx5M2Of15eb2PdkhqqcsPTdWtm1Ev5Yzc/DNmFfRRxuifjOX1/km74ucOS4W30qVLS5KOHTvmcObt2LFjqlevnr3N8ePHHba7dOmSTp06Zd/elcDAQAUGBjot9/f3z5M/5NySG/3h4+OjhIQE+UjK6JMTpFxv45+QIP+EhDxTT1bbXIvPy6t95JPWxscnV//M8zsnc/RRxuifjOXV/slKTXnuadOMVK5cWaVLl9aKFSvsy86ePatNmzYpMjJSkhQZGakzZ84oKirK3uann35SamqqmjRpcs1rBgAA8KY8d+bt/Pnz2rdvn/39wYMHtX37dhUrVkzh4eF6+umnNX78eFWrVk2VK1fWyJEjVbZsWft9cREREbrtttv06KOPavLkyUpOTtbgwYPVvXt3njQFAACWl+fC29atW9WmTRv7+7T70Hr37q0ZM2boueee04ULF9S/f3+dOXNGt9xyi5YsWaKgoCD7NrNnz9bgwYN16623ysfHR926ddP7779/zY8FAADA2/JceGvdurUyegDWZrNp3LhxGjduXLptihUrpjlz5uREeQAAALnKUve8AQAA5HeENwAAAAshvAEAAFgI4Q0AAMBCCG8AAAAWkueeNgWA3LRnz54M15coUULh4eHXqBoAcEZ4AwBJR3T5UkTPnj0zbFcgKEh7oqMJcAByDeENACSdkZQqaZakiHTa7JHUMzFRsbGxhDcAuYbwBgBXiJBUP7eLAIAM8MACAACAhRDeAAAALITwBgAAYCGENwAAAAvhgQUAyCLGggOQmwhvAOAmxoIDkBcQ3gDATWfEWHAAch/hDQCyiLHgAOQmwhtyXUxMjGJjY9Ndn9n9RQAA5CeEN+SqmJgYRdSoofjExNwuBQAASyC8IVfFxsYqPjExw3uIFksaeQ1rAgAgLyO8IU/I6B4iLpoCAPA/DNILAABgIYQ3AAAACyG8AQAAWAjhDQAAwEIIbwAAABZCeAMAALAQwhsAAICFEN4AAAAshPAGAABgIYQ3AAAACyG8AQAAWAjhDQAAwEIIbwAAABZCeAMAALAQwhsAAICFEN4AAAAshPAGAABgIYQ3AAAACyG8AQAAWAjhDQAAwEIIbwAAABZCeAMAALAQwhsAAICF+OV2Abi+xcTEKDY2Nt31e/bsuYbVAABgfYQ35JiYmBhF1Kih+MTE3C4FAIDrBuENOSY2NlbxiYmaJSkinTaLJY28hjUBAGB1hDfkuAhJ9dNZx0VTAACyhvAGADkgs/s5S5QoofDw8GtUDYDrCeENALzoiC4/xt+zZ88M2wUFBmre11+rTJkykqTU1FRJ0o4dO+Tjc3kgAAIeAFcIbwDgRWckpUoZ3uu5VtLQpCT95z//sS8LDg7W559/rpYtWyohIUGSVCAoSHuiowlwABwQ3gAgB2R2r+fVAS9V0t+S1ujymbs9knomJio2NpbwBsAB4Q0AcsmVAS9Zl8NbXUn+uVYRACtghgUAAAALIbwBAABYCOENAADAQghvAAAAFkJ4AwAAsBDCGwAAgIUQ3gAAACyEcd4AIA9jjlQAVyO8AUAe5O4cqUyhBeQ/hDcAyIPOKPM5UplCC8ifCG8AkIdlNEcqgPyJBxYAAAAshPAGAABgIYQ3AAAAC+GeNwCAYmJiFBsbm2EbhiUB8gbCGwBYXHbHgouJiVFEjRqKT0zMcD8MSwLkDYQ3ALAob40FFxsbq/jERIYlASyC8AYAFnVG3h0LjmFJAGsgvAGAxRG6gPyF8AaPZXaDc2b34QAAgKwjvMEj7t7gDAAAvIvwBo+4c4PzYkkjr2FNAADkB4Q3ZEtG99pw0RQAAO9jhgUAAAALIbwBAABYCOENAADAQghvAAAAFsIDCwAAt2V3HlUA2Ud4A4B8IKPQ5c6A2t6aRxVA9hHeAOA65m7oyswZeXceVQCeI7wBwHXsjDIPXVkZUJt5VIHcR3gDgHyAAbWB6wfhDS5dPel8amqqJGnHjh3y8fFh0nkAAHIJ4Q1OXE06HxwcrM8//1wtW7ZUQkJCLlYHAED+RnjLh64+q3a1PXv2OE06nyrpb0lrdPnmZyadBwAgdxDe8hlXZ9XSc+U9Msm6HN7qSvIX98gAsIbM/rEqMTYdrIfwls/ExsY6nVW7GmfVAGSHNwbydRW6rr73NrP9uPuPVcamg9UQ3vIpnjwD4G3eGsg3vdB19b23me3HnX+sMjYdrIjwBgDwijPyzkC+6YWuK++9jXZjP2kYmw7XG8IbAMCrvBWWrt7Plffe+nhh/4BVEd6uM+48SQoA14vsztkKWBHh7TqSlSdJASA3ZTd0eWvOVsCKCG/XEZ4kBZDXeSt0nZF352wFrITwdh3iSVIAedUZeTd08fsO+RHhDQBwzeW10JXZpdqkpCQFBgZm2IbBfnGtEN4AAPmWu5dxfSWlZLIvdwf7ZdYHZBfhDQCQb52R+5dxvTHYL7M+wBsIbwCAfM+dy7jeGL+OWR/gDdd1eJs0aZLeeOMNHT16VHXr1tUHH3ygxo0b53ZZAIB8zhtB8OrLr1fP/Spd28uv3roczGXlzF234e2LL77Q0KFDNXnyZDVp0kTvvvuuOnbsqOjoaJUqVSq3ywMAwGOuLr9ePferdO0uv3rrcjCXld1z3Ya3t99+W48++qj69u0rSZo8ebIWLVqkadOmafjw4W7vZ/v27SpYsGC666/lvyKYPQEA8rbMfg9n5fd0ZgMZX3359cq5X330v8uva9euVUREehdp3XuSNrM2rupxauNGPd7aj5T3zs5l9nf4+fPn3d7XdRneLl68qKioKL3wwgv2ZT4+PmrXrp02bNjgcpukpCQlJSXZ38fFxUmSOnToIJvNlu5nBQcGavInn6R7Nu/48eMa0L+/Eq7Yd07uJygoSFGSzqazPlpSkJTlNqlBQYqPj9faoCD5GOPxfq7XNtdD/+T0510PfUT/5G6bK/so2phcr+dKmyUVkPTII4+k0+J/Mvs97e6+goKCFC/n79DZf79Df7u5H3eepHWnzdX1XM3dery1n6v/Xk1NTb38/Vm71n5Z2cfHx365OT3eaOPO3+HGGIf/Zshch/7++28jyaxfv95h+bPPPmsaN27scpvRo0cbSbx48eLFixcvXrn2+uuvvzLNOdflmTdPvPDCCxo6dKj9fWpqqk6dOqXixYtneOYtvzh79qwqVKigv/76S6GhobldTp5D/2SOPsoY/ZM5+ihj9E/G8nr/GGN07tw5lS1bNtO212V4K1GihHx9fXXs2DGH5ceOHVPp0qVdbhMYGOh0Pb9IkSI5VaJlhYaG5skvfV5B/2SOPsoY/ZM5+ihj9E/G8nL/FC5c2K12PjlcR64ICAhQgwYNtGLFCvuy1NRUrVixQpGRkblYGQAAQPZcl2feJGno0KHq3bu3GjZsqMaNG+vdd9/VhQsX7E+fAgAAWNF1G97uv/9+nThxQqNGjdLRo0dVr149LVmyRGFhYbldmiUFBgZq9OjRmT5Onl/RP5mjjzJG/2SOPsoY/ZOx66l/bMa480wqAAAA8oLr8p43AACA6xXhDQAAwEIIbwAAABZCeAMAALAQwtt1YNKkSapUqZKCgoLUpEkTbd68OcP2X331lW688UYFBQWpdu3aWrx4scN6Y4xGjRqlMmXKKDg4WO3atdPevXsd2pw6dUo9evRQaGioihQpon79+jlMqpuYmKg+ffqodu3a8vPz05133umylqSkJL300kuqWLGiAgMDValSJU2bNs2zjsiAlfto9uzZqlu3rgoUKKAyZcro4Ycf1smTJz3riHTkxf5ZtWqVunbtqjJlyigkJET16tXT7Nmzs1yLt1i1j6ZMmaIWLVqoaNGiKlq0qNq1a5dp7Z6wav9cae7cubLZbOn+WcwOK/fPmTNnNGjQIJUpU0aBgYGqXr16jvw5s3Ifvfvuu6pRo4aCg4NVoUIFDRkyRImJidnojUxkYwpR5AFz5841AQEBZtq0aWbXrl3m0UcfNUWKFDHHjh1z2f7nn382vr6+ZuLEiWb37t1mxIgRxt/f3+zcudPeZsKECaZw4cLmu+++Mzt27DB33HGHqVy5sklISLC3ue2220zdunXNxo0bzdq1a03VqlXNAw88YF9//vx5M2DAAPPJJ5+Yjh07mq5du7qs54477jBNmjQxy5cvNwcPHjTr168369at807n/MvKfbRu3Trj4+Nj3nvvPXPgwAGzdu1aU6tWLXPXXXdd9/3zyiuvmBEjRpiff/7Z7Nu3z7z77rvGx8fHLFy4MEu15Pc+evDBB82kSZPMtm3bzJ49e0yfPn1M4cKFzeHDh+mfKxw8eNCUK1fOtGjRIt3fV56ycv8kJSWZhg0bms6dO5t169aZgwcPmlWrVpnt27fTR/+aPXu2CQwMNLNnzzYHDx40S5cuNWXKlDFDhgzxah9difBmcY0bNzaDBg2yv09JSTFly5Y1r732msv29913n+nSpYvDsiZNmpjHHnvMGGNMamqqKV26tHnjjTfs68+cOWMCAwPN559/bowxZvfu3UaS2bJli73NDz/8YGw2m/n777+dPrN3794ufxn+8MMPpnDhwubkyZPuH7AHrNxHb7zxhrnhhhsclr3//vumXLlymRy1+6zQP2k6d+5s+vbt63Yt3mLlPrrapUuXTKFChczMmTMzOOKssXr/XLp0yTRr1sxMnTo13T+L2WHl/vn444/NDTfcYC5evJiFI846K/fRoEGDTNu2bR3aDB061DRv3jyzw/YYl00t7OLFi4qKilK7du3sy3x8fNSuXTtt2LDB5TYbNmxwaC9JHTt2tLc/ePCgjh496tCmcOHCatKkib3Nhg0bVKRIETVs2NDepl27dvLx8dGmTZvcrn/BggVq2LChJk6cqHLlyql69ep65plnlJCQ4PY+MmP1PoqMjNRff/2lxYsXyxijY8eOad68eercubPb+8iI1fonLi5OxYoVc7sWb7B6H10tPj5eycnJGbbJiuuhf8aNG6dSpUqpX79+bh61+6zePwsWLFBkZKQGDRqksLAw3XTTTXr11VeVkpKShV7ImNX7qFmzZoqKirJf5j1w4IAWL17std/Trly3MyzkB7GxsUpJSXGaNSIsLEy///67y22OHj3qsv3Ro0ft69OWZdSmVKlSDuv9/PxUrFgxext3HDhwQOvWrVNQUJC+/fZbxcbG6vHHH9fJkyc1ffp0t/eTEav3UfPmzTV79mzdf//9SkxM1KVLl3T77bdr0qRJbu8jI1bqny+//FJbtmzRf//7X7dr8Qar99HVnn/+eZUtW9bpLz5PWb1/1q1bp08//VTbt2/P5Eg9Y/X+OXDggH766Sf16NFDixcv1r59+/T4448rOTlZo0ePzuzw3WL1PnrwwQcVGxurW265RcYYXbp0SQMGDNCLL76Y2aF7jDNvyDWpqamy2WyaPXu2GjdurM6dO+vtt9/WzJkzvXr2zcp2796tp556SqNGjVJUVJSWLFmiQ4cOacCAAbld2jW1cuVK9e3bV1OmTFGtWrVyu5w8yZ0+mjBhgubOnatvv/1WQUFB17jC3OWqf86dO6devXppypQpKlGiRC5XmLvS+/6kpqaqVKlS+uSTT9SgQQPdf//9eumllzR58uRcrDZ3pNdHq1at0quvvqqPPvpIv/zyi7755hstWrRIL7/8co7VQnizsBIlSsjX11fHjh1zWH7s2DGVLl3a5TalS5fOsH3afzNrc/z4cYf1ly5d0qlTp9L9XFfKlCmjcuXKqXDhwvZlERERMsbo8OHDbu8nI1bvo9dee03NmzfXs88+qzp16qhjx4766KOPNG3aNB05csTt/aTHCv2zevVq3X777XrnnXf00EMPZakWb7B6H6V58803NWHCBC1btkx16tTJ6JCzxMr9s3//fh06dEi33367/Pz85Ofnp88++0wLFiyQn5+f9u/f7243pMvK/SNd/j1dvXp1+fr62pdFRETo6NGjunjxYobH7i6r99HIkSPVq1cvPfLII6pdu7buuusuvfrqq3rttdeUmprqThdkGeHNwgICAtSgQQOtWLHCviw1NVUrVqxQZGSky20iIyMd2kvS8uXL7e0rV66s0qVLO7Q5e/asNm3aZG8TGRmpM2fOKCoqyt7mp59+Umpqqpo0aeJ2/c2bN9c///zj8Fj2H3/8IR8fH5UvX97t/WTE6n0UHx8vHx/HP6Zpv0SNF6Ylzuv9s2rVKnXp0kWvv/66+vfvn+VavMHqfSRJEydO1Msvv6wlS5Y43N/jDVbunxtvvFE7d+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": [
+ "