class_docstr: A class that wraps the execution of a `VM` making it easier for use as a "function".
  
  `Function` is the callable object that does computation.  It has the storage
  of inputs and outputs, performs the packing and unpacking of inputs and
  return values. It implements the square-bracket indexing so that you can
  look up the value of a symbolic node.
  
  Functions are copyable via `Function.copy` and the `copy.copy` interface.
  When a function is copied, this instance is duplicated. Contrast with
  self.maker (instance of `FunctionMaker`) that is shared between copies.
  The meaning of copying a function is that the containers and their current
  values will all be duplicated. This requires that mutable inputs be
  copied, whereas immutable inputs may be shared between copies.
  
  A Function instance is hashable, on the basis of its memory address (its
  id).
  A Function instance is only equal to itself.
  A Function instance may be serialized using the `pickle` or
  `cPickle` modules.  This will save all default inputs, the graph,
  and WRITEME to the pickle file.
  
  A `Function` instance has a `Function.trust_input` field that defaults to
  ``False``. When ``True``, the `Function` will skip all checks on the
  inputs.
  
  Attributes
  ----------
  finder
      Dictionary mapping several kinds of things to containers.
  
      We set an entry in finder for:
      - the index of the input
      - the variable instance the input is based on
      - the name of the input
  
      All entries map to the container or to DUPLICATE if an ambiguity
      is detected.
  inv_finder
      Reverse lookup of `finder`.  It maps containers to `SymbolicInput`\s.
docstr: Evaluates value of a function on given arguments.
  
  Parameters
  ----------
  args : list
      List of inputs to the function. All inputs are required, even when
      some of them are not necessary to calculate requested subset of
      outputs.
  
  kwargs : dict
      The function inputs can be passed as keyword argument. For this, use
      the name of the input or the input instance as the key.
  
      Keyword argument ``output_subset`` is a list of either indices of the
      function's outputs or the keys belonging to the `output_keys` dict
      and represent outputs that are requested to be calculated. Regardless
      of the presence of ``output_subset``, the updates are always calculated
      and processed. To disable the updates, you should use the ``copy``
      method with ``delete_updates=True``.
  
  Returns
  -------
  list
      List of outputs on indices/keys from ``output_subset`` or all of them,
      if ``output_subset`` is not passed.
docstr_tok: ["Evaluates","value","of","a","function","on","given","arguments",".","Parameters","--","--","--","--","--","args",":","list","List","of","inputs","to","the","function",".","All","inputs","are","required",",","even","when","some","of","them","are","not","necessary","to","calculate","requested","subset","of","outputs",".","kwargs",":","dict","The","function","inputs","can","be","passed","as","keyword","argument",".","For","this",",","use","the","name","of","the","input","or","the","input","instance","as","the","key",".","Keyword","argument","``","output_subset","``","is","a","list","of","either","indices","of","the","function","'s","outputs","or","the","keys","belonging","to","the","`","output_keys","`","dict","and","represent","outputs","that","are","requested","to","be","calculated",".","Regardless","of","the","presence","of","``","output_subset","``",",","the","updates","are","always","calculated","and","processed",".","To","disable","the","updates",",","you","should","use","the","``","copy","``","method","with","``","delete_updates=True","``",".","Returns","--","--","--","-","list","List","of","outputs","on","indices\/keys","from","``","output_subset","``","or","all","of","them",",","if","``","output_subset","``","is","not","passed","."]
code: def __call__(self, *args, **kwargs):
      """
      Evaluates value of a function on given arguments.
  
      Parameters
      ----------
      args : list
          List of inputs to the function. All inputs are required, even when
          some of them are not necessary to calculate requested subset of
          outputs.
  
      kwargs : dict
          The function inputs can be passed as keyword argument. For this, use
          the name of the input or the input instance as the key.
  
          Keyword argument ``output_subset`` is a list of either indices of the
          function's outputs or the keys belonging to the `output_keys` dict
          and represent outputs that are requested to be calculated. Regardless
          of the presence of ``output_subset``, the updates are always calculated
          and processed. To disable the updates, you should use the ``copy``
          method with ``delete_updates=True``.
  
      Returns
      -------
      list
          List of outputs on indices/keys from ``output_subset`` or all of them,
          if ``output_subset`` is not passed.
      """
  
      def restore_defaults():
          for i, (required, refeed, value) in enumerate(self.defaults):
              if refeed:
                  if isinstance(value, Container):
                      value = value.storage[0]
                  self[i] = value
  
      profile = self.profile
      t0 = time.perf_counter()
  
      output_subset = kwargs.pop("output_subset", None)
      if output_subset is not None and self.output_keys is not None:
          output_subset = [
              self.output_keys.index(key) for key in output_subset
          ]
  
      # Reinitialize each container's 'provided' counter
      if self.trust_input:
          i = 0
          for arg in args:
              s = self.input_storage[i]
              s.storage[0] = arg
              i += 1
      else:
          for c in self.input_storage:
              c.provided = 0
  
          if len(args) + len(kwargs) > len(self.input_storage):
              raise TypeError(
                  "Too many parameter passed to pytensor function"
              )
  
          # Set positional arguments
          i = 0
          for arg in args:
              # TODO: provide a option for skipping the filter if we really
              # want speed.
              s = self.input_storage[i]
              # see this emails for a discuation about None as input
              # https://groups.google.com/group/theano-dev/browse_thread/thread/920a5e904e8a8525/4f1b311a28fc27e5
              if arg is None:
                  s.storage[0] = arg
              else:
                  try:
                      s.storage[0] = s.type.filter(
                          arg,
                          strict=s.strict,
                          allow_downcast=s.allow_downcast,
                      )
  
                  except Exception as e:
                      function_name = "pytensor function"
                      argument_name = "argument"
                      if self.name:
                          function_name += (
                              ' with name "' + self.name + '"'
                          )
                      if hasattr(arg, "name") and arg.name:
                          argument_name += (
                              ' with name "' + arg.name + '"'
                          )
                      where = get_variable_trace_string(
                          self.maker.inputs[i].variable
                      )
                      if len(e.args) == 1:
                          e.args = (
                              "Bad input "
                              + argument_name
                              + " to "
                              + function_name
                              + f" at index {int(i)} (0-based). {where}"
                              + e.args[0],
                          )
                      else:
                          e.args = (
                              "Bad input "
                              + argument_name
                              + " to "
                              + function_name
                              + f" at index {int(i)} (0-based). {where}"
                          ) + e.args
                      restore_defaults()
                      raise
              s.provided += 1
              i += 1
  
      # Set keyword arguments
      if kwargs:  # for speed, skip the items for empty kwargs
          for k, arg in kwargs.items():
              self[k] = arg
  
      if (
          not self.trust_input
          and
          # The getattr is only needed for old pickle
          getattr(self, "_check_for_aliased_inputs", True)
      ):
          # Collect aliased inputs among the storage space
          args_share_memory = []
          for i in range(len(self.input_storage)):
              i_var = self.maker.inputs[i].variable
              i_val = self.input_storage[i].storage[0]
              if hasattr(i_var.type, "may_share_memory"):
                  is_aliased = False
                  for j in range(len(args_share_memory)):
                      group_j = zip(
                          [
                              self.maker.inputs[k].variable
                              for k in args_share_memory[j]
                          ],
                          [
                              self.input_storage[k].storage[0]
                              for k in args_share_memory[j]
                          ],
                      )
                      if any(
                          (
                              var.type is i_var.type
                              and var.type.may_share_memory(val, i_val)
                          )
                          for (var, val) in group_j
                      ):
                          is_aliased = True
                          args_share_memory[j].append(i)
                          break
  
                  if not is_aliased:
                      args_share_memory.append([i])
  
          # Check for groups of more than one argument that share memory
          for group in args_share_memory:
              if len(group) > 1:
                  # copy all but the first
                  for j in group[1:]:
                      self.input_storage[j].storage[0] = copy.copy(
                          self.input_storage[j].storage[0]
                      )
  
      # Check if inputs are missing, or if inputs were set more than once, or
      # if we tried to provide inputs that are supposed to be implicit.
      if not self.trust_input:
          for c in self.input_storage:
              if c.required and not c.provided:
                  restore_defaults()
                  raise TypeError(
                      f"Missing required input: {getattr(self.inv_finder[c], 'variable', self.inv_finder[c])}"
                  )
              if c.provided > 1:
                  restore_defaults()
                  raise TypeError(
                      f"Multiple values for input: {getattr(self.inv_finder[c], 'variable', self.inv_finder[c])}"
                  )
              if c.implicit and c.provided > 0:
                  restore_defaults()
                  raise TypeError(
                      f"Tried to provide value for implicit input: {getattr(self.inv_finder[c], 'variable', self.inv_finder[c])}"
                  )
  
      # Do the actual work
      t0_fn = time.perf_counter()
      try:
          outputs = (
              self.vm()
              if output_subset is None
              else self.vm(output_subset=output_subset)
          )
      except Exception:
          restore_defaults()
          if hasattr(self.vm, "position_of_error"):
              # this is a new vm-provided function or c linker
              # they need this because the exception manipulation
              # done by raise_with_op is not implemented in C.
              thunk = None
              if hasattr(self.vm, "thunks"):
                  thunk = self.vm.thunks[self.vm.position_of_error]
              raise_with_op(
                  self.maker.fgraph,
                  node=self.vm.nodes[self.vm.position_of_error],
                  thunk=thunk,
                  storage_map=getattr(self.vm, "storage_map", None),
              )
          else:
              # old-style linkers raise their own exceptions
              raise
  
      dt_fn = time.perf_counter() - t0_fn
      self.maker.mode.fn_time += dt_fn
      if profile:
          profile.vm_call_time += dt_fn
  
      # Retrieve the values that were computed
      if outputs is None:
          outputs = [x.data for x in self.output_storage]
      assert len(outputs) == len(self.output_storage)
  
      # Remove internal references to required inputs.
      # These cannot be re-used anyway.
      for c in self.input_storage:
          if c.required:
              c.storage[0] = None
  
      # if we are allowing garbage collection, remove the
      # output reference from the internal storage cells
      if getattr(self.vm, "allow_gc", False):
          assert len(self.output_storage) == len(
              self.maker.fgraph.outputs
          )
          for o_container, o_variable in zip(
              self.output_storage, self.maker.fgraph.outputs
          ):
              if o_variable.owner is not None:
                  # this node is the variable of computation
                  # WARNING: This circumvents the 'readonly' attribute in x
                  o_container.storage[0] = None
  
      # TODO: Get rid of this and `expanded_inputs`, since all the VMs now
      # perform the updates themselves
      if getattr(self.vm, "need_update_inputs", True):
          # Update the inputs that have an update function
          for input, storage in reversed(
              list(zip(self.maker.expanded_inputs, self.input_storage))
          ):
              if input.update is not None:
                  storage.data = outputs.pop()
      else:
          outputs = outputs[: self.n_returned_outputs]
  
      # Put default values back in the storage
      restore_defaults()
      #
      # NOTE: This logic needs to be replicated in
      #       scan.
      #       grep for 'PROFILE_CODE'
      #
  
      dt_call = time.perf_counter() - t0
      pytensor.compile.profiling.total_fct_exec_time += dt_call
      self.maker.mode.call_time += dt_call
      if profile:
          profile.fct_callcount += 1
          profile.fct_call_time += dt_call
          if hasattr(self.vm, "update_profile"):
              self.vm.update_profile(profile)
          if profile.ignore_first_call:
              profile.reset()
              profile.ignore_first_call = False
      if self.return_none:
          return None
      elif (
          self.unpack_single
          and len(outputs) == 1
          and output_subset is None
      ):
          return outputs[0]
      else:
          if self.output_keys is not None:
              assert len(self.output_keys) == len(outputs)
  
              if output_subset is None:
                  return dict(zip(self.output_keys, outputs))
              else:
                  return {
                      self.output_keys[index]: outputs[index]
                      for index in output_subset
                  }
  
          if output_subset is None:
              return outputs
          else:
              return [outputs[i] for i in output_subset]
  
imports: import copy
  import copyreg
  import logging
  import time
  import warnings
  from itertools import chain
  from typing import TYPE_CHECKING, Optional
  import numpy
  import pytensor
  import pytensor.compile.profiling
  from pytensor.compile.io import In, SymbolicInput, SymbolicOutput
  from pytensor.compile.ops import deep_copy_op, view_op
  from pytensor.configdefaults import config
  from pytensor.graph.basic import Constant, Variable, ancestors, clone_get_equiv, graph_inputs
  from pytensor.graph.destroyhandler import DestroyHandler
  from pytensor.graph.features import AlreadyThere, Feature, PreserveVariableAttributes
  from pytensor.graph.fg import FunctionGraph
  from pytensor.graph.op import HasInnerGraph
  from pytensor.graph.utils import InconsistencyError, get_variable_trace_string
  from pytensor.link.basic import Container
  from pytensor.link.utils import raise_with_op
label_desc: Use image node_id 7 for calling the Function obj's underlying member method code with example usage: obj.__call__() and returns: None, outputs, outputs, unknown, dict, unknown