KnobIntoHoles between residues of different chains in assembly.

    Notes
    -----
    A KnobIntoHole is a found when the side-chain centre of a Residue a chain is close than (cutoff) Angstroms from at
    least (hole_size) side-chain centres of Residues of a different chain.

    Parameters
    ----------
    assembly : Assembly
    hole_size : int
        Number of Residues required to form each hole.
    cutoff : float
        Maximum distance between the knob and each of the hole residues.

    Returns
    -------
    kihs : [KnobIntoHole]

def find_kihs(assembly, hole_size=4, cutoff=7.0):
    """ KnobIntoHoles between residues of different chains in assembly.

    Notes
    -----
    A KnobIntoHole is a found when the side-chain centre of a Residue a chain is close than (cutoff) Angstroms from at
    least (hole_size) side-chain centres of Residues of a different chain.

    Parameters
    ----------
    assembly : Assembly
    hole_size : int
        Number of Residues required to form each hole.
    cutoff : float
        Maximum distance between the knob and each of the hole residues.

    Returns
    -------
    kihs : [KnobIntoHole]
    """
    pseudo_group = side_chain_centres(assembly=assembly, masses=False)
    pairs = itertools.permutations(pseudo_group, 2)
    kihs = []
    for pp_1, pp_2 in pairs:
        for r in pp_1:
            close_atoms = pp_2.is_within(cutoff, r)
            # kihs occur between residue and (hole_size) closest side-chains on adjacent polypeptide.
            if len(close_atoms) < hole_size:
                continue
            elif len(close_atoms) > hole_size:
                close_atoms = sorted(close_atoms, key=lambda x: distance(x, r))[:hole_size]
            kih = OrderedDict()
            kih['k'] = r
            for i, hole_atom in enumerate(close_atoms):
                kih['h{0}'.format(i)] = hole_atom
            knob_into_hole = KnobIntoHole(pseudo_atoms=kih)
            kihs.append(knob_into_hole)
    return kihs