def volume_delaunay(model):
    """Returns dictionary containing volume for each residue in `model`.

    Parameters
    ----------
    model: Bio.PDB.Model.Model
        Model of the protein structure.

    """

    volume_dict = {}
    atoms = np.array([atom for atom in model.get_atoms()])
    delaunay = Delaunay([atom.coord for atom in atoms])

    for simplex in delaunay.simplices:
        parent_residues = Selection.get_unique_parents(atoms[simplex])
        # Simplex is taken into account only if is totally contained in one
        # residue.
        if len(parent_residues) is 1:
            unique_parent = label_residue(parent_residues[0])
            cv_simplex = ConvexHull([atom.coord for atom in atoms[simplex]])
            volume_dict.setdefault(unique_parent, 0)
            volume_dict[unique_parent] += cv_simplex.volume

    return volume_dict
def void_ken_dill(model):
    """Return dict with the void of each residue in `model`.

    Atom radii are taken from [1] except for hidrogen taken from [2].

    Parameters
    ----------
    model: Bio.PDB.Model.Model
    The structure model of the protein

    Notes
    -----
    [1]: D. Flatow et al. (Volumes and surface areas: Geometries and scaling
    relationships between coarse grained and atomic structures).
    [2]: J.C Gaines et al. (Packing in protein cores).
    """

    atom_radii = {
        'C': 1.70,
        'F': 2.00,  # Iron
        'H': 1.02,
        'I': 1.98,
        'M': 2.00,  # Magnesium
        'N': 1.55,
        'O': 1.52,
        'S': 1.80
    }

    atoms = np.array([atom for atom in model.get_atoms()])
    delaunay_triangulation = Delaunay([atom.coord for atom in atoms])
    simplices = delaunay_triangulation.simplices
    neighbors = delaunay_triangulation.neighbors

    def _empty_triangles(simplex):
        """Return True if simplex is empty, False otherwise.

        """
        empty_triangles = []

        for i in range(2):
            for j in range(i + 1, 3):
                for k in range(j + 1, 4):
                    atom_i = atoms[simplex[i]]
                    atom_j = atoms[simplex[j]]
                    atom_k = atoms[simplex[k]]

                    try:
                        radius_i = atom_radii[atom_i.id[0]]
                    except KeyError:
                        raise Exception(
                            "Radius of atom {} is not defined".format(
                                atom_i.id))
                    try:
                        radius_j = atom_radii[atom_j.id[0]]
                    except KeyError:
                        raise Exception(
                            "Radius of atom {} is not defined".format(
                                atom_j.id))
                    try:
                        radius_k = atom_radii[atom_k.id[0]]
                    except KeyError:
                        raise Exception(
                            "Radius of atom {} is not defined".format(
                                atom_k.id))

                    if (radius_i + radius_j < atom_i - atom_j
                            and radius_i + radius_k < atom_i - atom_k
                            and radius_j + radius_k < atom_j - atom_k):
                        empty_triangles.append((i, j, k))

        # Simplex is open if for all 6 edges in the tetrahedron, the distance
        # between the endpoints is greater than the sum of their Van der Waals
        # radii.
        if len(empty_triangles) == 4:
            return True

        return False

    def _depth_first_search(index):
        """Return list of connected component and checked indices.

        """
        checked_indices = []
        stack = deque([index])
        connected_component = []

        if not _empty_triangles(simplices[index]):
            checked_indices.append(index)

            return connected_component, checked_indices

        while stack:
            checked_indices.append(index)
            local_neighbors = neighbors[index]
            # Check if residue at the boundary
            if -1 in local_neighbors:
                connected_component = []

                return connected_component, checked_indices

            good_neighbors = []
            for neighbor in local_neighbors:
                # Check if neighbor is an empty triangle and hasn't been
                # checked yet.
                if _empty_triangles(simplices[neighbor]):
                    if neighbor not in checked_indices:
                        good_neighbors.append(neighbor)

            if not good_neighbors:
                connected_component.append(index)
                # Remove `index` of stack as simplex has no `open` neighbors
                # left to check.
                stack.pop()
                # If still simplices in stack then set index to its last
                # simplex.
                if stack:
                    index = stack[-1]

            else:
                stack.extend(good_neighbors)
                index = stack[-1]

        return connected_component, checked_indices

    def _bounded_regions():
        """Return the bounded regions of the alpha-complex of the triangulation.

        """
        indices = set(range(len(simplices)))
        checked_indices = []
        connected_components = []

        while indices - set(checked_indices):
            # Take an element at random from the different between indices and
            # checked indices.
            remaining_set = indices - set(checked_indices)
            # Pick one of the remaining set.
            index = next(iter(remaining_set))
            connected_component, _checked_indices = _depth_first_search(index)
            if connected_component:
                connected_components.append(connected_component)
            checked_indices.extend(_checked_indices)

        return connected_components

    def _volume_overlap(conv_simplex, atoms_simplex):
        """Return the volume of the overlap between the sphere and the tetrahedron.

        The volume of the overlap between the tetrahedron and the sphere,
        V_{overlap} is given by the equation:

        V_{overlap} = frac{2*r^3}{6} * [ -pi + phi_1 + phi_2 + phi_3 ],

        where phi_i is the dihedral angle between the planes intersecting at edge
        `i` of the convex hull. If `n_1` and `n_2` are the normal vectors of the
        planes intersecting at `edge 1`, then

        phi_1 = - [ n_1 dot n_2 ].

        Where `dot` represents the dot product of both vectors. Note: The minus
        sign in the formula is added taken into account the direction of the
        normal vectors found in the equation of the planes defined by the facets
        in `scipy.spatial.ConvexHull`.

        Parameters
        ----------
        conv_simplex : scipy.spatial.ConvexHull
        Convex hull of a tetrahedron belonging to the Delaunay tesselation of a set
        of atomic coordinates.

        atoms_simplex : list
        Atoms used for the computation of the convexhull. The order of the
        `atoms_simplex` is the order of `conv_simplex.vertices`.

        """

        # Equations of the planes defined by the facets of the convex hull are of
        # the form [a, b, c, d] where a*x + b*y + c*z + d = 0. Vector
        # n = (a, b, c) is the normal vector of the facet with direction to
        # the `outside` of the convex hull.
        equations = conv_simplex.equations
        # Tehtrahedra
        simplices = conv_simplex.simplices
        vertices = conv_simplex.vertices
        volume_overlaps = []

        for vertex in vertices:
            dihedral_angles = []
            # Adjacent facets of `vertex`
            adjacent_facets = np.where(simplices == vertex)[0]
            for first, second in [[0, 1], [1, 2], [2, 0]]:
                # Normal vectors
                normal_first = equations[adjacent_facets[first]][:-1]
                normal_second = equations[adjacent_facets[second]][:-1]
                # cos(phi) = - [ n_1 \cdot n_2 ]
                cosine_angle = -np.dot(normal_first, normal_second)
                dihedral_angle = np.arccos(cosine_angle)
                dihedral_angles.append(dihedral_angle)

            # Volume overlap
            # For `radius` take only the starting letter of atom.
            radius = atom_radii[atoms_simplex[vertex].id[0]]
            factor = (radius**3) / 3.
            volume_overlap = factor * (-np.pi + sum(dihedral_angles))
            volume_overlaps.append(volume_overlap)

        return volume_overlaps

    connected_components = _bounded_regions()
    void = defaultdict(int)
    # In General expect nothing of this void. Residues are well-packed.
    inner_void = defaultdict(int)

    for connected_component in connected_components:
        for simplex in simplices[connected_component]:
            atoms_simplex = atoms[simplex]
            residues_simplex = Selection.get_unique_parents(atoms_simplex)
            conv_simplex = ConvexHull([atom.coord for atom in atoms_simplex])
            volume_overlap = _volume_overlap(conv_simplex, atoms_simplex)
            simplex_void = conv_simplex.volume - sum(volume_overlap)
            if len(residues_simplex) > 1:
                for residue in residues_simplex:
                    void[label_residue(residue)] += simplex_void
            else:
                residue = label_residue(residues_simplex[0])
                inner_void[residue] += simplex_void

    return void, inner_void