diff --git a/tutorials/baxus.ipynb b/tutorials/baxus.ipynb new file mode 100644 index 0000000000..7724e941a4 --- /dev/null +++ b/tutorials/baxus.ipynb @@ -0,0 +1,1033 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## BO with BAxUS and TS/EI\n", + "\n", + "In this tutorial, we show how to implement **B**ayesian optimization with **a**daptively e**x**panding s**u**bspace**s** (BAxUS) [1] in a closed loop in BoTorch.\n", + "The tutorial is purposefully similar to the [TuRBO tutorial](https://botorch.org/tutorials/turbo_1) to highlight the differences in the implementations.\n", + "\n", + "This implementation supports either Expected Improvement (EI) or Thompson sampling (TS). We optimize the Branin2 function [2] with 498 dummy dimensions$ and show that BAxUS outperforms EI as well as Sobol.\n", + "\n", + "Since BoTorch assumes a maximization problem, we will attempt to maximize $-f(x)$ to achieve $\\max_{x\\in \\mathcal{X}} -f(x)=0$.\n", + "\n", + "- [1]: [Papenmeier, Leonard, et al. Increasing the Scope as You Learn: Adaptive Bayesian Optimization in Nested Subspaces. Advances in Neural Information Processing Systems. 2022](https://openreview.net/pdf?id=e4Wf6112DI)\n", + "- [2]: [Branin Test Function](https://www.sfu.ca/~ssurjano/branin.html)\n" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": { + "tags": [] + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Running on cuda\n" + ] + } + ], + "source": [ + "import math\n", + "import os\n", + "from dataclasses import dataclass\n", + "\n", + "import botorch\n", + "import gpytorch\n", + "import matplotlib.pyplot as plt\n", + "import numpy as np\n", + "import torch\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", + "from torch.quasirandom import SobolEngine\n", + "\n", + "from botorch.acquisition.analytic import ExpectedImprovement\n", + "from botorch.exceptions import ModelFittingError\n", + "from botorch.fit import fit_gpytorch_mll\n", + "from botorch.generation import MaxPosteriorSampling\n", + "from botorch.models import SingleTaskGP\n", + "from botorch.optim import optimize_acqf\n", + "from botorch.test_functions import Branin\n", + "\n", + "device = torch.device(\"cuda\" if torch.cuda.is_available() else \"cpu\")\n", + "print(f\"Running on {device}\")\n", + "dtype = torch.double\n", + "SMOKE_TEST = os.environ.get(\"SMOKE_TEST\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Optimize the augmented Branin function\n", + "\n", + "The goal is to minimize the embedded Branin function\n", + "\n", + "$f(x_1, x_2, \\ldots, x_{20}) = \\left (x_2-\\frac{5.1}{4\\pi^2}x_1^2+\\frac{5}{\\pi}x_1-6\\right )^2+10\\cdot \\left (1-\\frac{1}{8\\pi}\\right )\\cos(x_1)+10$\n", + "\n", + "with bounds [-5, 10] for $x_1$ and [0, 15] for $x_2$ (all other dimensions are ignored). The function has three minima with an optimal value of $0.397887$.\n", + "\n", + "As mentioned above, since botorch assumes a maximization problem, we instead maximize $-f(x)$." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Define a function with dummy variables\n", + "\n", + "We first define a new function where we only pass the first two input dimensions to the actual Branin function." + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [ + "branin = Branin(negate=True).to(device=device, dtype=dtype)\n", + "\n", + "\n", + "def branin_emb(x):\n", + " \"\"\"x is assumed to be in [-1, 1]^D\"\"\"\n", + " lb, ub = branin.bounds\n", + " return branin(lb + (ub - lb) * (x[..., :2] + 1) / 2)" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [ + "fun = branin_emb\n", + "dim = 500\n", + "\n", + "n_init = 10\n", + "max_cholesky_size = float(\"inf\") # Always use Cholesky" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Maintain the BAxUS state\n", + "BAxUS needs to maintain a state, which includes the length of the trust region, success and failure counters, success and failure tolerance, etc. \n", + "In contrast to TuRBO, the failure tolerance depends on the target dimensionality.\n", + "\n", + "In this tutorial we store the state in a dataclass and update the state of TuRBO after each batch evaluation. \n", + "\n", + "**Note**: These settings assume that the domain has been scaled to $[-1, 1]^d$" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [ + "@dataclass\n", + "class BaxusState:\n", + " dim: int\n", + " eval_budget: int\n", + " new_bins_on_split: int = 3\n", + " d_init: int = float(\"nan\") # Note: post-initialized\n", + " target_dim: int = float(\"nan\") # Note: post-initialized\n", + " n_splits: int = float(\"nan\") # Note: post-initialized\n", + " length: float = 0.8\n", + " length_init: float = 0.8\n", + " length_min: float = 0.5**7\n", + " length_max: float = 1.6\n", + " failure_counter: int = 0\n", + " success_counter: int = 0\n", + " success_tolerance: int = 3\n", + " best_value: float = -float(\"inf\")\n", + " restart_triggered: bool = False\n", + "\n", + " def __post_init__(self):\n", + " n_splits = round(math.log(self.dim, self.new_bins_on_split + 1))\n", + " self.d_init = 1 + np.argmin(\n", + " np.abs(\n", + " (1 + np.arange(self.new_bins_on_split))\n", + " * (1 + self.new_bins_on_split) ** n_splits\n", + " - self.dim\n", + " )\n", + " )\n", + " self.target_dim = self.d_init\n", + " self.n_splits = n_splits\n", + "\n", + " @property\n", + " def split_budget(self) -> int:\n", + " return round(\n", + " -1\n", + " * (self.new_bins_on_split * self.eval_budget * self.target_dim)\n", + " / (self.d_init * (1 - (self.new_bins_on_split + 1) ** (self.n_splits + 1)))\n", + " )\n", + "\n", + " @property\n", + " def failure_tolerance(self) -> int:\n", + " if self.target_dim == self.dim:\n", + " return self.target_dim\n", + " k = math.floor(math.log(self.length_min / self.length_init, 0.5))\n", + " split_budget = self.split_budget\n", + " return min(self.target_dim, max(1, math.floor(split_budget / k)))\n", + "\n", + "\n", + "def update_state(state, Y_next):\n", + " if max(Y_next) > state.best_value + 1e-3 * math.fabs(state.best_value):\n", + " state.success_counter += 1\n", + " state.failure_counter = 0\n", + " else:\n", + " state.success_counter = 0\n", + " state.failure_counter += 1\n", + "\n", + " if state.success_counter == state.success_tolerance: # Expand trust region\n", + " state.length = min(2.0 * state.length, state.length_max)\n", + " state.success_counter = 0\n", + " elif state.failure_counter == state.failure_tolerance: # Shrink trust region\n", + " state.length /= 2.0\n", + " state.failure_counter = 0\n", + "\n", + " state.best_value = max(state.best_value, max(Y_next).item())\n", + " if state.length < state.length_min:\n", + " state.restart_triggered = True\n", + " return state" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Create a BAxUS embedding\n", + "\n", + "We now show how to create the BAxUS embedding. The essential idea is to assign input dimensions to target dimensions and to assign a sign $\\in \\pm 1$ to each input dimension, similar to the HeSBO embedding. \n", + "We create the embedding matrix that is used to project points from the target to the input space. The matrix is sparse, each column has precisely one non-zero entry that is either 1 or -1." + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": { + "tags": [] + }, + "outputs": [ + { + "data": { + "text/plain": [ + "tensor([[ 1., 0., -1., 0., 0., 1., 0., 0., -1., 0.],\n", + " [ 0., -1., 0., 0., 0., 0., -1., 0., 0., -1.],\n", + " [ 0., 0., 0., -1., 1., 0., 0., 1., 0., 0.]], device='cuda:0',\n", + " dtype=torch.float64)" + ] + }, + "execution_count": 5, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "def embedding_matrix(input_dim: int, target_dim: int) -> torch.Tensor:\n", + " if (\n", + " target_dim >= input_dim\n", + " ): # return identity matrix if target size greater than input size\n", + " return torch.eye(input_dim, device=device, dtype=dtype)\n", + "\n", + " input_dims_perm = (\n", + " torch.randperm(input_dim, device=device) + 1\n", + " ) # add 1 to indices for padding column in matrix\n", + "\n", + " bins = torch.tensor_split(\n", + " input_dims_perm, target_dim\n", + " ) # split dims into almost equally-sized bins\n", + " bins = torch.nn.utils.rnn.pad_sequence(\n", + " bins, batch_first=True\n", + " ) # zero pad bins, the index 0 will be cut off later\n", + "\n", + " mtrx = torch.zeros(\n", + " (target_dim, input_dim + 1), dtype=dtype, device=device\n", + " ) # add one extra column for padding\n", + " mtrx = mtrx.scatter_(\n", + " 1,\n", + " bins,\n", + " 2 * torch.randint(2, (target_dim, input_dim), dtype=dtype, device=device) - 1,\n", + " ) # fill mask with random +/- 1 at indices\n", + "\n", + " return mtrx[:, 1:] # cut off index zero as this corresponds to zero padding\n", + "\n", + "\n", + "embedding_matrix(10, 3) # example for an embedding matrix" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Function to increase the embedding\n", + "\n", + "Next, we write a helper function to increase the embedding and to bring observations to the increased target space." + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [ + "def increase_embedding_and_observations(\n", + " S: torch.Tensor, X: torch.Tensor, n_new_bins: int\n", + ") -> torch.Tensor:\n", + " assert X.size(1) == S.size(0), \"Observations don't lie in row space of S\"\n", + "\n", + " S_update = S.clone()\n", + " X_update = X.clone()\n", + "\n", + " for row_idx in range(len(S)):\n", + " row = S[row_idx]\n", + " idxs_non_zero = torch.nonzero(row)\n", + " idxs_non_zero = idxs_non_zero[torch.randperm(len(idxs_non_zero))].squeeze()\n", + "\n", + " non_zero_elements = row[idxs_non_zero].squeeze()\n", + "\n", + " n_row_bins = min(\n", + " n_new_bins, len(idxs_non_zero)\n", + " ) # number of new bins is always less or equal than the contributing input dims in the row minus one\n", + "\n", + " new_bins = torch.tensor_split(idxs_non_zero, n_row_bins)[\n", + " 1:\n", + " ] # the dims in the first bin won't be moved\n", + " elements_to_move = torch.tensor_split(non_zero_elements, n_row_bins)[1:]\n", + "\n", + " new_bins_padded = torch.nn.utils.rnn.pad_sequence(\n", + " new_bins, batch_first=True\n", + " ) # pad the tuples of bins with zeros to apply _scatter\n", + " els_to_move_padded = torch.nn.utils.rnn.pad_sequence(\n", + " elements_to_move, batch_first=True\n", + " )\n", + "\n", + " S_stack = torch.zeros(\n", + " (n_row_bins - 1, len(row) + 1), device=device, dtype=dtype\n", + " ) # submatrix to stack on S_update\n", + "\n", + " S_stack = S_stack.scatter_(\n", + " 1, new_bins_padded + 1, els_to_move_padded\n", + " ) # fill with old values (add 1 to indices for padding column)\n", + "\n", + " S_update[\n", + " row_idx, torch.hstack(new_bins)\n", + " ] = 0 # set values that were move to zero in current row\n", + "\n", + " X_update = torch.hstack(\n", + " (X_update, X[:, row_idx].reshape(-1, 1).repeat(1, len(new_bins)))\n", + " ) # repeat observations for row at the end of X (column-wise)\n", + " S_update = torch.vstack(\n", + " (S_update, S_stack[:, 1:])\n", + " ) # stack onto S_update except for padding column\n", + "\n", + " return S_update, X_update" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "metadata": { + "tags": [] + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "S before increase\n", + "tensor([[-1., -1., 0., 0., 0., -1., -1., 0., -1., 0.],\n", + " [ 0., 0., -1., -1., 1., 0., 0., 1., 0., -1.]], device='cuda:0',\n", + " dtype=torch.float64)\n", + "X before increase\n", + "tensor([[79, 84],\n", + " [85, 65],\n", + " [46, 11],\n", + " [95, 34],\n", + " [14, 36],\n", + " [10, 55],\n", + " [48, 47]])\n", + "S after increase\n", + "tensor([[-1., -1., 0., 0., 0., 0., 0., 0., 0., 0.],\n", + " [ 0., 0., 0., 0., 1., 0., 0., 0., 0., -1.],\n", + " [ 0., 0., 0., 0., 0., -1., 0., 0., -1., 0.],\n", + " [ 0., 0., 0., 0., 0., 0., -1., 0., 0., 0.],\n", + " [ 0., 0., -1., 0., 0., 0., 0., 1., 0., 0.],\n", + " [ 0., 0., 0., -1., 0., 0., 0., 0., 0., 0.]], device='cuda:0',\n", + " dtype=torch.float64)\n", + "X after increase\n", + "tensor([[79, 84, 79, 79, 84, 84],\n", + " [85, 65, 85, 85, 65, 65],\n", + " [46, 11, 46, 46, 11, 11],\n", + " [95, 34, 95, 95, 34, 34],\n", + " [14, 36, 14, 14, 36, 36],\n", + " [10, 55, 10, 10, 55, 55],\n", + " [48, 47, 48, 48, 47, 47]])\n" + ] + } + ], + "source": [ + "S = embedding_matrix(10, 2)\n", + "X = torch.randint(100, (7, 2))\n", + "print(f\"S before increase\\n{S}\")\n", + "print(f\"X before increase\\n{X}\")\n", + "\n", + "S, X = increase_embedding_and_observations(S, X, 3)\n", + "print(f\"S after increase\\n{S}\")\n", + "print(f\"X after increase\\n{X}\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Take a look at the state" + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "metadata": { + "tags": [] + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "BaxusState(dim=500, eval_budget=500, new_bins_on_split=3, d_init=2, target_dim=2, n_splits=4, length=0.8, length_init=0.8, length_min=0.0078125, length_max=1.6, failure_counter=0, success_counter=0, success_tolerance=3, best_value=-inf, restart_triggered=False)\n" + ] + } + ], + "source": [ + "state = BaxusState(dim=dim, eval_budget=500)\n", + "print(state)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Generate initial points\n", + "This generates an initial set of Sobol points that we use to start of the BO loop." + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [ + "def get_initial_points(dim, n_pts, seed=0):\n", + " sobol = SobolEngine(dimension=dim, scramble=True, seed=seed)\n", + " X_init = (\n", + " 2 * sobol.draw(n=n_pts).to(dtype=dtype, device=device) - 1\n", + " ) # points have to be in [-1, 1]^d\n", + " return X_init" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Generate new batch\n", + "Given the current `state` and a probabilistic (GP) `model` built from observations `X` and `Y`, we generate a new batch of points. \n", + "\n", + "This method works on the domain $[-1, +1]^d$, so make sure to not pass in observations from the true domain. `unnormalize` is called before the true function is evaluated which will first map the points back to the original domain.\n", + "\n", + "We support either TS and qEI which can be specified via the `acqf` argument." + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [ + "def create_candidate(\n", + " state,\n", + " model, # GP model\n", + " X, # Evaluated points on the domain [-1, 1]^d\n", + " Y, # Function values\n", + " n_candidates=None, # Number of candidates for Thompson sampling\n", + " num_restarts=10,\n", + " raw_samples=512,\n", + " acqf=\"ts\", # \"ei\" or \"ts\"\n", + "):\n", + " assert acqf in (\"ts\", \"ei\")\n", + " assert X.min() >= -1.0 and X.max() <= 1.0 and torch.all(torch.isfinite(Y))\n", + " if n_candidates is None:\n", + " n_candidates = min(5000, max(2000, 200 * X.shape[-1]))\n", + "\n", + " # Scale the TR to be proportional to the lengthscales\n", + " x_center = X[Y.argmax(), :].clone()\n", + " weights = model.covar_module.base_kernel.lengthscale.detach().view(-1)\n", + " weights = weights / weights.mean()\n", + " weights = weights / torch.prod(weights.pow(1.0 / len(weights)))\n", + " tr_lb = torch.clamp(x_center - weights * state.length, -1.0, 1.0)\n", + " tr_ub = torch.clamp(x_center + weights * state.length, -1.0, 1.0)\n", + "\n", + " if acqf == \"ts\":\n", + " dim = X.shape[-1]\n", + " sobol = SobolEngine(dim, scramble=True)\n", + " pert = sobol.draw(n_candidates).to(dtype=dtype, device=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, dtype=dtype, device=device) <= prob_perturb\n", + " ind = torch.where(mask.sum(dim=1) == 0)[0]\n", + " mask[ind, torch.randint(0, dim, size=(len(ind),), device=device)] = 1\n", + "\n", + " # Create candidate points from the perturbations and the mask\n", + " X_cand = x_center.expand(n_candidates, dim).clone()\n", + " X_cand[mask] = pert[mask]\n", + "\n", + " # Sample on the candidate points\n", + " thompson_sampling = MaxPosteriorSampling(model=model, replacement=False)\n", + " with torch.no_grad(): # We don't need gradients when using TS\n", + " X_next = thompson_sampling(X_cand, num_samples=1)\n", + "\n", + " elif acqf == \"ei\":\n", + " ei = ExpectedImprovement(model, train_Y.max(), maximize=True)\n", + " X_next, acq_value = optimize_acqf(\n", + " ei,\n", + " bounds=torch.stack([tr_lb, tr_ub]),\n", + " q=1,\n", + " num_restarts=num_restarts,\n", + " raw_samples=raw_samples,\n", + " )\n", + "\n", + " return X_next" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Optimization loop\n", + "This simple loop runs one instance of BAxUS with Thompson sampling until convergence.\n", + "\n", + "BAxUS works on a fixed evaluation budget and shrinks the trust region until the minimal trust region size is reached (`state[\"restart_triggered\"]` is set to `True`).\n", + "Then, BAxUS increases the target space and carries over the observations to the updated space. \n" + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "metadata": { + "tags": [] + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "iteration 11, d=2) Best value: -2.74, TR length: 0.4\n", + "iteration 12, d=2) Best value: -2.64, TR length: 0.4\n", + "iteration 13, d=2) Best value: -2.55, TR length: 0.4\n", + "iteration 14, d=2) Best value: -2.55, TR length: 0.2\n", + "iteration 15, d=2) Best value: -2.55, TR length: 0.1\n", + "iteration 16, d=2) Best value: -2.55, TR length: 0.05\n", + "iteration 17, d=2) Best value: -2.55, TR length: 0.025\n", + "iteration 18, d=2) Best value: -2.55, TR length: 0.0125\n", + "iteration 19, d=2) Best value: -2.55, TR length: 0.00625\n", + "increasing target space\n", + "new dimensionality: 6\n", + "iteration 20, d=6) Best value: -2.54, TR length: 0.8\n", + "iteration 21, d=6) Best value: -2.54, TR length: 0.4\n", + "iteration 22, d=6) Best value: -1.79, TR length: 0.4\n", + "iteration 23, d=6) Best value: -0.628, TR length: 0.4\n", + "iteration 24, d=6) Best value: -0.456, TR length: 0.8\n", + "iteration 25, d=6) Best value: -0.456, TR length: 0.4\n", + "iteration 26, d=6) Best value: -0.42, TR length: 0.4\n", + "iteration 27, d=6) Best value: -0.42, TR length: 0.2\n", + "iteration 28, d=6) Best value: -0.42, TR length: 0.1\n", + "iteration 29, d=6) Best value: -0.42, TR length: 0.05\n", + "iteration 30, d=6) Best value: -0.42, TR length: 0.025\n", + "iteration 31, d=6) Best value: -0.41, TR length: 0.025\n", + "iteration 32, d=6) Best value: -0.403, TR length: 0.025\n", + "iteration 33, d=6) Best value: -0.4, TR length: 0.05\n", + "iteration 34, d=6) Best value: -0.399, TR length: 0.05\n", + "iteration 35, d=6) Best value: -0.399, TR length: 0.025\n", + "iteration 36, d=6) Best value: -0.398, TR length: 0.025\n", + "iteration 37, d=6) Best value: -0.398, TR length: 0.0125\n", + "iteration 38, d=6) Best value: -0.398, TR length: 0.00625\n", + "increasing target space\n", + "new dimensionality: 18\n", + "iteration 39, d=18) Best value: -0.398, TR length: 0.4\n", + "iteration 40, d=18) Best value: -0.398, TR length: 0.2\n", + "iteration 41, d=18) Best value: -0.398, TR length: 0.1\n", + "iteration 42, d=18) Best value: -0.398, TR length: 0.05\n", + "iteration 43, d=18) Best value: -0.398, TR length: 0.025\n", + "iteration 44, d=18) Best value: -0.398, TR length: 0.0125\n", + "iteration 45, d=18) Best value: -0.398, TR length: 0.00625\n", + "increasing target space\n", + "new dimensionality: 54\n", + "iteration 46, d=54) Best value: -0.398, TR length: 0.4\n", + "iteration 47, d=54) Best value: -0.398, TR length: 0.2\n", + "iteration 48, d=54) Best value: -0.398, TR length: 0.1\n", + "iteration 49, d=54) Best value: -0.398, TR length: 0.05\n", + "iteration 50, d=54) Best value: -0.398, TR length: 0.025\n", + "iteration 51, d=54) Best value: -0.398, TR length: 0.0125\n", + "iteration 52, d=54) Best value: -0.398, TR length: 0.00625\n", + "increasing target space\n", + "new dimensionality: 162\n", + "iteration 53, d=162) Best value: -0.398, TR length: 0.8\n", + "iteration 54, d=162) Best value: -0.398, TR length: 0.8\n", + "iteration 55, d=162) Best value: -0.398, TR length: 0.4\n", + "iteration 56, d=162) Best value: -0.398, TR length: 0.4\n", + "iteration 57, d=162) Best value: -0.398, TR length: 0.4\n", + "iteration 58, d=162) Best value: -0.398, TR length: 0.2\n", + "iteration 59, d=162) Best value: -0.398, TR length: 0.2\n", + "iteration 60, d=162) Best value: -0.398, TR length: 0.2\n", + "iteration 61, d=162) Best value: -0.398, TR length: 0.1\n", + "iteration 62, d=162) Best value: -0.398, TR length: 0.1\n", + "iteration 63, d=162) Best value: -0.398, TR length: 0.1\n", + "iteration 64, d=162) Best value: -0.398, TR length: 0.05\n", + "iteration 65, d=162) Best value: -0.398, TR length: 0.05\n", + "iteration 66, d=162) Best value: -0.398, TR length: 0.05\n", + "iteration 67, d=162) Best value: -0.398, TR length: 0.025\n", + "iteration 68, d=162) Best value: -0.398, TR length: 0.025\n", + "iteration 69, d=162) Best value: -0.398, TR length: 0.025\n", + "iteration 70, d=162) Best value: -0.398, TR length: 0.0125\n", + "iteration 71, d=162) Best value: -0.398, TR length: 0.0125\n", + "iteration 72, d=162) Best value: -0.398, TR length: 0.0125\n", + "iteration 73, d=162) Best value: -0.398, TR length: 0.00625\n", + "increasing target space\n", + "new dimensionality: 486\n", + "iteration 74, d=486) Best value: -0.398, TR length: 0.8\n", + "iteration 75, d=486) Best value: -0.398, TR length: 0.8\n", + "iteration 76, d=486) Best value: -0.398, TR length: 0.8\n", + "iteration 77, d=486) Best value: -0.398, TR length: 0.8\n", + "iteration 78, d=486) Best value: -0.398, TR length: 0.8\n", + "iteration 79, d=486) Best value: -0.398, TR length: 0.8\n", + "iteration 80, d=486) Best value: -0.398, TR length: 0.8\n", + "iteration 81, d=486) Best value: -0.398, TR length: 0.8\n", + "iteration 82, d=486) Best value: -0.398, TR length: 0.8\n", + "iteration 83, d=486) Best value: -0.398, TR length: 0.4\n", + "iteration 84, d=486) Best value: -0.398, TR length: 0.4\n", + "iteration 85, d=486) Best value: -0.398, TR length: 0.4\n", + "iteration 86, d=486) Best value: -0.398, TR length: 0.4\n", + "iteration 87, d=486) Best value: -0.398, TR length: 0.4\n", + "iteration 88, d=486) Best value: -0.398, TR length: 0.4\n", + "iteration 89, d=486) Best value: -0.398, TR length: 0.4\n", + "iteration 90, d=486) Best value: -0.398, TR length: 0.4\n", + "iteration 91, d=486) Best value: -0.398, TR length: 0.4\n", + "iteration 92, d=486) Best value: -0.398, TR length: 0.4\n", + "iteration 93, d=486) Best value: -0.398, TR length: 0.2\n", + "iteration 94, d=486) Best value: -0.398, TR length: 0.2\n", + "iteration 95, d=486) Best value: -0.398, TR length: 0.2\n", + "iteration 96, d=486) Best value: -0.398, TR length: 0.2\n", + "iteration 97, d=486) Best value: -0.398, TR length: 0.2\n", + "iteration 98, d=486) Best value: -0.398, TR length: 0.2\n", + "iteration 99, d=486) Best value: -0.398, TR length: 0.2\n", + "iteration 100, d=486) Best value: -0.398, TR length: 0.2\n" + ] + } + ], + "source": [ + "evaluation_budget = 100\n", + "\n", + "state = BaxusState(dim=dim, eval_budget=evaluation_budget - n_init)\n", + "S = embedding_matrix(input_dim=state.dim, target_dim=state.d_init)\n", + "\n", + "X_baxus_target = get_initial_points(state.d_init, n_init)\n", + "X_baxus_input = X_baxus_target @ S\n", + "Y_baxus = torch.tensor(\n", + " [branin_emb(x) for x in X_baxus_input], dtype=dtype, device=device\n", + ").unsqueeze(-1)\n", + "\n", + "\n", + "NUM_RESTARTS = 10 if not SMOKE_TEST else 2\n", + "RAW_SAMPLES = 512 if not SMOKE_TEST else 4\n", + "N_CANDIDATES = min(5000, max(2000, 200 * dim)) if not SMOKE_TEST else 4\n", + "\n", + "# Disable input scaling checks as we normalize to [-1, 1]\n", + "with botorch.settings.validate_input_scaling(False):\n", + "\n", + " for _ in range(evaluation_budget - n_init): # Run until evaluation budget depleted\n", + " # Fit a GP model\n", + " train_Y = (Y_baxus - Y_baxus.mean()) / Y_baxus.std()\n", + " likelihood = GaussianLikelihood(noise_constraint=Interval(1e-8, 1e-3))\n", + " covar_module = (\n", + " ScaleKernel( # Use the same lengthscale prior as in the TuRBO paper\n", + " MaternKernel(\n", + " nu=2.5,\n", + " ard_num_dims=state.target_dim,\n", + " lengthscale_constraint=Interval(0.005, 10),\n", + " ),\n", + " outputscale_constraint=Interval(0.05, 10),\n", + " )\n", + " )\n", + " model = SingleTaskGP(\n", + " X_baxus_target, train_Y, covar_module=covar_module, likelihood=likelihood\n", + " )\n", + " mll = ExactMarginalLogLikelihood(model.likelihood, model)\n", + "\n", + " # Do the fitting and acquisition function optimization inside the Cholesky context\n", + " with gpytorch.settings.max_cholesky_size(max_cholesky_size):\n", + " # Fit the model\n", + " try:\n", + " fit_gpytorch_mll(mll)\n", + " except ModelFittingError:\n", + " # Right after increasing the target dimensionality, the covariance matrix becomes indefinite\n", + " # In this case, the Cholesky decomposition might fail due to numerical instabilities\n", + " # In this case, we revert to Adam-based optimization\n", + " optimizer = torch.optim.Adam([{\"params\": model.parameters()}], lr=0.1)\n", + "\n", + " for _ in range(100):\n", + " optimizer.zero_grad()\n", + " output = model(X_baxus_target)\n", + " loss = -mll(output, train_Y.flatten())\n", + " loss.backward()\n", + " optimizer.step()\n", + "\n", + " # Create a batch\n", + " X_next_target = create_candidate(\n", + " state=state,\n", + " model=model,\n", + " X=X_baxus_target,\n", + " Y=train_Y,\n", + " n_candidates=N_CANDIDATES,\n", + " num_restarts=NUM_RESTARTS,\n", + " raw_samples=RAW_SAMPLES,\n", + " acqf=\"ts\",\n", + " )\n", + "\n", + " X_next_input = X_next_target @ S\n", + "\n", + " Y_next = torch.tensor(\n", + " [branin_emb(x) for x in X_next_input], dtype=dtype, device=device\n", + " ).unsqueeze(-1)\n", + "\n", + " # Update state\n", + " state = update_state(state=state, Y_next=Y_next)\n", + "\n", + " # Append data\n", + " X_baxus_input = torch.cat((X_baxus_input, X_next_input), dim=0)\n", + " X_baxus_target = torch.cat((X_baxus_target, X_next_target), dim=0)\n", + " Y_baxus = torch.cat((Y_baxus, Y_next), dim=0)\n", + "\n", + " # Print current status\n", + " print(\n", + " f\"iteration {len(X_baxus_input)}, d={len(X_baxus_target.T)}) Best value: {state.best_value:.3}, TR length: {state.length:.3}\"\n", + " )\n", + "\n", + " if state.restart_triggered:\n", + " state.restart_triggered = False\n", + " print(\"increasing target space\")\n", + " S, X_baxus_target = increase_embedding_and_observations(\n", + " S, X_baxus_target, state.new_bins_on_split\n", + " )\n", + " print(f\"new dimensionality: {len(S)}\")\n", + " state.target_dim = len(S)\n", + " state.length = state.length_init\n", + " state.failure_counter = 0\n", + " state.success_counter = 0" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## GP-EI\n", + "As a baseline, we compare BAxUS to Expected Improvement (EI)" + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "metadata": { + "tags": [] + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "11) Best value: -7.04e-01\n", + "12) Best value: -7.04e-01\n", + "13) Best value: -7.04e-01\n", + "14) Best value: -7.04e-01\n", + "15) Best value: -7.04e-01\n", + "16) Best value: -7.04e-01\n", + "17) Best value: -7.04e-01\n", + "18) Best value: -7.04e-01\n", + "19) Best value: -7.04e-01\n", + "20) Best value: -7.04e-01\n", + "21) Best value: -7.04e-01\n", + "22) Best value: -7.04e-01\n", + "23) Best value: -7.04e-01\n", + "24) Best value: -7.04e-01\n", + "25) Best value: -7.04e-01\n", + "26) Best value: -7.04e-01\n", + "27) Best value: -7.04e-01\n", + "28) Best value: -7.04e-01\n", + "29) Best value: -7.04e-01\n", + "30) Best value: -7.04e-01\n", + "31) Best value: -7.04e-01\n", + "32) Best value: -7.04e-01\n", + "33) Best value: -7.04e-01\n", + "34) Best value: -7.04e-01\n", + "35) Best value: -7.04e-01\n", + "36) Best value: -7.04e-01\n", + "37) Best value: -7.04e-01\n", + "38) Best value: -7.04e-01\n", + "39) Best value: -7.04e-01\n", + "40) Best value: -7.04e-01\n", + "41) Best value: -7.04e-01\n", + "42) Best value: -7.04e-01\n", + "43) Best value: -7.04e-01\n", + "44) Best value: -7.04e-01\n", + "45) Best value: -7.04e-01\n", + "46) Best value: -7.04e-01\n", + "47) Best value: -7.04e-01\n", + "48) Best value: -7.04e-01\n", + "49) Best value: -7.04e-01\n", + "50) Best value: -7.04e-01\n", + "51) Best value: -7.04e-01\n", + "52) Best value: -7.04e-01\n", + "53) Best value: -7.04e-01\n", + "54) Best value: -7.04e-01\n", + "55) Best value: -7.04e-01\n", + "56) Best value: -7.04e-01\n", + "57) Best value: -7.04e-01\n", + "58) Best value: -7.04e-01\n", + "59) Best value: -7.04e-01\n", + "60) Best value: -7.04e-01\n", + "61) Best value: -7.04e-01\n", + "62) Best value: -7.04e-01\n", + "63) Best value: -7.04e-01\n", + "64) Best value: -7.04e-01\n", + "65) Best value: -7.04e-01\n", + "66) Best value: -7.04e-01\n", + "67) Best value: -7.04e-01\n", + "68) Best value: -7.04e-01\n", + "69) Best value: -7.04e-01\n", + "70) Best value: -7.04e-01\n", + "71) Best value: -7.04e-01\n", + "72) Best value: -7.04e-01\n", + "73) Best value: -7.04e-01\n", + "74) Best value: -7.04e-01\n", + "75) Best value: -7.04e-01\n", + "76) Best value: -7.04e-01\n", + "77) Best value: -7.04e-01\n", + "78) Best value: -7.04e-01\n", + "79) Best value: -7.04e-01\n", + "80) Best value: -7.04e-01\n", + "81) Best value: -7.04e-01\n", + "82) Best value: -7.04e-01\n", + "83) Best value: -7.04e-01\n", + "84) Best value: -7.04e-01\n", + "85) Best value: -7.04e-01\n", + "86) Best value: -7.04e-01\n", + "87) Best value: -7.04e-01\n", + "88) Best value: -7.04e-01\n", + "89) Best value: -7.04e-01\n", + "90) Best value: -7.04e-01\n", + "91) Best value: -7.04e-01\n", + "92) Best value: -7.04e-01\n", + "93) Best value: -7.04e-01\n", + "94) Best value: -7.04e-01\n", + "95) Best value: -7.04e-01\n", + "96) Best value: -7.04e-01\n", + "97) Best value: -7.04e-01\n", + "98) Best value: -7.04e-01\n", + "99) Best value: -7.04e-01\n", + "100) Best value: -7.04e-01\n" + ] + } + ], + "source": [ + "X_ei = get_initial_points(dim, n_init)\n", + "Y_ei = torch.tensor(\n", + " [branin_emb(x) for x in X_ei], dtype=dtype, device=device\n", + ").unsqueeze(-1)\n", + "\n", + "# Disable input scaling checks as we normalize to [-1, 1]\n", + "with botorch.settings.validate_input_scaling(False):\n", + " while len(Y_ei) < len(Y_baxus):\n", + " train_Y = (Y_ei - Y_ei.mean()) / Y_ei.std()\n", + " likelihood = GaussianLikelihood(noise_constraint=Interval(1e-8, 1e-3))\n", + " model = SingleTaskGP(X_ei, train_Y, likelihood=likelihood)\n", + " mll = ExactMarginalLogLikelihood(model.likelihood, model)\n", + " optimizer = torch.optim.Adam([{\"params\": model.parameters()}], lr=0.1)\n", + " model.train()\n", + " model.likelihood.train()\n", + " for _ in range(50):\n", + " optimizer.zero_grad()\n", + " output = model(X_ei)\n", + " loss = -mll(output, train_Y.squeeze())\n", + " loss.backward()\n", + " optimizer.step()\n", + "\n", + " # Create a batch\n", + " ei = ExpectedImprovement(model, train_Y.max(), maximize=True)\n", + " candidate, acq_value = optimize_acqf(\n", + " ei,\n", + " bounds=torch.stack(\n", + " [\n", + " -torch.ones(dim, dtype=dtype, device=device),\n", + " torch.ones(dim, dtype=dtype, device=device),\n", + " ]\n", + " ),\n", + " q=1,\n", + " num_restarts=NUM_RESTARTS,\n", + " raw_samples=RAW_SAMPLES,\n", + " )\n", + " Y_next = torch.tensor(\n", + " [branin_emb(x) for x in candidate], dtype=dtype, device=device\n", + " ).unsqueeze(-1)\n", + "\n", + " # Append data\n", + " X_ei = torch.cat((X_ei, candidate), axis=0)\n", + " Y_ei = torch.cat((Y_ei, Y_next), axis=0)\n", + "\n", + " # Print current status\n", + " print(f\"{len(X_ei)}) Best value: {Y_ei.max().item():.2e}\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Sobol" + ] + }, + { + "cell_type": "code", + "execution_count": 13, + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [ + "X_Sobol = (\n", + " SobolEngine(dim, scramble=True, seed=0)\n", + " .draw(len(X_baxus_input))\n", + " .to(dtype=dtype, device=device)\n", + " * 2\n", + " - 1\n", + ")\n", + "Y_Sobol = torch.tensor(\n", + " [branin_emb(x) for x in X_Sobol], dtype=dtype, device=device\n", + ").unsqueeze(-1)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Compare the methods\n", + "\n", + "We show the regret of the different methods." + ] + }, + { + "cell_type": "code", + "execution_count": 14, + "metadata": { + "tags": [] + }, + "outputs": [ + { + "data": { + "image/png": "iVBORw0KGgoAAAANSUhEUgAAAxYAAAKBCAYAAADKqj3oAAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjYuMywgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy/P9b71AAAACXBIWXMAAA9hAAAPYQGoP6dpAACYXElEQVR4nOzdd3hUVf7H8c8kmfSeQEIgIKD0XkVEiiCWtRcUV6Ou+lsNNtQVXRXYVVld3cWSVdeGXewFKyKICtKRXqSXECCk18nM/f3BZshM2iQzk5kk79fz5DH3zLnnfm9ygvOde4rJMAxDAAAAAOCGAF8HAAAAAKD5I7EAAAAA4DYSCwAAAABuI7EAAAAA4DYSCwAAAABuI7EAAAAA4DYSCwAAAABuI7EAAAAA4DYSCwAAAABuI7EAgFZk9+7dMplMMplMOumkk3wdjsd4675OOukke7u7d+/2WLue1FJ/p83NokWL7L+HMWPG+DocwCdILAA/MmfOHPv/mFz9uvHGGxt0jQULFujaa69Vt27dFBERofj4ePXr10/33nuvtmzZ0qi4N2/erHvvvVf9+vVTfHy8IiIi1K1bN6WlpWnBggUut1PXfUZGRiolJUU9e/bUxIkT9cADD+ijjz5Sfn5+o2J2xXXXXdfg3wdvLNDcVX2DXNtXcHCw2rRpo+HDh+uOO+7QypUrfR02AD8Q5OsAADSN/Px83XzzzZo7d65DeXFxsXJycrR+/Xo9/fTTmjlzpu6//36X23300Uc1c+ZMWSwWh/Lt27dr+/bteuONN3TVVVfpxRdfVFRUVKPjLyoqUlFRkTIzM7VlyxZ99913kqSIiAhdeeWVmjp1qnr16tXo9gG4zmKx6OjRozp69KiWL1+uZ555RpMmTdJLL73k1t85gOaNxALwUz169NCZZ55Zb73TTjut3joWi0UXX3yxfvjhB3tZnz59NGjQIJWWluqnn35SZmamLBaLHnjgAVksFj388MP1tvvwww/r73//u/24Xbt2GjVqlEJDQ7Vq1Spt3LhRkvTuu+8qOztbX375pYKCXPtn56KLLlL79u3txxUVFcrJyVF2drbWrFmjY8eOSTqecLzyyit666239Nhjj+muu+6SyWRy6RoN4ervo9Ipp5zi8RgAX0hPT69WVlJSov3792vJkiUqLCyUJM2dO1f79+/XokWLXP47B9DCGAD8xmuvvWZIMiQZaWlpHmv3oYcesrcbGhpqvPvuuw6vl5WVGffee6+9jslkMhYtWlRnm99//729viTj3nvvNcrKyhzqvPPOO0ZoaKi9zsyZM+tss2p7CxcurLPuxo0bjdtuu82IiIhwOC89Pb3O8xoiLS3NK78PX9q1a5f9njp16uTrcDzGW/fVqVMne7u7du3yWLue5Ol7X7hwocPfVF0KCwuNKVOmONTPyMhwOwYAzRNzLIAW7vDhw/rXv/5lP549e7auvPJKhzrBwcF64oknNGnSJEmSYRj1Doeq+vqVV16pJ554QsHBwQ51rrrqKv373/+2Hz/55JM6evRoo++lql69eumZZ57R2rVr1bdvX3t5RkaGnn/+eY9cA0DdIiIi9Oyzz2rixIn2srffftuHEQHwJRILoIV7/fXXVVRUJEnq1q2bbr755lrrPvHEEwoIOP7PwtKlS7VmzZoa661YsUIrVqyQJAUEBOiJJ56otc3/+7//sw8LKigo0Jtvvtmo+6jNySefrIULFyo1NdVe9sADD3h1UjcAR5MnT7Z/v2nTJh9GAsCXSCyAFu7TTz+1f1+5ylFtOnbsqHHjxtmPP/nkk3rbHD9+vMObemcmk0lpaWn1tumOhIQEvfLKK/bj3NxcZWRkePw6njBmzBj7yjqLFi2SJGVmZmrmzJkaOHCg4uPjFRoaqh49emjatGn2uSRV7d+/Xw888IAGDhyouLg4RUVFacCAAXrsscdUUlLSqLgWLFigq666Sl27dlVYWJjatGmjUaNG6bnnnlNZWVmD2ioqKtLzzz+v888/X506dVJ4eLiioqJ0yimn6IYbbnCY6+OKzMxM/fWvf1W/fv0UHR2t6Oho9e7dW3fddZe2bt3aoLYqlZWV6dlnn9WoUaPUpk0bhYWFqWvXrpo8ebIWLlzYqDal5nHv3tCuXTv795UfZNSk6kprc+bMkXT87/Xpp5/WGWecofbt2ysoKEgmk0m5ubkO5x4+fFivvfaa0tLS7H8rZrNZsbGx6tGjh66//np9++23LsU7Y8YMexwzZsyQdHwe1xtvvKHx48erffv2CgkJUbt27XTRRRdp3rx59bbpynKztS0NvHLlSt14443q1q2bwsPDFRcXp2HDhumxxx6r8+cJ+B1fj8UCcIKn51iUlJQYAQEB9jaXLFlS7zmPPvqovf7IkSNrrDNixAh7nccee6zeNn/55Rd7/cDAQKO0tLTGepLrcyxq0rdvX/v5/fr1a/D5zrwxx2L06NEO9/jtt98aCQkJDvde9atTp07G7t277ee/8sorRkhISK31e/fubRw+fLjW6zuPxy8vLzduvvnmWtuTZPTs2dPYunWrS/f3/vvvG8nJyXW2J8n4wx/+YOTm5tbb3scff2zExsbW2k5ISIjx0ksvNWiewaZNm4zu3bvXGd+f//xno7y8vEFzLJrDvbuiIXMsKr3xxhv2+qmpqbXWq/o39dprrxk///yzkZqaWuP95eTk2M97+umnjcDAwHp/tpKMcePGGUePHq0z3unTp9vrT58+3di/f79x2mmn1dnu9ddfb1itVpd+bqNHj66xjvPvymazGQ8//LDDv9POX507dzZ27NhR5/0A/oJlGwA/lZubqw8++EAbN25UXl6eoqOjlZKSohEjRqhv374urXy0detW2Ww2ScefHAwcOLDecwYNGmT/fvPmzTXWqVpetX5tql7XarVq27ZtDvMiPOXyyy/X+vXrJUkbNmxQbm6uYmNjPX4dT1m7dq0eeOABlZSUqEOHDho5cqSioqK0bds2/fTTTzIMQ3v27NE555yj9evXa+7cufrTn/4k6fiqU8OGDVNoaKjWr1+v5cuXS5I2btyoa665Rt98841LMdx3333673//K0nq16+fBgwYIMMwtGrVKvuQls2bN2vcuHFaunRpnU+n/v3vf+vuu++WYRiSpOjoaI0YMUIdOnSQ1WrVxo0btXLlShmGoXnz5mnMmDH65ZdfFB4eXmN7X375pa644gpVVFRIOj7sbuTIkerWrZsKCwu1ePFiZWZm6qabbtIzzzzj0v3u2bNHZ555pjIzM+1lvXv31qBBg2QymbR69Wpt2LBBL7zwQq1xNdd796aqy1iPGjXKpXN+//133XnnncrLy1NUVJTOOOMMpaSkKCcnR4sXL3aoe/DgQVmtVklSly5d1LNnT7Vp00ahoaHKzc3V+vXr7avQ/fDDDxo/frx+/fVXhYSE1BtHYWGhzj77bG3YsEHh4eEaNWqUUlNTVVBQoIULF+rw4cOSpNdee03du3fXfffd59L9uWLmzJn629/+JkkaMGCA+vbtK7PZrLVr12r16tWSpF27dumiiy7S6tWrWW0L/s+XWQ0AR1WfWNT1dcoppxgvv/yyYbPZ6mxv7ty59nOSkpJcimHjxo0O13L+9DsrK8vh9c2bN7vUbps2beznvP/++zXWqdpuY55YfPvttw5tfPvttw1uoypvP7EICQkxzGazkZGRUe2T0EWLFjmsePXYY48ZkZGRRnR0tPHhhx9Wa3fu3LkOn+j++OOPNV6/6iemZrPZkGQkJCTU+LP6/PPPjejoaHv9iRMn1npf33//vf1T1+DgYOMf//iHUVRUVK3emjVrjF69etnbvOWWW2ps7+jRo0bbtm3t9fr27Wts2rTJoY7VajUef/xxw2QyGcHBwS59an/mmWfa68XExBhffPFFtTpfffWVERcX5/AzUh1PLJrLvbuqIU8siouLjXvuucdeNygoyFi1alWt9av+TQUFBRnS8ZXcCgoKHOqVl5c7/E288sorxrPPPmvs37+/1rZ/++03Y8iQIfb2//73v9dat+oTi8ongGlpaUZ2drZDvaKiIuOqq66y142MjDQKCwtrbLOhTyyCg4MNk8lkdO3a1Vi2bFm1uu+//75D/3v99ddrvR/AX5BYAH7E1cSi8usPf/hDrf+TMwzD+M9//mOv6+rQoOzsbIdrbNmyxeH1TZs2Obx+7Ngxl9qtOkzphRdeqLGOu4nF7t27Hdp44403GtxGVVXfBPXo0cNIT093+Wvbtm01tlk1sZBkvPzyy7Ve/5FHHnGoazKZjAULFtRa/8Ybb6z3TWvVNzaSjICAAOOXX36ptc358+c71K/p+lar1TjllFPsdT7++ONa2zMMw8jMzDSSkpLsb9z37dtXrc4DDzzgkBRnZWXV2p7zz6m2N9ffffedw8/yhx9+qLXNxYsXGyaTyaHdmhKL5nLvDeGcWNTUv2+88Ubj7LPPdkg8o6OjjW+++abOtqv+TUkybrzxRrfjrSo3N9c+HK1du3ZGRUVFjfWqJhaSjKuuuqrWNktKShyGa7333ns11mtoYlGZ1B84cKDWa1dN2s4+++zabxzwEyQWgB957bXXjI4dOxp333238dVXXxn79u0zSktLjaKiImPr1q3Gf/7zH6NHjx4O/2O64IILah33+8QTT9jrDR8+3KUYiouLHdpfuXKlw+vLly93eL2kpMSldocNG2Y/58knn6yxjruJRU5OjkMbTz/9dIPbqMr5TVBDvmqLv2pi0b9//zqvv2PHDoc2L7roojrrL1iwwF538ODBNdZxfmNzzTXX1PtzuOSSS+z1r7zyymqvf/rppy7HWGnWrFn2c5566imH12w2m8Nchfr2RXCeC1Hbm+srrrjCXufyyy+vN8bJkyfXm1g0l3tvCOfEwpWvq6++ut55DYbh+DcVGhrq8gcTDXHLLbfYr7Fu3boa61RNLIKDg43MzMw62/zLX/5irz916tQa6zQmsXD+/Tur+kFOQkJCnXUBf8CqUIAfueiii7Rr1y49+eSTOuecc9ShQweFhIQoPDxc3bp10y233KLffvtN119/vf2czz//XO+8806N7ZWWltq/d95jojbOY5KdVxmq2mZj223sykX1iYyMdDguKCjwynU85bLLLqvz9S5duigiIsLl+n369LF/v2vXLpdiuPbaa+utU3VVr5pWTPrqq6/s31dddrQuVVcf+/nnnx1e27x5sw4dOiRJCgoKqrdNs9ns0nWrxt7Q+65Nc7l3b3v77bc1cODABq36dtZZZykuLq7B1zp8+LA+//xzPf7445o2bZpuu+02TZkyxf61cuVKe921a9fW297pp5+u5OTkOutUnSe2e/fuBsdcm8svv7zO13v06KGwsDBJUnZ2tt//mwYwCwjwI65MNA4ODtbLL7+s33//XT/99JMk6fHHH9cf//jHanVDQ0Pt35eXl7sUg/PSopX/U6upzcp2ncvqa9e5TU9x/p9udHS0x9pOS0uzL4/pKVUTgdrExsbal5vs3bt3nXXj4+Pt37uyj4fJZNLw4cPrrTdixAj791lZWcrMzHRYXnTp0qX27z/66CP9+OOP9baZl5dn/37fvn0Or1XdP6VHjx4u/V1UjbEmBw4c0JEjR+zHp556ar1tnnrqqTKZTPYJ2TVpDvfurpru32q1Kjs7W6tWrdILL7ygzz//XPv27dMll1yiF198sc79cioNHjy4QXFs2rRJ9913n77++mv7RO76uLIhpysLSSQkJNi/99QeOTExMXUuhiAd/xuNi4uzfxiTn5+vqKgoj1wf8AYSC6AZCggI0PTp0zV+/HhJx1dA2r9/vzp06OBQr+on+K4+JXCu5/wUwPm4pKTEpcSiarvObXhK1TdskuMbbX8UExNTb52qq8DUV79q3crVhOpSuQdGfSpX36l8WnXkyBGHxOLgwYP276uuDuSqnJwch+OqCUDHjh1daqO+elXbDA8PV2JiYr1tRkdHKyYmptp+ClU1h3v3hsDAQLVt21bnnHOOzjnnHM2YMUMzZ86UJN1+++0aM2aMunXrVmcbbdq0cfl63377rS688MIG76niyif8rvwdms1m+/cWi6VBMbhzXW9dG/AWhkIBzdQZZ5zh8D+cmpaGrfopW1ZWlkvtVg7DqOT85rxqm41t11tv+Lds2eJwXN/wBl9zZclgd+rXpyHLqVYdkuX8Zs05oWso5ySosLDQ/r2rMVaNryaNadOVdpvDvTeFBx980J7glJWVubQErqtPLo8cOaJJkybZk4pOnTpp1qxZ+vnnn3Xw4EEVFxfLZrPJOD5vVNOnT7efW7ncdl08/XflKl9dF/AmEgugmTKbzQ6futb0yL979+727w8fPlxtfkRN9u7da/8+Pj6+2qeKbdu2dRiesWfPnnrbLC0tdfgktkePHvWe0xjLli2zfx8YGKihQ4d65TotRXFxsct1q+7+6/yUo+ob29WrV9vf4Ln65TxmveoTLVdjrG934sa06Uq7zeHem0JQUJDOPPNM+/GCBQs81vZLL71kT+D69++vdevWadq0aRo5cqTatWunsLAwhzfpzEMAfIfEAmjGqr6hqOlTy+7duysg4PifuWEYLk1krNyUSZJ69uxZY52q5VXHhLvSZmBgYL1DJBrrww8/tH/fv39/j86xaIlycnIcPiGvzdGjRx2SUudhRElJSfbvnZ94NUbVZLZqolsX57kKdbVZXFys7OzsetssKCio94lEc7j3plJ1eJwrHzi4qmqS8uCDD9b7d+3JawNoGBILoJnauXOnwyTClJSUanVCQ0MdJqkuWrSo3narTj6tunpNVWPHjm10m6eddppLu+E21HfffacNGzbYj6+88kqPX6OlMQzD4SlPbapOUE5KSqrW16pOAP/ll1/cjqvqCjxbtmxxabhR1Rhr0r59e4c37b/++mu9bf766691TtyWmse9N5WqT1gqP9DwhKrzWOqbaG21Wj3yewDQOCQWQDP16quv2r+PiYnRgAEDaqx30UUX2b+vb1Wjffv2OXw6WPXc2tr8/vvvtX///jrbrXrd2tp0R3Z2tm688Ub7cUJCgm655RaPX6clevPNN+ut88Ybb9i/r5pUVvrDH/5g//7VV191achdXXr06GGfH1NRUaF33323zvqu1JEcY2/ofdemudx7U6j6ZLJ9+/Yea7dqklLf8LBPP/3UI0+OADQOiQXgJ1wZklJpyZIleuqpp+zHV155pcOKQFWlpaXZh0lt3bpVL7/8cq3t3nffffZlHEeMGKFBgwbVWG/o0KH2+QtWq1XTpk2rtc3//ve/2rZtm6TjY/Nd2T+gIX7//XeNGzfOYTjIE0884bWVp1qat956q86nFgsXLtRHH31kP66awFW69NJLdfLJJ0uSMjMzdeutt9b7SX+lwsLCanMEAgICdMMNN9iPZ86c6TBHx9mTTz7p0r4dVWN///33tXjx4lrr/vLLL7XuD1NVc7l3b1u5cqV9+WtJDvMt3NWlSxf7959//nmt9Y4cOaK77rrLY9cF0HAkFoCf+PDDDzVs2DC98cYbtQ5/KC0t1TPPPKPx48fbPxmNjY11WAXFWdu2bTV16lT78e23367333/foY7FYtG0adMcPvmcNWtWnfFWff3tt9/WtGnTqi2F+P777+vOO++0H99zzz0uLfPpis2bN+uOO+7QgAEDtG7dOnv51KlTHd6YoXZms1lWq1V/+MMf9P3331d7/csvv9TFF19sf6M8YcKEGt8wBgYG6vnnn1dgYKAk6bXXXtN5551X40plldauXav77rtPqampNb4xvuuuu+x95dChQ5owYUK1Vb9sNpueeuop/fWvf3Vpo8YJEybYn1oYhqGLLrrIYYO7St99950uuOAC2Ww2h5XXatJc7t2bvv/+e1144YX2fhIUFKTbbrvNY+2ff/759u9nzZqlt956q1qd1atXa/To0dq3b59frJIFtFbsYwH4kRUrVigtLU1BQUHq0aOHevToobi4OFmtVh04cEBLly51mFcRFhamzz77zGHSZE0eeugh/fLLL/rhhx9UUlKiSZMm6ZFHHtGgQYNUWlqqxYsXKzMz015/5syZGj16dJ1tnnnmmXrwwQf1yCOPSDq+Sd+bb76pUaNGKTQ0VKtWrXKY8zBhwgQ98MADLv8snn76aYfJ2BUVFcrNzVV2drbWrFlTbfJtWFiYHn/8cU2ZMsXlazTEsmXLGtz2P//5T69tBugJKSkpuvjiizV79mxNmDBB/fv314ABA2QYhlatWqWNGzfa67Zr104vvfRSrW2NHz9ezz//vG655RZZrVZ9/fXX+uabb9SrVy/169dP0dHRKi4uVmZmpn777bc6P4WXjk8Qf+WVV3TJJZfIarXqt99+U+/evXX66aerW7duKiws1OLFi+3j7//5z3/q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0uuRWAB+LiQoUKnx4dqTXWwv20FiAdSoV0Iv9Uro5eswvMJiseirfV/p3L7nymw2+zoc+CH6CHyNdBZoBmragRsAAMCfkFgAzQArQwEAAH/ndmLRpUsXnXrqqS7XHzVqlLp27eruZb3i4osvVlxcnC677DJfhwI4qPbEgsQCAAD4GbcTi927d2vv3r0u19+/f792797t7mW94o477tAbb7zh6zCAapxXhtp9tFgVVpuPogEAAKiuyYdCVVRUKCDAP0dgjRkzRlFRUb4OA6jG+YlFudWm/TklPooGAACguiZ9h19SUqLDhw836s374sWLdf755yslJUUmk0mffvpptToZGRk66aSTFBoaquHDh2v58uUeiBrwvfiIYMWFO67wwXAoAADgTxq83OzevXurDWUqLy/XTz/9JMMwajzHMAzl5ubq7bfflsViUd++fRscaFFRkfr3768bbrhBl1xySbXX586dq6lTp+qFF17Q8OHDNXv2bE2cOFFbt25V27ZtJUkDBgxQRUX1jcW+++47paSkNCiesrIylZWV2Y/z8/MlHV/qzWKxNKgttA6V/aKx/aNLYoRW7c21H287lK8zTo73RGjwA+72D7R89BHUhz6C+ni7b5iM2rKBWsycOVN/+9vf7MeGYchkMrl0bmXdN998U5MnT25YpFWYTCZ98sknuuiii+xlw4cP19ChQ/Xcc89Jkmw2m1JTU3Xbbbdp2rRpLre9aNEiPffcc/rwww/rrDdjxgzNnDmzWvk777yj8PBwl68HuOrdHQH69fCJh4yntrXpqq7MswAAAK4pLi7W5MmTlZeXp+joaI+336gN8qrmIiaTqdYnFVXrREdHq0+fPvrzn//sVlJRk/Lycq1atUr333+/vSwgIEDjx4/X0qVLPXqtSvfff7+mTp1qP87Pz1dqaqrGjh2rhIQEr1wTzZvFYtH8+fM1YcKERm1cdPDn3fr1223244qweJ177jBPhggfcrd/oOWjj6A+9BHUJzs726vtNzixmD59uqZPn24/DggIUHJysg4ePOjRwBri6NGjslqtSkpKcihPSkrSli1bXG5n/Pjx+u2331RUVKQOHTrogw8+0IgRI2qsGxISopCQkGrlZrOZP2bUqbF95JQkx08Wdh4tpq+1QPwbgvrQR1Af+ghq4+1+0agnFlVde+21io2N9UAovvf99983+JyM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UlJkMpn06aefOrzuSn84duyYrr76akVHRys2NlZ/+tOfVFhY2OBYSCzcNHfuXE2dOlXTp0/X6tWr1b9/f02cOFGHDx/2dWjwgR9//FHp6en69ddfNX/+fFksFp111lkqKiqy17nrrrv0xRdf6IMPPtCPP/6ogwcP6pJLLvFh1PCFFStW6MUXX1S/fv0cyukfrVtOTo5Gjhwps9msr7/+Wps2bdJTTz2luLg4e50nnnhCzzzzjF544QUtW7ZMERERmjhxokpLS30YOZrK448/rueff17PPfecNm/erMcff1xPPPGEnn32WXsd+kjrUlRUpP79+ysjI6PG113pD1dffbU2btyo+fPna968eVq8eLFuvvnmhgdjwC3Dhg0z0tPT7cdWq9VISUkxZs2a5cOo4C8OHz5sSDJ+/PFHwzAMIzc31zCbzcYHH3xgr7N582ZDkrF06VJfhYkmVlBQYJxyyinG/PnzjdGjRxt33HGHYRj0DxjGfffdZ5x++um1vm6z2Yzk5GTjn//8p70sNzfXCAkJMd59992mCBE+dt555xk33HCDQ9kll1xiXH311YZh0EdaO0nGJ598Yj92pT9s2rTJkGSsWLHCXufrr782TCaTceDAgQZdnycWbigvL9eqVas0fvx4e1lAQIDGjx+vpUuX+jAy+Iu8vDxJUnx8vCRp1apVslgsDn2mR48e6tixI32mFUlPT9d5553n0A8k+gekzz//XEOGDNHll1+utm3bauDAgXrppZfsr+/atUuHDh1y6CMxMTEaPnw4faSVOO2007RgwQJt27ZNkvTbb7/p559/1jnnnCOJPgJHrvSHpUuXKjY2VkOGDLHXGT9+vAICArRs2bIGXS/IM2G3TkePHpXValVSUpJDeVJSkrZs2eKjqOAvbDab7rzzTo0cOVJ9+vSRJB06dEjBwcGKjY11qJuUlKRDhw75IEo0tffee0+rV6/WihUrqr1G/8DOnTv1/PPPa+rUqXrggQe0YsUK3X777QoODlZaWpq9H9T0/x36SOswbdo05efnq0ePHgoMDJTVatWjjz6qq6++WpLoI3DgSn84dOiQ2rZt6/B6UFCQ4uPjG9xnSCwAL0lPT9eGDRv0888/+zoU+Il9+/bpjjvu0Pz58xUaGurrcOCHbDabhgwZoscee0ySNHDgQG3YsEEvvPCC0tLSfBwd/MH777+vt99+W++884569+6ttWvX6s4771RKSgp9BD7HUCg3JCYmKjAwsNqKLVlZWUpOTvZRVPAHU6ZM0bx587Rw4UJ16NDBXp6cnKzy8nLl5uY61KfPtA6rVq3S4cOHNWjQIAUFBSkoKEg//vijnnnmGQUFBSkpKYn+0cq1a9dOvXr1cijr2bOn9u7dK0n2fsD/d1qve++9V9OmTdOVV16pvn376pprrtFdd92lWbNmSaKPwJEr/SE5ObnaokMVFRU6duxYg/sMiYUbgoODNXjwYC1YsMBeZrPZtGDBAo0YMcKHkcFXDMPQlClT9Mknn+iHH35Q586dHV4fPHiwzGazQ5/ZunWr9u7dS59pBc4880ytX79ea9eutX8NGTJEV199tf17+kfrNnLkyGpLVG/btk2dOnWSJHXu3FnJyckOfSQ/P1/Lli2jj7QSxcXFCghwfPsWGBgom80miT4CR670hxEjRig3N1erVq2y1/nhhx9ks9k0fPjwhl3QrannMN577z0jJCTEmDNnjrFp0ybj5ptvNmJjY41Dhw75OjT4wC233GLExMQYixYtMjIzM+1fxcXF9jp//vOfjY4dOxo//PCDsXLlSmPEiBHGiBEjfBg1fKnqqlCGQf9o7ZYvX24EBQUZjz76qLF9+3bj7bffNsLDw4233nrLXucf//iHERsba3z22WfGunXrjAsvvNDo3LmzUVJS4sPI0VTS0tKM9u3bG/PmzTN27dplfPzxx0ZiYqLxl7/8xV6HPtK6FBQUGGvWrDHWrFljSDL+9a9/GWvWrDH27NljGIZr/eHss882Bg4caCxbtsz4+eefjVNOOcW46qqrGhwLiYUHPPvss0bHjh2N4OBgY9iwYcavv/7q65DgI5Jq/HrttdfsdUpKSoxbb73ViIuLM8LDw42LL77YyMzM9F3Q8CnnxIL+gS+++MLo06ePERISYvTo0cP473//6/C6zWYzHnroISMpKckICQkxzjzzTGPr1q0+ihZNLT8/37jjjjuMjh07GqGhoUaXLl2Mv/71r0ZZWZm9Dn2kdVm4cGGN7z3S0tIMw3CtP2RnZxtXXXWVERkZaURHRxvXX3+9UVBQ0OBYTIZRZatGAAAAAGgE5lgAAAAAcBuJBQAAAAC3kVgAAAAAcBuJBQAAAAC3kVgAAAAAcBuJBQAAAAC3kVgAAAAAcBuJBQAAAAC3kVgAgBeMGTNGJpNJM2bM8HUoPlVcXKyHHnpIPXv2VFhYmEwmk0wmk9auXevr0LxmxowZMplMGjNmjK9DaZTrrrtOJpNJ1113na9DAdDMkFgAaDKVb7hMJpPCw8N18ODBWuvu3r3bXnfRokVNFyQ8atKkSXrkkUe0ZcsWmUwmJSUlKSkpSWaz2dehtTqLFi3SjBkzNGfOHF+HAqCFIrEA4BMlJSWaOXOmr8OAF23ZskXz5s2TJM2dO1fFxcU6dOiQDh06pN69e/s4utZn0aJFmjlzZr2JRbt27dS9e3e1a9euaQID0GKQWADwmVdffVXbtm3zdRjwkvXr10uSEhISdMUVV/g4Grhq1qxZ2rJli2bNmuXrUAA0MyQWAJpcamqq+vXrp4qKCj3wwAO+DgdeUlxcLEmKjIz0cSQAgKZAYgGgyQUEBNg/Df3oo4+0fPnyBp1fdf7F7t27a6130kknyWQyVRv64Xz+nj17dNNNN6ljx44KDQ1V165d9eCDD6qoqMh+zoYNG/THP/5RqampCg0N1SmnnKJHHnlEFoul3njLy8v1j3/8Q/369VNERITi4uI0YcIEff311/Weu2HDBt1888065ZRTFB4ersjISPXr109//etfdfTo0RrPcZ48/NFHH+mss85S27ZtFRAQ0OAJ5aWlpZo9e7ZOO+00xcXFKTQ0VJ06ddK1115b4yTsyutXTv7ds2eP/efd2EnBv/zyi/74xz+qU6dOCg0NVUxMjIYNG6bHH39chYWFDnUtFosSExNlMpn0zDPP1Nnuq6++KpPJpOjoaHsiJEmHDh3Ss88+qwsvvFA9e/ZUTEyMwsLCdPLJJ+vGG2/Uxo0bG3wPkmuT+uua/J2Tk6NXXnlFV1xxhfr27av4+Hj772Py5Mn69ddfq51T2d8rhx7++OOPDr8P578RVyZvL1q0SJdffrnat2+vkJAQJSYm6swzz9Rrr70mq9Xq0n0tWLBA5513ntq0aaPQ0FD17NlTM2fOVGlpaa3X/fbbb3XJJZeoQ4cOCg4OVnR0tLp06aKzzjpLTz75pI4dO1bruQCagAEATWT69OmGJKNTp06GYRjG6NGjDUnG2LFjq9XdtWuXIcmQZCxcuLDW13bt2lXr9Tp16mRIMl577bVaz//oo4+M2NhYQ5IRHR1tBAYG2l8bNWqUUV5ebsybN88IDw83JBkxMTGGyWSy15k0aVKN1668t/vvv98YNWqUIckICgqyX6vya/r06bXG//jjjxsBAQH2uuHh4UZwcLD9uF27dsbq1atr/TmPHj3amDp1qiHJMJlMRlxcnBEYGFjnNZ3t37/f6NOnj/2aZrPZiImJsR8HBAQYzzzzjMM5//znP42kpCQjOjraXicpKcn+dfvtt7t8favVatx+++0OP7PIyEiH31P37t2N3bt3O5yXnp5uSDKGDBlSZ/tjxowxJBnXXXedQ3laWpq9/aCgICM+Pt4ICgqyl4WEhBgffvhhjW1W/fk7q+wXdf0O6jq/8jVJRmBgoBEXF2eEhITYy0wmk/H00087nLN3714jKSnJiIiIsP8Oq/4+kpKSjPfee6/avaelpdUY31133eVwvdjYWIffx7hx44z8/Pw67+uJJ54wTCaT/fyqf1Njx441Kioqqp0/c+ZMh34QHh5uREZGOpQ5/1sBoGmRWABoMs6JxdKlS+1vCL7++muHuk2VWMTGxhpnnnmmsXHjRsMwDKO4uNh45pln7G+UHnzwQSMmJsaYNGmS/c1rQUGB8de//tXexvz586tdu/INZExMjBESEmK88MILRklJiWEYx9/oXXbZZfbzP/vss2rnv/zyy/Y30Y8++qiRmZlpGIZhVFRUGCtXrjTGjRtnSDI6dOhgFBQU1PhzrnzTdd999xmHDx82DMMwSktLq70Jr01FRYUxfPhw+3289dZbRllZmWEYhrFjxw7jD3/4g/3N5VdffVXt/Ndee83h990YDz74oCHJaNu2rZGRkWFkZ2cbhmEY5eXlxsKFC42BAwcakoxBgwYZVqvVft6yZcvsP9/NmzfX2PaePXvsb2h/+OEHh9f+/ve/G//85z+N9evXGxaLxTCM40nOhg0bjKuvvtqQZERERBgHDhyo1q43E4sXX3zRmD59urFy5Ur778Jmsxk7d+407rjjDsNkMhmBgYH1Jpx1qSuxePbZZ+0/15tvvtneLwsLC41///vf9uSrpoS78vqxsbFGQECAcf/99xtHjhwxDMMw8vLyjIcfftje9iuvvOJw7u7du+1J9tSpUx1+7rm5ucZPP/1k3HrrrcbKlSvrvDcA3kViAaDJOCcWhmEYF198sSHJGDBggGGz2ezlTZVY9O7d2ygtLa127jXXXGOvM2HCBIfYKlU+ifjTn/5U7bXKN5A1vUkyjONvUs844wx7DFXl5+fbn2x88803Nd6bxWIxBg8ebEgy/v3vfzu8VvVT7alTp9Z4vivee+89ezvffvttjTFUJh59+vSp9rq7icWuXbuMwMBAIywszFi7dm2NdfLz840OHToYkoxPPvnE4bXu3bvbnxrV5LHHHjMkGR07dqzx91uX8847z5Bk/P3vf6/2mjcTi/pUPqmpqU+6m1gUFxcb8fHxhiTjqquuqvHcZ555xt5nnN/kV+2Xtd3/JZdcYkgyxo8f71A+d+5cQ5LRrVu3OmMH4FvMsQDgU4899pgCAwO1du1avfvuu01+/bvuukshISHVyidOnGj/ftq0aTKZTLXWWbduXa3tp6am6vrrr69WHhAQoAcffFCStHHjRvsKStLxORG5ubkaOHCgQxxVBQUF6aqrrpJ0fNx5TQICAnTffffVGlt95s6dK0kaMWKEzjrrrBpjmD59uqTjc0Gq3oMnzJkzR1arVWeffbb69+9fY52oqChddNFFkqr/HK655hpJ0ttvvy3DMKqd++abb0qSrr766hp/v3U577zzJEk///xzg87zNm/GNX/+fPschtrmiNx66632ZWrfeeedGuuEhITonnvuqfG1Cy+8UFL1v6nY2FhJUkFBgcPcJwD+hcQCgE/16NHD/sb7oYcecmkytCcNGzasxvKkpCT790OHDq2zTk5OTq3tV07WrcmoUaMUFBQkSVq5cqW9/JdffpEkbd68WcnJybV+/e1vf5N0fHJ0TU4++WS1bdu21tjqUxnT+PHja60zduxYBQYGVrsHT6j8OXz33Xd1/hxee+01SdV/Dtdcc41MJpP27t2rH3/80eG1VatWafPmzZKka6+9tsbr//bbb7r11lvVr18/RUdHKyAgwD7Z+dZbb5Uk7d+/36P37IqdO3fqnnvu0eDBgxUbG6vAwEB7XOeee67X4qr8/aampqpbt2411gkMDNS4ceMc6jvr3bt3rSuFpaSkSFK1SdjDhg1TYmKiMjMzNXz4cD333HPasmVLjQkjAN8J8nUAADBjxgy9/fbb2rlzp1544QXddtttTXbtqKioGssr3/C7UqeuZKh9+/a1vhYaGqqEhARlZWXp8OHD9vLKHclLS0vrXCGnUtXVjKpyJ6mQZI+pvntITEysdg+eUPlzKCoqculTauefQ8eOHTV69GgtWrRIb775psMqS5VPK4YOHaoePXpU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0fz58z0eO5pWbX3a+WvOnDnVzqlahuZr+/btmjJlinr16qWIiAiFhoaqQ4cOGjp0qKZMmaKPPvrI7WtU/ltT379JnjJnzhyZTCZdd911TXI9oCkF+ToAAJ41cuRInXzyyZKOryBy8OBBLVmyRP/4xz/0xhtv6KefflKXLl1qPLesrExvv/22/fjVV1/V+eef3yRxN8Szzz6rO++8UzabTe3bt9fYsWMVFxenI0eOaPXq1VqyZIkWLVqkCRMm+DpUeEDVPl2Tul5D8/Xxxx9r8uTJKisrU0JCgkaOHKk2bdooJydHa9euVUZGht577z1deumlvg4VwP+QWAAtzI033ljtk7BDhw5p9OjR2rZtm/7yl7/oww8/rPHcTz75RMeOHVNKSooyMzM1b948ZWVl+dVur+vWrbMnFf/+97912223OSxFbLPZ9PPPP+vnn3/2YZTwpJr6NFq2rKwspaWlqaysTHfffbceeeQRhYaGOtRZtWpVrf+WAfANhkIBrUBycrLuvfdeSdKCBQtqrffKK69Iku644w6NHj1aFRUVeuONN5okRld98MEHstlsGjFihO68885q+5sEBATojDPO0AMPPOCjCAG4a968eSosLFRKSoqefPLJakmFJA0ePFizZs3yQXQAasMTCzRrNpuhnOJyX4fhtrjwYAUEeHed8uTkZElSRUVFja/v3r1bCxYsUFBQkK699lqlpKRo0aJFevXVV+1JSVU7d+7UoEGDlJ+fry+//FLnnHOOw+sHDx7UgAEDdOTIEb333nuaNGmSR+4jKytLktS2bVuPtOd3bDap5Jivo3BPWLwUwOdWDWUzbMoty/V1GG6JDYlVgMn9333l33mbNm0afO6xY8f05JNP6rPPPtOuXbsUGBiobt26adKkSbrtttsUFhZW67nFxcV65JFH9P7772v//v2Kj4/XOeeco7/97W9q3759jeds2bJFjz/+uH744QcdOnRIERERGjhwoP7v//5PV1xxRYPjB5ozEgs0aznF5Rr8yPe+DsNtqx4cr4TIEK9eY/ny5ZKk3r171/j6q6++KsMwdO655yo5OVmXXnqppkyZoi1btmjJkiU67bTTHOp36dJFr776qi699FJde+21WrNmjf6/vfuPibqO/wD+hDsQDsyTxY8SIuYcGC7HWaDpVPALY+HMWU4E5wXGzIwoyzF3VFuxfkDZ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+uqr4sqVK2PG2tTUJJRKpSlGuVwugoODRU5OjsX2hw8fFgCEUql84OPB8yBNFTZCsMQBTW6Dg4O4dOkSfHx84ODgMNHhEBERPVI8D9JUwTkWRERERERkNSYWRERERERkNSYWRERERERkNSYWRERERERkNSYWRERERERkNSYWRERERERkNSYWRERERERkNSYWNGXwkStERDQT8fxHUwUTC5r0pFIpAODWrVsTHAkREdGjZzz/Gc+HRJMVEwua9KRSKZycnNDV1QWDwTDR4RARET0yBoMBXV1dcHJyYmJBk56N4PgaTQE6nQ5tbW2QSCSYM2cOHB0dIZFIYGNjM9GhERERjSshBAwGAwYGBtDT04ORkRF4eXnB0dFxokMjuicmFjRlDA0N4caNG9DpdBy5ICKiaU8ikUAmk8HNzQ329vYTHQ7RfTGxoClHCIHh4WGMjIxMdChEREQPha2tLezs7DgyT1MKEwsiIiIiIrIaJ28TEREREZHVmFgQEREREZHVmFgQEREREZHVmFgQEREREZHVmFgQEREREZHVmFgQEREREZHVmFgQEREREZHV/g9ba/daksjFTQAAAABJRU5ErkJggg==\n", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "%matplotlib inline\n", + "\n", + "names = [\"BAxUS\", \"EI\", \"Sobol\"]\n", + "runs = [Y_baxus, Y_ei, Y_Sobol]\n", + "fig, ax = plt.subplots(figsize=(8, 6))\n", + "\n", + "for name, run in zip(names, runs):\n", + " fx = np.maximum.accumulate(run.cpu())\n", + " plt.plot(-fx + branin.optimal_value, marker=\"\", lw=3)\n", + "\n", + "plt.ylabel(\"Regret\", fontsize=18)\n", + "plt.xlabel(\"Number of evaluations\", fontsize=18)\n", + "plt.title(f\"{dim}D Embedded Branin\", fontsize=24)\n", + "plt.xlim([0, len(Y_baxus)])\n", + "plt.yscale(\"log\")\n", + "\n", + "plt.grid(True)\n", + "plt.tight_layout()\n", + "plt.legend(\n", + " names + [\"Global optimal value\"],\n", + " loc=\"lower center\",\n", + " bbox_to_anchor=(0, -0.08, 1, 1),\n", + " bbox_transform=plt.gcf().transFigure,\n", + " ncol=4,\n", + " fontsize=16,\n", + ")\n", + "plt.show()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "bento_stylesheets": { + "bento/extensions/flow/main.css": true, + "bento/extensions/kernel_selector/main.css": true, + "bento/extensions/kernel_ui/main.css": true, + "bento/extensions/new_kernel/main.css": true, + "bento/extensions/system_usage/main.css": true, + "bento/extensions/theme/main.css": true + }, + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "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.10.6" + } + }, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/website/tutorials.json b/website/tutorials.json index 1534de39c1..7f61e0b3a1 100644 --- a/website/tutorials.json +++ b/website/tutorials.json @@ -26,6 +26,10 @@ "id": "turbo_1", "title": "Trust Region Bayesian Optimization (TuRBO)" }, + { + "id": "baxus", + "title": "Bayesian optimization with adaptively expanding subspaces (BAxUS)" + }, { "id": "scalable_constrained_bo", "title": "Scalable Constrained Bayesian Optimization (SCBO)"