docs: def _map_residues(self):
          '''For each pair of equivalence classes, match the residues of a chain in the first class to the residues of appropriate chains in the second class.
  
              Note: we do a lot of repeated work here. Some of the lookups e.g. atom_sequences/seqres_sequences here could be cached.'''
  
          pdbs = self.pdbs
          pdb_names = self.pdb_names
          partition_by_sequence = self.partition_by_sequence
          representative_pdbs = self.representative_pdbs
          representative_ids = sorted(representative_pdbs.keys())
  
          # Map the SEQRES sequences to the ATOM sequences
          # Note: The correct way to do this for RCSB files would be to use the SIFTS information like the ResidueRelatrix
          # does. However, we have to consider the case where users upload PDB files which have not yet been deposited in
          # the PDB so we have to resort to automatic sequence alignments. Ideally, we would store these alignments in a
          # database and then do a lookup at this point. This would not only speed up the computation here but also allow
          # us to manually fix misalignments (which will probably only occur due to gaps rather than mismatches).
          seqres_to_atom_maps = {}
          atom_to_seqres_maps = {}
          for x in range(len(representative_ids)):
              representative_id = representative_ids[x]
              pdb_object = representative_pdbs[representative_id]
              seqres_to_atom_map, atom_to_seqres_map = pdb_object.construct_seqres_to_atom_residue_map()
  
              # todo: I tested the remainder of this class on PDBs with no SEQRES records so any code related to these maps is untested
              #       when these assertions fail, remove them and fix the code below accordingly
  
              seqres_to_atom_maps[representative_id] = seqres_to_atom_map
              atom_to_seqres_maps[representative_id] = atom_to_seqres_map
  
  
          # Iterate over all pairs of representative PDBs and determine the residue mapping and sets of differing ATOM residues
  
          # self.residue_id_mapping maps tuples of representative ids e.g. (0, 1) to residue_id_mapping where
          #            residue_id_mapping is a mapping: 'ATOM' -> chain_1_id -> residue_1_id -> tuple(chain_2_id, residue_2_id)
          # where chain_x_id and residue_x_id are associated to representative_id_x
  
          # self.differing_atom_residue_ids maps tuples of representative ids e.g. (0, 1) to PDB residues IDs which differ between
          # the two representatives
          for x in range(len(representative_ids) - 1):
              for y in range(x + 1, len(representative_ids)):
                  representative_pdb_id_1 = representative_ids[x]
                  representative_pdb_id_2 = representative_ids[y]
                  rpdb_object_1 = representative_pdbs[representative_pdb_id_1]
                  rpdb_object_2 = representative_pdbs[representative_pdb_id_2]
  
                  mapping_key = (representative_pdb_id_1, representative_pdb_id_2)
                  reverse_mapping_key = mapping_key[::-1]
  
                  residue_id_mapping = {'ATOM' : {}, 'SEQRES' : {}} # todo: add the other types of mapping here e.g. FASTA and Rosetta
                  pdb1_differing_atom_residue_ids = []
                  pdb2_differing_atom_residue_ids = []
  
                  for pdb1_chain, pdb2_chains in self.get_representative_chain_mapping(mapping_key[0], mapping_key[1]).iteritems():
                      # e.g. pdb1_chain = 'A', pdb2_chains = ['A', 'E']
  
                      residue_id_mapping['ATOM'][pdb1_chain] = {}
                      residue_id_mapping['SEQRES'][pdb1_chain] = {}
  
                      # Use the SEQRES or ATOM sequence appropriately
                      pdb1_chain_sequence_type, pdb1_chain_sequence = rpdb_object_1.get_annotated_chain_sequence_string(pdb1_chain, self.use_seqres_sequences_if_possible)
  
                      for pdb2_chain in pdb2_chains:
                          # Get the mapping between the sequences
                          # Note: sequences and mappings are 1-based following the UniProt convention
                          # The mapping returned from sa.get_residue_mapping is an abstract mapping between *sequences of characters*
                          # and knows nothing about residue identifiers e.g. ATOM residue IDs or whether the sequences are
                          # SEQRES or ATOM sequences
  
                          sa = SequenceAligner()
                          pdb2_chain_sequence_type, pdb2_chain_sequence = rpdb_object_2.get_annotated_chain_sequence_string(pdb2_chain, self.use_seqres_sequences_if_possible)
  
                          sa.add_sequence('%s_%s' % (representative_pdb_id_1, pdb1_chain), str(pdb1_chain_sequence))
                          sa.add_sequence('%s_%s' % (representative_pdb_id_2, pdb2_chain), str(pdb2_chain_sequence))
                          mapping, match_mapping = sa.get_residue_mapping()
  
                          # Since the mapping is only between sequences and we wish to use the original residue identifiers of
                          # the sequence e.g. the PDB/ATOM residue ID, we look this information up in the order mapping of the
                          # Sequence objects
                          for pdb1_residue_index, pdb2_residue_index in mapping.iteritems():
                              pdb1_residue_id = pdb1_chain_sequence.order[pdb1_residue_index - 1] # order is a 0-based list
                              pdb2_residue_id = pdb2_chain_sequence.order[pdb2_residue_index - 1] # order is a 0-based list
                              pdb1_atom_residue_id, pdb2_atom_residue_id = None, None
  
                              if pdb1_chain_sequence_type == 'SEQRES' and pdb2_chain_sequence_type == 'SEQRES':
                                  residue_id_mapping['SEQRES'][pdb1_chain][pdb1_residue_id] = residue_id_mapping['SEQRES'][pdb1_chain].get(pdb1_residue_id, [])
                                  residue_id_mapping['SEQRES'][pdb1_chain][pdb1_residue_id].append((pdb2_chain, pdb2_residue_id))
  
                                  pdb1_atom_residue_id = seqres_to_atom_maps.get(representative_pdb_id_1, {}).get(pdb1_chain, {}).get(pdb1_residue_id)
                                  pdb2_atom_residue_id = seqres_to_atom_maps.get(representative_pdb_id_2, {}).get(pdb2_chain, {}).get(pdb2_residue_id)
                                  if pdb1_atom_residue_id != None and pdb2_atom_residue_id != None:
                                      residue_id_mapping['ATOM'][pdb1_chain][pdb1_atom_residue_id] = residue_id_mapping['ATOM'][pdb1_chain].get(pdb1_atom_residue_id, [])
                                      residue_id_mapping['ATOM'][pdb1_chain][pdb1_atom_residue_id].append(pdb2_atom_residue_id)
  
                              elif pdb1_chain_sequence_type == 'SEQRES' and pdb2_chain_sequence_type == 'ATOM':
                                  pdb1_atom_residue_id = seqres_to_atom_maps.get(representative_pdb_id_1, {}).get(pdb1_chain, {}).get(pdb1_residue_id)
                                  if pdb1_atom_residue_id != None:
                                      residue_id_mapping['ATOM'][pdb1_chain][pdb1_atom_residue_id] = residue_id_mapping['ATOM'][pdb1_chain].get(pdb1_atom_residue_id, [])
                                      residue_id_mapping['ATOM'][pdb1_chain][pdb1_atom_residue_id].append(pdb2_residue_id)
  
                              elif pdb1_chain_sequence_type == 'ATOM' and pdb2_chain_sequence_type == 'SEQRES':
                                  pdb2_atom_residue_id = seqres_to_atom_maps.get(representative_pdb_id_2, {}).get(pdb2_chain, {}).get(pdb2_residue_id)
                                  if pdb2_atom_residue_id != None:
                                      residue_id_mapping['ATOM'][pdb1_chain][pdb1_residue_id] = residue_id_mapping['ATOM'][pdb1_chain].get(pdb1_residue_id, [])
                                      residue_id_mapping['ATOM'][pdb1_chain][pdb1_residue_id].append(pdb2_atom_residue_id)
  
                              elif pdb1_chain_sequence_type == 'ATOM' and pdb2_chain_sequence_type == 'ATOM':
                                  residue_id_mapping['ATOM'][pdb1_chain][pdb1_residue_id] = residue_id_mapping['ATOM'][pdb1_chain].get(pdb1_residue_id, [])
                                  residue_id_mapping['ATOM'][pdb1_chain][pdb1_residue_id].append(pdb2_residue_id)
                              else:
                                  raise Exception('An exception occurred.') # this should not happen
  
                              # We store a *list* of corresponding residues i.e. if pdb1_chain matches pdb2_chain_1 and pdb2_chain_2
                              # then we may map a residue in pdb1_chain to a residue in each of those chains
                              #residue_id_mapping[pdb1_chain][pdb1_residue_id] = residue_id_mapping[pdb1_chain].get(pdb1_residue_id, [])
                              #residue_id_mapping[pdb1_chain][pdb1_residue_id].append(pdb2_residue_id)
  
                          # Determine which residues of each sequence differ between the sequences
                          # We ignore leading and trailing residues from both sequences
                          pdb1_residue_indices = mapping.keys()
                          pdb2_residue_indices = mapping.values()
                          differing_pdb1_indices = []
                          differing_pdb2_indices = []
                          for pdb1_residue_index, match_details in match_mapping.iteritems():
                              if match_details.clustal == 0 or match_details.clustal == -1 or match_details.clustal == -2:
                                  # The residues differed
                                  differing_pdb1_indices.append(pdb1_residue_index)
                                  differing_pdb2_indices.append(mapping[pdb1_residue_index])
  
                          # Convert the different sequence indices into PDB ATOM residue IDs. Sometimes there may not be a
                          # mapping from SEQRES residues to the ATOM residues e.g. missing density
                          for idx in differing_pdb1_indices:
                              if pdb1_chain_sequence_type == 'SEQRES':
                                  pdb1_seqres_residue_id = pdb1_chain_sequence.order[idx - 1]
                                  pdb1_atom_residue_id = seqres_to_atom_maps.get(representative_pdb_id_1, {}).get(pdb1_chain, {}).get(pdb1_seqres_residue_id)
                                  if pdb1_atom_residue_id != None:
                                      pdb1_differing_atom_residue_ids.append(pdb1_atom_residue_id)
                              elif pdb1_chain_sequence_type == 'ATOM':
                                  pdb1_differing_atom_residue_ids.append(pdb1_chain_sequence.order[idx - 1])
                          for idx in differing_pdb2_indices:
                              if pdb2_chain_sequence_type == 'SEQRES':
                                  pdb2_seqres_residue_id = pdb2_chain_sequence.order[idx - 1]
                                  pdb2_atom_residue_id = seqres_to_atom_maps.get(representative_pdb_id_2, {}).get(pdb2_chain, {}).get(pdb2_seqres_residue_id)
                                  if pdb2_atom_residue_id != None:
                                      pdb2_differing_atom_residue_ids.append(pdb2_atom_residue_id)
                              elif pdb2_chain_sequence_type == 'ATOM':
                                  pdb2_differing_atom_residue_ids.append(pdb2_chain_sequence.order[idx - 1])
  
                  self.residue_id_mapping[mapping_key] = residue_id_mapping
                  self.differing_atom_residue_ids[mapping_key] = pdb1_differing_atom_residue_ids
                  self.differing_atom_residue_ids[reverse_mapping_key] = pdb2_differing_atom_residue_ids
  
          for k, v in sorted(self.differing_atom_residue_ids.iteritems()):
              self.differing_atom_residue_ids[k] = sorted(set(v)) # the list of residues may not be unique in general so we make it unique here
  
          self.seqres_to_atom_maps = seqres_to_atom_maps
          self.atom_to_seqres_maps = atom_to_seqres_maps