def to_graph(self): """ Convert molecule to graph. Returns ------- Graph Molecular graph. Notes ----- If the molecule does not have an associated adjacency matrix, a simple bond perception is used. The molecular graph is cached. """ if self.G is None: try: self.G = graph.graph_from_adjacency_matrix( self.adjacency_matrix, self.atomicnums) except AttributeError: warnings.warn( "Molecule was not initialized with an adjacency matrix. " + "Using bond perception...") # Automatic bond perception (with very simple rule) self.adjacency_matrix = graph.adjacency_matrix_from_atomic_coordinates( self.atomicnums, self.coordinates) self.G = graph.graph_from_adjacency_matrix( self.adjacency_matrix, self.atomicnums) return self.G
def test_adjacency_matrix_from_atomic_coordinates(mol: molecule.Molecule, n_bonds: int) -> None: A = graph.adjacency_matrix_from_atomic_coordinates(mol.atomicnums, mol.coordinates) G = graph.graph_from_adjacency_matrix(A) assert graph.num_vertices(G) == len(mol) assert graph.num_edges(G) == n_bonds
def test_build_graph_node_features_unsupported() -> None: pytest.importorskip( "graph_tool", reason="NetworkX supports all Python objects as node properties.") A = np.array([[0, 1, 1], [1, 0, 0], [1, 0, 1]]) property = [True, False, True] with pytest.raises(ValueError, match="Unsupported property type:"): _ = graph.graph_from_adjacency_matrix(A, property)
def test_graph_from_adjacency_matrix(mol) -> None: natoms = io.numatoms(mol) nbonds = io.numbonds(mol) A = io.adjacency_matrix(mol) assert A.shape == (natoms, natoms) assert np.alltrue(A == A.T) assert np.sum(A) == nbonds * 2 G = graph.graph_from_adjacency_matrix(A) assert graph.num_vertices(G) == natoms assert graph.num_edges(G) == nbonds
def test_adjacency_matrix_from_atomic_coordinates_distance() -> None: # Lithium hydride (LiH) # H and Li have very different covalent radii atomicnums = np.array([1, 3]) # Distance is the sum of covalent radii d = sum([constants.anum_to_covalentradius[anum] for anum in atomicnums]) # Distance between two atoms is barely enough to create a bond # If the covalent radii are not correct, no bond will be created coordinates = np.array( [[0, 0, 0], [0, 0, d + constants.connectivity_tolerance - 0.01]]) A = graph.adjacency_matrix_from_atomic_coordinates(atomicnums, coordinates) G = graph.graph_from_adjacency_matrix(A) assert graph.num_edges(G) == 1
def test_build_graph_node_features(property) -> None: A = np.array([[0, 1, 1], [1, 0, 0], [1, 0, 1]]) G = graph.graph_from_adjacency_matrix(A, property) assert graph.num_edges(G) == 3
def _rmsd_isomorphic_core( coords1: np.ndarray, coords2: np.ndarray, aprops1: np.ndarray, aprops2: np.ndarray, am1: np.ndarray, am2: np.ndarray, center: bool = False, minimize: bool = False, isomorphisms: Optional[List[Tuple[List[int], List[int]]]] = None, atol: float = 1e-9, ) -> Tuple[float, List[Tuple[List[int], List[int]]]]: """ Compute RMSD using graph isomorphism. Parameters ---------- coords1: np.ndarray Coordinate of molecule 1 coords2: np.ndarray Coordinates of molecule 2 aprops1: np.ndarray Atomic properties for molecule 1 aprops2: np.ndarray Atomic properties for molecule 2 am1: np.ndarray Adjacency matrix for molecule 1 am2: np.ndarray Adjacency matrix for molecule 2 center: bool Centering flag minimize: bool Compute minized RMSD isomorphisms: Optional[List[Dict[int,int]]] Previously computed graph isomorphism atol: float Absolute tolerance parameter for QCP (see :func:`qcp_rmsd`) Returns ------- Tuple[float, List[Dict[int, int]]] RMSD (after graph matching) and graph isomorphisms """ assert coords1.shape == coords2.shape n = coords1.shape[0] # Center coordinates if required c1 = utils.center(coords1) if center or minimize else coords1 c2 = utils.center(coords2) if center or minimize else coords2 # No cached isomorphisms if isomorphisms is None: # Convert molecules to graphs G1 = graph.graph_from_adjacency_matrix(am1, aprops1) G2 = graph.graph_from_adjacency_matrix(am2, aprops2) # Get all the possible graph isomorphisms isomorphisms = graph.match_graphs(G1, G2) # Minimum result # Squared displacement (not minimize) or RMSD (minimize) min_result = np.inf # Loop over all graph isomorphisms to find the lowest RMSD for idx1, idx2 in isomorphisms: # Use the isomorphism to shuffle coordinates around (from original order) c1i = c1[idx1, :] c2i = c2[idx2, :] if not minimize: # Compute square displacement # Avoid dividing by n and an expensive sqrt() operation result = np.sum((c1i - c2i)**2) else: # Compute minimized RMSD using QCP result = qcp.qcp_rmsd(c1i, c2i, atol) min_result = result if result < min_result else min_result if not minimize: # Compute actual RMSD from square displacement min_result = np.sqrt(min_result / n) # Return the actual RMSD return min_result, isomorphisms