Example #1
0
    def _update_pareto_Y(self) -> bool:
        r"""Update the non-dominated front.

        Returns:
            A boolean indicating whether the Pareto frontier has changed.
        """
        # is_non_dominated assumes maximization
        if self._neg_Y.shape[-2] == 0:
            pareto_Y = self._neg_Y
        else:
            # assumes maximization
            pareto_Y = -_pad_batch_pareto_frontier(
                Y=self.Y,
                ref_point=_expand_ref_point(
                    ref_point=self.ref_point, batch_shape=self.batch_shape
                ),
            )
            if self.sort:
                # sort by first objective
                if len(self.batch_shape) > 0:
                    pareto_Y = pareto_Y.gather(
                        index=torch.argsort(pareto_Y[..., :1], dim=-2).expand(
                            pareto_Y.shape
                        ),
                        dim=-2,
                    )
                else:
                    pareto_Y = pareto_Y[torch.argsort(pareto_Y[:, 0])]

        if not hasattr(self, "_neg_pareto_Y") or not torch.equal(
            pareto_Y, self._neg_pareto_Y
        ):
            self.register_buffer("_neg_pareto_Y", pareto_Y)
            return True
        return False
Example #2
0
    def get_hypercell_bounds(self) -> Tensor:
        r"""Get the bounds of each hypercell in the decomposition.

        Args:
            ref_point: A `(batch_shape) x m`-dim tensor containing the reference point.

        Returns:
            A `2 x num_cells x num_outcomes`-dim tensor containing the
                lower and upper vertices bounding each hypercell.
        """
        ref_point = _expand_ref_point(ref_point=self.ref_point,
                                      batch_shape=self.batch_shape)
        aug_pareto_Y = torch.cat(
            [
                # -inf is the lower bound of the non-dominated space
                torch.full(
                    torch.Size([
                        *self.batch_shape,
                        1,
                        self.num_outcomes,
                    ]),
                    float("-inf"),
                    dtype=self._neg_pareto_Y.dtype,
                    device=self._neg_pareto_Y.device,
                ),
                self._neg_pareto_Y,
                # note: internally, this class minimizes, so use negative here
                -(ref_point.unsqueeze(-2)),
            ],
            dim=-2,
        )
        minimization_cell_bounds = self._get_hypercell_bounds(
            aug_pareto_Y=aug_pareto_Y)
        # swap upper and lower bounds and multiply by -1
        return -minimization_cell_bounds.flip(0)
Example #3
0
    def compute_hypervolume(self) -> Tensor:
        r"""Compute the hypervolume for the given reference point.

        This method computes the hypervolume of the non-dominated space
        and computes the difference between the hypervolume between the
        ideal point and hypervolume of the non-dominated space.

        Returns:
            `(batch_shape)`-dim tensor containing the dominated hypervolume.
        """
        if self._neg_pareto_Y.shape[-2] == 0:
            return torch.zeros(
                self._neg_pareto_Y.shape[:-2],
                dtype=self._neg_pareto_Y.dtype,
                device=self._neg_pareto_Y.device,
            )
        ref_point = _expand_ref_point(ref_point=self.ref_point,
                                      batch_shape=self.batch_shape)
        # internally we minimize
        ref_point = -ref_point.unsqueeze(-2)
        ideal_point = self._neg_pareto_Y.min(dim=-2, keepdim=True).values
        aug_pareto_Y = torch.cat([ideal_point, self._neg_pareto_Y, ref_point],
                                 dim=-2)
        cell_bounds_values = self._get_hypercell_bounds(
            aug_pareto_Y=aug_pareto_Y)
        total_volume = (ref_point - ideal_point).squeeze(-2).prod(dim=-1)
        non_dom_volume = ((cell_bounds_values[1] -
                           cell_bounds_values[0]).prod(dim=-1).sum(dim=-1))
        return total_volume - non_dom_volume
Example #4
0
 def test_expand_ref_point(self):
     ref_point = torch.tensor([1.0, 2.0], device=self.device)
     for dtype in (torch.float, torch.double):
         ref_point = ref_point.to(dtype=dtype)
         # test non-batch
         self.assertTrue(
             torch.equal(
                 _expand_ref_point(ref_point, batch_shape=torch.Size([])),
                 ref_point,
             ))
         self.assertTrue(
             torch.equal(
                 _expand_ref_point(ref_point, batch_shape=torch.Size([3])),
                 ref_point.unsqueeze(0).expand(3, -1),
             ))
         # test ref point with wrong shape batch_shape
         with self.assertRaises(BotorchTensorDimensionError):
             _expand_ref_point(ref_point.unsqueeze(0),
                               batch_shape=torch.Size([]))
         with self.assertRaises(BotorchTensorDimensionError):
             _expand_ref_point(
                 ref_point.unsqueeze(0).expand(3, -1), torch.Size([2]))