surface-construct 0.6__tar.gz → 0.7__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.
- {surface_construct-0.6/surface_construct.egg-info → surface_construct-0.7}/PKG-INFO +2 -1
- {surface_construct-0.6 → surface_construct-0.7}/setup.py +2 -1
- {surface_construct-0.6 → surface_construct-0.7}/surface_construct/surface_grid.py +113 -145
- {surface_construct-0.6 → surface_construct-0.7}/surface_construct/utils.py +71 -30
- {surface_construct-0.6 → surface_construct-0.7/surface_construct.egg-info}/PKG-INFO +2 -1
- {surface_construct-0.6 → surface_construct-0.7}/surface_construct.egg-info/requires.txt +1 -0
- {surface_construct-0.6 → surface_construct-0.7}/LICENSE +0 -0
- {surface_construct-0.6 → surface_construct-0.7}/README.md +0 -0
- {surface_construct-0.6 → surface_construct-0.7}/setup.cfg +0 -0
- {surface_construct-0.6 → surface_construct-0.7}/surface_construct/__init__.py +0 -0
- {surface_construct-0.6 → surface_construct-0.7}/surface_construct/atoms.py +0 -0
- {surface_construct-0.6 → surface_construct-0.7}/surface_construct/db.py +0 -0
- {surface_construct-0.6 → surface_construct-0.7}/surface_construct/default_parameter.py +0 -0
- {surface_construct-0.6 → surface_construct-0.7}/surface_construct/sampling.py +0 -0
- {surface_construct-0.6 → surface_construct-0.7}/surface_construct/structure.py +0 -0
- {surface_construct-0.6 → surface_construct-0.7}/surface_construct/surface.py +0 -0
- {surface_construct-0.6 → surface_construct-0.7}/surface_construct.egg-info/SOURCES.txt +0 -0
- {surface_construct-0.6 → surface_construct-0.7}/surface_construct.egg-info/dependency_links.txt +0 -0
- {surface_construct-0.6 → surface_construct-0.7}/surface_construct.egg-info/top_level.txt +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: surface_construct
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.7
|
|
4
4
|
Summary: Surface termination construction especially for complex model, such as oxides or carbides.
|
|
5
5
|
Home-page: https://gitee.com/pjren/surface_construct/
|
|
6
6
|
Author: ren
|
|
@@ -20,6 +20,7 @@ Requires-Dist: tqdm
|
|
|
20
20
|
Requires-Dist: matplotlib
|
|
21
21
|
Requires-Dist: scipy
|
|
22
22
|
Requires-Dist: scikit-learn
|
|
23
|
+
Requires-Dist: scikit-image
|
|
23
24
|
|
|
24
25
|
# 基于分层采样策略的催化剂表面位点全局分析
|
|
25
26
|
|
|
@@ -13,11 +13,12 @@ install_requires = [
|
|
|
13
13
|
'matplotlib',
|
|
14
14
|
'scipy',
|
|
15
15
|
'scikit-learn',
|
|
16
|
+
'scikit-image'
|
|
16
17
|
]
|
|
17
18
|
|
|
18
19
|
setup(
|
|
19
20
|
name='surface_construct',
|
|
20
|
-
version='0.
|
|
21
|
+
version='0.7',
|
|
21
22
|
packages=['surface_construct'],
|
|
22
23
|
url='https://gitee.com/pjren/surface_construct/',
|
|
23
24
|
license='GPL',
|
|
@@ -12,25 +12,25 @@ TODO: 双结合位点情况。表面位点采样[(x1, y1, z1), (x2, y2, z2)]。
|
|
|
12
12
|
TODO: 构效关系建立。将不同的位点向量合并,然后进行性能分布的关联,投影到二维平面(PCA)。
|
|
13
13
|
"""
|
|
14
14
|
|
|
15
|
-
import ase
|
|
16
|
-
import numpy as np
|
|
17
15
|
import pickle
|
|
16
|
+
from logging import warning
|
|
17
|
+
|
|
18
|
+
import matplotlib.tri as mtri
|
|
19
|
+
import numpy as np
|
|
18
20
|
from ase.data import covalent_radii, vdw_radii
|
|
19
21
|
from ase.geometry import find_mic
|
|
20
|
-
from ase.visualize import view
|
|
21
22
|
from matplotlib import pyplot as plt
|
|
22
|
-
import matplotlib.tri as mtri
|
|
23
23
|
from scipy.interpolate import griddata
|
|
24
24
|
from scipy.spatial import ConvexHull
|
|
25
|
-
from scipy.spatial.distance import
|
|
26
|
-
from scipy.stats import triang
|
|
25
|
+
from scipy.spatial.distance import euclidean, cdist
|
|
27
26
|
from sklearn.cluster import KMeans as Cluster
|
|
27
|
+
from sklearn.cluster import kmeans_plusplus
|
|
28
28
|
from sklearn.decomposition import PCA
|
|
29
29
|
from sklearn.gaussian_process import GaussianProcessRegressor
|
|
30
30
|
from sklearn.gaussian_process.kernels import RBF, ConstantKernel, WhiteKernel
|
|
31
31
|
from sklearn.preprocessing import StandardScaler
|
|
32
32
|
|
|
33
|
-
from surface_construct.utils import get_calc_info, GridGenerator, get_distances
|
|
33
|
+
from surface_construct.utils import get_calc_info, GridGenerator, get_distances, furthest_sites
|
|
34
34
|
|
|
35
35
|
|
|
36
36
|
def no_weight(v, **kwargs):
|
|
@@ -76,10 +76,21 @@ def vb_weight(v, **kwargs):
|
|
|
76
76
|
|
|
77
77
|
|
|
78
78
|
def reciprocal_weight(v, **kwargs):
|
|
79
|
+
"""Relate to Coulumb interaction
|
|
80
|
+
Need charge for each atom.
|
|
81
|
+
TODO: add charge option.
|
|
82
|
+
Calculate each pairs between q and q_ads.
|
|
83
|
+
q = kwargs.get('charge',0.1)
|
|
84
|
+
q_ads = kwargs.get('charge_ads',1)
|
|
85
|
+
q 可以使用 CHELG 方法快速计算。
|
|
86
|
+
"""
|
|
79
87
|
r0 = kwargs['r0']
|
|
80
88
|
v_w = r0 / v
|
|
81
89
|
return v_w
|
|
82
90
|
|
|
91
|
+
# TODO: vdw 相互作用项
|
|
92
|
+
|
|
93
|
+
# TODO: 总的加和是 valent + coulumb + vdw,或者三者的合并
|
|
83
94
|
|
|
84
95
|
def reciprocal_square_weight(v, **kwargs):
|
|
85
96
|
return reciprocal_weight(v, **kwargs) ** 2
|
|
@@ -94,6 +105,7 @@ class SurfaceGrid:
|
|
|
94
105
|
radii_type='covalent_radii',
|
|
95
106
|
radii_factor=1.0,
|
|
96
107
|
lpca=True,
|
|
108
|
+
cutoff=10,
|
|
97
109
|
):
|
|
98
110
|
self.atoms = atoms
|
|
99
111
|
num_set = set(self.atoms.numbers)
|
|
@@ -110,7 +122,7 @@ class SurfaceGrid:
|
|
|
110
122
|
else:
|
|
111
123
|
raise NotImplementedError
|
|
112
124
|
self.radii = radii
|
|
113
|
-
|
|
125
|
+
self.cutoff = cutoff
|
|
114
126
|
if rads is not None:
|
|
115
127
|
self.rads = rads
|
|
116
128
|
else:
|
|
@@ -175,76 +187,26 @@ class SurfaceGrid:
|
|
|
175
187
|
* surface_index: 表面原子的序号
|
|
176
188
|
:return:
|
|
177
189
|
"""
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
if gridxy is None:
|
|
181
|
-
nx, ny = self.grid_nx, self.grid_ny
|
|
182
|
-
|
|
183
|
-
fx_list = np.linspace(0, 1, nx, endpoint=False) # endpoint=false指的是不包括右侧端点
|
|
184
|
-
fy_list = np.linspace(0, 1, ny, endpoint=False)
|
|
185
|
-
# 格点生成
|
|
186
|
-
fgrid_x, fgrid_y = np.meshgrid(fx_list, fy_list)
|
|
187
|
-
fxyz = np.zeros((nx * ny, 3))
|
|
188
|
-
fxyz[:, 0] = fgrid_x.reshape(nx * ny)
|
|
189
|
-
fxyz[:, 1] = fgrid_y.reshape(nx * ny)
|
|
190
|
-
xyz = self.atoms.cell.cartesian_positions(fxyz)
|
|
191
|
-
else:
|
|
192
|
-
Xgrid, Ygrid = gridxy
|
|
193
|
-
x = Xgrid.reshape(-1)
|
|
194
|
-
y = Ygrid.reshape(-1)
|
|
195
|
-
z = np.zeros(x.shape)
|
|
196
|
-
xyz = np.asarray([x, y, z]).T
|
|
197
|
-
|
|
198
|
-
xmin, xmax, ymin, ymax, zmin, zmax = self.structure_boundary()
|
|
199
|
-
condition = (xyz[:, 0] > xmin) * (xyz[:, 0] < xmax) * (xyz[:, 1] > ymin) * (xyz[:, 1] < ymax)
|
|
200
|
-
xyz = xyz[condition]
|
|
201
|
-
|
|
202
|
-
xyz[:, 2] = zmax
|
|
203
|
-
z = zmax
|
|
204
|
-
nonbond_index = np.ones(len(xyz), bool)
|
|
205
|
-
atoms_pos = self.atoms.positions
|
|
206
|
-
atoms_num = self.atoms.numbers
|
|
207
|
-
if surface_index is not None:
|
|
208
|
-
atoms_pos = atoms_pos[surface_index]
|
|
209
|
-
atoms_num = atoms_num[surface_index]
|
|
210
|
-
grid_dist_array = np.zeros((len(xyz), len(atoms_pos), 3))
|
|
211
|
-
grid_dist = np.zeros((len(xyz), len(atoms_pos)))
|
|
212
|
-
while np.any(nonbond_index):
|
|
213
|
-
z -= self.interval / 10.0
|
|
214
|
-
xyz[nonbond_index, 2] = z
|
|
215
|
-
grid_dist_array[nonbond_index], grid_dist[nonbond_index] = get_distances(
|
|
216
|
-
xyz[nonbond_index], atoms_pos, cell=self.atoms.cell, pbc=self.atoms.pbc)
|
|
217
|
-
R0 = np.asarray([self.radii[i] for i in atoms_num]) + self.rads
|
|
218
|
-
# 判断最小距离是否小于共价半径之和
|
|
219
|
-
nonbond_index = nonbond_index & (~np.any(grid_dist - R0 <= 0, axis=1))
|
|
220
|
-
|
|
221
|
-
if z < zmin:
|
|
222
|
-
z_index = xyz[:, 2] > z
|
|
223
|
-
xyz = xyz[z_index] # exclude the points that lower than zmin
|
|
224
|
-
grid_dist_array = grid_dist_array[z_index]
|
|
225
|
-
grid_dist = grid_dist[z_index]
|
|
226
|
-
break
|
|
227
|
-
self.points = xyz
|
|
228
|
-
|
|
229
|
-
if surface_index is not None: # grid_dist should contain all atoms
|
|
230
|
-
grid_dist_array, grid_dist = get_distances(
|
|
231
|
-
self.points, self.atoms.positions, cell=self.atoms.cell, pbc=self.atoms.pbc)
|
|
232
|
-
"""
|
|
233
|
-
subtype = kwargs.get('subtype', None)
|
|
190
|
+
subtype = kwargs.get('subtype', 'slab') # default is slab
|
|
191
|
+
self._subtype = subtype
|
|
234
192
|
rsub = [self.radii[atomnum] for atomnum in self.atoms.numbers]
|
|
235
193
|
gridgen = GridGenerator(self.atoms, interval=self.interval, subtype=subtype, rads=self.rads, rsub=rsub)
|
|
236
|
-
self.points = gridgen.grid
|
|
194
|
+
self.points = gridgen.grid
|
|
237
195
|
self._gridgen = gridgen
|
|
238
196
|
|
|
239
197
|
#self._Dga = grid_dist
|
|
240
198
|
#self._DAga = grid_dist_array
|
|
241
199
|
|
|
242
200
|
def _calc_Dga(self):
|
|
243
|
-
# TODO: add cutoff to kwargs or as self attr
|
|
244
|
-
cutoff = 10
|
|
245
201
|
if self.points is None:
|
|
246
202
|
self.gridize()
|
|
247
|
-
|
|
203
|
+
if self._subtype == 'slab':
|
|
204
|
+
pbc = [True, True, False]
|
|
205
|
+
elif self._subtype == 'cluster':
|
|
206
|
+
pbc = [False, False, False]
|
|
207
|
+
else:
|
|
208
|
+
pbc = self.atoms.pbc
|
|
209
|
+
_, Dga = get_distances(self.points, self.atoms.positions, cutoff=self.cutoff, pbc=pbc,
|
|
248
210
|
use_ase=False, cell=self.atoms.cell)
|
|
249
211
|
self._Dga = Dga
|
|
250
212
|
|
|
@@ -255,19 +217,7 @@ class SurfaceGrid:
|
|
|
255
217
|
:param grid_idx:
|
|
256
218
|
:return:
|
|
257
219
|
"""
|
|
258
|
-
|
|
259
|
-
# # 一般使用表面格点与最近邻原子的向量, 效果不好,在顶位会出现混乱。放弃
|
|
260
|
-
# # v = self._DAga[grid_idx][self._Dga[grid_idx].argmin()]
|
|
261
|
-
# # if v[0] < 0.1 and v[1] < 0.1:
|
|
262
|
-
# if True:
|
|
263
|
-
# # 对于顶位,即向量x,y为0,0 的情况,使用最近邻原子和次紧邻原子的向量
|
|
264
|
-
# order = np.argsort(self._Dga[grid_idx])
|
|
265
|
-
# p1, p2 = self.atoms.positions[order[0:2]]
|
|
266
|
-
# v, vlen = get_distances(p1, p2, cell=self.atoms.cell, pbc=self.atoms.pbc)
|
|
267
|
-
# v = v[0, 0]
|
|
268
|
-
# return v
|
|
269
|
-
#else:
|
|
270
|
-
# raise NotImplementedError
|
|
220
|
+
|
|
271
221
|
if self._Dga is None:
|
|
272
222
|
self._calc_Dga()
|
|
273
223
|
order = np.argsort(self._Dga[grid_idx])
|
|
@@ -287,26 +237,15 @@ class SurfaceGrid:
|
|
|
287
237
|
else:
|
|
288
238
|
return None
|
|
289
239
|
|
|
290
|
-
def filter_grid_z(self, z=None):
|
|
291
|
-
"""
|
|
292
|
-
过滤 z 方向不合理的格点,默认原子坐标 zmax. 对于孔材料和cluster 已经不合时宜了
|
|
293
|
-
:param z:
|
|
294
|
-
:return:
|
|
295
|
-
"""
|
|
296
|
-
pass
|
|
297
|
-
# if z is None:
|
|
298
|
-
# z = self.atoms.positions[:, 2].max()
|
|
299
|
-
# index = self.points[:, 2] > z
|
|
300
|
-
# self.points = self.points[index]
|
|
301
|
-
# self._Dga = self._Dga[index]
|
|
302
|
-
# self._DAga = self._DAga[index]
|
|
303
|
-
|
|
304
240
|
def vectorize(self, Dga=None, return_vector=False, wf=vb_weight, pca=True, pca_ratio=0.90, **kwargs):
|
|
305
241
|
"""
|
|
306
242
|
TODO: 使用 DScribe 来进行向量化,并进行测试。如何测试?测试什么内容?
|
|
243
|
+
TODO: 产生 cluster_mesh, 生成 cluster_mesh_id 与 point_id 之间的正反向 dict
|
|
307
244
|
使用 distance matrix 来进行向量化
|
|
308
245
|
使用衰减函数对 vector 加权重。备选函数:S型,指数型(键价),1/sqrt,1/x,线性。倾向使用指数型,键价理论支持。 1/x,或者 1/sqrt,衰减更慢。
|
|
309
246
|
这种方法类似于多点地位方法(Multilateration),因而暂时称其为 Multilateration vectorization.
|
|
247
|
+
:param pca_ratio:
|
|
248
|
+
:param pca:
|
|
310
249
|
:param wf:
|
|
311
250
|
:param Dga: grid-atoms distance matrix
|
|
312
251
|
:param return_vector: 是否返回 vector 向量
|
|
@@ -371,22 +310,20 @@ class SurfaceGrid:
|
|
|
371
310
|
计算实空间和向量空间的距离转化系数。随机取十个点,求平均值。
|
|
372
311
|
:return:
|
|
373
312
|
"""
|
|
374
|
-
nsample =
|
|
313
|
+
nsample = 100
|
|
375
314
|
rng = np.random.default_rng()
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
d_vector = np.
|
|
315
|
+
idx_0 = rng.choice(range(len(self.points)-1), size=nsample)
|
|
316
|
+
idx_1 = idx_0+1
|
|
317
|
+
idx = np.asarray([[i,j] for i,j in zip(idx_0, idx_1) if (i not in idx_1 and j not in idx_0)])
|
|
318
|
+
d_grid = np.linalg.norm(self.points[idx[:, 0]] - self.points[idx[:, 1]], axis=1)
|
|
319
|
+
idx = idx[d_grid<1.2 * self.interval]
|
|
320
|
+
d_vector = np.linalg.norm(self.vector[idx[:,0]]-self.vector[idx[:,1]], axis=1).mean()
|
|
382
321
|
k = d_vector / self.interval
|
|
383
|
-
return np.
|
|
322
|
+
return np.min(k)
|
|
384
323
|
|
|
385
|
-
def grid_sample(self, N=10
|
|
324
|
+
def grid_sample(self, N=10):
|
|
386
325
|
"""
|
|
387
326
|
Warning: Obsoleted, replaced by Sampling class
|
|
388
|
-
TODO: 使用vertex 点作为分类的中心点
|
|
389
|
-
:param include_vertex: 是否对边界额外采样
|
|
390
327
|
:param N:
|
|
391
328
|
:return:
|
|
392
329
|
"""
|
|
@@ -398,57 +335,76 @@ class SurfaceGrid:
|
|
|
398
335
|
self.sample_idx = np.concatenate([self.sample_idx, [idx]])
|
|
399
336
|
self._sample_vector = np.concatenate([self._sample_vector, [self.vector[idx]]])
|
|
400
337
|
self.sample_points = np.concatenate([self.sample_points, max_sigma_point])
|
|
401
|
-
|
|
402
338
|
return [idx]
|
|
403
339
|
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
#
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
340
|
+
assert N > 1
|
|
341
|
+
hull = ConvexHull(self.vector)
|
|
342
|
+
vertices = []
|
|
343
|
+
# 去掉 hull 的 simplices 的角度较大的点
|
|
344
|
+
for i in hull.vertices:
|
|
345
|
+
p1_idx, p2_idx = np.argwhere(hull.simplices == i)
|
|
346
|
+
p0 = hull.points[i]
|
|
347
|
+
p1 = hull.points[hull.simplices[p1_idx[0],1-p1_idx[1]]]
|
|
348
|
+
p2 = hull.points[hull.simplices[p2_idx[0],1-p2_idx[1]]]
|
|
349
|
+
a = p1 - p0
|
|
350
|
+
b = p2 - p0
|
|
351
|
+
cosangle = a.dot(b)/(np.linalg.norm(a) * np.linalg.norm(b))
|
|
352
|
+
if cosangle > np.cos(np.pi*150/180):
|
|
353
|
+
vertices.append(i)
|
|
354
|
+
# 聚类,vector_mesh
|
|
355
|
+
n_vector_mesh = int(hull.volume / (self._vector_unit * self.interval)**self.vector.shape[1]) + 1
|
|
356
|
+
cluster0 = Cluster(n_clusters=n_vector_mesh)
|
|
357
|
+
cluster0.fit(self.vector)
|
|
358
|
+
mesh_centers = cluster0.cluster_centers_
|
|
359
|
+
self._mesh_centers = mesh_centers
|
|
360
|
+
nvert = len(vertices)
|
|
361
|
+
cluster = Cluster(n_clusters=N)
|
|
362
|
+
cluster.fit(mesh_centers)
|
|
363
|
+
if nvert >= N:
|
|
364
|
+
warning("Sample number should be larger than {nvert}")
|
|
365
|
+
sample_idx = [vertices[i] for i in furthest_sites(self.vector[vertices], N)]
|
|
366
|
+
else:
|
|
367
|
+
# 聚类
|
|
368
|
+
cluster2 = Cluster(n_clusters=N-nvert)
|
|
369
|
+
cluster2.fit(mesh_centers)
|
|
370
|
+
center_dist = cdist(cluster2.cluster_centers_, self.vector) # 计算每个点到中心的距离
|
|
371
|
+
sample_idx = vertices + np.argmin(center_dist, axis=-1).tolist()
|
|
372
|
+
|
|
373
|
+
self._clusters = cluster
|
|
374
|
+
self.sample_idx = sample_idx
|
|
375
|
+
self.sample_points = self.points[sample_idx]
|
|
376
|
+
self._sample_vector = self.vector[sample_idx] # 保存用于作图
|
|
377
|
+
return self.sample_idx
|
|
378
|
+
|
|
379
|
+
# TODO: 将中心重新映射回到Cartesian坐标 :
|
|
380
|
+
# 找到向量空间最紧邻的N个点,判断其实空间的距离是否小于 interval × 2,直到有三个点满足
|
|
381
|
+
# 然后进行空间变换 A x M = B,M = B / A = B x A-1, R = V x M
|
|
382
|
+
# 或者直接使用最近邻点的坐标
|
|
431
383
|
|
|
432
384
|
def plot_cluster(self, figname=None):
|
|
433
385
|
"""
|
|
434
|
-
TODO: plot cluster in real space and vector space.
|
|
435
386
|
:param figname:
|
|
436
387
|
:return:
|
|
437
388
|
"""
|
|
438
389
|
if figname is None:
|
|
439
390
|
figname = 'site_cluster.png'
|
|
440
391
|
print("Plot the site verctor and cluster ...")
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
392
|
+
if self._pca:
|
|
393
|
+
reduced_vector = self._mesh_centers[:,:2]
|
|
394
|
+
sample_vector = self._sample_vector
|
|
395
|
+
else:
|
|
396
|
+
pca = PCA(n_components=2)
|
|
397
|
+
pca.fit(self.vector)
|
|
398
|
+
reduced_vector = pca.transform(self._mesh_centers)
|
|
399
|
+
sample_vector = pca.transform(self._sample_vector)
|
|
444
400
|
# Obtain labels for each point
|
|
445
|
-
labels = self._clusters.labels_
|
|
446
|
-
|
|
447
401
|
# plot in vector space
|
|
448
402
|
fig, ax = plt.subplots()
|
|
449
403
|
ax.set_aspect('equal')
|
|
450
|
-
ax.scatter(reduced_vector[:, 0], reduced_vector[:, 1], c=
|
|
451
|
-
|
|
404
|
+
ax.scatter(reduced_vector[:, 0], reduced_vector[:, 1], c=self._clusters.labels_, cmap=plt.cm.Paired)
|
|
405
|
+
ax.scatter(sample_vector[:, 0],sample_vector[:, 1], marker="+", s=100, linewidths=2,
|
|
406
|
+
color="black", zorder=10)
|
|
407
|
+
title = f"The site vector colored in {self._clusters.n_clusters} clusters"
|
|
452
408
|
ax.set_title(title)
|
|
453
409
|
fig.set_dpi(300)
|
|
454
410
|
fig.set_size_inches(10, 10)
|
|
@@ -457,11 +413,14 @@ class SurfaceGrid:
|
|
|
457
413
|
plt.cla()
|
|
458
414
|
plt.close("all")
|
|
459
415
|
|
|
460
|
-
# plot
|
|
416
|
+
# plot grid
|
|
417
|
+
labels = self._clusters.predict(self.vector)
|
|
461
418
|
fig, ax = plt.subplots()
|
|
462
419
|
ax.set_aspect('equal')
|
|
463
420
|
ax.scatter(self.points[:, 0], self.points[:, 1], c=labels, s=1, cmap=plt.cm.Paired)
|
|
464
|
-
|
|
421
|
+
ax.scatter(self.sample_points[:, 0],self.sample_points[:, 1], marker="+", s=100, linewidths=2,
|
|
422
|
+
color="black", zorder=10)
|
|
423
|
+
title = f"The site grid colored in {self._clusters.n_clusters} clusters"
|
|
465
424
|
ax.set_title(title)
|
|
466
425
|
fig.set_dpi(300)
|
|
467
426
|
fig.set_size_inches(10, 10)
|
|
@@ -470,6 +429,7 @@ class SurfaceGrid:
|
|
|
470
429
|
plt.cla()
|
|
471
430
|
plt.close("all")
|
|
472
431
|
|
|
432
|
+
|
|
473
433
|
def set_sample(self, sample_points, keep_old_sample=True):
|
|
474
434
|
"""
|
|
475
435
|
手动设置采样格点,计算 self._sample_vector。z 坐标自动被忽略。
|
|
@@ -482,7 +442,15 @@ class SurfaceGrid:
|
|
|
482
442
|
new_sample_points = sample_points.copy()
|
|
483
443
|
new_sample_points[:, 2] = z
|
|
484
444
|
# 计算 vector
|
|
485
|
-
|
|
445
|
+
if self._subtype == 'slab':
|
|
446
|
+
pbc = [True, True, False]
|
|
447
|
+
elif self._subtype == 'cluster':
|
|
448
|
+
pbc = [False, False, False]
|
|
449
|
+
else:
|
|
450
|
+
pbc = self.atoms.pbc
|
|
451
|
+
DAga, Dga = get_distances(new_sample_points, self.atoms.positions,
|
|
452
|
+
cutoff=self.cutoff,
|
|
453
|
+
cell=self.atoms.cell, pbc=pbc)
|
|
486
454
|
# 计算 points 的 vector
|
|
487
455
|
vector = self.vectorize(Dga=Dga, pca=self.lpca)
|
|
488
456
|
if keep_old_sample and self.sample_points is not None:
|
|
@@ -1,17 +1,15 @@
|
|
|
1
1
|
import itertools
|
|
2
|
-
from distutils.command.sdist import sdist
|
|
3
|
-
from enum import unique
|
|
4
2
|
|
|
3
|
+
import ase
|
|
4
|
+
import ase.geometry
|
|
5
5
|
import numpy as np
|
|
6
|
-
from ase.data import
|
|
6
|
+
from ase.data import vdw_radii, chemical_symbols
|
|
7
7
|
from ase.neighborlist import natural_cutoffs
|
|
8
|
-
from numpy import dtype
|
|
9
|
-
from numpy.ma.core import nonzero
|
|
10
|
-
from scipy.spatial import ConvexHull, cKDTree
|
|
11
|
-
from skimage.measure import marching_cubes
|
|
12
|
-
import ase
|
|
13
8
|
from ase.visualize import view
|
|
14
|
-
import
|
|
9
|
+
from networkx.algorithms.cuts import volume
|
|
10
|
+
from scipy.sparse import coo_matrix
|
|
11
|
+
from scipy.spatial import ConvexHull, cKDTree, Delaunay
|
|
12
|
+
from skimage.measure import marching_cubes
|
|
15
13
|
|
|
16
14
|
|
|
17
15
|
def calc_hull_vertices(v):
|
|
@@ -48,9 +46,10 @@ def get_calc_info(calc=None):
|
|
|
48
46
|
}
|
|
49
47
|
return calc_info
|
|
50
48
|
|
|
51
|
-
def get_distances(p1, p2=None, cutoff=10, cell=None, use_ase=False):
|
|
49
|
+
def get_distances(p1, p2=None, cutoff=10, cell=None, pbc=None, use_ase=False):
|
|
52
50
|
"""
|
|
53
51
|
计算位点周围原子的距离,参考 ase.geometry.get_distances. 对于更大的体系使用 cDTree 来计算。
|
|
52
|
+
:param pbc:
|
|
54
53
|
:param p1: grid positions
|
|
55
54
|
:param p2: atoms.positions
|
|
56
55
|
:param cutoff: 截断半径,只考虑距离之内的距离,超过该距离的定为 np.inf
|
|
@@ -58,8 +57,11 @@ def get_distances(p1, p2=None, cutoff=10, cell=None, use_ase=False):
|
|
|
58
57
|
:param use_ase: 如果 use_ase is True,则使用 ase.geometry.get_distances,即周期性条件等价的原子只考虑一次
|
|
59
58
|
:return:
|
|
60
59
|
"""
|
|
61
|
-
|
|
62
|
-
|
|
60
|
+
ncell = np.floor((cutoff * 2) / cell.lengths())
|
|
61
|
+
for ip,p in enumerate(pbc):
|
|
62
|
+
if not p: # 如果不是周期性的,则不要重复
|
|
63
|
+
ncell[ip] = 0
|
|
64
|
+
if np.all(ncell==1):
|
|
63
65
|
use_ase = True
|
|
64
66
|
|
|
65
67
|
if use_ase:
|
|
@@ -68,7 +70,7 @@ def get_distances(p1, p2=None, cutoff=10, cell=None, use_ase=False):
|
|
|
68
70
|
if p2 is None:
|
|
69
71
|
p2 = p1.copy()
|
|
70
72
|
|
|
71
|
-
ranges = [np.arange(-1 * p, p + 1) for p in
|
|
73
|
+
ranges = [np.arange(-1 * p, p + 1) for p in ncell]
|
|
72
74
|
hkls = np.array(list(itertools.product(*ranges)))
|
|
73
75
|
hkls = np.concatenate([hkls, np.zeros([hkls.shape[0], 3-hkls.shape[1]], dtype=int)], axis=1)
|
|
74
76
|
vrvecs = hkls @ cell
|
|
@@ -76,13 +78,14 @@ def get_distances(p1, p2=None, cutoff=10, cell=None, use_ase=False):
|
|
|
76
78
|
tree1 = cKDTree(p1, copy_data=True)
|
|
77
79
|
tree2 = cKDTree(p2, copy_data=True)
|
|
78
80
|
sdm = tree1.sparse_distance_matrix(tree2, max_distance=cutoff)
|
|
81
|
+
dist = sdm.toarray()
|
|
79
82
|
# set 0 to np.inf
|
|
80
|
-
|
|
81
|
-
for k in
|
|
82
|
-
|
|
83
|
-
np.seterr(divide='ignore')
|
|
84
|
-
s = np.divide(1,
|
|
85
|
-
dist =
|
|
83
|
+
mask = dist==0
|
|
84
|
+
#for k in sdm.keys():
|
|
85
|
+
# sdm[k] = np.inf
|
|
86
|
+
#np.seterr(divide='ignore')
|
|
87
|
+
#s = np.divide(1, sdm.toarray())
|
|
88
|
+
dist[mask] = np.inf
|
|
86
89
|
return None, dist
|
|
87
90
|
|
|
88
91
|
class GridGenerator:
|
|
@@ -109,10 +112,18 @@ class GridGenerator:
|
|
|
109
112
|
self.interval = interval
|
|
110
113
|
|
|
111
114
|
if subtype is None:
|
|
112
|
-
|
|
115
|
+
npbc = sum(atoms.pbc)
|
|
116
|
+
if npbc == 0:
|
|
113
117
|
self.subtype = 'cluster'
|
|
114
|
-
|
|
118
|
+
elif npbc == 2:
|
|
119
|
+
if atoms.pbc[-1]:
|
|
120
|
+
raise "Error: Slab should in xy direction!"
|
|
121
|
+
self.subtype = 'slab'
|
|
122
|
+
elif npbc == 3:
|
|
115
123
|
self.subtype = 'bulk'
|
|
124
|
+
else:
|
|
125
|
+
raise NotImplementedError("Subtype not implemented yet!")
|
|
126
|
+
|
|
116
127
|
elif subtype.lower() in ['slab', 'bulk', 'cluster']:
|
|
117
128
|
self.subtype = subtype.lower()
|
|
118
129
|
else:
|
|
@@ -161,9 +172,10 @@ class GridGenerator:
|
|
|
161
172
|
# 格点生成
|
|
162
173
|
grid_x, grid_y, grid_z = np.meshgrid(xarray, yarray, zarray, indexing='ij')
|
|
163
174
|
xyz = np.asarray([grid_x.ravel(), grid_y.ravel(), grid_z.ravel()]).T
|
|
175
|
+
xyz = rattle(xyz, stdev=self.interval/3)
|
|
164
176
|
grid_tree = cKDTree(xyz, copy_data=True)
|
|
165
177
|
|
|
166
|
-
|
|
178
|
+
dist_max = coo_matrix((1,nx*ny*nz))
|
|
167
179
|
atoms_num_type = set(atoms.numbers)
|
|
168
180
|
# 对于不同的原子类型取不同的半径
|
|
169
181
|
for num_type in atoms_num_type:
|
|
@@ -176,9 +188,9 @@ class GridGenerator:
|
|
|
176
188
|
sdm0 = atoms_tree.sparse_distance_matrix(grid_tree, max_distance=0)
|
|
177
189
|
for k in sdm0.keys():
|
|
178
190
|
sdm[k] = 1
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
verts, faces, normals, values = marching_cubes(
|
|
191
|
+
dist_max = sdm.tocoo().nanmax(axis=0).maximum(dist_max)
|
|
192
|
+
dist_max = dist_max.transpose().toarray()
|
|
193
|
+
verts, faces, normals, values = marching_cubes(dist_max.reshape((nx, ny, nz)), 0, allow_degenerate=False)
|
|
182
194
|
verts = np.asarray(verts, dtype=int)
|
|
183
195
|
unique_verts = np.unique(verts,axis=0) # exclude some repeat points
|
|
184
196
|
# _points = np.asarray([[grid_x[i,j,k],grid_y[i,j,k],grid_z[i,j,k]] for i, j, k in unique_verts[:]]) # 校验数值
|
|
@@ -203,7 +215,7 @@ class GridGenerator:
|
|
|
203
215
|
nx, ny, nz = map(len, [fx_list, fy_list, fz_list])
|
|
204
216
|
fgrid_x, fgrid_y, fgrid_z = np.meshgrid(fx_list, fy_list, fz_list, indexing='ij')
|
|
205
217
|
fxyz = np.asarray([fgrid_x.ravel(), fgrid_y.ravel(), fgrid_z.ravel()]).T
|
|
206
|
-
xyz = atoms.cell.cartesian_positions(fxyz)
|
|
218
|
+
xyz = rattle(atoms.cell.cartesian_positions(fxyz), stdev=self.interval/3)
|
|
207
219
|
grid_tree = cKDTree(xyz, copy_data=True)
|
|
208
220
|
|
|
209
221
|
# 对atoms 在 xy 方向超胞. Adapt from ase.geometry.geometry.general_find_mic
|
|
@@ -215,7 +227,7 @@ class GridGenerator:
|
|
|
215
227
|
super_num = np.concatenate([atoms.numbers] * 9)
|
|
216
228
|
rsub = np.concatenate([self.rsub] * 9)
|
|
217
229
|
|
|
218
|
-
|
|
230
|
+
dist_max = coo_matrix((1,nx*ny*nz))
|
|
219
231
|
atoms_num_type = set(atoms.numbers)
|
|
220
232
|
# 对于不同的原子类型取不同的半径
|
|
221
233
|
for num_type in atoms_num_type:
|
|
@@ -228,9 +240,9 @@ class GridGenerator:
|
|
|
228
240
|
sdm0 = atoms_tree.sparse_distance_matrix(grid_tree, max_distance=0)
|
|
229
241
|
for k in sdm0.keys():
|
|
230
242
|
sdm[k] = 1
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
verts, faces, normals, values = marching_cubes(
|
|
243
|
+
dist_max = sdm.tocoo().nanmax(axis=0).maximum(dist_max)
|
|
244
|
+
dist_max = dist_max.transpose().toarray()
|
|
245
|
+
verts, faces, normals, values = marching_cubes(dist_max.reshape((nx, ny, nz)), 0, allow_degenerate=False)
|
|
234
246
|
verts = np.asarray(verts, dtype=int)
|
|
235
247
|
unique_verts = np.unique(verts,axis=0) # exclude some repeat points
|
|
236
248
|
fpoints = np.asarray([fgrid_x[unique_verts[:,0], unique_verts[:,1], unique_verts[:,2]],
|
|
@@ -253,3 +265,32 @@ class GridGenerator:
|
|
|
253
265
|
if len(self.grid) > 10000:
|
|
254
266
|
print("Too much grid number, it will be very slow.")
|
|
255
267
|
view(self.atoms + ase.Atoms(symbols=['X'] * len(self.grid), positions=self.grid))
|
|
268
|
+
|
|
269
|
+
|
|
270
|
+
def rattle(positions, stdev=0.001, rng=None, seed=None):
|
|
271
|
+
"""Rattle the grid to make the vector distribution more smooth.
|
|
272
|
+
Adapt from ase.Atoms.rattle
|
|
273
|
+
"""
|
|
274
|
+
if seed is not None and rng is not None:
|
|
275
|
+
raise ValueError('Please do not provide both seed and rng.')
|
|
276
|
+
if rng is None:
|
|
277
|
+
if seed is None:
|
|
278
|
+
seed = 42
|
|
279
|
+
rng = np.random.RandomState(seed)
|
|
280
|
+
return positions + rng.normal(scale=stdev, size=positions.shape)
|
|
281
|
+
|
|
282
|
+
|
|
283
|
+
def furthest_sites(points, n):
|
|
284
|
+
# return the n sites that covers the max volume
|
|
285
|
+
assert n < len(points)
|
|
286
|
+
combs = list(itertools.combinations(range(len(points)), n))
|
|
287
|
+
volumes = []
|
|
288
|
+
if n==2:
|
|
289
|
+
for c in combs:
|
|
290
|
+
volumes.append(np.linalg.norm(points[c[0]] - points[c[1]]))
|
|
291
|
+
elif n>2:
|
|
292
|
+
for c in combs:
|
|
293
|
+
pp = [points[i] for i in c]
|
|
294
|
+
volumes.append(ConvexHull(pp).volume)
|
|
295
|
+
idx = combs[np.argmax(volumes)]
|
|
296
|
+
return idx
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: surface_construct
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.7
|
|
4
4
|
Summary: Surface termination construction especially for complex model, such as oxides or carbides.
|
|
5
5
|
Home-page: https://gitee.com/pjren/surface_construct/
|
|
6
6
|
Author: ren
|
|
@@ -20,6 +20,7 @@ Requires-Dist: tqdm
|
|
|
20
20
|
Requires-Dist: matplotlib
|
|
21
21
|
Requires-Dist: scipy
|
|
22
22
|
Requires-Dist: scikit-learn
|
|
23
|
+
Requires-Dist: scikit-image
|
|
23
24
|
|
|
24
25
|
# 基于分层采样策略的催化剂表面位点全局分析
|
|
25
26
|
|
|
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
|
{surface_construct-0.6 → surface_construct-0.7}/surface_construct.egg-info/dependency_links.txt
RENAMED
|
File without changes
|
|
File without changes
|