pyemaps 2.0.0__py3-none-any.whl
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.
- pyemaps/COPYING +798 -0
- pyemaps/CifFile/CifFile_module.py +3117 -0
- pyemaps/CifFile/StarFile.py +2496 -0
- pyemaps/CifFile/TypeContentsParser.py +82 -0
- pyemaps/CifFile/YappsStarParser_1_0.py +289 -0
- pyemaps/CifFile/YappsStarParser_1_1.py +288 -0
- pyemaps/CifFile/YappsStarParser_2_0.py +369 -0
- pyemaps/CifFile/YappsStarParser_STAR2.py +382 -0
- pyemaps/CifFile/__init__.py +7 -0
- pyemaps/CifFile/drel/__init__.py +1 -0
- pyemaps/CifFile/drel/drel_ast_yacc.py +582 -0
- pyemaps/CifFile/drel/drel_lex.py +228 -0
- pyemaps/CifFile/drel/drel_runtime.py +126 -0
- pyemaps/CifFile/drel/parsetab.py +185 -0
- pyemaps/CifFile/drel/py_from_ast.py +628 -0
- pyemaps/CifFile/parsetab.py +185 -0
- pyemaps/CifFile/yapps3_compiled_rt.py +390 -0
- pyemaps/README.md +201 -0
- pyemaps/__config__.py +91 -0
- pyemaps/__init__.py +233 -0
- pyemaps/__main__.py +219 -0
- pyemaps/__version__.py +1 -0
- pyemaps/cdata/Aluminium.xtl +4 -0
- pyemaps/cdata/AluminiumOxide.xtl +5 -0
- pyemaps/cdata/Aluminium_FCC.xtl +4 -0
- pyemaps/cdata/BariumTitanate_180k.xtl +6 -0
- pyemaps/cdata/BariumTitanate_270k.xtl +7 -0
- pyemaps/cdata/BariumTitanate_Tetra.xtl +7 -0
- pyemaps/cdata/BiMnO3.xtl +27 -0
- pyemaps/cdata/Boron_Tetra.xtl +8 -0
- pyemaps/cdata/CadmiumSelenide_Hex.xtl +5 -0
- pyemaps/cdata/CadmiumSulfide_Cubic.xtl +5 -0
- pyemaps/cdata/CadmiumSulfide_Hex.xtl +5 -0
- pyemaps/cdata/Chromium_BCC.xtl +4 -0
- pyemaps/cdata/CoSb3_Skutterudite.xtl +5 -0
- pyemaps/cdata/CopperOxide.xtl +5 -0
- pyemaps/cdata/Copper_FCC.xtl +4 -0
- pyemaps/cdata/Cu2O_Cuprite.xtl +5 -0
- pyemaps/cdata/Diamond.xtl +4 -0
- pyemaps/cdata/ErbiumPyrogermanate.xtl +9 -0
- pyemaps/cdata/FePd_Tetra.xtl +5 -0
- pyemaps/cdata/FeS2_Pyrite.xtl +15 -0
- pyemaps/cdata/GalliumAntimonide.xtl +5 -0
- pyemaps/cdata/GalliumArsenide.xtl +5 -0
- pyemaps/cdata/GalliumNitride.xtl +5 -0
- pyemaps/cdata/Germanium.xtl +4 -0
- pyemaps/cdata/Gold_FCC.xtl +4 -0
- pyemaps/cdata/IndiumArsenide.xtl +5 -0
- pyemaps/cdata/LaMnO3.xtl +7 -0
- pyemaps/cdata/LeadZirconateTitanate.xtl +8 -0
- pyemaps/cdata/Li2MnO3.xtl +13 -0
- pyemaps/cdata/NaFeO2.xtl +6 -0
- pyemaps/cdata/Nb3Sn.xtl +5 -0
- pyemaps/cdata/Pentacene.xtl +39 -0
- pyemaps/cdata/Silicon.xtl +4 -0
- pyemaps/cdata/StrontiumTitanate.xtl +6 -0
- pyemaps/cdata/TelluriumDioxide.xtl +5 -0
- pyemaps/cdata/TinDioxide_RT.xtl +5 -0
- pyemaps/cdata/TitaniumDioxide_Anatase.xtl +5 -0
- pyemaps/cdata/TitaniumDioxide_Rutile.xtl +5 -0
- pyemaps/cdata/TungstenDiselenide.xtl +5 -0
- pyemaps/cdata/VanadiumDioxide_RT.xtl +6 -0
- pyemaps/cdata/ZincOxide.xtl +5 -0
- pyemaps/cdata/Zinc_HCP.xtl +4 -0
- pyemaps/cdata/ZirconiumNitride.xtl +5 -0
- pyemaps/cdata/limno2.xtl +6 -0
- pyemaps/crystals.py +1415 -0
- pyemaps/ddiffs.py +147 -0
- pyemaps/diffract/__init__.py +70 -0
- pyemaps/diffract/bloch_dec.py +779 -0
- pyemaps/diffract/csf_dec.py +242 -0
- pyemaps/diffract/dif_dec.py +519 -0
- pyemaps/diffract/dpgen_dec.py +175 -0
- pyemaps/diffract/mxtal_dec.py +201 -0
- pyemaps/diffract/powder_dec.py +137 -0
- pyemaps/diffract/stereo_dec.py +111 -0
- pyemaps/display.py +791 -0
- pyemaps/emcontrols.py +1017 -0
- pyemaps/errors.py +266 -0
- pyemaps/fileutils.py +1001 -0
- pyemaps/kdiffs.py +1221 -0
- pyemaps/license.txt +42 -0
- pyemaps/samples/adf.py +137 -0
- pyemaps/samples/al.img +0 -0
- pyemaps/samples/al_dpgen.py +57 -0
- pyemaps/samples/al_ediom.py +94 -0
- pyemaps/samples/convert_image.py +88 -0
- pyemaps/samples/dm_diff.py +225 -0
- pyemaps/samples/mask.im3 +0 -0
- pyemaps/samples/powder.py +51 -0
- pyemaps/samples/raw.img +0 -0
- pyemaps/samples/si_bloch.py +120 -0
- pyemaps/samples/si_constructor.py +51 -0
- pyemaps/samples/si_csf.py +59 -0
- pyemaps/samples/si_dif.py +134 -0
- pyemaps/samples/si_lacbed.py +59 -0
- pyemaps/samples/si_pyemaps.py +80 -0
- pyemaps/samples/si_scm.py +156 -0
- pyemaps/samples/si_stereo.py +116 -0
- pyemaps/scattering/__init__.py +26 -0
- pyemaps/scattering/sct_dec.py +31 -0
- pyemaps/spg/__init__.py +25 -0
- pyemaps/spg/spg_dec.py +60 -0
- pyemaps/stackimg.py +1031 -0
- pyemaps/xtal.py +222 -0
- pyemaps-2.0.0.dist-info/METADATA +231 -0
- pyemaps-2.0.0.dist-info/RECORD +112 -0
- pyemaps-2.0.0.dist-info/WHEEL +5 -0
- pyemaps-2.0.0.dist-info/entry_points.txt +2 -0
- pyemaps-2.0.0.dist-info/licenses/COPYING +798 -0
- pyemaps-2.0.0.dist-info/licenses/license.txt +42 -0
- pyemaps-2.0.0.dist-info/top_level.txt +1 -0
pyemaps/fileutils.py
ADDED
|
@@ -0,0 +1,1001 @@
|
|
|
1
|
+
|
|
2
|
+
"""
|
|
3
|
+
.. This file is part of pyEMAPS
|
|
4
|
+
|
|
5
|
+
Fileutils is a helper module in assisting pyemaps file i/o functions.
|
|
6
|
+
Its methods include reading crystal data files and writing simulation
|
|
7
|
+
output files.
|
|
8
|
+
|
|
9
|
+
It also detects pyemaps data home environment variables and directs
|
|
10
|
+
file i/o accoridng to the rule set in :ref:`Environment Variables <Environment Variables>`.
|
|
11
|
+
|
|
12
|
+
.. ----
|
|
13
|
+
|
|
14
|
+
.. pyEMAPS is free software. You can redistribute it and/or modify
|
|
15
|
+
.. it under the terms of the GNU General Public License as published
|
|
16
|
+
.. by the Free Software Foundation, either version 3 of the License,
|
|
17
|
+
.. or (at your option) any later version..
|
|
18
|
+
|
|
19
|
+
.. pyEMAPS is distributed in the hope that it will be useful,
|
|
20
|
+
.. but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
21
|
+
.. MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
22
|
+
.. GNU General Public License for more details.
|
|
23
|
+
|
|
24
|
+
.. You should have received a copy of the GNU General Public License
|
|
25
|
+
.. along with pyEMAPS. If not, see `<https://www.gnu.org/licenses/>`_.
|
|
26
|
+
|
|
27
|
+
.. Contact supprort@emlabsoftware.com for any questions and comments.
|
|
28
|
+
|
|
29
|
+
.. ----
|
|
30
|
+
|
|
31
|
+
.. Author: EMLab Solutions, Inc.
|
|
32
|
+
.. Date: September 26th, 2022
|
|
33
|
+
|
|
34
|
+
"""
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
from pathlib import Path
|
|
38
|
+
import os
|
|
39
|
+
import re
|
|
40
|
+
|
|
41
|
+
# ------------------pyemaps data file and locations-------------
|
|
42
|
+
# set by PYEMAPS_DATAHOME
|
|
43
|
+
# -- location to retrieve data and put darta
|
|
44
|
+
#
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
pyemaps_datahome = None
|
|
48
|
+
|
|
49
|
+
def find_datahome():
|
|
50
|
+
bLegacy = False
|
|
51
|
+
if 'PYEMAPS_CRYSTALS' in os.environ:
|
|
52
|
+
bLegacy = True
|
|
53
|
+
|
|
54
|
+
env_name = 'PYEMAPS_CRYSTALS' if bLegacy else 'PYEMAPS_DATA'
|
|
55
|
+
|
|
56
|
+
# defaults to current working directory
|
|
57
|
+
datahome = os.getcwd()
|
|
58
|
+
|
|
59
|
+
# but find pyeamsp home if exists
|
|
60
|
+
if env_name in os.environ:
|
|
61
|
+
pyemaps_home = os.getenv(env_name)
|
|
62
|
+
|
|
63
|
+
if Path(pyemaps_home).exists():
|
|
64
|
+
datahome = pyemaps_home
|
|
65
|
+
else:
|
|
66
|
+
try:
|
|
67
|
+
os.mkdir(pyemaps_home)
|
|
68
|
+
|
|
69
|
+
except OSError:
|
|
70
|
+
# can't create the directory, fall back to current directory
|
|
71
|
+
pass
|
|
72
|
+
else:
|
|
73
|
+
datahome = pyemaps_home
|
|
74
|
+
|
|
75
|
+
return datahome
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
def auto_fn(cn):
|
|
79
|
+
'''
|
|
80
|
+
Auto-generate file name based on crystal name and time stamp
|
|
81
|
+
when file is generated from pyemaps simulations.
|
|
82
|
+
|
|
83
|
+
This can be modified to tune the output to any other specific needs.
|
|
84
|
+
|
|
85
|
+
:param cn: A crystal name.
|
|
86
|
+
:type cn: string, required
|
|
87
|
+
|
|
88
|
+
:return: file name composed of crytsal name and time stamp in yyyymmddmmss format
|
|
89
|
+
rtype: string
|
|
90
|
+
|
|
91
|
+
'''
|
|
92
|
+
import datetime
|
|
93
|
+
|
|
94
|
+
curr_time = datetime.datetime.now().strftime("%Y%m%d%H%M%S")
|
|
95
|
+
|
|
96
|
+
return cn + '-' + curr_time
|
|
97
|
+
|
|
98
|
+
def find_pyemaps_datahome(home_type='crystals'):
|
|
99
|
+
'''
|
|
100
|
+
Detects enviroment variable set by PYEMAPS_DATA and return the
|
|
101
|
+
diretcory set by the variable or return current working directory.
|
|
102
|
+
|
|
103
|
+
:param home_type: Type of home directory
|
|
104
|
+
:type home_type: string, optional
|
|
105
|
+
|
|
106
|
+
:return: data home path.
|
|
107
|
+
:rtype: string
|
|
108
|
+
|
|
109
|
+
Folder names depending by *home_type* input:
|
|
110
|
+
|
|
111
|
+
1. **crystals**: all crystal data files.
|
|
112
|
+
2. **bloch**: all dynamic simulation output files.
|
|
113
|
+
3. **mxtal**: all .xyz files saved from crystal constructor.
|
|
114
|
+
4. **stereo**: all .png files saved from stereodiagram plotting methods
|
|
115
|
+
|
|
116
|
+
'''
|
|
117
|
+
|
|
118
|
+
global pyemaps_datahome
|
|
119
|
+
if pyemaps_datahome is None:
|
|
120
|
+
pyemaps_datahome = find_datahome()
|
|
121
|
+
|
|
122
|
+
if 'PYEMAPS_CRYSTALS' in os.environ:
|
|
123
|
+
return pyemaps_datahome # done when legacy
|
|
124
|
+
|
|
125
|
+
# if the environment home folder does extists
|
|
126
|
+
|
|
127
|
+
pyemaps_home = os.path.join(pyemaps_datahome, home_type)
|
|
128
|
+
|
|
129
|
+
if Path(pyemaps_home).exists():
|
|
130
|
+
return pyemaps_home
|
|
131
|
+
|
|
132
|
+
try:
|
|
133
|
+
os.mkdir(pyemaps_home)
|
|
134
|
+
except OSError:
|
|
135
|
+
print(f'failed to create {home_type} folder in pyemaps data home directory {pyemaps_datahome}')
|
|
136
|
+
print(f'{pyemaps_datahome} will be used to host {home_type} data instead')
|
|
137
|
+
return pyemaps_datahome
|
|
138
|
+
else:
|
|
139
|
+
return pyemaps_home
|
|
140
|
+
|
|
141
|
+
def compose_ofn(fn, name, ty='diffraction'):
|
|
142
|
+
'''
|
|
143
|
+
Compose output file name based file name and file type.
|
|
144
|
+
|
|
145
|
+
'''
|
|
146
|
+
pyemaps_datahome=find_pyemaps_datahome(home_type=ty)
|
|
147
|
+
|
|
148
|
+
if fn is None:
|
|
149
|
+
fn = auto_fn(name)
|
|
150
|
+
return os.path.join(pyemaps_datahome, fn)
|
|
151
|
+
|
|
152
|
+
# valid input fn
|
|
153
|
+
fpath, fname = os.path.split(fn)
|
|
154
|
+
|
|
155
|
+
# can't write any file to a path that does not exist
|
|
156
|
+
if fpath and not Path(fpath).exists():
|
|
157
|
+
raise FileNotFoundError('Error: file path not found')
|
|
158
|
+
|
|
159
|
+
if not fpath: # if no path compose fn
|
|
160
|
+
if not fname:
|
|
161
|
+
fn = auto_fn(name, ty=ty)
|
|
162
|
+
else:
|
|
163
|
+
fn = fname
|
|
164
|
+
return os.path.join(pyemaps_datahome, fn)
|
|
165
|
+
|
|
166
|
+
# left with both path not empty and exists, then go with the input
|
|
167
|
+
return fn
|
|
168
|
+
# -----------------crystal data loading and saving -----------------
|
|
169
|
+
# from/to data files on disks
|
|
170
|
+
#
|
|
171
|
+
# CIF files
|
|
172
|
+
|
|
173
|
+
|
|
174
|
+
import numpy as np
|
|
175
|
+
from numpy import asfortranarray as farray
|
|
176
|
+
from .spg.spg_dec import *
|
|
177
|
+
|
|
178
|
+
from . import (XTLError, SPGError, CIFError,
|
|
179
|
+
SPGSettingNotInRangeError,SPGITMumberNotInRangeError)
|
|
180
|
+
|
|
181
|
+
|
|
182
|
+
cell_keys=['a','b','c','alpha','beta','gamma']
|
|
183
|
+
|
|
184
|
+
at_iso_keys=['symb','x','y','z','d-w','occ']
|
|
185
|
+
|
|
186
|
+
at_noniso_keys=['symb','x','y','z','b11','b22','b33','b12','b13','b23','occ']
|
|
187
|
+
|
|
188
|
+
spg_keys=['number','setting']
|
|
189
|
+
|
|
190
|
+
crystal_data_basedir = 'cdata'
|
|
191
|
+
|
|
192
|
+
negative_infinity = float(-np.inf)
|
|
193
|
+
|
|
194
|
+
required_keys = ['cell', 'dw', 'atoms', 'spg']
|
|
195
|
+
|
|
196
|
+
NUMERIC_PATTERN = r'[-+]? (?: (?: \d* \. \d+ ) | (?: \d+ \.? ) )(?: [Ee] [+-]? \d+ ) ?'
|
|
197
|
+
RX = re.compile(NUMERIC_PATTERN, re.VERBOSE)
|
|
198
|
+
|
|
199
|
+
HM_CORE_PATTERN = r'[A-Za-z0-9 /\-+]+'
|
|
200
|
+
HM_RX = re.compile(HM_CORE_PATTERN, re.VERBOSE)
|
|
201
|
+
|
|
202
|
+
|
|
203
|
+
def _float_eq(a,b):
|
|
204
|
+
return abs(a-b) < 0.000001
|
|
205
|
+
|
|
206
|
+
def _getCIFFloats(sArr, fillval=0.0):
|
|
207
|
+
'''
|
|
208
|
+
input sArr: dictionary string array from CIF file
|
|
209
|
+
Return: float array
|
|
210
|
+
handling cases:
|
|
211
|
+
1) with () symbols in the input
|
|
212
|
+
2) with ? in the input
|
|
213
|
+
3) with . in the input
|
|
214
|
+
...
|
|
215
|
+
'''
|
|
216
|
+
|
|
217
|
+
sl = len(sArr)
|
|
218
|
+
|
|
219
|
+
retArr = [0.0]*sl
|
|
220
|
+
|
|
221
|
+
for i, w in enumerate(sArr):
|
|
222
|
+
|
|
223
|
+
ww = RX.findall(w)
|
|
224
|
+
|
|
225
|
+
try:
|
|
226
|
+
fel = float(ww[0])
|
|
227
|
+
|
|
228
|
+
except (IndexError, ValueError):
|
|
229
|
+
fel = fillval
|
|
230
|
+
|
|
231
|
+
retArr[i] = fel
|
|
232
|
+
|
|
233
|
+
return retArr
|
|
234
|
+
|
|
235
|
+
def _getCIFFloat(s):
|
|
236
|
+
'''
|
|
237
|
+
input s: dictionary string from CIF file
|
|
238
|
+
Return: float number
|
|
239
|
+
'''
|
|
240
|
+
|
|
241
|
+
digits = RX.findall(s)
|
|
242
|
+
if len(digits) == 0:
|
|
243
|
+
return negative_infinity
|
|
244
|
+
|
|
245
|
+
sw = RX.findall(s)[0]
|
|
246
|
+
|
|
247
|
+
ret = negative_infinity
|
|
248
|
+
try:
|
|
249
|
+
ret = float(sw)
|
|
250
|
+
|
|
251
|
+
except (IndexError, ValueError):
|
|
252
|
+
return negative_infinity
|
|
253
|
+
|
|
254
|
+
return ret
|
|
255
|
+
|
|
256
|
+
def _getCIFInt(s):
|
|
257
|
+
'''
|
|
258
|
+
input s: dictionary string from CIF file
|
|
259
|
+
Return: int number
|
|
260
|
+
'''
|
|
261
|
+
|
|
262
|
+
digits = RX.findall(s)
|
|
263
|
+
if len(digits) == 0:
|
|
264
|
+
return -np.inf
|
|
265
|
+
|
|
266
|
+
sw = RX.findall(s)[0]
|
|
267
|
+
try:
|
|
268
|
+
int(sw)
|
|
269
|
+
except (IndexError, ValueError):
|
|
270
|
+
return -np.inf
|
|
271
|
+
|
|
272
|
+
return int(sw)
|
|
273
|
+
|
|
274
|
+
def _scrubCIFSymmXYZ(cf, maxlen):
|
|
275
|
+
'''
|
|
276
|
+
scrub off those items white space and longer than MAX_SPG_LEN
|
|
277
|
+
|
|
278
|
+
'''
|
|
279
|
+
|
|
280
|
+
res = []
|
|
281
|
+
l = 0
|
|
282
|
+
for c in cf:
|
|
283
|
+
toks = re.split(r'\s+', c)
|
|
284
|
+
s = ''.join(toks)
|
|
285
|
+
if len(s) <= maxlen:
|
|
286
|
+
l += 1
|
|
287
|
+
res.append(s)
|
|
288
|
+
|
|
289
|
+
return l, res
|
|
290
|
+
|
|
291
|
+
def _simplifyCIF(str1, str2):
|
|
292
|
+
s1 = str1.replace('-', '+-').strip('+')
|
|
293
|
+
s2 = str2.replace('-', '+-').strip('+')
|
|
294
|
+
|
|
295
|
+
l1 = re.split(r'[+]', s1)
|
|
296
|
+
l2 = re.split(r'[+]', s2)
|
|
297
|
+
|
|
298
|
+
if len(l1) != len(l2):
|
|
299
|
+
|
|
300
|
+
return 1
|
|
301
|
+
|
|
302
|
+
exp1 = set(l1)
|
|
303
|
+
exp2 = set(l2)
|
|
304
|
+
|
|
305
|
+
if exp1 == exp2:
|
|
306
|
+
return 0
|
|
307
|
+
|
|
308
|
+
return 1
|
|
309
|
+
|
|
310
|
+
def _isCIFEquivalent(c, cc):
|
|
311
|
+
'''
|
|
312
|
+
Compare two expressions of symmetry in the form of
|
|
313
|
+
expr1,expr2,expr3
|
|
314
|
+
If they are the same arithmetic expressions, return True
|
|
315
|
+
otherwise False
|
|
316
|
+
'''
|
|
317
|
+
# import concurrent.futures
|
|
318
|
+
|
|
319
|
+
ct = re.split(r',', c)
|
|
320
|
+
cct = re.split(r',', cc)
|
|
321
|
+
|
|
322
|
+
if len(ct) != 3 or len(cct) != 3:
|
|
323
|
+
return False
|
|
324
|
+
|
|
325
|
+
sym_pairs = list(zip(ct, cct))
|
|
326
|
+
for p in sym_pairs:
|
|
327
|
+
res = _simplifyCIF(p[0], p[1])
|
|
328
|
+
if res != 0:
|
|
329
|
+
return False
|
|
330
|
+
return True
|
|
331
|
+
|
|
332
|
+
def _compareCIFSymmetry(n, coords, cf_coords):
|
|
333
|
+
'''
|
|
334
|
+
compare two symmrety array if:
|
|
335
|
+
1. coords contained in cf_coords, return 0
|
|
336
|
+
2. otherwise return 1
|
|
337
|
+
the comparison is done using sympy module with expression equivalence
|
|
338
|
+
'''
|
|
339
|
+
count = 0
|
|
340
|
+
for c in coords:
|
|
341
|
+
count += 1
|
|
342
|
+
if count > n:
|
|
343
|
+
break
|
|
344
|
+
cstr = ''.join([c[i].decode() for i in range(SPG_SYMMETRY_MAXLEN)]).strip()
|
|
345
|
+
|
|
346
|
+
found = False
|
|
347
|
+
for cfc in cf_coords:
|
|
348
|
+
if _isCIFEquivalent(cstr, cfc):
|
|
349
|
+
found = True
|
|
350
|
+
break
|
|
351
|
+
if not found:
|
|
352
|
+
return 1
|
|
353
|
+
return 0
|
|
354
|
+
|
|
355
|
+
def _matchCIFHM(l1, l0):
|
|
356
|
+
'''
|
|
357
|
+
Compare two Space Group H-M strings and find the longest match length
|
|
358
|
+
'''
|
|
359
|
+
|
|
360
|
+
res = 0
|
|
361
|
+
for i in range(len(l1)):
|
|
362
|
+
if l1[i].upper() == l0[i].upper():
|
|
363
|
+
res +=1
|
|
364
|
+
else:
|
|
365
|
+
break
|
|
366
|
+
return res
|
|
367
|
+
|
|
368
|
+
def _validAtomSiteLabel(l):
|
|
369
|
+
try:
|
|
370
|
+
from .scattering.sct_dec import sct_symbtable, sct_cifsymbtable
|
|
371
|
+
except:
|
|
372
|
+
raise CIFError(f"pyemaps scattering database module must be available")
|
|
373
|
+
|
|
374
|
+
ll = l.strip().upper()
|
|
375
|
+
|
|
376
|
+
# cover most of the cases hopefully
|
|
377
|
+
if ll in sct_symbtable or ll in sct_cifsymbtable:
|
|
378
|
+
return True, ll
|
|
379
|
+
|
|
380
|
+
# remove one character from the back until it appears in pyemaps element table
|
|
381
|
+
ellen = len(ll)
|
|
382
|
+
|
|
383
|
+
for i in range(ellen-2, -1, -1):
|
|
384
|
+
lll = ll[0:i+1]
|
|
385
|
+
|
|
386
|
+
if lll in sct_symbtable or lll in sct_cifsymbtable:
|
|
387
|
+
return True, lll
|
|
388
|
+
|
|
389
|
+
return False, l
|
|
390
|
+
|
|
391
|
+
|
|
392
|
+
def loadCrystalCIFData(fn):
|
|
393
|
+
"""
|
|
394
|
+
Reads a .cif file for crystal data.
|
|
395
|
+
|
|
396
|
+
:param fn: cif file name
|
|
397
|
+
:type: string, required
|
|
398
|
+
|
|
399
|
+
:return: tuple of crystal name and crystal data as dict object
|
|
400
|
+
:rtype: dict
|
|
401
|
+
|
|
402
|
+
.. note::
|
|
403
|
+
|
|
404
|
+
This method is still in active development.
|
|
405
|
+
|
|
406
|
+
"""
|
|
407
|
+
from .CifFile import ReadCif
|
|
408
|
+
|
|
409
|
+
SPG_NUMBER_CIFKEY=['_symmetry_Int_Tables_number',
|
|
410
|
+
'_symmetry_IT_number',
|
|
411
|
+
'_space_group_IT_number']
|
|
412
|
+
|
|
413
|
+
# check for full path
|
|
414
|
+
cfn = fn
|
|
415
|
+
if not os.path.exists(fn):
|
|
416
|
+
|
|
417
|
+
# pyemaps_home = os.getenv('PYEMAPS_CRYSTALS')
|
|
418
|
+
datahome = find_pyemaps_datahome(home_type = 'crystals')
|
|
419
|
+
cfn = os.path.join(datahome, fn)
|
|
420
|
+
|
|
421
|
+
if not os.path.exists(cfn):
|
|
422
|
+
raise CIFError('Failed to find the crystal data file', cfn)
|
|
423
|
+
|
|
424
|
+
cf = None
|
|
425
|
+
try:
|
|
426
|
+
cf = ReadCif(cfn)
|
|
427
|
+
except IOError as e:
|
|
428
|
+
raise CIFError(cfn, e.message)
|
|
429
|
+
|
|
430
|
+
if len(cf.keys()) == 0:
|
|
431
|
+
raise CIFError(cfn, 'missing required data keys')
|
|
432
|
+
|
|
433
|
+
name = cf.keys()[0]
|
|
434
|
+
if not isinstance(name, str) or len(name) == 0:
|
|
435
|
+
raise CIFError(cfn, 'missing required data name')
|
|
436
|
+
|
|
437
|
+
# retirving cell constants data
|
|
438
|
+
data = {}
|
|
439
|
+
|
|
440
|
+
cell = {}
|
|
441
|
+
|
|
442
|
+
c_dict = cf[name]
|
|
443
|
+
|
|
444
|
+
if '_cell_length_a' not in c_dict or \
|
|
445
|
+
'_cell_length_b' not in c_dict or \
|
|
446
|
+
'_cell_length_c' not in c_dict or \
|
|
447
|
+
'_cell_angle_alpha' not in c_dict or \
|
|
448
|
+
'_cell_angle_beta' not in c_dict or \
|
|
449
|
+
'_cell_angle_gamma' not in c_dict:
|
|
450
|
+
raise CIFError(cfn, 'missing required cell parameters data keys')
|
|
451
|
+
|
|
452
|
+
lp = _getCIFFloat( c_dict['_cell_length_a'])
|
|
453
|
+
|
|
454
|
+
if lp == negative_infinity:
|
|
455
|
+
raise CIFError(cfn, 'missing or invalid cell parameters value for _cell_length_a')
|
|
456
|
+
|
|
457
|
+
cell['a'] = lp
|
|
458
|
+
|
|
459
|
+
lp = _getCIFFloat( c_dict['_cell_length_b'])
|
|
460
|
+
if lp == negative_infinity:
|
|
461
|
+
raise CIFError(cfn, 'missing or invalid cell parameters value for _cell_length_b')
|
|
462
|
+
cell['b'] = lp
|
|
463
|
+
|
|
464
|
+
lp = _getCIFFloat( c_dict['_cell_length_c'])
|
|
465
|
+
if lp == negative_infinity:
|
|
466
|
+
raise CIFError(cfn, 'missing or invalid cell parameters value for _cell_length_c')
|
|
467
|
+
cell['c'] = lp
|
|
468
|
+
|
|
469
|
+
lp = _getCIFFloat( c_dict['_cell_angle_alpha'])
|
|
470
|
+
if lp == negative_infinity:
|
|
471
|
+
raise CIFError(cfn, 'missing or invalid cell parameters value for _cell_angle_alpha')
|
|
472
|
+
cell['alpha'] = lp
|
|
473
|
+
|
|
474
|
+
lp = _getCIFFloat( c_dict['_cell_angle_beta'])
|
|
475
|
+
if lp == negative_infinity:
|
|
476
|
+
raise CIFError(cfn, 'missing or invalid cell parameters value for _cell_angle_beta')
|
|
477
|
+
cell['beta'] = lp
|
|
478
|
+
|
|
479
|
+
lp = _getCIFFloat( c_dict['_cell_angle_gamma'])
|
|
480
|
+
if lp == negative_infinity:
|
|
481
|
+
raise CIFError(cfn, 'missing or invalid cell parameters value for _cell_angle_gamma')
|
|
482
|
+
cell['gamma'] = lp
|
|
483
|
+
|
|
484
|
+
data['cell'] = cell
|
|
485
|
+
|
|
486
|
+
# retrieving unitcell data
|
|
487
|
+
if '_atom_site_fract_x' not in c_dict or \
|
|
488
|
+
'_atom_site_fract_y' not in c_dict or \
|
|
489
|
+
'_atom_site_fract_z' not in c_dict:
|
|
490
|
+
raise CIFError(cfn, 'missing unit cell keys _atom_site_fract_x[y|z]')
|
|
491
|
+
|
|
492
|
+
if '_atom_site_label' not in c_dict and '_atom_site_type_symbol' not in c_dict:
|
|
493
|
+
raise CIFError(cfn, 'missing unit cell keys for atom site label')
|
|
494
|
+
|
|
495
|
+
atlabels=[]
|
|
496
|
+
|
|
497
|
+
if '_atom_site_type_symbol' in c_dict:
|
|
498
|
+
atlabels = c_dict['_atom_site_type_symbol']
|
|
499
|
+
|
|
500
|
+
else:
|
|
501
|
+
atlabels = c_dict['_atom_site_label']
|
|
502
|
+
|
|
503
|
+
|
|
504
|
+
at_len = len(atlabels)
|
|
505
|
+
|
|
506
|
+
xs = c_dict['_atom_site_fract_x']
|
|
507
|
+
if not xs and len(xs) != at_len:
|
|
508
|
+
raise CIFError(cfn, 'invalid unit cell value for _atom_site_fract_x')
|
|
509
|
+
|
|
510
|
+
xx = _getCIFFloats(xs)
|
|
511
|
+
|
|
512
|
+
ys = c_dict['_atom_site_fract_y']
|
|
513
|
+
if not ys and len(ys) != at_len:
|
|
514
|
+
raise CIFError(cfn, 'invalid unit cell value for _atom_site_fract_y')
|
|
515
|
+
|
|
516
|
+
yy = _getCIFFloats(ys)
|
|
517
|
+
|
|
518
|
+
zs = c_dict['_atom_site_fract_z']
|
|
519
|
+
if not zs and len(zs) != at_len:
|
|
520
|
+
raise CIFError(cfn, 'invalid unit cell value for _atom_site_fract_z')
|
|
521
|
+
|
|
522
|
+
zz = _getCIFFloats(zs)
|
|
523
|
+
|
|
524
|
+
# Occupancy data
|
|
525
|
+
occ = [1.0]*at_len
|
|
526
|
+
if '_atom_site_occupancy' in c_dict:
|
|
527
|
+
occ_raw = _getCIFFloats(c_dict['_atom_site_occupancy'])
|
|
528
|
+
if not occ_raw or len(occ_raw) != at_len:
|
|
529
|
+
raise CIFError(cfn, 'invalid unit cell value for _atom_site_occupancy')
|
|
530
|
+
occ = occ_raw
|
|
531
|
+
|
|
532
|
+
atoms = [dict(symb=atlabels[i], x=xx[i], y=yy[i], z=zz[i], occ=occ[i]) for i in range(at_len)]
|
|
533
|
+
|
|
534
|
+
if '_atom_site_aniso_label' in c_dict:
|
|
535
|
+
if '_atom_site_aniso_U_11' not in c_dict and \
|
|
536
|
+
'_atom_site_aniso_U_22' not in c_dict and \
|
|
537
|
+
'_atom_site_aniso_U_33' not in c_dict and \
|
|
538
|
+
'_atom_site_aniso_U_12' not in c_dict and \
|
|
539
|
+
'_atom_site_aniso_U_13' not in c_dict and \
|
|
540
|
+
'_atom_site_aniso_U_23' not in c_dict and \
|
|
541
|
+
'_atom_site_aniso_B_11' not in c_dict and \
|
|
542
|
+
'_atom_site_aniso_B_22' not in c_dict and \
|
|
543
|
+
'_atom_site_aniso_B_33' not in c_dict and \
|
|
544
|
+
'_atom_site_aniso_B_12' not in c_dict and \
|
|
545
|
+
'_atom_site_aniso_B_13' not in c_dict and \
|
|
546
|
+
'_atom_site_aniso_B_23' not in c_dict:
|
|
547
|
+
raise CIFError(cfn, 'missing required unit cell keys such as _atom_site_aniso_U_NN or _atom_site_aniso_B_NN')
|
|
548
|
+
|
|
549
|
+
for i in range(at_len):
|
|
550
|
+
atoms[i]['b11'] = 0.0
|
|
551
|
+
atoms[i]['b22'] = 0.0
|
|
552
|
+
atoms[i]['b33'] = 0.0
|
|
553
|
+
atoms[i]['b12'] = 0.0
|
|
554
|
+
atoms[i]['b13'] = 0.0
|
|
555
|
+
atoms[i]['b23'] = 0.0
|
|
556
|
+
|
|
557
|
+
if '_atom_site_aniso_U_11' in c_dict:
|
|
558
|
+
dw = 'uij'
|
|
559
|
+
u11 = _getCIFFloats(c_dict['_atom_site_aniso_U_11'])
|
|
560
|
+
|
|
561
|
+
for i, uf11 in enumerate(u11):
|
|
562
|
+
atoms[i]['b11'] = uf11
|
|
563
|
+
|
|
564
|
+
if '_atom_site_aniso_U_22' in c_dict:
|
|
565
|
+
dw = 'uij'
|
|
566
|
+
u22 = _getCIFFloats(c_dict['_atom_site_aniso_U_22'])
|
|
567
|
+
|
|
568
|
+
for i, uf22 in enumerate(u22):
|
|
569
|
+
atoms[i]['b22'] = uf22
|
|
570
|
+
|
|
571
|
+
if '_atom_site_aniso_U_33' in c_dict:
|
|
572
|
+
dw = 'uij'
|
|
573
|
+
u33 = _getCIFFloats(c_dict['_atom_site_aniso_U_33'])
|
|
574
|
+
for i, uf33 in enumerate(u33):
|
|
575
|
+
atoms[i]['b33'] = uf33
|
|
576
|
+
|
|
577
|
+
if '_atom_site_aniso_U_12' in c_dict:
|
|
578
|
+
dw = 'uij'
|
|
579
|
+
u12 = _getCIFFloats(c_dict['_atom_site_aniso_U_12'])
|
|
580
|
+
|
|
581
|
+
for i, uf12 in enumerate(u12):
|
|
582
|
+
atoms[i]['b12'] = uf12
|
|
583
|
+
|
|
584
|
+
if '_atom_site_aniso_U_13' in c_dict:
|
|
585
|
+
dw = 'uij'
|
|
586
|
+
u13 = _getCIFFloats(c_dict['_atom_site_aniso_U_13'])
|
|
587
|
+
for i, uf13 in enumerate(u13):
|
|
588
|
+
atoms[i]['b13'] = uf13
|
|
589
|
+
|
|
590
|
+
if '_atom_site_aniso_U_23' in c_dict:
|
|
591
|
+
dw = 'uij'
|
|
592
|
+
u23 = _getCIFFloats(c_dict['_atom_site_aniso_U_23'])
|
|
593
|
+
for i, uf23 in enumerate(u23):
|
|
594
|
+
atoms[i]['b23'] = uf23
|
|
595
|
+
|
|
596
|
+
if '_atom_site_aniso_B_11' in c_dict:
|
|
597
|
+
b11 = [0.0]*at_len
|
|
598
|
+
dw = 'bij'
|
|
599
|
+
b11 = _getCIFFloats(c_dict['_atom_site_aniso_B_11'])
|
|
600
|
+
for i, bf11 in enumerate(b11):
|
|
601
|
+
atoms[i]['b11'] = bf11
|
|
602
|
+
|
|
603
|
+
if '_atom_site_aniso_B_22' in c_dict:
|
|
604
|
+
b22 = [0.0]*at_len
|
|
605
|
+
dw = 'bij'
|
|
606
|
+
b22 = _getCIFFloats(c_dict['_atom_site_aniso_B_22'])
|
|
607
|
+
for i, bf22 in enumerate(b22):
|
|
608
|
+
atoms[i]['b22'] = bf22
|
|
609
|
+
|
|
610
|
+
if '_atom_site_aniso_B_33' in c_dict:
|
|
611
|
+
b33 = [0.0]*at_len
|
|
612
|
+
dw = 'bij'
|
|
613
|
+
b33 = _getCIFFloats(c_dict['_atom_site_aniso_B_33'])
|
|
614
|
+
for i, bf33 in enumerate(b33):
|
|
615
|
+
atoms[i]['b33'] = bf33
|
|
616
|
+
|
|
617
|
+
if '_atom_site_aniso_B_12' in c_dict:
|
|
618
|
+
b12 = [0.0]*at_len
|
|
619
|
+
dw = 'bij'
|
|
620
|
+
b12 = _getCIFFloats(c_dict['_atom_site_aniso_B_12'])
|
|
621
|
+
for i, bf12 in enumerate(b12):
|
|
622
|
+
atoms[i]['b12'] = bf12
|
|
623
|
+
|
|
624
|
+
if '_atom_site_aniso_B_13' in c_dict:
|
|
625
|
+
b13 = [0.0]*at_len
|
|
626
|
+
dw = 'bij'
|
|
627
|
+
b13 = _getCIFFloats(c_dict['_atom_site_aniso_B_13'])
|
|
628
|
+
for i, bf13 in enumerate(b13):
|
|
629
|
+
atoms[i]['b13'] = bf13
|
|
630
|
+
|
|
631
|
+
if '_atom_site_aniso_B_23' in c_dict:
|
|
632
|
+
b23 = [0.0]*at_len
|
|
633
|
+
dw = 'bij'
|
|
634
|
+
b23 = _getCIFFloats(c_dict['_atom_site_aniso_B_23'])
|
|
635
|
+
for i, bf23 in enumerate(b23):
|
|
636
|
+
atoms[i]['b23'] = bf23
|
|
637
|
+
|
|
638
|
+
elif '_atom_site_U_iso_or_equiv' in c_dict or '_atom_site_Uiso_or_equiv' in c_dict:
|
|
639
|
+
|
|
640
|
+
dw = 'uij'
|
|
641
|
+
bxx = _getCIFFloats(c_dict['_atom_site_U_iso_or_equiv'])
|
|
642
|
+
for i, b in enumerate(bxx):
|
|
643
|
+
atoms[i]['b11'] = atoms[i]['b22'] = atoms[i]['b33'] = b
|
|
644
|
+
atoms[i]['b12'] = atoms[i]['b13'] = atoms[i]['b23'] = 0.0
|
|
645
|
+
|
|
646
|
+
|
|
647
|
+
elif '_atom_site_B_iso_or_equiv' in c_dict:
|
|
648
|
+
dw = 'iso'
|
|
649
|
+
|
|
650
|
+
d_w = _getCIFFloats(c_dict['_atom_site_B_iso_or_equiv'], 0.05)
|
|
651
|
+
for i, d in enumerate(d_w):
|
|
652
|
+
atoms[i]['d-w'] = d
|
|
653
|
+
else:
|
|
654
|
+
# assuming iso with default values of d-w
|
|
655
|
+
dw = 'iso'
|
|
656
|
+
for i in range(at_len):
|
|
657
|
+
atoms[i]['d-w'] = 0.05
|
|
658
|
+
|
|
659
|
+
vatoms = []
|
|
660
|
+
for at in atoms:
|
|
661
|
+
symb = at['symb']
|
|
662
|
+
v, vsymb = _validAtomSiteLabel(symb)
|
|
663
|
+
if not v:
|
|
664
|
+
print(f'Warning: unrecognized unit cell site label: {symb}, removing the corresponding unit cell')
|
|
665
|
+
else:
|
|
666
|
+
at['symb'] = vsymb
|
|
667
|
+
vatoms.append(at)
|
|
668
|
+
|
|
669
|
+
if len(vatoms) == 0:
|
|
670
|
+
raise CIFError(cfn, 'invalid unit cells')
|
|
671
|
+
|
|
672
|
+
data['dw'] = dw
|
|
673
|
+
data['atoms'] = vatoms
|
|
674
|
+
|
|
675
|
+
|
|
676
|
+
spgInCIF = False
|
|
677
|
+
spg = {}
|
|
678
|
+
for k in SPG_NUMBER_CIFKEY:
|
|
679
|
+
if k in c_dict:
|
|
680
|
+
spgInCIF = True
|
|
681
|
+
break
|
|
682
|
+
|
|
683
|
+
spg_num = 0
|
|
684
|
+
spg_setting = 0
|
|
685
|
+
|
|
686
|
+
spg_pairs =[(0,0)]
|
|
687
|
+
|
|
688
|
+
if spgInCIF:
|
|
689
|
+
spg_num = _getCIFInt(c_dict[k])
|
|
690
|
+
|
|
691
|
+
if spgInCIF and spg_num >= 1 and spg_num <= SPG_ITNUM_MAX:
|
|
692
|
+
|
|
693
|
+
spg_pairs.clear()
|
|
694
|
+
spg_smax = get_settingmax(spg_num)
|
|
695
|
+
for i in range(1, spg_smax+1):
|
|
696
|
+
spg_pairs.append((spg_num, i))
|
|
697
|
+
else:
|
|
698
|
+
if not '_symmetry_space_group_name_H-M' in c_dict and \
|
|
699
|
+
not '_space_group_name_H-M_alt' in c_dict and \
|
|
700
|
+
not '_cod_original_sg_symbol_H-M' in c_dict:
|
|
701
|
+
raise CIFError(cfn, 'missing required space group keys. ' +
|
|
702
|
+
'it must be on of:\n' +
|
|
703
|
+
'_symmetry_Int_Tables_number\n' +
|
|
704
|
+
'_symmetry_IT_number\n' +
|
|
705
|
+
'_space_group_IT_number\n' +
|
|
706
|
+
'or, one of:\n' +
|
|
707
|
+
'_symmetry_space_group_name_H-M\n' +
|
|
708
|
+
'_space_group_name_H-M_alt\n' +
|
|
709
|
+
'_cod_original_sg_symbol_H-M'
|
|
710
|
+
)
|
|
711
|
+
|
|
712
|
+
hm = None
|
|
713
|
+
if '_symmetry_space_group_name_H-M' in c_dict:
|
|
714
|
+
hm0 = c_dict['_symmetry_space_group_name_H-M']
|
|
715
|
+
|
|
716
|
+
hm00 = HM_RX.findall(hm0)
|
|
717
|
+
|
|
718
|
+
if len(hm00) > 0:
|
|
719
|
+
hm = hm00[0]
|
|
720
|
+
|
|
721
|
+
if not hm and '_space_group_name_H-M_alt' in c_dict:
|
|
722
|
+
|
|
723
|
+
hm0 = c_dict['_space_group_name_H-M_alt']
|
|
724
|
+
|
|
725
|
+
hm00 = HM_RX.findall(hm0)
|
|
726
|
+
|
|
727
|
+
if len(hm00) > 0:
|
|
728
|
+
hm = hm00[0]
|
|
729
|
+
|
|
730
|
+
if not hm and '_cod_original_sg_symbol_H-M' in c_dict:
|
|
731
|
+
hm0 = c_dict['_cod_original_sg_symbol_H-M']
|
|
732
|
+
|
|
733
|
+
hm00 = HM_RX.findall(hm0)
|
|
734
|
+
|
|
735
|
+
if len(hm00) > 0:
|
|
736
|
+
hm = hm00[0]
|
|
737
|
+
|
|
738
|
+
|
|
739
|
+
if not hm:
|
|
740
|
+
raise CIFError(cfn, 'invalid space group H-M value')
|
|
741
|
+
|
|
742
|
+
spgHM = re.split(r'\s+', hm)
|
|
743
|
+
maxMatched = 0
|
|
744
|
+
mIndex = -1
|
|
745
|
+
for i in range(SPG_ENTRY_MAX):
|
|
746
|
+
emaps_spghm, spgnum, spgsetting = lookuphm(i+1)
|
|
747
|
+
|
|
748
|
+
cemaps_spghm = emaps_spghm.decode().strip()
|
|
749
|
+
|
|
750
|
+
emaps_spghm_list = re.split(r'\s+', cemaps_spghm)
|
|
751
|
+
|
|
752
|
+
nmatched = _matchCIFHM(emaps_spghm_list, spgHM)
|
|
753
|
+
|
|
754
|
+
if nmatched > maxMatched:
|
|
755
|
+
maxMatched = nmatched
|
|
756
|
+
mIndex = i + 1
|
|
757
|
+
spg_pairs.clear()
|
|
758
|
+
spg_pairs.append((spgnum, spgsetting))
|
|
759
|
+
|
|
760
|
+
elif nmatched == maxMatched and mIndex >= 0:
|
|
761
|
+
spg_pairs.append((spgnum, spgsetting))
|
|
762
|
+
|
|
763
|
+
|
|
764
|
+
if mIndex == -1:
|
|
765
|
+
raise CIFError(cfn, 'no match found in pyemaps space group database')
|
|
766
|
+
|
|
767
|
+
if '_symmetry_space_group_setting' in c_dict:
|
|
768
|
+
spg_setting = _getCIFInt(c_dict['_symmetry_space_group_setting'])
|
|
769
|
+
|
|
770
|
+
if spg_setting < 1 or spg_setting > SPG_SETTING_MAX:
|
|
771
|
+
raise SPGSettingNotInRangeError()
|
|
772
|
+
|
|
773
|
+
spg_num = 0
|
|
774
|
+
spg_tmppairs = []
|
|
775
|
+
for sp in spg_pairs:
|
|
776
|
+
if sp[1] == spg_setting:
|
|
777
|
+
spg_tmppairs.append(sp)
|
|
778
|
+
|
|
779
|
+
spg_pairs = spg_tmppairs
|
|
780
|
+
|
|
781
|
+
if '_symmetry_equiv_pos_as_xyz' in c_dict or \
|
|
782
|
+
'_space_group_symop_operation_xyz' in c_dict:
|
|
783
|
+
|
|
784
|
+
cif_coords = []
|
|
785
|
+
if '_symmetry_equiv_pos_as_xyz' in c_dict:
|
|
786
|
+
cif_coords = c_dict['_symmetry_equiv_pos_as_xyz']
|
|
787
|
+
elif '_space_group_symop_operation_xyz' in c_dict:
|
|
788
|
+
cif_coords = c_dict['_space_group_symop_operation_xyz']
|
|
789
|
+
|
|
790
|
+
l, pact = _scrubCIFSymmXYZ(cif_coords, SPG_SYMMETRY_MAXLEN)
|
|
791
|
+
|
|
792
|
+
max_match = -1
|
|
793
|
+
new_pairs = []
|
|
794
|
+
for sp in spg_pairs:
|
|
795
|
+
coords = farray(np.empty((SPG_SYMMETRY_MAXCOL, SPG_SYMMETRY_MAXLEN), dtype='c'))
|
|
796
|
+
coords, n = getsymmetryxyz(sp, coords)
|
|
797
|
+
|
|
798
|
+
|
|
799
|
+
if n == -1:
|
|
800
|
+
# This should not happen but check for it anyway
|
|
801
|
+
raise CIFError(cfn, 'symmetry lookup in pyemaps space group failed')
|
|
802
|
+
if n > l:
|
|
803
|
+
# no match
|
|
804
|
+
continue
|
|
805
|
+
|
|
806
|
+
if _compareCIFSymmetry(n, coords, pact) == 0:
|
|
807
|
+
if max_match < n:
|
|
808
|
+
max_match = n
|
|
809
|
+
new_pairs.append(sp)
|
|
810
|
+
|
|
811
|
+
if max_match == -1:
|
|
812
|
+
raise CIFError(cfn, 'symmetry data provided does match any in pyemaps database')
|
|
813
|
+
|
|
814
|
+
spg_pairs.clear()
|
|
815
|
+
spg_pairs = new_pairs
|
|
816
|
+
else:
|
|
817
|
+
print(f'Info: no symmetry infomation provided in {cfn}')
|
|
818
|
+
|
|
819
|
+
if len(spg_pairs) == 1:
|
|
820
|
+
print(f'\nInfo: closest matching space group information found in pyemaps: {spg_pairs}')
|
|
821
|
+
else:
|
|
822
|
+
errmsg = str(f'Multiple sets of space group match found: {spg_pairs}, retry with _symmetry_space_group_setting field in {cfn}')
|
|
823
|
+
raise SPGError(errmsg)
|
|
824
|
+
|
|
825
|
+
# make sure the spg pair is not empty
|
|
826
|
+
if len(spg_pairs) == 0:
|
|
827
|
+
raise SPGError('No space group info found')
|
|
828
|
+
|
|
829
|
+
spg_num = spg_pairs[0][0]
|
|
830
|
+
spg_setting = spg_pairs[0][1]
|
|
831
|
+
|
|
832
|
+
if spg_setting <= 0 or spg_setting > SPG_SETTING_MAX:
|
|
833
|
+
raise SPGSettingNotInRangeError()
|
|
834
|
+
|
|
835
|
+
if spg_num <= 0 or spg_num > SPG_ITNUM_MAX:
|
|
836
|
+
raise SPGITMumberNotInRangeError()
|
|
837
|
+
|
|
838
|
+
spg['number'] = spg_num
|
|
839
|
+
spg['setting'] = spg_setting
|
|
840
|
+
|
|
841
|
+
data['spg'] = spg
|
|
842
|
+
|
|
843
|
+
for key in required_keys:
|
|
844
|
+
if key not in data:
|
|
845
|
+
raise CIFError(cfn, 'parsing and extraction failed to produce valid crystal data')
|
|
846
|
+
|
|
847
|
+
return name, data
|
|
848
|
+
|
|
849
|
+
def _get_atom_len(dw):
|
|
850
|
+
if dw == 'iso':
|
|
851
|
+
return len(at_iso_keys)
|
|
852
|
+
|
|
853
|
+
if dw == 'uij' or dw == 'bij':
|
|
854
|
+
return len(at_noniso_keys)
|
|
855
|
+
|
|
856
|
+
return -1 #error
|
|
857
|
+
|
|
858
|
+
def loadCrystalData(fn, cn=None):
|
|
859
|
+
"""
|
|
860
|
+
Reading built-in datbase or a .xtl file for crystal data.
|
|
861
|
+
|
|
862
|
+
:param fn: XTL formatted Crystal file name
|
|
863
|
+
:type: string, required
|
|
864
|
+
|
|
865
|
+
:param cn: Crystal name
|
|
866
|
+
:type: string, optional
|
|
867
|
+
|
|
868
|
+
:return: tuple of crystal name and crystal data as dict object
|
|
869
|
+
:rtype: dict
|
|
870
|
+
|
|
871
|
+
"""
|
|
872
|
+
|
|
873
|
+
data = {}
|
|
874
|
+
|
|
875
|
+
name=""
|
|
876
|
+
|
|
877
|
+
try:
|
|
878
|
+
with open(fn) as f:
|
|
879
|
+
lines = f.readlines()
|
|
880
|
+
atoms = []
|
|
881
|
+
for line in lines:
|
|
882
|
+
ln = line.strip().lower()
|
|
883
|
+
if ln.startswith('crystal'):
|
|
884
|
+
token = line.split(':')
|
|
885
|
+
|
|
886
|
+
cname = re.split(' +', token[0].strip())[1].strip()
|
|
887
|
+
name = cname
|
|
888
|
+
|
|
889
|
+
if cn and name.lower() != cn.lower():
|
|
890
|
+
raise XTLError(fn, 'mismatch of input name with builtin crystal name, only in builtin files')
|
|
891
|
+
|
|
892
|
+
# parsing for dw and occ data
|
|
893
|
+
dwocc = re.split(' =+', token[1].strip())
|
|
894
|
+
owlen = len(dwocc)
|
|
895
|
+
|
|
896
|
+
# dw = iso or
|
|
897
|
+
# dw = iso occ = 1
|
|
898
|
+
|
|
899
|
+
if owlen > 3 or owlen < 2:
|
|
900
|
+
raise XTLError(fn, 'invalid dw and occ data input')
|
|
901
|
+
|
|
902
|
+
dwkey = dwocc[0].strip().lower()
|
|
903
|
+
if dwkey != 'dw':
|
|
904
|
+
raise XTLError(fn, 'missing dye-waller factor key')
|
|
905
|
+
|
|
906
|
+
dw = re.split(' +', dwocc[1].strip())[0]
|
|
907
|
+
|
|
908
|
+
occ = 1.0 #default
|
|
909
|
+
if owlen == 3:
|
|
910
|
+
occkey = re.split(' +', dwocc[1].strip())[1].lower()
|
|
911
|
+
if occkey and occkey != 'occ':
|
|
912
|
+
raise XTLError(fn, 'missing occupancy key')
|
|
913
|
+
|
|
914
|
+
occ = int(dwocc[2].strip())
|
|
915
|
+
|
|
916
|
+
# validating dw:
|
|
917
|
+
if dw != 'iso' and dw != 'uij' and dw != 'bij':
|
|
918
|
+
raise XTLError(fn, 'invalid dye-Waller facttor data: must be one of iso, bij and uij')
|
|
919
|
+
|
|
920
|
+
data['dw'] = dw
|
|
921
|
+
|
|
922
|
+
# if len(dwocc) == 3:
|
|
923
|
+
# occ = dwocc[2].strip()
|
|
924
|
+
|
|
925
|
+
|
|
926
|
+
if ln.startswith('cell'):
|
|
927
|
+
|
|
928
|
+
cellStr = ln[4:].strip()
|
|
929
|
+
tokens = np.array([n for n in re.split(' +', cellStr)])
|
|
930
|
+
if tokens.size != len(cell_keys):
|
|
931
|
+
raise XTLError(fn, 'invalid cell parameters input')
|
|
932
|
+
|
|
933
|
+
cell = {}
|
|
934
|
+
for k, t in zip(np.array(cell_keys), tokens):
|
|
935
|
+
cell[k] = t
|
|
936
|
+
data['cell'] = cell
|
|
937
|
+
|
|
938
|
+
if ln.startswith('atom'):
|
|
939
|
+
|
|
940
|
+
atomStr = ln[4:].strip()
|
|
941
|
+
|
|
942
|
+
token = [n for n in re.split(' +', atomStr)]
|
|
943
|
+
|
|
944
|
+
alen = _get_atom_len(data['dw'])
|
|
945
|
+
tokenlen = len(token)
|
|
946
|
+
if tokenlen < alen - 1 or tokenlen > alen:
|
|
947
|
+
raise XTLError(fn, 'invalid unit cell input')
|
|
948
|
+
|
|
949
|
+
if tokenlen == alen - 1:
|
|
950
|
+
token.append(occ)
|
|
951
|
+
|
|
952
|
+
atom = {}
|
|
953
|
+
keys = at_iso_keys if data['dw']== 'iso' else at_noniso_keys
|
|
954
|
+
|
|
955
|
+
for k, t in zip(np.array(keys), token):
|
|
956
|
+
atom[k] = t
|
|
957
|
+
atoms.append(atom)
|
|
958
|
+
|
|
959
|
+
if ln.startswith('spg'):
|
|
960
|
+
|
|
961
|
+
sStr = ln[3:].strip()
|
|
962
|
+
if not sStr:
|
|
963
|
+
raise ValueError("Space group data missing")
|
|
964
|
+
|
|
965
|
+
tokens = np.array([n for n in re.split(' +', sStr)])
|
|
966
|
+
if tokens.size != 2:
|
|
967
|
+
raise XTLError(fn, 'invalid space group input')
|
|
968
|
+
|
|
969
|
+
spg = {}
|
|
970
|
+
for k, t in zip(np.array(spg_keys), tokens):
|
|
971
|
+
spg[k] = t
|
|
972
|
+
|
|
973
|
+
# validateSPG(tokens)
|
|
974
|
+
|
|
975
|
+
data['spg'] = spg
|
|
976
|
+
new_atoms = []
|
|
977
|
+
for a in atoms:
|
|
978
|
+
symb = a['symb']
|
|
979
|
+
valided, new_symb = _validAtomSiteLabel(symb)
|
|
980
|
+
if not valided:
|
|
981
|
+
print(f'Warning: invalid unit cell symbol {symb}, removing corresponding unit cell')
|
|
982
|
+
continue
|
|
983
|
+
a['symb'] = new_symb
|
|
984
|
+
new_atoms.append(a)
|
|
985
|
+
|
|
986
|
+
if len(new_atoms) == 0:
|
|
987
|
+
raise XTLError(fn, 'missing unit cell data')
|
|
988
|
+
|
|
989
|
+
data['atoms'] = new_atoms
|
|
990
|
+
|
|
991
|
+
for key in required_keys:
|
|
992
|
+
if key not in data:
|
|
993
|
+
raise XTLError(fn, str(f'missing data for {key}'))
|
|
994
|
+
|
|
995
|
+
except (FileNotFoundError,IOError) as e:
|
|
996
|
+
raise XTLError(fn, str(e))
|
|
997
|
+
|
|
998
|
+
return name, data
|
|
999
|
+
|
|
1000
|
+
|
|
1001
|
+
|