def print_stats(self, prefix='') -> None:
        """
        Prints statistics to stdout

        Args:
            prefix: Text prefix to prepend to printed data
        """
        stats = self.get_stats()
        self.print_model_stats(prefix)
        self.print_chain_stats(prefix)

        print('{} Num. residues:  {}'.format(prefix, stats['num_res']))
        print('{} Num. residues with ins. codes:  {}'.format(
            prefix, stats['res_insc']))
        print('{} Num. HETATM residues:  {}'.format(prefix,
                                                    stats['res_hetats']))
        print('{} Num. ligands or modified residues:  {}'.format(
            prefix, stats['res_ligands']))
        print('{} Num. water mol.:  {}'.format(prefix, stats['num_wat']))
        print('{} Num. atoms:  {}'.format(prefix, stats['num_ats']))
        if stats['ca_only']:
            print('Possible CA-Only structure')
        if self.hetatm[mu.MODRES]:
            print('Modified residues found')
            for res in self.hetatm[mu.MODRES]:
                print(mu.residue_id(res))
        if self.hetatm[mu.METAL]:
            print('Metal/Ion residues found')
            for res in self.hetatm[mu.METAL]:
                print(mu.residue_id(res))
        if self.hetatm[mu.ORGANIC]:
            print('Small mol ligands found')
            for res in self.hetatm[mu.ORGANIC]:
                print(mu.residue_id(res))
    def add_hydrogens(self, ion_res_list, remove_h: bool = True):
        """
        Add hydrogens considering selections in ion_res_list

        Args:
           **r_at_list**: dict as Bio.PDB.Residue: Tauromeric Option
           **remove_h**: Remove Hydrogen atom before adding new ones
        """
        add_h_rules = self.data_library.get_add_h_rules()

        for res in self.all_residues:
            if mu.is_hetatm(res):
                continue

            if remove_h:
                mu.remove_H_from_r(res, verbose=False)

            if res not in self.prev_residue:
                prev_residue = None
            else:
                prev_residue = self.prev_residue[res]

            error_msg = mu.add_hydrogens_backbone(res, prev_residue)

            if error_msg:
                print(error_msg, mu.residue_id(res))

            rcode = res.get_resname()

            if rcode == 'GLY':
                continue

            if rcode not in add_h_rules:
                print(NotAValidResidueError(rcode).message)
                continue

            if res in ion_res_list:
                if rcode != ion_res_list[res]:
                    print('Replacing {} by {}'.format(mu.residue_id(res),
                                                      ion_res_list[res]))
                error_msg = mu.add_hydrogens_side(
                    res, self.res_library, ion_res_list[res],
                    add_h_rules[rcode][ion_res_list[res]])
                res.resname = ion_res_list[res]
            else:
                error_msg = mu.add_hydrogens_side(res, self.res_library, rcode,
                                                  add_h_rules[rcode])

            if error_msg:
                print(error_msg, mu.residue_id(res))

        self.residue_renumbering()
        self.atom_renumbering()
        self.modified = True
    def fix_backbone_O_atoms(self, r_at: Tuple[Residue,
                                               Sequence[str]]) -> bool:
        """Adding missing backbone atoms not affecting main-chain like O and OXT
                Args:
            **r_at**: tuple as [Bio.PDB.Residue, [list of atom ids]]
        """
        res, at_list = r_at
        print(mu.residue_id(res))
        if 'C' not in res:
            raise NotEnoughAtomsError
        if len(at_list) == 2 or at_list == ['O']:
            if 'CA' not in res or 'N' not in res or 'C' not in res:
                raise NotEnoughAtomsError
            print("  Adding new atom O")
            mu.add_new_atom_to_residue(res, 'O', mu.build_coords_O(res))
        if 'OXT' in at_list:
            if 'CA' not in res or 'C' not in res or 'O' not in res:
                raise NotEnoughAtomsError
            print("  Adding new atom OXT")
            mu.add_new_atom_to_residue(
                res, 'OXT',
                mu.build_coords_SP2(mu.OINTERNALS[0], res['C'], res['CA'],
                                    res['O']))

        self.atom_renumbering()
        self.modified = True
        return True
    def merge_structure(self, new_st: Structure, mod_id: str, ch_id: str,
                        brk_list: Union[Sequence[Atom],
                                        Sequence[Sequence[Atom]]],
                        offset: int) -> Union[str, List[str]]:
        spimp = Superimposer()
        fixed_ats = [
            atm for atm in self.st[mod_id][ch_id].get_atoms() if atm.id == 'CA'
        ]
        moving_ats = []
        for atm in fixed_ats:
            moving_ats.append(new_st[0][' '][atm.get_parent().id[1] - offset +
                                             1]['CA'])
        spimp.set_atoms(fixed_ats, moving_ats)
        spimp.apply(new_st.get_atoms())

        list_res = self.st[mod_id][ch_id].get_list()
        fixed_gaps = []
        for i in range(0,
                       len(self.sequence_data.data[ch_id]['pdb'][mod_id]) - 1):
            gap_start = self.sequence_data.data[ch_id]['pdb'][mod_id][
                i].features[0].location.end
            gap_end = self.sequence_data.data[ch_id]['pdb'][mod_id][
                i + 1].features[0].location.start

            if [
                    self.st[mod_id][ch_id][gap_start],
                    self.st[mod_id][ch_id][gap_end]
            ] not in brk_list:
                continue

            pos = 0
            while pos < len(list_res) and self.st[mod_id][ch_id].child_list[
                    pos].id[1] != gap_start:
                pos += 1
            self.remove_residue(self.st[mod_id][ch_id][gap_start],
                                update_int=False)
            self.remove_residue(self.st[mod_id][ch_id][gap_end],
                                update_int=False)
            for nres in range(gap_start, gap_end + 1):
                res = new_st[0][' '][nres - offset + 1].copy()
                res.id = (' ', nres, ' ')
                self.st[mod_id][ch_id].insert(pos, res)
                pos += 1
                print("Adding " + mu.residue_id(res))
            fixed_gaps.append('{}{}-{}{}/{}'.format(ch_id, gap_start, ch_id,
                                                    gap_end, mod_id + 1))
            print()
        return fixed_gaps
    def fix_side_chain(self, r_at: Tuple[Residue, Sequence[str]]) -> None:
        """
        Fix missing side chain atoms in given residue. Triggers **modified** flag

        Args:
            **r_at**: tuple as [Bio.PDB.Residue, [list of atom ids]]
        """
        print(mu.residue_id(r_at[0]))
        for at_id in r_at[1]:
            print("  Adding new atom " + at_id)
            if at_id == 'CB':
                coords = mu.build_coords_CB(r_at[0])
            else:
                coords = mu.build_coords_from_lib(r_at[0], self.res_library,
                                                  r_at[0].get_resname(), at_id)
            mu.add_new_atom_to_residue(r_at[0], at_id, coords)
        self.atom_renumbering()
        self.modified = True
 def apply(self, mut_map, res_lib, remove_h):
     """ Perform the individual mutations on the set. """
     mutated_res = []
     for mut in self.mutations:
         res = mut['resobj']
         #struc[mut['model']][mut['chain']][mut['residue']]
         rname = res.get_resname().replace(' ', '')
         # Deleting H
         if remove_h == 'mut':
             mu.remove_H_from_r(res, verbose=True)
         # checking side chain
         side_atoms = []
         bck_atoms = []
         for atm in res.get_atoms():
             if atm.id in mut_map[rname]['side_atoms']:
                 side_atoms.append(atm.id)
             else:
                 bck_atoms.append(atm.id)
         for at_id in ['N', 'CA', 'C']:
             if at_id not in bck_atoms:
                 sys.exit('#ERROR: Backbone atoms missing for {}, aborting'.
                          format(mu.residue_id(res)))
         missing_ats = [
             at_id for at_id in mut_map[rname]['side_atoms']
             if at_id not in side_atoms
         ]
         print("Replacing " + mu.residue_id(res) + " into " + self.new_id)
         in_rules = []
         extra_adds = []
         # Renaming ats
         if MOV in mut_map[rname][self.new_id]:
             for rule in mut_map[rname][self.new_id][MOV]:
                 old_at, new_at = rule.split("-")
                 print('  Renaming {} to {}'.format(old_at, new_at))
                 if old_at in side_atoms:
                     mu.rename_atom(res, old_at, new_at)
                 else:
                     print('#WARNING: atom {} missing in {}'.format(
                         old_at, mu.residue_id(res)))
                     extra_adds.append(new_at)
                 in_rules.append(old_at)
         # Deleting atoms
         if DEL in mut_map[rname][self.new_id]:
             for at_id in mut_map[rname][self.new_id][DEL]:
                 print('  Deleting {}'.format(at_id))
                 if at_id in side_atoms:
                     mu.delete_atom(res, at_id)
                 else:
                     print('#WARNING: atom {} already missing in {}'.format(
                         at_id, mu.residue_id(res)))
                 in_rules.append(at_id)
         # Adding atoms (new_id required as r.resname is still the original)
         # Adding missing atoms that keep name
         for at_id in mut_map[rname]['side_atoms']:
             if at_id not in in_rules and at_id in missing_ats:
                 print('  Adding missing atom {}'.format(at_id))
                 mu.build_atom(res, at_id, res_lib, self.new_id)
         for at_id in extra_adds:
             print('  Adding new atom {}'.format(at_id))
             mu.build_atom(res, at_id, res_lib, self.new_id)
         if ADD in mut_map[rname][self.new_id]:
             for at_id in mut_map[rname][self.new_id][ADD]:
                 print('  Adding new atom {}'.format(at_id))
                 mu.build_atom(res, at_id, res_lib, self.new_id)
         #Renaming residue
         res.resname = self.new_id
         mutated_res.append(res)
     print("")
     return mutated_res