class_docstr: Does Bayesian optimization with structural additive contextual GP (SACGP).
  The parameter space decomposition must be provided.
  
  Args:
      decomposition: Keys are context names. Values are the lists of parameter
          names belong to the context, e.g.
          {'context1': ['p1_c1', 'p2_c1'],'context2': ['p1_c2', 'p2_c2']}.
class_docstr_tok: ["Does","Bayesian","optimization","with","structural","additive","contextual","GP","(","SACGP",")",".","The","parameter","space","decomposition","must","be","provided",".","Args",":","decomposition",":","Keys","are","context","names",".","Values","are","the","lists","of","parameter","names","belong","to","the","context",",","e.g",".","{","'context1","'",":","[","'p1_c1","'",",","'p2_c1","'","]",",","'context2","'",":","[","'p1_c2","'",",","'p2_c2","'","]","}","."]
docstr: Get a fitted StructuralAdditiveContextualGP model for each outcome.
  Args:
      Xs: X for each outcome.
      Ys: Y for each outcome.
      Yvars: Noise variance of Y for each outcome.
  Returns: Fitted StructuralAdditiveContextualGP model.
docstr_tok: ["Get","a","fitted","StructuralAdditiveContextualGP","model","for","each","outcome",".","Args",":","Xs",":","X","for","each","outcome",".","Ys",":","Y","for","each","outcome",".","Yvars",":","Noise","variance","of","Y","for","each","outcome",".","Returns",":","Fitted","StructuralAdditiveContextualGP","model","."]
code: def get_and_fit_model(
      self,
      Xs: List[Tensor],
      Ys: List[Tensor],
      Yvars: List[Tensor],
      task_features: List[int],
      fidelity_features: List[int],
      metric_names: List[str],
      state_dict: Optional[Dict[str, Tensor]] = None,
      fidelity_model_id: Optional[int] = None,
      **kwargs: Any,
  ) -> GPyTorchModel:
      """Get a fitted StructuralAdditiveContextualGP model for each outcome.
      Args:
          Xs: X for each outcome.
          Ys: Y for each outcome.
          Yvars: Noise variance of Y for each outcome.
      Returns: Fitted StructuralAdditiveContextualGP model.
      """
      # generate model space decomposition dict
      decomp_index = generate_model_space_decomposition(
          decomposition=self.decomposition,
          feature_names=self.feature_names,
      )
  
      models = []
      for i, X in enumerate(Xs):
          Yvar = Yvars[i].clamp_min_(MIN_OBSERVED_NOISE_LEVEL)
          gp_m = SACGP(X, Ys[i], Yvar, decomp_index)
          mll = ExactMarginalLogLikelihood(gp_m.likelihood, gp_m)
          fit_gpytorch_mll(mll)
          models.append(gp_m)
  
      if len(models) == 1:
          model = models[0]
      else:
          model = ModelListGP(*models)
      model.to(Xs[0])
      return model
  
imports: from logging import Logger
  from typing import Any, Dict, List, Optional
  from ax.core.search_space import SearchSpaceDigest
  from ax.core.types import TCandidateMetadata
  from ax.models.torch.botorch import BotorchModel
  from ax.models.torch_base import TorchModel
  from ax.utils.common.docutils import copy_doc
  from ax.utils.common.logger import get_logger
  from botorch.fit import fit_gpytorch_mll
  from botorch.models.contextual import SACGP
  from botorch.models.gpytorch import GPyTorchModel
  from botorch.models.model_list_gp_regression import ModelListGP
  from botorch.utils.datasets import SupervisedDataset
  from gpytorch.mlls.exact_marginal_log_likelihood import ExactMarginalLogLikelihood
  from torch import Tensor
label_desc: Use image node_id 3 for calling the SACBO obj's underlying member method code with example usage: obj.get_and_fit_model(Xs, Ys, Yvars, task_features, fidelity_features, metric_names, state_dict, fidelity_model_id) and returns: model