File size: 2,271 Bytes
072f65e
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
"""
Grid search for accessible positions


This script is heavily adapted from the `DAC-SIM <https://github.com/hspark1212/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,
    }