code: def test_optimize_discrete(self) -> None:
      ssd1 = SearchSpaceDigest(
          feature_names=["a", "b", "c"],
          # pyre-fixme[6]: For 2nd param expected `List[Tuple[Union[float, int],
          #  Union[float, int]]]` but got `List[Tuple[int, int, int]]`.
          bounds=[(1, 2, 3), (2, 3, 4)],
          categorical_features=[0, 1, 2],
          discrete_choices={0: [1, 2], 1: [2, 3], 2: [3, 4]},
      )
      # check fixed_feature index validation
      with self.assertRaisesRegex(
          ValueError, "Invalid fixed_feature index"
      ):
          acquisition = self.get_acquisition_function()
          acquisition.optimize(
              n=3,
              search_space_digest=ssd1,
              fixed_features={3: 2.0},
              rounding_func=self.rounding_func,
          )
      # check that SearchSpaceExhausted is raised correctly
      acquisition = self.get_acquisition_function()
      all_possible_choices = list(
          itertools.product(*ssd1.discrete_choices.values())
      )
      acquisition.X_observed = torch.tensor(
          all_possible_choices, **self.tkwargs
      )
      with self.assertRaisesRegex(
          SearchSpaceExhausted,
          "No more feasible choices in a fully discrete search space.",
      ):
          acquisition.optimize(
              n=1,
              search_space_digest=ssd1,
              rounding_func=self.rounding_func,
          )
      acquisition = self.get_acquisition_function()
      with self.assertWarnsRegex(
          AxWarning,
          "only.*possible choices remain.",
      ):
          acquisition.optimize(
              n=8,
              search_space_digest=ssd1,
              rounding_func=self.rounding_func,
          )
  
      # check this works without any fixed_feature specified
      # 2 candidates have acqf value 8, but [1, 3, 4] is pending and thus should
      # not be selected. [2, 3, 4] is the best point, but has already been picked
      acquisition = self.get_acquisition_function()
      X_selected, _ = acquisition.optimize(
          n=2,
          search_space_digest=ssd1,
          rounding_func=self.rounding_func,
      )
      expected = torch.tensor([[2, 2, 4], [2, 3, 3]]).to(self.X)
      self.assertTrue(X_selected.shape == (2, 3))
      self.assertTrue(
          all(
              (x.unsqueeze(0) == expected).all(dim=-1).any()
              for x in X_selected
          )
      )
      # check with fixed feature
      # Since parameter 1 is fixed to 2, the best 3 candidates are
      # [4, 2, 4], [3, 2, 4], [4, 2, 3]
      ssd2 = SearchSpaceDigest(
          feature_names=["a", "b", "c"],
          # pyre-fixme[6]: For 2nd param expected `List[Tuple[Union[float, int],
          #  Union[float, int]]]` but got `List[Tuple[int, int, int]]`.
          bounds=[(0, 0, 0), (4, 4, 4)],
          categorical_features=[0, 1, 2],
          # pyre-fixme[6]: For 4th param expected `Dict[int, List[Union[float,
          #  int]]]` but got `Dict[int, List[int]]`.
          discrete_choices={k: [0, 1, 2, 3, 4] for k in range(3)},
      )
      X_selected, _ = acquisition.optimize(
          n=3,
          search_space_digest=ssd2,
          fixed_features=self.fixed_features,
          rounding_func=self.rounding_func,
      )
      expected = torch.tensor([[4, 2, 4], [3, 2, 4], [4, 2, 3]]).to(
          self.X
      )
      self.assertTrue(X_selected.shape == (3, 3))
      self.assertTrue(
          all(
              (x.unsqueeze(0) == expected).all(dim=-1).any()
              for x in X_selected
          )
      )
      # check with a constraint that -1 * x[0]  -1 * x[1] >= 0 which should make
      # [0, 0, 4] the best candidate.
      X_selected, _ = acquisition.optimize(
          n=1,
          search_space_digest=ssd2,
          rounding_func=self.rounding_func,
          inequality_constraints=[
              (
                  torch.tensor([0, 1], dtype=torch.int64),
                  -torch.ones(2),
                  0,
              )
          ],
      )
      expected = torch.tensor([[0, 0, 4]]).to(self.X)
      self.assertTrue(torch.equal(expected, X_selected))
      # Same thing but use two constraints instead
      X_selected, _ = acquisition.optimize(
          n=1,
          search_space_digest=ssd2,
          rounding_func=self.rounding_func,
          inequality_constraints=[
              (torch.tensor([0], dtype=torch.int64), -torch.ones(1), 0),
              (torch.tensor([1], dtype=torch.int64), -torch.ones(1), 0),
          ],
      )
      expected = torch.tensor([[0, 0, 4]]).to(self.X)
      self.assertTrue(torch.equal(expected, X_selected))
  
imports: from __future__ import annotations
  import dataclasses
  import itertools
  from contextlib import ExitStack
  from itertools import chain
  from typing import Any, Dict, Optional
  from unittest import mock
  from unittest.mock import Mock
  import numpy
  import torch
  from ax.core.search_space import SearchSpaceDigest
  from ax.exceptions.core import AxWarning, SearchSpaceExhausted
  from ax.models.torch.botorch_modular.acquisition import Acquisition
  from ax.models.torch.botorch_modular.surrogate import Surrogate
  from ax.models.torch.utils import _get_X_pending_and_observed, subset_model, SubsetModelData
  from ax.models.torch_base import TorchOptConfig
  from ax.utils.common.constants import Keys
  from ax.utils.common.testutils import TestCase
  from ax.utils.testing.mock import fast_botorch_optimize
  from ax.utils.testing.utils import generic_equals
  from botorch.acquisition.acquisition import AcquisitionFunction
  from botorch.acquisition.input_constructors import _register_acqf_input_constructor, ACQF_INPUT_CONSTRUCTOR_REGISTRY, get_acqf_input_constructor
  from botorch.acquisition.knowledge_gradient import qKnowledgeGradient
  from botorch.acquisition.monte_carlo import qNoisyExpectedImprovement
  from botorch.acquisition.multi_objective.monte_carlo import qNoisyExpectedHypervolumeImprovement
  from botorch.acquisition.objective import LinearMCObjective
  from botorch.models.gp_regression import SingleTaskGP
  from botorch.utils.constraints import get_outcome_constraint_transforms
  from botorch.utils.datasets import SupervisedDataset
  from botorch.utils.testing import MockPosterior
  from torch import Tensor
label_desc: Use image node_id 8 for calling the AcquisitionTest obj's underlying member method code with example usage: obj.test_optimize_discrete() without return types