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.
- {gridmlip-0.2 → gridmlip-0.2.5}/LICENSE +1 -2
- {gridmlip-0.2 → gridmlip-0.2.5}/MANIFEST.in +0 -1
- gridmlip-0.2.5/PKG-INFO +184 -0
- gridmlip-0.2.5/README.md +165 -0
- gridmlip-0.2.5/gridmlip/_distortion.py +44 -0
- gridmlip-0.2.5/gridmlip/_generate.py +251 -0
- {gridmlip-0.2 → gridmlip-0.2.5}/gridmlip/_mesh.py +0 -1
- gridmlip-0.2.5/gridmlip/_neighborhood.py +77 -0
- {gridmlip-0.2 → gridmlip-0.2.5}/gridmlip/_percolation_analysis.py +8 -8
- gridmlip-0.2.5/gridmlip/_version.py +1 -0
- gridmlip-0.2.5/gridmlip/_void_search.py +114 -0
- {gridmlip-0.2 → gridmlip-0.2.5}/gridmlip/core.py +182 -104
- gridmlip-0.2.5/gridmlip/integrations/__init__.py +0 -0
- gridmlip-0.2.5/gridmlip/integrations/bvse.py +142 -0
- gridmlip-0.2.5/gridmlip/integrations/gnn_lib.py +107 -0
- gridmlip-0.2.5/gridmlip/integrations/mace.py +93 -0
- gridmlip-0.2.5/gridmlip/integrations/sevennet.py +87 -0
- gridmlip-0.2.5/gridmlip/select.py +1 -0
- {gridmlip-0.2 → gridmlip-0.2.5}/gridmlip/utils.py +2 -1
- gridmlip-0.2.5/gridmlip.egg-info/PKG-INFO +184 -0
- gridmlip-0.2.5/gridmlip.egg-info/SOURCES.txt +33 -0
- {gridmlip-0.2 → gridmlip-0.2.5}/gridmlip.egg-info/requires.txt +2 -2
- {gridmlip-0.2 → gridmlip-0.2.5}/gridmlip.egg-info/top_level.txt +1 -0
- gridmlip-0.2.5/scripts/__init__.py +0 -0
- gridmlip-0.2.5/scripts/_nebtools.py +0 -0
- gridmlip-0.2.5/scripts/_void_search.py +76 -0
- gridmlip-0.2.5/scripts/estimate_bvel13k_dataset_size.py +446 -0
- {gridmlip-0.2 → gridmlip-0.2.5}/setup.cfg +9 -4
- gridmlip-0.2.5/setup.py +2 -0
- gridmlip-0.2/PKG-INFO +0 -94
- gridmlip-0.2/README.md +0 -76
- gridmlip-0.2/gridmlip/_neighborhood.py +0 -34
- gridmlip-0.2/gridmlip.egg-info/PKG-INFO +0 -94
- gridmlip-0.2/gridmlip.egg-info/SOURCES.txt +0 -19
- gridmlip-0.2/setup.py +0 -35
- {gridmlip-0.2 → gridmlip-0.2.5}/gridmlip/__init__.py +0 -0
- {gridmlip-0.2 → gridmlip-0.2.5}/gridmlip/cli.py +0 -0
- {gridmlip-0.2 → gridmlip-0.2.5}/gridmlip.egg-info/dependency_links.txt +0 -0
- {gridmlip-0.2 → gridmlip-0.2.5}/gridmlip.egg-info/zip-safe +0 -0
- {gridmlip-0.2 → gridmlip-0.2.5}/pyproject.toml +0 -0
gridmlip-0.2.5/PKG-INFO
ADDED
|
@@ -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
|
+
|
gridmlip-0.2.5/README.md
ADDED
|
@@ -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
|