diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile deleted file mode 100644 index 2300915..0000000 --- a/.devcontainer/Dockerfile +++ /dev/null @@ -1,5 +0,0 @@ -# See here for image contents: https://github.com/microsoft/vscode-dev-containers/tree/v0.245.0/containers/python-3/.devcontainer/base.Dockerfile - -# [Choice] Python version (use -bullseye variants on local arm64/Apple Silicon): 3, 3.10, 3.9, 3.8, 3.7, 3.6, 3-bullseye, 3.10-bullseye, 3.9-bullseye, 3.8-bullseye, 3.7-bullseye, 3.6-bullseye, 3-buster, 3.10-buster, 3.9-buster, 3.8-buster, 3.7-buster, 3.6-buster -ARG VARIANT="3.10-bullseye" -FROM mcr.microsoft.com/vscode/devcontainers/python:0-${VARIANT} \ No newline at end of file diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json deleted file mode 100644 index 684b518..0000000 --- a/.devcontainer/devcontainer.json +++ /dev/null @@ -1,45 +0,0 @@ -// For format details, see https://aka.ms/devcontainer.json. For config options, see the README at: -// https://github.com/microsoft/vscode-dev-containers/tree/v0.245.0/containers/python-3 -{ - "name": "Python 3", - "build": { - "dockerfile": "Dockerfile", - "context": "..", - "args": { - // Update 'VARIANT' to pick a Python version: 3, 3.10, 3.9, 3.8, 3.7, 3.6 - // Append -bullseye or -buster to pin to an OS version. - // Use -bullseye variants on local on arm64/Apple Silicon. - "VARIANT": "3.10-bullseye", - // Options - "NODE_VERSION": "none" - } - }, - // Configure tool-specific properties. - "customizations": { - // Configure properties specific to VS Code. - "vscode": { - // // Set *default* container specific settings.json values on container create. - // "settings": { - // "python.defaultInterpreterPath": "/home/vscode/venv/bin/python" - // }, - // Add the IDs of extensions you want installed when the container is created. - "extensions": [ - "ms-python.python", - "ms-python.vscode-pylance", - "GitHub.vscode-github-actions", - "GitHub.copilot", - "GitHub.copilot-chat" - ] - } - }, - // Use 'forwardPorts' to make a list of ports inside the container available locally. - // "forwardPorts": [], - // Use 'postCreateCommand' to run commands after the container is created. - "onCreateCommand": "sudo cp .devcontainer/welcome.txt /usr/local/etc/vscode-dev-containers/first-run-notice.txt", - "postCreateCommand": "sudo -H pip3 install -r requirements.txt" // alternatively, `pip3 install --user -r requirements.txt` - // Comment out to connect as root instead. More info: https://aka.ms/vscode-remote/containers/non-root. - // "remoteUser": "vscode", - // "features": { - // "azure-cli": "latest" - // } -} \ No newline at end of file diff --git a/.devcontainer/welcome.txt b/.devcontainer/welcome.txt deleted file mode 100644 index 8921939..0000000 --- a/.devcontainer/welcome.txt +++ /dev/null @@ -1,5 +0,0 @@ -👋 Welcome to Codespaces! Wait a few seconds for the Python requirements to install. - -📝 In the meantime, begin following the instructions in the README. - -đŸ€” If you have questions, use the GitHub Copilot "Chat" extension along the left panel (note: you may need to click the ellipses (...) to see the extension) diff --git a/.github/.keep b/.github/.keep deleted file mode 100644 index e69de29..0000000 diff --git a/.github/workflows/classroom.yml b/.github/workflows/classroom.yml index 92a4edd..b58bdff 100644 --- a/.github/workflows/classroom.yml +++ b/.github/workflows/classroom.yml @@ -16,8 +16,11 @@ jobs: uses: actions/checkout@v4 - name: Hello world test id: hello-world-test - uses: education/autograding-python-grader@v1 + uses: education/autograding-command-grader@v1 with: + test-name: Hello world test + setup-command: sudo -H pip3 install pytest + command: pytest timeout: 5 max-score: 5 - name: Autograding Reporter diff --git a/.github/workflows/docker-image.yml b/.github/workflows/docker-image.yml deleted file mode 100644 index 763a95f..0000000 --- a/.github/workflows/docker-image.yml +++ /dev/null @@ -1,18 +0,0 @@ -name: Docker Image CI - -on: - pull_request: - branches: [ "main" ] - # Allow mannually trigger - workflow_dispatch: - -jobs: - - build: - - runs-on: ubuntu-latest - - steps: - - uses: actions/checkout@v3 - - name: Build the Codespaces container image - run: docker build . --file .devcontainer/Dockerfile diff --git a/.gitignore b/.gitignore deleted file mode 100644 index 4ded053..0000000 --- a/.gitignore +++ /dev/null @@ -1,5 +0,0 @@ -**/__pycache__/ -.pytest_cache/ -*.pyc -.coverage -*.egg-info/ diff --git a/.vscode/launch.json b/.vscode/launch.json deleted file mode 100644 index 948a236..0000000 --- a/.vscode/launch.json +++ /dev/null @@ -1,16 +0,0 @@ -{ - // Use IntelliSense to learn about possible attributes. - // Hover to view descriptions of existing attributes. - // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 - "version": "0.2.0", - "configurations": [ - { - "name": "Python: Current File", - "type": "python", - "request": "launch", - "program": "${file}", - "console": "integratedTerminal", - "justMyCode": true - } - ] -} \ No newline at end of file diff --git a/.vscode/settings.json b/.vscode/settings.json deleted file mode 100644 index 1f2b0f4..0000000 --- a/.vscode/settings.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "python.testing.pytestArgs": [ - "." - ], - "python.testing.unittestEnabled": false, - "python.testing.pytestEnabled": true, - "githubPullRequests.ignoredPullRequestBranches": [ - "main" - ] -} \ No newline at end of file diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..d07993a --- /dev/null +++ b/LICENSE @@ -0,0 +1,22 @@ +MIT License + +Copyright (c) 2024 Dieter Plessers +Copyright (c) 2024 AC BO Hackathon + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md index da21a6d..be24328 100644 --- a/README.md +++ b/README.md @@ -1,15 +1,41 @@ -# Autograding Example: Python -This example project is written in Python, and tested with pytest. - -## The assignment -The tests are failing right now because the method isn't outputting the correct string. Fixing this up will make the tests green. - -## Setup command - -See `postCreateCommand` from [`devcontainer.json`](.devcontainer/devcontainer.json). - -## Run command -`pytest` - -## Notes -- pip's install path is not included in the PATH var by default, so without installing via `sudo -H`, pytest would be unaccessible. +# Bayesian Optimisation for Zeolite Synthesis Zeolite + +## Overview +Summarizing poster + +## Scope +This repository was initiated as an entry for the [Bayesian Optimization Hackathon for Chemistry and Materials](https://ac-bo-hackathon.github.io/), held on March 27-28, 2024, and sponsored by the Acceleration Consortium and Merck KGaA. Here, we propose Bayesian optimization within the field of zeolite synthesis. This concept is also explained in a [short video](https://www.youtube.com/watch?v=4lFEUixwkE8). + +Despite their significant industrial applications as catalysts, ion exchangers and adsorbents, the synthesis of zeolites predominantly relies on heuristics, experience and a sprinkle of magic. Employing Bayesian optimization has the potential to swiftly navigate the extensive parameter space in zeolite synthesis research and reduce associated costs. + +## [Introductory text](./zeolite_synthesis_bo_introduction.md) on zeolites, zeolite synthesis and Bayesian optimization +In [zeolite_synthesis_bo_introduction.md](./zeolite_synthesis_bo_introduction.md) we provide an overview of the following topics: +1. **Zeolites:** definition, properties and relevance in industry +2. **Zeolite Synthesis:** typical procedures, ingredients and equipments +3. **Zeolite Synthesis Optimization:** the limited literature on active learning for zeolite synthesis is discussed, and considerations regarding BO: + 3.1 Parameter space of a zeolite synthesis + 3.2 Constraints of a zeolite synthesis + 3.3 Objectives that might be pursued in zeolite synthesis + 3.4 Zeolite synthesis datasets that are available in literature +4. **Discussion:** various aspects of BO in zeolite synthesis are discussed, and how they can be tackled +5. **References** + +While numerous references are provided for further exploration, this document is self-contained and aims to be easily understood. We hope it inspires the reader to consider active learning approaches in their zeolite synthesis endeavors. +This introductory text is also provided as [pdf](./zeolite_synthesis_bo_introduction.pdf). + +## [Notebook](./demo_zeolite_synthesis_bo.ipynb) tutorial on Bayesian optimization for zeolite synthesis (with [Ax](https://ax.dev/)) Open In Colab +Within the [demo_zeolite_synthesis_bo.ipynb](./demo_zeolite_synthesis_bo.ipynb) notebook, we illustrate the concepts of the introductory text with code, leveraging real-world literature data acquired through grid search in [Table S4](https://pubs.acs.org/doi/suppl/10.1021/acs.chemmater.9b03738/suppl_file/cm9b03738_si_001.pdf#page=10) and [Table S3](https://pubs.acs.org/doi/suppl/10.1021/jacs.1c07590/suppl_file/ja1c07590_si_001.pdf#page=9) in the Supporting Information of respectively [*Chem. Mater.* **2020**, *32*, 273–285](https://pubs.acs.org/doi/abs/10.1021/acs.chemmater.9b03738) and [*J. Am. Chem. Soc.* **2021**, *143*, 16243–16255](https://pubs.acs.org/doi/10.1021/jacs.1c07590). + +This notebook is divided into two sections: +1. The first section uses Bayesian optimization to **optimize an analytical dummy function** using zeolite synthesis parameters, showing a significant improvement compared to grid search. +2. The second section uses Bayesian optimization to **propose a new experiment** based on existing literature data. + +A common overarching objective in zeolite synthesis is to achieve a **high yield** of the desired zeolite product. In the papers under consideration, a more specialized goal involves **maximizing the presence of proximal Al pairs** within synthesized CHA zeolites, which is required for stabilizing Fe2+ sites (so-called divalent cation capacity, DCC). Upon activation, these sites can selectively oxidize methane to **methanol**. Accordingly, we will provide examples with synthesis yield, DCC and methanol yield as optimization objectives for the Bayesian optimization process. + +The various examples touch upon different aspects of Bayesian optimization, including **continuous variables, categorical variables, mixed variable types, parameter constraints, as well as single and multiple objectives**. + + + + + + diff --git a/demo_zeolite_synthesis_bo.ipynb b/demo_zeolite_synthesis_bo.ipynb new file mode 100644 index 0000000..deb1fe9 --- /dev/null +++ b/demo_zeolite_synthesis_bo.ipynb @@ -0,0 +1,2181 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "33956ea9-21bf-41a8-9c4a-d24f89d1d52d", + "metadata": {}, + "source": [ + "# Code Demonstration: Bayesian Optimization for Zeolite Synthesis" + ] + }, + { + "cell_type": "markdown", + "id": "72398f31-f1bc-459f-aca4-6358d7e1a0c8", + "metadata": {}, + "source": [ + "Author: Plessers Dieter \n", + "March 28th, 2024" + ] + }, + { + "cell_type": "markdown", + "id": "e8b0873e-52ee-41ad-8ea6-dda138b77892", + "metadata": {}, + "source": [ + "## Table of Contents \n", + "[Introduction](#Introduction) \n", + "[Import necessary packages](#Packages) \n", + "\n", + "[Part 1: Optimize an Analytical 'Dummy' Function with Zeolite Synthesis Parameters](#Part1)\n", + "* [1.1 Define Dummy Analytical Objective Functions](#Part1.1) \n", + "* [1.2 Define a Function that Optimizes the Dummy Function with Ax Package](#Part1.2)\n", + "* [1.3 Single-Objective with 4 Continuous Variables](#Part1.3) \n", + "* [1.4 Multi-Objective with 4 Continuous Variables](#Part1.4)\n", + "* [1.5 Multi-Objective with 4 Continuous Variables and 2 Categorical Variables](#Part1.5) \n", + "* [1.6 Multi-Objective with 4 Continuous Variables, 2 Categorical Variables and Parameter Constraints](#Part1.6)\n", + "* [1.7 Code Example without Using the General `optimize_experiment_ax` Function of Part 1.2](#Part1.7)\n", + " \n", + "[Part 2: Propose a New Experiment Based on a Batch of Prior (Literature) Experiments](#Part2)\n", + "* [2.1 Define a Function that Proposes the Next Best Experiment with Ax Package](#Part2.1) \n", + "* [2.2 Collect the Literature Input and Output Data](#Part2.2)\n", + "* [2.3 Single-Objective (Synthesis Yield) with 3 Continuous Variables and 2 Categorical Variables](#Part2.3) \n", + "* [2.4 Multi-Objective (Synthesis Yield + Methanol Production) with 3 Continuous Variables and 2 Categorical Variables](#Part2.4)\n" + ] + }, + { + "cell_type": "markdown", + "id": "e3c2a160-5c0a-4e9c-bfcd-45491aef19b9", + "metadata": {}, + "source": [ + "## Introduction \n", + "#### Scope \n", + "This Jupyter notebook accompanies an introductory text on Bayesian optimization (BO) for zeolite synthesis. In the text, we outlined a **typical parameter space with tunable variables and constraints, along with possible objectives derived from a zeolite target application**. Within this notebook, we exemplify these concepts with code, leveraging real-world literature data acquired through grid search in [Table S4](https://pubs.acs.org/doi/suppl/10.1021/acs.chemmater.9b03738/suppl_file/cm9b03738_si_001.pdf#page=10) and [Table S3](https://pubs.acs.org/doi/suppl/10.1021/jacs.1c07590/suppl_file/ja1c07590_si_001.pdf#page=9) in the Supporting Information of respectively:\n", + "\n", + "1. Devos, J.; Bols, M. L.; Plessers, D.; Goethem, C. Van; Seo, J. W.; Hwang, S.-J.; Sels, B. F.; Dusselier, M. Synthesis–Structure–Activity Relations in Fe-CHA for C–H Activation: Control of Al Distribution by Interzeolite Conversion. [*Chem. Mater.* **2020**, *32*, 273–285](https://pubs.acs.org/doi/abs/10.1021/acs.chemmater.9b03738). \n", + "2. Bols, M. L.; Devos, J.; Rhoda, H. M.; Plessers, D.; Solomon, E. I.; Schoonheydt, R. A.; Sels, B. F.; Dusselier, M. Selective Formation of $\\alpha$-Fe(II) Sites on Fe-Zeolites through One-Pot Synthesis. [*J. Am. Chem. Soc.* **2021**, *143*, 16243–16255](https://pubs.acs.org/doi/10.1021/jacs.1c07590). \n", + "\n", + "A common overarching objective in zeolite synthesis is to achieve a **high yield** of the desired zeolite product. In the papers under consideration, a more specialized goal involves **maximizing the presence of proximal Al pairs** within synthesized CHA zeolites, which is required for stabilizing Fe2+ sites (so-called divalent cation capacity, DCC). Upon activation, these sites can selectively oxidize methane to **methanol**. Accordingly, we will provide examples with synthesis yield, DCC and methanol yield as optimization objectives for the Bayesian optimization process.\n", + "\n", + "This notebook is divided into two sections: \n", + "1. The first section uses Bayesian optimization to optimize an analytical dummy function using zeolite synthesis parameters. \n", + "2. The second section uses Bayesian optimization to propose a new experiment based on existing literature data.\n", + "\n", + "#### [Part 1: Optimize an Analytical 'Dummy' Function with Zeolite Synthesis Parameters](#Part1)\n", + "We create a ['dummy' function](#Part1.1) linking the input parameters of zeolite synthesis to a specific output. The Bayesian optimizer operates without knowledge of the underlying analytical function we have defined, but this approach allows us to run a Bayesian optimization scheme as follows: 1) BO suggests parameters for experimentation based on a list of previous experimental outcomes, 2) our analytical function yields the outcome of the suggested experiment, 3) the outcome is added to the list of previous experimental outcomes and 4) this process is iteratively repeated. \n", + "\n", + "We run three different experiment modes to demonstrate various options available with BO:\n", + "\n", + "1. Four **continuous** variables with a **single** objective (high synthesis yield) [[link]](#Part1.3) \n", + "2. Four **continuous** variables with **multiple** objectives (high synthesis yield and high DCC) [[link]](#Part1.4) \n", + "3. **Mixed variable types** (4 continuous and 2 categorical) with **multiple** objectives (high synthesis yield and high DCC) [[link]](#Part1.5) \n", + "4. **Mixed variable types** (4 continuous and 2 categorical) with **parameter constraints** and with **multiple objectives** (high synthesis yield and high DCC) [[link]](#Part1.6) \n", + "\n", + "In the simplest case of 4 continuous variables, a grid search choosing 3 values per variable would require $3^4=81$ experiments, with no guarantee that any of these experiments will come close to the global optimum. In contrast, it can be seen in the code that the Bayesian optimizer reaches close to the global optimum in less than 30 iterations. \n", + " \n", + "#### [Part 2: Propose a New Experiment Based on a Batch of Prior (Literature) Experiments](#Part2)\n", + "Here Bayesian optimization is applied within a **real-world context** where the underlying target function remains unknown. We start from a batch of experiments collected from literature, and we allow the Bayesian optimizer to **suggest the next experiment**. This process aims to explore the search space further and/or leverage past results. In practice, one would implement the suggested experiment, incorporate its outcome into the existing results, and subsequently rerun the Bayesian optimization algorithm. We provide an example with **mixed variable types** (3 continuous and 2 categorical) with a **single-objective** and **multi-objective** target.\n", + "\r\n", + "\n", + "\n" + ] + }, + { + "cell_type": "markdown", + "id": "293ba810-16e0-4db1-8bd8-ed91705d0fe5", + "metadata": {}, + "source": [ + "## Import necessary packages " + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "id": "bec011d0-be00-428b-b4e3-ace5c2940f3e", + "metadata": {}, + "outputs": [], + "source": [ + "import numpy as np\n", + "\n", + "# Ax (adaptive experimentation) is the package we use for BO: https://ax.dev/\n", + "try:\n", + " import ax\n", + "except: \n", + " %pip install ax-platform\n", + "from ax.service.ax_client import AxClient, ObjectiveProperties\n", + "from ax.modelbridge.generation_strategy import GenerationStrategy, GenerationStep\n", + "from ax.modelbridge.registry import Models\n", + "from ax.service.utils.instantiation import ObjectiveProperties\n", + "\n", + "# We will use plotly to plot the optimization trace of a single-objective experiment\n", + "try: \n", + " import plotly\n", + "except:\n", + " %pip install plotly\n", + "import plotly.graph_objects as go\n", + "\n", + "from IPython.display import display # Enables the display of more than one dataframe per code cell\n", + "import pandas as pd\n", + "pd.set_option('max_colwidth', None) # Show full width Panda columns\n", + "from collections import OrderedDict\n", + "\n", + "# To hide all the info and warning messages of Ax. Comment the next lines if you want to see them.\n", + "import logging, sys\n", + "logging.disable(sys.maxsize)\n", + "import warnings\n", + "warnings.filterwarnings('ignore')\n" + ] + }, + { + "cell_type": "markdown", + "id": "4f326992-3de6-443f-875b-d72e35aaab7b", + "metadata": {}, + "source": [ + "## Part 1: Optimize an Analytical 'Dummy' Function with Zeolite Synthesis Parameters " + ] + }, + { + "cell_type": "markdown", + "id": "6c7517af-a5d3-4d5c-b7e9-05af59588548", + "metadata": {}, + "source": [ + "### 1.1 Define Dummy Analytical Objective Functions " + ] + }, + { + "cell_type": "markdown", + "id": "2b43f842-8109-499d-a064-048b377cb9b3", + "metadata": {}, + "source": [ + "Here we will define analytical functions for BO to **minimize**:\n", + "1) The first function has 4 continuous variables with a single objective\n", + "2) The second function has 4 continuous variables with two objectives\n", + "3) The third fuctions has 4 continuous and 2 categorical variables with 2 objectives\n", + "\n", + "We choose a simple function definition that has a **clear global minimum** (both for single and multi-objective) around \n", + "x1 = 0.35 $\\ \\ \\ $ (molar ratio of organic structure directing agents (OSDA) compared to Si) \n", + "x2 = 12.5 $\\ \\ \\ $ (the molar ratio of water compared to Si) \n", + "x3 = 140 $\\ \\ \\ \\ $ (synthesis temperature (°C)) \n", + "x4 = 24 $\\ \\ \\ \\ \\ \\ $ (synthesis time (h)) \n", + "x5 = Static $\\ $ (stirring mode) \n", + "x6 = Fumed (silicon source)" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "fae9fcab-28a4-4245-8354-677b7a4034d1", + "metadata": {}, + "outputs": [], + "source": [ + "def dummy_function_4_continuous_1_objective(x1, x2, x3, x4, obj_name1):\n", + " y1 = float((90*x1- 90*0.35)**2 + (x2-12.5)**2 + (x3-140)**2 + (x4/8-24/8)**2 + np.cos(x1*x2*x3*x4))\n", + " return {obj_name1:y1}\n", + " \n", + "def dummy_function_4_continuous_2_objective(x1, x2, x3, x4, obj_name1, obj_name2):\n", + " y1 = float((90*x1- 90*0.35)**2 + (x2-12.5)**2 + (x3-140)**2 + (x4/8-24/8)**2 + np.cos(x1*x2*x3*x4))\n", + " y2 = float((90*x1- 90*0.35)**2 + (x2-12.5)**2 + (x3-140)**2 + (x4/8-24/8)**2 + np.sin(x1*x2*x3*x4))\n", + "\n", + " # In our case, standard error is 0.0, since we are computing a synthetic function.\n", + " # See: https://ax.dev/tutorials/multiobjective_optimization.html\n", + " return {obj_name1:(y1, 0.0), obj_name2 : (y2, 0.0)}\n", + "\n", + "def dummy_function_4_continuous_2_categorical_2_objective(x1, x2, x3, x4, c1, c2, obj_name1, obj_name2):\n", + "\n", + " # The penalty lookup idea comes from https://honegumi.readthedocs.io/en/latest/\n", + " penalty_lookup1 = {\"Stirring\": 5000, \"Tumbling\": 2500, \"Static\": 0.0}\n", + " penalty_lookup2 = {\"Colloidal\": 5000, \"Fumed\": 0.0}\n", + " \n", + " y1 = float((90*x1- 90*0.35)**2 + (x2-12.5)**2 + (x3-140)**2 + (x4/8-24/8)**2 + np.cos(x1*x2*x3*x4))\n", + " y1 += penalty_lookup1[c1] + penalty_lookup2[c2]\n", + " \n", + " y2 = float((90*x1- 90*0.35)**2 + (x2-12.5)**2 + (x3-140)**2 + (x4/8-24/8)**2 + np.sin(x1*x2*x3*x4))\n", + " y2 += penalty_lookup1[c1] + penalty_lookup2[c2]\n", + " \n", + " # In our case, standard error is 0.0, since we are computing a synthetic function.\n", + " # See: https://ax.dev/tutorials/multiobjective_optimization.html\n", + " return {obj_name1:(y1, 0.0), obj_name2 : (y2, 0.0)}\n" + ] + }, + { + "cell_type": "markdown", + "id": "9b4cc353-4c83-42f8-9bfd-bf1d926e97e0", + "metadata": {}, + "source": [ + "### 1.2 Define a Function that Optimizes the Dummy Function with Ax Package " + ] + }, + { + "cell_type": "markdown", + "id": "fdb712df-d17b-4662-a671-ddf0b3335c2c", + "metadata": {}, + "source": [ + "Here we will create a **general function** `optimize_experiment_ax` to run a BO experiment with the Ax package. This general function allows focus on the output of the experiments further on, without repeatedly modifying similar sections of code. At the end, we offer an example code not using this general function, which may be employed if only one type of experiment is considered. \n", + "\n", + "The `optimize_experiment_ax` function takes a list of **objective names** (can be any length, so single or multi-objective), an **analytical function** of the objectives (defined in [section 1.1](#Part1.1)), a list of **continuous parameters**, a list of **categorical parameters**, a list of parameter **constraints**, and an amount of **iterations**. This function will be used throughout part 1 of the notebook to run different experiments. \n", + "\n", + "\n", + "This function is inspired by the different options provided by **Honegumi** (https://honegumi.readthedocs.io/en/latest/), and can easily be modified to incorporate batch sizes. " + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "5bcfb4b9-c580-4061-9b95-34a66f1667c6", + "metadata": {}, + "outputs": [], + "source": [ + "def to_plotly(axplotconfig):\n", + " \"\"\"Converts AxPlotConfig to plotly Figure.\"\"\"\n", + " data = axplotconfig[0][\"data\"]\n", + " layout = axplotconfig[0][\"layout\"]\n", + " fig = go.Figure({\"data\": data, \"layout\": layout})\n", + " return fig\n", + " \n", + "def optimize_experiment_ax(list_obj_names, objective_function, continuous_parameters_list, categorical_parameters_list, \n", + " parameter_constraints_lists, iterations):\n", + " \"\"\"\n", + " Creates an optimization experiment with the Ax package. \n", + " Inputs: \n", + " list_obj_names: list with all the objective names\n", + " objective_function: function used to calculate objectives\n", + " continuous_parameters_list: list of continuous parameters for the objective function\n", + " categorical_parameters_list: list of categorical parameters for the objective function\n", + " parameter_constraints_lists: list of constraints in the parameter space\n", + " iterations: number of iterations for the BO algorithm\n", + "\n", + " Outputs:\n", + " The best parameters and their objective function outputs are printed.\n", + " For single-objective experiments, the optimization trace is plotted. \n", + "\n", + " Source:\n", + " This function is inspired by the different options provided by Honegumi (https://honegumi.readthedocs.io/en/latest/)\n", + " \"\"\"\n", + "\n", + " all_parameters_list = continuous_parameters_list + categorical_parameters_list\n", + " objectives_dictionary={}\n", + " for obj_name in list_obj_names:\n", + " objectives_dictionary[obj_name]=ObjectiveProperties(minimize=True)\n", + " \n", + " ax_client = AxClient(verbose_logging=False)\n", + " ax_client.create_experiment(parameters= all_parameters_list, \n", + " parameter_constraints = parameter_constraints_lists,\n", + " objectives = objectives_dictionary \n", + " )\n", + " \n", + " for _ in range(iterations):\n", + " parameterization, trial_index = ax_client.get_next_trial()\n", + " \n", + " # Extract parameters and collect in dictionary\n", + " dict_extract_parameters={}\n", + " for parameter_info in all_parameters_list:\n", + " parameter_name = parameter_info[\"name\"]\n", + " dict_extract_parameters[parameter_name] = parameterization[parameter_name]\n", + " \n", + " # Calculate the results by plugging the parameters and objective names in the provided objective function\n", + " results = objective_function(*dict_extract_parameters.values(), *list_obj_names)\n", + " ax_client.complete_trial(trial_index=trial_index, raw_data=results)\n", + "\n", + " # Create dataframe with details of all experiment iterations\n", + " df_experiments = ax_client.generation_strategy.trials_as_df\n", + " number_of_experiments_to_show= min(iterations, 5)\n", + " print(f'The details of the last {number_of_experiments_to_show} experiments:')\n", + " display(df_experiments.tail())\n", + "\n", + " if len(list_obj_names) == 1:\n", + " # Single-objective optimization\n", + " # Get the AxClient's optimization trace \n", + " optimization_trace = ax_client.get_optimization_trace()\n", + " \n", + " # Convert the optimization trace to a Plotly figure and plot it\n", + " fig = to_plotly(optimization_trace)\n", + " fig.show(renderer='png')\n", + "\n", + " # Output the best parameters and their corresponding objective values\n", + " best_parameters, value = ax_client.get_best_parameters()\n", + " print('The best parameters were:')\n", + " display(best_parameters)\n", + " print('\\nThe objective value for the best parameters was:')\n", + " display(value[0])\n", + "\n", + " else:\n", + " # Multi-objective optimization\n", + " # Output the Pareto optimal parameters and their corresponding objective values\n", + " best_parameters = ax_client.get_pareto_optimal_parameters()\n", + " print('The Pareto optimal parameters were:')\n", + " display(list(best_parameters.values())[0][0])\n", + " print('\\nThe objective values for the Pareto optimal parameters were:')\n", + " display(list(best_parameters.values())[0][1][0])\n", + " \n", + " return None" + ] + }, + { + "cell_type": "markdown", + "id": "e2739dec-a5c4-4a32-bdbb-de0c014cf4dc", + "metadata": {}, + "source": [ + "### 1.3 Single-Objective with 4 Continuous Variables " + ] + }, + { + "cell_type": "markdown", + "id": "2f9df5b9-9b2a-4785-a347-061da3d110fd", + "metadata": {}, + "source": [ + "The first demonstration uses the general function [`optimize_experiment_ax`](#Part1.2) to optimize an experiment with **4 continuous variables** with typical ranges that are based on [Table S4](https://pubs.acs.org/doi/suppl/10.1021/acs.chemmater.9b03738/suppl_file/cm9b03738_si_001.pdf#page=10) in the Supporting Information of [reference 1](https://pubs.acs.org/doi/abs/10.1021/acs.chemmater.9b03738): \n", + "* the molar ratio of organic structure directing agents (OSDA) compared to Si \n", + "* the molar ratio of water compared to Si\n", + "* synthesis temperature (°C)\n", + "* synthesis time (h)\n", + "\n", + "The **single target variable** is synthesis yield. We assume that minimizing the above defined [dummy function](#Part1.1) `dummy_function_4_continuous_1_objective` corresponds to maximizing the yield of the zeolite synthesis. We also plot the **optimization trace** of the BO algorithm throughout the different experiment iterations.\n" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "id": "c1825bf3-f5be-4504-834b-f18551cecd63", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "The details of the last 5 experiments:\n" + ] + }, + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
Generation StepGeneration ModelTrial IndexTrial StatusArm Parameterizations
25GenerationStep_1BoTorch25COMPLETED{'25_0': {'OSDA_molar_ratio': 0.38, 'H2O_molar_ratio': 13.61, 'Synthesis_temperature': 136.78, 'Synthesis_time': 28.85}}
26GenerationStep_1BoTorch26COMPLETED{'26_0': {'OSDA_molar_ratio': 0.38, 'H2O_molar_ratio': 20.35, 'Synthesis_temperature': 143.58, 'Synthesis_time': 2.0}}
27GenerationStep_1BoTorch27COMPLETED{'27_0': {'OSDA_molar_ratio': 0.36, 'H2O_molar_ratio': 11.54, 'Synthesis_temperature': 142.55, 'Synthesis_time': 37.52}}
28GenerationStep_1BoTorch28COMPLETED{'28_0': {'OSDA_molar_ratio': 0.38, 'H2O_molar_ratio': 5.0, 'Synthesis_temperature': 138.76, 'Synthesis_time': 2.0}}
29GenerationStep_1BoTorch29COMPLETED{'29_0': {'OSDA_molar_ratio': 0.35, 'H2O_molar_ratio': 12.39, 'Synthesis_temperature': 138.91, 'Synthesis_time': 25.45}}
\n", + "
" + ], + "text/plain": [ + " Generation Step Generation Model Trial Index Trial Status \\\n", + "25 GenerationStep_1 BoTorch 25 COMPLETED \n", + "26 GenerationStep_1 BoTorch 26 COMPLETED \n", + "27 GenerationStep_1 BoTorch 27 COMPLETED \n", + "28 GenerationStep_1 BoTorch 28 COMPLETED \n", + "29 GenerationStep_1 BoTorch 29 COMPLETED \n", + "\n", + " Arm Parameterizations \n", + "25 {'25_0': {'OSDA_molar_ratio': 0.38, 'H2O_molar_ratio': 13.61, 'Synthesis_temperature': 136.78, 'Synthesis_time': 28.85}} \n", + "26 {'26_0': {'OSDA_molar_ratio': 0.38, 'H2O_molar_ratio': 20.35, 'Synthesis_temperature': 143.58, 'Synthesis_time': 2.0}} \n", + "27 {'27_0': {'OSDA_molar_ratio': 0.36, 'H2O_molar_ratio': 11.54, 'Synthesis_temperature': 142.55, 'Synthesis_time': 37.52}} \n", + "28 {'28_0': {'OSDA_molar_ratio': 0.38, 'H2O_molar_ratio': 5.0, 'Synthesis_temperature': 138.76, 'Synthesis_time': 2.0}} \n", + "29 {'29_0': {'OSDA_molar_ratio': 0.35, 'H2O_molar_ratio': 12.39, 'Synthesis_temperature': 138.91, 'Synthesis_time': 25.45}} " + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "image/png": "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" + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "The best parameters were:\n" + ] + }, + { + "data": { + "text/plain": [ + "{'OSDA_molar_ratio': 0.34775812136334816,\n", + " 'H2O_molar_ratio': 12.389732172987166,\n", + " 'Synthesis_temperature': 138.9139401644853,\n", + " 'Synthesis_time': 25.450990377516668}" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\n", + "The objective value for the best parameters was:\n" + ] + }, + { + "data": { + "text/plain": [ + "{'Synthesis_yield': 0.8177388940177934}" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "optimize_experiment_ax(list_obj_names = ['Synthesis_yield'],\n", + " objective_function = dummy_function_4_continuous_1_objective,\n", + " continuous_parameters_list = [{\"name\": \"OSDA_molar_ratio\", \"type\": \"range\", \"bounds\": [0.1, 1.0]},\n", + " {\"name\": \"H2O_molar_ratio\", \"type\": \"range\", \"bounds\": [5.0, 100.0]},\n", + " {\"name\": \"Synthesis_temperature\", \"type\": \"range\", \"bounds\": [100.0, 200.0]},\n", + " {\"name\": \"Synthesis_time\", \"type\": \"range\", \"bounds\": [2.0, 432.0]},\n", + " ],\n", + " categorical_parameters_list = [],\n", + " parameter_constraints_lists = [],\n", + " iterations=30)" + ] + }, + { + "cell_type": "markdown", + "id": "c1e91c48-fc9c-40c6-9ef5-7a9251db33da", + "metadata": {}, + "source": [ + "### 1.4 Multi-Objective with 4 Continuous Variables " + ] + }, + { + "cell_type": "markdown", + "id": "91b6dbc9-cf6b-4090-99f2-a2a027c188af", + "metadata": {}, + "source": [ + "The second demonstration uses the general function [`optimize_experiment_ax`](#Part1.2) to optimize an experiment with **4 continuous variables** with typical ranges that are based on [Table S4](https://pubs.acs.org/doi/suppl/10.1021/acs.chemmater.9b03738/suppl_file/cm9b03738_si_001.pdf#page=10) in the Supporting Information of [reference 1](https://pubs.acs.org/doi/abs/10.1021/acs.chemmater.9b03738): \n", + "* the molar ratio of organic structure directing agents (OSDA) compared to Si \n", + "* the molar ratio of water compared to Si\n", + "* synthesis temperature (°C)\n", + "* synthesis time (h)\n", + "\n", + "The **2 target variables** are synthesis yield and DCC (divalent cation capacity). We assume that minimizing the above defined [dummy function](#Part1.1) `dummy_function_4_continuous_2_objective` corresponds to maximizing the yield and DCC of the zeolite synthesis. Refer to https://ax.dev/tutorials/multiobjective_optimization.html for more background information." + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "id": "498f8715-7d4c-47d6-98d6-2ac1f8dcba15", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "The details of the last 5 experiments:\n" + ] + }, + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
Generation StepGeneration ModelTrial IndexTrial StatusArm Parameterizations
25GenerationStep_1BoTorch25COMPLETED{'25_0': {'OSDA_molar_ratio': 0.35, 'H2O_molar_ratio': 12.76, 'Synthesis_temperature': 140.14, 'Synthesis_time': 23.7}}
26GenerationStep_1BoTorch26COMPLETED{'26_0': {'OSDA_molar_ratio': 0.38, 'H2O_molar_ratio': 11.41, 'Synthesis_temperature': 140.21, 'Synthesis_time': 23.86}}
27GenerationStep_1BoTorch27COMPLETED{'27_0': {'OSDA_molar_ratio': 0.36, 'H2O_molar_ratio': 13.2, 'Synthesis_temperature': 140.56, 'Synthesis_time': 23.78}}
28GenerationStep_1BoTorch28COMPLETED{'28_0': {'OSDA_molar_ratio': 0.33, 'H2O_molar_ratio': 8.25, 'Synthesis_temperature': 143.34, 'Synthesis_time': 2.0}}
29GenerationStep_1BoTorch29COMPLETED{'29_0': {'OSDA_molar_ratio': 0.35, 'H2O_molar_ratio': 12.67, 'Synthesis_temperature': 139.9, 'Synthesis_time': 30.48}}
\n", + "
" + ], + "text/plain": [ + " Generation Step Generation Model Trial Index Trial Status \\\n", + "25 GenerationStep_1 BoTorch 25 COMPLETED \n", + "26 GenerationStep_1 BoTorch 26 COMPLETED \n", + "27 GenerationStep_1 BoTorch 27 COMPLETED \n", + "28 GenerationStep_1 BoTorch 28 COMPLETED \n", + "29 GenerationStep_1 BoTorch 29 COMPLETED \n", + "\n", + " Arm Parameterizations \n", + "25 {'25_0': {'OSDA_molar_ratio': 0.35, 'H2O_molar_ratio': 12.76, 'Synthesis_temperature': 140.14, 'Synthesis_time': 23.7}} \n", + "26 {'26_0': {'OSDA_molar_ratio': 0.38, 'H2O_molar_ratio': 11.41, 'Synthesis_temperature': 140.21, 'Synthesis_time': 23.86}} \n", + "27 {'27_0': {'OSDA_molar_ratio': 0.36, 'H2O_molar_ratio': 13.2, 'Synthesis_temperature': 140.56, 'Synthesis_time': 23.78}} \n", + "28 {'28_0': {'OSDA_molar_ratio': 0.33, 'H2O_molar_ratio': 8.25, 'Synthesis_temperature': 143.34, 'Synthesis_time': 2.0}} \n", + "29 {'29_0': {'OSDA_molar_ratio': 0.35, 'H2O_molar_ratio': 12.67, 'Synthesis_temperature': 139.9, 'Synthesis_time': 30.48}} " + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "The Pareto optimal parameters were:\n" + ] + }, + { + "data": { + "text/plain": [ + "{'OSDA_molar_ratio': 0.3514134608325845,\n", + " 'H2O_molar_ratio': 12.763524283729826,\n", + " 'Synthesis_temperature': 140.14219782729086,\n", + " 'Synthesis_time': 23.698891067110903}" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\n", + "The objective values for the Pareto optimal parameters were:\n" + ] + }, + { + "data": { + "text/plain": [ + "{'DCC': 0.24512712912701318, 'Synthesis_yield': 0.04231119590133403}" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "optimize_experiment_ax(list_obj_names = ['DCC', 'Synthesis_yield'],\n", + " objective_function = dummy_function_4_continuous_2_objective,\n", + " continuous_parameters_list = [{\"name\": \"OSDA_molar_ratio\", \"type\": \"range\", \"bounds\": [0.1, 1.0]},\n", + " {\"name\": \"H2O_molar_ratio\", \"type\": \"range\", \"bounds\": [5.0, 100.0]},\n", + " {\"name\": \"Synthesis_temperature\", \"type\": \"range\", \"bounds\": [100.0, 200.0]},\n", + " {\"name\": \"Synthesis_time\", \"type\": \"range\", \"bounds\": [2.0, 432.0]},\n", + " ],\n", + " categorical_parameters_list = [],\n", + " parameter_constraints_lists = [],\n", + " iterations=30)" + ] + }, + { + "cell_type": "markdown", + "id": "267ec0c3-0aec-4294-b3ad-9d3c5bfc099e", + "metadata": {}, + "source": [ + "### 1.5 Multi-Objective with 4 Continuous Variables and 2 Categorical Variables " + ] + }, + { + "cell_type": "markdown", + "id": "b11b8976-7a1c-4030-a735-727db98cf320", + "metadata": {}, + "source": [ + "The third demonstration uses the general function [`optimize_experiment_ax`](#Part1.2) to optimize an experiment with **4 continuous variables** with typical ranges that are based on [Table S4](https://pubs.acs.org/doi/suppl/10.1021/acs.chemmater.9b03738/suppl_file/cm9b03738_si_001.pdf#page=10) in the Supporting Information of [reference 1](https://pubs.acs.org/doi/abs/10.1021/acs.chemmater.9b03738): \n", + "* the molar ratio of organic structure directing agents (OSDA) compared to Si \n", + "* the molar ratio of water compared to Si\n", + "* synthesis temperature (°C)\n", + "* synthesis time (h)\n", + "\n", + "We also add **2 categorical variables** that are inspired of [Table S3](https://pubs.acs.org/doi/suppl/10.1021/jacs.1c07590/suppl_file/ja1c07590_si_001.pdf#page=9) in the Supporting Information of [reference 2](https://pubs.acs.org/doi/10.1021/jacs.1c07590): \n", + "* Stirring mode: stirring (600 rpm), tumbling or static. \n", + "* Silicon source: colloidal silica or fumed silica.\n", + "\n", + "The **2 target variables** are synthesis yield and DCC (divalent cation capacity). We assume that minimizing the above defined [dummy function](#Part1.1) `dummy_function_4_continuous_2_categorical_2_objective` corresponds to maximizing the yield and DCC of the zeolite synthesis. Refer to https://ax.dev/tutorials/multiobjective_optimization.html for more background information." + ] + }, + { + "cell_type": "code", + "execution_count": 13, + "id": "88f3cbdb-d13b-4875-b86b-7efe5094f6e6", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "The details of the last 5 experiments:\n" + ] + }, + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
Generation StepGeneration ModelTrial IndexTrial StatusArm Parameterizations
25GenerationStep_1BoTorch25COMPLETED{'25_0': {'OSDA_molar_ratio': 0.35, 'H2O_molar_ratio': 12.04, 'Synthesis_temperature': 139.7, 'Synthesis_time': 27.79, 'Stirring': 'Static', 'Silicon_source': 'Fumed'}}
26GenerationStep_1BoTorch26COMPLETED{'26_0': {'OSDA_molar_ratio': 0.34, 'H2O_molar_ratio': 11.67, 'Synthesis_temperature': 140.26, 'Synthesis_time': 25.62, 'Stirring': 'Static', 'Silicon_source': 'Fumed'}}
27GenerationStep_1BoTorch27COMPLETED{'27_0': {'OSDA_molar_ratio': 0.35, 'H2O_molar_ratio': 12.09, 'Synthesis_temperature': 140.02, 'Synthesis_time': 29.19, 'Stirring': 'Static', 'Silicon_source': 'Fumed'}}
28GenerationStep_1BoTorch28COMPLETED{'28_0': {'OSDA_molar_ratio': 0.34, 'H2O_molar_ratio': 12.8, 'Synthesis_temperature': 140.5, 'Synthesis_time': 26.23, 'Stirring': 'Static', 'Silicon_source': 'Fumed'}}
29GenerationStep_1BoTorch29COMPLETED{'29_0': {'OSDA_molar_ratio': 0.34, 'H2O_molar_ratio': 12.48, 'Synthesis_temperature': 140.3, 'Synthesis_time': 27.02, 'Stirring': 'Static', 'Silicon_source': 'Fumed'}}
\n", + "
" + ], + "text/plain": [ + " Generation Step Generation Model Trial Index Trial Status \\\n", + "25 GenerationStep_1 BoTorch 25 COMPLETED \n", + "26 GenerationStep_1 BoTorch 26 COMPLETED \n", + "27 GenerationStep_1 BoTorch 27 COMPLETED \n", + "28 GenerationStep_1 BoTorch 28 COMPLETED \n", + "29 GenerationStep_1 BoTorch 29 COMPLETED \n", + "\n", + " Arm Parameterizations \n", + "25 {'25_0': {'OSDA_molar_ratio': 0.35, 'H2O_molar_ratio': 12.04, 'Synthesis_temperature': 139.7, 'Synthesis_time': 27.79, 'Stirring': 'Static', 'Silicon_source': 'Fumed'}} \n", + "26 {'26_0': {'OSDA_molar_ratio': 0.34, 'H2O_molar_ratio': 11.67, 'Synthesis_temperature': 140.26, 'Synthesis_time': 25.62, 'Stirring': 'Static', 'Silicon_source': 'Fumed'}} \n", + "27 {'27_0': {'OSDA_molar_ratio': 0.35, 'H2O_molar_ratio': 12.09, 'Synthesis_temperature': 140.02, 'Synthesis_time': 29.19, 'Stirring': 'Static', 'Silicon_source': 'Fumed'}} \n", + "28 {'28_0': {'OSDA_molar_ratio': 0.34, 'H2O_molar_ratio': 12.8, 'Synthesis_temperature': 140.5, 'Synthesis_time': 26.23, 'Stirring': 'Static', 'Silicon_source': 'Fumed'}} \n", + "29 {'29_0': {'OSDA_molar_ratio': 0.34, 'H2O_molar_ratio': 12.48, 'Synthesis_temperature': 140.3, 'Synthesis_time': 27.02, 'Stirring': 'Static', 'Silicon_source': 'Fumed'}} " + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "The Pareto optimal parameters were:\n" + ] + }, + { + "data": { + "text/plain": [ + "{'OSDA_molar_ratio': 0.34576462735686786,\n", + " 'H2O_molar_ratio': 12.042266849002447,\n", + " 'Synthesis_temperature': 139.70090696302174,\n", + " 'Synthesis_time': 27.788607802183623,\n", + " 'Stirring': 'Static',\n", + " 'Silicon_source': 'Fumed'}" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\n", + "The objective values for the Pareto optimal parameters were:\n" + ] + }, + { + "data": { + "text/plain": [ + "{'DCC': 0.468011099197156, 'Synthesis_yield': 0.7500239907244577}" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "optimize_experiment_ax(list_obj_names = ['DCC', 'Synthesis_yield'],\n", + " objective_function = dummy_function_4_continuous_2_categorical_2_objective,\n", + " continuous_parameters_list = [{\"name\": \"OSDA_molar_ratio\", \"type\": \"range\", \"bounds\": [0.1, 1.0]},\n", + " {\"name\": \"H2O_molar_ratio\", \"type\": \"range\", \"bounds\": [5.0, 100.0]},\n", + " {\"name\": \"Synthesis_temperature\", \"type\": \"range\", \"bounds\": [100.0, 200.0]},\n", + " {\"name\": \"Synthesis_time\", \"type\": \"range\", \"bounds\": [2.0, 432.0]},\n", + " ],\n", + " categorical_parameters_list = [{\"name\": \"Stirring\", \"type\": \"choice\", \"is_ordered\": False,\"values\": [\"Stirring\", \"Tumbling\", \"Static\"]},\n", + " {\"name\": \"Silicon_source\", \"type\": \"choice\", \"is_ordered\": False,\"values\": [\"Colloidal\", \"Fumed\"]},\n", + " ],\n", + " parameter_constraints_lists = [],\n", + " iterations=30)" + ] + }, + { + "cell_type": "markdown", + "id": "32a88991-c821-41a3-b317-d4c2af0c817b", + "metadata": {}, + "source": [ + "### 1.6 Multi-Objective with 4 Continuous Variables, 2 Categorical Variables and Parameter Constraints " + ] + }, + { + "cell_type": "markdown", + "id": "5e679fc4-ed26-4d9a-99b7-3b22e56d8a4b", + "metadata": {}, + "source": [ + "The fourth demonstration uses the general function [`optimize_experiment_ax`](#Part1.2) to optimize an experiment with **4 continuous variables** with typical ranges that are based on [Table S4](https://pubs.acs.org/doi/suppl/10.1021/acs.chemmater.9b03738/suppl_file/cm9b03738_si_001.pdf#page=10) in the Supporting Information of [reference 1](https://pubs.acs.org/doi/abs/10.1021/acs.chemmater.9b03738): \n", + "* the molar ratio of organic structure directing agents (OSDA) compared to Si \n", + "* the molar ratio of water compared to Si\n", + "* synthesis temperature (°C)\n", + "* synthesis time (h)\n", + "\n", + "We also add **2 categorical variables** that are inspired of [Table S3](https://pubs.acs.org/doi/suppl/10.1021/jacs.1c07590/suppl_file/ja1c07590_si_001.pdf#page=9) in the Supporting Information of [reference 2](https://pubs.acs.org/doi/10.1021/jacs.1c07590): \n", + "* Stirring mode: stirring (600 rpm), tumbling or static. \n", + "* Silicon source: colloidal silica or fumed silica.\n", + "\n", + "Lastly, we add **one parameter constraint**. In the context of zeolite synthesis, parameter constraints are uncommon (the parameter bounds are sufficient), so we invent a physically meaningless constraint for illustrative purposes. This constraint will prevent attaining the global minimum, where approximately H2O_molar_ratio = 12.5 and Synthesis_temperature = 140\n", + "* (H2O_molar_ratio) + (Synthesis_temperature) <= 145\n", + "\n", + "The **2 target variables** are synthesis yield and DCC (divalent cation capacity). We assume that minimizing the above defined [dummy function](#Part1.1) `dummy_function_4_continuous_2_categorical_2_objective` corresponds to maximizing the yield and DCC of the zeolite synthesis. Refer to https://ax.dev/tutorials/multiobjective_optimization.html for more background information." + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "id": "e5e76038-b015-4ae3-8e90-2ceebff5e44d", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "The details of the last 5 experiments:\n" + ] + }, + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
Generation StepGeneration ModelTrial IndexTrial StatusArm Parameterizations
25GenerationStep_1BoTorch25COMPLETED{'25_0': {'OSDA_molar_ratio': 0.35, 'H2O_molar_ratio': 8.65, 'Synthesis_temperature': 136.35, 'Synthesis_time': 22.99, 'Stirring': 'Static', 'Silicon_source': 'Fumed'}}
26GenerationStep_1BoTorch26COMPLETED{'26_0': {'OSDA_molar_ratio': 0.34, 'H2O_molar_ratio': 8.55, 'Synthesis_temperature': 136.45, 'Synthesis_time': 23.4, 'Stirring': 'Static', 'Silicon_source': 'Fumed'}}
27GenerationStep_1BoTorch27COMPLETED{'27_0': {'OSDA_molar_ratio': 0.34, 'H2O_molar_ratio': 8.08, 'Synthesis_temperature': 136.92, 'Synthesis_time': 20.49, 'Stirring': 'Static', 'Silicon_source': 'Fumed'}}
28GenerationStep_1BoTorch28COMPLETED{'28_0': {'OSDA_molar_ratio': 0.34, 'H2O_molar_ratio': 8.05, 'Synthesis_temperature': 136.95, 'Synthesis_time': 18.65, 'Stirring': 'Static', 'Silicon_source': 'Fumed'}}
29GenerationStep_1BoTorch29COMPLETED{'29_0': {'OSDA_molar_ratio': 0.34, 'H2O_molar_ratio': 8.49, 'Synthesis_temperature': 136.51, 'Synthesis_time': 23.21, 'Stirring': 'Static', 'Silicon_source': 'Fumed'}}
\n", + "
" + ], + "text/plain": [ + " Generation Step Generation Model Trial Index Trial Status \\\n", + "25 GenerationStep_1 BoTorch 25 COMPLETED \n", + "26 GenerationStep_1 BoTorch 26 COMPLETED \n", + "27 GenerationStep_1 BoTorch 27 COMPLETED \n", + "28 GenerationStep_1 BoTorch 28 COMPLETED \n", + "29 GenerationStep_1 BoTorch 29 COMPLETED \n", + "\n", + " Arm Parameterizations \n", + "25 {'25_0': {'OSDA_molar_ratio': 0.35, 'H2O_molar_ratio': 8.65, 'Synthesis_temperature': 136.35, 'Synthesis_time': 22.99, 'Stirring': 'Static', 'Silicon_source': 'Fumed'}} \n", + "26 {'26_0': {'OSDA_molar_ratio': 0.34, 'H2O_molar_ratio': 8.55, 'Synthesis_temperature': 136.45, 'Synthesis_time': 23.4, 'Stirring': 'Static', 'Silicon_source': 'Fumed'}} \n", + "27 {'27_0': {'OSDA_molar_ratio': 0.34, 'H2O_molar_ratio': 8.08, 'Synthesis_temperature': 136.92, 'Synthesis_time': 20.49, 'Stirring': 'Static', 'Silicon_source': 'Fumed'}} \n", + "28 {'28_0': {'OSDA_molar_ratio': 0.34, 'H2O_molar_ratio': 8.05, 'Synthesis_temperature': 136.95, 'Synthesis_time': 18.65, 'Stirring': 'Static', 'Silicon_source': 'Fumed'}} \n", + "29 {'29_0': {'OSDA_molar_ratio': 0.34, 'H2O_molar_ratio': 8.49, 'Synthesis_temperature': 136.51, 'Synthesis_time': 23.21, 'Stirring': 'Static', 'Silicon_source': 'Fumed'}} " + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "The Pareto optimal parameters were:\n" + ] + }, + { + "data": { + "text/plain": [ + "{'OSDA_molar_ratio': 0.34503914342964936,\n", + " 'H2O_molar_ratio': 8.64551286485448,\n", + " 'Synthesis_temperature': 136.35448713516206,\n", + " 'Synthesis_time': 22.991767675435288,\n", + " 'Stirring': 'Static',\n", + " 'Silicon_source': 'Fumed'}" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\n", + "The objective values for the Pareto optimal parameters were:\n" + ] + }, + { + "data": { + "text/plain": [ + "{'DCC': 28.414187472214962, 'Synthesis_yield': 28.29822867583607}" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "optimize_experiment_ax(list_obj_names = ['DCC', 'Synthesis_yield'],\n", + " objective_function = dummy_function_4_continuous_2_categorical_2_objective,\n", + " continuous_parameters_list = [{\"name\": \"OSDA_molar_ratio\", \"type\": \"range\", \"bounds\": [0.1, 1.0]},\n", + " {\"name\": \"H2O_molar_ratio\", \"type\": \"range\", \"bounds\": [5.0, 100.0]},\n", + " {\"name\": \"Synthesis_temperature\", \"type\": \"range\", \"bounds\": [100.0, 200.0]},\n", + " {\"name\": \"Synthesis_time\", \"type\": \"range\", \"bounds\": [2.0, 432.0]},\n", + " ],\n", + " categorical_parameters_list = [{\"name\": \"Stirring\", \"type\": \"choice\", \"is_ordered\": False,\"values\": [\"Stirring\", \"Tumbling\", \"Static\"]},\n", + " {\"name\": \"Silicon_source\", \"type\": \"choice\", \"is_ordered\": False,\"values\": [\"Colloidal\", \"Fumed\"]},\n", + " ],\n", + " parameter_constraints_lists = [\"H2O_molar_ratio + Synthesis_temperature <= 145.0\"],\n", + " iterations=30)" + ] + }, + { + "cell_type": "markdown", + "id": "c396a207-acc7-4771-86aa-3c3510582185", + "metadata": {}, + "source": [ + "### 1.7 Code Example without Using the General `optimize_experiment_ax` Function of Part 1.2 " + ] + }, + { + "cell_type": "markdown", + "id": "71011e62-d53e-4568-8323-c8bcc2a1a0e4", + "metadata": {}, + "source": [ + "The fifth demonstration shows an experiment with **4 continuous variables** with typical ranges that are based on [Table S4](https://pubs.acs.org/doi/suppl/10.1021/acs.chemmater.9b03738/suppl_file/cm9b03738_si_001.pdf#page=10) in the Supporting Information of [reference 1](https://pubs.acs.org/doi/abs/10.1021/acs.chemmater.9b03738), but this time **without using the general function** [`optimize_experiment_ax`](#Part1.2). This is how one would probably use Ax when there is only one type of experiment. \n", + "\n", + "* the molar ratio of organic structure directing agents (OSDA) compared to Si \n", + "* the molar ratio of water compared to Si\n", + "* synthesis temperature (°C)\n", + "* synthesis time (h)\n", + "\n", + "The **single target variable** is synthesis yield. We assume that minimizing the above defined [dummy function](#Part1.1) `dummy_function_4_continuous_1_objective` corresponds to maximizing the yield of the zeolite synthesis. We also plot the **optimization trace** of the BO algorithm throughout the different experiment iterations." + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "id": "d6d2d76c-e6ca-481c-bdc9-5c59221f7436", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "The details of the last 5 experiment iterations:\n" + ] + }, + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
Generation StepGeneration ModelTrial IndexTrial StatusArm Parameterizations
25GenerationStep_1BoTorch25COMPLETED{'25_0': {'OSDA_molar_ratio': 0.36, 'H2O_molar_ratio': 12.41, 'Synthesis_temperature': 140.96, 'Synthesis_time': 21.67}}
26GenerationStep_1BoTorch26COMPLETED{'26_0': {'OSDA_molar_ratio': 0.37, 'H2O_molar_ratio': 11.45, 'Synthesis_temperature': 141.27, 'Synthesis_time': 2.0}}
27GenerationStep_1BoTorch27COMPLETED{'27_0': {'OSDA_molar_ratio': 0.34, 'H2O_molar_ratio': 12.26, 'Synthesis_temperature': 139.76, 'Synthesis_time': 21.9}}
28GenerationStep_1BoTorch28COMPLETED{'28_0': {'OSDA_molar_ratio': 0.1, 'H2O_molar_ratio': 5.0, 'Synthesis_temperature': 200.0, 'Synthesis_time': 2.0}}
29GenerationStep_1BoTorch29COMPLETED{'29_0': {'OSDA_molar_ratio': 0.13, 'H2O_molar_ratio': 5.0, 'Synthesis_temperature': 132.33, 'Synthesis_time': 234.7}}
\n", + "
" + ], + "text/plain": [ + " Generation Step Generation Model Trial Index Trial Status \\\n", + "25 GenerationStep_1 BoTorch 25 COMPLETED \n", + "26 GenerationStep_1 BoTorch 26 COMPLETED \n", + "27 GenerationStep_1 BoTorch 27 COMPLETED \n", + "28 GenerationStep_1 BoTorch 28 COMPLETED \n", + "29 GenerationStep_1 BoTorch 29 COMPLETED \n", + "\n", + " Arm Parameterizations \n", + "25 {'25_0': {'OSDA_molar_ratio': 0.36, 'H2O_molar_ratio': 12.41, 'Synthesis_temperature': 140.96, 'Synthesis_time': 21.67}} \n", + "26 {'26_0': {'OSDA_molar_ratio': 0.37, 'H2O_molar_ratio': 11.45, 'Synthesis_temperature': 141.27, 'Synthesis_time': 2.0}} \n", + "27 {'27_0': {'OSDA_molar_ratio': 0.34, 'H2O_molar_ratio': 12.26, 'Synthesis_temperature': 139.76, 'Synthesis_time': 21.9}} \n", + "28 {'28_0': {'OSDA_molar_ratio': 0.1, 'H2O_molar_ratio': 5.0, 'Synthesis_temperature': 200.0, 'Synthesis_time': 2.0}} \n", + "29 {'29_0': {'OSDA_molar_ratio': 0.13, 'H2O_molar_ratio': 5.0, 'Synthesis_temperature': 132.33, 'Synthesis_time': 234.7}} " + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "image/png": "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ILSAwBt6ehVHgAABAgQIECAg8OoBAgQIECBAgACB0AICb+jpVRwBAgQIECBAgIDAqwcIECBAgAABAgRCCwi8oadXcQQIECBAgAABAgKvHiBAgAABAgQIEAgtIPCGnl7FESBAgAABAgQICLx6gAABAgQIECBAILSAwBt6ehVHgAABAgQIECAg8OoBAgQIECBAgACB0AICb+jpVRwBAgQIECBAgIDAqwcIECBAgAABAgRCCwi8oadXcQQIECBAgAABAgKvHiBAgAABAgQIEAgtIPCGnl7FESBAgAABAgQICLx6gAABAgQIECBAILSAwBt6ehVHgAABAgQIECAg8OoBAgQIECBAgACB0AICb+jpVRwBAgQIECBAgIDAqwcIECBAgAABAgRCCwi8oadXcQQIECBAgAABAgKvHiBAgAABAgQIEAgtIPCGnl7FESBAgAABAgQICLx6gAABAgQIECBAILSAwBt6ehVHgAABAgQIECAg8OoBAgQIECBAgACB0AICb+jpVRwBAgQIECBAgIDAqwcIECBAgAABAgRCCwi8oadXcQQIECBAgAABAgKvHiBAgAABAgQIEAgtIPCGnl7FESBAgAABAgQICLx6gAABAgQIECBAILSAwBt6ehVHgAABAgQIECAg8OoBAgQIECBAgACB0AJ/Aa0p6ca8TbJuAAAAAElFTkSuQmCC" + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "The best parameters were:\n" + ] + }, + { + "data": { + "text/plain": [ + "{'OSDA_molar_ratio': 0.34406537118593616,\n", + " 'H2O_molar_ratio': 12.255890900708632,\n", + " 'Synthesis_temperature': 139.7614030816516,\n", + " 'Synthesis_time': 21.90491099424178}" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\n", + "The objective value for the best parameters was:\n" + ] + }, + { + "data": { + "text/plain": [ + "{'Synthesis_yield': 0.4317714423295911}" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "obj_name1 = \"Synthesis_yield\"\n", + " \n", + "ax_client = AxClient(verbose_logging=False)\n", + "ax_client.create_experiment(parameters=[{\"name\": \"OSDA_molar_ratio\", \"type\": \"range\", \"bounds\": [0.1, 1.0]},\n", + " {\"name\": \"H2O_molar_ratio\", \"type\": \"range\", \"bounds\": [5.0, 100.0]},\n", + " {\"name\": \"Synthesis_temperature\", \"type\": \"range\", \"bounds\": [100.0, 200.0]},\n", + " {\"name\": \"Synthesis_time\", \"type\": \"range\", \"bounds\": [2.0, 432.0]},\n", + " ],\n", + " objectives={obj_name1: ObjectiveProperties(minimize=True),},\n", + " )\n", + "\n", + "for _ in range(30):\n", + " parameterization, trial_index = ax_client.get_next_trial()\n", + "\n", + " # Extract parameters\n", + " x1 = parameterization[\"OSDA_molar_ratio\"]\n", + " x2 = parameterization[\"H2O_molar_ratio\"]\n", + " x3 = parameterization[\"Synthesis_temperature\"]\n", + " x4 = parameterization[\"Synthesis_time\"]\n", + " \n", + " results = dummy_function_4_continuous_1_objective(x1, x2, x3, x4, obj_name1=obj_name1)\n", + " ax_client.complete_trial(trial_index=trial_index, raw_data=results)\n", + "\n", + "best_parameters, value = ax_client.get_best_parameters()\n", + "\n", + "# Create dataframe with details of all experiment iterations\n", + "df_experiments = ax_client.generation_strategy.trials_as_df\n", + "print('The details of the last 5 experiment iterations:')\n", + "display(df_experiments.tail())\n", + "\n", + "# Get the AxClient's optimization trace using the built-in plotting method\n", + "optimization_trace = ax_client.get_optimization_trace()\n", + "\n", + "# Convert the optimization trace to a Plotly figure and plot it\n", + "fig = to_plotly(optimization_trace)\n", + "fig.show(renderer='png')\n", + "\n", + "# Output the best parameters and their corresponding objective values\n", + "print('The best parameters were:')\n", + "display(best_parameters)\n", + "print('\\nThe objective value for the best parameters was:')\n", + "display(value[0])" + ] + }, + { + "cell_type": "markdown", + "id": "e3e390de-f733-4f34-bfdb-2d094e7cbcfb", + "metadata": {}, + "source": [ + "## Part 2: Propose a New Experiment Based on a Batch of Prior (Literature) Experiments " + ] + }, + { + "cell_type": "markdown", + "id": "883f3525-fa4a-47b4-b4bb-ce858debf643", + "metadata": {}, + "source": [ + "Here Bayesian optimization is applied within a **real-world context** where the underlying target function remains unknown. We start from a batch of experiments collected from literature, and we allow the Bayesian optimizer to **suggest the next experiment**. This process aims to explore the search space further and/or leverage past results. In practice, one would carry out the suggested experiment, incorporate its outcome into the existing results, and subsequently rerun the Bayesian optimization algorithm." + ] + }, + { + "cell_type": "markdown", + "id": "d33d5ee3-637c-4cd7-9af5-36ecb0e7e607", + "metadata": {}, + "source": [ + "### 2.1 Define a Function that Proposes the Next Best Experiment with Ax Package " + ] + }, + { + "cell_type": "markdown", + "id": "d36c6596-8c8a-4476-98d7-cf0d0cc4004c", + "metadata": {}, + "source": [ + "Here we will create a **general function** `propose_experiment_ax` to propose a next best experiment with the Ax package. This general function allows focus on the output of the experiments further on, without repeatedly modifying similar sections of code. \n", + "\n", + "The `propose_experiment_ax` function takes a list of **continuous parameters**, a list of **categorical parameters**, a list of parameter **constraints**, a dataframe with **previous continuous experimental inputs**, a dataframe with **previous categorical experimental inputs** and a dictionary with **previous experimental outcomes (single or multi-objective)**. \n", + "\n", + "This function is inspired by the the following GitHub issue: https://github.com/Facebook/ax/issues/743." + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "id": "1c2547c1-beb5-4e29-baa6-c41eff675ece", + "metadata": {}, + "outputs": [], + "source": [ + "def propose_experiment_ax(continuous_parameters_list, categorical_parameters_list, parameter_constraints_lists,\n", + " df_prior_experiments_input_continuous, df_prior_experiments_input_categorical, \n", + " dictionary_prior_experiments_output):\n", + " \"\"\"\n", + " Proposes the next best experiment with the Ax package. \n", + " Inputs: \n", + " continuous_parameters_list: list of continuous parameters for the objective function\n", + " categorical_parameters_list: list of categorical parameters for the objective function\n", + " parameter_constraints_lists: list of constraints in the parameter space\n", + " df_prior_experiments_input_continuous: dataframe with the previous experimental inputs of the continuous parameters\n", + " df_prior_experiments_input_categorical: dataframe with the previous experimental inputs of the categorical parameters\n", + " dictionary_prior_experiments_output: a dictionary {objective_name : objective_result} in which objective_name is a string naming the objective,\n", + " and objective_result is a pandas Series with the previous experimental outcomes \n", + " Outputs:\n", + " A suggestion for the best subsequent experiment to try out.\n", + " For single-objective experiments, the best previous parameters and their objective function output are printed.\n", + "\n", + " Source:\n", + " This function is inspired by the following GitHub issue: https://github.com/Facebook/ax/issues/743 \n", + " \"\"\"\n", + " all_parameters_list = continuous_parameters_list + categorical_parameters_list\n", + " \n", + " df_prior_experiments_input_all = pd.concat([df_prior_experiments_input_continuous, df_prior_experiments_input_categorical], axis=1)\n", + " df_prior_experiments_output_all = pd.concat(dictionary_prior_experiments_output.values(), axis=1)\n", + " df_prior_experiments_input_output_all = pd.concat([df_prior_experiments_input_all, df_prior_experiments_output_all], axis=1)\n", + " print('The considered dataset with previous experimental inputs and outputs:')\n", + " display(df_prior_experiments_input_output_all)\n", + "\n", + " objectives_dictionary={}\n", + " for obj_name in dictionary_prior_experiments_output.keys():\n", + " objectives_dictionary[obj_name]=ObjectiveProperties(minimize=False)\n", + " \n", + " # Skip the pseudo-random suggested points by specifying a custom generation strategy\n", + " # See: https://ax.dev/tutorials/generation_strategy.html and\n", + " # https://ax.dev/docs/bayesopt.html#tradeoff-between-parallelism-and-total-number-of-trials\n", + " gs = GenerationStrategy(steps=[GenerationStep(model=Models.GPEI, num_trials=-1, max_parallelism=3)])\n", + "\n", + " # Setup the experiment\n", + " ax_client = AxClient(generation_strategy=gs, verbose_logging=False)\n", + " ax_client.create_experiment(parameters=all_parameters_list, \n", + " parameter_constraints=parameter_constraints_lists, \n", + " objectives=objectives_dictionary)\n", + "\n", + " # Attach the training data\n", + " for i in range(df_prior_experiments_input_all.shape[0]):\n", + " ax_client.attach_trial(df_prior_experiments_input_all.iloc[i, :].to_dict())\n", + "\n", + " result_dictionary={}\n", + " for obj_name in dictionary_prior_experiments_output.keys():\n", + " # We assume standard error is 0.0\n", + " result_dictionary[obj_name] = (dictionary_prior_experiments_output[obj_name][i], 0.0)\n", + " ax_client.complete_trial(trial_index=i, raw_data=result_dictionary)\n", + " \n", + " # Produce a single next suggested experiment\n", + " next_experiment, trial_index = ax_client.get_next_trial()\n", + " print(\"\\nNext suggested experiment:\" )\n", + " display(next_experiment)\n", + "\n", + " if len(dictionary_prior_experiments_output.keys()) == 1 :\n", + " # Single-objective experiment\n", + " # Output the best parameters and their corresponding objective values\n", + " best_parameters, value = ax_client.get_best_parameters()\n", + " print('\\nThe best parameters were:')\n", + " display(best_parameters)\n", + " print('\\nThe objective value for the best parameters was:')\n", + " display(value[0])\n", + " \n", + " return None" + ] + }, + { + "cell_type": "markdown", + "id": "dab418c7-4257-45e1-a4f9-a2aabb9ef5e3", + "metadata": {}, + "source": [ + "### 2.2 Collect the Literature Input and Output Data " + ] + }, + { + "cell_type": "markdown", + "id": "c0292d5b-6fe0-4e08-a9aa-d138f0661555", + "metadata": {}, + "source": [ + "The objective of the considered study was to find a one-pot synthesis of Fe-CHA zeolites that maximizes the methanol yield when the synthesized material is activated and reacted with methane. 'One-pot' means that Fe is already added in the zeolite synthesis, instead of being post-synthetically ion-exchanged on the zeolite.\n", + "\n", + "The input literature data are based on [Table S3](https://pubs.acs.org/doi/suppl/10.1021/jacs.1c07590/suppl_file/ja1c07590_si_001.pdf#page=9) in the Supporting Information of [reference 2](https://pubs.acs.org/doi/10.1021/jacs.1c07590). A reasonable value was chosen for missing data.\n", + "\n", + "The **continuous** variables are:\n", + "* the molar ratio of organic structure directing agents, OSDA, compared to Si \n", + "* the molar ratio of water compared to Si\n", + "* the molar ratio of Fe compared to Si\n", + "\n", + "The **categorical** variables are:\n", + "* Fe source: iron(III) acetate (A) or iron(III) nitrate (N).\n", + "* Stirring mode: stirring (600 rpm) or static.\n", + "\n", + "The **2 target variables** are based on respectively [Table S3](https://pubs.acs.org/doi/suppl/10.1021/jacs.1c07590/suppl_file/ja1c07590_si_001.pdf#page=9) and [Table S4](https://pubs.acs.org/doi/suppl/10.1021/jacs.1c07590/suppl_file/ja1c07590_si_001.pdf#page=10) in the Supporting Information of [reference 2](https://pubs.acs.org/doi/10.1021/jacs.1c07590):\n", + "* Zeolite synthesis yield \n", + "* Methanol yield (MeOH/Al)\n" + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "id": "46777f23-c717-46c2-830b-e929618976c3", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\n", + "Literature input data with the experimental Synthesis_yield and Methanol_yield outcome. This will be fed as prior information to BO:\n" + ] + }, + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
OSDA_molar_ratioH2O_molar_ratioFe_molar_ratioFe_SaltStirringSynthesis_yieldMethanol_yield
00.3615.40.019AStatic0.830.089
10.3314.10.016AStatic0.850.085
20.3615.50.018NStatic0.960.084
30.3615.50.017NStatic0.840.108
40.3615.50.035AStatic0.930.116
50.3615.50.037NStirring1.000.063
60.3414.40.039NStirring0.910.108
70.3615.50.055AStatic0.000.053
80.3615.60.055NStatic0.000.013
90.3615.50.090AStatic0.000.020
100.3314.10.016AStirring0.910.062
\n", + "
" + ], + "text/plain": [ + " OSDA_molar_ratio H2O_molar_ratio Fe_molar_ratio Fe_Salt Stirring \\\n", + "0 0.36 15.4 0.019 A Static \n", + "1 0.33 14.1 0.016 A Static \n", + "2 0.36 15.5 0.018 N Static \n", + "3 0.36 15.5 0.017 N Static \n", + "4 0.36 15.5 0.035 A Static \n", + "5 0.36 15.5 0.037 N Stirring \n", + "6 0.34 14.4 0.039 N Stirring \n", + "7 0.36 15.5 0.055 A Static \n", + "8 0.36 15.6 0.055 N Static \n", + "9 0.36 15.5 0.090 A Static \n", + "10 0.33 14.1 0.016 A Stirring \n", + "\n", + " Synthesis_yield Methanol_yield \n", + "0 0.83 0.089 \n", + "1 0.85 0.085 \n", + "2 0.96 0.084 \n", + "3 0.84 0.108 \n", + "4 0.93 0.116 \n", + "5 1.00 0.063 \n", + "6 0.91 0.108 \n", + "7 0.00 0.053 \n", + "8 0.00 0.013 \n", + "9 0.00 0.020 \n", + "10 0.91 0.062 " + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/plain": [ + "OSDA_molar_ratio float64\n", + "H2O_molar_ratio float64\n", + "Fe_molar_ratio float64\n", + "Fe_Salt object\n", + "Stirring object\n", + "Synthesis_yield float64\n", + "Methanol_yield float64\n", + "dtype: object" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "parameter_names = [\"OSDA_molar_ratio\", \"H2O_molar_ratio\", \"Fe_molar_ratio\", \"Fe_Salt\", \"Stirring\"]\n", + "\n", + "X_train = np.array([[0.36, 15.4, 0.019, 'A', 'Static'],\n", + " [0.33, 14.1, 0.016, 'A', 'Static'],\n", + " [0.36, 15.5, 0.018, 'N', 'Static'],\n", + " [0.36, 15.5, 0.017, 'N', 'Static'],\n", + " [0.36, 15.5, 0.035, 'A', 'Static'],\n", + " [0.36, 15.5, 0.037, 'N', 'Stirring'],\n", + " [0.34, 14.4, 0.039, 'N', 'Stirring'],\n", + " [0.36, 15.5, 0.055, 'A', 'Static'],\n", + " [0.36, 15.6, 0.055, 'N', 'Static'],\n", + " [0.36, 15.5, 0.090, 'A', 'Static'],\n", + " [0.33, 14.1, 0.016, 'A', 'Stirring'],\n", + " ])\n", + "\n", + "X_train = pd.DataFrame(X_train, columns=parameter_names)\n", + "dict_types = {'OSDA_molar_ratio': 'float64', 'H2O_molar_ratio': 'float64', 'Fe_molar_ratio': 'float64', \n", + " 'Fe_Salt': 'object', 'Stirring': 'object' }\n", + "X_train = X_train.astype(dict_types)\n", + "\n", + "y_train_yield = np.array([0.83, 0.85, 0.96, 0.84, 0.93, 1.0, 0.91, 0.0, 0.0, 0.0, 0.91])\n", + "y_train_methanol = np.array([0.089, 0.085, 0.084, 0.108, 0.116, 0.063, 0.108, 0.053, 0.013, 0.020, 0.062])\n", + "\n", + "print('\\nLiterature input data with the experimental Synthesis_yield and Methanol_yield outcome. This will be fed as prior information to BO:')\n", + "X_total = X_train.copy()\n", + "X_total['Synthesis_yield'] = y_train_yield.tolist()\n", + "X_total['Methanol_yield'] = y_train_methanol.tolist()\n", + "display(X_total)\n", + "display(X_total.dtypes)\n" + ] + }, + { + "cell_type": "markdown", + "id": "e69374e8-c314-4614-974b-eb9726fa4978", + "metadata": {}, + "source": [ + "### 2.3 Single-Objective (Synthesis Yield) with 3 Continuous Variables and 2 Categorical Variables " + ] + }, + { + "cell_type": "markdown", + "id": "105100e5-d6ba-4081-9538-16a84692ada9", + "metadata": {}, + "source": [ + "In this scenario, we are interested in **maximizing the zeolite synthesis yield** (single-objective). We run [`propose_experiment_ax`](#Part2.1) to generate the next best experiment to try out in the labaratory. " + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "id": "0a3c541d-7756-48bd-9bdf-90d9cec023ff", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "The considered dataset with previous experimental inputs and outputs:\n" + ] + }, + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
OSDA_molar_ratioH2O_molar_ratioFe_molar_ratioFe_SaltStirringSynthesis_yield
00.3615.40.019AStatic0.83
10.3314.10.016AStatic0.85
20.3615.50.018NStatic0.96
30.3615.50.017NStatic0.84
40.3615.50.035AStatic0.93
50.3615.50.037NStirring1.00
60.3414.40.039NStirring0.91
70.3615.50.055AStatic0.00
80.3615.60.055NStatic0.00
90.3615.50.090AStatic0.00
100.3314.10.016AStirring0.91
\n", + "
" + ], + "text/plain": [ + " OSDA_molar_ratio H2O_molar_ratio Fe_molar_ratio Fe_Salt Stirring \\\n", + "0 0.36 15.4 0.019 A Static \n", + "1 0.33 14.1 0.016 A Static \n", + "2 0.36 15.5 0.018 N Static \n", + "3 0.36 15.5 0.017 N Static \n", + "4 0.36 15.5 0.035 A Static \n", + "5 0.36 15.5 0.037 N Stirring \n", + "6 0.34 14.4 0.039 N Stirring \n", + "7 0.36 15.5 0.055 A Static \n", + "8 0.36 15.6 0.055 N Static \n", + "9 0.36 15.5 0.090 A Static \n", + "10 0.33 14.1 0.016 A Stirring \n", + "\n", + " Synthesis_yield \n", + "0 0.83 \n", + "1 0.85 \n", + "2 0.96 \n", + "3 0.84 \n", + "4 0.93 \n", + "5 1.00 \n", + "6 0.91 \n", + "7 0.00 \n", + "8 0.00 \n", + "9 0.00 \n", + "10 0.91 " + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\n", + "Next suggested experiment:\n" + ] + }, + { + "data": { + "text/plain": [ + "{'OSDA_molar_ratio': 0.3597986726498765,\n", + " 'H2O_molar_ratio': 15.488919708603255,\n", + " 'Fe_molar_ratio': 0.023291856734901355,\n", + " 'Fe_Salt': 'N',\n", + " 'Stirring': 'Static'}" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\n", + "The best parameters were:\n" + ] + }, + { + "data": { + "text/plain": [ + "{'OSDA_molar_ratio': 0.36,\n", + " 'H2O_molar_ratio': 15.5,\n", + " 'Fe_molar_ratio': 0.037,\n", + " 'Fe_Salt': 'N',\n", + " 'Stirring': 'Stirring'}" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\n", + "The objective value for the best parameters was:\n" + ] + }, + { + "data": { + "text/plain": [ + "{'Synthesis_yield': 1.0}" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "propose_experiment_ax(continuous_parameters_list = [{\"name\": \"OSDA_molar_ratio\", \"type\": \"range\", \"bounds\": [0.30, 0.40]},\n", + " {\"name\": \"H2O_molar_ratio\", \"type\": \"range\", \"bounds\": [14.0, 16.0]},\n", + " {\"name\": \"Fe_molar_ratio\", \"type\": \"range\", \"bounds\": [0.01, 0.10]},\n", + " ],\n", + " categorical_parameters_list = [{\"name\": \"Fe_Salt\", \"type\": \"choice\", \"is_ordered\": False,\"values\": [\"A\", \"N\"]},\n", + " {\"name\": \"Stirring\", \"type\": \"choice\", \"is_ordered\": False, \"values\": [\"Stirring\", \"Static\"]},\n", + " ], \n", + " parameter_constraints_lists = [],\n", + " df_prior_experiments_input_continuous = X_total[[\"OSDA_molar_ratio\", \"H2O_molar_ratio\", \"Fe_molar_ratio\"]],\n", + " df_prior_experiments_input_categorical = X_total[[\"Fe_Salt\", \"Stirring\"]],\n", + " dictionary_prior_experiments_output = {\"Synthesis_yield\" : X_total.loc[:,\"Synthesis_yield\"]} \n", + " )\n", + " " + ] + }, + { + "cell_type": "markdown", + "id": "374d8a9e-df50-4855-b771-ab061603b4cc", + "metadata": {}, + "source": [ + "### 2.4 Multi-Objective (Synthesis Yield + Methanol Production) with 3 Continuous Variables and 2 Categorical Variables " + ] + }, + { + "cell_type": "markdown", + "id": "40fcd82c-648b-4204-8b49-29fdff9960fa", + "metadata": {}, + "source": [ + "In this scenario, we are interested in **maximizing both the zeolite synthesis yield and the methanol production** (multi-objective). We run [`propose_experiment_ax`](#Part2.1) to generate the next best experiment to try out in the labaratory. " + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "id": "23cf5c47-f360-4fc1-9a2a-4586732e2f2b", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "The considered dataset with previous experimental inputs and outputs:\n" + ] + }, + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
OSDA_molar_ratioH2O_molar_ratioFe_molar_ratioFe_SaltStirringSynthesis_yieldMethanol_yield
00.3615.40.019AStatic0.830.089
10.3314.10.016AStatic0.850.085
20.3615.50.018NStatic0.960.084
30.3615.50.017NStatic0.840.108
40.3615.50.035AStatic0.930.116
50.3615.50.037NStirring1.000.063
60.3414.40.039NStirring0.910.108
70.3615.50.055AStatic0.000.053
80.3615.60.055NStatic0.000.013
90.3615.50.090AStatic0.000.020
100.3314.10.016AStirring0.910.062
\n", + "
" + ], + "text/plain": [ + " OSDA_molar_ratio H2O_molar_ratio Fe_molar_ratio Fe_Salt Stirring \\\n", + "0 0.36 15.4 0.019 A Static \n", + "1 0.33 14.1 0.016 A Static \n", + "2 0.36 15.5 0.018 N Static \n", + "3 0.36 15.5 0.017 N Static \n", + "4 0.36 15.5 0.035 A Static \n", + "5 0.36 15.5 0.037 N Stirring \n", + "6 0.34 14.4 0.039 N Stirring \n", + "7 0.36 15.5 0.055 A Static \n", + "8 0.36 15.6 0.055 N Static \n", + "9 0.36 15.5 0.090 A Static \n", + "10 0.33 14.1 0.016 A Stirring \n", + "\n", + " Synthesis_yield Methanol_yield \n", + "0 0.83 0.089 \n", + "1 0.85 0.085 \n", + "2 0.96 0.084 \n", + "3 0.84 0.108 \n", + "4 0.93 0.116 \n", + "5 1.00 0.063 \n", + "6 0.91 0.108 \n", + "7 0.00 0.053 \n", + "8 0.00 0.013 \n", + "9 0.00 0.020 \n", + "10 0.91 0.062 " + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\n", + "Next suggested experiment:\n" + ] + }, + { + "data": { + "text/plain": [ + "{'OSDA_molar_ratio': 0.3526783041131582,\n", + " 'H2O_molar_ratio': 15.331573897619075,\n", + " 'Fe_molar_ratio': 0.033573534446308506,\n", + " 'Fe_Salt': 'A',\n", + " 'Stirring': 'Static'}" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "propose_experiment_ax(continuous_parameters_list = [{\"name\": \"OSDA_molar_ratio\", \"type\": \"range\", \"bounds\": [0.30, 0.40]},\n", + " {\"name\": \"H2O_molar_ratio\", \"type\": \"range\", \"bounds\": [14.0, 16.0]},\n", + " {\"name\": \"Fe_molar_ratio\", \"type\": \"range\", \"bounds\": [0.01, 0.10]},\n", + " ],\n", + " categorical_parameters_list = [{\"name\": \"Fe_Salt\", \"type\": \"choice\", \"is_ordered\": False,\"values\": [\"A\", \"N\"]},\n", + " {\"name\": \"Stirring\", \"type\": \"choice\", \"is_ordered\": False, \"values\": [\"Stirring\", \"Static\"]},\n", + " ], \n", + " parameter_constraints_lists = [],\n", + " df_prior_experiments_input_continuous = X_total[[\"OSDA_molar_ratio\", \"H2O_molar_ratio\", \"Fe_molar_ratio\"]],\n", + " df_prior_experiments_input_categorical = X_total[[\"Fe_Salt\", \"Stirring\"]],\n", + " dictionary_prior_experiments_output = {\"Synthesis_yield\" : X_total.loc[:,\"Synthesis_yield\"], \n", + " \"Methanol_yield\" : X_total.loc[:,\"Methanol_yield\"],\n", + " } \n", + " ) " + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python [conda env:jupyter_vscode]", + "language": "python", + "name": "conda-env-jupyter_vscode-py" + }, + "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.12.1" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/hello.py b/hello.py deleted file mode 100644 index a025bbf..0000000 --- a/hello.py +++ /dev/null @@ -1,2 +0,0 @@ -def hello_world(): - return "Hello!" diff --git a/hello_test.py b/hello_test.py deleted file mode 100644 index 708a061..0000000 --- a/hello_test.py +++ /dev/null @@ -1,5 +0,0 @@ -import hello - - -def test_hello(): - assert hello.hello_world() == "Hello World!" diff --git a/images/BO_Zeolite.png b/images/BO_Zeolite.png new file mode 100644 index 0000000..511ede8 Binary files /dev/null and b/images/BO_Zeolite.png differ diff --git a/images/Poster_BO_Zeolite.png b/images/Poster_BO_Zeolite.png new file mode 100644 index 0000000..518a580 Binary files /dev/null and b/images/Poster_BO_Zeolite.png differ diff --git a/images/Zeolite_rings.png b/images/Zeolite_rings.png new file mode 100644 index 0000000..4b479d7 Binary files /dev/null and b/images/Zeolite_rings.png differ diff --git a/images/Zeolite_synthesis_equipment.png b/images/Zeolite_synthesis_equipment.png new file mode 100644 index 0000000..1226c6c Binary files /dev/null and b/images/Zeolite_synthesis_equipment.png differ diff --git a/images/Zeolite_topologies.png b/images/Zeolite_topologies.png new file mode 100644 index 0000000..d8143a5 Binary files /dev/null and b/images/Zeolite_topologies.png differ diff --git a/pyproject.toml b/pyproject.toml deleted file mode 100644 index c953815..0000000 --- a/pyproject.toml +++ /dev/null @@ -1,2 +0,0 @@ -[build-system] -requires = ["setuptools", "wheel"] \ No newline at end of file diff --git a/requirements.txt b/requirements.txt index 097a011..6843d9e 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,9 +1,11 @@ --e . # package from this repo -# numpy -# scipy -# pandas -# matplotlib -# matplotlib-inline -# ipython -# ipykernel -pytest +numpy +pandas +plotly +ax +ipython +collections + +# optional +logging +sys +warnings diff --git a/setup.cfg b/setup.cfg deleted file mode 100644 index cc93fb7..0000000 --- a/setup.cfg +++ /dev/null @@ -1,11 +0,0 @@ -[metadata] -name = helper_functions -version = 0.1 - -[options] -packages = find: -package_dir = - =src - -[options.packages.find] -where=src \ No newline at end of file diff --git a/src/mock1/__init__.py b/src/mock1/__init__.py deleted file mode 100644 index d98cc4b..0000000 --- a/src/mock1/__init__.py +++ /dev/null @@ -1,2 +0,0 @@ -"""Mock function that gets installed by requirements.txt""" -from mock1._mock1 import Mock1 diff --git a/src/mock1/_mock1.py b/src/mock1/_mock1.py deleted file mode 100644 index 43f32d9..0000000 --- a/src/mock1/_mock1.py +++ /dev/null @@ -1,6 +0,0 @@ -class Mock1: - def __init__(self): - pass - - def mock(self): - print("this is a mock function") diff --git a/src/mock2/__init__.py b/src/mock2/__init__.py deleted file mode 100644 index 9d29bf3..0000000 --- a/src/mock2/__init__.py +++ /dev/null @@ -1,2 +0,0 @@ -"""Mock function that gets installed by requirements.txt""" -from mock2._mock2 import Mock2 diff --git a/src/mock2/_mock2.py b/src/mock2/_mock2.py deleted file mode 100644 index f5b7725..0000000 --- a/src/mock2/_mock2.py +++ /dev/null @@ -1,6 +0,0 @@ -class Mock2: - def __init__(self): - pass - - def mock(self): - print("this is a mock function") diff --git a/zeolite_synthesis_bo_introduction.md b/zeolite_synthesis_bo_introduction.md new file mode 100644 index 0000000..7f24929 --- /dev/null +++ b/zeolite_synthesis_bo_introduction.md @@ -0,0 +1,238 @@ +# Navigating black box zeolite synthesis with Bayesian optimization + +Author: Plessers Dieter +March 28th, 2024 +*Department of Microbial and Molecular Systems, Center for Sustainable Catalysis and Engineering, KU Leuven-University of Leuven, Belgium.* + +## 0. Scope + +This introductory document proposes the integration of active learning techniques such as Bayesian optimization into zeolite synthesis research. We first provide an overview of zeolites and their importance. Subsequently, we explore the standard components of zeolite synthesis experiments, including reagents, equipment, and conditions. We then delve into the potential of Bayesian optimization to accelerate zeolite synthesis experiments and reduce associated costs. The optimization problem addressed by Bayesian optimization is outlined, detailing a typical parameter space with tunable variables and constraints, along with possible objectives derived from a target application. Additionally, we briefly highlight a few zeolite synthesis datasets that recently have been compiled from literature. The final discussion section further elaborates on various facets of the Bayesian optimization process and introduces a specific coding example with real-world data, provided in the [accompanying Jupyter notebook](./demo_zeolite_synthesis_bo.ipynb). + +While numerous references are provided for further exploration, this document is self-contained and aims to be easily understood. We hope it inspires the reader to consider active learning approaches in their zeolite synthesis endeavors. + +## 1. Zeolites – Microporous Materials + +Zeolites are porous, crystalline aluminosilicates built from linked \[SiO4\] and \[AlO4\]\- tetrahedra in which every oxygen atom is shared between neighboring Si or Al atoms (i.e. T-atoms).[^1][^2][^3] These T-atoms are arranged together in secondary building units (SBUs), that are connected and form a three-dimensional framework with channels and cages. The existence of multiple SBUs and numerous ways to connect them results in a theoretical enormous amount of zeolite topologies.[^4] Topologies observed experimentally or in nature are represented by a three letter code (e.g. FAU for faujasite, CHA for chabazite). All known zeolite topologies can be found on the website of the International Zeolite Association (IZA).[^5] As of March 2024, the website lists 256 distinct topologies, excluding intergrowths. The unique ring, pore and channel system of each zeolite topology determines their potential applications. Figure 1 illustrates the topologies of the most relevant commercial zeolites. + +Relevant zeolite topologies + +**Figure 1.** Framework structures of the most relevant zeolite topologies. Vertices represent T atoms (usually Si and Al) connected via O-atoms (not depicted)*.* Dashed lines demarcate the unit cells. All figures are sourced from the online IZA Database.[^5] + +Pore and channel dimensions, typically in the Ă„ngström scale, are characterized by the number *n* of T-atoms in the limiting ring, denoted as *n*MR (*n*\-membered ring) (Figure 2). Ring sizes vary from 3MR to 24MR, and exhibit various shapes, from circular to elliptical.[^5][^6] This ring and channel system enables zeolites to function as molecular sieves, allowing only molecules smaller than the free diameter to traverse the rings, while larger molecules are excluded.[^1][^2] Zeolites are also known for their shape selectivity, wherein the distribution of products in a reaction system is influenced by how reactants, intermediates, and products fit within the cage and channel structure of the zeolites.[^7] Moreover, atoms beyond the first coordination sphere can impact the reactivity of active sites by guiding substrate approach and/or by (de)stabilizing the active site or transition state. These effects, known as 'second-sphere effects' play a crucial role in zeolite reactivity.[^8][^9] + +Different ring sizes + +**Figure 2.** Different ring sizes in zeolites.[^9][^10] + +A specific zeolite topology is defined by the spatial arrangement of its lattice, regardless of the material’s chemical composition. Consequently, even within materials sharing the same zeolite topology, numerous parameters remain adjustable. Among these, the most prevalent is the amount and distribution of aluminum (Al) atoms within the zeolite lattice. Each \[AlO4\]\- tetrahedron introduces one negative lattice charge, which is counterbalanced by exchangeable, extra-framework cations. These cations can be protons, imparting zeolites with their distinctive acidic properties,[^11] or metal cations such as transition metal ions (TMIs), employed in redox reactions.[^9] They can also be combined in bifunctional catalysis.[^12] + +Thanks to their thermal stability, low cost (particularly for commercially available bulk zeolites like depicted in Figure 1) and unique chemical properties (acidity, cation exchange capacity, molecular sieving capabilities
), zeolites find extensive use across various industrial sectors. They are integral components in detergents (such as washing powders), they serve as adsorbents and desiccants,[^13] and they play critical roles in petrochemistry, notably as acid catalysts in the fluid catalytic cracking (FCC) process.[^14] Beyond these applications, zeolites demonstrate remarkable versatility, being employed in sensors,[^15] membranes[^16] and redox processes, such as the removal of nitrogen-containing exhaust gases where Cu-exchanged CHA zeolites are at the forefront of selective catalytic reduction (SCR) of NOx gasses in vehicles.[^17][^18] The vast applicability of zeolites is evidenced by their annual production of approximately 6 million tons, with a global market valued at around 13 billion USD.[^19] + +In addition to their extensive industrial applications, the favorable properties of zeolites guarantee their continued significance in fundamental research, both within academia and industry. Zeolites persist as one of the most extensively studied materials for various innovative applications, with numerous laboratories worldwide striving to optimize the synthesis process, customize the resulting properties of zeolites, and discover new topologies.[^20] + +## 2. Zeolite Synthesis – Ingredients and Equipment + +The zeolite synthesis research field boasts a rich history,[^21] reflected in the abundance of literature detailing various synthesis recipes for each zeolite topology (and different materials within the topology). These recipes span from straightforward methods with minimal ingredients, resilient to experimental errors, to intricate procedures demanding specialized equipment.[^22] Typically, hydrothermal synthesis methods are employed, involving the mixing of precursors in water followed by crystallization under elevated temperature and pressure conditions. + +A ‘typical’ hydrothermal zeolite synthesis procedure involves combining a silicon source, an aluminum source, an organic structure-directing agent (OSDA) serving as a template to fill the zeolite pores during synthesis (afterwards removed via calcination), inorganic cations (typically alkali metal hydroxides) and water.[^22] These ingredients are mixed within an inert Teflon liner and placed inside a stainless steel autoclave (Figure 3A), which is quite literally a black box for which it is hard to understand, let alone predict, the internal processes.[^23] The synthesis occurs at autogenous pressure in an oven maintained at a certain temperature (typically 100-200°C) for a specific time (typically 3-7 days, though durations may vary). The synthesis can occur under stirring conditions using a magnetic stirring bar within the Teflon liner, by tumbling the autoclave in the oven, or under static conditions without agitation. An optional step prior to hydrothermal synthesis involves stirring the synthesis solution or gel for a period (often a day) at room temperature, known as ‘aging’. + +However, the term ‘typical zeolite synthesis’ is somewhat contradictory since there are numerous variations on this process. While the aforementioned ingredients are common, they are not strictly required. Additionally, many other components could be introduced, such as fluoride as a mineralizer, alternative metals (e.g. B, Fe, Zn) for lattice incorporation alongside or in place of aluminum, a secondary OSDA, or the application of ultrasound or microwaves during synthesis.[^22] Even applying a voltage with electrodes recently emerged as a novel possibility.[^24] However, most of these unconventional conditions are utilized for niche materials with limited industrial significance, often due to the exotic requirements of such conditions. + +Zeolite synthesis equipment + +**Figure 3.** A) Teflon liners and corresponding autoclaves.[^25] B) Multi-autoclave system for high-throughput parallel experiments with a robotic arm.[^26] + +This brief overview of zeolite synthesis highlights the vast parameter space involved and the ongoing challenge of fully understanding the crystallization mechanisms. Consequently, despite the significant industrial importance of zeolites, their synthesis predominantly relies on heuristic approaches, practitioner experience, and an element of serendipity. An experienced zeolite synthesis practitioner can narrow down this parameter space by drawing from literature and past experiences. For instance, they might opt for a fixed set of ingredients and consider a limited range of temperatures and concentrations. Within this confined search space, typically, either a grid search or a random search is conducted. This process can be arduous, especially considering the sensitivity of certain zeolite synthesis processes to small changes in conditions. Fortunately, in many laboratories, parallel experiments using small multi-autoclave synthesis systems accelerate this process (Figure 3B).[^26] Figure 3B also offers a glimpse into a future with fully autonomous systems, where robots handle synthesis solution preparation, oven transfer, and synthesis product characterization. When integrated with ‘intelligent’ experimental design techniques like active learning, such as Bayesian optimization, this advancement could pave the way for the development of self-driving laboratories, drastically enhancing the pace of discovery and optimization in zeolite synthesis.[^27] + +## 3. Zeolite Synthesis – Optimization + +As discussed in the previous section, active learning has the potential to navigate the extensive parameter space of black box zeolite syntheses. This would prove beneficial in both manual and self-driving laboratories, saving time, reducing associated costs, and increasing the likelihood of discovering materials that are close to optimal. + +Nonetheless, conducting a search on Science Direct and Google Scholar using the terms "zeolite Bayesian optimization" and "zeolite active learning" yields only a limited number of relevant examples, excluding cases where active learning is employed to optimize machine learning model hyperparameters: + +1. Bayesian optimization was employed to experimentally find optimal Si/Al and Cu loading for Cu-CHA zeolites to optimize catalytic activity and selectivity in the partial oxidation of methane to methanol. As the CHA zeolites were commercially purchased, the Si/Al ratio was limited to 4 possibilities (categorical), whereas the in-house Cu ion exchange allowed for a continuous range of Cu loading.[^28] +2. Bayesian optimization was employed to experimentally determine the optimal metal loading of Cu/Fe-CHA catalysts, aiming to maximize their performance in both fresh and hydrothermally aged conditions for the selective catalytic reduction of NOx in diesel engine exhaust.[^29] +3. Bayesian optimization was used to find mechanically superior zeolite structures (in term of shear and bulk moduli calculated with DFT and machine learning) in an existing database containing approximately 590,000 hypothetical zeolites.[^30] + +However, none of these examples directly relate to zeolite synthesis. The first example effectively illustrates how an optimized in-house synthesis could lead to further improvement of the catalyst. This would involve expanding the feasible range of Si/Al values and considering additional variables such as synthesis temperature and duration, which can impact Al distribution. Thus, it appears that the zeolite synthesis field with its associated large parameter space could benefit from leveraging active learning methods. Therefore, the optimization characteristics are explored below, building upon the earlier section detailing zeolite synthesis ingredients and equipment. + +### 3.1 Parameter Space + +Zeolite synthesis involves numerous inherently **continuous** variables, such as the concentrations of the aforementioned ingredients. Typically, syntheses are presented in literature as molar ratio *xi* of ingredient *i* with respect to silicon, e.g., 1 Si: 0.1 Al: 0.5 OSDA: 0.2 Na+: 30 H2O. Given the known synthesis volume in the teflon liner, these ratios determine the required ingredient masses. Consequently, in Bayesian optimization, it is sufficient to optimize the molar ratio values. Other continuous variables include synthesis temperature, duration, and stirring/tumbling speed. + +The parameter bounds encompass reasonable molar ratio values (ranging from a lower bound of 0 to upper bounds determined by the ingredient and target, such as *xAl* < 0.5), along with constraints linked to equipment specifications. These constraints may include the maximum temperature of the synthesis oven and the predefined maximum speeds for stirring and tumbling systems. Depending on the equipment at hand, certain continuous variables may need to be discretized. For instance, in a setup where only one tumbling speed is available, the tumbling variable would become categorical: {Yes, No}. + +On the other hand, the selection of precursor materials is **categorical**. Even though the difference between certain sources seems minimal, they could have a small difference in solubility or basicity and lead to different crystallization kinetics.[^31] + +Below, we list several standard choices for the typical hydrothermal synthesis outlined above: + +| **Precursor** | **Options** | +| --- | --- | +| | | +| Silicon | Colloidal (Ludox HS-40, Ludox AS-40
), fumed (aerosil, Cab-O-Sil
), solid (sodium silicates, other zeolites in an interzeolite conversion
), liquids (tetraethyl orthosilicate
) 
 | +| Aluminum | Aluminum metal, Al(OH)3, Al(NO3)3, Al2(SO4)3, NaAlO2
 | +| OSDA | Typically one to a few OSDAs are known to effectively template the pores of a specific zeolite topology. For example, the typical OSDA for CHA synthesis is N,N,N-trimethyladamantylammonium hydroxide, but alternatives like tetraethylammonium hydroxide have also been documented.[^32] However, most often, a fixed OSDA is chosen a priori, and other variables are adjusted, as syntheses with different OSDAs may require entirely different conditions. | +| Inorganic cation | LiOH, NaOH, KOH, CsOH, NaCl
 | +| Water | Demineralized water is preferred for reproducibility, and further filtration (e.g., Milli-Q purification) is desirable. Therefore, no choices are to be made in this regard. | + +### 3.2 Constraints + +In zeolite synthesis, additional constraints on top of the specified bounds for the parameter space are uncommon. For instance, one might envision rare scenarios where both Al and a small amount of B are desired in the synthesis. Then a constraint could be introduced, e.g.: 10*xB* < *xAl*. Another scenario might involve limiting the total synthesis cost by assigning a monetary value to each parameter. + +### 3.3 Objectives + +Various objectives might be pursued depending on the economic considerations of the process and the specific application requirements: + +**General objectives:** + +- Attaining high crystallinity of the resulting zeolite. +- Ensuring high purity by minimizing the presence of side-products. + +**Economic objectives:** + +- Achieving a high synthesis yield. +- Maintaining low concentrations of expensive ingredients such as the organic structure-directing agent (OSDA), or other potentially harmful ingredients like fluoride. +- Operating at low synthesis temperatures. +- Reducing the synthesis time. + +**Application objectives:** + +- Targeting specific Si/Al ratios, which might be important for acid-catalyzed applications and also influences the zeolite stability.[^33] +- Controlling the distribution of aluminum within the structure, important for ion exchange and catalysis.[^34] +- Adjusting crystal size or morphology to optimize catalysis, adsorption or separation applications.[^35] + +Most of these objectives are correlated in positive or negative ways. For instance, low crystal size is generally accomplished through lower synthesis temperatures, favoring nucleation over crystal growth. However, this also results in slow nucleation and crystal growth and – depending on the synthesis time – low yields.[^36] + +### 3.4 Zeolite Synthesis Datasets + +Most zeolite synthesis papers include tables, either in the main text or supplementary information, that contain the molar ratios of the ingredients used in the ‘grid search’ synthesis experiments. The exact procedure and details of the ingredients are often described in textual form in the method sections. Following the recent advancements in material informatics, some studies have systematically scraped and summarized zeolite synthesis data. The data can be combined with structural parameters available on the IZA website for the respective topologies.[^5] + +Jensen et al. gathered data for 1,200 distinct synthetic pathways for Ge-containing zeolites[^37] and 5,663 synthesis routes specifying OSDAs.[^38] Muraoka et al. compiled a dataset with 686 unique OSDA-free synthesis routes for 23 distinct frameworks, detailing gel compositions and reaction conditions.[^39] However, these datasets are rather small and do not fully encapsulate all parameters essential for zeolite synthesis: synthesis composition, OSDA and reaction conditions. + +In a recent impressive development, Pan et al. introduced ZeoSyn, a comprehensive dataset containing 23,961 zeolite synthesis pathways for 233 distinct zeolite topologies, representing over 80% of synthesized frameworks to date, using 921 unique OSDAs.[^40] Each synthesis route details gel composition (a combination of 51 possible ingredients, including Si, Al, P, Na+, K+, OH−, F−, Ge, Ti, B, OSDA, H2O and additional solvents), reaction conditions and the resulting zeolite structure (or absence thereof, e.g. formation of dense or amorphous phases). In some cases, resulting zeolite properties such as Si/Al ratio, crystal size, crystallinity and BET surface area are reported. Each synthesis pathway is cross-referenced with the corresponding article from which it was sourced, alongside its publication year. The dataset is available on GitHub.[^41] + +## 4. Discussion + +Despite the significant industrial importance of zeolites, their synthesis primarily relies on heuristic approaches, domain knowledge, and a degree of serendipity. An experienced researcher can narrow down the extensive zeolite synthesis parameter space by drawing from literature and past experiences. Typically, within this constrained search space, practitioners resort to either a grid search or a random search. However, this procedure can prove time-consuming and costly. Active learning techniques, such as Bayesian optimization, offer the potential to more efficiently navigate the parameter space of black box zeolite syntheses and increase the likelihood of discovering materials that are close to optimal. + +Zeolite synthesis involves numerous continuous and categorical variables (see the Zeolite Synthesis sections). Depending on the considered search space, **mixed variable type** Bayesian optimization becomes necessary.[^42][^43] Additionally, many syntheses are **multi-objective**;[^44] for example, researchers may seek high synthesis yield coupled with a small crystal size. As mentioned earlier, laboratories often conduct parallel experiments using small multi-autoclave synthesis systems. Such setups could benefit from **batch** Bayesian optimization, which involves evaluating multiple candidate solutions simultaneously in each iteration of the optimization process. This can potentially reduce the total number of iterations required to find the desired solution.[^45][^46] + +One advantage of the extensive history of zeolite synthesis is the abundance of recipes available for most interesting topologies. Furthermore, unlike many other fields, failed synthesis attempts are frequently documented in the literature.[^40] Optimization campaigns enriched by **knowledge transfer** from past campaigns could identify desired materials with fewer experiments.[^47] + +Nowadays, various packages, such as Ax[^48], BayBE[^49] and scikit-optimize[^50]
, readily provide access to these different aspects of Bayesian optimization. In the [accompanying Jupyter notebook](./demo_zeolite_synthesis_bo.ipynb), we illustrate these features using real-world literature data.[^51][^52] One of the objectives of these studies was to maximize the amount of proximal Al pairs in synthesized CHA zeolites, which is required for stabilizing Fe2+ sites. Upon activation, these sites can selectively oxidize methane to methanol. The importance of these proximal Al pairs is underscored by the fact that in Cu-CHA zeolites, conversely, a low concentration of proximal Al pairs is preferred. In this context, these proximal Al pairs stabilize redox-inactive Cu2+ species that do not participate in the oxidation of methane to methanol.[^53][^54] + +Two approaches are followed in the notebook: +1) Optimization of a simulated analytical target function based on synthesis parameters. +2) Existing literature data is fed as prior experimental data to the Bayesian optimization algorithm, which then recommends subsequent experimental conditions to try out. + +This [code demonstration](./demo_zeolite_synthesis_bo.ipynb) showcases the effectiveness of Bayesian optimization in zeolite synthesis, hopefully encouraging the exploration of active learning approaches in similar endeavors. Open In Colab + +## References + +[^1]: Davis, M. E. Zeolites and Molecular Sieves: Not Just Ordinary Catalysts. [*Ind. Eng. Chem. Res.* **1991**, *30*, 1675–1683.](https://doi.org/10.1021/ie00056a001) + +[^2]: Flanigen, E. M. Chapter 2 - Zeolites and Molecular Sieves an Historical Perspective. In *Studies in Surface Science and Catalysis*; Van Bekkum, H., Flanigen, E. M., Jacobs, P. A., Jansen, J. C., Eds.; [Elsevier Science B.V., 2001; Vol. 137, pp 11–35.](https://doi.org/10.1016/S0167-2991(01)80243-3) + +[^3]: Dusselier, M.; Davis, M. E. Small-Pore Zeolites: Synthesis and Catalysis. [*Chem. Rev.* **2018**, *118*, 5265–5329.](https://doi.org/10.1021/acs.chemrev.7b00738) + +[^4]: Pophale, R.; Cheeseman, P. A.; Deem, M. W. A Database of New Zeolite-like Materials. [*Phys. Chem. Chem. Phys.* **2011**, *13*, 12407–12412.](https://doi.org/10.1039/C0CP02255A) + +[^5]: Structure Commission of the International Zeolite Association. Database of Zeolite Structures. . + +[^6]: Baerlocher, C.; McCusker, L. B.; Olson, D. H. [*Atlas of Zeolite Framework Types*; Elsevier, 2007.](https://www.sciencedirect.com/book/9780444530646/atlas-of-zeolite-framework-types) + +[^7]: Csicsery, S. M. Shape-Selective Catalysis in Zeolites. [*Zeolites* **1984**, *4*, 116–126.](https://doi.org/10.1016/0144-2449(84)90024-1) + +[^8]: Snyder, B. E. R.; Vanelderen, P.; Schoonheydt, R. A.; Sels, B. F.; Solomon, E. I. Second-Sphere Effects on Methane Hydroxylation in Cu-Zeolites. [*J. Am. Chem. Soc.* **2018**, *140*, 9236–9243.](https://doi.org/10.1021/jacs.8b05320) + +[^9]: Snyder, B. E. R.; Bols, M. L.; Schoonheydt, R. A.; Sels, B. F.; Solomon, E. I. Iron and Copper Active Sites in Zeolites and Their Correlation to Metalloenzymes. [*Chem. Rev.* **2018**, *118*, 2718–2768.](https://doi.org/10.1021/acs.chemrev.7b00344) + +[^10]: Plessers, D.; Bols, M. L.; Rhoda, H. M.; Heyer, A. J.; Solomon, E. I.; Sels, B. F.; Schoonheydt, R. A. Single Site Spectroscopy of Transition Metal Ions and Reactive Oxygen Complexes in Zeolites. [In *Comprehensive Inorganic Chemistry III*; Reedijk, J., Poeppelmeier, K. R., Eds.; Elsevier, 2023; pp 148–164.](https://doi.org/10.1016/B978-0-12-823144-9.00008-X) + +[^11]: Derouane, E. G.; VĂ©drine, J. C.; Ramos Pinto, R.; Borges, P. M.; Costa, L.; Lemos, M. A. N. D. A.; Lemos, F.; RamĂŽa Ribeiro, F. The Acidity of Zeolites: Concepts, Measurements and Relation to Catalysis: A Review on Experimental and Theoretical Methods for the Study of Zeolite Acidity. [*Catal. Rev. - Sci. Eng.* **2013**, *55*, 454–515.](https://doi.org/10.1080/01614940.2013.822266) + +[^12]: Zhang, Q.; Gao, S.; Yu, J. Metal Sites in Zeolites: Synthesis, Characterization, and Catalysis. [*Chem. Rev.* **2023**, *123*, 6039–6106.](https://doi.org/10.1021/acs.chemrev.2c00315) + +[^13]: PĂ©rez-Botella, E.; Valencia, S.; Rey, F. Zeolites in Adsorption Processes: State of the Art and Future Prospects. [*Chem. Rev.* **2022**, *122*, 17647–17695.](https://doi.org/10.1021/acs.chemrev.2c00140) + +[^14]: Yilmaz, B.; MĂŒller, U. Catalytic Applications of Zeolites in Chemical Industry. [*Top. Catal.* **2009**, *52*, 888–895.](https://doi.org/10.1007/s11244-009-9226-0) + +[^15]: Xu, X.; Wang, J.; Long, Y. Zeolite-Based Materials for Gas Sensors. [*Sensors* **2006**, *6*, 1751–1764.](https://doi.org/10.3390/s6121751) + +[^16]: Rangnekar, N.; Mittal, N.; Elyassi, B.; Caro, J.; Tsapatsis, M. Zeolite Membranes - a Review and Comparison with MOFs. [*Chem. Soc. Rev.* **2015**, *44*, 7128–7154.](https://doi.org/10.1039/C5CS00292C) + +[^17]: Beale, A. M.; Gao, F.; Lezcano-Gonzalez, I.; Peden, C. H. F.; Szanyi, J. Recent Advances in Automotive Catalysis for NOx Emission Control by Small-Pore Microporous Materials. [*Chem. Soc. Rev.* **2015**, *44*, 7371–7405.](https://doi.org/10.1039/C5CS00108K) + +[^18]: Zhang, R.; Liu, N.; Lei, Z.; Chen, B. Selective Transformation of Various Nitrogen-Containing Exhaust Gases toward N2 over Zeolite Catalysts. [*Chem. Rev.* **2016**, *116*, 3658–3721.](https://doi.org/10.1021/acs.chemrev.5b00474) + +[^19]: Zeolite Market Size, Share & Trends Analysis Report By Application (Catalyst, Adsorbent, Detergent Builder), By Product (Natural, Synthetic), By Region (North America, Europe, APAC, CSA, MEA), And Segment Forecasts, 2022 - 2030. ISBN 978-1-68038-601-1. . + +[^20]: Davis, M. E. Zeolites from a Materials Chemistry Perspective. [*Chem. Mater.* **2014**, *26*, 239–245.](https://doi.org/10.1021/cm401914u) + +[^21]: Cundy, C. S.; Cox, P. A. The Hydrothermal Synthesis of Zeolites: History and Development from the Eearliest Days to the Present Time. [*Chem. Rev.* **2003**, *103*, 663–701.](https://doi.org/10.1021/cr020060i) + +[^22]: Deneyer, A.; Ke, Q.; Devos, J.; Dusselier, M. Zeolite Synthesis under Nonconventional Conditions: Reagents, Reactors, and Modi Operandi. [*Chem. Mater.* **2020**, *32*, 4884–4919.](https://doi.org/10.1021/acs.chemmater.9b04741) + +[^23]: Asselman, K.; Kirschhock, C.; Breynaert, E. Illuminating the Black Box: A Perspective on Zeolite Crystallization in Inorganic Media. [*Acc. Chem. Res.* **2023**, *56*, 2391–2402.](https://doi.org/10.1021/acs.accounts.3c00269) + +[^24]: Ivanushkin, G.; Dusselier, M. Engineering Lewis Acidity in Zeolite Catalysts by Electrochemical Release of Heteroatoms during Synthesis. [*Chem. Mater.* **2023**, *35*, 5049–5058.](https://doi.org/10.1021/acs.chemmater.3c00552) + +[^25]: Hydrothermal Synthesis. . + +[^26]: Moliner, M.; Serra, J. M.; Corma, A.; Argente, E.; Valero, S.; Botti, V. Application of Artificial Neural Networks to High-Throughput Synthesis of Zeolites. [*Microporous Mesoporous Mater.* **2005**, *78*, 73–81.](https://doi.org/10.1016/j.micromeso.2004.09.018) + +[^27]: Tom, G.; Schmid, S. P.; Baird, S. G.; Cao, Y.; Darvish, K.; Hao, H.; Lo, S.; Pablo-GarcĂ­a, S.; Rajaonson, E. M.; Skreta, M.; Yoshikawa, N.; Corapi, S.; Akkoc, G. D.; Strieth-Kalthoff, F.; Seifrid, M.; Aspuru-Guzik, A. Self-Driving Laboratories for Chemistry and Materials Science. [*ChemRxiv* **2024**, doi:10.26434/chemrxiv-2024-rj946.](https://doi.org/10.26434/chemrxiv-2024-rj946) + +[^28]: Ohyama, J.; Tsuchimura, Y.; Yoshida, H.; Machida, M.; Nishimura, S.; Takahashi, K. Bayesian-Optimization-Based Improvement of Cu-CHA Catalysts for Direct Partial Oxidation of CH4. [*J. Phys. Chem. C* **2022**, *126*, 19660–19666.](https://doi.org/10.1021/acs.jpcc.2c04229) + +[^29]: Lim, S.; Lee, H.; Bae, S.; Shin, J. S.; Kim, D. H.; Lee, J. M. Bayesian Optimization for Automobile Catalyst Development. [In *14th International Symposium on Process Systems Engineering*; Elsevier, 2022; Vol. 49, pp 1213–1218.](https://doi.org/10.1016/B978-0-323-85159-6.50202-5) + +[^30]: Kim, N.; Min, K. Accelerated Discovery of Zeolite Structures with Superior Mechanical Properties via Active Learning. [*J. Phys. Chem. Lett.* **2021**, *12*, 2334–2339.](https://doi.org/10.1021/acs.jpclett.1c00339) + +[^31]: Liang, D.; Liu, Y.; Zhang, R.; Xie, Q.; Zhang, L. A Review on the Influence Factors in the Synthesis of Zeolites and the Transformation Behavior of Silicon and Aluminum During the Process. [*Comments Inorg. Chem.* **2024**, *00*, 1–37.](https://doi.org/10.1080/02603594.2024.2309878) + +[^32]: MartĂ­n, N.; Moliner, M.; Corma, A. High Yield Synthesis of High-Silica Chabazite by Combining the Role of Zeolite Precursors and Tetraethylammonium: SCR of NOx. [*Chem. Commun.* **2015**, *51*, 9965–9968.](https://doi.org/10.1039/C5CC02670A) + +[^33]: Li, J.; Gao, M.; Yan, W.; Yu, J. Regulation of the Si/Al Ratios and Al Distributions of Zeolites and Their Impact on Properties. [*Chem. Sci.* **2022**, *14*, 1935–1959.](https://doi.org/10.1039/D2SC06010H) + +[^34]: Knott, B. C.; Nimlos, C. T.; Robichaud, D. J.; Nimlos, M. R.; Kim, S.; Gounder, R. Consideration of the Aluminum Distribution in Zeolites in Theoretical and Experimental Catalysis Research. [*ACS Catal.* **2018**, *8*, 770–784.](https://doi.org/10.1021/acscatal.7b03676) + +[^35]: Li, S.; Li, J.; Dong, M.; Fan, S.; Zhao, T.; Wang, J.; Fan, W. Strategies to Control Zeolite Particle Morphology. [*Chem. Soc. Rev.* **2019**, *48*, 885–907.](https://doi.org/10.1039/C8CS00774H) + +[^36]: Larsen, S. C. Nanocrystalline Zeolites and Zeolite Structures: Synthesis, Characterization, and Applications. [*J. Phys. Chem. C* **2007**, *111*, 18464–18474.](https://doi.org/10.1021/jp074980m) + +[^37]: Jensen, Z.; Kim, E.; Kwon, S.; Gani, T. Z. H.; RomĂĄn-Leshkov, Y.; Moliner, M.; Corma, A.; Olivetti, E. A Machine Learning Approach to Zeolite Synthesis Enabled by Automatic Literature Data Extraction. [*ACS Cent. Sci.* **2019**.](https://doi.org/10.1021/acscentsci.9b00193) + +[^38]: Jensen, Z.; Kwon, S.; Schwalbe-Koda, D.; Paris, C.; GĂłmez-Bombarelli, R.; RomĂĄn-Leshkov, Y.; Corma, A.; Moliner, M.; Olivetti, E. A. Discovering Relationships between OSDAs and Zeolites through Data Mining and Generative Neural Networks. [*ACS Cent. Sci.* **2021**, *7*, 858–867.](https://doi.org/10.1021/acscentsci.1c00024) + +[^39]: Muraoka, K.; Sada, Y.; Miyazaki, D.; Chaikittisilp, W.; Okubo, T. Linking Synthesis and Structure Descriptors from a Large Collection of Synthetic Records of Zeolite Materials. [*Nat. Commun.* **2019**, *10*, 1–11.](https://doi.org/10.1038/s41467-019-12394-0) + +[^40]: Pan, E.; Kwon, S.; Jensen, Z.; Xie, M.; GĂłmez-Bombarelli, R.; Moliner, M.; RomĂĄn-Leshkov, Y.; Olivetti, E. ZeoSyn: A Comprehensive Zeolite Synthesis Dataset Enabling Machine-Learning Rationalization of Hydrothermal Parameters. [*ACS Cent. Sci.* **2024**.](https://doi.org/10.1021/acscentsci.3c01615) + +[^41]: Pan, E.; Kwon, S.; Jensen, Z.; Xie, M.; GĂłmez-Bombarelli, R.; Moliner, M.; RomĂĄn-Leshkov, Y.; Olivetti, E. ZeoSyn: A Comprehensive Zeolite Synthesis Dataset Enabling Machine-learning Rationalization of Hydrothermal Parameters (ACS Central Science 2024). . + +[^42]: HĂ€se, F.; Aldeghi, M.; Hickman, R. J.; Roch, L. M.; Aspuru-Guzik, A. Gryffin: An Algorithm for Bayesian Optimization of Categorical Variables Informed by Expert Knowledge. [*Appl. Phys. Rev.* **2021**, *8*.](https://doi.org/10.1063/5.0048164) + +[^43]: Aldulaijan, N.; Marsden, J. A.; Manson, J. A.; Clayton, A. D. Adaptive Mixed Variable Bayesian Self-Optimisation of Catalytic Reactions. [*React. Chem. Eng.* **2023**, 308–316.](https://doi.org/10.1039/D3RE00476G) + +[^44]: Torres, J. A. G.; Lau, S. H.; Anchuri, P.; Stevens, J. M.; Tabora, J. E.; Li, J.; Borovika, A.; Adams, R. P.; Doyle, A. G. A Multi-Objective Active Learning Platform and Web App for Reaction Optimization. [*J. Am. Chem. Soc.* **2022**, *144*, 19999–20007.](https://doi.org/10.1021/jacs.2c08592) + +[^45]: HĂ€se, F.; Roch, L. M.; Kreisbeck, C.; Aspuru-Guzik, A. Phoenics: A Bayesian Optimizer for Chemistry. [*ACS Cent. Sci.* **2018**, *4*, 1134–1145.](https://doi.org/10.1021/acscentsci.8b00307) + +[^46]: GonzĂĄlez, L. D.; Zavala, V. M. New Paradigms for Exploiting Parallel Experiments in Bayesian Optimization. [*Comput. Chem. Eng.* **2023**, *170*, 108110.](https://doi.org/10.1016/j.compchemeng.2022.108110) + +[^47]: Hickman, R. J.; RuĆŸa, J.; Tribukait, H.; Roch, L. M.; GarcĂ­a-DurĂĄn, A. Equipping Data-Driven Experiment Planning for Self-Driving Laboratories with Semantic Memory: Case Studies of Transfer Learning in Chemical Reaction Optimization. [*React. Chem. Eng.* **2023**, *8*, 2284–2296.](https://doi.org/10.1039/D3RE00008G) + +[^48]: Ax, Adaptive Experimentation Platform. . + +[^49]: BayBE - A Bayesian Back End for Design of Experiments. . + +[^50]: Scikit-optimize, Sequential model-based optimization in Python. . + +[^51]: Devos, J.; Bols, M. L.; Plessers, D.; Goethem, C. Van; Seo, J. W.; Hwang, S.-J.; Sels, B. F.; Dusselier, M. Synthesis–Structure–Activity Relations in Fe-CHA for C–H Activation: Control of Al Distribution by Interzeolite Conversion. [*Chem. Mater.* **2020**, *32*, 273–285.](https://doi.org/10.1021/acs.chemmater.9b03738) + +[^52]: Bols, M. L.; Devos, J.; Rhoda, H. M.; Plessers, D.; Solomon, E. I.; Schoonheydt, R. A.; Sels, B. F.; Dusselier, M. Selective Formation of α-Fe(II) Sites on Fe-Zeolites through One-Pot Synthesis. [*J. Am. Chem. Soc.* **2021**, *143*, 16243–16255.](https://doi.org/10.1021/jacs.1c07590) + +[^53]: Pappas, D. K.; Borfecchia, E.; Dyballa, M.; Pankin, I. A.; Lomachenko, K. A.; Martini, A.; Signorile, M.; Teketel, S.; Arstad, B.; Berlier, G.; Lamberti, C.; Bordiga, S.; Olsbye, U.; Lillerud, K. P.; Svelle, S.; Beato, P. Methane to Methanol: Structure–Activity Relationships for Cu-CHA. [*J. Am. Chem. Soc.* **2017**, *139*, 14961–14975.](https://doi.org/10.1021/jacs.7b06472) + +[^54]: Rhoda, H. M.; Plessers, D.; Heyer, A. J.; Bols, M. L.; Schoonheydt, R. A.; Sels, B. F.; Solomon, E. I. Spectroscopic Definition of a Highly Reactive Site in Cu-CHA for Selective Methane Oxidation: Tuning a Mono-ÎŒ-Oxo Dicopper(II) Active Site for Reactivity. [*J. Am. Chem. Soc.* **2021**, *143*, 7531–7540.](https://doi.org/10.1021/jacs.1c02835) diff --git a/zeolite_synthesis_bo_introduction.pdf b/zeolite_synthesis_bo_introduction.pdf new file mode 100644 index 0000000..2745005 Binary files /dev/null and b/zeolite_synthesis_bo_introduction.pdf differ