def get_chain_mutations(self, pdb_id_1, chain_1, pdb_id_2, chain_2):
        '''Returns a list of tuples each containing a SEQRES Mutation object and an ATOM Mutation object representing the
           mutations from pdb_id_1, chain_1 to pdb_id_2, chain_2.
           SequenceMaps are constructed in this function between the chains based on the alignment.
           PDBMutationPair are returned as they are hashable and amenable to Set construction to eliminate duplicates.
           '''
        p1 = self.add_pdb(pdb_id_1)
        p2 = self.add_pdb(pdb_id_2)
        sifts_1, pdb_1 = p1['sifts'], p1['pdb']
        sifts_2, pdb_2 = p2['sifts'], p2['pdb']
        seqres_to_atom_sequence_maps_1 = sifts_1.seqres_to_atom_sequence_maps.get(chain_1, {})
        seqres_1, atom_1 = pdb_1.seqres_sequences.get(chain_1), pdb_1.atom_sequences.get(chain_1)
        seqres_2, atom_2 = pdb_2.seqres_sequences.get(chain_2), pdb_2.atom_sequences.get(chain_2)
        if not seqres_1: raise Exception('No SEQRES sequence for chain {0} of {1}.'.format(chain_1, pdb_1))
        if not atom_1: raise Exception('No ATOM sequence for chain {0} of {1}.'.format(chain_1, pdb_1))
        if not seqres_2: raise Exception('No SEQRES sequence for chain {0} of {1}.'.format(chain_2, pdb_2))
        if not atom_2: raise Exception('No ATOM sequence for chain {0} of {1}.'.format(chain_2, pdb_2))
        seqres_str_1 = str(seqres_1)
        seqres_str_2 = str(seqres_2)
        sa = SequenceAligner()
        sa.add_sequence('{0}_{1}'.format(pdb_id_1, chain_1), seqres_str_1)
        sa.add_sequence('{0}_{1}'.format(pdb_id_2, chain_2), seqres_str_2)
        sa.align()
        seqres_residue_mapping, seqres_match_mapping = sa.get_residue_mapping()
        seqres_sequence_map = SequenceMap()
        assert(sorted(seqres_residue_mapping.keys()) == sorted(seqres_match_mapping.keys()))
        for k, v in seqres_residue_mapping.iteritems():
            seqres_sequence_map.add(k, v, seqres_match_mapping[k])
        self.seqres_sequence_maps[(pdb_id_1, chain_1)][(pdb_id_2, chain_2)] = seqres_sequence_map
        mutations = []
        clustal_symbols = SubstitutionScore.clustal_symbols
        for seqres_res_id, v in seqres_match_mapping.iteritems():
            if clustal_symbols[v.clustal] != '*':
                seqres_wt_residue = seqres_1[seqres_res_id]
                seqres_mutant_residue = seqres_2[seqres_residue_mapping[seqres_res_id]]
                atom_res_id = None
                atom_chain_res_id = seqres_to_atom_sequence_maps_1.get(seqres_res_id)
                try:
                    if atom_chain_res_id:
                        assert(atom_chain_res_id[0] == chain_1)
                        atom_residue = atom_1[atom_chain_res_id]
                        atom_res_id = atom_chain_res_id[1:]
                        assert(atom_residue.ResidueAA == seqres_wt_residue.ResidueAA)
                        assert(atom_residue.ResidueID == atom_res_id)
                except:
                    atom_res_id = None
                    if seqres_wt_residue.ResidueAA != 'X':
                        raise
                seqres_mutation = ChainMutation(seqres_wt_residue.ResidueAA, seqres_res_id,seqres_mutant_residue.ResidueAA, Chain = chain_1)
                atom_mutation = None
                if atom_res_id:
                    atom_mutation = ChainMutation(seqres_wt_residue.ResidueAA, atom_res_id, seqres_mutant_residue.ResidueAA, Chain = chain_1)
                mutations.append(PDBMutationPair(seqres_mutation, atom_mutation))
        return mutations