def find_supercell_contacts_by_residue(residue_contacts, atoms, prop_vec,
                    nres, nsymop, rt_mx_matrices, ignore_waters=True):
  supercell_contacts={}
  #skip all solvent ions added that does not belong to any asymmetric unit
  #by calculating maxresidue and skipping residues above that
  maxres=nres*prop_vec[0]*prop_vec[1]*prop_vec[2]*nsymop  
  #loop over each residue in residue_contacts
  for (residue,contacts) in residue_contacts:
    i_resid=int(residue[2])
    i_resname=residue[1]
    if (ignore_waters and i_resname in ["HOH","WAT"]):
      continue
    if i_resid >= maxres: continue  

    # residue_contacts may have multiple entries between two residues due to
    # different atoms of the same residues. Duplicate_test{} is used
    # to filter this so that only one contact between each pair of residues
    # is returned with the shortest atom to atom distance reported. 
    # sc_sym_op is the translation that relates the original supercell to 
    # the supercell where residue_j resides when the contact occurs (remember
    # that the supercell is being treated as a P1 'unit cell'   
    duplicate_test={}
    for contact in contacts:
      sc_sym_op=contact[1]
      j_seq=contact[0]
      j_resid = int(atoms[j_seq].fetch_labels().resid())
      j_resname = atoms[j_seq].fetch_labels().resname
      dist=contact[2]
      if j_resid > maxres: continue
        
      # unit_mx sc_sym_op means the contact is in the original supercell
      # if same asu in same supercell, it's an "intra_asu" contact so
      # ignore
      if is_same_asu(nres,i_resid,j_resid):
        if sc_sym_op.is_unit_mx() :
          continue
      if (ignore_waters and j_resname in ["HOH","WAT"]):
        continue
        
      # populate duplicate_test{} with only one instance of a given 
      # residue pair, keeping the shortest distance value
      if (j_resid, j_resname,sc_sym_op) in duplicate_test.keys():
        if dist < duplicate_test[(j_resid,j_resname,sc_sym_op)]:
          duplicate_test[(j_resid,j_resname,sc_sym_op)]=dist
      else:
        duplicate_test[(j_resid,j_resname,sc_sym_op)]=dist
      
    # for contact residue pair, get residue's number in original asu,
    # symop number to arrive at the residue's asu (this is the ordinal number
    # of the symop from the rt_mx_matrices object (SYMM cards in PDB file)
    # applied to the resiude in the original ASU to bring it to it's current 
    # ASU), and the translation operation to bring it to its current unit cell
    for (j_resid, j_resname,sc_sym_op) in duplicate_test.keys():
      dist=duplicate_test[(j_resid, j_resname,sc_sym_op)]
      i_transop, symop1, i_asymresid = SC_mapping(prop_vec, nres, nsymop,i_resid)
      j_transop, symop2, j_asymresid = SC_mapping(prop_vec, nres, nsymop,j_resid)

      # both residues within the original supercell, so get the translation
      # that relates j's unit_cell to i's unit_cell
      if sc_sym_op.is_unit_mx() : 
        ij_transop=j_transop.multiply(i_transop.inverse())
      #res_j outside the original supercell 
      else:
        # get the translation operation of the supercell where the contact
        # residue would reside relative to the original supercell 
        t=sc_sym_op.t().as_double()
        # augment by the translations inherent in the supercell itself
        t=[t[0]*prop_vec[0],t[1]*prop_vec[1],t[2]*prop_vec[2]]
        # convert back to rt_mx 
        t=[int(i) for i in t]
        from cctbx_sgtbx_ext import rt_mx,tr_vec
        
        # t is the translation vector to go from an asu in the original
        #           supercell to an asu in the supercell where the 
        #           contact would occur 
        # j_transop is the translation from the original unit cell to the
        #           unit cell in the supercell where resid_j is located
        # to get the translation necessary to go from resid_i unit cell to 
        #           resid_j supercell we do:
        # 
        #                 t+j_transop - i_transop
        #
        t=rt_mx(tr_vec(t,tr_den=1))
        j_transop=j_transop.multiply(t)
        ij_transop=j_transop.multiply(i_transop.inverse())


      
####THIS SHOULD WORK BUT DOESNT
      ## get the symmetry operation that relates the asu of resid_j to 
      ## the asu of resid_i:
      ##
      ##             Rij*Ri=Rj
      ##                Rij=Rj*Ri^-1
      ##
      #i_r=rt_mx_matrices[symop1]
      #j_r=rt_mx_matrices[symop2]
      #ij_r=j_r.multiply(i_r.inverse())
      
      ## now add the translation that relates unit cell of resiude_j to 
      ## unit cell of residue_i:
      ##
      ##             sym_op=Rij+transop
      ##
      #sym_op=ij_transop.multiply(ij_r)
      
      ## this symop is relative to the asu of residue_i. Apply the inverse
      ## of the symmetry operation that creates residue_i's asu to obtain
      ## the symop relative to the original asu.
      #transop=i_r.inverse().multiply(sym_op)
####THIS SHOULD WORK BUT DOESNT

###THIS WORKS ON MANY SPACEGROUPS BUT NOT P212121
      # The transop translation is relative to the asu of resid_i. Rotate
      # it by the inverse of the rotation part of the rot/trans matrix
      # used to create the asu of resid_i to obtain the translation relative
      # to the original asu.
      rot=rt_mx_matrices[symop1]
      transop=rot.inverse().r().multiply(ij_transop.t())
      transop=rt_mx(transop)      

      # Obtain the rot/trans matrix that relates the the asu of resid_j to
      # the asu of resid_i.
      i_r=rt_mx_matrices[symop1]
      j_r=rt_mx_matrices[symop2]
      ij_r=j_r.multiply(i_r.inverse())


              
      # Theoretically this rotation should now be rotated by the inverse of 
      # the rotation used to get to asu of resid_i to get the rotation 
      # relative to the original ASU. But this does not work; works without
      # it. Don't know why.
      #TEST
      #~ ij_r=i_r.inverse().multiply(ij_r)
      
      # Add the translation relative to the original ASU to the symmetry op
      # relative to the original ASU
      transop=transop.multiply(ij_r)
###THIS WORKS ON MANY SPACEGROUPS BUT NOT P212121

      #populate supercell_contacts{}. To avoid duplicates chose by highest
      #x trans op, then y then z
      if transop.as_xyz()==transop.inverse().as_xyz():
        if i_asymresid <= j_asymresid:
          i_key=(i_asymresid, i_resname, j_asymresid, j_resname,transop.as_xyz())
        else:
          i_key=(j_asymresid, j_resname, i_asymresid, i_resname,transop.as_xyz())  
      elif transop.t().as_double()[0] > transop.inverse().t().as_double()[0]:
        i_key=(i_asymresid, i_resname, j_asymresid, j_resname,transop.as_xyz())
      elif transop.t().as_double()[0] < transop.inverse().t().as_double()[0]:  
        i_key=(j_asymresid, j_resname, i_asymresid, i_resname,transop.inverse().as_xyz())
      else:
        if transop.t().as_double()[1] > transop.inverse().t().as_double()[1]:
          i_key=(i_asymresid, i_resname, j_asymresid, j_resname,transop.as_xyz())
        elif transop.t().as_double()[1] < transop.inverse().t().as_double()[1]:  
          i_key=(j_asymresid, j_resname, i_asymresid, i_resname,transop.inverse().as_xyz())     
        else:
          if transop.t().as_double()[2] > transop.inverse().t().as_double()[2]:
            i_key=(i_asymresid, i_resname, j_asymresid, j_resname,transop.as_xyz())
          elif transop.t().as_double()[2] < transop.inverse().t().as_double()[2]:  
            i_key=(j_asymresid, j_resname, i_asymresid, i_resname,transop.inverse().as_xyz())     
          else:
            print "Warning: maybe something is wrong. Transop is: "
            print transop.as_xyz()
            import code; code.interact(local=locals())
            sys.exit()

      #~ print i_resid, j_resid, i_asymresid, j_asymresid, transop

      # The residue numbers, names and symmetry operation uniquely identify
      # each contact. Populate supercell_contacts dictionary object. Keys
      # are the cotnact identified. Values are list of distances. 
      if i_key in supercell_contacts.keys():
        supercell_contacts[i_key].append(dist)
      else:
        supercell_contacts[i_key]=[dist]
  return supercell_contacts
def find_supercell_contacts_by_residue(residue_contacts, atoms, prop_vec, nres, nsymop, ignore_waters=True):
    supercell_contacts = {}
    # skip all solvent ions added that does not belong to any asymmetric unit
    # by calculating maxresidue and skipping residues above that
    maxres = nres * prop_vec[0] * prop_vec[1] * prop_vec[2] * nsymop
    for (residue, contacts) in residue_contacts:
        debug = False
        i_resid = int(residue[2])
        i_resname = residue[1]
        if ignore_waters and i_resname in ["HOH", "WAT"]:
            continue
        if i_resid >= maxres:
            continue
        # residue_contacts may have multiple entries for contacts between
        # different atoms of the same residues. Duplicate_test{} is used
        # to filter this so that only one contact is returned with the
        # shortest atom to atom distance reported.
        duplicate_test = {}
        for contact in contacts:
            sym_op = contact[1]
            j_seq = contact[0]
            j_resid = int(atoms[j_seq].fetch_labels().resid())
            j_resname = atoms[j_seq].fetch_labels().resname
            dist = contact[2]
            if j_resid >= maxres:
                continue

            # ~ if i_resname in ["Na+"] and j_resname in ["ASN"]:
            # ~ print i_resname, i_resid
            # ~ print j_resname, j_resid
            # ~ print sym_op
            # ~ debug=True
            # ~ elif i_resname in ["ASN"] and j_resname in ["Na+"]:
            # ~ print i_resname, i_resid
            # ~ print j_resname, j_resid
            # ~ print sym_op
            # ~ debug=True

            # unit_mx sym_op means the contact is in the original supercell
            # if same asu in same supercell, it's an "intra_asu" contact so
            # ignore
            if is_same_asu(nres, i_resid, j_resid):
                print i_resid, j_resid, sym_op
                if sym_op.is_unit_mx():
                    continue
            if ignore_waters and j_resname in ["HOH", "WAT"]:
                continue
            # populate duplicate_test{} with only one instance of a given
            # residue pair, keeping the shortest distance value
            if (j_resid, j_resname, sym_op) in duplicate_test.keys():
                if dist < duplicate_test[(j_resid, j_resname, sym_op)]:
                    duplicate_test[(j_resid, j_resname, sym_op)] = dist
            else:
                duplicate_test[(j_resid, j_resname, sym_op)] = dist

        # ~ if i_resname=="ASN" and i_resid==324:
        # ~ print duplicate_test
        # ~ for key in duplicate_test.keys():
        # ~ print key[2]
        # ~ debug=True

        # for contact residue pair, get residue's number in original asu
        # and symmetry operation relating res_j to res_i
        for (j_resid, j_resname, sym_op) in duplicate_test.keys():
            dist = duplicate_test[(j_resid, j_resname, sym_op)]
            i_transop, symop1, i_asymresid = SC_mapping(prop_vec, nres, nsymop, i_resid)
            j_transop, symop2, j_asymresid = SC_mapping(prop_vec, nres, nsymop, j_resid)
            if debug:
                print i_transop, symop1, i_asymresid
                print j_transop, symop2, j_asymresid
            # both residues within the original supercell
            if sym_op.is_unit_mx():
                transop = j_transop.multiply(i_transop.inverse())
            # res_j outside the original supercell
            else:
                t = sym_op.t().as_double()
                t = [t[0] * prop_vec[0], t[1] * prop_vec[1], t[2] * prop_vec[2]]
                t = [int(i) for i in t]
                from cctbx_sgtbx_ext import rt_mx, tr_vec

                t = rt_mx(tr_vec(t, tr_den=1))
                j_transop, symop2, j_asymresid = SC_mapping(prop_vec, nres, nsymop, j_resid)
                j_transop = j_transop.multiply(t)
                transop = j_transop.multiply(i_transop.inverse())

            ##################################################################
            ###TO DO and TEST!: for multiple asyms in unit cell
            # transop=j.transop.multiply(symop2).multiply(symop1.inverse())
            # where symop1 and symop2 must be turned into an rt_mx first...
            #################################################################

            # populate supercell_contacts{}. To avoid duplicates chose by highest
            # x trans op, then y then z
            if transop.t().as_double()[0] > transop.inverse().t().as_double()[0]:
                i_key = (i_asymresid, i_resname, j_asymresid, j_resname, transop.as_xyz())
            elif transop.t().as_double()[0] < transop.inverse().t().as_double()[0]:
                i_key = (j_asymresid, j_resname, i_asymresid, i_resname, transop.inverse().as_xyz())
            else:
                if transop.t().as_double()[1] > transop.inverse().t().as_double()[1]:
                    i_key = (i_asymresid, i_resname, j_asymresid, j_resname, transop.as_xyz())
                elif transop.t().as_double()[1] < transop.inverse().t().as_double()[1]:
                    i_key = (j_asymresid, j_resname, i_asymresid, i_resname, transop.inverse().as_xyz())
                else:
                    if transop.t().as_double()[2] > transop.inverse().t().as_double()[2]:
                        i_key = (i_asymresid, i_resname, j_asymresid, j_resname, transop.as_xyz())
                    elif transop.t().as_double()[2] < transop.inverse().t().as_double()[2]:
                        i_key = (j_asymresid, j_resname, i_asymresid, i_resname, transop.inverse().as_xyz())
                    else:
                        print "something is wrong. Transop is: "
                        print transop.as_xyz()
                        import code

                        code.interact(local=locals())

            # This was the old way. It would avoid duplicates in the same frame but
            # not between frames (eg. would have (x+1,y,z) iface in one frame
            # and (x-1,y,z) contacts in another.
            # ~ if transop.inverse().as_xyz() in [i[4] for i in supercell_contacts.keys()]:
            # ~ i_key=(j_asymresid, j_resname, i_asymresid, i_resname,transop.inverse().as_xyz())
            # ~ else:
            # ~ i_key=(i_asymresid, i_resname, j_asymresid, j_resname,transop.as_xyz())

            if i_key in supercell_contacts.keys():
                supercell_contacts[i_key].append(dist)
            else:
                supercell_contacts[i_key] = [dist]
    return supercell_contacts