/
pyscal.py
254 lines (219 loc) · 8.27 KB
/
pyscal.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
# coding: utf-8
# Copyright (c) Max-Planck-Institut für Eisenforschung GmbH - Computational Materials Design (CM) Department
# Distributed under the terms of "New BSD License", see the LICENSE file.
import numpy as np
from pyiron_base import Settings
from pyiron.atomistics.structure.atoms import pyiron_to_ase
import pyscal.core as pc
from sklearn import cluster
__author__ = "Sarath Menon, Jan Janssen"
__copyright__ = (
"Copyright 2020, Max-Planck-Institut für Eisenforschung GmbH - "
"Computational Materials Design (CM) Department"
)
__version__ = "1.0"
__maintainer__ = "Sarath Menon"
__email__ = "sarath.menon@rub.de"
__status__ = "development"
__date__ = "Nov 6, 2019"
s = Settings()
def get_steinhardt_parameter_structure(atoms, neighbor_method="cutoff", cutoff=0, n_clusters=2,
q=(4, 6), averaged=False, clustering=True):
"""
Calculate Steinhardts parameters
Args:
atoms: Atoms object
neighbor_method (str) : can be ['cutoff', 'voronoi']
cutoff (float) : can be 0 for adaptive cutoff or any other value
n_clusters (int) : number of clusters for K means clustering
q (list) : can be from 2-12, the required q values to be calculated
averaged (bool) : If True, calculates the averaged versions of the parameter
clustering (bool) : If True, cluster based on the q values
Returns:
q (list) : calculated q parameters
"""
s.publication_add(publication())
sys = pc.System()
sys.read_inputfile(
pyiron_to_ase(atoms),
format='ase'
)
sys.find_neighbors(
method=neighbor_method,
cutoff=cutoff
)
sys.calculate_q(
q,
averaged=averaged
)
sysq = sys.get_qvals(
q,
averaged=averaged
)
if clustering:
cl = cluster.KMeans(
n_clusters=n_clusters
)
ind = cl.fit(list(zip(*sysq))).labels_ == 0
return sysq, ind
else:
return sysq
def analyse_centro_symmetry(atoms, num_neighbors=12):
"""
Analyse centrosymmetry parameter
Args:
atoms: Atoms object
num_neighbors (int) : number of neighbors
Returns:
csm (list) : list of centrosymmetry parameter
"""
s.publication_add(publication())
sys = pc.System()
sys.read_inputfile(atoms, format="ase")
return np.array(sys.calculate_centrosymmetry(nmax=num_neighbors))
def analyse_diamond_structure(atoms, mode="total", ovito_compatibility=False):
"""
Analyse diamond structure
Args:
atoms: Atoms object
mode ("total"/"numeric"/"str"): Controls the style and level
of detail of the output.
- total : return number of atoms belonging to each structure
- numeric : return a per atom list of numbers- 0 for unknown,
1 fcc, 2 hcp, 3 bcc and 4 icosa
- str : return a per atom string of sructures
ovito_compatibility(bool): use ovito compatiblity mode
Returns:
(depends on `mode`)
"""
s.publication_add(publication())
sys = pc.System()
sys.read_inputfile(atoms, format="ase")
diamond_dict = sys.identify_diamond()
ovito_identifiers = [
'Cubic diamond',
'Cubic diamond (1st neighbor)',
'Cubic diamond (2nd neighbor)',
'Hexagonal diamond',
'Hexagonal diamond (1st neighbor)',
'Hexagonal diamond (2nd neighbor)',
'Other'
]
pyscal_identifiers = [
'others', 'fcc', 'hcp', 'bcc', 'ico', 'cubic diamond',
'cubic diamond 1NN', 'cubic diamond 2NN',
'hex diamond', 'hex diamond 1NN', 'hex diamond 2NN'
]
convert_to_ovito = {
0: 6, 1: 6, 2: 6, 3: 6, 4: 6,
5: 0, 6: 1, 7: 2, 8: 3, 9: 4, 10: 5
}
if mode == "total":
if not ovito_compatibility:
return diamond_dict
else:
return {
'IdentifyDiamond.counts.CUBIC_DIAMOND': diamond_dict['cubic diamond'],
'IdentifyDiamond.counts.CUBIC_DIAMOND_FIRST_NEIGHBOR': diamond_dict['cubic diamond 1NN'],
'IdentifyDiamond.counts.CUBIC_DIAMOND_SECOND_NEIGHBOR': diamond_dict['cubic diamond 2NN'],
'IdentifyDiamond.counts.HEX_DIAMOND': diamond_dict['hex diamond'],
'IdentifyDiamond.counts.HEX_DIAMOND_FIRST_NEIGHBOR': diamond_dict['hex diamond 1NN'],
'IdentifyDiamond.counts.HEX_DIAMOND_SECOND_NEIGHBOR': diamond_dict['hex diamond 2NN'],
'IdentifyDiamond.counts.OTHER':
diamond_dict['others'] +
diamond_dict['fcc'] +
diamond_dict['hcp'] +
diamond_dict['bcc'] +
diamond_dict['ico']
}
elif mode == "numeric":
if not ovito_compatibility:
return np.array([atom.structure for atom in sys.atoms])
else:
return np.array([convert_to_ovito[atom.structure] for atom in sys.atoms])
elif mode == "str":
if not ovito_compatibility:
return np.array([pyscal_identifiers[atom.structure] for atom in sys.atoms])
else:
return np.array([ovito_identifiers[convert_to_ovito[atom.structure]] for atom in sys.atoms])
else:
raise ValueError("Only total, str and numeric mode is imported for analyse_diamond_structure()")
def analyse_cna_adaptive(atoms, mode="total", ovito_compatibility=False):
"""
Use common neighbor analysis
Args:
atoms (pyiron.structure.atoms.Atoms): The structure to analyze.
mode ("total"/"numeric"/"str"): Controls the style and level
of detail of the output.
- total : return number of atoms belonging to each structure
- numeric : return a per atom list of numbers- 0 for unknown,
1 fcc, 2 hcp, 3 bcc and 4 icosa
- str : return a per atom string of sructures
ovito_compatibility(bool): use ovito compatiblity mode
Returns:
(depends on `mode`)
"""
s.publication_add(publication())
if mode not in ["total", "numeric", "str"]:
raise ValueError("Unsupported mode")
pyscal_parameter = ['others', 'fcc', 'hcp', 'bcc', 'ico']
ovito_parameter = [
'CommonNeighborAnalysis.counts.OTHER',
'CommonNeighborAnalysis.counts.FCC',
'CommonNeighborAnalysis.counts.HCP',
'CommonNeighborAnalysis.counts.BCC',
'CommonNeighborAnalysis.counts.ICO'
]
sys = pc.System()
sys.read_inputfile(atoms, format="ase")
cna = sys.calculate_cna()
if mode == "total":
if not ovito_compatibility:
return cna
else:
return {o: cna[p] for o, p in zip(
ovito_parameter,
pyscal_parameter
)}
else:
atoms = sys.atoms
cnalist = np.array([atom.structure for atom in atoms])
if mode == "numeric":
return cnalist
elif mode == "str":
if not ovito_compatibility:
dd = ['others', 'fcc', 'hcp', 'bcc', 'ico']
return np.array([dd[int(x)] for x in cnalist])
else:
dd = ['Other', "FCC", "HCP", "BCC", "ICO"]
return np.array([dd[int(x)] for x in cnalist])
else:
raise ValueError("Only total, str and numeric mode is imported for analyse_cna_adaptive()")
def analyse_voronoi_volume(atoms):
"""
Calculate the Voronoi volume of atoms
Args:
atoms : (pyiron.structure.atoms.Atoms): The structure to analyze.
"""
s.publication_add(publication())
sys = pc.System()
sys.read_inputfile(atoms, format="ase")
sys.find_neighbors(method="voronoi")
atoms = sys.atoms
return np.array([atom.volume for atom in atoms])
def publication():
return {
"pyscal": {
"Menon2019": {
"doi": "10.21105/joss.01824",
"url": "https://doi.org/10.21105/joss.01824",
"year": "2019",
"volume": "4",
"number": "43",
"pages": "1824",
"author": ["Sarath Menon", "Grisell Diaz Leines", "Jutta Rogal"],
"title": "pyscal: A python module for structural analysis of atomic environments",
"journal": "Journal of Open Source Software",
}
}
}