InterOptimus 0.0.0__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.
@@ -0,0 +1,84 @@
1
+ """
2
+ This module calculate CNID vectors for a interface
3
+ """
4
+ from numpy import *
5
+ from numpy.linalg import *
6
+ from interfacemaster.cellcalc import DSCcalc
7
+ from interfacemaster.hetero_searching import apply_function_to_array, float_to_rational
8
+
9
+ def get_au_vector(B):
10
+ """
11
+ calculate the auxiliary vector of two vectors
12
+
13
+ Args:
14
+ B (array): two column vectors
15
+
16
+ Return:
17
+ (array): auxiliary normalized vector perpendicular to the input
18
+ """
19
+ auv = cross(B[:,0], B[:,1])
20
+ return auv / norm(auv)
21
+
22
+ def from_2D_to_3D_transformation(B1, B2):
23
+ """
24
+ calculate the 3D transformation matrix of two 2D bases
25
+
26
+ Args:
27
+ B1, B2 (arrays): two bases
28
+
29
+ Return:
30
+ rotation matrix converting B2 to B1
31
+ """
32
+ auv_B1 = get_au_vector(B1)
33
+ auv_B2 = get_au_vector(B2)
34
+ C1 = column_stack((B1, auv_B1))
35
+ C2 = column_stack((B2, auv_B2))
36
+ return dot(C1, inv(C2))
37
+
38
+ def get_au_lattice(B):
39
+ """
40
+ get the auxiliary 3D lattice for a 2D basis
41
+
42
+ Args:
43
+ B (array): 2D basis
44
+
45
+ Return:
46
+ (array): 3D auxiliary lattice vectors
47
+ """
48
+ auv = get_au_vector(B)
49
+ return column_stack((B,auv))
50
+
51
+ def triple_dot(a, b, c):
52
+ """
53
+ combined product
54
+ """
55
+ return dot(a, dot(b, c))
56
+
57
+ def calculate_cnid_in_supercell(interface):
58
+ """
59
+ calculate CNID for a interface
60
+
61
+ Args:
62
+ interface (Interface)
63
+
64
+ Return:
65
+ (array, dtype = float): CNID vectors by float
66
+ (array, dtype = string): CNID vectors by rational numbers
67
+ """
68
+ props = interface.interface_properties
69
+ transformation = from_2D_to_3D_transformation(props['substrate_sl_vectors'].T, props['film_sl_vectors'].T)
70
+ B_substrate = array(props['substrate_vectors']).T
71
+ B_film = array(props['film_vectors']).T
72
+ B_substrate = get_au_lattice(B_substrate)
73
+ B_film = get_au_lattice(B_film)
74
+ B_film = dot(transformation, B_film)
75
+ calc = DSCcalc()
76
+ calc.parse_int_U(B_substrate, B_film, 200)
77
+ calc.compute_CSL()
78
+ calc.compute_CNID([0,0,1])
79
+ CSL = calc.CSL
80
+ CNID = calc.CNID
81
+ slB = get_au_lattice(array(props['substrate_sl_vectors']).T)
82
+ B = get_au_lattice(array(props['substrate_vectors']).T)
83
+ CNID_sl = triple_dot(inv(slB), B, CNID)
84
+ return CNID_sl, apply_function_to_array(CNID_sl, float_to_rational)
@@ -0,0 +1,368 @@
1
+ """This module provides class to extract SOAP descriptors of the non-identical sites in the crystalline materials in Materials Project."""
2
+ from pymatgen.analysis.local_env import CrystalNN
3
+ from pymatgen.analysis.interfaces.substrate_analyzer import SubstrateAnalyzer
4
+ from pymatgen.core.structure import Structure
5
+ from pymatgen.analysis.structure_analyzer import SpacegroupAnalyzer
6
+ from dscribe.descriptors import SOAP
7
+ from ase.io import read as aR
8
+ from numpy import *
9
+ from pymatgen.core.periodic_table import Element
10
+ from mp_api.client import MPRester
11
+ import os
12
+ import itertools
13
+ import shutil
14
+ import pandas as pd
15
+ from scipy.spatial.distance import pdist
16
+ from tqdm import tqdm
17
+ import pickle
18
+ import time
19
+ from InterOptimus.tool import read_key_item, existfilehere
20
+
21
+ def get_Z(struct):
22
+ """given structure, get element names.
23
+
24
+ Args:
25
+ struct (Structure).
26
+
27
+ Return:
28
+ list of atomic numbers.
29
+ """
30
+
31
+ return [i.Z for i in struct.elements]
32
+
33
+ def generate_combinations(elements):
34
+ """given elements, get all possible combinations.
35
+
36
+ Args:
37
+ elements (list): list of elements.
38
+
39
+ Return:
40
+ combinations (list): list of combinations.
41
+ """
42
+ combinations = []
43
+
44
+ for i in range(1, len(elements) + 1):
45
+ for combo in itertools.combinations(elements, i):
46
+ combinations.append('-'.join(combo))
47
+ return combinations
48
+
49
+ def get_elements(struct):
50
+ """given structure, get atomic number list.
51
+
52
+ Args:
53
+ struct (Structure).
54
+
55
+ Return:
56
+ list of elements.
57
+ """
58
+ return [i.symbol for i in struct.elements]
59
+
60
+ def to_ase(pymatgen_struct):
61
+ """given pymatgen structure, get ase Atoms.
62
+
63
+ Args:
64
+ struct (Structure).
65
+
66
+ return:
67
+ ase Atoms.
68
+ """
69
+ pymatgen_struct.to_file('POSCAR_tt')
70
+ ase_struct = aR('POSCAR_tt')
71
+ os.remove('POSCAR_tt')
72
+ return ase_struct
73
+
74
+ def MPsearch(elements, API_KEY, theoretical = False, is_stable = True, filter_elemental_materials = True):
75
+ """searching for synthesized structures including at least a set of elements from Materials Project.
76
+
77
+ Args:
78
+ elements (list): list of elments included at least.
79
+ API_KEY (str): API key.
80
+
81
+ return:
82
+ docs (list): list of searching results.
83
+ """
84
+ #print(generate_combinations(elements))
85
+ with MPRester(API_KEY) as mpr:
86
+ docs = mpr.materials.summary.search(
87
+ chemsys=generate_combinations(elements), \
88
+ fields=["material_id", "structure", "nelements"], \
89
+ theoretical=False,
90
+ is_stable=is_stable,
91
+ )
92
+ if theoretical:
93
+ with MPRester(API_KEY) as mpr:
94
+ docs.extend(mpr.materials.summary.search(
95
+ chemsys=generate_combinations(elements), \
96
+ fields=["material_id", "structure", "nelements"], \
97
+ theoretical=True,
98
+ is_stable=is_stable,
99
+ ))
100
+ if filter_elemental_materials:
101
+ docs = [i for i in docs if i.nelements > 1]
102
+ return docs
103
+
104
+ class stct_help_class:
105
+ def __init__(self, structure):
106
+ self.structure = structure
107
+
108
+ class soap_data_generator:
109
+ """generate soap data from MP database.
110
+ """
111
+ def __init__(self, \
112
+ elements, \
113
+ API_KEY, \
114
+ theoretical, is_stable, filter_elemental_materials, structure_from_MP, film, substrate, from_dir = False):
115
+ self.elements = elements
116
+ self.theoretical = theoretical
117
+ self.structure_from_MP = structure_from_MP
118
+ if not from_dir:
119
+ if self.structure_from_MP:
120
+ self.docs = MPsearch(elements, API_KEY, theoretical, is_stable, filter_elemental_materials)
121
+ else:
122
+ self.docs = [stct_help_class(film), stct_help_class(substrate)]
123
+ else:
124
+ with open('MPdocs.pkl', 'rb') as file:
125
+ docs = pickle.load(file)
126
+ self.docs = []
127
+ for i in docs.keys():
128
+ self.docs.append(stct_help_class(Structure.from_dict(docs[i])))
129
+ """
130
+ Args:
131
+ elements (list): list of elements to consider.
132
+ API_KEY (string): API key for using Materials Project.
133
+ theoretical (bool): whether to consider theoretical materials (not synthesized yet).
134
+ """
135
+ @classmethod
136
+ def from_dir(cls):
137
+ set_data = read_key_item('INTAR')
138
+ substrate_conv = Structure.from_file('SBS.cif')
139
+ film_conv = Structure.from_file('FLM.cif')
140
+ elements = list(set([i.symbol for i in film_conv.elements]).union([i.symbol for i in substrate_conv.elements]))
141
+ return cls(elements, set_data['APIKEY'], set_data['THEORETICAL'], set_data['STABLE'], set_data['NOELEM'],\
142
+ set_data['STCTMP'], film_conv, substrate_conv, True)
143
+
144
+ def calculate_soaps(self, soap_params = None, output_sym_stct = False):
145
+ """
146
+ get soap descriptors for all the searched materials.
147
+
148
+ Args:
149
+ soap_params (dict): SOAP parameters.
150
+ """
151
+ soap_params_default = {'r_cut':6, 'n_max':7, 'l_max':7, \
152
+ 'weighting':{"function":"pow", "r0":4, "c":1, "d":1,
153
+ "m":20}}
154
+ if soap_params == None or len(soap_params) == 0:
155
+ self.soap_params = soap_params_default
156
+ else:
157
+ for j in soap_params.keys():
158
+ soap_params_default[j] = soap_params[j]
159
+ self.soap_params = soap_params_default
160
+ self.soap_elements = []
161
+ self.soap_struct_indices = []
162
+ self.sym_structures = []
163
+ self.soap_site_indices = []
164
+ self.soap_descs = []
165
+ self.min_nb_distances = []
166
+ self.EN_diffs = []
167
+ #soap analyzer initialization
168
+ with tqdm(total=len(self.docs), desc="calculating SOAPs", leave=False) as struct_bar:
169
+ for i in range(len(self.docs)):
170
+ my_soap_analyzer = soap_analyzer(self.elements, self.docs[i].structure, i, self.soap_params)
171
+
172
+ #extract soap for each element
173
+ my_soap_analyzer.extract_soap_for_searching_elements(self.elements)
174
+ #update symmetrized structure info
175
+ self.sym_structures.append(my_soap_analyzer.struct)
176
+ #update soap structured data
177
+ for j in my_soap_analyzer.soap_infos:
178
+ if len(self.soap_descs) == 0:
179
+ self.soap_descs = j.vector
180
+ else:
181
+ self.soap_descs = vstack((self.soap_descs, j.vector))
182
+ self.soap_elements.append(j.center_element)
183
+ self.soap_struct_indices.append(j.belonging_structure_index)
184
+ self.soap_site_indices.append(j.site_index)
185
+ self.min_nb_distances.append(j.min_nb_distance)
186
+ self.EN_diffs.append(j.EN_diff)
187
+ struct_bar.update(1)
188
+ self.soap_elements, self.soap_struct_indices, \
189
+ self.soap_site_indices, self.min_nb_distances, self.EN_diffs = \
190
+ array(self.soap_elements), array(self.soap_struct_indices), \
191
+ array(self.soap_site_indices), array(self.min_nb_distances),\
192
+ array(self.EN_diffs)
193
+
194
+
195
+ self.cluster_by_element()
196
+
197
+ if output_sym_stct:
198
+ try:
199
+ shutil.rmtree('docs_sym_structures')
200
+ except:
201
+ print('generate searched structures')
202
+ os.mkdir('docs_sym_structures')
203
+ for i in range(len(self.sym_structures)):
204
+ self.sym_structures[i].to_file(f'docs_sym_structures/{i}_POSCAR')
205
+
206
+ def cluster_by_element(self):
207
+ """
208
+ cluster the soap descriptors by element names.
209
+ """
210
+ self.by_element_dict = {}
211
+ min_dists_saved = existfilehere('min_dists.dat')
212
+ for i in self.elements:
213
+ self.by_element_dict[i] = {}
214
+ self.by_element_dict[i]['soap_descs'] = \
215
+ self.soap_descs[self.soap_elements == i]
216
+
217
+ self.by_element_dict[i]['soap_struct_indices'] = \
218
+ self.soap_struct_indices[self.soap_elements == i]
219
+
220
+ self.by_element_dict[i]['soap_site_indices'] = \
221
+ self.soap_site_indices[self.soap_elements == i]
222
+
223
+ self.by_element_dict[i]['min_nb_distances'] = \
224
+ self.min_nb_distances[self.soap_elements == i]
225
+ if not min_dists_saved:
226
+ with open('min_dists.dat','a') as f:
227
+ for distance in self.min_nb_distances[self.soap_elements == i]:
228
+ f.write(f'{distance} ')
229
+ f.write(f'\n')
230
+
231
+ self.by_element_dict[i]['EN_diffs'] = \
232
+ self.EN_diffs[self.soap_elements == i]
233
+
234
+ self.by_element_dict[i]['min_nb_distance'] = \
235
+ min(self.by_element_dict[i]['min_nb_distances'])
236
+
237
+ self.by_element_dict[i]['pd'] =\
238
+ pd.DataFrame(columns=['elements','struct_id','site_id'])
239
+
240
+ for j in range(len(self.by_element_dict[i]['soap_descs'])):
241
+ self.by_element_dict[i]['pd'].loc[j] =\
242
+ [self.docs[self.by_element_dict[i]['soap_struct_indices'][j]].structure.elements,\
243
+ self.by_element_dict[i]['soap_struct_indices'][j],\
244
+ self.by_element_dict[i]['soap_site_indices'][j]]
245
+
246
+ def get_distances(self):
247
+ """
248
+ get the distances(dissimilarities) of all the descriptors
249
+
250
+ Return:
251
+ distance_pdist (array): distance list.
252
+ """
253
+ distance_pdist = {}
254
+ for i in self.elements:
255
+ dis_list = pdist(self.by_element_dict[i]['soap_descs'], \
256
+ metric = 'cosine')
257
+ distance_pdist[i] = dis_list
258
+ return distance_pdist
259
+
260
+ class soap_info:
261
+ """
262
+ soap descriptor information
263
+
264
+ Args:
265
+ vector (array): soap descripor.
266
+ center_element (string): center element name.
267
+ belonging_structure_index (int): which structure it belongs to.
268
+ site_index (int): which site it is.
269
+ min_nb_distance: nearest neighboring distance.
270
+ """
271
+ def __init__(self, vector, center_element, belonging_structure_index, \
272
+ site_index, min_nb_distance, EN_diff):
273
+ self.vector = vector #soap vector
274
+ self.center_element = center_element #center element
275
+ self.belonging_structure_index = belonging_structure_index #which structure it belongs to
276
+ self.site_index = site_index #at which site
277
+ self.min_nb_distance = min_nb_distance #minimum neighboring distance
278
+ self.EN_diff = EN_diff
279
+
280
+ class soap_analyzer:
281
+ """
282
+ for a given structure, get the soap descriptors for all the non-identical sites
283
+ """
284
+ def __init__(self, elements, struct, struct_index, soap_params):
285
+ """
286
+ Args:
287
+
288
+ elements: (list): list of elements considered
289
+ structure (Structure): structure to calculate soap
290
+ struct_index (int): index of the structure
291
+ soap_params (dict): soap parameters
292
+ """
293
+ self.struct = struct
294
+ self.get_non_equi_sites_indices()
295
+
296
+ periodic_soap = SOAP(
297
+ species={i: Element(i).Z for i in elements},
298
+ r_cut=soap_params['r_cut'],
299
+ n_max=soap_params['n_max'],
300
+ l_max=soap_params['l_max'],
301
+ periodic=True,
302
+ sparse=False,
303
+ weighting = soap_params['weighting'],
304
+ #compression = {"mode": "mu2", "species_weighting":{el.symbol:el.Z * soap_params['Z_scale'] for el in Element}}
305
+ )
306
+ self.soap_discriptors_nesites = periodic_soap.create(self.ase_struct, \
307
+ centers = self.non_equi_sites_indices)
308
+ self.struct_index = struct_index
309
+
310
+ def get_non_equi_sites_indices(self):
311
+ """given structure, get the indices of the non-equivalent sites
312
+ """
313
+ analyzer = SpacegroupAnalyzer(self.struct.get_primitive_structure())
314
+ symmetrized_structure = analyzer.get_symmetrized_structure()
315
+ self.non_equi_sites_indices = [i[0] for i in symmetrized_structure.equivalent_indices]
316
+ self.non_equi_sites_elements = [i[0].label for i in symmetrized_structure.equivalent_sites]
317
+ self.struct = symmetrized_structure
318
+ self.ase_struct = to_ase(symmetrized_structure)
319
+
320
+ def extract_soap_for_searching_elements(self, cons_elements):
321
+ """extract soap for determined elements
322
+
323
+ Args:
324
+ cons_elements (list): elements to extract their soaps
325
+ """
326
+ self.soap_infos = []
327
+ for i in range(len(self.non_equi_sites_elements)):
328
+ if self.non_equi_sites_elements[i] in cons_elements:
329
+ this_soap = soap_info(self.soap_discriptors_nesites[i],
330
+ self.non_equi_sites_elements[i],
331
+ self.struct_index,
332
+ self.non_equi_sites_indices[i],
333
+ get_min_nb_distance(self.non_equi_sites_indices[i], self.struct),
334
+ get_EN_diff_crystall(self.struct, self.non_equi_sites_indices[i]))
335
+ self.soap_infos.append(this_soap)
336
+
337
+ def get_delta_distances(atom_index, structure, cutoff):
338
+ neighbors = structure.get_neighbors(structure[atom_index], r=cutoff)
339
+ if len(neighbors) > 0:
340
+ return array([neighbor[1] for neighbor in neighbors])
341
+ else:
342
+ return array([cutoff])
343
+
344
+ def get_min_nb_distance(atom_index, structure):
345
+ """
346
+ get the minimum neighboring distance for certain atom in a structure
347
+
348
+ Args:
349
+ atom_index (int): atom index in the structure
350
+ structure (Structure)
351
+
352
+ Return:
353
+ (float): nearest neighboring distance
354
+ """
355
+ neighbors = structure.get_neighbors(structure[atom_index], r=10)
356
+ return min([neighbor[1] for neighbor in neighbors])
357
+
358
+ def get_EN_diff_crystall(structure, site_idx):
359
+ cn = CrystalNN()
360
+ center_EN = structure[site_idx].specie.X
361
+ nb_ENs = array([i['site'].specie.X for i in cn.get_nn_shell_info(structure, site_idx, 1)])
362
+ return sum(nb_ENs - center_EN)
363
+
364
+ def get_EN_diff_interface(interface, site_idx, r_cut):
365
+ cn = CrystalNN()
366
+ center_EN = interface[site_idx].specie.X
367
+ nb_ENs = array([i['site'].specie.X for i in cn.get_nn_info(interface, site_idx) if i['site'].distance(interface[site_idx]) < r_cut])
368
+ return sum(nb_ENs - center_EN)