Exemple #1
0
    def _surface_from_sasa(
        self,
        elements: Iterable[int] | Iterable[str],
        coordinates: ArrayLike2D,
    ) -> None:
        """Get surface from SASA."""
        sasa = SASA(
            elements,
            coordinates,
            radii=self._radii,
            density=self._density,
            probe_radius=0,
        )
        self._atoms = sasa._atoms
        self.area = sum(
            [
                atom.area
                for atom in self._atoms
                if atom.index not in self._excluded_atoms
            ]
        )
        self.atom_areas = sasa.atom_areas
        self.volume = sum(
            [
                atom.volume
                for atom in self._atoms
                if atom.index not in self._excluded_atoms
            ]
        )

        # Get point areas and map from point to atom
        point_areas: list[Array1DFloat] = []
        point_map = []
        for atom in self._atoms:
            n_points = len(atom.accessible_points)
            if n_points > 0:
                point_area = atom.area / n_points
            else:
                point_area = 0.0
            atom.point_areas = np.repeat(point_area, n_points)
            point_areas.extend(atom.point_areas)
            point_map.extend([atom.index] * n_points)
        self._point_areas = np.array(point_areas)
        self._point_map = np.array(point_map)
Exemple #2
0
    def surface_from_radii(self, density: float = 0.01) -> "Sterimol":
        """Create surface points from vdW surface.

        Args:
            density: Area per point on surface (Ų)

        Returns:
            self: Self
        """
        # Calculate vdW surface for all active atoms
        elements = []
        coordinates = []
        radii = []
        for atom in self._atoms:
            if atom.index not in self._excluded_atoms and atom is not self._dummy_atom:
                elements.append(atom.element)
                coordinates.append(atom.coordinates)
                radii.append(atom.radius)
        elements = np.array(elements)
        coordinates = np.vstack(coordinates)
        radii = radii
        sasa = SASA(elements,
                    coordinates,
                    radii=radii,
                    density=density,
                    probe_radius=0)

        # Take out points of vdW surface
        points = np.vstack([
            atom.accessible_points for atom in sasa._atoms
            if atom.index not in self._excluded_atoms
            and atom.accessible_points.size > 0
        ])
        self._points = points

        return self
Exemple #3
0
    def compute_distal_volume(self,
                              method: str = "sasa",
                              octants: bool = False,
                              sasa_density: float = 0.01) -> "BuriedVolume":
        """Computes the distal volume.

        Uses either SASA or Buried volume with large radius to calculate the molecular
        volume.

        Args:
            method: Method to get total volume: 'buried_volume' or 'sasa'
            octants: Whether to compute distal volume for quadrants and octants.
                Requires method='buried_volume'
            sasa_density: Density of points on SASA surface. Ignored unless
                method='sasa'

        Returns:
            self: Self

        Raises:
            ValueError: When method is not specified correctly.
        """
        loop_coordinates: list[Array1DFloat]
        # Use SASA to calculate total volume of the molecule
        if method == "sasa":
            # Calculate total volume
            elements: list[int] = []
            loop_coordinates = []
            radii: list[float] = []
            for atom in self._atoms:
                elements.append(atom.element)
                loop_coordinates.append(atom.coordinates)
                radii.append(atom.radius)
            coordinates: Array2DFloat = np.vstack(loop_coordinates)
            sasa = SASA(
                elements,
                coordinates,
                radii=radii,
                probe_radius=0.0,
                density=sasa_density,
            )
            self.molecular_volume = sasa.volume

            # Calculate distal volume
            self.distal_volume = self.molecular_volume - self.buried_volume
        elif method == "buried_volume":
            if octants is True and self.octants is None:
                raise ValueError("Needs octant analysis.")

            # Save the values for the old buried volume calculation
            temp_bv = copy.deepcopy(self)

            # Determine sphere radius to cover the whole molecule
            loop_coordinates = []
            radii = []
            for atom in self._atoms:
                loop_coordinates.append(atom.coordinates)
                radii.append(atom.radius)
            coordinates = np.vstack(loop_coordinates)
            distances = scipy.spatial.distance.cdist(
                self._sphere.center.reshape(1, -1), coordinates)
            new_radius = np.max(distances + radii) + 0.5

            # Compute the distal volume
            temp_bv._compute_buried_volume(
                center=self._sphere.center,
                radius=new_radius,
                density=self._sphere.density,
            )
            self.molecular_volume = temp_bv.buried_volume
            self.distal_volume = self.molecular_volume - self.buried_volume
            if octants is True:
                temp_bv.octant_analysis()

                # Octant analysis
                distal_volume = {}
                molecular_volume = {}
                for name in self.octants["buried_volume"].keys():
                    molecular_volume[name] = temp_bv.octants["buried_volume"][
                        name]
                    distal_volume[name] = (
                        temp_bv.octants["buried_volume"][name] -
                        self.octants["buried_volume"][name])
                self.octants["distal_volume"] = distal_volume
                self.octants["molecular_volume"] = molecular_volume

                # Quadrant analyis
                distal_volume = {}
                molecular_volume = {}
                for name, octants_ in QUADRANT_OCTANT_MAP.items():
                    distal_volume[name] = sum([
                        self.octants["distal_volume"][octant]
                        for octant in octants_
                    ])
                    molecular_volume[name] = sum([
                        self.octants["molecular_volume"][octant]
                        for octant in octants_
                    ])
                self.quadrants["distal_volume"] = distal_volume
                self.quadrants["molecular_volume"] = molecular_volume
        else:
            raise ValueError(f"Method {method} is not valid.")

        return self
    def __init__(
        self,
        elements: Iterable[int] | Iterable[str],
        coordinates: ArrayLike2D,
        metal_index: int,
        include_hs: bool = True,
        radii: ArrayLike1D | None = None,
        radii_type: str = "pyykko",
        radius: float = 3.5,
        density: float = 0.01,
    ) -> None:
        # Set up arrays and get radii
        elements: Array1DInt = np.array(
            convert_elements(elements, output="numbers"))
        coordinates: Array2DFloat = np.array(coordinates)
        if radii is None:
            radii = get_radii(elements, radii_type=radii_type)
        radii: Array1DFloat = np.array(radii)

        # Check so that no atom is within vdW distance of metal atom
        within = check_distances(elements,
                                 coordinates,
                                 metal_index,
                                 radii=radii)
        if len(within) > 0:
            atom_string = " ".join([str(i) for i in within])
            raise Exception("Atoms within vdW radius of central atom:",
                            atom_string)

        # Center coordinate system around metal and remove it
        coordinates -= coordinates[metal_index - 1]
        elements: Array1DFloat = np.delete(elements, metal_index - 1)
        coordinates: Array2DFloat = np.delete(coordinates,
                                              metal_index - 1,
                                              axis=0)
        radii: Array1DFloat = np.delete(radii, metal_index - 1)

        # Remove H atoms
        if include_hs is False:
            h_mask = elements == 1
            elements = elements[~h_mask]
            coordinates = coordinates[~h_mask]
            radii = radii[~h_mask]

        # Construct SASA object
        sasa = SASA(
            elements,
            coordinates,
            radii=radii,
            radii_type=radii_type,
            probe_radius=0.0,
            density=density,
        )

        # Set invisible and proximal masks for each atom
        atoms = sasa._atoms
        for atom in atoms:
            atom.invisible_mask = np.zeros(len(atom.accessible_points),
                                           dtype=bool)
            atom.proximal_mask = np.linalg.norm(atom.accessible_points,
                                                axis=1) < radius

        # Check points on other atoms against cone for each atom
        atom_coordinates: Array2DFloat = np.array(
            [atom.coordinates for atom in atoms])
        atoms: Array1DAny = np.array(atoms)
        for atom in atoms:
            # Calculate distances to other atoms
            atom.get_cone()
            cone = Cone(atom.cone.angle, [atom.index], atom.cone.normal)
            atom_dist = np.linalg.norm(atom.coordinates)
            other_distances = np.dot(atom_coordinates, cone.normal)

            # Check whether points are (1) within cone and (2) beyond atom
            # center
            check_atoms = atoms[other_distances >= (atom_dist - atom.radius)]
            for check_atom in check_atoms:
                if check_atom == atom:
                    continue
                is_inside_mask = cone.is_inside_points(
                    check_atom.accessible_points, method="cross")
                is_beyond_mask = (np.dot(check_atom.accessible_points,
                                         cone.normal) >= atom_dist)
                invisible_mask = np.logical_and(is_inside_mask, is_beyond_mask)
                check_atom.invisible_mask = np.logical_or(
                    check_atom.invisible_mask, invisible_mask)

        # Calculate visible, invisible and proximal_visible volume
        visible_volume = 0
        invisible_volume = 0
        proximal_visible_volume = 0
        proximal_volume = 0
        visible_area = 0
        invisible_area = 0
        proximal_visible_area = 0
        proximal_area = 0
        for atom in atoms:
            point_areas = atom.point_areas[atom.accessible_mask]
            point_volumes = atom.point_volumes[atom.accessible_mask]

            invisible_volume += point_volumes[atom.invisible_mask].sum()
            visible_volume += point_volumes[~atom.invisible_mask].sum()
            proximal_visible_volume += point_volumes[np.logical_and(
                ~atom.invisible_mask, atom.proximal_mask)].sum()
            proximal_volume += point_volumes[atom.proximal_mask].sum()

            invisible_area += point_areas[atom.invisible_mask].sum()
            visible_area += point_areas[~atom.invisible_mask].sum()
            proximal_visible_area += point_areas[np.logical_and(
                ~atom.invisible_mask, atom.proximal_mask)].sum()
            proximal_area += point_areas[atom.proximal_mask].sum()

        # Store attributes
        self.total_volume = sasa.volume
        self.proximal_volume = proximal_volume
        self.distal_volume = sasa.volume - proximal_volume
        self.invisible_volume = invisible_volume
        self.visible_volume = visible_volume
        self.proximal_visible_volume = proximal_visible_volume
        self.distal_visible_volume = visible_volume - proximal_visible_volume

        self.total_area = sasa.area
        self.proximal_area = proximal_area
        self.distal_area = sasa.area - proximal_area
        self.invisible_area = invisible_area
        self.visible_area = visible_area
        self.proximal_visible_area = proximal_visible_area
        self.distal_visible_area = visible_area - proximal_visible_area

        self._atoms = atoms