From b621e0296454aa518d42200a0314a3d2c4ce5d38 Mon Sep 17 00:00:00 2001 From: Rui Ribeiro <rui.ribeiro@icloud.com> Date: Wed, 30 Nov 2022 11:55:14 +0100 Subject: [PATCH] release --- .gitignore | 10 - .../Gi_parameters.csv | 0 {SI => Reactions_Parameters}/Gi_reactions.csv | 0 .../Gq_parameters.csv | 0 {SI => Reactions_Parameters}/Gq_reactions.csv | 0 .../Gs_parameters.csv | 0 {SI => Reactions_Parameters}/Gs_reactions.csv | 0 .../OXTR_pathway_parameters.csv | 0 .../OXTR_pathway_reactions.csv | 0 docs/_build/doctrees/apidocs.doctree | Bin 114127 -> 123520 bytes docs/_build/doctrees/environment.pickle | Bin 27290 -> 27024 bytes docs/_build/doctrees/index.doctree | Bin 8232 -> 13985 bytes docs/_build/html/.buildinfo | 2 +- docs/_build/html/_sources/apidocs.rst.txt | 7 +- docs/_build/html/_sources/index.rst.txt | 49 + docs/_build/html/_static/custom.css | 15 + .../html/_static/documentation_options.js | 2 +- docs/_build/html/about.html | 2 +- docs/_build/html/apidocs.html | 508 +++--- docs/_build/html/faq.html | 2 +- docs/_build/html/genindex.html | 163 +- docs/_build/html/index.html | 36 +- docs/_build/html/introduction.html | 2 +- docs/_build/html/objects.inv | Bin 740 -> 762 bytes docs/_build/html/py-modindex.html | 24 +- docs/_build/html/search.html | 2 +- docs/_build/html/searchindex.js | 2 +- docs/apidocs.rst | 7 +- docs/conf.py | 5 +- ssbtoolkit/Binding.py | 126 ++ ssbtoolkit/Simulation.py | 1277 ++++++++++++++ ssbtoolkit/__init__.py | 1414 ---------------- ssbtoolkit/__init__py | 15 + ssbtoolkit/__pycache__/Binding.cpython-39.pyc | Bin 0 -> 4055 bytes .../__pycache__/Simulation.cpython-39.pyc | Bin 0 -> 34045 bytes ssbtoolkit/__pycache__/Utils.cpython-39.pyc | Bin 13842 -> 13902 bytes .../__pycache__/__init__.cpython-39.pyc | Bin 39044 -> 39153 bytes .../pathways/__pycache__/Gi.cpython-39.pyc | Bin 9152 -> 9148 bytes .../pathways/__pycache__/Gq.cpython-39.pyc | Bin 6707 -> 6703 bytes .../pathways/__pycache__/Gs.cpython-39.pyc | Bin 8394 -> 8390 bytes .../__pycache__/OXTR_pathway.cpython-39.pyc | Bin 9023 -> 9019 bytes .../OXTR_pathway_testing.cpython-39.pyc | Bin 7557 -> 7553 bytes .../__pycache__/__init__.cpython-39.pyc | Bin 161 -> 157 bytes ssbtoolkit/ssbmain.py.txt | 1477 ----------------- 44 files changed, 1919 insertions(+), 3228 deletions(-) rename {SI => Reactions_Parameters}/Gi_parameters.csv (100%) rename {SI => Reactions_Parameters}/Gi_reactions.csv (100%) rename {SI => Reactions_Parameters}/Gq_parameters.csv (100%) rename {SI => Reactions_Parameters}/Gq_reactions.csv (100%) rename {SI => Reactions_Parameters}/Gs_parameters.csv (100%) rename {SI => Reactions_Parameters}/Gs_reactions.csv (100%) rename {SI => Reactions_Parameters}/OXTR_pathway_parameters.csv (100%) rename {SI => Reactions_Parameters}/OXTR_pathway_reactions.csv (100%) create mode 100644 docs/_build/html/_static/custom.css create mode 100644 ssbtoolkit/Binding.py create mode 100644 ssbtoolkit/Simulation.py delete mode 100644 ssbtoolkit/__init__.py create mode 100644 ssbtoolkit/__init__py create mode 100644 ssbtoolkit/__pycache__/Binding.cpython-39.pyc create mode 100644 ssbtoolkit/__pycache__/Simulation.cpython-39.pyc delete mode 100644 ssbtoolkit/ssbmain.py.txt diff --git a/.gitignore b/.gitignore index 075c900..e69de29 100644 --- a/.gitignore +++ b/.gitignore @@ -1,10 +0,0 @@ -SSBtoolkit-Tutorial1-DEV.ipynb -SSBtoolkit-Tutorial2-DEV.ipynb -SSBtoolkit-Tutorial3A-DEV.ipynb -SSBtoolkit-Tutorial4-OXTR-DEV.ipynb -src/lib/__pycache__/ -src/lib/autogrid_reference_files/ -src/lib/autogrids/ -src/lib/ssbtoolkit_BK.py -.ipynb_checkpoints/ -debugging.txt diff --git a/SI/Gi_parameters.csv b/Reactions_Parameters/Gi_parameters.csv similarity index 100% rename from SI/Gi_parameters.csv rename to Reactions_Parameters/Gi_parameters.csv diff --git a/SI/Gi_reactions.csv b/Reactions_Parameters/Gi_reactions.csv similarity index 100% rename from SI/Gi_reactions.csv rename to Reactions_Parameters/Gi_reactions.csv diff --git a/SI/Gq_parameters.csv b/Reactions_Parameters/Gq_parameters.csv similarity index 100% rename from SI/Gq_parameters.csv rename to Reactions_Parameters/Gq_parameters.csv diff --git a/SI/Gq_reactions.csv b/Reactions_Parameters/Gq_reactions.csv similarity index 100% rename from SI/Gq_reactions.csv rename to Reactions_Parameters/Gq_reactions.csv diff --git a/SI/Gs_parameters.csv b/Reactions_Parameters/Gs_parameters.csv similarity index 100% rename from SI/Gs_parameters.csv rename to Reactions_Parameters/Gs_parameters.csv diff --git a/SI/Gs_reactions.csv b/Reactions_Parameters/Gs_reactions.csv similarity index 100% rename from SI/Gs_reactions.csv rename to Reactions_Parameters/Gs_reactions.csv diff --git a/SI/OXTR_pathway_parameters.csv b/Reactions_Parameters/OXTR_pathway_parameters.csv similarity index 100% rename from SI/OXTR_pathway_parameters.csv rename to Reactions_Parameters/OXTR_pathway_parameters.csv diff --git a/SI/OXTR_pathway_reactions.csv b/Reactions_Parameters/OXTR_pathway_reactions.csv similarity index 100% rename from SI/OXTR_pathway_reactions.csv rename to Reactions_Parameters/OXTR_pathway_reactions.csv diff --git a/docs/_build/doctrees/apidocs.doctree b/docs/_build/doctrees/apidocs.doctree index 950c7982dccbe876784f1a765790a16807645995..411e468fec93d068e06c10eb2c07fda60af01e46 100644 GIT binary patch literal 123520 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 DXFvf| literal 114127 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≫EH`xMB(I@ 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 Yu6HXieKe1u?pR*IsaDKNe3Xp*Kj>lIMgRZ+ diff --git a/docs/_build/doctrees/environment.pickle b/docs/_build/doctrees/environment.pickle index 7e1e4f608ec182198255e6006d6276e30ec742eb..a455faaafa7b458943156ea1b8570c26912d1cd0 100644 GIT binary patch literal 27024 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 literal 27290 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 diff --git a/docs/_build/doctrees/index.doctree b/docs/_build/doctrees/index.doctree index d31a676d874af6ec0359f144403d1d64915ba005..7eea75adca40d1a3ae7007752dabe9d9b066a9f9 100644 GIT binary patch literal 13985 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 JKs{<-{s+lwV59&5 delta 710 zcmZ3OyTXCBfpw~^!ba9K!O6x#3nqV1wP4Hmmm!w%XYxXREx{5X>t}{q26KjbhDL^_ zxAEj6wHPL0#>p4dl$?Ya85lCyW2a<r_ONB<<z(iiPMJJKvvEr86paklEV!&-4@Y`x zUS?iOYQ>b+DNyMQwhXZ>`N;~xvXcYVH92KLMu}xfF;2dyDK}YHTY^c8adLyE&gAFn z(t;8oNr?TXKt-Y;MOthirXb^FMlB_2eu!=!5CwOuUuja70MHN(Z9Q+01kj;bl0XLB zfpAedRME*dH53_xK~7`Q$kJq-ETgFmb6)_$eX$_->8e-*_4aV&=BGgXSDK-kh2&f_ zpc{Q5&IK_&!OoS%<6H)Y$q8CAkr{z-SJq;6J6sgS?J&^{!5+5a)Wo9X4731m#p<gj zgs=L+zET4F3T6bFpUi<CsfYLp#H<GUi4*Rp$se^X1h_zshR0aO&&hvu6Bvyr7l_Mj zuGIU>$aJ4^bA`bQ=E+Vb;*+aQR4g*SWqi)s3DlIe4M>#saONfErj}F|q!xn{(>Fh` Yq+e-<Bq(it&L{+{%lNt3&or130KZ4bWB>pF diff --git a/docs/_build/html/.buildinfo b/docs/_build/html/.buildinfo index 3b43f5e..0aef523 100644 --- a/docs/_build/html/.buildinfo +++ b/docs/_build/html/.buildinfo @@ -1,4 +1,4 @@ # Sphinx build info version 1 # This file hashes the configuration used when building these files. When it is not found, a full rebuild will be done. -config: b647779c966b74360299afc589cf109b +config: b18c64a1de10b91df8b26059330c35f6 tags: 645f666f9bcd5a90fca523b33c5a78b7 diff --git a/docs/_build/html/_sources/apidocs.rst.txt b/docs/_build/html/_sources/apidocs.rst.txt index 5e709c6..9648907 100644 --- a/docs/_build/html/_sources/apidocs.rst.txt +++ b/docs/_build/html/_sources/apidocs.rst.txt @@ -1,5 +1,10 @@ API Documentation ================= +.. automodule:: Binding + :members: + +.. automodule:: Simulation + :members: -.. automodule:: ssbtoolkit +.. automodule:: Utils :members: diff --git a/docs/_build/html/_sources/index.rst.txt b/docs/_build/html/_sources/index.rst.txt index 9efec59..e304d48 100644 --- a/docs/_build/html/_sources/index.rst.txt +++ b/docs/_build/html/_sources/index.rst.txt @@ -46,6 +46,51 @@ protocols. apidocs faq +\ + +\ + +Availability and License +======================== +The source code is freely available at https://github.com/rribeiro-sci/SSBtoolkit under +the Apache 2.0 license. Tutorial notebooks containing minimal working examples can be found at https://github.com/rribeiro-sci/SSBtoolkit. + +\ + +\ + +Developed by +============ +.. image:: https://res.cloudinary.com/djz27k5hg/image/upload/v1637333672/logos/Juelich_logo_100px_ecv2hy.png + :width: 200 + +\ + +\ + +Funded by +========= +.. image:: https://res.cloudinary.com/djz27k5hg/image/upload/v1637657234/logos/HBP_horizontal_logo_qtcyzn.png + :width: 200 + :alt: Alternative text + +Citation |DOI for Citing ssbtoolkit| +==================================== + +pyGOMoDo is research software. If you make use of pyGOMoDo in scientific +publications, please cite it. The BibTeX reference is + +:: + + @article{ribeiro_ssb_2022, + title={{SSB} toolkit: from molecular structure to subcellular signaling pathways.}, + author={Ribeiro, Rui Pedro and Gossen, Jonas and Rossetti, Giulia and Giorgetti, Alejandro}, + publisher={bioRxiv}, + url={https://www.biorxiv.org/content/10.1101/2022.11.08.515595v1}, + doi={10.1101/2022.11.08.515595}, + year={2022} + } + Indices and tables ================== @@ -53,3 +98,7 @@ Indices and tables * :ref:`genindex` * :ref:`modindex` * :ref:`search` + + +.. |DOI for Citing ssbtoolkit| image:: https://img.shields.io/badge/DOI-10.1016%2Fj.bpj.2015.08.015-blue.svg + :target: https://github.com/rribeiro-sci/SSBtoolkit \ No newline at end of file diff --git a/docs/_build/html/_static/custom.css b/docs/_build/html/_static/custom.css new file mode 100644 index 0000000..ff0b629 --- /dev/null +++ b/docs/_build/html/_static/custom.css @@ -0,0 +1,15 @@ +.wy-side-nav-search, .wy-nav-top { + background: #EFEFEF; + +} +/* Text inside search */ +.wy-side-nav-search input[type=text] { + color: #000000; +} + + +/* Home button */ +.wy-side-nav-search > a { + color: #555753; + font-size: 25px; +} \ No newline at end of file diff --git a/docs/_build/html/_static/documentation_options.js b/docs/_build/html/_static/documentation_options.js index 742ecad..f933e82 100644 --- a/docs/_build/html/_static/documentation_options.js +++ b/docs/_build/html/_static/documentation_options.js @@ -1,6 +1,6 @@ var DOCUMENTATION_OPTIONS = { URL_ROOT: document.getElementById("documentation_options").getAttribute('data-url_root'), - VERSION: 'v1', + VERSION: 'v1.0.1', LANGUAGE: 'None', COLLAPSE_INDEX: false, BUILDER: 'html', diff --git a/docs/_build/html/about.html b/docs/_build/html/about.html index cbf3f0b..a20dae4 100644 --- a/docs/_build/html/about.html +++ b/docs/_build/html/about.html @@ -4,7 +4,7 @@ <meta charset="utf-8" /><meta name="generator" content="Docutils 0.17.1: http://docutils.sourceforge.net/" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> - <title>What is the SSBtoolkit? — SSBtoolkit v1 documentation</title> + <title>What is the SSBtoolkit? — SSBtoolkit v1.0.1 documentation</title> <link rel="stylesheet" href="_static/pygments.css" type="text/css" /> <link rel="stylesheet" href="_static/css/theme.css" type="text/css" /> <!--[if lt IE 9]> diff --git a/docs/_build/html/apidocs.html b/docs/_build/html/apidocs.html index 068c98c..a4eecd1 100644 --- a/docs/_build/html/apidocs.html +++ b/docs/_build/html/apidocs.html @@ -4,7 +4,7 @@ <meta charset="utf-8" /><meta name="generator" content="Docutils 0.17.1: http://docutils.sourceforge.net/" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> - <title>API Documentation — SSBtoolkit v1 documentation</title> + <title>API Documentation — SSBtoolkit v1.0.1 documentation</title> <link rel="stylesheet" href="_static/pygments.css" type="text/css" /> <link rel="stylesheet" href="_static/css/theme.css" type="text/css" /> <!--[if lt IE 9]> @@ -70,15 +70,11 @@ <div role="main" class="document" itemscope="itemscope" itemtype="http://schema.org/Article"> <div itemprop="articleBody"> - <section id="module-ssbtoolkit"> -<span id="api-documentation"></span><h1>API Documentation<a class="headerlink" href="#module-ssbtoolkit" title="Permalink to this headline">ïƒ</a></h1> -<dl class="py class"> -<dt class="sig sig-object py" id="ssbtoolkit.binding"> -<em class="property"><span class="pre">class</span><span class="w"> </span></em><span class="sig-prename descclassname"><span class="pre">ssbtoolkit.</span></span><span class="sig-name descname"><span class="pre">binding</span></span><a class="headerlink" href="#ssbtoolkit.binding" title="Permalink to this definition">ïƒ</a></dt> -<dd><p>This class simulate ligand-target binding curves.</p> -<dl class="py method"> -<dt class="sig sig-object py" id="ssbtoolkit.binding.bind"> -<span class="sig-name descname"><span class="pre">bind</span></span><span class="sig-paren">(</span><em class="sig-param"><span class="o"><span class="pre">**</span></span><span class="n"><span class="pre">kwargs</span></span></em><span class="sig-paren">)</span><a class="headerlink" href="#ssbtoolkit.binding.bind" title="Permalink to this definition">ïƒ</a></dt> + <section id="module-Binding"> +<span id="api-documentation"></span><h1>API Documentation<a class="headerlink" href="#module-Binding" title="Permalink to this headline">ïƒ</a></h1> +<dl class="py function"> +<dt class="sig sig-object py" id="Binding.Bind"> +<span class="sig-prename descclassname"><span class="pre">Binding.</span></span><span class="sig-name descname"><span class="pre">Bind</span></span><span class="sig-paren">(</span><em class="sig-param"><span class="n"><span class="pre">self</span></span></em>, <em class="sig-param"><span class="o"><span class="pre">**</span></span><span class="n"><span class="pre">kwargs</span></span></em><span class="sig-paren">)</span><a class="headerlink" href="#Binding.Bind" title="Permalink to this definition">ïƒ</a></dt> <dd><p>Applies an function to calculate the fraction of occupited receptors at equilibrium.</p> <dl class="field-list simple"> <dt class="field-odd">Parameters</dt> @@ -91,9 +87,15 @@ </dl> </dd></dl> -<dl class="py method"> -<dt class="sig sig-object py" id="ssbtoolkit.binding.maxbend"> -<span class="sig-name descname"><span class="pre">maxbend</span></span><span class="sig-paren">(</span><span class="sig-paren">)</span><a class="headerlink" href="#ssbtoolkit.binding.maxbend" title="Permalink to this definition">ïƒ</a></dt> +<dl class="py function"> +<dt class="sig sig-object py" id="Binding.ShowCurve"> +<span class="sig-prename descclassname"><span class="pre">Binding.</span></span><span class="sig-name descname"><span class="pre">ShowCurve</span></span><span class="sig-paren">(</span><em class="sig-param"><span class="n"><span class="pre">self</span></span></em><span class="sig-paren">)</span><a class="headerlink" href="#Binding.ShowCurve" title="Permalink to this definition">ïƒ</a></dt> +<dd><p>Plots ligand-target binding curve</p> +</dd></dl> + +<dl class="py function"> +<dt class="sig sig-object py" id="Binding.SubMaxConcentration"> +<span class="sig-prename descclassname"><span class="pre">Binding.</span></span><span class="sig-name descname"><span class="pre">SubMaxConcentration</span></span><span class="sig-paren">(</span><em class="sig-param"><span class="n"><span class="pre">self</span></span></em><span class="sig-paren">)</span><a class="headerlink" href="#Binding.SubMaxConcentration" title="Permalink to this definition">ïƒ</a></dt> <dd><p>Calculates the maximum bending point of a sigmoid-shaped curve according to the mathod of Sebaugh et al., 2003.</p> <dl class="field-list simple"> <dt class="field-odd">Parameters</dt> @@ -112,150 +114,13 @@ </div> </dd></dl> -<dl class="py method"> -<dt class="sig sig-object py" id="ssbtoolkit.binding.show_curve"> -<span class="sig-name descname"><span class="pre">show_curve</span></span><span class="sig-paren">(</span><span class="sig-paren">)</span><a class="headerlink" href="#ssbtoolkit.binding.show_curve" title="Permalink to this definition">ïƒ</a></dt> -<dd><p>Plots ligand-target binding curve</p> -</dd></dl> - -</dd></dl> - -<dl class="py class"> -<dt class="sig sig-object py" id="ssbtoolkit.convert"> -<em class="property"><span class="pre">class</span><span class="w"> </span></em><span class="sig-prename descclassname"><span class="pre">ssbtoolkit.</span></span><span class="sig-name descname"><span class="pre">convert</span></span><a class="headerlink" href="#ssbtoolkit.convert" title="Permalink to this definition">ïƒ</a></dt> -<dd><p>Helper functions</p> -<dl class="py method"> -<dt class="sig sig-object py" id="ssbtoolkit.convert.KineticTempScale"> -<span class="sig-name descname"><span class="pre">KineticTempScale</span></span><span class="sig-paren">(</span><em class="sig-param"><span class="n"><span class="pre">koff</span></span></em>, <em class="sig-param"><span class="n"><span class="pre">T1</span></span></em>, <em class="sig-param"><span class="n"><span class="pre">T2</span></span></em>, <em class="sig-param"><span class="n"><span class="pre">Tu</span></span><span class="o"><span class="pre">=</span></span><span class="default_value"><span class="pre">'K'</span></span></em>, <em class="sig-param"><span class="o"><span class="pre">*</span></span><span class="n"><span class="pre">kwargs</span></span></em><span class="sig-paren">)</span><a class="headerlink" href="#ssbtoolkit.convert.KineticTempScale" title="Permalink to this definition">ïƒ</a></dt> -<dd><p>This function rescales the kinetics constants to a specific temperature.</p> -<dl class="field-list simple"> -<dt class="field-odd">Parameters</dt> -<dd class="field-odd"><ul class="simple"> -<li><p><strong>kon</strong> – Required (flt): foward kinetic constant</p></li> -<li><p><strong>koff</strong> – Required (flt): reverse kinetic constant</p></li> -<li><p><strong>T1</strong> – Required (flt): Initial temperature</p></li> -<li><p><strong>T2</strong> – Required (flt): Final temperature</p></li> -</ul> -</dd> -<dt class="field-even">Paramter Tu</dt> -<dd class="field-even"><p>Optional (kwarg str): Temperature Units (kelvin=’K’, celsius=’C’)</p> -</dd> -<dt class="field-odd">Returns</dt> -<dd class="field-odd"><p>(flt, flt)</p> -</dd> -</dl> -</dd></dl> - -<dl class="py method"> -<dt class="sig sig-object py" id="ssbtoolkit.convert.microgr2nanomolar"> -<span class="sig-name descname"><span class="pre">microgr2nanomolar</span></span><span class="sig-paren">(</span><em class="sig-param"><span class="n"><span class="pre">concentration</span></span></em><span class="sig-paren">)</span><a class="headerlink" href="#ssbtoolkit.convert.microgr2nanomolar" title="Permalink to this definition">ïƒ</a></dt> -<dd><p>This function converts micrograms of protein in nanomolar.</p> -<dl class="field-list simple"> -<dt class="field-odd">Parameters</dt> -<dd class="field-odd"><ul class="simple"> -<li><p><strong>uniprotID</strong> – Required (str)</p></li> -<li><p><strong>concentration</strong> – Required (int): concentration od protein in micrograms</p></li> -</ul> -</dd> -<dt class="field-even">Returns</dt> -<dd class="field-even"><p>(flt) concentration of protein in nM</p> -</dd> -</dl> -<div class="admonition note"> -<p class="admonition-title">Note</p> -<p>This function will obtain the sequence of the protein from UNIPROT and calculate automatically its molecular mass</p> -</div> -</dd></dl> - -</dd></dl> - -<dl class="py class"> -<dt class="sig sig-object py" id="ssbtoolkit.get"> -<em class="property"><span class="pre">class</span><span class="w"> </span></em><span class="sig-prename descclassname"><span class="pre">ssbtoolkit.</span></span><span class="sig-name descname"><span class="pre">get</span></span><a class="headerlink" href="#ssbtoolkit.get" title="Permalink to this definition">ïƒ</a></dt> -<dd><p>Tools to retrive protein information</p> -<dl class="py method"> -<dt class="sig sig-object py" id="ssbtoolkit.get.gprotein"> -<span class="sig-name descname"><span class="pre">gprotein</span></span><span class="sig-paren">(</span><span class="sig-paren">)</span><a class="headerlink" href="#ssbtoolkit.get.gprotein" title="Permalink to this definition">ïƒ</a></dt> -<dd><p>This function query the SSBtoolkit internal database to extract the G protein associated to GPCR.</p> -<div class="admonition warning"> -<p class="admonition-title">Warning</p> -<p>it just works for Human GPCRS!</p> -</div> -<dl class="field-list simple"> -<dt class="field-odd">Parameters</dt> -<dd class="field-odd"><p><strong>uniprotID</strong> – Required (str)</p> -</dd> -<dt class="field-even">Returns</dt> -<dd class="field-even"><p>(str)</p> -</dd> -</dl> -</dd></dl> - -<dl class="py class"> -<dt class="sig sig-object py" id="ssbtoolkit.get.tauRAMD"> -<em class="property"><span class="pre">class</span><span class="w"> </span></em><span class="sig-name descname"><span class="pre">tauRAMD</span></span><a class="headerlink" href="#ssbtoolkit.get.tauRAMD" title="Permalink to this definition">ïƒ</a></dt> -<dd><p>Implementation of the tRAMD method by Kokh et al., 2018.</p> -<dl class="py method"> -<dt class="sig sig-object py" id="ssbtoolkit.get.tauRAMD.Run"> -<span class="sig-name descname"><span class="pre">Run</span></span><span class="sig-paren">(</span><em class="sig-param"><span class="o"><span class="pre">**</span></span><span class="n"><span class="pre">kwargs</span></span></em><span class="sig-paren">)</span><a class="headerlink" href="#ssbtoolkit.get.tauRAMD.Run" title="Permalink to this definition">ïƒ</a></dt> -<dd><p>Calulates the residence time of a ligand from RAMD simualtions.</p> -<dl class="field-list simple"> -<dt class="field-odd">Parameters</dt> -<dd class="field-odd"><ul class="simple"> -<li><p><strong>prefix</strong> – Required (kwarg str): directory path of .dat files</p></li> -<li><p><strong>dt</strong> – Optional (kwarg flt): MD simulations time step in ns (defaul is 2E-6)</p></li> -<li><p><strong>softwr</strong> – Optional (kwarg str): software used to perform RAMD simulations: NAMD, GROMACS (default)</p></li> -</ul> -</dd> -<dt class="field-even">Return (str)</dt> -<dd class="field-even"><p>residence time</p> -</dd> -</dl> -</dd></dl> - -<dl class="py method"> -<dt class="sig sig-object py" id="ssbtoolkit.get.tauRAMD.plotRTdistribuitons"> -<span class="sig-name descname"><span class="pre">plotRTdistribuitons</span></span><span class="sig-paren">(</span><em class="sig-param"><span class="n"><span class="pre">save</span></span><span class="o"><span class="pre">=</span></span><span class="default_value"><span class="pre">False</span></span></em>, <em class="sig-param"><span class="n"><span class="pre">filename</span></span><span class="o"><span class="pre">=</span></span><span class="default_value"><span class="pre">None</span></span></em><span class="sig-paren">)</span><a class="headerlink" href="#ssbtoolkit.get.tauRAMD.plotRTdistribuitons" title="Permalink to this definition">ïƒ</a></dt> -<dd><p>Plots the residence time distributions</p> -<dl class="field-list simple"> -<dt class="field-odd">Parameters</dt> -<dd class="field-odd"><ul class="simple"> -<li><p><strong>save</strong> – Optional (kwarg boolean): default False</p></li> -<li><p><strong>filename</strong> – Optional (kwarg str)</p></li> -</ul> -</dd> -</dl> -</dd></dl> - -<dl class="py method"> -<dt class="sig sig-object py" id="ssbtoolkit.get.tauRAMD.plotRTstats"> -<span class="sig-name descname"><span class="pre">plotRTstats</span></span><span class="sig-paren">(</span><em class="sig-param"><span class="n"><span class="pre">save</span></span><span class="o"><span class="pre">=</span></span><span class="default_value"><span class="pre">False</span></span></em>, <em class="sig-param"><span class="n"><span class="pre">filename</span></span><span class="o"><span class="pre">=</span></span><span class="default_value"><span class="pre">None</span></span></em><span class="sig-paren">)</span><a class="headerlink" href="#ssbtoolkit.get.tauRAMD.plotRTstats" title="Permalink to this definition">ïƒ</a></dt> -<dd><p>Plots the residence time statistics</p> -<dl class="field-list simple"> -<dt class="field-odd">Parameters</dt> -<dd class="field-odd"><ul class="simple"> -<li><p><strong>save</strong> – Optional (kwarg boolean): default False</p></li> -<li><p><strong>filename</strong> – Optional (kwarg str)</p></li> -</ul> -</dd> -</dl> -</dd></dl> - -</dd></dl> - -</dd></dl> - -<dl class="py class"> -<dt class="sig sig-object py" id="ssbtoolkit.simulation"> -<em class="property"><span class="pre">class</span><span class="w"> </span></em><span class="sig-prename descclassname"><span class="pre">ssbtoolkit.</span></span><span class="sig-name descname"><span class="pre">simulation</span></span><a class="headerlink" href="#ssbtoolkit.simulation" title="Permalink to this definition">ïƒ</a></dt> -<dd><p>This class simulates the mathematical models of the signaling pathways.</p> -<dl class="py class"> -<dt class="sig sig-object py" id="ssbtoolkit.simulation.activation"> -<em class="property"><span class="pre">class</span><span class="w"> </span></em><span class="sig-name descname"><span class="pre">activation</span></span><a class="headerlink" href="#ssbtoolkit.simulation.activation" title="Permalink to this definition">ïƒ</a></dt> +<span class="target" id="module-Simulation"></span><dl class="py class"> +<dt class="sig sig-object py" id="Simulation.Activation"> +<em class="property"><span class="pre">class</span><span class="w"> </span></em><span class="sig-prename descclassname"><span class="pre">Simulation.</span></span><span class="sig-name descname"><span class="pre">Activation</span></span><a class="headerlink" href="#Simulation.Activation" title="Permalink to this definition">ïƒ</a></dt> <dd><p>Simulation of the activation of signaling pathways (i.e. activation by agonists)</p> <dl class="py method"> -<dt class="sig sig-object py" id="ssbtoolkit.simulation.activation.Analysis"> -<span class="sig-name descname"><span class="pre">Analysis</span></span><span class="sig-paren">(</span><span class="sig-paren">)</span><a class="headerlink" href="#ssbtoolkit.simulation.activation.Analysis" title="Permalink to this definition">ïƒ</a></dt> +<dt class="sig sig-object py" id="Simulation.Activation.Analysis"> +<span class="sig-name descname"><span class="pre">Analysis</span></span><span class="sig-paren">(</span><span class="sig-paren">)</span><a class="headerlink" href="#Simulation.Activation.Analysis" title="Permalink to this definition">ïƒ</a></dt> <dd><p>This function calculates the dose-response effect.</p> <dl class="field-list simple"> <dt class="field-odd">Returns</dt> @@ -265,14 +130,8 @@ </dd></dl> <dl class="py method"> -<dt class="sig sig-object py" id="ssbtoolkit.simulation.activation.Curve"> -<span class="sig-name descname"><span class="pre">Curve</span></span><span class="sig-paren">(</span><em class="sig-param"><span class="n"><span class="pre">save</span></span><span class="o"><span class="pre">=</span></span><span class="default_value"><span class="pre">False</span></span></em>, <em class="sig-param"><span class="n"><span class="pre">filename</span></span><span class="o"><span class="pre">=</span></span><span class="default_value"><span class="pre">None</span></span></em><span class="sig-paren">)</span><a class="headerlink" href="#ssbtoolkit.simulation.activation.Curve" title="Permalink to this definition">ïƒ</a></dt> -<dd><p>Plots the dose-response curve.</p> -</dd></dl> - -<dl class="py method"> -<dt class="sig sig-object py" id="ssbtoolkit.simulation.activation.PathwayParameters"> -<span class="sig-name descname"><span class="pre">PathwayParameters</span></span><span class="sig-paren">(</span><span class="sig-paren">)</span><a class="headerlink" href="#ssbtoolkit.simulation.activation.PathwayParameters" title="Permalink to this definition">ïƒ</a></dt> +<dt class="sig sig-object py" id="Simulation.Activation.PathwayParameters"> +<span class="sig-name descname"><span class="pre">PathwayParameters</span></span><span class="sig-paren">(</span><span class="sig-paren">)</span><a class="headerlink" href="#Simulation.Activation.PathwayParameters" title="Permalink to this definition">ïƒ</a></dt> <dd><p>Display table with default pathway parameters.</p> <div class="admonition warning"> <p class="admonition-title">Warning</p> @@ -281,8 +140,8 @@ </dd></dl> <dl class="py method"> -<dt class="sig sig-object py" id="ssbtoolkit.simulation.activation.PathwayParametersToCSV"> -<span class="sig-name descname"><span class="pre">PathwayParametersToCSV</span></span><span class="sig-paren">(</span><em class="sig-param"><span class="n"><span class="pre">path</span></span></em><span class="sig-paren">)</span><a class="headerlink" href="#ssbtoolkit.simulation.activation.PathwayParametersToCSV" title="Permalink to this definition">ïƒ</a></dt> +<dt class="sig sig-object py" id="Simulation.Activation.PathwayParametersToCSV"> +<span class="sig-name descname"><span class="pre">PathwayParametersToCSV</span></span><span class="sig-paren">(</span><em class="sig-param"><span class="n"><span class="pre">path</span></span></em><span class="sig-paren">)</span><a class="headerlink" href="#Simulation.Activation.PathwayParametersToCSV" title="Permalink to this definition">ïƒ</a></dt> <dd><p>Export pathway parameters into CSV format.</p> <dl class="field-list simple"> <dt class="field-odd">Parameters</dt> @@ -292,14 +151,8 @@ </dd></dl> <dl class="py method"> -<dt class="sig sig-object py" id="ssbtoolkit.simulation.activation.Potency"> -<span class="sig-name descname"><span class="pre">Potency</span></span><span class="sig-paren">(</span><span class="sig-paren">)</span><a class="headerlink" href="#ssbtoolkit.simulation.activation.Potency" title="Permalink to this definition">ïƒ</a></dt> -<dd><p>Return the potency values as a pandas DataFrame.</p> -</dd></dl> - -<dl class="py method"> -<dt class="sig sig-object py" id="ssbtoolkit.simulation.activation.PotencyToCSV"> -<span class="sig-name descname"><span class="pre">PotencyToCSV</span></span><span class="sig-paren">(</span><em class="sig-param"><span class="n"><span class="pre">path</span></span></em><span class="sig-paren">)</span><a class="headerlink" href="#ssbtoolkit.simulation.activation.PotencyToCSV" title="Permalink to this definition">ïƒ</a></dt> +<dt class="sig sig-object py" id="Simulation.Activation.PotencyToCSV"> +<span class="sig-name descname"><span class="pre">PotencyToCSV</span></span><span class="sig-paren">(</span><em class="sig-param"><span class="n"><span class="pre">path</span></span></em><span class="sig-paren">)</span><a class="headerlink" href="#Simulation.Activation.PotencyToCSV" title="Permalink to this definition">ïƒ</a></dt> <dd><p>Exports the potency values into csv format.</p> <dl class="field-list simple"> <dt class="field-odd">Parameters</dt> @@ -309,26 +162,26 @@ </dd></dl> <dl class="py method"> -<dt class="sig sig-object py" id="ssbtoolkit.simulation.activation.PotencyToDict"> -<span class="sig-name descname"><span class="pre">PotencyToDict</span></span><span class="sig-paren">(</span><span class="sig-paren">)</span><a class="headerlink" href="#ssbtoolkit.simulation.activation.PotencyToDict" title="Permalink to this definition">ïƒ</a></dt> +<dt class="sig sig-object py" id="Simulation.Activation.PotencyToDict"> +<span class="sig-name descname"><span class="pre">PotencyToDict</span></span><span class="sig-paren">(</span><span class="sig-paren">)</span><a class="headerlink" href="#Simulation.Activation.PotencyToDict" title="Permalink to this definition">ïƒ</a></dt> <dd><p>Convert potencies into a dictionary.</p> </dd></dl> <dl class="py method"> -<dt class="sig sig-object py" id="ssbtoolkit.simulation.activation.Reactions"> -<span class="sig-name descname"><span class="pre">Reactions</span></span><span class="sig-paren">(</span><span class="sig-paren">)</span><a class="headerlink" href="#ssbtoolkit.simulation.activation.Reactions" title="Permalink to this definition">ïƒ</a></dt> +<dt class="sig sig-object py" id="Simulation.Activation.Reactions"> +<span class="sig-name descname"><span class="pre">Reactions</span></span><span class="sig-paren">(</span><span class="sig-paren">)</span><a class="headerlink" href="#Simulation.Activation.Reactions" title="Permalink to this definition">ïƒ</a></dt> <dd><p>Display pathway reactions.</p> </dd></dl> <dl class="py method"> -<dt class="sig sig-object py" id="ssbtoolkit.simulation.activation.Run"> -<span class="sig-name descname"><span class="pre">Run</span></span><span class="sig-paren">(</span><span class="sig-paren">)</span><a class="headerlink" href="#ssbtoolkit.simulation.activation.Run" title="Permalink to this definition">ïƒ</a></dt> +<dt class="sig sig-object py" id="Simulation.Activation.Run"> +<span class="sig-name descname"><span class="pre">Run</span></span><span class="sig-paren">(</span><span class="sig-paren">)</span><a class="headerlink" href="#Simulation.Activation.Run" title="Permalink to this definition">ïƒ</a></dt> <dd><p>This function runs the pathway simulation and returns the raw simulation data.</p> </dd></dl> <dl class="py method"> -<dt class="sig sig-object py" id="ssbtoolkit.simulation.activation.SetSimulationParameters"> -<span class="sig-name descname"><span class="pre">SetSimulationParameters</span></span><span class="sig-paren">(</span><em class="sig-param"><span class="o"><span class="pre">**</span></span><span class="n"><span class="pre">kwargs</span></span></em><span class="sig-paren">)</span><a class="headerlink" href="#ssbtoolkit.simulation.activation.SetSimulationParameters" title="Permalink to this definition">ïƒ</a></dt> +<dt class="sig sig-object py" id="Simulation.Activation.SetSimulationParameters"> +<span class="sig-name descname"><span class="pre">SetSimulationParameters</span></span><span class="sig-paren">(</span><em class="sig-param"><span class="o"><span class="pre">**</span></span><span class="n"><span class="pre">kwargs</span></span></em><span class="sig-paren">)</span><a class="headerlink" href="#Simulation.Activation.SetSimulationParameters" title="Permalink to this definition">ïƒ</a></dt> <dd><dl class="field-list simple"> <dt class="field-odd">Parameters</dt> <dd class="field-odd"><ul class="simple"> @@ -350,8 +203,20 @@ </dd></dl> <dl class="py method"> -<dt class="sig sig-object py" id="ssbtoolkit.simulation.activation.UserPathwayParameters"> -<span class="sig-name descname"><span class="pre">UserPathwayParameters</span></span><span class="sig-paren">(</span><em class="sig-param"><span class="n"><span class="pre">path</span></span></em><span class="sig-paren">)</span><a class="headerlink" href="#ssbtoolkit.simulation.activation.UserPathwayParameters" title="Permalink to this definition">ïƒ</a></dt> +<dt class="sig sig-object py" id="Simulation.Activation.ShowCurve"> +<span class="sig-name descname"><span class="pre">ShowCurve</span></span><span class="sig-paren">(</span><em class="sig-param"><span class="n"><span class="pre">save</span></span><span class="o"><span class="pre">=</span></span><span class="default_value"><span class="pre">False</span></span></em>, <em class="sig-param"><span class="n"><span class="pre">filename</span></span><span class="o"><span class="pre">=</span></span><span class="default_value"><span class="pre">None</span></span></em><span class="sig-paren">)</span><a class="headerlink" href="#Simulation.Activation.ShowCurve" title="Permalink to this definition">ïƒ</a></dt> +<dd><p>Plots the dose-response curve.</p> +</dd></dl> + +<dl class="py method"> +<dt class="sig sig-object py" id="Simulation.Activation.ShowPotency"> +<span class="sig-name descname"><span class="pre">ShowPotency</span></span><span class="sig-paren">(</span><span class="sig-paren">)</span><a class="headerlink" href="#Simulation.Activation.ShowPotency" title="Permalink to this definition">ïƒ</a></dt> +<dd><p>Return the potency values as a pandas DataFrame.</p> +</dd></dl> + +<dl class="py method"> +<dt class="sig sig-object py" id="Simulation.Activation.UserPathwayParameters"> +<span class="sig-name descname"><span class="pre">UserPathwayParameters</span></span><span class="sig-paren">(</span><em class="sig-param"><span class="n"><span class="pre">path</span></span></em><span class="sig-paren">)</span><a class="headerlink" href="#Simulation.Activation.UserPathwayParameters" title="Permalink to this definition">ïƒ</a></dt> <dd><p>Import user pathway parameters.</p> <dl class="field-list simple"> <dt class="field-odd">Parameters</dt> @@ -363,16 +228,16 @@ </dd></dl> <dl class="py class"> -<dt class="sig sig-object py" id="ssbtoolkit.simulation.fitModel"> -<em class="property"><span class="pre">class</span><span class="w"> </span></em><span class="sig-name descname"><span class="pre">fitModel</span></span><a class="headerlink" href="#ssbtoolkit.simulation.fitModel" title="Permalink to this definition">ïƒ</a></dt> +<dt class="sig sig-object py" id="Simulation.FitModel"> +<em class="property"><span class="pre">class</span><span class="w"> </span></em><span class="sig-prename descclassname"><span class="pre">Simulation.</span></span><span class="sig-name descname"><span class="pre">FitModel</span></span><a class="headerlink" href="#Simulation.FitModel" title="Permalink to this definition">ïƒ</a></dt> <dd><p>Fit a model to experimental data.</p> <div class="admonition note"> <p class="admonition-title">Note</p> <p>This class was developed to reproduce data from a specific experimental setup. Please see tutorial 4 (OXTR pathay). Use carefully!</p> </div> <dl class="py method"> -<dt class="sig sig-object py" id="ssbtoolkit.simulation.fitModel.PathwayParameters"> -<span class="sig-name descname"><span class="pre">PathwayParameters</span></span><span class="sig-paren">(</span><span class="sig-paren">)</span><a class="headerlink" href="#ssbtoolkit.simulation.fitModel.PathwayParameters" title="Permalink to this definition">ïƒ</a></dt> +<dt class="sig sig-object py" id="Simulation.FitModel.PathwayParameters"> +<span class="sig-name descname"><span class="pre">PathwayParameters</span></span><span class="sig-paren">(</span><span class="sig-paren">)</span><a class="headerlink" href="#Simulation.FitModel.PathwayParameters" title="Permalink to this definition">ïƒ</a></dt> <dd><p>Display table with default pathway parameters.</p> <div class="admonition warning"> <p class="admonition-title">Warning</p> @@ -381,8 +246,8 @@ </dd></dl> <dl class="py method"> -<dt class="sig sig-object py" id="ssbtoolkit.simulation.fitModel.PathwayParametersToCSV"> -<span class="sig-name descname"><span class="pre">PathwayParametersToCSV</span></span><span class="sig-paren">(</span><em class="sig-param"><span class="n"><span class="pre">path</span></span></em><span class="sig-paren">)</span><a class="headerlink" href="#ssbtoolkit.simulation.fitModel.PathwayParametersToCSV" title="Permalink to this definition">ïƒ</a></dt> +<dt class="sig sig-object py" id="Simulation.FitModel.PathwayParametersToCSV"> +<span class="sig-name descname"><span class="pre">PathwayParametersToCSV</span></span><span class="sig-paren">(</span><em class="sig-param"><span class="n"><span class="pre">path</span></span></em><span class="sig-paren">)</span><a class="headerlink" href="#Simulation.FitModel.PathwayParametersToCSV" title="Permalink to this definition">ïƒ</a></dt> <dd><p>Export pathway parameters into CSV format.</p> <dl class="field-list simple"> <dt class="field-odd">Parameters</dt> @@ -392,14 +257,20 @@ </dd></dl> <dl class="py method"> -<dt class="sig sig-object py" id="ssbtoolkit.simulation.fitModel.Reactions"> -<span class="sig-name descname"><span class="pre">Reactions</span></span><span class="sig-paren">(</span><span class="sig-paren">)</span><a class="headerlink" href="#ssbtoolkit.simulation.fitModel.Reactions" title="Permalink to this definition">ïƒ</a></dt> +<dt class="sig sig-object py" id="Simulation.FitModel.PlotIterations"> +<span class="sig-name descname"><span class="pre">PlotIterations</span></span><span class="sig-paren">(</span><em class="sig-param"><span class="n"><span class="pre">save</span></span><span class="o"><span class="pre">=</span></span><span class="default_value"><span class="pre">False</span></span></em>, <em class="sig-param"><span class="n"><span class="pre">filename</span></span><span class="o"><span class="pre">=</span></span><span class="default_value"><span class="pre">None</span></span></em><span class="sig-paren">)</span><a class="headerlink" href="#Simulation.FitModel.PlotIterations" title="Permalink to this definition">ïƒ</a></dt> +<dd><p>Plot iterations.</p> +</dd></dl> + +<dl class="py method"> +<dt class="sig sig-object py" id="Simulation.FitModel.Reactions"> +<span class="sig-name descname"><span class="pre">Reactions</span></span><span class="sig-paren">(</span><span class="sig-paren">)</span><a class="headerlink" href="#Simulation.FitModel.Reactions" title="Permalink to this definition">ïƒ</a></dt> <dd><p>Display pathway reactions.</p> </dd></dl> <dl class="py method"> -<dt class="sig sig-object py" id="ssbtoolkit.simulation.fitModel.Run"> -<span class="sig-name descname"><span class="pre">Run</span></span><span class="sig-paren">(</span><em class="sig-param"><span class="o"><span class="pre">**</span></span><span class="n"><span class="pre">kwargs</span></span></em><span class="sig-paren">)</span><a class="headerlink" href="#ssbtoolkit.simulation.fitModel.Run" title="Permalink to this definition">ïƒ</a></dt> +<dt class="sig sig-object py" id="Simulation.FitModel.Run"> +<span class="sig-name descname"><span class="pre">Run</span></span><span class="sig-paren">(</span><em class="sig-param"><span class="o"><span class="pre">**</span></span><span class="n"><span class="pre">kwargs</span></span></em><span class="sig-paren">)</span><a class="headerlink" href="#Simulation.FitModel.Run" title="Permalink to this definition">ïƒ</a></dt> <dd><p>Fits of the model to experimental data.</p> <dl class="field-list simple"> <dt class="field-odd">Parameters</dt> @@ -416,8 +287,8 @@ </dd></dl> <dl class="py method"> -<dt class="sig sig-object py" id="ssbtoolkit.simulation.fitModel.SetSimulationParameters"> -<span class="sig-name descname"><span class="pre">SetSimulationParameters</span></span><span class="sig-paren">(</span><em class="sig-param"><span class="o"><span class="pre">**</span></span><span class="n"><span class="pre">kwargs</span></span></em><span class="sig-paren">)</span><a class="headerlink" href="#ssbtoolkit.simulation.fitModel.SetSimulationParameters" title="Permalink to this definition">ïƒ</a></dt> +<dt class="sig sig-object py" id="Simulation.FitModel.SetSimulationParameters"> +<span class="sig-name descname"><span class="pre">SetSimulationParameters</span></span><span class="sig-paren">(</span><em class="sig-param"><span class="o"><span class="pre">**</span></span><span class="n"><span class="pre">kwargs</span></span></em><span class="sig-paren">)</span><a class="headerlink" href="#Simulation.FitModel.SetSimulationParameters" title="Permalink to this definition">ïƒ</a></dt> <dd><dl class="field-list simple"> <dt class="field-odd">Parameters</dt> <dd class="field-odd"><ul class="simple"> @@ -435,19 +306,8 @@ </dd></dl> <dl class="py method"> -<dt class="sig sig-object py" id="ssbtoolkit.simulation.fitModel.UserPathwayParameters"> -<span class="sig-name descname"><span class="pre">UserPathwayParameters</span></span><span class="sig-paren">(</span><em class="sig-param"><span class="n"><span class="pre">path</span></span></em><span class="sig-paren">)</span><a class="headerlink" href="#ssbtoolkit.simulation.fitModel.UserPathwayParameters" title="Permalink to this definition">ïƒ</a></dt> -<dd><p>Import user pathway parameters.</p> -<dl class="field-list simple"> -<dt class="field-odd">Parameters</dt> -<dd class="field-odd"><p><strong>path</strong> – Required (kwarg str): directory path</p> -</dd> -</dl> -</dd></dl> - -<dl class="py method"> -<dt class="sig sig-object py" id="ssbtoolkit.simulation.fitModel.plotCurves"> -<span class="sig-name descname"><span class="pre">plotCurves</span></span><span class="sig-paren">(</span><em class="sig-param"><span class="n"><span class="pre">save</span></span><span class="o"><span class="pre">=</span></span><span class="default_value"><span class="pre">False</span></span></em>, <em class="sig-param"><span class="n"><span class="pre">filename</span></span><span class="o"><span class="pre">=</span></span><span class="default_value"><span class="pre">None</span></span></em><span class="sig-paren">)</span><a class="headerlink" href="#ssbtoolkit.simulation.fitModel.plotCurves" title="Permalink to this definition">ïƒ</a></dt> +<dt class="sig sig-object py" id="Simulation.FitModel.ShowGraphs"> +<span class="sig-name descname"><span class="pre">ShowGraphs</span></span><span class="sig-paren">(</span><em class="sig-param"><span class="n"><span class="pre">save</span></span><span class="o"><span class="pre">=</span></span><span class="default_value"><span class="pre">False</span></span></em>, <em class="sig-param"><span class="n"><span class="pre">filename</span></span><span class="o"><span class="pre">=</span></span><span class="default_value"><span class="pre">None</span></span></em><span class="sig-paren">)</span><a class="headerlink" href="#Simulation.FitModel.ShowGraphs" title="Permalink to this definition">ïƒ</a></dt> <dd><p>Plot the amount of obeservable in function of time, Amplitude, Area Under the Curve, and Full Width at Half Maximum.</p> <dl class="field-list simple"> <dt class="field-odd">Parameters</dt> @@ -460,20 +320,25 @@ </dd></dl> <dl class="py method"> -<dt class="sig sig-object py" id="ssbtoolkit.simulation.fitModel.plotIterations"> -<span class="sig-name descname"><span class="pre">plotIterations</span></span><span class="sig-paren">(</span><em class="sig-param"><span class="n"><span class="pre">save</span></span><span class="o"><span class="pre">=</span></span><span class="default_value"><span class="pre">False</span></span></em>, <em class="sig-param"><span class="n"><span class="pre">filename</span></span><span class="o"><span class="pre">=</span></span><span class="default_value"><span class="pre">None</span></span></em><span class="sig-paren">)</span><a class="headerlink" href="#ssbtoolkit.simulation.fitModel.plotIterations" title="Permalink to this definition">ïƒ</a></dt> -<dd><p>Plot iterations.</p> +<dt class="sig sig-object py" id="Simulation.FitModel.UserPathwayParameters"> +<span class="sig-name descname"><span class="pre">UserPathwayParameters</span></span><span class="sig-paren">(</span><em class="sig-param"><span class="n"><span class="pre">path</span></span></em><span class="sig-paren">)</span><a class="headerlink" href="#Simulation.FitModel.UserPathwayParameters" title="Permalink to this definition">ïƒ</a></dt> +<dd><p>Import user pathway parameters.</p> +<dl class="field-list simple"> +<dt class="field-odd">Parameters</dt> +<dd class="field-odd"><p><strong>path</strong> – Required (kwarg str): directory path</p> +</dd> +</dl> </dd></dl> </dd></dl> <dl class="py class"> -<dt class="sig sig-object py" id="ssbtoolkit.simulation.inhibition"> -<em class="property"><span class="pre">class</span><span class="w"> </span></em><span class="sig-name descname"><span class="pre">inhibition</span></span><a class="headerlink" href="#ssbtoolkit.simulation.inhibition" title="Permalink to this definition">ïƒ</a></dt> +<dt class="sig sig-object py" id="Simulation.Inhibition"> +<em class="property"><span class="pre">class</span><span class="w"> </span></em><span class="sig-prename descclassname"><span class="pre">Simulation.</span></span><span class="sig-name descname"><span class="pre">Inhibition</span></span><a class="headerlink" href="#Simulation.Inhibition" title="Permalink to this definition">ïƒ</a></dt> <dd><p>Simulation of the inhibition of signaling pathways (i.e. inhibition by antagonists).</p> <dl class="py method"> -<dt class="sig sig-object py" id="ssbtoolkit.simulation.inhibition.Analysis"> -<span class="sig-name descname"><span class="pre">Analysis</span></span><span class="sig-paren">(</span><span class="sig-paren">)</span><a class="headerlink" href="#ssbtoolkit.simulation.inhibition.Analysis" title="Permalink to this definition">ïƒ</a></dt> +<dt class="sig sig-object py" id="Simulation.Inhibition.Analysis"> +<span class="sig-name descname"><span class="pre">Analysis</span></span><span class="sig-paren">(</span><span class="sig-paren">)</span><a class="headerlink" href="#Simulation.Inhibition.Analysis" title="Permalink to this definition">ïƒ</a></dt> <dd><p>This function calculates the dose-response effect.</p> <dl class="field-list simple"> <dt class="field-odd">Returns</dt> @@ -483,14 +348,8 @@ </dd></dl> <dl class="py method"> -<dt class="sig sig-object py" id="ssbtoolkit.simulation.inhibition.Curve"> -<span class="sig-name descname"><span class="pre">Curve</span></span><span class="sig-paren">(</span><em class="sig-param"><span class="n"><span class="pre">save</span></span><span class="o"><span class="pre">=</span></span><span class="default_value"><span class="pre">False</span></span></em>, <em class="sig-param"><span class="n"><span class="pre">filename</span></span><span class="o"><span class="pre">=</span></span><span class="default_value"><span class="pre">None</span></span></em><span class="sig-paren">)</span><a class="headerlink" href="#ssbtoolkit.simulation.inhibition.Curve" title="Permalink to this definition">ïƒ</a></dt> -<dd><p>Plot the dose-response curve.</p> -</dd></dl> - -<dl class="py method"> -<dt class="sig sig-object py" id="ssbtoolkit.simulation.inhibition.PathwayParameters"> -<span class="sig-name descname"><span class="pre">PathwayParameters</span></span><span class="sig-paren">(</span><span class="sig-paren">)</span><a class="headerlink" href="#ssbtoolkit.simulation.inhibition.PathwayParameters" title="Permalink to this definition">ïƒ</a></dt> +<dt class="sig sig-object py" id="Simulation.Inhibition.PathwayParameters"> +<span class="sig-name descname"><span class="pre">PathwayParameters</span></span><span class="sig-paren">(</span><span class="sig-paren">)</span><a class="headerlink" href="#Simulation.Inhibition.PathwayParameters" title="Permalink to this definition">ïƒ</a></dt> <dd><p>Display table with default pathway parameters.</p> <div class="admonition warning"> <p class="admonition-title">Warning</p> @@ -499,8 +358,8 @@ </dd></dl> <dl class="py method"> -<dt class="sig sig-object py" id="ssbtoolkit.simulation.inhibition.PathwayParametersToCSV"> -<span class="sig-name descname"><span class="pre">PathwayParametersToCSV</span></span><span class="sig-paren">(</span><em class="sig-param"><span class="n"><span class="pre">path</span></span></em><span class="sig-paren">)</span><a class="headerlink" href="#ssbtoolkit.simulation.inhibition.PathwayParametersToCSV" title="Permalink to this definition">ïƒ</a></dt> +<dt class="sig sig-object py" id="Simulation.Inhibition.PathwayParametersToCSV"> +<span class="sig-name descname"><span class="pre">PathwayParametersToCSV</span></span><span class="sig-paren">(</span><em class="sig-param"><span class="n"><span class="pre">path</span></span></em><span class="sig-paren">)</span><a class="headerlink" href="#Simulation.Inhibition.PathwayParametersToCSV" title="Permalink to this definition">ïƒ</a></dt> <dd><p>Export pathway parameters into CSV format.</p> <dl class="field-list simple"> <dt class="field-odd">Parameters</dt> @@ -510,14 +369,8 @@ </dd></dl> <dl class="py method"> -<dt class="sig sig-object py" id="ssbtoolkit.simulation.inhibition.Potency"> -<span class="sig-name descname"><span class="pre">Potency</span></span><span class="sig-paren">(</span><span class="sig-paren">)</span><a class="headerlink" href="#ssbtoolkit.simulation.inhibition.Potency" title="Permalink to this definition">ïƒ</a></dt> -<dd><p>Return the potency values as a pandas DataFrame.</p> -</dd></dl> - -<dl class="py method"> -<dt class="sig sig-object py" id="ssbtoolkit.simulation.inhibition.PotencyToCSV"> -<span class="sig-name descname"><span class="pre">PotencyToCSV</span></span><span class="sig-paren">(</span><em class="sig-param"><span class="n"><span class="pre">path</span></span></em><span class="sig-paren">)</span><a class="headerlink" href="#ssbtoolkit.simulation.inhibition.PotencyToCSV" title="Permalink to this definition">ïƒ</a></dt> +<dt class="sig sig-object py" id="Simulation.Inhibition.PotencyToCSV"> +<span class="sig-name descname"><span class="pre">PotencyToCSV</span></span><span class="sig-paren">(</span><em class="sig-param"><span class="n"><span class="pre">path</span></span></em><span class="sig-paren">)</span><a class="headerlink" href="#Simulation.Inhibition.PotencyToCSV" title="Permalink to this definition">ïƒ</a></dt> <dd><p>Exports the potency values into csv format.</p> <dl class="field-list simple"> <dt class="field-odd">Parameters</dt> @@ -527,26 +380,26 @@ </dd></dl> <dl class="py method"> -<dt class="sig sig-object py" id="ssbtoolkit.simulation.inhibition.PotencyToDict"> -<span class="sig-name descname"><span class="pre">PotencyToDict</span></span><span class="sig-paren">(</span><span class="sig-paren">)</span><a class="headerlink" href="#ssbtoolkit.simulation.inhibition.PotencyToDict" title="Permalink to this definition">ïƒ</a></dt> +<dt class="sig sig-object py" id="Simulation.Inhibition.PotencyToDict"> +<span class="sig-name descname"><span class="pre">PotencyToDict</span></span><span class="sig-paren">(</span><span class="sig-paren">)</span><a class="headerlink" href="#Simulation.Inhibition.PotencyToDict" title="Permalink to this definition">ïƒ</a></dt> <dd><p>Convert potencies into a dictionary.</p> </dd></dl> <dl class="py method"> -<dt class="sig sig-object py" id="ssbtoolkit.simulation.inhibition.Reactions"> -<span class="sig-name descname"><span class="pre">Reactions</span></span><span class="sig-paren">(</span><span class="sig-paren">)</span><a class="headerlink" href="#ssbtoolkit.simulation.inhibition.Reactions" title="Permalink to this definition">ïƒ</a></dt> +<dt class="sig sig-object py" id="Simulation.Inhibition.Reactions"> +<span class="sig-name descname"><span class="pre">Reactions</span></span><span class="sig-paren">(</span><span class="sig-paren">)</span><a class="headerlink" href="#Simulation.Inhibition.Reactions" title="Permalink to this definition">ïƒ</a></dt> <dd><p>Display pathway reactions.</p> </dd></dl> <dl class="py method"> -<dt class="sig sig-object py" id="ssbtoolkit.simulation.inhibition.Run"> -<span class="sig-name descname"><span class="pre">Run</span></span><span class="sig-paren">(</span><span class="sig-paren">)</span><a class="headerlink" href="#ssbtoolkit.simulation.inhibition.Run" title="Permalink to this definition">ïƒ</a></dt> +<dt class="sig sig-object py" id="Simulation.Inhibition.Run"> +<span class="sig-name descname"><span class="pre">Run</span></span><span class="sig-paren">(</span><span class="sig-paren">)</span><a class="headerlink" href="#Simulation.Inhibition.Run" title="Permalink to this definition">ïƒ</a></dt> <dd><p>This function runs the pathway simulation and returns the raw simulation data.</p> </dd></dl> <dl class="py method"> -<dt class="sig sig-object py" id="ssbtoolkit.simulation.inhibition.SetSimulationParameters"> -<span class="sig-name descname"><span class="pre">SetSimulationParameters</span></span><span class="sig-paren">(</span><em class="sig-param"><span class="o"><span class="pre">**</span></span><span class="n"><span class="pre">kwargs</span></span></em><span class="sig-paren">)</span><a class="headerlink" href="#ssbtoolkit.simulation.inhibition.SetSimulationParameters" title="Permalink to this definition">ïƒ</a></dt> +<dt class="sig sig-object py" id="Simulation.Inhibition.SetSimulationParameters"> +<span class="sig-name descname"><span class="pre">SetSimulationParameters</span></span><span class="sig-paren">(</span><em class="sig-param"><span class="o"><span class="pre">**</span></span><span class="n"><span class="pre">kwargs</span></span></em><span class="sig-paren">)</span><a class="headerlink" href="#Simulation.Inhibition.SetSimulationParameters" title="Permalink to this definition">ïƒ</a></dt> <dd><dl class="field-list simple"> <dt class="field-odd">Parameters</dt> <dd class="field-odd"><ul class="simple"> @@ -574,8 +427,20 @@ </dd></dl> <dl class="py method"> -<dt class="sig sig-object py" id="ssbtoolkit.simulation.inhibition.UserPathwayParameters"> -<span class="sig-name descname"><span class="pre">UserPathwayParameters</span></span><span class="sig-paren">(</span><em class="sig-param"><span class="n"><span class="pre">path</span></span></em><span class="sig-paren">)</span><a class="headerlink" href="#ssbtoolkit.simulation.inhibition.UserPathwayParameters" title="Permalink to this definition">ïƒ</a></dt> +<dt class="sig sig-object py" id="Simulation.Inhibition.ShowCurve"> +<span class="sig-name descname"><span class="pre">ShowCurve</span></span><span class="sig-paren">(</span><em class="sig-param"><span class="n"><span class="pre">save</span></span><span class="o"><span class="pre">=</span></span><span class="default_value"><span class="pre">False</span></span></em>, <em class="sig-param"><span class="n"><span class="pre">filename</span></span><span class="o"><span class="pre">=</span></span><span class="default_value"><span class="pre">None</span></span></em><span class="sig-paren">)</span><a class="headerlink" href="#Simulation.Inhibition.ShowCurve" title="Permalink to this definition">ïƒ</a></dt> +<dd><p>Plot the dose-response curve.</p> +</dd></dl> + +<dl class="py method"> +<dt class="sig sig-object py" id="Simulation.Inhibition.ShowPotency"> +<span class="sig-name descname"><span class="pre">ShowPotency</span></span><span class="sig-paren">(</span><span class="sig-paren">)</span><a class="headerlink" href="#Simulation.Inhibition.ShowPotency" title="Permalink to this definition">ïƒ</a></dt> +<dd><p>Return the potency values as a pandas DataFrame.</p> +</dd></dl> + +<dl class="py method"> +<dt class="sig sig-object py" id="Simulation.Inhibition.UserPathwayParameters"> +<span class="sig-name descname"><span class="pre">UserPathwayParameters</span></span><span class="sig-paren">(</span><em class="sig-param"><span class="n"><span class="pre">path</span></span></em><span class="sig-paren">)</span><a class="headerlink" href="#Simulation.Inhibition.UserPathwayParameters" title="Permalink to this definition">ïƒ</a></dt> <dd><p>Import user pathway parameters.</p> <dl class="field-list simple"> <dt class="field-odd">Parameters</dt> @@ -584,12 +449,163 @@ </dl> </dd></dl> +</dd></dl> + +<dl class="py data"> +<dt class="sig sig-object py" id="Simulation.pathways_path"> +<span class="sig-prename descclassname"><span class="pre">Simulation.</span></span><span class="sig-name descname"><span class="pre">pathways_path</span></span><em class="property"><span class="w"> </span><span class="p"><span class="pre">=</span></span><span class="w"> </span><span class="pre">'/home/rribeiro/Projects/SSBtoolkit/ssbtoolkit/pathways'</span></em><a class="headerlink" href="#Simulation.pathways_path" title="Permalink to this definition">ïƒ</a></dt> +<dd><p>Module to simulate the mathematical models of the signaling pathways.</p> +</dd></dl> + +<span class="target" id="module-Utils"></span><dl class="py function"> +<dt class="sig sig-object py" id="Utils.CalcOccupancy"> +<span class="sig-prename descclassname"><span class="pre">Utils.</span></span><span class="sig-name descname"><span class="pre">CalcOccupancy</span></span><span class="sig-paren">(</span><em class="sig-param"><span class="n"><span class="pre">receptor_conc</span></span></em>, <em class="sig-param"><span class="n"><span class="pre">agonist_conc</span></span></em>, <em class="sig-param"><span class="n"><span class="pre">antagonist_conc</span></span></em>, <em class="sig-param"><span class="n"><span class="pre">pkd_agonist</span></span></em>, <em class="sig-param"><span class="n"><span class="pre">pkd_antagonist</span></span></em><span class="sig-paren">)</span><a class="headerlink" href="#Utils.CalcOccupancy" title="Permalink to this definition">ïƒ</a></dt> +<dd><p>This function calculates the fraction of occupited receptors at equilibrium.</p> +<dl class="field-list simple"> +<dt class="field-odd">Parameters</dt> +<dd class="field-odd"><ul class="simple"> +<li><p><strong>receptor_conc</strong> – Required (int): concentration of the receptor</p></li> +<li><p><strong>agonists_conc</strong> – Required (int): concentration of the agonist</p></li> +<li><p><strong>antagonists_conc</strong> – Required (int): concentration of the antagonists (0 if antagonist shoul not be considered)</p></li> +<li><p><strong>pkd_agonist</strong> – Required (int): pKd of agonist</p></li> +<li><p><strong>pkd_antagonist</strong> – Required (int): pKd of antagonists (if antagonist shoul not be considered)</p></li> +</ul> +</dd> +<dt class="field-even">Return int</dt> +<dd class="field-even"><p>fraction of occupied receptors in the equilibrium</p> +</dd> +</dl> +</dd></dl> + +<dl class="py function"> +<dt class="sig sig-object py" id="Utils.GetGProtein"> +<span class="sig-prename descclassname"><span class="pre">Utils.</span></span><span class="sig-name descname"><span class="pre">GetGProtein</span></span><span class="sig-paren">(</span><em class="sig-param"><span class="n"><span class="pre">uniprotID</span></span></em><span class="sig-paren">)</span><a class="headerlink" href="#Utils.GetGProtein" title="Permalink to this definition">ïƒ</a></dt> +<dd><p>This function query the SSBtoolkit internal database to extract the G protein associated to GPCR.</p> +<div class="admonition warning"> +<p class="admonition-title">Warning</p> +<p>it just works for Human GPCRS!</p> +</div> +<dl class="field-list simple"> +<dt class="field-odd">Parameters</dt> +<dd class="field-odd"><p><strong>uniprotID</strong> – Required (str)</p> +</dd> +<dt class="field-even">Returns</dt> +<dd class="field-even"><p>(str)</p> +</dd> +</dl> +</dd></dl> + +<dl class="py function"> +<dt class="sig sig-object py" id="Utils.KineticTempScale"> +<span class="sig-prename descclassname"><span class="pre">Utils.</span></span><span class="sig-name descname"><span class="pre">KineticTempScale</span></span><span class="sig-paren">(</span><em class="sig-param"><span class="n"><span class="pre">kon</span></span></em>, <em class="sig-param"><span class="n"><span class="pre">koff</span></span></em>, <em class="sig-param"><span class="n"><span class="pre">T1</span></span></em>, <em class="sig-param"><span class="n"><span class="pre">T2</span></span></em>, <em class="sig-param"><span class="n"><span class="pre">Tu</span></span><span class="o"><span class="pre">=</span></span><span class="default_value"><span class="pre">'K'</span></span></em>, <em class="sig-param"><span class="o"><span class="pre">*</span></span><span class="n"><span class="pre">kwargs</span></span></em><span class="sig-paren">)</span><a class="headerlink" href="#Utils.KineticTempScale" title="Permalink to this definition">ïƒ</a></dt> +<dd><p>This function rescales the kinetics constants to a specific temperature.</p> +<dl class="field-list simple"> +<dt class="field-odd">Parameters</dt> +<dd class="field-odd"><ul class="simple"> +<li><p><strong>kon</strong> – Required (flt): foward kinetic constant</p></li> +<li><p><strong>koff</strong> – Required (flt): reverse kinetic constant</p></li> +<li><p><strong>T1</strong> – Required (flt): Initial temperature</p></li> +<li><p><strong>T2</strong> – Required (flt): Final temperature</p></li> +</ul> +</dd> +<dt class="field-even">Paramter Tu</dt> +<dd class="field-even"><p>Optional (kwarg str): Temperature Units (kelvin=’K’, celsius=’C’)</p> +</dd> +<dt class="field-odd">Returns</dt> +<dd class="field-odd"><p>(flt, flt)</p> +</dd> +</dl> +</dd></dl> + +<dl class="py function"> +<dt class="sig sig-object py" id="Utils.MicrogramsToNanomolar"> +<span class="sig-prename descclassname"><span class="pre">Utils.</span></span><span class="sig-name descname"><span class="pre">MicrogramsToNanomolar</span></span><span class="sig-paren">(</span><em class="sig-param"><span class="n"><span class="pre">uniprotID</span></span></em>, <em class="sig-param"><span class="n"><span class="pre">concentration</span></span></em><span class="sig-paren">)</span><a class="headerlink" href="#Utils.MicrogramsToNanomolar" title="Permalink to this definition">ïƒ</a></dt> +<dd><p>This function converts micrograms of protein in nanomolar.</p> +<dl class="field-list simple"> +<dt class="field-odd">Parameters</dt> +<dd class="field-odd"><ul class="simple"> +<li><p><strong>uniprotID</strong> – Required (str)</p></li> +<li><p><strong>concentration</strong> – Required (int): concentration od protein in micrograms</p></li> +</ul> +</dd> +<dt class="field-even">Returns</dt> +<dd class="field-even"><p>(flt) concentration of protein in nM</p> +</dd> +</dl> +<div class="admonition note"> +<p class="admonition-title">Note</p> +<p>This function will obtain the sequence of the protein from UNIPROT and calculate automatically its molecular mass</p> +</div> +</dd></dl> + +<dl class="py function"> +<dt class="sig sig-object py" id="Utils.PrintProgressBar"> +<span class="sig-prename descclassname"><span class="pre">Utils.</span></span><span class="sig-name descname"><span class="pre">PrintProgressBar</span></span><span class="sig-paren">(</span><em class="sig-param"><span class="n"><span class="pre">iteration</span></span></em>, <em class="sig-param"><span class="n"><span class="pre">total</span></span></em>, <em class="sig-param"><span class="n"><span class="pre">prefix</span></span><span class="o"><span class="pre">=</span></span><span class="default_value"><span class="pre">''</span></span></em>, <em class="sig-param"><span class="n"><span class="pre">suffix</span></span><span class="o"><span class="pre">=</span></span><span class="default_value"><span class="pre">''</span></span></em>, <em class="sig-param"><span class="n"><span class="pre">decimals</span></span><span class="o"><span class="pre">=</span></span><span class="default_value"><span class="pre">1</span></span></em>, <em class="sig-param"><span class="n"><span class="pre">length</span></span><span class="o"><span class="pre">=</span></span><span class="default_value"><span class="pre">100</span></span></em>, <em class="sig-param"><span class="n"><span class="pre">fill</span></span><span class="o"><span class="pre">=</span></span><span class="default_value"><span class="pre">'â–ˆ'</span></span></em>, <em class="sig-param"><span class="n"><span class="pre">printEnd</span></span><span class="o"><span class="pre">=</span></span><span class="default_value"><span class="pre">'\r'</span></span></em><span class="sig-paren">)</span><a class="headerlink" href="#Utils.PrintProgressBar" title="Permalink to this definition">ïƒ</a></dt> +<dd><p>Call in a loop to create terminal progress bar</p> +<dl class="field-list simple"> +<dt class="field-odd">Parameters</dt> +<dd class="field-odd"><ul class="simple"> +<li><p><strong>iteration:Required</strong> – current iteration (Int)</p></li> +<li><p><strong>total</strong> – Required: total iterations (Int)</p></li> +<li><p><strong>prefix</strong> – Optional: prefix string (Str)</p></li> +<li><p><strong>suffix</strong> – Optional: suffix string (Str)</p></li> +<li><p><strong>decimals</strong> – Optional: positive number of decimals in percent complete (Int)</p></li> +<li><p><strong>length</strong> – Optional: character length of bar (Int)</p></li> +<li><p><strong>fill</strong> – Optional: bar fill character (Str)</p></li> +<li><p><strong>printEnd</strong> – Optional: end character (Str)</p></li> +</ul> +</dd> +</dl> +</dd></dl> + +<dl class="py class"> +<dt class="sig sig-object py" id="Utils.tauRAMD"> +<em class="property"><span class="pre">class</span><span class="w"> </span></em><span class="sig-prename descclassname"><span class="pre">Utils.</span></span><span class="sig-name descname"><span class="pre">tauRAMD</span></span><a class="headerlink" href="#Utils.tauRAMD" title="Permalink to this definition">ïƒ</a></dt> +<dd><p>Implementation of the tRAMD method by Kokh et al., 2018.</p> +<dl class="py method"> +<dt class="sig sig-object py" id="Utils.tauRAMD.PlotRTDistribuitons"> +<span class="sig-name descname"><span class="pre">PlotRTDistribuitons</span></span><span class="sig-paren">(</span><em class="sig-param"><span class="n"><span class="pre">save</span></span><span class="o"><span class="pre">=</span></span><span class="default_value"><span class="pre">False</span></span></em>, <em class="sig-param"><span class="n"><span class="pre">filename</span></span><span class="o"><span class="pre">=</span></span><span class="default_value"><span class="pre">None</span></span></em><span class="sig-paren">)</span><a class="headerlink" href="#Utils.tauRAMD.PlotRTDistribuitons" title="Permalink to this definition">ïƒ</a></dt> +<dd><p>Plots the residence time distributions</p> +<dl class="field-list simple"> +<dt class="field-odd">Parameters</dt> +<dd class="field-odd"><ul class="simple"> +<li><p><strong>save</strong> – Optional (kwarg boolean): default False</p></li> +<li><p><strong>filename</strong> – Optional (kwarg str)</p></li> +</ul> +</dd> +</dl> +</dd></dl> + <dl class="py method"> -<dt class="sig sig-object py" id="ssbtoolkit.simulation.inhibition.constants"> -<span class="sig-name descname"><span class="pre">constants</span></span><span class="sig-paren">(</span><span class="sig-paren">)</span><a class="headerlink" href="#ssbtoolkit.simulation.inhibition.constants" title="Permalink to this definition">ïƒ</a></dt> -<dd><p>Returns the potency values.</p> +<dt class="sig sig-object py" id="Utils.tauRAMD.PlotRTStats"> +<span class="sig-name descname"><span class="pre">PlotRTStats</span></span><span class="sig-paren">(</span><em class="sig-param"><span class="n"><span class="pre">save</span></span><span class="o"><span class="pre">=</span></span><span class="default_value"><span class="pre">False</span></span></em>, <em class="sig-param"><span class="n"><span class="pre">filename</span></span><span class="o"><span class="pre">=</span></span><span class="default_value"><span class="pre">None</span></span></em><span class="sig-paren">)</span><a class="headerlink" href="#Utils.tauRAMD.PlotRTStats" title="Permalink to this definition">ïƒ</a></dt> +<dd><p>Plots the residence time statistics</p> +<dl class="field-list simple"> +<dt class="field-odd">Parameters</dt> +<dd class="field-odd"><ul class="simple"> +<li><p><strong>save</strong> – Optional (kwarg boolean): default False</p></li> +<li><p><strong>filename</strong> – Optional (kwarg str)</p></li> +</ul> +</dd> +</dl> </dd></dl> +<dl class="py method"> +<dt class="sig sig-object py" id="Utils.tauRAMD.Run"> +<span class="sig-name descname"><span class="pre">Run</span></span><span class="sig-paren">(</span><em class="sig-param"><span class="o"><span class="pre">**</span></span><span class="n"><span class="pre">kwargs</span></span></em><span class="sig-paren">)</span><a class="headerlink" href="#Utils.tauRAMD.Run" title="Permalink to this definition">ïƒ</a></dt> +<dd><p>Calulates the residence time of a ligand from RAMD simualtions.</p> +<dl class="field-list simple"> +<dt class="field-odd">Parameters</dt> +<dd class="field-odd"><ul class="simple"> +<li><p><strong>prefix</strong> – Required (kwarg str): directory path of .dat files</p></li> +<li><p><strong>dt</strong> – Optional (kwarg flt): MD simulations time step in ns (defaul is 2E-6)</p></li> +<li><p><strong>softwr</strong> – Optional (kwarg str): software used to perform RAMD simulations: NAMD, GROMACS (default)</p></li> +</ul> +</dd> +<dt class="field-even">Return (str)</dt> +<dd class="field-even"><p>residence time</p> +</dd> +</dl> </dd></dl> </dd></dl> diff --git a/docs/_build/html/faq.html b/docs/_build/html/faq.html index 45eff2f..33356e4 100644 --- a/docs/_build/html/faq.html +++ b/docs/_build/html/faq.html @@ -4,7 +4,7 @@ <meta charset="utf-8" /><meta name="generator" content="Docutils 0.17.1: http://docutils.sourceforge.net/" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> - <title>Frequently Asked Questions — SSBtoolkit v1 documentation</title> + <title>Frequently Asked Questions — SSBtoolkit v1.0.1 documentation</title> <link rel="stylesheet" href="_static/pygments.css" type="text/css" /> <link rel="stylesheet" href="_static/css/theme.css" type="text/css" /> <!--[if lt IE 9]> diff --git a/docs/_build/html/genindex.html b/docs/_build/html/genindex.html index dc0e12d..e3142a3 100644 --- a/docs/_build/html/genindex.html +++ b/docs/_build/html/genindex.html @@ -3,7 +3,7 @@ <head> <meta charset="utf-8" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> - <title>Index — SSBtoolkit v1 documentation</title> + <title>Index — SSBtoolkit v1.0.1 documentation</title> <link rel="stylesheet" href="_static/pygments.css" type="text/css" /> <link rel="stylesheet" href="_static/css/theme.css" type="text/css" /> <!--[if lt IE 9]> @@ -73,22 +73,29 @@ <a href="#A"><strong>A</strong></a> | <a href="#B"><strong>B</strong></a> | <a href="#C"><strong>C</strong></a> + | <a href="#F"><strong>F</strong></a> | <a href="#G"><strong>G</strong></a> + | <a href="#I"><strong>I</strong></a> | <a href="#K"><strong>K</strong></a> | <a href="#M"><strong>M</strong></a> | <a href="#P"><strong>P</strong></a> | <a href="#R"><strong>R</strong></a> | <a href="#S"><strong>S</strong></a> + | <a href="#T"><strong>T</strong></a> | <a href="#U"><strong>U</strong></a> </div> <h2 id="A">A</h2> <table style="width: 100%" class="indextable genindextable"><tr> <td style="width: 33%; vertical-align: top;"><ul> - <li><a href="apidocs.html#ssbtoolkit.simulation.activation.Analysis">Analysis() (ssbtoolkit.simulation.activation method)</a> + <li><a href="apidocs.html#Simulation.Activation">Activation (class in Simulation)</a> +</li> + </ul></td> + <td style="width: 33%; vertical-align: top;"><ul> + <li><a href="apidocs.html#Simulation.Activation.Analysis">Analysis() (Simulation.Activation method)</a> <ul> - <li><a href="apidocs.html#ssbtoolkit.simulation.inhibition.Analysis">(ssbtoolkit.simulation.inhibition method)</a> + <li><a href="apidocs.html#Simulation.Inhibition.Analysis">(Simulation.Inhibition method)</a> </li> </ul></li> </ul></td> @@ -97,43 +104,48 @@ <h2 id="B">B</h2> <table style="width: 100%" class="indextable genindextable"><tr> <td style="width: 33%; vertical-align: top;"><ul> - <li><a href="apidocs.html#ssbtoolkit.binding.bind">bind() (ssbtoolkit.binding method)</a> + <li><a href="apidocs.html#Binding.Bind">Bind() (in module Binding)</a> </li> </ul></td> <td style="width: 33%; vertical-align: top;"><ul> - <li><a href="apidocs.html#ssbtoolkit.binding">binding (class in ssbtoolkit)</a> + <li> + Binding + + <ul> + <li><a href="apidocs.html#module-Binding">module</a> </li> + </ul></li> </ul></td> </tr></table> <h2 id="C">C</h2> <table style="width: 100%" class="indextable genindextable"><tr> <td style="width: 33%; vertical-align: top;"><ul> - <li><a href="apidocs.html#ssbtoolkit.simulation.inhibition.constants">constants() (ssbtoolkit.simulation.inhibition method)</a> -</li> - <li><a href="apidocs.html#ssbtoolkit.convert">convert (class in ssbtoolkit)</a> + <li><a href="apidocs.html#Utils.CalcOccupancy">CalcOccupancy() (in module Utils)</a> </li> </ul></td> - <td style="width: 33%; vertical-align: top;"><ul> - <li><a href="apidocs.html#ssbtoolkit.simulation.activation.Curve">Curve() (ssbtoolkit.simulation.activation method)</a> +</tr></table> - <ul> - <li><a href="apidocs.html#ssbtoolkit.simulation.inhibition.Curve">(ssbtoolkit.simulation.inhibition method)</a> +<h2 id="F">F</h2> +<table style="width: 100%" class="indextable genindextable"><tr> + <td style="width: 33%; vertical-align: top;"><ul> + <li><a href="apidocs.html#Simulation.FitModel">FitModel (class in Simulation)</a> </li> - </ul></li> </ul></td> </tr></table> <h2 id="G">G</h2> <table style="width: 100%" class="indextable genindextable"><tr> <td style="width: 33%; vertical-align: top;"><ul> - <li><a href="apidocs.html#ssbtoolkit.get">get (class in ssbtoolkit)</a> + <li><a href="apidocs.html#Utils.GetGProtein">GetGProtein() (in module Utils)</a> </li> </ul></td> +</tr></table> + +<h2 id="I">I</h2> +<table style="width: 100%" class="indextable genindextable"><tr> <td style="width: 33%; vertical-align: top;"><ul> - <li><a href="apidocs.html#ssbtoolkit.get.tauRAMD">get.tauRAMD (class in ssbtoolkit)</a> -</li> - <li><a href="apidocs.html#ssbtoolkit.get.gprotein">gprotein() (ssbtoolkit.get method)</a> + <li><a href="apidocs.html#Simulation.Inhibition">Inhibition (class in Simulation)</a> </li> </ul></td> </tr></table> @@ -141,7 +153,7 @@ <h2 id="K">K</h2> <table style="width: 100%" class="indextable genindextable"><tr> <td style="width: 33%; vertical-align: top;"><ul> - <li><a href="apidocs.html#ssbtoolkit.convert.KineticTempScale">KineticTempScale() (ssbtoolkit.convert method)</a> + <li><a href="apidocs.html#Utils.KineticTempScale">KineticTempScale() (in module Utils)</a> </li> </ul></td> </tr></table> @@ -149,17 +161,17 @@ <h2 id="M">M</h2> <table style="width: 100%" class="indextable genindextable"><tr> <td style="width: 33%; vertical-align: top;"><ul> - <li><a href="apidocs.html#ssbtoolkit.binding.maxbend">maxbend() (ssbtoolkit.binding method)</a> -</li> - <li><a href="apidocs.html#ssbtoolkit.convert.microgr2nanomolar">microgr2nanomolar() (ssbtoolkit.convert method)</a> + <li><a href="apidocs.html#Utils.MicrogramsToNanomolar">MicrogramsToNanomolar() (in module Utils)</a> </li> - </ul></td> - <td style="width: 33%; vertical-align: top;"><ul> <li> module <ul> - <li><a href="apidocs.html#module-ssbtoolkit">ssbtoolkit</a> + <li><a href="apidocs.html#module-Binding">Binding</a> +</li> + <li><a href="apidocs.html#module-Simulation">Simulation</a> +</li> + <li><a href="apidocs.html#module-Utils">Utils</a> </li> </ul></li> </ul></td> @@ -168,74 +180,70 @@ <h2 id="P">P</h2> <table style="width: 100%" class="indextable genindextable"><tr> <td style="width: 33%; vertical-align: top;"><ul> - <li><a href="apidocs.html#ssbtoolkit.simulation.activation.PathwayParameters">PathwayParameters() (ssbtoolkit.simulation.activation method)</a> + <li><a href="apidocs.html#Simulation.Activation.PathwayParameters">PathwayParameters() (Simulation.Activation method)</a> <ul> - <li><a href="apidocs.html#ssbtoolkit.simulation.fitModel.PathwayParameters">(ssbtoolkit.simulation.fitModel method)</a> + <li><a href="apidocs.html#Simulation.FitModel.PathwayParameters">(Simulation.FitModel method)</a> </li> - <li><a href="apidocs.html#ssbtoolkit.simulation.inhibition.PathwayParameters">(ssbtoolkit.simulation.inhibition method)</a> + <li><a href="apidocs.html#Simulation.Inhibition.PathwayParameters">(Simulation.Inhibition method)</a> </li> </ul></li> - <li><a href="apidocs.html#ssbtoolkit.simulation.activation.PathwayParametersToCSV">PathwayParametersToCSV() (ssbtoolkit.simulation.activation method)</a> + <li><a href="apidocs.html#Simulation.Activation.PathwayParametersToCSV">PathwayParametersToCSV() (Simulation.Activation method)</a> <ul> - <li><a href="apidocs.html#ssbtoolkit.simulation.fitModel.PathwayParametersToCSV">(ssbtoolkit.simulation.fitModel method)</a> + <li><a href="apidocs.html#Simulation.FitModel.PathwayParametersToCSV">(Simulation.FitModel method)</a> </li> - <li><a href="apidocs.html#ssbtoolkit.simulation.inhibition.PathwayParametersToCSV">(ssbtoolkit.simulation.inhibition method)</a> + <li><a href="apidocs.html#Simulation.Inhibition.PathwayParametersToCSV">(Simulation.Inhibition method)</a> </li> </ul></li> - <li><a href="apidocs.html#ssbtoolkit.simulation.fitModel.plotCurves">plotCurves() (ssbtoolkit.simulation.fitModel method)</a> -</li> - <li><a href="apidocs.html#ssbtoolkit.simulation.fitModel.plotIterations">plotIterations() (ssbtoolkit.simulation.fitModel method)</a> + <li><a href="apidocs.html#Simulation.pathways_path">pathways_path (in module Simulation)</a> </li> </ul></td> <td style="width: 33%; vertical-align: top;"><ul> - <li><a href="apidocs.html#ssbtoolkit.get.tauRAMD.plotRTdistribuitons">plotRTdistribuitons() (ssbtoolkit.get.tauRAMD method)</a> + <li><a href="apidocs.html#Simulation.FitModel.PlotIterations">PlotIterations() (Simulation.FitModel method)</a> </li> - <li><a href="apidocs.html#ssbtoolkit.get.tauRAMD.plotRTstats">plotRTstats() (ssbtoolkit.get.tauRAMD method)</a> + <li><a href="apidocs.html#Utils.tauRAMD.PlotRTDistribuitons">PlotRTDistribuitons() (Utils.tauRAMD method)</a> </li> - <li><a href="apidocs.html#ssbtoolkit.simulation.activation.Potency">Potency() (ssbtoolkit.simulation.activation method)</a> - - <ul> - <li><a href="apidocs.html#ssbtoolkit.simulation.inhibition.Potency">(ssbtoolkit.simulation.inhibition method)</a> + <li><a href="apidocs.html#Utils.tauRAMD.PlotRTStats">PlotRTStats() (Utils.tauRAMD method)</a> </li> - </ul></li> - <li><a href="apidocs.html#ssbtoolkit.simulation.activation.PotencyToCSV">PotencyToCSV() (ssbtoolkit.simulation.activation method)</a> + <li><a href="apidocs.html#Simulation.Activation.PotencyToCSV">PotencyToCSV() (Simulation.Activation method)</a> <ul> - <li><a href="apidocs.html#ssbtoolkit.simulation.inhibition.PotencyToCSV">(ssbtoolkit.simulation.inhibition method)</a> + <li><a href="apidocs.html#Simulation.Inhibition.PotencyToCSV">(Simulation.Inhibition method)</a> </li> </ul></li> - <li><a href="apidocs.html#ssbtoolkit.simulation.activation.PotencyToDict">PotencyToDict() (ssbtoolkit.simulation.activation method)</a> + <li><a href="apidocs.html#Simulation.Activation.PotencyToDict">PotencyToDict() (Simulation.Activation method)</a> <ul> - <li><a href="apidocs.html#ssbtoolkit.simulation.inhibition.PotencyToDict">(ssbtoolkit.simulation.inhibition method)</a> + <li><a href="apidocs.html#Simulation.Inhibition.PotencyToDict">(Simulation.Inhibition method)</a> </li> </ul></li> + <li><a href="apidocs.html#Utils.PrintProgressBar">PrintProgressBar() (in module Utils)</a> +</li> </ul></td> </tr></table> <h2 id="R">R</h2> <table style="width: 100%" class="indextable genindextable"><tr> <td style="width: 33%; vertical-align: top;"><ul> - <li><a href="apidocs.html#ssbtoolkit.simulation.activation.Reactions">Reactions() (ssbtoolkit.simulation.activation method)</a> + <li><a href="apidocs.html#Simulation.Activation.Reactions">Reactions() (Simulation.Activation method)</a> <ul> - <li><a href="apidocs.html#ssbtoolkit.simulation.fitModel.Reactions">(ssbtoolkit.simulation.fitModel method)</a> + <li><a href="apidocs.html#Simulation.FitModel.Reactions">(Simulation.FitModel method)</a> </li> - <li><a href="apidocs.html#ssbtoolkit.simulation.inhibition.Reactions">(ssbtoolkit.simulation.inhibition method)</a> + <li><a href="apidocs.html#Simulation.Inhibition.Reactions">(Simulation.Inhibition method)</a> </li> </ul></li> </ul></td> <td style="width: 33%; vertical-align: top;"><ul> - <li><a href="apidocs.html#ssbtoolkit.get.tauRAMD.Run">Run() (ssbtoolkit.get.tauRAMD method)</a> + <li><a href="apidocs.html#Simulation.Activation.Run">Run() (Simulation.Activation method)</a> <ul> - <li><a href="apidocs.html#ssbtoolkit.simulation.activation.Run">(ssbtoolkit.simulation.activation method)</a> + <li><a href="apidocs.html#Simulation.FitModel.Run">(Simulation.FitModel method)</a> </li> - <li><a href="apidocs.html#ssbtoolkit.simulation.fitModel.Run">(ssbtoolkit.simulation.fitModel method)</a> + <li><a href="apidocs.html#Simulation.Inhibition.Run">(Simulation.Inhibition method)</a> </li> - <li><a href="apidocs.html#ssbtoolkit.simulation.inhibition.Run">(ssbtoolkit.simulation.inhibition method)</a> + <li><a href="apidocs.html#Utils.tauRAMD.Run">(Utils.tauRAMD method)</a> </li> </ul></li> </ul></td> @@ -244,45 +252,70 @@ <h2 id="S">S</h2> <table style="width: 100%" class="indextable genindextable"><tr> <td style="width: 33%; vertical-align: top;"><ul> - <li><a href="apidocs.html#ssbtoolkit.simulation.activation.SetSimulationParameters">SetSimulationParameters() (ssbtoolkit.simulation.activation method)</a> + <li><a href="apidocs.html#Simulation.Activation.SetSimulationParameters">SetSimulationParameters() (Simulation.Activation method)</a> <ul> - <li><a href="apidocs.html#ssbtoolkit.simulation.fitModel.SetSimulationParameters">(ssbtoolkit.simulation.fitModel method)</a> + <li><a href="apidocs.html#Simulation.FitModel.SetSimulationParameters">(Simulation.FitModel method)</a> </li> - <li><a href="apidocs.html#ssbtoolkit.simulation.inhibition.SetSimulationParameters">(ssbtoolkit.simulation.inhibition method)</a> + <li><a href="apidocs.html#Simulation.Inhibition.SetSimulationParameters">(Simulation.Inhibition method)</a> </li> </ul></li> - <li><a href="apidocs.html#ssbtoolkit.binding.show_curve">show_curve() (ssbtoolkit.binding method)</a> + <li><a href="apidocs.html#Binding.ShowCurve">ShowCurve() (in module Binding)</a> + + <ul> + <li><a href="apidocs.html#Simulation.Activation.ShowCurve">(Simulation.Activation method)</a> </li> - <li><a href="apidocs.html#ssbtoolkit.simulation">simulation (class in ssbtoolkit)</a> + <li><a href="apidocs.html#Simulation.Inhibition.ShowCurve">(Simulation.Inhibition method)</a> </li> + </ul></li> </ul></td> <td style="width: 33%; vertical-align: top;"><ul> - <li><a href="apidocs.html#ssbtoolkit.simulation.activation">simulation.activation (class in ssbtoolkit)</a> + <li><a href="apidocs.html#Simulation.FitModel.ShowGraphs">ShowGraphs() (Simulation.FitModel method)</a> </li> - <li><a href="apidocs.html#ssbtoolkit.simulation.fitModel">simulation.fitModel (class in ssbtoolkit)</a> -</li> - <li><a href="apidocs.html#ssbtoolkit.simulation.inhibition">simulation.inhibition (class in ssbtoolkit)</a> + <li><a href="apidocs.html#Simulation.Activation.ShowPotency">ShowPotency() (Simulation.Activation method)</a> + + <ul> + <li><a href="apidocs.html#Simulation.Inhibition.ShowPotency">(Simulation.Inhibition method)</a> </li> + </ul></li> <li> - ssbtoolkit + Simulation <ul> - <li><a href="apidocs.html#module-ssbtoolkit">module</a> + <li><a href="apidocs.html#module-Simulation">module</a> </li> </ul></li> + <li><a href="apidocs.html#Binding.SubMaxConcentration">SubMaxConcentration() (in module Binding)</a> +</li> + </ul></td> +</tr></table> + +<h2 id="T">T</h2> +<table style="width: 100%" class="indextable genindextable"><tr> + <td style="width: 33%; vertical-align: top;"><ul> + <li><a href="apidocs.html#Utils.tauRAMD">tauRAMD (class in Utils)</a> +</li> </ul></td> </tr></table> <h2 id="U">U</h2> <table style="width: 100%" class="indextable genindextable"><tr> <td style="width: 33%; vertical-align: top;"><ul> - <li><a href="apidocs.html#ssbtoolkit.simulation.activation.UserPathwayParameters">UserPathwayParameters() (ssbtoolkit.simulation.activation method)</a> + <li><a href="apidocs.html#Simulation.Activation.UserPathwayParameters">UserPathwayParameters() (Simulation.Activation method)</a> <ul> - <li><a href="apidocs.html#ssbtoolkit.simulation.fitModel.UserPathwayParameters">(ssbtoolkit.simulation.fitModel method)</a> + <li><a href="apidocs.html#Simulation.FitModel.UserPathwayParameters">(Simulation.FitModel method)</a> +</li> + <li><a href="apidocs.html#Simulation.Inhibition.UserPathwayParameters">(Simulation.Inhibition method)</a> </li> - <li><a href="apidocs.html#ssbtoolkit.simulation.inhibition.UserPathwayParameters">(ssbtoolkit.simulation.inhibition method)</a> + </ul></li> + </ul></td> + <td style="width: 33%; vertical-align: top;"><ul> + <li> + Utils + + <ul> + <li><a href="apidocs.html#module-Utils">module</a> </li> </ul></li> </ul></td> diff --git a/docs/_build/html/index.html b/docs/_build/html/index.html index 1f903ed..fc6512e 100644 --- a/docs/_build/html/index.html +++ b/docs/_build/html/index.html @@ -4,7 +4,7 @@ <meta charset="utf-8" /><meta name="generator" content="Docutils 0.17.1: http://docutils.sourceforge.net/" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> - <title>Welcome to SSBtoolkit’s documentation! — SSBtoolkit v1 documentation</title> + <title>Welcome to SSBtoolkit’s documentation! — SSBtoolkit v1.0.1 documentation</title> <link rel="stylesheet" href="_static/pygments.css" type="text/css" /> <link rel="stylesheet" href="_static/css/theme.css" type="text/css" /> <!--[if lt IE 9]> @@ -102,6 +102,40 @@ protocols.</p> </li> </ul> </div> +<p></p> +<p></p> +</section> +<section id="availability-and-license"> +<h1>Availability and License<a class="headerlink" href="#availability-and-license" title="Permalink to this headline">ïƒ</a></h1> +<p>The source code is freely available at <a class="reference external" href="https://github.com/rribeiro-sci/SSBtoolkit">https://github.com/rribeiro-sci/SSBtoolkit</a> under +the Apache 2.0 license. Tutorial notebooks containing minimal working examples can be found at <a class="reference external" href="https://github.com/rribeiro-sci/SSBtoolkit">https://github.com/rribeiro-sci/SSBtoolkit</a>.</p> +<p></p> +<p></p> +</section> +<section id="developed-by"> +<h1>Developed by<a class="headerlink" href="#developed-by" title="Permalink to this headline">ïƒ</a></h1> +<a class="reference internal image-reference" href="https://res.cloudinary.com/djz27k5hg/image/upload/v1637333672/logos/Juelich_logo_100px_ecv2hy.png"><img alt="https://res.cloudinary.com/djz27k5hg/image/upload/v1637333672/logos/Juelich_logo_100px_ecv2hy.png" src="https://res.cloudinary.com/djz27k5hg/image/upload/v1637333672/logos/Juelich_logo_100px_ecv2hy.png" style="width: 200px;" /></a> +<p></p> +<p></p> +</section> +<section id="funded-by"> +<h1>Funded by<a class="headerlink" href="#funded-by" title="Permalink to this headline">ïƒ</a></h1> +<a class="reference internal image-reference" href="https://res.cloudinary.com/djz27k5hg/image/upload/v1637657234/logos/HBP_horizontal_logo_qtcyzn.png"><img alt="Alternative text" src="https://res.cloudinary.com/djz27k5hg/image/upload/v1637657234/logos/HBP_horizontal_logo_qtcyzn.png" style="width: 200px;" /></a> +</section> +<section id="citation-doi-for-citing-ssbtoolkit"> +<h1>Citation <a class="reference external" href="https://github.com/rribeiro-sci/SSBtoolkit"><img alt="DOI for Citing ssbtoolkit" src="https://img.shields.io/badge/DOI-10.1016%2Fj.bpj.2015.08.015-blue.svg" /></a><a class="headerlink" href="#citation-doi-for-citing-ssbtoolkit" title="Permalink to this headline">ïƒ</a></h1> +<p>pyGOMoDo is research software. If you make use of pyGOMoDo in scientific +publications, please cite it. The BibTeX reference is</p> +<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="nd">@article</span><span class="p">{</span><span class="n">ribeiro_ssb_2022</span><span class="p">,</span> + <span class="n">title</span><span class="o">=</span><span class="p">{{</span><span class="n">SSB</span><span class="p">}</span> <span class="n">toolkit</span><span class="p">:</span> <span class="kn">from</span> <span class="nn">molecular</span> <span class="n">structure</span> <span class="n">to</span> <span class="n">subcellular</span> <span class="n">signaling</span> <span class="n">pathways</span><span class="o">.</span><span class="p">},</span> + <span class="n">author</span><span class="o">=</span><span class="p">{</span><span class="n">Ribeiro</span><span class="p">,</span> <span class="n">Rui</span> <span class="n">Pedro</span> <span class="ow">and</span> <span class="n">Gossen</span><span class="p">,</span> <span class="n">Jonas</span> <span class="ow">and</span> <span class="n">Rossetti</span><span class="p">,</span> <span class="n">Giulia</span> <span class="ow">and</span> <span class="n">Giorgetti</span><span class="p">,</span> <span class="n">Alejandro</span><span class="p">},</span> + <span class="n">publisher</span><span class="o">=</span><span class="p">{</span><span class="n">bioRxiv</span><span class="p">},</span> + <span class="n">url</span><span class="o">=</span><span class="p">{</span><span class="n">https</span><span class="p">:</span><span class="o">//</span><span class="n">www</span><span class="o">.</span><span class="n">biorxiv</span><span class="o">.</span><span class="n">org</span><span class="o">/</span><span class="n">content</span><span class="o">/</span><span class="mf">10.1101</span><span class="o">/</span><span class="mf">2022.11.08.515595</span><span class="n">v1</span><span class="p">},</span> + <span class="n">doi</span><span class="o">=</span><span class="p">{</span><span class="mf">10.1101</span><span class="o">/</span><span class="mf">2022.11.08.515595</span><span class="p">},</span> + <span class="n">year</span><span class="o">=</span><span class="p">{</span><span class="mi">2022</span><span class="p">}</span> +<span class="p">}</span> +</pre></div> +</div> </section> <section id="indices-and-tables"> <h1>Indices and tables<a class="headerlink" href="#indices-and-tables" title="Permalink to this headline">ïƒ</a></h1> diff --git a/docs/_build/html/introduction.html b/docs/_build/html/introduction.html index b11de8e..64f5e97 100644 --- a/docs/_build/html/introduction.html +++ b/docs/_build/html/introduction.html @@ -4,7 +4,7 @@ <meta charset="utf-8" /><meta name="generator" content="Docutils 0.17.1: http://docutils.sourceforge.net/" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> - <title>Introduction to Structure Systems Biology — SSBtoolkit v1 documentation</title> + <title>Introduction to Structure Systems Biology — SSBtoolkit v1.0.1 documentation</title> <link rel="stylesheet" href="_static/pygments.css" type="text/css" /> <link rel="stylesheet" href="_static/css/theme.css" type="text/css" /> <!--[if lt IE 9]> diff --git a/docs/_build/html/objects.inv b/docs/_build/html/objects.inv index 0c21759fdd3c5c2c947181620ffc433818551dc0..a3b541ea75cebc5e5b3e2462f8982f9693db5817 100644 GIT binary patch delta 653 zcmV;80&@N21^NY$d4HBmZrd;vMdw}xi*{<Gon}(_gBV8PR<)dFSUgjVMT8<1@-c~K z-A1?9m6Q@~iwX=|pNuS#_gsE-`OF!Y373147dM$otYG9bft)9bnn{Y8_~={5pU8ML z^V&)ITLKSOQshdKFHI!W#;JOlTm8&h19rED*SV6ENz_ouuYaI1puP&pOPo~cE z`9x9y##|mCASP1@qA=W4gECB2(w>a~(-$Zr(BM#}-^XOD=JBsyP70Z%#c+PXDfThg zFsMFnuYK59*$0am)*ZvXL?h*W;KK&?!S4*y{o{1Yajg<2+UEbLp9~*zXnt!Sp>XAR z=$4=p-#vUa+J7N>CunH!Y9p7NrG`B1!48YfN~V0ryA;lv=w3Jj_C2ONG<cy?pTc>L z?^HMggYMZ4;ST8yu%Y=4tvVOZH*nX&8Q{ZCK<-KNyL>3A1Q5<KcgTflbup=wlQ{_b zgHoHrVO^K5J|0;zTt?dM1efiAZ(K6ubjz|lrXW~5YJbhCR(rQo?!0^lsWK%%w?iY% zB}OI9jG48i5bS2UUR*3O4Q2ILu-PuSLCtrT<JE(44v!JA6L$3+!A@Bu29q1tPR34A zHdOuOJ_RJM7-Gsu9M4cG@yPM(=wQ;I0$(zXR%D^5&D=e?isa}4o<g;I^~zh#o~%=e zf=taLOMl2u%S=@?PKbb=y8{G}oAw24&!oGqv)3kjZ1tnA%EwVHJIpFj+%rK{#t5ol z;s5^pH6#hw^}(5-cE`l0rQub#9|sMTr7vptVzt}a!XV2GnQ<l5z8JZ4cV-EBF+O&9 n!?i!OelbDs6)Y-T6l9Y&3^Yxfpl|a`?E53wvj)+B1<TjJw5dbK delta 630 zcmV-+0*U?l1>^;gd4HF|Zrd;nhVOm~4cj%wwqq|t+F(F11jyoSJr1(W+CpW?mFTv< z?KSp#dy*-3;>c*$+OkfGCH@~to1!FRQdCOZb0k%BRjRBOKt2*$@k}uj7g&n--RR;2 zSwyBck5eu)F7tzo32F@aG*n$SZ+}}?fQ8CNic0#Nf``r&>VN4MtMwy{mNF$DLF3r8 zj^^<<E&(~)Ls=z^ipl=sk~6Jx{aI3}N+qZsO~_$%wAM7vE3FW?oWhTh*1P3qHO0?S z67Om+S8*j2?)DissCim*RMO1g7^FB}Z+KY?id@OD8J`s~Wyl}ri{quFqA}bjY8P`e zsr)Z9wiJse+J9`Rrj`QQgy!wlP4aUt)RwhmO&ChRfc9#|8O~(w;P5bI4g7~QVKrVR zB!Edj5v(G97|=ng?2h9`WgtA(-boq<4J2T(_sCh*aHjaxLaz^42|Y7-9kl(w?Sb<# zYa0ype(zisJmsGI!dX0PUvMzq`+~y=@}*wmc`p?g>VLm4yrcvS$bVmOShG5T%kk?4 z4k2JSaHzrhfy1;%ZhIV*Z=DBes%j(#vnxAJ7S`f0(9V<Ff+Dd?5DOqla((poB|1U| zDu+v!+cjA!R+k{LjVrpN53aR+>@2@(c&M$FXvossLq@*WVA?O;O9V~rKIe#R*ssWP zkoH;x&u>HRX|(gYKKzZ&X2+-;i(3#(l|WEAiU0lm$B@j;>z#$5wim~?I)|+D_{phb zdElb954E<hwlN5$A=g|9l{b;SiC##dY8I!74!G&Av0XCG>>ILeV@p8})dr|$#ZcId Qb(*A`-_jhSe|9jHe2M=!D*ylh diff --git a/docs/_build/html/py-modindex.html b/docs/_build/html/py-modindex.html index 782ba59..2b5427b 100644 --- a/docs/_build/html/py-modindex.html +++ b/docs/_build/html/py-modindex.html @@ -3,7 +3,7 @@ <head> <meta charset="utf-8" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> - <title>Python Module Index — SSBtoolkit v1 documentation</title> + <title>Python Module Index — SSBtoolkit v1.0.1 documentation</title> <link rel="stylesheet" href="_static/pygments.css" type="text/css" /> <link rel="stylesheet" href="_static/css/theme.css" type="text/css" /> <!--[if lt IE 9]> @@ -77,17 +77,35 @@ <h1>Python Module Index</h1> <div class="modindex-jumpbox"> - <a href="#cap-s"><strong>s</strong></a> + <a href="#cap-b"><strong>b</strong></a> | + <a href="#cap-s"><strong>s</strong></a> | + <a href="#cap-u"><strong>u</strong></a> </div> <table class="indextable modindextable"> <tr class="pcap"><td></td><td> </td><td></td></tr> + <tr class="cap" id="cap-b"><td></td><td> + <strong>b</strong></td><td></td></tr> + <tr> + <td></td> + <td> + <a href="apidocs.html#module-Binding"><code class="xref">Binding</code></a></td><td> + <em></em></td></tr> + <tr class="pcap"><td></td><td> </td><td></td></tr> <tr class="cap" id="cap-s"><td></td><td> <strong>s</strong></td><td></td></tr> <tr> <td></td> <td> - <a href="apidocs.html#module-ssbtoolkit"><code class="xref">ssbtoolkit</code></a></td><td> + <a href="apidocs.html#module-Simulation"><code class="xref">Simulation</code></a></td><td> + <em></em></td></tr> + <tr class="pcap"><td></td><td> </td><td></td></tr> + <tr class="cap" id="cap-u"><td></td><td> + <strong>u</strong></td><td></td></tr> + <tr> + <td></td> + <td> + <a href="apidocs.html#module-Utils"><code class="xref">Utils</code></a></td><td> <em></em></td></tr> </table> diff --git a/docs/_build/html/search.html b/docs/_build/html/search.html index 311d523..29084b6 100644 --- a/docs/_build/html/search.html +++ b/docs/_build/html/search.html @@ -3,7 +3,7 @@ <head> <meta charset="utf-8" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> - <title>Search — SSBtoolkit v1 documentation</title> + <title>Search — SSBtoolkit v1.0.1 documentation</title> <link rel="stylesheet" href="_static/pygments.css" type="text/css" /> <link rel="stylesheet" href="_static/css/theme.css" type="text/css" /> diff --git a/docs/_build/html/searchindex.js b/docs/_build/html/searchindex.js index 95ef7fd..3276789 100644 --- a/docs/_build/html/searchindex.js +++ b/docs/_build/html/searchindex.js @@ -1 +1 @@ -Search.setIndex({docnames:["about","apidocs","faq","index","introduction"],envversion:{"sphinx.domains.c":2,"sphinx.domains.changeset":1,"sphinx.domains.citation":1,"sphinx.domains.cpp":4,"sphinx.domains.index":1,"sphinx.domains.javascript":2,"sphinx.domains.math":2,"sphinx.domains.python":3,"sphinx.domains.rst":2,"sphinx.domains.std":2,"sphinx.ext.intersphinx":1,sphinx:56},filenames:["about.rst","apidocs.rst","faq.rst","index.rst","introduction.rst"],objects:{"":[[1,0,0,"-","ssbtoolkit"]],"ssbtoolkit.binding":[[1,2,1,"","bind"],[1,2,1,"","maxbend"],[1,2,1,"","show_curve"]],"ssbtoolkit.convert":[[1,2,1,"","KineticTempScale"],[1,2,1,"","microgr2nanomolar"]],"ssbtoolkit.get":[[1,2,1,"","gprotein"],[1,1,1,"","tauRAMD"]],"ssbtoolkit.get.tauRAMD":[[1,2,1,"","Run"],[1,2,1,"","plotRTdistribuitons"],[1,2,1,"","plotRTstats"]],"ssbtoolkit.simulation":[[1,1,1,"","activation"],[1,1,1,"","fitModel"],[1,1,1,"","inhibition"]],"ssbtoolkit.simulation.activation":[[1,2,1,"","Analysis"],[1,2,1,"","Curve"],[1,2,1,"","PathwayParameters"],[1,2,1,"","PathwayParametersToCSV"],[1,2,1,"","Potency"],[1,2,1,"","PotencyToCSV"],[1,2,1,"","PotencyToDict"],[1,2,1,"","Reactions"],[1,2,1,"","Run"],[1,2,1,"","SetSimulationParameters"],[1,2,1,"","UserPathwayParameters"]],"ssbtoolkit.simulation.fitModel":[[1,2,1,"","PathwayParameters"],[1,2,1,"","PathwayParametersToCSV"],[1,2,1,"","Reactions"],[1,2,1,"","Run"],[1,2,1,"","SetSimulationParameters"],[1,2,1,"","UserPathwayParameters"],[1,2,1,"","plotCurves"],[1,2,1,"","plotIterations"]],"ssbtoolkit.simulation.inhibition":[[1,2,1,"","Analysis"],[1,2,1,"","Curve"],[1,2,1,"","PathwayParameters"],[1,2,1,"","PathwayParametersToCSV"],[1,2,1,"","Potency"],[1,2,1,"","PotencyToCSV"],[1,2,1,"","PotencyToDict"],[1,2,1,"","Reactions"],[1,2,1,"","Run"],[1,2,1,"","SetSimulationParameters"],[1,2,1,"","UserPathwayParameters"],[1,2,1,"","constants"]],ssbtoolkit:[[1,1,1,"","binding"],[1,1,1,"","convert"],[1,1,1,"","get"],[1,1,1,"","simulation"]]},objnames:{"0":["py","module","Python module"],"1":["py","class","Python class"],"2":["py","method","Python method"]},objtypes:{"0":"py:module","1":"py:class","2":"py:method"},terms:{"0":0,"1":0,"100":0,"11":0,"12":0,"13":0,"1978":0,"1999":4,"2":0,"20":0,"2002":0,"2003":[0,1],"2007":[0,4],"2009":[0,4],"2010":4,"2011":0,"2012":0,"2013":[0,4],"2014":4,"2015":[0,4],"2016":4,"2017":4,"2018":[1,4],"2019":[0,4],"2020":[0,4],"2e":1,"3":0,"3rd":0,"4":[0,1],"5":0,"50":0,"5th":0,"6":[0,1],"7":0,"80":0,"\u03b1":0,"boolean":1,"class":[0,1,3],"default":[0,1,2],"export":1,"final":[0,1],"function":[0,1,4],"import":[1,4],"int":1,"new":4,"return":1,"short":4,"while":[0,4],A:[0,3,4],And:4,As:[0,4],By:0,For:[0,4],If:[0,2],In:[0,4],It:1,No:2,Such:[0,4],That:0,The:[0,1,3,4],Then:0,To:0,about:4,abov:0,absenc:0,access:0,accord:[0,1],accur:4,accuraci:0,achiev:0,across:4,act:0,action:[0,4],activ:[0,1,4],actual:4,ad:0,adapt:0,adjac:0,advoc:4,affect:4,affin:[1,3,4],after:0,against:0,agonist:[0,1],agonist_affin:1,agonist_submaximal_conc:1,al:[0,1,4],all:[0,1,4],allow:[0,4],alreadi:4,also:[0,4],altern:4,although:0,alv:4,am:2,amount:[0,1],amplitud:1,an:[0,1,3,4],analysi:[0,1,3],androulaki:4,ani:[0,3],anoth:4,antagonist:[0,1],antagonists_affin:1,antagonists_conc_rang:1,api:3,appli:[0,1,4],approach:[0,4],ar:[0,3,4],area:1,arithmet:0,arrai:[0,1],ask:3,assai:0,assess:0,assist:4,associ:[0,1,4],assum:[0,4],assumpt:[0,4],automat:1,avail:[0,4],b:0,basal:0,base:[0,4],baselin:0,becaus:[0,4],becom:[0,4],been:[0,4],being:[0,3,4],bend:[0,1],better:4,between:[0,4],bind:[1,2,3,4],binding_kinet:[1,2],biochem:[0,4],bioinformat:[0,3],biolog:[0,4],biologi:[0,3],biomed:4,biomodel:4,biopython:0,birch:4,bound:0,bridg:4,bring:[0,3,4],bruce:4,built:[0,3],c:[0,1],calcul:[0,1,2,4],call:0,calul:1,camp:0,campaign:0,can:[0,4],capelli:4,carefulli:1,cascad:4,cat:4,catalysi:4,catalyz:[0,4],cell:[0,4],cellular:[0,4],celsiu:1,central:4,certain:0,chang:[0,2,4],character:[0,4],chemic:0,choic:[0,3],chose:0,chosen:0,claim:4,clark:4,classic:[0,3],classif:0,classifi:0,close:0,code:[0,3],colab:1,come:0,commonli:[0,4],commun:[0,3],compar:0,comparison:[0,4],compet:0,competit:0,complementari:0,complex:[0,4],comput:[0,3],computation:[0,4],concentr:[0,1,4],concept:[0,4],conceptu:0,concurr:4,condit:[0,4],conform:0,consid:[0,2],consist:4,constant:[0,1,4],constitut:[0,4],contain:0,contribut:4,convert:1,convolut:4,core:0,correspond:0,corrrespond:0,coupl:0,critic:4,csv:1,curv:[0,1],d:0,d_:0,dat:1,data:[0,1,4],databas:[0,1,4],datafram:1,de:4,deal:0,decad:4,decemb:0,decreas:0,decrement:1,decrementor:1,deduc:0,deep:4,defaul:1,defin:0,definit:0,delean:0,dell:4,demonstr:0,depend:[0,4],deploy:0,deriv:[0,4],describ:[0,4],descript:0,design:0,despit:4,detail:0,determin:4,develop:[0,1,3,4],deward:4,dict:1,dictionari:1,didn:0,differ:[0,4],differenti:[0,4],dilut:0,dimens:4,dimension:4,directli:2,directori:1,disciplin:4,discoveri:4,discret:0,diseas:4,displac:0,displai:1,dissoci:[0,4],distribut:[1,4],divid:0,doe:2,doen:1,doesn:[0,3],dopaminerg:4,dose:[0,1],downstream:4,drug:[3,4],drug_receptor:1,drugreceptor:4,dt:1,due:0,duran:4,dure:0,dynam:4,e:[0,1],each:[0,1,4],easi:0,easier:0,easili:[0,3,4],ec_:0,ed:0,effect:[0,1,4],effector:0,efficaci:0,either:0,element:4,encod:4,end:0,endogen:0,enough:4,environ:[0,3],enzym:4,epiderm:4,eq:0,equal:0,equat:[0,4],equilibrium:[0,1,4],error:4,essenti:0,estim:[0,4],et:[0,1,4],evalu:4,even:4,event:[0,4],exist:[0,4],experi:0,experiment:[0,1,4],expert:[0,3],explicit:4,explor:[0,3],expratio:1,express:0,extens:[0,3],extract:1,fact:[0,4],factor:[0,4],fals:[1,2],famili:0,fast:4,feed:4,field:4,fig:0,figur:0,file:[0,1],filenam:1,find:0,first:[0,3,4],fit:[0,1],fitmodel:1,fix:0,flt:1,focu:4,follow:0,format:1,found:4,four:0,foward:1,fraction:[0,1],framework:4,free:[0,3],frequent:3,friendli:[0,3],frigola:4,from:[0,1,4],full:1,fundament:4,g:[0,1,4],g_:0,gener:[3,4],geometr:0,get:1,gi:1,give:0,given:[0,3],glont:4,goe:0,golan:0,googl:1,govern:4,gpcr:[0,1,3],gpcrdb:0,gprotein:1,gq:[0,1],grant:0,grcr:0,green:0,gromac:1,grow:4,growth:4,gs:1,ha:[0,4],half:1,halfwai:0,hallucinogen:4,hamper:4,have:[0,4],help:0,helper:1,henc:0,high:4,higher:0,hormon:0,how:[0,4],howev:[0,4],human:[0,1],hyperbol:0,i:[0,1,2],ic_:0,idea:4,ideal:4,identif:4,impact:0,implement:[0,1],impli:0,includ:[0,4],incomplet:4,incorpor:0,increas:0,increment:1,incrementor:1,index:3,indic:4,individu:0,infer:0,inform:[0,1,3,4],inhibit:[0,1],initi:[0,1,4],input:[0,3],insid:4,instanc:[0,1,3,4],instead:[0,3],integr:[0,4],intens:4,interact:[0,3,4],intern:1,internet:4,interplai:4,intric:[0,4],introduct:3,invergo:4,ip_3:0,iter:1,its:[0,1,4],j:[0,4],jupyt:[0,3],just:[0,1,3],k:1,k_:[0,4],k_d:0,k_m:4,kd:[0,2],kelvin:1,kholodenko:4,kinas:[0,4],kinet:[1,4],kinetictempscal:1,known:0,koff:1,kokh:1,kon:1,krzyzanski:4,kwarg:1,l2:0,l:0,l_init:2,label:0,lack:4,last:0,latter:0,law:0,learn:[0,4],less:0,librari:[0,1],lig_conc_rang:1,ligand:[0,1,2,3,4],light:4,like:[0,4],line:0,linear:0,list:1,literatur:[0,4],liu:4,logarithm:0,logist:0,lowest:0,lr:[0,2],m:0,machin:4,macromolecular:4,made:0,magnitud:0,mai:[0,4],mainli:0,make:[0,3,4],malik:4,mani:[0,4],mass:[0,1],mathemat:[0,1,4],mathod:1,matplotlib:0,matter:[0,4],max:4,maxbend:1,maxim:0,maximum:[0,1],maxit:1,mccrai:0,md:[1,4],me:1,mead:1,mean:4,measur:[0,1,4],mechan:4,mediat:4,medicin:4,messeng:0,metabol:0,metabolit:0,metadynam:4,method:[0,1,3,4],microgr2nanomolar:1,microgram:1,might:0,mimic:0,minim:1,minimum:0,miss:4,model:[0,1,3,4],modifi:[0,1],modul:[0,3],modular:0,molar:0,molecular:[0,1,4],more:[0,4],moreov:0,most:4,must:[0,1],myriad:0,n:4,nair:[0,4],namd:1,name:1,nanomolar:1,natur:0,need:[0,4],nelder:1,network:4,neubig:0,neural:4,neurotransmitt:0,neve:0,next:4,nm:1,non:[0,3],none:1,normal:[0,4],notebook:[0,3],novel:[0,3],nowadai:[0,3],ns:1,nstep:1,number:[1,4],numer:[0,4],numpi:0,nune:4,o:0,obeserv:1,observ:[0,1],obtain:[0,1,2],occup:[0,4],occupi:0,occupit:1,occur:[0,4],od:[1,4],off:4,one:[0,4],onli:[0,4],open:[0,3],option:1,orco:4,order:[0,1,4],ordinari:4,other:[0,2,4],out:0,over:[0,4],overcom:4,oxtr:1,page:3,panda:[0,1],paradigm:4,paramet:[0,1,2,4],paramt:1,part:[0,4],past:4,path:1,pathai:1,pathophysiolog:4,pathwai:[1,2,3,4],pathway_paramet:1,pathwayparamet:1,pathwayparameterstocsv:1,patrick:0,percentag:0,perform:[0,1],person:4,pfleger:0,pharmacodynam:[0,3],pharmacolog:4,phosphoryl:[0,4],phototransduct:4,physic:4,pipelin:[0,3,4],pkd:1,plai:[0,4],plateau:0,pleas:1,plot:[0,1],plotcurv:1,plotiter:1,plotrtdistribuiton:1,plotrtstat:1,point:[0,1],portion:0,possibl:0,potenc:[0,1],potencytocsv:1,potencytodict:1,potent:0,power:[0,4],pre:0,predict:[0,3,4],prefer:[0,3],prefix:1,presenc:0,present:0,previou:0,process:4,processed_data:1,produc:0,progress:0,promis:4,prone:4,proof:4,proport:[0,2],propos:[0,4],protein:[0,1,4],protocol:[0,3],provid:4,pujol:4,pysb:0,python:0,q:0,qgrid:1,quantif:4,quantit:[0,4],quantiti:4,queri:1,question:3,r:0,r_:0,radioact:0,radioligand:0,ragoza:4,ramd:1,ramdom:1,rang:[0,1],ratio:1,raw:1,reach:0,reaction:[0,1,2,4],reactom:0,readabl:0,reason:[0,4],recent:4,receptor:[1,2,3,4],receptor_conc:1,red:0,reduc:0,refer:0,regress:0,rel:4,relat:4,relationship:[0,4],relev:4,reli:4,render:0,rendit:4,replic:[0,3],repositori:4,repres:0,reproduc:1,repsons:0,requir:1,rerun:[0,3],rescal:1,research:4,resid:1,resolut:4,resolv:0,respect:0,respons:[0,1,4],result:[0,2,4],retriv:1,reusabl:0,revers:1,reward:4,rhodopsin:4,right:0,rise:4,role:4,rule:[0,2],run:1,s:0,sai:0,same:[0,1,2,4],satur:0,save:1,scale:[1,4],scenario:0,scheme:0,scientif:[0,3],scikit:0,scipi:0,screen:0,search:3,sebaugh:[0,1],second:[0,1,4],see:[0,1],seed:1,seed_decrementor:1,seed_incrementor:1,select:0,semi:0,sens:4,sequenc:1,seri:[0,4],serotonin:4,serv:0,set:4,setsimulationparamet:1,setup:1,sever:0,shape:[0,1],share:[0,3],sheriff:4,shift:0,should:[0,4],show_curv:1,sigmoid:[0,1],signal:[1,3,4],signaltransduct:0,similar:4,simualt:1,simul:[1,2,3,4],sinc:[0,4],site:0,slope:0,so:[0,2,4],softwar:1,softwr:1,solv:4,sourc:[0,3],space:0,spare:0,spatial:4,speci:[0,1,4],special:4,specif:[0,1,4],ssb:[0,3],ssbtoolkit:1,start:0,state:4,statement:0,statist:1,steadi:4,steep:0,stein:[0,4],step:[1,4],still:4,store:4,str:1,straight:0,strength:4,strong:0,stronger:0,structur:[0,3],studi:[0,4],subcellular:4,subfamili:0,submax_concentr:1,submaxim:[0,1],subsequ:4,subunit:0,sue:2,suitabl:0,system:[0,3],systemat:0,t1:1,t2:1,t:[0,1,3],tabl:1,taken:[0,4],target:[0,1,4],target_paramet:1,tauramd:1,temperatur:1,tempor:4,term:0,than:0,thank:4,thei:4,them:[0,3,4],theori:[0,4],therebi:4,therefor:[0,4],thesi:0,thi:[0,1,4],think:4,those:4,though:0,three:[0,4],through:0,time:[0,1,4],tissu:0,todai:4,togeth:[0,3],tool:[0,1,3],toolkit:[0,3],total:0,toward:4,tramd:1,transduct:4,transfer:4,tremend:4,truth:4,ttotal:1,tu:1,tumor:4,tutori:1,typic:[0,4],umbrella:0,unbound:0,under:[0,1,4],underneath:4,understand:4,undoubtedli:4,uniprot:1,uniprotid:1,uniqu:0,unit:1,unk:0,unknown:0,unlabel:0,unpreced:4,unquestion:4,untest:[0,3],up:[0,4],upper:0,us:[0,1,2,3,4],user:[0,1,3],userpathwayparamet:1,v_:4,valid:0,valu:[0,1,2,4],variabl:4,variat:0,varieti:4,vein:0,version:0,versu:0,virtual:0,vivo:4,wa:[0,1,3],wai:[0,4],wang:4,wash:0,we:0,well:0,were:0,what:3,when:[0,2],where:[0,4],wherea:0,which:0,whole:4,why:[0,2],wide:0,width:1,without:0,word:[0,4],work:1,x:0,xie:4,y:0,year:4,zero:0},titles:["What is the SSBtoolkit?","API Documentation","Frequently Asked Questions","Welcome to SSBtoolkit\u2019s documentation!","Introduction to Structure Systems Biology"],titleterms:{affin:0,api:1,ask:2,bind:0,biologi:4,content:3,document:[1,3],drug:0,frequent:2,gener:2,indic:3,introduct:4,pathwai:0,question:2,receptor:0,s:3,signal:0,simul:0,ssbtoolkit:[0,3],structur:4,system:4,tabl:3,welcom:3,what:0}}) \ No newline at end of file +Search.setIndex({docnames:["about","apidocs","faq","index","introduction"],envversion:{"sphinx.domains.c":2,"sphinx.domains.changeset":1,"sphinx.domains.citation":1,"sphinx.domains.cpp":4,"sphinx.domains.index":1,"sphinx.domains.javascript":2,"sphinx.domains.math":2,"sphinx.domains.python":3,"sphinx.domains.rst":2,"sphinx.domains.std":2,"sphinx.ext.intersphinx":1,sphinx:56},filenames:["about.rst","apidocs.rst","faq.rst","index.rst","introduction.rst"],objects:{"":[[1,0,0,"-","Binding"],[1,0,0,"-","Simulation"],[1,0,0,"-","Utils"]],"Simulation.Activation":[[1,3,1,"","Analysis"],[1,3,1,"","PathwayParameters"],[1,3,1,"","PathwayParametersToCSV"],[1,3,1,"","PotencyToCSV"],[1,3,1,"","PotencyToDict"],[1,3,1,"","Reactions"],[1,3,1,"","Run"],[1,3,1,"","SetSimulationParameters"],[1,3,1,"","ShowCurve"],[1,3,1,"","ShowPotency"],[1,3,1,"","UserPathwayParameters"]],"Simulation.FitModel":[[1,3,1,"","PathwayParameters"],[1,3,1,"","PathwayParametersToCSV"],[1,3,1,"","PlotIterations"],[1,3,1,"","Reactions"],[1,3,1,"","Run"],[1,3,1,"","SetSimulationParameters"],[1,3,1,"","ShowGraphs"],[1,3,1,"","UserPathwayParameters"]],"Simulation.Inhibition":[[1,3,1,"","Analysis"],[1,3,1,"","PathwayParameters"],[1,3,1,"","PathwayParametersToCSV"],[1,3,1,"","PotencyToCSV"],[1,3,1,"","PotencyToDict"],[1,3,1,"","Reactions"],[1,3,1,"","Run"],[1,3,1,"","SetSimulationParameters"],[1,3,1,"","ShowCurve"],[1,3,1,"","ShowPotency"],[1,3,1,"","UserPathwayParameters"]],"Utils.tauRAMD":[[1,3,1,"","PlotRTDistribuitons"],[1,3,1,"","PlotRTStats"],[1,3,1,"","Run"]],Binding:[[1,1,1,"","Bind"],[1,1,1,"","ShowCurve"],[1,1,1,"","SubMaxConcentration"]],Simulation:[[1,2,1,"","Activation"],[1,2,1,"","FitModel"],[1,2,1,"","Inhibition"],[1,4,1,"","pathways_path"]],Utils:[[1,1,1,"","CalcOccupancy"],[1,1,1,"","GetGProtein"],[1,1,1,"","KineticTempScale"],[1,1,1,"","MicrogramsToNanomolar"],[1,1,1,"","PrintProgressBar"],[1,2,1,"","tauRAMD"]]},objnames:{"0":["py","module","Python module"],"1":["py","function","Python function"],"2":["py","class","Python class"],"3":["py","method","Python method"],"4":["py","data","Python data"]},objtypes:{"0":"py:module","1":"py:function","2":"py:class","3":"py:method","4":"py:data"},terms:{"0":[0,1,3],"08":3,"1":[0,1],"10":3,"100":[0,1],"11":[0,3],"1101":3,"12":0,"13":0,"1978":0,"1999":4,"2":[0,3],"20":0,"2002":0,"2003":[0,1],"2007":[0,4],"2009":[0,4],"2010":4,"2011":0,"2012":0,"2013":[0,4],"2014":4,"2015":[0,4],"2016":4,"2017":4,"2018":[1,4],"2019":[0,4],"2020":[0,4],"2022":3,"2e":1,"3":0,"3rd":0,"4":[0,1],"5":0,"50":0,"515595":3,"515595v1":3,"5th":0,"6":[0,1],"7":0,"80":0,"\u03b1":0,"boolean":1,"class":[0,1,3],"default":[0,1,2],"export":1,"final":[0,1],"function":[0,1,4],"import":[1,4],"int":1,"new":4,"public":3,"return":1,"short":4,"while":[0,4],A:[0,3,4],And:4,As:[0,4],By:0,For:[0,4],If:[0,2,3],In:[0,4],It:1,No:2,Such:[0,4],That:0,The:[0,1,3,4],Then:0,To:0,__init__:[],about:4,abov:0,absenc:0,access:0,accord:[0,1],accur:4,accuraci:0,achiev:0,across:4,act:0,action:[0,4],activ:[0,1,4],actual:4,ad:0,adapt:0,adjac:0,advoc:4,affect:4,affin:[1,3,4],after:0,against:0,agonist:[0,1],agonist_affin:1,agonist_conc:1,agonist_submaximal_conc:1,agonists_conc:1,al:[0,1,4],alejandro:3,all:[0,1,4],allow:[0,4],alreadi:4,also:[0,4],altern:4,although:0,alv:4,am:2,amount:[0,1],amplitud:1,an:[0,1,3,4],analysi:[0,1,3],androulaki:4,ani:[0,3],anoth:4,antagonist:[0,1],antagonist_conc:1,antagonists_affin:1,antagonists_conc:1,antagonists_conc_rang:1,apach:3,api:3,appli:[0,1,4],approach:[0,4],ar:[0,3,4],area:1,arithmet:0,arrai:[0,1],articl:3,ask:3,assai:0,assess:0,assist:4,associ:[0,1,4],assum:[0,4],assumpt:[0,4],author:3,automat:1,avail:[0,4],b:0,bar:1,basal:0,base:[0,4],baselin:0,becaus:[0,4],becom:[0,4],been:[0,4],being:[0,3,4],bend:[0,1],better:4,between:[0,4],bibtex:3,bind:[1,2,3,4],binding_kinet:[1,2],biochem:[0,4],bioinformat:[0,3],biolog:[0,4],biologi:[0,3],biomed:4,biomodel:4,biopython:0,biorxiv:3,birch:4,bound:0,bridg:4,bring:[0,3,4],bruce:4,built:[0,3],c:[0,1],calcoccup:1,calcul:[0,1,2,4],call:[0,1],calul:1,camp:0,campaign:0,can:[0,3,4],capelli:4,carefulli:1,cascad:4,cat:4,catalysi:4,catalyz:[0,4],cell:[0,4],cellular:[0,4],celsiu:1,central:4,certain:0,chang:[0,2,4],charact:1,character:[0,4],chemic:0,choic:[0,3],chose:0,chosen:0,claim:4,clark:4,classic:[0,3],classif:0,classifi:0,close:0,code:[0,3],colab:1,com:3,come:0,commonli:[0,4],commun:[0,3],compar:0,comparison:[0,4],compet:0,competit:0,complementari:0,complet:1,complex:[0,4],comput:[0,3],computation:[0,4],concentr:[0,1,4],concept:[0,4],conceptu:0,concurr:4,condit:[0,4],conform:0,consid:[0,1,2],consist:4,constant:[0,1,4],constitut:[0,4],contain:[0,3],contribut:4,convert:1,convolut:4,core:0,correspond:0,corrrespond:0,coupl:0,creat:1,critic:4,csv:1,current:1,curv:[0,1],d:0,d_:0,dat:1,data:[0,1,4],databas:[0,1,4],datafram:1,de:4,deal:0,decad:4,decemb:0,decim:1,decreas:0,decrement:1,decrementor:1,deduc:0,deep:4,defaul:1,defin:0,definit:0,delean:0,dell:4,demonstr:0,depend:[0,4],deploy:0,deriv:[0,4],describ:[0,4],descript:0,design:0,despit:4,detail:0,determin:4,develop:[0,1,4],deward:4,dict:1,dictionari:1,didn:0,differ:[0,4],differenti:[0,4],dilut:0,dimens:4,dimension:4,directli:2,directori:1,disciplin:4,discoveri:4,discret:0,diseas:4,displac:0,displai:1,dissoci:[0,4],distribut:[1,4],divid:0,doe:2,doen:1,doesn:[0,3],dopaminerg:4,dose:[0,1],downstream:4,drug:[3,4],drug_receptor:1,drugreceptor:4,dt:1,due:0,duran:4,dure:0,dynam:4,e:[0,1],each:[0,1,4],easi:0,easier:0,easili:[0,3,4],ec_:0,ed:0,effect:[0,1,4],effector:0,efficaci:0,either:0,element:4,encod:4,end:[0,1],endogen:0,enough:4,environ:[0,3],enzym:4,epiderm:4,eq:0,equal:0,equat:[0,4],equilibrium:[0,1,4],error:4,essenti:0,estim:[0,4],et:[0,1,4],evalu:4,even:4,event:[0,4],exampl:3,exist:[0,4],experi:0,experiment:[0,1,4],expert:[0,3],explicit:4,explor:[0,3],expratio:1,express:0,extens:[0,3],extract:1,fact:[0,4],factor:[0,4],fals:[1,2],famili:0,fast:4,fastasequ:[],feed:4,field:4,fig:0,figur:0,file:[0,1],filenam:1,fill:1,find:0,first:[0,3,4],fit:[0,1],fitmodel:1,fix:0,flt:1,focu:4,follow:0,format:1,found:[3,4],four:0,foward:1,fraction:[0,1],framework:4,free:[0,3],freeli:3,frequent:3,friendli:[0,3],frigola:4,from:[0,1,3,4],full:1,fundament:4,g:[0,1,4],g_:0,gener:[3,4],geometr:0,get:[],getgprotein:1,gi:1,giorgetti:3,github:3,giulia:3,give:0,given:[0,3],glont:4,goe:0,golan:0,googl:1,gossen:3,govern:4,gpcr:[0,1,3],gpcrdb:0,gprotein:[],gq:[0,1],grant:0,grcr:0,green:0,gromac:1,grow:4,growth:4,gs:1,ha:[0,4],half:1,halfwai:0,hallucinogen:4,hamper:4,have:[0,4],help:0,helper:[],henc:0,high:4,higher:0,home:1,hormon:0,how:[0,4],howev:[0,4],http:3,human:[0,1],hyperbol:0,i:[0,1,2],ic_:0,idea:4,ideal:4,identif:4,impact:0,implement:[0,1],impli:0,includ:[0,4],incomplet:4,incorpor:0,increas:0,increment:1,incrementor:1,index:3,indic:4,individu:0,infer:0,inform:[0,3,4],inhibit:[0,1],initi:[0,1,4],input:[0,3],insid:4,instanc:[0,1,3,4],instead:[0,3],integr:[0,4],intens:4,interact:[0,3,4],intern:1,internet:4,interplai:4,intric:[0,4],introduct:3,invergo:4,ip_3:0,iter:1,its:[0,1,4],j:[0,4],jona:3,jupyt:[0,3],just:[0,1,3],k:1,k_:[0,4],k_d:0,k_m:4,kd:[0,2],kelvin:1,kholodenko:4,kinas:[0,4],kinet:[1,4],kinetictempscal:1,known:0,koff:1,kokh:1,kon:1,krzyzanski:4,kwarg:1,l2:0,l:0,l_init:2,label:0,lack:4,last:0,latter:0,law:0,learn:[0,4],length:1,less:0,librari:[0,1],lig_conc_rang:1,ligand:[0,1,2,3,4],light:4,like:[0,4],line:0,linear:0,list:1,literatur:[0,4],liu:4,logarithm:0,logist:0,loop:1,lowest:0,lr:[0,2],m:0,machin:4,macromolecular:4,made:0,magnitud:0,mai:[0,4],mainli:0,make:[0,3,4],malik:4,mani:[0,4],mass:[0,1],mathemat:[0,1,4],mathod:1,matplotlib:0,matter:[0,4],max:4,maxbend:[],maxim:0,maximum:[0,1],maxit:1,mccrai:0,md:[1,4],me:1,mead:1,mean:4,measur:[0,1,4],mechan:4,mediat:4,medicin:4,messeng:0,metabol:0,metabolit:0,metadynam:4,method:[0,1,3,4],microgr2nanomolar:[],microgram:1,microgramstonanomolar:1,might:0,mimic:0,minim:[1,3],minimum:0,miss:4,model:[0,1,3,4],modifi:[0,1],modul:[0,1,3],modular:0,molar:0,molecular:[0,1,3,4],more:[0,4],moreov:0,most:4,must:[0,1],myriad:0,n:4,nair:[0,4],namd:1,name:1,nanomolar:1,natur:0,need:[0,4],nelder:1,network:4,neubig:0,neural:4,neurotransmitt:0,neve:0,next:4,nm:1,non:[0,3],none:1,normal:[0,4],notebook:[0,3],novel:[0,3],nowadai:[0,3],ns:1,nstep:1,number:[1,4],numer:[0,4],numpi:0,nune:4,o:0,obeserv:1,observ:[0,1],obtain:[0,1,2],occup:[0,4],occupi:[0,1],occupit:1,occur:[0,4],od:[1,4],off:4,one:[0,4],onli:[0,4],open:[0,3],option:1,orco:4,order:[0,1,4],ordinari:4,org:3,other:[0,2,4],out:0,over:[0,4],overcom:4,oxtr:1,page:3,panda:[0,1],paradigm:4,paramet:[0,1,2,4],paramt:1,part:[0,4],past:4,path:1,pathai:1,pathophysiolog:4,pathwai:[1,2,3,4],pathway_paramet:1,pathwayparamet:1,pathwayparameterstocsv:1,pathways_path:1,patrick:0,pedro:3,percent:1,percentag:0,perform:[0,1],person:4,pfleger:0,pharmacodynam:[0,3],pharmacolog:4,phosphoryl:[0,4],phototransduct:4,physic:4,pipelin:[0,3,4],pkd:1,pkd_agonist:1,pkd_antagonist:1,plai:[0,4],plateau:0,pleas:[1,3],plot:[0,1],plotcurv:[],plotiter:1,plotrtdistribuiton:1,plotrtstat:1,point:[0,1],portion:0,posit:1,possibl:0,potenc:[0,1],potencytocsv:1,potencytodict:1,potent:0,power:[0,4],pre:0,predict:[0,3,4],prefer:[0,3],prefix:1,presenc:0,present:0,previou:0,printend:1,printprogressbar:1,process:4,processed_data:1,produc:0,progress:[0,1],project:1,promis:4,prone:4,proof:4,proport:[0,2],propos:[0,4],protein:[0,1,4],protocol:[0,3],provid:4,publish:3,pujol:4,pygomodo:3,pysb:0,python:0,q:0,qgrid:1,quantif:4,quantit:[0,4],quantiti:4,queri:1,question:3,r:[0,1],r_:0,radioact:0,radioligand:0,ragoza:4,ramd:1,ramdom:1,rang:[0,1],ratio:1,raw:1,reach:0,reaction:[0,1,2,4],reactom:0,readabl:0,reason:[0,4],recent:4,receptor:[1,2,3,4],receptor_conc:1,red:0,reduc:0,refer:[0,3],regress:0,rel:4,relat:4,relationship:[0,4],relev:4,reli:4,render:0,rendit:4,replic:[0,3],repositori:4,repres:0,reproduc:1,repsons:0,requir:1,rerun:[0,3],rescal:1,research:[3,4],resid:1,resolut:4,resolv:0,respect:0,respons:[0,1,4],result:[0,2,4],retriv:[],reusabl:0,revers:1,reward:4,rhodopsin:4,ribeiro:3,ribeiro_ssb_2022:3,right:0,rise:4,role:4,rossetti:3,rribeiro:[1,3],rui:3,rule:[0,2],run:1,s:0,sai:0,same:[0,1,2,4],satur:0,save:1,scale:[1,4],scenario:0,scheme:0,sci:3,scientif:[0,3],scikit:0,scipi:0,screen:0,search:3,sebaugh:[0,1],second:[0,1,4],see:[0,1],seed:1,seed_decrementor:1,seed_incrementor:1,select:0,self:1,semi:0,sens:4,sequenc:1,seri:[0,4],serotonin:4,serv:0,set:4,setsimulationparamet:1,setup:1,sever:0,shape:[0,1],share:[0,3],sheriff:4,shift:0,shoul:1,should:[0,4],show_curv:[],showcurv:1,showgraph:1,showpot:1,sigmoid:[0,1],signal:[1,3,4],signaltransduct:0,similar:4,simualt:1,simul:[1,2,3,4],sinc:[0,4],site:0,slope:0,so:[0,2,4],softwar:[1,3],softwr:1,solv:4,sourc:[0,3],space:0,spare:0,spatial:4,speci:[0,1,4],special:4,specif:[0,1,4],ssb:[0,3],ssbtoolkit:1,start:0,state:4,statement:0,statist:1,steadi:4,steep:0,stein:[0,4],step:[1,4],still:4,store:4,str:1,straight:0,strength:4,string:1,strong:0,stronger:0,structur:[0,3],studi:[0,4],subcellular:[3,4],subfamili:0,submax_concentr:1,submaxconcentr:1,submaxim:[0,1],subsequ:4,subunit:0,sue:2,suffix:1,suitabl:0,system:[0,3],systemat:0,t1:1,t2:1,t:[0,1,3],tabl:1,taken:[0,4],target:[0,1,4],target_paramet:1,tauramd:1,temperatur:1,tempor:4,term:0,termin:1,than:0,thank:4,thei:4,them:[0,3,4],theori:[0,4],therebi:4,therefor:[0,4],thesi:0,thi:[0,1,4],think:4,those:4,though:0,three:[0,4],through:0,time:[0,1,4],tissu:0,titl:3,todai:4,togeth:[0,3],tool:[0,3],toolkit:[0,3],total:[0,1],toward:4,tramd:1,transduct:4,transfer:4,tremend:4,truth:4,ttotal:1,tu:1,tumor:4,tutori:[1,3],typic:[0,4],umbrella:0,unbound:0,under:[0,1,3,4],underneath:4,understand:4,undoubtedli:4,uniprot:1,uniprotid:1,uniqu:0,unit:1,unk:0,unknown:0,unlabel:0,unpreced:4,unquestion:4,untest:[0,3],up:[0,4],upper:0,url:3,us:[0,1,2,3,4],user:[0,1,3],userpathwayparamet:1,util:1,v_:4,valid:0,valu:[0,1,2,4],variabl:4,variat:0,varieti:4,vein:0,version:0,versu:0,virtual:0,vivo:4,wa:[0,1,3],wai:[0,4],wang:4,wash:0,we:0,well:0,were:0,what:3,when:[0,2],where:[0,4],wherea:0,which:0,whole:4,why:[0,2],wide:0,width:1,without:0,word:[0,4],work:[1,3],www:3,x:0,xie:4,y:0,year:[3,4],you:3,zero:0},titles:["What is the SSBtoolkit?","API Documentation","Frequently Asked Questions","Welcome to SSBtoolkit\u2019s documentation!","Introduction to Structure Systems Biology"],titleterms:{affin:0,api:1,ask:2,avail:3,bind:0,biologi:4,citat:3,cite:3,content:3,develop:3,document:[1,3],doi:3,drug:0,frequent:2,fund:3,gener:2,indic:3,introduct:4,licens:3,pathwai:0,question:2,receptor:0,s:3,signal:0,simul:0,ssbtoolkit:[0,3],structur:4,system:4,tabl:3,welcom:3,what:0}}) \ No newline at end of file diff --git a/docs/apidocs.rst b/docs/apidocs.rst index 388a749..9648907 100644 --- a/docs/apidocs.rst +++ b/docs/apidocs.rst @@ -1,7 +1,10 @@ API Documentation ================= -.. automodule:: ssbtoolkit +.. automodule:: Binding :members: - + +.. automodule:: Simulation + :members: + .. automodule:: Utils :members: diff --git a/docs/conf.py b/docs/conf.py index 8571d44..d51c86f 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -12,7 +12,8 @@ # import os import sys -sys.path.insert(0, os.path.abspath('../src/lib')) +sys.path.insert(0, os.path.abspath('../ssbtoolkit')) +sys.path.insert(0, os.path.abspath('../')) # -- Project information ----------------------------------------------------- @@ -22,7 +23,7 @@ copyright = '2022, Rui Pedro Ribeiro' author = 'Rui Pedro Ribeiro' # The full version, including alpha/beta/rc tags -release = 'v1' +release = 'v1.0.1' # -- General configuration --------------------------------------------------- diff --git a/ssbtoolkit/Binding.py b/ssbtoolkit/Binding.py new file mode 100644 index 0000000..e68ef4f --- /dev/null +++ b/ssbtoolkit/Binding.py @@ -0,0 +1,126 @@ +#LIBRARIES +import numpy as np +import ssbtoolkit.Utils as utils + +"""Module to simulate ligand-target binding curves.""" +def __init__(self): + self.receptor_conc = None + self.lig_conc_range = None + self.pKd = None + self.submax_concentration = None + +def Bind(self, **kwargs): + """ + Applies an function to calculate the fraction of occupited receptors at equilibrium. + + :parameter receptor_conc: Required (kwarg flt): concentration of receptor + :parameter lig_conc_range: Required (kwarg array): array of range of ligand concentration + :parameter pKd: Required (kwarg flt): pKd value of the ligand + """ + + if 'receptor_conc' not in kwargs: raise TypeError("ERROR: receptor_conc is missing") + if 'lig_conc_range' not in kwargs: raise TypeError("ERROR: lig_conc_range is missing") + if 'pKd' not in kwargs: raise TypeError("ERROR: pKd is missing") + + self._receptor_conc = kwargs.pop('receptor_conc') + self._lig_conc_range = kwargs.pop('lig_conc_range') + self._pKd = kwargs.pop('pKd') + + binding_data=[] + for conc in self._lig_conc_range: + binding_data.append(utils.CalcOccupancy(self._receptor_conc, conc, 0, self._pKd, 0)) + self.binding_data=binding_data + return self.binding_data + +def SubMaxConcentration(self): + """ + Calculates the maximum bending point of a sigmoid-shaped curve according to the mathod of Sebaugh et al., 2003. + + :parameter drug_receptor: Required (int): concentration of the receptor + :parameter lig_conc_range: Required (array): array of a range of ligand concentration + :return: instance .submax_concentration (flt) + + .. note:: The minimization uses the Nelder-Mead method. + """ + + from scipy.optimize import curve_fit, minimize + + + def sigmoid(X, Bottom, Top, Kd, p): + return Bottom + (Top-Bottom)/(1+np.power((Kd/X),p)) + + xfit = np.geomspace(np.min(self._lig_conc_range), np.max(self._lig_conc_range), 50000) #warning: shoud this be the minimum and maximum of concentration + popt, pcov = curve_fit(sigmoid, self._lig_conc_range, self.binding_data, bounds=([np.min(self.binding_data),-np.inf,-np.inf, 0.5],[np.inf,np.max(self.binding_data),np.inf, 2.5])) + + + def sigmoid_deriv_b(x, a,d,c,b): + return (x/c)**b*(a - d)*np.log(x/c)/((x/c)**b + 1)**2 + + min_value = minimize(sigmoid_deriv_b, np.max(xfit), args=(popt[0],popt[1],popt[2],popt[3]), method = 'Nelder-Mead') + + self.submax_concentration = round(min_value.x[0],3) + return self.submax_concentration + +def ShowCurve(self): + """ + Plots ligand-target binding curve + """ + + #import plotly + import plotly.graph_objs as go + from scipy.optimize import curve_fit + from sklearn.preprocessing import minmax_scale + + ##Fitting curve to the data + + yy = minmax_scale(self.binding_data)*100 + + def equation_dose(X, Bottom, Top, EC50, p): + return Bottom + (Top-Bottom)/(1+np.power((EC50/X),p)) + + popt, pcov = curve_fit(equation_dose, self._lig_conc_range, yy, bounds=([np.min(yy),-np.inf,-np.inf, 0.5],[np.inf,np.max(yy),np.inf, 2.5])) + + xfit = np.geomspace(np.min(self._lig_conc_range), np.max(self._lig_conc_range), 50000) # These values are the same as the values for the simulation time and not ligand concentration + yfit = minmax_scale(equation_dose(xfit, *popt))*100 + + + + trace1 = go.Line(x=xfit, y=yfit, showlegend=False, name='radioligand') + if self.submax_concentration: + xsubmaximal = np.array(self.submax_concentration) + ysubmaximal = np.array(equation_dose(xsubmaximal, *popt)) + trace2 = go.Scatter(x=xsubmaximal, y=ysubmaximal, showlegend=True, mode='markers', name='submaximal ({} μM)'.format(xsubmaximal), + marker=dict(size=14)) + else: trace2=[] + + + layout = dict(title = '', + xaxis = dict( + title = '[ligand] μM', + type ='log', + exponentformat='e', + titlefont=dict( + size=20 + ), + tickfont=dict( + size=20 + )), + yaxis = dict( + title = '% occupied receptors', + + titlefont=dict( + size=20), + tickfont=dict( + size=20) + + ), + legend=dict(font=dict(size=15)), + autosize=False, + width=850, + height=650, + ) + if self.submax_concentration: + fig = go.Figure(data=[trace1, trace2], layout=layout) + else: + fig = go.Figure(data=[trace1], layout=layout) + return fig diff --git a/ssbtoolkit/Simulation.py b/ssbtoolkit/Simulation.py new file mode 100644 index 0000000..263ff9a --- /dev/null +++ b/ssbtoolkit/Simulation.py @@ -0,0 +1,1277 @@ +#LIBRARIES + +import sys, os, warnings, platform, site, importlib +import pylab as pl +from pysb.simulator import ScipyOdeSimulator +from scipy.optimize import curve_fit +from sklearn.preprocessing import minmax_scale +import plotly.graph_objs as go +import pandas as pd +import numpy as np +import ssbtoolkit.Utils as utils + +sys.path.insert(0, os.path.abspath(os.path.split(os.path.realpath(__file__))[0])) +warnings.simplefilter(action='ignore') + +distpath = site.getsitepackages()[0] +if platform.system() == 'Linux': + BioNetGen=os.path.join(distpath, 'bionetgen/bng-linux:') +elif platform.system() == 'Darwin': + BioNetGen=os.path.join(distpath, 'bionetgen/bng-mac:') +elif platform.system()=='Windows': + BioNetGen=os.path.join(distpath, 'bionetgen/bng-win:') +else: + raise ValueError('BioNetGen error. Platform unknown! The pygomodo was tested in Linux and Darwin (Mac) platforms.') +os.environ['PATH']=BioNetGen+os.environ['PATH'] + +pathways_path=(os.path.join(os.path.split(os.path.realpath(__file__))[0], 'pathways')) + + +""" +Module to simulate the mathematical models of the signaling pathways. +""" +class Activation: + """ + Simulation of the activation of signaling pathways (i.e. activation by agonists) + """ + def __init__(self): + self._ligands=None + self._affinities=None + self._pathway=None + self._receptor_conc=None + self._lig_conc_range=None + self._ttotal=None + self._nsteps=None + self._binding_kinetics=True + self._binding_kinetic_parameters=None + self.simulation_data=None + self.processed_data=None + + def SetSimulationParameters(self, **kwargs): + """ + :parameter ligands: Required (kwargs list): list of ligands' names (str) + :parameter affinities: Required (kwargs list): list of pKd values (flt) + :parameter pathway: Required (kwargs str): name of the pathway ('Gs', 'Gi', 'Gq') + :parameter receptor_conc: Required (kwargs flt): receptors concentration (nM) + :parameter lig_conc_range: Required (kwargs array): range of ligands' concentration + :parameter ttotal: Required (kwargs int): simulation time (seconds) + :parameter nsteps: Required (kwargs int): simulation time step + :parameter binding_kinetics: Optional (kwargs boolean): default (False) + + + .. warning:: the order of the lists of ligands names and affinities list must be the same. + + """ + self._ligands= kwargs.pop('ligands') + if 'affinities' in kwargs: + self._affinities=kwargs.pop('affinities') + self._pathway=kwargs.pop('pathway') + self._receptor_conc=kwargs.pop('receptor_conc') + self._lig_conc_range=kwargs.pop('lig_conc_range') + self._ttotal=kwargs.pop('ttotal') + self._nsteps=kwargs.pop('nsteps') + self._binding_kinetics=kwargs.pop('binding_kinetics') + if 'binding_kinetic_parameters' in kwargs:self._binding_kinetic_parameters=kwargs.pop('binding_kinetic_parameters') + self._DefaultPathwayParametersDataFrame=pd.DataFrame() + + return + + def PathwayParameters(self): + """ + Display table with default pathway parameters. + + .. warning:: this functions requires the qgrid library. It doens't work on Google Colab. + """ + import qgrid + self._DefaultPathwayParametersDataFrame = pd.read_csv(pathways_path+'/{}_parameters.csv'.format(self._pathway)) + + col_opts = { 'editable': False, 'sortable':False} + col_defs = {'Value': { 'editable': True, 'width': 150 }} + self._DefaultPathwayParametersTable = qgrid.show_grid(self._DefaultPathwayParametersDataFrame, column_options=col_opts,column_definitions=col_defs) + return self._DefaultPathwayParametersTable + + def UserPathwayParameters(self, path): + """ + Import user pathway parameters. + + :parameter path: Required (kwarg str): directory path + """ + import qgrid + self._DefaultPathwayParametersDataFrame = pd.read_csv(path) + col_opts = { 'editable': False, 'sortable':False} + col_defs = {'Value': { 'editable': True, 'width': 150 }} + self._DefaultPathwayParametersTable = qgrid.show_grid(self._DefaultPathwayParametersDataFrame, column_options=col_opts,column_definitions=col_defs) + return self._DefaultPathwayParametersTable + + def PathwayParametersToCSV(self, path): + """ + Export pathway parameters into CSV format. + + :parameter path: Required (kwarg str): directory path + """ + self._DefaultPathwayParametersTable.get_changed_df().to_csv(path, index=False) + print('saved in:', path) + return + + def Reactions(self): + """ + Display pathway reactions. + """ + from IPython.display import display, HTML + display(HTML("<style>.container {width:90% !important}</style>")) + return pd.read_csv(pathways_path+'/{}_reactions.csv'.format(self._pathway)) + + def Run(self): + ''' + This function runs the pathway simulation and returns the raw simulation data. + ''' + + #Check inputs + if self._ligands==None: raise TypeError("ligands list undefined.") + elif self._pathway==None: raise TypeError("pathway name undefined.") + elif self._binding_kinetics==False and self._affinities==None: raise TypeError("affinity_values_dict undefined.") + elif self._binding_kinetics==True and self._affinities==None: pass + elif self._lig_conc_range.any() == False: raise TypeError("lig_conc_range undefined.") + elif self._ttotal==None: raise TypeError("ttotal undefined.") + elif self._nsteps==None: raise TypeError("nsteps undefined.") + elif self._receptor_conc==None: raise TypeError("receptor_conc undefined.") + else: pass + + + #Check Pathway availability and import it + available_pathways = ['Gs', 'Gi', 'Gq'] + if self._pathway == 'Gz(Gi)': self._pathway = 'Gi' + if self._pathway not in available_pathways: raise Exception('Unvailable Pathway. Please, introduce it manually. Pathways available: "Gs", "Gi", "Gq".') + mypathway = importlib.import_module('.'+self._pathway, package='ssbtoolkit.pathways') + + #Get default pathway parameters + if self._DefaultPathwayParametersDataFrame.empty and self._binding_kinetic_parameters==None: + self._DefaultPathwayParametersDataFrame = pd.read_csv(pathways_path+'/{}_parameters.csv'.format(self._pathway)) + self._PathwayParameters = self._DefaultPathwayParametersDataFrame.set_index('Parameter').iloc[:,0].to_dict() + + elif self._DefaultPathwayParametersDataFrame.empty is False and self._binding_kinetic_parameters is None: + try: + #extract data from qgrid + newparameters = self._DefaultPathwayParametersTable.get_changed_df() + self._PathwayParameters = newparameters.set_index('Parameter').iloc[:,0].to_dict() + except: + self._PathwayParameters = self._DefaultPathwayParametersDataFrame.set_index('Parameter').iloc[:,0].to_dict() + + elif self._DefaultPathwayParametersDataFrame.empty and self._binding_kinetic_parameters is not None: + self._DefaultPathwayParametersDataFrame = pd.read_csv(pathways_path+'/{}_parameters.csv'.format(self._pathway)) + + elif self._DefaultPathwayParametersDataFrame.empty is False and self._binding_kinetic_parameters is not None: + try: + #extract data from qgrid + newparameters = self._DefaultPathwayParametersTable.get_changed_df() + self._PathwayParameters = {**newparameters.set_index('Parameter').iloc[:,0].to_dict(), **self._binding_kinetic_parameters} + except: + self._PathwayParameters = {**self._DefaultPathwayParametersDataFrame.set_index('Parameter').iloc[:,0].to_dict(), **self._binding_kinetic_parameters} + + #Input + t = pl.geomspace(0.00001, self._ttotal, num=self._nsteps) # (a,b,c); a is the starting time ; b is the total time simulated ; c is the number of points + + + #Output + simulation_data={} + + #Function + for ligand in self._ligands: + ligand_name = os.path.splitext(str(ligand))[0] + data=[] + utils.PrintProgressBar(0, len(self._lig_conc_range), prefix = "{:<15}".format(ligand_name[:15]), suffix = 'Complete', length = 50) + + ###DANGER ZONE### + if self._binding_kinetic_parameters is not None: + self._PathwayParameters = {**self._DefaultPathwayParametersDataFrame.set_index('Parameter').iloc[:,0].to_dict(), **self._binding_kinetic_parameters[self._ligands.index(ligand)]} + ###################### + + for idx in range(len(self._lig_conc_range)): + + ligand_conc = self._lig_conc_range[idx] + if self._binding_kinetics == False: + #get LR conc + parameters = {**self._PathwayParameters, 'R_init':self._receptor_conc} + LR_conc_init = utils.CalcOccupancy(self._receptor_conc, ligand_conc, 0, self._affinities[self._ligands.index(ligand)], 0) + mymodel = mypathway.network(LR=LR_conc_init, kinetics=False, **parameters) + simres = ScipyOdeSimulator(mymodel, tspan=t, compiler='cython').run() + yout = simres.all + + elif self._binding_kinetics == True: + parameters={**self._PathwayParameters,'R_init':self._receptor_conc, 'L_init':self._lig_conc_range[idx] } + mymodel = mypathway.network(kinetics=True, **parameters) + simres = ScipyOdeSimulator(mymodel, tspan=t, compiler='cython').run() + yout = simres.all + + d1={'ligand_conc':ligand_conc, 'time':t } + + for idx2 in range(len(mypathway.list_of_observables)): + d2={mypathway.list_of_observables[idx2]:yout[mypathway.list_of_observables[idx2]]} + d1.update(d2) + data.append(d1) + utils.PrintProgressBar(idx + 1, len(self._lig_conc_range), prefix = "{:<15}".format(ligand_name[:15]), suffix = 'Complete', length = 50) + + + simulation_data[ligand_name] = {'sim_data':data, + 'label':ligand_name,} + self.simulation_data = simulation_data + return + + def Analysis(self): + ''' + This function calculates the dose-response effect. + + :return: instance of processed_data + ''' + + if self.simulation_data == None: raise TypeError('There is no simulation data. simulation.activation.run() must be run first.') + + # Define all the lists and dictionaries used in this function + raw_data=[] + normalized_data=[] + dose={} + + #defining concentration range + lig_conc_min = self._lig_conc_range.min() + lig_conc_max = self._lig_conc_range.max() + + #Main function + for ligand in self.simulation_data: + + #definig and dictionaries used in this loop: + raw_data_dict={} + normalized_data_dict={} + + # Calculate dose-response curve + #get metabolite concentration, rescale, and transform data if pathway/metabolite decrease + # metabolite_raw is not normalized + if self._pathway == 'Gi' or self._pathway == 'Gz(Gi)': + metabolite='cAMP' + metabolite_conc_raw=[] + for i in range(len(self._lig_conc_range)): + n=np.amax(self.simulation_data[ligand]['sim_data'][i]['obs_'+metabolite]) #cad + metabolite_conc_raw.append(n) + metabolite_conc_norm = minmax_scale(1-np.array(metabolite_conc_raw)) + elif self._pathway == 'Gs': + metabolite='cAMP' + metabolite_conc_raw=[] + for i in range(len(self._lig_conc_range)): + n=np.amax(self.simulation_data[ligand]['sim_data'][i]['obs_'+metabolite]) #cad + metabolite_conc_raw.append(n) + metabolite_conc_norm = minmax_scale(np.array(metabolite_conc_raw)) + elif self._pathway == 'Gq': + metabolite='IP3' + metabolite_conc_raw=[] + for i in range(len(self._lig_conc_range)): + n=np.amax(self.simulation_data[ligand]['sim_data'][i]['obs_'+metabolite]) #cad + metabolite_conc_raw.append(n) + metabolite_conc_norm = minmax_scale(np.array(metabolite_conc_raw)) + else: raise Exception('Unvailable Pathway. Please, introduce it manually. Networs available: "Gs", "Gi", "Gq".') + + + ## save results + raw_data_dict['x']=self._lig_conc_range + raw_data_dict['y']=metabolite_conc_raw + raw_data_dict['label']=self.simulation_data[ligand]['label'] + + normalized_data_dict['x']=self._lig_conc_range + normalized_data_dict['y']=metabolite_conc_norm + normalized_data_dict['label']=self.simulation_data[ligand]['label'] + + ## create a list of all data + raw_data.append(raw_data_dict) + normalized_data.append(normalized_data_dict) + + ##Fitting curve to the data + + def equation_dose(X, Bottom, Top, EC50, p): + return Bottom + (Top-Bottom)/(1+np.power((EC50/X),p)) + + popt_EC50, pcov = curve_fit(equation_dose, self._lig_conc_range, metabolite_conc_norm, bounds=([np.min(metabolite_conc_norm),-np.inf,-np.inf, 0.5],[np.inf,np.max(metabolite_conc_norm),np.inf, 2.5])) + + xfit_EC50 = np.geomspace(lig_conc_min, lig_conc_max, 50000) # These values are the same as the values for the simulation time and not ligand concentration + yfit_EC50 = equation_dose(xfit_EC50, *popt_EC50) + + fit_EC50={'x':xfit_EC50, 'y':yfit_EC50, 'label':self.simulation_data[ligand]['label']} + + dose[ligand] = {'raw_data': raw_data_dict, + 'normalized_data':normalized_data_dict , + 'fitted_data': fit_EC50, + 'EC50 (μM)': round(popt_EC50[2],5), + 'pEC50': round(-np.log10(popt_EC50[2]*1E-6),2)} + + self.processed_data=dose + return + + def ShowCurve(self, save=False, filename=None): + ''' + Plots the dose-response curve. + ''' + + if self.simulation_data == None: raise TypeError('There is no simulation data. simulation.activation.run() must be run first.') + + import plotly + import plotly.graph_objs as go + import plotly.offline as pyoff + + colors = plotly.colors.DEFAULT_PLOTLY_COLORS + + plot_data=[] + + color_id=0 + for ligand in self.processed_data: + trace_norm = go.Scatter(x=self.processed_data[ligand]['normalized_data']['x'], + y=minmax_scale(self.processed_data[ligand]['normalized_data']['y'])*100 , + mode='markers', + showlegend=True, + name=self.processed_data[ligand]['normalized_data']['label'], + marker=dict(color=colors[color_id])) + plot_data.append(trace_norm) + + trace_fitted = go.Scatter(x=self.processed_data[ligand]['fitted_data']['x'], + y=minmax_scale(self.processed_data[ligand]['fitted_data']['y'])*100, + mode='lines', + showlegend=False, + name=self.processed_data[ligand]['fitted_data']['label'], + line=dict(color=colors[color_id])) + plot_data.append(trace_fitted) + color_id +=1 + + layout = dict(title = '', + xaxis = dict( + title = '[ligand] μM', + type ='log', + #range = [-3, 2], + exponentformat='e', + titlefont=dict( + size=20 + ), + tickfont=dict( + size=20 + )), + yaxis = dict( + title = '% Response', + #range = [0, 100], + titlefont=dict( + size=20), + tickfont=dict( + size=20) + + ), + legend=dict(font=dict(size=15)), + autosize=False, + width=850, + height=650, + ) + + fig = go.Figure(data=plot_data, layout=layout) + #fig['layout']['yaxis'].update(autorange = True) + + if save==True: + if filename==None: + filename='plot.html' + return pyoff.plot(fig, filename=filename) + else: + ext = os.path.splitext(filename)[-1] + if ext == '.png': fig.write_image(filename, scale=3) + elif ext == '.html': pyoff.plot(fig, filename=filename) + else: raise TypeError("extension not valid. Use png or html.") + elif save ==False: return fig + return + + def ShowPotency(self): + ''' + Return the potency values as a pandas DataFrame. + ''' + import pandas as pd + data = Simulation.Activation.PotencyToDict(self) + df = pd.DataFrame.from_dict(data, orient='index') + return df + + def PotencyToDict(self): + ''' + Convert potencies into a dictionary. + ''' + + #dependencies + if self.processed_data == None: raise TypeError('Simulation data unprocessed. simulation.activation.analysis() must be run first.') + + kvalues={} + for ligand in self.processed_data: + IC50 = list(self.processed_data[ligand].keys())[-2] + IC50_value = self.processed_data[ligand][IC50] + pIC50 = list(self.processed_data[ligand].keys())[-1] + pIC50_value = self.processed_data[ligand][pIC50] + kvalues[ligand]={IC50:IC50_value, pIC50:pIC50_value} + return kvalues + + def PotencyToCSV(self, path): + ''' + Exports the potency values into csv format. + + :parameter path: Required (kwarg str): directory path to save the csv file + ''' + + data = Simulation.Activation.PotencyToDict(self) + df = pd.DataFrame.from_dict(data, orient='index') + df.to_csv(path, index=False) + return + +class Inhibition: + """ + Simulation of the inhibition of signaling pathways (i.e. inhibition by antagonists). + """ + def __init__(self): + self._agonist=None + self._agonist_affinity=None + self._agonist_submaximal_conc=None + self._antagonists=None + self._antagonists_affinities=None + self._pathway=None + self._receptor_conc=None + self._lig_conc_range=None + self._ttotal=None + self._nsteps=None + self._binding_kinetics=False + self._binding_kinetic_parameters=None + self.simulation_data=None + self.processed_data=None + + def SetSimulationParameters(self, **kwargs): + """ + :parameter agonist: Required (kwargs str): agonist name + :parameter agonist_affinity: Required (kwargs flt): agonist pKd value + :parameter agonist_submaximal_conc: Required (kwargs flt): agonist submaximal concentration + :parameter antagonists: Required (kwargs list):list of antagonists names (str) + :parameter antagonists_affinities: Required (kwargs list): list of antagonists affinity values (flt) + :parameter antagonists_conc_range: Required (kwargs array): range of ligands' concentration (nM) + :parameter pathway: Required (kwargs str): name of the pathway ('Gs', 'Gi', 'Gq') + :parameter receptor_conc: Required (kwargs flt): receptors concentration (nM) + :parameter ttotal: Required (kwargs int): simulation time (seconds) + :parameter nsteps: Required (kwargs int): simulation time step + :parameter kinetics: Optional (kwargs boolean): default (False) + + + :return: instances of all parameters + + .. warning:: the order of the lists of the antagonists names and affinities list must be the same. + + """ + self._agonist= kwargs.pop('agonist') + self._agonist_affinity=kwargs.pop('agonist_affinity') + self._agonist_submaximal_conc=kwargs.pop('agonist_submaximal_conc') + self._antagonists=kwargs.pop('antagonists') + self._antagonists_affinities=kwargs.pop('antagonists_affinities') + self._pathway=kwargs.pop('pathway') + self._receptor_conc=kwargs.pop('receptor_conc') + self._lig_conc_range=kwargs.pop('lig_conc_range') + self._ttotal=kwargs.pop('ttotal') + self._nsteps=kwargs.pop('nsteps') + if 'kinetics' in kwargs: + self._binding_kinetics=kwargs.pop('kinetics') + if self._binding_kinetics==True: raise TypeError("The of Kinetic parameters during an inhibition simulation it is not supported yet.") + else: self._binding_kinetics=False + if 'binding_kinetic_parameters' in kwargs: + self._binding_kinetic_parameters=kwargs.pop('binding_kinetic_parameters') + self._DefaultPathwayParametersDataFrame=pd.DataFrame() + + return + + def PathwayParameters(self): + """ + Display table with default pathway parameters. + + .. warning:: this functions requires the qgrid library. It doens't work on Google Colab. + """ + import qgrid + self._DefaultPathwayParametersDataFrame = pd.read_csv(pathways_path+'/{}_parameters.csv'.format(self._pathway)) + + col_opts = { 'editable': False, 'sortable':False} + col_defs = {'Value': { 'editable': True, 'width': 150 }} + self._DefaultPathwayParametersTable = qgrid.show_grid(self._DefaultPathwayParametersDataFrame, column_options=col_opts,column_definitions=col_defs) + return self._DefaultPathwayParametersTable + + def UserPathwayParameters(self, path): + """ + Import user pathway parameters. + + :parameter path: Required (kwarg str): directory path + """ + import qgrid + self._DefaultPathwayParametersDataFrame = pd.read_csv(path) + col_opts = { 'editable': False, 'sortable':False} + col_defs = {'Value': { 'editable': True, 'width': 150 }} + self._DefaultPathwayParametersTable = qgrid.show_grid(self._DefaultPathwayParametersDataFrame, column_options=col_opts,column_definitions=col_defs) + return self._DefaultPathwayParametersTable + + def PathwayParametersToCSV(self, path): + """ + Export pathway parameters into CSV format. + + :parameter path: Required (kwarg str): directory path + """ + self._DefaultPathwayParametersTable.get_changed_df().to_csv(path, index=False) + print('saved in:', path) + return + + def Reactions(self): + """ + Display pathway reactions. + """ + from IPython.display import display, HTML + display(HTML("<style>.container {width:90% !important}</style>")) + return pd.read_csv(pathways_path+'/{}_reactions.csv'.format(self._pathway)) + + def Run(self): + ''' + This function runs the pathway simulation and returns the raw simulation data. + ''' + + #Check inputs + if self._agonist==None: raise TypeError("agonist undefined.") + elif self._agonist_affinity==None: raise TypeError("agonist_affinity undifined.") + elif self._antagonists==None: raise TypeError("antagonists list undefined.") + elif self._antagonists_affinities==None: raise TypeError("antagonists affinity values undefined.") + elif self._pathway==None: raise TypeError("pathway undefined.") + elif self._lig_conc_range.any() == False: raise TypeError("lig_conc_range undefined.") + elif self._agonist_submaximal_conc == None: raise TypeError("agonist_submaximal_conc undifined.") + elif self._ttotal==None: raise TypeError("ttotal undefined.") + elif self._nsteps==None: raise TypeError("nsteps undefined.") + elif self._receptor_conc==None: raise TypeError("receptor_conc undefined.") + elif self._binding_kinetics==True: raise TypeError("The of Kinetic parameters during an inhibition simulation it is not supported yet.") + else: pass + + #check pathway + available_pathways = ['Gs', 'Gi', 'Gq'] + if self._pathway == 'Gz(Gi)': self._pathway = 'Gi' + if self._pathway not in available_pathways: raise Exception('Unvailable Pathway. Please, introduce it manually. Networs available: "Gs", "Gi", "Gq".') + mypathway = importlib.import_module('.'+self._pathway, package='ssbtoolkit.pathways') + + #Get default pathway parameters + if self._DefaultPathwayParametersDataFrame.empty: + self._DefaultPathwayParametersDataFrame = pd.read_csv(pathways_path+'/{}_parameters.csv'.format(self._pathway)) + self._PathwayParameters = self._DefaultPathwayParametersDataFrame.set_index('Parameter').iloc[:,0].to_dict() + + elif self._DefaultPathwayParametersDataFrame.empty is False: + try: + #extract data from qgrid + newparameters = self._DefaultPathwayParametersTable.get_changed_df() + self._PathwayParameters = newparameters.set_index('Parameter').iloc[:,0].to_dict() + except: + self._PathwayParameters = self._DefaultPathwayParametersDataFrame.set_index('Parameter').iloc[:,0].to_dict() + + #Input + t = pl.geomspace(0.00001, self._ttotal, num=self._nsteps) # (a,b,c); a is the starting time ; b is the total time simulated ; c is the number of points + + #Output + simulation_data={} + + #Function + for ligand in self._antagonists: + ligand_name = os.path.splitext(ligand)[0] + data=[] + utils.PrintProgressBar(0, len(self._lig_conc_range), prefix = "{:<15}".format(ligand_name[:15]), suffix = 'Complete', length = 50) + + for idx in range(len(self._lig_conc_range)): + + ligand_conc = self._lig_conc_range[idx] + + #get LR conc + parameters = {**self._PathwayParameters, 'R_init':self._receptor_conc} + LR_conc_init = utils.CalcOccupancy(self._receptor_conc, self._agonist_submaximal_conc, ligand_conc, self._agonist_affinity, self._antagonists_affinities[self._antagonists.index(ligand)]) + mymodel = mypathway.network(LR=LR_conc_init, kinetics=False, **parameters) + simres = ScipyOdeSimulator(mymodel, tspan=t, compiler='cython').run() + yout = simres.all + + d1={'ligand_conc':ligand_conc, 'time':t } + + for idx2 in range(len(mypathway.list_of_observables)): + d2={mypathway.list_of_observables[idx2]:yout[mypathway.list_of_observables[idx2]]} + d1.update(d2) + data.append(d1) + utils.PrintProgressBar(idx + 1, len(self._lig_conc_range), prefix = "{:<15}".format(ligand_name[:15]), suffix = 'Complete', length = 50) + + simulation_data[ligand_name] = {'sim_data':data, + 'label':self._agonist+' + ' + ligand_name} + + self.simulation_data=simulation_data + return + + def Analysis(self): + ''' + This function calculates the dose-response effect. + + :return: instance processed_data + ''' + #dependencies + if self.simulation_data == None: raise TypeError('There is no simulation data. simulation.inhibition.run() must be run first.') + + # Define all the lists and dictionaries used in this function + raw_data=[] + normalized_data=[] + dose={} + + #defining concentration range + #defining concentration range + lig_conc_min = self._lig_conc_range.min() + lig_conc_max = self._lig_conc_range.max() + + #Main function + for ligand in self.simulation_data: + + #definig and dictionaries used in this loop: + raw_data_dict={} + normalized_data_dict={} + + # Calculate dose-response curve + #get metabolite concentration, rescale, and transform data if pathway/metabolite decrease + # metabolite_raw is not normalized + if self._pathway == 'Gi' or self._pathway == 'Gz(Gi)': + metabolite='cAMP' + metabolite_conc_raw=[] + for i in range(len(self._lig_conc_range)): + n=np.amax(self.simulation_data[ligand]['sim_data'][i]['obs_'+metabolite]) #cad + metabolite_conc_raw.append(n) + metabolite_conc_norm = minmax_scale(1-np.array(metabolite_conc_raw)) + elif self._pathway == 'Gs': + metabolite='cAMP' + metabolite_conc_raw=[] + for i in range(len(self._lig_conc_range)): + n=np.amax(self.simulation_data[ligand]['sim_data'][i]['obs_'+metabolite]) #cad + metabolite_conc_raw.append(n) + metabolite_conc_norm = minmax_scale(np.array(metabolite_conc_raw)) + elif self._pathway == 'Gq': + metabolite='IP3' + metabolite_conc_raw=[] + for i in range(len(self._lig_conc_range)): + n=np.amax(self.simulation_data[ligand]['sim_data'][i]['obs_'+metabolite]) #cad + metabolite_conc_raw.append(n) + metabolite_conc_norm = minmax_scale(np.array(metabolite_conc_raw)) + else: raise Exception('Unvailable Pathway. Please, introduce it manually. Networs available: "Gs", "Gi", "Gq".') + + + ## save results + raw_data_dict['x']=self._lig_conc_range + raw_data_dict['y']=metabolite_conc_raw + raw_data_dict['label']=self.simulation_data[ligand]['label'] + + normalized_data_dict['x']=self._lig_conc_range + normalized_data_dict['y']=metabolite_conc_norm + normalized_data_dict['label']=self.simulation_data[ligand]['label'] + + ## create a list of all data + raw_data.append(raw_data_dict) + normalized_data.append(normalized_data_dict) + + ##Fitting curve to the data + + def equation_dose(X, Bottom, Top, EC50, p): + return Bottom + (Top-Bottom)/(1+np.power((EC50/X),p)) + + popt_IC50, pcov = curve_fit(equation_dose, self._lig_conc_range, metabolite_conc_norm, bounds=([np.min(metabolite_conc_norm),-np.inf,-np.inf, 0.5],[np.inf,np.max(metabolite_conc_norm),np.inf, 2.5])) + + xfit_IC50 = np.geomspace(lig_conc_min, lig_conc_max, 50000) # These values are the same as the values for the simulation time and not ligand concentration + yfit_IC50 = equation_dose(xfit_IC50, *popt_IC50) + + fit_IC50={'x':xfit_IC50, 'y':yfit_IC50, 'label':self.simulation_data[ligand]['label']} + + dose[ligand] = {'raw_data': raw_data_dict, + 'normalized_data':normalized_data_dict , + 'fitted_data': fit_IC50, + 'IC50 (μM)': round(popt_IC50[2],5), + 'pIC50': round(-np.log10(popt_IC50[2]*1E-6),2)} + + self.processed_data=dose + return + + def ShowCurve(self, save=False, filename=None): + ''' + Plot the dose-response curve. + ''' + #dependencies + if self.processed_data == None: raise TypeError('Simulation data unprocessed. simulation.inhibition.analysis() must be run first.') + + import plotly + import plotly.graph_objs as go + import plotly.offline as pyoff + + colors = plotly.colors.DEFAULT_PLOTLY_COLORS + + plot_data=[] + + color_id=0 + for ligand in self.processed_data: + trace_norm = go.Scatter(x=self.processed_data[ligand]['normalized_data']['x'], + y=minmax_scale(self.processed_data[ligand]['normalized_data']['y'])*100 , + mode='markers', + showlegend=True, + name=self.processed_data[ligand]['normalized_data']['label'], + marker=dict(color=colors[color_id])) + plot_data.append(trace_norm) + + trace_fitted = go.Scatter(x=self.processed_data[ligand]['fitted_data']['x'], + y=minmax_scale(self.processed_data[ligand]['fitted_data']['y'])*100, + mode='lines', + showlegend=False, + name=self.processed_data[ligand]['fitted_data']['label'], + line=dict(color=colors[color_id])) + plot_data.append(trace_fitted) + color_id +=1 + + layout = dict(title = '', + xaxis = dict( + title = '[ligand] μM', + type ='log', + #range = [-4, 2], + exponentformat='e', + titlefont=dict( + size=20 + ), + tickfont=dict( + size=20 + )), + yaxis = dict( + title = '% Response', + #range = [0, 100], + titlefont=dict( + size=20), + tickfont=dict( + size=20) + + ), + legend=dict(font=dict(size=15)), + autosize=False, + width=850, + height=650, + ) + + fig = go.Figure(data=plot_data, layout=layout) + if save==True: + if filename==None: + filename='plot.html' + return pyoff.plot(fig, filename=filename) + else: + ext = os.path.splitext(filename)[-1] + if ext == '.png': fig.write_image(filename, scale=3) + elif ext == '.html': pyoff.plot(fig, filename=filename) + else: raise TypeError("extension not valid. Use png or html.") + elif save ==False: return fig + return + + def ShowPotency(self): + ''' + Return the potency values as a pandas DataFrame. + ''' + import pandas as pd + data = Simulation.Inhibition.PotencyToDict(self) + df = pd.DataFrame.from_dict(data, orient='index') + return df + + def PotencyToDict(self): + ''' + Convert potencies into a dictionary. + ''' + #dependencies + if self.processed_data == None: raise TypeError('Simulation data unprocessed. simulation.inhibition.analysis() must be run first.') + + kvalues={} + for ligand in self.processed_data: + IC50 = list(self.processed_data[ligand].keys())[-2] + IC50_value = self.processed_data[ligand][IC50] + pIC50 = list(self.processed_data[ligand].keys())[-1] + pIC50_value = self.processed_data[ligand][pIC50] + kvalues[ligand]={IC50:IC50_value, pIC50:pIC50_value} + return kvalues + + def PotencyToCSV(self, path): + ''' + Exports the potency values into csv format. + + :parameter path: Required (kwarg str): directory path to save the csv file + ''' + data = Simulation.Inhibition.PotencyToDict(self) + df = pd.DataFrame.from_dict(data, orient='index') + df.to_csv(path, index=False) + return + +class FitModel: + """ + Fit a model to experimental data. + + .. note:: This class was developed to reproduce data from a specific experimental setup. Please see tutorial 4 (OXTR pathay). Use carefully! + """ + def __init__(self): + + #fitting parameters + self._expratio = None + self._seed = None + self._maxiter = None + self._seed_incrementor = None + self._target_parameter = None + + #Pathway parameters + self._ttotal = None + self._nsteps = None + self._pathway = None + self._observable = None + self.pathway_parameters = {} + + def SetSimulationParameters(self, **kwargs): + """ + :parameter pathway_parameters: Required (kwargs): dict of pathway parameters + :parameter pathway: Required (kwargs str): name of the pathway ('Gs', 'Gi', 'Gq') + :parameter ttotal: Required (kwargs int): simulation time (seconds) + :parameter nsteps: Required (kwargs int): simulation time step + :parameter observable: Required (kwargs str): molecular specie to be measured + + :return: instances of all parameters + + """ + + if 'pathway_parameters' in kwargs: + self.pathway_parameters = kwargs.pop('pathway_parameters') + #print('pathway_parameters YES') + self._DefaultPathwayParametersDataFrame=pd.DataFrame() + if 'ttotal' in kwargs: + self._ttotal = int(kwargs.pop('ttotal')) + print('ttotal =', self._ttotal) + else: raise TypeError("ttotal undefined.") + + if 'nsteps' in kwargs: + self._nsteps = int(kwargs.pop('nsteps', 1000)) + print('nsteps =', self._nsteps) + + if 'pathway' in kwargs: + self._pathway = str(kwargs.pop('pathway')) + print('pathway ->', self._pathway) + else: raise TypeError("pathway undefined.") + + available_pathways = ['Gs', 'Gi', 'Gq', 'OXTR_pathway'] + if self._pathway == 'Gz(Gi)': self._pathway = 'Gi' + if self._pathway not in available_pathways: raise Exception('Unvailable Pathway. Please, introduce it manually. Networs available: "Gs", "Gi", "Gq".') + + + if'observable' in kwargs: + self._observable = str(kwargs.pop('observable')) + print('observable ->', self._observable) + else: raise TypeError("observable undefined.") + + return + + def PathwayParameters(self): + """ + Display table with default pathway parameters. + + .. warning:: this functions requires the qgrid library. It doens't work on Google Colab. + """ + import qgrid + self._DefaultPathwayParametersDataFrame = pd.read_csv(pathways_path+'/{}_parameters.csv'.format(self._pathway)) + + col_opts = { 'editable': False, 'sortable':False} + col_defs = {'Value': { 'editable': True, 'width': 150 }} + self._DefaultPathwayParametersTable = qgrid.show_grid(self._DefaultPathwayParametersDataFrame, column_options=col_opts,column_definitions=col_defs) + return self._DefaultPathwayParametersTable + + def UserPathwayParameters(self, path): + """ + Import user pathway parameters. + + :parameter path: Required (kwarg str): directory path + """ + import qgrid + self._DefaultPathwayParametersDataFrame = pd.read_csv(path) + col_opts = { 'editable': False, 'sortable':False} + col_defs = {'Value': { 'editable': True, 'width': 150 }} + self._DefaultPathwayParametersTable = qgrid.show_grid(self._DefaultPathwayParametersDataFrame, column_options=col_opts,column_definitions=col_defs) + return self._DefaultPathwayParametersTable + + def PathwayParametersToCSV(self, path): + """ + Export pathway parameters into CSV format. + + :parameter path: Required (kwarg str): directory path + """ + self._DefaultPathwayParametersTable.get_changed_df().to_csv(path, index=False) + print('saved in:', path) + return + + def Reactions(self): + """ + Display pathway reactions. + """ + from IPython.display import display, HTML + display(HTML("<style>.container {width:90% !important}</style>")) + return pd.read_csv(pathways_path+'/{}_reactions.csv'.format(self._pathway)) + + def Run(self, **kwargs): + """ + Fits of the model to experimental data. + + :parameter expratio: Required (kwargs flt): experimental signalling specie concentration ratio + :parameter target_parameter: Required (kwargs str):kinetic parameter to me modified + :parameter maxiter: Required (kwargs int): maximum number of iteration + :parameter seed: Required (kwargs flt): ramdom seed for scaling the modified parameter + :parameter seed_incrementor: Required (kwargs flt): seed incrementor (each iteration will increment the seed by this value) + :parameter seed_decrementor: Required (kwargs flt): seed decrementor (each iteration will decrement the seed by this value) + + """ + + from scipy.signal import find_peaks + import decimal + #fitting parameters + if 'expratio' in kwargs: + self._expratio = float(kwargs.pop('expratio')) + print('expratio =', self._expratio) + else: raise TypeError("exratio undefined.") + + if 'seed' in kwargs: + self._seed = float(kwargs.pop('seed')) + print('seed =', self._seed) + else: raise TypeError("seed undefined.") + + if 'maxiter' in kwargs: + self._maxiter = int(kwargs.pop('maxiter', 100)) + print('maxiter =', self._maxiter) + + if 'seed_incrementor' in kwargs: + self._seed_incrementor = float(kwargs.pop('seed_incrementor', 0.1)) + print('seed_incrementor =', self._seed_incrementor) + + if 'seed_decrementor' in kwargs: + self._seed_decrementor = float(kwargs.pop('seed_decrementor', 0.1)) + print('seed_decrementor =', self._seed_decrementor) + + if 'target_parameter' in kwargs: + self._target_parameter = str(kwargs.pop('target_parameter')) + print('target_parameter ->', self._target_parameter) + else: raise TypeError("target_parameter undefined.") + + + #Get default pathway parameters + if self._DefaultPathwayParametersDataFrame.empty and self.pathway_parameters==None: + self._DefaultPathwayParametersDataFrame = pd.read_csv(pathways_path+'/{}_parameters.csv'.format(self._pathway)) + self._PathwayParameters = self._DefaultPathwayParametersDataFrame.set_index('Parameter').iloc[:,0].to_dict() + + elif self._DefaultPathwayParametersDataFrame.empty is False and self.pathway_parameters is None: + try: + #extract data from qgrid + newparameters = self._DefaultPathwayParametersTable.get_changed_df() + self._PathwayParameters = newparameters.set_index('Parameter').iloc[:,0].to_dict() + except: + self._PathwayParameters = self._DefaultPathwayParametersDataFrame.set_index('Parameter').iloc[:,0].to_dict() + + elif self._DefaultPathwayParametersDataFrame.empty and self.pathway_parameters is not None: + self._DefaultPathwayParametersDataFrame = pd.read_csv(pathways_path+'/{}_parameters.csv'.format(self._pathway)) + self._PathwayParameters = {**self._DefaultPathwayParametersDataFrame.set_index('Parameter').iloc[:,0].to_dict(), **self.pathway_parameters} + + elif self._DefaultPathwayParametersDataFrame.empty is False and self.pathway_parameters is not None: + try: + #extract data from qgrid + newparameters = self._DefaultPathwayParametersTable.get_changed_df() + self._PathwayParameters = {**newparameters.set_index('Parameter').iloc[:,0].to_dict(), **self.pathway_parameters} + except: + self._PathwayParameters = {**self._DefaultPathwayParametersDataFrame.set_index('Parameter').iloc[:,0].to_dict(), **self.pathway_parameters} + + + + #simulation parameters: + if not self._ttotal: + raise TypeError("simulation parameters unknown. Set the the simulation parameters first wiht set_simulation_parameters()") + + #Main function + mypathway = importlib.import_module('.'+self._pathway, package='ssbtoolkit.pathways') + self.simtime = pl.geomspace(0.00001, self._ttotal, num=self._nsteps) + + #Simulation 1 + pathway_model = mypathway.network(LR=None, kinetics=True, **self._PathwayParameters) + sim1 = ScipyOdeSimulator(pathway_model, tspan=self.simtime,compiler='cython').run() + self.simres1 = sim1.all + + def calc_ratio(self): + + + #Simulation 2 + sim2 = ScipyOdeSimulator(mypathway.network(**self.new_pathway_parameters), tspan=self.simtime, compiler='cython').run() + self.simres2 = sim2.all + + #analysis + obs_name = 'obs_'+self._observable + obs_1 = self.simres1[obs_name] + obs_2 = self.simres2[obs_name] + + if 'time_in' in self.new_pathway_parameters: + self._time = np.take(self.simtime, np.where(self.simtime > int(self.new_pathway_parameters['time_in'])))[0] + obs_curve_1 = np.take(obs_1, np.where(self.simtime > int(self.new_pathway_parameters['time_in'])))[0] + obs_curve_2 = np.take(obs_2, np.where(self.simtime > int(self.new_pathway_parameters['time_in'])))[0] + + else: + self._time = np.take(self.simtime, np.where(self.simtime>0))[0] + obs_curve_1 = np.take(obs_1, np.where(self.simtime > 0))[0] + obs_curve_2 = np.take(obs_2, np.where(self.simtime > 0))[0] + + obs_peaks_1, _ = find_peaks(obs_curve_1) + obs_peaks_2, _ = find_peaks(obs_curve_2) + + vmax_obs_curve_1 = obs_curve_1[obs_peaks_1][-1]*1E3 + vmax_obs_curve_2 = obs_curve_2[obs_peaks_2][-1]*1E3 + + obs_ratio = round(vmax_obs_curve_2/vmax_obs_curve_1, abs(decimal.Decimal(str(self._expratio).rstrip('0')).as_tuple().exponent)) + + self._obs_curve_1=obs_curve_1 + self._obs_curve_2=obs_curve_2 + self._obs_peaks_1=obs_peaks_1 + self._obs_peaks_2=obs_peaks_2 + self._vmax_obs_curve_1=vmax_obs_curve_1 + self._vmax_obs_curve_2=vmax_obs_curve_2 + + return obs_ratio + + self._iteration=1 + print('\n') + + self._lst_ratio=[] + self._lst_seed=[] + + for idx in range(self._maxiter): + + prefix = 'iteration' + iteration_n = str(self._iteration) + print(f'\r{prefix} {iteration_n}', end='\r') + + self.new_pathway_parameters={**self._PathwayParameters, **{self._target_parameter:mypathway.defaultParameters[self._target_parameter]*self._seed}} + self.obs_ratio = calc_ratio(self) + + if self.obs_ratio == self._expratio: + self._lst_ratio.append(self.obs_ratio) + self._lst_seed.append(self._seed) + self._fold=round(self._seed, abs(decimal.Decimal(str(self._expratio).rstrip('0')).as_tuple().exponent)) + print('\n\nDONE!\n', '\nRatio: '+str(self.obs_ratio), '\nFOLD: '+str(self._fold), '\nNumber of iterations: '+str(self._iteration)) + break + elif self.obs_ratio < self._expratio: + + self._lst_ratio.append(self.obs_ratio) + self._lst_seed.append(self._seed) + self._iteration+=1 + self._seed += self._seed_incrementor + + else: + self._lst_ratio.append(self.obs_ratio) + self._lst_seed.append(self._seed) + + + self._iteration+=1 + self._seed -= self._seed_decrementor + + return + + def PlotIterations(self, save=False, filename=None): + ''' + Plot iterations. + ''' + import plotly.offline as pyoff + #dependencies + if self._iteration == None: raise TypeError('Simulation data not exist. simulation.fitModel.run() must be run first.') + + #import plotly + import plotly.graph_objs as go + + iterations = np.arange(1,self._iteration+1) + + trace=dict(type='scatter', x=self._lst_seed, y=self._lst_ratio, mode='markers', + marker=dict(color= iterations, colorscale='Bluered_r', size=14, colorbar=dict(thickness=20, title='iteration number'))) + #axis_style=dict(zeroline=False, showline=True, mirror=True) + layout = dict(title = '', + xaxis = dict( + title = 'seed', + titlefont=dict( + size=20 + ), + tickfont=dict( + size=20 + )), + yaxis = dict( + title = '['+self._observable+']' + ' ratio', + titlefont=dict( + size=20), + tickfont=dict( + size=20) + + ), + legend=dict(font=dict(size=15)), + autosize=False, + width=850, + height=650 + ) + + fig = go.Figure(data=[trace], layout=layout) + if save==True: + if filename==None: + filename='plot.html' + return pyoff.plot(fig, filename=filename) + else: + ext = os.path.splitext(filename)[-1] + if ext == '.png': fig.write_image(filename, scale=3) + elif ext == '.html': pyoff.plot(fig, filename=filename) + else: raise TypeError("extension not valid. Use png or html.") + elif save ==False: return fig + return fig + + def ShowGraphs(self, save=False, filename=None): + ''' + Plot the amount of obeservable in function of time, Amplitude, Area Under the Curve, and Full Width at Half Maximum. + + :parameter save: Optional (kwarg boolean): default False + :parameter filename: Optional (kwarg str) + ''' + + from IPython.core.display import display, HTML + display(HTML("<style>.container { width:90% !important; }</style>")) + + + from plotly.subplots import make_subplots + from scipy.signal import peak_widths + from sklearn import metrics + import plotly.offline as pyoff + + + half_1 = peak_widths(self._obs_curve_1, self._obs_peaks_1, rel_height=0.5) + half_2 = peak_widths(self._obs_curve_2, self._obs_peaks_2, rel_height=0.5) + fwhm_1 = self._time[int(half_1[3])]-self._time[int(half_1[2])] + fwhm_2 = self._time[int(half_2[3])]-self._time[int(half_2[2])] + + + fig = make_subplots(rows=2, cols=2,vertical_spacing=0.15, + subplot_titles=("{} concentration".format(self._observable), "Amplitude", "Area under the curve", "Full Width at Half Maximum")) + + #################### + #### MAIN PLOT #### + #################### + fig.add_trace(go.Scatter(x=self._time, y=self._obs_curve_1*1E3, name='control'), row=1, col=1) + fig.add_trace(go.Scatter(x=self._time, y=self._obs_curve_2*1E3, name='{}-fold'.format(self._fold)), row=1, col=1) + fig.add_trace(go.Scatter(x=self._time[self._obs_peaks_1], y=self._obs_curve_1[self._obs_peaks_1]*1E3, + name='max value', showlegend=False, mode='markers', + marker=dict(symbol='x', size=13, color='Black')), row=1,col=1) + fig.add_trace(go.Scatter(x=self._time[self._obs_peaks_2], y=self._obs_curve_2[self._obs_peaks_2]*1E3, + name='max value', showlegend=False, mode='markers', + marker=dict(symbol='x', size=13, color='Black')), row=1,col=1) + fig.add_shape(type='line', x0=self._time[int(half_1[2])],y0=half_1[1][0]*1E3, x1=self._time[int(half_1[3])], y1=half_1[1][0]*1E3, + line=dict(color='Blue',dash='dash'),xref='x',yref='y', row=1, col=1) + fig.add_shape(type='line', x0=self._time[int(half_2[2])],y0=half_2[1][0]*1E3, x1=self._time[int(half_2[3])], y1=half_2[1][0]*1E3, + line=dict(color='Red',dash='dash'),xref='x',yref='y', row=1, col=1) + + # Update xaxis properties + fig.update_xaxes(title_text="Time (s)", showgrid=False, row=1, col=1, titlefont=dict(size=18), + linecolor='black', linewidth=2, + ticks='inside', tickfont=dict(size=18), tickcolor='black', ticklen=10, tickwidth=2) + + fig.update_yaxes(title_text=self._observable+' (nM)', titlefont=dict(size=18), showgrid=False, row=1, col=1, + linecolor='black', linewidth=2, + ticks='inside', tickfont=dict(size=18), tickcolor='black', ticklen=10, tickwidth=2) + + + #################### + #### AMPLITUDE #### + #################### + + AMP_labels = [1,2] + AMP_values = [self._vmax_obs_curve_1, self._vmax_obs_curve_2] + fig.add_trace(go.Bar(x=AMP_labels,y=AMP_values, width = [0.35,0.35], showlegend=False, marker_color='black', name=''), row=1, col=2 ) + + + + # Update xaxis properties + fig.update_xaxes(row=1, col=2, showgrid=False, linecolor='black', linewidth=2, range=[0,3], + tickmode='array', tickvals=[1,2], ticktext=['control', '{}-fold'.format(self._fold)], tickfont=dict(size=18)) + + fig.update_yaxes(showgrid=False, range=[round((min(AMP_values)-min(AMP_values)*0.5)/5)*5,round((max(AMP_values)+max(AMP_values)*0.5)/5)*5 ], row=1, col=2, + title_text=self._observable+' (nM)', titlefont=dict(size=18), + linecolor='black', linewidth=2, ticks='inside', ticklen=10, tickwidth=2, tickfont=dict(size=18)) + + # Add diff lines + AMP_diffs = [max(AMP_values) - v for v in AMP_values] + AMP_diff_labels = dict(zip(AMP_labels, AMP_diffs)) + fig.add_trace(go.Scatter(name='',x=[1,1.5,2], y=[max(AMP_values)+(max(AMP_values)*0.3)]*3, mode = 'lines+text',showlegend=False, line=dict(color='black', width=1),text=['', 'diff. = {} nM'.format(round(AMP_diffs[0], 3)),''], textposition='top center'), row=1, col=2) + fig.add_trace(go.Scatter(name='',x=[AMP_labels[0]-0.175, AMP_labels[0]+0.175], y=[AMP_values[0]+(AMP_values[0]*0.03)]*2, mode = 'lines',showlegend=False, line=dict(color='black', width=1)), row=1, col=2) + fig.add_trace(go.Scatter(name='',x=[AMP_labels[1]-0.175, AMP_labels[1]+0.175], y=[AMP_values[1]+(AMP_values[1]*0.03)]*2, mode = 'lines',showlegend=False, line=dict(color='black', width=1)), row=1, col=2) + fig.add_trace(go.Scatter(name='',x=[AMP_labels[0], AMP_labels[0]], y=[AMP_values[0]+(AMP_values[0]*0.03), max(AMP_values)+(max(AMP_values)*0.3)], mode = 'lines',showlegend=False, line=dict(color='black', width=1)), row=1, col=2) + fig.add_trace(go.Scatter(name='',x=[AMP_labels[1], AMP_labels[1]], y=[AMP_values[1]+(AMP_values[1]*0.03), max(AMP_values)+(max(AMP_values)*0.3)], mode = 'lines',showlegend=False, line=dict(color='black', width=1)), row=1, col=2) + + + #################### + #### AUC #### + #################### + + # Data + AUC_labels = [1,2] + AUC_values = [round(metrics.auc(self._time, self._obs_curve_1),2), round(metrics.auc(self._time, self._obs_curve_2),2)] + fig.add_trace(go.Bar(x=AUC_labels,y=AUC_values, width = [0.35,0.35], showlegend=False, marker_color='black', name=''), row=2, col=1 ) + + # Update xaxis properties + fig.update_xaxes(row=2, col=1, tickmode='array', showgrid=False, range=[0,3], linecolor='black', linewidth=2, + tickvals=[1,2], ticktext=['control', '{}-fold'.format(self._fold)], tickfont=dict(size=18)) + + fig.update_yaxes(row=2, col=1,showgrid=False, title_text=self._observable+' (nM)', range=[round((min(AUC_values)-min(AUC_values)*0.5)/5)*5,round((max(AUC_values)+max(AUC_values)*0.5)/5)*5], + titlefont=dict(size=18),linecolor='black', linewidth=2, + ticks='inside', tickfont=dict(size=18),ticklen=10, tickwidth=2) + + # Add diff lines + AUC_diffs = [max(AUC_values) - v for v in AUC_values] + AUC_diff_labels = dict(zip(AUC_labels, AUC_diffs)) + fig.add_trace(go.Scatter(name='',x=[1,1.5,2], y=[max(AUC_values)+(max(AUC_values)*0.3)]*3, mode = 'lines+text',showlegend=False, + line=dict(color='black', width=1), text=['', 'diff. = {} nM'.format(round(AUC_diffs[0], 3)),''], textposition='top center'), row=2, col=1) + fig.add_trace(go.Scatter(name='',x=[AUC_labels[0]-0.175, AUC_labels[0]+0.175], y=[AUC_values[0]+(AUC_values[0]*0.03)]*2, mode = 'lines',showlegend=False, line=dict(color='black', width=1)), row=2, col=1) + fig.add_trace(go.Scatter(name='',x=[AUC_labels[1]-0.175, AUC_labels[1]+0.175], y=[AUC_values[1]+(AUC_values[1]*0.03)]*2, mode = 'lines',showlegend=False, line=dict(color='black', width=1)), row=2, col=1) + fig.add_trace(go.Scatter(name='',x=[AUC_labels[0], AUC_labels[0]], y=[AUC_values[0]+(AUC_values[0]*0.03), max(AUC_values)+(max(AUC_values)*0.3)], mode = 'lines',showlegend=False, line=dict(color='black', width=1)), row=2, col=1) + fig.add_trace(go.Scatter(name='',x=[AUC_labels[1], AUC_labels[1]], y=[AUC_values[1]+(AUC_values[1]*0.03), max(AUC_values)+(max(AUC_values)*0.3)], mode = 'lines',showlegend=False, line=dict(color='black', width=1)), row=2, col=1) + + + #################### + #### FWHM #### + #################### + # Data + FWHM_labels = [1,2] + FWHM_values = [fwhm_1, fwhm_2] + fig.add_trace(go.Bar(x=FWHM_labels,y=FWHM_values, width = [0.35,0.35], showlegend=False,marker_color='black', name=''), row=2, col=2 ) + + # Update xaxis properties + fig.update_xaxes(row=2, col=2, showgrid=False, range=[0,3], linecolor='black', linewidth=2, + tickmode='array', tickvals=[1,2], ticktext=['control', '{}-fold'.format(self._fold)], tickfont=dict(size=18)) + + fig.update_yaxes(row=2, col=2, showgrid=False, range=[self.pathway_parameters['time_in'],round((max(FWHM_values)+(max(FWHM_values)-self.pathway_parameters['time_in'])*0.5)/5)*5], + title_text='Time (s)', titlefont=dict(size=18), linecolor='black', linewidth=2, + ticks='inside', ticklen=10, tickwidth=2, tickfont=dict(size=18)) + + # Add diff lines + FWHM_diffs = [max(FWHM_values) - v for v in FWHM_values] + FWHM_diff_labels = dict(zip(FWHM_labels, FWHM_diffs)) + line_height = max(FWHM_values)+((max(FWHM_values)-self.pathway_parameters['time_in'])*0.30) + fig.add_trace(go.Scatter(x=[1,1.5,2], y=[line_height]*3, mode = 'lines+text',showlegend=False, line=dict(color='black', width=1),name='', + text=['', 'diff. = {} s'.format(round(FWHM_diffs[0], 3)),''], textposition='top center'), row=2, col=2) + fig.add_trace(go.Scatter(name='',x=[FWHM_labels[0]-0.175, FWHM_labels[0]+0.175], y=[FWHM_values[0]+(FWHM_values[0]*0.005)]*2, mode = 'lines',showlegend=False, line=dict(color='black', width=1)), row=2, col=2) + fig.add_trace(go.Scatter(name='',x=[FWHM_labels[1]-0.175, FWHM_labels[1]+0.175], y=[FWHM_values[1]+(FWHM_values[1]*0.005)]*2, mode = 'lines',showlegend=False, line=dict(color='black', width=1)), row=2, col=2) + fig.add_trace(go.Scatter(name='',x=[FWHM_labels[0], FWHM_labels[0]], y=[FWHM_values[0]+(FWHM_values[0]*0.005), line_height], mode = 'lines',showlegend=False, line=dict(color='black', width=1)), row=2, col=2) + fig.add_trace(go.Scatter(name='',x=[FWHM_labels[1], FWHM_labels[1]], y=[FWHM_values[1]+(FWHM_values[1]*0.005), line_height], mode = 'lines',showlegend=False, line=dict(color='black', width=1)), row=2, col=2) + + + #################### + #### FIGURE #### + #################### + + fig.update_layout(height=1200, width=1300, title_text="", plot_bgcolor='white',showlegend=True, + legend=dict(yanchor="top", x=0.3, y=.99,font=dict(family="sans-serif", size=14,color="black"))) + fig.update_annotations(font_size=20, font_color='black') + + if save==True: + if filename==None: + filename='plot.html' + return pyoff.plot(fig, filename=filename) + else: + ext = os.path.splitext(filename)[-1] + if ext == '.png': fig.write_image(filename, scale=3) + elif ext == '.html': pyoff.plot(fig, filename=filename) + else: raise TypeError("extension not valid. Use png or html.") + elif save ==False: return fig + + return + + diff --git a/ssbtoolkit/__init__.py b/ssbtoolkit/__init__.py deleted file mode 100644 index 4e42005..0000000 --- a/ssbtoolkit/__init__.py +++ /dev/null @@ -1,1414 +0,0 @@ -import pkgutil - -__all__ = [] -for loader, module_name, is_pkg in pkgutil.walk_packages(__path__): - __all__.append(module_name) - _module = loader.find_module(module_name).load_module(module_name) - globals()[module_name] = _module - - -import sys, os, warnings -sys.path.insert(0, os.path.abspath(os.path.split(os.path.realpath(__file__))[0])) -warnings.simplefilter(action='ignore') - - - - -import platform, site -distpath = site.getsitepackages()[0] -if platform.system() == 'Linux': - BioNetGen=os.path.join(distpath, 'bionetgen/bng-linux:') -elif platform.system() == 'Darwin': - BioNetGen=os.path.join(distpath, 'bionetgen/bng-mac:') -elif platform.system()=='Windows': - BioNetGen=os.path.join(distpath, 'bionetgen/bng-win:') -else: - raise ValueError('BioNetGen error. Platform unknown! The pygomodo was tested in Linux and Darwin (Mac) platforms.') -os.environ['PATH']=BioNetGen+os.environ['PATH'] - - -#LIBRARIES -import importlib -import pylab as pl -from pysb.simulator import ScipyOdeSimulator -from scipy.optimize import curve_fit -from sklearn.preprocessing import minmax_scale -import plotly.graph_objs as go -import pandas as pd -import numpy as np -import ssbtoolkit.Utils as utils - -pathways_path=(os.path.join(os.path.split(os.path.realpath(__file__))[0], 'pathways')) - -######MAIN CODE##### -class Binding: - """This class simulate ligand-target binding curves.""" - def __init__(self): - self.receptor_conc = None - self.lig_conc_range = None - self.pKd = None - self.submax_concentration = None - - def Bind(self, **kwargs): - """ - Applies an function to calculate the fraction of occupited receptors at equilibrium. - - :parameter receptor_conc: Required (kwarg flt): concentration of receptor - :parameter lig_conc_range: Required (kwarg array): array of range of ligand concentration - :parameter pKd: Required (kwarg flt): pKd value of the ligand - """ - - if 'receptor_conc' not in kwargs: raise TypeError("ERROR: receptor_conc is missing") - if 'lig_conc_range' not in kwargs: raise TypeError("ERROR: lig_conc_range is missing") - if 'pKd' not in kwargs: raise TypeError("ERROR: pKd is missing") - - self._receptor_conc = kwargs.pop('receptor_conc') - self._lig_conc_range = kwargs.pop('lig_conc_range') - self._pKd = kwargs.pop('pKd') - - binding_data=[] - for conc in self._lig_conc_range: - binding_data.append(utils.CalcOccupancy(self._receptor_conc, conc, 0, self._pKd, 0)) - self.binding_data=binding_data - return self.binding_data - - def SubMaxConcentration(self): - """ - Calculates the maximum bending point of a sigmoid-shaped curve according to the mathod of Sebaugh et al., 2003. - - :parameter drug_receptor: Required (int): concentration of the receptor - :parameter lig_conc_range: Required (array): array of a range of ligand concentration - :return: instance .submax_concentration (flt) - - .. note:: The minimization uses the Nelder-Mead method. - """ - - from scipy.optimize import curve_fit, minimize - - - def sigmoid(X, Bottom, Top, Kd, p): - return Bottom + (Top-Bottom)/(1+np.power((Kd/X),p)) - - xfit = np.geomspace(np.min(self._lig_conc_range), np.max(self._lig_conc_range), 50000) #warning: shoud this be the minimum and maximum of concentration - popt, pcov = curve_fit(sigmoid, self._lig_conc_range, self.binding_data, bounds=([np.min(self.binding_data),-np.inf,-np.inf, 0.5],[np.inf,np.max(self.binding_data),np.inf, 2.5])) - - - def sigmoid_deriv_b(x, a,d,c,b): - return (x/c)**b*(a - d)*np.log(x/c)/((x/c)**b + 1)**2 - - min_value = minimize(sigmoid_deriv_b, np.max(xfit), args=(popt[0],popt[1],popt[2],popt[3]), method = 'Nelder-Mead') - - self.submax_concentration = round(min_value.x[0],3) - return self.submax_concentration - - def ShowCurve(self): - """ - Plots ligand-target binding curve - """ - - #import plotly - import plotly.graph_objs as go - from scipy.optimize import curve_fit - from sklearn.preprocessing import minmax_scale - - ##Fitting curve to the data - - yy = minmax_scale(self.binding_data)*100 - - def equation_dose(X, Bottom, Top, EC50, p): - return Bottom + (Top-Bottom)/(1+np.power((EC50/X),p)) - - popt, pcov = curve_fit(equation_dose, self._lig_conc_range, yy, bounds=([np.min(yy),-np.inf,-np.inf, 0.5],[np.inf,np.max(yy),np.inf, 2.5])) - - xfit = np.geomspace(np.min(self._lig_conc_range), np.max(self._lig_conc_range), 50000) # These values are the same as the values for the simulation time and not ligand concentration - yfit = minmax_scale(equation_dose(xfit, *popt))*100 - - - - trace1 = go.Line(x=xfit, y=yfit, showlegend=False, name='radioligand') - if self.submax_concentration: - xsubmaximal = np.array(self.submax_concentration) - ysubmaximal = np.array(equation_dose(xsubmaximal, *popt)) - trace2 = go.Scatter(x=xsubmaximal, y=ysubmaximal, showlegend=True, mode='markers', name='submaximal ({} μM)'.format(xsubmaximal), - marker=dict(size=14)) - else: trace2=[] - - - layout = dict(title = '', - xaxis = dict( - title = '[ligand] μM', - type ='log', - exponentformat='e', - titlefont=dict( - size=20 - ), - tickfont=dict( - size=20 - )), - yaxis = dict( - title = '% occupied receptors', - - titlefont=dict( - size=20), - tickfont=dict( - size=20) - - ), - legend=dict(font=dict(size=15)), - autosize=False, - width=850, - height=650, - ) - if self.submax_concentration: - fig = go.Figure(data=[trace1, trace2], layout=layout) - else: - fig = go.Figure(data=[trace1], layout=layout) - return fig - -class Simulation: - """ - This class simulates the mathematical models of the signaling pathways. - """ - class Activation: - """ - Simulation of the activation of signaling pathways (i.e. activation by agonists) - """ - def __init__(self): - self._ligands=None - self._affinities=None - self._pathway=None - self._receptor_conc=None - self._lig_conc_range=None - self._ttotal=None - self._nsteps=None - self._binding_kinetics=True - self._binding_kinetic_parameters=None - self.simulation_data=None - self.processed_data=None - - def SetSimulationParameters(self, **kwargs): - """ - :parameter ligands: Required (kwargs list): list of ligands' names (str) - :parameter affinities: Required (kwargs list): list of pKd values (flt) - :parameter pathway: Required (kwargs str): name of the pathway ('Gs', 'Gi', 'Gq') - :parameter receptor_conc: Required (kwargs flt): receptors concentration (nM) - :parameter lig_conc_range: Required (kwargs array): range of ligands' concentration - :parameter ttotal: Required (kwargs int): simulation time (seconds) - :parameter nsteps: Required (kwargs int): simulation time step - :parameter binding_kinetics: Optional (kwargs boolean): default (False) - - - .. warning:: the order of the lists of ligands names and affinities list must be the same. - - """ - self._ligands= kwargs.pop('ligands') - if 'affinities' in kwargs: - self._affinities=kwargs.pop('affinities') - self._pathway=kwargs.pop('pathway') - self._receptor_conc=kwargs.pop('receptor_conc') - self._lig_conc_range=kwargs.pop('lig_conc_range') - self._ttotal=kwargs.pop('ttotal') - self._nsteps=kwargs.pop('nsteps') - self._binding_kinetics=kwargs.pop('binding_kinetics') - if 'binding_kinetic_parameters' in kwargs:self._binding_kinetic_parameters=kwargs.pop('binding_kinetic_parameters') - self._DefaultPathwayParametersDataFrame=pd.DataFrame() - - return - - def PathwayParameters(self): - """ - Display table with default pathway parameters. - - .. warning:: this functions requires the qgrid library. It doens't work on Google Colab. - """ - import qgrid - self._DefaultPathwayParametersDataFrame = pd.read_csv(pathways_path+'/{}_parameters.csv'.format(self._pathway)) - - col_opts = { 'editable': False, 'sortable':False} - col_defs = {'Value': { 'editable': True, 'width': 150 }} - self._DefaultPathwayParametersTable = qgrid.show_grid(self._DefaultPathwayParametersDataFrame, column_options=col_opts,column_definitions=col_defs) - return self._DefaultPathwayParametersTable - - def UserPathwayParameters(self, path): - """ - Import user pathway parameters. - - :parameter path: Required (kwarg str): directory path - """ - import qgrid - self._DefaultPathwayParametersDataFrame = pd.read_csv(path) - col_opts = { 'editable': False, 'sortable':False} - col_defs = {'Value': { 'editable': True, 'width': 150 }} - self._DefaultPathwayParametersTable = qgrid.show_grid(self._DefaultPathwayParametersDataFrame, column_options=col_opts,column_definitions=col_defs) - return self._DefaultPathwayParametersTable - - def PathwayParametersToCSV(self, path): - """ - Export pathway parameters into CSV format. - - :parameter path: Required (kwarg str): directory path - """ - self._DefaultPathwayParametersTable.get_changed_df().to_csv(path, index=False) - print('saved in:', path) - return - - def Reactions(self): - """ - Display pathway reactions. - """ - from IPython.display import display, HTML - display(HTML("<style>.container {width:90% !important}</style>")) - return pd.read_csv(pathways_path+'/{}_reactions.csv'.format(self._pathway)) - - def Run(self): - ''' - This function runs the pathway simulation and returns the raw simulation data. - ''' - - #Check inputs - if self._ligands==None: raise TypeError("ligands list undefined.") - elif self._pathway==None: raise TypeError("pathway name undefined.") - elif self._binding_kinetics==False and self._affinities==None: raise TypeError("affinity_values_dict undefined.") - elif self._binding_kinetics==True and self._affinities==None: pass - elif self._lig_conc_range.any() == False: raise TypeError("lig_conc_range undefined.") - elif self._ttotal==None: raise TypeError("ttotal undefined.") - elif self._nsteps==None: raise TypeError("nsteps undefined.") - elif self._receptor_conc==None: raise TypeError("receptor_conc undefined.") - else: pass - - - #Check Pathway availability and import it - available_pathways = ['Gs', 'Gi', 'Gq'] - if self._pathway == 'Gz(Gi)': self._pathway = 'Gi' - if self._pathway not in available_pathways: raise Exception('Unvailable Pathway. Please, introduce it manually. Pathways available: "Gs", "Gi", "Gq".') - mypathway = importlib.import_module('.'+self._pathway, package='ssbtoolkit.pathways') - - #Get default pathway parameters - if self._DefaultPathwayParametersDataFrame.empty and self._binding_kinetic_parameters==None: - self._DefaultPathwayParametersDataFrame = pd.read_csv(pathways_path+'/{}_parameters.csv'.format(self._pathway)) - self._PathwayParameters = self._DefaultPathwayParametersDataFrame.set_index('Parameter').iloc[:,0].to_dict() - - elif self._DefaultPathwayParametersDataFrame.empty is False and self._binding_kinetic_parameters is None: - try: - #extract data from qgrid - newparameters = self._DefaultPathwayParametersTable.get_changed_df() - self._PathwayParameters = newparameters.set_index('Parameter').iloc[:,0].to_dict() - except: - self._PathwayParameters = self._DefaultPathwayParametersDataFrame.set_index('Parameter').iloc[:,0].to_dict() - - elif self._DefaultPathwayParametersDataFrame.empty and self._binding_kinetic_parameters is not None: - self._DefaultPathwayParametersDataFrame = pd.read_csv(pathways_path+'/{}_parameters.csv'.format(self._pathway)) - - elif self._DefaultPathwayParametersDataFrame.empty is False and self._binding_kinetic_parameters is not None: - try: - #extract data from qgrid - newparameters = self._DefaultPathwayParametersTable.get_changed_df() - self._PathwayParameters = {**newparameters.set_index('Parameter').iloc[:,0].to_dict(), **self._binding_kinetic_parameters} - except: - self._PathwayParameters = {**self._DefaultPathwayParametersDataFrame.set_index('Parameter').iloc[:,0].to_dict(), **self._binding_kinetic_parameters} - - #Input - t = pl.geomspace(0.00001, self._ttotal, num=self._nsteps) # (a,b,c); a is the starting time ; b is the total time simulated ; c is the number of points - - - #Output - simulation_data={} - - #Function - for ligand in self._ligands: - ligand_name = os.path.splitext(str(ligand))[0] - data=[] - utils.PrintProgressBar(0, len(self._lig_conc_range), prefix = "{:<15}".format(ligand_name[:15]), suffix = 'Complete', length = 50) - - ###DANGER ZONE### - if self._binding_kinetic_parameters is not None: - self._PathwayParameters = {**self._DefaultPathwayParametersDataFrame.set_index('Parameter').iloc[:,0].to_dict(), **self._binding_kinetic_parameters[self._ligands.index(ligand)]} - ###################### - - for idx in range(len(self._lig_conc_range)): - - ligand_conc = self._lig_conc_range[idx] - if self._binding_kinetics == False: - #get LR conc - parameters = {**self._PathwayParameters, 'R_init':self._receptor_conc} - LR_conc_init = utils.CalcOccupancy(self._receptor_conc, ligand_conc, 0, self._affinities[self._ligands.index(ligand)], 0) - mymodel = mypathway.network(LR=LR_conc_init, kinetics=False, **parameters) - simres = ScipyOdeSimulator(mymodel, tspan=t, compiler='cython').run() - yout = simres.all - - elif self._binding_kinetics == True: - parameters={**self._PathwayParameters,'R_init':self._receptor_conc, 'L_init':self._lig_conc_range[idx] } - mymodel = mypathway.network(kinetics=True, **parameters) - simres = ScipyOdeSimulator(mymodel, tspan=t, compiler='cython').run() - yout = simres.all - - d1={'ligand_conc':ligand_conc, 'time':t } - - for idx2 in range(len(mypathway.list_of_observables)): - d2={mypathway.list_of_observables[idx2]:yout[mypathway.list_of_observables[idx2]]} - d1.update(d2) - data.append(d1) - utils.PrintProgressBar(idx + 1, len(self._lig_conc_range), prefix = "{:<15}".format(ligand_name[:15]), suffix = 'Complete', length = 50) - - - simulation_data[ligand_name] = {'sim_data':data, - 'label':ligand_name,} - self.simulation_data = simulation_data - return - - def Analysis(self): - ''' - This function calculates the dose-response effect. - - :return: instance of processed_data - ''' - - if self.simulation_data == None: raise TypeError('There is no simulation data. simulation.activation.run() must be run first.') - - # Define all the lists and dictionaries used in this function - raw_data=[] - normalized_data=[] - dose={} - - #defining concentration range - lig_conc_min = self._lig_conc_range.min() - lig_conc_max = self._lig_conc_range.max() - - #Main function - for ligand in self.simulation_data: - - #definig and dictionaries used in this loop: - raw_data_dict={} - normalized_data_dict={} - - # Calculate dose-response curve - #get metabolite concentration, rescale, and transform data if pathway/metabolite decrease - # metabolite_raw is not normalized - if self._pathway == 'Gi' or self._pathway == 'Gz(Gi)': - metabolite='cAMP' - metabolite_conc_raw=[] - for i in range(len(self._lig_conc_range)): - n=np.amax(self.simulation_data[ligand]['sim_data'][i]['obs_'+metabolite]) #cad - metabolite_conc_raw.append(n) - metabolite_conc_norm = minmax_scale(1-np.array(metabolite_conc_raw)) - elif self._pathway == 'Gs': - metabolite='cAMP' - metabolite_conc_raw=[] - for i in range(len(self._lig_conc_range)): - n=np.amax(self.simulation_data[ligand]['sim_data'][i]['obs_'+metabolite]) #cad - metabolite_conc_raw.append(n) - metabolite_conc_norm = minmax_scale(np.array(metabolite_conc_raw)) - elif self._pathway == 'Gq': - metabolite='IP3' - metabolite_conc_raw=[] - for i in range(len(self._lig_conc_range)): - n=np.amax(self.simulation_data[ligand]['sim_data'][i]['obs_'+metabolite]) #cad - metabolite_conc_raw.append(n) - metabolite_conc_norm = minmax_scale(np.array(metabolite_conc_raw)) - else: raise Exception('Unvailable Pathway. Please, introduce it manually. Networs available: "Gs", "Gi", "Gq".') - - - ## save results - raw_data_dict['x']=self._lig_conc_range - raw_data_dict['y']=metabolite_conc_raw - raw_data_dict['label']=self.simulation_data[ligand]['label'] - - normalized_data_dict['x']=self._lig_conc_range - normalized_data_dict['y']=metabolite_conc_norm - normalized_data_dict['label']=self.simulation_data[ligand]['label'] - - ## create a list of all data - raw_data.append(raw_data_dict) - normalized_data.append(normalized_data_dict) - - ##Fitting curve to the data - - def equation_dose(X, Bottom, Top, EC50, p): - return Bottom + (Top-Bottom)/(1+np.power((EC50/X),p)) - - popt_EC50, pcov = curve_fit(equation_dose, self._lig_conc_range, metabolite_conc_norm, bounds=([np.min(metabolite_conc_norm),-np.inf,-np.inf, 0.5],[np.inf,np.max(metabolite_conc_norm),np.inf, 2.5])) - - xfit_EC50 = np.geomspace(lig_conc_min, lig_conc_max, 50000) # These values are the same as the values for the simulation time and not ligand concentration - yfit_EC50 = equation_dose(xfit_EC50, *popt_EC50) - - fit_EC50={'x':xfit_EC50, 'y':yfit_EC50, 'label':self.simulation_data[ligand]['label']} - - dose[ligand] = {'raw_data': raw_data_dict, - 'normalized_data':normalized_data_dict , - 'fitted_data': fit_EC50, - 'EC50 (μM)': round(popt_EC50[2],5), - 'pEC50': round(-np.log10(popt_EC50[2]*1E-6),2)} - - self.processed_data=dose - return - - def ShowCurve(self, save=False, filename=None): - ''' - Plots the dose-response curve. - ''' - - if self.simulation_data == None: raise TypeError('There is no simulation data. simulation.activation.run() must be run first.') - - import plotly - import plotly.graph_objs as go - import plotly.offline as pyoff - - colors = plotly.colors.DEFAULT_PLOTLY_COLORS - - plot_data=[] - - color_id=0 - for ligand in self.processed_data: - trace_norm = go.Scatter(x=self.processed_data[ligand]['normalized_data']['x'], - y=minmax_scale(self.processed_data[ligand]['normalized_data']['y'])*100 , - mode='markers', - showlegend=True, - name=self.processed_data[ligand]['normalized_data']['label'], - marker=dict(color=colors[color_id])) - plot_data.append(trace_norm) - - trace_fitted = go.Scatter(x=self.processed_data[ligand]['fitted_data']['x'], - y=minmax_scale(self.processed_data[ligand]['fitted_data']['y'])*100, - mode='lines', - showlegend=False, - name=self.processed_data[ligand]['fitted_data']['label'], - line=dict(color=colors[color_id])) - plot_data.append(trace_fitted) - color_id +=1 - - layout = dict(title = '', - xaxis = dict( - title = '[ligand] μM', - type ='log', - #range = [-3, 2], - exponentformat='e', - titlefont=dict( - size=20 - ), - tickfont=dict( - size=20 - )), - yaxis = dict( - title = '% Response', - #range = [0, 100], - titlefont=dict( - size=20), - tickfont=dict( - size=20) - - ), - legend=dict(font=dict(size=15)), - autosize=False, - width=850, - height=650, - ) - - fig = go.Figure(data=plot_data, layout=layout) - #fig['layout']['yaxis'].update(autorange = True) - - if save==True: - if filename==None: - filename='plot.html' - return pyoff.plot(fig, filename=filename) - else: - ext = os.path.splitext(filename)[-1] - if ext == '.png': fig.write_image(filename, scale=3) - elif ext == '.html': pyoff.plot(fig, filename=filename) - else: raise TypeError("extension not valid. Use png or html.") - elif save ==False: return fig - return - - def ShowPotency(self): - ''' - Return the potency values as a pandas DataFrame. - ''' - import pandas as pd - data = Simulation.Activation.PotencyToDict(self) - df = pd.DataFrame.from_dict(data, orient='index') - return df - - def PotencyToDict(self): - ''' - Convert potencies into a dictionary. - ''' - - #dependencies - if self.processed_data == None: raise TypeError('Simulation data unprocessed. simulation.activation.analysis() must be run first.') - - kvalues={} - for ligand in self.processed_data: - IC50 = list(self.processed_data[ligand].keys())[-2] - IC50_value = self.processed_data[ligand][IC50] - pIC50 = list(self.processed_data[ligand].keys())[-1] - pIC50_value = self.processed_data[ligand][pIC50] - kvalues[ligand]={IC50:IC50_value, pIC50:pIC50_value} - return kvalues - - def PotencyToCSV(self, path): - ''' - Exports the potency values into csv format. - - :parameter path: Required (kwarg str): directory path to save the csv file - ''' - - data = Simulation.Activation.PotencyToDict(self) - df = pd.DataFrame.from_dict(data, orient='index') - df.to_csv(path, index=False) - return - - class Inhibition: - """ - Simulation of the inhibition of signaling pathways (i.e. inhibition by antagonists). - """ - def __init__(self): - self._agonist=None - self._agonist_affinity=None - self._agonist_submaximal_conc=None - self._antagonists=None - self._antagonists_affinities=None - self._pathway=None - self._receptor_conc=None - self._lig_conc_range=None - self._ttotal=None - self._nsteps=None - self._binding_kinetics=False - self._binding_kinetic_parameters=None - self.simulation_data=None - self.processed_data=None - - def SetSimulationParameters(self, **kwargs): - """ - :parameter agonist: Required (kwargs str): agonist name - :parameter agonist_affinity: Required (kwargs flt): agonist pKd value - :parameter agonist_submaximal_conc: Required (kwargs flt): agonist submaximal concentration - :parameter antagonists: Required (kwargs list):list of antagonists names (str) - :parameter antagonists_affinities: Required (kwargs list): list of antagonists affinity values (flt) - :parameter antagonists_conc_range: Required (kwargs array): range of ligands' concentration (nM) - :parameter pathway: Required (kwargs str): name of the pathway ('Gs', 'Gi', 'Gq') - :parameter receptor_conc: Required (kwargs flt): receptors concentration (nM) - :parameter ttotal: Required (kwargs int): simulation time (seconds) - :parameter nsteps: Required (kwargs int): simulation time step - :parameter kinetics: Optional (kwargs boolean): default (False) - - - :return: instances of all parameters - - .. warning:: the order of the lists of the antagonists names and affinities list must be the same. - - """ - self._agonist= kwargs.pop('agonist') - self._agonist_affinity=kwargs.pop('agonist_affinity') - self._agonist_submaximal_conc=kwargs.pop('agonist_submaximal_conc') - self._antagonists=kwargs.pop('antagonists') - self._antagonists_affinities=kwargs.pop('antagonists_affinities') - self._pathway=kwargs.pop('pathway') - self._receptor_conc=kwargs.pop('receptor_conc') - self._lig_conc_range=kwargs.pop('lig_conc_range') - self._ttotal=kwargs.pop('ttotal') - self._nsteps=kwargs.pop('nsteps') - if 'kinetics' in kwargs: - self._binding_kinetics=kwargs.pop('kinetics') - if self._binding_kinetics==True: raise TypeError("The of Kinetic parameters during an inhibition simulation it is not supported yet.") - else: self._binding_kinetics=False - if 'binding_kinetic_parameters' in kwargs: - self._binding_kinetic_parameters=kwargs.pop('binding_kinetic_parameters') - self._DefaultPathwayParametersDataFrame=pd.DataFrame() - - return - - def PathwayParameters(self): - """ - Display table with default pathway parameters. - - .. warning:: this functions requires the qgrid library. It doens't work on Google Colab. - """ - import qgrid - self._DefaultPathwayParametersDataFrame = pd.read_csv(pathways_path+'/{}_parameters.csv'.format(self._pathway)) - - col_opts = { 'editable': False, 'sortable':False} - col_defs = {'Value': { 'editable': True, 'width': 150 }} - self._DefaultPathwayParametersTable = qgrid.show_grid(self._DefaultPathwayParametersDataFrame, column_options=col_opts,column_definitions=col_defs) - return self._DefaultPathwayParametersTable - - def UserPathwayParameters(self, path): - """ - Import user pathway parameters. - - :parameter path: Required (kwarg str): directory path - """ - import qgrid - self._DefaultPathwayParametersDataFrame = pd.read_csv(path) - col_opts = { 'editable': False, 'sortable':False} - col_defs = {'Value': { 'editable': True, 'width': 150 }} - self._DefaultPathwayParametersTable = qgrid.show_grid(self._DefaultPathwayParametersDataFrame, column_options=col_opts,column_definitions=col_defs) - return self._DefaultPathwayParametersTable - - def PathwayParametersToCSV(self, path): - """ - Export pathway parameters into CSV format. - - :parameter path: Required (kwarg str): directory path - """ - self._DefaultPathwayParametersTable.get_changed_df().to_csv(path, index=False) - print('saved in:', path) - return - - def Reactions(self): - """ - Display pathway reactions. - """ - from IPython.display import display, HTML - display(HTML("<style>.container {width:90% !important}</style>")) - return pd.read_csv(pathways_path+'/{}_reactions.csv'.format(self._pathway)) - - def Run(self): - ''' - This function runs the pathway simulation and returns the raw simulation data. - ''' - - #Check inputs - if self._agonist==None: raise TypeError("agonist undefined.") - elif self._agonist_affinity==None: raise TypeError("agonist_affinity undifined.") - elif self._antagonists==None: raise TypeError("antagonists list undefined.") - elif self._antagonists_affinities==None: raise TypeError("antagonists affinity values undefined.") - elif self._pathway==None: raise TypeError("pathway undefined.") - elif self._lig_conc_range.any() == False: raise TypeError("lig_conc_range undefined.") - elif self._agonist_submaximal_conc == None: raise TypeError("agonist_submaximal_conc undifined.") - elif self._ttotal==None: raise TypeError("ttotal undefined.") - elif self._nsteps==None: raise TypeError("nsteps undefined.") - elif self._receptor_conc==None: raise TypeError("receptor_conc undefined.") - elif self._binding_kinetics==True: raise TypeError("The of Kinetic parameters during an inhibition simulation it is not supported yet.") - else: pass - - #check pathway - available_pathways = ['Gs', 'Gi', 'Gq'] - if self._pathway == 'Gz(Gi)': self._pathway = 'Gi' - if self._pathway not in available_pathways: raise Exception('Unvailable Pathway. Please, introduce it manually. Networs available: "Gs", "Gi", "Gq".') - mypathway = importlib.import_module('.'+self._pathway, package='ssbtoolkit.pathways') - - #Get default pathway parameters - if self._DefaultPathwayParametersDataFrame.empty: - self._DefaultPathwayParametersDataFrame = pd.read_csv(pathways_path+'/{}_parameters.csv'.format(self._pathway)) - self._PathwayParameters = self._DefaultPathwayParametersDataFrame.set_index('Parameter').iloc[:,0].to_dict() - - elif self._DefaultPathwayParametersDataFrame.empty is False: - try: - #extract data from qgrid - newparameters = self._DefaultPathwayParametersTable.get_changed_df() - self._PathwayParameters = newparameters.set_index('Parameter').iloc[:,0].to_dict() - except: - self._PathwayParameters = self._DefaultPathwayParametersDataFrame.set_index('Parameter').iloc[:,0].to_dict() - - #Input - t = pl.geomspace(0.00001, self._ttotal, num=self._nsteps) # (a,b,c); a is the starting time ; b is the total time simulated ; c is the number of points - - #Output - simulation_data={} - - #Function - for ligand in self._antagonists: - ligand_name = os.path.splitext(ligand)[0] - data=[] - utils.PrintProgressBar(0, len(self._lig_conc_range), prefix = "{:<15}".format(ligand_name[:15]), suffix = 'Complete', length = 50) - - for idx in range(len(self._lig_conc_range)): - - ligand_conc = self._lig_conc_range[idx] - - #get LR conc - parameters = {**self._PathwayParameters, 'R_init':self._receptor_conc} - LR_conc_init = utils.CalcOccupancy(self._receptor_conc, self._agonist_submaximal_conc, ligand_conc, self._agonist_affinity, self._antagonists_affinities[self._antagonists.index(ligand)]) - mymodel = mypathway.network(LR=LR_conc_init, kinetics=False, **parameters) - simres = ScipyOdeSimulator(mymodel, tspan=t, compiler='cython').run() - yout = simres.all - - d1={'ligand_conc':ligand_conc, 'time':t } - - for idx2 in range(len(mypathway.list_of_observables)): - d2={mypathway.list_of_observables[idx2]:yout[mypathway.list_of_observables[idx2]]} - d1.update(d2) - data.append(d1) - utils.PrintProgressBar(idx + 1, len(self._lig_conc_range), prefix = "{:<15}".format(ligand_name[:15]), suffix = 'Complete', length = 50) - - simulation_data[ligand_name] = {'sim_data':data, - 'label':self._agonist+' + ' + ligand_name} - - self.simulation_data=simulation_data - return - - def Analysis(self): - ''' - This function calculates the dose-response effect. - - :return: instance processed_data - ''' - #dependencies - if self.simulation_data == None: raise TypeError('There is no simulation data. simulation.inhibition.run() must be run first.') - - # Define all the lists and dictionaries used in this function - raw_data=[] - normalized_data=[] - dose={} - - #defining concentration range - #defining concentration range - lig_conc_min = self._lig_conc_range.min() - lig_conc_max = self._lig_conc_range.max() - - #Main function - for ligand in self.simulation_data: - - #definig and dictionaries used in this loop: - raw_data_dict={} - normalized_data_dict={} - - # Calculate dose-response curve - #get metabolite concentration, rescale, and transform data if pathway/metabolite decrease - # metabolite_raw is not normalized - if self._pathway == 'Gi' or self._pathway == 'Gz(Gi)': - metabolite='cAMP' - metabolite_conc_raw=[] - for i in range(len(self._lig_conc_range)): - n=np.amax(self.simulation_data[ligand]['sim_data'][i]['obs_'+metabolite]) #cad - metabolite_conc_raw.append(n) - metabolite_conc_norm = minmax_scale(1-np.array(metabolite_conc_raw)) - elif self._pathway == 'Gs': - metabolite='cAMP' - metabolite_conc_raw=[] - for i in range(len(self._lig_conc_range)): - n=np.amax(self.simulation_data[ligand]['sim_data'][i]['obs_'+metabolite]) #cad - metabolite_conc_raw.append(n) - metabolite_conc_norm = minmax_scale(np.array(metabolite_conc_raw)) - elif self._pathway == 'Gq': - metabolite='IP3' - metabolite_conc_raw=[] - for i in range(len(self._lig_conc_range)): - n=np.amax(self.simulation_data[ligand]['sim_data'][i]['obs_'+metabolite]) #cad - metabolite_conc_raw.append(n) - metabolite_conc_norm = minmax_scale(np.array(metabolite_conc_raw)) - else: raise Exception('Unvailable Pathway. Please, introduce it manually. Networs available: "Gs", "Gi", "Gq".') - - - ## save results - raw_data_dict['x']=self._lig_conc_range - raw_data_dict['y']=metabolite_conc_raw - raw_data_dict['label']=self.simulation_data[ligand]['label'] - - normalized_data_dict['x']=self._lig_conc_range - normalized_data_dict['y']=metabolite_conc_norm - normalized_data_dict['label']=self.simulation_data[ligand]['label'] - - ## create a list of all data - raw_data.append(raw_data_dict) - normalized_data.append(normalized_data_dict) - - ##Fitting curve to the data - - def equation_dose(X, Bottom, Top, EC50, p): - return Bottom + (Top-Bottom)/(1+np.power((EC50/X),p)) - - popt_IC50, pcov = curve_fit(equation_dose, self._lig_conc_range, metabolite_conc_norm, bounds=([np.min(metabolite_conc_norm),-np.inf,-np.inf, 0.5],[np.inf,np.max(metabolite_conc_norm),np.inf, 2.5])) - - xfit_IC50 = np.geomspace(lig_conc_min, lig_conc_max, 50000) # These values are the same as the values for the simulation time and not ligand concentration - yfit_IC50 = equation_dose(xfit_IC50, *popt_IC50) - - fit_IC50={'x':xfit_IC50, 'y':yfit_IC50, 'label':self.simulation_data[ligand]['label']} - - dose[ligand] = {'raw_data': raw_data_dict, - 'normalized_data':normalized_data_dict , - 'fitted_data': fit_IC50, - 'IC50 (μM)': round(popt_IC50[2],5), - 'pIC50': round(-np.log10(popt_IC50[2]*1E-6),2)} - - self.processed_data=dose - return - - def ShowCurve(self, save=False, filename=None): - ''' - Plot the dose-response curve. - ''' - #dependencies - if self.processed_data == None: raise TypeError('Simulation data unprocessed. simulation.inhibition.analysis() must be run first.') - - import plotly - import plotly.graph_objs as go - import plotly.offline as pyoff - - colors = plotly.colors.DEFAULT_PLOTLY_COLORS - - plot_data=[] - - color_id=0 - for ligand in self.processed_data: - trace_norm = go.Scatter(x=self.processed_data[ligand]['normalized_data']['x'], - y=minmax_scale(self.processed_data[ligand]['normalized_data']['y'])*100 , - mode='markers', - showlegend=True, - name=self.processed_data[ligand]['normalized_data']['label'], - marker=dict(color=colors[color_id])) - plot_data.append(trace_norm) - - trace_fitted = go.Scatter(x=self.processed_data[ligand]['fitted_data']['x'], - y=minmax_scale(self.processed_data[ligand]['fitted_data']['y'])*100, - mode='lines', - showlegend=False, - name=self.processed_data[ligand]['fitted_data']['label'], - line=dict(color=colors[color_id])) - plot_data.append(trace_fitted) - color_id +=1 - - layout = dict(title = '', - xaxis = dict( - title = '[ligand] μM', - type ='log', - #range = [-4, 2], - exponentformat='e', - titlefont=dict( - size=20 - ), - tickfont=dict( - size=20 - )), - yaxis = dict( - title = '% Response', - #range = [0, 100], - titlefont=dict( - size=20), - tickfont=dict( - size=20) - - ), - legend=dict(font=dict(size=15)), - autosize=False, - width=850, - height=650, - ) - - fig = go.Figure(data=plot_data, layout=layout) - if save==True: - if filename==None: - filename='plot.html' - return pyoff.plot(fig, filename=filename) - else: - ext = os.path.splitext(filename)[-1] - if ext == '.png': fig.write_image(filename, scale=3) - elif ext == '.html': pyoff.plot(fig, filename=filename) - else: raise TypeError("extension not valid. Use png or html.") - elif save ==False: return fig - return - - def ShowPotency(self): - ''' - Return the potency values as a pandas DataFrame. - ''' - import pandas as pd - data = Simulation.Inhibition.PotencyToDict(self) - df = pd.DataFrame.from_dict(data, orient='index') - return df - - def PotencyToDict(self): - ''' - Convert potencies into a dictionary. - ''' - #dependencies - if self.processed_data == None: raise TypeError('Simulation data unprocessed. simulation.inhibition.analysis() must be run first.') - - kvalues={} - for ligand in self.processed_data: - IC50 = list(self.processed_data[ligand].keys())[-2] - IC50_value = self.processed_data[ligand][IC50] - pIC50 = list(self.processed_data[ligand].keys())[-1] - pIC50_value = self.processed_data[ligand][pIC50] - kvalues[ligand]={IC50:IC50_value, pIC50:pIC50_value} - return kvalues - - def PotencyToCSV(self, path): - ''' - Exports the potency values into csv format. - - :parameter path: Required (kwarg str): directory path to save the csv file - ''' - data = Simulation.Inhibition.PotencyToDict(self) - df = pd.DataFrame.from_dict(data, orient='index') - df.to_csv(path, index=False) - return - - class FitModel: - """ - Fit a model to experimental data. - - .. note:: This class was developed to reproduce data from a specific experimental setup. Please see tutorial 4 (OXTR pathay). Use carefully! - """ - def __init__(self): - - #fitting parameters - self._expratio = None - self._seed = None - self._maxiter = None - self._seed_incrementor = None - self._target_parameter = None - - #Pathway parameters - self._ttotal = None - self._nsteps = None - self._pathway = None - self._observable = None - self.pathway_parameters = {} - - def SetSimulationParameters(self, **kwargs): - """ - :parameter pathway_parameters: Required (kwargs): dict of pathway parameters - :parameter pathway: Required (kwargs str): name of the pathway ('Gs', 'Gi', 'Gq') - :parameter ttotal: Required (kwargs int): simulation time (seconds) - :parameter nsteps: Required (kwargs int): simulation time step - :parameter observable: Required (kwargs str): molecular specie to be measured - - :return: instances of all parameters - - """ - - if 'pathway_parameters' in kwargs: - self.pathway_parameters = kwargs.pop('pathway_parameters') - #print('pathway_parameters YES') - self._DefaultPathwayParametersDataFrame=pd.DataFrame() - if 'ttotal' in kwargs: - self._ttotal = int(kwargs.pop('ttotal')) - print('ttotal =', self._ttotal) - else: raise TypeError("ttotal undefined.") - - if 'nsteps' in kwargs: - self._nsteps = int(kwargs.pop('nsteps', 1000)) - print('nsteps =', self._nsteps) - - if 'pathway' in kwargs: - self._pathway = str(kwargs.pop('pathway')) - print('pathway ->', self._pathway) - else: raise TypeError("pathway undefined.") - - available_pathways = ['Gs', 'Gi', 'Gq', 'OXTR_pathway'] - if self._pathway == 'Gz(Gi)': self._pathway = 'Gi' - if self._pathway not in available_pathways: raise Exception('Unvailable Pathway. Please, introduce it manually. Networs available: "Gs", "Gi", "Gq".') - - - if'observable' in kwargs: - self._observable = str(kwargs.pop('observable')) - print('observable ->', self._observable) - else: raise TypeError("observable undefined.") - - return - - def PathwayParameters(self): - """ - Display table with default pathway parameters. - - .. warning:: this functions requires the qgrid library. It doens't work on Google Colab. - """ - import qgrid - self._DefaultPathwayParametersDataFrame = pd.read_csv(pathways_path+'/{}_parameters.csv'.format(self._pathway)) - - col_opts = { 'editable': False, 'sortable':False} - col_defs = {'Value': { 'editable': True, 'width': 150 }} - self._DefaultPathwayParametersTable = qgrid.show_grid(self._DefaultPathwayParametersDataFrame, column_options=col_opts,column_definitions=col_defs) - return self._DefaultPathwayParametersTable - - def UserPathwayParameters(self, path): - """ - Import user pathway parameters. - - :parameter path: Required (kwarg str): directory path - """ - import qgrid - self._DefaultPathwayParametersDataFrame = pd.read_csv(path) - col_opts = { 'editable': False, 'sortable':False} - col_defs = {'Value': { 'editable': True, 'width': 150 }} - self._DefaultPathwayParametersTable = qgrid.show_grid(self._DefaultPathwayParametersDataFrame, column_options=col_opts,column_definitions=col_defs) - return self._DefaultPathwayParametersTable - - def PathwayParametersToCSV(self, path): - """ - Export pathway parameters into CSV format. - - :parameter path: Required (kwarg str): directory path - """ - self._DefaultPathwayParametersTable.get_changed_df().to_csv(path, index=False) - print('saved in:', path) - return - - def Reactions(self): - """ - Display pathway reactions. - """ - from IPython.display import display, HTML - display(HTML("<style>.container {width:90% !important}</style>")) - return pd.read_csv(pathways_path+'/{}_reactions.csv'.format(self._pathway)) - - def Run(self, **kwargs): - """ - Fits of the model to experimental data. - - :parameter expratio: Required (kwargs flt): experimental signalling specie concentration ratio - :parameter target_parameter: Required (kwargs str):kinetic parameter to me modified - :parameter maxiter: Required (kwargs int): maximum number of iteration - :parameter seed: Required (kwargs flt): ramdom seed for scaling the modified parameter - :parameter seed_incrementor: Required (kwargs flt): seed incrementor (each iteration will increment the seed by this value) - :parameter seed_decrementor: Required (kwargs flt): seed decrementor (each iteration will decrement the seed by this value) - - """ - - from scipy.signal import find_peaks - import decimal - #fitting parameters - if 'expratio' in kwargs: - self._expratio = float(kwargs.pop('expratio')) - print('expratio =', self._expratio) - else: raise TypeError("exratio undefined.") - - if 'seed' in kwargs: - self._seed = float(kwargs.pop('seed')) - print('seed =', self._seed) - else: raise TypeError("seed undefined.") - - if 'maxiter' in kwargs: - self._maxiter = int(kwargs.pop('maxiter', 100)) - print('maxiter =', self._maxiter) - - if 'seed_incrementor' in kwargs: - self._seed_incrementor = float(kwargs.pop('seed_incrementor', 0.1)) - print('seed_incrementor =', self._seed_incrementor) - - if 'seed_decrementor' in kwargs: - self._seed_decrementor = float(kwargs.pop('seed_decrementor', 0.1)) - print('seed_decrementor =', self._seed_decrementor) - - if 'target_parameter' in kwargs: - self._target_parameter = str(kwargs.pop('target_parameter')) - print('target_parameter ->', self._target_parameter) - else: raise TypeError("target_parameter undefined.") - - - #Get default pathway parameters - if self._DefaultPathwayParametersDataFrame.empty and self.pathway_parameters==None: - self._DefaultPathwayParametersDataFrame = pd.read_csv(pathways_path+'/{}_parameters.csv'.format(self._pathway)) - self._PathwayParameters = self._DefaultPathwayParametersDataFrame.set_index('Parameter').iloc[:,0].to_dict() - - elif self._DefaultPathwayParametersDataFrame.empty is False and self.pathway_parameters is None: - try: - #extract data from qgrid - newparameters = self._DefaultPathwayParametersTable.get_changed_df() - self._PathwayParameters = newparameters.set_index('Parameter').iloc[:,0].to_dict() - except: - self._PathwayParameters = self._DefaultPathwayParametersDataFrame.set_index('Parameter').iloc[:,0].to_dict() - - elif self._DefaultPathwayParametersDataFrame.empty and self.pathway_parameters is not None: - self._DefaultPathwayParametersDataFrame = pd.read_csv(pathways_path+'/{}_parameters.csv'.format(self._pathway)) - self._PathwayParameters = {**self._DefaultPathwayParametersDataFrame.set_index('Parameter').iloc[:,0].to_dict(), **self.pathway_parameters} - - elif self._DefaultPathwayParametersDataFrame.empty is False and self.pathway_parameters is not None: - try: - #extract data from qgrid - newparameters = self._DefaultPathwayParametersTable.get_changed_df() - self._PathwayParameters = {**newparameters.set_index('Parameter').iloc[:,0].to_dict(), **self.pathway_parameters} - except: - self._PathwayParameters = {**self._DefaultPathwayParametersDataFrame.set_index('Parameter').iloc[:,0].to_dict(), **self.pathway_parameters} - - - - #simulation parameters: - if not self._ttotal: - raise TypeError("simulation parameters unknown. Set the the simulation parameters first wiht set_simulation_parameters()") - - #Main function - mypathway = importlib.import_module('.'+self._pathway, package='ssbtoolkit.pathways') - self.simtime = pl.geomspace(0.00001, self._ttotal, num=self._nsteps) - - #Simulation 1 - pathway_model = mypathway.network(LR=None, kinetics=True, **self._PathwayParameters) - sim1 = ScipyOdeSimulator(pathway_model, tspan=self.simtime,compiler='cython').run() - self.simres1 = sim1.all - - def calc_ratio(self): - - - #Simulation 2 - sim2 = ScipyOdeSimulator(mypathway.network(**self.new_pathway_parameters), tspan=self.simtime, compiler='cython').run() - self.simres2 = sim2.all - - #analysis - obs_name = 'obs_'+self._observable - obs_1 = self.simres1[obs_name] - obs_2 = self.simres2[obs_name] - - if 'time_in' in self.new_pathway_parameters: - self._time = np.take(self.simtime, np.where(self.simtime > int(self.new_pathway_parameters['time_in'])))[0] - obs_curve_1 = np.take(obs_1, np.where(self.simtime > int(self.new_pathway_parameters['time_in'])))[0] - obs_curve_2 = np.take(obs_2, np.where(self.simtime > int(self.new_pathway_parameters['time_in'])))[0] - - else: - self._time = np.take(self.simtime, np.where(self.simtime>0))[0] - obs_curve_1 = np.take(obs_1, np.where(self.simtime > 0))[0] - obs_curve_2 = np.take(obs_2, np.where(self.simtime > 0))[0] - - obs_peaks_1, _ = find_peaks(obs_curve_1) - obs_peaks_2, _ = find_peaks(obs_curve_2) - - vmax_obs_curve_1 = obs_curve_1[obs_peaks_1][-1]*1E3 - vmax_obs_curve_2 = obs_curve_2[obs_peaks_2][-1]*1E3 - - obs_ratio = round(vmax_obs_curve_2/vmax_obs_curve_1, abs(decimal.Decimal(str(self._expratio).rstrip('0')).as_tuple().exponent)) - - self._obs_curve_1=obs_curve_1 - self._obs_curve_2=obs_curve_2 - self._obs_peaks_1=obs_peaks_1 - self._obs_peaks_2=obs_peaks_2 - self._vmax_obs_curve_1=vmax_obs_curve_1 - self._vmax_obs_curve_2=vmax_obs_curve_2 - - return obs_ratio - - self._iteration=1 - print('\n') - - self._lst_ratio=[] - self._lst_seed=[] - - for idx in range(self._maxiter): - - prefix = 'iteration' - iteration_n = str(self._iteration) - print(f'\r{prefix} {iteration_n}', end='\r') - - self.new_pathway_parameters={**self._PathwayParameters, **{self._target_parameter:mypathway.defaultParameters[self._target_parameter]*self._seed}} - self.obs_ratio = calc_ratio(self) - - if self.obs_ratio == self._expratio: - self._lst_ratio.append(self.obs_ratio) - self._lst_seed.append(self._seed) - self._fold=round(self._seed, abs(decimal.Decimal(str(self._expratio).rstrip('0')).as_tuple().exponent)) - print('\n\nDONE!\n', '\nRatio: '+str(self.obs_ratio), '\nFOLD: '+str(self._fold), '\nNumber of iterations: '+str(self._iteration)) - break - elif self.obs_ratio < self._expratio: - - self._lst_ratio.append(self.obs_ratio) - self._lst_seed.append(self._seed) - self._iteration+=1 - self._seed += self._seed_incrementor - - else: - self._lst_ratio.append(self.obs_ratio) - self._lst_seed.append(self._seed) - - - self._iteration+=1 - self._seed -= self._seed_decrementor - - return - - def PlotIterations(self, save=False, filename=None): - ''' - Plot iterations. - ''' - import plotly.offline as pyoff - #dependencies - if self._iteration == None: raise TypeError('Simulation data not exist. simulation.fitModel.run() must be run first.') - - #import plotly - import plotly.graph_objs as go - - iterations = np.arange(1,self._iteration+1) - - trace=dict(type='scatter', x=self._lst_seed, y=self._lst_ratio, mode='markers', - marker=dict(color= iterations, colorscale='Bluered_r', size=14, colorbar=dict(thickness=20, title='iteration number'))) - #axis_style=dict(zeroline=False, showline=True, mirror=True) - layout = dict(title = '', - xaxis = dict( - title = 'seed', - titlefont=dict( - size=20 - ), - tickfont=dict( - size=20 - )), - yaxis = dict( - title = '['+self._observable+']' + ' ratio', - titlefont=dict( - size=20), - tickfont=dict( - size=20) - - ), - legend=dict(font=dict(size=15)), - autosize=False, - width=850, - height=650 - ) - - fig = go.Figure(data=[trace], layout=layout) - if save==True: - if filename==None: - filename='plot.html' - return pyoff.plot(fig, filename=filename) - else: - ext = os.path.splitext(filename)[-1] - if ext == '.png': fig.write_image(filename, scale=3) - elif ext == '.html': pyoff.plot(fig, filename=filename) - else: raise TypeError("extension not valid. Use png or html.") - elif save ==False: return fig - return fig - - def ShowGraphs(self, save=False, filename=None): - ''' - Plot the amount of obeservable in function of time, Amplitude, Area Under the Curve, and Full Width at Half Maximum. - - :parameter save: Optional (kwarg boolean): default False - :parameter filename: Optional (kwarg str) - ''' - - from IPython.core.display import display, HTML - display(HTML("<style>.container { width:90% !important; }</style>")) - - - from plotly.subplots import make_subplots - from scipy.signal import peak_widths - from sklearn import metrics - import plotly.offline as pyoff - - - half_1 = peak_widths(self._obs_curve_1, self._obs_peaks_1, rel_height=0.5) - half_2 = peak_widths(self._obs_curve_2, self._obs_peaks_2, rel_height=0.5) - fwhm_1 = self._time[int(half_1[3])]-self._time[int(half_1[2])] - fwhm_2 = self._time[int(half_2[3])]-self._time[int(half_2[2])] - - - fig = make_subplots(rows=2, cols=2,vertical_spacing=0.15, - subplot_titles=("{} concentration".format(self._observable), "Amplitude", "Area under the curve", "Full Width at Half Maximum")) - - #################### - #### MAIN PLOT #### - #################### - fig.add_trace(go.Scatter(x=self._time, y=self._obs_curve_1*1E3, name='control'), row=1, col=1) - fig.add_trace(go.Scatter(x=self._time, y=self._obs_curve_2*1E3, name='{}-fold'.format(self._fold)), row=1, col=1) - fig.add_trace(go.Scatter(x=self._time[self._obs_peaks_1], y=self._obs_curve_1[self._obs_peaks_1]*1E3, - name='max value', showlegend=False, mode='markers', - marker=dict(symbol='x', size=13, color='Black')), row=1,col=1) - fig.add_trace(go.Scatter(x=self._time[self._obs_peaks_2], y=self._obs_curve_2[self._obs_peaks_2]*1E3, - name='max value', showlegend=False, mode='markers', - marker=dict(symbol='x', size=13, color='Black')), row=1,col=1) - fig.add_shape(type='line', x0=self._time[int(half_1[2])],y0=half_1[1][0]*1E3, x1=self._time[int(half_1[3])], y1=half_1[1][0]*1E3, - line=dict(color='Blue',dash='dash'),xref='x',yref='y', row=1, col=1) - fig.add_shape(type='line', x0=self._time[int(half_2[2])],y0=half_2[1][0]*1E3, x1=self._time[int(half_2[3])], y1=half_2[1][0]*1E3, - line=dict(color='Red',dash='dash'),xref='x',yref='y', row=1, col=1) - - # Update xaxis properties - fig.update_xaxes(title_text="Time (s)", showgrid=False, row=1, col=1, titlefont=dict(size=18), - linecolor='black', linewidth=2, - ticks='inside', tickfont=dict(size=18), tickcolor='black', ticklen=10, tickwidth=2) - - fig.update_yaxes(title_text=self._observable+' (nM)', titlefont=dict(size=18), showgrid=False, row=1, col=1, - linecolor='black', linewidth=2, - ticks='inside', tickfont=dict(size=18), tickcolor='black', ticklen=10, tickwidth=2) - - - #################### - #### AMPLITUDE #### - #################### - - AMP_labels = [1,2] - AMP_values = [self._vmax_obs_curve_1, self._vmax_obs_curve_2] - fig.add_trace(go.Bar(x=AMP_labels,y=AMP_values, width = [0.35,0.35], showlegend=False, marker_color='black', name=''), row=1, col=2 ) - - - - # Update xaxis properties - fig.update_xaxes(row=1, col=2, showgrid=False, linecolor='black', linewidth=2, range=[0,3], - tickmode='array', tickvals=[1,2], ticktext=['control', '{}-fold'.format(self._fold)], tickfont=dict(size=18)) - - fig.update_yaxes(showgrid=False, range=[round((min(AMP_values)-min(AMP_values)*0.5)/5)*5,round((max(AMP_values)+max(AMP_values)*0.5)/5)*5 ], row=1, col=2, - title_text=self._observable+' (nM)', titlefont=dict(size=18), - linecolor='black', linewidth=2, ticks='inside', ticklen=10, tickwidth=2, tickfont=dict(size=18)) - - # Add diff lines - AMP_diffs = [max(AMP_values) - v for v in AMP_values] - AMP_diff_labels = dict(zip(AMP_labels, AMP_diffs)) - fig.add_trace(go.Scatter(name='',x=[1,1.5,2], y=[max(AMP_values)+(max(AMP_values)*0.3)]*3, mode = 'lines+text',showlegend=False, line=dict(color='black', width=1),text=['', 'diff. = {} nM'.format(round(AMP_diffs[0], 3)),''], textposition='top center'), row=1, col=2) - fig.add_trace(go.Scatter(name='',x=[AMP_labels[0]-0.175, AMP_labels[0]+0.175], y=[AMP_values[0]+(AMP_values[0]*0.03)]*2, mode = 'lines',showlegend=False, line=dict(color='black', width=1)), row=1, col=2) - fig.add_trace(go.Scatter(name='',x=[AMP_labels[1]-0.175, AMP_labels[1]+0.175], y=[AMP_values[1]+(AMP_values[1]*0.03)]*2, mode = 'lines',showlegend=False, line=dict(color='black', width=1)), row=1, col=2) - fig.add_trace(go.Scatter(name='',x=[AMP_labels[0], AMP_labels[0]], y=[AMP_values[0]+(AMP_values[0]*0.03), max(AMP_values)+(max(AMP_values)*0.3)], mode = 'lines',showlegend=False, line=dict(color='black', width=1)), row=1, col=2) - fig.add_trace(go.Scatter(name='',x=[AMP_labels[1], AMP_labels[1]], y=[AMP_values[1]+(AMP_values[1]*0.03), max(AMP_values)+(max(AMP_values)*0.3)], mode = 'lines',showlegend=False, line=dict(color='black', width=1)), row=1, col=2) - - - #################### - #### AUC #### - #################### - - # Data - AUC_labels = [1,2] - AUC_values = [round(metrics.auc(self._time, self._obs_curve_1),2), round(metrics.auc(self._time, self._obs_curve_2),2)] - fig.add_trace(go.Bar(x=AUC_labels,y=AUC_values, width = [0.35,0.35], showlegend=False, marker_color='black', name=''), row=2, col=1 ) - - # Update xaxis properties - fig.update_xaxes(row=2, col=1, tickmode='array', showgrid=False, range=[0,3], linecolor='black', linewidth=2, - tickvals=[1,2], ticktext=['control', '{}-fold'.format(self._fold)], tickfont=dict(size=18)) - - fig.update_yaxes(row=2, col=1,showgrid=False, title_text=self._observable+' (nM)', range=[round((min(AUC_values)-min(AUC_values)*0.5)/5)*5,round((max(AUC_values)+max(AUC_values)*0.5)/5)*5], - titlefont=dict(size=18),linecolor='black', linewidth=2, - ticks='inside', tickfont=dict(size=18),ticklen=10, tickwidth=2) - - # Add diff lines - AUC_diffs = [max(AUC_values) - v for v in AUC_values] - AUC_diff_labels = dict(zip(AUC_labels, AUC_diffs)) - fig.add_trace(go.Scatter(name='',x=[1,1.5,2], y=[max(AUC_values)+(max(AUC_values)*0.3)]*3, mode = 'lines+text',showlegend=False, - line=dict(color='black', width=1), text=['', 'diff. = {} nM'.format(round(AUC_diffs[0], 3)),''], textposition='top center'), row=2, col=1) - fig.add_trace(go.Scatter(name='',x=[AUC_labels[0]-0.175, AUC_labels[0]+0.175], y=[AUC_values[0]+(AUC_values[0]*0.03)]*2, mode = 'lines',showlegend=False, line=dict(color='black', width=1)), row=2, col=1) - fig.add_trace(go.Scatter(name='',x=[AUC_labels[1]-0.175, AUC_labels[1]+0.175], y=[AUC_values[1]+(AUC_values[1]*0.03)]*2, mode = 'lines',showlegend=False, line=dict(color='black', width=1)), row=2, col=1) - fig.add_trace(go.Scatter(name='',x=[AUC_labels[0], AUC_labels[0]], y=[AUC_values[0]+(AUC_values[0]*0.03), max(AUC_values)+(max(AUC_values)*0.3)], mode = 'lines',showlegend=False, line=dict(color='black', width=1)), row=2, col=1) - fig.add_trace(go.Scatter(name='',x=[AUC_labels[1], AUC_labels[1]], y=[AUC_values[1]+(AUC_values[1]*0.03), max(AUC_values)+(max(AUC_values)*0.3)], mode = 'lines',showlegend=False, line=dict(color='black', width=1)), row=2, col=1) - - - #################### - #### FWHM #### - #################### - # Data - FWHM_labels = [1,2] - FWHM_values = [fwhm_1, fwhm_2] - fig.add_trace(go.Bar(x=FWHM_labels,y=FWHM_values, width = [0.35,0.35], showlegend=False,marker_color='black', name=''), row=2, col=2 ) - - # Update xaxis properties - fig.update_xaxes(row=2, col=2, showgrid=False, range=[0,3], linecolor='black', linewidth=2, - tickmode='array', tickvals=[1,2], ticktext=['control', '{}-fold'.format(self._fold)], tickfont=dict(size=18)) - - fig.update_yaxes(row=2, col=2, showgrid=False, range=[self.pathway_parameters['time_in'],round((max(FWHM_values)+(max(FWHM_values)-self.pathway_parameters['time_in'])*0.5)/5)*5], - title_text='Time (s)', titlefont=dict(size=18), linecolor='black', linewidth=2, - ticks='inside', ticklen=10, tickwidth=2, tickfont=dict(size=18)) - - # Add diff lines - FWHM_diffs = [max(FWHM_values) - v for v in FWHM_values] - FWHM_diff_labels = dict(zip(FWHM_labels, FWHM_diffs)) - line_height = max(FWHM_values)+((max(FWHM_values)-self.pathway_parameters['time_in'])*0.30) - fig.add_trace(go.Scatter(x=[1,1.5,2], y=[line_height]*3, mode = 'lines+text',showlegend=False, line=dict(color='black', width=1),name='', - text=['', 'diff. = {} s'.format(round(FWHM_diffs[0], 3)),''], textposition='top center'), row=2, col=2) - fig.add_trace(go.Scatter(name='',x=[FWHM_labels[0]-0.175, FWHM_labels[0]+0.175], y=[FWHM_values[0]+(FWHM_values[0]*0.005)]*2, mode = 'lines',showlegend=False, line=dict(color='black', width=1)), row=2, col=2) - fig.add_trace(go.Scatter(name='',x=[FWHM_labels[1]-0.175, FWHM_labels[1]+0.175], y=[FWHM_values[1]+(FWHM_values[1]*0.005)]*2, mode = 'lines',showlegend=False, line=dict(color='black', width=1)), row=2, col=2) - fig.add_trace(go.Scatter(name='',x=[FWHM_labels[0], FWHM_labels[0]], y=[FWHM_values[0]+(FWHM_values[0]*0.005), line_height], mode = 'lines',showlegend=False, line=dict(color='black', width=1)), row=2, col=2) - fig.add_trace(go.Scatter(name='',x=[FWHM_labels[1], FWHM_labels[1]], y=[FWHM_values[1]+(FWHM_values[1]*0.005), line_height], mode = 'lines',showlegend=False, line=dict(color='black', width=1)), row=2, col=2) - - - #################### - #### FIGURE #### - #################### - - fig.update_layout(height=1200, width=1300, title_text="", plot_bgcolor='white',showlegend=True, - legend=dict(yanchor="top", x=0.3, y=.99,font=dict(family="sans-serif", size=14,color="black"))) - fig.update_annotations(font_size=20, font_color='black') - - if save==True: - if filename==None: - filename='plot.html' - return pyoff.plot(fig, filename=filename) - else: - ext = os.path.splitext(filename)[-1] - if ext == '.png': fig.write_image(filename, scale=3) - elif ext == '.html': pyoff.plot(fig, filename=filename) - else: raise TypeError("extension not valid. Use png or html.") - elif save ==False: return fig - - return diff --git a/ssbtoolkit/__init__py b/ssbtoolkit/__init__py new file mode 100644 index 0000000..73756ce --- /dev/null +++ b/ssbtoolkit/__init__py @@ -0,0 +1,15 @@ +import pkgutil + +__all__ = [] +for loader, module_name, is_pkg in pkgutil.walk_packages(__path__): + __all__.append(module_name) + _module = loader.find_module(module_name).load_module(module_name) + globals()[module_name] = _module + + +import sys, os, warnings +sys.path.insert(0, os.path.abspath(os.path.split(os.path.realpath(__file__))[0])) +warnings.simplefilter(action='ignore') + + + diff --git a/ssbtoolkit/__pycache__/Binding.cpython-39.pyc b/ssbtoolkit/__pycache__/Binding.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..cef465df6d41f8c1c907a2a41a0fb20bd33dfe4d GIT binary patch literal 4055 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 O!9LckS*EpWUHK0$u_(m= literal 0 HcmV?d00001 diff --git a/ssbtoolkit/__pycache__/Simulation.cpython-39.pyc b/ssbtoolkit/__pycache__/Simulation.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..460138feae6ca850aee45d0f6443def752cf6f7c GIT binary patch literal 34045 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 literal 0 HcmV?d00001 diff --git a/ssbtoolkit/__pycache__/Utils.cpython-39.pyc b/ssbtoolkit/__pycache__/Utils.cpython-39.pyc index a8b0c57ab461229eea031894352efa76c5bb7d7b..ff1259fb1d1bac67343b743163f5fa046c7adeeb 100644 GIT binary patch delta 2936 zcmZveX>c4z6@a&Aui49ztizElS%)pj3+TjhRI)K5RxB3^!O^a9Wr$dfW?Ir%voq`0 zvzC;EY=Ip}K~ja)5YD)W3n;5{6tJ8jkPsl`5B#90{1}SjDyqmIFjf3W<rnYu<`k?- z-@Ja^ulvpG*Kc~;{jHlbbGdW`f9~V=TQ6VDb=N+H4Lf2)Y-TW9v!kwNXzCd?qWFxt zv2x6av53;ejf8q84V_0Xr0lqzc!XhJ($=p;jI@2g-eWgiVMfMowp&n>wfEYs_{`Z| zcDvnyJ$vkZ_ARJsnv2+-_Wmomh`s-^W;EN;LL}dP_4@eUFyX9vKJYvX6Q&h7z9&n= z?N|TtD2vM)8*GoG!y&6e5}!K}Xu-#8$K*4te`ZvO<-qsdbtf1-7dWmMsceP0)20Z_ z0>4=0p2f2`TIh%f%d)iA%%BxzND(siUhO|xstx7W*f4a+?)V}z<bCmzx6&eNCD@Bn z*nAcoFE|VSDsUlgGu0%5=8ok0Su<;6hc%tSVEtzNWRzv(XsRQ3fDB{eA_V!IAs15f z>|OF&YV?qy-LcS^S}mgH&z&+|>%*2+t(cy*bwu7w^{}yebNYU*mj+>E*ye7V_^dgx z3g_gL+38xI2I*mwu$;=)h~;}20WT0Rj(V6Nycc0)Z*%++jEssE93?$PBaDMz_3Zo} zXrfU$w70kyfJu9?YmSE1F|vvZe$hx)zz-zPO%Cn~S^I|^i{fRLMbW?0^!&2#nsB>( zKG(;l>wnF?q_N}jiRNPet>nfD<%U-+SGM4NB&Uj76@EK_4=^SBTH4MVT5$sZt6^e& z!-Q3#Ow?E-Iy*N`z<#k}#KgMq8FA#w%I=so&pRFu9IMF7m4aou+>;NtOtRVf3oU<V zi5z*dM?Teh<dBi%o7}1f+|XBeV6B<18^$fy7d)R-QG(O*)z+b|T`!%P<G~!JNx&WN zx;)-?fZbI;-_{g;PpDf?pyG*p857+7AJ_C!$?=?^RC>0)z3;IECehfzj@%J+X-9_{ z@~lf14u0amt`*bEz8?tOt4if}@?QsMSg#!Gnd>AwC?LYPxe{;)<09Y{0U7zZp0j(_ zv114QSav)COY)yRi&KlJ$`6K_Mh0Q}VD{WYlYD?CiB|jyoTKJP2<HhJp(ro+zI(z* zlme&B1+0;-3lYXv9M1-q#H1-q0CUUGOXVsr2_AqY|J>Whrli(4K5e9C&A>d3iNcM{ zLea*?3N@gcChJ7xJK%1TPZ5+;K1mm3xo<M@SrRwojlTXtkHld_sGXV(wfVyC?T;>0 zy%*~@`~IfkG5`JHnZ!P_Sd#k(?&vpB+|l40r1>TxgLp-Oip36EJT%b5hUATbW9*vz z)4+1=t2FFuh%m9@tX6@CG25{MBf0EvR$M=Tk5gZ4%XP|OY!w{aNNi#9t&2unbQi;4 zF)iMRShVbTIKVZRIp$j61TI%^4;C#`Bz%VOFin~i=A}N4tHK=V&X7(Q71s&)W}x;g zpM|^SUj{o94KKHcP8{BqI<05X$A#jogX!ZeSDgUM#x3=wp_}pSM{&M8;VU%tWA%H6 z2iWiu$rK}?)XK*iDY;4t+Hd6~YA=xN%BM#<I(Mz60}oV|oRNPVnbH;`@`0`&9EOjP z!7{-iyweD1p9}KeqaSR(M!iFXuM^%c=f{rK$dw`<Crs^-t*af&$6*7tM%t>Dt1h0n zOFY!q@D&5BC-5!m(%g+O3fxw&6DDj^tZ_RZRh>|#*(nL`O@g`6dusMHsHLAERfp{e z;aiS6$i2b#cxq9(w1k~`?QIiOQ2`eTCF+X9B+2BMS|Ci?+!Ic)1-~H`eSq>zW!)QY zs5C6gmGQyEbsBO>ZjT?Sk^2;K{Jr|zEA(B2#|bwO_v-V7-hhi>8lEKGQ-r4p-zI#A z@LdEhq|W-7kuIA7rK#&I!;92?A3>e3I!WInyqV+h{YVXir_W&?7b@I(TUtIq11Z;y zjA`2?QC(Kqukzyaq>icfGc=5DAMg`Wrm+x|HdVUAWzwz@sA9kr^~W~x+`td1{bK~y zpqjnXuzLLzJw$z<BwQgph%l1EG6PlcTz_?Z%+P5SI)M#|<}{j=j!vRlUBHjX{L=&~ zH}EWBj&O$XQv#L2T0Y+>L_Z_Va|9JP6|(24`Bt=EqNXZSKSwFlR)vv#qxz{sauwA^ z)Z83XX%R93zb2FI*VW@7sY%rhBSEDhwplBioA3gue?g#}3R#6FnFzI@5+*A?<tJ7V z6zKC$!G|vp9woe4AK{DC{1V}pgm<30Fw-au1^Ng~F}y!f$%*J$IeGNunn4C%Cj5%< z2f|zSN}?+bEFfs}1l>M$7K`a=eIebe^!Y^N0@k>N(P%mX&`#(;V2&QAM@yJ4mCS0e z=0m9zrb;DVu9=QoDyiKGb-W08j)!*vjVxyflt1|XriaHG9>GSK6;8S0Vy$#BEgHAK zK^i$mm?q2;?jn%W|L<)={Y2Tft1drzh?Wxj5iu6S>tvSc_?M09$#lEkt>@E6_4fY) D@4(~P delta 2924 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 diff --git a/ssbtoolkit/__pycache__/__init__.cpython-39.pyc b/ssbtoolkit/__pycache__/__init__.cpython-39.pyc index a650def0a4ad5939e819b758c2da741abc190047..48ab15b99374e7ef7ef007a6d7495fb46a4bf955 100644 GIT binary patch delta 7412 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 C3W>1* delta 7096 zcma)AdvKH2mDiPIS(ab2@%w>aGE}w!+du%vfQ@<BrVs<hfscUrY<-p$>y`V-F9;EV zG^M=pO@Jh%!RaG7fp!v2JDs-EnNI)cw9}dNkM3so+jOT%HX$TUXVaaX>~wZ__WZ6K zWQ1wf3_qRkp2t1+oO{oGeEz2SnK#WjYIe56M1Ogwjt>2>_)<;<d;L=5{+O2ac+T}) zuSv_MU#G-8iTT>188*Ybrcx9AnwiOK^0GjIQXqc|l|rrXdeIEEmYS4et>AhI(dJPz z|B2Kt)r!5A8M9Ir>X_qMiT8)Qyfn#kX|aT&jhO#_mzge^cCFlNKgmuot&BJo-i%UH zG2A4oJ>g8HQYoETtW?pjBTyZzQEH^4nSt71ol+<1tbi+6uhdJL1(pPtDqowU-AcnD zX`oSQlE2G>%as)@5sOmk&V8})W|MQx+f3dZZ|*ZDZ|+I6(n4QOZytT+(O0XN?J>FY z?;zGY<N4+cvuzQPe_JnVthLr<UTs`-S!-BhT(hn=vuyE_t)AtGU)q+iT=B8(o6GV^ z*szUGsNARXO8QW&L*dX6hmiynK*GAoAM&=uucg0hW_B^<I8u@#kgofE!LZ*uATl#| zF+&{8^lYmo%{yiSx0$<O#S#en+-U}zG?;2Iufc{4HkjH~?wlo$SL6P1&)`8}$?CMh z7H<~4S!-i<kktTbG{dKbgL*_A(hN(`AL6CNAxfQ;!!a!W(6Eul!($;YZ-tVRz@5P{ zaGo^P04?j}us>>8BjE^eC_K*BP_vRjW<BF-U`&IrHS^=LXwTT-4t27NLZ*eHT#fp} zq3(EXw%cq=M+X^VSMIjWH6ZH<X4Ci*&`SYdBT%ecx>h%HH@;IMnyo!CChd8=VO`V3 zySb~KO^{m#SPp0uHO>OoDjst77Rj-7qqT>?um-~vzg}_KS;bn!kDdE62f*70*e^=+ zD%pToljpTPj0U@y$tz!h^(tc~LnwpG*Q0GCU<06?z_5PZAJVu}e45v^R8AkWFPw-Z zEEwjgDZUNDn*d#aPO%|>m1`JW%dp><I?{|iBjJgzF+Q$o;&T3t&O+!^0X744Kr%Rz z!ldMx1c;oDe(lW4>^6I`y^?3fV~h5hZ6%N?6-J@2G!JCbgw#eax8h3CQ&w9x*g4`~ z%eE{p1~~;d0yqjt7y|Vq;27X3Kuqi^Usot+(qeqi>p{FyUcr7E|9Saa%yu5UK@qK7 z$(|KADl0RCXpp8)iQiYYbVv)&QKMlW3I{wi{qzLo)rR%?Rw0ttr$$F6)Tus|t3fTQ zas8cm=i+5dJqN272>SU&ke2|{1hX~{<NR5`^MF%;D}bv6G%IButr4=E5PcE6mjLs} z^mbJl`!N1Vm7B4vqM)WeG4$)HVmoWf*<a(2*VM4XMs82vRCFX9TJH7hk$^hIZ$bJ8 zfTTN@@p(5WvzEl-OiIr4fh3KL-I_Xtm8So9{L@-tV`oLo-NX)vo9=IQqfpG64ZH3S zdc0~>HPQm=pcZg1Ph_<SUCGeP49X-YDux=GnwQ@aajd~zgE$#!S}+ow;xTBZ`Ye9k z&{~2R^6x{`90?@Wh&inD>msjl(VB9!JNvjl6z$_-AJ=q!lgg<SY9anz@Y1*%@@c#R zbb3gOPK5a=w}}IdjoD??#*Y)sF5>w@G_EekhhYo&eQL<7WF~$+A({tV6CX70C`gHv zW%pRf8sFTs)5>avXb#v~NuPI%m-cR}yIYj_Ry5wLDOd1oBHXfRNovg@Vaia#5ES15 zG>dmy`eVmI$_OK*iQ*yah79;^w0#7Sb=#_<M#~|}$}<YiKLhLlS#(fucn1MxG3;w; z=1&vtE=bG|6C)-gFs!JMMwWQJ^+X;@kCK5B?vbUnORQh%D!q4fhP^YS2Bvhs?u{Q= z`MEik9L*L|=i3ST<-lf{kw!~0%xMiJg&u(r$}H~%^a1F|0xzjF>RbPguwjJ=m)qGc z=<O!h!dpP@0rUfQ0QM66%~4;yThk*n*EO!7^#I^8z#)JKa2VhfE7mMwYsJ8t^(DvA zfJ7L!NFW>yOmQDtM#LLy+8Yo<9stO=hd{|Rgo&bkj}-X(Xru|ewxlRuw(9D*#>)_K zI`Wu!L~LBUSypt;!MA9W(bOrV6XT~$oIel|#p~WK!(^XjiY?6jv{kxo<Qh$4Rp*n_ zZB?D$gq4h89;Q|6ABxUe6FHXaO#{#D9Zu=M$i%+U7mjM7p($JZaN85CRE|v!oZ_t0 zE<UPF>9gsydOmr0ZOZE4%`}qS_(Sc>m><sZW%Rw3e}~AO?sPthCOV$c<OWDj;3aRt z=W9fL>Ev~^!b!dotdzNyHRoY}xNFZqwfNoohy78=jseC2698;S{50SiU<U9y;4Q!x z04J;?s(ckF>DGpOdz5__Km?x8#v>aRnVoX=&D+Gp`HsrPc@VnSRp~>snKsl4;VCMO z-|mRn3Qs^T8;}Dy33x^v-R6mXgUFrkJYI~Z4*1at*aYYTYzCxu4&meuDvL$3vdZ|% z&FNc^OK}g;>41BvA11NC`&V^mASM7W08$hX$q})!=T+Cs;C>tM$^vpT)HZGwpH@|i zih)eg+&kcU6=L55%r2mS#GEvX(V8-;@OH1y^#;U#2zYY=1tjsDS^T-SOe}tk&L`VL zR@$}x@<=7^T3LNfv0CCEu~au!^M4@9y&@4$866qalz7VW`cLRbM*B%nGN>}ka%Yp3 z)BxjO05YiWf|3*Yc~F-Da@!RjP87th;`94}_W<pH_W{=d9{|b$9|ArC+y+zt<ofv- zlnrnP@Co2kz@GuK#2g3p86YK9UzjW=)AR8iyH;SUI<~iD-d5$RfkGW%>jJ9VsLf>& zk(MG#Mq}9m`xXBms5J|9ppE5=YXe*6?-D&YfOm<%4!Bb51J>K0;th|kw>Vp2pc}9S zuw6_jE)7ZJy8ycZEr30Mduuo^COcnMyrEVCxl%wmpaQUV`aogqX>jFakj2-B&k;Z^ zMCjg0TPlEVoe!?;d<nQI30;adx}e^d51q`fp_J$}x5RS?K5v_(2E)-48u1VMarR1- zAGu&<L2O;X2+Gvc7NNFP$Yt`;W4~FPl6KUv)95Cj%$w3gM|-6d{m+AHJ9^~ZFe@wF zv-nK*_ZmLeFVHC};)`PX(1v-NzU)<&Mc8YK-#PR?V_D*Nhr2Urd~<8(sHjrwA9z)G zBYsf5XmOt;@gw$VG#pg})$93>p#2Pi|6>}(5qn6FY7w0nCq$7p8Iz^)5jdIj9xD;I z(rDtec>@+?Zsh|0H+;&PDHrfg(sj?BhZlqO7eGo7Q5S!PAY$s9vg|C&>|l20WFmX` z7p|`^ksn$_M^AyU_|DXQb)EbV@uu&1-K%ip@0LfC=pQ*#_tjO=5jXo^w6iVZ<tJ{i zv+=F`1v9gWKSj4QmuMQRWF4Y+>?*C=55`_Dx|>GBF{B2DJo3IhEG~}^+p!i6=1_!n zBI=Z3??KoT(G{mAR=W^){w^Sykd!ibRJ=D)vNBl*<=jZt(D$I+4v?wZhYmi*r%g0X z?z71P*C9?%F0o-Y^Tp!FlWmEb@;F50{QH79`El#iO|uIwE9v@>8l_8|JaZ};oAAh> zc|Dwe9VYt4ACA_=28eXifryMK4tW|SrGUnF!T@fJVLqsGcN%{bI>}NckE!x-nkZ6m zLY^!Yk~7!=gB?6#tCm4b9xJ!rcdo>^`WG%v;^LE4F&%d03{Dm%?9hgdZm780$P?xm zq@n=KX^ykz11pl-kvRRiAt?`?4WJ^Vqd5E&E%`2@8)@|7GSsZ`VHo3pM#$Z}JdB(^ zI??vr)r!Xt9rI?ho8niewNk7YH&Yy33wTB}zi%YU*TPP-=sa_6Ue)hKW3Bk?O#LI< zK_bX^a4Is=HU-q7QMbjg=~KbMa6o27=E^<)${-EMD?>6XMdYj@)U%7$o`T!mWR6CT zBr4{~X2m?!Y?voo=s~50LsYR&a&1_#P7(4G&^Re>o-N5jN{zHZxS@#OpRLbK@F1{n z`dkg$FKW*<xh}|W66F^`C1oz*a~dGTdl!rQeZ%taA2-Ua?VdplLLGxpvVtlf)woAa zzQiqPl-kMtq+!`l!n*k5xuu);L;HUrm6h<}HX4%hNe}0_1izjKTn6kH-RIlv$nPxU z8^r1J<@0MoJC6KA;b3IL3h}e^8*{P1FeT~lzcx}zGcHuKLD6ubrch=hWuf$l!xtWk zEyy7QG6VMv?+P4}W0d1a`g|3P7o@wSNP|rdQHslPN}YW$oKzIdltW`g<J^!%&-ryR zIh|jGy_X2w%y4w>?Ml2y&OHvv%JK>^d(%kjI;8GPM2eHr`+Gg*{ZPEmUANTy|0bvP zK5sL(KH9xwVRtp@u83SJaHS^W%aAAL)(m;ZUllhlt#hI-VzmvcK|kGVcZ$z16|75Y z9|YURhoCQ~D;!s{aH;hS`V!MsE<+U({bIv(C%sl5pDtQ;3k^RYP;wJ@=H>d>ASj(b ziSC~O9D}uVeKeq{Jj9<8znyNX4?+<|P03KbUJq)LCTmsVl06Q+kSG_c@(c?-{mA!> zxwjE<Sk&xS^2Z|dQl)v+P>7Bd@?q<NrBN1IcWG*Uyz6|zSGY;u8N$uDus-)xn?Y|y zA??c&cehH?(c;?>{SM%}08Ayrfs6Gvu0}@m+wq@2SIS~hjByMw4wwKG0!{<20cHTN z1Kt8)qvJ<m@+KfzlO-j)v2XQ9chEC+;Cnc{U<aW`+%&t>-eAwMXWJciyFF&Ab*$pI zVq^k$@`c=30rT>hc<TWj$*VjLEAo^{eOPj0Cj7I>-=mW6UTLI9Mtx&Zf51rhc+^0^ z<1w-))WE1GqKZFWu6GzVzwRLsUM8}xtTWT|$gV5R4m-VSMu)>ZXowfC?4h~z*_Dcz z4TFCeU<ddC4uB6Z0>Ea*m&0Qm4UM$WSTHgrU&a)tt`A1T;lQXrx_mGBqbq6hCvp9{ z48=@<JRmO=*cJA6dj1~^XgCX-^fm&!*~$u-gARpR7AI?<|5@}8NdLtzu2%jZAEYJ? diff --git a/ssbtoolkit/pathways/__pycache__/Gi.cpython-39.pyc b/ssbtoolkit/pathways/__pycache__/Gi.cpython-39.pyc index 90575c1b81be1350a768a8c3e93401a12ac20d7d..465eaabb494545925492e3a595ed849104b0d3f8 100644 GIT binary patch delta 282 zcmX@$zQ>(Ak(ZZ?0SIyz)@<Z9kYKc#Y$KsGIY}aiF>3O8AelPZM$()u9jKxxV{)aW zGg}deSv>iWWD;ZiWK}6;#{9{iQt})XKw(YBqVmZFQW=bin{P@@V`8kFTp%mOST(s@ zR)(==^9tDwjEuJ?N6PET$AQd90TF2+A_qj|frvs7Q8sy*yqtCvOHgS}YLNs;L=r?u zfe2|3kpv<VK!gm4NS^#&UV<@qGKazv#)8Rf6<kHCLBh2l!Vg6F1Bs~3Y>EpR#U}%q enk+?)AmJtu(E=ixC+}0zWNQU6Cr^H>bQu7kBSo12 delta 333 zcmdnve!!hOk(ZZ?0SFGHR&V4skYIF}Y$Kt>qMMRhHaSirhw;|r(?Bw9vW28MTLw^B zQRd`QNoTfV5VK_RKFK7;gvp9h%8Uh*U8UqXDuKe9j71fbbEGmDlQv(Kn#ROfH91FC zO0F8D!4E|E1BocEqSVCXlFa-(3nQTV+RaO3H!w0rO%9dUvx^5QN(B+=AR-q;<b#MJ z5K#^!ZgII*6cnWv7X$SdF#`pnI7%{eQ{(fKif>ImC@-a%3{sg0B4j{B3W$&e5pp0x z9z-aBh`h-x3QHIZC$Cg+6{!IU)PV@ND{pOPQe4O=J{2ga$x_q=l4}MLtstUh@-8J! Nwl)xR>g1P7mjU|?Q#Swr diff --git a/ssbtoolkit/pathways/__pycache__/Gq.cpython-39.pyc b/ssbtoolkit/pathways/__pycache__/Gq.cpython-39.pyc index fc9fdfb33151758ee35559f7b552a6788f835e08..67c3f6b11c0ab685fa225bc8fa8057291a9f91dc 100644 GIT binary patch delta 31 lcmdmNvfhL{k(ZZ?0SIyz)@<aq5MZ>K>>{AFIZNOqCjfp`2rd8s delta 36 qcmZ2)ve|??k(ZZ?0SLCIR&V6C5MXqe>>{AVqMMRhwmD7UBqso=F$t>x diff --git a/ssbtoolkit/pathways/__pycache__/Gs.cpython-39.pyc b/ssbtoolkit/pathways/__pycache__/Gs.cpython-39.pyc index 7c707f341b8c5fbcde4986c40aa53d5fba6eb2e4..4e4edf05202da0d2e49b00e100c236281adee152 100644 GIT binary patch delta 31 lcmX@*c+8PIk(ZZ?0SIyz)@<Z%5@WQP+$W~Ad5f4B7XXa}2($nI delta 36 qcmX@+c*>DGk(ZZ?0SGpxR&V5P5@U3j+$W~QqMMRhwt0h?7#9G$-U=%K diff --git a/ssbtoolkit/pathways/__pycache__/OXTR_pathway.cpython-39.pyc b/ssbtoolkit/pathways/__pycache__/OXTR_pathway.cpython-39.pyc index 5b2f6392a2ca8ca76ba97f0891b93bf247c12ff7..b612432d031a19e64a986eec29dd22bbb1eb2918 100644 GIT binary patch delta 31 lcmdn*w%d(6k(ZZ?0SNLI)@<a?kzjP2Tp^*fd7i{VE&z)p2?78B delta 36 qcmdn(w%?6Ak(ZZ?0SH7VmT%<Fkzn+iTp^*vqMMRhwt1GsLM{NViV6e( diff --git a/ssbtoolkit/pathways/__pycache__/OXTR_pathway_testing.cpython-39.pyc b/ssbtoolkit/pathways/__pycache__/OXTR_pathway_testing.cpython-39.pyc index 08ef00fc91a148a55b99f4764ee945d04538dc67..8c15d617fdd3b9b05441cdc85c8090f28d1dcafb 100644 GIT binary patch delta 31 lcmZp*ZnWl3<mKgJ0D_!_H5<8SNHh9PULviu`IPi-MgV{F2^;_b delta 36 qcmZp)Znfr4<mKgJ00Pm8<r}$YNHYdaULvi;qMMRhw)vR!Zbks2$qFC< diff --git a/ssbtoolkit/pathways/__pycache__/__init__.cpython-39.pyc b/ssbtoolkit/pathways/__pycache__/__init__.cpython-39.pyc index 05e041438bd8c0a475bfdf625d5417c05098cdc6..0eac17baffc854b251379832ee7ce48671f41b40 100644 GIT binary patch delta 27 hcmZ3;IG2$-k(ZZ?0SIyy)=cEKVRV}4p)@hi7ywgS2BH7} delta 32 mcmbQsxR8-Mk(ZZ?0SH7VmQUoiVRW15p~Rw_l3F$~%NPJ|MF*w; diff --git a/ssbtoolkit/ssbmain.py.txt b/ssbtoolkit/ssbmain.py.txt deleted file mode 100644 index 9031e98..0000000 --- a/ssbtoolkit/ssbmain.py.txt +++ /dev/null @@ -1,1477 +0,0 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- -# -# Copyright 2021 Rui Ribeiro -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -__author__ = "Rui Ribeiro" -__email__ = "rui.ribeiro@univr.it" - -#system libraries -from re import sub -import sys -import os -import importlib -import urllib.request as urllib -from glob import glob - -#Scientific Libraries -#import math -import numpy as np -import pandas as pd -from pandas.core import construction -from scipy.optimize import curve_fit, minimize -from sklearn.preprocessing import minmax_scale - -#Plotting Libraries -import plotly.graph_objs as go -import pylab as pl -from matplotlib import * - -#PYSB -from pysb import * -from pysb.macros import * -from pysb.simulator import ScipyOdeSimulator - -#Layout libraries -#import qgrid - -#directories (problem with sphinx) -abs_path=(os.path.join(os.path.split(os.getcwd())[0], 'src/lib')) -sys.path.insert(0, os.path.abspath(abs_path)) -try: - from ssbtoolkit.directories import * - import ssbtoolkit.utils as utils -except: - from directories import * - from utils import * - - -import warnings -warnings.simplefilter(action='ignore') - - - - -#Human Target Receptors DataBase directory PATH -HuTRdb_path = os.path.join(os.getcwd(),'SSBtoolkit/src/databases/HuTRdb.sqlite3') - -""" -SSBtoolkit API -""" - -class binding: - """This class simulate ligand-target binding curves.""" - def __init__(self): - self.receptor_conc = None - self.lig_conc_range = None - self.pKd = None - self.submax_concentration = None - - def bind(self, **kwargs): - """ - Applies an function to calculate the fraction of occupited receptors at equilibrium. - - :parameter receptor_conc: Required (kwarg flt): concentration of receptor - :parameter lig_conc_range: Required (kwarg array): array of range of ligand concentration - :parameter pKd: Required (kwarg flt): pKd value of the ligand - """ - - if 'receptor_conc' not in kwargs: raise TypeError("ERROR: receptor_conc is missing") - if 'lig_conc_range' not in kwargs: raise TypeError("ERROR: lig_conc_range is missing") - if 'pKd' not in kwargs: raise TypeError("ERROR: pKd is missing") - - self._receptor_conc = kwargs.pop('receptor_conc') - self._lig_conc_range = kwargs.pop('lig_conc_range') - self._pKd = kwargs.pop('pKd') - - binding_data=[] - for conc in self._lig_conc_range: - binding_data.append(utils.LR_eq_conc(self._receptor_conc, conc, 0, self._pKd, 0)) - self.binding_data=binding_data - return self.binding_data - - def maxbend(self): - """ - Calculates the maximum bending point of a sigmoid-shaped curve according to the mathod of Sebaugh et al., 2003. - - :parameter drug_receptor: Required (int): concentration of the receptor - :parameter lig_conc_range: Required (array): array of a range of ligand concentration - :return: instance .submax_concentration (flt) - - .. note:: The minimization uses the Nelder-Mead method. - """ - - from scipy.optimize import curve_fit, minimize - - - def sigmoid(X, Bottom, Top, Kd, p): - return Bottom + (Top-Bottom)/(1+np.power((Kd/X),p)) - - xfit = np.geomspace(np.min(self._lig_conc_range), np.max(self._lig_conc_range), 50000) #warning: shoud this be the minimum and maximum of concentration - popt, pcov = curve_fit(sigmoid, self._lig_conc_range, self.binding_data, bounds=([np.min(self.binding_data),-np.inf,-np.inf, 0.5],[np.inf,np.max(self.binding_data),np.inf, 2.5])) - - - def sigmoid_deriv_b(x, a,d,c,b): - return (x/c)**b*(a - d)*np.log(x/c)/((x/c)**b + 1)**2 - - min_value = minimize(sigmoid_deriv_b, np.max(xfit), args=(popt[0],popt[1],popt[2],popt[3]), method = 'Nelder-Mead') - - self.submax_concentration = round(min_value.x[0],3) - return self.submax_concentration - - def show_curve(self): - """ - Plots ligand-target binding curve - """ - - #import plotly - import plotly.graph_objs as go - from scipy.optimize import curve_fit - from sklearn.preprocessing import minmax_scale - - ##Fitting curve to the data - - yy = minmax_scale(self.binding_data)*100 - - def equation_dose(X, Bottom, Top, EC50, p): - return Bottom + (Top-Bottom)/(1+np.power((EC50/X),p)) - - popt, pcov = curve_fit(equation_dose, self._lig_conc_range, yy, bounds=([np.min(yy),-np.inf,-np.inf, 0.5],[np.inf,np.max(yy),np.inf, 2.5])) - - xfit = np.geomspace(np.min(self._lig_conc_range), np.max(self._lig_conc_range), 50000) # These values are the same as the values for the simulation time and not ligand concentration - yfit = minmax_scale(equation_dose(xfit, *popt))*100 - - - - trace1 = go.Line(x=xfit, y=yfit, showlegend=False, name='radioligand') - if self.submax_concentration: - xsubmaximal = np.array(self.submax_concentration) - ysubmaximal = np.array(equation_dose(xsubmaximal, *popt)) - trace2 = go.Scatter(x=xsubmaximal, y=ysubmaximal, showlegend=True, mode='markers', name='submaximal ({} μM)'.format(xsubmaximal), - marker=dict(size=14)) - else: trace2=[] - - - layout = dict(title = '', - xaxis = dict( - title = '[ligand] μM', - type ='log', - exponentformat='e', - titlefont=dict( - size=20 - ), - tickfont=dict( - size=20 - )), - yaxis = dict( - title = '% occupied receptors', - - titlefont=dict( - size=20), - tickfont=dict( - size=20) - - ), - legend=dict(font=dict(size=15)), - autosize=False, - width=850, - height=650, - ) - if self.submax_concentration: - fig = go.Figure(data=[trace1, trace2], layout=layout) - else: - fig = go.Figure(data=[trace1], layout=layout) - return fig - -class simulation: - """ - This class simulates the mathematical models of the signaling pathways. - """ - class activation: - """ - Simulation of the activation of signaling pathways (i.e. activation by agonists) - """ - def __init__(self): - self._ligands=None - self._affinities=None - self._pathway=None - self._receptor_conc=None - self._lig_conc_range=None - self._ttotal=None - self._nsteps=None - self._binding_kinetics=True - self._binding_kinetic_parameters=None - self.simulation_data=None - self.processed_data=None - - def SetSimulationParameters(self, **kwargs): - """ - :parameter ligands: Required (kwargs list): list of ligands' names (str) - :parameter affinities: Required (kwargs list): list of pKd values (flt) - :parameter pathway: Required (kwargs str): name of the pathway ('Gs', 'Gi', 'Gq') - :parameter receptor_conc: Required (kwargs flt): receptors concentration (nM) - :parameter lig_conc_range: Required (kwargs array): range of ligands' concentration - :parameter ttotal: Required (kwargs int): simulation time (seconds) - :parameter nsteps: Required (kwargs int): simulation time step - :parameter binding_kinetics: Optional (kwargs boolean): default (False) - - - .. warning:: the order of the lists of ligands names and affinities list must be the same. - - """ - self._ligands= kwargs.pop('ligands') - if 'affinities' in kwargs: - self._affinities=kwargs.pop('affinities') - self._pathway=kwargs.pop('pathway') - self._receptor_conc=kwargs.pop('receptor_conc') - self._lig_conc_range=kwargs.pop('lig_conc_range') - self._ttotal=kwargs.pop('ttotal') - self._nsteps=kwargs.pop('nsteps') - self._binding_kinetics=kwargs.pop('binding_kinetics') - if 'binding_kinetic_parameters' in kwargs:self._binding_kinetic_parameters=kwargs.pop('binding_kinetic_parameters') - self._DefaultPathwayParametersDataFrame=pd.DataFrame() - - return - - def PathwayParameters(self): - """ - Display table with default pathway parameters. - - .. warning:: this functions requires the qgrid library. It doens't work on Google Colab. - """ - import qgrid - self._DefaultPathwayParametersDataFrame = pd.read_csv('src/lib/pathways/{}_parameters.csv'.format(self._pathway)) - - col_opts = { 'editable': False, 'sortable':False} - col_defs = {'Value': { 'editable': True, 'width': 150 }} - self._DefaultPathwayParametersTable = qgrid.show_grid(self._DefaultPathwayParametersDataFrame, column_options=col_opts,column_definitions=col_defs) - return self._DefaultPathwayParametersTable - - def UserPathwayParameters(self, path): - """ - Import user pathway parameters. - - :parameter path: Required (kwarg str): directory path - """ - import qgrid - self._DefaultPathwayParametersDataFrame = pd.read_csv(path) - col_opts = { 'editable': False, 'sortable':False} - col_defs = {'Value': { 'editable': True, 'width': 150 }} - self._DefaultPathwayParametersTable = qgrid.show_grid(self._DefaultPathwayParametersDataFrame, column_options=col_opts,column_definitions=col_defs) - return self._DefaultPathwayParametersTable - - def PathwayParametersToCSV(self, path): - """ - Export pathway parameters into CSV format. - - :parameter path: Required (kwarg str): directory path - """ - self._DefaultPathwayParametersTable.get_changed_df().to_csv(path, index=False) - print('saved in:', path) - return - - def Reactions(self): - """ - Display pathway reactions. - """ - from IPython.display import display, HTML - display(HTML("<style>.container {width:90% !important}</style>")) - return pd.read_csv('src/lib/pathways/{}_reactions.csv'.format(self._pathway)) - - def Run(self): - ''' - This function runs the pathway simulation and returns the raw simulation data. - ''' - - #Check inputs - if self._ligands==None: raise TypeError("ligands list undefined.") - elif self._pathway==None: raise TypeError("pathway name undefined.") - elif self._binding_kinetics==False and self._affinities==None: raise TypeError("affinity_values_dict undefined.") - elif self._binding_kinetics==True and self._affinities==None: pass - elif self._lig_conc_range.any() == False: raise TypeError("lig_conc_range undefined.") - elif self._ttotal==None: raise TypeError("ttotal undefined.") - elif self._nsteps==None: raise TypeError("nsteps undefined.") - elif self._receptor_conc==None: raise TypeError("receptor_conc undefined.") - else: pass - - - #Check Pathway availability and import it - available_pathways = ['Gs', 'Gi', 'Gq'] - if self._pathway == 'Gz(Gi)': self._pathway = 'Gi' - if self._pathway not in available_pathways: raise Exception('Unvailable Pathway. Please, introduce it manually. Pathways available: "Gs", "Gi", "Gq".') - mypathway = importlib.import_module('.'+self._pathway, package='src.lib.pathways') - - #Get default pathway parameters - if self._DefaultPathwayParametersDataFrame.empty and self._binding_kinetic_parameters==None: - self._DefaultPathwayParametersDataFrame = pd.read_csv('src/lib/pathways/{}_parameters.csv'.format(self._pathway)) - self._PathwayParameters = self._DefaultPathwayParametersDataFrame.set_index('Parameter').iloc[:,0].to_dict() - - elif self._DefaultPathwayParametersDataFrame.empty is False and self._binding_kinetic_parameters is None: - try: - #extract data from qgrid - newparameters = self._DefaultPathwayParametersTable.get_changed_df() - self._PathwayParameters = newparameters.set_index('Parameter').iloc[:,0].to_dict() - except: - self._PathwayParameters = self._DefaultPathwayParametersDataFrame.set_index('Parameter').iloc[:,0].to_dict() - - elif self._DefaultPathwayParametersDataFrame.empty and self._binding_kinetic_parameters is not None: - self._DefaultPathwayParametersDataFrame = pd.read_csv('src/lib/pathways/{}_parameters.csv'.format(self._pathway)) - - elif self._DefaultPathwayParametersDataFrame.empty is False and self._binding_kinetic_parameters is not None: - try: - #extract data from qgrid - newparameters = self._DefaultPathwayParametersTable.get_changed_df() - self._PathwayParameters = {**newparameters.set_index('Parameter').iloc[:,0].to_dict(), **self._binding_kinetic_parameters} - except: - self._PathwayParameters = {**self._DefaultPathwayParametersDataFrame.set_index('Parameter').iloc[:,0].to_dict(), **self._binding_kinetic_parameters} - - #Input - t = pl.geomspace(0.00001, self._ttotal, num=self._nsteps) # (a,b,c); a is the starting time ; b is the total time simulated ; c is the number of points - - - #Output - simulation_data={} - - #Function - for ligand in self._ligands: - ligand_name = os.path.splitext(str(ligand))[0] - data=[] - utils.printProgressBar(0, len(self._lig_conc_range), prefix = "{:<15}".format(ligand_name[:15]), suffix = 'Complete', length = 50) - - ###DANGER ZONE### - if self._binding_kinetic_parameters is not None: - self._PathwayParameters = {**self._DefaultPathwayParametersDataFrame.set_index('Parameter').iloc[:,0].to_dict(), **self._binding_kinetic_parameters[self._ligands.index(ligand)]} - ###################### - - for idx in range(len(self._lig_conc_range)): - - ligand_conc = self._lig_conc_range[idx] - if self._binding_kinetics == False: - #get LR conc - parameters = {**self._PathwayParameters, 'R_init':self._receptor_conc} - LR_conc_init = utils.LR_eq_conc(self._receptor_conc, ligand_conc, 0, self._affinities[self._ligands.index(ligand)], 0) - mymodel = mypathway.network(LR=LR_conc_init, kinetics=False, **parameters) - simres = ScipyOdeSimulator(mymodel, tspan=t, compiler='cython').run() - yout = simres.all - - elif self._binding_kinetics == True: - parameters={**self._PathwayParameters,'R_init':self._receptor_conc, 'L_init':self._lig_conc_range[idx] } - mymodel = mypathway.network(kinetics=True, **parameters) - simres = ScipyOdeSimulator(mymodel, tspan=t, compiler='cython').run() - yout = simres.all - - d1={'ligand_conc':ligand_conc, 'time':t } - - for idx2 in range(len(mypathway.list_of_observables)): - d2={mypathway.list_of_observables[idx2]:yout[mypathway.list_of_observables[idx2]]} - d1.update(d2) - data.append(d1) - utils.printProgressBar(idx + 1, len(self._lig_conc_range), prefix = "{:<15}".format(ligand_name[:15]), suffix = 'Complete', length = 50) - - - simulation_data[ligand_name] = {'sim_data':data, - 'label':ligand_name,} - self.simulation_data = simulation_data - return - - def Analysis(self): - ''' - This function calculates the dose-response effect. - - :return: instance of processed_data - ''' - - if self.simulation_data == None: raise TypeError('There is no simulation data. simulation.activation.run() must be run first.') - - from sklearn.preprocessing import minmax_scale - - # Define all the lists and dictionaries used in this function - raw_data=[] - normalized_data=[] - fitted_data=[] - - dose={} - - #defining concentration range - lig_conc_min = self._lig_conc_range.min() - lig_conc_max = self._lig_conc_range.max() - - #Main function - for ligand in self.simulation_data: - - #definig and dictionaries used in this loop: - raw_data_dict={} - normalized_data_dict={} - fitted_data_dict={} - - # Calculate dose-response curve - #get metabolite concentration, rescale, and transform data if pathway/metabolite decrease - # metabolite_raw is not normalized - if self._pathway == 'Gi' or self._pathway == 'Gz(Gi)': - metabolite='cAMP' - metabolite_conc_raw=[] - for i in range(len(self._lig_conc_range)): - n=np.amax(self.simulation_data[ligand]['sim_data'][i]['obs_'+metabolite]) #cad - metabolite_conc_raw.append(n) - metabolite_conc_norm = minmax_scale(1-np.array(metabolite_conc_raw)) - elif self._pathway == 'Gs': - metabolite='cAMP' - metabolite_conc_raw=[] - for i in range(len(self._lig_conc_range)): - n=np.amax(self.simulation_data[ligand]['sim_data'][i]['obs_'+metabolite]) #cad - metabolite_conc_raw.append(n) - metabolite_conc_norm = minmax_scale(np.array(metabolite_conc_raw)) - elif self._pathway == 'Gq': - metabolite='IP3' - metabolite_conc_raw=[] - for i in range(len(self._lig_conc_range)): - n=np.amax(self.simulation_data[ligand]['sim_data'][i]['obs_'+metabolite]) #cad - metabolite_conc_raw.append(n) - metabolite_conc_norm = minmax_scale(np.array(metabolite_conc_raw)) - else: raise Exception('Unvailable Pathway. Please, introduce it manually. Networs available: "Gs", "Gi", "Gq".') - - - ## save results - raw_data_dict['x']=self._lig_conc_range - raw_data_dict['y']=metabolite_conc_raw - raw_data_dict['label']=self.simulation_data[ligand]['label'] - - normalized_data_dict['x']=self._lig_conc_range - normalized_data_dict['y']=metabolite_conc_norm - normalized_data_dict['label']=self.simulation_data[ligand]['label'] - - ## create a list of all data - raw_data.append(raw_data_dict) - normalized_data.append(normalized_data_dict) - - ##Fitting curve to the data - - def equation_dose(X, Bottom, Top, EC50, p): - return Bottom + (Top-Bottom)/(1+np.power((EC50/X),p)) - - popt_EC50, pcov = curve_fit(equation_dose, self._lig_conc_range, metabolite_conc_norm, bounds=([np.min(metabolite_conc_norm),-np.inf,-np.inf, 0.5],[np.inf,np.max(metabolite_conc_norm),np.inf, 2.5])) - - xfit_EC50 = np.geomspace(lig_conc_min, lig_conc_max, 50000) # These values are the same as the values for the simulation time and not ligand concentration - yfit_EC50 = equation_dose(xfit_EC50, *popt_EC50) - - fit_EC50={'x':xfit_EC50, 'y':yfit_EC50, 'label':self.simulation_data[ligand]['label']} - - dose[ligand] = {'raw_data': raw_data_dict, - 'normalized_data':normalized_data_dict , - 'fitted_data': fit_EC50, - 'EC50 (μM)': round(popt_EC50[2],5), - 'pEC50': round(-np.log10(popt_EC50[2]*1E-6),2)} - - self.processed_data=dose - return - - def Curve(self, save=False, filename=None): - ''' - Plots the dose-response curve. - ''' - - if self.simulation_data == None: raise TypeError('There is no simulation data. simulation.activation.run() must be run first.') - - import plotly - import plotly.graph_objs as go - import plotly.offline as pyoff - - colors = plotly.colors.DEFAULT_PLOTLY_COLORS - - plot_data=[] - - color_id=0 - for ligand in self.processed_data: - trace_norm = go.Scatter(x=self.processed_data[ligand]['normalized_data']['x'], - y=minmax_scale(self.processed_data[ligand]['normalized_data']['y'])*100 , - mode='markers', - showlegend=True, - name=self.processed_data[ligand]['normalized_data']['label'], - marker=dict(color=colors[color_id])) - plot_data.append(trace_norm) - - trace_fitted = go.Scatter(x=self.processed_data[ligand]['fitted_data']['x'], - y=minmax_scale(self.processed_data[ligand]['fitted_data']['y'])*100, - mode='lines', - showlegend=False, - name=self.processed_data[ligand]['fitted_data']['label'], - line=dict(color=colors[color_id])) - plot_data.append(trace_fitted) - color_id +=1 - - layout = dict(title = '', - xaxis = dict( - title = '[ligand] μM', - type ='log', - #range = [-3, 2], - exponentformat='e', - titlefont=dict( - size=20 - ), - tickfont=dict( - size=20 - )), - yaxis = dict( - title = '% Response', - #range = [0, 100], - titlefont=dict( - size=20), - tickfont=dict( - size=20) - - ), - legend=dict(font=dict(size=15)), - autosize=False, - width=850, - height=650, - ) - - fig = go.Figure(data=plot_data, layout=layout) - #fig['layout']['yaxis'].update(autorange = True) - - if save==True: - if filename==None: - filename='plot.html' - return pyoff.plot(fig, filename=filename) - else: - ext = os.path.splitext(filename)[-1] - if ext == '.png': fig.write_image(filename, scale=3) - elif ext == '.html': pyoff.plot(fig, filename=filename) - else: raise TypeError("extension not valid. Use png or html.") - elif save ==False: return fig - return - - def Potency(self): - ''' - Return the potency values as a pandas DataFrame. - ''' - import pandas as pd - data = simulation.activation.PotencyToDict(self) - df = pd.DataFrame.from_dict(data, orient='index') - return df - - def PotencyToDict(self): - ''' - Convert potencies into a dictionary. - ''' - - #dependencies - if self.processed_data == None: raise TypeError('Simulation data unprocessed. simulation.activation.analysis() must be run first.') - - kvalues={} - for ligand in self.processed_data: - IC50 = list(self.processed_data[ligand].keys())[-2] - IC50_value = self.processed_data[ligand][IC50] - pIC50 = list(self.processed_data[ligand].keys())[-1] - pIC50_value = self.processed_data[ligand][pIC50] - kvalues[ligand]={IC50:IC50_value, pIC50:pIC50_value} - return kvalues - - def PotencyToCSV(self, path): - ''' - Exports the potency values into csv format. - - :parameter path: Required (kwarg str): directory path to save the csv file - ''' - - data = simulation.activation.PotencyToDict(self) - df = pd.DataFrame.from_dict(data, orient='index') - df.to_csv(path, index=False) - return - - class inhibition: - """ - Simulation of the inhibition of signaling pathways (i.e. inhibition by antagonists). - """ - def __init__(self): - self._agonist=None - self._agonist_affinity=None - self._agonist_submaximal_conc=None - self._antagonists=None - self._antagonists_affinities=None - self._pathway=None - self._receptor_conc=None - self._lig_conc_range=None - self._ttotal=None - self._nsteps=None - self._binding_kinetics=False - self._binding_kinetic_parameters=None - self.simulation_data=None - self.processed_data=None - - def SetSimulationParameters(self, **kwargs): - """ - :parameter agonist: Required (kwargs str): agonist name - :parameter agonist_affinity: Required (kwargs flt): agonist pKd value - :parameter agonist_submaximal_conc: Required (kwargs flt): agonist submaximal concentration - :parameter antagonists: Required (kwargs list):list of antagonists names (str) - :parameter antagonists_affinities: Required (kwargs list): list of antagonists affinity values (flt) - :parameter antagonists_conc_range: Required (kwargs array): range of ligands' concentration (nM) - :parameter pathway: Required (kwargs str): name of the pathway ('Gs', 'Gi', 'Gq') - :parameter receptor_conc: Required (kwargs flt): receptors concentration (nM) - :parameter ttotal: Required (kwargs int): simulation time (seconds) - :parameter nsteps: Required (kwargs int): simulation time step - :parameter kinetics: Optional (kwargs boolean): default (False) - - - :return: instances of all parameters - - .. warning:: the order of the lists of the antagonists names and affinities list must be the same. - - """ - self._agonist= kwargs.pop('agonist') - self._agonist_affinity=kwargs.pop('agonist_affinity') - self._agonist_submaximal_conc=kwargs.pop('agonist_submaximal_conc') - self._antagonists=kwargs.pop('antagonists') - self._antagonists_affinities=kwargs.pop('antagonists_affinities') - self._pathway=kwargs.pop('pathway') - self._receptor_conc=kwargs.pop('receptor_conc') - self._lig_conc_range=kwargs.pop('lig_conc_range') - self._ttotal=kwargs.pop('ttotal') - self._nsteps=kwargs.pop('nsteps') - if 'kinetics' in kwargs: - self._binding_kinetics=kwargs.pop('kinetics') - if self._binding_kinetics==True: raise TypeError("The of Kinetic parameters during an inhibition simulation it is not supported yet.") - else: self._binding_kinetics=False - if 'binding_kinetic_parameters' in kwargs: - self._binding_kinetic_parameters=kwargs.pop('binding_kinetic_parameters') - self._DefaultPathwayParametersDataFrame=pd.DataFrame() - - return - - def PathwayParameters(self): - """ - Display table with default pathway parameters. - - .. warning:: this functions requires the qgrid library. It doens't work on Google Colab. - """ - import qgrid - self._DefaultPathwayParametersDataFrame = pd.read_csv('src/lib/pathways/{}_parameters.csv'.format(self._pathway)) - - col_opts = { 'editable': False, 'sortable':False} - col_defs = {'Value': { 'editable': True, 'width': 150 }} - self._DefaultPathwayParametersTable = qgrid.show_grid(self._DefaultPathwayParametersDataFrame, column_options=col_opts,column_definitions=col_defs) - return self._DefaultPathwayParametersTable - - def UserPathwayParameters(self, path): - """ - Import user pathway parameters. - - :parameter path: Required (kwarg str): directory path - """ - import qgrid - self._DefaultPathwayParametersDataFrame = pd.read_csv(path) - col_opts = { 'editable': False, 'sortable':False} - col_defs = {'Value': { 'editable': True, 'width': 150 }} - self._DefaultPathwayParametersTable = qgrid.show_grid(self._DefaultPathwayParametersDataFrame, column_options=col_opts,column_definitions=col_defs) - return self._DefaultPathwayParametersTable - - def PathwayParametersToCSV(self, path): - """ - Export pathway parameters into CSV format. - - :parameter path: Required (kwarg str): directory path - """ - self._DefaultPathwayParametersTable.get_changed_df().to_csv(path, index=False) - print('saved in:', path) - return - - def Reactions(self): - """ - Display pathway reactions. - """ - from IPython.display import display, HTML - display(HTML("<style>.container {width:90% !important}</style>")) - return pd.read_csv('src/lib/pathways/{}_reactions.csv'.format(self._pathway)) - - def Run(self): - ''' - This function runs the pathway simulation and returns the raw simulation data. - ''' - - #Check inputs - if self._agonist==None: raise TypeError("agonist undefined.") - elif self._agonist_affinity==None: raise TypeError("agonist_affinity undifined.") - elif self._antagonists==None: raise TypeError("antagonists list undefined.") - elif self._antagonists_affinities==None: raise TypeError("antagonists affinity values undefined.") - elif self._pathway==None: raise TypeError("pathway undefined.") - elif self._lig_conc_range.any() == False: raise TypeError("lig_conc_range undefined.") - elif self._agonist_submaximal_conc == None: raise TypeError("agonist_submaximal_conc undifined.") - elif self._ttotal==None: raise TypeError("ttotal undefined.") - elif self._nsteps==None: raise TypeError("nsteps undefined.") - elif self._receptor_conc==None: raise TypeError("receptor_conc undefined.") - elif self._binding_kinetics==True: raise TypeError("The of Kinetic parameters during an inhibition simulation it is not supported yet.") - else: pass - - #check pathway - available_pathways = ['Gs', 'Gi', 'Gq'] - if self._pathway == 'Gz(Gi)': self._pathway = 'Gi' - if self._pathway not in available_pathways: raise Exception('Unvailable Pathway. Please, introduce it manually. Networs available: "Gs", "Gi", "Gq".') - mypathway = importlib.import_module('.'+self._pathway, package='src.lib.pathways') - - #Get default pathway parameters - if self._DefaultPathwayParametersDataFrame.empty: - self._DefaultPathwayParametersDataFrame = pd.read_csv('src/lib/pathways/{}_parameters.csv'.format(self._pathway)) - self._PathwayParameters = self._DefaultPathwayParametersDataFrame.set_index('Parameter').iloc[:,0].to_dict() - - elif self._DefaultPathwayParametersDataFrame.empty is False: - try: - #extract data from qgrid - newparameters = self._DefaultPathwayParametersTable.get_changed_df() - self._PathwayParameters = newparameters.set_index('Parameter').iloc[:,0].to_dict() - except: - self._PathwayParameters = self._DefaultPathwayParametersDataFrame.set_index('Parameter').iloc[:,0].to_dict() - - #Input - t = pl.geomspace(0.00001, self._ttotal, num=self._nsteps) # (a,b,c); a is the starting time ; b is the total time simulated ; c is the number of points - - #Output - simulation_data={} - - #Function - for ligand in self._antagonists: - ligand_name = os.path.splitext(ligand)[0] - data=[] - utils.printProgressBar(0, len(self._lig_conc_range), prefix = "{:<15}".format(ligand_name[:15]), suffix = 'Complete', length = 50) - - for idx in range(len(self._lig_conc_range)): - - ligand_conc = self._lig_conc_range[idx] - - #get LR conc - parameters = {**self._PathwayParameters, 'R_init':self._receptor_conc} - LR_conc_init = utils.LR_eq_conc(self._receptor_conc, self._agonist_submaximal_conc, ligand_conc, self._agonist_affinity, self._antagonists_affinities[self._antagonists.index(ligand)]) - mymodel = mypathway.network(LR=LR_conc_init, kinetics=False, **parameters) - simres = ScipyOdeSimulator(mymodel, tspan=t, compiler='cython').run() - yout = simres.all - - d1={'ligand_conc':ligand_conc, 'time':t } - - for idx2 in range(len(mypathway.list_of_observables)): - d2={mypathway.list_of_observables[idx2]:yout[mypathway.list_of_observables[idx2]]} - d1.update(d2) - data.append(d1) - utils.printProgressBar(idx + 1, len(self._lig_conc_range), prefix = "{:<15}".format(ligand_name[:15]), suffix = 'Complete', length = 50) - - simulation_data[ligand_name] = {'sim_data':data, - 'label':self._agonist+' + ' + ligand_name} - - self.simulation_data=simulation_data - return - - def Analysis(self): - ''' - This function calculates the dose-response effect. - - :return: instance processed_data - ''' - #dependencies - if self.simulation_data == None: raise TypeError('There is no simulation data. simulation.inhibition.run() must be run first.') - - - from sklearn.preprocessing import minmax_scale - - # Define all the lists and dictionaries used in this function - raw_data=[] - normalized_data=[] - fitted_data=[] - - dose={} - - #defining concentration range - #defining concentration range - lig_conc_min = self._lig_conc_range.min() - lig_conc_max = self._lig_conc_range.max() - - #Main function - for ligand in self.simulation_data: - - #definig and dictionaries used in this loop: - raw_data_dict={} - normalized_data_dict={} - fitted_data_dict={} - - # Calculate dose-response curve - #get metabolite concentration, rescale, and transform data if pathway/metabolite decrease - # metabolite_raw is not normalized - if self._pathway == 'Gi' or self._pathway == 'Gz(Gi)': - metabolite='cAMP' - metabolite_conc_raw=[] - for i in range(len(self._lig_conc_range)): - n=np.amax(self.simulation_data[ligand]['sim_data'][i]['obs_'+metabolite]) #cad - metabolite_conc_raw.append(n) - metabolite_conc_norm = minmax_scale(1-np.array(metabolite_conc_raw)) - elif self._pathway == 'Gs': - metabolite='cAMP' - metabolite_conc_raw=[] - for i in range(len(self._lig_conc_range)): - n=np.amax(self.simulation_data[ligand]['sim_data'][i]['obs_'+metabolite]) #cad - metabolite_conc_raw.append(n) - metabolite_conc_norm = minmax_scale(np.array(metabolite_conc_raw)) - elif self._pathway == 'Gq': - metabolite='IP3' - metabolite_conc_raw=[] - for i in range(len(self._lig_conc_range)): - n=np.amax(self.simulation_data[ligand]['sim_data'][i]['obs_'+metabolite]) #cad - metabolite_conc_raw.append(n) - metabolite_conc_norm = minmax_scale(np.array(metabolite_conc_raw)) - else: raise Exception('Unvailable Pathway. Please, introduce it manually. Networs available: "Gs", "Gi", "Gq".') - - - ## save results - raw_data_dict['x']=self._lig_conc_range - raw_data_dict['y']=metabolite_conc_raw - raw_data_dict['label']=self.simulation_data[ligand]['label'] - - normalized_data_dict['x']=self._lig_conc_range - normalized_data_dict['y']=metabolite_conc_norm - normalized_data_dict['label']=self.simulation_data[ligand]['label'] - - ## create a list of all data - raw_data.append(raw_data_dict) - normalized_data.append(normalized_data_dict) - - ##Fitting curve to the data - - def equation_dose(X, Bottom, Top, EC50, p): - return Bottom + (Top-Bottom)/(1+np.power((EC50/X),p)) - - popt_IC50, pcov = curve_fit(equation_dose, self._lig_conc_range, metabolite_conc_norm, bounds=([np.min(metabolite_conc_norm),-np.inf,-np.inf, 0.5],[np.inf,np.max(metabolite_conc_norm),np.inf, 2.5])) - - xfit_IC50 = np.geomspace(lig_conc_min, lig_conc_max, 50000) # These values are the same as the values for the simulation time and not ligand concentration - yfit_IC50 = equation_dose(xfit_IC50, *popt_IC50) - - fit_IC50={'x':xfit_IC50, 'y':yfit_IC50, 'label':self.simulation_data[ligand]['label']} - - - - - dose[ligand] = {'raw_data': raw_data_dict, - 'normalized_data':normalized_data_dict , - 'fitted_data': fit_IC50, - 'IC50 (μM)': round(popt_IC50[2],5), - 'pIC50': round(-np.log10(popt_IC50[2]*1E-6),2)} - - self.processed_data=dose - return - - def Curve(self, save=False, filename=None): - ''' - Plot the dose-response curve. - ''' - #dependencies - if self.processed_data == None: raise TypeError('Simulation data unprocessed. simulation.inhibition.analysis() must be run first.') - - import plotly - import plotly.graph_objs as go - import plotly.offline as pyoff - - colors = plotly.colors.DEFAULT_PLOTLY_COLORS - - plot_data=[] - - color_id=0 - for ligand in self.processed_data: - trace_norm = go.Scatter(x=self.processed_data[ligand]['normalized_data']['x'], - y=minmax_scale(self.processed_data[ligand]['normalized_data']['y'])*100 , - mode='markers', - showlegend=True, - name=self.processed_data[ligand]['normalized_data']['label'], - marker=dict(color=colors[color_id])) - plot_data.append(trace_norm) - - trace_fitted = go.Scatter(x=self.processed_data[ligand]['fitted_data']['x'], - y=minmax_scale(self.processed_data[ligand]['fitted_data']['y'])*100, - mode='lines', - showlegend=False, - name=self.processed_data[ligand]['fitted_data']['label'], - line=dict(color=colors[color_id])) - plot_data.append(trace_fitted) - color_id +=1 - - layout = dict(title = '', - xaxis = dict( - title = '[ligand] μM', - type ='log', - #range = [-4, 2], - exponentformat='e', - titlefont=dict( - size=20 - ), - tickfont=dict( - size=20 - )), - yaxis = dict( - title = '% Response', - #range = [0, 100], - titlefont=dict( - size=20), - tickfont=dict( - size=20) - - ), - legend=dict(font=dict(size=15)), - autosize=False, - width=850, - height=650, - ) - - fig = go.Figure(data=plot_data, layout=layout) - if save==True: - if filename==None: - filename='plot.html' - return pyoff.plot(fig, filename=filename) - else: - ext = os.path.splitext(filename)[-1] - if ext == '.png': fig.write_image(filename, scale=3) - elif ext == '.html': pyoff.plot(fig, filename=filename) - else: raise TypeError("extension not valid. Use png or html.") - elif save ==False: return fig - return - - def constants(self): - ''' - Returns the potency values. - ''' - - #dependencies - if self.processed_data == None: raise TypeError('Simulation data unprocessed. simulation.activation.analysis() must be run first.') - - kvalues={} - for ligand in self.processed_data: - IC50 = list(self.processed_data[ligand].keys())[-2] - IC50_value = self.processed_data[ligand][IC50] - pIC50 = list(self.processed_data[ligand].keys())[-1] - pIC50_value = self.processed_data[ligand][pIC50] - kvalues[ligand]={IC50:IC50_value, pIC50:pIC50_value} - self.constants = kvalues - return kvalues - - def PotencyToDict(self): - ''' - Convert potencies into a dictionary. - ''' - #dependencies - if self.processed_data == None: raise TypeError('Simulation data unprocessed. simulation.inhibition.analysis() must be run first.') - - kvalues={} - for ligand in self.processed_data: - IC50 = list(self.processed_data[ligand].keys())[-2] - IC50_value = self.processed_data[ligand][IC50] - pIC50 = list(self.processed_data[ligand].keys())[-1] - pIC50_value = self.processed_data[ligand][pIC50] - kvalues[ligand]={IC50:IC50_value, pIC50:pIC50_value} - return kvalues - - def Potency(self): - ''' - Return the potency values as a pandas DataFrame. - ''' - import pandas as pd - data = simulation.inhibition.PotencyToDict(self) - df = pd.DataFrame.from_dict(data, orient='index') - return df - - def PotencyToCSV(self, path): - ''' - Exports the potency values into csv format. - - :parameter path: Required (kwarg str): directory path to save the csv file - ''' - data = simulation.inhibition.PotencyToDict(self) - df = pd.DataFrame.from_dict(data, orient='index') - df.to_csv(path, index=False) - return - - class fitModel: - """ - Fit a model to experimental data. - - .. note:: This class was developed to reproduce data from a specific experimental setup. Please see tutorial 4 (OXTR pathay). Use carefully! - """ - def __init__(self): - - #fitting parameters - self._expratio = None - self._seed = None - self._maxiter = None - self._seed_incrementor = None - self._target_parameter = None - - #Pathway parameters - self._ttotal = None - self._nsteps = None - self._pathway = None - self._observable = None - self.pathway_parameters = {} - - def SetSimulationParameters(self, **kwargs): - """ - :parameter pathway_parameters: Required (kwargs): dict of pathway parameters - :parameter pathway: Required (kwargs str): name of the pathway ('Gs', 'Gi', 'Gq') - :parameter ttotal: Required (kwargs int): simulation time (seconds) - :parameter nsteps: Required (kwargs int): simulation time step - :parameter observable: Required (kwargs str): molecular specie to be measured - - :return: instances of all parameters - - """ - - if 'pathway_parameters' in kwargs: - self.pathway_parameters = kwargs.pop('pathway_parameters') - #print('pathway_parameters YES') - self._DefaultPathwayParametersDataFrame=pd.DataFrame() - if 'ttotal' in kwargs: - self._ttotal = int(kwargs.pop('ttotal')) - print('ttotal =', self._ttotal) - else: raise TypeError("ttotal undefined.") - - if 'nsteps' in kwargs: - self._nsteps = int(kwargs.pop('nsteps', 1000)) - print('nsteps =', self._nsteps) - - if 'pathway' in kwargs: - self._pathway = str(kwargs.pop('pathway')) - print('pathway ->', self._pathway) - else: raise TypeError("pathway undefined.") - - available_pathways = ['Gs', 'Gi', 'Gq', 'OXTR_pathway'] - if self._pathway == 'Gz(Gi)': self._pathway = 'Gi' - if self._pathway not in available_pathways: raise Exception('Unvailable Pathway. Please, introduce it manually. Networs available: "Gs", "Gi", "Gq".') - - - if'observable' in kwargs: - self._observable = str(kwargs.pop('observable')) - print('observable ->', self._observable) - else: raise TypeError("observable undefined.") - - return - - def PathwayParameters(self): - """ - Display table with default pathway parameters. - - .. warning:: this functions requires the qgrid library. It doens't work on Google Colab. - """ - import qgrid - self._DefaultPathwayParametersDataFrame = pd.read_csv('src/lib/pathways/{}_parameters.csv'.format(self._pathway)) - - col_opts = { 'editable': False, 'sortable':False} - col_defs = {'Value': { 'editable': True, 'width': 150 }} - self._DefaultPathwayParametersTable = qgrid.show_grid(self._DefaultPathwayParametersDataFrame, column_options=col_opts,column_definitions=col_defs) - return self._DefaultPathwayParametersTable - - def UserPathwayParameters(self, path): - """ - Import user pathway parameters. - - :parameter path: Required (kwarg str): directory path - """ - import qgrid - self._DefaultPathwayParametersDataFrame = pd.read_csv(path) - col_opts = { 'editable': False, 'sortable':False} - col_defs = {'Value': { 'editable': True, 'width': 150 }} - self._DefaultPathwayParametersTable = qgrid.show_grid(self._DefaultPathwayParametersDataFrame, column_options=col_opts,column_definitions=col_defs) - return self._DefaultPathwayParametersTable - - def PathwayParametersToCSV(self, path): - """ - Export pathway parameters into CSV format. - - :parameter path: Required (kwarg str): directory path - """ - self._DefaultPathwayParametersTable.get_changed_df().to_csv(path, index=False) - print('saved in:', path) - return - - def Reactions(self): - """ - Display pathway reactions. - """ - from IPython.display import display, HTML - display(HTML("<style>.container {width:90% !important}</style>")) - return pd.read_csv('src/lib/pathways/{}_reactions.csv'.format(self._pathway)) - - def Run(self, **kwargs): - """ - Fits of the model to experimental data. - - :parameter expratio: Required (kwargs flt): experimental signalling specie concentration ratio - :parameter target_parameter: Required (kwargs str):kinetic parameter to me modified - :parameter maxiter: Required (kwargs int): maximum number of iteration - :parameter seed: Required (kwargs flt): ramdom seed for scaling the modified parameter - :parameter seed_incrementor: Required (kwargs flt): seed incrementor (each iteration will increment the seed by this value) - :parameter seed_decrementor: Required (kwargs flt): seed decrementor (each iteration will decrement the seed by this value) - - """ - - from scipy.signal import find_peaks - import decimal - #fitting parameters - if 'expratio' in kwargs: - self._expratio = float(kwargs.pop('expratio')) - print('expratio =', self._expratio) - else: raise TypeError("exratio undefined.") - - if 'seed' in kwargs: - self._seed = float(kwargs.pop('seed')) - print('seed =', self._seed) - else: raise TypeError("seed undefined.") - - if 'maxiter' in kwargs: - self._maxiter = int(kwargs.pop('maxiter', 100)) - print('maxiter =', self._maxiter) - - if 'seed_incrementor' in kwargs: - self._seed_incrementor = float(kwargs.pop('seed_incrementor', 0.1)) - print('seed_incrementor =', self._seed_incrementor) - - if 'seed_decrementor' in kwargs: - self._seed_decrementor = float(kwargs.pop('seed_decrementor', 0.1)) - print('seed_decrementor =', self._seed_decrementor) - - if 'target_parameter' in kwargs: - self._target_parameter = str(kwargs.pop('target_parameter')) - print('target_parameter ->', self._target_parameter) - else: raise TypeError("target_parameter undefined.") - - - #Get default pathway parameters - if self._DefaultPathwayParametersDataFrame.empty and self.pathway_parameters==None: - self._DefaultPathwayParametersDataFrame = pd.read_csv('src/lib/pathways/{}_parameters.csv'.format(self._pathway)) - self._PathwayParameters = self._DefaultPathwayParametersDataFrame.set_index('Parameter').iloc[:,0].to_dict() - - elif self._DefaultPathwayParametersDataFrame.empty is False and self.pathway_parameters is None: - try: - #extract data from qgrid - newparameters = self._DefaultPathwayParametersTable.get_changed_df() - self._PathwayParameters = newparameters.set_index('Parameter').iloc[:,0].to_dict() - except: - self._PathwayParameters = self._DefaultPathwayParametersDataFrame.set_index('Parameter').iloc[:,0].to_dict() - - elif self._DefaultPathwayParametersDataFrame.empty and self.pathway_parameters is not None: - self._DefaultPathwayParametersDataFrame = pd.read_csv('src/lib/pathways/{}_parameters.csv'.format(self._pathway)) - self._PathwayParameters = {**self._DefaultPathwayParametersDataFrame.set_index('Parameter').iloc[:,0].to_dict(), **self.pathway_parameters} - - elif self._DefaultPathwayParametersDataFrame.empty is False and self.pathway_parameters is not None: - try: - #extract data from qgrid - newparameters = self._DefaultPathwayParametersTable.get_changed_df() - self._PathwayParameters = {**newparameters.set_index('Parameter').iloc[:,0].to_dict(), **self.pathway_parameters} - except: - self._PathwayParameters = {**self._DefaultPathwayParametersDataFrame.set_index('Parameter').iloc[:,0].to_dict(), **self.pathway_parameters} - - - - #simulation parameters: - if not self._ttotal: - raise TypeError("simulation parameters unknown. Set the the simulation parameters first wiht set_simulation_parameters()") - - #Main function - mypathway = importlib.import_module('.'+self._pathway, package='src.lib.pathways') - self.simtime = pl.geomspace(0.00001, self._ttotal, num=self._nsteps) - - #Simulation 1 - pathway_model = mypathway.network(LR=None, kinetics=True, **self._PathwayParameters) - sim1 = ScipyOdeSimulator(pathway_model, tspan=self.simtime,compiler='cython').run() - self.simres1 = sim1.all - - def calc_ratio(self): - - - #Simulation 2 - sim2 = ScipyOdeSimulator(mypathway.network(**self.new_pathway_parameters), tspan=self.simtime, compiler='cython').run() - self.simres2 = sim2.all - - #analysis - obs_name = 'obs_'+self._observable - obs_1 = self.simres1[obs_name] - obs_2 = self.simres2[obs_name] - - if 'time_in' in self.new_pathway_parameters: - self._time = np.take(self.simtime, np.where(self.simtime > int(self.new_pathway_parameters['time_in'])))[0] - obs_curve_1 = np.take(obs_1, np.where(self.simtime > int(self.new_pathway_parameters['time_in'])))[0] - obs_curve_2 = np.take(obs_2, np.where(self.simtime > int(self.new_pathway_parameters['time_in'])))[0] - - else: - self._time = np.take(self.simtime, np.where(self.simtime>0))[0] - obs_curve_1 = np.take(obs_1, np.where(self.simtime > 0))[0] - obs_curve_2 = np.take(obs_2, np.where(self.simtime > 0))[0] - - obs_peaks_1, _ = find_peaks(obs_curve_1) - obs_peaks_2, _ = find_peaks(obs_curve_2) - - vmax_obs_curve_1 = obs_curve_1[obs_peaks_1][-1]*1E3 - vmax_obs_curve_2 = obs_curve_2[obs_peaks_2][-1]*1E3 - - obs_ratio = round(vmax_obs_curve_2/vmax_obs_curve_1, abs(decimal.Decimal(str(self._expratio).rstrip('0')).as_tuple().exponent)) - - self._obs_curve_1=obs_curve_1 - self._obs_curve_2=obs_curve_2 - self._obs_peaks_1=obs_peaks_1 - self._obs_peaks_2=obs_peaks_2 - self._vmax_obs_curve_1=vmax_obs_curve_1 - self._vmax_obs_curve_2=vmax_obs_curve_2 - - return obs_ratio - - self._iteration=1 - print('\n') - - self._lst_ratio=[] - self._lst_seed=[] - - for idx in range(self._maxiter): - - prefix = 'iteration' - iteration_n = str(self._iteration) - print(f'\r{prefix} {iteration_n}', end='\r') - - self.new_pathway_parameters={**self._PathwayParameters, **{self._target_parameter:mypathway.defaultParameters[self._target_parameter]*self._seed}} - self.obs_ratio = calc_ratio(self) - - if self.obs_ratio == self._expratio: - self._lst_ratio.append(self.obs_ratio) - self._lst_seed.append(self._seed) - self._fold=round(self._seed, abs(decimal.Decimal(str(self._expratio).rstrip('0')).as_tuple().exponent)) - print('\n\nDONE!\n', '\nRatio: '+str(self.obs_ratio), '\nFOLD: '+str(self._fold), '\nNumber of iterations: '+str(self._iteration)) - break - elif self.obs_ratio < self._expratio: - - self._lst_ratio.append(self.obs_ratio) - self._lst_seed.append(self._seed) - self._iteration+=1 - self._seed += self._seed_incrementor - - else: - self._lst_ratio.append(self.obs_ratio) - self._lst_seed.append(self._seed) - - - self._iteration+=1 - self._seed -= self._seed_decrementor - - return - - def plotIterations(self, save=False, filename=None): - ''' - Plot iterations. - ''' - import plotly.offline as pyoff - #dependencies - if self._iteration == None: raise TypeError('Simulation data not exist. simulation.fitModel.run() must be run first.') - - #import plotly - import plotly.graph_objs as go - - iterations = np.arange(1,self._iteration+1) - - trace=dict(type='scatter', x=self._lst_seed, y=self._lst_ratio, mode='markers', - marker=dict(color= iterations, colorscale='Bluered_r', size=14, colorbar=dict(thickness=20, title='iteration number'))) - #axis_style=dict(zeroline=False, showline=True, mirror=True) - layout = dict(title = '', - xaxis = dict( - title = 'seed', - titlefont=dict( - size=20 - ), - tickfont=dict( - size=20 - )), - yaxis = dict( - title = '['+self._observable+']' + ' ratio', - titlefont=dict( - size=20), - tickfont=dict( - size=20) - - ), - legend=dict(font=dict(size=15)), - autosize=False, - width=850, - height=650 - ) - - fig = go.Figure(data=[trace], layout=layout) - if save==True: - if filename==None: - filename='plot.html' - return pyoff.plot(fig, filename=filename) - else: - ext = os.path.splitext(filename)[-1] - if ext == '.png': fig.write_image(filename, scale=3) - elif ext == '.html': pyoff.plot(fig, filename=filename) - else: raise TypeError("extension not valid. Use png or html.") - elif save ==False: return fig - return fig - - def plotCurves(self, save=False, filename=None): - ''' - Plot the amount of obeservable in function of time, Amplitude, Area Under the Curve, and Full Width at Half Maximum. - - :parameter save: Optional (kwarg boolean): default False - :parameter filename: Optional (kwarg str) - ''' - - from IPython.core.display import display, HTML - display(HTML("<style>.container { width:90% !important; }</style>")) - - - from plotly.subplots import make_subplots - from scipy.signal import peak_widths - from sklearn import metrics - import plotly.offline as pyoff - - - half_1 = peak_widths(self._obs_curve_1, self._obs_peaks_1, rel_height=0.5) - half_2 = peak_widths(self._obs_curve_2, self._obs_peaks_2, rel_height=0.5) - fwhm_1 = self._time[int(half_1[3])]-self._time[int(half_1[2])] - fwhm_2 = self._time[int(half_2[3])]-self._time[int(half_2[2])] - - - fig = make_subplots(rows=2, cols=2,vertical_spacing=0.15, - subplot_titles=("{} concentration".format(self._observable), "Amplitude", "Area under the curve", "Full Width at Half Maximum")) - - #################### - #### MAIN PLOT #### - #################### - fig.add_trace(go.Scatter(x=self._time, y=self._obs_curve_1*1E3, name='control'), row=1, col=1) - fig.add_trace(go.Scatter(x=self._time, y=self._obs_curve_2*1E3, name='{}-fold'.format(self._fold)), row=1, col=1) - fig.add_trace(go.Scatter(x=self._time[self._obs_peaks_1], y=self._obs_curve_1[self._obs_peaks_1]*1E3, - name='max value', showlegend=False, mode='markers', - marker=dict(symbol='x', size=13, color='Black')), row=1,col=1) - fig.add_trace(go.Scatter(x=self._time[self._obs_peaks_2], y=self._obs_curve_2[self._obs_peaks_2]*1E3, - name='max value', showlegend=False, mode='markers', - marker=dict(symbol='x', size=13, color='Black')), row=1,col=1) - fig.add_shape(type='line', x0=self._time[int(half_1[2])],y0=half_1[1][0]*1E3, x1=self._time[int(half_1[3])], y1=half_1[1][0]*1E3, - line=dict(color='Blue',dash='dash'),xref='x',yref='y', row=1, col=1) - fig.add_shape(type='line', x0=self._time[int(half_2[2])],y0=half_2[1][0]*1E3, x1=self._time[int(half_2[3])], y1=half_2[1][0]*1E3, - line=dict(color='Red',dash='dash'),xref='x',yref='y', row=1, col=1) - - # Update xaxis properties - fig.update_xaxes(title_text="Time (s)", showgrid=False, row=1, col=1, titlefont=dict(size=18), - linecolor='black', linewidth=2, - ticks='inside', tickfont=dict(size=18), tickcolor='black', ticklen=10, tickwidth=2) - - fig.update_yaxes(title_text=self._observable+' (nM)', titlefont=dict(size=18), showgrid=False, row=1, col=1, - linecolor='black', linewidth=2, - ticks='inside', tickfont=dict(size=18), tickcolor='black', ticklen=10, tickwidth=2) - - - #################### - #### AMPLITUDE #### - #################### - - AMP_labels = [1,2] - AMP_values = [self._vmax_obs_curve_1, self._vmax_obs_curve_2] - fig.add_trace(go.Bar(x=AMP_labels,y=AMP_values, width = [0.35,0.35], showlegend=False, marker_color='black', name=''), row=1, col=2 ) - - - - # Update xaxis properties - fig.update_xaxes(row=1, col=2, showgrid=False, linecolor='black', linewidth=2, range=[0,3], - tickmode='array', tickvals=[1,2], ticktext=['control', '{}-fold'.format(self._fold)], tickfont=dict(size=18)) - - fig.update_yaxes(showgrid=False, range=[round((min(AMP_values)-min(AMP_values)*0.5)/5)*5,round((max(AMP_values)+max(AMP_values)*0.5)/5)*5 ], row=1, col=2, - title_text=self._observable+' (nM)', titlefont=dict(size=18), - linecolor='black', linewidth=2, ticks='inside', ticklen=10, tickwidth=2, tickfont=dict(size=18)) - - # Add diff lines - AMP_diffs = [max(AMP_values) - v for v in AMP_values] - AMP_diff_labels = dict(zip(AMP_labels, AMP_diffs)) - fig.add_trace(go.Scatter(name='',x=[1,1.5,2], y=[max(AMP_values)+(max(AMP_values)*0.3)]*3, mode = 'lines+text',showlegend=False, line=dict(color='black', width=1),text=['', 'diff. = {} nM'.format(round(AMP_diffs[0], 3)),''], textposition='top center'), row=1, col=2) - fig.add_trace(go.Scatter(name='',x=[AMP_labels[0]-0.175, AMP_labels[0]+0.175], y=[AMP_values[0]+(AMP_values[0]*0.03)]*2, mode = 'lines',showlegend=False, line=dict(color='black', width=1)), row=1, col=2) - fig.add_trace(go.Scatter(name='',x=[AMP_labels[1]-0.175, AMP_labels[1]+0.175], y=[AMP_values[1]+(AMP_values[1]*0.03)]*2, mode = 'lines',showlegend=False, line=dict(color='black', width=1)), row=1, col=2) - fig.add_trace(go.Scatter(name='',x=[AMP_labels[0], AMP_labels[0]], y=[AMP_values[0]+(AMP_values[0]*0.03), max(AMP_values)+(max(AMP_values)*0.3)], mode = 'lines',showlegend=False, line=dict(color='black', width=1)), row=1, col=2) - fig.add_trace(go.Scatter(name='',x=[AMP_labels[1], AMP_labels[1]], y=[AMP_values[1]+(AMP_values[1]*0.03), max(AMP_values)+(max(AMP_values)*0.3)], mode = 'lines',showlegend=False, line=dict(color='black', width=1)), row=1, col=2) - - - #################### - #### AUC #### - #################### - - # Data - AUC_labels = [1,2] - AUC_values = [round(metrics.auc(self._time, self._obs_curve_1),2), round(metrics.auc(self._time, self._obs_curve_2),2)] - fig.add_trace(go.Bar(x=AUC_labels,y=AUC_values, width = [0.35,0.35], showlegend=False, marker_color='black', name=''), row=2, col=1 ) - - # Update xaxis properties - fig.update_xaxes(row=2, col=1, tickmode='array', showgrid=False, range=[0,3], linecolor='black', linewidth=2, - tickvals=[1,2], ticktext=['control', '{}-fold'.format(self._fold)], tickfont=dict(size=18)) - - fig.update_yaxes(row=2, col=1,showgrid=False, title_text=self._observable+' (nM)', range=[round((min(AUC_values)-min(AUC_values)*0.5)/5)*5,round((max(AUC_values)+max(AUC_values)*0.5)/5)*5], - titlefont=dict(size=18),linecolor='black', linewidth=2, - ticks='inside', tickfont=dict(size=18),ticklen=10, tickwidth=2) - - # Add diff lines - AUC_diffs = [max(AUC_values) - v for v in AUC_values] - AUC_diff_labels = dict(zip(AUC_labels, AUC_diffs)) - fig.add_trace(go.Scatter(name='',x=[1,1.5,2], y=[max(AUC_values)+(max(AUC_values)*0.3)]*3, mode = 'lines+text',showlegend=False, - line=dict(color='black', width=1), text=['', 'diff. = {} nM'.format(round(AUC_diffs[0], 3)),''], textposition='top center'), row=2, col=1) - fig.add_trace(go.Scatter(name='',x=[AUC_labels[0]-0.175, AUC_labels[0]+0.175], y=[AUC_values[0]+(AUC_values[0]*0.03)]*2, mode = 'lines',showlegend=False, line=dict(color='black', width=1)), row=2, col=1) - fig.add_trace(go.Scatter(name='',x=[AUC_labels[1]-0.175, AUC_labels[1]+0.175], y=[AUC_values[1]+(AUC_values[1]*0.03)]*2, mode = 'lines',showlegend=False, line=dict(color='black', width=1)), row=2, col=1) - fig.add_trace(go.Scatter(name='',x=[AUC_labels[0], AUC_labels[0]], y=[AUC_values[0]+(AUC_values[0]*0.03), max(AUC_values)+(max(AUC_values)*0.3)], mode = 'lines',showlegend=False, line=dict(color='black', width=1)), row=2, col=1) - fig.add_trace(go.Scatter(name='',x=[AUC_labels[1], AUC_labels[1]], y=[AUC_values[1]+(AUC_values[1]*0.03), max(AUC_values)+(max(AUC_values)*0.3)], mode = 'lines',showlegend=False, line=dict(color='black', width=1)), row=2, col=1) - - - #################### - #### FWHM #### - #################### - # Data - FWHM_labels = [1,2] - FWHM_values = [fwhm_1, fwhm_2] - fig.add_trace(go.Bar(x=FWHM_labels,y=FWHM_values, width = [0.35,0.35], showlegend=False,marker_color='black', name=''), row=2, col=2 ) - - # Update xaxis properties - fig.update_xaxes(row=2, col=2, showgrid=False, range=[0,3], linecolor='black', linewidth=2, - tickmode='array', tickvals=[1,2], ticktext=['control', '{}-fold'.format(self._fold)], tickfont=dict(size=18)) - - fig.update_yaxes(row=2, col=2, showgrid=False, range=[self.pathway_parameters['time_in'],round((max(FWHM_values)+(max(FWHM_values)-self.pathway_parameters['time_in'])*0.5)/5)*5], - title_text='Time (s)', titlefont=dict(size=18), linecolor='black', linewidth=2, - ticks='inside', ticklen=10, tickwidth=2, tickfont=dict(size=18)) - - # Add diff lines - FWHM_diffs = [max(FWHM_values) - v for v in FWHM_values] - FWHM_diff_labels = dict(zip(FWHM_labels, FWHM_diffs)) - line_height = max(FWHM_values)+((max(FWHM_values)-self.pathway_parameters['time_in'])*0.30) - fig.add_trace(go.Scatter(x=[1,1.5,2], y=[line_height]*3, mode = 'lines+text',showlegend=False, line=dict(color='black', width=1),name='', - text=['', 'diff. = {} s'.format(round(FWHM_diffs[0], 3)),''], textposition='top center'), row=2, col=2) - fig.add_trace(go.Scatter(name='',x=[FWHM_labels[0]-0.175, FWHM_labels[0]+0.175], y=[FWHM_values[0]+(FWHM_values[0]*0.005)]*2, mode = 'lines',showlegend=False, line=dict(color='black', width=1)), row=2, col=2) - fig.add_trace(go.Scatter(name='',x=[FWHM_labels[1]-0.175, FWHM_labels[1]+0.175], y=[FWHM_values[1]+(FWHM_values[1]*0.005)]*2, mode = 'lines',showlegend=False, line=dict(color='black', width=1)), row=2, col=2) - fig.add_trace(go.Scatter(name='',x=[FWHM_labels[0], FWHM_labels[0]], y=[FWHM_values[0]+(FWHM_values[0]*0.005), line_height], mode = 'lines',showlegend=False, line=dict(color='black', width=1)), row=2, col=2) - fig.add_trace(go.Scatter(name='',x=[FWHM_labels[1], FWHM_labels[1]], y=[FWHM_values[1]+(FWHM_values[1]*0.005), line_height], mode = 'lines',showlegend=False, line=dict(color='black', width=1)), row=2, col=2) - - - #################### - #### FIGURE #### - #################### - - fig.update_layout(height=1200, width=1300, title_text="", plot_bgcolor='white',showlegend=True, - legend=dict(yanchor="top", x=0.3, y=.99,font=dict(family="sans-serif", size=14,color="black"))) - fig.update_annotations(font_size=20, font_color='black') - - if save==True: - if filename==None: - filename='plot.html' - return pyoff.plot(fig, filename=filename) - else: - ext = os.path.splitext(filename)[-1] - if ext == '.png': fig.write_image(filename, scale=3) - elif ext == '.html': pyoff.plot(fig, filename=filename) - else: raise TypeError("extension not valid. Use png or html.") - elif save ==False: return fig - - return - -- GitLab