diff --git a/bloptools/bayesian/agent.py b/bloptools/bayesian/agent.py index 8c9c08c..5faffa8 100644 --- a/bloptools/bayesian/agent.py +++ b/bloptools/bayesian/agent.py @@ -82,9 +82,9 @@ def __init__( # # below are the behaviors of DOFs of each kind and mode: # - # "read": the agent will read the input on every acquisition (all dofs are always read) - # "move": the agent will try to set and optimize over these (there must be at least one of these) - # "input" means that the agent will use the value to make its posterior + # 'read': the agent will read the input on every acquisition (all dofs are always read) + # 'move': the agent will try to set and optimize over these (there must be at least one of these) + # 'input' means that the agent will use the value to make its posterior # # # not read-only read-only @@ -170,7 +170,7 @@ def tell( elif int(self.n_last_trained / self.train_every) != int(len(self.table) / self.train_every): if train_models: - self._train_models(a_priori_hypers=hypers) + self._construct_models(train=train_models, a_priori_hypers=hypers) def _construct_models(self, train=True, skew_dims=None, a_priori_hypers=None): skew_dims = skew_dims if skew_dims is not None else self.latent_dim_tuples @@ -235,11 +235,11 @@ def _construct_models(self, train=True, skew_dims=None, a_priori_hypers=None): # if self.initialized: # self._set_hypers(cached_hypers) # else: - # raise RuntimeError("Could not fit model on initialization!") + # raise RuntimeError('Could not fit model on initialization!') self.constraint = GenericDeterministicModel(f=lambda x: self.classifier.probabilities(x)[..., -1]) - def ask(self, acq_func_identifier="qei", n=1, route=True, sequential=True, upsample=1): + def ask(self, acq_func_identifier="qei", n=1, route=True, sequential=True, upsample=1, **acq_func_kwargs): """Ask the agent for the best point to sample, given an acquisition function. Parameters @@ -261,19 +261,21 @@ def ask(self, acq_func_identifier="qei", n=1, route=True, sequential=True, upsam start_time = ttime.monotonic() if self.verbose: - print(f'finding points with acquisition function "{acq_func_name}" ...') + print(f"finding points with acquisition function '{acq_func_name}' ...") if acq_func_type in ["analytic", "monte_carlo"]: if not all(hasattr(obj, "model") for obj in self.objectives): raise RuntimeError( - f'Can\'t construct non-trivial acquisition function "{acq_func_identifier}"' + f"Can't construct non-trivial acquisition function '{acq_func_identifier}'" f" (the agent is not initialized!)" ) if acq_func_type == "analytic" and n > 1: raise ValueError("Can't generate multiple design points for analytic acquisition functions.") - acq_func, acq_func_meta = self.get_acquisition_function(identifier=acq_func_identifier, return_metadata=True) + acq_func, acq_func_meta = self.get_acquisition_function( + identifier=acq_func_identifier, return_metadata=True, **acq_func_kwargs + ) NUM_RESTARTS = 8 RAW_SAMPLES = 1024 @@ -332,7 +334,17 @@ def ask(self, acq_func_identifier="qei", n=1, route=True, sequential=True, upsam upsampled_idx = np.linspace(0, len(idx) - 1, upsample * len(idx) - 1) acq_points = sp.interpolate.interp1d(idx, acq_points, axis=0)(upsampled_idx) - return acq_points, acq_func_meta + res = { + "points": acq_points, + "acq_func": acq_func_meta["name"], + "acq_func_kwargs": acq_func_kwargs, + "duration": acq_func_meta["duration"], + "sequential": sequential, + "upsample": upsample, + "read_only_values": acq_func_meta.get("read_only_values"), + } + + return res def acquire(self, acquisition_inputs): """Acquire and digest according to the self's acquisition and digestion plans. @@ -366,7 +378,7 @@ def acquire(self, acquisition_inputs): # compute the fitness for each objective # for index, entry in products.iterrows(): # for obj in self.objectives: - # products.loc[index, objective["key"]] = getattr(entry, objective["key"]) + # products.loc[index, objective['key']] = getattr(entry, objective['key']) except KeyboardInterrupt as interrupt: raise interrupt @@ -405,8 +417,8 @@ def learn( For example: - RE(agent.learn("qr", n=16)) - RE(agent.learn("qei", n=4, iterations=4)) + RE(agent.learn('qr', n=16)) + RE(agent.learn('qei', n=4, iterations=4)) Parameters ---------- @@ -435,9 +447,9 @@ def learn( for i in range(iterations): print(f"running iteration {i + 1} / {iterations}") for single_acq_func in np.atleast_1d(acq_func): - acq_points, acq_func_meta = self.ask(n=n, acq_func_identifier=single_acq_func, upsample=upsample) - new_table = yield from self.acquire(acq_points) - new_table.loc[:, "acq_func"] = acq_func_meta["name"] + res = self.ask(n=n, acq_func_identifier=single_acq_func, upsample=upsample) + new_table = yield from self.acquire(res["points"]) + new_table.loc[:, "acq_func"] = res["acq_func"] x = {key: new_table.pop(key).tolist() for key in self.dofs.names} y = {key: new_table.pop(key).tolist() for key in self.objectives.names} @@ -725,8 +737,8 @@ def all_acq_funcs(self): entries = [] for k, d in acquisition.config.items(): ret = "" - ret += f'{d["pretty_name"].upper()} (identifiers: {d["identifiers"]})\n' - ret += f'-> {d["description"]}' + ret += f"{d['pretty_name'].upper()} (identifiers: {d['identifiers']})\n" + ret += f"-> {d['description']}" entries.append(ret) print("\n\n".join(entries)) diff --git a/bloptools/bayesian/kernels.py b/bloptools/bayesian/kernels.py index dc46438..dc9f642 100644 --- a/bloptools/bayesian/kernels.py +++ b/bloptools/bayesian/kernels.py @@ -12,7 +12,7 @@ def __init__( self, num_inputs=1, skew_dims=True, - diag_prior=True, + priors=True, scale_output=True, **kwargs, ): @@ -63,21 +63,23 @@ def __init__( self.n_skew_entries = len(self.skew_matrix_indices[0]) - diag_entries_constraint = gpytorch.constraints.Positive() - raw_diag_entries_initial = ( - diag_entries_constraint.inverse_transform(torch.tensor(1e-1)) + lengthscale_constraint = gpytorch.constraints.Positive() + raw_lengthscale_entries_initial = ( + lengthscale_constraint.inverse_transform(torch.tensor(1e-1)) * torch.ones(self.num_outputs, self.num_inputs).double() ) - self.register_parameter(name="raw_diag_entries", parameter=torch.nn.Parameter(raw_diag_entries_initial)) - self.register_constraint(param_name="raw_diag_entries", constraint=diag_entries_constraint) + self.register_parameter( + name="raw_lengthscale_entries", parameter=torch.nn.Parameter(raw_lengthscale_entries_initial) + ) + self.register_constraint(param_name="raw_lengthscale_entries", constraint=lengthscale_constraint) - if diag_prior: + if priors: self.register_prior( - name="diag_entries_prior", + name="lengthscale_entries_prior", prior=gpytorch.priors.GammaPrior(concentration=2, rate=1), - param_or_closure=lambda m: m.diag_entries, - setting_closure=lambda m, v: m._set_diag_entries(v), + param_or_closure=lambda m: m.lengthscale_entries, + setting_closure=lambda m, v: m._set_lengthscale_entries(v), ) if self.n_skew_entries > 0: @@ -105,8 +107,8 @@ def __init__( ) @property - def diag_entries(self): - return self.raw_diag_entries_constraint.transform(self.raw_diag_entries) + def lengthscale_entries(self): + return self.raw_lengthscale_entries_constraint.transform(self.raw_lengthscale_entries) @property def skew_entries(self): @@ -116,9 +118,9 @@ def skew_entries(self): def outputscale(self): return self.raw_outputscale_constraint.transform(self.raw_outputscale) - @diag_entries.setter - def diag_entries(self, value): - self._set_diag_entries(value) + @lengthscale_entries.setter + def lengthscale_entries(self, value): + self._set_lengthscale_entries(value) @skew_entries.setter def skew_entries(self, value): @@ -128,10 +130,10 @@ def skew_entries(self, value): def outputscale(self, value): self._set_outputscale(value) - def _set_diag_entries(self, value): + def _set_lengthscale_entries(self, value): if not torch.is_tensor(value): - value = torch.as_tensor(value).to(self.raw_diag_entries) - self.initialize(raw_diag_entries=self.raw_diag_entries_constraint.inverse_transform(value)) + value = torch.as_tensor(value).to(self.raw_lengthscale_entries) + self.initialize(raw_lengthscale_entries=self.raw_lengthscale_entries_constraint.inverse_transform(value)) def _set_skew_entries(self, value): if not torch.is_tensor(value): @@ -155,7 +157,7 @@ def skew_matrix(self): @property def diag_matrix(self): D = torch.zeros((self.num_outputs, self.num_inputs, self.num_inputs), dtype=torch.float64) - D[self.diag_matrix_indices] = self.diag_entries.ravel() + D[self.diag_matrix_indices] = self.lengthscale_entries.ravel() ** -1 return D @property diff --git a/bloptools/bayesian/models.py b/bloptools/bayesian/models.py index fab5f38..a50e84e 100644 --- a/bloptools/bayesian/models.py +++ b/bloptools/bayesian/models.py @@ -15,7 +15,7 @@ def __init__(self, train_inputs, train_targets, skew_dims=True, *args, **kwargs) num_inputs=train_inputs.shape[-1], num_outputs=train_targets.shape[-1], skew_dims=skew_dims, - diag_prior=True, + priors=True, scale=True, **kwargs ) @@ -31,7 +31,7 @@ def __init__(self, train_inputs, train_targets, skew_dims=True, *args, **kwargs) num_inputs=train_inputs.shape[-1], num_outputs=train_targets.shape[-1], skew_dims=skew_dims, - diag_prior=True, + priors=True, scale=True, **kwargs ) diff --git a/docs/source/tutorials/himmelblau.ipynb b/docs/source/tutorials/himmelblau.ipynb index 802befa..6fb98ac 100644 --- a/docs/source/tutorials/himmelblau.ipynb +++ b/docs/source/tutorials/himmelblau.ipynb @@ -43,7 +43,7 @@ "from matplotlib import pyplot as plt\n", "from bloptools.utils import functions\n", "\n", - "x1 = x2 = np.linspace(-6, 6, 1024)\n", + "x1 = x2 = np.linspace(-6, 6, 256)\n", "X1, X2 = np.meshgrid(x1, x2)\n", "\n", "F = functions.himmelblau(X1, X2)\n", @@ -82,7 +82,7 @@ "id": "54b6f23e", "metadata": {}, "source": [ - "We also need to give the agent something to do. We want our agent to look in the feedback for a variable called \"himmelblau\", and try to minimize it." + "We also need to give the agent something to do. We want our agent to look in the feedback for a variable called 'himmelblau', and try to minimize it." ] }, { @@ -94,7 +94,7 @@ "source": [ "from bloptools.bayesian import Objective\n", "\n", - "objectives = [Objective(name=\"himmelblau\", target=\"min\")]" + "objectives = [Objective(name=\"himmelblau\", description=\"Himmeblau's function\", target=\"min\")]" ] }, { @@ -170,24 +170,6 @@ "agent.plot_objectives()" ] }, - { - "cell_type": "code", - "execution_count": null, - "id": "75a6a54f-0140-4bd0-8367-0dd14cdf8116", - "metadata": {}, - "outputs": [], - "source": [ - "{x % 2 for x in np.arange(10)}" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "ad31b676-8236-4cf8-b9be-c1214d2e8458", - "metadata": {}, - "outputs": [], - "source": [] - }, { "cell_type": "markdown", "id": "dc264346-10fb-4c88-9925-4bfcf0dd3b07", @@ -264,11 +246,11 @@ }, "outputs": [], "source": [ - "X, _ = agent.ask(\"qei\", n=8, route=True)\n", + "res = agent.ask(\"qei\", n=8, route=True)\n", "agent.plot_acquisition(acq_func=\"qei\")\n", - "plt.scatter(*X.T, marker=\"d\", facecolor=\"w\", edgecolor=\"k\")\n", + "plt.scatter(*res[\"points\"].T, marker=\"d\", facecolor=\"w\", edgecolor=\"k\")\n", "plt.plot(\n", - " *X.T,\n", + " *res[\"points\"].T,\n", " color=\"r\",\n", ")" ]