gridmlip 0.1__tar.gz → 0.2__tar.gz

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.
@@ -1,12 +1,12 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: gridmlip
3
- Version: 0.1
3
+ Version: 0.2
4
4
  Summary: Grid-based method for calculating the percolation barrier of mobile species using machine learning interatomic potentials
5
5
  Home-page: https://github.com/dembart/gridmlip
6
6
  Author: Artem Dembitskiy
7
7
  Author-email: art.dembitskiy@gmail.com
8
8
  License: MIT
9
- Keywords: MLIP,percolation barrier
9
+ Keywords: MLIP,percolation barrier,irreducible mesh
10
10
  Platform: UNKNOWN
11
11
  Classifier: Programming Language :: Python :: 3
12
12
  Classifier: License :: OSI Approved :: MIT License
@@ -31,7 +31,6 @@ License-File: LICENSE
31
31
 
32
32
  **gridmlip** is a library for calculating percolation barriers of mobile species in solids using grid-based method with machine learning interatomic potentials (MLIPs).
33
33
 
34
-
35
34
  ## Installation
36
35
 
37
36
  ```
@@ -53,7 +52,7 @@ Here we describe the pipeline in general. For a specific example, see [Notebooks
53
52
  from gridmlip import Grid
54
53
 
55
54
  atomic_types_mapper = {3:0, 31:1, 17:2}
56
- grig = Grid.from_file('your.cif', specie = 3, r_min = 1.8,
55
+ grid = Grid.from_file('your.cif', specie = 3, r_min = 1.8,
57
56
  atomic_types_mapper=atomic_types_mapper # optional
58
57
  )
59
58
  cfgs = grid.construct_configurations('data.cfg')
@@ -72,7 +71,7 @@ from gridmlip import Grid
72
71
 
73
72
  grid = Grid.from_file('your.cif', specie = 3, r_min = 1.8)
74
73
  grid.read_processed_configurations('processed_data.cfg', format = 'cfg')
75
- grid.percolation_barriers()
74
+ barriers = grid.percolation_barriers()
76
75
  ```
77
76
 
78
77
  #### Step #4: Write .grd or .cube file for visualization in VESTA 3.0
@@ -84,7 +83,8 @@ g.write_grd('test.grd')
84
83
  ## Notebooks
85
84
 
86
85
 
87
- - [Using TensorNet model pre-trained on MatPES](notebooks/TensorNet_MatPES.ipynb)
86
+ - [Using TensorNet model pre-trained on MatPES](notebooks/TensorNet_MatPES.ipynb)
87
+ [![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/drive/14gabZ-u19K-_I_e7-g67N8-xtPIN36Nw#sandboxMode=true&scrollTo=5TySp6C6UBoZ)
88
88
 
89
89
 
90
90
  ### How to cite
@@ -15,7 +15,6 @@
15
15
 
16
16
  **gridmlip** is a library for calculating percolation barriers of mobile species in solids using grid-based method with machine learning interatomic potentials (MLIPs).
17
17
 
18
-
19
18
  ## Installation
20
19
 
21
20
  ```
@@ -37,7 +36,7 @@ Here we describe the pipeline in general. For a specific example, see [Notebooks
37
36
  from gridmlip import Grid
38
37
 
39
38
  atomic_types_mapper = {3:0, 31:1, 17:2}
40
- grig = Grid.from_file('your.cif', specie = 3, r_min = 1.8,
39
+ grid = Grid.from_file('your.cif', specie = 3, r_min = 1.8,
41
40
  atomic_types_mapper=atomic_types_mapper # optional
42
41
  )
43
42
  cfgs = grid.construct_configurations('data.cfg')
@@ -56,7 +55,7 @@ from gridmlip import Grid
56
55
 
57
56
  grid = Grid.from_file('your.cif', specie = 3, r_min = 1.8)
58
57
  grid.read_processed_configurations('processed_data.cfg', format = 'cfg')
59
- grid.percolation_barriers()
58
+ barriers = grid.percolation_barriers()
60
59
  ```
61
60
 
62
61
  #### Step #4: Write .grd or .cube file for visualization in VESTA 3.0
@@ -68,7 +67,8 @@ g.write_grd('test.grd')
68
67
  ## Notebooks
69
68
 
70
69
 
71
- - [Using TensorNet model pre-trained on MatPES](notebooks/TensorNet_MatPES.ipynb)
70
+ - [Using TensorNet model pre-trained on MatPES](notebooks/TensorNet_MatPES.ipynb)
71
+ [![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/drive/14gabZ-u19K-_I_e7-g67N8-xtPIN36Nw#sandboxMode=true&scrollTo=5TySp6C6UBoZ)
72
72
 
73
73
 
74
74
  ### How to cite
@@ -0,0 +1,139 @@
1
+ import numpy as np
2
+ from spglib import get_symmetry_dataset
3
+
4
+ __author__ = "Artem Dembitskiy"
5
+
6
+ class Mesh:
7
+ """
8
+ This class implements mesh generation and finding irreducible mesh
9
+ points given the rotations and translations symmetry operations.
10
+
11
+ Example
12
+ -------
13
+
14
+ mesh = Mesh.from_atoms(atoms, resolution = 0.2, symprec = 0.1)
15
+ ir_grid_points, mapping = mesh.get_irreducible_mapping()
16
+
17
+ """
18
+
19
+ def __init__(self, mesh_shape, rotations, translations, symmetry_dataset=None):
20
+ """
21
+ Initialization.
22
+
23
+
24
+ Parameters
25
+ ----------
26
+
27
+ mesh_shape: tuple of integers, (nx, ny, nz)
28
+ shape of the mesh
29
+
30
+ rotations: list of numpy arrays of shape (3, 3)
31
+ rotation matrices
32
+
33
+ translations: list of numpy arrays of shape (3, 1)
34
+ translation vector
35
+
36
+ symmetry_dataset: dict generated by spglib library, None by default (Optional)
37
+ dataset containing symmetry operations, used for tests
38
+ """
39
+
40
+ self.mesh_shape = mesh_shape
41
+ self.rotations = rotations
42
+ self.translations = translations
43
+ self.symmetry_dataset = symmetry_dataset
44
+ self.mesh = self._generate_mesh()
45
+
46
+ @property
47
+ def addresses(self):
48
+ return self.mesh
49
+
50
+
51
+ @classmethod
52
+ def from_atoms(cls, atoms, resolution=0.2, symprec=None):
53
+ """
54
+ Create Mesh object form ASE's Atoms object.
55
+
56
+
57
+ Parameters
58
+ ----------
59
+
60
+ atoms: ASE's Atoms object
61
+ atomic structure
62
+
63
+ resolution: float, 0.2 by default
64
+ distance between points of the mesh in angstroms
65
+ symprec: None or float, None by default
66
+ symmetry precision
67
+ used by spglib' get_symmetry_dataset to find symmetry operations
68
+ """
69
+
70
+ rotations = [np.eye(3, dtype=int)]
71
+ translations = [np.zeros(3)]
72
+ symmetry_dataset = None
73
+ a, b, c = np.linalg.norm(atoms.cell, axis = 1)
74
+ nx, ny, nz = int(a // resolution), int(b // resolution), int(c // resolution)
75
+ nx, ny, nz = [n + (n % 2 + 1) % 2 for n in (nx, ny, nz)] # make odd numbers
76
+ mesh_shape = (nx, ny, nz)
77
+
78
+ if symprec:
79
+ spglib_cell = (atoms.cell, atoms.get_scaled_positions(), atoms.numbers)
80
+ symmetry_dataset = get_symmetry_dataset(spglib_cell, symprec=symprec)
81
+ rotations = symmetry_dataset.rotations
82
+ translations = symmetry_dataset.translations
83
+
84
+ return cls(mesh_shape, rotations, translations, symmetry_dataset=symmetry_dataset)
85
+
86
+ def _generate_mesh(self):
87
+ """Generate integer grid points (i,j,k) for a 3D mesh, including edges and faces."""
88
+ grid = np.mgrid[
89
+ 0:self.mesh_shape[0],
90
+ 0:self.mesh_shape[1],
91
+ 0:self.mesh_shape[2]
92
+ ].reshape(3, -1).T
93
+ return grid # shape (N,3), integer
94
+
95
+
96
+ def _index_to_linear(self, indices):
97
+ """Convert 3D integer indices -> 1D linear indices for fast lookup."""
98
+ return np.ravel_multi_index(indices.T, self.mesh_shape)
99
+
100
+
101
+ def _apply_sym_ops(self):
102
+ """Apply symmetry ops to integer grid points, wrapping by mesh_shape."""
103
+ all_images = []
104
+ mesh_shape = np.array(self.mesh_shape)
105
+ for R, t in zip(self.rotations, self.translations):
106
+ t_mesh = np.rint(t * (mesh_shape - 1)).astype(int)
107
+ transformed = (self.mesh @ R.T + t_mesh) % (mesh_shape - 1)
108
+ all_images.append(transformed)
109
+ return np.stack(all_images, axis=0) # (nsym, npoints, 3)
110
+
111
+
112
+ def get_irreducible_mapping(self):
113
+ """
114
+ Find symmetry-equivalent points on an integer 3D mesh.
115
+
116
+ Returns
117
+ -------
118
+ indices of the irreducible points of the mesh and
119
+ mapping indices to restore full mesh from irreducible points
120
+
121
+ """
122
+
123
+ npoints = len(self.mesh)
124
+ nsym = len(self.rotations)
125
+
126
+ lin_indices = self._index_to_linear(self.mesh)
127
+ index_map = np.arange(npoints, dtype=int)
128
+
129
+ all_images = self._apply_sym_ops()
130
+
131
+ transformed_lin = self._index_to_linear(all_images.reshape(-1, 3))
132
+ transformed_lin = transformed_lin.reshape(nsym, npoints)
133
+
134
+ eq_ids = index_map[transformed_lin]
135
+ eq_ids = np.concatenate([eq_ids, index_map[None, :]], axis=0)
136
+ irreducible_ids = eq_ids.min(axis=0)
137
+
138
+ unique, inverse = np.unique(irreducible_ids, return_inverse=True)
139
+ return unique, inverse
@@ -0,0 +1,333 @@
1
+ import numpy as np
2
+ from tqdm import tqdm
3
+ from ase.io import read, write, cube
4
+ from pymatgen.io.ase import AseAtomsAdaptor
5
+ from .utils import read_cfg, write_cfg
6
+ from ._neighborhood import nn_list
7
+ from ._percolation_analysis import Percolyze
8
+ from ._mesh import Mesh
9
+
10
+
11
+
12
+ __author__ = "Artem Dembitskiy"
13
+ __version__ = "0.2"
14
+
15
+
16
+
17
+ class Grid:
18
+
19
+ def __init__(self,
20
+ atoms,
21
+ specie,
22
+ resolution=0.25,
23
+ r_cut=5.0,
24
+ r_min=1.5,
25
+ symprec=0.1,
26
+ atomic_types_mapper= None,
27
+ empty_framework=True,
28
+ verbose=True,
29
+ ):
30
+ """
31
+ Initialization.
32
+
33
+ Parameters
34
+ ----------
35
+
36
+ atoms: ASE's Atoms object
37
+ atomic structure
38
+
39
+ specie: int
40
+ atomic number of the mobile specie
41
+
42
+ resolution: float, 0.2 by default
43
+ spacing between points (in Angstroms)
44
+
45
+ r_cut: float
46
+ cutoff radius to find the first nearest neighbor for each grid point
47
+
48
+ r_min: float
49
+ blocking sphere radius
50
+
51
+ atomic_types_mapper: dict, optional
52
+ mapper of atomic numbers into species used by MLIP
53
+
54
+ symprec: None or float, 0.1 by default
55
+ symmetry precision
56
+ used to get symmetry operations for finding the irreducible mesh points
57
+ may reduce number of MLIP calculations by factor of ~10-100
58
+ if None, the symmetry will not be used.
59
+
60
+ empty_framework: boolean, True by default
61
+ remove mobile types of interest from the structure
62
+ """
63
+
64
+ self.symprec = symprec
65
+ self.verbose = verbose
66
+ if atomic_types_mapper:
67
+ numbers = self._map_atomic_types(atomic_types_mapper, atoms.numbers)
68
+ atoms = atoms.copy()
69
+ atoms.numbers = numbers
70
+ specie = atomic_types_mapper[specie]
71
+ self.atoms = atoms.copy()
72
+ self.specie = specie
73
+ self.resolution = resolution
74
+ self.cell = self.atoms.cell
75
+ self.r_cut = r_cut
76
+ self.r_min = r_min
77
+ self.base = self.atoms[self.atoms.numbers != self.specie] if empty_framework else atoms.copy()
78
+
79
+ self._mesh = Mesh.from_atoms(self.base,
80
+ resolution = self.resolution,
81
+ symprec = self.symprec)
82
+ self.mesh_shape = self._mesh.mesh_shape
83
+ self.ir_grid_points, self.mapping = self._mesh.get_irreducible_mapping()
84
+ self.mesh_frac = self._mesh.addresses / (np.array(self._mesh.mesh_shape) - 1)
85
+ self.mesh_cart = np.dot(self.mesh_frac, self.base.cell)
86
+
87
+ self.min_dists, _ = nn_list(self.base.positions,
88
+ self.mesh_cart[self.ir_grid_points],
89
+ r_cut,
90
+ self.cell)
91
+ if self.verbose:
92
+ total_points = self.mesh_frac.shape[0]
93
+ irreducible_points = self.ir_grid_points.shape[0]
94
+ passed_filter = (self.min_dists > self.r_min).sum()
95
+ compression = total_points / passed_filter if passed_filter else np.nan
96
+
97
+ print("Mesh Summary")
98
+ print("────────────────────────────────────")
99
+ print(f" Mesh shape: {tuple(self.mesh_shape)}")
100
+ print(f" Symops: {len(self._mesh.rotations)}")
101
+ print(f" Total points: {total_points:,}")
102
+ print(f" Irreducible points: {irreducible_points:,}")
103
+ print(f" Irreducible points (r_min filter): {passed_filter:,}")
104
+ print(f" Compression ratio: {compression:6.2f}×")
105
+ print("────────────────────────────────────\n")
106
+
107
+
108
+
109
+ def _map_atomic_types(self, atomic_types_mapper, numbers):
110
+ u,inv = np.unique(numbers,return_inverse = True)
111
+ return np.array([atomic_types_mapper[x] for x in u])[inv].reshape(numbers.shape)
112
+
113
+
114
+ @classmethod
115
+ def from_file(cls, file,
116
+ specie,
117
+ resolution = 0.25,
118
+ r_cut = 5.0,
119
+ r_min = 1.5,
120
+ symprec = 0.1,
121
+ atomic_types_mapper = None,
122
+ empty_framework = True,
123
+ ):
124
+ """
125
+ Create Grid object from the file.
126
+
127
+ Parameters
128
+ ----------
129
+
130
+ file: string
131
+ .xyz of .cfg file representing atomic structure
132
+
133
+ specie: int
134
+ atomic number of the mobile specie
135
+
136
+ resolution: float, 0.2 by default
137
+ spacing between points (in Angstroms)
138
+
139
+ r_cut: float
140
+ cutoff radius to find the first nearest neighbor for each grid point
141
+
142
+ r_min: float
143
+ blocking sphere radius
144
+
145
+ atomic_types_mapper: dict, optional
146
+ mapper of the species into atomic numbers
147
+
148
+ empty_framework: boolean, True by default
149
+ whether to remove mobile types of interest from the structure
150
+
151
+ symprec: None or float, 0.1 by default
152
+ symmetry precision
153
+ used to get symmetry operations for finding the irreducible mesh points
154
+ may reduce number of MLIP calculations by factor of 10-100
155
+ if None, the symmetry will not be used.
156
+
157
+ Returns
158
+ -------
159
+ Grid object
160
+ """
161
+
162
+ if file.split('.')[-1] == 'cfg':
163
+ atoms = read_cfg(file)[0]
164
+ else:
165
+ atoms = read(file)
166
+ return cls(atoms, specie, resolution=resolution,
167
+ symprec=symprec,
168
+ r_cut=r_cut, r_min=r_min, atomic_types_mapper=atomic_types_mapper,
169
+ empty_framework=empty_framework
170
+ )
171
+
172
+
173
+
174
+ def construct_configurations(self, config_format = 'ase', filename = None):
175
+ """
176
+ Construct atomic configurations for further calculations.
177
+
178
+ Parameters
179
+ ----------
180
+
181
+ filename: string (Optional)
182
+ path to save .xyz of .cfg file with atomic configurations
183
+
184
+ config_format: string, "ase" by default, can be "pymatgen" or "ase"
185
+ data format of the created configurations (pymatgen's Structure of ASE's Atoms)
186
+
187
+
188
+ Returns
189
+ -------
190
+ configurations: list of ASE's atoms object
191
+ """
192
+
193
+ configurations = []
194
+ if config_format == 'ase':
195
+ ir_mesh_cart = self.mesh_cart[self.ir_grid_points]
196
+ assert len(ir_mesh_cart) == len(self.min_dists)
197
+ for p, d in tqdm(zip(ir_mesh_cart, self.min_dists), desc = 'creating configurations'):
198
+ if d > self.r_min:
199
+ framework = self.base.copy()
200
+ framework.append(self.specie)
201
+ framework.positions[-1] = p
202
+ configurations.append(framework)
203
+ elif config_format == 'pymatgen':
204
+ base = AseAtomsAdaptor.get_structure(self.base)
205
+ ir_mesh_frac = self.mesh_frac[self.ir_grid_points]
206
+ for p, d in tqdm(zip(ir_mesh_frac, self.min_dists), desc = 'creating configurations'):
207
+ if d > self.r_min:
208
+ framework = base.copy()
209
+ framework.append(self.specie, coords = p)
210
+ configurations.append(framework)
211
+ else:
212
+ raise ValueError(f"Wrong config_format {config_format}")
213
+
214
+ if filename:
215
+ if config_format != 'ase':
216
+ raise ValueError('Only "ase" format is allowed for saving files')
217
+ if filename.split('.')[-1] == 'cfg':
218
+ write_cfg(filename, configurations)
219
+ else:
220
+ write(filename, configurations)
221
+ return configurations
222
+
223
+
224
+
225
+ def load_energies(self, energies):
226
+
227
+ """
228
+ Load energies obtained by any MLIP or structure-to-property model.
229
+ Should match order in created configurations.
230
+
231
+ Parameters
232
+ ----------
233
+
234
+ energies: np.array
235
+ calculated energies for the created configurations
236
+ """
237
+
238
+ self.energies = energies
239
+
240
+ ir_distribution = np.ones_like(self.min_dists) * np.inf
241
+ ir_distribution[self.min_dists > self.r_min] = self.energies
242
+
243
+ self.distribution = np.nan_to_num(ir_distribution, copy = False, nan = np.inf)[self.mapping]
244
+ self.data = self.distribution.reshape(self.mesh_shape)
245
+
246
+
247
+ def read_processed_configurations(self, filename, file_format = 'xyz'):
248
+
249
+ """
250
+ Read processed (by any MLIP) atomic configurations
251
+ with calculated energies.
252
+
253
+ Parameters
254
+ ----------
255
+
256
+ filename: string
257
+ path to the processed .xyz of .cfg file
258
+
259
+ file_format: string, 'xyz' by default
260
+ format of the file
261
+ if 'cfg' will use custom function read_cfg() to read MLIP 2.0 generated data
262
+ otherwise will use ASE's read()
263
+ """
264
+
265
+ if file_format == 'cfg':
266
+ atoms_list = read_cfg(filename)
267
+ else:
268
+ atoms_list = read(filename, index = ':')
269
+ energies = np.array([atoms.get_potential_energy() for atoms in atoms_list])
270
+ del atoms_list
271
+ self.load_energies(energies)
272
+
273
+
274
+
275
+ def write_cube(self, filename):
276
+
277
+ """
278
+ Write .cube file containing structural and MLIP distribution data.
279
+
280
+ Parameters
281
+ ----------
282
+
283
+ filename: str
284
+ file name to write .cube
285
+ """
286
+
287
+ data = self.data
288
+ nx, ny, nz = data.shape
289
+ with open(f'{filename}.cube', 'w') as f:
290
+ cube.write_cube(f, self.atoms, data = data[:nx-1, :ny-1, :nz-1])
291
+
292
+
293
+
294
+ def write_grd(self, filename):
295
+
296
+ """
297
+ Write MLIP distribution volumetric file for VESTA 3.0.
298
+
299
+ Parameters
300
+ ----------
301
+
302
+ filename: str
303
+ file name to write .grd
304
+ """
305
+
306
+
307
+ data = self.data
308
+ voxels = data.shape[0] - 1, data.shape[1] - 1, data.shape[2] - 1
309
+ cellpars = self.cell.cellpar()
310
+ with open(f'{filename}.grd' , 'w') as report:
311
+ comment = '# MLIP data made with gridmlip package: https://github.com/dembart/gridmlip'
312
+ report.write(comment + '\n')
313
+ report.write(''.join(str(p) + ' ' for p in cellpars).strip() + '\n')
314
+ report.write(''.join(str(v) + ' ' for v in voxels).strip() + '\n')
315
+ for i in range(voxels[0]):
316
+ for j in range(voxels[1]):
317
+ for k in range(voxels[2]):
318
+ val = data[i, j, k]
319
+ report.write(str(val) + '\n')
320
+
321
+
322
+
323
+ def percolation_barriers(self, encut = 10.0):
324
+ """
325
+ Calculate percolation barriers.
326
+
327
+ Parameters
328
+ ----------
329
+ encut: float, 10.0 by default
330
+ upper bound for searching the barrier
331
+ """
332
+ pl = Percolyze(self.data)
333
+ return pl.percolation_barriers(encut = encut)
@@ -24,7 +24,7 @@ def read_cfg(filename):
24
24
  configurations = []
25
25
  for i_s, i_e in tqdm(zip(np.where(np.array(text) == 'BEGIN_CFG\n')[0], np.where(np.array(text) == 'END_CFG\n')[0])):
26
26
  subtext = text[i_s:i_e]
27
- grade = None
27
+ grade = None # not used
28
28
  results = {}
29
29
  for i, line in enumerate(subtext):
30
30
  if 'Size' in line:
@@ -1,12 +1,12 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: gridmlip
3
- Version: 0.1
3
+ Version: 0.2
4
4
  Summary: Grid-based method for calculating the percolation barrier of mobile species using machine learning interatomic potentials
5
5
  Home-page: https://github.com/dembart/gridmlip
6
6
  Author: Artem Dembitskiy
7
7
  Author-email: art.dembitskiy@gmail.com
8
8
  License: MIT
9
- Keywords: MLIP,percolation barrier
9
+ Keywords: MLIP,percolation barrier,irreducible mesh
10
10
  Platform: UNKNOWN
11
11
  Classifier: Programming Language :: Python :: 3
12
12
  Classifier: License :: OSI Approved :: MIT License
@@ -31,7 +31,6 @@ License-File: LICENSE
31
31
 
32
32
  **gridmlip** is a library for calculating percolation barriers of mobile species in solids using grid-based method with machine learning interatomic potentials (MLIPs).
33
33
 
34
-
35
34
  ## Installation
36
35
 
37
36
  ```
@@ -53,7 +52,7 @@ Here we describe the pipeline in general. For a specific example, see [Notebooks
53
52
  from gridmlip import Grid
54
53
 
55
54
  atomic_types_mapper = {3:0, 31:1, 17:2}
56
- grig = Grid.from_file('your.cif', specie = 3, r_min = 1.8,
55
+ grid = Grid.from_file('your.cif', specie = 3, r_min = 1.8,
57
56
  atomic_types_mapper=atomic_types_mapper # optional
58
57
  )
59
58
  cfgs = grid.construct_configurations('data.cfg')
@@ -72,7 +71,7 @@ from gridmlip import Grid
72
71
 
73
72
  grid = Grid.from_file('your.cif', specie = 3, r_min = 1.8)
74
73
  grid.read_processed_configurations('processed_data.cfg', format = 'cfg')
75
- grid.percolation_barriers()
74
+ barriers = grid.percolation_barriers()
76
75
  ```
77
76
 
78
77
  #### Step #4: Write .grd or .cube file for visualization in VESTA 3.0
@@ -84,7 +83,8 @@ g.write_grd('test.grd')
84
83
  ## Notebooks
85
84
 
86
85
 
87
- - [Using TensorNet model pre-trained on MatPES](notebooks/TensorNet_MatPES.ipynb)
86
+ - [Using TensorNet model pre-trained on MatPES](notebooks/TensorNet_MatPES.ipynb)
87
+ [![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/drive/14gabZ-u19K-_I_e7-g67N8-xtPIN36Nw#sandboxMode=true&scrollTo=5TySp6C6UBoZ)
88
88
 
89
89
 
90
90
  ### How to cite
@@ -5,6 +5,7 @@ pyproject.toml
5
5
  setup.cfg
6
6
  setup.py
7
7
  gridmlip/__init__.py
8
+ gridmlip/_mesh.py
8
9
  gridmlip/_neighborhood.py
9
10
  gridmlip/_percolation_analysis.py
10
11
  gridmlip/cli.py
@@ -7,7 +7,7 @@ url = https://github.com/dembart/gridmlip
7
7
  description = gridmlip
8
8
  long_description = file: README.md
9
9
  long_description_content_type = text/markdown
10
- keywords = MLIP, percolation barrier
10
+ keywords = MLIP, percolation barrier, irreducible mesh
11
11
  license = MIT
12
12
  classifiers =
13
13
  License :: OSI Approved :: BSD License
@@ -5,7 +5,7 @@ with open("README.md", "r") as fh:
5
5
 
6
6
  setuptools.setup(
7
7
  name='gridmlip',
8
- version='0.1',
8
+ version='0.2',
9
9
  py_modules = ["gridmlip"],
10
10
  install_requires = [
11
11
  "pandas",
@@ -18,7 +18,7 @@ setuptools.setup(
18
18
  author="Artem Dembitskiy",
19
19
  author_email="art.dembitskiy@gmail.com",
20
20
  description="Grid-based method for calculating the percolation barrier of mobile species using machine learning interatomic potentials",
21
- key_words = ['percolation-barrier', 'umlip', 'conductivity', 'diffusion'],
21
+ key_words = ['percolation-barrier', 'UMLIP', 'ionic conductivity', 'diffusion'],
22
22
  long_description=long_description,
23
23
  long_description_content_type="text/markdown",
24
24
  url="https://github.com/dembart/gridmlip",
@@ -1,274 +0,0 @@
1
- import numpy as np
2
- from tqdm import tqdm
3
- from ase.io import read, write, cube
4
- from .utils import read_cfg, write_cfg
5
- from ._neighborhood import nn_list
6
- from ._percolation_analysis import Percolyze
7
-
8
-
9
-
10
- __version__ = "0.1"
11
-
12
-
13
-
14
- class Grid:
15
-
16
- def __init__(self, atoms, specie = None, resolution = 0.25,
17
- r_cut = 5.0, r_min = 1.5, atomic_types_mapper = None):
18
-
19
- """
20
- Initialization.
21
-
22
- Parameters
23
- ----------
24
-
25
- atoms: ASE's Atoms object
26
- atomic structure
27
-
28
- specie: int
29
- atomic number of the mobile specie
30
-
31
- resolution: float, 0.2 by default
32
- spacing between points (in Angstroms)
33
-
34
- r_cut: float
35
- cutoff radius to find the first nearest neighbor for each grid point
36
-
37
- r_min: float
38
- blocking sphere radius
39
-
40
- atomic_types_mapper: dict, optional
41
- mapper of atomic numbers into species used by MLIP
42
- """
43
-
44
- if atomic_types_mapper:
45
- numbers = self._map_atomic_types(atomic_types_mapper, atoms.numbers)
46
- atoms = atoms.copy()
47
- atoms.numbers = numbers
48
- specie = atomic_types_mapper[specie]
49
- self.atoms = atoms.copy()
50
- self.specie = specie
51
- self.resolution = resolution
52
- self.cell = self.atoms.cell
53
- self._mesh(resolution)
54
- self.base = self.atoms[self.atoms.numbers != self.specie]
55
- self.min_dists, _ = nn_list(self.base.positions, self.mesh_cart, r_cut, self.cell)
56
- self.r_cut = r_cut
57
- self.r_min = r_min
58
-
59
-
60
-
61
- def _map_atomic_types(self, atomic_types_mapper, numbers):
62
- u,inv = np.unique(numbers,return_inverse = True)
63
- return np.array([atomic_types_mapper[x] for x in u])[inv].reshape(numbers.shape)
64
-
65
-
66
-
67
- def _mesh(self, resolution):
68
-
69
- """
70
- This method creates grid of equidistant points in 3D
71
- with respect to the input resolution.
72
-
73
- Parameters
74
- ----------
75
- resolution: float, 0.2 by default
76
- spacing between points (in Angstroms)
77
-
78
- Returns
79
- -------
80
- mesh_cart: np.array
81
- cartesian coordinates of meshgrid
82
-
83
- """
84
-
85
- a, b, c, _, _, _ = self.cell.cellpar()
86
- nx, ny, nz = int(a // resolution), int(b // resolution), int(c // resolution)
87
- x = np.linspace(0, 1, nx)
88
- y = np.linspace(0, 1, ny)
89
- z = np.linspace(0, 1, nz)
90
- self.mesh_frac = np.stack(np.meshgrid(x, y, z, indexing='ij'), axis=-1).reshape(-1, 3)
91
- self.mesh_cart = self.cell.cartesian_positions(self.mesh_frac)
92
- self.size = (nx, ny, nz)
93
- return self.mesh_cart
94
-
95
-
96
-
97
- @classmethod
98
- def from_file(cls, file, specie = None, resolution = 0.25,
99
- r_cut = 5.0, r_min = 1.5, atomic_types_mapper = None):
100
- """
101
- Create Grid object from the file.
102
-
103
- Parameters
104
- ----------
105
-
106
- file: string
107
- .xyz of .cfg file representing atomic structure
108
-
109
- specie: int
110
- atomic number of the mobile specie
111
-
112
- resolution: float, 0.2 by default
113
- spacing between points (in Angstroms)
114
-
115
- r_cut: float
116
- cutoff radius to find the first nearest neighbor for each grid point
117
-
118
- r_min: float
119
- blocking sphere radius
120
-
121
- atomic_types_mapper: dict, optional
122
- mapper of the species into atomic numbers
123
-
124
- Returns
125
- -------
126
- Grid object
127
- """
128
-
129
- if file.split('.')[-1] == 'cfg':
130
- atoms = read_cfg(file)[0]
131
- else:
132
- atoms = read(file)
133
- return cls(atoms, specie, resolution=resolution,
134
- r_cut = r_cut, r_min = r_min, atomic_types_mapper = atomic_types_mapper)
135
-
136
-
137
-
138
- def construct_configurations(self, filename = None):
139
- """
140
- Construct atomic configurations for further calculations.
141
-
142
- Parameters
143
- ----------
144
-
145
- filename: string (Optional)
146
- path to save .xyz of .cfg file with atomic configurations
147
-
148
- Returns
149
- -------
150
- configurations: list of ASE's atoms object
151
- """
152
-
153
- configurations = []
154
- for p, d in tqdm(zip(self.mesh_cart, self.min_dists), desc = 'creating configurations'):
155
- if d > self.r_min:
156
- framework = self.base.copy()
157
- framework.append(self.specie)
158
- framework.positions[-1] = p
159
- configurations.append(framework)
160
-
161
- if filename:
162
- if filename.split('.')[-1] == 'cfg':
163
- write_cfg(filename, configurations)
164
- else:
165
- write(filename, configurations)
166
- return configurations
167
-
168
-
169
-
170
- def load_energies(self, energies):
171
-
172
- """
173
- Load energies obtained by any MLIP or structure-to-property model.
174
- Should match order in created configurations.
175
-
176
- Parameters
177
- ----------
178
-
179
- energies: np.array
180
- calculated energies for the created configurations
181
- """
182
-
183
- self.energies = energies
184
- self.distribution = np.ones_like(self.min_dists) * np.inf
185
- self.distribution[self.min_dists > self.r_min] = self.energies
186
- self.distribution = np.nan_to_num(self.distribution, copy = False, nan = np.inf)
187
- self.data = self.distribution.reshape(self.size)
188
-
189
-
190
- def read_processed_configurations(self, filename, format = 'xyz'):
191
-
192
- """
193
- Read processed (by any MLIP) atomic configurations
194
- with calculated energies.
195
-
196
- Parameters
197
- ----------
198
-
199
- filename: string
200
- path to the processed .xyz of .cfg file
201
- """
202
-
203
- if format == 'cfg':
204
- atoms_list = read_cfg(filename)
205
- else:
206
- atoms_list = read(filename, index = ':')
207
- self.energies = np.array([atoms.get_potential_energy() for atoms in atoms_list])
208
- del atoms_list
209
- self.distribution = np.ones_like(self.min_dists) * np.inf
210
- self.distribution[self.min_dists > self.r_min] = self.energies
211
- self.distribution = np.nan_to_num(self.distribution, copy = False, nan = np.inf)
212
- self.data = self.distribution.reshape(self.size)
213
-
214
-
215
-
216
- def write_cube(self, filename):
217
-
218
- """
219
- Write .cube file containing structural and MLIP distribution data.
220
-
221
- Parameters
222
- ----------
223
-
224
- filename: str
225
- file name to write .cube
226
- """
227
-
228
- data = self.data
229
- nx, ny, nz = data.shape
230
- with open(f'{filename}.cube', 'w') as f:
231
- cube.write_cube(f, self.atoms, data = data[:nx-1, :ny-1, :nz-1])
232
-
233
-
234
-
235
- def write_grd(self, filename):
236
-
237
- """
238
- Write MLIP distribution volumetric file for VESTA 3.0.
239
-
240
- Parameters
241
- ----------
242
-
243
- filename: str
244
- file name to write .grd
245
- """
246
-
247
-
248
- data = self.data
249
- voxels = data.shape[0] - 1, data.shape[1] - 1, data.shape[2] - 1
250
- cellpars = self.cell.cellpar()
251
- with open(f'{filename}.grd' , 'w') as report:
252
- comment = '# MLIP data made with gridmlip package: https://github.com/dembart/gridmlip'
253
- report.write(comment + '\n')
254
- report.write(''.join(str(p) + ' ' for p in cellpars).strip() + '\n')
255
- report.write(''.join(str(v) + ' ' for v in voxels).strip() + '\n')
256
- for i in range(voxels[0]):
257
- for j in range(voxels[1]):
258
- for k in range(voxels[2]):
259
- val = data[i, j, k]
260
- report.write(str(val) + '\n')
261
-
262
-
263
-
264
- def percolation_barriers(self, encut = 10.0):
265
- """
266
- Calculate percolation barriers.
267
-
268
- Parameters
269
- ----------
270
- encut: float, 10.0 by default
271
- upper bound for searching the barrier
272
- """
273
- pl = Percolyze(self.data)
274
- return pl.percolation_barriers(encut = encut)
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes