diff --git a/notebooks_community/FuRBO/FuRBO.ipynb b/notebooks_community/FuRBO/FuRBO.ipynb
new file mode 100644
index 0000000000..c8362d42e3
--- /dev/null
+++ b/notebooks_community/FuRBO/FuRBO.ipynb
@@ -0,0 +1,1786 @@
+{
+ "cells": [
+ {
+ "cell_type": "markdown",
+ "id": "b5831947-283e-4682-aae4-bd19bcce03e0",
+ "metadata": {},
+ "source": [
+ "# Feasibility-driven trust Region Bayesian Optimization (FuRBO)\n",
+ "\n",
+ "- Contributors: paoloascia, elenaraponi\n",
+ "- Last update 19 December 2025\n",
+ "- BoTorch version: 0.16.1\n",
+ "\n",
+ "In this tutorial, we show how to implement the Feasibility-driven trust Region Bayesian Optimization (FuRBO) [1] algorithm in a closed loop, with restarts. This is a Bayesian optimization (BO) algorithm developed specifically to handle severely constrained problems, while still performing well in simpler settings. \n",
+ "\n",
+ "The key feature of FuRBO is the new definition of the trust region. At each iteration, we define the trust regions as a hyper-rectangle encapsulating subregions of the search space predicted to be promising by the Gaussian process regression (GPR) models of the objective and constraints. Compared to other trust-region-based methods, such as Scalable Constrained Bayesian Optimization (SCBO) [2], FuRBO offers higher flexibility in how the trust region evolves. Its position and shape adapt dynamically to the regions predicted to be both feasible and optimal, allowing the search to align with the structure of the GP models.\n",
+ "\n",
+ "In case of mildly constrained scenarios, the new definition of the trust region is advantageous when several samples are evaluated at each iteration (batches). \n",
+ "\n",
+ "Therefore, we recommend using FuRBO when solving:\n",
+ " - high-dimensional constrained black-box problems;\n",
+ " - severely constrained black-box problems of any dimension D;\n",
+ " - constrained problems evaluated with large batch sizes (i.e., bigger than 1D).\n",
+ "\n",
+ "[1] [Paolo Ascia, Elena Raponi, Thomas Bäck and Fabian Duddeck. \"Feasibility-Driven Trust Region Bayesian Optimization.\" In AutoML 2025 Methods Track.](https://doi.org/10.48550/arXiv.2506.14619)\n",
+ "\n",
+ "[2] [David Eriksson and Matthias Poloczek. Scalable constrained Bayesian optimization. In International Conference on Artificial Intelligence and Statistics, pages 730–738. PMLR, 2021.](https://doi.org/10.48550/arxiv.2002.08526)\n"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "07b7f421",
+ "metadata": {},
+ "source": [
+ ""
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "ba4aa821",
+ "metadata": {},
+ "source": [
+ "## Tutorial on FuRBO\n",
+ "\n",
+ "Tho show the implementation of FuRBO, we use a 20D Ackley function on the domain $[−5,10]^{10}$ subject to two constraint functions $c_1$ and $c_2$. The problem maximizes the Ackley function under the constraints $c_1(x) \\leq 0$ and $c_2(x) \\leq 0$. The Ackley function is translated in every dimension, so that the optimum of the unconstrained problem lies outside of the feasible area. Since this problem presents only two constraints, we showcase the performance with a batch of $q = 3D = 30$."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "ba051b56",
+ "metadata": {},
+ "source": [
+ "### Objective function\n",
+ "\n",
+ "In this block, we define a handle to evaluate the objective function."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 3,
+ "id": "890f1a54-b6cf-4af4-9bfb-1835b2f737a6",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "import torch\n",
+ "from botorch.test_functions import Ackley\n",
+ "from botorch.utils.transforms import unnormalize\n",
+ "\n",
+ "import warnings\n",
+ "\n",
+ "# Setting up the device\n",
+ "device = torch.device(\"cuda\" if torch.cuda.is_available() else \"cpu\")\n",
+ "dtype = torch.double\n",
+ "tkwargs = {\"device\": device, \"dtype\": dtype}\n",
+ "\n",
+ "warnings.filterwarnings(\"ignore\")\n",
+ "\n",
+ "# Defining objective function\n",
+ "fun = Ackley(dim=10, negate=True).to(**tkwargs)\n",
+ "fun.bounds[0, :].fill_(-5)\n",
+ "fun.bounds[1, :].fill_(10)\n",
+ "\n",
+ "def eval_objective(x):\n",
+ " \"\"\"This is a helper function we use to unnormalize and evalaute a point\"\"\"\n",
+ " return fun(unnormalize(x, fun.bounds))\n"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "6b710672-51d0-4fc5-a3e2-ffb3da6f5649",
+ "metadata": {},
+ "source": [
+ "### Constraint functions\n",
+ "\n",
+ "The problem is constrained by two functions, $c_1$ and $c_2$. In this block, we define the constriant functions and a handle to call when evaluating the constraints."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "64122b23-fc01-4e1a-94de-2fba4839fe72",
+ "metadata": {},
+ "source": [
+ "\n",
+ "1. Constraint $c_1$: enforce the $\\sum_{i=1}^{10} x_i \\leq 0$. "
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 4,
+ "id": "228d816a-6452-4078-b3fd-6a42569237c3",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "def c1(x):\n",
+ " return x.sum()"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "08472c8a-67de-4206-bf0f-871e40edfe7c",
+ "metadata": {},
+ "source": [
+ "2. Constraint $c_2$: enforce the $l_2$ norm $\\| \\mathbb{x}\\|_2 \\leq 0.5$."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 5,
+ "id": "1e0f4e8d-657f-49d8-bb59-eb8c2ff4a9f5",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "def c2(x):\n",
+ " return torch.norm(x, p=2) - 5\n",
+ " \n",
+ "def eval_constraints(x):\n",
+ " \"\"\"This is a helper function we use to unnormalize and evalaute a point on the constraints\"\"\"\n",
+ " return Tensor([c1(unnormalize(x - 0.3, fun.bounds)), c2(unnormalize(x - 0.3, fun.bounds))])"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "e3c6e1cd-15de-4985-ac6c-bf96c6eafc24",
+ "metadata": {},
+ "source": [
+ "### FuRBO Class\n",
+ "We define a class to hold the information needed for the optimization loop. \n",
+ "\n",
+ "The state is updated with the samples evaluated at each iteration. Therefore, the class presents a method for self-updating.\n",
+ "\n",
+ "Prior to the class, two utility functions are defined. The first one identifies the current best sample, while the second one fits a GPR model to the current dataset. \n",
+ "\n",
+ "The ```FurboState``` class features a function to reset the status when restarting. Notice that the state is emptied when restarting. Therefore the samples previously evaluated are extracted and saved (see main optimization loop)."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 6,
+ "id": "020338e2-9eaf-49cf-9e5c-fd00e1a46835",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "import gpytorch\n",
+ "\n",
+ "from botorch.fit import fit_gpytorch_mll\n",
+ "\n",
+ "from botorch.models import SingleTaskGP\n",
+ "from botorch.models.transforms.outcome import Standardize\n",
+ "from botorch.models.transforms import Normalize\n",
+ "from botorch.models.model_list_gp_regression import ModelListGP\n",
+ "\n",
+ "from gpytorch.constraints import Interval\n",
+ "from gpytorch.kernels import MaternKernel, ScaleKernel\n",
+ "from gpytorch.likelihoods import GaussianLikelihood\n",
+ "from gpytorch.mlls import ExactMarginalLogLikelihood\n",
+ "\n",
+ "from torch.quasirandom import SobolEngine\n",
+ "\n",
+ "from torch import Tensor\n",
+ "\n",
+ "def get_best_index_for_batch(n_tr, Y: Tensor, C: Tensor):\n",
+ " \"\"\"Return the index for the best point. One for each trust region.\n",
+ " For reference, see https://botorch.org/docs/tutorials/scalable_constrained_bo/\"\"\"\n",
+ " is_feas = (C <= 0).all(dim=-1)\n",
+ " if is_feas.any(): # Choose best feasible candidate\n",
+ " score = Y.clone()\n",
+ " score[~is_feas] = -float(\"inf\")\n",
+ " return torch.topk(score.reshape(-1), k=n_tr).indices\n",
+ " return torch.topk(C.clamp(min=0).sum(dim=-1), k=n_tr, largest=False).indices # Return smallest violation\n",
+ "\n",
+ "def get_fitted_model(X, Y, dim):\n",
+ " '''Function to fit a GPR to a given set of data.\n",
+ " For reference, see https://botorch.org/docs/tutorials/scalable_constrained_bo/'''\n",
+ " likelihood = GaussianLikelihood(noise_constraint=Interval(1e-8, 1e-3))\n",
+ " covar_module = ScaleKernel( # Use the same lengthscale prior as in the TuRBO paper\n",
+ " MaternKernel(nu=2.5, ard_num_dims=dim, lengthscale_constraint=Interval(0.005, 4.0))\n",
+ " )\n",
+ " model = SingleTaskGP(\n",
+ " X,\n",
+ " Y,\n",
+ " covar_module=covar_module,\n",
+ " likelihood=likelihood,\n",
+ " outcome_transform=Standardize(m=1)\n",
+ " )\n",
+ " mll = ExactMarginalLogLikelihood(model.likelihood, model)\n",
+ "\n",
+ " fit_gpytorch_mll(mll)\n",
+ "\n",
+ " return model\n",
+ "\n",
+ "class FurboState():\n",
+ " '''\n",
+ " Class to track optimization state and update it with newly evaluated samples\n",
+ "\n",
+ " Args:\n",
+ " fcn: objective function class\n",
+ " batch_size: batch size\n",
+ " n_init: number of initial points to evaluate\n",
+ " n_iteration: number of total iterations\n",
+ " \n",
+ " '''\n",
+ " # Initialization of the status\n",
+ " def __init__(self, fcn, batch_size, n_init, max_budget, **tkwargs):\n",
+ " \n",
+ " # Domain bounds\n",
+ " self.lb, self.ub = fcn.bounds\n",
+ " self.bounds = fcn.bounds\n",
+ " \n",
+ " # Problem dimensions\n",
+ " self.batch_size: int = batch_size # Dimension of the batch at each iteration\n",
+ " self.n_init: int = n_init # Number of initial samples\n",
+ " self.dim: int = fcn.dim # Dimension of the problem\n",
+ " \n",
+ " # Trust regions information\n",
+ " self.tr_ub: float = torch.ones((1, self.dim), **tkwargs) # Upper bounds of trust region\n",
+ " self.tr_lb: float = torch.zeros((1, self.dim), **tkwargs) # Lower bounds of trust region\n",
+ " self.tr_vol: float = torch.prod(self.tr_ub - self.tr_lb, dim=1) # Volume of trust region\n",
+ " self.radius: float = 1.0 # Percentage around which the trust region is built\n",
+ "\n",
+ " # Trust region updating \n",
+ " self.failure_counter: int = 0 # Counter for failure points to asses how algorithm is going\n",
+ " self.success_counter: int = 0 # Counter for success points to asses how algorithm is going\n",
+ " self.success_tolerance: int = 2 # Success tolerance for \n",
+ " self.failure_tolerance: int = 3 # Failure tolerance for\n",
+ " \n",
+ " # Tensor to save current batch information\n",
+ " self.batch_X: Tensor # Current batch to evaluate: X values\n",
+ " self.batch_Y: Tensor # Current batch to evaluate: Y value\n",
+ " self.batch_C: Tensor # Current batch to evaluate: C values\n",
+ " \n",
+ " # Stopping criteria information\n",
+ " self.it_counter: int = 0 # Counter for iterations\n",
+ " self.n_counter: int = 0 # Counter for sampled evaluated\n",
+ " self.max_budget = max_budget # Maximum number of samples allowed to be evaluated\n",
+ " self.finish_trigger: bool = False # Trigger to stop optimization\n",
+ " \n",
+ " # Restart criteria information\n",
+ " self.radius_min: float = 0.5**9 # Minimum percentage for trust region\n",
+ " self.restart_trigger: bool = False # Trigger to stop optimization\n",
+ " \n",
+ " # Sobol sampler engine\n",
+ " self.sobol = SobolEngine(dimension=self.dim, scramble=True)\n",
+ " \n",
+ " # Update the status\n",
+ " def update(self, X_next, Y_next, C_next, **tkwargs):\n",
+ " '''\n",
+ " Function to update optimization status\n",
+ " \n",
+ " Args:\n",
+ " X_next: samples X (input values) to update the status\n",
+ " Y_next: samples Y (objective value) to update the status\n",
+ " C_next: Samples C (constraints values) to update the status\n",
+ "\n",
+ " '''\n",
+ " \n",
+ " # Merge current batch with previously evaluated samples\n",
+ " if not hasattr(self, 'X'):\n",
+ " # If there are no previous samples, declare the Tensors\n",
+ " self.X = X_next\n",
+ " self.Y = Y_next\n",
+ " self.C = C_next\n",
+ " else:\n",
+ " # Else, concatenate the new batch to the previous samples\n",
+ " self.X = torch.cat((self.X, X_next), dim=0)\n",
+ " self.Y = torch.cat((self.Y, Y_next), dim=0)\n",
+ " self.C = torch.cat((self.C, C_next), dim=0)\n",
+ "\n",
+ " # update GPR surrogates\n",
+ " self.Y_model = get_fitted_model(self.X, self.Y, self.dim)\n",
+ " self.C_model = ModelListGP(*[get_fitted_model(self.X, C.reshape(-1, 1), self.dim) for C in self.C.t()])\n",
+ " \n",
+ " # Update batch information \n",
+ " self.batch_X = X_next\n",
+ " self.batch_Y = Y_next\n",
+ " self.batch_C = C_next\n",
+ " \n",
+ " # Update best value\n",
+ " # Find the best value among the candidates\n",
+ " best_id = get_best_index_for_batch(n_tr=1, Y=self.Y, C=self.C)\n",
+ " \n",
+ " # Update success and failure counters for trust region update\n",
+ " # If attribute 'best_X' does not exist, DoE was just evaluated -> no update on counters\n",
+ " if hasattr(self, 'best_X'):\n",
+ " if (self.C[best_id] <= 0).all():\n",
+ " # At least one new candidate is feasible\n",
+ " if (self.Y[best_id] > self.best_Y).any() or (self.best_C > 0).any():\n",
+ " self.success_counter += 1\n",
+ " self.failure_counter = 0 \n",
+ " else:\n",
+ " self.success_counter = 0\n",
+ " self.failure_counter += 1\n",
+ " else:\n",
+ " # No new candidate is feasible\n",
+ " total_violation_next = self.C[best_id].clamp(min=0).sum(dim=-1)\n",
+ " total_violation_center = self.best_C.clamp(min=0).sum(dim=-1)\n",
+ " if total_violation_next < total_violation_center:\n",
+ " self.success_counter += 1\n",
+ " self.failure_counter = 0\n",
+ " else:\n",
+ " self.success_counter = 0\n",
+ " self.failure_counter += 1\n",
+ " \n",
+ " # Update best values\n",
+ " self.best_X = self.X[best_id]\n",
+ " self.best_Y = self.Y[best_id]\n",
+ " self.best_C = self.C[best_id]\n",
+ " \n",
+ " # Update iteration counter\n",
+ " self.it_counter += 1\n",
+ " self.n_counter += len(Y_next)\n",
+ " \n",
+ " def reset_status(self, **tkwargs):\n",
+ " '''Function to reset the status for the restart'''\n",
+ " \n",
+ " # Reset trust regions size\n",
+ " self.tr_ub: float = torch.ones((1, self.dim), **tkwargs) # Upper bounds of trust region\n",
+ " self.tr_lb: float = torch.zeros((1, self.dim), **tkwargs) # Lower bounds of trust region\n",
+ " self.tr_vol: float = torch.prod(self.tr_ub - self.tr_lb, dim=1) # Volume of trust region\n",
+ " self.radius: float = 1.0 # Percentage around which the trust region is built\n",
+ " self.radius_min: float = 0.5**7 # Minimum percentage for trust region\n",
+ "\n",
+ " # Reset counters to change trust region size \n",
+ " self.failure_counter: int = 0 # Counter of failure points to asses how algorithm is going\n",
+ " self.success_counter: int = 0 # Counter of success points to asses how algorithm is going\n",
+ " \n",
+ " # Reset restart criteria trigger\n",
+ " self.restart_trigger: bool = False # Trigger to restart optimization\n",
+ " \n",
+ " # Delete tensors with samples for training GPRs\n",
+ " if hasattr(self, 'X'):\n",
+ " del self.X\n",
+ " del self.Y\n",
+ " del self.C\n",
+ " \n",
+ " # Delete tensors with best value so far\n",
+ " if hasattr(self, 'best_X'):\n",
+ " del self.best_X\n",
+ " del self.best_Y\n",
+ " del self.best_C\n",
+ " \n",
+ " # Clear GPU memory\n",
+ " if tkwargs[\"device\"] == \"cuda\":\n",
+ " torch.cuda.empty_cache() "
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "58fee9fb-ca01-4024-8859-1fc3d9d4aea4",
+ "metadata": {},
+ "source": [
+ "### Trust region\n",
+ "\n",
+ "In this block contains the trust region definition, according to the following steps:\n",
+ "\n",
+ "1. Sample GPR surrogates
\n",
+ " - Draw ```n_samples``` with a uniform distribution in a sphere centred in $x_{best}$ and of radius $\\mathcal{R}$
\n",
+ " - Evaluate the samples on the GPR surrogates
\n",
+ "2. Rank samples
\n",
+ " - Rank the samples based on optimality and feasibility:
\n",
+ " - first come all feasible samples in order of optimality
\n",
+ " - second come the infeasible samples ranked based on the total violation
\n",
+ "3. Define trust region
\n",
+ " - Select the top P% ranked samples
\n",
+ " - Find the samllest hyper-rectangle that includes all selected samples
\n",
+ "\n",
+ "Step 1 is performed by the function ```multivariate_circular```. ```update_tr``` calls ```multivariate_circular``` and performs steps 2 and 3.\n",
+ "\n",
+ "This definition yields two main properties:\n",
+ "1. The trust region can jump across the entire domain since it is defined based on the posterior of the GPR models instead of the current best evaluated sample, as SCBO[2] or TuRBO[3].\n",
+ "2. The trust region shape adapts to the most promising area according to the GPR models of objective and constraints, e.g., if the promising area is narrow and long, the trust region will also be narrow and long. \n",
+ "\n",
+ "Note that the trust region is defined with its sides parallel to the axes. Further improvements could be expected by allowing the trust region to rotate and allign with the feasible area.\n",
+ "\n",
+ "[2] [David Eriksson and Matthias Poloczek. Scalable constrained Bayesian optimization. In International Conference on Artificial Intelligence and Statistics, pages 730–738. PMLR, 2021.](https://doi.org/10.48550/arxiv.2002.08526)\n",
+ "\n",
+ "[3] [David Eriksson, Michael Pearce, Jacob Gardner, Ryan D Turner, Matthias Poloczek. Scalable global optimization via local Bayesian optimization. Advances in Neural Information Processing Systems. 2019](https://proceedings.neurips.cc/paper_files/paper/2019/file/6c990b7aca7bc7058f5e98ea909e924b-Paper.pdf)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 7,
+ "id": "3d36cf01-c5be-44ee-96bb-12564225bd7b",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "def multivariate_circular(centre, radius, n_samples, lb = None, ub = None, **tkwargs):\n",
+ " '''\n",
+ " Function to generate distribution of given radius and centre within a given domain.\n",
+ " \n",
+ " Args:\n",
+ " centre: centre of the hypersphere\n",
+ " radius: radius of the hypersphere\n",
+ " n_samples: number of samples to evaluate\n",
+ " lb: (optional) domain lower bound\n",
+ " ub: (optional) domain upper bound\n",
+ "\n",
+ " Return: \n",
+ " samples: samples generated inside the radius given\n",
+ " \n",
+ " '''\n",
+ " # Dimension of the design domain\n",
+ " dim = centre.shape[0]\n",
+ " \n",
+ " # Generate a multivariate normal distribution centered at 0\n",
+ " multivariate_normal = torch.distributions.multivariate_normal.MultivariateNormal(torch.zeros(dim, **tkwargs), 0.025*torch.eye(dim, **tkwargs))\n",
+ " \n",
+ " # Draw samples torch.distributions.multivariate_normal import MultivariateNormal\n",
+ " samples = multivariate_normal.sample(sample_shape=torch.Size([n_samples]))\n",
+ " \n",
+ " # Normalize each sample to have unit norm, then scale by the radius\n",
+ " norms = torch.norm(samples, dim=1, keepdim=True) # Euclidean norms\n",
+ " normalized_samples = samples / norms # Normalize to unit hypersphere\n",
+ " scaled_samples = normalized_samples * torch.rand(n_samples, 1, **tkwargs) * radius # Scale by random factor within radius\n",
+ " \n",
+ " # Translate samples to be centered at centre\n",
+ " samples = scaled_samples + centre\n",
+ "\n",
+ " # Trim samples outside domain\n",
+ " for dim in range(len(lb)):\n",
+ " samples = samples[torch.where(samples[:,dim]>=lb[dim])]\n",
+ " samples = samples[torch.where(samples[:,dim]<=ub[dim])]\n",
+ " \n",
+ " return samples\n",
+ "\n",
+ "def update_tr(state, percentage = 0.1, **tkwargs):\n",
+ " '''\n",
+ " Function to sample Multinormal Distribution of GPRs and define trust region\n",
+ " \n",
+ " Args:\n",
+ " state: FurboState object\n",
+ " percentage: percentage of inspectors defining the trust region\n",
+ " \n",
+ " Return:\n",
+ " state: updated FurboState with new trust region\n",
+ "\n",
+ " '''\n",
+ " # Update the trust regions based on the feasible region\n",
+ " n_samples = 1000 * state.dim\n",
+ " lb = torch.zeros(state.dim, **tkwargs)\n",
+ " ub = torch.ones(state.dim, **tkwargs)\n",
+ " \n",
+ " # Update radius dimension\n",
+ " if state.success_counter == state.success_tolerance: # Expand trust region\n",
+ " state.radius = min(2.0 * state.radius, 1.0)\n",
+ " state.success_counter = 0\n",
+ " elif state.failure_counter == state.failure_tolerance: # Shrink trust region\n",
+ " state.radius /= 2.0\n",
+ " state.failure_counter = 0\n",
+ " \n",
+ " for ind, x_candidate in enumerate(state.best_X):\n",
+ " # Generate the samples to evaluathe the feasible area on\n",
+ " radius = state.radius\n",
+ " samples = multivariate_circular(x_candidate, radius, n_samples, lb=lb, ub=ub, **tkwargs)\n",
+ " \n",
+ " # Evaluate samples on the models of the objective -> yy Tensor\n",
+ " with torch.no_grad():\n",
+ " posterior = state.Y_model.posterior(samples)\n",
+ " samples_yy = posterior.mean.squeeze()\n",
+ " \n",
+ " # Evaluate samples on the models of the constraints -> yy Tensor\n",
+ " with torch.no_grad():\n",
+ " posterior = state.C_model.posterior(samples)\n",
+ " samples_cc = posterior.mean\n",
+ " \n",
+ " # Combine the constraints values\n",
+ " # Normalize\n",
+ " samples_cc /= torch.abs(samples_cc).max(dim=0).values\n",
+ " samples_cc = torch.max(samples_cc, dim=1).values\n",
+ " \n",
+ " # Take the best X% of the drawn samples to define the trust region\n",
+ " n_samples_tr = int(n_samples * percentage)\n",
+ " \n",
+ " # Order the samples for feasibility and for best objective\n",
+ " if torch.any(samples_cc < 0):\n",
+ " \n",
+ " feasible_samples_id = torch.where(samples_cc <= 0)[0]\n",
+ " infeasible_samples_id = torch.where(samples_cc > 0)[0]\n",
+ " \n",
+ " feasible_cc = -1 * samples_yy[feasible_samples_id]\n",
+ " infeasible_cc = samples_cc[infeasible_samples_id]\n",
+ " \n",
+ " feasible_sorted, feasible_sorted_id = torch.sort(feasible_cc)\n",
+ " infeasible_sorted, infeasible_sorted_id = torch.sort(infeasible_cc)\n",
+ " \n",
+ " original_feasible_sorted_indices = feasible_samples_id[feasible_sorted_id]\n",
+ " original_infeasible_sorted_indices = infeasible_samples_id[infeasible_sorted_id]\n",
+ " \n",
+ " top_indices = torch.cat((original_feasible_sorted_indices, original_infeasible_sorted_indices))[:n_samples_tr]\n",
+ " \n",
+ " # If no feasible point is found\n",
+ " else:\n",
+ " \n",
+ " if n_samples_tr > len(samples_cc):\n",
+ " n_samples_tr = len(samples_cc)\n",
+ " \n",
+ " if n_samples_tr < 4:\n",
+ " n_samples_tr = 4\n",
+ " \n",
+ " top_values, top_indices = torch.topk(samples_cc, n_samples_tr, largest=False)\n",
+ " \n",
+ " # Set the box around the selected samples\n",
+ " state.tr_lb[ind] = torch.min(samples[top_indices], dim=0).values\n",
+ " state.tr_ub[ind] = torch.max(samples[top_indices], dim=0).values\n",
+ " \n",
+ " # Update volume of trust region\n",
+ " state.tr_vol[ind] = torch.prod(state.tr_ub[ind] - state.tr_lb[ind])\n",
+ " \n",
+ " # return updated status with new trust regions\n",
+ " return state"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "f49690e5-6505-47df-89f7-1a56f9b087b2",
+ "metadata": {},
+ "source": [
+ "### Sampling strategies\n",
+ "\n",
+ "In this block, we define sampling functions for:\n",
+ "\n",
+ "1. Generating an initial experimental design using Sobol sampling strategy. We use this function when a restart is triggered."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 8,
+ "id": "0116ca79-7555-4da3-bfd4-69941926eb11",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "def get_initial_points(state, **tkwargs):\n",
+ " '''Function to generate the initial experimental design'''\n",
+ " X_init = state.sobol.draw(n=state.n_init).to(**tkwargs)\n",
+ " return X_init"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "55726979-92f6-488f-869c-2fa6140f6b85",
+ "metadata": {},
+ "source": [
+ "2. Identifing the best next candidate point using Thompson sampling. Definitions 1 and 2 are the same as in the SCBO tutorial (https://botorch.org/docs/tutorials/scalable_constrained_bo/)."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 9,
+ "id": "2d969ea7-2f1e-4433-b3b4-53413546c2f2",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "from botorch.generation.sampling import ConstrainedMaxPosteriorSampling\n",
+ "\n",
+ "def generate_batch(state, n_candidates, **tkwargs):\n",
+ " '''Function to find net candidate optimum\n",
+ " \n",
+ " Args:\n",
+ " state: FurboState object\n",
+ " n_candidates: number of candidates to draw\n",
+ "\n",
+ " Return:\n",
+ " X_next: n_candidates to be evaluated\n",
+ "\n",
+ " '''\n",
+ "\n",
+ " assert state.X.min() >= 0.0 and state.X.max() <= 1.0 and torch.all(torch.isfinite(state.Y))\n",
+ "\n",
+ " # Initialize tensor with samples to evaluate\n",
+ " X_next = torch.ones((state.batch_size, state.dim), **tkwargs)\n",
+ " \n",
+ " # Iterate over the several trust regions\n",
+ "\n",
+ " tr_lb = state.tr_lb[0]\n",
+ " tr_ub = state.tr_ub[0]\n",
+ "\n",
+ " # Thompson Sampling w/ Constraints (like SCBO)\n",
+ " pert = state.sobol.draw(n_candidates).to(**tkwargs)\n",
+ " pert = tr_lb + (tr_ub - tr_lb) * pert\n",
+ "\n",
+ " # Create a perturbation mask\n",
+ " prob_perturb = min(20.0 / state.dim, 1.0)\n",
+ " mask = torch.rand(n_candidates, state.dim, **tkwargs) <= prob_perturb\n",
+ " ind = torch.where(mask.sum(dim=1) == 0)[0]\n",
+ " mask[ind, torch.randint(0, state.dim - 1, size=(len(ind),), device=tkwargs['device'])] = 1\n",
+ "\n",
+ " # Create candidate points from the perturbations and the mask\n",
+ " X_cand = state.best_X[0].expand(n_candidates, state.dim).clone()\n",
+ " X_cand[mask] = pert[mask]\n",
+ " \n",
+ " # Sample on the candidate points using Constrained Max Posterior Sampling\n",
+ " constrained_thompson_sampling = ConstrainedMaxPosteriorSampling(\n",
+ " model=state.Y_model, constraint_model=state.C_model, replacement=False\n",
+ " )\n",
+ " with torch.no_grad():\n",
+ " X_next[0*state.batch_size:0*state.batch_size+state.batch_size, :] = constrained_thompson_sampling(X_cand, num_samples=state.batch_size)\n",
+ " \n",
+ " return X_next"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "024e10f4-4de9-433b-8827-c58a7177784f",
+ "metadata": {},
+ "source": [
+ "### Stopping criterion\n",
+ "\n",
+ "This function detects when the maximum number of samples evaluated is met and returns a flag to stop the optimization."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 10,
+ "id": "189aae72-f033-49db-bcc8-0e791774bd76",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "def stopping_criterion(state):\n",
+ " '''Function to evaluate if the maximum number of allowed iterations is reached.'''\n",
+ " return state.n_counter > state.max_budget"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "44f15711-8503-4226-8ee0-171f26f7b8f4",
+ "metadata": {},
+ "source": [
+ "### Restart criterion"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "476bada4",
+ "metadata": {},
+ "source": [
+ "This function triggers a restart when $\\mathcal{R} < \\mathcal{R}_{\\min}$."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 11,
+ "id": "81f9bd26",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "def restart_criterion(state):\n",
+ " '''Function to evaluate if MND radius is smaller than the minimum allowed radius'''\n",
+ " return state.radius < state.radius_min"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "a80c7b75-a62d-46c2-aa80-28f6f29501be",
+ "metadata": {},
+ "source": [
+ "### Main optimization loop\n",
+ "\n",
+ "This function runs the main optimization loop of FuRBO. "
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 12,
+ "id": "b9d52417-aaa5-40b6-bf07-12c074460283",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "def furbo_optimize(fcn, objective, constraints, X_ini, batch_size = 1, n_init = 10, max_budget = 200, N_CANDIDATES = 2000):\n",
+ " '''Function to optimize an objective under a set of given constraints using FuRBO\n",
+ " \n",
+ " Args:\n",
+ " objective: handle to evaluate objective\n",
+ " constraints: list of handles to evaluate constraints\n",
+ " X_ini: initial DoE (needed for reproducibility)\n",
+ " batch_size: size of the batch to evaluate at each iteration\n",
+ " n_init: number of initial samples\n",
+ " n_iterations: computational budget (maximum number of iterations)\n",
+ "\n",
+ " Return:\n",
+ " X_all: samples evaluated\n",
+ " Y_all: objective values of the samples evaluated\n",
+ " C_all: constraints values of the samples evaluated\n",
+ "\n",
+ " '''\n",
+ "\n",
+ " # FuRBO state initialization\n",
+ " state = FurboState(fcn,\n",
+ " batch_size = batch_size, # Batch size of each iteration\n",
+ " n_init = n_init, # Number of initial points to evaluate\n",
+ " max_budget = max_budget, # Maximum number of evaluations allowed\n",
+ " **tkwargs)\n",
+ "\n",
+ " # Initiate lists to save samples over the restarts\n",
+ " X_all, Y_all, C_all = [], [], []\n",
+ "\n",
+ " # Continue optimization the stopping criterions isn't triggered\n",
+ " while not state.finish_trigger: \n",
+ " \n",
+ " # Reset status for restarting\n",
+ " state.reset_status(**tkwargs)\n",
+ " \n",
+ " # generate intial batch of X\n",
+ " X_next = X_ini \n",
+ " \n",
+ " # Reset and restart optimization\n",
+ " while not state.restart_trigger and not state.finish_trigger:\n",
+ " \n",
+ " # Evaluate current batch (samples in X_next)\n",
+ " Y_next = []\n",
+ " C_next = []\n",
+ " for x in X_next:\n",
+ " # Evaluate batch on obj ...\n",
+ " Y_next.append(objective(x))\n",
+ " # ... and constraints\n",
+ " C_next.append(constraints(x))\n",
+ " \n",
+ " # process vector for PyTorch\n",
+ " Y_next = torch.stack(Y_next).unsqueeze(-1).to(**tkwargs)\n",
+ " C_next = torch.stack(C_next).to(**tkwargs)\n",
+ " \n",
+ " # Update FuRBO status with newly evaluated batch\n",
+ " state.update(X_next, Y_next, C_next, **tkwargs) \n",
+ " \n",
+ " # Printing current best\n",
+ " # If a feasible has been evaluated -> print current optimum (feasible sample with best objective value)\n",
+ " if (state.best_C <= 0).all():\n",
+ " best = state.best_Y.amax()\n",
+ " print(f\"Samples evaluated: {state.n_counter} | Best value: {best:.2e},\"\n",
+ " f\" MND radius: {state.radius}\")\n",
+ " \n",
+ " # Else, if no feasible has been evaluated -> print smallest violation (the sample that violatest the least all constraints)\n",
+ " else:\n",
+ " violation = state.best_C.clamp(min=0).sum()\n",
+ " print(f\"Samples evaluated: {state.n_counter} | No feasible point yet! Smallest total violation: \"\n",
+ " f\"{violation:.2e}, MND radius: {state.radius}\")\n",
+ " \n",
+ " # Update Trust regions\n",
+ " state = update_tr(state, **tkwargs)\n",
+ " \n",
+ " # generate next batch to evaluate \n",
+ " X_next = generate_batch(state, N_CANDIDATES, **tkwargs)\n",
+ " \n",
+ " # Check if stopping criterion is met (budget exhausted and if GP failed)\n",
+ " state.finish_trigger = stopping_criterion(state) \n",
+ " \n",
+ " # Check if restart criterion is met\n",
+ " state.restart_trigger = restart_criterion(state)\n",
+ "\n",
+ " # Save samples evaluated before resetting the status\n",
+ " X_all.append(state.X)\n",
+ " Y_all.append(state.Y)\n",
+ " C_all.append(state.C)\n",
+ "\n",
+ " # Ri-elaborate for processing\n",
+ " X_all = torch.cat(X_all)\n",
+ " Y_all = torch.cat(Y_all)\n",
+ " C_all = torch.cat(C_all)\n",
+ "\n",
+ " return X_all, Y_all, C_all"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "28e7acb3",
+ "metadata": {},
+ "source": [
+ "### Post-processing\n",
+ "\n",
+ "In this block, we define two functions for post-processing the optimization data, print the optimum sample and its value, and plot the monotonic convergence curve. "
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 13,
+ "id": "17675f23",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "import numpy as np\n",
+ "\n",
+ "def print_results(X_all, Y_all, C_all):\n",
+ " '''Function to print the best sample evaluated from the optimization.'''\n",
+ " best_id = get_best_index_for_batch(n_tr=1, Y=Y_all, C=C_all)\n",
+ "\n",
+ " X_best = X_all[best_id]\n",
+ " Y_best = Y_all[best_id]\n",
+ " C_best = C_all[best_id]\n",
+ "\n",
+ " # If a feasible has been evaluated -> print current optimum sample and yielded value\n",
+ " if (C_best <= 0).all():\n",
+ " print(\"Optimization finished \\n\"\n",
+ " f\"\\t Optimum: {Y_best.item():.2e}, \\n\"\n",
+ " f\"\\t X: {X_best.cpu().numpy()}\")\n",
+ " \n",
+ " # Else, if no feasible has been evaluated -> print sample with smallest violation and the violation value\n",
+ " else:\n",
+ " violation = C_best.sum()\n",
+ " print(\"Optimization failed \\n\"\n",
+ " f\"\\t Smallest violation: {violation:.2e}, \\n\"\n",
+ " f\"\\t X: {X_best.cpu().numpy()}\")\n",
+ " \n",
+ " return\n",
+ "\n",
+ "def plot_results(ax, color, Y_all, C_all):\n",
+ " '''Function to plot the convergence curve of the sample evaluated on a given plot.'''\n",
+ "\n",
+ " score = Y_all.clone()\n",
+ " # Set infeasible to -inf\n",
+ " score[~(C_all <= 0).all(dim=-1)] = float(\"-inf\")\n",
+ " fx = np.maximum.accumulate(score.cpu())\n",
+ " ax.plot(fx, marker=\"\", lw=3, color=color)\n",
+ "\n",
+ " return"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "a3d28f6c-5940-4d56-a3b5-4635980db76f",
+ "metadata": {},
+ "source": [
+ "### Evaluating FuRBO\n",
+ "\n",
+ "We run the optimization with a batch size of 60, an initial DoE of 10 samples over 50 iteration."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 14,
+ "id": "027a9ec9-930a-48d0-b481-ffe9e9ca0d90",
+ "metadata": {},
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "Samples evaluated: 10 | No feasible point yet! Smallest total violation: 5.98e+00, MND radius: 1.0\n",
+ "Samples evaluated: 20 | No feasible point yet! Smallest total violation: 5.98e+00, MND radius: 1.0\n",
+ "Samples evaluated: 30 | No feasible point yet! Smallest total violation: 5.24e+00, MND radius: 1.0\n",
+ "Samples evaluated: 40 | No feasible point yet! Smallest total violation: 2.28e+00, MND radius: 1.0\n",
+ "Samples evaluated: 50 | Best value: -1.38e+01, MND radius: 1.0\n",
+ "Samples evaluated: 60 | Best value: -1.18e+01, MND radius: 1.0\n",
+ "Samples evaluated: 70 | Best value: -1.18e+01, MND radius: 1.0\n",
+ "Samples evaluated: 80 | Best value: -1.10e+01, MND radius: 1.0\n",
+ "Samples evaluated: 90 | Best value: -1.10e+01, MND radius: 1.0\n",
+ "Samples evaluated: 100 | Best value: -1.10e+01, MND radius: 1.0\n",
+ "Samples evaluated: 110 | Best value: -1.10e+01, MND radius: 1.0\n",
+ "Samples evaluated: 120 | Best value: -1.10e+01, MND radius: 1.0\n",
+ "Samples evaluated: 130 | Best value: -1.08e+01, MND radius: 0.5\n",
+ "Samples evaluated: 140 | Best value: -1.05e+01, MND radius: 0.5\n",
+ "Samples evaluated: 150 | Best value: -1.05e+01, MND radius: 1.0\n",
+ "Samples evaluated: 160 | Best value: -1.05e+01, MND radius: 1.0\n",
+ "Samples evaluated: 170 | Best value: -1.05e+01, MND radius: 1.0\n",
+ "Samples evaluated: 180 | Best value: -1.05e+01, MND radius: 0.5\n",
+ "Samples evaluated: 190 | Best value: -1.05e+01, MND radius: 0.5\n",
+ "Samples evaluated: 200 | Best value: -1.02e+01, MND radius: 0.5\n",
+ "Samples evaluated: 210 | Best value: -1.02e+01, MND radius: 0.5\n",
+ "Samples evaluated: 220 | Best value: -1.02e+01, MND radius: 0.5\n",
+ "Samples evaluated: 230 | Best value: -1.02e+01, MND radius: 0.5\n",
+ "Samples evaluated: 240 | Best value: -1.00e+01, MND radius: 0.25\n",
+ "Samples evaluated: 250 | Best value: -1.00e+01, MND radius: 0.25\n",
+ "Samples evaluated: 260 | Best value: -9.94e+00, MND radius: 0.25\n",
+ "Samples evaluated: 270 | Best value: -9.94e+00, MND radius: 0.25\n",
+ "Samples evaluated: 280 | Best value: -9.79e+00, MND radius: 0.25\n",
+ "Samples evaluated: 290 | Best value: -9.79e+00, MND radius: 0.25\n",
+ "Samples evaluated: 300 | Best value: -9.79e+00, MND radius: 0.25\n",
+ "Samples evaluated: 310 | Best value: -9.79e+00, MND radius: 0.25\n",
+ "Samples evaluated: 320 | Best value: -9.29e+00, MND radius: 0.125\n",
+ "Samples evaluated: 330 | Best value: -9.29e+00, MND radius: 0.125\n",
+ "Samples evaluated: 340 | Best value: -9.23e+00, MND radius: 0.125\n",
+ "Samples evaluated: 350 | Best value: -9.23e+00, MND radius: 0.125\n",
+ "Samples evaluated: 360 | Best value: -9.23e+00, MND radius: 0.125\n",
+ "Samples evaluated: 370 | Best value: -9.23e+00, MND radius: 0.125\n",
+ "Samples evaluated: 380 | Best value: -9.18e+00, MND radius: 0.0625\n",
+ "Samples evaluated: 390 | Best value: -9.18e+00, MND radius: 0.0625\n",
+ "Samples evaluated: 400 | Best value: -9.18e+00, MND radius: 0.0625\n",
+ "Samples evaluated: 410 | Best value: -9.17e+00, MND radius: 0.0625\n",
+ "Samples evaluated: 420 | Best value: -9.12e+00, MND radius: 0.0625\n",
+ "Samples evaluated: 430 | Best value: -9.12e+00, MND radius: 0.125\n",
+ "Samples evaluated: 440 | Best value: -9.12e+00, MND radius: 0.125\n",
+ "Samples evaluated: 450 | Best value: -9.12e+00, MND radius: 0.125\n",
+ "Samples evaluated: 460 | Best value: -9.12e+00, MND radius: 0.0625\n",
+ "Samples evaluated: 470 | Best value: -9.12e+00, MND radius: 0.0625\n",
+ "Samples evaluated: 480 | Best value: -9.11e+00, MND radius: 0.0625\n",
+ "Samples evaluated: 490 | Best value: -9.11e+00, MND radius: 0.125\n",
+ "Samples evaluated: 500 | Best value: -9.11e+00, MND radius: 0.125\n",
+ "Samples evaluated: 510 | Best value: -9.11e+00, MND radius: 0.125\n",
+ "Optimization finished \n",
+ "\t Optimum: -9.11e+00, \n",
+ "\t X: [[0.47302596 0.5352108 0.53682257 0.53979566 0.53497679 0.53344312\n",
+ " 0.53510397 0.53976004 0.53606554 0.53302409]]\n"
+ ]
+ },
+ {
+ "data": {
+ "image/png": "iVBORw0KGgoAAAANSUhEUgAAAsUAAAI1CAYAAAA+WYcBAAAAOnRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjEwLjcsIGh0dHBzOi8vbWF0cGxvdGxpYi5vcmcvTLEjVAAAAAlwSFlzAAAPYQAAD2EBqD+naQAAbLxJREFUeJzt3QeYU2X69/F7Cr036XVRUSliL6siCCqufbEj2MWCCq5lLYCui+ju2ttasKxrQde1YUFF14JYUUTWCmKhI70NM3mv3/PfkzeTSTJJJm3O+X6uKwxJTpKT03Kf+9zP8xSFQqGQAQAAAAFWnO8ZAAAAAPKNoBgAAACBR1AMAACAwCMoBgAAQOARFAMAACDwCIoBAAAQeATFAAAACDyCYgAAAAQeQTEAAAACj6AYSMH8+fOtqKjI3R588MG0lp33+vHjx7PsY3jzzTfDy0j/r4kBAwa499FfIBeCun9v3rzZtt56a/fdn3rqqXzPTsHQduBtE0H1008/Wb169axu3br29ddfWyEjKK4llixZYi+88IJdffXVdvDBB1vr1q3DO9rIkSNTfr+XXnrJjjzySOvUqZPbWPVX9/V4dbp16xb+7Mhb48aNrWPHjtavXz87+eST7bbbbrOff/7ZsumUU04Jf/62226b1c8Cqjth0jZ/9NFHu+CgYcOGVr9+fbdvHXHEEfb444/bli1bWIjwpVtuucW+/fZb6927t9sHEgWH0beSkhJr3ry57bTTTjZmzBj76quv8vIdajsdXz799FO755577PTTT7e+fftaaWlpeDnrGJVK4qco4lZcXOzWUdeuXW2PPfawc8891x555BFbu3Ztte+pY6B+q8vKyuziiy+2ghZCraBVFe82YsSIpN+nvLw8dNpppyV8v9NPP91NF0/Xrl0Tvj7yVlJSEjryyCND8+bNC2XaunXrQo0bN670eTNmzAhlk76H91mTJ09O6z28148bNy7j8+cH06dPDy8j/T+alpv3fHX2228/N53+ZtOVV14ZKioqqnZ/2HXXXUM//PBDVufF77TfecszG8eVmsrF/q1jvj5Dx+JCsHr16lCrVq3cPD3++OMxp4ncb6u71alTJ3T77bdndZ5TOY7Uhs+R8ePHJ1yuyewv8yJ+45K5NWnSJHTRRReF1q5dm/B958+f79arXjNz5sxQoSJTXAt16dLFhgwZktZrr7jiCrv//vvd//v372+PPfaYffDBB+6v7st9991nV155ZbXv1aFDB5s9e3b49sknn9jrr79uDzzwgDtLbdasmZWXl9szzzzjssfPP/+8ZdK//vWv8Flqo0aN3N+HH344o5+B3FOpg2IL3WpL2cPChQvd/Go7POmkk2zy5Mn2zjvv2EcffeSyKbvuuqub7sMPP7QDDjggqewKaidv2w1S+cRdd91ly5cvd79Nw4YNq3Z6/UZE/nbMmjXLXaUcPXq01alTx2UUzzvvPHvttddyMv9+8X/nZP9HV6mU0f3Nb36T9vsdfvjhldbTzJkz7ZVXXrGbbrrJXf1SFnrNmjXuvrL833zzTdz3UobZu4Lwpz/9yQpWvqNyJOfqq68OPf/886FFixZVOZtLNlP81VdfhUpLS91rdtlll9D69eurZF71uJ7XdN98803CTHF1WYo1a9aExowZE57PBg0ahD788MOMrfLBgwe79915551D55xzjvt/y5YtQ5s2bQplC5ni/CvETPEll1wSmjRpksuYxbJly5bQMcccE57vCRMmZHV+/KzQM8W5UEiZYm3bnTt3dvNz6aWXJrXfxroC5HnggQfC0w0ZMiRLc+3PTPHLL78cuvvuu0OffPJJqKysrNK2kk6meEQ1sYWuenm/w7pts802oRUrVsSd/rnnnnPTFRcXh7777rtQISJTXEtMmDDBfve731nbtm3Tfo+bb745XNOo2scGDRpUel41kHpcNJ3O/mpCNcZ//etf7frrr3f3N2zY4DLImfDLL7+4rLSceOKJLjsnK1ascLXXQC5NmjTJLrnkEmvSpEnM51Uzeeedd7qGJkJDJPjFtGnT7Mcffwwfi2tKtadqM+NdWUHyDjzwQDvrrLPcVV9lcbOtS5cuLsN/yCGHuPtqRJfoCslBBx1krVq1soqKCnc1rRARFAfossqzzz7r/t+rVy93WSUWPe41WNP0kZdj0qVgYffdd3f//+yzz2zq1Kk1fs9//OMfbsdSsHH88cfbnnvuaT179ky5hOKLL76w888/3/r06WMtWrRwl+7atWvnLnHfcMMN7rJ4qjZu3OguO3kNFBQwpUPlKGeffbZbHzrB0KV5/X/UqFFxW/DqEpY+c7vttqv2/XW5U40sNf0555yT9HwpoPO+23//+99qG2P++9//jnuA1PPR22K83ifU24ce0wmiJ1aDkESNSdTwUw15tK3opFAHaP2QJNPAtKb0WWr4It99912N30/bv8qedElSP076Prpts802LjjRetJl6Hg9BShI33///a1NmzYuWNd2P3To0PC+FY8a9mo5ax3LypUrXQPgHXbYwW2jaoyz77772qOPPlrtd1BplS7Deg1+dVLRo0cP22effeyqq65ypV3R24WCJk/37t2rrP/IbSZ6XrU/X3rppW5e9VnR0//666/ux1on2dtvv73b77xlo+3k73//u1t26fY+4W3D3naq5az33GuvvdzxR8tP28h1111n69evj9tY7aGHHnL3f/jhh5j7QLQ33njDHSe1vLSNKAHiNZhSwyc9n64nn3zS/VXjUh1HM8FbX5s2bYo7zfvvv+/K/FRipfWj9dS0aVO33nSM/PLLL2O+ribHkZrsc5G/DzfeeKM7Vmsb1G233Xaz22+/vVY2xC0pKXHLVNuU3HvvvbZs2bKY0+r39dBDD3X/V8PjgpTvVDXSk2r5hC5VeNOfddZZCac988wzw9N+//33aZdPRPrnP/8Zfs8zzjgjVFO9e/eucnnNu0ylYv5ly5ZVe8lPjQOqaxwVvWyrK5/Q5fMBAwaELxH9/e9/T7khjho5VjdvKm+55557qrz2jjvuSLrR4S233BKeNpWyliVLloRfd9ddd8VsUBE5rxdccEGVaXRpz2skGX3JNV5Du8jL5sk2Joksn3jnnXdCrVu3jvu6G2+8MZRtffr0CTdOqQl9xx133LHaZRHrMrVe26tXr4Sv++1vfxtavnx5tZfu//vf/4a6desW933OPffcuPvfsGHDqp1/lUbF2i6S/c6R86r9Idb6j5w+mUbE/fv3Dy1cuDDuukm0f0duw3PmzAkNGjQo7ufstttuVRovJdtYLdKFF15Y7fRqJJcub/0PHz484XTJlk9ImzZt3HTaTmNJ5ligRt46Hqbz2ujjSE32ucjvrfLHRO9x6KGHJmzkno5slk/EixseffTRUDwq7/Cm+/rrr0OFhkxxQESeNStTnEjk83Pnzs3I5yvz6nn77bdr9F7KoCrDK17ZROT/daaus/lEzjzzTFceot+w9u3bu8zM9OnT3XurIcG1117rGgemYunSpS7zpsyTshaahzPOOCPl76fMtTdvyripUYreU1kznYUry6WMgi6TPffcc5Veq2yFVxZT3eUp73llpnbZZZek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",
+ "text/plain": [
+ ""
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ }
+ ],
+ "source": [
+ "import matplotlib.pyplot as plt\n",
+ "\n",
+ "# Evaluate optimization\n",
+ "batch_size = 1 * fun.dim\n",
+ "n_init = int(10)\n",
+ "max_budget = 500\n",
+ "N_CANDIDATES = 2000 # Number of candidates used during the Thompson sampling\n",
+ "\n",
+ "# First generate initial DoE\n",
+ "X_ini = SobolEngine(dimension=fun.dim, scramble=True, seed=1).draw(n=n_init).to(**tkwargs)\n",
+ "\n",
+ "# Run optimization loop \n",
+ "X_all, Y_all, C_all = furbo_optimize(fun,\n",
+ " eval_objective, \n",
+ " eval_constraints,\n",
+ " X_ini,\n",
+ " batch_size = batch_size,\n",
+ " n_init = n_init,\n",
+ " max_budget = max_budget,\n",
+ " N_CANDIDATES = N_CANDIDATES) \n",
+ "\n",
+ "# Print optimization result\n",
+ "print_results(X_all, Y_all, C_all)\n",
+ "\n",
+ "# Plotting monotic convergence curve\n",
+ "fig, ax = plt.subplots(figsize=(8, 6))\n",
+ "plot_results(ax, \"darkgreen\", Y_all, C_all)\n",
+ "\n",
+ "# Adding description\n",
+ "plt.ylabel(\"Function value\", fontsize=18)\n",
+ "plt.xlabel(\"Number of evaluations\", fontsize=18)\n",
+ "plt.title(\"10D Ackley with 2 constraints (Batch 1D)\", fontsize=20)\n",
+ "plt.xlim([0, len(Y_all)])\n",
+ "\n",
+ "plt.grid(True)\n",
+ "plt.show()"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "9ff0989b",
+ "metadata": {},
+ "source": [
+ "## Comparison with SCBO\n",
+ "\n",
+ "In this section, we compare the performance of SCBO [2] and FuRBO in two scenarios:\n",
+ "1. 20D Ackley function with 2 cosntraints and large batch (batch size = 3D = 60)\n",
+ "2. Speed reducer volume minimization problem [4], a severely constrained black-box problem (7 dimensions and 11 constraints).\n",
+ "\n",
+ "For a more in-depth comparison of FuRBO with other algorithms, please refer to [1] or to the data published on [GitHub](https://github.com/paoloascia/FuRBO).\n",
+ "\n",
+ "[1] [Paolo Ascia, Elena Raponi, Thomas Bäck and Fabian Duddeck. \"Feasibility-Driven Trust Region Bayesian Optimization.\" In AutoML 2025 Methods Track.](https://doi.org/10.48550/arXiv.2506.14619)\n",
+ "\n",
+ "[2] [David Eriksson and Matthias Poloczek. Scalable constrained Bayesian optimization. In International Conference on Artificial Intelligence and Statistics, pages 730–738. PMLR, 2021.](https://doi.org/10.48550/arxiv.2002.08526)\n",
+ "\n",
+ "[4] [Afonso C.C. Lemonge, Helio J.C. Barbosa, Carlos C.H. Borges and Francilene B.S. Silva. \"Constrained optimization problems in mechanical engineering design using a real-coded steady-state genetic algorithm.\" Mecánica Computacional, 29(95):9287–9303, 2010.](http://venus.ceride.gov.ar/ojs/index.php/mc/article/viewFile/3669/3581)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "8a75e708",
+ "metadata": {},
+ "source": [
+ "### SCBO class and other utility functions"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 15,
+ "id": "faf1d844",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "class ScboState():\n",
+ " '''\n",
+ " Class to track SCBO optimization state and update it with newly evaluated samples\n",
+ "\n",
+ " Args:\n",
+ " fcn: objective function class\n",
+ " batch_size: batch size\n",
+ " n_init: number of initial points to evaluate\n",
+ " n_iteration: number of total iterations\n",
+ " \n",
+ " '''\n",
+ " # Initialization of the status\n",
+ " def __init__(self, fcn, batch_size, n_init, max_budget, **tkwargs):\n",
+ " \n",
+ " # Domain bounds\n",
+ " self.lb, self.ub = fcn.bounds\n",
+ " self.bounds = fcn.bounds\n",
+ " \n",
+ " # Problem dimensions\n",
+ " self.batch_size: int = batch_size # Dimension of the batch at each iteration\n",
+ " self.n_init: int = n_init # Number of initial samples\n",
+ " self.dim: int = fcn.dim # Dimension of the problem\n",
+ " \n",
+ " # Trust regions information # Lower bounds of trust region\n",
+ " self.tr_length: float = 0.8 # side length of trust region\n",
+ " self.tr_length_max: float = 0.8\n",
+ " self.tr_lb = torch.clamp(0.5*torch.ones(self.dim, **tkwargs) - self.tr_length / 2.0, 0.0, 1.0)\n",
+ " self.tr_ub = torch.clamp(0.5*torch.ones(self.dim, **tkwargs) + self.tr_length / 2.0, 0.0, 1.0)\n",
+ " self.tr_vol = torch.prod(self.tr_ub - self.tr_lb)\n",
+ "\n",
+ " # Trust region updating \n",
+ " self.failure_counter: int = 0 # Counter for failure points to asses how algorithm is going\n",
+ " self.success_counter: int = 0 # Counter for success points to asses how algorithm is going\n",
+ " self.success_tolerance: int = 2 # Success tolerance \n",
+ " self.failure_tolerance: int = 3 # Failure tolerance \n",
+ " \n",
+ " # Tensor to save current batch information\n",
+ " self.batch_X: Tensor # Current batch to evaluate: X values\n",
+ " self.batch_Y: Tensor # Current batch to evaluate: Y value\n",
+ " self.batch_C: Tensor # Current batch to evaluate: C values\n",
+ " \n",
+ " # Stopping criteria information\n",
+ " self.it_counter: int = 0 # Counter for iterations\n",
+ " self.n_counter: int = 0 # Counter for samples evaluated\n",
+ " self.max_budget: int = max_budget # Maximum number of evaluations allowed\n",
+ " self.finish_trigger: bool = False # Trigger to stop optimization\n",
+ " \n",
+ " # Restart criteria information\n",
+ " self.tr_length_min: float = 0.5**7 # Minimum volume allowed for trust region\n",
+ " self.restart_trigger: bool = False # Trigger to stop optimization\n",
+ " \n",
+ " # Sobol sampler engine\n",
+ " self.sobol = SobolEngine(dimension=self.dim, scramble=True, seed=1)\n",
+ " \n",
+ " # Update the status\n",
+ " def update(self, X_next, Y_next, C_next, **tkwargs):\n",
+ " '''\n",
+ " Function to update optimization status\n",
+ " \n",
+ " Args:\n",
+ " X_next: samples X (input values) to update the status\n",
+ " Y_next: samples Y (objective value) to update the status\n",
+ " C_next: Samples C (constraints values) to update the status\n",
+ "\n",
+ " '''\n",
+ " \n",
+ " # Merge current batch with previously evaluated samples\n",
+ " if not hasattr(self, 'X'):\n",
+ " # If there are no previous samples, declare the Tensors\n",
+ " self.X = X_next\n",
+ " self.Y = Y_next\n",
+ " self.C = C_next\n",
+ " else:\n",
+ " # Else, concatenate the new batch to the previous samples\n",
+ " self.X = torch.cat((self.X, X_next), dim=0)\n",
+ " self.Y = torch.cat((self.Y, Y_next), dim=0)\n",
+ " self.C = torch.cat((self.C, C_next), dim=0)\n",
+ "\n",
+ " # update GPR surrogates\n",
+ " self.Y_model = get_fitted_model(self.X, self.Y, self.dim)\n",
+ " self.C_model = ModelListGP(*[get_fitted_model(self.X, C.reshape(-1, 1), self.dim) for C in self.C.t()])\n",
+ " \n",
+ " # Update batch information \n",
+ " self.batch_X = X_next\n",
+ " self.batch_Y = Y_next\n",
+ " self.batch_C = C_next\n",
+ " \n",
+ " # Update best value\n",
+ " # Find the best value among the candidates\n",
+ " best_id = get_best_index_for_batch(n_tr=1, Y=self.Y, C=self.C)\n",
+ " \n",
+ " # Update success and failure counters for trust region update\n",
+ " # If attribute 'best_X' does not exist, DoE was just evaluated -> no update on counters\n",
+ " if hasattr(self, 'best_X'):\n",
+ " if (self.C[best_id] <= 0).all():\n",
+ " # At least one new candidate is feasible\n",
+ " if (self.Y[best_id] > self.best_Y).any() or (self.best_C > 0).any():\n",
+ " self.success_counter += 1\n",
+ " self.failure_counter = 0 \n",
+ " else:\n",
+ " self.success_counter = 0\n",
+ " self.failure_counter += 1\n",
+ " else:\n",
+ " # No new candidate is feasible\n",
+ " total_violation_next = self.C[best_id].clamp(min=0).sum(dim=-1)\n",
+ " total_violation_center = self.best_C.clamp(min=0).sum(dim=-1)\n",
+ " if total_violation_next < total_violation_center:\n",
+ " self.success_counter += 1\n",
+ " self.failure_counter = 0\n",
+ " else:\n",
+ " self.success_counter = 0\n",
+ " self.failure_counter += 1\n",
+ " \n",
+ " # Update best values\n",
+ " self.best_X = self.X[best_id]\n",
+ " self.best_Y = self.Y[best_id]\n",
+ " self.best_C = self.C[best_id]\n",
+ " \n",
+ " # Update iteration counter\n",
+ " self.it_counter += 1\n",
+ " self.n_counter += len(Y_next)\n",
+ " \n",
+ " def reset_status(self, **tkwargs):\n",
+ " '''Function to reset the status for the restart'''\n",
+ " \n",
+ " # Reset trust regions size\n",
+ " self.tr_length: float = 0.8 # side length of trust region\n",
+ " self.tr_length_max: float = 0.8\n",
+ " self.tr_lb = torch.clamp(0.5*torch.ones(self.dim, **tkwargs) - self.tr_length / 2.0, 0.0, 1.0)\n",
+ " self.tr_ub = torch.clamp(0.5*torch.ones(self.dim, **tkwargs) + self.tr_length / 2.0, 0.0, 1.0)\n",
+ " self.tr_vol = torch.prod(self.tr_ub - self.tr_lb)\n",
+ "\n",
+ " # Reset counters to change trust region size \n",
+ " self.failure_counter: int = 0 # Counter of failure points to asses how algorithm is going\n",
+ " self.success_counter: int = 0 # Counter of success points to asses how algorithm is going\n",
+ " \n",
+ " # Reset restart criteria trigger\n",
+ " self.restart_trigger: bool = False # Trigger to restart optimization\n",
+ " \n",
+ " # Delete tensors with samples for training GPRs\n",
+ " if hasattr(self, 'X'):\n",
+ " del self.X\n",
+ " del self.Y\n",
+ " del self.C\n",
+ " \n",
+ " # Delete tensors with best value so far\n",
+ " if hasattr(self, 'best_X'):\n",
+ " del self.best_X\n",
+ " del self.best_Y\n",
+ " del self.best_C\n",
+ " \n",
+ " # Clear GPU memory\n",
+ " if tkwargs[\"device\"] == \"cuda\":\n",
+ " torch.cuda.empty_cache() \n",
+ "\n",
+ "def scbo_update_tr(state, **tkwargs):\n",
+ " \"\"\"\n",
+ " Function to update the side length of the trust region\n",
+ "\n",
+ " Args:\n",
+ " state: ScboState object\n",
+ "\n",
+ " \"\"\"\n",
+ " if state.success_counter == state.success_tolerance: # Expand trust region\n",
+ " state.tr_length = min(2.0 * state.tr_length, state.tr_length_max)\n",
+ " state.success_counter = 0\n",
+ " elif state.failure_counter == state.failure_tolerance: # Shrink trust region\n",
+ " state.tr_length /= 2.0\n",
+ " state.failure_counter = 0\n",
+ " \n",
+ " state.tr_lb = torch.clamp(state.best_X - state.tr_length / 2.0, 0.0, 1.0)\n",
+ " state.tr_ub = torch.clamp(state.best_X + state.tr_length / 2.0, 0.0, 1.0)\n",
+ "\n",
+ " state.tr_vol = torch.prod(state.tr_ub - state.tr_lb)\n",
+ " return state\n",
+ "\n",
+ "def scbo_generate_batch(state, n_candidates, **tkwargs):\n",
+ " \"\"\"\n",
+ " Function to compute next candidate to evaluate\n",
+ "\n",
+ " Args:\n",
+ " state: ScboState object\n",
+ " n_candidates: number of candidates inspecting the surrogates\n",
+ "\n",
+ " \"\"\"\n",
+ "\n",
+ " assert state.X.min() >= 0.0 and state.X.max() <= 1.0 and torch.all(torch.isfinite(state.Y))\n",
+ "\n",
+ " # Create the TR bounds\n",
+ " tr_lb = state.tr_lb\n",
+ " tr_ub = state.tr_ub\n",
+ "\n",
+ " # Thompson Sampling w/ Constraints (SCBO)\n",
+ " dim = state.X.shape[-1]\n",
+ " pert = state.sobol.draw(n_candidates).to(dtype=tkwargs['dtype'], device=tkwargs['device'])\n",
+ " pert = tr_lb + (tr_ub - tr_lb) * pert\n",
+ "\n",
+ " # Create a perturbation mask\n",
+ " prob_perturb = min(20.0 / dim, 1.0)\n",
+ " mask = torch.rand(n_candidates, dim, **tkwargs) <= prob_perturb\n",
+ " ind = torch.where(mask.sum(dim=1) == 0)[0]\n",
+ " mask[ind, torch.randint(0, dim - 1, size=(len(ind),), device=tkwargs['device'])] = 1\n",
+ "\n",
+ " # Create candidate points from the perturbations and the mask\n",
+ " X_cand = state.best_X.expand(n_candidates, dim).clone()\n",
+ " X_cand[mask] = pert[mask]\n",
+ "\n",
+ " # Sample on the candidate points using Constrained Max Posterior Sampling\n",
+ " constrained_thompson_sampling = ConstrainedMaxPosteriorSampling(\n",
+ " model=state.Y_model, constraint_model=state.C_model, replacement=False\n",
+ " )\n",
+ " with torch.no_grad():\n",
+ " X_next = constrained_thompson_sampling(X_cand, num_samples=state.batch_size)\n",
+ "\n",
+ " return X_next\n",
+ "\n",
+ "def scbo_stopping_criterion(state):\n",
+ " '''Function to evaluate if the maximum number of allowed iterations is reached.'''\n",
+ " return state.n_counter > state.max_budget\n",
+ "\n",
+ "def scbo_restart_criterion(state):\n",
+ " '''Function to evaluate if the minimum side length of the trust region is reached.'''\n",
+ " return state.tr_length < state.tr_length_min\n",
+ "\n",
+ "def scbo_optimize(fcn, objective, constraints, X_ini, batch_size = 1, n_init = 10, max_budget = 200, N_CANDIDATES = 2000):\n",
+ " '''Function to optimize an objective under a set of given constraints using SCBO\n",
+ " \n",
+ " Args:\n",
+ " objective: handle to evaluate objective\n",
+ " constraints: list of handles to evaluate constraints\n",
+ " batch_size: size of the batch to evaluate at each iteration\n",
+ " n_init: number of initial samples\n",
+ " max_budget: maximum number of evaluations allowed\n",
+ "\n",
+ " Return:\n",
+ " X_all: samples evaluated\n",
+ " Y_all: objective values of the samples evaluated\n",
+ " C_all: constraints values of the samples evaluated\n",
+ "\n",
+ " '''\n",
+ " # SCBO state initialization\n",
+ " state = ScboState(fcn,\n",
+ " batch_size = batch_size, # Batch size of each iteration\n",
+ " n_init = n_init, # Number of initial points to evaluate\n",
+ " max_budget = max_budget, # Number of iterations\n",
+ " **tkwargs)\n",
+ "\n",
+ " # Initiate lists to save samples over the restarts\n",
+ " X_all, Y_all, C_all = [], [], []\n",
+ "\n",
+ " # Continue optimization the stopping criterions isn't triggered\n",
+ " while not state.finish_trigger: \n",
+ " \n",
+ " # Reset status for restarting\n",
+ " state.reset_status(**tkwargs)\n",
+ " \n",
+ " # generate intial batch of X\n",
+ " X_next = X_ini # Use same initial DoE of FuRBO\n",
+ " \n",
+ " # Reset and restart optimization\n",
+ " while not state.restart_trigger and not state.finish_trigger:\n",
+ " \n",
+ " # Evaluate current batch (samples in X_next)\n",
+ " Y_next = []\n",
+ " C_next = []\n",
+ " for x in X_next:\n",
+ " # Evaluate batch on obj ...\n",
+ " Y_next.append(objective(x))\n",
+ " # ... and constraints\n",
+ " C_next.append(constraints(x))\n",
+ " \n",
+ " # process vector for PyTorch\n",
+ " Y_next = torch.stack(Y_next).unsqueeze(-1).to(**tkwargs)\n",
+ " C_next = torch.stack(C_next).to(**tkwargs)\n",
+ " \n",
+ " # Update SCBO status with newly evaluated batch\n",
+ " state.update(X_next, Y_next, C_next, **tkwargs) \n",
+ " \n",
+ " # Printing current best\n",
+ " # If a feasible has been evaluated -> print current optimum (feasible sample with best objective value)\n",
+ " if (state.best_C <= 0).all():\n",
+ " best = state.best_Y.amax()\n",
+ " print(f\"Samples evaluated: {state.n_counter} | Best value: {best:.2e},\"\n",
+ " f\" TR volume: {state.tr_vol}\")\n",
+ " \n",
+ " # Else, if no feasible has been evaluated -> print smallest violation (the sample that violatest the least all constraints)\n",
+ " else:\n",
+ " violation = state.best_C.clamp(min=0).sum()\n",
+ " print(f\"Samples evaluated: {state.n_counter} | No feasible point yet! Smallest total violation: \"\n",
+ " f\"{violation:.2e}, TR volume: {state.tr_vol}\")\n",
+ " \n",
+ " # Update Trust region\n",
+ " state = scbo_update_tr(state, **tkwargs)\n",
+ " \n",
+ " # generate next batch to evaluate \n",
+ " X_next = scbo_generate_batch(state, N_CANDIDATES, **tkwargs)\n",
+ " \n",
+ " # Check if stopping criterion is met (budget exhausted and if GP failed)\n",
+ " state.finish_trigger = scbo_stopping_criterion(state) \n",
+ " \n",
+ " # Check if restart criterion is met\n",
+ " state.restart_trigger = scbo_restart_criterion(state)\n",
+ "\n",
+ " # Save samples evaluated before resetting the status\n",
+ " X_all.append(state.X)\n",
+ " Y_all.append(state.Y)\n",
+ " C_all.append(state.C)\n",
+ "\n",
+ " # Ri-elaborate for processing\n",
+ " X_all = torch.cat(X_all)\n",
+ " Y_all = torch.cat(Y_all)\n",
+ " C_all = torch.cat(C_all)\n",
+ "\n",
+ " return X_all, Y_all, C_all"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "96d1dc88",
+ "metadata": {},
+ "source": [
+ "### Scenario 1: 20D Ackley function\n",
+ "\n",
+ "The problem maximizes the Ackley function in 20D"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 16,
+ "id": "05045730",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "fun20 = Ackley(dim=20, negate=True).to(**tkwargs)\n",
+ "fun20.bounds[0, :].fill_(-5)\n",
+ "fun20.bounds[1, :].fill_(10)\n",
+ "\n",
+ "def eval_objective(x):\n",
+ " \"\"\"This is a helper function we use to unnormalize and evalaute a point\"\"\"\n",
+ " return fun20(unnormalize(x, fun20.bounds))"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "c150dd70",
+ "metadata": {},
+ "source": [
+ "With two constraint functions: \n",
+ "\n",
+ "$c_1 = \\sum_{i=1}^{20} x_i \\leq 0$\n",
+ "\n",
+ "$c_2 = \\| \\mathbb{x}\\|_2 \\leq 0.5$"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 17,
+ "id": "3abb68dd",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "def c1(x):\n",
+ " return x.sum()\n",
+ "\n",
+ "def c2(x):\n",
+ " return torch.norm(x, p=2) - 5\n",
+ " \n",
+ "def eval_constraints(x):\n",
+ " \"\"\"This is a helper function we use to unnormalize and evalaute a point on the constraints\"\"\"\n",
+ " return Tensor([c1(unnormalize(x - 0.3, fun20.bounds)), c2(unnormalize(x - 0.3, fun20.bounds))])"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "b4c5059b",
+ "metadata": {},
+ "source": [
+ "#### Evaluate algorithms"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 18,
+ "id": "63e1529c",
+ "metadata": {},
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "FuRBO on 20D Ackley function\n",
+ "Samples evaluated: 20 | No feasible point yet! Smallest total violation: 1.28e+01, MND radius: 1.0\n",
+ "Samples evaluated: 80 | No feasible point yet! Smallest total violation: 4.99e+00, MND radius: 1.0\n",
+ "Samples evaluated: 140 | No feasible point yet! Smallest total violation: 2.91e+00, MND radius: 1.0\n",
+ "Samples evaluated: 200 | No feasible point yet! Smallest total violation: 2.97e-01, MND radius: 1.0\n",
+ "Samples evaluated: 260 | Best value: -1.32e+01, MND radius: 1.0\n",
+ "Samples evaluated: 320 | Best value: -1.23e+01, MND radius: 1.0\n",
+ "Samples evaluated: 380 | Best value: -1.18e+01, MND radius: 1.0\n",
+ "Samples evaluated: 440 | Best value: -1.16e+01, MND radius: 1.0\n",
+ "Samples evaluated: 500 | Best value: -1.15e+01, MND radius: 1.0\n",
+ "Samples evaluated: 560 | Best value: -1.15e+01, MND radius: 1.0\n",
+ "Samples evaluated: 620 | Best value: -1.15e+01, MND radius: 1.0\n",
+ "Samples evaluated: 680 | Best value: -1.15e+01, MND radius: 1.0\n",
+ "Samples evaluated: 740 | Best value: -1.15e+01, MND radius: 0.5\n",
+ "Samples evaluated: 800 | Best value: -1.15e+01, MND radius: 0.5\n",
+ "Samples evaluated: 860 | Best value: -1.15e+01, MND radius: 0.5\n",
+ "Samples evaluated: 920 | Best value: -1.15e+01, MND radius: 0.5\n",
+ "Samples evaluated: 980 | Best value: -1.15e+01, MND radius: 0.5\n",
+ "Samples evaluated: 1040 | Best value: -1.13e+01, MND radius: 0.25\n",
+ "Samples evaluated: 1100 | Best value: -1.13e+01, MND radius: 0.25\n",
+ "Samples evaluated: 1160 | Best value: -1.08e+01, MND radius: 0.25\n",
+ "Samples evaluated: 1220 | Best value: -1.08e+01, MND radius: 0.25\n",
+ "Samples evaluated: 1280 | Best value: -1.08e+01, MND radius: 0.5\n",
+ "Samples evaluated: 1340 | Best value: -1.08e+01, MND radius: 0.5\n",
+ "Samples evaluated: 1400 | Best value: -1.08e+01, MND radius: 0.5\n",
+ "Samples evaluated: 1460 | Best value: -1.07e+01, MND radius: 0.25\n",
+ "Samples evaluated: 1520 | Best value: -1.07e+01, MND radius: 0.25\n",
+ "Samples evaluated: 1580 | Best value: -1.07e+01, MND radius: 0.25\n",
+ "Samples evaluated: 1640 | Best value: -1.07e+01, MND radius: 0.25\n",
+ "Samples evaluated: 1700 | Best value: -1.04e+01, MND radius: 0.125\n",
+ "Samples evaluated: 1760 | Best value: -1.04e+01, MND radius: 0.125\n",
+ "Samples evaluated: 1820 | Best value: -1.04e+01, MND radius: 0.25\n",
+ "Samples evaluated: 1880 | Best value: -1.04e+01, MND radius: 0.25\n",
+ "Samples evaluated: 1940 | Best value: -1.04e+01, MND radius: 0.25\n",
+ "Samples evaluated: 2000 | Best value: -1.03e+01, MND radius: 0.125\n",
+ "Samples evaluated: 2060 | Best value: -1.03e+01, MND radius: 0.125\n",
+ "Optimization finished \n",
+ "\t Optimum: -1.03e+01, \n",
+ "\t X: [[0.60256461 0.60120611 0.53488419 0.52727941 0.59689253 0.59943624\n",
+ " 0.53083688 0.53199396 0.59954937 0.53219281 0.53400376 0.59956843\n",
+ " 0.59933336 0.52968873 0.53267404 0.59606141 0.59290767 0.60142492\n",
+ " 0.60550072 0.53692576]]\n",
+ "\n",
+ " SCBO on 20D Ackley function\n",
+ "Samples evaluated: 20 | No feasible point yet! Smallest total violation: 1.28e+01, TR volume: 0.011529215046068493\n",
+ "Samples evaluated: 80 | No feasible point yet! Smallest total violation: 7.55e+00, TR volume: 9.493838712662034e-05\n",
+ "Samples evaluated: 140 | No feasible point yet! Smallest total violation: 3.62e+00, TR volume: 0.0007152000816873457\n",
+ "Samples evaluated: 200 | No feasible point yet! Smallest total violation: 2.60e+00, TR volume: 0.0018178914917088116\n",
+ "Samples evaluated: 260 | No feasible point yet! Smallest total violation: 2.60e+00, TR volume: 0.0019607148503827557\n",
+ "Samples evaluated: 320 | No feasible point yet! Smallest total violation: 2.60e+00, TR volume: 0.0019607148503827557\n",
+ "Samples evaluated: 380 | No feasible point yet! Smallest total violation: 2.60e+00, TR volume: 0.0019607148503827557\n",
+ "Samples evaluated: 440 | No feasible point yet! Smallest total violation: 7.24e-01, TR volume: 9.01920780049975e-09\n",
+ "Samples evaluated: 500 | No feasible point yet! Smallest total violation: 3.13e-01, TR volume: 1.0995116277760001e-08\n",
+ "Samples evaluated: 560 | No feasible point yet! Smallest total violation: 3.13e-01, TR volume: 0.003105585761579358\n",
+ "Samples evaluated: 620 | No feasible point yet! Smallest total violation: 3.13e-01, TR volume: 0.003105585761579358\n",
+ "Samples evaluated: 680 | No feasible point yet! Smallest total violation: 3.13e-01, TR volume: 0.003105585761579358\n",
+ "Samples evaluated: 740 | No feasible point yet! Smallest total violation: 3.13e-01, TR volume: 1.0510773194237722e-08\n",
+ "Samples evaluated: 800 | No feasible point yet! Smallest total violation: 3.13e-01, TR volume: 1.0510773194237722e-08\n",
+ "Samples evaluated: 860 | No feasible point yet! Smallest total violation: 3.13e-01, TR volume: 1.0510773194237722e-08\n",
+ "Samples evaluated: 920 | Best value: -1.33e+01, TR volume: 1.0485759999999946e-14\n",
+ "Samples evaluated: 980 | Best value: -1.28e+01, TR volume: 1.0485759999999946e-14\n",
+ "Samples evaluated: 1040 | Best value: -1.28e+01, TR volume: 1.0995116277760013e-08\n",
+ "Samples evaluated: 1100 | Best value: -1.28e+01, TR volume: 1.0995116277760013e-08\n",
+ "Samples evaluated: 1160 | Best value: -1.28e+01, TR volume: 1.0995116277760013e-08\n",
+ "Samples evaluated: 1220 | Best value: -1.28e+01, TR volume: 1.0485759999999955e-14\n",
+ "Samples evaluated: 1280 | Best value: -1.22e+01, TR volume: 1.0485759999999962e-14\n",
+ "Samples evaluated: 1340 | Best value: -1.22e+01, TR volume: 1.0995116277760013e-08\n",
+ "Samples evaluated: 1400 | Best value: -1.22e+01, TR volume: 1.0995116277760013e-08\n",
+ "Samples evaluated: 1460 | Best value: -1.22e+01, TR volume: 1.0995116277760013e-08\n",
+ "Samples evaluated: 1520 | Best value: -1.19e+01, TR volume: 1.0485759999999946e-14\n",
+ "Samples evaluated: 1580 | Best value: -1.16e+01, TR volume: 1.0485759999999954e-14\n",
+ "Samples evaluated: 1640 | Best value: -1.16e+01, TR volume: 1.0995116277760013e-08\n",
+ "Samples evaluated: 1700 | Best value: -1.16e+01, TR volume: 1.0995116277760013e-08\n",
+ "Samples evaluated: 1760 | Best value: -1.16e+01, TR volume: 1.0995116277760013e-08\n",
+ "Samples evaluated: 1820 | Best value: -1.16e+01, TR volume: 1.0485759999999946e-14\n",
+ "Samples evaluated: 1880 | Best value: -1.16e+01, TR volume: 1.0485759999999946e-14\n",
+ "Samples evaluated: 1940 | Best value: -1.16e+01, TR volume: 1.0485759999999946e-14\n",
+ "Samples evaluated: 2000 | Best value: -1.16e+01, TR volume: 1.0000000000000144e-20\n",
+ "Samples evaluated: 2060 | Best value: -1.16e+01, TR volume: 1.0000000000000144e-20\n",
+ "Optimization finished \n",
+ "\t Optimum: -1.16e+01, \n",
+ "\t X: [[0.60107475 0.6496233 0.67631394 0.60271574 0.60526597 0.46455616\n",
+ " 0.53615492 0.65658673 0.50182849 0.59929085 0.58332372 0.53025066\n",
+ " 0.65932202 0.60278264 0.53806963 0.59039729 0.5222196 0.59601261\n",
+ " 0.55778584 0.59322277]]\n"
+ ]
+ },
+ {
+ "data": {
+ "image/png": "iVBORw0KGgoAAAANSUhEUgAAArQAAAIQCAYAAABws/0iAAAAOnRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjEwLjcsIGh0dHBzOi8vbWF0cGxvdGxpYi5vcmcvTLEjVAAAAAlwSFlzAAAPYQAAD2EBqD+naQAAZgdJREFUeJzt3QecU1X2wPEzvTAMvUsVFVBUREWwgYAgFlQWFVRAWVAXRYG/ChYQFBF1xbruriLgimV1UXcRC80KgqLAUkQREKW3YRimz7z/51w2Y/okM+n5fT+fMOTlJXk5uXk5ue/c+xIsy7IEAAAAiFKJ4d4AAAAAoDpIaAEAABDVSGgBAAAQ1UhoAQAAENVIaAEAABDVSGgBAAAQ1UhoAQAAENVIaAEAABDVSGgBAAAQ1UhogTBLSEiQ22+/3es627ZtM+vNnj1bQm3Pnj3yhz/8QerVq2e24emnn5ZINGzYMGnVqpVEi4ceesjE05919+/fH/Ttwu+6d+9uLpFk5cqVkpqaKr/88otEMo3bKaecIpHor3/9q7Ro0UKKiorCvSkIIBJaRJxvvvnGJHgnn3yy1KhRw+x4rrnmGvnxxx/drr9x40bp27evZGVlSd26deXGG2+Uffv2Oazz6aefmoTAdklLS5NGjRqZne6jjz7qsr4v9Hn1sdLT0yUnJ0di1ZgxY+Tjjz+WCRMmyD/+8Q8T63DZuXOnSe5Wr14tsUjb4nvvvRfwx503b55ce+210qZNG8nMzJSTTjpJxo0bF/XtdsOGDaY96A++SPP6668H5cff/fffL4MGDZKWLVtWLNP9mP3+TRPe1q1by8iRI+XXX3+NytjqZ+Gcc86RBg0amH3sCSecIHfddVdA9u3647e4uFj+9re/hfAVIegsIMIMGDDAaty4sXXHHXdYL730kvXwww9bjRo1smrUqGH997//dVj3119/terXr28df/zx1jPPPGNNnTrVqlOnjnXaaadZRUVFFestXbrU0uY+evRo6x//+Ic1e/Zs64knnrCuuuoqKzk52apXr561ePFiv7bzvvvuM9uZlpZmtrOqdLtGjRrldZ2tW7ea9WbNmmWFmsb++uuvtyLBN9984zEOxcXFVmFhoRUtSkpKrIKCAodl2saHDh3qsu6kSZPM6963b1+Vnkvbd8eOHa0HH3zQtFX9HKSmplrt2rWz8vPzrWj19ttvm7jo5zsYdB9ivx/xx6WXXmq1bNkyoNvz/fffm9e7bNkyh+UXXnihddxxx5l9m15mzpxpjRs3zrSnFi1aWEePHg15bHWbTj75ZKuqrr76auuWW26xZsyYYb388svm9WRnZ1tt27a18vLyqr1vv+eee8z7U15eXuVtRGRJDn7KDPhn7NixpndDexlstHepY8eO8thjj8lrr71WsVx/gR89elRWrVplenLV2WefLb179zaH57WHwt75559vDp/bW7NmjVx88cUyYMAA0yvRpEmTSrdR81DdxsGDB8vWrVtl7ty58sc//jEm3+q9e/dK7dq1JdKlpKRINElOTjaXUHjnnXdcDp137txZhg4dGtNt1/kzW1hYKBkZGT7fx34fFAlmzZpl9nPac+msVq1acsMNNzgs015aPdr11VdfmX1iNPnXv/7lsqxr165m//2f//xHrrvuumrt2/Wo3+OPPy5Lly6Viy66KIivBKFCyQEiTrdu3Vy+SPRwk5Yg6GF+553eZZddVpHMql69esmJJ54o//znP316vtNOO80cGtTDr88//7xP99EvCD0UpztVvXz++efy22+/uaxXXl4uzzzzjEnG9bCZHj7TQ/bffvut18d/5JFHJDExUZ577jmv6/3www9mJ66lFvr4Z555pvz73/+uuH3Lli3mMNyMGTNc7rts2TJz2xtvvOH2sfUHgd6uicALL7xQcUjPW/2n7T72hym1rlXfoy+//NL82NDt1EPfr776qsv99T3QEge9jx46PO6442TIkCGmdlQPLZ511llmvZtuuqlie2x1xe5qaPXHjh5ab968uXk8PdT+5JNPmtfkro5ZD/dr3Z+uq+3to48+8hp/fZz69eubH2H277n+AEhKSnI4pD99+nSTwObl5bmNof5ft3fOnDkVr01fk3N8dJk+viYwGof8/HypjLs60Kuuusr8df5MefLhhx/KhRdeKDVr1pTs7GzzXuiPOntvv/22SZQ1adS4aIK1Y8cOh3V0+7U8SJdfeeWV5v/6ufi///s/KSsrc1j3zTffNI9ne079HOnnSen7PnDgQPP/Hj16VMRM24l9u9NyGf1c6DbZDjFrYqhJTMOGDc173aFDB3nxxRfdxs0+drbD27pvmTp1qmmf2p579uwpmzdvdrjfBx98YOpcbdtl3zb1c63tS8s/6tSpY7bPOZbuaPvU7fa19rpx48bmr/0PJ92mP/3pT+azoDHR2niNo/1ntrLY+toelCaS+hj6Wps1a2aSyKqyxdDXUhlv+3ZtV7rffP/996u8PYgsJLSICpo46OAk/ZK00S9E7T3ULwNnmjh9//33Pj++JoW6c//kk098Wl97tY4//nizE7/88svNztpdYjh8+HBT96UJlSY048ePN1+AX3/9tcfHfuCBB2TixInmy/eOO+7wuN769etNT40mJPq4f/7zn03NsSYJ7777rllHE8dzzz3XbK+716BfRv3793f7+BdccIGpmVXau6P/t133l37Za4z1cXQ79UtcExt9DTaa6Gkvi37Za6+KJi633nqrSdr1x0L79u1lypQpZl3tebdtj26npzZzxRVXmGRef0Q89dRT5kv87rvvdkhAbTTh1i96/YGiX7ram6c9OwcOHPD4uvRLXuOrP2hs1q5dK4cPH6744WPzxRdfSKdOnUwC546+Fk2uNAa213bLLbc4rKO9SkeOHJFp06aZ/2viMXnyZKmK3bt3m7/2nylP9HkuvfRSOXjwoKml1iMlp59+ukPCr+voNmkir9s3YsQIU7t73nnnuSQgmrj26dPHJFP6A0MTI20Xf//73yvWWbhwoakV1bainx19Tk0UbTHV93306NHm//fdd19FzLSd2GzatMk8hrY7bU+6zUqTV61B1fvp8+rnU997/eHmC90W/YxpEq7x0M/z9ddf71Dnqs+lsbVtl62e9qWXXjLbrUm0LtP3T9ddsWKF1+fU/d327dvljDPOcHu7xlR/+Oll165dsmTJEpk0aZK0bdvWtFH7MQr6Y1bb+bPPPms+Y4sXLzaxtf04qiy2vrQHdejQIfPZ08RS49yuXTu59957TTLsC/0M6+vRtqqfH90mbV/+DNTztm/XWNp/RhHlwl3zAPhCa6O0uWptmHM95auvvuqy/t13321us9VU2uqstC7ME6271frbymitptZl3X///RXLBg8ebO5vb8mSJRW1Xc7s67bsa2i1TiwxMdHUgVVWQ9uzZ09TF2lfN6qP261bN+uEE06oWPa3v/3N3Hfjxo0Or0Frj93Va/pS42ur6XSm26fLdXtttE5Nl33++ecVy/bu3Wtqj/X12kycONGsN2/ePI/x8lZDq6/FvmbxvffeM+s+8sgjDuv94Q9/sBISEqzNmzc7vEatKbVftmbNGrP8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m8uvv/4q2dnZ4d6ciEe8iBXtis9gNGGfRawiuV1pz6wms02bNq103bhMaG2T92oyS5LmG40TsfId8SJWwUC7IlbBQtsiVpHarnzteGRQGAAAAKIaCS0AAACiWlwmtGlpaTJp0iTzF8SKtsXnMNKxzyJWtK3w43MY2bFKsHyZCwEAAACIUHHZQwsAAIDYQUILAACAqEZCCwAAgKhGQgsAAICoFpcJ7QsvvCCtWrWS9PR06dKli6xcuVLiybRp0+Sss86SmjVrSsOGDeXKK6+UTZs2OazTvXt3SUhIcLjceuutDuvomdYuvfRSyczMNI9z9913S2lpqcSahx56yCUW7dq1q7i9sLBQRo0aJfXq1ZOsrCwZMGCA7NmzJy5jpZ8r51jpReMT7+3q888/l8svv9yc8UZf93vvvedwu47PnThxojRp0kQyMjKkV69e8tNPPzmsc/DgQbn++uvNROW1a9eW4cOHS15ensM6a9eulfPPP9/s3/RMPY8//rjEUqz0HPH33nuvdOzYUWrUqGHWGTJkiOzcubPStvjYY4/FXKx8aVvDhg1ziUXfvn0d1qFtHeNu/6WXJ554Iu7a1jQfcoVAff99+umncsYZZ5hZEdq2bSuzZ8/2f4OtOPPmm29aqamp1iuvvGKtX7/eGjFihFW7dm1rz549Vrzo06ePNWvWLGvdunXW6tWrrX79+lktWrSw8vLyKta58MILTWx27dpVcTl8+HDF7aWlpdYpp5xi9erVy/r++++tBQsWWPXr17cmTJhgxZpJkyZZJ598skMs9u3bV3H7rbfeajVv3txavHix9e2331rnnHOO1a1bt7iM1d69ex3itHDhQp1FxVq6dKkV7+1KX8v9999vzZs3z8Tk3Xffdbj9scces2rVqmW999571po1a6wrrrjCat26tVVQUFCxTt++fa3TTjvN+vrrr60vvvjCatu2rTVo0KCK2zWWjRo1sq6//nrz+X7jjTesjIwM629/+5sVK7HKyckx7eOtt96yfvjhB2v58uXW2WefbXXu3NnhMVq2bGlNmTLFoa3Z7+NiJVa+tK2hQ4eatmMfi4MHDzqsQ9s6xj5GetFcISEhwfr555/jrm318SFXCMT335YtW6zMzExr7Nix1oYNG6znnnvOSkpKsj766CO/tjfuElrd8Y0aNariellZmdW0aVNr2rRpVrzSJER3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",
+ "text/plain": [
+ ""
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ }
+ ],
+ "source": [
+ "from matplotlib import patches\n",
+ "\n",
+ "fig, ax = plt.subplots(figsize=(8, 6))\n",
+ "\n",
+ "# Defining settings\n",
+ "batch_size = 3 * fun20.dim\n",
+ "n_init = fun20.dim\n",
+ "max_budget = 2000\n",
+ "N_CANDIDATES = 2000\n",
+ "\n",
+ "# Generate initial DoE\n",
+ "X_ini = SobolEngine(dimension=fun20.dim, scramble=True, seed=1).draw(n=n_init).to(**tkwargs)\n",
+ "\n",
+ "# Evaluate FuRBO optimization \n",
+ "print(\"FuRBO on 20D Ackley function\")\n",
+ "X_all, Y_all, C_all = furbo_optimize(fun20,\n",
+ " eval_objective, \n",
+ " eval_constraints,\n",
+ " X_ini,\n",
+ " batch_size = batch_size,\n",
+ " n_init = n_init,\n",
+ " max_budget = max_budget,\n",
+ " N_CANDIDATES = N_CANDIDATES) \n",
+ "\n",
+ "# Print optimization result\n",
+ "print_results(X_all, Y_all, C_all)\n",
+ "\n",
+ "# Plot FuRBO convergence curve\n",
+ "plot_results(ax, \"darkgreen\", Y_all, C_all)\n",
+ "\n",
+ "# Evaluate SCBO optimization \n",
+ "print(\"\\n SCBO on 20D Ackley function\")\n",
+ "X_all, Y_all, C_all = scbo_optimize(fun20,\n",
+ " eval_objective, \n",
+ " eval_constraints,\n",
+ " X_ini,\n",
+ " batch_size = batch_size,\n",
+ " n_init = n_init,\n",
+ " max_budget = max_budget,\n",
+ " N_CANDIDATES = N_CANDIDATES) \n",
+ "\n",
+ "# Print optimization result\n",
+ "print_results(X_all, Y_all, C_all)\n",
+ "\n",
+ "# Plot SCBO convergence curve\n",
+ "plot_results(ax, \"darkorange\", Y_all, C_all)\n",
+ "\n",
+ "plt.xlim([0, len(Y_all)])\n",
+ "\n",
+ "patchList = []\n",
+ "patchList.append(patches.Patch(color='darkgreen', label='FuRBO'))\n",
+ "patchList.append(patches.Patch(color='darkorange', label='SCBO'))\n",
+ "ax.legend(handles=patchList, loc='lower right')\n",
+ "\n",
+ "ax.set_title(\"20D Ackley function with 2 constraints (Batch 3D)\")\n",
+ "\n",
+ "plt.grid(True)\n",
+ "plt.show()"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "378feaab",
+ "metadata": {},
+ "source": [
+ "### Scenario 2: Speed Reducer Objective function\n",
+ "\n",
+ "The problem minimizes the weight $W$ of a speed reducer:\n",
+ "\n",
+ "$W = 0.7854x_1x_2^2(3.3333x_e^2+14.9334x_3-43.0934)-1.508x_1(x_6^2+x_7^2)+7.4777(x_6^3+x_7^3)+0.7854(x_4x_6^2+x_5x_7^2)$\n",
+ "\n",
+ "Under the following constraints:\n",
+ "\n",
+ "$g_1(x) = 27x^{−1}_1 x^{−2}_2 x^{−1}_3 \\leq 1$\n",
+ "\n",
+ "$g_2(x)=397.5x^{−1}_1 x^{−2}_2 x^{−2}_3 \\leq 1$\n",
+ "\n",
+ "$g_3(x)=1.93x^{−1}_2 x^{−1}_3 x^3_4 x^{−4}_6 \\leq 1$\n",
+ "\n",
+ "$g_4(x)=1.93x^{−1}_2 x^{−1}_3 x^3_5 x^{−4}_7 \\leq 1$\n",
+ "\n",
+ "$g_5(x)= \\frac{1}{0.1x^3_6} \\sqrt{\\left( \\frac{745x_4}{x_2x_3} \\right)^2 + 16.9 \\cdot 10^6} \\leq 1100$\n",
+ "\n",
+ "$g_6(x)= \\frac{1}{0.1x^3_7} \\sqrt{\\left( \\frac{745x5}{x_2x_3} \\right)^2 + 157.5 \\cdot 10^6} \\leq 850$\n",
+ "\n",
+ "$g_7(x)=x_2x_3 \\leq 40$\n",
+ "\n",
+ "$g_8(x)=\\frac{x_1}{x_2} \\geq 5$\n",
+ "\n",
+ "$g_9(x)=\\frac{x_1}{x_2} \\leq 12$\n",
+ "\n",
+ "$g_{10}(x)=(1.5x_6 + 1.9)x^{−1}_4 \\leq 1$\n",
+ "\n",
+ "$g_{11}(x)=(1.1x_7 + 1.9)x^{−1}_5 \\leq 1$"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 19,
+ "id": "5a56a1e4",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "from botorch.test_functions.synthetic import SpeedReducer \n",
+ "\n",
+ "funS = SpeedReducer()\n",
+ "\n",
+ "def eval_objective(x):\n",
+ " \"\"\"This is a helper function we use to unnormalize and evalaute a point\"\"\"\n",
+ " return -1 * funS.evaluate_true(unnormalize(x, funS.bounds))\n",
+ "\n",
+ "def eval_constraints(x):\n",
+ " return -1 * funS.evaluate_slack_true(unnormalize(x, funS.bounds))"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "e7668384",
+ "metadata": {},
+ "source": [
+ "#### Evaluate algorithms"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 21,
+ "id": "ea71286a",
+ "metadata": {},
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "FuRBO on speed reducer problem\n",
+ "Samples evaluated: 10 | No feasible point yet! Smallest total violation: 1.47e-01, MND radius: 1.0\n",
+ "Samples evaluated: 17 | Best value: -3.63e+03, MND radius: 1.0\n",
+ "Samples evaluated: 24 | Best value: -3.11e+03, MND radius: 1.0\n",
+ "Samples evaluated: 31 | Best value: -3.11e+03, MND radius: 1.0\n",
+ "Samples evaluated: 38 | Best value: -3.11e+03, MND radius: 1.0\n",
+ "Samples evaluated: 45 | Best value: -3.11e+03, MND radius: 1.0\n",
+ "Samples evaluated: 52 | Best value: -3.06e+03, MND radius: 0.5\n",
+ "Samples evaluated: 59 | Best value: -3.06e+03, MND radius: 0.5\n",
+ "Samples evaluated: 66 | Best value: -3.06e+03, MND radius: 0.5\n",
+ "Samples evaluated: 73 | Best value: -3.04e+03, MND radius: 0.5\n",
+ "Samples evaluated: 80 | Best value: -3.04e+03, MND radius: 0.5\n",
+ "Samples evaluated: 87 | Best value: -3.04e+03, MND radius: 0.5\n",
+ "Samples evaluated: 94 | Best value: -3.04e+03, MND radius: 0.5\n",
+ "Samples evaluated: 101 | Best value: -3.04e+03, MND radius: 0.25\n",
+ "Samples evaluated: 108 | Best value: -3.04e+03, MND radius: 0.25\n",
+ "Samples evaluated: 115 | Best value: -3.04e+03, MND radius: 0.25\n",
+ "Samples evaluated: 122 | Best value: -3.03e+03, MND radius: 0.25\n",
+ "Samples evaluated: 129 | Best value: -3.03e+03, MND radius: 0.5\n",
+ "Samples evaluated: 136 | Best value: -3.03e+03, MND radius: 0.5\n",
+ "Samples evaluated: 143 | Best value: -3.03e+03, MND radius: 0.5\n",
+ "Samples evaluated: 150 | Best value: -3.03e+03, MND radius: 0.25\n",
+ "Samples evaluated: 157 | Best value: -3.03e+03, MND radius: 0.25\n",
+ "Samples evaluated: 164 | Best value: -3.03e+03, MND radius: 0.25\n",
+ "Samples evaluated: 171 | Best value: -3.02e+03, MND radius: 0.125\n",
+ "Samples evaluated: 178 | Best value: -3.02e+03, MND radius: 0.125\n",
+ "Samples evaluated: 185 | Best value: -3.02e+03, MND radius: 0.125\n",
+ "Samples evaluated: 192 | Best value: -3.02e+03, MND radius: 0.125\n",
+ "Samples evaluated: 199 | Best value: -3.01e+03, MND radius: 0.0625\n",
+ "Samples evaluated: 206 | Best value: -3.01e+03, MND radius: 0.0625\n",
+ "Samples evaluated: 213 | Best value: -3.01e+03, MND radius: 0.0625\n",
+ "Optimization finished \n",
+ "\t Optimum: -3.01e+03, \n",
+ "\t X: [[9.02336695e-01 3.16653570e-04 2.27025084e-04 5.73721960e-01\n",
+ " 3.57072089e-01 4.53617526e-01 5.73731164e-01]]\n",
+ "\n",
+ " SCBO on speed reducer problem\n",
+ "Samples evaluated: 10 | No feasible point yet! Smallest total violation: 1.47e-01, TR volume: 0.2097152000000001\n",
+ "Samples evaluated: 17 | Best value: -3.94e+03, TR volume: 0.03737699729156021\n",
+ "Samples evaluated: 24 | Best value: -3.17e+03, TR volume: 0.03373239388744261\n",
+ "Samples evaluated: 31 | Best value: -3.17e+03, TR volume: 0.020444206473667594\n",
+ "Samples evaluated: 38 | Best value: -3.17e+03, TR volume: 0.020444206473667594\n",
+ "Samples evaluated: 45 | Best value: -3.17e+03, TR volume: 0.020444206473667594\n",
+ "Samples evaluated: 52 | Best value: -3.08e+03, TR volume: 0.00027066473891622817\n",
+ "Samples evaluated: 59 | Best value: -3.07e+03, TR volume: 0.00019705197652677295\n",
+ "Samples evaluated: 66 | Best value: -3.07e+03, TR volume: 0.015282894754867538\n",
+ "Samples evaluated: 73 | Best value: -3.07e+03, TR volume: 0.015282894754867538\n",
+ "Samples evaluated: 80 | Best value: -3.07e+03, TR volume: 0.015282894754867538\n",
+ "Samples evaluated: 87 | Best value: -3.07e+03, TR volume: 0.0002285679021852515\n",
+ "Samples evaluated: 94 | Best value: -3.07e+03, TR volume: 0.0002285679021852515\n",
+ "Samples evaluated: 101 | Best value: -3.07e+03, TR volume: 0.0002285679021852515\n",
+ "Samples evaluated: 108 | Best value: -3.05e+03, TR volume: 3.3678680097487228e-06\n",
+ "Samples evaluated: 115 | Best value: -3.05e+03, TR volume: 2.249204192881997e-06\n",
+ "Samples evaluated: 122 | Best value: -3.04e+03, TR volume: 2.249204192881997e-06\n",
+ "Samples evaluated: 129 | Best value: -3.04e+03, TR volume: 2.279091575415814e-06\n",
+ "Samples evaluated: 136 | Best value: -3.04e+03, TR volume: 2.279091575415814e-06\n",
+ "Samples evaluated: 143 | Best value: -3.04e+03, TR volume: 2.279091575415814e-06\n",
+ "Samples evaluated: 150 | Best value: -3.02e+03, TR volume: 2.7096532197889234e-08\n",
+ "Samples evaluated: 157 | Best value: -3.02e+03, TR volume: 2.597092345178189e-08\n",
+ "Samples evaluated: 164 | Best value: -3.02e+03, TR volume: 2.597092345178189e-08\n",
+ "Samples evaluated: 171 | Best value: -3.02e+03, TR volume: 2.597092345178189e-08\n",
+ "Samples evaluated: 178 | Best value: -3.02e+03, TR volume: 2.3164793875383555e-10\n",
+ "Samples evaluated: 185 | Best value: -3.02e+03, TR volume: 2.3164793875383555e-10\n",
+ "Samples evaluated: 192 | Best value: -3.02e+03, TR volume: 2.3164793875383555e-10\n",
+ "Samples evaluated: 199 | Best value: -3.01e+03, TR volume: 2.0939318604694748e-12\n",
+ "Samples evaluated: 206 | Best value: -3.01e+03, TR volume: 1.6188776032377124e-12\n",
+ "Samples evaluated: 213 | Best value: -3.01e+03, TR volume: 2.184814696346965e-10\n",
+ "Optimization finished \n",
+ "\t Optimum: -3.01e+03, \n",
+ "\t X: [[9.02411601e-01 2.88696295e-03 7.05214473e-05 2.30094595e-01\n",
+ " 9.59491912e-01 4.51435260e-01 5.76963988e-01]]\n"
+ ]
+ },
+ {
+ "data": {
+ "image/png": "iVBORw0KGgoAAAANSUhEUgAAArgAAAIQCAYAAABqjx2sAAAAOnRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjEwLjcsIGh0dHBzOi8vbWF0cGxvdGxpYi5vcmcvTLEjVAAAAAlwSFlzAAAPYQAAD2EBqD+naQAAbNdJREFUeJzt3QeYVNX5x/F3d1mWpVfpXUCUIjbEXkBQItgVjYgajUaNiEFERWz5EytqNBJjjx0LMYSgCIgaBVGxIyoiqAiodJayZf7P7+gdpm3f2Z259/t5nmGZmTszd86cufPe977n3IxQKBQyAAAAwCcya3oFAAAAgKpEgAsAAABfIcAFAACArxDgAgAAwFcIcAEAAOArBLgAAADwFQJcAAAA+AoBLgAAAHyFABcAAAC+QoCLGvXII49YRkaGffPNNynzSVx33XVunZCYPiu1jz67ij72tttu80UffO2119xj9TcV1icdX7eiytP23rLPPfdctawbyq9Tp042atSolGq6Z5991po2bWqbN2+2VG+73/zmN5aKrrzySuvfv3+NvDYBbhXTF1Qb0uIu33//fVSn9G7PzMy0xo0bW+/eve3888+3BQsWlPk1i4qK7LHHHnOdSF/GBg0aWPfu3W3kyJE2f/588wMv6PQu2dnZrv3++Mc/2vr162t69VLC7rvvbn379o27/cUXX3Rtduihh8bd99BDD7n7XnnlFUs1M2bMcJ97WR122GHuvXTr1i3h/bNmzQr3nyAHOv/3f/9n06ZNMz968skn7c4776zy51WAM3HiRBsyZIjbxpa0g/fOO+/YH/7wB9t7773ddsovO8tvvfWW+z6m4vb2b3/7W4V2uEtSWFjoPvNLLrnE6tevn/B3W5c6deq4bc7YsWNt7dq1adm2zzzzjP32t79170PvSdvSknaCI997mzZtbPDgwXb33Xfbpk2b4h4zevRo+/DDD+2ll16y6lar2l/R537/+9/bwIEDo24LhUJ2wQUXuC9G27Zto+7bc8897fLLL3f/V+dYvHixTZ061f7xj3/YZZddZnfccUepr6kg795777Xhw4fbGWecYbVq1bIlS5bYf//7X+vSpYvtv//+5hf33Xef29hs2bLFZs+ebX/961/t/ffftzfffNOC7qCDDrIHH3zQNmzYYI0aNQrf/r///c/1iYULF1p+fr770Y28LysrywYMGFDm1+nYsaNt3bo16nmSFeCqX5cnyNUG96uvvnJBxn777Rd13xNPPOHu37ZtW9TtZ555pp122mmWk5NT7nU85JBDXFvUrl3bqkpl1qesAe5JJ51kxx13XLW+blVL1PYKcD/55BP3o1qVfvrpJ7vhhhusQ4cObieypKyx+u0DDzxgffr0cdvfL774wvxAQdj111/vkjhKxlQ1/WYp0VPRALd58+ZVmgH+97//7dZJCadYkb/b2p689957bsdq3rx5btuTam1blt9VvYd9993Xfv7551KX13ehc+fO7vdk1apV7vug75ziFQWy6vueVq1audhER+2GDRtm1YkAt4opUIgNFhR85eXlueAzlgJe7TlFuvnmm+3000+3yZMnuz2qCy+8sNjXW716tftyn3feeXb//fdH3acv3I8//mh+oh9mbci8nQn9IGvvM1FA40cFBQUuY58ooFKAqx0jbSyPPvroqCD2lFNOcT/+2ohF7vCob2pjpKx/WXl77qmoa9euro2eeuqpqP6gHyFlsocOHWrPP/981GMU4OtSEfpBruq2qMz6pOPrVlQy2r44rVu3th9++MH9WL/77rsuECiOttfjxo2z3Nxcu/jii30T4JaHtlE7duwo1+eTajtWDz/8sB144IFxSalEv9u/+93vXOJFQdyXX35Z7FGkVPXPf/7TvSd9p3r16lXq8vp92WeffcLXx48fb3PmzHFlEgpilahT//fo9+fkk0+2r7/+2u30VRdKFKqBAgsFBQpay0IdQx1Oh8L+/Oc/uwxwcZYtW+bu1xcxll5zl112iTu88Prrr7vgsFmzZtawYUNXyrBu3bq4xysDfPDBB1u9evVcAKTg4NNPP41b7vPPP3eBp9ZXGzR1/ESHI/TYI444wr2/du3a2U033eQ2hJWh9ZOlS5dG3a4SDx1OVCazbt267vC8Ar1YCvD0Y6X1VnD097//vVw1p7o9NsOoMpRzzz3XHbrRRlt7uvrR0wbfo0NR2uNt3769W2bXXXd1OzaR7RFZr6qdFa2flv3ss88StoUCXIl8nwrslOE+4YQT3IYl8j7t/OjH13uct+7nnHOOtWzZ0r3WHnvs4coYytIeOvKgMgm1pTaSCiiVkdCRi0S0Q+a9J30GyjB79Dhlb7029i5lMWLECLfTE9mWysZoJ1Mb2rLUnno1beofCpT1ntR+KgUqrQ5Uh/f0/j/66CPX79T/9Pl6ZRHK8qicSN+DHj162Kuvvlri+sSW50ReIjNW6icHHHCA+17ruXWIPLYUQ4/R0Y9HH3007jmKq8HVDrT6gT4n9emLLroo7lCq957VNw8//HD3nvWDecstt5T6ealv7rXXXlG3HXvssW5dIrcj+k7rNm2XErW91uE///mPLV++PPzeYvue+oS2qdr+6DM98sgjXca/NHrvCm7LQt+dyB/3inj88cddv1M7NmnSxGWrY8uIqvpz0dEwPZ/3mtqO67fL64M6BC/annnt6/UV/V/BvI6SeOs0c+bMMvfLRDW4Xn/UNmvMmDHWokUL91t0/PHHRyVu9Dj9tuh75a2Xd4hdGUZlRhVw6vPWOmh7p3Klkmi7qfWPPRpbEq9/6GiZR9sAvSdtO/T6Wkbb18gsaWltW9b+IKVtr4qj36GKZs89+m2fMGGC+/5pfSN57fivf/3LqhMZ3CTTF0yF6vqCF/dDn4j2BvVF1iFnbZy00SjucLEXXGgPSV+A0mhDpMMg+mLpEIwOT6hTej8YogD7rLPOcrU1CrwUHGg5bRwWLVoUfi/asHh7uSom1wZI71eHP5Up03sQHcbQBlbZNW85BTiV/SHwNgL60nu0J6k9TG1IVUOlL672xvUFfOONN8KZvY8//tiOOuoot+FUW2jdtLx+oCpq5cqV7vn1Q6NDW7vttpsLGrVBVxsq86q/Cnx0u3Y0dNhTWVftBStLFFtDqHXXBlfPpx8O7Ugkog2afugiyzUUNCqwVv/TRT8W3qE1vaZ4Aa6OBii76/1YqV0UTChY37hxY4mHfRVYnHrqqa6GfNKkSW6HSY9LlP0Q/XCqJEfvX6+nH1wFOtrDV+mDbldb6odIfbE8tCOpz1P9WZ+593oKZiJ3+EqjwEc7bnof+i4o0NePlfpVcd9Hj96/AmQdYdD3Ut8d/V8BgNpRJUtaz1tvvdW9xrfffltsFl3togA5kndINPL93HXXXS57oiNF+syffvpp99rTp093O6eitlS2SX3UO/SqnYziqB0VIOgHSjtp3vZC/Up9KbJMRe9ZO5VaX+1IqM8rk6k+EXlEIdFOqn741Me0w60ddj23vrf6vnqHNfV/3ZZoZ16uvvpqV57z3XffuaNfElk7KX/5y1/cc/zpT39yy6rfqb3KM+Yh2dTeand9X3UoWNsMrZ+2a9peJeNz0ZEflbqpL1566aVue6PgTK+rfqrHamdYR0bUtt5RNG0jPFo/bfu17dD93m9EWfplSVQDq+27ts3a3qvf6zW0Eyu67tXJqg+Itw1XO2l75PV59TFl4LXTP2jQoGJfU98vrWvsjlfk77rKVkRtpd9EHZ5X4Kkg1aPtl7ZpZ599tgtu9Xup3z391fgYbftKa9uy9IfKbq+qisqcrrrqKhd866iyR4kmbWfUN1V6WW1CSKp///vfSr+G/va3v8Xd17Fjx9DQoUOLfezkyZPdY//1r3+V+BojR450yzVp0iR0/PHHh2677bbQ4sWL45Z7+OGH3XJ77713aMeOHeHbb7nllqjX2bRpU6hx48ah8847L+rxq1atCjVq1Cjq9iOPPDLUu3fv0LZt28K3FRUVhQ444IBQt27dwreNHj3avcaCBQvCt61Zs8Y9n25ftmxZie9x4sSJbrklS5aEfvzxx9A333wTeuihh0K5ubmhFi1ahLZs2RJ+bb3u4MGD3f89eXl5oc6dO4cGDRoUvu24444L1alTJ7R8+fLwbZ999lkoKyvLvZZH66brar9Yul3rFvlZZGZmhhYuXBi3rLc+N954Y6hevXqhL774Iur+K6+80r32ihUrol63YcOGrq3K4uSTT3Zt4n2+kyZNcu9b1Ad32WWX8LJ/+tOf3PN///337vq5554bat26deinn36Kes7TTjvNfU5qw+LaQ32gXbt2ru94XnvtNbec+nlsWzZr1iy0du3a8O3qe7pd3xfPRRddFPU5lObQQw8N7bHHHu7/++yzj3s/sm7dulDt2rVDjz76aGju3LnuOadOnRr3vYjsg1pn3fb666+Hb9NnkJOTE7r88svDt3nPp7+R66HbnnzyyfBtn3/+ubtNfWP+/Pnh219++eW4tky0PpHU/zt06ODafPPmzeHbvc/Hoz7Qq1ev0BFHHBF1u/reWWedFfe8sa+r96t2O+qoo0KFhYXh5e655x63nL5/se/5scceC9+2ffv2UKtWrUInnnhiqCT6ruixM2bMcNc/+ugjd119uX///uHlhg0bFurXr1+Jba/taWR/i122Z8+ebr08d911l7v9448/LnEdE61vou1BrPL24S+//NL1EW3HI9s8cvuRjM9l+PDh4e9OcW699dZi+6XXtz/99NO4+8raL/W5RfZLrz8OHDgwalt+2WWXue3k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QJojwPVdBjfTbOoRZqvfq9gTchYzAACQ5ihR8FkNbp/aoYoHt1K7fuVXCgAAoAYR4PqsRKFDZXPyHQdV8gkAAABqFiUKPitRaJEVil6gThOz1gNKfyKdorfrMLNuJ1TxGgIAAFQvAlyfBbgts4qiF2h7sNlx/6relQIAAKhBlCj4rERhl9gAt27L6l0hAACAGkaA67NBZs0zowNeq9eqelcIAACghhHg+qxEoXkGAS4AAAg2AlyflSg0y4wOeClRAAAAQUOA67MMbjPLj16AEgUAABAwBLg+qsGtl2FWN4NBZgAAINgIcH1UotAy0aRvZHABAEDAEOD6qEShVWyAW6sup94FAACBQ4DrowC3ZVbMnWRvAQBAABHg+qhEIS6Dy0keAABAABHg+miQWVwNLhlcAAAQQAS4fqrBpUQBAACAANfXsyhQogAAAAKIDK6fZ1GgRAEAAAQQAa6PanAZZAYAAECA668SBWpwAQAAyOD6pUShYaZZbmw+vl7LGlknAAAA35YoDBs2zDp06GB16tSx1q1b25lnnmkrV65MuOxXX31lDRo0sMaNG8fdN3XqVNttt93c8/Tu3dtmzJgRdX8oFLJrr73WvUZubq4NHDjQvvzySwtSBjcueysMMgMAAAGU1AD38MMPt2effdaWLFlizz//vC1dutROOumkuOXy8/NtxIgRdvDBB8fd99Zbb7n7zj33XFu0aJEdd9xx7vLJJ5+El7nlllvs7rvvtilTptiCBQusXr16NnjwYNu2bZsFJYMbV39bu4FZdt0aWScAAADfBriXXXaZ7b///taxY0c74IAD7Morr7T58+e7gDbSNddc4zK0p5xyStxz3HXXXTZkyBAbO3as9ezZ02688Ubba6+97J577glnb++88073HMOHD7c+ffrYY4895jLF06ZNs6AMMuMkDwAAANU8i8LatWvtiSeecIFudnZ2+PY5c+a4EoR777034ePefvttV3IQSdlZ3S7Lli2zVatWRS3TqFEj69+/f3iZIJQoMIMCAADAL2IPbFe5cePGuWxrXl6ey+ZOnz49fN/PP/9so0aNsscff9waNmyY8PEKXlu2jB4speu63bvfu624ZWJt377dXTwbN250f5VZjs0up7odBTsS1uAW5e5ihVX4Xrx2Sbf2qU60EW1EP+J7lgrYFtFGfu1H5VmXcge4KjO4+eab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/e53v7OXX345vMwzzzxjY8aMsYkTJ9r7779vffv2tcGDB9uaNWssCDW4TBMGAAAQLWmjkS677LLw/zt27GhXXnmlHXfccZafn2/Z2dm2ePFiu+++++yTTz6xHj16uOU6d+4c9RxTpkxxt91+++3ues+ePe3NN9+0yZMnuyBW7rjjDjvvvPPs7LPPDj/mP//5jz300EPuNf1eosA0YQAAANGqZbj92rVr7YknnrADDjjABbfy73//27p06WLTp0+3IUOGuHKFgQMH2i233GJNmzZ1y7z99tvutkgKbJXJlR07dth7771n48ePD9+fmZnpHqPHFmf79u3u4tm4caP7q+Bbl3SRX5ifcJBZfkGR/q3a1/q1XdKpfaobbUQb0Y/4nqUCtkW0kV/7UXnWJakB7rhx4+yee+6xvLw823///V0w6/n6669t+fLlNnXqVHvsscessLDQZX1VxjBnzhy3zKpVq6xly5ZRz6nrCki3bt1q69atc49LtIxKHoozadIku/766+Nunzt3rtWtW9fSxYaNG6x+gkFm/335FQslaaDZrFmzkvK8fkIb0Ub0I75nqYBtEW3kt36keDIpAa4O+d98880lLqPSg9122839f+zYsXbuuee6QFYB5ciRI12Qm5GRYUVFRS6LquBWg8zkwQcftL333tvV7XplC8mgjK/qdj0KmNu3b+9qfZs1a2bpIndBrmVtiL/96GN+Y5aRaHqFyu01qZMPGjQonIUHbUQ/qnp812gf+lDy8T1LzzbyjrhXeYB7+eWX26hRo0pcRmUHnubNm7uLAljVzyqInD9/vg0YMMDNrFCrVq1wcCtaRlasWOEC3FatWtnq1aujnl/XGzZsaLm5uZaVleUuiZbRY4ujGRl0iaUPMFU+xLLOolA3No7NyLTs2rWT9prp1kY1gTaijehHfM9SAdsi2shv/ag861GuALdFixbuUhHK2IpX+3rggQdaQUGBm12ha9eu7rYvvvgiPChNFAjPmDEj6nm0N6HbpXbt2i7jO3v2bDeAzXsdXb/44ostkCd6yOQsZgAAINiSEg0tWLDAFi5caAcddJA1adLEBbETJkxwgawXnGogmOa3Peecc+zOO+90gelFF13kUuFeVveCCy5wNbxXXHGFW061uZp6TLMkeFRqcNZZZ9k+++zjphjTc23ZsiU8q0LgZlHgJA8AACDgkjIP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VFRkmzdvtvr161fb+1e75+Xl2Zo1a9z11q1bV/i5CHD9pFYZphYDAAAlKigocIFWmzZtrG7duoEuz6hTp061Bvi5ub/EMgpyd9lllwqXKwRvl8Svmu1uVr9tTa8FAABpz6s5rV27dk2vSiDV/XWnQnXAFUUGtyY038Os+8nhqzM/fdk2bt0Yvt6/S3/r2LRD2Z+vQQezvUebVXONDAAAflbdtaeounYnwK0JPU755fKrc19vayvX7wxw/3vyddax15AaWTUAAIB0R4CbgtOElXkeXAAAUG1W/LzCftr8U7W9XvP6za1Ds3Ic0UUYAW4N27pjq23L31axM5kBAIBqC257XNPDthVE/2YnU51adWzJTUvKHOSOGjXKHn300bjbv/zyS9t1113L/FjNf9uuXTs79thj7S9/+UvUQLvI8gENANNAvJNOOskmTZpkOTk54fs0vdjEiRNt5syZ9tNPP7kZEY477ji79tprrVmzZpZsDDJLsbOYCQEuAACpRZnb6gxuRa9X3ozxkCFD3BRnkZfOnTuX67Fff/213X777fbII4/YddddF7fcww8/7JbTPMF/+9vf7J///KfddNNN4fv1+H322ccF1k899ZR99dVXNmXKFJs9e7YNGDDA1q6NPnKdDGRwa9i6LfEBbuO6jWtkXQAAQHpTFrVVq1Zx2dn169fbtGnTwreNHj3aPvjgA3vttdcSPrZt27Z22GGH2auvvlrsiRhE8wQPHz7c3n///fD9F110kZuB4pVXXglP+9WhQwfr16+fde3a1a6++mq77777LJnI4KZY/W2j3EaWxQkbAABADfrkk0/snXfeKXWqtC+++MLmzJlj/fv3d9eVnX355ZftD3/4Qzi49SgoPuOMM+yZZ55xJ3VIJjK4NWxtHgPMAABA1Zg+fbo7+5jn6KOPtnr16pXrsTrRxfbt290JHv7617/GLTdixAhXf+st95vf/MbGjx/v7lNZgoLXnj17JnwN3b5u3Tr78ccf3YkckoUMboqVKFB/CwAAKurwww93pQfe5e677y73YxcsWGAjR4502dYTTzwxbrnJkye75T788EMXFCuLe+aZZ0Ytk+wMbWnI4KbaFGH1mCIMAABUjLK1u8bMmKBMbGzAmegsYZGPffDBB61Pnz7u73nnnRdXauAt16NHD9u0aZPL6mqgmW7XTAuLFy+2448/Pu41dHuTJk2sRYsWSf2IyeCm2CwKTesyRRgAAKg6LVq0cLMeRFIGtiQKiseMGeOm9dq6dWuJy6pcQbScpgAbNGiQm10h9nGrVq2yJ554wk499dSknyWOALeG/bjpx6jrZHABAEBVOuKII+zdd9+1xx57zNXIan5aDSIrjeatVfB67733Rt2uGRkUrK5cudLmzZtnN9xwg3Xv3j1cd3vPPfe42tzBgwfb66+/7ubE1Xy4Cnw1O8Of//znpH/ABLg17LMfPou63qlZpxpbFwAA4D+DBw+2CRMm2BVXXGH77ruvKylQjW1pdMIHTfl1yy232JYtW8K3n3322e7EDToZhEoT9thjD/vvf//rlpdu3bq5gLpLly52yimnuKnBzj//fFfj+/bbb1vTpsk/Wk0Nbg1SPczH338cdVvvtr1rbH0AAEDxp83VmcWq+0xmet2y0okZinP99de7S3kfO27cuPAMCeUZPNaxY8cS1yfZCHBr0Pfrvrf1eeujbuvdjgAXAIBUo9Pl6rS55T2zWGUouC3raXoRjQC3Bn30/UdR1xvmNrQOTenIAACkIgWbBJzpgRrcGvTxd9HlCb3a9Er6qEIAAAC/I8CtQdTfAgAAVD0C3FQKcKm/BQAASI8AV3Oh7bnnnu7we+zEwh999JEdfPDBVqdOHWvfvr2biiLW1KlTbbfddnPL9O7d22bMmBF1v0b0aSJiTVmRm5trAwcOdPO8pbL8gnxb/MPiqNuYQQEAACBNAlzNu9amTZu42zdu3GhHHXWUm0rivffes1tvvdWuu+46u//++8PLvPXWW26OtXPPPdcWLVrkJh3WJXKCYgXFOtfylClT3PmTdao5zfm2bVv1TeVRXl+s/sLyC6NPk0eACwAAkAYBrib+feWVV+y2226Lu0+na9uxY4c99NBDbpLg0047zf74xz/aHXfcEV7mrrvusiFDhtjYsWPdGTJuvPFG22uvvdxZMrzs7Z133mnXXHONDR8+3J03WWfq0Nk1pk2bZulSntCuSTvOYgYAAJDqAe7q1avtvPPOs3/+859Wt27duPt1NotDDjnEateuHb5NmdclS5bYunXrwsuo5CCSltHtsmzZMne6uMhlGjVqZP379w8vk4oYYAYAAJBm8+Aqszpq1Ci74IILbJ999rFvvvkmbhkFpp07d466rWXLluH7mjRp4v56t0Uuo9u95SIfl2iZRDXBukSWSkh+fr67VIcPv/0w6vrurXevtteuCG/dUnkdaxptRBvRj/iepQK2RZVvI92uOKaoqMhdwjauMNtafSd6sNzmZg1rZn780K9nLPPaoTrp9fS6+hyysrLCt5cnBil3gHvllVfazTffXOIyixcvdmUJOtdx5OndUsWkSZMSnq5u7ty5CTPNybDwq4VR1wt/LIwbPJeKZs2aVdOrkPJoI9qIfsT3LBWwLap4G9WqVctatWplmzdvdqWUkrH5W2v43L6WUbgzQZZsoawc23jSQgvVb1/mx/z000/2f//3fy4O+/HHH61x48bWq1cvV+q5//77hwf4qxxU45yU5Gvbtq0ddNBBdskll9iuu+5qK1assL59+4afMzs729q1a2enn366XX755VFz9ivm01ioN954w8V9mjDghBNOsNGjR1c4plKbb9261V5//XUrKCgI356Xl5e8AFdvTJnZknTp0sXmzJnjSgRycnKi7lM294wzzrBHH33UdR6VMUTyrus+72+iZSLv927TLAqRy2jmhkQUdI8ZMyZ8XR+uPpDDDz/cmjVrZsm2cetGWzN9TdRtZxxzhvVtt7MzpRrtNWlDMGjQINfRQRvRj/iusS1KTWyvK99GGqT+7bffWv369d0MTs7W7dUa3Iper0HWdrOGDcv8mGHDhrkAUXGW4jHFQ4rJ9J4aNmxo06dPt5NPPtkN8n/88ceta9eutmbNGnvuuedcoPr000+79y0ay6S4Tc/35ptv2vnnn+8mBtDAf5k/f757niOPPNI9r46ev/POOy6Y/t///mezZ8+OKkMtK62rZsVSGWu4/SOOuCclwG3RooW7lEazGtx0003h6xr0pdrZZ555xtXHyoABA+zqq692Hc3rYOpwPXr0cOUJ3jJqIO0JeLSMbheVOCjI1TJeQKsG0GwKF154YcJ1U9AdG3iL1qE6grcly5dEXc/KzHJz4KZD4FhdbZTOaCPaiH7E9ywVsC2qeBsVFha6LGVmZqa7ON7fauZev4yvvX79epdJfe211+zQQw8Nx0le5lYZUAWnxxxzjL344ovhxynIVVylx0e+56ZNm7rkoa7reRQ0a7pXXVcJgcZZaQIAPZf3GC2nqV379evnJgoYN25chd6z2j/28ylP/JG0T6tDhw4uJe5dunfvHm5EpblFqW5F9mrsTz/91AW/aozI7Oql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ABeEPlTW3/nSfscKCwtdcLtjxw5766237NFHH7VHHnnE7aSnlFDA7bfffqGLLroofL2wsDDUpk2b0KRJk0JBt2bNGk0hF5o3b174tkMPPTR06aWXhoJq4sSJob59+ya8b/369aHs7OzQ1KlTw7ctXrzYteHbb78dCir1l65du4aKiorc9aD3IfWHF198MXxd7dKqVavQrbfeGtWXcnJyQk899ZS7/tlnn7nHLVy4MLzMf//731BGRkbo+++/D/m9jRJ555133HLLly8P39axY8fQ5MmTQ36XqH3OOuus0PDhw4t9DH0ontrriCOOiLotKH2ouN/5svyOzZgxI5SZmRlatWpVeJn77rsv1LBhw9D27dtDqSLQGVztfbz33nvucKAnMzPTXX/77bct6DZs2OD+Nm3aNOr2J554wpo3b269evWy8ePHu+xKkOjQsQ6BdenSxWVEdKhG1Je0NxzZn1S+0KFDh8D2J33HHn/8cTvnnHNcpsQT9D4UadmyZbZq1aqofqNzratcyus3+qtDyvvss094GS2v7ZUyvkHdPqlPqV0i6XCyDq3269fPHXpOtcOmyfTaa6+5w8U9evSwCy+80H7++efwffShaDrk/p///MeVacQKUh/aEPM7X5bfMf1VuVDLli3Dy+iI08aNG90RglRRywLsp59+cqn2yA9JdP3zzz+3ICsqKrLRo0fbgQce6IIQz+mnn24dO3Z0Ad5HH33k6uJ0qFCHDINAQYcOxegHRIeurr/+ejv44IPtk08+cUFK7dq1435w1Z90XxCpBm79+vWuPtAT9D4Uy+sbibZD3n36q8AlUq1atdyPUhD7lko31G9GjBjh6kk9f/zjH22vvfZy7aJDp9p50vf0jjvuML9TeYIOI3fu3NmWLl1qV111lR199NEuGMnKyqIPxdBhddWhxpaQBakPFSX4nS/L75j+JtpeefelikAHuCieanQUtEXWl0pkvZb24DQo5sgjj3Qb1K5du/q+SfWD4enTp48LeBWsPfvss25wEKI9+OCDrs0UzHqC3odQOcounXLKKW5g3n333Rd1n8ZTRH4/9UP9+9//3g2sSdfTjZbVaaedFvW90vvX90lZXX2/EE31tzoCp8HlQe1DFxXzO+8XgS5R0CFS7dnGjg7U9VatWllQXXzxxW4Awty5c61du3YlLqsAT7766isLIu3ldu/e3b1/9RkdklfGMlJQ+9Py5cvt1Vdftd/97nclLhf0PuT1jZK2Q/obO/BVh001Kj5IfcsLbtW3NEAmMntbXN9SO33zzTcWNCqh0m+c972iD+30xhtvuKNGpW2b/NyHLi7md74sv2P6m2h75d2XKgId4GrPbO+997bZs2dHpex1fcCAARY0yoio07/44os2Z84cd6irNB988IH7qyxcEGmKHWUe9f7Vl7Kzs6P6kzaiqtENYn96+OGH3WF1jbYtSdD7kL5n+lGI7DeqZVNtrddv9Fc/OKqP8+g7qu2Vt4MQlOBWNfDacVKNZGnUt1SnHFveEQTfffedq8H1vlf0oegjS9pea8aFoPWhUCm/82X5HdPfjz/+OGqn29vh3H333S1lhALu6aefdqOVH3nkETfK9Pzzzw81btw4anRgUFx44YWhRo0ahV577bXQDz/8EL7k5eW5+7/66qvQDTfcEHr33XdDy5YtC/3rX/8KdenSJXTIIYeEguLyyy937aP3/7///S80cODAUPPmzd1IVLngggtCHTp0CM2ZM8e104ABA9wlaDQbidph3LhxUbcHtQ9t2rQptGjRInfRZveOO+5w//dmAPjLX/7itjtqj48++siN7u7cuXNo69at4ecYMmRIqF+/fqEFCxaE3nzzzVC3bt1CI0aMCAWhjXbs2BEaNmxYqF27dqEPPvggavvkjdp+66233Oh33b906dLQ448/HmrRokVo5MiRIb+3j+7705/+5Ea563v16quvhvbaay/XR7Zt2xZ+jiD3Ic+GDRtCdevWdaP+Y/m9D5Xld74sv2MFBQWhXr16hY466ijXVjNnznTtNH78+FAqCXyAK3/961/dh1m7dm03bdj8+fNDQaQNQqLLww8/7O5fsWKFC0SaNm3qdgp23XXX0NixY90GIyhOPfXUUOvWrV1fadu2rbuuoM2jgOQPf/hDqEmTJm4jevzxx7uNR9C8/PLLru8sWbIk6vag9qG5c+cm/G5paidvqrAJEyaEWrZs6drlyCOPjGu7n3/+2QUj9evXd9PxnH322e4HPQhtpKCtuO2THifvvfdeqH///u7Hu06dOqGePXuG/u///i8qwPNr+yg4UbChIENTPGmqq/POOy8uURPkPuT5+9//HsrNzXXTYcXyex8qy+98WX/Hvvnmm9DRRx/t2lJJHiV/8vPzQ6kkQ//UdBYZAAAAqCqBrsEFAACA/xDgAgAAwFcIcAEAAOArBLgAAADwFQJcAAAA+AoBLgAAAHyFABcAAAC+QoALAAAAXyHABQAAgK8Q4AIAAMBXCHABAADgKwS4AAAAMD/5f/w71+ymYNq/AAAAAElFTkSuQmCC",
+ "text/plain": [
+ ""
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ }
+ ],
+ "source": [
+ "from matplotlib import patches\n",
+ "\n",
+ "fig, ax = plt.subplots(figsize=(8, 6))\n",
+ "\n",
+ "# Defining settings\n",
+ "batch_size = funS.dim\n",
+ "n_init = int(10)\n",
+ "max_budget = 210\n",
+ "N_CANDIDATES = 2000\n",
+ "\n",
+ "# Generate initial DoE\n",
+ "X_ini = SobolEngine(dimension=funS.dim, scramble=True, seed=1).draw(n=n_init).to(**tkwargs)\n",
+ "\n",
+ "# Evaluate FuRBO optimization \n",
+ "print(\"FuRBO on speed reducer problem\")\n",
+ "X_all, Y_all, C_all = furbo_optimize(funS,\n",
+ " eval_objective, \n",
+ " eval_constraints,\n",
+ " X_ini,\n",
+ " batch_size = batch_size,\n",
+ " n_init = n_init,\n",
+ " max_budget = max_budget,\n",
+ " N_CANDIDATES = N_CANDIDATES) \n",
+ "\n",
+ "# Print optimization result\n",
+ "print_results(X_all, Y_all, C_all)\n",
+ "\n",
+ "# Plot FuRBO convergence curve\n",
+ "plot_results(ax, \"darkgreen\", Y_all, C_all)\n",
+ "\n",
+ "# Evaluate SCBO optimization \n",
+ "print(\"\\n SCBO on speed reducer problem\")\n",
+ "X_all, Y_all, C_all = scbo_optimize(funS,\n",
+ " eval_objective, \n",
+ " eval_constraints,\n",
+ " X_ini,\n",
+ " batch_size = batch_size,\n",
+ " n_init = n_init,\n",
+ " max_budget = max_budget,\n",
+ " N_CANDIDATES = N_CANDIDATES) \n",
+ "\n",
+ "# Print optimization result\n",
+ "print_results(X_all, Y_all, C_all)\n",
+ "\n",
+ "# Plot SCBO convergence curve\n",
+ "plot_results(ax, \"darkorange\", Y_all, C_all)\n",
+ "\n",
+ "plt.xlim([0, len(Y_all)])\n",
+ "\n",
+ "patchList = []\n",
+ "patchList.append(patches.Patch(color='darkgreen', label='FuRBO'))\n",
+ "patchList.append(patches.Patch(color='darkorange', label='SCBO'))\n",
+ "ax.legend(handles=patchList, loc='lower right')\n",
+ "\n",
+ "ax.set_title(\"7D Speed Reducer Weight Minimization with 11 constraints (Batch 1D)\")\n",
+ "\n",
+ "plt.grid(True)\n",
+ "plt.show()"
+ ]
+ }
+ ],
+ "metadata": {
+ "kernelspec": {
+ "display_name": "LastBoTorch",
+ "language": "python",
+ "name": "python3"
+ },
+ "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.13.10"
+ }
+ },
+ "nbformat": 4,
+ "nbformat_minor": 5
+}
diff --git a/notebooks_community/FuRBO/graphical_abstract_furbo.png b/notebooks_community/FuRBO/graphical_abstract_furbo.png
new file mode 100644
index 0000000000..7238a9856f
Binary files /dev/null and b/notebooks_community/FuRBO/graphical_abstract_furbo.png differ