gridmlip 0.2__tar.gz → 0.2.5__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.
Files changed (40) hide show
  1. {gridmlip-0.2 → gridmlip-0.2.5}/LICENSE +1 -2
  2. {gridmlip-0.2 → gridmlip-0.2.5}/MANIFEST.in +0 -1
  3. gridmlip-0.2.5/PKG-INFO +184 -0
  4. gridmlip-0.2.5/README.md +165 -0
  5. gridmlip-0.2.5/gridmlip/_distortion.py +44 -0
  6. gridmlip-0.2.5/gridmlip/_generate.py +251 -0
  7. {gridmlip-0.2 → gridmlip-0.2.5}/gridmlip/_mesh.py +0 -1
  8. gridmlip-0.2.5/gridmlip/_neighborhood.py +77 -0
  9. {gridmlip-0.2 → gridmlip-0.2.5}/gridmlip/_percolation_analysis.py +8 -8
  10. gridmlip-0.2.5/gridmlip/_version.py +1 -0
  11. gridmlip-0.2.5/gridmlip/_void_search.py +114 -0
  12. {gridmlip-0.2 → gridmlip-0.2.5}/gridmlip/core.py +182 -104
  13. gridmlip-0.2.5/gridmlip/integrations/__init__.py +0 -0
  14. gridmlip-0.2.5/gridmlip/integrations/bvse.py +142 -0
  15. gridmlip-0.2.5/gridmlip/integrations/gnn_lib.py +107 -0
  16. gridmlip-0.2.5/gridmlip/integrations/mace.py +93 -0
  17. gridmlip-0.2.5/gridmlip/integrations/sevennet.py +87 -0
  18. gridmlip-0.2.5/gridmlip/select.py +1 -0
  19. {gridmlip-0.2 → gridmlip-0.2.5}/gridmlip/utils.py +2 -1
  20. gridmlip-0.2.5/gridmlip.egg-info/PKG-INFO +184 -0
  21. gridmlip-0.2.5/gridmlip.egg-info/SOURCES.txt +33 -0
  22. {gridmlip-0.2 → gridmlip-0.2.5}/gridmlip.egg-info/requires.txt +2 -2
  23. {gridmlip-0.2 → gridmlip-0.2.5}/gridmlip.egg-info/top_level.txt +1 -0
  24. gridmlip-0.2.5/scripts/__init__.py +0 -0
  25. gridmlip-0.2.5/scripts/_nebtools.py +0 -0
  26. gridmlip-0.2.5/scripts/_void_search.py +76 -0
  27. gridmlip-0.2.5/scripts/estimate_bvel13k_dataset_size.py +446 -0
  28. {gridmlip-0.2 → gridmlip-0.2.5}/setup.cfg +9 -4
  29. gridmlip-0.2.5/setup.py +2 -0
  30. gridmlip-0.2/PKG-INFO +0 -94
  31. gridmlip-0.2/README.md +0 -76
  32. gridmlip-0.2/gridmlip/_neighborhood.py +0 -34
  33. gridmlip-0.2/gridmlip.egg-info/PKG-INFO +0 -94
  34. gridmlip-0.2/gridmlip.egg-info/SOURCES.txt +0 -19
  35. gridmlip-0.2/setup.py +0 -35
  36. {gridmlip-0.2 → gridmlip-0.2.5}/gridmlip/__init__.py +0 -0
  37. {gridmlip-0.2 → gridmlip-0.2.5}/gridmlip/cli.py +0 -0
  38. {gridmlip-0.2 → gridmlip-0.2.5}/gridmlip.egg-info/dependency_links.txt +0 -0
  39. {gridmlip-0.2 → gridmlip-0.2.5}/gridmlip.egg-info/zip-safe +0 -0
  40. {gridmlip-0.2 → gridmlip-0.2.5}/pyproject.toml +0 -0
@@ -1,5 +1,4 @@
1
-
2
- Copyright (c) 2025 Artem Dembitskiy
1
+ Copyright (c) 2025-2026 Artem D. Dembitskiy
3
2
 
4
3
  Permission is hereby granted, free of charge, to any person obtaining a copy
5
4
  of this software and associated documentation files (the "Software"), to deal
@@ -1,3 +1,2 @@
1
1
  include LICENSE
2
2
  include README.md
3
-
@@ -0,0 +1,184 @@
1
+ Metadata-Version: 2.1
2
+ Name: gridmlip
3
+ Version: 0.2.5
4
+ Summary: gridmlip
5
+ Home-page: https://github.com/dembart/gridmlip
6
+ Author: Artem Dembitskiy
7
+ Author-email: art.dembitskiy@gmail.com
8
+ License: MIT
9
+ Keywords: MLIP,percolation barrier,irreducible mesh
10
+ Classifier: License :: OSI Approved :: BSD License
11
+ Classifier: Programming Language :: Python :: 3.10
12
+ Classifier: Programming Language :: Python :: 3.11
13
+ Classifier: Programming Language :: Python :: 3.12
14
+ Classifier: Programming Language :: Python :: 3.13
15
+ Classifier: Programming Language :: Python :: 3.14
16
+ Classifier: Programming Language :: Python :: 3.15
17
+ Description-Content-Type: text/markdown
18
+ License-File: LICENSE
19
+
20
+ ## gridmlip
21
+ <p align="left">
22
+ <a href="https://github.com/dembart/gridmlip/blob/main/LICENSE"><img alt="License" src="https://img.shields.io/badge/license-MIT-darkred"></a>
23
+
24
+
25
+ ## Contents
26
+ - [About](#about)
27
+ - [Installation](#installation)
28
+ - [How to use](#how-to-use)
29
+ - [Notebooks](#notebooks)
30
+ - [How to cite](#how-to-cite)
31
+
32
+
33
+ ## About
34
+
35
+ **gridmlip** is a library for the symmetry-aware grid-based sampling of the energy landscape of mobile species in solids using machine learning interatomic potentials (MLIPs).
36
+
37
+ > (!) Note: The API and functionality may change. Errors are expected. The library is under development.
38
+
39
+ ## Installation
40
+
41
+ ```
42
+ pip install gridmlip
43
+ ```
44
+ or
45
+
46
+ ```python
47
+ git clone https://github.com/dembart/gridmlip
48
+ cd gridmlip
49
+ pip install .
50
+ ```
51
+
52
+ ## How to use
53
+ Here we describe the pipeline in general. For a specific example, see [Notebooks](#notebooks).
54
+
55
+ ### Percolation analysis
56
+
57
+ ```python
58
+ from gridmlip import Grid
59
+ from gridmlip.integrations.sevennet import evaluate_atoms_list # sevenn must be installed
60
+
61
+ file = './data/Li10Ge(PS6)2_mp.cif'
62
+ specie = 3
63
+
64
+ ### Create grid
65
+ g = Grid.from_file(
66
+ file,
67
+ specie,
68
+ r_min=0.8,
69
+ r_cut=5.0,
70
+ resolution=0.2,
71
+ symprec=0.1,
72
+ empty_framework=True,
73
+ verbose=True
74
+ )
75
+
76
+ ### Prepare inequivalent atomic configurations
77
+ atoms_list = g.construct_configurations(config_format='ase')
78
+
79
+ ### Predict energies
80
+ energy_list, forces_list = evaluate_atoms_list(
81
+ atoms_list,
82
+ model_path='./data/checkpoint_sevennet_0.pth',
83
+ device='cuda',
84
+ compute_force=False,
85
+ batch_size=256
86
+ )
87
+
88
+ ### Load energies
89
+ g.load_energies(energy_list)
90
+
91
+ ### Determine percolation barriers
92
+ barriers = g.percolation_barriers(n_jobs=-1)
93
+
94
+ ### Save .grd file for visualization in VESTA 3.0
95
+ g.write_grd('test.grd')
96
+ ```
97
+ ### Generate defected configurations for MLIP pre-training
98
+
99
+ ```python
100
+ from ase.io import read
101
+ from gridmlip._generate import DefectGenerator
102
+
103
+ GENERATOR_PARAMS = {
104
+ 'specie': 3,
105
+ 'resolution': 0.25,
106
+ 'r_min': 1.3,
107
+ 'r_cut': 7.5,
108
+ 'symprec': 0.1,
109
+ 'max_sep_dist': 'auto',
110
+ 'upper_bound': 6.0,
111
+ }
112
+
113
+ gen = DefectGenerator(atoms, **GENERATOR_PARAMS)
114
+
115
+ ### Symmetrically distinct defect configurations
116
+ cfgs_int = gen.generate_dataset(defect_type='int')
117
+ cfgs_vac= gen.generate_dataset(defect_type='vac')
118
+ cfgs_frk = gen.generate_dataset(defect_type='frenkel')
119
+ ```
120
+
121
+ ### Find voids
122
+
123
+ ```python
124
+ from ase.io import read
125
+ from gridmlip._void_search import clustered_local_minima
126
+
127
+ atoms = read('file.cif')
128
+
129
+ g = Grid(atoms, specie=3, empty_framework=False)
130
+ g.generate_mesh()
131
+
132
+ clusters = clustered_local_minima(
133
+ -g.min_distances_data, # can be any data computed for the generated grid
134
+ min_distance=2.0,
135
+ relative_max_value=0.5,
136
+ cell=g.atoms.cell,
137
+ coord_type='cart'
138
+ )
139
+
140
+ for (pos, energy, n) in clusters:
141
+ print('Site', pos)
142
+ print('Site energy', energy) # here, energy is the minimal distances
143
+ print('Number of points in a cluster', n)
144
+
145
+ ```
146
+
147
+ ### Bond valence site energy calculations
148
+
149
+ ```python
150
+ from ase.io import read
151
+ from ions.decorate import assign_oxidation_states
152
+ from gridmlip.integrations.bvse import bvse_distribution
153
+
154
+ atoms = read('LiFePO4.cif')
155
+ atoms = assign_oxidation_states(atoms)
156
+
157
+ PARAMS = {
158
+ "mobile_ion": "Li+",
159
+ "resolution": 0.2,
160
+ "r_min": 0.8,
161
+ "r_cut": 10.0,
162
+ "empty_framework": True,
163
+ "k": 100,
164
+ "f": 0.74,
165
+ }
166
+
167
+ grid, energies = bvse_distribution(atoms, **PARAMS)
168
+ grid.percolation_barriers() # energies already loaded via grid.load_energies(energies)
169
+
170
+ ```
171
+
172
+
173
+
174
+ ## Notebooks
175
+
176
+
177
+ - [Using SevenNet for calculating percolation barriers](notebooks/integrations.ipynb)
178
+
179
+ - [Using moment tensor potentials (as implemented in MLIP 2.0) for calculating percolation barriers](notebooks/mtp.ipynb)
180
+
181
+
182
+ ### How to cite
183
+ If you use the gridmlip library, please, consider citing this repository.
184
+
@@ -0,0 +1,165 @@
1
+ ## gridmlip
2
+ <p align="left">
3
+ <a href="https://github.com/dembart/gridmlip/blob/main/LICENSE"><img alt="License" src="https://img.shields.io/badge/license-MIT-darkred"></a>
4
+
5
+
6
+ ## Contents
7
+ - [About](#about)
8
+ - [Installation](#installation)
9
+ - [How to use](#how-to-use)
10
+ - [Notebooks](#notebooks)
11
+ - [How to cite](#how-to-cite)
12
+
13
+
14
+ ## About
15
+
16
+ **gridmlip** is a library for the symmetry-aware grid-based sampling of the energy landscape of mobile species in solids using machine learning interatomic potentials (MLIPs).
17
+
18
+ > (!) Note: The API and functionality may change. Errors are expected. The library is under development.
19
+
20
+ ## Installation
21
+
22
+ ```
23
+ pip install gridmlip
24
+ ```
25
+ or
26
+
27
+ ```python
28
+ git clone https://github.com/dembart/gridmlip
29
+ cd gridmlip
30
+ pip install .
31
+ ```
32
+
33
+ ## How to use
34
+ Here we describe the pipeline in general. For a specific example, see [Notebooks](#notebooks).
35
+
36
+ ### Percolation analysis
37
+
38
+ ```python
39
+ from gridmlip import Grid
40
+ from gridmlip.integrations.sevennet import evaluate_atoms_list # sevenn must be installed
41
+
42
+ file = './data/Li10Ge(PS6)2_mp.cif'
43
+ specie = 3
44
+
45
+ ### Create grid
46
+ g = Grid.from_file(
47
+ file,
48
+ specie,
49
+ r_min=0.8,
50
+ r_cut=5.0,
51
+ resolution=0.2,
52
+ symprec=0.1,
53
+ empty_framework=True,
54
+ verbose=True
55
+ )
56
+
57
+ ### Prepare inequivalent atomic configurations
58
+ atoms_list = g.construct_configurations(config_format='ase')
59
+
60
+ ### Predict energies
61
+ energy_list, forces_list = evaluate_atoms_list(
62
+ atoms_list,
63
+ model_path='./data/checkpoint_sevennet_0.pth',
64
+ device='cuda',
65
+ compute_force=False,
66
+ batch_size=256
67
+ )
68
+
69
+ ### Load energies
70
+ g.load_energies(energy_list)
71
+
72
+ ### Determine percolation barriers
73
+ barriers = g.percolation_barriers(n_jobs=-1)
74
+
75
+ ### Save .grd file for visualization in VESTA 3.0
76
+ g.write_grd('test.grd')
77
+ ```
78
+ ### Generate defected configurations for MLIP pre-training
79
+
80
+ ```python
81
+ from ase.io import read
82
+ from gridmlip._generate import DefectGenerator
83
+
84
+ GENERATOR_PARAMS = {
85
+ 'specie': 3,
86
+ 'resolution': 0.25,
87
+ 'r_min': 1.3,
88
+ 'r_cut': 7.5,
89
+ 'symprec': 0.1,
90
+ 'max_sep_dist': 'auto',
91
+ 'upper_bound': 6.0,
92
+ }
93
+
94
+ gen = DefectGenerator(atoms, **GENERATOR_PARAMS)
95
+
96
+ ### Symmetrically distinct defect configurations
97
+ cfgs_int = gen.generate_dataset(defect_type='int')
98
+ cfgs_vac= gen.generate_dataset(defect_type='vac')
99
+ cfgs_frk = gen.generate_dataset(defect_type='frenkel')
100
+ ```
101
+
102
+ ### Find voids
103
+
104
+ ```python
105
+ from ase.io import read
106
+ from gridmlip._void_search import clustered_local_minima
107
+
108
+ atoms = read('file.cif')
109
+
110
+ g = Grid(atoms, specie=3, empty_framework=False)
111
+ g.generate_mesh()
112
+
113
+ clusters = clustered_local_minima(
114
+ -g.min_distances_data, # can be any data computed for the generated grid
115
+ min_distance=2.0,
116
+ relative_max_value=0.5,
117
+ cell=g.atoms.cell,
118
+ coord_type='cart'
119
+ )
120
+
121
+ for (pos, energy, n) in clusters:
122
+ print('Site', pos)
123
+ print('Site energy', energy) # here, energy is the minimal distances
124
+ print('Number of points in a cluster', n)
125
+
126
+ ```
127
+
128
+ ### Bond valence site energy calculations
129
+
130
+ ```python
131
+ from ase.io import read
132
+ from ions.decorate import assign_oxidation_states
133
+ from gridmlip.integrations.bvse import bvse_distribution
134
+
135
+ atoms = read('LiFePO4.cif')
136
+ atoms = assign_oxidation_states(atoms)
137
+
138
+ PARAMS = {
139
+ "mobile_ion": "Li+",
140
+ "resolution": 0.2,
141
+ "r_min": 0.8,
142
+ "r_cut": 10.0,
143
+ "empty_framework": True,
144
+ "k": 100,
145
+ "f": 0.74,
146
+ }
147
+
148
+ grid, energies = bvse_distribution(atoms, **PARAMS)
149
+ grid.percolation_barriers() # energies already loaded via grid.load_energies(energies)
150
+
151
+ ```
152
+
153
+
154
+
155
+ ## Notebooks
156
+
157
+
158
+ - [Using SevenNet for calculating percolation barriers](notebooks/integrations.ipynb)
159
+
160
+ - [Using moment tensor potentials (as implemented in MLIP 2.0) for calculating percolation barriers](notebooks/mtp.ipynb)
161
+
162
+
163
+ ### How to cite
164
+ If you use the gridmlip library, please, consider citing this repository.
165
+
@@ -0,0 +1,44 @@
1
+ import numpy as np
2
+
3
+
4
+ def _get_rng(rng=None):
5
+ if isinstance(rng, np.random.Generator):
6
+ return rng
7
+ return np.random.default_rng(rng)
8
+
9
+
10
+ def distort_lattice(atoms, std=0.03, rng=None):
11
+ """Randomly apply independent strain to the three lattice vectors."""
12
+ rng = _get_rng(rng)
13
+
14
+ cell = atoms.cell.array.copy()
15
+ lengths = np.linalg.norm(cell, axis=1)
16
+
17
+ scale = rng.uniform(
18
+ 1 - std,
19
+ 1 + std,
20
+ size=3,
21
+ )
22
+
23
+ new_lengths = lengths * scale
24
+ new_cell = cell * (new_lengths / lengths)[:, None]
25
+
26
+ distorted = atoms.copy()
27
+ distorted.info["original_cell"] = atoms.cell.array.copy()
28
+ distorted.set_cell(new_cell, scale_atoms=True)
29
+
30
+ return distorted
31
+
32
+
33
+ def rattle_positions(atoms, std=0.05, rng=None):
34
+ """Randomly rattle atomic positions."""
35
+ rng = _get_rng(rng)
36
+
37
+ rattled = atoms.copy()
38
+ rattled.arrays["original_positions"] = (
39
+ rattled.positions.copy()
40
+ )
41
+
42
+ rattled.rattle(stdev=std, rng=rng)
43
+
44
+ return rattled
@@ -0,0 +1,251 @@
1
+ import numpy as np
2
+ from tqdm import tqdm
3
+
4
+ from spglib import get_symmetry_dataset
5
+ from ions import Percolator, Edge
6
+
7
+ from .core import Grid
8
+ from ._neighborhood import nn_list
9
+ from ._distortion import rattle_positions, distort_lattice
10
+
11
+
12
+
13
+ class DefectGenerator:
14
+
15
+ def __init__(
16
+ self,
17
+ atoms,
18
+ specie: int,
19
+ resolution: float = 0.25,
20
+ r_cut: float = 6.0,
21
+ r_min: float = 1.0,
22
+ symprec: float | None = 0.1,
23
+ max_sep_dist: float | str = 'auto',
24
+ upper_bound: float = 6.0,
25
+ verbose: bool = False,
26
+ seed: int | np.random.Generator | None = None,
27
+ ):
28
+ if specie is None:
29
+ raise ValueError("specie is required (atomic number of the mobile species)")
30
+
31
+ self.atoms = atoms.copy()
32
+ self.specie = specie
33
+ self.resolution = resolution
34
+ self.r_cut = r_cut
35
+ self.r_min = r_min
36
+ self.symprec = symprec
37
+ self.max_sep_dist = max_sep_dist
38
+ self.verbose = verbose
39
+ self.upper_bound = upper_bound
40
+ self._specie_symbol = atoms[atoms.numbers == specie].symbols[0]
41
+ self.rng = np.random.default_rng(seed)
42
+ self._percolator = None # placeholder
43
+ self._mincut_maxdim = None # placeholder
44
+
45
+
46
+ def _make_grid(self, atoms):
47
+ grid = Grid(
48
+ atoms,
49
+ specie=self.specie,
50
+ resolution=self.resolution,
51
+ symprec=self.symprec,
52
+ empty_framework=False,
53
+ r_cut=self.r_cut,
54
+ r_min=self.r_min,
55
+ verbose=self.verbose,
56
+ )
57
+ grid.generate_mesh()
58
+ return grid
59
+
60
+ @staticmethod
61
+ def _valid_positions(mesh_pos, framework, r_min):
62
+ """Filter candidate positions using the actual framework."""
63
+ if r_min <= 0:
64
+ return mesh_pos
65
+
66
+ distances, _ = nn_list(framework.positions, mesh_pos, r_cut=r_min, cell=framework.cell)
67
+
68
+ return mesh_pos[distances >= r_min]
69
+
70
+
71
+ @staticmethod
72
+ def _remove_atoms(atoms, indices):
73
+ mask = np.ones(len(atoms), dtype=bool)
74
+ mask[indices] = False
75
+ return atoms[mask].copy()
76
+
77
+
78
+ @staticmethod
79
+ def _filter_by_distance(mesh_pos, center, r_cut, cell):
80
+ """Keep points within cutoff of center."""
81
+ distances, _ = nn_list(center.reshape(1, 3), mesh_pos, r_cut=r_cut, cell=cell)
82
+ return mesh_pos[distances <= r_cut]
83
+
84
+
85
+ def _get_percolator(self):
86
+ """Note: bottleneck_radius is hardcoded to 0.0 here."""
87
+ if self._percolator is None:
88
+ pl = Percolator(self.atoms, mobile_specie=self._specie_symbol, upper_bound=self.upper_bound)
89
+ mincut, maxdim = pl.mincut_maxdim(bottleneck_radius=0.0)
90
+ self._percolator = pl
91
+ self._mincut_maxdim = (mincut, maxdim)
92
+ return (self._percolator, *self._mincut_maxdim)
93
+
94
+
95
+ def _prepare_site(self, atoms, base, remove_ids, center, max_sep):
96
+ grid = self._make_grid(self._remove_atoms(atoms, remove_ids))
97
+ ir_mesh_cart = grid.mesh_cart[grid.ir_grid_points]
98
+ filtered_pos = ir_mesh_cart[grid.min_dists >= grid.r_min]
99
+ filtered_pos = self._filter_by_distance(filtered_pos, center, max_sep, atoms.cell)
100
+
101
+ # filtering due to the lower defect concentration in a supercell
102
+ defect_base = self._remove_atoms(base, remove_ids)
103
+
104
+ filtered_pos = self._valid_positions(
105
+ filtered_pos,
106
+ defect_base[defect_base.numbers == self.specie],
107
+ self.r_min,
108
+ )
109
+
110
+ return filtered_pos, defect_base
111
+
112
+
113
+ def _prepare_interstitials(self):
114
+
115
+ atoms = self.atoms.copy()
116
+ grid = self._make_grid(atoms)
117
+ base = grid._repeat_atoms(atoms, grid.r_cut)
118
+ ir_mesh_cart = grid.mesh_cart[grid.ir_grid_points]
119
+ filtered_pos = ir_mesh_cart[grid.min_dists >= grid.r_min]
120
+ return [(filtered_pos, base)]
121
+
122
+
123
+
124
+ def _prepare_vacancies(self):
125
+
126
+ atoms = self.atoms.copy()
127
+ pl, mincut, maxdim = self._get_percolator()
128
+ edges = pl.unique_edges(cutoff=mincut, bottleneck_radius=0.0)
129
+
130
+ base = Grid._repeat_atoms(atoms, self.r_cut)
131
+
132
+ sites_and_base = []
133
+ for edge_data in edges:
134
+
135
+ source, target, offset = (edge_data[0], edge_data[1], edge_data[2:])
136
+ scale = (atoms.cell.cellpar()[:3] / base.cell.cellpar()[:3])
137
+ edge = Edge(base, source, target, offset * scale)
138
+
139
+ local_max_sep = self.max_sep_dist
140
+ if local_max_sep == "auto":
141
+ local_max_sep = edge.length / 2 + self.resolution
142
+
143
+ filtered_pos, defect_base = self._prepare_site(
144
+ atoms, base, [source, target], edge.centroid, local_max_sep
145
+ )
146
+ sites_and_base.append((filtered_pos, defect_base))
147
+
148
+ return sites_and_base
149
+
150
+
151
+
152
+ def _prepare_frenkels(self):
153
+
154
+ atoms = self.atoms.copy()
155
+
156
+ equivalent_atoms = np.arange(len(atoms))
157
+ if self.symprec is not None:
158
+ dataset = get_symmetry_dataset(
159
+ (
160
+ atoms.cell,
161
+ atoms.get_scaled_positions(),
162
+ atoms.numbers,
163
+ ),
164
+ symprec=self.symprec,
165
+ )
166
+ equivalent_atoms = dataset.equivalent_atoms
167
+
168
+ ineq_sites = np.unique(equivalent_atoms)
169
+ vac_ids = ineq_sites[atoms.numbers[ineq_sites] == self.specie]
170
+
171
+ if self.max_sep_dist == "auto":
172
+ _, mincut, _ = self._get_percolator()
173
+ global_max_sep = mincut
174
+ else:
175
+ global_max_sep = self.max_sep_dist
176
+
177
+ base = Grid._repeat_atoms(atoms, self.r_cut)
178
+
179
+ sites_and_base = []
180
+ for vac_id in vac_ids:
181
+ filtered_pos, defect_base = self._prepare_site(
182
+ atoms, base, [vac_id], atoms.positions[vac_id], global_max_sep
183
+ )
184
+ sites_and_base.append((filtered_pos, defect_base))
185
+ return sites_and_base
186
+
187
+
188
+ @staticmethod
189
+ def _statistics(sites_and_base):
190
+ summary = {
191
+ 'points': sum([d[0].shape[0] for d in sites_and_base]),
192
+ 'bases': len(sites_and_base),
193
+ }
194
+ return summary
195
+
196
+
197
+ def _append_configurations(self, base, mesh_pos, pos_std=None, strain_std=None, verbose=False):
198
+ """Construct configurations"""
199
+ configs = []
200
+
201
+ for p in tqdm(mesh_pos, desc="creating configurations", disable=not verbose):
202
+ framework = base.copy()
203
+
204
+ if pos_std is not None:
205
+ framework = rattle_positions(framework, std=pos_std, rng=self.rng) # rng added
206
+
207
+ framework.append(self.specie)
208
+ framework.positions[-1] = p
209
+
210
+ if strain_std is not None:
211
+ framework = distort_lattice(framework, std=strain_std, rng=self.rng) # rng added
212
+
213
+ configs.append(framework)
214
+
215
+ return configs
216
+
217
+
218
+ def generate_dataset(
219
+ self,
220
+ defect_type: str,
221
+ pos_std: float | None = None,
222
+ strain_std: float | None = None,
223
+ verbose: bool = False,
224
+ dry_run: bool = False,
225
+ ):
226
+ if defect_type == 'int':
227
+ sites_and_base = self._prepare_interstitials()
228
+ elif defect_type == 'vac':
229
+ sites_and_base = self._prepare_vacancies()
230
+ elif defect_type == 'frenkel':
231
+ sites_and_base = self._prepare_frenkels()
232
+ else:
233
+ raise ValueError(
234
+ f"defect_type must be 'int', 'vac', or 'frenkel', got {defect_type!r}"
235
+ )
236
+
237
+ if dry_run:
238
+ return self._statistics(sites_and_base)
239
+
240
+ cfgs = []
241
+ for (mesh_pos, base) in sites_and_base:
242
+ cfgs.extend(
243
+ self._append_configurations(
244
+ base,
245
+ mesh_pos,
246
+ pos_std=pos_std,
247
+ strain_std=strain_std,
248
+ verbose=verbose,
249
+ )
250
+ )
251
+ return cfgs
@@ -123,7 +123,6 @@ class Mesh:
123
123
  npoints = len(self.mesh)
124
124
  nsym = len(self.rotations)
125
125
 
126
- lin_indices = self._index_to_linear(self.mesh)
127
126
  index_map = np.arange(npoints, dtype=int)
128
127
 
129
128
  all_images = self._apply_sym_ops()