code: def compute_pi_stack(
      mol1,
      mol2,
      pairwise_distances=None,
      dist_cutoff=4.4,
      angle_cutoff=30.0,
  ):
      """Find aromatic rings in both molecules that form pi-pi contacts.
      For each atom in the contact, count number of atoms in the other molecule
      that form this contact.
  
      Pseudocode:
  
      for each aromatic ring in mol1:
          for each aromatic ring in mol2:
              compute distance between centers
              compute angle between normals
          if it counts as parallel pi-pi:
              count interacting atoms
          if it counts as pi-T:
              count interacting atoms
  
      Parameters:
      -----------
      mol1: rdkit.rdchem.Mol
          First molecule.
      mol2: rdkit.rdchem.Mol
          Second molecule.
      pairwise_distances: np.ndarray (optional)
          Array of pairwise interatomic distances (Angstroms)
      dist_cutoff: float
          Distance cutoff. Max allowed distance between the ring center (Angstroms).
      angle_cutoff: float
          Angle cutoff. Max allowed deviation from the ideal angle between rings.
  
      Returns:
      --------
      mol1_pi_t, mol1_pi_parallel, mol2_pi_t, mol2_pi_parallel: dict
          Dictionaries mapping atom indices to number of atoms they interact with.
          Separate dictionary is created for each type of pi stacking (parallel and
          T-shaped) and each molecule (mol1 and mol2).
      """
  
      mol1_pi_parallel = Counter()
      mol1_pi_t = Counter()
      mol2_pi_parallel = Counter()
      mol2_pi_t = Counter()
  
      mol1_aromatic_rings = []
      mol2_aromatic_rings = []
      from rdkit import Chem
  
      for mol, ring_list in (
          (mol1, mol1_aromatic_rings),
          (mol2, mol2_aromatic_rings),
      ):
          aromatic_atoms = {
              atom.GetIdx() for atom in mol.GetAromaticAtoms()
          }
          for ring in Chem.GetSymmSSSR(mol):
              # if ring is aromatic
              if set(ring).issubset(aromatic_atoms):
                  # save its indices, center, and normal
                  ring_center = compute_ring_center(mol, ring)
                  ring_normal = compute_ring_normal(mol, ring)
                  ring_list.append((ring, ring_center, ring_normal))
  
      # remember mol1-mol2 pairs we already counted
      counted_pairs_parallel = set()
      counted_pairs_t = set()
      for (
          prot_ring,
          prot_ring_center,
          prot_ring_normal,
      ) in mol1_aromatic_rings:
          for (
              lig_ring,
              lig_ring_center,
              lig_ring_normal,
          ) in mol2_aromatic_rings:
              if is_pi_parallel(
                  prot_ring_center,
                  prot_ring_normal,
                  lig_ring_center,
                  lig_ring_normal,
                  angle_cutoff=angle_cutoff,
                  dist_cutoff=dist_cutoff,
              ):
                  prot_to_update = set()
                  lig_to_update = set()
                  for prot_atom_idx in prot_ring:
                      for lig_atom_idx in lig_ring:
                          if (
                              prot_atom_idx,
                              lig_atom_idx,
                          ) not in counted_pairs_parallel:
                              # if this pair is new, count atoms forming a contact
                              prot_to_update.add(prot_atom_idx)
                              lig_to_update.add(lig_atom_idx)
                              counted_pairs_parallel.add(
                                  (prot_atom_idx, lig_atom_idx)
                              )
  
                  mol1_pi_parallel.update(prot_to_update)
                  mol2_pi_parallel.update(lig_to_update)
  
              if is_pi_t(
                  prot_ring_center,
                  prot_ring_normal,
                  lig_ring_center,
                  lig_ring_normal,
                  angle_cutoff=angle_cutoff,
                  dist_cutoff=dist_cutoff,
              ):
                  prot_to_update = set()
                  lig_to_update = set()
                  for prot_atom_idx in prot_ring:
                      for lig_atom_idx in lig_ring:
                          if (
                              prot_atom_idx,
                              lig_atom_idx,
                          ) not in counted_pairs_t:
                              # if this pair is new, count atoms forming a contact
                              prot_to_update.add(prot_atom_idx)
                              lig_to_update.add(lig_atom_idx)
                              counted_pairs_t.add(
                                  (prot_atom_idx, lig_atom_idx)
                              )
  
                  mol1_pi_t.update(prot_to_update)
                  mol2_pi_t.update(lig_to_update)
  
      return (mol1_pi_t, mol1_pi_parallel, mol2_pi_t, mol2_pi_parallel)
  
file_path: deepchem/deepchem/utils/noncovalent_utils.py
imports: import numpy
  from collections import Counter
  from deepchem.utils.fragment_utils import get_partial_charge
  from deepchem.utils.rdkit_utils import compute_ring_center
  from deepchem.utils.rdkit_utils import compute_ring_normal
  from deepchem.utils.geometry_utils import angle_between
  from deepchem.utils.geometry_utils import is_angle_within_cutoff
label_desc: Use image node_id 8 for calling a global function with example usage: compute_pi_stack(mol1, mol2, pairwise_distances, dist_cutoff, angle_cutoff) and returns: mol1_pi_t,  mol1_pi_parallel,  mol2_pi_t,  mol2_pi_parallel