code: def idx_to_str(
      array_name: str,
      offset: int,
      size: Optional[str] = None,
      idx_symbol: str = "i",
      allow_scalar=False,
  ) -> str:
      if offset < 0:
          indices = f"{idx_symbol} + {array_name}.shape[0] - {offset}"
      elif offset > 0:
          indices = f"{idx_symbol} + {offset}"
      else:
          indices = idx_symbol
  
      if size:
          # TODO FIXME: The `Scan` `Op` should tell us which outputs are computed
          # in this way.  We shouldn't have to waste run-time efforts in order to
          # compensate for this poor `Op`/rewrite design and implementation.
          indices = f"({indices}) % {size}"
  
      if allow_scalar:
          return f"{array_name}[{indices}]"
      else:
          return f"np.asarray({array_name}[{indices}])"
  
file_path: pytensor/pytensor/link/numba/dispatch/scan.py
imports: from textwrap import dedent, indent
  from typing import Optional
  import numpy
  from numba import types
  from numba.extending import overload
  from pytensor.compile.mode import NUMBA
  from pytensor.link.numba.dispatch import basic
  from pytensor.link.numba.dispatch.basic import create_arg_string, create_tuple_string, numba_funcify
  from pytensor.link.utils import compile_function_src
  from pytensor.scan.op import Scan
  from pytensor.tensor.type import TensorType
label_desc: Use image node_id 1 for calling a global function with example usage: idx_to_str(array_name, offset, size, idx_symbol, allow_scalar) and returns: array_name, str, indices, str, str, array_name, str, indices, str