chembayes 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- chembayes-0.1.0/LICENSE +6 -0
- chembayes-0.1.0/PKG-INFO +106 -0
- chembayes-0.1.0/README.md +82 -0
- chembayes-0.1.0/pyproject.toml +41 -0
- chembayes-0.1.0/setup.cfg +4 -0
- chembayes-0.1.0/src/chembayes/__init__.py +13 -0
- chembayes-0.1.0/src/chembayes/optimizer.py +366 -0
- chembayes-0.1.0/src/chembayes/sampler.py +106 -0
- chembayes-0.1.0/src/chembayes.egg-info/PKG-INFO +106 -0
- chembayes-0.1.0/src/chembayes.egg-info/SOURCES.txt +11 -0
- chembayes-0.1.0/src/chembayes.egg-info/dependency_links.txt +1 -0
- chembayes-0.1.0/src/chembayes.egg-info/requires.txt +6 -0
- chembayes-0.1.0/src/chembayes.egg-info/top_level.txt +1 -0
chembayes-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
Copyright (c) 2026 Jesus Alberto Martin del Campo
|
|
2
|
+
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:
|
|
3
|
+
|
|
4
|
+
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
|
5
|
+
|
|
6
|
+
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.
|
chembayes-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: chembayes
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Experimental design and Bayesian optimization with Gaussian Processes and QMC sampling
|
|
5
|
+
Author-email: Jesus Alberto Martin del Campo <j.a.martin-campo@hotmail.com>
|
|
6
|
+
Project-URL: Homepage, https://github.com/jesusalmartin/chembayes
|
|
7
|
+
Project-URL: Bug Tracker, https://github.com/jesusalmartin/chembayes/issues
|
|
8
|
+
Keywords: bayesian-optimization,gaussian-processes,experimental-design,qmc,sampling,chemistry
|
|
9
|
+
Classifier: Programming Language :: Python :: 3
|
|
10
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
11
|
+
Classifier: Operating System :: OS Independent
|
|
12
|
+
Classifier: Topic :: Scientific/Engineering :: Chemistry
|
|
13
|
+
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
|
|
14
|
+
Requires-Python: >=3.9
|
|
15
|
+
Description-Content-Type: text/markdown
|
|
16
|
+
License-File: LICENSE
|
|
17
|
+
Requires-Dist: numpy>=1.21
|
|
18
|
+
Requires-Dist: pandas>=1.3
|
|
19
|
+
Requires-Dist: scikit-learn>=1.0
|
|
20
|
+
Requires-Dist: optuna>=3.0
|
|
21
|
+
Requires-Dist: matplotlib>=3.4
|
|
22
|
+
Requires-Dist: scipy>=1.7
|
|
23
|
+
Dynamic: license-file
|
|
24
|
+
|
|
25
|
+
# ChemBayes
|
|
26
|
+
|
|
27
|
+
**ChemBayes** is a Python library designed to streamline **experimental design (DoE)** and **Bayesian optimization** for scientific and chemical research. It leverages Gaussian Processes to model complex response surfaces and efficiently identify optimal conditions.
|
|
28
|
+
|
|
29
|
+
## 🚀 Key Features
|
|
30
|
+
|
|
31
|
+
* **Intelligent Sampling (QMC):** Generate initial experimental designs using Halton sequences (Quasi-Monte Carlo) for optimal space-filling coverage.
|
|
32
|
+
* **Automatic Preprocessing:** Integrated handling of numeric variables (scaling) and categorical variables (one-hot encoding).
|
|
33
|
+
* **Hyperparameter Tuning:** Automated Gaussian Process parameter adjustment (Matern + WhiteKernel) using Leave-One-Out (LOO) cross-validation and NLPD loss.
|
|
34
|
+
* **Bayesian Optimization:** Global maximum search using the Expected Improvement (EI) acquisition function.
|
|
35
|
+
* **Visual Diagnostics:** Automated generation of feature importance, partial dependence plots (1D and 2D), and model validation charts.
|
|
36
|
+
|
|
37
|
+
## 📦 Installation
|
|
38
|
+
|
|
39
|
+
From the project root (where `pyproject.toml` is located):
|
|
40
|
+
|
|
41
|
+
```bash
|
|
42
|
+
pip install .
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
Or for development mode:
|
|
46
|
+
|
|
47
|
+
```bash
|
|
48
|
+
pip install -e .
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
## 🛠️ Quick Start
|
|
52
|
+
|
|
53
|
+
### 1. Generating an Experimental Design (Sampling)
|
|
54
|
+
|
|
55
|
+
Define your search space with numeric and categorical parameters:
|
|
56
|
+
|
|
57
|
+
```python
|
|
58
|
+
import chembayes as cb
|
|
59
|
+
|
|
60
|
+
parameters = {
|
|
61
|
+
'temperature': {'type': 'float', 'l_bound': 20.0, 'u_bound': 100.0},
|
|
62
|
+
'time': {'type': 'int', 'l_bound': 5, 'u_bound': 60},
|
|
63
|
+
'catalyst': {'type': 'categoric', 'categories': ['Pd', 'Ni', 'Cu']}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
# Generate 20 optimal experimental points
|
|
67
|
+
df_experiments = cb.qmc_sampling(parameters, n_points=20)
|
|
68
|
+
|
|
69
|
+
# Visualize the distribution
|
|
70
|
+
cb.plot_qmc_sampling(df_experiments)
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
### 2. Bayesian Optimization
|
|
74
|
+
|
|
75
|
+
Once you have experimental data, find the optimal conditions:
|
|
76
|
+
|
|
77
|
+
```python
|
|
78
|
+
# Define input features and the target column
|
|
79
|
+
inputs = ['temperature', 'time', 'catalyst']
|
|
80
|
+
output = 'yield'
|
|
81
|
+
|
|
82
|
+
# Run the complete optimization pipeline
|
|
83
|
+
results = cb.optimize_experiment(
|
|
84
|
+
df=df_data,
|
|
85
|
+
inputs=inputs,
|
|
86
|
+
output=output,
|
|
87
|
+
n_tuning_trials=50,
|
|
88
|
+
n_opt_trials=100
|
|
89
|
+
)
|
|
90
|
+
|
|
91
|
+
# Access the best found parameters
|
|
92
|
+
print(f"Best configuration: {results['best_params']}")
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
## 📂 Project Structure
|
|
96
|
+
|
|
97
|
+
* `src/chembayes/sampler.py`: Tools for QMC sample generation.
|
|
98
|
+
* `src/chembayes/optimizer.py`: Bayesian optimization engine and Gaussian Processes.
|
|
99
|
+
* `src/chembayes/__init__.py`: Public API exposure.
|
|
100
|
+
* `pyproject.toml`: Modern package configuration and dependencies.
|
|
101
|
+
|
|
102
|
+
## ✉️ Contact & Contribution
|
|
103
|
+
|
|
104
|
+
**Author:** Jesus Alberto Martin del Campo
|
|
105
|
+
**Email:** j.a.martin-campo@hotmail.com
|
|
106
|
+
**GitHub:** [jesusalmartin/chembayes](https://github.com/jesusalmartin/chembayes)
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
# ChemBayes
|
|
2
|
+
|
|
3
|
+
**ChemBayes** is a Python library designed to streamline **experimental design (DoE)** and **Bayesian optimization** for scientific and chemical research. It leverages Gaussian Processes to model complex response surfaces and efficiently identify optimal conditions.
|
|
4
|
+
|
|
5
|
+
## 🚀 Key Features
|
|
6
|
+
|
|
7
|
+
* **Intelligent Sampling (QMC):** Generate initial experimental designs using Halton sequences (Quasi-Monte Carlo) for optimal space-filling coverage.
|
|
8
|
+
* **Automatic Preprocessing:** Integrated handling of numeric variables (scaling) and categorical variables (one-hot encoding).
|
|
9
|
+
* **Hyperparameter Tuning:** Automated Gaussian Process parameter adjustment (Matern + WhiteKernel) using Leave-One-Out (LOO) cross-validation and NLPD loss.
|
|
10
|
+
* **Bayesian Optimization:** Global maximum search using the Expected Improvement (EI) acquisition function.
|
|
11
|
+
* **Visual Diagnostics:** Automated generation of feature importance, partial dependence plots (1D and 2D), and model validation charts.
|
|
12
|
+
|
|
13
|
+
## 📦 Installation
|
|
14
|
+
|
|
15
|
+
From the project root (where `pyproject.toml` is located):
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
pip install .
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
Or for development mode:
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
pip install -e .
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
## 🛠️ Quick Start
|
|
28
|
+
|
|
29
|
+
### 1. Generating an Experimental Design (Sampling)
|
|
30
|
+
|
|
31
|
+
Define your search space with numeric and categorical parameters:
|
|
32
|
+
|
|
33
|
+
```python
|
|
34
|
+
import chembayes as cb
|
|
35
|
+
|
|
36
|
+
parameters = {
|
|
37
|
+
'temperature': {'type': 'float', 'l_bound': 20.0, 'u_bound': 100.0},
|
|
38
|
+
'time': {'type': 'int', 'l_bound': 5, 'u_bound': 60},
|
|
39
|
+
'catalyst': {'type': 'categoric', 'categories': ['Pd', 'Ni', 'Cu']}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
# Generate 20 optimal experimental points
|
|
43
|
+
df_experiments = cb.qmc_sampling(parameters, n_points=20)
|
|
44
|
+
|
|
45
|
+
# Visualize the distribution
|
|
46
|
+
cb.plot_qmc_sampling(df_experiments)
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
### 2. Bayesian Optimization
|
|
50
|
+
|
|
51
|
+
Once you have experimental data, find the optimal conditions:
|
|
52
|
+
|
|
53
|
+
```python
|
|
54
|
+
# Define input features and the target column
|
|
55
|
+
inputs = ['temperature', 'time', 'catalyst']
|
|
56
|
+
output = 'yield'
|
|
57
|
+
|
|
58
|
+
# Run the complete optimization pipeline
|
|
59
|
+
results = cb.optimize_experiment(
|
|
60
|
+
df=df_data,
|
|
61
|
+
inputs=inputs,
|
|
62
|
+
output=output,
|
|
63
|
+
n_tuning_trials=50,
|
|
64
|
+
n_opt_trials=100
|
|
65
|
+
)
|
|
66
|
+
|
|
67
|
+
# Access the best found parameters
|
|
68
|
+
print(f"Best configuration: {results['best_params']}")
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
## 📂 Project Structure
|
|
72
|
+
|
|
73
|
+
* `src/chembayes/sampler.py`: Tools for QMC sample generation.
|
|
74
|
+
* `src/chembayes/optimizer.py`: Bayesian optimization engine and Gaussian Processes.
|
|
75
|
+
* `src/chembayes/__init__.py`: Public API exposure.
|
|
76
|
+
* `pyproject.toml`: Modern package configuration and dependencies.
|
|
77
|
+
|
|
78
|
+
## ✉️ Contact & Contribution
|
|
79
|
+
|
|
80
|
+
**Author:** Jesus Alberto Martin del Campo
|
|
81
|
+
**Email:** j.a.martin-campo@hotmail.com
|
|
82
|
+
**GitHub:** [jesusalmartin/chembayes](https://github.com/jesusalmartin/chembayes)
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=61.0"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "chembayes"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
authors = [
|
|
9
|
+
{ name="Jesus Alberto Martin del Campo", email="j.a.martin-campo@hotmail.com" },
|
|
10
|
+
]
|
|
11
|
+
# Descripción actualizada para incluir el muestreo experimental
|
|
12
|
+
description = "Experimental design and Bayesian optimization with Gaussian Processes and QMC sampling"
|
|
13
|
+
readme = "README.md"
|
|
14
|
+
requires-python = ">=3.9"
|
|
15
|
+
# Se añaden keywords para mejorar la búsqueda del paquete
|
|
16
|
+
keywords = ["bayesian-optimization", "gaussian-processes", "experimental-design", "qmc", "sampling", "chemistry"]
|
|
17
|
+
classifiers = [
|
|
18
|
+
"Programming Language :: Python :: 3",
|
|
19
|
+
"License :: OSI Approved :: MIT License",
|
|
20
|
+
"Operating System :: OS Independent",
|
|
21
|
+
"Topic :: Scientific/Engineering :: Chemistry",
|
|
22
|
+
"Topic :: Scientific/Engineering :: Artificial Intelligence",
|
|
23
|
+
]
|
|
24
|
+
dependencies = [
|
|
25
|
+
"numpy>=1.21",
|
|
26
|
+
"pandas>=1.3",
|
|
27
|
+
"scikit-learn>=1.0",
|
|
28
|
+
"optuna>=3.0",
|
|
29
|
+
"matplotlib>=3.4",
|
|
30
|
+
"scipy>=1.7",
|
|
31
|
+
]
|
|
32
|
+
|
|
33
|
+
[project.urls]
|
|
34
|
+
"Homepage" = "https://github.com/jesusalmartin/chembayes"
|
|
35
|
+
"Bug Tracker" = "https://github.com/jesusalmartin/chembayes/issues"
|
|
36
|
+
|
|
37
|
+
[tool.setuptools]
|
|
38
|
+
package-dir = {"" = "src"}
|
|
39
|
+
|
|
40
|
+
[tool.setuptools.packages.find]
|
|
41
|
+
where = ["src"]
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
"""
|
|
2
|
+
ChemBayes - Bayesian optimization with Gaussian Processes for experimental design.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from .optimizer import create_objective, optimize_experiment
|
|
6
|
+
from .sampler import qmc_sampling, plot_qmc_sampling
|
|
7
|
+
|
|
8
|
+
__all__ = [
|
|
9
|
+
'create_objective',
|
|
10
|
+
'optimize_experiment',
|
|
11
|
+
'qmc_sampling',
|
|
12
|
+
'plot_qmc_sampling'
|
|
13
|
+
]
|
|
@@ -0,0 +1,366 @@
|
|
|
1
|
+
import numpy as np
|
|
2
|
+
import pandas as pd
|
|
3
|
+
import matplotlib.pyplot as plt
|
|
4
|
+
from sklearn.preprocessing import StandardScaler, OneHotEncoder
|
|
5
|
+
from sklearn.compose import ColumnTransformer
|
|
6
|
+
from sklearn.gaussian_process import GaussianProcessRegressor
|
|
7
|
+
from sklearn.gaussian_process.kernels import Matern, WhiteKernel
|
|
8
|
+
from sklearn.model_selection import LeaveOneOut
|
|
9
|
+
from sklearn.inspection import permutation_importance, partial_dependence
|
|
10
|
+
from scipy.stats import norm
|
|
11
|
+
import optuna
|
|
12
|
+
|
|
13
|
+
def create_objective(df, weights):
|
|
14
|
+
"""
|
|
15
|
+
Create a weighted objective column.
|
|
16
|
+
"""
|
|
17
|
+
cols = list(weights.keys())
|
|
18
|
+
subset = df[cols]
|
|
19
|
+
scaler = StandardScaler()
|
|
20
|
+
scaled_data = scaler.fit_transform(subset)
|
|
21
|
+
weight_series = pd.Series(weights)
|
|
22
|
+
objective = scaled_data @ weight_series.values
|
|
23
|
+
return objective
|
|
24
|
+
|
|
25
|
+
def optimize_experiment(df, inputs, output, n_tuning_trials=100, n_opt_trials=100, plot=True):
|
|
26
|
+
"""
|
|
27
|
+
Run the complete optimization pipeline using Gaussian Processes and Bayesian optimization.
|
|
28
|
+
|
|
29
|
+
The pipeline consists of:
|
|
30
|
+
1. Preprocessing numeric features (StandardScaler) and categorical features (OneHotEncoder).
|
|
31
|
+
2. Tuning GP hyperparameters (Matern + WhiteKernel) via Leave-One-Out cross-validation
|
|
32
|
+
minimizing the average negative log predictive density (NLPD). Tuning uses Optuna.
|
|
33
|
+
3. Fitting the final GP model with the best hyperparameters.
|
|
34
|
+
4. (Optional) Diagnostic plots: true vs predicted (colored by uncertainty), permutation importance.
|
|
35
|
+
5. Bayesian optimization (Expected Improvement) to find the input parameters that maximize
|
|
36
|
+
the objective. This also uses Optuna.
|
|
37
|
+
6. Reconstructing the best numeric parameters on the original scale.
|
|
38
|
+
7. (Optional) Partial dependence plots:
|
|
39
|
+
- For numeric features: 1D line plots on the diagonal, 2D contour plots below the diagonal.
|
|
40
|
+
- For categorical features: bar charts showing the average partial dependence per category.
|
|
41
|
+
8. Printing the best parameters and predicted objective.
|
|
42
|
+
|
|
43
|
+
Parameters
|
|
44
|
+
----------
|
|
45
|
+
df : pd.DataFrame
|
|
46
|
+
Input data. It must already be cleaned (e.g., NaNs handled) if needed.
|
|
47
|
+
inputs : list of str
|
|
48
|
+
Column names to use as input features.
|
|
49
|
+
output : str or dict
|
|
50
|
+
If str: name of the column to maximize.
|
|
51
|
+
If dict: weights for weighted objective (passed to create_objective).
|
|
52
|
+
n_tuning_trials : int, default=100
|
|
53
|
+
Number of Optuna trials for GP hyperparameter tuning.
|
|
54
|
+
n_opt_trials : int, default=100
|
|
55
|
+
Number of Optuna trials for Bayesian optimization (Expected Improvement).
|
|
56
|
+
plot : bool, default=True
|
|
57
|
+
Whether to show diagnostic plots:
|
|
58
|
+
- true vs predicted (with uncertainty)
|
|
59
|
+
- permutation importance
|
|
60
|
+
- partial dependence (1D/2D for numeric, bar charts for categorical)
|
|
61
|
+
|
|
62
|
+
Returns
|
|
63
|
+
-------
|
|
64
|
+
dict
|
|
65
|
+
A dictionary containing:
|
|
66
|
+
- 'best_params': dict of optimal input parameters (on original scale).
|
|
67
|
+
- 'best_value': float, predicted objective at best_params.
|
|
68
|
+
- 'model': fitted GaussianProcessRegressor.
|
|
69
|
+
- 'preprocessor': fitted ColumnTransformer.
|
|
70
|
+
- 'X': preprocessed input matrix (numpy array).
|
|
71
|
+
- 'y': objective array.
|
|
72
|
+
"""
|
|
73
|
+
# Create reduced DataFrame with weighted objective if needed
|
|
74
|
+
if isinstance(output, dict):
|
|
75
|
+
df['objective'] = create_objective(df, output)
|
|
76
|
+
output = 'objective'
|
|
77
|
+
|
|
78
|
+
# Drop rows with missing values in selected inputs or output
|
|
79
|
+
model_df = df.dropna(subset=inputs+[output])
|
|
80
|
+
|
|
81
|
+
X_df = model_df[inputs]
|
|
82
|
+
y = model_df[output]
|
|
83
|
+
|
|
84
|
+
# Identify numeric and categorical columns
|
|
85
|
+
numeric_features = [col for col in X_df.columns if col in X_df.select_dtypes(include='number').columns]
|
|
86
|
+
categorical_features = [col for col in X_df.columns if col in X_df.select_dtypes(include=['object', 'category', 'string']).columns]
|
|
87
|
+
|
|
88
|
+
# Preprocess input columns using ColumnTransformer
|
|
89
|
+
preprocessor = ColumnTransformer(
|
|
90
|
+
transformers=[
|
|
91
|
+
('num', StandardScaler(), numeric_features),
|
|
92
|
+
('cat', OneHotEncoder(handle_unknown='ignore', sparse_output=False), categorical_features)
|
|
93
|
+
])
|
|
94
|
+
|
|
95
|
+
X = preprocessor.fit_transform(X_df)
|
|
96
|
+
model_features = preprocessor.get_feature_names_out()
|
|
97
|
+
X_model = pd.DataFrame(X, columns=model_features)
|
|
98
|
+
|
|
99
|
+
# Keep a reference to the scaler for inverse transformation later
|
|
100
|
+
scaler = preprocessor.named_transformers_['num']
|
|
101
|
+
|
|
102
|
+
# ----- Gaussian Process hyperparameter tuning (LOO-CV with NLPD) -----
|
|
103
|
+
def gp_tuning_objective(trial):
|
|
104
|
+
# Suggest hyperparameters
|
|
105
|
+
length_scale = trial.suggest_float('length_scale', 0.1, 10.0, log=True)
|
|
106
|
+
noise_level = trial.suggest_float('noise_level', 1e-3, 10.0, log=True)
|
|
107
|
+
|
|
108
|
+
nu_values = [0.5, 1.5, 2.5, np.inf]
|
|
109
|
+
nu_idx = trial.suggest_int('nu_idx', 0, 3)
|
|
110
|
+
nu = nu_values[nu_idx]
|
|
111
|
+
|
|
112
|
+
# Leave-one-out cross-validation
|
|
113
|
+
loo = LeaveOneOut()
|
|
114
|
+
nlpds = [] # store negative log predictive densities
|
|
115
|
+
|
|
116
|
+
for train_idx, test_idx in loo.split(X_df):
|
|
117
|
+
X_train_df = X_model.iloc[train_idx]
|
|
118
|
+
y_train = y.iloc[train_idx]
|
|
119
|
+
X_test_df = X_model.iloc[test_idx]
|
|
120
|
+
y_test = y.iloc[test_idx]
|
|
121
|
+
|
|
122
|
+
# Create kernel with current hyperparameters
|
|
123
|
+
kernel = Matern(length_scale=length_scale, nu=nu) + WhiteKernel(noise_level=noise_level)
|
|
124
|
+
|
|
125
|
+
# Build GP (no internal optimization, fixed kernel parameters)
|
|
126
|
+
gp = GaussianProcessRegressor(
|
|
127
|
+
kernel=kernel,
|
|
128
|
+
alpha=0.0, # we use WhiteKernel instead
|
|
129
|
+
optimizer=None,
|
|
130
|
+
normalize_y=True,
|
|
131
|
+
random_state=42
|
|
132
|
+
)
|
|
133
|
+
gp.fit(X_train_df, y_train)
|
|
134
|
+
|
|
135
|
+
# Predict on test point (mean and std)
|
|
136
|
+
mu, sigma = gp.predict(X_test_df, return_std=True)
|
|
137
|
+
mu = mu[0]
|
|
138
|
+
sigma = sigma[0]
|
|
139
|
+
|
|
140
|
+
# Avoid sigma=0 by clipping
|
|
141
|
+
sigma = max(sigma, 1e-6)
|
|
142
|
+
nlpd = 0.5 * np.log(2 * np.pi * sigma**2) + (y_test - mu)**2 / (2 * sigma**2)
|
|
143
|
+
nlpds.append(nlpd)
|
|
144
|
+
|
|
145
|
+
# Average NLPD across folds (to be minimized)
|
|
146
|
+
return np.mean(nlpds)
|
|
147
|
+
|
|
148
|
+
gp_tuning_study = optuna.create_study(direction='minimize')
|
|
149
|
+
gp_tuning_study.optimize(gp_tuning_objective, n_trials=n_tuning_trials)
|
|
150
|
+
gp_best_params = gp_tuning_study.best_params
|
|
151
|
+
|
|
152
|
+
# Build final GP with best hyperparameters
|
|
153
|
+
nu_values = [0.5, 1.5, 2.5, np.inf]
|
|
154
|
+
length_scale = gp_best_params['length_scale']
|
|
155
|
+
noise_level = gp_best_params['noise_level']
|
|
156
|
+
nu = nu_values[gp_best_params['nu_idx']]
|
|
157
|
+
|
|
158
|
+
kernel = Matern(length_scale=length_scale, nu=nu) + WhiteKernel(noise_level)
|
|
159
|
+
model = GaussianProcessRegressor(
|
|
160
|
+
kernel=kernel,
|
|
161
|
+
alpha=0.0,
|
|
162
|
+
optimizer=None,
|
|
163
|
+
normalize_y=True,
|
|
164
|
+
random_state=42
|
|
165
|
+
)
|
|
166
|
+
model.fit(X_model, y)
|
|
167
|
+
|
|
168
|
+
|
|
169
|
+
# ----- Bayesian optimization (Expected Improvement) -----
|
|
170
|
+
y_best = y.max()
|
|
171
|
+
|
|
172
|
+
def objective(trial):
|
|
173
|
+
numeric_model_features = [f for f in model_features if f.startswith('num__')]
|
|
174
|
+
categorical_model_features = [f for f in model_features if f.startswith('cat__')]
|
|
175
|
+
trial_dict = {}
|
|
176
|
+
for feature in numeric_model_features:
|
|
177
|
+
trial_dict[feature] = [trial.suggest_float(feature, X_model[feature].min(), X_model[feature].max())]
|
|
178
|
+
for feature in categorical_features:
|
|
179
|
+
ohe_group = [ohe_feature for ohe_feature in categorical_model_features if f'cat__{feature}' in ohe_feature]
|
|
180
|
+
trial_category = trial.suggest_categorical(feature, X_df[feature].unique())
|
|
181
|
+
for ohe_feature in ohe_group:
|
|
182
|
+
trial_dict[ohe_feature] = 1 if ohe_feature == f'cat__{feature}_{trial_category}' else 0
|
|
183
|
+
trial_df = pd.DataFrame(trial_dict)
|
|
184
|
+
trial_df = trial_df[model_features]
|
|
185
|
+
|
|
186
|
+
# Predict mean and standard deviation
|
|
187
|
+
mu, sigma = model.predict(trial_df, return_std=True)
|
|
188
|
+
mu = mu[0]
|
|
189
|
+
sigma = sigma[0]
|
|
190
|
+
|
|
191
|
+
# Expected Improvement for maximization
|
|
192
|
+
if sigma <= 0:
|
|
193
|
+
return 0.0
|
|
194
|
+
imp = mu - y_best
|
|
195
|
+
z = imp / sigma
|
|
196
|
+
ei = imp * norm.cdf(z) + sigma * norm.pdf(z)
|
|
197
|
+
return ei
|
|
198
|
+
|
|
199
|
+
study = optuna.create_study(direction="maximize")
|
|
200
|
+
study.optimize(objective, n_trials=n_opt_trials)
|
|
201
|
+
best_params = study.best_params
|
|
202
|
+
|
|
203
|
+
# ----- Reconstruct best numeric values on original scale -----
|
|
204
|
+
best_num_values_scaled = np.array([value for feature, value in best_params.items() if 'num__' in feature]).reshape(1, -1)
|
|
205
|
+
best_num_values = scaler.inverse_transform(best_num_values_scaled)
|
|
206
|
+
opt_params = {}
|
|
207
|
+
for index, feature in enumerate(numeric_features):
|
|
208
|
+
opt_params[feature] = best_num_values[0, index]
|
|
209
|
+
for feature in categorical_features:
|
|
210
|
+
opt_params[feature] = best_params[feature]
|
|
211
|
+
|
|
212
|
+
# ----- Diagnostic plots (if requested) -----
|
|
213
|
+
if plot:
|
|
214
|
+
# True vs predicted with uncertainty
|
|
215
|
+
y_pred, sigma = model.predict(pd.DataFrame(X, columns=model.feature_names_in_), return_std=True)
|
|
216
|
+
plt.figure()
|
|
217
|
+
sc = plt.scatter(y, y_pred, c=sigma, alpha=0.6, cmap='coolwarm', edgecolor='k', linewidth=0.5)
|
|
218
|
+
plt.colorbar(sc, label=f'Prediction std ({output})')
|
|
219
|
+
plt.xlabel(f"True {output}")
|
|
220
|
+
plt.ylabel(f"Predicted {output}")
|
|
221
|
+
plt.title(f"{output}: true vs. predicted (colored by uncertainty)")
|
|
222
|
+
plt.plot([y.min(), y.max()], [y.min(), y.max()], 'k--', linewidth=1)
|
|
223
|
+
plt.tight_layout()
|
|
224
|
+
plt.show()
|
|
225
|
+
|
|
226
|
+
# Permutation importance
|
|
227
|
+
permutation_result = permutation_importance(model, X_model, y, scoring='r2', n_repeats=30, random_state=42)
|
|
228
|
+
importances = permutation_result.importances_mean
|
|
229
|
+
sorted_idx = importances.argsort()[::-1]
|
|
230
|
+
sorted_importances = importances[sorted_idx]
|
|
231
|
+
sorted_features = model.feature_names_in_[sorted_idx]
|
|
232
|
+
plt.figure()
|
|
233
|
+
plt.barh(sorted_features[:15], sorted_importances[:15])
|
|
234
|
+
plt.gca().invert_yaxis()
|
|
235
|
+
plt.xlabel("Permutation importance")
|
|
236
|
+
plt.tight_layout()
|
|
237
|
+
plt.show()
|
|
238
|
+
|
|
239
|
+
# Partial dependence for numeric features: 1D on diagonal, 2D below diagonal
|
|
240
|
+
if numeric_features:
|
|
241
|
+
numeric_model_features = [f for f in model_features if f.startswith('num__')]
|
|
242
|
+
|
|
243
|
+
n_plots = len(numeric_model_features)
|
|
244
|
+
grid_point_number = 25
|
|
245
|
+
|
|
246
|
+
fig, axes = plt.subplots(n_plots, n_plots, figsize=(3*n_plots, 3*n_plots))
|
|
247
|
+
|
|
248
|
+
for i, feature_i in enumerate(numeric_model_features):
|
|
249
|
+
for j, feature_j in enumerate(numeric_model_features):
|
|
250
|
+
ax = axes[i, j] if n_plots > 1 else axes
|
|
251
|
+
|
|
252
|
+
if i == j:
|
|
253
|
+
# 1D partial dependence on diagonal
|
|
254
|
+
part_dep = partial_dependence(
|
|
255
|
+
model,
|
|
256
|
+
X=X_model,
|
|
257
|
+
features=[feature_i],
|
|
258
|
+
custom_values={feature_i: np.linspace(X_model[feature_i].min(), X_model[feature_i].max(), grid_point_number)}
|
|
259
|
+
)
|
|
260
|
+
xi = part_dep['grid_values'][0]
|
|
261
|
+
yi = part_dep['average'][0]
|
|
262
|
+
|
|
263
|
+
# Transform back to original scale for labeling
|
|
264
|
+
original_feature_i = feature_i[5:]
|
|
265
|
+
original_idx_i = numeric_features.index(original_feature_i)
|
|
266
|
+
dummy_array = np.zeros((grid_point_number, len(numeric_features)))
|
|
267
|
+
dummy_array[:, original_idx_i] = xi
|
|
268
|
+
transformed_array = scaler.inverse_transform(dummy_array)
|
|
269
|
+
xi = transformed_array[:, original_idx_i]
|
|
270
|
+
|
|
271
|
+
ax.plot(xi, yi)
|
|
272
|
+
ax.axvline(x=opt_params[original_feature_i], color='black', alpha=0.5)
|
|
273
|
+
ax.set_xlabel(original_feature_i)
|
|
274
|
+
ax.set_ylabel(output)
|
|
275
|
+
|
|
276
|
+
elif j < i:
|
|
277
|
+
# 2D partial dependence below diagonal
|
|
278
|
+
part_dep = partial_dependence(
|
|
279
|
+
model,
|
|
280
|
+
X=X_model,
|
|
281
|
+
features=[feature_i, feature_j],
|
|
282
|
+
custom_values={
|
|
283
|
+
feature_i: np.linspace(X_model[feature_i].min(), X_model[feature_i].max(), grid_point_number),
|
|
284
|
+
feature_j: np.linspace(X_model[feature_j].min(), X_model[feature_j].max(), grid_point_number)
|
|
285
|
+
}
|
|
286
|
+
)
|
|
287
|
+
|
|
288
|
+
xi = part_dep['grid_values'][0]
|
|
289
|
+
yi = part_dep['grid_values'][1]
|
|
290
|
+
zi = part_dep['average'][0].T
|
|
291
|
+
|
|
292
|
+
original_feature_i = feature_i[5:]
|
|
293
|
+
original_feature_j = feature_j[5:]
|
|
294
|
+
|
|
295
|
+
original_idx_i = numeric_features.index(original_feature_i)
|
|
296
|
+
original_idx_j = numeric_features.index(original_feature_j)
|
|
297
|
+
|
|
298
|
+
dummy_array = np.zeros((grid_point_number, len(numeric_features)))
|
|
299
|
+
dummy_array[:, original_idx_i] = xi
|
|
300
|
+
dummy_array[:, original_idx_j] = yi
|
|
301
|
+
|
|
302
|
+
transformed_array = scaler.inverse_transform(dummy_array)
|
|
303
|
+
xi = transformed_array[:, original_idx_i]
|
|
304
|
+
yi = transformed_array[:, original_idx_j]
|
|
305
|
+
|
|
306
|
+
ax.pcolormesh(xi, yi, zi, cmap='coolwarm')
|
|
307
|
+
ax.scatter(opt_params[original_feature_i], opt_params[original_feature_j], color='black', alpha=0.5)
|
|
308
|
+
ax.set_xlabel(original_feature_i)
|
|
309
|
+
ax.set_ylabel(original_feature_j)
|
|
310
|
+
else:
|
|
311
|
+
# Upper triangular part: hide axes
|
|
312
|
+
ax.axis('off')
|
|
313
|
+
fig.tight_layout()
|
|
314
|
+
plt.show()
|
|
315
|
+
|
|
316
|
+
# Partial dependence for categorical features: bar charts per category
|
|
317
|
+
if categorical_features:
|
|
318
|
+
categorical_model_features = [f for f in model_features if f.startswith('cat__')]
|
|
319
|
+
|
|
320
|
+
n_plots = len(categorical_features)
|
|
321
|
+
fig, axes = plt.subplots(n_plots, 1, figsize=(6, 5*n_plots))
|
|
322
|
+
|
|
323
|
+
for i, feature in enumerate(categorical_features):
|
|
324
|
+
ax = axes[i] if n_plots > 1 else axes
|
|
325
|
+
|
|
326
|
+
# Get all one-hot columns for this original feature
|
|
327
|
+
ohe_features = [f for f in categorical_model_features if f.startswith(f'cat__{feature}')]
|
|
328
|
+
|
|
329
|
+
x_labels = []
|
|
330
|
+
y_values = []
|
|
331
|
+
|
|
332
|
+
for ohe_feature in ohe_features:
|
|
333
|
+
part_dep = partial_dependence(model, X=X_model, features=[ohe_feature])
|
|
334
|
+
yi = part_dep['average'][0]
|
|
335
|
+
category = ohe_feature[len(f'cat__{feature}_'):]
|
|
336
|
+
y_values.append(yi[-1]) # partial dependence value for this category
|
|
337
|
+
x_labels.append(category)
|
|
338
|
+
|
|
339
|
+
ax.bar(x_labels, y_values)
|
|
340
|
+
ax.set_xlabel(feature)
|
|
341
|
+
ax.set_ylabel(output)
|
|
342
|
+
ax.tick_params(axis='x', labelrotation=45)
|
|
343
|
+
fig.tight_layout()
|
|
344
|
+
plt.show()
|
|
345
|
+
|
|
346
|
+
# Predict objective at the optimal input
|
|
347
|
+
opt_params_df = pd.DataFrame([opt_params])
|
|
348
|
+
opt_params_input = preprocessor.transform(opt_params_df)
|
|
349
|
+
best_output = model.predict(pd.DataFrame(opt_params_input, columns=model.feature_names_in_))[0]
|
|
350
|
+
|
|
351
|
+
print("\n--- Optimization results ---")
|
|
352
|
+
print(f"Model score (R²): {model.score(pd.DataFrame(X, columns=model.feature_names_in_), y):.4f}")
|
|
353
|
+
print("Best input parameters:")
|
|
354
|
+
for feature, value in opt_params.items():
|
|
355
|
+
print(f" {feature}: {value}")
|
|
356
|
+
print(f"Best predicted {output}: {best_output:.4f}")
|
|
357
|
+
|
|
358
|
+
# ----- Return all important objects -----
|
|
359
|
+
return {
|
|
360
|
+
'best_params': opt_params,
|
|
361
|
+
'best_value': best_output,
|
|
362
|
+
'model': model,
|
|
363
|
+
'preprocessor': preprocessor,
|
|
364
|
+
'X': X,
|
|
365
|
+
'y': y
|
|
366
|
+
}
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import pandas as pd
|
|
2
|
+
import numpy as np
|
|
3
|
+
import matplotlib.pyplot as plt
|
|
4
|
+
from scipy.stats import qmc
|
|
5
|
+
|
|
6
|
+
def qmc_sampling(parameters, n_points, force_edges=True):
|
|
7
|
+
"""
|
|
8
|
+
Generate a Quasi-Monte Carlo (QMC) sample using the Halton sequence.
|
|
9
|
+
|
|
10
|
+
This function creates a multidimensional sample based on the provided parameter
|
|
11
|
+
definitions, scaling each dimension to its specified bounds and types.
|
|
12
|
+
|
|
13
|
+
Parameters
|
|
14
|
+
----------
|
|
15
|
+
parameters : dict
|
|
16
|
+
A dictionary defining the search space. Each key is a parameter name,
|
|
17
|
+
and the value is another dict containing:
|
|
18
|
+
- 'type': str ('float', 'int', or 'categoric')
|
|
19
|
+
- 'l_bound': float/int (required for numeric types)
|
|
20
|
+
- 'u_bound': float/int (required for numeric types)
|
|
21
|
+
- 'categories': list (required for 'categoric' type)
|
|
22
|
+
n_points : int
|
|
23
|
+
The number of samples to generate.
|
|
24
|
+
force_edges : bool, default=True
|
|
25
|
+
If True, the samples are rescaled so that the minimum and maximum
|
|
26
|
+
generated values exactly match the specified bounds.
|
|
27
|
+
If False, values are kept in the [0, 1] probability space before scaling.
|
|
28
|
+
|
|
29
|
+
Returns
|
|
30
|
+
-------
|
|
31
|
+
pd.DataFrame
|
|
32
|
+
A DataFrame where each column corresponds to a parameter and each
|
|
33
|
+
row is a generated sample point.
|
|
34
|
+
"""
|
|
35
|
+
sampler = qmc.Halton(d=len(parameters), seed=42)
|
|
36
|
+
sample = sampler.random(n=n_points)
|
|
37
|
+
|
|
38
|
+
sample_dict = {}
|
|
39
|
+
for index, p in enumerate(parameters.keys()):
|
|
40
|
+
|
|
41
|
+
values = sample[:, index]
|
|
42
|
+
|
|
43
|
+
if force_edges:
|
|
44
|
+
v_min, v_max = values.min(), values.max()
|
|
45
|
+
elif force_edges==False:
|
|
46
|
+
v_min, v_max = 0.0, 1.0
|
|
47
|
+
else:
|
|
48
|
+
return print('forced_edges must be True or False')
|
|
49
|
+
|
|
50
|
+
p_type = parameters[p]['type']
|
|
51
|
+
|
|
52
|
+
if p_type == 'float':
|
|
53
|
+
l_bound = parameters[p]['l_bound']
|
|
54
|
+
u_bound = parameters[p]['u_bound']
|
|
55
|
+
scaled = l_bound + (values - v_min) * (u_bound - l_bound) / (v_max - v_min)
|
|
56
|
+
sample_dict[p] = scaled
|
|
57
|
+
|
|
58
|
+
elif p_type == 'int':
|
|
59
|
+
l_bound = parameters[p]['l_bound']
|
|
60
|
+
u_bound = parameters[p]['u_bound']
|
|
61
|
+
scaled = l_bound + (values - v_min) * (u_bound - l_bound) / (v_max - v_min)
|
|
62
|
+
scaled = np.round(scaled).astype(int)
|
|
63
|
+
sample_dict[p] = scaled
|
|
64
|
+
|
|
65
|
+
elif p_type == 'categoric':
|
|
66
|
+
v_min, v_max = values.min(), values.max()
|
|
67
|
+
categories = parameters[p]['categories']
|
|
68
|
+
l_bound = 0
|
|
69
|
+
u_bound = len(categories)-1
|
|
70
|
+
scaled = l_bound + (values - v_min) * (u_bound - l_bound) / (v_max - v_min)
|
|
71
|
+
scaled = np.round(scaled).astype(int).tolist()
|
|
72
|
+
scaled = [categories[v] for v in scaled]
|
|
73
|
+
sample_dict[p] = scaled
|
|
74
|
+
else:
|
|
75
|
+
return print(f'{p}: type must be float, int or categoric')
|
|
76
|
+
df = pd.DataFrame(sample_dict)
|
|
77
|
+
return df
|
|
78
|
+
|
|
79
|
+
def plot_qmc_sampling(df):
|
|
80
|
+
"""
|
|
81
|
+
Create a scatter plot matrix of the numerical variables in the sample.
|
|
82
|
+
|
|
83
|
+
This visualizes the space-filling properties of the QMC sampling by
|
|
84
|
+
plotting each numeric parameter against every other numeric parameter
|
|
85
|
+
in a grid of subplots.
|
|
86
|
+
|
|
87
|
+
Parameters
|
|
88
|
+
----------
|
|
89
|
+
df : pd.DataFrame
|
|
90
|
+
The DataFrame generated by `qmc_sampling`.
|
|
91
|
+
"""
|
|
92
|
+
num_df = df.select_dtypes(include='number')
|
|
93
|
+
n_vars = len(num_df.columns)
|
|
94
|
+
|
|
95
|
+
plt.figure(figsize=(3*n_vars, 3*n_vars))
|
|
96
|
+
plot = 1
|
|
97
|
+
for i, var_1 in enumerate(num_df.columns):
|
|
98
|
+
for j, var_2 in enumerate(num_df.columns):
|
|
99
|
+
if i >= j:
|
|
100
|
+
plt.subplot(n_vars, n_vars, plot)
|
|
101
|
+
plt.scatter(num_df[var_1], num_df[var_2])
|
|
102
|
+
plt.xlabel(var_1)
|
|
103
|
+
plt.ylabel(var_2)
|
|
104
|
+
plot +=1
|
|
105
|
+
plt.tight_layout()
|
|
106
|
+
plt.show()
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: chembayes
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Experimental design and Bayesian optimization with Gaussian Processes and QMC sampling
|
|
5
|
+
Author-email: Jesus Alberto Martin del Campo <j.a.martin-campo@hotmail.com>
|
|
6
|
+
Project-URL: Homepage, https://github.com/jesusalmartin/chembayes
|
|
7
|
+
Project-URL: Bug Tracker, https://github.com/jesusalmartin/chembayes/issues
|
|
8
|
+
Keywords: bayesian-optimization,gaussian-processes,experimental-design,qmc,sampling,chemistry
|
|
9
|
+
Classifier: Programming Language :: Python :: 3
|
|
10
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
11
|
+
Classifier: Operating System :: OS Independent
|
|
12
|
+
Classifier: Topic :: Scientific/Engineering :: Chemistry
|
|
13
|
+
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
|
|
14
|
+
Requires-Python: >=3.9
|
|
15
|
+
Description-Content-Type: text/markdown
|
|
16
|
+
License-File: LICENSE
|
|
17
|
+
Requires-Dist: numpy>=1.21
|
|
18
|
+
Requires-Dist: pandas>=1.3
|
|
19
|
+
Requires-Dist: scikit-learn>=1.0
|
|
20
|
+
Requires-Dist: optuna>=3.0
|
|
21
|
+
Requires-Dist: matplotlib>=3.4
|
|
22
|
+
Requires-Dist: scipy>=1.7
|
|
23
|
+
Dynamic: license-file
|
|
24
|
+
|
|
25
|
+
# ChemBayes
|
|
26
|
+
|
|
27
|
+
**ChemBayes** is a Python library designed to streamline **experimental design (DoE)** and **Bayesian optimization** for scientific and chemical research. It leverages Gaussian Processes to model complex response surfaces and efficiently identify optimal conditions.
|
|
28
|
+
|
|
29
|
+
## 🚀 Key Features
|
|
30
|
+
|
|
31
|
+
* **Intelligent Sampling (QMC):** Generate initial experimental designs using Halton sequences (Quasi-Monte Carlo) for optimal space-filling coverage.
|
|
32
|
+
* **Automatic Preprocessing:** Integrated handling of numeric variables (scaling) and categorical variables (one-hot encoding).
|
|
33
|
+
* **Hyperparameter Tuning:** Automated Gaussian Process parameter adjustment (Matern + WhiteKernel) using Leave-One-Out (LOO) cross-validation and NLPD loss.
|
|
34
|
+
* **Bayesian Optimization:** Global maximum search using the Expected Improvement (EI) acquisition function.
|
|
35
|
+
* **Visual Diagnostics:** Automated generation of feature importance, partial dependence plots (1D and 2D), and model validation charts.
|
|
36
|
+
|
|
37
|
+
## 📦 Installation
|
|
38
|
+
|
|
39
|
+
From the project root (where `pyproject.toml` is located):
|
|
40
|
+
|
|
41
|
+
```bash
|
|
42
|
+
pip install .
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
Or for development mode:
|
|
46
|
+
|
|
47
|
+
```bash
|
|
48
|
+
pip install -e .
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
## 🛠️ Quick Start
|
|
52
|
+
|
|
53
|
+
### 1. Generating an Experimental Design (Sampling)
|
|
54
|
+
|
|
55
|
+
Define your search space with numeric and categorical parameters:
|
|
56
|
+
|
|
57
|
+
```python
|
|
58
|
+
import chembayes as cb
|
|
59
|
+
|
|
60
|
+
parameters = {
|
|
61
|
+
'temperature': {'type': 'float', 'l_bound': 20.0, 'u_bound': 100.0},
|
|
62
|
+
'time': {'type': 'int', 'l_bound': 5, 'u_bound': 60},
|
|
63
|
+
'catalyst': {'type': 'categoric', 'categories': ['Pd', 'Ni', 'Cu']}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
# Generate 20 optimal experimental points
|
|
67
|
+
df_experiments = cb.qmc_sampling(parameters, n_points=20)
|
|
68
|
+
|
|
69
|
+
# Visualize the distribution
|
|
70
|
+
cb.plot_qmc_sampling(df_experiments)
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
### 2. Bayesian Optimization
|
|
74
|
+
|
|
75
|
+
Once you have experimental data, find the optimal conditions:
|
|
76
|
+
|
|
77
|
+
```python
|
|
78
|
+
# Define input features and the target column
|
|
79
|
+
inputs = ['temperature', 'time', 'catalyst']
|
|
80
|
+
output = 'yield'
|
|
81
|
+
|
|
82
|
+
# Run the complete optimization pipeline
|
|
83
|
+
results = cb.optimize_experiment(
|
|
84
|
+
df=df_data,
|
|
85
|
+
inputs=inputs,
|
|
86
|
+
output=output,
|
|
87
|
+
n_tuning_trials=50,
|
|
88
|
+
n_opt_trials=100
|
|
89
|
+
)
|
|
90
|
+
|
|
91
|
+
# Access the best found parameters
|
|
92
|
+
print(f"Best configuration: {results['best_params']}")
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
## 📂 Project Structure
|
|
96
|
+
|
|
97
|
+
* `src/chembayes/sampler.py`: Tools for QMC sample generation.
|
|
98
|
+
* `src/chembayes/optimizer.py`: Bayesian optimization engine and Gaussian Processes.
|
|
99
|
+
* `src/chembayes/__init__.py`: Public API exposure.
|
|
100
|
+
* `pyproject.toml`: Modern package configuration and dependencies.
|
|
101
|
+
|
|
102
|
+
## ✉️ Contact & Contribution
|
|
103
|
+
|
|
104
|
+
**Author:** Jesus Alberto Martin del Campo
|
|
105
|
+
**Email:** j.a.martin-campo@hotmail.com
|
|
106
|
+
**GitHub:** [jesusalmartin/chembayes](https://github.com/jesusalmartin/chembayes)
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
LICENSE
|
|
2
|
+
README.md
|
|
3
|
+
pyproject.toml
|
|
4
|
+
src/chembayes/__init__.py
|
|
5
|
+
src/chembayes/optimizer.py
|
|
6
|
+
src/chembayes/sampler.py
|
|
7
|
+
src/chembayes.egg-info/PKG-INFO
|
|
8
|
+
src/chembayes.egg-info/SOURCES.txt
|
|
9
|
+
src/chembayes.egg-info/dependency_links.txt
|
|
10
|
+
src/chembayes.egg-info/requires.txt
|
|
11
|
+
src/chembayes.egg-info/top_level.txt
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
chembayes
|