""" Grid search for accessible positions This script is heavily adapted from the `DAC-SIM `_ package. Please cite the original work if you use this script. References ~~~~~~~~~~~ - Lim, Y., Park, H., Walsh, A., & Kim, J. (2024). Accelerating CO₂ Direct Air Capture Screening for Metal-Organic Frameworks with a Transferable Machine Learning Force Field. """ import MDAnalysis as mda import numpy as np from ase import Atoms def get_accessible_positions( structure: Atoms, grid_spacing: float = 0.5, cutoff_distance: float = 10.0, min_interplanar_distance: float = 2.0, ) -> dict: # get the supercell structure cell_volume = structure.get_volume() cell_vectors = np.array(structure.cell) dist_a = cell_volume / np.linalg.norm(np.cross(cell_vectors[1], cell_vectors[2])) dist_b = cell_volume / np.linalg.norm(np.cross(cell_vectors[2], cell_vectors[0])) dist_c = cell_volume / np.linalg.norm(np.cross(cell_vectors[0], cell_vectors[1])) plane_distances = np.array([dist_a, dist_b, dist_c]) supercell = np.ceil(min_interplanar_distance / plane_distances).astype(int) if np.any(supercell > 1): print( f"Making supercell: {supercell} to prevent interplanar distance < {min_interplanar_distance}" ) structure = structure.repeat(supercell) # get position for grid grid_size = np.ceil(np.array(structure.cell.cellpar()[:3]) / grid_spacing).astype( int ) indices = np.indices(grid_size).reshape(3, -1).T # (G, 3) pos_grid = indices.dot(cell_vectors / grid_size) # (G, 3) # get positions for atoms pos_atoms = structure.get_positions() # (N, 3) # distance matrix dist_matrix = mda.lib.distances.distance_array( pos_grid, pos_atoms, box=structure.cell.cellpar() ) # (G, N) # TODO: check if we could use other packages instead of mda # calculate the accessible positions min_dist = np.min(dist_matrix, axis=1) # (G,) idx_accessible_pos = np.where(min_dist > cutoff_distance)[0] # result return { "pos_grid": pos_grid, "idx_accessible_pos": idx_accessible_pos, "accessible_pos": pos_grid[idx_accessible_pos], "structure": structure, }