code: def insert_deepcopy(fgraph, wrapped_inputs, wrapped_outputs):
      """Insert deepcopy in the fgraph to break aliasing of outputs.
  
      This loop was inserted to remove aliasing between outputs when they all
      evaluate to the same value. Originally it was OK for outputs to be aliased,
      but some of the outputs can be shared variables, and is not good for shared
      variables to be aliased. It might be possible to rewrite this by making
      sure there is no aliasing only between shared variables.
  
      If some outputs are constant, we add deep copy to respect the memory
      contract
  
      We don't insert deep copy when :attr:`SymbolicOutput.borrow` is ``True``
      for all concerned outputs.
      """
  
      assert len(wrapped_inputs) == len(fgraph.inputs)
      assert len(wrapped_outputs) == len(fgraph.outputs)
      reason = "insert_deepcopy"
      updated_fgraph_inputs = {
          fgraph_i
          for i, fgraph_i in zip(wrapped_inputs, fgraph.inputs)
          if getattr(i, "update", False)
      }
  
      # We can't use fgraph.inputs as this don't include Constant Value.
      all_graph_inputs = list(graph_inputs(fgraph.outputs))
      has_destroyers_attr = hasattr(fgraph, "has_destroyers")
  
      for i in range(len(fgraph.outputs)):
          views_of_output_i = set()
          view_tree_set(
              fgraph, alias_root(fgraph.outputs[i]), views_of_output_i
          )
          copied = False
          # do not allow outputs to be aliased
          for j in range(i + 1, len(fgraph.outputs)):
              # We could don't put deep copy if both outputs have borrow==True
              # and not(wrapped_outputs[i].borrow and wrapped_outputs[j].borrow):
              if fgraph.outputs[j] in views_of_output_i:
                  if (
                      wrapped_outputs[i].borrow
                      and wrapped_outputs[j].borrow
                  ):
                      fgraph.change_node_input(
                          "output",
                          i,
                          view_op(fgraph.outputs[i]),
                          reason=reason,
                      )
                  else:
                      fgraph.change_node_input(
                          "output",
                          i,
                          deep_copy_op(fgraph.outputs[i]),
                          reason=reason,
                      )
                  copied = True
                  break
  
          if not copied:
              for input_j in all_graph_inputs:
                  # do not allow outputs to be aliased to an inputs (j), unless
                  # a) that j'th input has been 'destroyed' by
                  #    e.g. in-place computations
                  # b) that j'th input is a shared variable that is also
                  #    being updated
                  if input_j in updated_fgraph_inputs:
                      continue
                  if input_j in views_of_output_i and not (
                      has_destroyers_attr
                      and fgraph.has_destroyers([input_j])
                  ):
                      # We don't put deep_copy_op if the input and the
                      # output have borrow==True
                      if input_j in fgraph.inputs:
                          j = fgraph.inputs.index(input_j)
                          if (
                              wrapped_outputs[i].borrow
                              and wrapped_inputs[j].borrow
                          ):
                              fgraph.change_node_input(
                                  "output",
                                  i,
                                  view_op(fgraph.outputs[i]),
                                  reason=reason,
                              )
                              break
                          else:
                              fgraph.change_node_input(
                                  "output",
                                  i,
                                  deep_copy_op(fgraph.outputs[i]),
                                  reason=reason,
                              )
                              break
                      elif wrapped_outputs[i].borrow:
                          fgraph.change_node_input(
                              "output",
                              i,
                              view_op(fgraph.outputs[i]),
                              reason=reason,
                          )
                          break
                      else:
                          fgraph.change_node_input(
                              "output",
                              i,
                              deep_copy_op(fgraph.outputs[i]),
                              reason=reason,
                          )
                          break
  
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 8 for calling a global function with example usage: insert_deepcopy(fgraph, wrapped_inputs, wrapped_outputs) without return types