It is advisable to set up and use an atom hit cache object. This reduces the number of distance calculations and gives better performance.
           See find_sidechain_atoms_within_radius_of_residue_objects for an example of how to set this up e.g.
               atom_hit_cache = set()
               for x in some_loop:
                   this_object.find_atoms_near_atom(source_atom, search_radius, atom_hit_cache = atom_hit_cache)

def find_atoms_near_atom(self, source_atom, search_radius, atom_hit_cache = set(), atom_names_to_include = set(), atom_names_to_exclude = set(), restrict_to_CA = False):
        '''It is advisable to set up and use an atom hit cache object. This reduces the number of distance calculations and gives better performance.
           See find_sidechain_atoms_within_radius_of_residue_objects for an example of how to set this up e.g.
               atom_hit_cache = set()
               for x in some_loop:
                   this_object.find_atoms_near_atom(source_atom, search_radius, atom_hit_cache = atom_hit_cache)
        '''
        if len(atom_names_to_include) > 0 and len(atom_names_to_exclude) > 0:
            raise Exception('Error: either one of the set of atoms types to include or the set of atom types to exclude can be set but not both.')

        atom_names_to_exclude = set(atom_names_to_exclude)
        if atom_names_to_include:
            atom_names_to_exclude = set(self.atom_name_to_group.keys()).difference(atom_names_to_include)

        radius = float(search_radius) + self.buffer # add buffer to account for edge cases in searching
        bin_size = self.bin_size
        atom_bins = self.atom_bins
        if source_atom:
            bin_radius = int(math.ceil(radius / bin_size)) # search this many bins in all directions
            xrange = range(max(0, source_atom.bin.x - bin_radius), min(self.atom_bin_dimensions[0], source_atom.bin.x + bin_radius) + 1)
            yrange = range(max(0, source_atom.bin.y - bin_radius), min(self.atom_bin_dimensions[1], source_atom.bin.y + bin_radius) + 1)
            zrange = range(max(0, source_atom.bin.z - bin_radius), min(self.atom_bin_dimensions[2], source_atom.bin.z + bin_radius) + 1)
            for x in xrange:
                for y in yrange:
                    for z in zrange:
                        for atom in atom_bins[x][y][z]:
                            if atom not in atom_hit_cache:
                                if restrict_to_CA:
                                    if atom.name == 'CA' and (source_atom - atom <= search_radius):
                                        atom_hit_cache.add(atom)
                                else:
                                    if (source_atom - atom <= search_radius) and (atom.name not in atom_names_to_exclude):
                                        atom_hit_cache.add(atom)

            return atom_hit_cache