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)
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
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