Source code for botorch.optim.homotopy

# Copyright (c) Meta Platforms, Inc. and affiliates.
#
# This source code is licensed under the MIT license found in the
# LICENSE file in the root directory of this source tree.

from __future__ import annotations

import math
from collections.abc import Callable
from dataclasses import dataclass

import torch
from torch import Tensor
from torch.nn import Parameter


[docs] class FixedHomotopySchedule: """Homotopy schedule with a fixed list of values.""" def __init__(self, values: list[float]) -> None: r"""Initialize FixedHomotopySchedule. Args: values: A list of values used in homotopy """ self._values = values self.idx = 0 @property def num_steps(self) -> int: return len(self._values) @property def value(self) -> float: return self._values[self.idx] @property def should_stop(self) -> bool: return self.idx == len(self._values)
[docs] def restart(self) -> None: self.idx = 0
[docs] def step(self) -> None: self.idx += 1
[docs] class LinearHomotopySchedule(FixedHomotopySchedule): """Linear homotopy schedule.""" def __init__(self, start: float, end: float, num_steps: int) -> None: r"""Initialize LinearHomotopySchedule. Args: start: start value of homotopy end: end value of homotopy num_steps: number of steps in the homotopy schedule. """ super().__init__( values=torch.linspace(start, end, num_steps, dtype=torch.double).tolist() )
[docs] class LogLinearHomotopySchedule(FixedHomotopySchedule): """Log-linear homotopy schedule.""" def __init__(self, start: float, end: float, num_steps: int): r"""Initialize LogLinearHomotopySchedule. Args: start: start value of homotopy end: end value of homotopy num_steps: number of steps in the homotopy schedule. """ super().__init__( values=torch.logspace( math.log10(start), math.log10(end), num_steps, dtype=torch.double ).tolist() )
[docs] @dataclass class HomotopyParameter: r"""Homotopy parameter. The parameter is expected to either be a torch parameter or a torch tensor which may correspond to a buffer of a module. The parameter has a corresponding schedule. """ parameter: Parameter | Tensor schedule: FixedHomotopySchedule
[docs] class Homotopy: """Generic homotopy class. This class is designed to be used in `optimize_acqf_homotopy`. Given a set of homotopy parameters and corresponding schedules we step through the homotopies until we have solved the final problem. We additionally support passing in a list of callbacks that will be executed each time `step`, `reset`, and `restart` are called. """ def __init__( self, homotopy_parameters: list[HomotopyParameter], callbacks: list[Callable] | None = None, ) -> None: r"""Initialize the homotopy. Args: homotopy_parameters: List of homotopy parameters callbacks: Optional list of callbacks that are executed each time `restart`, `reset`, or `step` are called. These may be used to, e.g., reinitialize the acquisition function which is needed when using qNEHVI. """ self._homotopy_parameters = homotopy_parameters self._callbacks = callbacks or [] self._original_values = [ hp.parameter.item() for hp in self._homotopy_parameters ] assert all( isinstance(hp.parameter, Parameter) or isinstance(hp.parameter, Tensor) for hp in self._homotopy_parameters ) # Assume the same number of steps for now assert len({h.schedule.num_steps for h in self._homotopy_parameters}) == 1 # Initialize the homotopy parameters self.restart() def _execute_callbacks(self) -> None: """Execute the callbacks.""" for callback in self._callbacks: callback() @property def should_stop(self) -> bool: """Returns true if all schedules have reached the end.""" return all(h.schedule.should_stop for h in self._homotopy_parameters)
[docs] def restart(self) -> None: """Restart the homotopy to use the initial value in the schedule.""" for hp in self._homotopy_parameters: hp.schedule.restart() hp.parameter.data.fill_(hp.schedule.value) self._execute_callbacks()
[docs] def reset(self) -> None: """Reset the homotopy parameter to their original values.""" for hp, val in zip(self._homotopy_parameters, self._original_values): hp.parameter.data.fill_(val) self._execute_callbacks()
[docs] def step(self) -> None: """Take a step according to the schedules.""" for hp in self._homotopy_parameters: hp.schedule.step() if not hp.schedule.should_stop: hp.parameter.data.fill_(hp.schedule.value) self._execute_callbacks()