class_docstr: A "wrapper" layer designed for heterogeneous graph layers. It takes a
  heterogeneous graph layer, such as :class:`deepsnap.hetero_gnn.HeteroSAGEConv`, 
  at the initializing stage. Currently DeepSNAP does not support `parallelize=True`.
  
  .. note::
  
      For more detailed use of :class:`HeteroConv`, see the `examples/node_classification_hetero 
      <https://github.com/snap-stanford/deepsnap/tree/master/examples/node_classification_hetero>`_ 
      folder.
docstr: The forward function for :class:`HeteroConv`.
  
  Args:
      node_features (Dict[str, Tensor]): A dictionary each key is node type and the corresponding
          value is a node feature tensor.
      edge_indices (Dict[str, Tensor]): A dictionary each key is message type and the corresponding
          value is an `edge _ndex` tensor.
      edge_features (Dict[str, Tensor]): A dictionary each key is edge type and the corresponding
          value is an edge feature tensor. The default value is `None`.
docstr_tok: ["The","forward","function","for",":","class",":","`","HeteroConv","`",".","Args",":","node_features","(","Dict","[","str",",","Tensor","]",")",":","A","dictionary","each","key","is","node","type","and","the","corresponding","value","is","a","node","feature","tensor",".","edge_indices","(","Dict","[","str",",","Tensor","]",")",":","A","dictionary","each","key","is","message","type","and","the","corresponding","value","is","an","`","edge","_ndex","`","tensor",".","edge_features","(","Dict","[","str",",","Tensor","]",")",":","A","dictionary","each","key","is","edge","type","and","the","corresponding","value","is","an","edge","feature","tensor",".","The","default","value","is","`","None","`","."]
code: def forward(self, node_features, edge_indices, edge_features=None):
      r"""The forward function for :class:`HeteroConv`.
  
      Args:
          node_features (Dict[str, Tensor]): A dictionary each key is node type and the corresponding
              value is a node feature tensor.
          edge_indices (Dict[str, Tensor]): A dictionary each key is message type and the corresponding
              value is an `edge _ndex` tensor.
          edge_features (Dict[str, Tensor]): A dictionary each key is edge type and the corresponding
              value is an edge feature tensor. The default value is `None`.
      """
      # TODO: graph is not defined
      if self.streams is not None and graph.not_in_gpu():
          raise RuntimeError("Cannot parallelize on non-gpu graphs")
  
      # node embedding computed from each message type
      message_type_emb = {}
      for message_key, message_type in edge_indices.items():
          if message_key not in self.convs:
              continue
          neigh_type, edge_type, self_type = message_key
          node_feature_neigh = node_features[neigh_type]
          node_feature_self = node_features[self_type]
          # TODO: edge_features is not used
          if edge_features is not None:
              edge_feature = edge_features[edge_type]
          edge_index = edge_indices[message_key]
  
          # Perform message passing.
          if self.streams is not None:
              with torch.cuda.stream(self.streams[message_key]):
                  message_type_emb[message_key] = self.convs[
                      message_key
                  ](
                      node_feature_neigh,
                      node_feature_self,
                      edge_index,
                  )
          else:
              message_type_emb[message_key] = self.convs[message_key](
                  node_feature_neigh,
                  node_feature_self,
                  edge_index,
              )
  
      if self.streams is not None:
          torch.cuda.synchronize()
  
      # aggregate node embeddings from different message types into 1 node
      # embedding for each node
      node_emb = {tail: [] for _, _, tail in message_type_emb.keys()}
  
      for (_, _, tail), item in message_type_emb.items():
          node_emb[tail].append(item)
  
      # Aggregate multiple embeddings with the same tail.
      for node_type, embs in node_emb.items():
          if len(embs) == 1:
              node_emb[node_type] = embs[0]
          else:
              node_emb[node_type] = self.aggregate(embs)
  
      return node_emb
  
imports: import collections.abc
  import torch
  import torch_geometric.nn
  import torch_geometric.utils
  import torch.nn
  from torch import Tensor
  from torch_geometric.nn.inits import reset
  from torch_sparse import matmul
  from typing import List, Dict
label_desc: Use image node_id 3 for calling the HeteroConv obj's underlying member method code with example usage: obj.forward(node_features, edge_indices, edge_features) and returns: node_emb