class_docstr: This class implements the featurization to implement Duvenaud graph convolutions.
  
  Duvenaud graph convolutions [1]_ construct a vector of descriptors for each
  atom in a molecule. The featurizer computes that vector of local descriptors.
  
  Examples
  ---------
  >>> import deepchem as dc
  >>> smiles = ["C", "CCC"]
  >>> featurizer=dc.feat.ConvMolFeaturizer(per_atom_fragmentation=False)
  >>> f = featurizer.featurize(smiles)
  >>> # Using ConvMolFeaturizer to create featurized fragments derived from molecules of interest.
  ... # This is used only in the context of performing interpretation of models using atomic
  ... # contributions (atom-based model interpretation)
  ... smiles = ["C", "CCC"]
  >>> featurizer=dc.feat.ConvMolFeaturizer(per_atom_fragmentation=True)
  >>> f = featurizer.featurize(smiles)
  >>> len(f) # contains 2 lists with  featurized fragments from 2 mols
  2
  
  See Also
  --------
  Detailed examples of `GraphConvModel` interpretation are provided in Tutorial #28
  
  References
  ---------
  
  .. [1] Duvenaud, David K., et al. "Convolutional networks on graphs for
      learning molecular fingerprints." Advances in neural information
      processing systems. 2015.
  
  Note
  ----
  This class requires RDKit to be installed.
docstr: Encodes mol as a ConvMol object.
  If per_atom_fragmentation is True,
  then for each molecule a list of ConvMolObjects
  will be created
docstr_tok: ["Encodes","mol","as","a","ConvMol","object",".","If","per_atom_fragmentation","is","True",",","then","for","each","molecule","a","list","of","ConvMolObjects","will","be","created"]
code: def _featurize(self, mol):
      """Encodes mol as a ConvMol object.
      If per_atom_fragmentation is True,
      then for each molecule a list of ConvMolObjects
      will be created"""
  
      def per_atom(n, a):
          """
          Enumerates fragments resulting from mol object,
          s.t. each fragment = mol with single atom removed (all possible removals are enumerated)
          Goes over nodes, deletes one at a time and updates adjacency list of lists (removes connections to that node)
  
          Parameters
          ----------
          n: np.array of nodes (number_of_nodes X number_of_features)
          a: list of nested lists of adjacent node pairs
  
          """
          for i in range(n.shape[0]):
              new_n = np.delete(n, (i), axis=0)
              new_a = []
              for j, node_pair in enumerate(a):
                  if (
                      i != j
                  ):  # don't need this pair, no more connections to deleted node
                      tmp_node_pair = []
                      for v in node_pair:
                          if v < i:
                              tmp_node_pair.append(v)
                          elif v > i:
                              tmp_node_pair.append(
                                  v - 1
                              )  # renumber node, because of offset after node deletion
                      new_a.append(tmp_node_pair)
              yield new_n, new_a
  
      # Get the node features
      idx_nodes = [
          (
              a.GetIdx(),
              np.concatenate(
                  (
                      atom_features(
                          a, use_chirality=self.use_chirality
                      ),
                      self._get_atom_properties(a),
                  )
              ),
          )
          for a in mol.GetAtoms()
      ]
  
      idx_nodes.sort()  # Sort by ind to ensure same order as rd_kit
      idx, nodes = list(zip(*idx_nodes))
  
      # Stack nodes into an array
      nodes = np.vstack(nodes)
      if self.master_atom:
          master_atom_features = np.expand_dims(
              np.mean(nodes, axis=0), axis=0
          )
          nodes = np.concatenate([nodes, master_atom_features], axis=0)
  
      # Get bond lists with reverse edges included
      edge_list = [
          (b.GetBeginAtomIdx(), b.GetEndAtomIdx())
          for b in mol.GetBonds()
      ]
      # Get canonical adjacency list
      canon_adj_list = [[] for mol_id in range(len(nodes))]
      for edge in edge_list:
          canon_adj_list[edge[0]].append(edge[1])
          canon_adj_list[edge[1]].append(edge[0])
  
      if self.master_atom:
          fake_atom_index = len(nodes) - 1
          for index in range(len(nodes) - 1):
              canon_adj_list[index].append(fake_atom_index)
  
      if not self.per_atom_fragmentation:
          return ConvMol(nodes, canon_adj_list)
      else:
          return [
              ConvMol(n, a) for n, a in per_atom(nodes, canon_adj_list)
          ]
  
imports: import numpy
  import deepchem
  from deepchem.feat.base_classes import MolecularFeaturizer
  from deepchem.feat.mol_graphs import ConvMol, WeaveMol
  from deepchem.data import DiskDataset
  import logging
  from typing import Optional, List, Tuple, Union, Iterable
  from deepchem.utils.typing import RDKitMol, RDKitAtom
  from deepchem.utils.molecule_feature_utils import one_hot_encode
label_desc: Use image node_id 4 for calling the ConvMolFeaturizer obj's underlying member method code with example usage: obj._featurize(mol) and returns: ConvMol, unknown