diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json new file mode 100644 index 00000000..1374640e --- /dev/null +++ b/.devcontainer/devcontainer.json @@ -0,0 +1,18 @@ +{ + "name": "Codespace for dingo tutorial", + "build": { + "dockerfile": "../Dockerfile", + "context": ".." + }, + "customizations": { + "vscode": { + "extensions": [ + "ms-python.python", + "ms-toolsai.jupyter", + "james-yu.latex-workshop" + ] + } + }, + "features": {}, + "postCreateCommand": "bash .devcontainer/post_create.sh" +} \ No newline at end of file diff --git a/.devcontainer/post_create.sh b/.devcontainer/post_create.sh new file mode 100644 index 00000000..460ea05a --- /dev/null +++ b/.devcontainer/post_create.sh @@ -0,0 +1,20 @@ +#!/bin/bash +set -e # stop on any error + +apt-get update +apt-get install -y \ + libnss3 \ + libatk-bridge2.0-0 \ + libcups2 \ + libxcomposite1 \ + libxdamage1 \ + libxfixes3 \ + libxrandr2 \ + libgbm1 \ + libxkbcommon0 \ + libpango-1.0-0 \ + libcairo2 \ + libasound2 + +pip install --upgrade nbformat kaleido +echo y | plotly_get_chrome \ No newline at end of file diff --git a/.gitignore b/.gitignore index 23c482eb..4c49c9cf 100644 --- a/.gitignore +++ b/.gitignore @@ -11,5 +11,4 @@ volestipy.egg-info .vscode venv lp_solve_5.5/ -.devcontainer/ .github/dependabot.yml \ No newline at end of file diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 00000000..186a9285 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,52 @@ +# Use the Gurobi Docker image as a base +FROM gurobi/python + +# Install any additional dependencies for dingo +RUN apt-get update && apt-get install -y \ + cmake \ + lp-solve \ + git \ + wget \ + vim \ + bzip2 \ + g++ \ + && rm -rf /var/lib/apt/lists/* + +# Install dependencies +RUN apt-get update && apt-get install -y libsuitesparse-dev +RUN pip install sparseqr \ + Cython \ + cobra \ + kaleido + +# Get PySQR +RUN apt-get install libsuitesparse-dev + +# Install Python dependencies +RUN pip install matplotlib \ + plotly \ + networkx \ + pyoptinterface[highs] + +# Get dingo +WORKDIR /workspaces/dingo +COPY . . + +# Get submodules +RUN git submodule update --init + +# Get lp-solve +RUN wget https://sourceforge.net/projects/lpsolve/files/lpsolve/5.5.2.11/lp_solve_5.5.2.11_source.tar.gz &&\ + tar xzvf lp_solve_5.5.2.11_source.tar.gz &&\ + rm lp_solve_5.5.2.11_source.tar.gz + +# Get boost library +RUN wget -O boost_1_76_0.tar.bz2 https://archives.boost.io/release/1.76.0/source/boost_1_76_0.tar.bz2 &&\ + tar xjf boost_1_76_0.tar.bz2 &&\ + rm boost_1_76_0.tar.bz2 + +# Set environmental variable gurobi license path +ENV GRB_LICENSE_FILE=/opt/gurobi/gurobi.lic + +# Install dingo +RUN ["python", "setup.py", "install", "--user"] diff --git a/README.md b/README.md index 274f814f..6d2fb3ba 100644 --- a/README.md +++ b/README.md @@ -11,44 +11,80 @@ metabolic network, namely Flux Balance Analysis and Flux Variability Analysis. `dingo` is part of [GeomScale](https://geomscale.github.io/) project. -[![unit-tests](https://github.com/GeomScale/dingo/workflows/dingo-ubuntu/badge.svg)](https://github.com/GeomScale/dingo/actions?query=workflow%3Adingo-ubuntu) -[![Tutorial In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/GeomScale/dingo/blob/develop/tutorials/dingo_tutorial.ipynb) -[![Chat](https://badges.gitter.im/geomscale.png)](https://gitter.im/GeomScale/community?utm_source=share-link&utm_medium=link&utm_campaign=share-link) + + + + + + + + + -## Installation +## Installation + +### LP solver (optional, probably better performance) + +`dingo` makes use of [`pyoptinterface`](https://metab0t.github.io/PyOptInterface/) to interface with a range of linear programming solvers. + +The default solver is [`highs`](https://highs.dev/#get-started). + +However, one may switch to other solvers that `pyoptinterface` supports, for example the commonly used [`gurobi`](https://www.gurobi.com/). +Yet, in that case a Gurobi license is required. + +> **Get a Gurobi license** +> +> If you are affiliated in an academic insitute, you can generate a **free academic license**. +> +> First, register and/or login to your [Gurobi account](https://portal.gurobi.com/iam/login/), and +> +> * if you are about to use `dingo` as a container, get a [**Web License Service (WLS) academic license**](https://support.gurobi.com/hc/en-us/articles/13210193318033-What-is-an-Academic-WLS-license) +> * otherwise, you should go for the typical [**free academic license**](https://www.gurobi.com/academics) +> +> 🔴 In both cases, make sure you are connected to the internet of an academic institution. + + +### Installation (on Linux) **Note:** Python version should be 3.8.x. You can check this by running the following command in your terminal: ```bash python --version ``` -If you have a different version of Python installed, you'll need to install it ([start here](https://linuxize.com/post/how-to-install-python-3-8-on-ubuntu-18-04/)) and update-alternatives ([start here](https://linuxhint.com/update_alternatives_ubuntu/)) -**Note:** If you are using `GitHub Codespaces`. Start [here](https://docs.github.com/en/codespaces/setting-up-your-project-for-codespaces/adding-a-dev-container-configuration/setting-up-your-python-project-for-codespaces) to set the python version. Once your Python version is `3.8.x` you can start following the below instructions. +If you have a different version of Python installed, you'll need to install it ([start here](https://linuxize.com/post/how-to-install-python-3-8-on-ubuntu-18-04/)) +and update-alternatives ([start here](https://linuxhint.com/update_alternatives_ubuntu/)). + +Clone the `dingo` repo by +``` +git clone https://github.com/GeomScale/dingo.git +``` -To load the submodules that dingo uses, run +and load the submodules that `dingo` uses: ````bash +cd dingo git submodule update --init ```` -You will need to download and unzip the Boost library: +You will then need to download and unzip the [Boost C++](https://www.boost.org/) library: ``` wget -O boost_1_76_0.tar.bz2 https://archives.boost.io/release/1.76.0/source/boost_1_76_0.tar.bz2 tar xjf boost_1_76_0.tar.bz2 rm boost_1_76_0.tar.bz2 ``` -You will also need to download and unzip the lpsolve library: +You will also need to download and unzip the [`lpsolve`](https://lpsolve.sourceforge.net/5.5/) library: ``` wget https://sourceforge.net/projects/lpsolve/files/lpsolve/5.5.2.11/lp_solve_5.5.2.11_source.tar.gz tar xzvf lp_solve_5.5.2.11_source.tar.gz rm lp_solve_5.5.2.11_source.tar.gz ``` -Then, you need to install the dependencies for the PySPQR library; for Debian/Ubuntu Linux, run +Then, you need to install the dependencies for the [PySPQR](https://github.com/yig/PySPQR) library; +for this, you will most likely need `sudo` rights: ```bash sudo apt-get update -y @@ -57,19 +93,63 @@ sudo apt-get install -y libsuitesparse-dev To install the Python dependencies, `dingo` is using [Poetry](https://python-poetry.org/), ``` -curl -sSL https://install.python-poetry.org | python3 - --version 1.3.2 +curl -sSL https://install.python-poetry.org | python - --version 1.3.2 poetry shell poetry install ``` -You can install the [Gurobi solver](https://www.gurobi.com/) for faster linear programming optimization. Run +otherwise, you may try: + +``` +python setup.py install --user +``` + + +Last, in case you are about to use Gurobi, remember to install the Python interface of Gurobi, [`gurobipy`](https://www.gurobi.com/resources/faq/gurobipy): + +``` +pip install -i https://pypi.gurobi.com gurobipy +``` + + + +## Using `dingo` as a Docker container + +To use `dingo` as a container, you need to [install Docker](https://docs.docker.com/engine/install/), +or [Docker desktop](https://docs.docker.com/desktop/), first. + +Then you can clone the `dingo` repo and build its Docker image: + +``` +git clone https://github.com/GeomScale/dingo.git +cd dingo +docker build -f Dockerfile -t dingo . +``` + +Once the image is built, you may run: ``` -pip3 install -i https://pypi.gurobi.com gurobipy +docker run --rm -it -v :/data dingo ``` -Then, you will need a [license](https://www.gurobi.com/downloads/end-user-license-agreement-academic/). For more information, we refer to the Gurobi [download center](https://www.gurobi.com/downloads/). +or, if you are using Gurobi, you may run: + +``` +docker run --rm -it -v :/opt/gurobi/gurobi.lic -v :/data dingo +``` +> **Remember!** in this case, where `dingo` is run in a containerized environment and Gurobi is used as the solver, a standard node-locked Gurobi license would not work; a WLS license is typically required instead. +> +> This would look something like this: +> +> ``` +> # Gurobi WLS license file +> # Your credentials are private and should not be shared or copied to public repositories. +> # Visit https://license.gurobi.com/manager/doc/overview for more information. +> WLSACCESSID=d5419c87-0d36-4a93-9385-773f5483b3c1 +> WLSSECRET=afa5d95f-ad0b-4a38-9550-a8913aacb7c0 +> LICENSEID=000000 +>``` @@ -77,28 +157,33 @@ Then, you will need a [license](https://www.gurobi.com/downloads/end-user-licens Now, you can run the unit tests by the following commands (with the default solver `highs`): ``` -python3 tests/fba.py -python3 tests/full_dimensional.py -python3 tests/max_ball.py -python3 tests/scaling.py -python3 tests/rounding.py -python3 tests/sampling.py +python tests/fba.py +python tests/full_dimensional.py +python tests/max_ball.py +python tests/scaling.py +python tests/rounding.py +python tests/sampling.py ``` -If you have installed Gurobi successfully, then run +Or, assuming you have installed Gurobi successfully, or an other `pyoptinterface`-supported solver, you may run: ``` -python3 tests/fba.py gurobi -python3 tests/full_dimensional.py gurobi -python3 tests/max_ball.py gurobi -python3 tests/scaling.py gurobi -python3 tests/rounding.py gurobi -python3 tests/sampling.py gurobi +python tests/fba.py gurobi +python tests/full_dimensional.py gurobi +python tests/max_ball.py gurobi +python tests/scaling.py gurobi +python tests/rounding.py gurobi +python tests/sampling.py gurobi ``` ## Tutorial -You can have a look at our [Google Colab notebook](https://colab.research.google.com/github/GeomScale/dingo/blob/develop/tutorials/dingo_tutorial.ipynb) -on how to use `dingo`. +You may check out `dingo`'s main features through a GitHub codespace. +To do this, you may click [here](https://github.com/codespaces/new?repo=GeomScale/dingo&ref=main) and fire a new codespace +by clicking on the "Create codespace" button. + +This will take a few minutes (~5'). + +Once the codespace is ready, you may try to follow the [`dingo_tutorial`](./tutorials/dingo_tutorial.ipynb) Jupyter notebook. ## Documentation @@ -191,14 +276,17 @@ The MCMC methods that dingo (through `volesti` library) provides are the followi #### Switch the linear programming solver -We use `pyoptinterface` to interface with the linear programming solvers. To switch the solver that `dingo` uses, you can use the `set_default_solver` function. The default solver is `highs` and you can switch to `gurobi` by running, +We use `pyoptinterface` to interface with the linear programming solvers. +To switch the solver that `dingo` uses, you can use the `set_default_solver` function. +The default solver is `highs` and you can switch to `gurobi` by running: ```python from dingo import set_default_solver set_default_solver("gurobi") ``` -You can also switch to other solvers that `pyoptinterface` supports, but we recommend using `highs` or `gurobi`. If you have issues with the solver, you can check the `pyoptinterface` [documentation](https://metab0t.github.io/PyOptInterface/getting_started.html). +You can also switch to other solvers that `pyoptinterface` supports, but we recommend using `highs` or `gurobi`. +If you have issues with the solver, you can check the `pyoptinterface` [documentation](https://metab0t.github.io/PyOptInterface/getting_started.html). ### Apply FBA and FVA methods diff --git a/dingo/illustrations.py b/dingo/illustrations.py index fd37b651..f2b64e2b 100644 --- a/dingo/illustrations.py +++ b/dingo/illustrations.py @@ -14,7 +14,7 @@ import plotly.figure_factory as ff from scipy.cluster import hierarchy -def plot_copula(data_flux1, data_flux2, n = 5, width = 900 , height = 600, export_format = "svg"): +def plot_copula(data_flux1, data_flux2, n = 5, width = 900 , height = 600, save = True, export_format = "svg"): """A Python function to plot the copula between two fluxes Keyword arguments: @@ -59,17 +59,21 @@ def plot_copula(data_flux1, data_flux2, n = 5, width = 900 , height = 600, expor ) fig.update_layout(scene_camera=camera) - fig.to_image(format = export_format, engine="kaleido") - pio.write_image(fig, fig_name, scale=2) + + if save: + fig.to_image(format = export_format, engine="kaleido") + pio.write_image(fig, fig_name, scale=2) -def plot_histogram(reaction_fluxes, reaction, n_bins=40): +def plot_histogram(reaction_fluxes, reaction, n_bins = 40, save = False, export_format = "png"): """A Python function to plot the histogram of a certain reaction flux. Keyword arguments: reaction_fluxes -- a vector that contains sampled fluxes of a reaction reaction -- a string with the name of the reacion n_bins -- the number of bins for the histogram + save -- save plot to a file + export_format -- file format to save the plot """ plt.figure(figsize=(7, 7)) @@ -84,10 +88,14 @@ def plot_histogram(reaction_fluxes, reaction, n_bins=40): plt.title("Reaction: " + reaction, fontweight="bold", fontsize=18) plt.axis([np.amin(reaction_fluxes), np.amax(reaction_fluxes), 0, np.amax(n) * 1.2]) + if save: + plt.savefig(reaction + "." + export_format, dpi = 150, bbox_inches = "tight", format = export_format) + plt.show() + def plot_corr_matrix(corr_matrix, reactions, removed_reactions=[], format="svg"): """A Python function to plot the heatmap of a model's pearson correlation matrix. diff --git a/doc/logo/geomscale.png b/doc/logo/geomscale.png new file mode 100644 index 00000000..26ba0053 Binary files /dev/null and b/doc/logo/geomscale.png differ diff --git a/ext_data/Abiotrophia_defectiva_ATCC_49176.xml.gz b/ext_data/Abiotrophia_defectiva_ATCC_49176.xml.gz new file mode 100644 index 00000000..e63bc98d Binary files /dev/null and b/ext_data/Abiotrophia_defectiva_ATCC_49176.xml.gz differ diff --git a/tutorials/dingo_tutorial.ipynb b/tutorials/dingo_tutorial.ipynb index c95aa8f8..e8ebd9bc 100644 --- a/tutorials/dingo_tutorial.ipynb +++ b/tutorials/dingo_tutorial.ipynb @@ -16,7 +16,7 @@ }, "source": [ "\n", - "## About this Colab notebook " + "## About this notebook " ] }, { @@ -25,43 +25,19 @@ "id": "-IBI5DoLMDCn" }, "source": [ - "The aim of this notebook is to highlight the importance of metabolic networks analysis and the insight it may provide, as well as to demonstrate how to use the `dingo` Python library for the analysis of metabolic networks. \n" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "L-pK94S-GPYG" - }, - "source": [ - "For your convenience, we suggest making a copy of the notebook on your Google Drive \n", - "\n", - "(`File > Save a copy in Drive`). \n", + "The aim of this notebook is to highlight the importance of metabolic networks analysis and the insight it may provide, as well as to demonstrate how to use the `dingo` Python library for the analysis of metabolic networks.\n", "\n", - "This way you can change things on the notebook and make your own experiments as you like!" + "In case, notebook asks you for which environment to be used, click on the Python 3.10.19.\n" ] }, { "cell_type": "markdown", - "metadata": { - "id": "zUpwu6ue1MEG" - }, - "source": [ - "* The `&> /dev/null` in the end of a command is so you do not see all the messages returned when the command is executed.\n", - "* The `%%capture` flag is so Google Colab does not print the output of the chunk of code in a cell. We have used it in some cases to avoid long warning messages. " - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "8CKAQaoN1ORJ" - }, + "metadata": {}, "source": [ - "In case an error comes up when running a notebook's command, you may click \n", - "\n", - "`Runtime > Factory reset runtime` \n", + "To install `dingo` locally, please follow instructions [here](../README.md#installation-on-linux).\n", "\n", - "and run the notebook from scratch." + "Currently, `dingo` is available on Linux systems. \n", + "If you use MacOS or Windows, you may run `dingo` as a container, following instructions [here](../README.md#using-dingo-as-a-docker-container)." ] }, { @@ -101,8 +77,8 @@ "id": "H2UE0ORSS_sQ" }, "source": [ - "\n", - "" + "\n", + "" ] }, { @@ -120,8 +96,8 @@ "id": "ZwJEvcTfY_fC" }, "source": [ - "\n", - "" + "\n", + "" ] }, { @@ -149,7 +125,7 @@ "id": "l7YlhJjSPFRo" }, "source": [ - "" + "" ] }, { @@ -163,7 +139,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 6, "metadata": { "colab": { "base_uri": "https://localhost:8080/", @@ -172,7 +148,21 @@ "id": "G5m2-7t7fUkf", "outputId": "a053a2c0-967c-4580-94f7-2d9ed20061e3" }, - "outputs": [], + "outputs": [ + { + "data": { + "text/html": [ + "" + ], + "text/plain": [ + "" + ] + }, + "execution_count": 6, + "metadata": {}, + "output_type": "execute_result" + } + ], "source": [ "#@title Pathway reactions fluxes representation{ display-mode: \"form\" }\n", "from IPython.display import HTML\n", @@ -213,7 +203,7 @@ "id": "v8cgveHyHdmq" }, "source": [ - "So, from each metabolic model we can get a stoichiometric matrix ($S$) of the reactions that take place in the organism along with their corresponding constraints. \n", + "So, from each metabolic model we can get a stoichiometric matrix ( $S$ ) of the reactions that take place in the organism along with their corresponding constraints. \n", "As you can guess, $S$ is nothing but a matrix with $m$ metabolites and $n$ reactions!" ] }, @@ -223,13 +213,15 @@ "id": "e4rCPCwghY_6" }, "source": [ - "$S_{m,n} =\n", + "$$\n", + "S_{m,n} =\n", " \\begin{pmatrix}\n", " a_{1,1} & a_{1,2} & \\cdots & a_{1,n} \\\\\n", " a_{2,1} & a_{2,2} & \\cdots & a_{2,n} \\\\\n", " \\vdots & \\vdots & \\ddots & \\vdots \\\\\n", " a_{m,1} & a_{m,2} & \\cdots & a_{m,n}\n", - " \\end{pmatrix}$" + " \\end{pmatrix}\n", + "$$" ] }, { @@ -262,7 +254,7 @@ "source": [ "The solution space for that system is a **polytope**; a geometric object with \"flat\" sides that lives in a large number of dimensions. \n", "\n", - "\n" + "\n" ] }, { @@ -274,7 +266,7 @@ "**Flux Balance Analysis (FBA)** is commonly used to identify an optimal flux distribution by optimizing a linear objective function; e.g the biomass value of the organism. You may see more about FBA [here](https://www.nature.com/articles/nbt.1614/).\n", "\n", "\n", - "\n", + "\n", "\n", "Figure from: [Libourel, I. G., & Shachar-Hill, Y.. Annu. Rev. Plant Biol. 59 (2008)](10.1146/annurev.arplant.58.032806.103822)\n" ] @@ -289,7 +281,7 @@ "\n", "To this end FVA uses a double linear programming problem, a maximization and a subsequent minimization for each reaction.\n", "\n", - "\n", + "\n", "\n", "Figure by a presentation of [Lizbeth Hampton](https://slideplayer.com/slide/13509212/)." ] @@ -326,7 +318,7 @@ "💪 Our method splits sampling in phases and makes use of **rounding** steps; this way it is both efficient and returns high quality samples. \n", "\n", "\n", - "\n", + "\n", " " ] }, @@ -347,12 +339,13 @@ "id": "WX5_VagweffU" }, "source": [ - "\n", - "\n", - "\n", - "\n", - "\n", - "" + "\n", + "\n", + "\n", + "
\n", + "\n", + "\n", + "" ] }, { @@ -376,741 +369,708 @@ { "cell_type": "markdown", "metadata": { - "id": "PqWGwdr9Cs-H" + "id": "2dtcobmYQOWp" }, "source": [ - "## Get and install *dingo*" + "## Usingo `dingo`.." ] }, { "cell_type": "markdown", - "metadata": { - "id": "164yhNEXMcSi" - }, + "metadata": {}, + "source": [ + "Let's import `dingo` and the rest of the Python libraries to be used during this tutorial:" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "metadata": {}, + "outputs": [], "source": [ + "import os\n", + "import sys\n", + "import cobra\n", + "import dingo\n", "\n", - "### Requirements" + "import numpy as np" ] }, { "cell_type": "markdown", "metadata": { - "id": "BpZ3fcqKMcSj" + "id": "OKZhVhU5McSp" }, "source": [ - "Mandatory:\n", - "* a Unix-like environment, e.g Linux, MacOS etc.\n", - "* a computer machine with at least 4GB of RAM; `dingo` requires about 4GB to compile \n", - "\n", - "Optional:\n", - "* for better performance, you may use `dingo` with the [`gurobi`](https://www.gurobi.com) solver. To do so, a license is required (free for academic purposes)." + "### ..to manipulate your model" ] }, { "cell_type": "markdown", "metadata": { - "id": "tTvoGY3DMcSj" + "id": "AzFd-qTWryBW" }, "source": [ - "You can get the `gurobipy` Python interface of the `gurobi` solver by running:\n", + "To load a metabolic model on `dingo`, we use the `MetabolicNetwork` class.\n", "\n", - "`\n", - "pip3 install -i https://pypi.gurobi.com gurobipy\n", - "`\n", - "\n", - "and download an academic license [from here](https://www.gurobi.com/downloads/end-user-license-agreement-academic/). \n", - "\n", - "For more information on how to get `gurobi` you may also have a look [here](https://support.gurobi.com/hc/en-us/articles/360044290292-How-do-I-install-Gurobi-for-Python-)." + "Currently, `dingo` may get a model either in `.xml`, `.json` or `.mat` format." ] }, { - "cell_type": "markdown", + "cell_type": "code", + "execution_count": 8, "metadata": { - "id": "UkdSbnJQwJSm" + "id": "NNSJM_Z3McSs" }, + "outputs": [], "source": [ - "> **Remember!**
\n", - "When working on a normal console (terminal) you will have to remove any `!` and `%` characters from the beginning of the following commands; these allow Google Colab to work as a terminal console. " + "model = dingo.MetabolicNetwork.from_json('../ext_data/e_coli_core.json')" ] }, { "cell_type": "markdown", "metadata": { - "id": "IQL-qaW4McSk" + "id": "8BU8YYL9PC-z" }, "source": [ - "### Installation" + "Let's see what the model comprises. " ] }, { "cell_type": "markdown", "metadata": { - "id": "dqa089jFDXaH" + "id": "jxLug5xuPMcB" }, "source": [ - "Install the dependencies for PySPQR library; for Debian/Ubuntu Linux, run" + "First of all, let's see the metabolites ($m$) included in our model. \n", + "Here is the first 10 of them. " ] }, { "cell_type": "code", - "execution_count": null, + "execution_count": 9, "metadata": { - "id": "0eBtFIaIzZ66" + "colab": { + "base_uri": "https://localhost:8080/" + }, + "id": "_1r63O_AXzTO", + "outputId": "d54ee08c-7aae-4d4b-9aa7-8090bcdd2fb5" }, - "outputs": [], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Number of metabolites in my model: 72\n", + "Here are the first 10 metabolites in your model: ['glc__D_e', 'gln__L_c', 'gln__L_e', 'glu__L_c', 'glu__L_e', 'glx_c', 'h2o_c', 'h2o_e', 'h_c']\n" + ] + } + ], "source": [ - "!apt-get install libsuitesparse-dev &> /dev/null\n", - "%pip install sparseqr &> /dev/null\n", - "%pip install Cython &> /dev/null\n", - "%pip install cobra &> /dev/null\n", - "%pip install kaleido &> /dev/null\n", - "%pip install pyoptinterface[highs] &> /dev/null" + "print('Number of metabolites in my model: ', len(model.metabolites))\n", + "print('Here are the first 10 metabolites in your model: ', model.metabolites[:9])" ] }, { "cell_type": "markdown", "metadata": { - "id": "yYqOnx7_4ZRN" + "id": "sxaWOTJ-Paoq" }, "source": [ - "Get the `dingo` repo." + "And now, let us have a look in the reactions ($n$) that occur. \n", + "Again, here is the first 10 reactions. " ] }, { "cell_type": "code", - "execution_count": null, + "execution_count": 10, "metadata": { "colab": { "base_uri": "https://localhost:8080/" }, - "id": "A6Mcqrqt3_Sa", - "outputId": "e340135f-48c2-4e34-87e6-fa0c54e3024e" + "id": "oEYN_hbeXjpy", + "outputId": "a8dab6dd-9d69-4e35-d98c-3278b55b7f65" }, - "outputs": [], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Number of reactions in my model: 95\n", + "Here are the first 10 reactions in your model: ['PFK', 'PFL', 'PGI', 'PGK', 'PGL', 'ACALD', 'AKGt2r', 'PGM', 'PIt2r']\n" + ] + } + ], "source": [ - "!git clone https://github.com/GeomScale/dingo.git" + "print(f'Number of reactions in my model: {len(model.reactions)}')\n", + "print(f'Here are the first 10 reactions in your model: {model.reactions[:9]}')" ] }, { "cell_type": "markdown", "metadata": { - "id": "qePKvNyVFrHG" + "id": "qZk9H2aBPrFj" }, "source": [ - "To load the submodules that `dingo` uses, run:" + "Moreover, let us see the stoichiometrix matrix ($S$) of our model." ] }, { "cell_type": "code", - "execution_count": null, + "execution_count": 11, "metadata": { "colab": { "base_uri": "https://localhost:8080/" }, - "id": "n-zpDmAy9V-8", - "outputId": "97e759a1-99ed-4a5b-8953-6113d3b5c315" + "id": "DurJhomTPzO1", + "outputId": "0d4f107a-7bdb-48ff-e8f8-ee6909476ac2" }, - "outputs": [], + "outputs": [ + { + "data": { + "text/plain": [ + "array([[ 0., 0., 0., ..., 0., 0., 0.],\n", + " [ 0., 0., 0., ..., 0., 0., 0.],\n", + " [ 0., 0., 0., ..., 0., 0., 0.],\n", + " ...,\n", + " [ 0., 0., 0., ..., 0., 0., 0.],\n", + " [ 0., 0., 0., ..., 0., 0., 0.],\n", + " [ 0., 0., -1., ..., 0., 0., 0.]], shape=(72, 95))" + ] + }, + "execution_count": 11, + "metadata": {}, + "output_type": "execute_result" + } + ], "source": [ - "%cd dingo/\n", - "!git submodule update --init" + "model.S" ] }, { "cell_type": "markdown", "metadata": { - "id": "jb6XxVcbF0h3" + "id": "wHkrYqfTemnW" }, "source": [ - "Then, download and unzip the `boost` library:" + "And its dimensions are of course $m*n$." ] }, { "cell_type": "code", - "execution_count": null, - "metadata": { - "id": "vkjI-YD69dkw" - }, - "outputs": [], - "source": [ - "!wget -O boost_1_76_0.tar.bz2 https://archives.boost.io/release/1.76.0/source/boost_1_76_0.tar.bz2 &> /dev/null\n", - "!tar xjf boost_1_76_0.tar.bz2 &> /dev/null\n", - "!rm boost_1_76_0.tar.bz2 &> /dev/null" - ] - }, - { - "cell_type": "markdown", + "execution_count": 12, "metadata": { - "id": "RK_xgnsYAfOr" + "colab": { + "base_uri": "https://localhost:8080/" + }, + "id": "Tl52cmuaenlY", + "outputId": "256123a8-4ef3-4c3d-a7e3-3298e3066535" }, + "outputs": [ + { + "data": { + "text/plain": [ + "(72, 95)" + ] + }, + "execution_count": 12, + "metadata": {}, + "output_type": "execute_result" + } + ], "source": [ - "Install `dingo`" + "model.S.shape" ] }, { "cell_type": "markdown", "metadata": { - "id": "Gu9E7fidgjnW" + "id": "nsGLu7c0d3Ra" }, "source": [ - "The following command will install `dingo` and it will take a while to compile. \n", - "\n", - "Remember that about 4GB of RAM are required to do so. " + "Last but not least, let us have a look at the **biomass** or **objective function** of our model. " ] }, { "cell_type": "code", - "execution_count": null, + "execution_count": 13, "metadata": { "colab": { "base_uri": "https://localhost:8080/" }, - "id": "hMNc6tbiwhG1", - "outputId": "59572b1f-f2f2-4579-ce74-e43382864040" + "id": "hpnbfsBVeCFK", + "outputId": "c7bf618f-eb30-4226-986e-752e15caa544" }, - "outputs": [], + "outputs": [ + { + "data": { + "text/plain": [ + "array([0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.,\n", + " 0., 0., 0., 0., 0., 0., 0., 1., 0., 0., 0., 0., 0., 0., 0., 0., 0.,\n", + " 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.,\n", + " 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.,\n", + " 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.,\n", + " 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.])" + ] + }, + "execution_count": 13, + "metadata": {}, + "output_type": "execute_result" + } + ], "source": [ - "!python setup.py install --user &> /dev/null\n", - "print('dingo is ready to go!')" + "model.objective_function" ] }, { "cell_type": "markdown", "metadata": { - "id": "ZJ3AW6QTMcSn" + "id": "MLXZt3umeGJm" }, "source": [ - "### Run tests" + "As you can see, this array has only a single `1` in index `24`.\n", + "Let's see which function is this. " ] }, { - "cell_type": "markdown", + "cell_type": "code", + "execution_count": 14, "metadata": { - "id": "PLCBCsYiGEXz" + "colab": { + "base_uri": "https://localhost:8080/", + "height": 35 + }, + "id": "-04jUh1veQNt", + "outputId": "054be7c9-8378-4609-98e8-28e0cac299ef" }, + "outputs": [ + { + "data": { + "text/plain": [ + "'BIOMASS_Ecoli_core_w_GAM'" + ] + }, + "execution_count": 14, + "metadata": {}, + "output_type": "execute_result" + } + ], "source": [ - "We have made some tests for you so you can make sure `dingo` has been installed properly.\n", - "\n", - "You may have a look at these [test files](https://github.com/GeomScale/dingo/tree/develop/tests) so you can run `dingo` from your terminal." + "model.reactions[24]" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "All the above, you may access them by loading your model as a `cobra.Model`:" ] }, { "cell_type": "code", - "execution_count": null, - "metadata": { - "id": "f7q8A5TDxtOS" - }, + "execution_count": 15, + "metadata": {}, "outputs": [], "source": [ - "%%capture\n", - "!python -m unittest discover tests \"*.py\"" + "cobra_model = cobra.io.read_sbml_model(\"../ext_data/e_coli_core.xml\")" ] }, { "cell_type": "markdown", - "metadata": { - "id": "rWJ00dGySfol" - }, + "metadata": {}, "source": [ - "If you look at the output of that command, you should see that every test passed.\n" + "Assuming the objective function is a single reaction of the reactions on the model:" ] }, { - "cell_type": "markdown", - "metadata": { - "id": "iwZEvB6tC6jc" - }, + "cell_type": "code", + "execution_count": 16, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "('BIOMASS_Ecoli_core_w_GAM', 24)" + ] + }, + "execution_count": 16, + "metadata": {}, + "output_type": "execute_result" + } + ], "source": [ - "--------------------------------------------------------------------------" + "obj_index = [r.objective_coefficient for r in cobra_model.reactions].index(True)\n", + "objective = cobra_model.reactions[obj_index].id\n", + "objective, obj_index" ] }, { "cell_type": "markdown", "metadata": { - "id": "rLfOQgaQuULC" + "id": "dLi2HZWTjS0e" }, "source": [ - "The following command is to make Colab have access to the `dingo` library that was just compiled. \n", - "There will probably be **no need for you to do this**. \n", - "In case Python does not find `dingo`, just check the \n", - "semifinal line of the compilation step, in this case:\n", - "\n", - "`Writing /root/.local/lib/python3.7/site-packages/dingo-0.1.0.egg-info`\n", + "Ok, so what exactly is the biomass function? 🤔 \n", "\n", - "This is the path where `dingo` was compiled. " + "Let's print the `24` column of our $S$ matrix to see what we have there." ] }, { "cell_type": "code", - "execution_count": null, - "metadata": { - "id": "ojsTmKVZYocZ" - }, - "outputs": [], - "source": [ - "import sys\n", - "_ = (sys.path.append(\"/root/.local/lib/python3.8/site-packages/\"))" - ] - }, - { - "cell_type": "markdown", + "execution_count": 17, "metadata": { - "id": "Ua5lGNc8C8QE" + "colab": { + "base_uri": "https://localhost:8080/" + }, + "id": "1c93OKZ7eUPX", + "outputId": "19169c24-22ae-4e5b-b3c9-054ca0270dd5" }, - "source": [ - "-------------------------------------------------------------------------------" + "outputs": [ + { + "data": { + "text/plain": [ + "array([ 0. , -0.2557, 0. , -4.9414, 0. , 0. ,\n", + " -59.81 , 0. , 59.81 , 0. , 0. , 0. ,\n", + " 0. , 0. , 0. , -3.547 , 3.547 , 13.0279,\n", + " -13.0279, 0. , 0. , 0. , 0. , 0. ,\n", + " 0. , -1.496 , -1.7867, -0.5191, 0. , 59.81 ,\n", + " 0. , 0. , 0. , -2.8328, 0. , 0. ,\n", + " 0. , -0.8977, 0. , 0. , 0. , 0. ,\n", + " 0. , -3.7478, 0. , 0. , 0. , 0. ,\n", + " 0. , 0. , 59.81 , 4.1182, 0. , 0. ,\n", + " -59.81 , 0. , 0. , 0. , 3.7478, 0. ,\n", + " -0.361 , 0. , 0. , -0.0709, 0. , 0. ,\n", + " 0. , 0. , 0. , 0. , -0.129 , -0.205 ])" + ] + }, + "execution_count": 17, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# Set the threshold parameter to max to enable the display of all the elements of the array\n", + "np.set_printoptions(threshold=sys.maxsize)\n", + "model.S[:,24]" ] }, { "cell_type": "markdown", "metadata": { - "id": "2dtcobmYQOWp" + "id": "iJa3YQr7jq2-" }, "source": [ - "## Run `dingo`.." + "And just to make sure, let's print the length of this array too. " ] }, { - "cell_type": "markdown", + "cell_type": "code", + "execution_count": 18, "metadata": { - "id": "OKZhVhU5McSp" + "colab": { + "base_uri": "https://localhost:8080/" + }, + "id": "wFTLZewLiAH-", + "outputId": "eb1b0b32-e025-426d-f59d-670cb416d8bc" }, + "outputs": [ + { + "data": { + "text/plain": [ + "(72,)" + ] + }, + "execution_count": 18, + "metadata": {}, + "output_type": "execute_result" + } + ], "source": [ - "### ..to manipulate your model" + "model.S[:,24].shape" ] }, { "cell_type": "markdown", "metadata": { - "id": "ljInPZvPMcSp" + "id": "6TlgVCJijyCO" }, "source": [ - "Get the path to the `dingo` directory. " + "As expected, the length of the biomass array equals to the number of the metabolites of our model. " ] }, { "cell_type": "code", - "execution_count": null, + "execution_count": 19, "metadata": { "colab": { "base_uri": "https://localhost:8080/" }, - "id": "S7ieYXqfMcSq", - "outputId": "d9ec3348-6c5a-46b5-b815-15238625b216" + "id": "EjD7uBgfhYu0", + "outputId": "ada5cb8b-0aa5-4292-a177-274b8f150b79" }, - "outputs": [], + "outputs": [ + { + "data": { + "text/plain": [ + "72" + ] + }, + "execution_count": 19, + "metadata": {}, + "output_type": "execute_result" + } + ], "source": [ - "import os\n", - "import numpy as np" + "len(model.metabolites)" ] }, { "cell_type": "markdown", "metadata": { - "id": "AiFwL4lRugVC" + "id": "Pq0IOSSdkECR" }, "source": [ - "And import the `MetabolicNetwork` class of `dingo`! " + "Moreover, you need to remember that the indexes of those 2 arrays are in order, meaning that the actual biomass function would be as follows:" ] }, { - "cell_type": "code", - "execution_count": null, + "cell_type": "markdown", "metadata": { - "id": "FahzUpQFommK" + "id": "oQ6GhgTblIND" }, - "outputs": [], "source": [ - "from dingo import MetabolicNetwork" + "$ 59.81*adp\\_c - 59.81*atp\\_c -0.0709*f6p\\_c + ... -0.8977*r5p\\_c$" ] }, { "cell_type": "markdown", "metadata": { - "id": "AzFd-qTWryBW" + "id": "NmVjo47VP8xj" }, "source": [ - "Now, you can load a metabolic model. \n", - "Currently, `dingo` may get either a `.json` or a '.mat` model format." + "You can also have a look at the `dingo` related parameters of the model you just built." ] }, { "cell_type": "code", - "execution_count": null, + "execution_count": 20, "metadata": { - "id": "NNSJM_Z3McSs" + "colab": { + "base_uri": "https://localhost:8080/" + }, + "id": "NuVrc64WYJbJ", + "outputId": "44747cd1-43f8-4dc0-8ebb-321218f08714" }, - "outputs": [], + "outputs": [ + { + "data": { + "text/plain": [ + "{'opt_percentage': 100,\n", + " 'distribution': 'uniform',\n", + " 'nullspace_method': 'sparseQR',\n", + " 'solver': None}" + ] + }, + "execution_count": 20, + "metadata": {}, + "output_type": "execute_result" + } + ], "source": [ - "model = MetabolicNetwork.from_json('ext_data/e_coli_core.json')" + "model.parameters" ] }, { "cell_type": "markdown", "metadata": { - "id": "8BU8YYL9PC-z" + "id": "ctpZFRndQGrb" }, "source": [ - "Let's see what the model comprises. " + "### ..to optimize an objective function (FBA)" ] }, { "cell_type": "markdown", "metadata": { - "id": "jxLug5xuPMcB" + "id": "uccGKr_hSJCM" }, "source": [ - "First of all, let's see the metabolites ($m$) included in our model. \n", - "Here is the first 10 of them. " + "To perform FBA with `dingo`, you just need to call the according method ([`fba`](https://github.com/GeomScale/dingo/blob/a76b4be22f33feac86dff38746c7f2706afb2b67/dingo/MetabolicNetwork.py#L107)) of the `model` you built using the [`MetabolicNetwork`](https://github.com/GeomScale/dingo/blob/a76b4be22f33feac86dff38746c7f2706afb2b67/dingo/MetabolicNetwork.py#L21) class. \n", + "\n", + "FBA is a lot easier both from the computational and the geometical point of view and it takes much less time. " ] }, { "cell_type": "code", - "execution_count": null, + "execution_count": 21, "metadata": { - "colab": { - "base_uri": "https://localhost:8080/" - }, - "id": "_1r63O_AXzTO", - "outputId": "d54ee08c-7aae-4d4b-9aa7-8090bcdd2fb5" + "id": "kWFbcJumShgh" }, "outputs": [], "source": [ - "print('Number of metabolites in my model: ', len(model.metabolites))\n", - "print('Here are the first 10 metabolites in your model: ', model.metabolites[:9])" + "fba_vector, fba_optimal = model.fba()" ] }, { "cell_type": "markdown", "metadata": { - "id": "sxaWOTJ-Paoq" + "id": "Ql-cIs8JxLuJ" }, "source": [ - "And now, let us have a look in the reactions ($n$) that occur. \n", - "Again, here is the first 10 reactions. " + "As already mentioned, FBA identifies an **optimal** flux distribution by optimizing a linear **objective function**.\n", + "\n", + "So the output of FBA can be considered as a single sample of the flux sampling method described above. Thus, if we check on the output this time we will see that it has 2 attributes:\n", + "* the **flux vector** (meaning the set of the reactions flux values in the same solution) which maximizes the objective function.\n", + "* the **flux value** that the objective function achieves at the maximum" ] }, { "cell_type": "code", - "execution_count": null, + "execution_count": 22, "metadata": { "colab": { "base_uri": "https://localhost:8080/" }, - "id": "oEYN_hbeXjpy", - "outputId": "a8dab6dd-9d69-4e35-d98c-3278b55b7f65" + "id": "Nxk0qI0ISmOB", + "outputId": "3bdaaffe-b7b2-464a-ec83-ad3a086c76d4" }, - "outputs": [], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "This is the value of that optimal solution of the objective function: \n", + " 0.8739215069684304\n", + "----------\n", + "This is the optimal flux vector according to the biomass objective function: \n", + " [ 7.47738196 0. 4.86086115 -16.02352614 4.95998494\n", + " -0. -0. -14.71613957 3.21489505 -0.\n", + " -0. -0. 2.50430947 6.00724958 6.00724958\n", + " 8.39 0. -0. 0. -0.\n", + " 5.06437566 45.51400977 -0. 1.75817744 0.87392151\n", + " -0. -22.80983331 2.67848185 6.00724958 -2.28150309\n", + " -0. 43.59898531 -0. 14.71613957 0.\n", + " -0. 5.06437566 -5.06437566 1.49698376 0.\n", + " 1.49698376 1.18149809 7.47738196 0. 0.\n", + " 0. 22.80983331 0. -0. 0.\n", + " 0. -10. 0. 0. 17.53086543\n", + " 29.17582714 0. 0. -4.76531919 -21.79949266\n", + " -3.21489505 0. 0. 7.47738196 0.\n", + " -0. 0. 0. -0. 5.06437566\n", + " -0. 4.95998494 16.02352614 10. 0.22346173\n", + " -0. -4.54185746 0. 0. -0.\n", + " 4.95998494 -29.17582714 6.00724958 -0. -0.\n", + " 0. -0. 5.06437566 0. 0.\n", + " 38.53460965 0. 4.76531919 21.79949266 9.2825326 ]\n" + ] + } + ], "source": [ - "print('Number of reactions in my model: ', len(model.reactions))\n", - "print('Here are the first 10 reactions in your model: ', model.reactions[:9])" + "np.set_printoptions(threshold=None)\n", + "\n", + "print('This is the value of that optimal solution of the objective function: \\n', fba_optimal)\n", + "print('----------')\n", + "print('This is the optimal flux vector according to the biomass objective function: \\n', fba_vector)" ] }, { "cell_type": "markdown", "metadata": { - "id": "qZk9H2aBPrFj" + "id": "rKBVhQI9SlbO" }, "source": [ - "Moreover, let us see the stoichiometrix matrix ($S$) of our model." + "💪 It is quite common that the objective function is the biomass function of a model, hoowever, **you can change your objective function** as you wish and use any reaction as your objective function.\n", + "\n", + "For example, to maximize the 1st reaction of the model (PFK: Phosphofructokinase), we first build an array with the number of reactions of our model with `0`s and then we set only its first elemet equal to `1`:" ] }, { "cell_type": "code", - "execution_count": null, - "metadata": { - "colab": { - "base_uri": "https://localhost:8080/" - }, - "id": "DurJhomTPzO1", - "outputId": "0d4f107a-7bdb-48ff-e8f8-ee6909476ac2" - }, - "outputs": [], + "execution_count": 23, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "'PFK'" + ] + }, + "execution_count": 23, + "metadata": {}, + "output_type": "execute_result" + } + ], "source": [ - "model.S" + "model.reactions[0]" ] }, { - "cell_type": "markdown", - "metadata": { - "id": "wHkrYqfTemnW" - }, - "source": [ - "And its dimensions are of course $m*n$." + "cell_type": "code", + "execution_count": 24, + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
Reaction identifier\n", + " PFK\n", + "
Name\n", + " Phosphofructokinase\n", + "
Memory address0x73916e7e38b0
Stoichiometry\n", + "

\n", + " atp_c + f6p_c --> adp_c + fdp_c + h_c\n", + "

\n", + "

\n", + " ATP C10H12N5O13P3 + D-Fructose 6-phosphate --> ADP C10H12N5O10P2 + D-Fructose 1,6-bisphosphate + H+\n", + "

\n", + "
GPR\n", + " b3916 or b1723\n", + "
Lower bound0.0
Upper bound1000.0
\n", + " " + ], + "text/plain": [ + "" + ] + }, + "execution_count": 24, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "cobra_model.reactions.PFK" ] }, { "cell_type": "code", - "execution_count": null, + "execution_count": 25, "metadata": { - "colab": { - "base_uri": "https://localhost:8080/" - }, - "id": "Tl52cmuaenlY", - "outputId": "256123a8-4ef3-4c3d-a7e3-3298e3066535" + "id": "fdLlAbhA0Ylg" }, "outputs": [], "source": [ - "model.S.shape" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "nsGLu7c0d3Ra" - }, - "source": [ - "Last but not least, let us have a look at the **biomass** or **objective function** of our model. " - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "colab": { - "base_uri": "https://localhost:8080/" - }, - "id": "hpnbfsBVeCFK", - "outputId": "c7bf618f-eb30-4226-986e-752e15caa544" - }, - "outputs": [], - "source": [ - "model.objective_function" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "MLXZt3umeGJm" - }, - "source": [ - "As you can see, this array has only a single `1` in index `24`.\n", - "Let's see which function is this. " - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "colab": { - "base_uri": "https://localhost:8080/", - "height": 35 - }, - "id": "-04jUh1veQNt", - "outputId": "054be7c9-8378-4609-98e8-28e0cac299ef" - }, - "outputs": [], - "source": [ - "model.reactions[24]" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "dLi2HZWTjS0e" - }, - "source": [ - "Ok, so what exactly is the biomass function? 🤔 \n", - "\n", - "Let's print the `24` column of our $S$ matrix to see what we have there." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "colab": { - "base_uri": "https://localhost:8080/" - }, - "id": "1c93OKZ7eUPX", - "outputId": "19169c24-22ae-4e5b-b3c9-054ca0270dd5" - }, - "outputs": [], - "source": [ - "# We import numpy and set the threshold parameter to max to enable the display of all the elements of the array\n", - "np.set_printoptions(threshold=sys.maxsize)\n", - "model.S[:,24]" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "iJa3YQr7jq2-" - }, - "source": [ - "And just to make sure, let's print the length of this array too. " - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "colab": { - "base_uri": "https://localhost:8080/" - }, - "id": "wFTLZewLiAH-", - "outputId": "eb1b0b32-e025-426d-f59d-670cb416d8bc" - }, - "outputs": [], - "source": [ - "model.S[:,24].shape" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "6TlgVCJijyCO" - }, - "source": [ - "As expected, the length of the biomass array equals to the number of the metabolites of our model. " - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "colab": { - "base_uri": "https://localhost:8080/" - }, - "id": "EjD7uBgfhYu0", - "outputId": "ada5cb8b-0aa5-4292-a177-274b8f150b79" - }, - "outputs": [], - "source": [ - "len(model.metabolites)" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "Pq0IOSSdkECR" - }, - "source": [ - "Moreover, you need to remember that the indexes of those 2 arrays are in order, meaning that the actual biomass function would be as follows:" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "oQ6GhgTblIND" - }, - "source": [ - "$ 59.81*adp\\_c - 59.81*atp\\_c -0.0709*f6p\\_c + ... -0.8977*r5p\\_c$" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "NmVjo47VP8xj" - }, - "source": [ - "You can also have a look at the `dingo` related parameters of the model you just built." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "colab": { - "base_uri": "https://localhost:8080/" - }, - "id": "NuVrc64WYJbJ", - "outputId": "44747cd1-43f8-4dc0-8ebb-321218f08714" - }, - "outputs": [], - "source": [ - "model.parameters" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "ctpZFRndQGrb" - }, - "source": [ - "### ..to optimize an objective function (FBA)" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "uccGKr_hSJCM" - }, - "source": [ - "To perform FBA with `dingo`, you just need to call the according method ([`fba`](https://github.com/GeomScale/dingo/blob/a76b4be22f33feac86dff38746c7f2706afb2b67/dingo/MetabolicNetwork.py#L107)) of the `model` you built using the [`MetabolicNetwork`](https://github.com/GeomScale/dingo/blob/a76b4be22f33feac86dff38746c7f2706afb2b67/dingo/MetabolicNetwork.py#L21) class. \n", - "\n", - "FBA is a lot easier both from the computational and the geometical point of view and it takes much less time. " - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "id": "kWFbcJumShgh" - }, - "outputs": [], - "source": [ - "%%capture\n", - "fba_output = model.fba()" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "Ql-cIs8JxLuJ" - }, - "source": [ - "As already mentioned, FBA identifies an **optimal** flux distribution by optimizing a linear **objective function**.\n", - "\n", - "So the output of FBA can be considered as a single sample of the flux sampling method described above. Thus, if we check on the output this time we will see that it has 2 attributes:\n", - "* the **flux vector** (meaning the set of the reactions flux values in the same solution) which maximizes the objective function.\n", - "* the **flux value** that the objective function achieves at the maximum" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "colab": { - "base_uri": "https://localhost:8080/" - }, - "id": "Nxk0qI0ISmOB", - "outputId": "3bdaaffe-b7b2-464a-ec83-ad3a086c76d4" - }, - "outputs": [], - "source": [ - "np.set_printoptions(threshold=None)\n", - "\n", - "max_biomass_flux_vector = fba_output[0]\n", - "max_biomass_objective = fba_output[1]\n", - "\n", - "print('This is the optimal flux vector according to the biomass objective function: ', max_biomass_flux_vector)\n", - "print('----------')\n", - "print('This is the value of that optimal solution of the objective function: ', max_biomass_objective)" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "rKBVhQI9SlbO" - }, - "source": [ - "💪 Even it is quite common that the objective function is the biomass function of a model, **you can change your objective function** as you wish and use any reaction as your objective function.\n", - "\n", - "For example, to maximize the 1st reaction of the model, we first build an array with the number of reactions of our model with `0`s and then we set only its first elemet equal to `1`:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "id": "fdLlAbhA0Ylg" - }, - "outputs": [], - "source": [ - "n = model.num_of_reactions()\n", - "obj_fun = np.zeros(n)\n", - "obj_fun[0] = 1" + "n = model.num_of_reactions()\n", + "obj_fun = np.zeros(n)\n", + "obj_fun[0] = 1" ] }, { @@ -1124,7 +1084,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 26, "metadata": { "id": "jrASE_SS2Nst" }, @@ -1144,7 +1104,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 27, "metadata": { "colab": { "base_uri": "https://localhost:8080/" @@ -1152,7 +1112,23 @@ "id": "Lf8jpTrU1dEr", "outputId": "14e6bd8c-4778-4f1b-9ef2-516bc4a9c9e3" }, - "outputs": [], + "outputs": [ + { + "data": { + "text/plain": [ + "array([1., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.,\n", + " 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.,\n", + " 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.,\n", + " 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.,\n", + " 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.,\n", + " 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.])" + ] + }, + "execution_count": 27, + "metadata": {}, + "output_type": "execute_result" + } + ], "source": [ "model.objective_function" ] @@ -1177,19 +1153,18 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 28, "metadata": { "id": "f11M0AClnfhZ" }, "outputs": [], "source": [ - "%%capture\n", "fba_output_new_obj = model.fba()" ] }, { "cell_type": "code", - "execution_count": null, + "execution_count": 29, "metadata": { "colab": { "base_uri": "https://localhost:8080/" @@ -1197,13 +1172,34 @@ "id": "80op83f4noZK", "outputId": "41729b2b-8a84-47f6-82f6-d218fa6040a4" }, - "outputs": [], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "This is the optimal flux vector according to my objective function:\n", + " [176.61 -0. 10. -20. -0. -0. -0. -20. -0. -0.\n", + " -0. -0. 0. 20. 20. 8.39 0. -0. 0. -0.\n", + " 20. 135. -0. 10. 0. -0. -60. -0. 20. -0.\n", + " -0. 120. -0. 20. 0. -0. 20. -20. -0. 0.\n", + " -0. -0. 10. 0. 0. 0. 60. 0. 0. 0.\n", + " 0. -10. 0. 0. -0. 60. 0. 0. -0. -60.\n", + " -0. 0. 0. 10. 166.61 -0. 0. 0. -0. 20.\n", + " -0. -0. 20. 10. 0. -0. -0. -0. 0. -0.\n", + " 0. -60. 20. -0. -0. 0. -0. 20. -0. 0.\n", + " 100. 20. -0. 60. 20. ]\n", + "----------\n", + "This is the value of that optimal solution of the new objective function:\n", + " 176.61\n" + ] + } + ], "source": [ "max_biomass_flux_vector_new_obj = fba_output_new_obj[0]\n", - "max_biomass_objective_new_obj = fba_output_new_obj[1]\n", - "print('This is the optimal flux vector according to my objective function: ', max_biomass_flux_vector_new_obj)\n", + "max_biomass_objective_new_obj = fba_output_new_obj[1]\n", + "print('This is the optimal flux vector according to my objective function:\\n', max_biomass_flux_vector_new_obj)\n", "print('----------')\n", - "print('This is the value of that optimal solution of the new objective function: ', max_biomass_objective_new_obj)" + "print('This is the value of that optimal solution of the new objective function:\\n', max_biomass_objective_new_obj)" ] }, { @@ -1212,7 +1208,7 @@ "id": "b1ZAlpHY6Ifm" }, "source": [ - "### ..to find the minimum and maximum flux for each reaction (FVA)" + "### .. to find the minimum and maximum flux for each reaction (FVA)" ] }, { @@ -1221,23 +1217,7 @@ "id": "MDxY8bCiMcS7" }, "source": [ - "Let's run FVA using that; this will take a while 😉" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "colab": { - "base_uri": "https://localhost:8080/", - "height": 35 - }, - "id": "OaxWPyakpriX", - "outputId": "e673ebb2-a8dc-44de-8b71-a44606100610" - }, - "outputs": [], - "source": [ - "model.reactions[0]" + "Let's run FVA using this altered model, i.e. after changing its objective function, so it optimizes for PFK. " ] }, { @@ -1246,22 +1226,17 @@ "id": "ex3H0OXtpGr1" }, "source": [ - "As we said earlier, FVA gets the min and max value of each reaction flux that can still satisfy the constraints of the metabolic model.\n", - "\n", - "**Remember!** \n", - "\n", - "In the previous step, we replaced the `objective_function` of our model. So if we run FVA in our current model, then the first reaction of our model " + "As we said earlier, FVA gets the minimum and maximum value of each reaction flux that can still satisfy the constraints of the metabolic model." ] }, { "cell_type": "code", - "execution_count": null, + "execution_count": 30, "metadata": { "id": "AK5OdFU4yZZL" }, "outputs": [], "source": [ - "%%capture\n", "# Run FVA\n", "fva_output = model.fva()\n", "\n", @@ -1285,15 +1260,14 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 31, "metadata": { "id": "Mk-CtSOJLWo8" }, "outputs": [], "source": [ - "%%capture\n", "# Build new instance of the model\n", - "model = MetabolicNetwork.from_json('ext_data/e_coli_core.json')\n", + "model = dingo.MetabolicNetwork.from_json('../ext_data/e_coli_core.json')\n", "\n", "# Run FVA\n", "fva_output = model.fva()\n", @@ -1304,7 +1278,7 @@ "\n", "# Get the max flux distribution and the max biomass value when the objective function is maximum\n", "max_biomass_flux_vector = fva_output[2]\n", - "max_biomass_objective = fva_output[3]" + "max_biomass_objective = fva_output[3]" ] }, { @@ -1313,12 +1287,46 @@ "id": "p81io8bFMcS_" }, "source": [ - "Let's see how they're different!" + "Let's see how the first 10 minimum fluxes look like in the two cases:" + ] + }, + { + "cell_type": "code", + "execution_count": 32, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "This is the flux vector with the minimum values of each flux when the biomass function gets maximum:\n", + " [ 7.47730610e+00 0.00000000e+00 4.86063216e+00 -1.60236104e+01\n", + " 4.95973618e+00 -1.47484815e-05 -8.29602083e-06 -1.47162246e+01\n", + " 3.21489318e+00 -1.28454516e-05]\n", + "----------\n", + "This is the flux vector with the maximum values of each flux when the PFK reaction gets maximum:\n", + " [176.61 0. 10. -20. -0. -0. -0. -20. -0. -0. ]\n" + ] + } + ], + "source": [ + "print('This is the flux vector with the minimum values of each flux when the biomass function gets maximum:\\n', min_fluxes[:10])\n", + "\n", + "print('----------')\n", + "\n", + "print('This is the flux vector with the maximum values of each flux when the PFK reaction gets maximum:\\n', pfk_min_fluxes[:10])\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "and the 10 maximum ones:" ] }, { "cell_type": "code", - "execution_count": null, + "execution_count": 33, "metadata": { "colab": { "base_uri": "https://localhost:8080/" @@ -1326,15 +1334,29 @@ "id": "XYIkjxGxMcS_", "outputId": "6b75b7b1-d749-4a8b-9ab0-6d01ce7b7f00" }, - "outputs": [], - "source": [ - "print('This is the flux vector with the minimum values of each flux when the PFK reaction gets maximum: ', pfk_min_fluxes)\n", - "print('This is the flux vector with the minimum values of each flux when the biomass function gets maximum: ', min_fluxes)\n", + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "This is the flux vector with the minimum values of each flux when the PFK reaction gets maximum:\n", + " [ 1.76610000e+02 -0.00000000e+00 1.00000000e+01 -2.00000000e+01\n", + " 7.48438348e-13 -0.00000000e+00 -0.00000000e+00 -2.00000000e+01\n", + " -0.00000000e+00 -0.00000000e+00]\n", + "----------\n", + "This is the flux vector with the maximum values of each flux when the biomass function gets maximum:\n", + " [ 7.47748530e+00 6.63681667e-05 4.86111001e+00 -1.60234511e+01\n", + " 4.96021403e+00 -0.00000000e+00 -0.00000000e+00 -1.47160653e+01\n", + " 3.21489505e+00 -0.00000000e+00]\n" + ] + } + ], + "source": [ + "print('This is the flux vector with the minimum values of each flux when the PFK reaction gets maximum:\\n', pfk_max_fluxes[:10])\n", "\n", "print('----------')\n", "\n", - "print('This is the flux vector with the maximum values of each flux when the PFK reaction gets maximum: ', pfk_max_fluxes)\n", - "print('This is the flux vector with the maximum values of each flux when the biomass function gets maximum: ', max_fluxes)" + "print('This is the flux vector with the maximum values of each flux when the biomass function gets maximum:\\n', max_fluxes[:10])" ] }, { @@ -1368,16 +1390,23 @@ "To do so, you first build an instance of this class based on your `model`:" ] }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "> It is always a good practice to load again your model if you are not sure what changes you have applied to it" + ] + }, { "cell_type": "code", - "execution_count": null, + "execution_count": 34, "metadata": { "id": "HHXSrEelXh4o" }, "outputs": [], "source": [ - "from dingo import PolytopeSampler\n", - "sampler = PolytopeSampler(model)" + "model = dingo.MetabolicNetwork.from_sbml('../ext_data/e_coli_core.xml')\n", + "sampler = dingo.PolytopeSampler(model)" ] }, { @@ -1397,23 +1426,42 @@ }, { "cell_type": "markdown", - "metadata": { - "id": "xo0P1z7BMcTA" - }, + "metadata": {}, "source": [ - "So, let's sample!" + "Applying the `generate_steady_states()` directly, will sample **after optimizing for the objective function** of the model; i.e. sampling on the subspace where the objective function, e.g. the biomass function, gets its optimal value. " ] }, { "cell_type": "code", - "execution_count": null, - "metadata": { - "id": "0y_TOtcGUmAW" - }, - "outputs": [], + "execution_count": 35, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "phase 1: number of correlated samples = 500, effective sample size = 6, ratio of the maximum singilar value over the minimum singular value = 1114.25\n", + "phase 2: number of correlated samples = 500, effective sample size = 8, ratio of the maximum singilar value over the minimum singular value = 129.393\n", + "phase 3: number of correlated samples = 500, effective sample size = 4, ratio of the maximum singilar value over the minimum singular value = 267.76\n", + "phase 4: number of correlated samples = 500, effective sample size = 42, ratio of the maximum singilar value over the minimum singular value = 68.078\n", + "phase 5: number of correlated samples = 500, effective sample size = 181, ratio of the maximum singilar value over the minimum singular value = 3.22454\n", + "phase 6: number of correlated samples = 500, effective sample size = 153, ratio of the maximum singilar value over the minimum singular value = 2.49271\n", + "phase 7: number of correlated samples = 1300, effective sample size = 625\n", + "[4]total ess 1019: number of correlated samples = 4300\n", + "\n", + "\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[4]maximum marginal PSRF: 1.02734\n" + ] + } + ], "source": [ - "%%capture\n", - "steady_states = sampler.generate_steady_states(ess = 1000, psrf = True) # this took a little bit more than 5 minutes" + "optimal_steady_states = sampler.generate_steady_states(ess = 1000, psrf = True)" ] }, { @@ -1438,7 +1486,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 36, "metadata": { "colab": { "base_uri": "https://localhost:8080/" @@ -1446,10 +1494,19 @@ "id": "5F1MNZZHZCU0", "outputId": "356fe326-1342-4127-fc56-00afede234b9" }, - "outputs": [], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Number of reactions in my model: 95\n", + "Number of flux distributions sampled: 95\n" + ] + } + ], "source": [ - "print('Number of reactions in my model: ', len(model.reactions))\n", - "print('Number of flux distributions sampled: ', steady_states.shape[0])" + "print(f'Number of reactions in my model: {len(model.reactions)}')\n", + "print(f'Number of flux distributions sampled: {optimal_steady_states.shape[0]}')" ] }, { @@ -1463,7 +1520,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 37, "metadata": { "colab": { "base_uri": "https://localhost:8080/" @@ -1471,97 +1528,656 @@ "id": "767f9ouRagU5", "outputId": "e330ec6b-4c57-4294-f81b-84b17e73f09e" }, - "outputs": [], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Number of flux values of the flux distributions returned: 4300" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\n" + ] + } + ], + "source": [ + "print(f'Number of flux values of the flux distributions returned: {optimal_steady_states.shape[1]}')" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Since we sample on the subspace where the objective function, in our case the biomass, is optimal, the flux distribution of the biomass function *looks like* a *fixed* number" + ] + }, + { + "cell_type": "code", + "execution_count": 38, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "(np.float64(0.8739210000061962), np.float64(0.8739211381746426))" + ] + }, + "execution_count": 38, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "min(optimal_steady_states[24]), max(optimal_steady_states[24])" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "As discussed, applying FBA through cobra's `summary()` function returns a **single** flux vector that attains the optimal objective value." + ] + }, + { + "cell_type": "code", + "execution_count": 39, + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "

Objective

1.0 BIOMASS_Ecoli_core_w_GAM = 0.8739215069684295

Uptake

\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
MetaboliteReactionFluxC-NumberC-Flux
glc__D_eEX_glc__D_e106100.00%
nh4_eEX_nh4_e4.76500.00%
o2_eEX_o2_e21.800.00%
pi_eEX_pi_e3.21500.00%

Secretion

\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
MetaboliteReactionFluxC-NumberC-Flux
co2_eEX_co2_e-22.811100.00%
h2o_eEX_h2o_e-29.1800.00%
h_eEX_h_e-17.5300.00%
" + ], + "text/plain": [ + "" + ] + }, + "execution_count": 39, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "cobra_model.summary()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We can now examine the flux distribution of a specific reaction, rather than obtaining a single value. \n", + "\n", + "For example, focusing on an exchange reaction for one of the compounds above, we can assess how likely it is to observe a flux close to the FBA optimum." + ] + }, + { + "cell_type": "code", + "execution_count": 40, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "46" + ] + }, + "execution_count": 40, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "model.reactions.index(\"EX_co2_e\")" + ] + }, + { + "cell_type": "code", + "execution_count": 41, + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "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+eQTLFiwQP09MjIS0dHRaNeuXdmrJCIiIiK9STqCd/z4cfTq1QsKxZOX3wgh8O2336JZs2ZITk7GqVOnYGtri88//9yoxRIRERFR6SQFvPT0dHh5eam/x8XF4c6dO5g6dSrq1q2Ltm3bYuDAgTh9+rTRCiUiIiIi/UgKeCqVSuMxKBEREZDJZOjRo4d62gsvvKBxCpeIiIiIKoakgOfp6YlTp06pv+/cuRPu7u5o2rSpelpqaiqcnJzKXCARERERGUZSwAsKCsLx48fx2muvYcSIEfjzzz8RFBSk0eby5cto0KCBUYokIiIiIv1Juov2vffew6FDh/D7778DAHx9fbFw4UL1/H/++QenTp3C7NmzjVIkERFRVZSQkGBwH2dnZ3h6epZDNUT/IyngOTg44MSJE7h48SIAwNvbG2ZmZhptfv/9d7Rt27bsFRIREVUxKXhyCmzEiBEG97WxskJCYiJDHpUrye+iBYAXX3xR53QvLy+Nu2yJiIhMSSYAFYBNALwN6JcAYEReHjIyMhjwqFyVKeClpqbi999/x19//YWcnBz88MMPAIA7d+4gKSkJPj4+sLa2NkqhREREVY03gNaVXQSRDpID3po1azBz5kzk5+cDAGQymTrgpaenw8/PD+vWrcP48eONUykRERER6UXSXbR79uzBlClT4OPjg927d2PSpEka81u0aAFfX1/s3LnTGDUSERERkQEkHcH7/PPP4enpiaNHj8LW1haxsbFabXx8fHDs2LEyF0hEREREhpF0BC8uLg6BgYGwtbUtts0LL7yAtLQ0yYURERERkTSSX1WmUChKbJOeng5LS0tJRRERERGRdJICXtOmTUs8/VpQUICoqCj4+PhILoyIiIiIpJEU8IYPH45z585h0aJFWvMKCwvx3nvv4fr16xg1alSZCyQiIiIiw0i6yWLq1KnYs2cPPv74Y2zevBlWVlYAgKFDh+LMmTO4ceMG/P39MW7cOKMWS0RERESlk3QET6FQICwsDLNnz8bdu3dx8eJFCCGwfft23Lt3D7NmzcLu3bshk8mMXS8RERERlULyg44tLCzwySefYMmSJUhMTMS9e/fg4OCg8720RERERFRxyvSqMuDJGyyaNWtmjFqIiIiIyAgknaIlIiIioqpLryN4PXr0kLRwmUyGw4cPS+pLRERERNLoFfAiIiIkLZw3WRARERFV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IiLSn6SAN27cOLz//vvIzc1FVlYWJk2ahGnTpqnnx8TE4O+//0bPnj2NVScRERER6UnSKVqZTIbPPvsMn332mc75bdq0wf3792Fra1um4oiIiIjIcJICXmksLCxgYWFRHosmIiIiolJIOkX78OFD/Pzzz7h586ax6yEiIiKiMpIU8NLS0jBmzBicPn1aPS05ORlRUVFGK4yIiIiIpNE74MXExKCwsFD9XQihMT8kJATdu3c3XmVEREREJIne1+C98sorsLOzQ6dOndC8eXPIZDLIZLLyrI2IiIiIJNA74O3btw+HDx/G0aNHcejQIQBAcHAwvv/+e3Tt2hVXrlwptyKJiIiISH96B7y+ffuib9++AIC4uDi0bt0aHTt2RHp6OubOnQuVSgWZTAZ/f3907doVXbt2RYcOHaBQKMqteCIiIiLSJukmC3t7ewDAxIkTERsbi7t376rfZJGZmYmFCxeiS5cuqFGjhvEqJSIiIiK96B3wateujaFDh2Lt2rX466+/NOY5OjrC09MTAHDq1ClkZmbiwIEDGm+30Nenn36Kdu3awd7eHi4uLhg4cCASExM12uTl5WHy5MmoVasW7OzsEBQUhLS0NI02ycnJCAwMhI2NDVxcXPD++++joKDA4HqIiIiIqhu9T9EOGDAAERER2L59u/oGi2+//RZpaWno2rWrxl21tra2CAgIQEBAgMEFRUZGYvLkyWjXrh0KCgrw4Ycfwt/fH5cvX1a/GWP69OnYt28ftm3bBkdHR0yZMgWDBw/G8ePHAQCFhYUIDAyEm5sboqOjkZKSglGjRkGhUGDp0qUG10RERERUnegd8H744QcAwK1bt7B9+3bMmDED58+fR0REBGQyGczMzAAAa9asQefOneHj4yOpoIMHD2p837BhA1xcXBAbG4suXbogKysLP/74I0JDQ9GjRw8ATx7R4u3tjRMnTqBjx444dOgQLl++jD/++AOurq5o1aoVFi9ejFmzZmHhwoV8ywYRERGZNINfVVa3bl30798fM2bMwPr169GhQwccPXoUa9euRUxMDKZMmQKZTAYnJyd07twZO3fuLFOBWVlZAICaNWsCAGJjY6FUKtGrVy91m2bNmsHT0xMxMTHo2LEjYmJi4OPjA1dXV3WbgIAATJo0CZcuXcJLL72ktZ78/Hzk5+erv2dnZwMAlEollEplmbbBFBWNCcfGMBw3aarTuKlUKlhbW0MFwJBqrQGD+5TWT2ltrfHfsq6vPGqsiusqadwqo0ZVUR+Vqsr/DFSnn9WqpDzGSyaefWKxHq5du4bGjRtj+/btGDx4MABg0aJF+Pjjj5GSkoKIiAhERkYiKioK8fHxkotTqVR49dVXkZmZiT///BMAEBoaijFjxmiEMQBo3749unfvjs8++wwTJkzAP//8g7CwMPX8nJwc2NraYv/+/eq7gZ+2cOFCLFq0SGt6aGgobGxsJG8DERERUWlycnLw5ptvIisrCw4ODmVensFH8ABAoVDAy8tLfU3c01xcXDB06FAMHTq0zMVNnjwZFy9eVIe78jRnzhzMmDFD/T07OxseHh7w9/c3ykCbGqVSifDwcPTu3ZuPwjEAx02a6jRu58+fR5cuXRAFoKWefbYCGA8Y1Eeffkpra4T/9BN6jx0LRW5umdZXXjVWxXUVN26VVeN5AF0AREVFoWVLQ9ZW8arTz2pVolQqsWvXLqMuU1LA8/T0RFJSksY0IYTW68vKYsqUKdi7dy+ioqJQt25d9XQ3Nzc8fvwYmZmZcHJyUk9PS0uDm5ubus2pU6c0lld0l21Rm2dZWlrC0tJSa7pCoeBOWgKOjzQcN2mqw7jJ5XLk5uZCDsCQSnMBg/vo20+Rm6sVVKSsrzxrrIrr0jVulVGjvKiPXF7l9/8i1eFn1dRJeg6eLgsXLoRKpSrzcoQQmDJlCnbs2IEjR46gfv36GvPbtGkDhUKBw4cPq6clJiYiOTkZfn5+AAA/Pz/Ex8cjPT1d3SY8PBwODg5o3rx5mWskIiIiqsokHcErT5MnT0ZoaCh27doFe3t7pKamAnjyrD1ra2s4Ojpi3LhxmDFjBmrWrAkHBwdMnToVfn5+6NixIwDA398fzZs3x8iRI7F8+XKkpqZi7ty5mDx5ss6jdERERESmpMoFvLVr1wIAunXrpjE9JCQEo0ePBgCsXLkScrkcQUFByM/PR0BAANasWaNua2Zmhr1792LSpEnw8/ODra0tgoOD8fHHH1fUZhARERFVGr0CXp8+fbB48WK0a9fO4BU8evQI33zzDezt7TF58uRS2+tzHZ+VlRVWr16N1atXF9vGy8sL+/fvN6hWIiIiIlOg1zV4d+7cQceOHdG9e3eEhISon01XkhMnTmDKlCnw8vLC4sWLNZ5JR0RERETlR68jeLGxsdi4cSMWLVqEcePGYfz48WjatCnatGkDV1dXODk5IS8vD/fu3UNiYiLOnDmDBw8ewMzMDMOGDcOSJUvU76olIiIiovKl9zV4wcHBGDVqFPbv34+QkBBERERg06ZNWu3kcjl8fX0xaNAgvPXWW3B3dzdqwURERERUMoNuspDJZAgMDERgYCAAICEhAbdu3cLdu3dhbW2N2rVro0WLFnB0dCyXYomIiIiodGW6i9bb2xve3t7GqoWIiIiIjMBoDzomIiIioqqBAY+IiIjIxDDgEREREZkYBjwiIiIiE1PlXlVGRERE2hISEgzu4+zszOfQPqcY8IiIiKqwFDw53TZixAiD+9pYWSEhMZEh7zkkKeDl5+fD0tLS2LUQERHRMzIBqABsAmDIg8kSAIzIy0NGRgYD3nNIUsCrU6cORowYgbfeegs+Pj7GromIiIie4Q2gdWUXQdWGpJss7O3t8c0336BVq1bw8/PDTz/9hJycHGPXRkREREQSSAp4SUlJOHDgAAYPHoxz585h/PjxcHd3x8SJE3HmzBlj10hEREREBpAU8GQyGQICArBt2zbcunULy5cvxwsvvIDvv/8eHTp0wEsvvYS1a9ciOzvb2PUSERERUSnK/Bw8Z2dnzJw5E5cvX8axY8cQHByMq1evYsqUKahTpw7GjBmDU6dOGaNWIiIiItKDUR90bG9vDxsbG5ibm0MIgcLCQmzcuBF+fn4IDAxEenq6MVdHRERERDqUOeA9fPgQ33//Pdq3b4+XXnoJa9asQZMmTfDjjz/i3r17OHXqFF577TUcOHAAb7/9tjFqJiIiIqISSH7Q8YkTJ7B+/Xps27YNDx8+hJ2dHSZMmIC3334brVq1Urdr27Ytfv31V1hYWGD37t3GqJmIiIiISiAp4Pn4+ODy5csQQuCll17C22+/jTfffBN2dnbF9mnRogU2b94suVAiIiIi0o+kgHf9+nWMGTMGb7/9Ntq1a6dXn+HDh8PPz0/K6oiIiIjIAJICXkpKChwcHAzq4+HhAQ8PDymrIyIiIiIDSLrJwtbWFtnZ2VCpVDrnq1QqZGdno7CwsEzFEREREZHhJAW8RYsWwcXFBXfv3tU5/+7du3B1dcUnn3xSpuKIiIiIyHCSAt7evXvRs2dP1K5dW+f82rVro1evXti1a1eZiiMiIiIiw0kKeNevX0ezZs1KbNO0aVMkJSVJKoqIiIiIpJMU8JRKJeTykrvKZDLk5eVJKoqIiIiIpJMU8Bo1aoQjR46U2ObIkSOoX7++pKKIiIiISDpJAW/w4MGIi4vD/Pnzte6ULSwsxLx58xAXF4chQ4YYpUgiIiIi0p+k5+DNnDkTW7ZswSeffIItW7age/fueOGFF3D79m0cPXoU165dg7e3N9577z1j10tEREREpZAU8Ozs7BAVFYVJkyZhx44duHr1qnqeXC7Ha6+9hjVr1pT46jIiIiIiKh+SAh7w5FEo27dvR1paGs6cOYOsrCw4OTmhbdu2cHFxMWaNRERERGQAyQGviKurKwIDA41RCxEREREZgaSbLIiIiIio6pJ8BO/y5cv49ttvcfr0aWRmZup876xMJsO1a9fKVCARERERGUZSwIuMjESfPn2Qn58Pc3NzuLq6wtxce1FCiDIXSERERESGkRTwZs+ejYKCAvzwww8IDg6GmZmZsesiIqp0ycnJyMjIMKhPQkJCOVVDRKQ/SQHv/PnzGDZsGMaOHWvseoiIqoTk5GR4N22KHL5ykYiqIUkBz9bWlo9CISKTlpGRgZy8PGwC4G1Av/0A5pVTTURE+pIU8Pr164djx44ZuxYioirHG0BrA9rzBC0RVQWSHpPy+eefIzMzE//973+Rk5Nj7JqIiIiIqAwkHcEbNmwY7OzssHr1amzYsAFNmjSBg4ODVjuZTIbDhw+XuUgiIiIi0p+kgBcREaH+/4cPH+Ls2bM628lkMklFEREREZF0kgKeSqUydh1EREREZCR8VRkRERGRiZH8qrIiDx8+xN9//41Hjx6hc+fOxqiJiIiIiMpA8hG8GzduYMCAAahRowbatWuH7t27q+cdP34czZs317hWj4iIiIgqhqSAl5ycjI4dO2L//v0YMGAA/Pz8NN4726FDB2RkZOCXX34xWqFEREREpB9JAW/BggW4f/8+IiMjsX37dvTu3Vtjvrm5OTp37ozjx48bpUgiIiIi0p+kgBcWFoZBgwbh5ZdfLraNl5cXbt++bfCyo6Ki0L9/f9SpUwcymQw7d+7UmD969GjIZDKNT58+fTTa3Lt3D8OHD4eDgwOcnJwwbtw4PHz40OBaiIiIiKojSQHv3r17qFevXolthBDIz883eNmPHj1Cy5YtsXr16mLb9OnTBykpKerPs6eChw8fjkuXLiE8PBx79+5FVFQUJkyYYHAtRERERNWRpLtoXV1dceXKlRLbxMfHw9PT0+Bl9+3bF3379i2xjaWlJdzc3HTOS0hIwMGDB3H69Gm0bdsWAPDNN9+gX79+WLFiBerUqWNwTURERETViaSA17t3b/y///f/cOHCBfj6+mrNP3bsGI4cOYJp06aVtT6dIiIi4OLigho1aqBHjx5YsmQJatWqBQCIiYmBk5OTOtwBQK9evSCXy3Hy5EkMGjRI5zLz8/M1jjhmZ2cDAJRKJZRKZblsR3VWNCYcG8Nw3KSpjHFTqVSwtraGCoCha7UGDO4npU9p/ZTW1hr/rYo1VsV1lTRuVaVGfaiK+qlUFfazw99x0pTHeMnE07e/6unGjRto1aoVAOD999/HX3/9hdDQUOzduxfR0dH48ssvYWtri/Pnz8Pd3V16cTIZduzYgYEDB6qnbdmyBTY2Nqhfvz6uXbuGDz/8EHZ2doiJiYGZmRmWLl2KjRs3IjExUWNZLi4uWLRoESZNmqRzXQsXLsSiRYu0poeGhsLGxkbyNhARERGVJicnB2+++SaysrLg4OBQ5uVJOoJXr149hIWFYdiwYZg3bx5kMhmEEPjPf/4DIQQ8PT2xffv2MoW74gwbNkz9/z4+PvD19UXDhg0RERGBnj17Sl7unDlzMGPGDPX37OxseHh4wN/f3ygDbWqUSiXCw8PRu3dvKBSKyi6n2uC4SVMZ43b+/Hl06dIFUQBaGtBvK4DxgEH9pPTRp5/S2hrhP/2E3mPHQpGbWyVrrIrrKm7cqlKN+jgPoAue3LzYsqUhPaXj7zhplEoldu3aZdRlSn6TRYcOHXDlyhXs2bMHJ0+exL179+Dg4IAOHTpgwIABsLCwMGadxWrQoAGcnZ1x9epV9OzZE25ubkhPT9doU1BQgHv37hV73R7w5Lo+S0tLrekKhYI7aQk4PtJw3KSpyHGTy+XIzc2FHICha8wFDO4npY++/RS5uVpBparVWBXXpWvcqlqNJZEX9ZPLK/z3DX/HVb4yvarM3NwcgwYNKva6topw69Yt3L17V3200M/PD5mZmYiNjUWbNm0AAEeOHIFKpUKHDh0qrU4iIiKiilLmd9Ea28OHD3H16lX196SkJMTFxaFmzZqoWbMmFi1ahKCgILi5ueHatWv44IMP0KhRIwQEBAAAvL290adPH4wfPx7r1q2DUqnElClTMGzYMN5BS0RERM8FSQHv448/1qudTCbDvHnzDFr2mTNnNN5rW3RdXHBwMNauXYsLFy5g48aNyMzMRJ06deDv74/FixdrnF7dvHkzpkyZgp49e0IulyMoKAhff/21QXUQERERVVeSAt7ChQtLnF9004WUgNetWzeUdGNvWFhYqcuoWbMmQkNDDVovERERkamQFPCOHj2qc3pWVhbOnj2Lr7/+Gr169cLkyZPLVBwRERERGU5SwOvatWux81599VUMHz4crVu3RlBQkOTCiIiIiEgaSe+iLU3jxo0xaNAgLFu2rDwWT0REREQlKJeABzx5c8Szb5MgIiIiovJXLgEvPz8fBw8ehJOTU3ksnoiIiIhKIOkavJ9//lnn9IKCAty+fRtbtmzBX3/9hf/+979lKo6IiIiIDCcp4I0ePRoymUxretHjTWQyGd544w1eg0dERERUCSQFvJCQEJ3T5XI5atSogTZt2qhfHUZEREREFUtSwAsODjZ2HURERERkJOV2Fy0RERERVQ5JR/CioqIkr7BLly6S+xIRERFR6SQFvG7duum8yUIfhYWFkvoRERGR4RISEgxq7+zsDE9Pz3KqhiqKpIA3f/58nDx5EmFhYWjcuDFeeeUVuLq6Ii0tDdHR0fj7778REBCAjh07GrteIiIi0kMKnlyHNWLECIP62VhZISExkSGvmpMU8Hr27Illy5bh+++/x7hx4zSO5gkhsH79erz77rv46KOP0KlTJ6MVS0RERPrJBKACsAmAt559EgCMyMtDRkYGA141JyngzZs3D4GBgXjrrbe05slkMkyYMAEHDx7EvHnzcPTo0TIXSURERNJ4A2hd2UVQhZN0F21sbCy8vUv+94C3tzfOnDkjqSgiIiIikk5SwLOwsMC5c+dKbHPu3DlYWFhIKoqIiIiIpJMU8Pz9/XHw4EEsW7YMjx8/1pj3+PFjfPrppwgLC0NAQIBRiiQiIiIi/Um6Bu/zzz/HsWPH8NFHH2HVqlVo27YtXFxckJ6ejjNnziA9PR116tTB8uXLjV0vEREREZVCUsCrW7cuzpw5g9mzZ2Pr1q3Yt2+fep6VlRVGjhyJZcuWwc3NzWiFEhEREZF+JAU8AHBzc8OGDRuwfv16JCYmIisrC46OjmjSpAmvvSMiIiKqRJIDXhGFQoEXX3zRGLUQERERkRGUKeClpqbi999/x19//YWcnBz88MMPAIA7d+4gKSkJPj4+sLa2NkqhRERERKQfyQFvzZo1mDlzJvLz8wE8ecBxUcBLT0+Hn58f1q1bh/HjxxunUiIiIiLSi6THpOzZswdTpkyBj48Pdu/ejUmTJmnMb9GiBXx9fbFz505j1EhEREREBpD8mBRPT08cPXoUtra2iI2N1Wrj4+ODY8eOlblAIiIiIjKMpCN4cXFxCAwMhK2tbbFtXnjhBaSlpUkujIiIiIikkRTwVCoVFApFiW3S09NhaWkpqSgiIiIikk5SwGvatGmJp18LCgoQFRUFHx8fyYURERERkTSSAt7w4cNx7tw5LFq0SGteYWEh3nvvPVy/fh2jRo0qc4FEREREZBhJN1lMnToVe/bswccff4zNmzfDysoKADB06FCcOXMGN27cgL+/P8aNG2fUYomIiIiodJKO4CkUCoSFhWH27Nm4e/cuLl68CCEEtm/fjnv37mHWrFnYvXs3ZDKZseslIiIiolJIftCxhYUFPvnkEyxZsgSJiYm4d+8eHBwc4O3tDTMzM2PWSEREREQGkBTwGjRogL59+2L16tWQyWRo1qyZsesiIiIiIokknaLNyMiAg4ODsWshIiIiIiOQdATP19cXf//9t7FrISIqN8nJycjIyNC7fUJCQjlWQ0RUviQFvFmzZiEoKAhHjx5F9+7djV0TEZFRJScnw7tpU+Tk5VV2KUREFUJSwLt//z78/f3h7++PgQMHol27dnB1ddV51yyfhUdElS0jIwM5eXnYBMBbzz77Acwrx5qIiMqTpIA3evRoyGQyCCHw22+/4bfffgMAjYAnhIBMJmPAI6IqwxtAaz3b8gQtEVVnkgJeSEiIsesgIiIiIiPRO+BlZ2fDysoKFhYWCA4OLs+aiIiIiKgM9H5MSo0aNfDZZ59pTDt16hS+/vproxdFRERERNLpHfCEEBBCaEw7cOAApk+fbvSiiIiIiEg6SQ86JiIiIqKqiwGPiIiIyMQw4BERERGZGAY8IiIiIhNj0HPwNm3ahBMnTqi/X716FQDQr18/ne1lMhn27dtXhvKIiIiIyFAGBbyrV6+qQ93TDh48qLO9rleXEREREVH50jvgJSUllWcdRERERGQkegc8Ly+v8qyDiIiIiIykyt1kERUVhf79+6NOnTqQyWTYuXOnxnwhBObPnw93d3dYW1ujV69euHLlikabe/fuYfjw4XBwcICTkxPGjRuHhw8fVuBWEBEREVWeKhfwHj16hJYtW2L16tU65y9fvhxff/011q1bh5MnT8LW1hYBAQHIy8tTtxk+fDguXbqE8PBw7N27F1FRUZgwYUJFbQIRERFRpTLoJouK0LdvX/Tt21fnPCEEvvrqK8ydOxcDBgwAAPz8889wdXXFzp07MWzYMCQkJODgwYM4ffo02rZtCwD45ptv0K9fP6xYsQJ16tSpsG0hIiIiqgxVLuCVJCkpCampqejVq5d6mqOjIzp06ICYmBgMGzYMMTExcHJyUoc7AOjVqxfkcjlOnjyJQYMG6Vx2fn4+8vPz1d+zs7MBAEqlEkqlspy2qPoqGhOOjWE4btKUddxUKhWsra2hAmDIEqwBg/tI7Vce61JaW2v8tyrWWBXXVdK4VZUay6ufqqiPSiXp542/46Qpj/GSCSGE0ZdqJDKZDDt27MDAgQMBANHR0XjllVfw77//wt3dXd1u6NChkMlk+PXXX7F06VJs3LgRiYmJGstycXHBokWLMGnSJJ3rWrhwIRYtWqQ1PTQ0FDY2NsbbKCIiIqJn5OTk4M0330RWVhYcHBzKvLxqdQSvPM2ZMwczZsxQf8/OzoaHhwf8/f2NMtCmRqlUIjw8HL1794ZCoajscqoNjps0ZR238+fPo0uXLogC0FLPPlsBjAcM6iO1X3mtS2ltjfCffkLvsWOhyM2tkjVWxXUVN25Vqcby6nceQBc8ueGxZUtD1vYEf8dJo1QqsWvXLqMus1oFPDc3NwBAWlqaxhG8tLQ0tGrVSt0mPT1do19BQQHu3bun7q+LpaUlLC0ttaYrFArupCXg+EjDcZNG6rjJ5XLk5uZCDsCQ3rmAwX2k9ivPdSlyc7WCSlWrsSquS9e4VbUajd1PXtRHLi/T7yj+jqt8Ve4u2pLUr18fbm5uOHz4sHpadnY2Tp48CT8/PwCAn58fMjMzERsbq25z5MgRqFQqdOjQocJrJiIiIqpoVe4I3sOHDzVeh5aUlIS4uDjUrFkTnp6emDZtGpYsWYLGjRujfv36mDdvHurUqaO+Ts/b2xt9+vTB+PHjsW7dOiiVSkyZMgXDhg3jHbRERET0XKhyAe/MmTPo3r27+nvRdXHBwcHYsGEDPvjgAzx69AgTJkxAZmYmOnXqhIMHD8LKykrdZ/PmzZgyZQp69uwJuVyOoKAgfP311xW+LURERESVocoFvG7duqGkG3tlMhk+/vhjfPzxx8W2qVmzJkJDQ8ujPCIiIqIqr1pdg0dEREREpWPAIyIiIjIxDHhEREREJqbKXYNHRFSS5ORkZGRkGNQnISGhnKohIqqaGPCIqNq4desWXmzeHDl5eZVdChFRlcaAR0TVxt27d5GTl4dNALwN6LcfwLxyqomIqCpiwCOiascbQGsD2vMELRE9b3iTBREREZGJYcAjIiIiMjEMeEREREQmhgGPiIiIyMQw4BERERGZGAY8IiIiIhPDgEdERERkYhjwiIiIiEwMAx4RERGRiWHAIyIiIjIxDHhEREREJoYBj4iIiMjEMOARERERmRgGPCIiIiITw4BHREREZGIY8IiIiIhMDAMeERERkYlhwCMiIiIyMeaVXQARERFVLQkJCQb3cXZ2hru7ezlUQ1Iw4BEREREAIAVPTu2NGDHC4L42Vla4ePmy0WsiaRjwiIiICACQCUAFYBMAbwP6JQAYkZeHu3fvlkdZJAEDHhEREWnwBtC6sougMuFNFkREREQmhgGPiIiIyMQw4BERERGZGAY8IiIiIhPDgEdERERkYhjwiIiIiEwMH5NCRERERpGYmAg7OzucP38ecnnpx5CcnZ3h6elZAZU9fxjwiKjSJCcnIyMjo9R2KpUKwJO/PIio6il6A8b48ePxyy+/oEuXLsjNzS21n42VFRISExnyygEDHhFViuTkZHg3bYqcvLxS21pbW+OXX37B+PHjK6AyIjJUJp68AWP9/32PQunXgBW9/SIjI4MBrxww4BFRpcjIyEBOXp5er0RSAbgNYC6Aj8q9MiKSqime/Ky2BKCo5Fqedwx4RFSp9HklkhJP/tLwKv9yiIhMAu+iJSIiIjIxDHhEREREJoYBj4iIiMjEMOARERERmRgGPCIiIiITw4BHREREZGIY8IiIiIhMDAMeERERkYlhwCMiIiIyMQx4RERERCam2gW8hQsXQiaTaXyaNWumnp+Xl4fJkyejVq1asLOzQ1BQENLS0iqxYiIiIqKKVe0CHgC0aNECKSkp6s+ff/6pnjd9+nTs2bMH27ZtQ2RkJP79918MHjy4EqslIiIiqljmlV2AFObm5nBzc9OanpWVhR9//BGhoaHo0aMHACAkJATe3t44ceIEOnbsWNGlEhEREVW4ahnwrly5gjp16sDKygp+fn749NNP4enpidjYWCiVSvTq1UvdtlmzZvD09ERMTEyJAS8/Px/5+fnq79nZ2QAApVIJpVJZfhtTTRWNCcfGMBy3/1GpVLC2toYKQGmjobS2fvI/1tawBvTq8ywp/ar7uorGTT1+VbDGqriuksatqtRYZdel59jh/9ZhjSe/C57334nlsf0yIYQw+lLL0YEDB/Dw4UM0bdoUKSkpWLRoEW7fvo2LFy9iz549GDNmjEZQA4D27duje/fu+Oyzz4pd7sKFC7Fo0SKt6aGhobCxsTH6dhAREREVycnJwZtvvomsrCw4ODiUeXnV7ghe37591f/v6+uLDh06wMvLC1u3boW1Hv9iKM6cOXMwY8YM9ffs7Gx4eHjA39/fKANtapRKJcLDw9G7d28oFIrKLqfa4Lj9z/nz59GlSxdEAWhZSlultTXCf/oJD8eOxfjcXL36PG0rgPGAQf2k9Klq6yoat95jx0KRm1sla6yK6ypu3KpSjVV1XRHW1kgpZeyKnAfQBUBUVBRatjRkbaZHqVRi165dRl1mtQt4z3JyckKTJk1w9epV9O7dG48fP0ZmZiacnJzUbdLS0nRes/c0S0tLWFpaak1XKBTP/V/EJeH4SMNxA+RyOXJzcyEHoPdI5OYa3qeoK2BwPyl9quK6FLm5Wn/ZVrUaq+K6dI1bVauxKq4LKH3s8H9tc/Hkd8Hz/vuwPFTLu2if9vDhQ1y7dg3u7u5o06YNFAoFDh8+rJ6fmJiI5ORk+Pn5VWKVRERERBWn2h3Be++999C/f394eXnh33//xYIFC2BmZoY33ngDjo6OGDduHGbMmIGaNWvCwcEBU6dOhZ+fH++gJSpHycnJyMjIMKhPQkJCOVVDRETVLuDdunULb7zxBu7evYvatWujU6dOOHHiBGrXrg0AWLlyJeRyOYKCgpCfn4+AgACsWbOmkqsmMl3JycnwbtoUOXl5lV0KERH9n2oX8LZs2VLifCsrK6xevRqrV6+uoIqInm8ZGRnIycvDJgDeBvTbD2BeOdVERPS8q3YBj4iqJm8ArQ1ozxO0RETlp9rfZEFEREREmhjwiIiIiEwMAx4RERGRiWHAIyIiIjIxDHhEREREJoYBj4iIiMjEMOARERERmRgGPCIiIiITw4BHREREZGIY8IiIiIhMDAMeERERkYlhwCMiIiIyMQx4RERERCaGAY+IiIjIxDDgEREREZkYBjwiIiIiE2Ne2QUQERHR8yshIcHgPs7OzvD09CyHakwHAx4RERFVuBQ8OY04YsQIg/vaWFkhITGRIa8EDHhERERU4TIBqABsAuBtQL8EACPy8pCRkcGAVwIGPCLSkJycjIyMDL3bSzm9QkRUxBtA68ouwgQx4BGRWnJyMrybNkVOXl5ll0JERGXAgEdEahkZGcjJyzPolMl+APPKsSYiIjIcAx4RaTHklAlP0BIRVT18Dh4RERGRiWHAIyIiIjIxDHhEREREJoYBj4iIiMjEMOARERERmRjeRUtUxRn64OEifFcjEdHziwGPqAory4OH+a5GIqLnFwMeURUm5cHDAN/VSET0vGPAI6oGpL6r0dD3xPK9skREpoEBj8gEpeDJHVQjRoyo7FKIiKgSMOARmaBMACrA4FO7fK8sEVUXhp5xeN5uPGPAIzJhhp7a5QlaIqrqpJ6heN5uPGPAIyIiomojE4afoXgebzxjwCMiIqJqR+rNZ88LvsmCiIiIyMQw4BERERGZGJ6iJSIioueClGd9Vte7bxnwiIiIyKSV5dmg1fXuWwY8IiIiMmmZkPZs0Op89y0DHhERET0Xnqc7b3mTBREREZGJYcAjIiIiMjE8RUtUgW7dugUAOH/+POTy0v99JeWOLyIiIgY8ogqSnJyMdm3a4MeQEHTp0gW5ubmVXRIREZkoBjyiCpKRkYGcvDwAQBT0uz5iP4B55VkUERGZJAY8eu4lJycjIyPDoD5lffBlSwAKPdrxBC0REUnBgEfPteTkZHg3bao+sqav6vrgSyIiej6YdMBbvXo1Pv/8c6SmpqJly5b45ptv0L59+8oui/RQUUfVik6bGvLwy6IHXx47dgze3vo/MpM3TBARVU/V8RVnJhvwfv31V8yYMQPr1q1Dhw4d8NVXXyEgIACJiYlwcXHRezlxcXGws7PTu73UP9DKOE1YVUk9qmZlaYntv/0Gd3d3vfsU/dAa8vDLsrzyxtra2uA+RERUOcry+96Qv5NUKpXhxZXCZAPel19+ifHjx2PMmDEAgHXr1mHfvn346aefMHv2bL2X07VrV4PWK+XUHU8TapJyVO0YgBn5+fjPf/5TjpU9kQlpr7zZD2BpeRRERETlIhPSft8b+neStbU1fvzxR4PrK4lJBrzHjx8jNjYWc+bMUU+Ty+Xo1asXYmJidPbJz89Hfn6++ntWVhYAYJqlJRrKZHqt9xaAVQDCwsLQuHFjveu9cuUKVABmWVmhrp59pK4LeDIWhv5r4dk+KpUKOTk5OHbsWInPc5OyritXrsDKygo5ALL17HMXgAWAdwG9xxAAzgL4BUCsAetKBGAFGFQfADwGnmxXTg6OWVlBLoTe6zKkvoruVxHrUv3fuF2xsoKVECazXeW9LlUx+1tVqrEqrqu4catKNVbVdZ2zsoKdnr/jqtN2Gfr73tC/k2T/t88BgNDj7wa9CBN0+/ZtAUBER0drTH///fdF+/btdfZZsGCBAMAPP/zwww8//PBTaZ+bN28aJQuZ5BE8KebMmYMZM2aov6tUKty7dw+1atWCTM8jeM+T7OxseHh44ObNm3BwcKjscqoNjps0HDdpOG7ScNyk49hJUzRuly9fRp06dYyyTJMMeM7OzjAzM0NaWprG9LS0NLi5uensY2lpCUtLS41pTk5O5VWiyXBwcOAPsQQcN2k4btJw3KThuEnHsZPmhRde0Os1lvowzlKqGAsLC7Rp0waHDx9WT1OpVDh8+DD8/PwqsTIiIiKi8meSR/AAYMaMGQgODkbbtm3Rvn17fPXVV3j06JH6rloiIiIiU2WyAe/111/HnTt3MH/+fKSmpqJVq1Y4ePAgXF1dK7s0k2BpaYkFCxZondamknHcpOG4ScNxk4bjJh3HTpryGDeZEMa6H5eIiIiIqgKTvAaPiIiI6HnGgEdERERkYhjwiIiIiEwMAx4RERGRiWHAIy2ffvop2rVrB3t7e7i4uGDgwIFITEwstd+2bdvQrFkzWFlZwcfHB/v376+AaqsOKeO2YcMGyGQyjY+VlVUFVVw1rF27Fr6+vuoHo/r5+eHAgQMl9nne9zXA8HHjvqbbsmXLIJPJMG3atBLbcZ/TpM+4cZ97YuHChVrj0KxZsxL7GGN/Y8AjLZGRkZg8eTJOnDiB8PBwKJVK+Pv749GjR8X2iY6OxhtvvIFx48bh3LlzGDhwIAYOHIiLFy9WYOWVS8q4AU+e+J6SkqL+/PPPPxVUcdVQt25dLFu2DLGxsThz5gx69OiBAQMG4NKlSzrbc197wtBxA7ivPev06dP47rvv4OvrW2I77nOa9B03gPtckRYtWmiMw59//llsW6Ptb0Z5oy2ZtPT0dAFAREZGFttm6NChIjAwUGNahw4dxNtvv13e5VVZ+oxbSEiIcHR0rLiiqokaNWqIH374Qec87mvFK2ncuK9pevDggWjcuLEIDw8XXbt2Fe+++26xbbnP/Y8h48Z97okFCxaIli1b6t3eWPsbj+BRqbKysgAANWvWLLZNTEwMevXqpTEtICAAMTEx5VpbVabPuAHAw4cP4eXlBQ8Pj1KPwJi6wsJCbNmyBY8ePSr2tYLc17TpM24A97WnTZ48GYGBgVr7ki7c5/7HkHEDuM8VuXLlCurUqYMGDRpg+PDhSE5OLratsfY3k32TBRmHSqXCtGnT8Morr+DFF18stl1qaqrWW0JcXV2Rmppa3iVWSfqOW9OmTfHTTz/B19cXWVlZWLFiBV5++WVcunQJdevWrcCKK1d8fDz8/PyQl5cHOzs77NixA82bN9fZlvva/xgybtzX/mfLli04e/YsTp8+rVd77nNPGDpu3Oee6NChAzZs2ICmTZsiJSUFixYtQufOnXHx4kXY29trtTfW/saARyWaPHkyLl68WOL1AqRN33Hz8/PTOOLy8ssvw9vbG9999x0WL15c3mVWGU2bNkVcXByysrKwfft2BAcHIzIystiwQk8YMm7c1564efMm3n33XYSHhz+XF/xLJWXcuM890bdvX/X/+/r6okOHDvDy8sLWrVsxbty4clsvAx4Va8qUKdi7dy+ioqJK/deWm5sb0tLSNKalpaXBzc2tPEuskgwZt2cpFAq89NJLuHr1ajlVVzVZWFigUaNGAIA2bdrg9OnTWLVqFb777jutttzX/seQcXvW87qvxcbGIj09Ha1bt1ZPKywsRFRUFL799lvk5+fDzMxMow/3OWnj9qzndZ97lpOTE5o0aVLsOBhrf+M1eKRFCIEpU6Zgx44dOHLkCOrXr19qHz8/Pxw+fFhjWnh4eInXA5kaKeP2rMLCQsTHx8Pd3b0cKqw+VCoV8vPzdc7jvla8ksbtWc/rvtazZ0/Ex8cjLi5O/Wnbti2GDx+OuLg4nSGF+5y0cXvW87rPPevhw4e4du1aseNgtP3NoFsy6LkwadIk4ejoKCIiIkRKSor6k5OTo24zcuRIMXv2bPX348ePC3Nzc7FixQqRkJAgFixYIBQKhYiPj6+MTagUUsZt0aJFIiwsTFy7dk3ExsaKYcOGCSsrK3Hp0qXK2IRKMXv2bBEZGSmSkpLEhQsXxOzZs4VMJhOHDh0SQnBfK46h48Z9rXjP3g3KfU4/pY0b97knZs6cKSIiIkRSUpI4fvy46NWrl3B2dhbp6elCiPLb33iKlrSsXbsWANCtWzeN6SEhIRg9ejQAIDk5GXL5/w4Av/zyywgNDcXcuXPx4YcfonHjxti5c2eJNxiYGinjdv/+fYwfPx6pqamoUaMG2rRpg+jo6Ofq2rP09HSMGjUKKSkpcHR0hK+vL8LCwtC7d28A3NeKY+i4cV/TH/c5abjP6Xbr1i288cYbuHv3LmrXro1OnTrhxIkTqF27NoDy299kQghh1C0hIiIiokrFa/CIiIiITAwDHhEREZGJYcAjIiIiMjEMeEREREQmhgGPiIiIyMQw4BERERGZGAY8IiIiIhPDgEdERFTFRUVFoX///qhTpw5kMhl27txZrutbuHAhZDKZxqdZs2aSlxcREYEBAwbA3d0dtra2aNWqFTZv3lxin/Pnz+ONN96Ah4cHrK2t4e3tjVWrVmm127x5M1q2bAkbGxu4u7tj7NixuHv3rkabbdu2oVmzZrCysoKPjw/279+vMT8tLQ2jR49GnTp1YGNjgz59+uDKlSsabVJTUzFy5Ei4ubnB1tYWrVu3xm+//WbQOFy6dAlBQUGoV68eZDIZvvrqK4P6G4IBj6gaKfqlUNLn6V8YN27cgEwmQ7169SqtZn2pVCq0bdsWbm5uePToUWWXU+GK/vxKEhISAplMhuXLl5dpXUX7xdMfMzMzODk5oUGDBujfvz+WLl2Kf/75R2f/w4cPQyaTwcnJCYWFhTrbLFmyRL3sP//8U2ebM2fOQCaTwdLSErm5uTrbBAcHQyaT4dSpUwCA0aNHQyaTYcOGDQZtc1E9z/7FXl08evQILVu2xOrVqytsnS1atEBKSor6U9yfYxGZTIYbN27onBcdHQ1fX1/89ttvuHDhAsaMGYNRo0Zh7969xS4vNjYWLi4u2LRpEy5duoSPPvoIc+bMwbfffqtuc/z4cYwaNQrjxo3DpUuXsG3bNpw6dQrjx4/XWPcbb7yBcePG4dy5cxg4cCAGDhyIixcvAnjyHvGBAwfi+vXr2LVrF86dOwcvLy/06tVL43fRqFGjkJiYiN27dyM+Ph6DBw/G0KFDce7cOX2GEwCQk5ODBg0aYNmyZXBzc9O7nyTGe9saEZU3Ly8vAUC88sorIjg4WOcnLCxM3T4pKUkAEF5eXpVXtJ6+//57AUB8++23lV1KpQAgSvuVHBgYKACIq1evlmldRfsFABEUFKTedwYPHiz8/PyElZWVACBkMpl4++23xYMHDzT65+TkCAsLCwFAnD59Wuc6evTooV7H4sWLdbZZvny5ACA6d+6sc/7jx4+Fk5OT8PDwECqVSgghRHBwsAAgQkJCDNrmhw8fCldXV9GkSRPx+PFjg/pWNQDEjh07NKbl5eWJmTNnijp16ggbGxvRvn17cfToUcnrWLBggWjZsqXBdSUlJendvl+/fmLMmDEGreOdd94R3bt3V3///PPPRYMGDTTafP311+KFF15Qfx86dKgIDAzUaNOhQwfx9ttvCyGESExMFADExYsX1fMLCwtF7dq1xfr169XTbG1txc8//6yxnJo1a2q0SU5OFkOGDBGOjo6iRo0a4tVXXy12TLy8vMTKlSv123AJeASPqBp66623sGHDBp0ff3//yi7PYLm5ufjoo49Qp04dTJgwobLLqZKys7MRHh6Oli1bomHDhkZb7ooVK9T7zm+//Ybo6Gjcu3cPq1evhp2dHb777jv069cP+fn56j7W1tbo2LEjAODo0aNay3z8+DFiYmLw4osvwsLCAhERETrXXTS9e/fuOucfOXIEmZmZGDx4cKlHN0tja2uL999/H3///bf6vdGmZMqUKYiJicGWLVtw4cIFDBkyROdpRkNcuXIFderUQYMGDTB8+HAkJycbsWIgKysLNWvWLFMfPz8/3Lx5E/v374cQAmlpadi+fTv69eunbhMTE4NevXppLCcgIAAxMTEAoN63rays1PPlcjksLS01jlq+/PLL+PXXX3Hv3j2oVCps2bIFeXl56vePK5VKBAQEwN7eHseOHcPx48dhZ2eHPn364PHjxwZtpzEw4BFRpdu0aRPu3LmDUaNGQaFQVHY5VdLevXvx+PFjDB48uNzXZW1tjXfeeQcRERGwsrLCsWPHtE4LF4UyXQHv5MmTyM3NRZ8+fdCuXTtER0dr/QVXWFiIY8eOaSzrWUXXNxlrm4v2r6+//hrChF7DnpycjJCQEGzbtg2dO3dGw4YN8d5776FTp04ICQmRtMwOHTpgw4YNOHjwINauXYukpCR07twZDx48MErNW7duxenTpzFmzBi9+0RHR+PXX3/V+EfgK6+8gs2bN+P111+HhYUF3Nzc4OjoqHEqOzU1Fa6urhrL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", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "dingo.illustrations.plot_histogram(\n", + " optimal_steady_states[46], # reaction's flux we need to get -- earlier we found that in e_coli_core, reaction 24 corresponds to Biomass\n", + " \"EX_co2_e\", # name of the reaction\n", + ")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The distribution is centered around 22.8 with a remarkably narrow spread, on the order of 1e-5." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, "source": [ - "print('Number of flux values of the flux distributions returned: ', steady_states.shape[1])" + "Yet, even in this toy example, even when the objective function is required to be optimal, fluxes can still range!\n", + "\n", + "So, let's see what flux from the optimal case has the highest range:" + ] + }, + { + "cell_type": "code", + "execution_count": 42, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "'SUCDi'" + ] + }, + "execution_count": 42, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "row_idx = (optimal_steady_states.max(axis=1) - optimal_steady_states.min(axis=1)).argmax()\n", + "model.reactions[row_idx]" + ] + }, + { + "cell_type": "code", + "execution_count": 43, + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "iVBORw0KGgoAAAANSUhEUgAAAncAAAJ7CAYAAACBCLcZAAAAOnRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjEwLjksIGh0dHBzOi8vbWF0cGxvdGxpYi5vcmcvJkbTWQAAAAlwSFlzAAAPYQAAD2EBqD+naQAAbY5JREFUeJzt3Xl4TOf///HXRFYhYk0EQVVRtZQosdPY26rqotU2Wp8qpbV0o0WrpZSuSvnQFv2gpRtKi9S+xC5qq1prqdiCIBKRnN8f/WW+HZPEzMlkmfF8XFeuds65zznvM3fGvHKW+1gMwzAEAAAAj+CV3wUAAADAdQh3AAAAHoRwBwAA4EEIdwAAAB6EcAcAAOBBCHcAAAAehHAHAADgQQh3AAAAHoRwBwAA4EEIdwBuOS1btpTFYrH+9OjRI79LQgF15MgRm98Vi8WilStX5ndZQLYId4AbujGc/PunUKFCKlq0qG6//XZ17txZU6ZMUXJycn6XnKvi4uL09ttv2/wga0lJSfrvf/+rjh07qnz58goICFBAQIDKlSunWrVqqWPHjhoyZIjmzJmj06dP2y2/cuVKu9+7rN5zZ9r+24oVKzRw4EA1bNhQZcuWlb+/vwICAlS+fHm1adNGI0eO1N69e+2W69Gjh932vLy85Ofnp2LFiik8PFyNGjVSdHS0pk6dqkuXLjn79gEFnwHA7bRo0cKQ5PBPpUqVjL179+Z32blm2rRpdvucnRvfv+jo6LwptADYuXOnUblyZYd/dz777DO7daxYscKu3VtvvZXp9pxpaxiGsXHjRqNu3boO1zd+/Hib5aOjo536bBQpUsQYMmSIce3atUzrOXz4sN0yK1ascPTtBvKFt0sSIoAC7ciRI3rssccUFxcni8WS3+Xku2+//dbmaGaRIkXysZq8c+nSJbVv314nTpzI71Iy9eWXX6pPnz5KTU11eJlz587laJuXL1/W6NGjtWLFCi1cuFAlS5a0mV++fHkdPnzYZlpoaGiOtgnkNsId4CEyvoAuXLig1atXa/Dgwbp69ap1/u+//67t27erXr16+VVigXGrfjlPnz7dJth5eXnplVde0X333aeQkBBdu3ZNJ06c0Pbt2xUTE6PVq1fnWW2LFi3S888/r7S0NJvpERER6t27t+rXr6/ChQvr7Nmz2rx5s7777jutW7fOoXWvWbNG5cuX16VLl3To0CEtXLhQ//vf/5SSkmJts2HDBj300EP67bff5OPjY53u7e2tSpUquWQfgTyT34cOATgvs9OyNxo0aJBdmzlz5mS5zqtXrxpTp0417r//fqN8+fKGv7+/ERgYaFStWtV49tlnjY0bN2a57OHDh41PP/3U6NGjhxEREWFUrFjRKFq0qOHt7W0UL17cuPvuu40+ffoYmzZtuum+paamGt9++63x2GOPGVWqVDGKFi1q+Pn5GeXLlzeaNm1qvPXWW8bBgwcNwzCMt956y+HTb/8+FejMadlt27YZL7zwglG7dm0jODjY8Pb2NkqWLGncc889xuDBg40jR45kuWzFihXtarh+/boxefJkIzIy0ggKCjIKFy5s1K1b1/j000+NtLS0TNfj7GnnrDzyyCM26+jRo0e27f/++29j3759dtNdfVr26tWrRoUKFezaDRw40EhPT8+yvvXr1xvff/+9zbTMTssePnzYbtmdO3ca5cqVs2s7YcIEm3acloU74sgd4KFuv/12u2lFixbNtO2GDRvUrVs3/fXXX3bz9u/fr/379+urr75S7969NX78eJsjG5I0b948DRw4MNN1nz9/XufPn9f27ds1efJkDRw4UB9++GGmbbdv364nnnhCf/zxh92848eP6/jx41q7dq2Cg4M1YMCATNfhKsnJyXrppZc0depUu3nnzp3TuXPntGnTJn3wwQcaNWqUXnvttZuu8/z582rZsqXWrl1rMz0uLk79+/fXtm3bNH36dFftgp0LFy7YvL7ZzQRly5ZV2bJlc62eDLNmzdKxY8dsprVs2VIffvhhtpcRREZGmt7mXXfdpTlz5qhp06Y208eMGaMXXniByxfg1rhbFvBQNwakQoUKqVatWnbttm/frqioqEyD3Y0mT56s3r17m67JMAx99NFH+vLLL+3m7dy5U61atco02OU1wzD01FNPZRrsbnT9+nW9/vrreu+9927a9rPPPrMLdv82Y8YMLVu2zKlanXHj6egffvhBbdu21eTJk7Vjxw5dv34917adnYULF9pNGzx4cK4HrCZNmtiFu+PHj+v333/P1e0CuY1wB3iII0eO6MiRI9qxY4c++eQT/fe//7WZHx0drfLly9tMMwxDPXv21JUrV6zTqlWrplmzZmnXrl3asmWLhgwZYvMl+9VXX2n58uU26/H19VXr1q310UcfadGiRdq0aZP279+vbdu26euvv1bNmjVt2o8bN86ujmeffVYXL160md66dWstWLBA+/bt044dO/Tll1/afBkPGDBAhw8ftluf9M81iP/+ceZI3w8//KDvv//eZlrNmjU1b948/f7775oxY4ZKly5tM/+tt97SgQMHsl2vYRiqUqWK5s+fr507d2rEiBF2bWbPnu1wnc568MEH7abFxMSoT58+qlu3rooUKaLGjRvrjTfe0I4dO3Ktjhtt377d5rW3t7eaN2+eJ9tu2bLlTesB3E6+nhQGYIozQ6FYLBajR48eRkpKit161qxZY9PWx8fHOH78uF27J5980qZd165dnap3y5YtdnXFx8db569bt85ufteuXbO83ur8+fM2r109FErr1q1t5gcFBRkJCQk2bWJjY+22+dprr9m0ufGaOy8vL2PPnj02bTp16mTTJiIiwq5eV11zl56ebjz88MMO/+488MADxqlTp+zW4+pr7goXLmwzPyQkxNT+GYbj19xlmDRpkl37sWPHWudzzR3cEUfuAA9WqFAhffTRR5o2bZp8fX3t5q9atcrmdWpqqsqXL283COzMmTNt2mV2F+XBgwf15ptvqlmzZipbtqwCAgKsy0dERNi1P378uPX/V6xYYTd/5MiRWZ6WCw4OznS6K6SlpdmdOn344YdVvHhxm2mNGjVS7dq1babd7O7S1q1bq0aNGjbTqlevbvP6/Pnzdsv16NFDhmHY/JhhsVg0Z84cjRs3zu7IY2YWLFig9u3b59vp2ryQ2XvJ9XZwd4Q7wIOlpaVp4MCB6tevX6bzzY53dvbsWZsv/ClTpqh69ep67733tHbtWsXHx9/0qRiXL1+2/v/ff/9tM69w4cJ2oSevnDt3TteuXbOZVqVKlUzb3nbbbTavb9yPG2W2TwEBATavcztIZQx/cuLECS1evFiDBw9Wy5Ytsxzrb/v27Zo3b57dOnJaw7/dGDTPnTtnM4xPbsrsM+BI8AUKMsId4CEMw1BycrLWrl2ru+66y2bexIkTNWPGDJdvS5L27NmjF154welQYvbokzu7cYBc6Z+jq/nBx8dH7dq1sw7gmzE+YqtWrezarl+/3uZ1UFCQXZuswnxSUpLdtGLFitm8vvvuu21eX79+Pc/G2MvsqPGN9QDuhnAHeBA/Pz81adJES5YssRv2ZPDgwTZHyyQpLCzM5nWxYsV08OBBu5sRMvsJDAyUJH333Xc2A896eXlpyJAh2rBhgw4cOKDDhw/rt99+y7buG+tISkrKt7tmS5YsaXcK++DBg5m2PXTokM3rvBg2JLcUKlRIzZo104IFC+TtbTtK1r9vuJGkcuXK2S3v6Hskye7Gnvvuu8+uzdixY3P9D4C1a9faBdfy5cvbnW4H3A3hDvBAYWFhduOuxcfHa8KECTbTbrxT8OLFi9q4caMqVaqU5U98fLzOnz9vvS7pxtNad911l9577z01bNhQVapUUaVKlW5692FmR4uGDx+e5Zf7jeO1ZXY9odnTeoUKFbIbHuO7776zuxZuw4YNdkNm5NYdntOnT7e7DtKM0aNHa/z48dmOb3f16lWlp6fbTAsJCbF5Xbp0ad1xxx0202JiYjK9XnDu3Ll205o0aWLzunv37naBb/ny5Xr99dezDXixsbH64YcfspyfnZ07d6pbt2520wcPHmxqfUBBQrgDPNSLL75od/rso48+sjkK06RJE9WpU8emzbPPPqtXXnlFq1at0v79+/X7779r3rx5GjJkiGrWrKnIyEibYTJuvD5pz549+uSTT7Rnzx5t2rRJb7zxht54441sa42MjFT9+vVtpn333Xdq166dFi5cqP3792vnzp2aOXOmoqKi7Ab6zewaqffff19//PGHdYgYZ04b9+nTx+b1pUuX1KxZM82fP1+7du3S119/rQceeMCmjbe3t3r16uXwNvLDX3/9pf79+yskJERdu3bVhAkTtGbNGu3bt087d+7UnDlz1KZNG7twl1n47tmzp83rixcvqkmTJvrxxx+1e/duxcTEqHPnzlqzZo1Nu/vuu89uvD1/f39NmjTJ7lq8cePGqVGjRpo2bZp27Nih/fv3a/369Ro/fryaNWumxo0ba+fOnTfd7+PHj+vIkSPatWuX5s+fr+eee04NGjSw+8OkefPmBb4PAYfk0126AHLAkcePGYZhvP7663btxo0bZ9Nmy5YtRmBgoMPDY0gypk2bZl1+8+bNN21ftmzZmw4nERcXZxQtWtSh7X/88cc2y547d87w8fHJdpl/D4dxs6FQ0tPTja5duzr1nowcOdLu/c/s8WM3uvERahUrVrRr46qhUJ5//nmn9kmS0aRJk0zXdeXKFaN27dpOratYsWLGH3/8kWV9X3zxheHt7e3UOm98TzMbCsWRn8jISOPs2bN2NTEUCtwRR+4ADzZw4ED5+/vbTPvggw9sTlnWr19fv/32mypXruzQOv38/GyOlEVEROj111/Psn14eLhmzZp10/XWqVNHK1assDvd54gSJUrYHW3LiYzhX/7zn//ctK23t7fGjBmjN99802Xbzy0lSpRwqn2jRo30448/ZjqvcOHC+vXXX9WiRQuH1lWpUiUtWbJE1apVy7JNz549tXbtWrujydnJ7CYVZwQFBWno0KFavXp1jtcFFBQ8WxbwYCEhIXr22Wf1+eefW6edOnVK//3vf22e2NCoUSPt3btXc+bM0fz587V161adOXNG165dU1BQkCpXrqy6deuqdevW6tSpk93djmPGjFFERIQ+++wzbd++XampqapQoYIefPBBDRkyxO7JE1mpX7++du3ape+//14//fSTtmzZotOnTys1NVVlypRR5cqV1apVK7tTopL08ccf6/bbb9f//vc/7d271+7mEWf5+/tr6tSp6tOnj7788kutWbNGR48e1ZUrVxQUFKQqVaqodevW6t27typVqpSjbeWV9957T7169VJMTIw2bNig3bt366+//tLFixd17do1FS5cWOXKlVO9evX08MMPq3PnztkOexIWFqYVK1YoJiZGc+bM0aZNm3T8+HFdunRJAQEBKl26tOrVq6f7779fjz32mN0fGplp2LCh4uLitHz5ci1YsEDr1q3TsWPHrNd5lipVStWrV1eLFi308MMP240bmBmLxaJChQopICBAwcHBCgsLU7Vq1dS8eXM99thjWQ4DA7gri2HcguMRAAAAeChOywIAAHgQwh0AAIAHIdwBAAB4EMIdAACAByHcAQAAeBDCHQAAgAdhnDuT0tPT9ffff6to0aKmn/MIAADgKMMwdOnSJYWFhWU7BiXhzqS///5bFSpUyO8yAADALebYsWMqX758lvMJdyYVLVpU0j9v8I0PZ0fBkpqaqqVLl6pt27by8fHJ73LgAPrM/dBn7oc+cz+JiYmqUKGCNYNkhXBnUsap2KCgIMJdAZeamqrChQsrKCiIf8DcBH3mfugz90Ofua+bXQ7GDRUAAAAehHAHAADgQQh3AAAAHoRwBwAA4EEIdwAAAB6EcAcAAOBBCHcAAAAehHAHAADgQQh3AAAAHoRwBwAA4EEIdwAAAB6EcAcAAOBBCHcAAAAehHAHAADgQQh3AAAA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AADAMabC3ZIlS9SlSxc1btw4yzYVK1bUiRMnTBcGAAAA55kKdwkJCapUqVK2bQzDUEpKipnVAwAAwCRT4S4kJET79+/Pts3OnTsVHh5uqigAAACYYyrctWnTRgsXLtTvv/+e6fw1a9Zo+fLl6tixY46KAwAAgHNMhbuhQ4cqICBAzZs316hRo3TgwAFJ0q+//qphw4apffv2KlWqlF599VWXFgsAAIDsmRrEuFKlSlqyZIm6deumYcOGyWKxyDAM3XfffTIMQ+Hh4fr+++9VtmxZV9cLAACAbJh+QkXDhg21f/9+/fzzz9q4caMSEhIUFBSkhg0bqnPnzvL19XVlnQAAAHBAjh4/5u3trS5duqhLly6uqgcAAAA5YPrxYwAAACh4TB25e+eddxxqZ7FYNGzYMDObAAAAgAmmwt3bb7+d7fyMGywIdwAAAHnLVLhbsWJFptMvXryobdu2afz48YqKilLfvn1zVBwAAACcYyrctWjRIst5DzzwgLp376569eqpa9eupgsDAACA83LlhoqqVauqS5cuGjNmTG6sHgAAAFnItbtly5Qpo3379uXW6gEAAJCJXAl3KSkpWrx4sYKDg3Nj9QAAAMiCqWvuvv7660ynX79+XSdOnNC3336rP/74Qy+99FKOigMAAIBzTIW7Hj16yGKx2E03DEPSP0OhPP7441xzBwAAkMdMhbtp06ZlOt3Ly0vFixdX/fr1VbZs2RwV5qgxY8ZoyJAh6t+/vz755BNJUnJysl5++WV9++23SklJUbt27fT5558rJCTEutzRo0fVp08frVixQkWKFFF0dLRGjx4tb+8cPZENAAAgX5lKMtHR0a6uw5TNmzfrv//9r2rXrm0zfeDAgVq0aJG+++47FStWTP369dNDDz2kdevWSZLS0tLUqVMnhYaGav369Tp58qSefvpp+fj46L333suPXQEAAHAJt3227OXLl9W9e3dNnTpVxYsXt06/ePGivvzyS3300Udq3bq16tevr2nTpmn9+vXasGGDJGnp0qXas2ePZs6cqbp166pDhw569913NXHiRF27di2/dgkAACDHTB25W716tekNNm/e3PSy/9a3b1916tRJUVFRGjlypHX61q1blZqaqqioKOu06tWrKzw8XLGxsWrUqJFiY2NVq1Ytm9O07dq1U58+fbR7927dfffddttLSUlRSkqK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OZWUlGSULl3aCAsLM65du5bf5eQL/f9nKGfl4sWLhq+vr1GnTp0cbyvj90KScfjwYbv5SUlJxsSJE42iRYsakoxmzZoZycnJNm2aN29uSDLGjh1rt3xKSooREBBg3HXXXYavr69x7733ZlpHx44dDUnG8OHDM52/ePFiQ5LNc4qjo6Od+gz82wcffGBIMj799FOnlwUKCk7LAnALM2fO1JkzZ/T0009zRCULCxcu1LVr1/TQQw/l+rYCAgL0wgsvaOXKlfL399eaNWvsTgW3atVKku0RugwbN27U1atX1b59ezVo0EDr16/XtWvXbNqkpaVpzZo1Nuu6UcaRSlftc8bv1/jx42Xw6HW4KcIdgEw5ci1exvV9R44csU778MMPZbFYdMcdd+jSpUt2y0ydOlUWi0UVKlTQ2bNnHa5nwoQJkv65nioz/74ebebMmbrnnntUpEgRlS5dWo8//riOHj0qSTIMQxMmTFDdunUVGBioUqVKqUePHjp9+rTdOqdPny6LxaIePXro4sWLGjRokCpVqiR/f39VrVpV77//vtLT0yVJJ06c0PPPP68KFSrIz89P1apV02effZbl/iQlJWnMmDGqV6+eihYtqsKFC6tmzZoaOnSozp8/7/D78m8//vijJPtTsoZh6KuvvlJERIQKFy6skiVLqkOHDlq/fr1Wrlwpi8Wili1bmtpmvXr19OKLL0qSPv74Y12/ft06LyOQrV271ma69M9pWElq2bKlWrRooatXr2rjxo02bbZu3apLly7J399fkZGRdttOT0/X/PnzFRISkukpXUk6fPiwnnrqKYWGhsrPz09VqlTR0KFDlZKSkmn70qVLq2PHjjp48KAWL17s2JsAFDT5fOQQgIu56rSsI6drM7Z142m7Bx54wJBkdOvWzWZ6XFyc4e/vb3h7exvr1q1zqD7DMIxDhw4Zkozy5ctn2Ub//xTi4MGDDW9vb6N169bGww8/bISHhxuSjAoVKhgJCQnGo48+avj7+xvt27c3unTpYpQpU8aQZNSuXdtISUmxWee0adMMSUbnzp2NGjVqGGXKlDG6du1qtG3b1ggICDAkGf369TMOHDhghIaGGhUqVDAeffRRo1WrVkahQoUMScaYMWPsaj137pxRt25dQ5IRFBRkPPDAA0bXrl2NUqVKGZKMypUrZ3oqVNmclr1y5YpRuHBho1q1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", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "dingo.illustrations.plot_histogram(\n", + " optimal_steady_states[row_idx], # reaction's flux we need to get -- earlier we found that in e_coli_core, reaction 24 corresponds to Biomass\n", + " \"SUCDi\", # name of the reaction\n", + ")" ] }, { "cell_type": "markdown", "metadata": { - "id": "svc47hxNbo6L" + "id": "xo0P1z7BMcTA" }, "source": [ - "Let us now assume that we are interested in a certain reaction of our model, for example the last reaction of the glycolysis which is catalyzed by the Pyruvate kinase (PYK) enzyme. \n", + "Yet, one may prefer studying **suboptimal cases**. \n", "\n", - "We first need to look in the `model.reactions` list and find the index of the `PYK` reaction; the enzyme is usually the name of reaction. " + "You can specify the minimum percentage of the optimal objective value that sampled flux vectors must satisfy. \n", + "For instance, in the `e_coli_core` model the optimal biomass flux is 0.87; setting `set_opt_percentage` to 10 would restrict sampling to flux vectors with a biomass flux of at least 0.087." ] }, { "cell_type": "code", - "execution_count": null, + "execution_count": 44, "metadata": { - "colab": { - "base_uri": "https://localhost:8080/" - }, - "id": "-im2Cgq5b3oS", - "outputId": "f0f9948a-99ef-46de-d05a-e21e49164011" + "id": "0y_TOtcGUmAW" }, - "outputs": [], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "phase 1: number of correlated samples = 500, effective sample size = 20, ratio of the maximum singilar value over the minimum singular value = 953.346\n", + "phase 2: number of correlated samples = 500, effective sample size = 157, ratio of the maximum singilar value over the minimum singular value = 2.09486\n", + "phase 3: number of correlated samples = 2400, effective sample size = 853\n", + "[4]total ess 1030: number of correlated samples = 3400\n", + "\n", + "\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[4]maximum marginal PSRF: 1.00196\n" + ] + } + ], "source": [ - "model.reactions.index('PYK')" + "model = dingo.MetabolicNetwork.from_sbml('../ext_data/e_coli_core.xml')\n", + "sampler = dingo.PolytopeSampler(model)\n", + "\n", + "# Set percentage of biomass as minimum value the objective function may get\n", + "sampler.set_opt_percentage(10)\n", + "suboptimal_steady_states = sampler.generate_steady_states(ess=1000, psrf=True)" ] }, { "cell_type": "markdown", - "metadata": { - "id": "k6uheDpHfiHq" - }, + "metadata": {}, "source": [ - "Now that we know the index of the reaction of our interest, we can use the [`plot_histogram`](https://github.com/GeomScale/dingo/blob/a76b4be22f33feac86dff38746c7f2706afb2b67/dingo/utils.py#L177) function of `dingo` to plot its corresponding flux distribution. " + "Let's have a look on the biomass now! " ] }, { "cell_type": "code", - "execution_count": null, - "metadata": { - "colab": { - "base_uri": "https://localhost:8080/", - "height": 467 - }, - "id": "-n2HKr82yKyh", - "outputId": "2d924fe9-975b-482c-9da7-016c002de61c" - }, - "outputs": [], + "execution_count": 45, + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "iVBORw0KGgoAAAANSUhEUgAAAm8AAAJ7CAYAAAC1cXYFAAAAOnRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjEwLjksIGh0dHBzOi8vbWF0cGxvdGxpYi5vcmcvJkbTWQAAAAlwSFlzAAAPYQAAD2EBqD+naQAAbQ9JREFUeJzt3Xd4FOXexvF70xuhBRJaAKmBQ69BIIChiQ1RRAEBOaCIqBQFjlTLEUFFERRFBXxpAipNaSJNCC0UaUaqCJJAKKGEhJDM+wdmD0s2sLvZJGzy/VxXruPOPM/Mb/Js9D5TnjEZhmEIAAAALsEttwsAAACA7QhvAAAALoTwBgAA4EIIbwAAAC6E8AYAAOBCCG8AAAAuhPAGAADgQghvAAAALoTwBgAA4EIIbwDuOS1atJDJZDL/9OzZM7dLyhEzZsywOG6TyeTQdsaMGWOxjXLlyjm3UAC5yiO3CwDyohYtWmj9+vVW17m5ucnPz0/BwcGqXr26OnTooGeffVY+Pj45XGXO2b17txYtWmSxbMyYMblSCwC4OsIbkMPS0tJ05coVXblyRUeOHNGSJUv07rvvavny5apatWpul5ctdu/erbFjx1osI7wBgGMIb8A94Pjx43rqqae0e/duhy+V5SXz5s1TUlKS+XNAQEAuVuN6Xn31VYtLzR4e/KseyEv4iwZyyLFjxyRJFy9e1IYNGzRs2DBdu3bNvP63337Trl27VLdu3dwq8Z4REhKS2yW4tEKFCqlQoUK5XQaAbMIDC0AOKVeunMqVK6fatWvr5ZdfVr9+/TK0OXz4cKb9k5KS9OWXX+qRRx5RmTJl5Ovrq4CAAFWuXFm9e/fWtm3bMu17/PhxTZo0Sb169VKDBg1Urlw5BQYGytPTU0WKFFHdunX14osvavv27Xc9jhs3bujbb79Vly5dVLFiRQUGBsrHx0dlypRRs2bNNGbMGB09elTS/26c79WrV4bt3H5j/q2XUe15YGHXrl3q37+/atWqpcKFC8vT01NBQUFq1KiRhg8frj///DPTvuXKlctQQ2pqqj7//HM1adJEBQsWlL+/v+rUqaNJkyYpLS3N6nac9aBBZg4fPqzevXurTJky8vHxUenSpdW7d28dP37cant7Hlj4+++/NWbMGDVt2lTFihWTl5eXChYsqOrVq6tv376KiorKtG/Pnj0t9tOiRQtJ0syZM9WkSRMVKFBARYsWVfv27bVhwwZzv8TERL399tuqVq2afH19VaxYMT322GOKjo62up8rV65o+vTpGjBggJo3b65KlSqpaNGi8vT0VMGCBVWlShV16dJFP/zwgwzDyLTemJgYvfrqq6pbt675u1KwYEFVrFhRLVq00JAhQzR//nxdvXo1Q981a9aoe/fuqlq1qgoUKGD+noWFhemhhx7S2LFjtXLlykz3DTiVAcDpIiIiDEkWP7f79NNPM7T56aefrG4vKirKKFu2bIb2t/+88MILxvXr1zP0nzhx4l37SjJMJpMxaNCgTI9r586dRtWqVe+6nYkTJxqGYRijR4+2ab+SjNGjR2f6++vRo0eGWq5du2b06dPnrtv18PAw3nvvPavHc/vv9OWXXzaaNm2a6bas1WEYhjF9+vS7jrctrG1nxYoVhr+/v9V6AgICjPXr12fYzu2/97Jly1rd35QpUwxvb++7/g6ffvpp4/Llyxn69+jRw6Jds2bNjKefftrqNtzd3Y3/+7//M2JjY43q1atbbePt7W38/PPPGfaza9cum79HLVq0MC5dupRhG999953h5eVl0zaioqIs+g4ePNimft7e3jaONJA1nHkDcsnvv/9u8dnd3V01atTI0G7Xrl2KjIy84xmkdFOnTtULL7zgcE2GYejDDz/UV199lWHd3r171bJlywx15wbDMNS9e3dNmzbtrm1v3LihoUOH6r///e9d237yySf69ddfM10/c+ZMrVmzxq5as+rxxx+3eiZIunlGqmPHjjp9+rTd250yZYr69++v5OTku7adO3eunnzySaWmpt6x3a+//qq5c+daXZeamqoXX3xRDz30kPbv32+1TXJysvr06ZPpGU5brFu3Ti+++KLFsuvXr6tPnz66fv263dvbsWOHPvjgA4frAbID4Q3IIcePH9fx48e1Z88effTRR/r8888t1vfo0UOlS5e2WGYYhnr37m3xH+8qVapo9uzZ2rdvn3bs2KHhw4dbXKb7+uuv9csvv1hsx8vLS61atdKHH36oH3/8Udu2bdOhQ4e0c+dOffPNN6pevbpF+wkTJmSo47nnnlNCQoLF8latWmnJkiWKiYnRnj179NVXX6lp06bm9a+++qqOHTuWYXvSzXsAb/159dVX7/Dbs/Tdd99p4cKFFsuqV6+uRYsW6bffftPMmTNVrFgxi/WjR4++42Xp9OOsUKGCFi9erL1792Z4QlaS5syZY3OdznDt2jW9/vrr2r59u9auXatHHnnEYv358+f11ltv2bXNkydPasiQIRbLChUqpM8//1x79uzRjz/+qHr16lmsX7Fihf7v//7vjts1DEMhISFasGCB9u/frzfeeMNi/eXLl7Vjxw5VqVJFK1eu1J49e9S1a1eLNseOHdPmzZstlplMJtWqVUtvvPGGFi1apE2bNikmJka//fablixZoocfftii/Zw5c3Tq1Cnz53379un8+fPmz0WKFNHs2bO1f/9+xcTEaOPGjfr888/VrVs3FSlSxGJbt17ulaS6detq5cqViomJ0f79+7Vq1SpNmDBBDz74YJ6e7gf3mNw87QfkVdYum2b2YzKZjJ49exrJyckZtrNx40aLtp6ensbJkycztOvWrZtFu06dOtlV744dOzLUFRsba16/adOmDOs7depkpKWlWd3ehQsXLD7be1nxbpdNW7VqZbE+MDDQOH/+vEWbqKioDPt8/fXXLdrcftnUzc3NOHDggEWbDh06WLSpX79+hnqz87Lpyy+/bNEmLS3NqFWrVobjT01NNbe522XTN998M8N+br9cefnyZSMoKMiiTcOGDS3a3H7ZVJKxaNEii1qLFi2aoc3u3bvNbc6fP2+4ublZrJ88ebJdv7cbN24YBQsWtNjGvHnzzOu3bt1qsa5du3aZbuv69evGtWvXzJ/fe+89i77jxo3LtK+1y7VAduDMG5CL3N3d9eGHH2r69Ony8vLKsP72iX5TUlJUunTpDDfHz5o1y6Ld7WcLJOnIkSN644031KxZM5UoUUK+vr7m/vXr18/Q/uTJk+Z/Xrt2bYb1b7/9dqY35mfnk46pqakZLm0+8cQTKly4sMWyxo0bq2bNmhbLrP1ebtWqVSuFhYVZLLt97r0LFy5k6NezZ08ZhmHx4yzPPfecxWdrD4BcunRJBw8etHmbt3+vKlSooAceeMBiWUBAgJ555hmLZTt27FBiYmKm2y1cuLAeeughi1pvf1iiZs2aqlWrlkWfoKAgizbWfscJCQn66KOP1L59e5UvX14BAQFyc3OTyWSSh4dHhrPCt35/0x+MSLdixQo1a9ZMQ4YM0RdffKH169eb+3t6elqcQbv9DOSoUaP06KOPauTIkfq///s/7dixw3w5tkCBApn+bgBnYqoQIBelpqZq4MCBOnz4sCZPnpxh/a2XfuwRHx+vGzdumOf3+uKLL9S/f3/duHHD5m1cuXLF/M9///23xTo/P79cm1D43LlzGe5dqlChgtW29913n3777Tfz59uP43bWjunW/+hLsut36Azly5e3aVlsbGyGy9+Zuf33cN9991ltd/vytLQ0xcXFWd2/JIWGhsrd3d1imZ+fn8Vna33v9jveunWrHn74YZ09e9bqfq259fsbEBCgd955R4MGDTIv+/XXXy3+T4Cbm5saNWqkV199VZ07dzYvf+CBB/Too49q8eLFkm7eP7dkyRItWbLE3MbHx0ft2rXTG2+8YfX/CAHOxpk3IIcYhqGkpCT9+uuv+te//mWxbsqUKZo5c6bT9yVJBw4c0Isvvmh36HDm2SNXUbRo0QzLbg8jyJy1M65ubm53bXMnKSkp6ty5s13BTcr4/R04cKDWrl1r9SytdDOYRkVF6amnntKHH35ose7777/X9OnT1bJlS3l7e2fom5SUpEWLFun++++/49QqgLMQ3oAc5O3trfvvv18rV67McIll2LBhFmcLJKlkyZIWnwsWLKgjR45kuNnf2o+/v78kacGCBRZPCbq5uWn48OHasmWLDh8+rGPHjunnn3++Y92315GYmJhrT50WLVo0wyXmI0eOWG2bPt9cuhIlSmRbXdklfXLnuy2zZ2Lj28fT1t+fm5ubgoODbd6PM2zevFknTpywWPb4449rzZo1iomJMX/fb7/0ak2LFi20YMECnT9/Xn/99ZfWrVunqVOnqlWrVhbt3nnnHYsnXt3c3NSzZ0/98ssvunLliv744w8tX75cEyZMUKVKlcztrl+/rvHjx2fxiIG7I7wBuaBkyZJ6/fXXLZbFxsZmuHSaPulpuoSEBG3dutU84a+1n9jYWF24cMF8P9rtl17/9a9/6b///a8aNWqkChUqqFy5ctq1a9cd623ZsmWGZaNGjcr07NzFixctPlu7n+/Wt0vYw93d3eKJVulmQL39PqktW7ZYXDKVpObNmzu0z7vJzkl6b5+2xTAMTZ8+3WJZYGCgXZexIyIiLD4fPXo0Q4C/cuWKZs+ebbGsXr16GS6DZjdrtw58+eWXatWqlSpXrqxy5copPj5e8fHxmW4jNTVVcXFxFstKly6tiIgIPf/88/ruu+8s1p0/f15nzpyRdPO7fOt31cPDQ5UqVVK7du00ZMiQDGHNnnsPAUcR3oBcMmDAAAUGBlos+/DDDy2mBbn//vstbu6Wbt7APmTIEK1fv16HDh3Sb7/9pkWLFmn48OGqXr26wsPDtWfPHnP726fMOHDggD766CMdOHBA27Zt03/+8x/95z//uWOt4eHhGW7cXrBggdq2batly5bp0KFD2rt3r2bNmqXIyEjNmDHDou3tNUjSe++9p99//908hYo9l3VvfzvF5cuX1axZMy1evFj79u3TN998k2FKDQ8PD/Xt29fmfdwrJk+erGHDhmnHjh1av369HnvsMYvxlaRnnnnGrsu7vXr1yjCtxZNPPqlp06Zp7969Wr58uVq0aKFz585ZtLl9/rScYO278/rrr2vnzp3au3evPvvsM3Xo0OGO27h27ZpKly6t9u3b64MPPtDq1au1f/9+HT58WJs2bdKAAQMy9Ek/c/3rr7+qRIkS6tq1q6ZNm6aNGzfq999/N599e/fddy368R5e5IjceswVyMtsecOCYRjG0KFDM7SbMGGCRZsdO3ZkOsN+Zj/Tp08399++fftd25coUSLDsrVr11rUsXv3bqNAgQI27T/9DQvpzp07Z3h6et6xz7FjxzL9/d0+VUhaWprRqVMnu34nb7/9dobf/+1Thdz6lod0trytIDunCvHw8LjjcRUqVCjD9DG21Dxp0iS7fn9t27Y1bty4YbGN26cKiYiIyLAfW96WcadxSExMNIoVK3bH2gICAjJ8N2/dxuXLl+061luPY+nSpXb1tfYdApyNM29ALho4cGCGMyDvv/++xWWaevXq6eeff870Cb/beXt7W5ytqF+/voYOHZpp+9DQ0AyXx6ypVauW1q5dq8qVK9tUx62KFCli9V2ujkqfHuXf//73Xdt6eHho3LhxGSaMdRWzZs2yetlZunl26Pvvv1epUqXs3u6AAQM0efJkqzfg365Lly5auHBhrjy84evrq6+++kqenp6Zrp83b16GyXUdVbZsWZve3GFNq1at7vi3BjgL4Q3IRcHBwRnm8YqLi8vw9oXGjRvr4MGDmjlzph5//HGVLVtWfn5+8vDwUJEiRVSvXj317t1bs2fPVlxcXIbLSOPGjdOCBQvUvHlzFShQQD4+PqpUqZJee+017d692+ZgWK9ePe3bt09z5szRk08+qfLly8vf319eXl4qXbq0mjVrplGjRmW4ZClJ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AABgwebwZhiGAgICVK9evUzbrF+/XiEhIapSpYpTigMAAIAlm8Pb888/r88//1zFixfXlClTFBQUlKGNm5ubHnroIX3xxRdOLRIAAAA32fzAwmeffaaVK1cqKipK1apV0/z587OzLgAAAFhh19OmrVu31r59+/Tggw+qS5cueuKJJ2x6kMFR48aNk8lk0quvvmpelpSUpP79+6to0aIKCAhQp06dFBcXZ9HvxIkT6tChg/z8/FS8eHG99tpr3HsHAADyBLunCgkMDNSMGTO0ePFibdq0SdWqVTM/oOBM27dv1+eff66aNWtaLB84cKCWLl2qBQsWaP369fr777/1+OOPm9enpqaqQ4cOun79ujZv3qyZM2dqxowZGjVqlNNrBAAAyGkOz/P28MMPa//+/XrggQfUvXt3PfLII+b3nGbVlStX1LVrV02bNk2FCxc2L09ISNBXX32lDz/8UK1atVK9evU0ffp0bd68WVu2bJEkrVq1SgcOHNCsWbNUu3ZttW/fXm+99ZamTJmi69evO6U+AACA3OLQ67HSFSlSRN9++62+/fZbvfTSS+aX02dV//791aFDB0VGRurtt982L4+OjlZKSooiIyPNy6pWrarQ0FBFRUWpcePGioqKUo0aNRQcHGxu07ZtW/Xr10/79+9XnTp1rO4zOTlZycnJ5s+XLl2SJKX5+CjFxlCaJslXUlpamlJSUuw44pvS+zjSF/cexjPvYCzzFsYzb8kr42lP/VkKb+meeuoptWnTRidOnFDRokWztK158+Zp586d2r59e4Z1sbGx8vLyUqFChSyWBwcHKzY21tzm1uCWvj59XWbeffddjR07NsPy019/rZ/8/Gyuf66kU6dOZek1YatXr3a4L+49jGfewVjmLYxn3uLq45mYmGhzW6eEN0kqXLiwxSVOR/z111965ZVXtHr1avn4+DipMtsMHz5cgwYNMn++dOmSypQpoxLPPaf7bTzztkdSc0kbNmxQrVq17K4hJSVFq1evVuvWreXp6Wl3f9xbGM+8g7HMWxjPvCWvjGf6FT9bOC28nTt3Tjt27JCnp6fCw8Pl6+tr9zaio6N15swZ1a1b17wsNTVVGzZs0OTJk7Vy5Updv35dFy9etDj7FhcXp5CQEElSSEiItm3bZrHd9KdR09tY4+3tLW9v7wzL3ZKSZOtXwU3SNd2c7y4rXyBPT0+X/gLCEuOZdzCWeQvjmbe4+njaU7td4W3dunUaOnSoDh8+rBo1amjixImqU6eO5s2bp759++rq1auSbp6F+/rrr/XII4/YVfgDDzygvXv3Wizr1auXqlatqqFDh6pMmTLy9PTUmjVr1KlTJ0lSTEyMTpw4ofDwcElSeHi43nnnHZ05c8b8XtXVq1crMDBQ1apVs6seRx08eNDuPkFBQTa9ggwAAORvNoe3Q4cOqX379kpOTlbhwoW1YcMGtW/fXsuXLzcHrJYtW+rUqVNatGiROnfurD179tj1qqwCBQroX//6l8Uyf39/FS1a1Ly8d+/eGjRokIoUKaLAwEANGDBA4eHhaty4sSSpTZs2qlatmrp3767x48crNjZWI0aMUP/+/a2eWXOm07p59q1bt2529/Xz8dG+AwecXhMAAMhbbA5v7733nlJTU7VmzRq1bNlSUVFRatWqlbp06aLIyEgtWrRI7u7ukqSVK1eqffv2+vjjj/Xpp586teCJEyfKzc1NnTp1UnJystq2bWuxD3d3dy1btkz9+vVTeHi4/P391aNHD7355ptOrcOai7r5xOksSWF29DsoqVtSks6dO5cdZQEAgDzE5vC2adMmtW/fXi1btpR08/Jkhw4d9MMPP2j27Nnm4CbdnJqjWbNmWrt2bZYLXLduncVnHx8fTZkyRVOmTMm0T9myZfXTTz9led+OCpNU966tAAAA7GfzJL0nTpxQWJjl+aTKlStLktV7yWrWrKkTJ05ksTwAAADcyubw5uXllWECufR7yPyszIPm7++vtLS0LJYHAACAW9kc3kJCQvT3339bLGvSpInF3Gi3OnnypIoVK5a16gAAAGDB5nveateura1bt1osa926tVq3bm21/W+//ZZjU3MAAADkFzaHtx49eqhYsWK6fv26vLy87tg2Ojpa+/btU9euXbNcIAAAAP7H5vDWrl07tWvXzqa29erV4343AACAbGDzPW8AAADIfYQ3AAAAF0J4AwAAcCGENwAAABdCeAMAAHAhhDcAAAAXQngDAABwIYQ3AAAAF0J4AwAAcCEOhzd3d3d5eHjojz/+yLAuJibGvB4AAADO43C6MgxDhmE4vB4AAAD2czi83endpVWqVOHdpgAAANmAe94AAABcCOENAADAhTh02fSvv/7SoUOH1LhxY/n5+Um6eRl1woQJWrJkiXx9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CxeqefPmeu2113T48GFJ0vLly7V582Z9+OGHCgoK0muvvWb3tlesWGHxecaMGSpevLiio6PVvHlzJSQk6KuvvtKcOXPUqlUrSdL06dMVFhamLVu2qHHjxlq1apUOHDign3/+WcHBwapdu7beeustDR06VGPGjJGXl5cjhw0AAJDrHApv5cqV08qVK9WlSxeNHDlSJpNJhmHooYcekmEYCg0N1cKFC1WiRIksF5iQkCBJKlKkiCQpOjpaKSkpioyMNLepWrWqQkNDFRUVpcaNGysqKko1atRQcHCwuU3btm3Vr18/7d+/X3Xq1Mmwn+TkZIt79C5duiRJSvPxUYod72j1lZQmKcWeg0zv5+srSUr553+zdV929nOkT1p6v7Q0paTYW6XrSz/m/HjseQ1jmbcwnnlLXhlPe+o3GbdO0GanGzduaOnSpdq6davOnz+vwMBANWrUSI8++qhTzm6lpaXpkUce0cWLF/Xrr79KkubMmaNevXpleBiiYcOGatmypd577z317dtXf/75p1auXGlen5iYKH9/f/30009q3759hn2NGTNGY8eOzbB8zpw58vPzy/KxAAAAZCYxMVHPPPOMEhISFBgYeMe2WXo9loeHhzp27KiOHTtmZTOZ6t+/v/bt22cObtlp+PDhGjRokPnzpUuXVKZMGZV47jndb+OZt/mS+kjaIKmWHftO77fO11env/5arZ97Tp7XrmXrvuzp5+i+9khqLmnDhg2qVcuennlDSkqKVq9erdatW8vT0zO3y0EWMJZ5C+OZt+SV8Uy/4meLLL/bNLu89NJLWrZsmTZs2KDSpUubl4eEhOj69eu6ePGiChUqZF4eFxenkJAQc5tt27ZZbC/9adT0Nrfz9vaWt7d3huVuSUmy56twTTefArH365PeT5I8r127a3hzxr6y+7jc0vu5ubn0H1RWeXp65uvjz0sYy7yF8cxbXH087andofD25ptv2tTOZDJp5MiRdm3bMAwNGDBAP/zwg9atW6fy5ctbrK9Xr548PT21Zs0aderUSZIUExOjEydOKDw8XJIUHh6ud955R2fOnFHx4sUlSatXr1ZgYKCqVatmVz0AAAD3EofC25gxY+64Pv0BBkfCW//+/TVnzhwtXrxYBQoUUGxsrCSpYMGC8vX1VcGCBdW7d28NGjRIRYoUUWBgoAYMGKDw8HA1btxYktSm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Nn3llVeMwMBAo2zZskbnzp2NyMhI87g99thjmR7P5MmTDUlG375973jcwL2M8Aa4kHshvF24cMEoV66cIcn47LPPDMMwjEuXLhmVKlUyJBkTJkyw44gM48svvzQkGU8++WSmbdLDQ9GiRY3du3eblycmJhpNmzY1JBk1atQwKlSoYBw/fty8/uzZs0bFihUNScasWbMstpke3iQZDz/8sHH16lXzuujoaMPDw8Nwc3MzqlWrZrzwwgtGSkqKef2iRYvM4ezWfoZhGE899ZQhyWjUqJERHx9vXn758mWjffv2hiSjSZMmmR6jNd99950hyfjPf/5jsXzx4sWGJCMgICBDYP7ggw/M28xKeEtNTTW8vLwMScaXX35psa5Vq1aGJOPdd981L0u/361evXrm4/bw8MgQ0tq1a2dIMt58802r+x0wYIAhyVi1apXF8vTwJsl44403jBs3bpjX7d271/D39zckGZs3b7a63Z07dxqSjAoVKtzxuIF7GeENcCHpgSqzn8z+I+3M8GYYhrFt2zbDy8vL8Pb2Nnbt2mV07tzZHILS0tLsOqb+/fvf8aZ1w/hfsJkyZUqGdd9//715/Y8//phhfXqI6dWrl8Xy9PAWEBBgxMXFZeiXfoYxNDTUuHbtWob1NWrUMCQZ69evNy/7888/DTc3N8NkMhl79uzJ0OfkyZPms1W3h607hbdnnnnGkGTs2LHDYnl6eBo+fLjVfg0aNMhyeDMMwwgJCTEkGe+9957F8rfeesuQZLRp08a8bP369YYkY/DgweZljRo1Mnx8fMwPPaSkpBgBAQGGJGPjxo0Z9peWlmaULl3aKFy4sEVoNoz/hbd69epZ/a698MILdwyFycnJ5mNPSEi467ED9yKmCgFcUGZThVStWjVH9t+gQQO9//77evnll9WiRQslJCSobNmymjlz5l3nKrtdXFycJKlo0aJ3bfvggw9mWFapUiVJkoeHh9q0aZPp+r///tvqNuvVq6fixYtn2q9ly5by8fGxun7v3r0W292wYYPS0tJUt25d1axZM0OfUqVKqW3btlq8eLHWrl2rJk2aWK3pVtevX9ePP/6ocuXKqV69eublN27c0ObNmyVJXbt2tdr3mWee0fbt2++6j7tJv/fv9rFNv+9t06ZNSklJkaenp/l+t4iICHO7iIgIbd26VVu2bFFERIR27NihK1euyM/PTw0bNsywv23btunkyZPq0aOHPDys/2fqoYcesvpdCwsLk3TzPkVrvLy8FBAQoCtXriguLk6BgYF3OXrg3kN4A1zQv//970xv7s8pAwYM0LJly7Rq1SqZTCbNmzdPhQsXtns7CQkJkmTTf0RDQ0MzLEuff6xEiRJW/0NfoEABSVJSUpLN27x1u5mtt7bd9MBQvnx5q30kqUKFChZt7+bnn39WQkKCevfubbE8Pj7evO/MHjxxxuTMqampunjxoiSpSJEiFusaNmwof39/Xb16Vdu3b1eTJk20bt06ubm5qXnz5uZ2ERERGj9+vNatW6eIiAhzwLv//vvl5eWVYZ+2zGeX2bikf48yG+/0NleuXHH44REgt/G0KQCr0s+2ZObQoUOKioqSdPOJx/QZ8O1VqFAhSdKlS5fu2tbNLfN/Zd1pnaPbzMp2nSUrE/PaexbUmn379pmn+ahRo4bFOk9PT91///2Sbk4ZkpycrC1btqh27doqWLCguV3Tpk3l7u5unlYk/X/vNEVIgQIFrJ5JTZeVcUn/PwyO/J8N4F5AeAPyofSzHdam+5Buzmx/+vTpTPsnJSWpc+fOunz5srp27Spvb2+99tpr2rFjh921pF+yPHfunN197zWlSpWSJB09ejTTNunr0tveSWpqqhYvXqwSJUpkmE6jaNGi8vb2lqRMX2N16zQvjpo1a5Z5f7detk1363xv6fO73XrJVLp5pqt27drasmWLLl++rE2bNln0vdXu3bt19OhRPfjgg+bjc6bk5GTz69eCg4Odvn0gJxDegHyoWLFi8vLy0vnz563OgbZy5coMc3fd6pVXXtHu3bvVsmVLffPNN/rggw90/fp1de7c2XyJzVZ169aVJB04cMCufvei5s2by83NTbt379aePXsyrD99+rR5frHMzjrdav369Tp37pw6duyY4Syap6enOdDNmTPHav+5c+faewgWdu7caZ6Db9CgQXJ3d8/QJv04Nm/erFWrVkmS1XnlIiIilJycrEmTJunq1asKCAiw+lqtzOazc5Z9+/ZJkipWrMj9bnBZhDcgH/L09DTfkzRixAiLS6R79uzRSy+9lGnfOXPm6IsvvlBwcLDmzJkjNzc39e/fX0888YSOHTum5557zq5a0v/jn34J1pWFhobqySeflGEYev755y3OJl69elV9+/ZVUlKSmjRpYtPDCne7ZPryyy9LkiZNmqQtW7ZYrPv4448zTIxrq2vXrumzzz5TixYtlJSUpBYtWmjIkCFW29arV08FChQw97n9frd06WfjPvzwQ0lSs2bNrN6j+N1338nX19fqwynOkP6QR6tWrbJl+0BO4IEFIJ96++23tWHDBk2bNk3r169XzZo1derUKe3YsUPPPPOM1q1bl+FyXExMjJ5//nm5ublpzpw5CgkJMa/78ssvtXPnTv3www/6+OOP9corr9hUR/ny5VWzZk399ttvOnjwoPlpQVc1ZcoU/f7779q6dasqVKigli1bysPDQ+vXr9fZs2dVvnx5zZ49+67bMQxDP/zwg4KCgjJchkzXsWNH9e3bV1988YWaNm2qZs2aqUSJEtq7d68OHjyogQMHauLEiVYfCkg3ZMgQ88MZV69e1d9//62dO3cqKSlJbm5ueuGFF/T+++9nug0PDw81a9ZMP/30k86fP6/atWub72O8VbNmzeTm5qbz589Lsn7m8ffff9eBAwf02GOPyd/f/26/Iof8/PPPkqTHHnssW7YP5ATOvAH5VKNGjbR+/Xq1adNGsbGx+vHHH5WYmKiPP/5Y06dPz9D+2rVrevLJJ3XlyhWNHDkyw5mLggULav78+fL29tbrr79u1xQV6Wf6ZsyYkaVjuhcULVpUmzdv1rvvvqvy5ctr1apVWrZsmYKCgvSf//xH0dHRNj0FunnzZp0+fVqPPPKI1cuV6aZOnapp06apVq1a2rJli5YvX66SJUtq7dq1qlOnjiQpKCgo0/7fffedZs6cqf/7v//TqlWr9PfffysyMlLvvPOOjh07ps8+++yuQerWIJbZq7gKFy5s8cCDtfDm7HeZ3u7s2bNavny5KlSooHbt2mXLPoCcYDIM3s4LIHclJiaqXLly8vDw0PHjx+94pii/GDx4sD788EP9+OOPDl9CfO655zR9+nR98MEHGjRokJMrdL569epp7969OnPmjNWzd1n1wQcfaMiQIfr444/Nl5wBV8SZNwC5zs/PT++8845Onz6tL774IrfLuSdUrVpVY8aMUWRk5B3b7d+/3/z0ZLq0tDRNmzZNM2bMkI+Pj55++unsLNUprl+/rkceeUSTJ0/OluB29epVjR8/XpUrV1a/fv2cvn0gJ3HmDcA9IS0tTQ0bNtTJkyd15MiRbLvnKa/p2bOn5s+frzp16qhUqVK6evWqDhw4oOPHj8vd3V3Tpk1Tr169crvMXPf2229r5MiRWTqTCdwrCG8A4MKWL1+uadOmKTo6WvHx8bpx44aKFy+u+++/X6+++qoaN26c2yUCcDLCGwAAgAvhnjcAAAAXQngDAABwIYQ3AAAAF0J4AwAAcCGENwAAABdCeAMAAHAhhDcAAAAXQngDAABwIf8PHQPluqPK8vYAAAAASUVORK5CYII=", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "dingo.illustrations.plot_histogram(\n", + " suboptimal_steady_states[24], \"biomass\"\n", + ")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "In this case, biomass, contrary to the optimal sampling, **ranges** between 0.08 and 0.275, in a gamma-like distribution. " + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Aparently, the most glucose the cell uptakes, the most it grows. Let's test this!" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Get index of reaction uptaking glucose" + ] + }, + { + "cell_type": "code", + "execution_count": 46, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "58" + ] + }, + "execution_count": 46, + "metadata": {}, + "output_type": "execute_result" + } + ], "source": [ - "from dingo import plot_histogram\n", + "model.reactions.index(\"EX_nh4_e\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "And then plot a copula for the glucose uptake and the biomass flux.in\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", - " n_bins = 60,\n", + "A copula visualizes the dependency structure between two or more variables, separated from their individual distributions.\n", + "In our case, it shows how two reaction fluxes co-vary — i.e. if one flux is high, does the other tend to be high or low too — regardless of their marginal distributions." + ] + }, + { + "cell_type": "code", + "execution_count": 47, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "'BIOMASS_Ecoli_core_w_GAM'" + ] + }, + "execution_count": 47, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "model.reactions[24]" + ] + }, + { + "cell_type": "code", + "execution_count": 48, + "metadata": {}, + "outputs": [ + { + "data": { + "application/vnd.plotly.v1+json": { + "config": { + "plotlyServerURL": "https://plot.ly" + }, + "data": [ + { + "type": "surface", + "z": { + "bdata": "/5gyzGX/mD9QHOm1gk+cP52cnJycnJw/X5LF+Ctfoj+JVSLvu4i1P4Qdt1Dqg50/kZCQkJCQoD+RkJCQkJCgPxUVFRUVFaU/t1Dqgx23sD8SEhISEhKiP5GQkJCQkKA/VFRUVFRUpD+BGrRN54CqP+9UuyGI7qQ/71S7IYjupD/vVLshiO6kP+cZTYCz5qk/qt0QRHeqrT/hE0d6reCDPxISEhISErI/F7FK5H0XsT9MGeayf0ypPxsbGxsbG4s/AAAAAAAAAAA=", + "dtype": "f8", + "shape": "5, 5" + } + } + ], + "layout": { + "height": 600, + "margin": { + "b": 30, + "l": 30, + "r": 30, + "t": 50 + }, + "scene": { + "xaxis": { + "title": { + "text": "biomass" + } + }, + "yaxis": { + "title": { + "text": "nh4" + } + }, + "zaxis": { + "title": { + "text": "prob, mass" + } + } + }, + "title": { + "text": "Copula between biomass and nh4" + }, + "width": 900 + } + } + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "dingo.illustrations.plot_copula(\n", + " [suboptimal_steady_states[24], \"biomass\"], \n", + " [suboptimal_steady_states[58], \"nh4\"], \n", + " n = 5, \n", + " width = 900 , \n", + " height = 600, \n", + " save = False\n", ")" ] }, { "cell_type": "markdown", - "metadata": { - "id": "jl7tJ-ObS6L3" - }, + "metadata": {}, + "source": [ + "### Sampling with other walks" + ] + }, + { + "cell_type": "code", + "execution_count": 66, + "metadata": {}, + "outputs": [], + "source": [ + "model = dingo.MetabolicNetwork.from_sbml('../ext_data/e_coli_core.xml')\n", + "sampler = dingo.PolytopeSampler(model)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "One may use the `generate_steady_states_no_multiphase()` attribute of the `PolytopeSampler` class, to sample steady states using another walk instead of our Multiphase Monte Carlo Sampling (MMCS) algorithm." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, "source": [ - "From the flux distribution of PYK we can that most values fall between 1.7 and 1.9 and that it looks approximately normal, with a mean value at 1.775 mmol/gDW/h (millimoles per gram dry weight per hour).\n" + "Using the `method` argument, you may choose an MCMC method of the following to sample with: \n", + "\n", + "- `billiard_walk`\n", + "- `cdhr`\n", + "- `rdhr`\n", + "- `ball_walk`\n", + "- `dikin_walk`\n", + "- `john_walk`\n", + "- `vaidya_walk`\n", + "- `gaussian_hmc_walk`\n", + "- `exponential_hmc_walk`\n", + "- `hmc_leapfrog_gaussian`\n", + "- `hmc_leapfrog_exponential`\n", + "\n", + "You can also specify the number of steady states to sample (`n`)\n", + "and the number of points to burn before sampling (`burn_in`)\n", + "and the walk length of the chain `thinning`." ] }, { "cell_type": "code", "execution_count": null, - "metadata": { - "colab": { - "base_uri": "https://localhost:8080/", - "height": 617 - }, - "id": "pQskY7EeWPdv", - "outputId": "715833d7-fc8f-452e-e0b2-3fdcf2532848" - }, + "metadata": {}, "outputs": [], "source": [ - "from dingo.illustrations import plot_copula\n", + "bw_samples = sampler.generate_steady_states_no_multiphase(method = \"ball_walk\", n = 1000)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Last, one may first export the polytope that derives from the model under study" + ] + }, + { + "cell_type": "code", + "execution_count": 73, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "((26, 24), (26,), (95, 24), (95,))" + ] + }, + "execution_count": 73, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "polytope = sampler.get_polytope()\n", + "A, b, N, N_shift = polytope\n", "\n", - "reaction_1 = [steady_states[23], reactions[23]]\n", - "reaction_2 = [steady_states[21], reactions[21]]\n", + "A.shape, b.shape, N.shape, N_shift.shape" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "And then use the **full dimenesional** polytope directly to sample from" + ] + }, + { + "cell_type": "code", + "execution_count": 78, + "metadata": {}, + "outputs": [], + "source": [ + "samples_from_p = dingo.PolytopeSampler.sample_from_polytope_no_multiphase(A, b, method = \"john_walk\", n = 1000, burn_in = 100, thinning = 10)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "However, in this case, your samples *live* in the full dimensional space as well, thus:" + ] + }, + { + "cell_type": "code", + "execution_count": 79, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "(24, 1000)" + ] + }, + "execution_count": 79, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "samples_from_p.shape" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "meaning you have a lower number of rows than the number of your model's reactions.\n", "\n", - "plot_copula(reaction_1, reaction_2, n = 5, width = 900 , height = 600, export_format = \"svg\")\n" + "Thus, you need to map your samples back to the low dimension space in order to get a flux value for each reaction:" + ] + }, + { + "cell_type": "code", + "execution_count": 80, + "metadata": {}, + "outputs": [], + "source": [ + "steady_states_samples_from_p = dingo.utils.map_samples_to_steady_states(samples_from_p, N, N_shift)" + ] + }, + { + "cell_type": "code", + "execution_count": 81, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "(95, 1000)" + ] + }, + "execution_count": 81, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "steady_states_samples_from_p.shape" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Sampling on real world GEMs" ] }, { @@ -1584,10 +2200,120 @@ "In line with the findings of Renz et al. (2020), flux sampling in the metabolic model built from the macrophage model and the VBOF using `dingo` indicates **guanylate kinase (GK1)** as a potential antiviral target. \n", "Flux sampling was perfored first with no objective function for the virus and then after maximizing the VBOF built by Renz et al. (2020). \n", "\n", - "\n", + "\n", "\n" ] }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Last, we use a bacterial species GEM from Prof. Machado [collection](https://github.com/cdanielmachado/embl_gems/), to have a first glance of how time scales.\n", + "\n", + "Just like before, we first load the GEM" + ] + }, + { + "cell_type": "code", + "execution_count": 49, + "metadata": {}, + "outputs": [], + "source": [ + "agora_model = dingo.MetabolicNetwork.from_sbml(\"../ext_data/Abiotrophia_defectiva_ATCC_49176.xml.gz\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "andd build a `PolytopeSampler` instance based on that:" + ] + }, + { + "cell_type": "code", + "execution_count": 50, + "metadata": {}, + "outputs": [], + "source": [ + "sampler = dingo.PolytopeSampler(agora_model)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "And we can now use the `generate_steady_states()` or any other sampling function, see above:" + ] + }, + { + "cell_type": "code", + "execution_count": 51, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "phase 1: number of correlated samples = 5400, effective sample size = 31, ratio of the maximum singilar value over the minimum singular value = 14429.6\n", + "phase 2: number of correlated samples = 5400, effective sample size = 40, ratio of the maximum singilar value over the minimum singular value = 65688.4\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[5]maximum marginal PSRF: 2.20378\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "phase 3: number of correlated samples = 4100, effective sample size = 33\n", + "[5]total ess 104: number of correlated samples = 14900\n", + "\n", + "\n" + ] + } + ], + "source": [ + "agora_samples = sampler.generate_steady_states(ess=100)" + ] + }, + { + "cell_type": "code", + "execution_count": 52, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "(1070, 14900)" + ] + }, + "execution_count": 52, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "agora_samples.shape" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Contributing" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "In case you are interested in contributing, please have a thorough look on how to do so on our instructions [here](./CONTRIBUTING.md)." + ] + }, { "cell_type": "markdown", "metadata": { @@ -1626,7 +2352,9 @@ "\n", "* Chalkis Apostolos, Fisikopoulos Vissarion, Tsigaridas Elias and Zafeiropoulos Haris [\"Geometric Algorithms for Sampling the Flux Space of Metabolic Networks\"](https://drops.dagstuhl.de/opus/volltexte/2021/13820/pdf/LIPIcs-SoCG-2021-21.pdf)\n", "\n", - "* Schellenberger, Jan, and Bernhard Ø. Palsson. [\"Use of randomized sampling for analysis of metabolic networks.\"](https://doi.org/10.1074/jbc.R800048200) Journal of biological chemistry 284, no. 9 (2009): 5457-5461." + "* Schellenberger, Jan, and Bernhard Ø. Palsson. [\"Use of randomized sampling for analysis of metabolic networks.\"](https://doi.org/10.1074/jbc.R800048200) Journal of biological chemistry 284, no. 9 (2009): 5457-5461.\n", + "\n", + "* Chalkis Apostolos, Fisikopoulos Vissarion, Tsigaridas Elias and Zafeiropoulos Haris [\"dingo: a Python package for metabolic flux sampling\"](https://doi.org/10.1093/bioadv/vbae037). Bioinformatics Advances. 2024 Jan 1;4(1):vbae037." ] }, { @@ -1653,7 +2381,7 @@ "\n", "
\n", "\n", - "" + "" ] } ], @@ -1663,7 +2391,7 @@ "toc_visible": true }, "kernelspec": { - "display_name": "Python 3.10.6 64-bit", + "display_name": "Python 3", "language": "python", "name": "python3" }, @@ -1677,12 +2405,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.10.6" - }, - "vscode": { - "interpreter": { - "hash": "e7370f93d1d0cde622a1f8e1c04877d8463912d04d973331ad4851f04de6915a" - } + "version": "3.10.19" } }, "nbformat": 4, diff --git a/tutorials/figs/fba.png b/tutorials/figs/fba.png new file mode 100644 index 00000000..6ec38fe5 Binary files /dev/null and b/tutorials/figs/fba.png differ diff --git a/tutorials/figs/fva.png b/tutorials/figs/fva.png new file mode 100644 index 00000000..590a06d0 Binary files /dev/null and b/tutorials/figs/fva.png differ diff --git a/tutorials/figs/genome.png b/tutorials/figs/genome.png new file mode 100644 index 00000000..a11681ff Binary files /dev/null and b/tutorials/figs/genome.png differ diff --git a/tutorials/figs/genome_zoom.png b/tutorials/figs/genome_zoom.png new file mode 100644 index 00000000..721536d4 Binary files /dev/null and b/tutorials/figs/genome_zoom.png differ diff --git a/tutorials/figs/gk1.png b/tutorials/figs/gk1.png new file mode 100644 index 00000000..36fd9625 Binary files /dev/null and b/tutorials/figs/gk1.png differ diff --git a/tutorials/figs/network.png b/tutorials/figs/network.png new file mode 100644 index 00000000..6c5079a2 Binary files /dev/null and b/tutorials/figs/network.png differ diff --git a/tutorials/figs/polytope.png b/tutorials/figs/polytope.png new file mode 100644 index 00000000..a31e06e1 Binary files /dev/null and b/tutorials/figs/polytope.png differ diff --git a/tutorials/figs/rounding.png b/tutorials/figs/rounding.png new file mode 100644 index 00000000..dbe0cf86 Binary files /dev/null and b/tutorials/figs/rounding.png differ diff --git a/tutorials/figs/stoichiometry.png b/tutorials/figs/stoichiometry.png new file mode 100644 index 00000000..c0276377 Binary files /dev/null and b/tutorials/figs/stoichiometry.png differ diff --git a/tutorials/figs/toy_model.png b/tutorials/figs/toy_model.png new file mode 100644 index 00000000..804e28fa Binary files /dev/null and b/tutorials/figs/toy_model.png differ diff --git a/tutorials/figs/walk_1.png b/tutorials/figs/walk_1.png new file mode 100644 index 00000000..3e557bfc Binary files /dev/null and b/tutorials/figs/walk_1.png differ diff --git a/tutorials/figs/walk_2.png b/tutorials/figs/walk_2.png new file mode 100644 index 00000000..9cebcd52 Binary files /dev/null and b/tutorials/figs/walk_2.png differ diff --git a/tutorials/figs/walk_3.png b/tutorials/figs/walk_3.png new file mode 100644 index 00000000..c3e0d431 Binary files /dev/null and b/tutorials/figs/walk_3.png differ diff --git a/tutorials/figs/walk_4.png b/tutorials/figs/walk_4.png new file mode 100644 index 00000000..98c72399 Binary files /dev/null and b/tutorials/figs/walk_4.png differ diff --git a/tutorials/figs/walk_5.png b/tutorials/figs/walk_5.png new file mode 100644 index 00000000..932f7902 Binary files /dev/null and b/tutorials/figs/walk_5.png differ diff --git a/tutorials/figs/walk_6.png b/tutorials/figs/walk_6.png new file mode 100644 index 00000000..9f08a1aa Binary files /dev/null and b/tutorials/figs/walk_6.png differ