code: def sample_posterior_predictive(
      trace,
      model: Optional[Model] = None,
      var_names: Optional[List[str]] = None,
      sample_dims: Optional[List[str]] = None,
      random_seed: RandomState = None,
      progressbar: bool = True,
      return_inferencedata: bool = True,
      extend_inferencedata: bool = False,
      predictions: bool = False,
      idata_kwargs: dict = None,
      compile_kwargs: dict = None,
  ) -> Union[InferenceData, Dict[str, np.ndarray]]:
      """Generate posterior predictive samples from a model given a trace.
  
      Parameters
      ----------
      trace : backend, list, xarray.Dataset, arviz.InferenceData, or MultiTrace
          Trace generated from MCMC sampling, or a list of dicts (eg. points or from find_MAP()),
          or xarray.Dataset (eg. InferenceData.posterior or InferenceData.prior)
      model : Model (optional if in ``with`` context)
          Model to be used to generate the posterior predictive samples. It will
          generally be the model used to generate the ``trace``, but it doesn't need to be.
      var_names : Iterable[str]
          Names of variables for which to compute the posterior predictive samples.
      sample_dims : list of str, optional
          Dimensions over which to loop and generate posterior predictive samples.
          When `sample_dims` is ``None`` (default) both "chain" and "draw" are considered sample
          dimensions. Only taken into account when `trace` is InferenceData or Dataset.
      random_seed : int, RandomState or Generator, optional
          Seed for the random number generator.
      progressbar : bool
          Whether or not to display a progress bar in the command line. The bar shows the percentage
          of completion, the sampling speed in samples per second (SPS), and the estimated remaining
          time until completion ("expected time of arrival"; ETA).
      return_inferencedata : bool, default True
          Whether to return an :class:`arviz:arviz.InferenceData` (True) object or a dictionary (False).
      extend_inferencedata : bool, default False
          Whether to automatically use :meth:`arviz.InferenceData.extend` to add the posterior predictive samples to
          ``trace`` or not. If True, ``trace`` is modified inplace but still returned.
      predictions : bool, default False
          Flag used to set the location of posterior predictive samples within the returned ``arviz.InferenceData`` object. If False, assumes samples are generated based on the fitting data to be used for posterior predictive checks, and samples are stored in the ``posterior_predictive``. If True, assumes samples are generated based on out-of-sample data as predictions, and samples are stored in the ``predictions`` group.
      idata_kwargs : dict, optional
          Keyword arguments for :func:`pymc.to_inference_data` if ``predictions=False`` or to
          :func:`pymc.predictions_to_inference_data` otherwise.
      compile_kwargs: dict, optional
          Keyword arguments for :func:`pymc.pytensorf.compile_pymc`.
  
      Returns
      -------
      arviz.InferenceData or Dict
          An ArviZ ``InferenceData`` object containing the posterior predictive samples (default), or
          a dictionary with variable names as keys, and samples as numpy arrays.
  
      Examples
      --------
      Thin a sampled inferencedata by keeping 1 out of every 5 draws
      before passing it to sample_posterior_predictive
  
      .. code:: python
  
          thinned_idata = idata.sel(draw=slice(None, None, 5))
          with model:
              idata.extend(pymc.sample_posterior_predictive(thinned_idata))
  
      Generate 5 posterior predictive samples per posterior sample.
  
      .. code:: python
  
          expanded_data = idata.posterior.expand_dims(pred_id=5)
          with model:
              idata.extend(pymc.sample_posterior_predictive(expanded_data))
      """
  
      _trace: Union[MultiTrace, PointList]
      nchain: int
      if idata_kwargs is None:
          idata_kwargs = {}
      else:
          idata_kwargs = idata_kwargs.copy()
      if sample_dims is None:
          sample_dims = ["chain", "draw"]
      constant_data: Dict[str, np.ndarray] = {}
      trace_coords: Dict[str, np.ndarray] = {}
      if "coords" not in idata_kwargs:
          idata_kwargs["coords"] = {}
      idata: Optional[InferenceData] = None
      stacked_dims = None
      if isinstance(trace, InferenceData):
          _constant_data = getattr(trace, "constant_data", None)
          if _constant_data is not None:
              trace_coords.update(
                  {
                      str(k): v.data
                      for k, v in _constant_data.coords.items()
                  }
              )
              constant_data.update(
                  {str(k): v.data for k, v in _constant_data.items()}
              )
          idata = trace
          trace = trace["posterior"]
      if isinstance(trace, xarray.Dataset):
          trace_coords.update(
              {str(k): v.data for k, v in trace.coords.items()}
          )
          _trace, stacked_dims = dataset_to_point_list(
              trace, sample_dims
          )
          nchain = 1
      elif isinstance(trace, MultiTrace):
          _trace = trace
          nchain = _trace.nchains
      elif isinstance(trace, list) and all(
          isinstance(x, dict) for x in trace
      ):
          _trace = trace
          nchain = 1
      else:
          raise TypeError(
              f"Unsupported type for `trace` argument: {type(trace)}."
          )
      len_trace = len(_trace)
  
      if isinstance(_trace, MultiTrace):
          samples = sum(len(v) for v in _trace._straces.values())
      elif isinstance(_trace, list):
          # this is a list of points
          samples = len(_trace)
      else:
          raise TypeError(
              "Do not know how to compute number of samples for trace argument of type %s"
              % type(_trace)
          )
  
      assert samples is not None
  
      model = modelcontext(model)
  
      if model.potentials:
          warnings.warn(
              "The effect of Potentials on other parameters is ignored during posterior predictive sampling. "
              "This is likely to lead to invalid or biased predictive samples.",
              UserWarning,
              stacklevel=2,
          )
  
      constant_coords = set()
      for dim, coord in trace_coords.items():
          current_coord = model.coords.get(dim, None)
          if (
              current_coord is not None
              and len(coord) == len(current_coord)
              and np.all(coord == current_coord)
          ):
              constant_coords.add(dim)
  
      if var_names is not None:
          vars_ = [model[x] for x in var_names]
      else:
          vars_ = (
              model.observed_RVs
              + observed_dependent_deterministics(model)
          )
  
      indices = np.arange(samples)
      if progressbar:
          indices = progress_bar(
              indices, total=samples, display=progressbar
          )
  
      vars_to_sample = list(
          get_default_varnames(vars_, include_transformed=False)
      )
  
      if not vars_to_sample:
          if return_inferencedata and not extend_inferencedata:
              return InferenceData()
          elif return_inferencedata and extend_inferencedata:
              return trace
          return {}
  
      vars_in_trace = get_vars_in_point_list(_trace, model)
  
      if random_seed is not None:
          (random_seed,) = _get_seeds_per_chain(random_seed, 1)
  
      if compile_kwargs is None:
          compile_kwargs = {}
      compile_kwargs.setdefault("allow_input_downcast", True)
      compile_kwargs.setdefault("accept_inplace", True)
  
      (
          _sampler_fn,
          volatile_basic_rvs,
      ) = compile_forward_sampling_function(
          outputs=vars_to_sample,
          vars_in_trace=vars_in_trace,
          basic_rvs=model.basic_RVs,
          givens_dict=None,
          random_seed=random_seed,
          constant_data=constant_data,
          constant_coords=constant_coords,
          **compile_kwargs,
      )
      sampler_fn = point_wrapper(_sampler_fn)
      # All model variables have a name, but mypy does not know this
      _log.info(f"Sampling: {list(sorted(volatile_basic_rvs, key=lambda var: var.name))}")  # type: ignore
      ppc_trace_t = _DefaultTrace(samples)
      try:
          for idx in indices:
              if nchain > 1:
                  # the trace object will either be a MultiTrace (and have _straces)...
                  if hasattr(_trace, "_straces"):
                      chain_idx, point_idx = np.divmod(idx, len_trace)
                      chain_idx = chain_idx % nchain
                      param = (
                          cast(MultiTrace, _trace)
                          ._straces[chain_idx]
                          .point(point_idx)
                      )
                  # ... or a PointList
                  else:
                      param = cast(PointList, _trace)[
                          idx % (len_trace * nchain)
                      ]
              # there's only a single chain, but the index might hit it multiple times if
              # the number of indices is greater than the length of the trace.
              else:
                  param = _trace[idx % len_trace]
  
              values = sampler_fn(**param)
  
              for k, v in zip(vars_, values):
                  ppc_trace_t.insert(k.name, v, idx)
      except KeyboardInterrupt:
          pass
  
      ppc_trace = ppc_trace_t.trace_dict
  
      for k, ary in ppc_trace.items():
          if stacked_dims is not None:
              ppc_trace[k] = ary.reshape(
                  (
                      *[len(coord) for coord in stacked_dims.values()],
                      *ary.shape[1:],
                  )
              )
          else:
              ppc_trace[k] = ary.reshape(
                  (nchain, len_trace, *ary.shape[1:])
              )
  
      if not return_inferencedata:
          return ppc_trace
      ikwargs: Dict[str, Any] = dict(model=model, **idata_kwargs)
      ikwargs.setdefault("sample_dims", sample_dims)
      if stacked_dims is not None:
          coords = ikwargs.get("coords", {})
          ikwargs["coords"] = {**stacked_dims, **coords}
      if predictions:
          if extend_inferencedata:
              ikwargs.setdefault("idata_orig", idata)
              ikwargs.setdefault("inplace", True)
          return pm.predictions_to_inference_data(ppc_trace, **ikwargs)
      idata_pp = pm.to_inference_data(
          posterior_predictive=ppc_trace, **ikwargs
      )
  
      if extend_inferencedata and idata is not None:
          idata.extend(idata_pp)
          return idata
      return idata_pp
  
imports: import logging
  import warnings
  from typing import Any, Callable, Dict, Iterable, List, Optional, Sequence, Set, Tuple, Union, cast
  import numpy
  import xarray
  from arviz import InferenceData
  from fastprogress.fastprogress import progress_bar
  from pytensor import tensor
  from pytensor.graph.basic import Apply, Constant, Variable, ancestors, general_toposort, walk
  from pytensor.graph.fg import FunctionGraph
  from pytensor.tensor.random.var import RandomGeneratorSharedVariable, RandomStateSharedVariable
  from pytensor.tensor.sharedvar import SharedVariable
  from typing_extensions import TypeAlias
  import pymc
  from pymc.backends.arviz import _DefaultTrace
  from pymc.backends.base import MultiTrace
  from pymc.blocking import PointType
  from pymc.model import Model, modelcontext
  from pymc.pytensorf import compile_pymc
  from pymc.util import RandomState, _get_seeds_per_chain, dataset_to_point_list, get_default_varnames, point_wrapper
label_desc: Use image node_id 6 for calling a global function with example usage: sample_posterior_predictive(trace, model, var_names, sample_dims, random_seed, progressbar, return_inferencedata, extend_inferencedata, predictions, idata_kwargs, compile_kwargs) and returns: idata_pp, dict, ppc_trace, pm, idata, InferenceData, trace