gridmlip 0.2.3__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.3/gridmlip.egg-info → gridmlip-0.2.5}/PKG-INFO +54 -1
- {gridmlip-0.2.3 → gridmlip-0.2.5}/README.md +53 -0
- gridmlip-0.2.5/gridmlip/_neighborhood.py +77 -0
- gridmlip-0.2.5/gridmlip/_version.py +1 -0
- gridmlip-0.2.5/gridmlip/_void_search.py +114 -0
- {gridmlip-0.2.3 → gridmlip-0.2.5}/gridmlip/core.py +6 -1
- gridmlip-0.2.5/gridmlip/integrations/bvse.py +142 -0
- gridmlip-0.2.5/gridmlip/select.py +1 -0
- {gridmlip-0.2.3 → gridmlip-0.2.5/gridmlip.egg-info}/PKG-INFO +54 -1
- {gridmlip-0.2.3 → gridmlip-0.2.5}/gridmlip.egg-info/SOURCES.txt +3 -0
- {gridmlip-0.2.3 → gridmlip-0.2.5}/scripts/_void_search.py +20 -4
- gridmlip-0.2.3/gridmlip/_neighborhood.py +0 -29
- gridmlip-0.2.3/gridmlip/_version.py +0 -1
- {gridmlip-0.2.3 → gridmlip-0.2.5}/LICENSE +0 -0
- {gridmlip-0.2.3 → gridmlip-0.2.5}/MANIFEST.in +0 -0
- {gridmlip-0.2.3 → gridmlip-0.2.5}/gridmlip/__init__.py +0 -0
- {gridmlip-0.2.3 → gridmlip-0.2.5}/gridmlip/_distortion.py +0 -0
- {gridmlip-0.2.3 → gridmlip-0.2.5}/gridmlip/_generate.py +0 -0
- {gridmlip-0.2.3 → gridmlip-0.2.5}/gridmlip/_mesh.py +0 -0
- {gridmlip-0.2.3 → gridmlip-0.2.5}/gridmlip/_percolation_analysis.py +0 -0
- {gridmlip-0.2.3 → gridmlip-0.2.5}/gridmlip/cli.py +0 -0
- {gridmlip-0.2.3 → gridmlip-0.2.5}/gridmlip/integrations/__init__.py +0 -0
- {gridmlip-0.2.3 → gridmlip-0.2.5}/gridmlip/integrations/gnn_lib.py +0 -0
- {gridmlip-0.2.3 → gridmlip-0.2.5}/gridmlip/integrations/mace.py +0 -0
- {gridmlip-0.2.3 → gridmlip-0.2.5}/gridmlip/integrations/sevennet.py +0 -0
- {gridmlip-0.2.3 → gridmlip-0.2.5}/gridmlip/utils.py +0 -0
- {gridmlip-0.2.3 → gridmlip-0.2.5}/gridmlip.egg-info/dependency_links.txt +0 -0
- {gridmlip-0.2.3 → gridmlip-0.2.5}/gridmlip.egg-info/requires.txt +0 -0
- {gridmlip-0.2.3 → gridmlip-0.2.5}/gridmlip.egg-info/top_level.txt +0 -0
- {gridmlip-0.2.3 → gridmlip-0.2.5}/gridmlip.egg-info/zip-safe +0 -0
- {gridmlip-0.2.3 → gridmlip-0.2.5}/pyproject.toml +0 -0
- {gridmlip-0.2.3 → gridmlip-0.2.5}/scripts/__init__.py +0 -0
- {gridmlip-0.2.3 → gridmlip-0.2.5}/scripts/_nebtools.py +0 -0
- {gridmlip-0.2.3 → gridmlip-0.2.5}/scripts/estimate_bvel13k_dataset_size.py +0 -0
- {gridmlip-0.2.3 → gridmlip-0.2.5}/setup.cfg +0 -0
- {gridmlip-0.2.3 → gridmlip-0.2.5}/setup.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: gridmlip
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.5
|
|
4
4
|
Summary: gridmlip
|
|
5
5
|
Home-page: https://github.com/dembart/gridmlip
|
|
6
6
|
Author: Artem Dembitskiy
|
|
@@ -118,6 +118,59 @@ cfgs_vac= gen.generate_dataset(defect_type='vac')
|
|
|
118
118
|
cfgs_frk = gen.generate_dataset(defect_type='frenkel')
|
|
119
119
|
```
|
|
120
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
|
+
|
|
121
174
|
## Notebooks
|
|
122
175
|
|
|
123
176
|
|
|
@@ -99,6 +99,59 @@ cfgs_vac= gen.generate_dataset(defect_type='vac')
|
|
|
99
99
|
cfgs_frk = gen.generate_dataset(defect_type='frenkel')
|
|
100
100
|
```
|
|
101
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
|
+
|
|
102
155
|
## Notebooks
|
|
103
156
|
|
|
104
157
|
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import itertools
|
|
2
|
+
import numpy as np
|
|
3
|
+
from scipy.spatial import cKDTree
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def nn_list(atoms_pos, mesh_pos, r_cut, cell=None, k=1, return_orig_ids=False):
|
|
7
|
+
"""
|
|
8
|
+
k nearest neighbors for each query point in `mesh_pos`.
|
|
9
|
+
|
|
10
|
+
Parameters
|
|
11
|
+
----------
|
|
12
|
+
atoms_pos : (N, 3) ndarray
|
|
13
|
+
Atom positions.
|
|
14
|
+
|
|
15
|
+
mesh_pos : (M, 3) ndarray
|
|
16
|
+
Query points.
|
|
17
|
+
|
|
18
|
+
r_cut : float
|
|
19
|
+
Cutoff
|
|
20
|
+
|
|
21
|
+
cell : (3, 3) ndarray or None
|
|
22
|
+
Periodic cell. If given, periodic images within `r_cut` are added.
|
|
23
|
+
|
|
24
|
+
k : int
|
|
25
|
+
Number of neighbors.
|
|
26
|
+
|
|
27
|
+
Returns
|
|
28
|
+
-------
|
|
29
|
+
distances : (M, k) ndarray
|
|
30
|
+
Distances. `inf` where no neighbor was found within `r_cut`.
|
|
31
|
+
|
|
32
|
+
indexes : (M, k) ndarray
|
|
33
|
+
Indices into the buffered array
|
|
34
|
+
|
|
35
|
+
orig_indexes : (M, k) ndarray
|
|
36
|
+
Indices into the original `atoms_pos`
|
|
37
|
+
"""
|
|
38
|
+
atoms_pos = np.asarray(atoms_pos)
|
|
39
|
+
n_atoms = len(atoms_pos)
|
|
40
|
+
|
|
41
|
+
if cell is not None and np.any(cell):
|
|
42
|
+
cell = np.asarray(cell)
|
|
43
|
+
volume = np.linalg.det(cell)
|
|
44
|
+
h1 = volume / np.linalg.norm(np.cross(cell[2, :], cell[1, :]))
|
|
45
|
+
h2 = volume / np.linalg.norm(np.cross(cell[0, :], cell[2, :]))
|
|
46
|
+
h3 = volume / np.linalg.norm(np.cross(cell[0, :], cell[1, :]))
|
|
47
|
+
scale = 2 * np.array(np.ceil(r_cut / np.array([h1, h2, h3])), dtype=int) + 1
|
|
48
|
+
scales = [np.arange(-(s // 2), s // 2 + 1) for s in scale]
|
|
49
|
+
|
|
50
|
+
translations = np.array(list(itertools.product(*scales))) # (T, 3)
|
|
51
|
+
n_trans = len(translations)
|
|
52
|
+
|
|
53
|
+
# (n_atoms, n_trans, 3) -> (n_atoms * n_trans, 3), C-order:
|
|
54
|
+
# buffer row k = i * n_trans + t
|
|
55
|
+
buffer = (atoms_pos[:, None, :] + translations @ cell).reshape(-1, 3)
|
|
56
|
+
|
|
57
|
+
# Consistent with the C-order reshape above.
|
|
58
|
+
buffer_to_orig = np.repeat(np.arange(n_atoms), n_trans)
|
|
59
|
+
else:
|
|
60
|
+
buffer = atoms_pos
|
|
61
|
+
buffer_to_orig = np.arange(n_atoms)
|
|
62
|
+
|
|
63
|
+
tree = cKDTree(buffer)
|
|
64
|
+
distances, indexes = tree.query(
|
|
65
|
+
mesh_pos, workers=-1, k=k, distance_upper_bound=r_cut
|
|
66
|
+
)
|
|
67
|
+
|
|
68
|
+
if not return_orig_ids:
|
|
69
|
+
return distances, indexes
|
|
70
|
+
|
|
71
|
+
# cKDTree returns len(buffer) for "not found". Map valid entries back
|
|
72
|
+
# to original atom indices; leave "not found" as -1.
|
|
73
|
+
orig_indexes = np.full_like(indexes, -1)
|
|
74
|
+
valid = indexes < len(buffer)
|
|
75
|
+
orig_indexes[valid] = buffer_to_orig[indexes[valid]]
|
|
76
|
+
|
|
77
|
+
return distances, indexes, orig_indexes
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "0.2.5"
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
import numpy as np
|
|
2
|
+
from scipy import ndimage
|
|
3
|
+
from scipy.spatial import cKDTree
|
|
4
|
+
from ase.cell import Cell
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def _find_local_minima(distribution, size=3):
|
|
8
|
+
E = distribution[:-1, :-1, :-1]
|
|
9
|
+
minf = ndimage.minimum_filter(E, size=size, mode='wrap')
|
|
10
|
+
mask = E == minf
|
|
11
|
+
|
|
12
|
+
padded = np.pad(mask, 1, mode='wrap')
|
|
13
|
+
labeled, _ = ndimage.label(padded, structure=np.ones((3, 3, 3), bool))
|
|
14
|
+
labeled = labeled[1:-1, 1:-1, 1:-1]
|
|
15
|
+
|
|
16
|
+
labels = np.unique(labeled)
|
|
17
|
+
labels = labels[labels != 0]
|
|
18
|
+
idx = np.array([np.argwhere(labeled == l)[0] for l in labels])
|
|
19
|
+
val = E[tuple(idx.T)]
|
|
20
|
+
return idx, val
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def _cluster_minima(idx, val, shape, cell, min_distance, max_value=None):
|
|
24
|
+
if max_value is not None:
|
|
25
|
+
m = val < max_value
|
|
26
|
+
idx, val = idx[m], val[m]
|
|
27
|
+
|
|
28
|
+
cell = np.asarray(cell, dtype=float)
|
|
29
|
+
frac = idx / np.array(shape, dtype=float)
|
|
30
|
+
cart = frac @ cell
|
|
31
|
+
|
|
32
|
+
shifts = np.array(np.meshgrid([-1, 0, 1], [-1, 0, 1], [-1, 0, 1], indexing='ij')).reshape(3, -1).T
|
|
33
|
+
n = len(idx)
|
|
34
|
+
rep_cart = ((shifts[:, None, :] + frac[None, :, :]).reshape(-1, 3)) @ cell
|
|
35
|
+
owner = np.tile(np.arange(n), len(shifts))
|
|
36
|
+
tree = cKDTree(rep_cart)
|
|
37
|
+
|
|
38
|
+
order = np.argsort(val)
|
|
39
|
+
alive = np.ones(n, bool)
|
|
40
|
+
|
|
41
|
+
clusters = []
|
|
42
|
+
for i in order:
|
|
43
|
+
if not alive[i]:
|
|
44
|
+
continue
|
|
45
|
+
neighbors = np.unique(owner[tree.query_ball_point(cart[i], min_distance)])
|
|
46
|
+
clusters.append((idx[i], val[i], len(neighbors)))
|
|
47
|
+
for j in neighbors:
|
|
48
|
+
alive[j] = False
|
|
49
|
+
|
|
50
|
+
return clusters
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def clustered_local_minima(
|
|
54
|
+
data: np.ndarray,
|
|
55
|
+
min_distance: float,
|
|
56
|
+
relative_max_value: float,
|
|
57
|
+
cell: np.ndarray,
|
|
58
|
+
coord_type : str = 'cart',
|
|
59
|
+
):
|
|
60
|
+
"""
|
|
61
|
+
Find local minima for calculated data on a grid.
|
|
62
|
+
|
|
63
|
+
Parameters
|
|
64
|
+
----------
|
|
65
|
+
data : np.ndarray of shape (N, M, K)
|
|
66
|
+
data calculated for the generated grid
|
|
67
|
+
|
|
68
|
+
min_distance : float
|
|
69
|
+
minimal distance between voids
|
|
70
|
+
|
|
71
|
+
relative_max_value : float
|
|
72
|
+
relative maximum value of the data to search for minima
|
|
73
|
+
`data.min() + relative_max_value`
|
|
74
|
+
|
|
75
|
+
cell : np.ndarray of size (3,3)
|
|
76
|
+
lattice matrix
|
|
77
|
+
|
|
78
|
+
coord_type : str, "cart" by default
|
|
79
|
+
in what format minima coordinates will be return
|
|
80
|
+
allowed values are "cart", "frac", mesh"
|
|
81
|
+
"cart" - cartesian coordinates
|
|
82
|
+
"frac" - fractional coordinates
|
|
83
|
+
"mesh" - mesh posititions
|
|
84
|
+
|
|
85
|
+
Returns
|
|
86
|
+
-------
|
|
87
|
+
clusters : list of lists,
|
|
88
|
+
each list contains position of a minimum,
|
|
89
|
+
energy value, and number of points clustered
|
|
90
|
+
|
|
91
|
+
"""
|
|
92
|
+
assert coord_type in ("cart", "frac", "mesh")
|
|
93
|
+
|
|
94
|
+
idx, val = _find_local_minima(data)
|
|
95
|
+
core_shape = tuple(s - 1 for s in data.shape)
|
|
96
|
+
clusters = _cluster_minima(idx, val, core_shape,
|
|
97
|
+
min_distance=min_distance,
|
|
98
|
+
max_value=data.min() + relative_max_value,
|
|
99
|
+
cell=cell,
|
|
100
|
+
)
|
|
101
|
+
|
|
102
|
+
if coord_type == "mesh":
|
|
103
|
+
return clusters
|
|
104
|
+
else:
|
|
105
|
+
cell_ase = Cell(cell)
|
|
106
|
+
clusters_new = []
|
|
107
|
+
for i, v, n in clusters:
|
|
108
|
+
scaled_site = i/(np.array(data.shape) - 1)
|
|
109
|
+
if coord_type == "cart":
|
|
110
|
+
cart_site = cell_ase.cartesian_positions(scaled_site)
|
|
111
|
+
clusters_new.append([cart_site, v, n])
|
|
112
|
+
else:
|
|
113
|
+
clusters_new.append([scaled_site, v, n])
|
|
114
|
+
return clusters_new
|
|
@@ -132,7 +132,12 @@ class Grid:
|
|
|
132
132
|
h3 = cell.volume/np.linalg.norm(np.cross(cell[0, :], cell[1, :]))
|
|
133
133
|
scale = np.array(np.ceil(r_cut/np.array([h1, h2, h3])), dtype=int)
|
|
134
134
|
return atoms.repeat(scale)
|
|
135
|
-
|
|
135
|
+
|
|
136
|
+
@property
|
|
137
|
+
def min_distances_data(self):
|
|
138
|
+
return self.min_dists[self.mapping].reshape(self.mesh_shape)
|
|
139
|
+
|
|
140
|
+
|
|
136
141
|
@classmethod
|
|
137
142
|
def from_file(
|
|
138
143
|
cls,
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
import re
|
|
2
|
+
import numpy as np
|
|
3
|
+
|
|
4
|
+
from ase.data import covalent_radii, atomic_numbers
|
|
5
|
+
from ions.data import principle_number
|
|
6
|
+
from ions.decorate import assign_bvse_params, assign_oxidation_states
|
|
7
|
+
from ions.calculators.potential import BVFF
|
|
8
|
+
|
|
9
|
+
from ..core import Grid
|
|
10
|
+
from .._neighborhood import nn_list
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def _decompose(mobile_ion):
|
|
15
|
+
|
|
16
|
+
""" Decompose input string into chemical element and oxidation state"""
|
|
17
|
+
|
|
18
|
+
element = re.sub('\d', '', mobile_ion).replace("+","").replace("-","")
|
|
19
|
+
oxi_state = re.sub('\D', '', mobile_ion)
|
|
20
|
+
|
|
21
|
+
if '-' in mobile_ion:
|
|
22
|
+
sign = -1
|
|
23
|
+
else:
|
|
24
|
+
sign = 1
|
|
25
|
+
if len(oxi_state) > 0:
|
|
26
|
+
if sign > 0:
|
|
27
|
+
oxi_state = float(oxi_state)
|
|
28
|
+
else:
|
|
29
|
+
oxi_state = -float(oxi_state)
|
|
30
|
+
else:
|
|
31
|
+
oxi_state = sign
|
|
32
|
+
|
|
33
|
+
return element, atomic_numbers[element], int(oxi_state)
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def _get_array(atoms, name):
|
|
37
|
+
""" Helper function"""
|
|
38
|
+
arr = atoms.get_array(name)
|
|
39
|
+
arr = np.concatenate([arr, [np.nan]]) # np.nan is added to deal with kDTree upper bound
|
|
40
|
+
return arr
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def bvse_distribution(
|
|
45
|
+
atoms: object,
|
|
46
|
+
mobile_ion: str = None,
|
|
47
|
+
resolution: float = 0.25,
|
|
48
|
+
r_cut: float = 10.0,
|
|
49
|
+
r_min: float = 0.8,
|
|
50
|
+
empty_framework: bool = True,
|
|
51
|
+
k: int = 100,
|
|
52
|
+
f: float = 0.74,
|
|
53
|
+
verbose: bool = False,
|
|
54
|
+
):
|
|
55
|
+
|
|
56
|
+
"""
|
|
57
|
+
Compute the bond valence site energy (BVSE) distribution for a mobile ion.
|
|
58
|
+
|
|
59
|
+
Parameters
|
|
60
|
+
----------
|
|
61
|
+
atoms : ase.Atoms
|
|
62
|
+
Structure containing the framework and mobile-ion species.
|
|
63
|
+
|
|
64
|
+
mobile_ion : str
|
|
65
|
+
Chemical symbol of the mobile ion (e.g., "Li", "Na").
|
|
66
|
+
|
|
67
|
+
resolution : float
|
|
68
|
+
Grid spacing in Å for the energy landscape. Default 0.25.
|
|
69
|
+
|
|
70
|
+
r_cut : float
|
|
71
|
+
Cutoff radius in Å for bond-valence interactions. Default 10.0.
|
|
72
|
+
|
|
73
|
+
r_min : float
|
|
74
|
+
Minimum interatomic distance in Å. Default 0.8.
|
|
75
|
+
|
|
76
|
+
empty_framework : bool
|
|
77
|
+
If True, remove mobile ions before computing the landscape. Default True.
|
|
78
|
+
|
|
79
|
+
k : int
|
|
80
|
+
Number of bins / grid points along each axis. Default 100.
|
|
81
|
+
|
|
82
|
+
f : float
|
|
83
|
+
Screening factor (e.g., for the BVSE prefactor). Default 0.74.
|
|
84
|
+
|
|
85
|
+
verbose : bool
|
|
86
|
+
print out some info
|
|
87
|
+
|
|
88
|
+
Returns
|
|
89
|
+
-------
|
|
90
|
+
gridmlip.Grid, energies
|
|
91
|
+
grid with generated mesh and an array of calculated energies for irreducible mesh points
|
|
92
|
+
"""
|
|
93
|
+
|
|
94
|
+
symbol, specie, q1 = _decompose(mobile_ion)
|
|
95
|
+
n1, rc1 = principle_number[specie], covalent_radii[specie]
|
|
96
|
+
|
|
97
|
+
atoms_decorated = assign_bvse_params(atoms, symbol, q1, self_interaction=True)
|
|
98
|
+
base = atoms_decorated.copy()
|
|
99
|
+
|
|
100
|
+
if empty_framework:
|
|
101
|
+
base = atoms_decorated[atoms_decorated.numbers != specie]
|
|
102
|
+
|
|
103
|
+
grid = Grid(atoms, specie=specie, resolution=resolution,
|
|
104
|
+
r_cut=r_cut, r_min=r_min, empty_framework=empty_framework,
|
|
105
|
+
verbose=verbose)
|
|
106
|
+
grid.generate_mesh()
|
|
107
|
+
mesh_cart = grid.mesh_cart[grid.ir_grid_points]
|
|
108
|
+
mesh_cart = mesh_cart[grid.min_dists > grid.r_min]
|
|
109
|
+
|
|
110
|
+
distances, _, ids = nn_list(base.positions, mesh_cart, r_cut=r_cut,
|
|
111
|
+
cell=base.cell, k=k, return_orig_ids=True)
|
|
112
|
+
|
|
113
|
+
r_min = np.take(_get_array(base, 'r_min'), ids, axis = -1)
|
|
114
|
+
alpha = np.take(_get_array(base, 'alpha'), ids, axis = -1)
|
|
115
|
+
rc2 = np.take(_get_array(base, 'rc2'), ids, axis = -1)
|
|
116
|
+
d0 = np.take(_get_array(base, 'd0'), ids, axis = -1)
|
|
117
|
+
q2 = np.take(_get_array(base, 'oxi_states'), ids, axis = -1)
|
|
118
|
+
q2 = np.where(q2 * q1 > 0, q2, 0)
|
|
119
|
+
n2 = np.take(_get_array(base, 'n2'), ids, axis = -1)
|
|
120
|
+
|
|
121
|
+
morse = np.nan_to_num(BVFF.Morse(distances, r_min, d0, alpha),
|
|
122
|
+
copy=False,
|
|
123
|
+
nan=0.0,
|
|
124
|
+
).sum(axis=1)
|
|
125
|
+
|
|
126
|
+
coulomb = np.nan_to_num(BVFF.Coulomb(
|
|
127
|
+
q1,
|
|
128
|
+
q2,
|
|
129
|
+
distances,
|
|
130
|
+
rc1,
|
|
131
|
+
rc2,
|
|
132
|
+
n1,
|
|
133
|
+
n2,
|
|
134
|
+
f=f),
|
|
135
|
+
copy=False,
|
|
136
|
+
nan=0.0,
|
|
137
|
+
).sum(axis=1)
|
|
138
|
+
|
|
139
|
+
energies = morse + coulomb
|
|
140
|
+
grid.load_energies(energies)
|
|
141
|
+
|
|
142
|
+
return grid, energies
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
# FPS sampling with/without relative distance
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: gridmlip
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.5
|
|
4
4
|
Summary: gridmlip
|
|
5
5
|
Home-page: https://github.com/dembart/gridmlip
|
|
6
6
|
Author: Artem Dembitskiy
|
|
@@ -118,6 +118,59 @@ cfgs_vac= gen.generate_dataset(defect_type='vac')
|
|
|
118
118
|
cfgs_frk = gen.generate_dataset(defect_type='frenkel')
|
|
119
119
|
```
|
|
120
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
|
+
|
|
121
174
|
## Notebooks
|
|
122
175
|
|
|
123
176
|
|
|
@@ -11,8 +11,10 @@ gridmlip/_mesh.py
|
|
|
11
11
|
gridmlip/_neighborhood.py
|
|
12
12
|
gridmlip/_percolation_analysis.py
|
|
13
13
|
gridmlip/_version.py
|
|
14
|
+
gridmlip/_void_search.py
|
|
14
15
|
gridmlip/cli.py
|
|
15
16
|
gridmlip/core.py
|
|
17
|
+
gridmlip/select.py
|
|
16
18
|
gridmlip/utils.py
|
|
17
19
|
gridmlip.egg-info/PKG-INFO
|
|
18
20
|
gridmlip.egg-info/SOURCES.txt
|
|
@@ -21,6 +23,7 @@ gridmlip.egg-info/requires.txt
|
|
|
21
23
|
gridmlip.egg-info/top_level.txt
|
|
22
24
|
gridmlip.egg-info/zip-safe
|
|
23
25
|
gridmlip/integrations/__init__.py
|
|
26
|
+
gridmlip/integrations/bvse.py
|
|
24
27
|
gridmlip/integrations/gnn_lib.py
|
|
25
28
|
gridmlip/integrations/mace.py
|
|
26
29
|
gridmlip/integrations/sevennet.py
|
|
@@ -3,7 +3,7 @@ from scipy import ndimage
|
|
|
3
3
|
from scipy.spatial import cKDTree
|
|
4
4
|
|
|
5
5
|
|
|
6
|
-
def
|
|
6
|
+
def _find_local_minima(distribution, size=3):
|
|
7
7
|
E = distribution[:-1, :-1, :-1]
|
|
8
8
|
minf = ndimage.minimum_filter(E, size=size, mode='wrap')
|
|
9
9
|
mask = E == minf
|
|
@@ -19,7 +19,7 @@ def find_local_minima(distribution, size=3):
|
|
|
19
19
|
return idx, val
|
|
20
20
|
|
|
21
21
|
|
|
22
|
-
def
|
|
22
|
+
def _cluster_minima(idx, val, shape, cell, min_distance, max_value=None):
|
|
23
23
|
if max_value is not None:
|
|
24
24
|
m = val < max_value
|
|
25
25
|
idx, val = idx[m], val[m]
|
|
@@ -50,9 +50,25 @@ def cluster_minima(idx, val, shape, cell, min_distance, max_value=None):
|
|
|
50
50
|
|
|
51
51
|
|
|
52
52
|
def clustered_local_minima(data, min_distance, relative_max_value, cell):
|
|
53
|
-
|
|
53
|
+
"""
|
|
54
|
+
Find local minima on a calculated grid distribution.
|
|
55
|
+
|
|
56
|
+
Parameters
|
|
57
|
+
----------
|
|
58
|
+
|
|
59
|
+
data : np.ndarray of shape (N, M, K)
|
|
60
|
+
data calculated for the generated grid
|
|
61
|
+
|
|
62
|
+
min_distance : float
|
|
63
|
+
minimal distance between clusters
|
|
64
|
+
|
|
65
|
+
relative_max_value : float
|
|
66
|
+
relative maximum value of the data to search for minima
|
|
67
|
+
`data.min() + relative_max_value`
|
|
68
|
+
"""
|
|
69
|
+
idx, val = _find_local_minima(data)
|
|
54
70
|
core_shape = tuple(s - 1 for s in data.shape)
|
|
55
|
-
clusters =
|
|
71
|
+
clusters = _cluster_minima(idx, val, core_shape,
|
|
56
72
|
min_distance=min_distance,
|
|
57
73
|
max_value=data.min() + relative_max_value,
|
|
58
74
|
cell = cell
|
|
@@ -1,29 +0,0 @@
|
|
|
1
|
-
import itertools
|
|
2
|
-
import numpy as np
|
|
3
|
-
from scipy.spatial import cKDTree
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
def nn_list(atoms_pos, mesh_pos, r_cut, cell=None):
|
|
7
|
-
|
|
8
|
-
if np.any(cell):
|
|
9
|
-
volume = np.linalg.det(cell)
|
|
10
|
-
h1 = volume/np.linalg.norm(np.cross(cell[2, :], cell[1, :]))
|
|
11
|
-
h2 = volume/np.linalg.norm(np.cross(cell[0, :], cell[2, :]))
|
|
12
|
-
h3 = volume/np.linalg.norm(np.cross(cell[0, :], cell[1, :]))
|
|
13
|
-
scale = 2 * np.array(np.ceil(r_cut/np.array([h1, h2, h3])), dtype=int) + 1
|
|
14
|
-
scales = []
|
|
15
|
-
for s in scale:
|
|
16
|
-
scales.append(np.arange(-(s // 2), s // 2 + 1))
|
|
17
|
-
|
|
18
|
-
translations = tuple(list(itertools.product(
|
|
19
|
-
scales[0],
|
|
20
|
-
scales[1],
|
|
21
|
-
scales[2])))
|
|
22
|
-
|
|
23
|
-
buffer = (atoms_pos[:, None, :] + np.dot(translations, cell)).reshape(-1, 3, order='F')
|
|
24
|
-
else:
|
|
25
|
-
buffer = atoms_pos
|
|
26
|
-
|
|
27
|
-
tree = cKDTree(buffer)
|
|
28
|
-
distances, indexes = tree.query(mesh_pos, workers=-1, k=1, distance_upper_bound=r_cut)
|
|
29
|
-
return distances, indexes
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
__version__ = "0.2.3"
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|