class_docstr: Create `pydot` graph object from PyTensor function.
  
  Parameters
  ----------
  compact : bool
      if True, will remove intermediate variables without name.
  
  Attributes
  ----------
  node_colors : dict
      Color table of node types.
  apply_colors : dict
      Color table of apply nodes.
  shapes : dict
      Shape table of node types.
class_docstr_tok: ["Create","`","pydot","`","graph","object","from","PyTensor","function",".","Parameters","--","--","--","--","--","compact",":","bool","if","True",",","will","remove","intermediate","variables","without","name",".","Attributes","--","--","--","--","--","node_colors",":","dict","Color","table","of","node","types",".","apply_colors",":","dict","Color","table","of","apply","nodes",".","shapes",":","dict","Shape","table","of","node","types","."]
docstr: Create pydot graph from function.
  
  Parameters
  ----------
  fct : pytensor.compile.function.types.Function
      A compiled PyTensor function, variable, apply or a list of variables.
  graph: pydot.Dot
      `pydot` graph to which nodes are added. Creates new one if
      undefined.
  
  Returns
  -------
  pydot.Dot
      Pydot graph of `fct`
docstr_tok: ["Create","pydot","graph","from","function",".","Parameters","--","--","--","--","--","fct",":","pytensor.compile.function.types.Function","A","compiled","PyTensor","function",",","variable",",","apply","or","a","list","of","variables",".","graph",":","pydot.Dot","`","pydot","`","graph","to","which","nodes","are","added",".","Creates","new","one","if","undefined",".","Returns","--","--","--","-","pydot.Dot","Pydot","graph","of","`","fct","`"]
code: def __call__(self, fct, graph=None):
      """Create pydot graph from function.
  
      Parameters
      ----------
      fct : pytensor.compile.function.types.Function
          A compiled PyTensor function, variable, apply or a list of variables.
      graph: pydot.Dot
          `pydot` graph to which nodes are added. Creates new one if
          undefined.
  
      Returns
      -------
      pydot.Dot
          Pydot graph of `fct`
      """
      if graph is None:
          graph = pd.Dot()
  
      self.__nodes = {}
  
      profile = None
  
      if isinstance(fct, Function):
          profile = getattr(fct, "profile", None)
          fgraph = fct.maker.fgraph
      elif isinstance(fct, FunctionGraph):
          fgraph = fct
      else:
          if isinstance(fct, Variable):
              fct = [fct]
          elif isinstance(fct, Apply):
              fct = fct.outputs
          assert isinstance(fct, (list, tuple))
          assert all(isinstance(v, Variable) for v in fct)
          fgraph = FunctionGraph(inputs=graph_inputs(fct), outputs=fct)
  
      outputs = fgraph.outputs
      topo = fgraph.toposort()
      outputs = list(outputs)
  
      # Loop over apply nodes
      for node in topo:
          nparams = {}
          __node_id = self.__node_id(node)
          nparams["name"] = __node_id
          nparams["label"] = apply_label(node)
          nparams["profile"] = apply_profile(fgraph, node, profile)
          nparams["node_type"] = "apply"
          nparams["apply_op"] = nparams["label"]
          nparams["shape"] = self.shapes["apply"]
  
          use_color = None
          for opName, color in self.apply_colors.items():
              if opName in node.op.__class__.__name__:
                  use_color = color
          if use_color:
              nparams["style"] = "filled"
              nparams["fillcolor"] = use_color
              nparams["type"] = "colored"
  
          pd_node = dict_to_pdnode(nparams)
          graph.add_node(pd_node)
  
          # Loop over input nodes
          for id, var in enumerate(node.inputs):
              var_id = self.__node_id(var.owner if var.owner else var)
              if var.owner is None:
                  vparams = {
                      "name": var_id,
                      "label": var_label(var),
                      "node_type": "input",
                  }
                  if isinstance(var, Constant):
                      vparams["node_type"] = "constant_input"
                  elif isinstance(
                      var,
                      pytensor.tensor.sharedvar.TensorSharedVariable,
                  ):
                      vparams["node_type"] = "shared_input"
                  vparams["dtype"] = type_to_str(var.type)
                  vparams["tag"] = var_tag(var)
                  vparams["style"] = "filled"
                  vparams["fillcolor"] = self.node_colors[
                      vparams["node_type"]
                  ]
                  vparams["shape"] = self.shapes["input"]
                  pd_var = dict_to_pdnode(vparams)
                  graph.add_node(pd_var)
  
              edge_params = {}
              if node.op.view_map and id in reduce(
                  list.__add__, node.op.view_map.values(), []
              ):
                  edge_params["color"] = self.node_colors["output"]
              elif node.op.destroy_map and id in reduce(
                  list.__add__, node.op.destroy_map.values(), []
              ):
                  edge_params["color"] = "red"
  
              edge_label = vparams["dtype"]
              if len(node.inputs) > 1:
                  edge_label = str(id) + " " + edge_label
              pdedge = pd.Edge(
                  var_id, __node_id, label=edge_label, **edge_params
              )
              graph.add_edge(pdedge)
  
          # Loop over output nodes
          for id, var in enumerate(node.outputs):
              var_id = self.__node_id(var)
  
              if var in outputs or len(fgraph.clients[var]) == 0:
                  vparams = {
                      "name": var_id,
                      "label": var_label(var),
                      "node_type": "output",
                      "dtype": type_to_str(var.type),
                      "tag": var_tag(var),
                      "style": "filled",
                  }
                  if len(fgraph.clients[var]) == 0:
                      vparams["fillcolor"] = self.node_colors["unused"]
                  else:
                      vparams["fillcolor"] = self.node_colors["output"]
                  vparams["shape"] = self.shapes["output"]
                  pd_var = dict_to_pdnode(vparams)
                  graph.add_node(pd_var)
  
                  graph.add_edge(
                      pd.Edge(__node_id, var_id, label=vparams["dtype"])
                  )
              elif var.name or not self.compact:
                  graph.add_edge(
                      pd.Edge(__node_id, var_id, label=vparams["dtype"])
                  )
  
          # Create sub-graph for OpFromGraph nodes
          if isinstance(node.op, builders.OpFromGraph):
              subgraph = pd.Cluster(__node_id)
              gf = PyDotFormatter()
              # Use different node prefix for sub-graphs
              gf.__node_prefix = __node_id
              gf(node.op.fn, subgraph)
              graph.add_subgraph(subgraph)
              pd_node.get_attributes()["subg"] = subgraph.get_name()
  
              def format_map(m):
                  return str([list(x) for x in m])
  
              # Inputs mapping
              ext_inputs = [self.__node_id(x) for x in node.inputs]
              int_inputs = [
                  gf.__node_id(x) for x in node.op.inner_inputs
              ]
              assert len(ext_inputs) == len(int_inputs)
              h = format_map(zip(ext_inputs, int_inputs))
              pd_node.get_attributes()["subg_map_inputs"] = h
  
              # Outputs mapping
              ext_outputs = [self.__node_id(x) for x in node.outputs]
              int_outputs = [
                  gf.__node_id(x) for x in node.op.inner_outputs
              ]
              assert len(ext_outputs) == len(int_outputs)
              h = format_map(zip(int_outputs, ext_outputs))
              pd_node.get_attributes()["subg_map_outputs"] = h
  
      return graph
  
imports: import os
  from functools import reduce
  import numpy
  import pytensor
  from pytensor.compile import Function, builders
  from pytensor.graph.basic import Apply, Constant, Variable, graph_inputs
  from pytensor.graph.fg import FunctionGraph
  from pytensor.printing import pydot_imported, pydot_imported_msg
label_desc: Use image node_id 4 for calling the PyDotFormatter obj's underlying member method code with example usage: obj.__call__(fct, graph) and returns: graph, str