code: def optimize_acqf_cyclic(
      acq_function: AcquisitionFunction,
      bounds: Tensor,
      q: int,
      num_restarts: int,
      raw_samples: Optional[int] = None,
      options: Optional[Dict[str, Union[bool, float, int, str]]] = None,
      inequality_constraints: Optional[
          List[Tuple[Tensor, Tensor, float]]
      ] = None,
      equality_constraints: Optional[
          List[Tuple[Tensor, Tensor, float]]
      ] = None,
      fixed_features: Optional[Dict[int, float]] = None,
      post_processing_func: Optional[Callable[[Tensor], Tensor]] = None,
      batch_initial_conditions: Optional[Tensor] = None,
      cyclic_options: Optional[
          Dict[str, Union[bool, float, int, str]]
      ] = None,
      *,
      ic_generator: Optional[TGenInitialConditions] = None,
      timeout_sec: Optional[float] = None,
      return_full_tree: bool = False,
      retry_on_optimization_warning: bool = True,
      **ic_gen_kwargs: Any,
  ) -> Tuple[Tensor, Tensor]:
      r"""Generate a set of `q` candidates via cyclic optimization.
  
      Args:
          acq_function: An AcquisitionFunction
          bounds: A `2 x d` tensor of lower and upper bounds for each column of `X`
              (if inequality_constraints is provided, these bounds can be -inf and
              +inf, respectively).
          q: The number of candidates.
          num_restarts:  Number of starting points for multistart acquisition
              function optimization.
          raw_samples: Number of samples for initialization. This is required
              if `batch_initial_conditions` is not specified.
          options: Options for candidate generation.
          inequality constraints: A list of tuples (indices, coefficients, rhs),
              with each tuple encoding an inequality constraint of the form
              `\sum_i (X[indices[i]] * coefficients[i]) >= rhs`
          equality constraints: A list of tuples (indices, coefficients, rhs),
              with each tuple encoding an inequality constraint of the form
              `\sum_i (X[indices[i]] * coefficients[i]) = rhs`
          fixed_features: A map `{feature_index: value}` for features that
              should be fixed to a particular value during generation.
          post_processing_func: A function that post-processes an optimization
              result appropriately (i.e., according to `round-trip`
              transformations).
          batch_initial_conditions: A tensor to specify the initial conditions.
              If no initial conditions are provided, the default initialization will
              be used.
          cyclic_options: Options for stopping criterion for outer cyclic optimization.
          ic_generator: Function for generating initial conditions. Not needed when
              `batch_initial_conditions` are provided. Defaults to
              `gen_one_shot_kg_initial_conditions` for `qKnowledgeGradient` acquisition
              functions and `gen_batch_initial_conditions` otherwise. Must be specified
              for nonlinear inequality constraints.
          timeout_sec: Max amount of time optimization can run for.
          return_full_tree:
          retry_on_optimization_warning: Whether to retry candidate generation with a new
              set of initial conditions when it fails with an `OptimizationWarning`.
          ic_gen_kwargs: Additional keyword arguments passed to function specified by
              `ic_generator`
  
      Returns:
          A two-element tuple containing
  
          - a `q x d`-dim tensor of generated candidates.
          - a `q`-dim tensor of expected acquisition values, where the value at
              index `i` is the acquisition value conditional on having observed
              all candidates except candidate `i`.
  
      Example:
          >>> # generate `q=3` candidates cyclically using 15 random restarts
          >>> # 256 raw samples, and 4 cycles
          >>>
          >>> qEI = qExpectedImprovement(model, best_f=0.2)
          >>> bounds = torch.tensor([[0.], [1.]])
          >>> candidates, acq_value_list = optimize_acqf_cyclic(
          >>>     qEI, bounds, 3, 15, 256, cyclic_options={"maxiter": 4}
          >>> )
      """
      opt_inputs = OptimizeAcqfInputs(
          acq_function=acq_function,
          bounds=bounds,
          q=q,
          num_restarts=num_restarts,
          raw_samples=raw_samples,
          options=options,
          inequality_constraints=inequality_constraints,
          equality_constraints=equality_constraints,
          nonlinear_inequality_constraints=None,
          fixed_features=fixed_features,
          post_processing_func=post_processing_func,
          batch_initial_conditions=batch_initial_conditions,
          return_best_only=True,
          gen_candidates=gen_candidates_scipy,
          sequential=True,
          ic_generator=ic_generator,
          timeout_sec=timeout_sec,
          return_full_tree=return_full_tree,
          retry_on_optimization_warning=retry_on_optimization_warning,
          ic_gen_kwargs=ic_gen_kwargs,
      )
  
      # for the first cycle, optimize the q candidates sequentially
      candidates, acq_vals = _optimize_acqf(opt_inputs)
      q = opt_inputs.q
      opt_inputs = dataclasses.replace(opt_inputs, q=1)
      acq_function = opt_inputs.acq_function
  
      if q > 1:
          cyclic_options = cyclic_options or {}
          stopping_criterion = ExpMAStoppingCriterion(**cyclic_options)
          stop = stopping_criterion.evaluate(fvals=acq_vals)
          base_X_pending = acq_function.X_pending
          idxr = torch.ones(
              q, dtype=torch.bool, device=opt_inputs.bounds.device
          )
          while not stop:
              for i in range(q):
                  # optimize only candidate i
                  idxr[i] = 0
                  acq_function.set_X_pending(
                      torch.cat(
                          [base_X_pending, candidates[idxr]], dim=-2
                      )
                      if base_X_pending is not None
                      else candidates[idxr]
                  )
                  opt_inputs = dataclasses.replace(
                      opt_inputs,
                      batch_initial_conditions=candidates[i].unsqueeze(
                          0
                      ),
                      sequential=False,
                  )
                  candidate_i, acq_val_i = _optimize_acqf(opt_inputs)
                  candidates[i] = candidate_i
                  acq_vals[i] = acq_val_i
                  idxr[i] = 1
              stop = stopping_criterion.evaluate(fvals=acq_vals)
          acq_function.set_X_pending(base_X_pending)
      return candidates, acq_vals
  
imports: from __future__ import annotations
  import dataclasses
  import warnings
  from typing import Any, Callable, Dict, List, Optional, Tuple, Union
  import torch
  from botorch.acquisition.acquisition import AcquisitionFunction, OneShotAcquisitionFunction
  from botorch.acquisition.knowledge_gradient import qKnowledgeGradient
  from botorch.acquisition.multi_objective.hypervolume_knowledge_gradient import qHypervolumeKnowledgeGradient
  from botorch.exceptions import InputDataError, UnsupportedError
  from botorch.exceptions.warnings import OptimizationWarning
  from botorch.generation.gen import gen_candidates_scipy, TGenCandidates
  from botorch.logging import logger
  from botorch.optim.initializers import gen_batch_initial_conditions, gen_one_shot_hvkg_initial_conditions, gen_one_shot_kg_initial_conditions, TGenInitialConditions
  from botorch.optim.stopping import ExpMAStoppingCriterion
  from botorch.optim.utils import _filter_kwargs
  from torch import Tensor
label_desc: Use image node_id 8 for calling a global function with example usage: optimize_acqf_cyclic(acq_function, bounds, q, num_restarts, raw_samples, options, inequality_constraints, equality_constraints, fixed_features, post_processing_func, batch_initial_conditions, cyclic_options) and returns: candidates,  acq_vals