BUCToolkit 1.0b0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (88) hide show
  1. BUCToolkit/BatchGenerate/CheckStructures.py +198 -0
  2. BUCToolkit/BatchGenerate/__init__.py +6 -0
  3. BUCToolkit/BatchGenerate/coords_interp.py +127 -0
  4. BUCToolkit/BatchGenerate/initial_ads_structures.py +351 -0
  5. BUCToolkit/BatchGenerate/random_replace.py +88 -0
  6. BUCToolkit/BatchMC/MetropolisMC.py +210 -0
  7. BUCToolkit/BatchMC/_BaseMC.py +863 -0
  8. BUCToolkit/BatchMC/__init__.py +8 -0
  9. BUCToolkit/BatchMD/BiasedMD.py +276 -0
  10. BUCToolkit/BatchMD/NVE.py +92 -0
  11. BUCToolkit/BatchMD/NVT.py +510 -0
  12. BUCToolkit/BatchMD/_BaseMD.py +1251 -0
  13. BUCToolkit/BatchMD/__init__.py +18 -0
  14. BUCToolkit/BatchMD/constrained_md/BiasedMD.py +273 -0
  15. BUCToolkit/BatchMD/constrained_md/ConstrNVE.py +169 -0
  16. BUCToolkit/BatchMD/constrained_md/ConstrNVT.py +462 -0
  17. BUCToolkit/BatchMD/constrained_md/NVE.py +78 -0
  18. BUCToolkit/BatchMD/constrained_md/NVT.py +346 -0
  19. BUCToolkit/BatchMD/constrained_md/_BaseMD.py +440 -0
  20. BUCToolkit/BatchMD/constrained_md/_ConstrBaseMD.py +607 -0
  21. BUCToolkit/BatchMD/constrained_md/__init__.py +18 -0
  22. BUCToolkit/BatchOptim/TS/CI_NEB.py +286 -0
  23. BUCToolkit/BatchOptim/TS/Dimer.py +950 -0
  24. BUCToolkit/BatchOptim/TS/__init__.py +10 -0
  25. BUCToolkit/BatchOptim/_BaseOpt.py +702 -0
  26. BUCToolkit/BatchOptim/__init__.py +22 -0
  27. BUCToolkit/BatchOptim/_utils/__init__.py +6 -0
  28. BUCToolkit/BatchOptim/_utils/_line_search.py +972 -0
  29. BUCToolkit/BatchOptim/_utils/_warnings.py +11 -0
  30. BUCToolkit/BatchOptim/frequency.py +496 -0
  31. BUCToolkit/BatchOptim/minimize/CG.py +256 -0
  32. BUCToolkit/BatchOptim/minimize/FIRE.py +310 -0
  33. BUCToolkit/BatchOptim/minimize/QN.py +205 -0
  34. BUCToolkit/BatchOptim/minimize/__init__.py +17 -0
  35. BUCToolkit/BatchStructures/BatchStructuresBase.py +2261 -0
  36. BUCToolkit/BatchStructures/StructuresIO.py +1394 -0
  37. BUCToolkit/BatchStructures/__init__.py +15 -0
  38. BUCToolkit/Preprocessing/__init__.py +7 -0
  39. BUCToolkit/Preprocessing/load_files.py +1680 -0
  40. BUCToolkit/Preprocessing/preprocessing.py +923 -0
  41. BUCToolkit/Preprocessing/sampling.py +299 -0
  42. BUCToolkit/Preprocessing/supercells.py +140 -0
  43. BUCToolkit/Preprocessing/write_files.py +524 -0
  44. BUCToolkit/StandardTemplate.py +417 -0
  45. BUCToolkit/__init__.py +33 -0
  46. BUCToolkit/_version.py +7 -0
  47. BUCToolkit/api/ConstrainedMolecularDynamics.py +347 -0
  48. BUCToolkit/api/DataLoaders.py +321 -0
  49. BUCToolkit/api/Losses.py +170 -0
  50. BUCToolkit/api/Metrics.py +84 -0
  51. BUCToolkit/api/ModelOptims.py +296 -0
  52. BUCToolkit/api/MolecularDynamics.py +282 -0
  53. BUCToolkit/api/MonteCarlo.py +280 -0
  54. BUCToolkit/api/NEB.py +325 -0
  55. BUCToolkit/api/Predictor.py +292 -0
  56. BUCToolkit/api/StructureOptimization.py +484 -0
  57. BUCToolkit/api/Trainer.py +635 -0
  58. BUCToolkit/api/VibrationAnalysis.py +279 -0
  59. BUCToolkit/api/__init__.py +38 -0
  60. BUCToolkit/api/_io.py +1026 -0
  61. BUCToolkit/cli/__init__.py +20 -0
  62. BUCToolkit/cli/cli.py +934 -0
  63. BUCToolkit/cli/input_stub.py +140 -0
  64. BUCToolkit/cli/main.py +368 -0
  65. BUCToolkit/cli/print_logo.py +149 -0
  66. BUCToolkit/cli/template_model.py +6 -0
  67. BUCToolkit/io.py +30 -0
  68. BUCToolkit/utils/AtomSelector.py +64 -0
  69. BUCToolkit/utils/AtomicNumber2Properties.py +39 -0
  70. BUCToolkit/utils/ElemListReduce.py +52 -0
  71. BUCToolkit/utils/Ewald.py +210 -0
  72. BUCToolkit/utils/IrregularTensorReformat.py +59 -0
  73. BUCToolkit/utils/_CheckModules.py +30 -0
  74. BUCToolkit/utils/_Element_info.py +527 -0
  75. BUCToolkit/utils/__init__.py +22 -0
  76. BUCToolkit/utils/_para_flatt_list.py +131 -0
  77. BUCToolkit/utils/_print_formatter.py +59 -0
  78. BUCToolkit/utils/check_structures.py +204 -0
  79. BUCToolkit/utils/function_utils.py +147 -0
  80. BUCToolkit/utils/grad_functions.py +69 -0
  81. BUCToolkit/utils/index_ops.py +432 -0
  82. BUCToolkit/utils/setup_loggers.py +137 -0
  83. buctoolkit-1.0b0.dist-info/METADATA +28 -0
  84. buctoolkit-1.0b0.dist-info/RECORD +88 -0
  85. buctoolkit-1.0b0.dist-info/WHEEL +5 -0
  86. buctoolkit-1.0b0.dist-info/entry_points.txt +6 -0
  87. buctoolkit-1.0b0.dist-info/licenses/LICENSE +21 -0
  88. buctoolkit-1.0b0.dist-info/top_level.txt +1 -0
BUCToolkit/io.py ADDED
@@ -0,0 +1,30 @@
1
+ """
2
+ A fast API for all i/o operations
3
+ """
4
+ # Copyright (c) 2026.4.13, BUCToolkit.
5
+ # Authors: Pu Pengxin, Song Xin
6
+ # Version: 1.0b
7
+ # File: io.py
8
+ # Environment: Python 3.12
9
+
10
+ from BUCToolkit.Preprocessing import load_files, write_files
11
+ from BUCToolkit.Preprocessing.load_files import OUTCAR2Feat, Xyz2Feat, ExtXyz2Feat, POSCARs2Feat, ASETraj2Feat, Cif2Feat, load_from_structures
12
+ from BUCToolkit.BatchStructures.StructuresIO import read_opt_structures, read_mc_traj, read_md_traj, ArrayDumpReader, ArrayDumper
13
+
14
+ __all__ = [
15
+ 'OUTCAR2Feat',
16
+ 'Xyz2Feat',
17
+ 'ExtXyz2Feat',
18
+ 'POSCARs2Feat',
19
+ 'ASETraj2Feat',
20
+ 'Cif2Feat',
21
+ 'read_opt_structures',
22
+ 'read_md_traj',
23
+ 'read_mc_traj',
24
+ 'ArrayDumpReader',
25
+ 'ArrayDumper',
26
+ 'load_from_structures',
27
+ 'load_files',
28
+ 'write_files',
29
+ ]
30
+
@@ -0,0 +1,64 @@
1
+ """ Selecting atoms by given conditions """
2
+ # Copyright (c) 2025.11.26, BUCToolkit.
3
+ # Authors: Pu Pengxin, Song Xin
4
+ # Version: 0.9a
5
+ # File: AtomSelector.py
6
+ # Environment: Python 3.12
7
+
8
+ import torch as th
9
+ from typing import Sequence, List, Literal
10
+ from ._Element_info import ATOMIC_SYMBOL
11
+
12
+ def atom_fix_selector(
13
+ mask: th.Tensor,
14
+ elements: th.Tensor,
15
+ coords: th.Tensor,
16
+ select_element: Sequence[str | int] | None = None,
17
+ select_height: float | None = None,
18
+ atom_index: List[List[float]] | None = None,
19
+ select_mode: Literal['fix', 'inv_fix', 'free'] = 'fix'
20
+ ) -> th.Tensor:
21
+ """
22
+ Selecting which atoms are fixed and updating the input mask accordingly.
23
+ Args:
24
+ mask:
25
+ elements:
26
+ coords:
27
+ select_element:
28
+ select_height:
29
+ atom_index:
30
+ select_mode:
31
+ fix: the atoms satisfied given conditions will be set to 0 in given mask, and others do not change.
32
+ inv_fix: only the atoms satisfied given conditions will be set to 1 in given mask, and others set to 0.
33
+ free: the atoms satisfied given conditions will be set to 1 in given mask, and others do not change.
34
+
35
+ Returns:
36
+
37
+ """
38
+ # check
39
+ if mask.shape != coords.shape:
40
+ raise ValueError(f'Expected `mask` has the same shape as `coords` ({coords.shape}), but got {mask.shape}.')
41
+ if elements.shape != (mask.shape[0], ):
42
+ raise ValueError(f'Expected `elements` has the same shape as the 1st dim of `mask` ({mask.shape[0]}), but got {elements.shape}.')
43
+
44
+ # initialize
45
+ FIX_DICT = {'fix': 0, 'inv_fix': 1, 'free': 1}
46
+ fill_elem = FIX_DICT[select_mode]
47
+ if select_mode == 'inv_fix':
48
+ _mask = th.zeros_like(mask).to(th.int8)
49
+ else:
50
+ _mask = th.where(mask.to(th.int8) == 0, 0, 1).to(th.int8)
51
+ elements = elements.unsqueeze(-1).broadcast_to(mask.shape)
52
+
53
+ # main
54
+ if select_element is not None:
55
+ sel_elem = [_ if isinstance(_, int) else ATOMIC_SYMBOL[_] for _ in select_element]
56
+ for elem in sel_elem:
57
+ _mask = th.where(elements == elem, fill_elem, _mask)
58
+ if select_height is not None:
59
+ _mask = th.where(coords[:, -1].unsqueeze(-1) <= select_height, fill_elem, _mask)
60
+ if atom_index is not None:
61
+ atom_index = th.tensor(atom_index, dtype=th.int64, device=_mask.device)
62
+ _mask[atom_index] = fill_elem
63
+
64
+ return _mask
@@ -0,0 +1,39 @@
1
+ """ Convert from atomic number to various properties, e.g., masses, elements."""
2
+
3
+ # Copyright (c) 2025.7.4, BUCToolkit.
4
+ # Authors: Pu Pengxin, Song Xin
5
+ # Version: 0.9a
6
+ # File: AtomicNumber2Properties.py
7
+ # Environment: Python 3.12
8
+
9
+ from typing import Sequence, List
10
+ from BUCToolkit.utils._Element_info import MASS, N_MASS, ATOMIC_SYMBOL, ATOMIC_NUMBER
11
+ import numpy as np
12
+
13
+
14
+ def atomic_numbers_to_masses(Z: Sequence[int]) -> List[float]:
15
+ """Convert atomic numbers to masses."""
16
+ mass = [N_MASS[int(_)] for _ in Z]
17
+ return mass
18
+
19
+
20
+ def atomic_numbers_to_elements(Z: Sequence[int]) -> List[int]:
21
+ elem = [ATOMIC_NUMBER[int(_)] for _ in Z]
22
+ return elem
23
+
24
+
25
+ def elements_to_atomic_numbers(Z: Sequence[str]) -> List[int]:
26
+ atomic_numbers = [ATOMIC_SYMBOL[str(_)] for _ in Z]
27
+ return atomic_numbers
28
+
29
+
30
+ __all__ = [
31
+ "atomic_numbers_to_masses",
32
+ "atomic_numbers_to_elements",
33
+ "elements_to_atomic_numbers",
34
+ ]
35
+
36
+ if __name__ == '__main__':
37
+ z = [1, 2, 5, 3, 66, 32]
38
+ m = atomic_numbers_to_masses(z)
39
+ pass
@@ -0,0 +1,52 @@
1
+ # Copyright (c) 2025.7.4, BUCToolkit.
2
+ # Authors: Pu Pengxin, Song Xin
3
+ # Version: 0.9a
4
+ # File: ElemListReduce.py
5
+ # Environment: Python 3.12
6
+ """
7
+ Convert a dense element list (atomic number corresponds to coordinates one by one) into VASP-like compressed format.
8
+ """
9
+ from typing import List, Tuple
10
+ from ._Element_info import ATOMIC_NUMBER, ATOMIC_SYMBOL
11
+ import numpy as np
12
+
13
+
14
+ def elem_list_reduce(
15
+ atom_list: List[str|int]|np.ndarray
16
+ ) -> Tuple[List[str], List[int], List[int]]:
17
+ """
18
+ Convert a dense element list (atomic number corresponds to coordinates one by one) into VASP-like compressed format.
19
+
20
+ Args:
21
+ atom_list:
22
+
23
+ Returns: element symbol list, atomic numbers list, atom number (count) list
24
+
25
+ """
26
+ if isinstance(atom_list, np.ndarray): atom_list = atom_list.tolist()
27
+ _elem_old = ATOMIC_NUMBER[atom_list[0]] if isinstance(atom_list[0], int) else atom_list[0]
28
+ elements = [_elem_old, ]
29
+ atomic_numbers = [ATOMIC_SYMBOL[_elem_old], ]
30
+ elem_numbers = list()
31
+ count_i = 0
32
+ for elem in atom_list:
33
+ if isinstance(elem, int):
34
+ elem_symb = ATOMIC_NUMBER[elem]
35
+ elem_numb = elem
36
+ elif isinstance(elem, str):
37
+ elem_symb = elem
38
+ elem_numb = ATOMIC_SYMBOL[elem]
39
+ else:
40
+ raise TypeError(f'Expected str or int in `atom_list`, but got {type(elem)}.')
41
+
42
+ if elem_symb == _elem_old:
43
+ count_i += 1
44
+ else:
45
+ _elem_old = elem_symb
46
+ elem_numbers.append(count_i)
47
+ elements.append(elem_symb)
48
+ atomic_numbers.append(elem_numb)
49
+ count_i = 1
50
+ elem_numbers.append(count_i)
51
+
52
+ return elements, atomic_numbers, elem_numbers
@@ -0,0 +1,210 @@
1
+ # Copyright (c) 2026.3.17, BUCToolkit.
2
+ # Authors: Pu Pengxin, Song Xin
3
+ # Version: 0.9a
4
+ # File: Ewald.py
5
+ # Environment: Python 3.12
6
+ from typing import Callable, Any, List, Tuple, Optional, Dict
7
+ import torch as th
8
+
9
+
10
+ class GeneralizedEwald:
11
+ """
12
+ A Generalized Ewald Summation Algorithm.
13
+ It calculates as follows:
14
+ \sum_n f(r + n R) = \sum_n {f(r + n R) - (a/\pi)^1.5 \int {f(x) exp(-a (x - r - n R)^2) dx}}
15
+ + 1/V \sum_k {g(k) exp(-k^2/(4 a) + i k \cdot r)}
16
+ """
17
+ def __init__(
18
+ self,
19
+ rfunc: Callable[[th.Tensor, Any, ...], th.Tensor],
20
+ gfunc: Callable[[th.Tensor, Any, ...], th.Tensor],
21
+ rfunc_gaussian_conv: Callable[[th.Tensor, Any, ...], th.Tensor] | None = None,
22
+ fft_grids: th.Tensor | List[int] | Tuple[int, int, int] | None = None,
23
+ alpha: float = 0.1,
24
+ ):
25
+ """
26
+
27
+ Args:
28
+ rfunc: the function in the real space.
29
+ Its first input should be a Tensor X with shape (B, N, 3), and output a Tensor with shape (B, N),
30
+ where `B` is the batch_size, and `N` is the number of feeler points within a sample.
31
+ For each N, it returns the function value of f at the given point X[B_i, N_i, :].
32
+ gfunc: the corresponding function in the reciprocal space. It has the same format as rfunc.
33
+ rfunc_gaussian_conv: the convolution between rfunc and Gaussian function,
34
+ i.e., rfunc_gaussian_conv(r) = (a/\pi)^1.5 \int {f(x) exp(-a (x - r)^2) dx}.
35
+ If None, a numeric FFT would be applied to calculate the reciprocal production instead of real-space convolution.
36
+ fft_grids: shape of (3, ). If `rfunc_gaussian_conv` is None, this argument is to define the FFT grids,
37
+ and gives the value at input point `r` by interpolation. Otherwise, this argument is ignored.
38
+ alpha: the coefficient of Gaussian exponential in Ewald summation.
39
+ """
40
+ self.rfunc = rfunc
41
+ self.gfunc = gfunc
42
+ self.rgc_func = rfunc_gaussian_conv
43
+ if rfunc_gaussian_conv is None:
44
+ fft_grids = th.as_tensor(fft_grids, dtype=th.int64)
45
+ if fft_grids.shape != (3, ):
46
+ raise ValueError(f"`fft_grids` must have the shape of (3, ), but got {fft_grids.shape}.")
47
+ if fft_grids.min() <= 0:
48
+ raise ValueError(f"FFT grids number must be positive, but got {fft_grids.tolist()}.")
49
+ self.fft_grids = fft_grids
50
+ else:
51
+ self.fft_grids = None
52
+
53
+ if alpha <= 1e-7:
54
+ raise ValueError(f"`alpha` must be positive, but got {alpha}.")
55
+ self.alpha = alpha
56
+
57
+ def run(
58
+ self,
59
+ r: th.Tensor,
60
+ R: th.Tensor,
61
+ n_r: int = 20,
62
+ n_g: int | None = None,
63
+ ):
64
+ """
65
+ Run the summation.
66
+ Args:
67
+ r: Tensor(B, N, 3), the coordinates of the feeler points.
68
+ R: Tensor(B, 3, 3), the real-space lattice of each sample in the batch. Each row is a lattice vector.
69
+ n_r: the number of real-space summation terms.
70
+ n_g: the number of reciprocal-space summation terms. If None, it will be set to `n_r`.
71
+
72
+ Returns: Tensor(B, N), the summations.
73
+
74
+ """
75
+ raise NotImplementedError
76
+
77
+ def _run_anal(
78
+ self,
79
+ r: th.Tensor,
80
+ R: th.Tensor,
81
+ rfunc_args: Tuple[Any, ...] = tuple(),
82
+ rfunc_kwargs: Dict[str, Any] | None = None,
83
+ gfunc_args: Tuple[Any, ...] = tuple(),
84
+ gfunc_kwargs: Dict[str, Any] | None = None,
85
+ n_r: int = 20,
86
+ n_g: Optional[int] = None,
87
+ chunk_size: int = 10000, # 分块大小,避免内存溢出
88
+ ) -> th.Tensor:
89
+ """
90
+ Run the Ewald summation using analytical convolution function.
91
+
92
+ Args:
93
+ r: (B, N, 3) query points.
94
+ R: (B, 3, 3) real-space lattice matrices (each row is a lattice vector).
95
+ n_r: number of real-space cells in each direction (half-width). Total cells = (2*n_r+1)^3.
96
+ rfunc_args:
97
+ rfunc_kwargs:
98
+ gfunc_args:
99
+ gfunc_kwargs:
100
+ n_g: number of reciprocal-space cells in each direction. If None, set to n_r.
101
+ chunk_size: max number of real-space cells processed at once to control memory.
102
+
103
+ Returns:
104
+ (B, N) tensor of summed values.
105
+ """
106
+ if rfunc_kwargs is None:
107
+ rfunc_kwargs = {}
108
+ if gfunc_kwargs is None:
109
+ gfunc_kwargs = {}
110
+
111
+ device = r.device
112
+ B, N, _ = r.shape
113
+ n_g = n_g if n_g is not None else n_r
114
+
115
+ # ------------------------------------------------------------------
116
+ # 1. Real-space part: sum over lattice vectors nR
117
+ # ------------------------------------------------------------------
118
+ # Generate integer lattice indices (i,j,k) for real space
119
+ idx_r = th.arange(-n_r, n_r + 1, device=device)
120
+ grid_r = th.stack(th.meshgrid(idx_r, idx_r, idx_r, indexing='ij'), dim=-1) # (Mr,3)
121
+ Mr = grid_r.shape[0] # = (2*n_r+1)^3
122
+ grid_r = grid_r.view(-1, 3) # (Mr, 3)
123
+
124
+ # Precompute offsets = i*R[:,0] + j*R[:,1] + k*R[:,2] for all batch items
125
+ # offsets shape: (B, Mr, 3)
126
+ offsets = th.einsum('mi,bij->bmj', grid_r, R) # sum over i (grid dim), multiply with R rows
127
+
128
+ # Prepare real-space sum accumulator
129
+ real_sum = th.zeros(B, N, device=device)
130
+
131
+ # Process real-space cells in chunks to avoid OOM
132
+ for start in range(0, Mr, chunk_size):
133
+ end = min(start + chunk_size, Mr)
134
+ offsets_chunk = offsets[:, start:end, :] # (B, chunk, 3)
135
+
136
+ # Shift query points by each offset: (B, N, chunk, 3)
137
+ r_shifted = r.unsqueeze(2) + offsets_chunk.unsqueeze(1)
138
+
139
+ # Reshape to (B, N*chunk, 3) for function evaluation
140
+ r_flat = r_shifted.reshape(B, N * (end - start), 3)
141
+
142
+ # Evaluate f and its Gaussian convolution
143
+ f_vals = self.rfunc(r_flat).reshape(B, N, end - start) # (B, N, chunk)
144
+ conv_vals = self.rgc_func(r_flat).reshape(B, N, end - start) # (B, N, chunk)
145
+
146
+ # Short-range contribution: f - conv, sum over chunk
147
+ real_sum += (f_vals - conv_vals).sum(dim=2)
148
+
149
+ # ------------------------------------------------------------------
150
+ # 2. Reciprocal-space part: sum over reciprocal vectors k
151
+ # ------------------------------------------------------------------
152
+ # Compute cell volume V = |det(R)|
153
+ det_R = th.det(R) # (B,)
154
+ V = det_R.abs().unsqueeze(-1).unsqueeze(-1) # (B,1,1) for broadcasting
155
+
156
+ # Compute reciprocal lattice vectors (rows of the matrix)
157
+ # b1 = 2π * (a2 × a3) / V, b2 = 2π * (a3 × a1) / V, b3 = 2π * (a1 × a2) / V
158
+ a1 = R[:, 0, :] # (B,3)
159
+ a2 = R[:, 1, :] # (B,3)
160
+ a3 = R[:, 2, :] # (B,3)
161
+ cross_a2_a3 = th.cross(a2, a3, dim=-1) # (B,3)
162
+ cross_a3_a1 = th.cross(a3, a1, dim=-1) # (B,3)
163
+ cross_a1_a2 = th.cross(a1, a2, dim=-1) # (B,3)
164
+
165
+ two_pi = 2 * th.pi
166
+ b1 = two_pi * cross_a2_a3 / V.squeeze(-1) # (B,3)
167
+ b2 = two_pi * cross_a3_a1 / V.squeeze(-1)
168
+ b3 = two_pi * cross_a1_a2 / V.squeeze(-1)
169
+
170
+ # Stack to form reciprocal matrix (B,3,3) where each row is a reciprocal vector
171
+ recip_matrix = th.stack([b1, b2, b3], dim=1) # (B,3,3)
172
+
173
+ # Generate integer indices for reciprocal space
174
+ idx_g = th.arange(-n_g, n_g + 1, device=device)
175
+ grid_g = th.stack(th.meshgrid(idx_g, idx_g, idx_g, indexing='ij'), dim=-1) # (Mg,3)
176
+ Mg = grid_g.shape[0]
177
+ grid_g = grid_g.view(-1, 3) # (Mg,3)
178
+
179
+ # Compute all k vectors: (B, Mg, 3)
180
+ k_vec = th.einsum('mi,bij->bmj', grid_g, recip_matrix)
181
+
182
+ # Compute k^2 = ||k||^2
183
+ k_sq = (k_vec ** 2).sum(dim=-1) # (B, Mg)
184
+
185
+ # Gaussian attenuation factor exp(-k^2/(4α))
186
+ atten = th.exp(-k_sq / (4 * self.alpha)) # (B, Mg)
187
+
188
+ # Evaluate g(k) for all k points
189
+ # gfunc expects input (B, N, 3), but here we have (B, Mg, 3). Mg may be large, so we call in one go.
190
+ g_vals = self.gfunc(k_vec) # (B, Mg) – assuming gfunc returns real or complex tensor
191
+
192
+ # Compute phase factor exp(i k·r) for all query points and all k
193
+ # k·r: (B, Mg, 3) dot (B, N, 3) -> (B, N, Mg)
194
+ k_dot_r = th.einsum('bmj,bnj->bnm', k_vec, r) # (B, N, Mg)
195
+ phase = th.exp(1j * k_dot_r) # complex (B, N, Mg)
196
+
197
+ # Combine: g(k) * attenuation * phase, sum over k, and divide by V
198
+ # g_vals may be real or complex; we promote to complex if needed.
199
+ if not th.is_complex(g_vals):
200
+ g_vals = g_vals.to(th.complex64)
201
+ contrib = g_vals.unsqueeze(1) * atten.unsqueeze(1) * phase # (B, N, Mg)
202
+ recip_sum = contrib.sum(dim=2) / V # (B, N) complex
203
+
204
+ # Take real part (should be real due to symmetry, but numerically small imaginary may remain)
205
+ recip_sum = recip_sum.real
206
+
207
+ # ------------------------------------------------------------------
208
+ # 3. Combine and return
209
+ # ------------------------------------------------------------------
210
+ return real_sum + recip_sum
@@ -0,0 +1,59 @@
1
+ """ regularize Sequence of Tensors by padding """
2
+ # Copyright (c) 2024-2025.7.4, BUCToolkit.
3
+ # Authors: Pu Pengxin, Song Xin
4
+ # Version: 0.9a
5
+ # File: IrregularTensorReformat.py
6
+ # Environment: Python 3.12
7
+
8
+ from typing import List, Tuple
9
+
10
+ import torch as th
11
+
12
+
13
+ class IrregularTensorReformat:
14
+ """
15
+ Regularize List[Tensor] into one Tensor by padding, and then recover them into List[Tensor].
16
+ The input tensors can only be irregular at 1st dim.
17
+ """
18
+ def __init__(self, ):
19
+ self._length_record = None
20
+ self.__shape_check = None
21
+
22
+ def regularize(self, X: List[th.Tensor], padding_value:float=0.) -> Tuple[th.Tensor, th.Tensor]:
23
+ """
24
+ Regularize the List of Tensors.
25
+ Args:
26
+ X: the input sequence of Tensors.
27
+ padding_value: the padding_value.
28
+
29
+ Returns:
30
+ X_r: Tensor, the regularized tensor.
31
+ mask: Tensor, the mask of paddings. The part of `False` is paddings.
32
+ """
33
+ if th.any(th.isnan(th.cat(X, dim=0))):
34
+ raise ValueError('Occurred NaN in input `X`.')
35
+ self._length_record = [len(xi) for xi in X]
36
+ # padding
37
+ X = th.nn.utils.rnn.pad_sequence(X, True, padding_value=padding_value)
38
+ pad_mask = ~th.isnan(X)
39
+ # shape check
40
+ self.__shape_check = len(X)
41
+
42
+ return X, pad_mask
43
+
44
+ def recover(self, X:th.Tensor) -> List[th.Tensor]:
45
+ """
46
+ Recover the regularized tensor into List[Tensor]
47
+ Args:
48
+ X: the Tensor that have same shape of X
49
+ Returns:
50
+ List[Tensor], the recovered Tensors.
51
+ """
52
+ if self._length_record is None:
53
+ raise RuntimeError('`X` has not regularized yet.')
54
+ elif len(X) != self.__shape_check:
55
+ raise ValueError(f'Wrong shape of `X`. The length of `X` should be {self.__shape_check}, but occurred {len(X)}.')
56
+
57
+ X = [X[i, :self._length_record[i]] for i in range(self.__shape_check)]
58
+
59
+ return X
@@ -0,0 +1,30 @@
1
+ """ check whether imported module exists """
2
+ # Copyright (c) 2024-2025.7.4, BUCToolkit.
3
+ # Authors: Pu Pengxin, Song Xin
4
+ # Version: 0.9a
5
+ # File: _CheckModules.py
6
+ # Environment: Python 3.12
7
+
8
+ import importlib
9
+ import warnings
10
+
11
+
12
+ # Check modules func
13
+ def check_module(module_name: str, pkg_name: None | str = None):
14
+ """
15
+ Check whether imported module exists.
16
+ Args:
17
+ module_name: imported module name.
18
+ pkg_name: package name. It is required when performing a relative import.
19
+ It specifies the package to use as the anchor point from which to resolve the relative import to an absolute import.
20
+
21
+ Returns:
22
+ the imported module if it exists, else return None.
23
+
24
+ """
25
+ try:
26
+ pkg = importlib.import_module(module_name, pkg_name)
27
+ return pkg
28
+ except ImportError:
29
+ #warnings.warn(f'Package {module_name} was not found, therefore some related methods would be unavailable.')
30
+ return None