Beispiel #1
0
def test_H():
    """Test H substituent."""
    elements, coordinates = read_gjf(DATA_DIR / "gjfs" / "H.gjf")
    radii = get_radii(elements, radii_type="bondi")
    radii = [1.09 if radius == 1.20 else radius for radius in radii]
    sterimol = Sterimol(elements, coordinates, 1, 2, radii=radii)
    assert_almost_equal(
        (sterimol.L_value, sterimol.B_1_value, sterimol.B_5_value),
        (2.15, 1.09, 1.09),
        decimal=2,
    )
Beispiel #2
0
def test_reference(sterimol_data):
    """Test against Sterimol reference data."""
    data = sterimol_data
    name = data["name"]
    L, B_1, B_5 = float(data["L"]), float(data["B_1"]), float(data["B_5"])

    # Use Paton's Bondi radii of 1.09 for H
    elements, coordinates = read_gjf(DATA_DIR / "gjfs" / f"{name}.gjf")
    radii = get_radii(elements, radii_type="bondi")
    radii = [1.09 if radius == 1.20 else radius for radius in radii]
    sterimol = Sterimol(elements, coordinates, 1, 2, radii=radii)

    assert_almost_equal(L, sterimol.L_value, decimal=2)
    assert_almost_equal(B_1, sterimol.B_1_value, decimal=2)
    assert_almost_equal(B_5, sterimol.B_5_value, decimal=2)
Beispiel #3
0
    def __init__(
        self,
        elements: Iterable[int] | Iterable[str],
        coordinates: ArrayLike2D,
        radii: ArrayLike1D | None = None,
        radii_type: str = "crc",
        probe_radius: float = 1.4,
        density: float = 0.01,
    ) -> None:
        # Converting elements to atomic numbers if the are symbols
        elements = convert_elements(elements, output="numbers")
        coordinates: Array2DFloat = np.array(coordinates)

        # Getting radii if they are not supplied
        if radii is None:
            radii = get_radii(elements, radii_type=radii_type)

        # Increment the radii with the probe radius
        radii: Array1DFloat = np.array(radii)
        radii = radii + probe_radius

        # Construct list of atoms
        atoms = []
        for i, (coordinate, radius,
                element) in enumerate(zip(coordinates, radii, elements),
                                      start=1):
            atom = Atom(element, coordinate, radius, i)
            atoms.append(atom)

        # Set up attributes
        self._atoms = atoms
        self._density = density
        self._probe_radius = probe_radius

        # Determine accessible and occluded points for each atom
        self._determine_accessible_points()

        # Calculate atom areas and volumes
        self._calculate()
Beispiel #4
0
    def __init__(
        self,
        elements: Union[Iterable[int], Iterable[str]],
        coordinates: ArrayLike2D,
        radii: Optional[ArrayLike1D] = None,
        radii_type: str = "rahm",
        point_surface: bool = True,
        compute_coefficients: bool = True,
        density: float = 0.1,
        excluded_atoms: Optional[Sequence[int]] = None,
        included_atoms: Optional[Sequence[int]] = None,
    ) -> None:
        # Check that only excluded or included atoms are given
        if excluded_atoms is not None and included_atoms is not None:
            raise Exception(
                "Give either excluded or included atoms but not both.")

        # Converting elements to atomic numbers if the are symbols
        elements = convert_elements(elements, output="numbers")
        coordinates = np.array(coordinates)

        # Set excluded atoms
        all_atoms = set(range(1, len(elements) + 1))
        if included_atoms is not None:
            included_atoms_ = set(included_atoms)
            excluded_atoms = list(all_atoms - included_atoms_)
        elif excluded_atoms is None:
            excluded_atoms = []
        else:
            excluded_atoms = list(excluded_atoms)

        self._excluded_atoms = excluded_atoms

        # Set up
        self._surface = None
        self._density = density

        # Getting radii if they are not supplied
        if radii is None:
            radii = get_radii(elements, radii_type=radii_type)
        radii = np.array(radii)

        self._radii = radii

        # Get vdW surface if requested
        if point_surface:
            self._surface_from_sasa(elements, coordinates)
        else:
            # Get list of atoms as Atom objects
            atoms: List[Atom] = []
            for i, (element, coord,
                    radius) in enumerate(zip(elements, coordinates, radii),
                                         start=1):
                atom = Atom(element, coord, radius, i)
                atoms.append(atom)
            self._atoms = atoms

        # Calculate coefficients
        if compute_coefficients:
            self.compute_coefficients(model="id3")

        # Calculatte P_int values
        if point_surface and compute_coefficients:
            self.compute_p_int()
Beispiel #5
0
    def __init__(
        self,
        elements: Iterable[int] | Iterable[str],
        coordinates: ArrayLike2D,
        order: int = 8,
    ) -> None:
        # Convert elements to atomic numbers
        elements = convert_elements(elements, output="numbers")
        coordinates: Array2DFloat = np.array(coordinates)

        # Load the covalent radii
        radii = get_radii(elements, radii_type="pyykko")

        # Set up the atoms objects
        atoms = []
        for i, (element, coordinate,
                radius) in enumerate(zip(elements, coordinates, radii),
                                     start=1):
            atom = Atom(element, coordinate, radius, i)
            atoms.append(atom)
        self._atoms = atoms

        # Calculate the coordination numbers according to Grimme's recipe.
        k_1 = 16
        k_2 = 4 / 3
        k_3 = 4
        for cn_atom in atoms:
            # Get coordinates and radii of all other atoms
            other_coordinates: Array2DFloat = np.array(
                [atom.coordinates for atom in atoms if atom is not cn_atom])
            other_radii: Array1DFloat = np.array(
                [atom.radius for atom in atoms if atom is not cn_atom])

            # Calculate distances
            dists = cdist(
                np.array(cn_atom.coordinates).reshape(-1, 3),
                other_coordinates.reshape(-1, 3),
            )

            # Calculate coordination number
            coordination_number = np.sum(
                1 / (1 + np.exp(-k_1 *
                                (k_2 *
                                 (cn_atom.radius + other_radii) / dists - 1))))
            cn_atom.coordination_number = coordination_number

        # Calculate the C_N coefficients
        c_n_coefficients: dict[int, list[float]] = {
            i: []
            for i in range(6, order + 1, 2)
        }
        for atom in atoms:
            # Get the reference data for atom
            reference_data = c6_reference_data[atom.element]
            cn = atom.coordination_number

            # Take out the coordination numbers and c6(aa) values
            c_6_ref = reference_data[:, 0]
            cn_1 = reference_data[:, 1]
            cn_2 = reference_data[:, 2]

            # Calculate c6  and c8 according to the recipe
            r = (cn - cn_1)**2 + (cn - cn_2)**2
            L = np.exp(-k_3 * r)
            W = np.sum(L)
            Z = np.sum(c_6_ref * L)
            c_6 = Z / W

            temp_coefficients: dict[int, float] = {
                i: np.nan
                for i in range(6, order + 1, 2)
            }
            for i in range(6, order + 1, 2):
                if i == 6:
                    temp_coefficients[i] = c_6
                if i == 8:
                    c_8 = 3 * c_6 * r2_r4[atom.element]**2
                    temp_coefficients[i] = c_8
                elif i == 10:
                    c_10 = 49.0 / 40.0 * c_8**2 / c_6
                    temp_coefficients[i] = c_10
                elif i > 10:
                    c_n = (temp_coefficients[i - 6] *
                           (temp_coefficients[i - 2] /
                            temp_coefficients[i - 4])**3)
                    temp_coefficients[i] = c_n
            for key, value in temp_coefficients.items():
                c_n_coefficients[key].append(value)

        # Set up attributes
        coordination_numbers: Array1DFloat = np.array(
            [atom.coordination_number for atom in self._atoms])
        c_n_coefficients: Array1DFloat = {
            key: np.array(value)
            for key, value in c_n_coefficients.items()
        }

        self._atoms = atoms
        self.c_n_coefficients = c_n_coefficients
        self.coordination_numbers = coordination_numbers
Beispiel #6
0
    def __init__(  # noqa: C901
        self,
        elements: Iterable[int] | Iterable[str],
        coordinates: ArrayLike2D,
        atom_1: int,
        radii: ArrayLike1D | None = None,
        radii_type: str = "crc",
        method: str = "libconeangle",
    ) -> None:
        # Convert elements to atomic numbers if the are symbols
        elements = convert_elements(elements, output="numbers")
        coordinates: Array2DFloat = np.array(coordinates)

        # Get radii if they are not supplied
        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 atom 1
        within = check_distances(elements, coordinates, atom_1, radii=radii)
        if len(within) > 0:
            atom_string = " ".join([str(i) for i in within])
            raise ValueError("Atoms within vdW radius of central atom:", atom_string)

        # Set up coordinate array and translate coordinates
        coordinates -= coordinates[atom_1 - 1]

        # Get list of atoms as Atom objects
        atoms: list[Atom] = []
        for i, (element, coord, radius) in enumerate(
            zip(elements, coordinates, radii), start=1
        ):
            if i != atom_1:
                atom = Atom(element, coord, radius, i)
                atom.get_cone()
                atoms.append(atom)
        self._atoms = atoms

        # Calculate cone angle
        if method == "libconeangle":
            try:
                from libconeangle import cone_angle

                angle, axis, tangent_atoms = cone_angle(coordinates, radii, atom_1 - 1)
                self.cone_angle = angle
                self.tangent_atoms = [i + 1 for i in tangent_atoms]
                atoms = [
                    atom for atom in self._atoms if atom.index in self.tangent_atoms
                ]
                self._cone = Cone(self.cone_angle, atoms, axis)
            except ImportError:
                warnings.warn(
                    "Failed to import libconeangle. Defaulting to method='internal'"
                )
                self._cone_angle_internal()
        elif method == "internal":
            self._cone_angle_internal()
        else:
            raise ValueError(
                "Method not implemented. Choose between 'libconeangle' and 'internal'"
            )
Beispiel #7
0
    def __init__(
        self,
        elements: Iterable[int] | Iterable[str],
        coordinates: ArrayLike2D,
        metal_index: int,
        excluded_atoms: Sequence[int] | None = None,
        radii: ArrayLike1D | None = None,
        include_hs: bool = False,
        radius: float = 3.5,
        radii_type: str = "bondi",
        radii_scale: float = 1.17,
        density: float = 0.001,
        z_axis_atoms: Sequence[int] | None = None,
        xz_plane_atoms: Sequence[int] | None = None,
    ) -> None:
        # Get center and and reortient coordinate system
        coordinates: Array2DFloat = np.array(coordinates)
        center = coordinates[metal_index - 1]
        coordinates -= center

        if excluded_atoms is None:
            excluded_atoms = []
        excluded_atoms = set(excluded_atoms)

        if metal_index not in excluded_atoms:
            excluded_atoms.add(metal_index)

        if z_axis_atoms is not None and xz_plane_atoms is not None:
            z_axis_coordinates = coordinates[np.array(z_axis_atoms) - 1]
            z_point = np.mean(z_axis_coordinates, axis=0)

            xz_plane_coordinates = coordinates[np.array(xz_plane_atoms) - 1]
            xz_point = np.mean(xz_plane_coordinates, axis=0)

            v_1 = z_point - center
            v_2 = xz_point - center
            # TODO: Remove type ignores when https://github.com/numpy/numpy/pull/21216 is released
            v_3: Array1DFloat = np.cross(v_2, v_1)
            real: Array2DFloat = np.vstack([v_1, v_3])  # type: ignore
            real /= np.linalg.norm(real, axis=1).reshape(-1, 1)
            ref_1 = np.array([0.0, 0.0, -1.0])
            ref_2 = np.array([0.0, 1.0, 0.0])
            ref = np.vstack([ref_1, ref_2])
            R = kabsch_rotation_matrix(real, ref, center=False)
            coordinates = (R @ coordinates.T).T
        elif z_axis_atoms is not None:
            z_axis_coordinates = coordinates[np.array(z_axis_atoms) - 1]
            z_point = np.mean(z_axis_coordinates, axis=0)
            v_1 = z_point - center
            v_1 = v_1 / np.linalg.norm(v_1)
            coordinates = rotate_coordinates(coordinates, v_1,
                                             np.array([0, 0, -1]))

        self._z_axis_atoms = z_axis_atoms
        self._xz_plane_atoms = xz_plane_atoms

        # Save density and coordinates for steric map plotting.
        self._density = density
        self._all_coordinates = coordinates

        # Converting element ids to atomic numbers if the are symbols
        elements = convert_elements(elements, output="numbers")

        # Getting radii if they are not supplied
        if radii is None:
            radii = get_radii(elements,
                              radii_type=radii_type,
                              scale=radii_scale)
        radii: Array1DFloat = np.array(radii)

        # Get list of atoms as Atom objects
        atoms = []
        for i, (element,
                radius_, coord) in enumerate(zip(elements, radii, coordinates),
                                             start=1):
            if i in excluded_atoms:
                continue
            elif (not include_hs) and element == 1:
                continue
            else:
                atom = Atom(element, coord, radius_, i)
                atoms.append(atom)

        # Set variables for outside access and function access.
        self._atoms = atoms
        self._excluded_atoms = set(excluded_atoms)

        # Compute buried volume
        self._compute_buried_volume(center=center,
                                    radius=radius,
                                    density=density)
Beispiel #8
0
    def __init__(
        self,
        elements: Union[Iterable[int], Iterable[str]],
        coordinates: ArrayLike2D,
        dummy_index: int,
        attached_index: Union[int, Iterable[int]],
        radii: Optional[ArrayLike1D] = None,
        radii_type: str = "crc",
        n_rot_vectors: int = 3600,
        excluded_atoms: Optional[Sequence[int]] = None,
        calculate: bool = True,
    ) -> None:
        # Convert elements to atomic numbers if the are symbols
        elements = convert_elements(elements, output="numbers")
        coordinates = np.array(coordinates)

        if excluded_atoms is None:
            excluded_atoms = []
        excluded_atoms = np.array(excluded_atoms)

        # Get radii if they are not supplied
        if radii is None:
            radii = get_radii(elements, radii_type=radii_type)
        radii = np.array(radii)

        # Add dummy atom if multiple attached indices are given
        if isinstance(attached_index, Iterable):
            attached_dummy_coordinates = np.mean(
                [coordinates[i - 1] for i in attached_index], axis=0)
            attached_dummy_coordinates = cast(np.ndarray,
                                              attached_dummy_coordinates)
            coordinates = np.vstack([coordinates, attached_dummy_coordinates])
            elements.append(0)
            radii = np.concatenate([radii, [0.0]])
            attached_index = len(elements)

        # Set up coordinate array
        all_coordinates = coordinates
        all_radii = radii

        # Translate coordinates so origin is at atom 2
        origin = all_coordinates[attached_index - 1]
        all_coordinates -= origin

        # Get vector pointing from atom 2 to atom 1
        vector_2_to_1 = (all_coordinates[attached_index - 1] -
                         all_coordinates[dummy_index - 1])
        bond_length = np.linalg.norm(vector_2_to_1)
        vector_2_to_1 = vector_2_to_1 / np.linalg.norm(vector_2_to_1)

        # Get rotation quaternion that overlays vector with x-axis
        x_axis = np.array([[1.0, 0.0, 0.0]])
        R = kabsch_rotation_matrix(vector_2_to_1.reshape(1, -1),
                                   x_axis,
                                   center=False)
        all_coordinates = (R @ all_coordinates.T).T
        self._rotation_matrix = R

        # Get list of atoms as Atom objects
        atoms = []
        for i, (element, radius,
                coord) in enumerate(zip(elements, all_radii, all_coordinates),
                                    start=1):
            atom = Atom(element, coord, radius, i)
            atoms.append(atom)
            if i == dummy_index:
                dummy_atom = atom
            if i == attached_index:
                attached_atom = atom

        # Set up attributes
        self._atoms = atoms
        self._excluded_atoms = set(excluded_atoms)
        self._origin = origin

        self._dummy_atom = dummy_atom
        self._attached_atom = attached_atom

        self.bond_length = bond_length

        self._n_rot_vectors = n_rot_vectors

        if calculate:
            self.calculate()
Beispiel #9
0
    def __init__(  # noqa: C901
        self,
        elements: Union[Iterable[int], Iterable[str]],
        coordinates: ArrayLike2D,
        atom_1: int,
        radii: Optional[ArrayLike1D] = None,
        radii_type: str = "crc",
    ) -> None:
        # Convert elements to atomic numbers if the are symbols
        elements = convert_elements(elements, output="numbers")
        coordinates = np.array(coordinates)

        # Get radii if they are not supplied
        if radii is None:
            radii = get_radii(elements, radii_type=radii_type)
        radii = np.array(radii)

        # Check so that no atom is within vdW distance of atom 1
        within = check_distances(elements, coordinates, atom_1, 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)

        # Set up coordinate array and translate coordinates
        coordinates -= coordinates[atom_1 - 1]

        # Get list of atoms as Atom objects
        atoms: List[Atom] = []
        for i, (element, coord,
                radius) in enumerate(zip(elements, coordinates, radii),
                                     start=1):
            if i != atom_1:
                atom = Atom(element, coord, radius, i)
                atom.get_cone()
                atoms.append(atom)
        self._atoms = atoms

        # Search for cone over single atoms
        cone = self._search_one_cones()

        # Prune out atoms that lie in the shadow of another atom's cone
        if cone is None:
            loop_atoms = list(atoms)
            remove_atoms: Set[Atom] = set()
            for cone_atom in loop_atoms:
                for test_atom in loop_atoms:
                    if cone_atom is not test_atom:
                        if cone_atom.cone.is_inside(test_atom):
                            remove_atoms.add(test_atom)
            for atom in remove_atoms:
                loop_atoms.remove(atom)
            self._loop_atoms = loop_atoms

        # Search for cone over pairs of atoms
        if cone is None:
            cone = self._search_two_cones()

        # Search for cones over triples of atoms
        if cone is None:
            cone = self._search_three_cones()

        # Set attributes
        self._cone = cone
        self.cone_angle = math.degrees(cone.angle * 2)
        self.tangent_atoms = [atom.index for atom in cone.atoms]
Beispiel #10
0
    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