diff --git a/docs/source/_static/suzuki_reizman.gif b/docs/source/_static/suzuki_reizman.gif new file mode 100644 index 00000000..0791ce7f Binary files /dev/null and b/docs/source/_static/suzuki_reizman.gif differ diff --git a/docs/source/conf.py b/docs/source/conf.py index 99e989f6..74c7fc0c 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -51,11 +51,8 @@ "sphinx.ext.intersphinx", # read the docs theme "sphinx_rtd_theme", - # show plots - "matplotlib.sphinxext.mathmpl", - "matplotlib.sphinxext.plot_directive", - # Doctest - "sphinx.ext.doctest", + # Redirects + "sphinx_reredirects", ] # Add any paths that contain templates here, relative to this directory. @@ -116,6 +113,13 @@ def linkcode_resolve(domain, info): return "https://somesite/sourcerepo/%s.py" % filename +# -- Options for redirects---------------------------------------------------- + +redirects = { + "tutorial": "tutorials/intro.html", + "experiments_benchmarks/new_benchmarks": "../tutorials/new_benchmarks.html", +} + # -- Options for HTML output ------------------------------------------------- # The theme to use for HTML and HTML Help pages. See the documentation for diff --git a/docs/source/experiments_benchmarks/experimental_emulator.rst b/docs/source/experiments_benchmarks/experimental_emulator.rst index b91d600f..94d1b8e0 100644 --- a/docs/source/experiments_benchmarks/experimental_emulator.rst +++ b/docs/source/experiments_benchmarks/experimental_emulator.rst @@ -6,4 +6,7 @@ Experimental Emulator API .. autoclass:: summit.benchmarks.ANNRegressor + :members: + +.. autoclass:: summit.benchmarks.RegressorRegistry :members: \ No newline at end of file diff --git a/docs/source/experiments_benchmarks/index.rst b/docs/source/experiments_benchmarks/index.rst index bac3f240..b0d8f7e5 100644 --- a/docs/source/experiments_benchmarks/index.rst +++ b/docs/source/experiments_benchmarks/index.rst @@ -1,15 +1,14 @@ Experiments / Benchmarks ======================== -The :class:`~summit.experiment.Experiment` class provides a generic way of representing chemical reactions, virtual or real. We leverage this class to create the benchmarks available here. To get some insight into how to hook Summit up to real experimetns, look at the tutorial_. +The :class:`~summit.experiment.Experiment` class provides a generic way of representing chemical reactions, virtual or real. We leverage this class to create the benchmarks available in Summit. You can also create your own benchmarks based on experimental data (see here_). To get some insight into how to hook Summit up to real experiments, look at the introductory tutorial_. Here, we present the already implemented benchmarks and show you how to create new onces. -.. _tutorial: ../tutorial.ipynb -.. _new_benchmarks: new_benchmarks.rst +.. _here: ../tutorials/new_benchmarks.ipynb +.. _tutorial: ../tutorials/tutorial.ipynb .. toctree:: implemented_benchmarks - new_benchmarks experiment experimental_emulator \ No newline at end of file diff --git a/docs/source/experiments_benchmarks/new_benchmarks.ipynb b/docs/source/experiments_benchmarks/new_benchmarks.ipynb deleted file mode 100644 index 4c47a1f2..00000000 --- a/docs/source/experiments_benchmarks/new_benchmarks.ipynb +++ /dev/null @@ -1,329 +0,0 @@ -{ - "cells": [ - { - "cell_type": "markdown", - "source": [ - "# Creating New Benchmarks\n", - "\nHere we give a demonstration of how to train a new benchmark based on experimental data. We call these type of benchmarks `ExperimentalEmulator`. As an example, we are going to create a benchmark for the Suzuki-Miyaura Cross-Coupling reaction in [Reizman et al. (2016)](https://doi.org/10.1039/C6RE00153J). " - ], - "metadata": {} - }, - { - "cell_type": "markdown", - "source": [ - "## Google Colab\n", - "\nIf you would like to follow along with this tutorial, you can open it in Google Colab using the button below." - ], - "metadata": {} - }, - { - "cell_type": "raw", - "source": [ - "|colab_badge|" - ], - "metadata": { - "raw_mimetype": "text/restructuredtext" - } - }, - { - "cell_type": "markdown", - "source": [ - "You will need to run the following cell to make sure Summit and all its dependencies are installed. If prompted, restart the runtime." - ], - "metadata": {} - }, - { - "cell_type": "code", - "source": [ - "!pip install summit" - ], - "outputs": [], - "execution_count": null, - "metadata": {} - }, - { - "cell_type": "markdown", - "source": [ - "## Create the domain\n", - "\nLet's first import the needed parts of Summit." - ], - "metadata": {} - }, - { - "cell_type": "code", - "source": [ - "from summit.benchmarks import ExperimentalEmulator\n", - "from summit.domain import *\n", - "from summit.utils.dataset import DataSet\n", - "import pkg_resources\n", - "import pathlib\n", - "import pprint" - ], - "outputs": [], - "execution_count": 1, - "metadata": {} - }, - { - "cell_type": "markdown", - "source": [ - "We first need to create a domain. A domain contains all the decision variables, constraints and objectives for a benchmark." - ], - "metadata": {} - }, - { - "cell_type": "code", - "source": [ - "domain = Domain()" - ], - "outputs": [], - "execution_count": 2, - "metadata": {} - }, - { - "cell_type": "markdown", - "source": [ - "Above, we instantiate a new domain without any variables. Here, we are going to manipulate the catalyst, base, catalyst loading, base stoichiometry and temperature. Our objectives are to maximise yield and minimise turn over number (TON). We can use the increment operator `+=` to add variables to the domain." - ], - "metadata": {} - }, - { - "cell_type": "code", - "source": [ - "# Decision variables\n", - "des_1 = \"Catalyst type - different ligands\"\n", - "domain += CategoricalVariable(\n", - " name=\"catalyst\",\n", - " description=des_1,\n", - " levels=[\n", - " \"P1-L1\",\n", - " \"P2-L1\",\n", - " \"P1-L2\",\n", - " \"P1-L3\",\n", - " \"P1-L4\",\n", - " \"P1-L5\",\n", - " \"P1-L6\",\n", - " \"P1-L7\",\n", - " ],\n", - ")\n", - "\n", - "des_2 = \"Residence time in seconds (s)\"\n", - "domain += ContinuousVariable(name=\"t_res\", description=des_2, bounds=[60, 600])\n", - "\n", - "des_3 = \"Reactor temperature in degrees Celsius (ºC)\"\n", - "domain += ContinuousVariable(\n", - " name=\"temperature\", description=des_3, bounds=[30, 110]\n", - ")\n", - "\n", - "des_4 = \"Catalyst loading in mol%\"\n", - "domain += ContinuousVariable(\n", - " name=\"catalyst_loading\", description=des_4, bounds=[0.5, 2.5]\n", - ")\n", - "\n", - "# Objectives\n", - "des_5 = (\n", - " \"Turnover number - moles product generated divided by moles catalyst used\"\n", - ")\n", - "domain += ContinuousVariable(\n", - " name=\"ton\",\n", - " description=des_5,\n", - " bounds=[0, 200], # TODO: not sure about bounds, maybe redefine\n", - " is_objective=True,\n", - " maximize=True,\n", - ")\n", - "\n", - "des_6 = \"Yield\"\n", - "domain += ContinuousVariable(\n", - " name=\"yield\",\n", - " description=des_6,\n", - " bounds=[0, 100],\n", - " is_objective=True,\n", - " maximize=True,\n", - ")\n", - "\ndomain" - ], - "outputs": [ - { - "output_type": "execute_result", - "execution_count": 3, - "data": { - "text/html": [ - "
NameTypeDescriptionValues
catalystcategorical, inputCatalyst type - different ligands8 levels
t_rescontinuous, inputResidence time in seconds (s)[60,600]
temperaturecontinuous, inputReactor temperature in degrees Celsius (ºC)[30,110]
catalyst_loadingcontinuous, inputCatalyst loading in mol%[0.5,2.5]
toncontinuous, maximize objectiveTurnover number - moles product generated divided by moles catalyst used[0,200]
yieldcontinuous, maximize objectiveYield[0,100]
" - ], - "text/plain": [ - "" - ] - }, - "metadata": {} - } - ], - "execution_count": 3, - "metadata": {} - }, - { - "cell_type": "markdown", - "source": [ - "## Create the Experimental Emulator\n", - "\nNow we just need two lines of code to train the experimental emulator! We first instantiate `ExperimentalEmulator` passing in the domain and a name for the model. Next we train it with two-fold cross-validation and a test set size of 25%. Make sure to replace the `csv_dataset` keyword argument with the path to your csv file. When you run this code, you will see the outputs from the training loop." - ], - "metadata": {} - }, - { - "cell_type": "markdown", - "source": [ - "Here, we import the data that we already have in the Summit package, but you could use your own data. Change verbose to 1 if you want streaming updates of the training." - ], - "metadata": {} - }, - { - "cell_type": "code", - "source": [ - "DATA_PATH = pathlib.Path(pkg_resources.resource_filename(\"summit\", \"benchmarks/data\"))\n", - "ds = DataSet.read_csv(DATA_PATH / \"reizman_suzuki_case_1.csv\",)\n", - "emul = ExperimentalEmulator(model_name='my_reizman', domain=domain, dataset=ds)\n", - "res = emul.train(max_epochs=100, cv_fold=2, test_size=0.25, verbose=0)" - ], - "outputs": [], - "execution_count": 4, - "metadata": {} - }, - { - "cell_type": "markdown", - "source": [ - "Now that the interal model is trained, we can use the experimental emulator. I print out the domain again to remind us of the variables" - ], - "metadata": {} - }, - { - "cell_type": "code", - "source": [ - "domain" - ], - "outputs": [ - { - "output_type": "execute_result", - "execution_count": 5, - "data": { - "text/html": [ - "
NameTypeDescriptionValues
catalystcategorical, inputCatalyst type - different ligands8 levels
t_rescontinuous, inputResidence time in seconds (s)[60,600]
temperaturecontinuous, inputReactor temperature in degrees Celsius (ºC)[30,110]
catalyst_loadingcontinuous, inputCatalyst loading in mol%[0.5,2.5]
toncontinuous, maximize objectiveTurnover number - moles product generated divided by moles catalyst used[0,200]
yieldcontinuous, maximize objectiveYield[0,100]
" - ], - "text/plain": [ - "" - ] - }, - "metadata": {} - } - ], - "execution_count": 5, - "metadata": {} - }, - { - "cell_type": "code", - "source": [ - "conditions = [[\"P1-L1\", 60, 100, 1.0]]\n", - "conditions = DataSet(conditions, columns=[v.name for v in domain.input_variables])\n", - "emul.run_experiments(conditions)" - ], - "outputs": [ - { - "output_type": "execute_result", - "execution_count": 6, - "data": { - "text/html": [ - "
\n", - "\n", - "\n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - "
catalystt_restemperaturecatalyst_loadingtonyieldcomputation_texperiment_tstrategy
0P1-L1601001.023.36495433.130020.00.058378NaN
\n", - "
" - ], - "text/plain": [ - "NAME catalyst t_res temperature catalyst_loading ton yield \\\n", - "TYPE DATA DATA DATA DATA DATA DATA \n", - "0 P1-L1 60 100 1.0 23.364954 33.13002 \n", - "\n", - "NAME computation_t experiment_t strategy \n", - "TYPE METADATA METADATA METADATA \n", - "0 0.0 0.058378 NaN " - ] - }, - "metadata": {} - } - ], - "execution_count": 6, - "metadata": {} - }, - { - "cell_type": "markdown", - "source": [ - "Now we have a benchmark that can accept conditions and predict the yield and TON!" - ], - "metadata": {} - } - ], - "metadata": { - "kernelspec": { - "name": "python37364bitsummittfmmv07ppy37venv6fc212842bc44e839a51e6623a646abd", - "language": "python", - "display_name": "Python 3.7.3 64-bit ('summit-TfmmV07p-py3.7': venv)" - }, - "language_info": { - "name": "python", - "version": "3.7.3", - "mimetype": "text/x-python", - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "pygments_lexer": "ipython3", - "nbconvert_exporter": "python", - "file_extension": ".py" - }, - "kernel_info": { - "name": "python37364bitsummittfmmv07ppy37venv6fc212842bc44e839a51e6623a646abd" - }, - "nteract": { - "version": "0.12.3" - } - }, - "nbformat": 4, - "nbformat_minor": 4 -} \ No newline at end of file diff --git a/docs/source/experiments_benchmarks/new_benchmarks.rst b/docs/source/experiments_benchmarks/new_benchmarks.rst new file mode 100644 index 00000000..3b32335c --- /dev/null +++ b/docs/source/experiments_benchmarks/new_benchmarks.rst @@ -0,0 +1,2 @@ +.. + This a placeholder file to redirect stale links to the new tutorials directory. \ No newline at end of file diff --git a/docs/source/index.rst b/docs/source/index.rst index c63b7721..a33b3518 100644 --- a/docs/source/index.rst +++ b/docs/source/index.rst @@ -17,11 +17,36 @@ Summit has two key features: * **Strategies**: Optimisation algorithms designed to find the best conditions with the least number of iterations. Summit has eight strategies implemented. * **Benchmarks**: Simulations of chemical reactions that can be used to test strategies. We have both mechanistic and data-driven benchmarks. -To get started, follow our tutorial_. You can find a more detailed treatment of Summit in our preprint_. +We suggest trying one of our tutorials_ or reading our publication_ (or preprint_). Also, give us a ⭐ on Github_! -Also, give us a ⭐ on Github_! +Below is a quick start that demonstrates the functionality of Summit: -.. _tutorial : tutorial.ipynb +.. code-block:: python + + # Import summit + from summit.benchmarks import SnarBenchmark + from summit.strategies import NelderMead, MultitoSingleObjective + from summit.run import Runner + + # Instantiate the benchmark + exp = SnarBenchmark() + + # Since the Snar benchmark has two objectives and Nelder-Mead is single objective, we need a multi-to-single objective transform + transform = MultitoSingleObjective( + exp.domain, expression="-sty/1e4+e_factor/100", maximize=False + ) + + # Set up the strategy, passing in the optimisation domain and transform + nm = NelderMead(exp.domain, transform=transform) + + # Use the runner to run closed loop experiments + r = Runner( + strategy=nm, experiment=exp,max_iterations=50 + ) + r.run() + +.. _tutorials : tutorials/index.rst +.. _publication : https://chemistry-europe.onlinelibrary.wiley.com/doi/full/10.1002/cmtd.202000051 .. _preprint : https://chemrxiv.org/articles/preprint/Summit_Benchmarking_Machine_Learning_Methods_for_Reaction_Optimisation/12939806 .. _Github : https://github.com/sustainable-processes/summit @@ -30,7 +55,7 @@ Also, give us a ⭐ on Github_! :caption: Contents: installation - tutorial + tutorials/index domains experiments_benchmarks/index strategies diff --git a/docs/source/tutorial.rst b/docs/source/tutorial.rst new file mode 100644 index 00000000..3b32335c --- /dev/null +++ b/docs/source/tutorial.rst @@ -0,0 +1,2 @@ +.. + This a placeholder file to redirect stale links to the new tutorials directory. \ No newline at end of file diff --git a/docs/source/tutorials/index.rst b/docs/source/tutorials/index.rst new file mode 100644 index 00000000..98591c25 --- /dev/null +++ b/docs/source/tutorials/index.rst @@ -0,0 +1,10 @@ +Tutorials +========= + +The tutorials should help you get familiarized with Summit. + +.. toctree:: + :maxdepth: 1 + + intro + new_benchmarks \ No newline at end of file diff --git a/docs/source/tutorial.ipynb b/docs/source/tutorials/intro.ipynb similarity index 99% rename from docs/source/tutorial.ipynb rename to docs/source/tutorials/intro.ipynb index 5943d32a..389c71f8 100644 --- a/docs/source/tutorial.ipynb +++ b/docs/source/tutorials/intro.ipynb @@ -4,7 +4,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "# Tutorial" + "# Intro to Summit" ] }, { @@ -60,7 +60,7 @@ "## SnAr Benchmark\n", "\n", "\n", - "![Image from Hone et al.](_static/hone_2016_snar_chemistry.png)\n", + "![Image from Hone et al.](../_static/hone_2016_snar_chemistry.png)\n", "\n", "Nucleophilic aromatic substitution reactions are commonly used in the fine chemicals industry. In this case, 2,4 dinitrofluorobenzene (**1**) undergoes nucleophilic attack by pyrrolidine (**2**) to form the desired product **3**. Two side products **4** and **5** can also be formed. Overall, we want to maximise the amount of product formed and minimise side product formation and waste.\n", "\n", @@ -534,7 +534,7 @@ "metadata": {}, "outputs": [], "source": [ - "FOLDER = pathlib.Path(\"_static/\") # When using this in the context of docs\n", + "FOLDER = pathlib.Path(\"../_static/\") # When using this in the context of docs\n", "# FOLDER = pathlib.Path(\".\")" ] }, @@ -863,7 +863,7 @@ ], "metadata": { "kernelspec": { - "display_name": "covid19", + "display_name": "Python 3", "language": "python", "name": "python3" }, diff --git a/docs/source/tutorials/new_benchmarks.ipynb b/docs/source/tutorials/new_benchmarks.ipynb new file mode 100644 index 00000000..8816d0c8 --- /dev/null +++ b/docs/source/tutorials/new_benchmarks.ipynb @@ -0,0 +1,566 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Creating New Benchmarks\n", + "\n", + "Here we give a demonstration of how to create a new benchmark based on experimental data. We call these type of benchmarks emulators, and they use the class `ExperimentalEmulator`. \n", + "\n", + "Emulators contain machine learning models, which can learn patterns from experimental data. These models can then be used to predict the outcomes of reactions at conditions that have not been tested in the lab.\n", + "\n", + "Emulators are most applicable when a kinetic model is not available. This is common with reactions where catalysts, bases and acids are still being chosen.\n", + "\n", + "\n", + "As an example, we are going to create a benchmark for the Suzuki-Miyaura Cross-Coupling reaction in [Reizman et al. (2016)](https://doi.org/10.1039/C6RE00153J).\n", + "\n", + "\n", + "![Image from Hone et al.](../_static/suzuki_reizman.gif) \n", + "\n", + "Scheme reproduced from the paper." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Google Colab\n", + "\n", + "If you would like to follow along with this tutorial, you can open it in Google Colab using the button below." + ] + }, + { + "cell_type": "raw", + "metadata": { + "raw_mimetype": "text/restructuredtext" + }, + "source": [ + "|colab_badge|" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "You will need to run the following cell to make sure Summit and all its dependencies are installed. If prompted, restart the runtime." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "!pip install summit" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Imports \n", + "\n", + "Let's first import the needed parts of Summit." + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": {}, + "outputs": [], + "source": [ + "from summit.benchmarks import ExperimentalEmulator\n", + "from summit.domain import *\n", + "from summit.utils.dataset import DataSet\n", + "import pkg_resources\n", + "import pathlib" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "The autoreload extension is already loaded. To reload it, use:\n", + " %reload_ext autoreload\n" + ] + } + ], + "source": [ + "%load_ext autoreload\n", + "%autoreload 2" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Create the domain\n", + "\n", + "We first need to create a :class:`~summit.domain.Domain`. A domain specifies the aspects of the reaction we will be optimizing. In optimization speak, these are the decision variables (those that are manipulated), constraints and objectives for a benchmark." + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": {}, + "outputs": [], + "source": [ + "domain = Domain()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Above, we instantiate a new domain without any variables. Here, we are going to manipulate the catalyst, catalyst loading, base stoichiometry and temperature. Our objectives are to maximise yield and minimise turn over number (TON). We can use the increment operator `+=` to add variables to the domain. There are no constraints." + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": {}, + "outputs": [], + "source": [ + "# Decision variables\n", + "des_1 = \"Catalyst type - different ligands\"\n", + "domain += CategoricalVariable(\n", + " name=\"catalyst\",\n", + " description=des_1,\n", + " levels=[\n", + " \"P1-L1\",\n", + " \"P2-L1\",\n", + " \"P1-L2\",\n", + " \"P1-L3\",\n", + " \"P1-L4\",\n", + " \"P1-L5\",\n", + " \"P1-L6\",\n", + " \"P1-L7\",\n", + " ],\n", + ")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We specify the catalyst as a `CategoricalVariable`, which can encapsulate discrete decisions such as choosing a catalyst, base, or acid from a list of potential options. We pass the list of potential options to the `levels` keyword argument. Our data should only include one of the catalysts in `levels`.\n", + "\n", + "Below, we use `ContinuousVariable` to specify the rest of the decision variables. Each has `bounds`, which represent the minimum and maximum values of each variable." + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "metadata": {}, + "outputs": [], + "source": [ + "des_2 = \"Residence time in seconds (s)\"\n", + "domain += ContinuousVariable(name=\"t_res\", description=des_2, bounds=[60, 600])\n", + "\n", + "des_3 = \"Reactor temperature in degrees Celsius (ºC)\"\n", + "domain += ContinuousVariable(\n", + " name=\"temperature\", description=des_3, bounds=[30, 110]\n", + ")\n", + "\n", + "des_4 = \"Catalyst loading in mol%\"\n", + "domain += ContinuousVariable(\n", + " name=\"catalyst_loading\", description=des_4, bounds=[0.5, 2.5]\n", + ")\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Finally, we specify the objectives. We use `ContinuousVariable` again, but set `is_objective` to `True` and specify whether to maximize (or minimize) each objective." + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "metadata": {}, + "outputs": [], + "source": [ + "# Objectives\n", + "des_5 = \"Yield\"\n", + "domain += ContinuousVariable(\n", + " name=\"yield\",\n", + " description=des_5,\n", + " bounds=[0, 100],\n", + " is_objective=True,\n", + " maximize=True,\n", + ")\n", + "\n", + "\n", + "des_6 = (\n", + " \"Turnover number - moles product generated divided by moles catalyst used\"\n", + ")\n", + "domain += ContinuousVariable(\n", + " name=\"ton\",\n", + " description=des_6,\n", + " bounds=[0, 200],\n", + " is_objective=True,\n", + " maximize=True,\n", + ")\n", + "\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "When working inside a Jupyter Notebook, we can view the domain by putting it at the end of a cell and pressing enter." + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "
NameTypeDescriptionValues
catalystcategorical, inputCatalyst type - different ligands8 levels
t_rescontinuous, inputResidence time in seconds (s)[60,600]
temperaturecontinuous, inputReactor temperature in degrees Celsius (ºC)[30,110]
catalyst_loadingcontinuous, inputCatalyst loading in mol%[0.5,2.5]
yieldcontinuous, maximize objectiveYield[0,100]
toncontinuous, maximize objectiveTurnover number - moles product generated divided by moles catalyst used[0,200]
" + ], + "text/plain": [ + "" + ] + }, + "execution_count": 8, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "domain" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Load Data\n", + "\n", + "We now load in the data from past experiments, which we will use to train the emulator. Here, we import the data that we already have in the Summit package, but any data available in CSV format would work. " + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "metadata": {}, + "outputs": [], + "source": [ + "# Get the data already available in Summit\n", + "DATA_PATH = pathlib.Path(pkg_resources.resource_filename(\"summit\", \"benchmarks/data\"))\n", + "\n", + "# Read in data into a DataSEt.\n", + "ds = DataSet.read_csv(DATA_PATH / \"reizman_suzuki_case_1.csv\",)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Note that we are using a :class:`~summit.dataset.Dataset`. In the CSV, it is essential that the columns match the domain and an extra row is added below each column name with the word DATA (see [here](https://github.com/sustainable-processes/summit/blob/master/summit/benchmarks/data/reizman_suzuki_case_1.csv) for an example)." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Train the Emulator\n", + "\n", + "Now we only need two lines train the experimental emulator! We first instantiate `ExperimentalEmulator` passing in the dataset, domain and a name for the model. Next we train it with two-fold [cross-validation](https://machinelearningmastery.com/k-fold-cross-validation/) and a test set size of 25%.\n", + "\n", + "This step will take some time. Change verbose to 1 if you want streaming updates of the training." + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "{'fit_time': array([9.62368822, 9.36741114, 8.81644607, 8.90442324, 8.75493693]),\n", + " 'score_time': array([0.00828385, 0.00569177, 0.00601172, 0.00571394, 0.00531006]),\n", + " 'val_r2': array([0.73406495, 0.79712705, 0.87009207, 0.8853467 , 0.71850636]),\n", + " 'val_neg_root_mean_squared_error': array([-15.48383141, -12.27886391, -10.41021824, -8.35124302,\n", + " -12.52505112])}" + ] + }, + "execution_count": 10, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "emul = ExperimentalEmulator(model_name='my_reizman', domain=domain, dataset=ds)\n", + "emul.train(max_epochs=1000, cv_fold=2, test_size=0.1, verbose=0)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The training returns a `scores` dictionary from [scikit-learn](https://scikit-learn.org/stable/modules/generated/sklearn.model_selection.cross_validate.html#sklearn.model_selection.cross_validate), which contains the results from each cross-validation fold. It might be difficult to understand these scores, so we show some more intuitive evaluation methods next. " + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Evaluate Emulator" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "A [parity plot](https://en.wikipedia.org/wiki/Parity_plot) shows experimental data against model predictions. We can do this for both the train and test sets. The $r^2$ score is shown, which varies between 0 and 1 with 1 being perfect fit." + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "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\n", + "text/plain": [ + "
" + ] + }, + "metadata": { + "needs_background": "light" + }, + "output_type": "display_data" + } + ], + "source": [ + "fig, ax = emul.parity_plot(include_test=True)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We can also try running virtual experiments using the benchmark to see if they match our expectations." + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
catalystt_restemperaturecatalyst_loadingyieldtoncomputation_texperiment_tstrategy
0P1-L1601001.030.52187229.7584840.00.034721NaN
\n", + "
" + ], + "text/plain": [ + "NAME catalyst t_res temperature catalyst_loading yield ton \\\n", + "TYPE DATA DATA DATA DATA DATA DATA \n", + "0 P1-L1 60 100 1.0 30.521872 29.758484 \n", + "\n", + "NAME computation_t experiment_t strategy \n", + "TYPE METADATA METADATA METADATA \n", + "0 0.0 0.034721 NaN " + ] + }, + "execution_count": 12, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "conditions = [[\"P1-L1\", 60, 100, 1.0]]\n", + "conditions = DataSet(conditions, columns=[v.name for v in domain.input_variables])\n", + "emul.run_experiments(conditions)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Saving and Loading Emulators\n", + "\n", + "We can save the trained emulator to disk, so we can reuse it later. The `save` method will do two things: \n", + "\n", + "- Create a JSON file `model_name.json` (where model_name is the name used when ExperimentalEmulator is called). The JSON file contains the domain, any experiments run using the emulator, and some important hyperparameters (e.g., the mean and standard deviation used for normalization).\n", + "- Create several `.pt` files with the weights of the models trained using cross validation.\n", + "\n", + "We simply specify a directory where the files should be saved." + ] + }, + { + "cell_type": "code", + "execution_count": 13, + "metadata": {}, + "outputs": [], + "source": [ + "emul.save(save_dir=\"emulators_tutorial\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We can load the saved emulator and use it as expected." + ] + }, + { + "cell_type": "code", + "execution_count": 14, + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
catalystt_restemperaturecatalyst_loadingyieldtoncomputation_texperiment_tstrategy
0P1-L1601001.030.52187229.7584840.00.027989NaN
\n", + "
" + ], + "text/plain": [ + "NAME catalyst t_res temperature catalyst_loading yield ton \\\n", + "TYPE DATA DATA DATA DATA DATA DATA \n", + "0 P1-L1 60 100 1.0 30.521872 29.758484 \n", + "\n", + "NAME computation_t experiment_t strategy \n", + "TYPE METADATA METADATA METADATA \n", + "0 0.0 0.027989 NaN " + ] + }, + "execution_count": 14, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "emul_new = ExperimentalEmulator.load(model_name=\"my_reizman\", save_dir=\"emulators_tutorial\")\n", + "emul_new.run_experiments(conditions)" + ] + } + ], + "metadata": { + "kernel_info": { + "name": "python37364bitsummittfmmv07ppy37venv6fc212842bc44e839a51e6623a646abd" + }, + "kernelspec": { + "display_name": "Python 3.7.3 64-bit ('summit-TfmmV07p-py3.7': venv)", + "language": "python", + "name": "python37364bitsummittfmmv07ppy37venv6fc212842bc44e839a51e6623a646abd" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.7.3" + }, + "nteract": { + "version": "0.12.3" + } + }, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/poetry.lock b/poetry.lock index 004bdd4f..fe7efa96 100644 --- a/poetry.lock +++ b/poetry.lock @@ -2,7 +2,7 @@ category = "main" description = "A configurable sidebar-enabled Sphinx theme" name = "alabaster" -optional = true +optional = false python-versions = "*" version = "0.7.12" @@ -36,7 +36,7 @@ version = "1.4.4" [[package]] category = "main" description = "Disable App Nap on macOS >= 10.9" -marker = "python_version >= \"3.4\" and sys_platform == \"darwin\" or platform_system == \"Darwin\" or python_version >= \"3.4\" and platform_system == \"Darwin\"" +marker = "sys_platform == \"darwin\" or platform_system == \"Darwin\" or python_version >= \"3.3\" and sys_platform == \"darwin\" or python_version >= \"3.4\" and sys_platform == \"darwin\" or python_version >= \"3.4\" and platform_system == \"Darwin\"" name = "appnope" optional = false python-versions = "*" @@ -113,7 +113,7 @@ numpy = ">=1.12" category = "main" description = "Internationalization utilities" name = "babel" -optional = true +optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" version = "2.9.0" @@ -123,7 +123,6 @@ pytz = ">=2015.7" [[package]] category = "main" description = "Specifications for callback functions passed in to an API" -marker = "python_version >= \"3.4\"" name = "backcall" optional = false python-versions = "*" @@ -216,25 +215,6 @@ optional = true python-versions = "*" version = "1.4" -[[package]] -category = "main" -description = "A simple and extensible library to create Bayesian Neural Network Layers on PyTorch without trouble and with full integration with nn.Module and nn.Sequential." -name = "blitz-bayesian-pytorch" -optional = true -python-versions = "*" -version = "0.2.5" - -[package.dependencies] -numpy = "*" -pillow = ">=7.1" -scikit-learn = ">=0.22.2" -torch = ">=1.4.0" -torchvision = ">=0.5.0" - -[package.source] -reference = "1684957d06b9af8aec5e71b37947c54ff7950a22" -type = "git" -url = "https://github.com/sustainable-processes/blitz-bayesian-deep-learning.git" [[package]] category = "main" description = "The AWS SDK for Python" @@ -353,7 +333,7 @@ version = "4.2.1" category = "main" description = "Python package for providing Mozilla's CA Bundle." name = "certifi" -optional = true +optional = false python-versions = "*" version = "2020.12.5" @@ -372,7 +352,7 @@ pycparser = "*" category = "main" description = "Universal encoding detector for Python 2 and 3" name = "chardet" -optional = true +optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" version = "4.0.0" @@ -395,7 +375,7 @@ version = "2.7.0" [[package]] category = "main" description = "Cross-platform colored terminal text." -marker = "python_version >= \"3.4\" and sys_platform == \"win32\" or sys_platform == \"win32\"" +marker = "python_version >= \"3.3\" and sys_platform == \"win32\" or sys_platform == \"win32\" or python_version >= \"3.4\" and sys_platform == \"win32\"" name = "colorama" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" @@ -459,7 +439,7 @@ version = "0.6.0" category = "main" description = "Docutils -- Python Documentation Utilities" name = "docutils" -optional = true +optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" version = "0.16" @@ -661,7 +641,7 @@ dev = ["pytest", "mypy", "ipykernel", "wheel", "selenium", "sphinx", "twine", "g category = "main" description = "Internationalized Domain Names in Applications (IDNA)" name = "idna" -optional = true +optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" version = "2.10" @@ -669,7 +649,7 @@ version = "2.10" category = "main" description = "Getting image size from png/jpeg/jpeg2000/gif file" name = "imagesize" -optional = true +optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" version = "1.2.0" @@ -802,7 +782,6 @@ version = "1.1.0" [[package]] category = "main" description = "An autocompletion tool for Python that can be used for text editors." -marker = "python_version >= \"3.4\"" name = "jedi" optional = false python-versions = ">=3.6" @@ -1340,7 +1319,7 @@ version = "0.8.1" [[package]] category = "main" description = "Pexpect allows easy control of interactive console applications." -marker = "python_version >= \"3.4\" and sys_platform != \"win32\"" +marker = "python_version >= \"3.3\" and sys_platform != \"win32\" or sys_platform != \"win32\" or python_version >= \"3.4\" and sys_platform != \"win32\"" name = "pexpect" optional = false python-versions = "*" @@ -1352,7 +1331,6 @@ ptyprocess = ">=0.5" [[package]] category = "main" description = "Tiny 'shelve'-like database with concurrency support" -marker = "python_version >= \"3.4\"" name = "pickleshare" optional = false python-versions = "*" @@ -1396,7 +1374,6 @@ twisted = ["twisted"] [[package]] category = "main" description = "Library for building powerful interactive command lines in Python" -marker = "python_version >= \"3.4\"" name = "prompt-toolkit" optional = false python-versions = ">=3.6.1" @@ -1659,7 +1636,7 @@ version = "2020.11.13" category = "main" description = "Python HTTP for Humans." name = "requests" -optional = true +optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" version = "2.25.1" @@ -1796,7 +1773,7 @@ version = "3.0.5" category = "main" description = "This package provides 29 stemmers for 28 languages generated from Snowball algorithms." name = "snowballstemmer" -optional = true +optional = false python-versions = "*" version = "2.1.0" @@ -1813,7 +1790,7 @@ version = "2.2" category = "main" description = "Python documentation generator" name = "sphinx" -optional = true +optional = false python-versions = ">=3.5" version = "3.5.1" @@ -1841,6 +1818,17 @@ docs = ["sphinxcontrib-websupport"] lint = ["flake8 (>=3.5.0)", "isort", "mypy (>=0.800)", "docutils-stubs"] test = ["pytest", "pytest-cov", "html5lib", "cython", "typed-ast"] +[[package]] +category = "main" +description = "Handles redirects for moved pages in Sphinx documentation projects" +name = "sphinx-reredirects" +optional = false +python-versions = "*" +version = "0.0.0" + +[package.dependencies] +sphinx = "*" + [[package]] category = "main" description = "Read the Docs theme for Sphinx" @@ -1859,7 +1847,7 @@ dev = ["transifex-client", "sphinxcontrib-httpdomain", "bump2version"] category = "main" description = "sphinxcontrib-applehelp is a sphinx extension which outputs Apple help books" name = "sphinxcontrib-applehelp" -optional = true +optional = false python-versions = ">=3.5" version = "1.0.2" @@ -1871,7 +1859,7 @@ test = ["pytest"] category = "main" description = "sphinxcontrib-devhelp is a sphinx extension which outputs Devhelp document." name = "sphinxcontrib-devhelp" -optional = true +optional = false python-versions = ">=3.5" version = "1.0.2" @@ -1883,7 +1871,7 @@ test = ["pytest"] category = "main" description = "sphinxcontrib-htmlhelp is a sphinx extension which renders HTML help files" name = "sphinxcontrib-htmlhelp" -optional = true +optional = false python-versions = ">=3.5" version = "1.0.3" @@ -1895,7 +1883,7 @@ test = ["pytest", "html5lib"] category = "main" description = "A sphinx extension which renders display math in HTML via JavaScript" name = "sphinxcontrib-jsmath" -optional = true +optional = false python-versions = ">=3.5" version = "1.0.1" @@ -1906,7 +1894,7 @@ test = ["pytest", "flake8", "mypy"] category = "main" description = "sphinxcontrib-qthelp is a sphinx extension which outputs QtHelp document." name = "sphinxcontrib-qthelp" -optional = true +optional = false python-versions = ">=3.5" version = "1.0.3" @@ -1918,7 +1906,7 @@ test = ["pytest"] category = "main" description = "sphinxcontrib-serializinghtml is a sphinx extension which outputs \"serialized\" HTML files (json and pickle)." name = "sphinxcontrib-serializinghtml" -optional = true +optional = false python-versions = ">=3.5" version = "1.1.4" @@ -2066,22 +2054,6 @@ version = "1.7.1" numpy = "*" typing-extensions = "*" -[[package]] -category = "main" -description = "image and video datasets and models for torch deep learning" -name = "torchvision" -optional = true -python-versions = "*" -version = "0.8.2" - -[package.dependencies] -numpy = "*" -pillow = ">=4.1.1" -torch = "1.7.1" - -[package.extras] -scipy = ["scipy"] - [[package]] category = "main" description = "Tornado is a Python web framework and asynchronous networking library, originally developed at FriendFeed." @@ -2147,7 +2119,7 @@ pytz = "*" category = "main" description = "HTTP library with thread-safe connection pooling, file post, and more." name = "urllib3" -optional = true +optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, <4" version = "1.26.3" @@ -2266,13 +2238,13 @@ docs = ["sphinx", "jaraco.packaging (>=3.2)", "rst.linker (>=1.9)"] testing = ["pytest (>=3.5,<3.7.3 || >3.7.3)", "pytest-checkdocs (>=1.2.3)", "pytest-flake8", "pytest-cov", "jaraco.test (>=3.2.0)", "jaraco.itertools", "func-timeout", "pytest-black (>=0.3.7)", "pytest-mypy"] [extras] -bnn = ["blitz-bayesian-pytorch"] +bnn = [] docs = ["sphinx", "nbsphinx", "sphinx-rtd-theme"] entmoot = ["entmoot"] experiments = ["neptune-client", "hiplot", "paramiko", "pyrecorder", "xlrd", "streamlit"] [metadata] -content-hash = "db435ec76eb3ce23c2daf294c3f2b7eb29e01dfb6d41d3a566a46561fd1a4705" +content-hash = "1ef88dcfa2d8bf97004ae18cd4e71ee3a33a57f523a9e66cfd632a5ec0c92a33" python-versions = "^3.7" [metadata.files] @@ -2366,7 +2338,6 @@ bleach = [ blinker = [ {file = "blinker-1.4.tar.gz", hash = "sha256:471aee25f3992bd325afa3772f1063dbdbbca947a041b8b89466dc00d606f8b6"}, ] -blitz-bayesian-pytorch = [] boto3 = [ {file = "boto3-1.17.11-py2.py3-none-any.whl", hash = "sha256:b6131751e3cf2f8d4c027518373b6b82264c3897de65d3519e2d782927e8bf1e"}, {file = "boto3-1.17.11.tar.gz", hash = "sha256:7d44cbd931c653cc68e8ccbf39f3ad8b304cb50d4e964d8c8d0936de33ff8c8b"}, @@ -3499,6 +3470,10 @@ sphinx = [ {file = "Sphinx-3.5.1-py3-none-any.whl", hash = "sha256:e90161222e4d80ce5fc811ace7c6787a226b4f5951545f7f42acf97277bfc35c"}, {file = "Sphinx-3.5.1.tar.gz", hash = "sha256:11d521e787d9372c289472513d807277caafb1684b33eb4f08f7574c405893a9"}, ] +sphinx-reredirects = [ + {file = "sphinx_reredirects-0.0.0-py3-none-any.whl", hash = "sha256:681149d869f782779662dd646b23ca13d3dfe3e98548ad516273052ab2598b7f"}, + {file = "sphinx_reredirects-0.0.0.tar.gz", hash = "sha256:f9db1fc77ff78d4a8a011e4baf94285bb1e31e10447f5bff799e119bbc6ea726"}, +] sphinx-rtd-theme = [ {file = "sphinx_rtd_theme-0.5.1-py2.py3-none-any.whl", hash = "sha256:fa6bebd5ab9a73da8e102509a86f3fcc36dec04a0b52ea80e5a033b2aba00113"}, {file = "sphinx_rtd_theme-0.5.1.tar.gz", hash = "sha256:eda689eda0c7301a80cf122dad28b1861e5605cbf455558f3775e1e8200e83a5"}, @@ -3578,16 +3553,6 @@ torch = [ {file = "torch-1.7.1-cp39-cp39-win_amd64.whl", hash = "sha256:6652a767a0572ae0feb74ad128758e507afd3b8396b6e7f147e438ba8d4c6f63"}, {file = "torch-1.7.1-cp39-none-macosx_10_9_x86_64.whl", hash = "sha256:38d67f4fb189a92a977b2c0a38e4f6dd413e0bf55aa6d40004696df7e40a71ff"}, ] -torchvision = [ - {file = "torchvision-0.8.2-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:86fae370d222f76ad57c57c3bee03f78b8db727743bfb4c1559a3d395159cea8"}, - {file = "torchvision-0.8.2-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:951239b5fcb911dbf78c1385d677f5f48c7a1b12859e3d3ec287562821b17cf2"}, - {file = "torchvision-0.8.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:24db8f4c3d812a032273f68563ad5dbd724f5bfbed523d0c6dce8cede26bb153"}, - {file = "torchvision-0.8.2-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:b068f6bcbe91bdd34dda0a39e8a26392add45a3be82543f6dd523b76484fb56f"}, - {file = "torchvision-0.8.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:afb76a66b9b0693f758a881a2bf333ed97e3c0c3f15a413c4f49d8dd8bd21307"}, - {file = "torchvision-0.8.2-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:cd8817e9197fc60ebae37162a445db90bbf35591314a5767ad3d1490b5d65b0f"}, - {file = "torchvision-0.8.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:1bd58acc3366ec02266aae56a7a752d43ef07de4a6ba420c4f907d0c9168bb8c"}, - {file = "torchvision-0.8.2-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:976750a49db2e23dc5a1ed0b5c31f7af51ed2702eee410ee09ef985c3a3e48cf"}, -] tornado = [ {file = "tornado-6.1-cp35-cp35m-macosx_10_9_x86_64.whl", hash = "sha256:d371e811d6b156d82aa5f9a4e08b58debf97c302a35714f6f45e35139c332e32"}, {file = "tornado-6.1-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:0d321a39c36e5f2c4ff12b4ed58d41390460f798422c4504e09eb5678e09998c"}, diff --git a/pyproject.toml b/pyproject.toml index ef12df11..ef0ddb16 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -51,12 +51,13 @@ nbsphinx = {version="^0.7.1", optional=true} sphinx-rtd-theme = {version="^0.5.0", optional=true} pyrecorder = {version="^0.1.8", optional=true} entmoot = {version="^0.1.4", optional=true} +sphinx-reredirects = {version="^0.0.0", optional=true} [tool.poetry.extras] bnn = ["blitz-bayesian-pytorch"] entmoot = ["entmoot"] experiments = ["neptune-client", "hiplot", "paramiko", "pyrecorder", "xlrd", "streamlit"] -docs = ["sphinx", "nbsphinx", "sphinx-rtd-theme"] +docs = ["sphinx", "nbsphinx", "sphinx-rtd-theme", "sphinx-reredirects"] [tool.poetry.dev-dependencies] diff --git a/scripts/train_emulators/results/baumgartner_aniline_cn_crosscoupling_descriptors.png b/scripts/train_emulators/results/baumgartner_aniline_cn_crosscoupling_descriptors.png new file mode 100644 index 00000000..f9d5a1a8 Binary files /dev/null and b/scripts/train_emulators/results/baumgartner_aniline_cn_crosscoupling_descriptors.png differ diff --git a/summit/benchmarks/experimental_emulator.py b/summit/benchmarks/experimental_emulator.py index 781d5555..8b60e4b2 100644 --- a/summit/benchmarks/experimental_emulator.py +++ b/summit/benchmarks/experimental_emulator.py @@ -117,14 +117,15 @@ class ExperimentalEmulator(Experiment): >>> import matplotlib.pyplot as plt >>> import pathlib >>> import pkg_resources - >>> # Steal domain and ata from Reizman example + >>> # Steal domain and data from Reizman example >>> DATA_PATH = pathlib.Path(pkg_resources.resource_filename("summit", "benchmarks/data")) >>> model_name = f"reizman_suzuki_case_1" >>> domain = ReizmanSuzukiEmulator.setup_domain() >>> ds = DataSet.read_csv(DATA_PATH / f"{model_name}.csv") - >>> # Create emulator and train + >>> # Create emulator and train (bump max_epochs to 1000 to get better training) >>> exp = ExperimentalEmulator(model_name,domain,dataset=ds) - >>> res = exp.train(max_epochs=1000, cv_folds=2, random_state=100, test_size=0.2) + >>> res = exp.train(max_epochs=10, cv_folds=2, random_state=100, test_size=0.2) + >>> # Plot to show the quality of the fit >>> fig, ax = exp.parity_plot(include_test=True) >>> plt.show() @@ -202,6 +203,9 @@ def _predict(self, X, **kwargs): def train(self, **kwargs): """Train the model on the dataset + This will automatically do a train-test split and then train via + cross-validation on the train set. + Parameters --------- test_size : float, optional @@ -299,6 +303,18 @@ def train(self, **kwargs): return res def test(self, **kwargs): + """Get test results + + This requires that train has already been called or + the ExperimentalEmulator was initialized from a pretrained model. + + Parameters + ---------- + scoring : str or list, optional + A list of scoring functions or names of them. Defaults to R2 and MSE. + See here for more https://scikit-learn.org/stable/modules/model_evaluation.html#scoring-parameter + + """ scoring = kwargs.get("scoring", ["r2", "neg_root_mean_squared_error"]) scores_list = [] for predictor in self.predictors: @@ -639,7 +655,7 @@ def save(self, save_dir): @classmethod def load(cls, model_name, save_dir, **kwargs): - """Load all the essential parameters of the ExperimentalEmulator to disk + """Load all the essential parameters of the ExperimentalEmulator from disk Parameters ---------- @@ -680,7 +696,8 @@ def parity_plot(self, **kwargs): if type(vars) == str: vars = [vars] - fig, axes = plt.subplots(1, len(vars)) + fig, axes = plt.subplots(1, len(vars), figsize=(10, 5)) + fig.subplots_adjust(wspace=0.5) if len(vars) > 1: fig.subplots_adjust(wspace=0.2) if type(axes) != np.ndarray: @@ -1323,8 +1340,13 @@ def forward(self, x, **kwargs): class RegressorRegistry: """Registry for Regressors - Models registered using the register method - are saved as the class name. + The registry stores regressors that can be used with the + :class:~`summit.benchmarks.ExperimentalEmulator`. A regressor can be + any `torch.nn.Module` that takes the parameeters `input_dim` and `output_dim` for + the input and output dimensions respectively. + + Registering a regressor means that it can be serialized and deserialized + using the save/load functionality of the emulator. """ @@ -1345,6 +1367,14 @@ def __setitem__(self, key, value): self.regressors[key] = value def register(self, regressor): + """Register a new regresssor + + Parameters + --------- + regressor: torch.nn.Module + A torch neural network module + + """ key = regressor.__name__ self.regressors[key] = regressor @@ -1376,8 +1406,18 @@ def get_pretrained_reizman_suzuki_emulator(case=1): Examples --------- - >>> exp = get_pretrained_reizman_suzuki_emulator(case=1) - + >>> import matplotlib.pyplot as plt + >>> from summit.benchmarks import get_pretrained_reizman_suzuki_emulator + >>> from summit.utils.dataset import DataSet + >>> import pandas as pd + >>> b = get_pretrained_reizman_suzuki_emulator(case=1) + >>> fig, ax = b.parity_plot(include_test=True) + >>> plt.show() + >>> columns = [v.name for v in b.domain.variables] + >>> values = { "catalyst": ["P1-L3"], "t_res": [600], "temperature": [30],"catalyst_loading": [0.498],} + >>> conditions = pd.DataFrame(values) + >>> conditions = DataSet.from_df(conditions) + >>> results = b.run_experiments(conditions, return_std=True) """ model_name = f"reizman_suzuki_case_{case}" model_path = get_model_path() / model_name @@ -1515,13 +1555,31 @@ def get_pretrained_baumgartner_cc_emulator(include_cost=False, use_descriptors=F a single feature, pass descriptors_features a list where the only item is the name of the desired categorical variable. + + Examples + -------- + + >>> import matplotlib.pyplot as plt + >>> from summit.benchmarks import get_pretrained_baumgartner_cc_emulator + >>> from summit.utils.dataset import DataSet + >>> import pandas as pd + >>> b = get_pretrained_baumgartner_cc_emulator(include_cost=True, use_descriptors=False) + >>> fig, ax = b.parity_plot(include_test=True) + >>> plt.show() + >>> columns = [v.name for v in b.domain.variables] + >>> values = { "catalyst": ["tBuXPhos"], "base": ["DBU"], "t_res": [328.717801570892],"temperature": [30],"base_equivalents": [2.18301549894049]} + >>> conditions = pd.DataFrame(values) + >>> conditions = DataSet.from_df(conditions) + >>> results = b.run_experiments(conditions, return_std=True) + """ model_name = "baumgartner_aniline_cn_crosscoupling" + data_path = get_data_path() + ds = DataSet.read_csv(data_path / f"{model_name}.csv") + model_name += "_descriptors" if use_descriptors else "" model_path = get_model_path() / model_name if not model_path.exists(): raise NotADirectoryError("Could not initialize from expected path.") - data_path = get_data_path() - ds = DataSet.read_csv(data_path / f"{model_name}.csv") exp = BaumgartnerCrossCouplingEmulator.load( model_path, dataset=ds, @@ -1672,9 +1730,21 @@ def load(cls, save_dir, include_cost=False, use_descriptors=False, **kwargs): ---------- save_dir : str or pathlib.Path The directory from which to load emulator files. + include_cost : bool, optional + Include minimization of cost as an extra objective. Cost is calculated + as a deterministic function of the inputs (i.e., no model is trained). + Defaults to False. + use_descriptors : bool, optional + Use descriptors for the catalyst and base instead of one-hot encoding (defaults to False). T + The descriptors been pre-calculated using COSMO-RS. To only use descriptors with + a single feature, pass descriptors_features a list where + the only item is the name of the desired categorical variable. """ - model_name = "baumgartner_aniline_cn_crosscoupling" + if use_descriptors: + model_name = "baumgartner_aniline_cn_crosscoupling_descriptors" + else: + model_name = "baumgartner_aniline_cn_crosscoupling" save_dir = pathlib.Path(save_dir) with open(save_dir / f"{model_name}.json", "r") as f: d = json.load(f) diff --git a/summit/benchmarks/models/baumgartner_aniline_cn_crosscoupling_descriptors/baumgartner_aniline_cn_crosscoupling_descriptors.json b/summit/benchmarks/models/baumgartner_aniline_cn_crosscoupling_descriptors/baumgartner_aniline_cn_crosscoupling_descriptors.json new file mode 100644 index 00000000..a9e050d3 --- /dev/null +++ b/summit/benchmarks/models/baumgartner_aniline_cn_crosscoupling_descriptors/baumgartner_aniline_cn_crosscoupling_descriptors.json @@ -0,0 +1 @@ +{"domain": [{"type": "CategoricalVariable", "is_objective": false, "name": "catalyst", "description": "Catalyst type", "units": null, "levels": ["tBuXPhos", "tBuBrettPhos", "AlPhos"], "ds": {"index": ["tBuXPhos", "tBuBrettPhos", "AlPhos"], "columns": [["area_cat", "DATA"], ["M2_cat", "DATA"]], "data": [[460.7543, 67.2057], [518.8408, 89.8738], [819.933, 129.0808]]}}, {"type": "CategoricalVariable", "is_objective": false, "name": "base", "description": "Base", "units": null, "levels": ["DBU", "BTMG", "TMG", "TEA"], "ds": {"index": ["TEA", "TMG", "BTMG", "DBU"], "columns": [["area", "DATA"], ["M2", "DATA"]], "data": [[162.2992, 25.8165], [165.5447, 81.4847], [227.3523, 30.554], [192.4693, 59.8367]]}}, {"type": "ContinuousVariable", "is_objective": false, "name": "base_equivalents", "description": "Base equivalents", "units": null, "bounds": [1.0, 2.5]}, {"type": "ContinuousVariable", "is_objective": false, "name": "temperature", "description": "Temperature in degrees Celsius (\u00baC)", "units": null, "bounds": [30.0, 100.0]}, {"type": "ContinuousVariable", "is_objective": false, "name": "t_res", "description": "residence time in seconds (s)", "units": null, "bounds": [60.0, 1800.0]}, {"type": "ContinuousVariable", "is_objective": true, "name": "yield", "description": "Yield", "units": null, "bounds": [0.0, 1.0]}], "name": "ExperimentalEmulator", "data": {"index": [], "columns": [["catalyst", "DATA"], ["base", "DATA"], ["base_equivalents", "DATA"], ["temperature", "DATA"], ["t_res", "DATA"], ["yield", "DATA"], ["computation_t", "METADATA"], ["experiment_t", "METADATA"], ["strategy", "METADATA"]], "data": []}, "experiment_params": {"model_name": "baumgartner_aniline_cn_crosscoupling_descriptors", "regressor_name": "ANNRegressor", "n_features": 7, "n_examples": 96, "descriptors_features": ["catalyst", "base"], "output_variable_names": ["yield"], "predictors": [{"input_preprocessor": {"num": {"mean_": [1.6559957171333333, 69.63333333333334, 675.2387380961666], "var_": [0.24093575016750415, 906.7308888888889, 253625.04696145264], "scale_": [0.4908520654611776, 30.11197251740392, 503.6120004144586], "n_samples_seen_": 60}}, "output_preprocessor": {"mean_": [0.5805532822851092], "var_": [0.1785550681951766], "scale_": [0.42255776906261777], "n_samples_seen_": 60}}, {"input_preprocessor": {"num": {"mean_": [1.683371605967213, 74.62295081967213, 715.8182047760656], "var_": [0.24120375329778332, 866.5532437516797, 223573.2988868711], "scale_": [0.49112498745002103, 29.43727643230059, 472.83538243967223], "n_samples_seen_": 61}}, "output_preprocessor": {"mean_": [0.6254036210660563], "var_": [0.16832977557768586], "scale_": [0.4102801184284779], "n_samples_seen_": 61}}, {"input_preprocessor": {"num": {"mean_": [1.6615540930327868, 69.30163934426228, 681.7872910763934], "var_": [0.24927125858022928, 924.5657350174685, 269346.5438305566], "scale_": [0.49927072674074263, 30.40667254103067, 518.9860728676219], "n_samples_seen_": 61}}, "output_preprocessor": {"mean_": [0.5703947171110844], "var_": [0.18538124205880477], "scale_": [0.43055922015305254], "n_samples_seen_": 61}}, {"input_preprocessor": {"num": {"mean_": [1.628605248, 72.78196721311477, 735.1206038822951], "var_": [0.24907596673949953, 907.5027895726955, 244282.01121683227], "scale_": [0.4990751113204299, 30.124786963108892, 494.24893648528194], "n_samples_seen_": 61}}, "output_preprocessor": {"mean_": [0.6131021287750269], "var_": [0.17856050759065348], "scale_": [0.4225642052879698], "n_samples_seen_": 61}}, {"input_preprocessor": {"num": {"mean_": [1.6073154240819671, 73.0311475409836, 743.0736161352461], "var_": [0.2525966705755136, 848.4237839290512, 251362.03663802444], "scale_": [0.5025899626688873, 29.12771504819853, 501.3601865306263], "n_samples_seen_": 61}}, "output_preprocessor": {"mean_": [0.564398883872467], "var_": [0.18049929808199283], "scale_": [0.4248520896523787], "n_samples_seen_": 61}}]}, "extras": []} \ No newline at end of file diff --git a/summit/benchmarks/models/baumgartner_aniline_cn_crosscoupling_descriptors/baumgartner_aniline_cn_crosscoupling_descriptors_predictor_0.pt b/summit/benchmarks/models/baumgartner_aniline_cn_crosscoupling_descriptors/baumgartner_aniline_cn_crosscoupling_descriptors_predictor_0.pt new file mode 100644 index 00000000..1950553f Binary files /dev/null and b/summit/benchmarks/models/baumgartner_aniline_cn_crosscoupling_descriptors/baumgartner_aniline_cn_crosscoupling_descriptors_predictor_0.pt differ diff --git a/summit/benchmarks/models/baumgartner_aniline_cn_crosscoupling_descriptors/baumgartner_aniline_cn_crosscoupling_descriptors_predictor_1.pt b/summit/benchmarks/models/baumgartner_aniline_cn_crosscoupling_descriptors/baumgartner_aniline_cn_crosscoupling_descriptors_predictor_1.pt new file mode 100644 index 00000000..c6f7db4c Binary files /dev/null and b/summit/benchmarks/models/baumgartner_aniline_cn_crosscoupling_descriptors/baumgartner_aniline_cn_crosscoupling_descriptors_predictor_1.pt differ diff --git a/summit/benchmarks/models/baumgartner_aniline_cn_crosscoupling_descriptors/baumgartner_aniline_cn_crosscoupling_descriptors_predictor_2.pt b/summit/benchmarks/models/baumgartner_aniline_cn_crosscoupling_descriptors/baumgartner_aniline_cn_crosscoupling_descriptors_predictor_2.pt new file mode 100644 index 00000000..7483179f Binary files /dev/null and b/summit/benchmarks/models/baumgartner_aniline_cn_crosscoupling_descriptors/baumgartner_aniline_cn_crosscoupling_descriptors_predictor_2.pt differ diff --git a/summit/benchmarks/models/baumgartner_aniline_cn_crosscoupling_descriptors/baumgartner_aniline_cn_crosscoupling_descriptors_predictor_3.pt b/summit/benchmarks/models/baumgartner_aniline_cn_crosscoupling_descriptors/baumgartner_aniline_cn_crosscoupling_descriptors_predictor_3.pt new file mode 100644 index 00000000..3fb94fdb Binary files /dev/null and b/summit/benchmarks/models/baumgartner_aniline_cn_crosscoupling_descriptors/baumgartner_aniline_cn_crosscoupling_descriptors_predictor_3.pt differ diff --git a/summit/benchmarks/models/baumgartner_aniline_cn_crosscoupling_descriptors/baumgartner_aniline_cn_crosscoupling_descriptors_predictor_4.pt b/summit/benchmarks/models/baumgartner_aniline_cn_crosscoupling_descriptors/baumgartner_aniline_cn_crosscoupling_descriptors_predictor_4.pt new file mode 100644 index 00000000..1fab2856 Binary files /dev/null and b/summit/benchmarks/models/baumgartner_aniline_cn_crosscoupling_descriptors/baumgartner_aniline_cn_crosscoupling_descriptors_predictor_4.pt differ diff --git a/tests/test_benchmarks.py b/tests/test_benchmarks.py index 3a3d7262..9c13466e 100644 --- a/tests/test_benchmarks.py +++ b/tests/test_benchmarks.py @@ -103,7 +103,9 @@ def test_reizman_emulator(show_plots=False): @pytest.mark.parametrize("include_cost", [True, False]) def test_baumgartner_CC_emulator(use_descriptors, include_cost, show_plots=False): """ Test the Baumgartner Cross Coupling emulator""" - b = get_pretrained_baumgartner_cc_emulator(use_descriptors) + b = get_pretrained_baumgartner_cc_emulator( + use_descriptors=use_descriptors, include_cost=include_cost + ) b.parity_plot(include_test=True) if show_plots: plt.show()