Removes the loop residues identified by the residues in loops_segments and all sidechains with heavy atoms
           within 10A of any heavy atom of the loop.
           If keep_Ca_buttress_atoms is set then the N and Ca backbone atoms of the first loop residue are kept and the
             Ca and C backbone atoms of the last loop residue are kept. This is a useful option to use for Rosetta as
             discarding those atoms can negatively affect some of the loop modeling protocols.

def prune_structure_according_to_loop_definitions(self, loops_segments, search_radius, expected_min_loop_length = None, expected_max_loop_length = None, generate_pymol_session = True, check_sequence = False, keep_Ca_buttress_atoms = True):
        '''Removes the loop residues identified by the residues in loops_segments and all sidechains with heavy atoms
           within 10A of any heavy atom of the loop.
           If keep_Ca_buttress_atoms is set then the N and Ca backbone atoms of the first loop residue are kept and the
             Ca and C backbone atoms of the last loop residue are kept. This is a useful option to use for Rosetta as
             discarding those atoms can negatively affect some of the loop modeling protocols.
        '''

        # Extract the list of Residues defined in the loops definitions
        loop_residues = []
        loop_N_terminii = []
        loop_C_terminii = []
        parsed_sequences = []
        for loop in loops_segments:

            # Identify the Residues corresponding to the loops file definition
            start_id = loop.StartResidueID
            end_id = loop.EndResidueID
            chain_id = loop.Chain
            start_residue, end_residue = None, None

            try:
                start_residue = self.residues[chain_id][loop.StartResidueID]
                end_residue = self.residues[chain_id][loop.EndResidueID]
            except Exception, e:
                raise Exception('Could not find the start or end residue in the chain.')

            if start_residue in loop_residues or end_residue in loop_residues:
                raise Exception('Error: The loops segments overlap.')

            # We assume that the loops file identifies a range in document order
            loop_residue_ids = []
            loop_start = False
            for l in self.indexed_lines:
                if l[0] == 'ATOM' or l[0] == 'HETATM':
                    atom_residue = l[3].residue
                    if not loop_start:
                        if atom_residue == start_residue:
                            loop_start = True
                            if atom_residue not in loop_residues:
                                if atom_residue.residue_id not in loop_residue_ids:
                                    loop_residue_ids.append(atom_residue.residue_id)
                                loop_residues.append(atom_residue)
                    else:
                        if atom_residue not in loop_residues:
                            if atom_residue.residue_id not in loop_residue_ids:
                                loop_residue_ids.append(atom_residue.residue_id)
                            loop_residues.append(atom_residue)
                        if atom_residue == end_residue:
                            break

            parsed_sequence = ''.join([loop_residue.get_amino_acid_code() for loop_residue in loop_residues])
            if check_sequence:
                if not parsed_sequence == loop.Sequence:
                    raise Exception('Expected to find sequence {0} but found sequence {1}.'.format(loop.Sequence, parsed_sequence))
            parsed_sequences.append((parsed_sequence, ';'.join([chain_id + lrid.strip() for lrid in loop_residue_ids])))

            # These parameters currently only makes sense for a single loop
            if expected_min_loop_length != None and ((expected_min_loop_length > len(loop_residues)) or (expected_min_loop_length > len(loop_residue_ids))):
                raise Exception('Expected to identify at least {0} residues but {1} were identified.'.format(expected_min_loop_length, len(loop_residues)))
            if expected_max_loop_length != None and ((expected_max_loop_length < len(loop_residues)) or (expected_max_loop_length < len(loop_residue_ids))):
                raise Exception('Expected to identify at most {0} residues but {1} were identified.'.format(expected_max_loop_length, len(loop_residues)))

            # Keep track of the loop terminii
            loop_N_terminii.append(start_residue)
            loop_C_terminii.append(end_residue)

        # Determine the sidechain atoms to be removed
        sidechain_atom_serial_numbers = self.find_sidechain_atoms_within_radius_of_residue_objects(loop_residues, search_radius)

        # Determine the loop residue atoms to be removed
        loop_residues_ids = set()
        loop_atom_serial_numbers = set()
        atoms_serial_numbers_to_keep_in_cutting = set()
        for loop_residue in loop_residues:
            for aatom in loop_residue.get('ATOM'):
                if keep_Ca_buttress_atoms:
                    # Keep the N and CA atoms of the N-terminus side and the CA and C atoms of the C-terminus side
                    if (loop_residue in loop_N_terminii) and (aatom.name == 'N' or aatom.name == 'CA'):
                        atoms_serial_numbers_to_keep_in_cutting.add(aatom.serial_number)
                        continue
                    elif (loop_residue in loop_C_terminii) and (aatom.name == 'CA' or aatom.name == 'C'):
                        atoms_serial_numbers_to_keep_in_cutting.add(aatom.serial_number)
                        continue
                loop_atom_serial_numbers.add(aatom.serial_number)
        assert(len(sidechain_atom_serial_numbers.intersection(loop_atom_serial_numbers)) == 0)

        # Create a FASTA file with the loops' sequences
        FASTA = []
        FASTA_line_length = self.FASTA_line_length
        for x in range(len(parsed_sequences)):
            parsed_sequence = parsed_sequences[x][0]
            FASTA.append('>loop_{0}|Residues {1}'.format(x + 1, parsed_sequences[x][1].strip()))
            for idx in range(0, len(parsed_sequence), FASTA_line_length):
                FASTA.append(parsed_sequence[idx:idx + FASTA_line_length])
        FASTA_file = '\n'.join(FASTA)

        bonsai_pdb_content, cutting_pdb_content, PSE_file, PSE_script = self.prune(loop_atom_serial_numbers, sidechain_atom_serial_numbers, atoms_serial_numbers_to_keep_in_cutting = atoms_serial_numbers_to_keep_in_cutting, generate_pymol_session = generate_pymol_session)
        return bonsai_pdb_content, cutting_pdb_content, PSE_file, PSE_script, FASTA_file