class_docstr: A `Feature` that tracks shape information in a graph.
  
  This `Feature` aids in the replacement of all `Shape`\s and `Subtensor`\s of `Shape`\s with
  `Shape_i` and `MakeVector` `Op`\s.
  
  This `Feature` and its associated rewrites have several goals:
  
  1. to "lift" `Shape`\s to as close to the inputs as possible,
  2. to infer the shape of every node in the graph in terms of the
     input shapes, and
  3. remove fill `Op`\s (e.g. `Second`) from the graph.
  
  Lifting shapes as close to the inputs as possible is important for
  canonicalization because it is very bad form to have to compute
  something just to know how big it will be.  Firstly, it is a waste
  of time to compute such outputs.  But it is important to get rid
  of these outputs as early as possible in the compilation process
  because the extra computations make it appear as if many internal
  graph nodes have multiple clients.  Many rewrites refuse to
  work on nodes with multiple clients.
  
  Lifting is done by using an `<Op>.infer_shape` function if one is
  present, or else using a conservative default.  An Op that
  supports shape-lifting should define a infer_shape(self, fgraph, node,
  input_shapes) function.  The argument input_shapes is a tuple of
  tuples... there is an interior tuple for each input to the node.
  The tuple has as many elements as dimensions.  The element in
  position i of tuple j represents the i'th shape component of the
  j'th input.  The function should return a tuple of tuples.  One
  output tuple for each node.output.  Again, the i'th element of the
  j'th output tuple represents the output[j].shape[i] of the
  function.  If an output is not a TensorType, then None should be
  returned instead of a tuple for that output.
  
  For example the infer_shape for a matrix-matrix product would accept
  input_shapes=((x0,x1), (y0,y1)) and return ((x0, y1),).
  
  Inferring the shape of internal nodes in the graph is important
  for doing size-driven rewrites.  If we know how big various
  intermediate results will be, we can estimate the cost of many Ops
  accurately, and generate c-code that is specific [e.g. unrolled]
  to particular sizes.
  
  In cases where you cannot figure out the shape, raise a ShapeError.
  
  Notes
  -----
  To use this shape information in rewrites, use the
  ``shape_of`` dictionary.
  
  For example:
  
  .. code-block:: python
  
      try:
          shape_of = fgraph.shape_feature.shape_of
      except AttributeError:
          # This can happen when the mode doesn't include the ShapeFeature.
          return
  
      shape_of_output_zero = shape_of[node.output[0]]
  
  The ``shape_of_output_zero`` symbol will contain a tuple, whose
  elements are either integers or symbolic integers.
  
  TODO: check to see if the symbols are necessarily
  non-constant... or are integer literals sometimes PyTensor
  constants?? That would be confusing.
class_docstr_tok: ["A","`","Feature","`","that","tracks","shape","information","in","a","graph",".","This","`","Feature","`","aids","in","the","replacement","of","all","`","Shape","`","\\s","and","`","Subtensor","`","\\s","of","`","Shape","`","\\s","with","`","Shape_i","`","and","`","MakeVector","`","`","Op","`","\\s",".","This","`","Feature","`","and","its","associated","rewrites","have","several","goals",":","1.","to","``","lift","''","`","Shape","`","\\s","to","as","close","to","the","inputs","as","possible",",","2.","to","infer","the","shape","of","every","node","in","the","graph","in","terms","of","the","input","shapes",",","and","3.","remove","fill","`","Op","`","\\s","(","e.g",".","`","Second","`",")","from","the","graph",".","Lifting","shapes","as","close","to","the","inputs","as","possible","is","important","for","canonicalization","because","it","is","very","bad","form","to","have","to","compute","something","just","to","know","how","big","it","will","be",".","Firstly",",","it","is","a","waste","of","time","to","compute","such","outputs",".","But","it","is","important","to","get","rid","of","these","outputs","as","early","as","possible","in","the","compilation","process","because","the","extra","computations","make","it","appear","as","if","many","internal","graph","nodes","have","multiple","clients",".","Many","rewrites","refuse","to","work","on","nodes","with","multiple","clients",".","Lifting","is","done","by","using","an","`","<","Op",">",".infer_shape","`","function","if","one","is","present",",","or","else","using","a","conservative","default",".","An","Op","that","supports","shape-lifting","should","define","a","infer_shape","(","self",",","fgraph",",","node",",","input_shapes",")","function",".","The","argument","input_shapes","is","a","tuple","of","tuples","...","there","is","an","interior","tuple","for","each","input","to","the","node",".","The","tuple","has","as","many","elements","as","dimensions",".","The","element","in","position","i","of","tuple","j","represents","the","i'th","shape","component","of","the","j'th","input",".","The","function","should","return","a","tuple","of","tuples",".","One","output","tuple","for","each","node.output",".","Again",",","the","i'th","element","of","the","j'th","output","tuple","represents","the","output","[","j","]",".shape","[","i","]","of","the","function",".","If","an","output","is","not","a","TensorType",",","then","None","should","be","returned","instead","of","a","tuple","for","that","output",".","For","example","the","infer_shape","for","a","matrix-matrix","product","would","accept","input_shapes=","(","(","x0",",","x1",")",",","(","y0",",","y1",")",")","and","return","(","(","x0",",","y1",")",",",")",".","Inferring","the","shape","of","internal","nodes","in","the","graph","is","important","for","doing","size-driven","rewrites",".","If","we","know","how","big","various","intermediate","results","will","be",",","we","can","estimate","the","cost","of","many","Ops","accurately",",","and","generate","c-code","that","is","specific","[","e.g",".","unrolled","]","to","particular","sizes",".","In","cases","where","you","can","not","figure","out","the","shape",",","raise","a","ShapeError",".","Notes","--","--","-","To","use","this","shape","information","in","rewrites",",","use","the","``","shape_of","``","dictionary",".","For","example",":","..","code-block",":",":","python","try",":","shape_of","=","fgraph.shape_feature.shape_of","except","AttributeError",":","#","This","can","happen","when","the","mode","does","n't","include","the","ShapeFeature",".","return","shape_of_output_zero","=","shape_of","[","node.output","[","0","]","]","The","``","shape_of_output_zero","``","symbol","will","contain","a","tuple",",","whose","elements","are","either","integers","or","symbolic","integers",".","TODO",":","check","to","see","if","the","symbols","are","necessarily","non-constant","...","or","are","integer","literals","sometimes","PyTensor","constants","?","?","That","would","be","confusing","."]
docstr: Return a symbolic integer scalar for the shape element s_i.
  
  The s_i argument was produced by the infer_shape() of an Op subclass.
  
  var: the variable that correspond to s_i. This is just for
  error reporting.
code: def unpack(self, s_i, var):
      """Return a symbolic integer scalar for the shape element s_i.
  
      The s_i argument was produced by the infer_shape() of an Op subclass.
  
      var: the variable that correspond to s_i. This is just for
      error reporting.
  
      """
      assert s_i is not None
  
      if s_i == 1:
          return self.lscalar_one
      if isinstance(s_i, float) and int(s_i) == s_i:
          s_i = int(s_i)
      if isinstance(s_i, (np.integer, int)) or (
          isinstance(s_i, np.ndarray) and s_i.ndim == 0
      ):
          # this shape is a constant
          if s_i < 0:
              msg = "There is a negative shape in the graph!"
              msg += get_variable_trace_string(var)
              # The rest of the pipeline don't handle correctly this
              # case.  So we have 2 choices, stop compilation or
              # consider the shape as unknown.  As we have more
              # chance to give the stack trace here then later, I
              # choose that options as it would give better error
              # message.
              raise AssertionError(msg)
          return constant(s_i, dtype="int64")
      if isinstance(s_i, (tuple, list)):
          # this dimension is the same as many of the inputs
          # which tells us that if one of the inputs is known,
          # the others all become known.
          # TODO: should be implemented in Elemwise, and Dot
          #
          # worst case, we loop over shape_of and replace things
          raise NotImplementedError(s_i)
  
      # s_i is x.shape[i] for some x, we change it to shape_of[x][i]
      if (
          s_i.owner
          and isinstance(s_i.owner.op, Subtensor)
          and s_i.owner.inputs[0].owner
          and isinstance(s_i.owner.inputs[0].owner.op, Shape)
      ):
          assert s_i.type.ndim == 0
          assert len(s_i.owner.op.idx_list) == 1
  
          # The current Subtensor always put constant index in the graph.
          # This was not True in the past. So call the Subtensor function
          # that will return the right index.
          idx = get_idx_list(s_i.owner.inputs, s_i.owner.op.idx_list)
          assert len(idx) == 1
          idx = idx[0]
          try:
              i = get_underlying_scalar_constant_value(idx)
          except NotScalarConstantError:
              pass
          else:
              # Executed only if no exception was raised
              x = s_i.owner.inputs[0].owner.inputs[0]
              # x should already have been imported, and should be in shape_of.
              s_i = self.shape_of[x][i]
  
      if s_i.type.dtype in integer_dtypes:
          if getattr(s_i.type, "ndim", 0):
              raise TypeError("Shape element must be scalar", s_i)
          return s_i
      else:
          raise TypeError(
              "Unsupported shape element",
              s_i,
              type(s_i),
              getattr(s_i, "type", None),
          )
  
imports: import traceback
  from io import StringIO
  from typing import Optional
  from typing import cast
  from warnings import warn
  import numpy
  import pytensor
  from pytensor.configdefaults import config
  from pytensor.graph.basic import Constant, Variable, ancestors, equal_computations
  from pytensor.graph.features import AlreadyThere, Feature
  from pytensor.graph.fg import FunctionGraph
  from pytensor.graph.rewriting.basic import GraphRewriter, check_chain, copy_stack_trace, node_rewriter
  from pytensor.graph.utils import InconsistencyError, get_variable_trace_string
  from pytensor.tensor.basic import MakeVector, as_tensor_variable, cast, constant, extract_constant, get_underlying_scalar_constant_value, register_infer_shape, stack
  from pytensor.tensor.elemwise import DimShuffle, Elemwise
  from pytensor.tensor.exceptions import NotScalarConstantError, ShapeError
  from pytensor.tensor.rewriting.basic import register_canonicalize, register_specialize, register_stabilize, register_useless, topo_constant_folding
  from pytensor.tensor.shape import Reshape, Shape, Shape_i, SpecifyShape, Unbroadcast, shape_i, specify_shape, unbroadcast
  from pytensor.tensor.subtensor import Subtensor, get_idx_list
  from pytensor.tensor.type import TensorType, discrete_dtypes, integer_dtypes
  from pytensor.tensor.type_other import NoneConst
label_desc: Use image node_id 6 for calling the ShapeFeature obj's underlying member method code with example usage: obj.unpack(s_i, var) and returns: self, constant, s_i