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.
Files changed (19) hide show
  1. {surface_construct-0.6/surface_construct.egg-info → surface_construct-0.7}/PKG-INFO +2 -1
  2. {surface_construct-0.6 → surface_construct-0.7}/setup.py +2 -1
  3. {surface_construct-0.6 → surface_construct-0.7}/surface_construct/surface_grid.py +113 -145
  4. {surface_construct-0.6 → surface_construct-0.7}/surface_construct/utils.py +71 -30
  5. {surface_construct-0.6 → surface_construct-0.7/surface_construct.egg-info}/PKG-INFO +2 -1
  6. {surface_construct-0.6 → surface_construct-0.7}/surface_construct.egg-info/requires.txt +1 -0
  7. {surface_construct-0.6 → surface_construct-0.7}/LICENSE +0 -0
  8. {surface_construct-0.6 → surface_construct-0.7}/README.md +0 -0
  9. {surface_construct-0.6 → surface_construct-0.7}/setup.cfg +0 -0
  10. {surface_construct-0.6 → surface_construct-0.7}/surface_construct/__init__.py +0 -0
  11. {surface_construct-0.6 → surface_construct-0.7}/surface_construct/atoms.py +0 -0
  12. {surface_construct-0.6 → surface_construct-0.7}/surface_construct/db.py +0 -0
  13. {surface_construct-0.6 → surface_construct-0.7}/surface_construct/default_parameter.py +0 -0
  14. {surface_construct-0.6 → surface_construct-0.7}/surface_construct/sampling.py +0 -0
  15. {surface_construct-0.6 → surface_construct-0.7}/surface_construct/structure.py +0 -0
  16. {surface_construct-0.6 → surface_construct-0.7}/surface_construct/surface.py +0 -0
  17. {surface_construct-0.6 → surface_construct-0.7}/surface_construct.egg-info/SOURCES.txt +0 -0
  18. {surface_construct-0.6 → surface_construct-0.7}/surface_construct.egg-info/dependency_links.txt +0 -0
  19. {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.6
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.6',
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 cdist, euclidean
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
- """ ### Old code
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.copy()
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
- _, Dga = get_distances(self.points, self.atoms.positions, cutoff=cutoff,
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
- #if self._Dga is not None:
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 = 10
313
+ nsample = 100
375
314
  rng = np.random.default_rng()
376
- idx = rng.choice(range(len(self.points)), size=nsample)
377
- d_grid = [get_distances(self.points[i], self.points[i+1], cell=self.atoms.cell, pbc=self.atoms.pbc)[1][0, 0]
378
- for i in idx]
379
- idx_filtered = [i for i, d in zip(idx, d_grid) if d < 1.2 * self.interval] # 去掉不相邻的格点对
380
- assert len(idx_filtered) > 0
381
- d_vector = np.asarray([euclidean(self.vector[i], self.vector[i+1]) for i in idx_filtered])
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.mean(k)
322
+ return np.min(k)
384
323
 
385
- def grid_sample(self, N=10, include_vertex=False):
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
- # 进行分类,然后采样。这里使用 Kmeans 方法进行聚类
405
- clusters = Cluster(n_clusters=N, random_state=0).fit(self.vector)
406
- self._clusters = clusters # 保存用于作图
407
- # 对于每一类取距离 cluster 中心最小的点的 idx
408
- centers = clusters.cluster_centers_
409
- center_dist = cdist(centers, self.vector) # 计算每个点到中心的距离
410
- index = np.argmin(center_dist, axis=-1)
411
-
412
- points_cluster_sample = self.points[index]
413
- if include_vertex:
414
- # 找到 Hull 点,加入采样
415
- hull = ConvexHull(self.vector)
416
- vertices = hull.vertices
417
- vector_hull_sample = self.vector[vertices]
418
- points_hull_sample = self.points[vertices]
419
-
420
- # 计算 hull 点与 cluster_sample 的距离, 排除太近的 cluster 采样点
421
- hull_cluster_dist_array, hull_cluster_dist = get_distances(points_hull_sample, points_cluster_sample,
422
- cell=self.atoms.cell, pbc=self.atoms.pbc)
423
- cluster_index = np.all(hull_cluster_dist > self.interval * 2, axis=0)
424
- index = np.concatenate([index, cluster_index]) # 读入能量的时候再保存
425
-
426
- self.sample_idx = index
427
- self.sample_points = self.points[index]
428
- self._sample_vector = self.vector[index] # 保存用于作图
429
-
430
- return index
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
- reduced_vector = PCA(n_components=2).fit_transform(self._raw_vector)
442
- self._reduced_vector = reduced_vector
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=labels, cmap=plt.cm.Paired)
451
- title = "The site vector colored in {} clusters".format(self._clusters.n_clusters)
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 in real space
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
- title = "The site grid colored in {} clusters".format(self._clusters.n_clusters)
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
- DAga, Dga = get_distances(new_sample_points, self.atoms.positions, cell=self.atoms.cell, pbc=self.atoms.pbc)
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 covalent_radii, vdw_radii, chemical_symbols
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 ase.geometry
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
- pbc = cell.lengths() < (cutoff * 2)
62
- if not np.any(pbc):
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 pbc]
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
- s = sdm.copy()
81
- for k in s.keys():
82
- s[k] = np.inf
83
- np.seterr(divide='ignore')
84
- s = np.divide(1, s.toarray())
85
- dist = sdm.toarray() + s
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
- if not np.all(atoms.pbc):
115
+ npbc = sum(atoms.pbc)
116
+ if npbc == 0:
113
117
  self.subtype = 'cluster'
114
- else:
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
- dist_sum = 0
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
- dist_sum = sdm.toarray().sum(axis=0).reshape((nx, ny, nz)) + dist_sum
180
-
181
- verts, faces, normals, values = marching_cubes(dist_sum, 0, allow_degenerate=False)
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
- dist_sum = 0
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
- dist_sum = sdm.toarray().sum(axis=0).reshape((nx, ny, nz)) + dist_sum
232
-
233
- verts, faces, normals, values = marching_cubes(dist_sum, 0, allow_degenerate=False)
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.6
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
 
@@ -7,3 +7,4 @@ tqdm
7
7
  matplotlib
8
8
  scipy
9
9
  scikit-learn
10
+ scikit-image
File without changes