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.
Files changed (112) hide show
  1. pyemaps/COPYING +798 -0
  2. pyemaps/CifFile/CifFile_module.py +3117 -0
  3. pyemaps/CifFile/StarFile.py +2496 -0
  4. pyemaps/CifFile/TypeContentsParser.py +82 -0
  5. pyemaps/CifFile/YappsStarParser_1_0.py +289 -0
  6. pyemaps/CifFile/YappsStarParser_1_1.py +288 -0
  7. pyemaps/CifFile/YappsStarParser_2_0.py +369 -0
  8. pyemaps/CifFile/YappsStarParser_STAR2.py +382 -0
  9. pyemaps/CifFile/__init__.py +7 -0
  10. pyemaps/CifFile/drel/__init__.py +1 -0
  11. pyemaps/CifFile/drel/drel_ast_yacc.py +582 -0
  12. pyemaps/CifFile/drel/drel_lex.py +228 -0
  13. pyemaps/CifFile/drel/drel_runtime.py +126 -0
  14. pyemaps/CifFile/drel/parsetab.py +185 -0
  15. pyemaps/CifFile/drel/py_from_ast.py +628 -0
  16. pyemaps/CifFile/parsetab.py +185 -0
  17. pyemaps/CifFile/yapps3_compiled_rt.py +390 -0
  18. pyemaps/README.md +201 -0
  19. pyemaps/__config__.py +91 -0
  20. pyemaps/__init__.py +233 -0
  21. pyemaps/__main__.py +219 -0
  22. pyemaps/__version__.py +1 -0
  23. pyemaps/cdata/Aluminium.xtl +4 -0
  24. pyemaps/cdata/AluminiumOxide.xtl +5 -0
  25. pyemaps/cdata/Aluminium_FCC.xtl +4 -0
  26. pyemaps/cdata/BariumTitanate_180k.xtl +6 -0
  27. pyemaps/cdata/BariumTitanate_270k.xtl +7 -0
  28. pyemaps/cdata/BariumTitanate_Tetra.xtl +7 -0
  29. pyemaps/cdata/BiMnO3.xtl +27 -0
  30. pyemaps/cdata/Boron_Tetra.xtl +8 -0
  31. pyemaps/cdata/CadmiumSelenide_Hex.xtl +5 -0
  32. pyemaps/cdata/CadmiumSulfide_Cubic.xtl +5 -0
  33. pyemaps/cdata/CadmiumSulfide_Hex.xtl +5 -0
  34. pyemaps/cdata/Chromium_BCC.xtl +4 -0
  35. pyemaps/cdata/CoSb3_Skutterudite.xtl +5 -0
  36. pyemaps/cdata/CopperOxide.xtl +5 -0
  37. pyemaps/cdata/Copper_FCC.xtl +4 -0
  38. pyemaps/cdata/Cu2O_Cuprite.xtl +5 -0
  39. pyemaps/cdata/Diamond.xtl +4 -0
  40. pyemaps/cdata/ErbiumPyrogermanate.xtl +9 -0
  41. pyemaps/cdata/FePd_Tetra.xtl +5 -0
  42. pyemaps/cdata/FeS2_Pyrite.xtl +15 -0
  43. pyemaps/cdata/GalliumAntimonide.xtl +5 -0
  44. pyemaps/cdata/GalliumArsenide.xtl +5 -0
  45. pyemaps/cdata/GalliumNitride.xtl +5 -0
  46. pyemaps/cdata/Germanium.xtl +4 -0
  47. pyemaps/cdata/Gold_FCC.xtl +4 -0
  48. pyemaps/cdata/IndiumArsenide.xtl +5 -0
  49. pyemaps/cdata/LaMnO3.xtl +7 -0
  50. pyemaps/cdata/LeadZirconateTitanate.xtl +8 -0
  51. pyemaps/cdata/Li2MnO3.xtl +13 -0
  52. pyemaps/cdata/NaFeO2.xtl +6 -0
  53. pyemaps/cdata/Nb3Sn.xtl +5 -0
  54. pyemaps/cdata/Pentacene.xtl +39 -0
  55. pyemaps/cdata/Silicon.xtl +4 -0
  56. pyemaps/cdata/StrontiumTitanate.xtl +6 -0
  57. pyemaps/cdata/TelluriumDioxide.xtl +5 -0
  58. pyemaps/cdata/TinDioxide_RT.xtl +5 -0
  59. pyemaps/cdata/TitaniumDioxide_Anatase.xtl +5 -0
  60. pyemaps/cdata/TitaniumDioxide_Rutile.xtl +5 -0
  61. pyemaps/cdata/TungstenDiselenide.xtl +5 -0
  62. pyemaps/cdata/VanadiumDioxide_RT.xtl +6 -0
  63. pyemaps/cdata/ZincOxide.xtl +5 -0
  64. pyemaps/cdata/Zinc_HCP.xtl +4 -0
  65. pyemaps/cdata/ZirconiumNitride.xtl +5 -0
  66. pyemaps/cdata/limno2.xtl +6 -0
  67. pyemaps/crystals.py +1415 -0
  68. pyemaps/ddiffs.py +147 -0
  69. pyemaps/diffract/__init__.py +70 -0
  70. pyemaps/diffract/bloch_dec.py +779 -0
  71. pyemaps/diffract/csf_dec.py +242 -0
  72. pyemaps/diffract/dif_dec.py +519 -0
  73. pyemaps/diffract/dpgen_dec.py +175 -0
  74. pyemaps/diffract/mxtal_dec.py +201 -0
  75. pyemaps/diffract/powder_dec.py +137 -0
  76. pyemaps/diffract/stereo_dec.py +111 -0
  77. pyemaps/display.py +791 -0
  78. pyemaps/emcontrols.py +1017 -0
  79. pyemaps/errors.py +266 -0
  80. pyemaps/fileutils.py +1001 -0
  81. pyemaps/kdiffs.py +1221 -0
  82. pyemaps/license.txt +42 -0
  83. pyemaps/samples/adf.py +137 -0
  84. pyemaps/samples/al.img +0 -0
  85. pyemaps/samples/al_dpgen.py +57 -0
  86. pyemaps/samples/al_ediom.py +94 -0
  87. pyemaps/samples/convert_image.py +88 -0
  88. pyemaps/samples/dm_diff.py +225 -0
  89. pyemaps/samples/mask.im3 +0 -0
  90. pyemaps/samples/powder.py +51 -0
  91. pyemaps/samples/raw.img +0 -0
  92. pyemaps/samples/si_bloch.py +120 -0
  93. pyemaps/samples/si_constructor.py +51 -0
  94. pyemaps/samples/si_csf.py +59 -0
  95. pyemaps/samples/si_dif.py +134 -0
  96. pyemaps/samples/si_lacbed.py +59 -0
  97. pyemaps/samples/si_pyemaps.py +80 -0
  98. pyemaps/samples/si_scm.py +156 -0
  99. pyemaps/samples/si_stereo.py +116 -0
  100. pyemaps/scattering/__init__.py +26 -0
  101. pyemaps/scattering/sct_dec.py +31 -0
  102. pyemaps/spg/__init__.py +25 -0
  103. pyemaps/spg/spg_dec.py +60 -0
  104. pyemaps/stackimg.py +1031 -0
  105. pyemaps/xtal.py +222 -0
  106. pyemaps-2.0.0.dist-info/METADATA +231 -0
  107. pyemaps-2.0.0.dist-info/RECORD +112 -0
  108. pyemaps-2.0.0.dist-info/WHEEL +5 -0
  109. pyemaps-2.0.0.dist-info/entry_points.txt +2 -0
  110. pyemaps-2.0.0.dist-info/licenses/COPYING +798 -0
  111. pyemaps-2.0.0.dist-info/licenses/license.txt +42 -0
  112. 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
+