| """ |
| Copyright (c) Facebook, Inc. and its affiliates. |
| Copyright (c) Microsoft Corporation. |
| Licensed under the MIT License. |
| Adapted from https://github.com/FAIR-Chem/fairchem/blob/main/src/fairchem/core/models/gemnet/utils.py. |
| """ |
|
|
| import json |
| from typing import Any, Dict, Optional, Tuple |
|
|
| import torch |
| from torch_scatter import segment_csr |
|
|
|
|
| def read_json(path: str) -> Dict: |
| """""" |
| if not path.endswith(".json"): |
| raise UserWarning(f"Path {path} is not a json-path.") |
|
|
| with open(path, "r") as f: |
| content = json.load(f) |
| return content |
|
|
|
|
| def update_json(path: str, data: Dict): |
| """""" |
| if not path.endswith(".json"): |
| raise UserWarning(f"Path {path} is not a json-path.") |
|
|
| content = read_json(path) |
| content.update(data) |
| write_json(path, content) |
|
|
|
|
| def write_json(path: str, data: Dict): |
| """""" |
| if not path.endswith(".json"): |
| raise UserWarning(f"Path {path} is not a json-path.") |
|
|
| with open(path, "w", encoding="utf-8") as f: |
| json.dump(data, f, ensure_ascii=False, indent=4) |
|
|
|
|
| def read_value_json(path: str, key: str) -> Optional[Any]: |
| """""" |
| content = read_json(path) |
|
|
| if key in content.keys(): |
| return content[key] |
| else: |
| return None |
|
|
|
|
| def ragged_range(sizes: torch.Tensor) -> torch.Tensor: |
| """Multiple concatenated ranges. |
| |
| Examples |
| -------- |
| sizes = [1 4 2 3] |
| Return: [0 0 1 2 3 0 1 0 1 2] |
| """ |
| assert sizes.dim() == 1 |
| if sizes.sum() == 0: |
| return sizes.new_empty(0) |
|
|
| |
| sizes_nonzero = sizes > 0 |
| if not torch.all(sizes_nonzero): |
| sizes = torch.masked_select(sizes, sizes_nonzero) |
|
|
| |
| |
| id_steps = torch.ones(sizes.sum(), dtype=torch.long, device=sizes.device) |
| id_steps[0] = 0 |
| insert_index = sizes[:-1].cumsum(0) |
| insert_val = (1 - sizes)[:-1] |
|
|
| |
| id_steps[insert_index] = insert_val |
|
|
| |
| res = id_steps.cumsum(0) |
| return res |
|
|
|
|
| def repeat_blocks( |
| sizes: torch.Tensor, |
| repeats: torch.Tensor, |
| continuous_indexing: bool = True, |
| start_idx: int = 0, |
| block_inc: int = 0, |
| repeat_inc: int = 0, |
| ) -> torch.Tensor: |
| """Repeat blocks of indices. |
| Adapted from https://stackoverflow.com/questions/51154989/numpy-vectorized-function-to-repeat-blocks-of-consecutive-elements |
| |
| continuous_indexing: Whether to keep increasing the index after each block |
| start_idx: Starting index |
| block_inc: Number to increment by after each block, |
| either global or per block. Shape: len(sizes) - 1 |
| repeat_inc: Number to increment by after each repetition, |
| either global or per block |
| |
| Examples |
| -------- |
| sizes = [1,3,2] ; repeats = [3,2,3] ; continuous_indexing = False |
| Return: [0 0 0 0 1 2 0 1 2 0 1 0 1 0 1] |
| sizes = [1,3,2] ; repeats = [3,2,3] ; continuous_indexing = True |
| Return: [0 0 0 1 2 3 1 2 3 4 5 4 5 4 5] |
| sizes = [1,3,2] ; repeats = [3,2,3] ; continuous_indexing = True ; |
| repeat_inc = 4 |
| Return: [0 4 8 1 2 3 5 6 7 4 5 8 9 12 13] |
| sizes = [1,3,2] ; repeats = [3,2,3] ; continuous_indexing = True ; |
| start_idx = 5 |
| Return: [5 5 5 6 7 8 6 7 8 9 10 9 10 9 10] |
| sizes = [1,3,2] ; repeats = [3,2,3] ; continuous_indexing = True ; |
| block_inc = 1 |
| Return: [0 0 0 2 3 4 2 3 4 6 7 6 7 6 7] |
| sizes = [0,3,2] ; repeats = [3,2,3] ; continuous_indexing = True |
| Return: [0 1 2 0 1 2 3 4 3 4 3 4] |
| sizes = [2,3,2] ; repeats = [2,0,2] ; continuous_indexing = True |
| Return: [0 1 0 1 5 6 5 6] |
| """ |
| assert sizes.dim() == 1 |
| assert all(sizes >= 0) |
|
|
| |
| sizes_nonzero = sizes > 0 |
| if not torch.all(sizes_nonzero): |
| assert block_inc == 0 |
| sizes = torch.masked_select(sizes, sizes_nonzero) |
| if isinstance(repeats, torch.Tensor): |
| repeats = torch.masked_select(repeats, sizes_nonzero) |
| if isinstance(repeat_inc, torch.Tensor): |
| repeat_inc = torch.masked_select(repeat_inc, sizes_nonzero) |
|
|
| if isinstance(repeats, torch.Tensor): |
| assert all(repeats >= 0) |
| insert_dummy = repeats[0] == 0 |
| if insert_dummy: |
| one = sizes.new_ones(1) |
| zero = sizes.new_zeros(1) |
| sizes = torch.cat((one, sizes)) |
| repeats = torch.cat((one, repeats)) |
| if isinstance(block_inc, torch.Tensor): |
| block_inc = torch.cat((zero, block_inc)) |
| if isinstance(repeat_inc, torch.Tensor): |
| repeat_inc = torch.cat((zero, repeat_inc)) |
| else: |
| assert repeats >= 0 |
| insert_dummy = False |
|
|
| |
| r1 = torch.repeat_interleave(torch.arange(len(sizes), device=sizes.device), repeats) |
|
|
| |
| N = (sizes * repeats).sum() |
|
|
| |
| |
| |
| |
| |
| id_ar = torch.ones(N, dtype=torch.long, device=sizes.device) |
| id_ar[0] = 0 |
| insert_index = sizes[r1[:-1]].cumsum(0) |
| insert_val = (1 - sizes)[r1[:-1]] |
|
|
| if isinstance(repeats, torch.Tensor) and torch.any(repeats == 0): |
| diffs = r1[1:] - r1[:-1] |
| indptr = torch.cat((sizes.new_zeros(1), diffs.cumsum(0))) |
| if continuous_indexing: |
| |
| insert_val += segment_csr(sizes[: r1[-1]], indptr, reduce="sum") |
|
|
| |
| if isinstance(block_inc, torch.Tensor): |
| insert_val += segment_csr(block_inc[: r1[-1]], indptr, reduce="sum") |
| else: |
| insert_val += block_inc * (indptr[1:] - indptr[:-1]) |
| if insert_dummy: |
| insert_val[0] -= block_inc |
| else: |
| idx = r1[1:] != r1[:-1] |
| if continuous_indexing: |
| |
| |
| insert_val[idx] = 1 |
|
|
| |
| insert_val[idx] += block_inc |
|
|
| |
| if isinstance(repeat_inc, torch.Tensor): |
| insert_val += repeat_inc[r1[:-1]] |
| if isinstance(repeats, torch.Tensor): |
| repeat_inc_inner = repeat_inc[repeats > 0][:-1] |
| else: |
| repeat_inc_inner = repeat_inc[:-1] |
| else: |
| insert_val += repeat_inc |
| repeat_inc_inner = repeat_inc |
|
|
| |
| if isinstance(repeats, torch.Tensor): |
| repeats_inner = repeats[repeats > 0][:-1] |
| else: |
| repeats_inner = repeats |
| insert_val[r1[1:] != r1[:-1]] -= repeat_inc_inner * repeats_inner |
|
|
| |
| id_ar[insert_index] = insert_val |
|
|
| if insert_dummy: |
| id_ar = id_ar[1:] |
| if continuous_indexing: |
| id_ar[0] -= 1 |
|
|
| |
| id_ar[0] += start_idx |
|
|
| |
| res = id_ar.cumsum(0) |
| return res |
|
|
|
|
| def calculate_interatomic_vectors( |
| R: torch.Tensor, id_s: torch.Tensor, id_t: torch.Tensor, offsets_st: torch.Tensor |
| ) -> Tuple[torch.Tensor, torch.Tensor]: |
| """ |
| Calculate the vectors connecting the given atom pairs, |
| considering offsets from periodic boundary conditions (PBC). |
| |
| Parameters |
| ---------- |
| R: Tensor, shape = (nAtoms, 3) |
| Atom positions. |
| id_s: Tensor, shape = (nEdges,) |
| Indices of the source atom of the edges. |
| id_t: Tensor, shape = (nEdges,) |
| Indices of the target atom of the edges. |
| offsets_st: Tensor, shape = (nEdges,) |
| PBC offsets of the edges. |
| Subtract this from the correct direction. |
| |
| Returns |
| ------- |
| (D_st, V_st): tuple |
| D_st: Tensor, shape = (nEdges,) |
| Distance from atom t to s. |
| V_st: Tensor, shape = (nEdges,) |
| Unit direction from atom t to s. |
| """ |
| Rs = R[id_s] |
| Rt = R[id_t] |
| |
| if offsets_st is None: |
| V_st = Rt - Rs |
| else: |
| V_st = Rt - Rs + offsets_st |
| D_st = torch.sqrt(torch.sum(V_st**2, dim=1)) |
| V_st = V_st / D_st[..., None] |
| return D_st, V_st |
|
|
|
|
| def inner_product_normalized(x: torch.Tensor, y: torch.Tensor) -> torch.Tensor: |
| """ |
| Calculate the inner product between the given normalized vectors, |
| giving a result between -1 and 1. |
| """ |
| return torch.sum(x * y, dim=-1).clamp(min=-1, max=1) |
|
|
|
|
| def mask_neighbors(neighbors: torch.Tensor, edge_mask: torch.Tensor) -> torch.Tensor: |
| neighbors_old_indptr = torch.cat([neighbors.new_zeros(1), neighbors]) |
| neighbors_old_indptr = torch.cumsum(neighbors_old_indptr, dim=0) |
| neighbors = segment_csr(edge_mask.long(), neighbors_old_indptr) |
| return neighbors |
|
|
|
|
| def get_k_index_product_set( |
| num_k_x: torch.LongTensor, num_k_y: torch.LongTensor, num_k_z: torch.LongTensor |
| ) -> tuple[torch.FloatTensor, int]: |
| |
| k_index_sets = ( |
| torch.arange(-num_k_x, num_k_x + 1, dtype=torch.float), |
| torch.arange(-num_k_y, num_k_y + 1, dtype=torch.float), |
| torch.arange(-num_k_z, num_k_z + 1, dtype=torch.float), |
| ) |
| k_index_product_set = torch.cartesian_prod(*k_index_sets) |
| |
| |
| |
| k_index_product_set = k_index_product_set[k_index_product_set.shape[0] // 2 + 1 :] |
|
|
| |
| num_k_degrees_of_freedom = k_index_product_set.shape[0] |
|
|
| return k_index_product_set, num_k_degrees_of_freedom |
|
|