class_docstr: Class that satisfies the `Linker` interface by acting as a `VM` factory.
  
  Parameters
  ----------
  allow_gc
      Force the virtual machine to clean up unnecessary references, in order
      to allow garbage collection on intermediate values during computation
      of a function.  If ``None``, use as default the value of the PyTensor
      flag `allow_gc`.
  use_cloop
      Use the C-based virtual machine if possible
  callback
      A callable object to call after each call to a thunk within the virtual
      machine.  It will be called with four arguments: ``node``, ``thunk``,
      ``storage_map``, and ``compute_map``.
  callback_input
      A callable object to call on each input to the graph (variables with no
      owner).  This includes constants and shared variables values.  It will
      be called with two arguments: ``var``, ``value``.
  lazy
      Useful only when `use_cloop` is False. When `lazy` is ``None``, use the
      PyTensor flag ``vm__lazy`` value. Then if we have a ``None`` (default) we
      auto detect if lazy evaluation is needed and use the appropriate
      version. If `lazy` is ``True`` or ``False``, we force the version used
      between `Loop` and `Stack`.
  c_thunks
      If ``None`` or ``True``, don't change the default. If ``False``, don't
      compile C code for the thunks.
  allow_partial_eval
      If ``True``, enforces usage of `Stack` or `CVM`, to allow for partial
      evaluation of functions (calculating a subset of outputs).
class_docstr_tok: ["Class","that","satisfies","the","`","Linker","`","interface","by","acting","as","a","`","VM","`","factory",".","Parameters","--","--","--","--","--","allow_gc","Force","the","virtual","machine","to","clean","up","unnecessary","references",",","in","order","to","allow","garbage","collection","on","intermediate","values","during","computation","of","a","function",".","If","``","None","``",",","use","as","default","the","value","of","the","PyTensor","flag","`","allow_gc","`",".","use_cloop","Use","the","C-based","virtual","machine","if","possible","callback","A","callable","object","to","call","after","each","call","to","a","thunk","within","the","virtual","machine",".","It","will","be","called","with","four","arguments",":","``","node","``",",","``","thunk","``",",","``","storage_map","``",",","and","``","compute_map","``",".","callback_input","A","callable","object","to","call","on","each","input","to","the","graph","(","variables","with","no","owner",")",".","This","includes","constants","and","shared","variables","values",".","It","will","be","called","with","two","arguments",":","``","var","``",",","``","value","``",".","lazy","Useful","only","when","`","use_cloop","`","is","False",".","When","`","lazy","`","is","``","None","``",",","use","the","PyTensor","flag","``","vm__lazy","``","value",".","Then","if","we","have","a","``","None","``","(","default",")","we","auto","detect","if","lazy","evaluation","is","needed","and","use","the","appropriate","version",".","If","`","lazy","`","is","``","True","``","or","``","False","``",",","we","force","the","version","used","between","`","Loop","`","and","`","Stack","`",".","c_thunks","If","``","None","``","or","``","True","``",",","do","n't","change","the","default",".","If","``","False","``",",","don't","compile","C","code","for","the","thunks",".","allow_partial_eval","If","``","True","``",",","enforces","usage","of","`","Stack","`","or","`","CVM","`",",","to","allow","for","partial","evaluation","of","functions","(","calculating","a","subset","of","outputs",")","."]
code: def make_vm(
      self,
      nodes,
      thunks,
      input_storage,
      output_storage,
      storage_map,
      post_thunk_clear,
      computed,
      compute_map,
      updated_vars,
  ):
      pre_call_clear = [storage_map[v] for v in self.no_recycling]
  
      try:
          from pytensor.link.c.cvm import CVM
      except (MissingGXX, ImportError):
          CVM = None
  
      if (
          self.callback is not None
          or self.callback_input is not None
          or (
              (config.profile or config.print_global_stats)
              and config.profile_memory
          )
          or (self.allow_partial_eval and not self.use_cloop)
      ):
          if self.use_cloop and (
              self.callback is not None
              or self.callback_input is not None
          ):
              warnings.warn(
                  "CVM does not support callback, using Stack VM."
              )
          if self.use_cloop and config.profile_memory:
              warnings.warn(
                  "CVM does not support memory profiling, using Stack VM."
              )
          if not self.use_cloop and self.allow_partial_eval:
              warnings.warn(
                  "Loop VM does not support partial evaluation, using Stack VM."
              )
          # Needed for allow_gc=True, profiling and storage_map reuse
          deps = self.compute_gc_dependencies(storage_map)
          vm = Stack(
              self.fgraph,
              nodes,
              thunks,
              pre_call_clear,
              storage_map,
              input_storage,
              output_storage,
              updated_vars,
              compute_map,
              self.allow_gc,
              dependencies=deps,
              callback=self.callback,
              callback_input=self.callback_input,
          )
      elif self.use_cloop and CVM is not None:
          # create a map from nodes to ints and vars to ints
          nodes_idx = {}
          vars_idx = {}
          for i, node in enumerate(nodes):
              nodes_idx[node] = i
              for v in node.inputs + node.outputs:
                  vars_idx.setdefault(v, len(vars_idx))
          for v in self.fgraph.inputs + self.fgraph.outputs:
              vars_idx.setdefault(v, len(vars_idx))
  
          nodes_idx_inv = {}
          vars_idx_inv = {}
          for node, i in nodes_idx.items():
              nodes_idx_inv[i] = node
          for var, i in vars_idx.items():
              vars_idx_inv[i] = var
  
          # put storage_map and compute_map into a int-based scheme
          storage_map_list = [
              storage_map[vars_idx_inv[i]]
              for i in range(len(vars_idx_inv))
          ]
          compute_map_list = [
              compute_map[vars_idx_inv[i]]
              for i in range(len(vars_idx_inv))
          ]
          if nodes:
              assert isinstance(storage_map_list[0], list)
              assert isinstance(compute_map_list[0], list)
  
          # Needed for allow_gc=True, profiling and storage_map reuse
          dependency_map = self.compute_gc_dependencies(storage_map)
          dependency_map_list = [
              [vars_idx[d] for d in dependency_map[vars_idx_inv[i]]]
              for i in range(len(vars_idx_inv))
          ]
  
          # build the pointers to node inputs and offsets
          base_input_output_list = []
          node_n_inputs = []
          node_n_outputs = []
          node_input_offset = []
          node_output_offset = []
          for node in nodes:
              inputs_idx = [vars_idx[v] for v in node.inputs]
              outputs_idx = [vars_idx[v] for v in node.outputs]
              node_n_inputs.append(len(inputs_idx))
              node_n_outputs.append(len(outputs_idx))
              node_input_offset.append(len(base_input_output_list))
              base_input_output_list.extend(inputs_idx)
              node_output_offset.append(len(base_input_output_list))
              base_input_output_list.extend(outputs_idx)
  
          # build the var owner array
          var_owner = [None] * len(vars_idx)
          for var, i in vars_idx.items():
              if var.owner:
                  var_owner[i] = nodes_idx[var.owner]
  
          is_lazy_list = [int(th.lazy) for th in thunks]
          output_vars = [vars_idx[v] for v in self.fgraph.outputs]
  
          # builds the list of prereqs induced by e.g. destroy_handler
          ords = self.fgraph.orderings()
          node_prereqs = []
          node_output_size = []
          for i, node in enumerate(nodes):
              node_output_size.append(0)
              prereq_var_idxs = []
              for prereq_node in ords.get(node, []):
                  prereq_var_idxs.extend(
                      [vars_idx[v] for v in prereq_node.outputs]
                  )
              prereq_var_idxs = list(set(prereq_var_idxs))
              prereq_var_idxs.sort()  # TODO: why sort?
              node_prereqs.append(prereq_var_idxs)
  
          # This is essentially a version of `self.fgraph.update_mapping`.
          # It specifies the outputs-to-inputs updates via the pairs
          # `(input_idx, output_idx)` (i.e. the input at index `input_idx`
          # takes the value of the output at index `output_idx`).
          update_storage = tuple(
              (vars_idx[in_var], self.fgraph.outputs.index(out_var))
              for in_var, out_var in updated_vars.items()
          )
  
          # PyPy has no sys.getrefcount, so ignore this check if not running
          # under CPython.
          if platform.python_implementation() == "CPython":
              c0 = sys.getrefcount(node_n_inputs)
  
          vm = CVM(
              self.fgraph,
              nodes,
              thunks,
              pre_call_clear,
              allow_gc=self.allow_gc,
              call_counts=[0] * len(nodes),
              call_times=[0.0] * len(nodes),
              compute_map_list=compute_map_list,
              storage_map_list=storage_map_list,
              base_input_output_list=base_input_output_list,
              node_n_inputs=node_n_inputs,
              node_n_outputs=node_n_outputs,
              node_input_offset=node_input_offset,
              node_output_offset=node_output_offset,
              var_owner=var_owner,
              is_lazy_list=is_lazy_list,
              output_vars=output_vars,
              node_prereqs=node_prereqs,
              node_output_size=node_output_size,
              update_storage=update_storage,
              dependencies=dependency_map_list,
          )
  
          if (
              platform.python_implementation() == "CPython"
              and c0 != sys.getrefcount(node_n_inputs)
          ):
              warnings.warn(
                  "Detected reference count inconsistency after CVM construction"
              )
      else:
          lazy = self.lazy
          if lazy is None:
              lazy = config.vm__lazy
          if lazy is None:
              lazy = any(th.lazy for th in thunks)
          if not lazy:
              # there is no conditional in the graph
              vm = Loop(
                  self.fgraph,
                  nodes,
                  thunks,
                  pre_call_clear,
                  storage_map,
                  input_storage,
                  output_storage,
                  updated_vars,
                  post_thunk_clear if self.allow_gc else None,
              )
          else:
              # Needed when allow_gc=True and profiling
              deps = self.compute_gc_dependencies(storage_map)
              vm = Stack(
                  self.fgraph,
                  nodes,
                  thunks,
                  pre_call_clear,
                  storage_map,
                  input_storage,
                  output_storage,
                  updated_vars,
                  compute_map,
                  self.allow_gc,
                  dependencies=deps,
              )
      return vm
  
imports: import platform
  import sys
  import time
  import warnings
  from abc import ABC, abstractmethod
  from collections import defaultdict
  from collections.abc import Sequence
  from itertools import zip_longest
  from typing import TYPE_CHECKING, Any, Optional
  from pytensor.configdefaults import config
  from pytensor.graph.basic import Apply, Constant, Variable
  from pytensor.link.basic import Container, LocalLinker
  from pytensor.link.c.exceptions import MissingGXX
  from pytensor.link.utils import gc_helper, get_destroy_dependencies, map_storage, raise_with_op
label_desc: Use image node_id 6 for calling the VMLinker obj's underlying member method code with example usage: obj.make_vm(nodes, thunks, input_storage, output_storage, storage_map, post_thunk_clear, computed, compute_map, updated_vars) and returns: vm