docs: def generate_all_paired_mutations_for_position(self, chain_ids, chain_sequence_mappings = {}, residue_ids_to_ignore = [], typed_residue_ids_to_ignore = [], silent = True):
          '''Generates a set of mutations for the chains in chain_ids where each set corresponds to the "same" residue (see
             below) in both chains and where the wildtype residues match.
               e.g. if chain A and B both have K19 then the set of mutations K19A, ... K19I, K19L, K19Y will be included in
                    in the returned results unless 19 is in residue_ids_to_ignore or typed_residue_ids_to_ignore.
             residue_ids_to_ignore should be a list/set of residue IDs.
             typed_residue_ids_to_ignore should be a dict residue ID -> residue AA. It is used similarly to residue_ids_to_ignore
             but we also assert that the residue types match the sequences in the chains.
  
             By default, "same residue" is inferred by residue ID i.e. the generation assumes that a residue with some ID
             in one chain corresponds to the residue with the same ID in another chain. If this is not true then a mapping
             between chain residues is necessary and should be provided using the chain_sequence_mappings parameter.
             chain_sequence_mappings should be a dict from pairs of chain IDs to SequenceMap objects. As all sequences are
             compared with the first chain in chain_ids, only mappings from that first chain to any other chain are used.
  
             This function is useful in certain cases e.g. generating a set of mutations where we make the same mutation in
             both chains of a homodimer or a quasi-homodimer (where we only mutate the positions which agree).
             '''
  
          residue_ids_to_ignore = set([str(r).strip() for r in residue_ids_to_ignore])
          for k, v in typed_residue_ids_to_ignore.iteritems():
              typed_residue_ids_to_ignore[k] = v.strip()
  
          assert(len(chain_ids) > 0)
          first_chain = chain_ids[0]
          mutations = []
          if sorted(set(self.atom_sequences.keys()).intersection(set(chain_ids))) == sorted(chain_ids):
              aas = sorted(residue_type_3to1_map.values())
              aas.remove('X')
              sequence = self.atom_sequences[first_chain]
              for res_id in sequence.order:
                  chain_res_ids = {}
                  for c in chain_ids:
                      chain_res_ids[c] = c + res_id[1:]
                      if c != first_chain and chain_sequence_mappings.get((first_chain, c)):
                          chain_res_ids[c] = chain_sequence_mappings[(first_chain, c)][res_id]
  
                  sres_id = str(res_id)[1:].strip()
                  skip = sres_id in residue_ids_to_ignore
  
                  if not skip and sres_id in typed_residue_ids_to_ignore:
                      for c in chain_ids:
                          if chain_res_ids[c] in self.atom_sequences[c].sequence:
                              if not typed_residue_ids_to_ignore[sres_id] == self.atom_sequences[c][chain_res_ids[c]].ResidueAA:
                                  raise Exception('Expected to find {0} at residue {1} but found {2} in chain {3} at this position.'.format(typed_residue_ids_to_ignore[sres_id], sres_id, self.atom_sequences[c][chain_res_id].ResidueAA, c))
                              skip = True
                  if skip:
                      if not silent:
                          print('Skipping residue {0} as requested.'.format(res_id))
                      continue
  
                  for c in chain_ids:
                      if (chain_res_ids[c]) not in self.atom_sequences[c].sequence:
                          if not silent:
                              print('Skipping residue {0} as it is missing from chain {1}.'.format(res_id, c))
                              skip = True
                  if skip:
                      continue
  
                  chain_res_aas = set([self.atom_sequences[c][chain_res_ids[c]].ResidueAA for c in chain_ids if chain_res_ids[c] in self.atom_sequences[c].sequence])
                  if len(chain_res_aas) > 1:
                      if not silent:
                          colortext.warning('Skipping residue {0} as the amino acid type differs between the specified chains.'.format(res_id))
                      continue
                  wt_aa = chain_res_aas.pop()
                  for mut_aa in aas:
                      if mut_aa != wt_aa:
                          mutations.append([ChainMutation(wt_aa, str(chain_res_ids[c])[1:].strip(), mut_aa, Chain = c) for c in chain_ids])
              return mutations
          else:
              raise Exception('Chain(s) {0} could not be found in the PDB file.'.format(', '.join(sorted(set(chain_ids).difference(set(self.atom_sequences.keys()))))))