diffpy.structure 3.2.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 (42) hide show
  1. diffpy/Structure.py +35 -0
  2. diffpy/__init__.py +23 -0
  3. diffpy/structure/__init__.py +93 -0
  4. diffpy/structure/_legacy_importer.py +88 -0
  5. diffpy/structure/apps/__init__.py +17 -0
  6. diffpy/structure/apps/anyeye.py +284 -0
  7. diffpy/structure/apps/transtru.py +126 -0
  8. diffpy/structure/atom.py +544 -0
  9. diffpy/structure/expansion/__init__.py +27 -0
  10. diffpy/structure/expansion/makeellipsoid.py +129 -0
  11. diffpy/structure/expansion/shapeutils.py +44 -0
  12. diffpy/structure/expansion/supercell_mod.py +91 -0
  13. diffpy/structure/lattice.py +663 -0
  14. diffpy/structure/mmlibspacegroups.py +8154 -0
  15. diffpy/structure/parsers/__init__.py +83 -0
  16. diffpy/structure/parsers/p_auto.py +217 -0
  17. diffpy/structure/parsers/p_cif.py +876 -0
  18. diffpy/structure/parsers/p_discus.py +312 -0
  19. diffpy/structure/parsers/p_pdb.py +405 -0
  20. diffpy/structure/parsers/p_pdffit.py +290 -0
  21. diffpy/structure/parsers/p_rawxyz.py +149 -0
  22. diffpy/structure/parsers/p_xcfg.py +457 -0
  23. diffpy/structure/parsers/p_xyz.py +161 -0
  24. diffpy/structure/parsers/parser_index_mod.py +108 -0
  25. diffpy/structure/parsers/structureparser.py +80 -0
  26. diffpy/structure/pdffitstructure.py +109 -0
  27. diffpy/structure/sgtbxspacegroups.py +5198 -0
  28. diffpy/structure/spacegroupmod.py +329 -0
  29. diffpy/structure/spacegroups.py +1441 -0
  30. diffpy/structure/structure.py +866 -0
  31. diffpy/structure/structureerrors.py +35 -0
  32. diffpy/structure/symmetryutilities.py +1100 -0
  33. diffpy/structure/utils.py +126 -0
  34. diffpy/structure/version.py +26 -0
  35. diffpy.structure-3.2.0.dist-info/AUTHORS.rst +13 -0
  36. diffpy.structure-3.2.0.dist-info/LICENSE.rst +141 -0
  37. diffpy.structure-3.2.0.dist-info/LICENSE_DANSE.rst +50 -0
  38. diffpy.structure-3.2.0.dist-info/LICENSE_pymmlib.rst +203 -0
  39. diffpy.structure-3.2.0.dist-info/METADATA +197 -0
  40. diffpy.structure-3.2.0.dist-info/RECORD +42 -0
  41. diffpy.structure-3.2.0.dist-info/WHEEL +5 -0
  42. diffpy.structure-3.2.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,876 @@
1
+ #!/usr/bin/env python
2
+ ##############################################################################
3
+ #
4
+ # diffpy.structure by DANSE Diffraction group
5
+ # Simon J. L. Billinge
6
+ # (c) 2007 trustees of the Michigan State University.
7
+ # All rights reserved.
8
+ #
9
+ # File coded by: Pavol Juhas
10
+ #
11
+ # See AUTHORS.txt for a list of people who contributed.
12
+ # See LICENSE_DANSE.txt for license information.
13
+ #
14
+ ##############################################################################
15
+
16
+ """Parser for basic CIF file format.
17
+
18
+ Attributes
19
+ ----------
20
+ rx_float : re.Pattern
21
+ Constant regular expression for `leading_float()`.
22
+ symvec : dict
23
+ Helper dictionary for `getSymOp()`.
24
+
25
+ Note
26
+ ----
27
+ References: https://www.iucr.org/resources/cif
28
+ """
29
+
30
+ import io
31
+ import re
32
+ import sys
33
+ from contextlib import contextmanager
34
+
35
+ import numpy
36
+
37
+ from diffpy.structure import Atom, Lattice, Structure
38
+ from diffpy.structure.parsers import StructureParser
39
+ from diffpy.structure.structureerrors import StructureFormatError
40
+
41
+ # ----------------------------------------------------------------------------
42
+
43
+
44
+ class P_cif(StructureParser):
45
+ """Simple parser for CIF structure format.
46
+
47
+ Reads Structure from the first block containing _atom_site_label key.
48
+ Following blocks, if any, are ignored.
49
+
50
+ Parameters
51
+ ----------
52
+ eps : float, Optional
53
+ Fractional coordinates cutoff for duplicate positions.
54
+ When ``None`` use the default for `ExpandAsymmetricUnit`: ``1.0e-5``.
55
+
56
+ Attributes
57
+ ----------
58
+ format : str
59
+ Structure format name.
60
+ ciffile : CifFile
61
+ Instance of `CifFile` from `PyCifRW`.
62
+ stru : Structure
63
+ `Structure` instance used for CIF input or output.
64
+ spacegroup : SpaceGroup
65
+ Instance of `SpaceGroup` used for symmetry expansion.
66
+ eps : float
67
+ Resolution in fractional coordinates for non-equal positions.
68
+ Used for expansion of asymmetric unit.
69
+ eau : ExpandAsymmetricUnit
70
+ Instance of `ExpandAsymmetricUnit` from `SymmetryUtilities`.
71
+ asymmetric_unit : list
72
+ List of `Atom` instances for the original asymmetric unit in the CIF file.
73
+ labelindex : dict
74
+ Dictionary mapping unique atom label to index of `Atom` in `self.asymmetric_unit`.
75
+ anisotropy : dict
76
+ Dictionary mapping unique atom label to displacement anisotropy resolved at that site.
77
+ cif_sgname : str or None
78
+ Space group name obtained by looking up the value of
79
+ `_space_group_name_Hall`,
80
+ `_symmetry_space_group_name_Hall`,
81
+ `_space_group_name_H-M_alt`,
82
+ `_symmetry_space_group_name_H-M`
83
+ items. ``None`` when neither is defined.
84
+ """
85
+
86
+ # static data and methods ------------------------------------------------
87
+
88
+ # dictionary set of class methods for translating CIF values
89
+ # to Atom attributes
90
+
91
+ # static data and methods ------------------------------------------------
92
+
93
+ # dictionary set of class methods for translating CIF values
94
+ # to Atom attributes
95
+
96
+ _atom_setters = dict.fromkeys(
97
+ (
98
+ "_tr_ignore",
99
+ "_tr_atom_site_label",
100
+ "_tr_atom_site_type_symbol",
101
+ "_tr_atom_site_fract_x",
102
+ "_tr_atom_site_fract_y",
103
+ "_tr_atom_site_fract_z",
104
+ "_tr_atom_site_cartn_x",
105
+ "_tr_atom_site_cartn_y",
106
+ "_tr_atom_site_cartn_z",
107
+ "_tr_atom_site_U_iso_or_equiv",
108
+ "_tr_atom_site_B_iso_or_equiv",
109
+ "_tr_atom_site_adp_type",
110
+ "_tr_atom_site_thermal_displace_type",
111
+ "_tr_atom_site_occupancy",
112
+ "_tr_atom_site_aniso_U_11",
113
+ "_tr_atom_site_aniso_U_22",
114
+ "_tr_atom_site_aniso_U_33",
115
+ "_tr_atom_site_aniso_U_12",
116
+ "_tr_atom_site_aniso_U_13",
117
+ "_tr_atom_site_aniso_U_23",
118
+ "_tr_atom_site_aniso_B_11",
119
+ "_tr_atom_site_aniso_B_22",
120
+ "_tr_atom_site_aniso_B_33",
121
+ "_tr_atom_site_aniso_B_12",
122
+ "_tr_atom_site_aniso_B_13",
123
+ "_tr_atom_site_aniso_B_23",
124
+ )
125
+ )
126
+ # make _atom_setters case insensitive
127
+ for k in list(_atom_setters.keys()):
128
+ _atom_setters[k] = _atom_setters[k.lower()] = k
129
+ del k
130
+
131
+ BtoU = 1.0 / (8 * numpy.pi**2)
132
+ """float: Conversion factor from B values to U values."""
133
+
134
+ def _tr_ignore(a, value):
135
+ return
136
+
137
+ _tr_ignore = staticmethod(_tr_ignore)
138
+
139
+ def _tr_atom_site_label(a, value):
140
+ a.label = str(value)
141
+ # set element when not specified by _atom_site_type_symbol
142
+ if not a.element:
143
+ P_cif._tr_atom_site_type_symbol(a, value)
144
+
145
+ _tr_atom_site_label = staticmethod(_tr_atom_site_label)
146
+
147
+ # 3 regexp groups for nucleon number, atom symbol, and oxidation state
148
+ _psymb = re.compile(r"(\d+-)?([a-zA-Z]+)(\d[+-])?")
149
+
150
+ def _tr_atom_site_type_symbol(a, value):
151
+ rx = P_cif._psymb.match(value)
152
+ smbl = rx and rx.group(0) or value
153
+ smbl = str(smbl)
154
+ a.element = smbl[:1].upper() + smbl[1:].lower()
155
+
156
+ _tr_atom_site_type_symbol = staticmethod(_tr_atom_site_type_symbol)
157
+
158
+ def _tr_atom_site_fract_x(a, value):
159
+ a.xyz[0] = leading_float(value)
160
+
161
+ _tr_atom_site_fract_x = staticmethod(_tr_atom_site_fract_x)
162
+
163
+ def _tr_atom_site_fract_y(a, value):
164
+ a.xyz[1] = leading_float(value)
165
+
166
+ _tr_atom_site_fract_y = staticmethod(_tr_atom_site_fract_y)
167
+
168
+ def _tr_atom_site_fract_z(a, value):
169
+ a.xyz[2] = leading_float(value)
170
+
171
+ _tr_atom_site_fract_z = staticmethod(_tr_atom_site_fract_z)
172
+
173
+ def _tr_atom_site_cartn_x(a, value):
174
+ a.xyz_cartn[0] = leading_float(value)
175
+
176
+ _tr_atom_site_cartn_x = staticmethod(_tr_atom_site_cartn_x)
177
+
178
+ def _tr_atom_site_cartn_y(a, value):
179
+ a.xyz_cartn[1] = leading_float(value)
180
+
181
+ _tr_atom_site_cartn_y = staticmethod(_tr_atom_site_cartn_y)
182
+
183
+ def _tr_atom_site_cartn_z(a, value):
184
+ a.xyz_cartn[2] = leading_float(value)
185
+
186
+ _tr_atom_site_cartn_z = staticmethod(_tr_atom_site_cartn_z)
187
+
188
+ def _tr_atom_site_U_iso_or_equiv(a, value):
189
+ a.Uisoequiv = leading_float(value)
190
+
191
+ _tr_atom_site_U_iso_or_equiv = staticmethod(_tr_atom_site_U_iso_or_equiv)
192
+
193
+ def _tr_atom_site_B_iso_or_equiv(a, value):
194
+ a.Uisoequiv = P_cif.BtoU * leading_float(value)
195
+
196
+ _tr_atom_site_B_iso_or_equiv = staticmethod(_tr_atom_site_B_iso_or_equiv)
197
+
198
+ def _tr_atom_site_adp_type(a, value):
199
+ a.anisotropy = value not in ("Uiso", "Biso")
200
+
201
+ _tr_atom_site_adp_type = staticmethod(_tr_atom_site_adp_type)
202
+ _tr_atom_site_thermal_displace_type = _tr_atom_site_adp_type
203
+
204
+ def _tr_atom_site_occupancy(a, value):
205
+ a.occupancy = leading_float(value, 1.0)
206
+
207
+ _tr_atom_site_occupancy = staticmethod(_tr_atom_site_occupancy)
208
+
209
+ def _tr_atom_site_aniso_U_11(a, value):
210
+ a.U11 = leading_float(value)
211
+
212
+ _tr_atom_site_aniso_U_11 = staticmethod(_tr_atom_site_aniso_U_11)
213
+
214
+ def _tr_atom_site_aniso_U_22(a, value):
215
+ a.U22 = leading_float(value)
216
+
217
+ _tr_atom_site_aniso_U_22 = staticmethod(_tr_atom_site_aniso_U_22)
218
+
219
+ def _tr_atom_site_aniso_U_33(a, value):
220
+ a.U33 = leading_float(value)
221
+
222
+ _tr_atom_site_aniso_U_33 = staticmethod(_tr_atom_site_aniso_U_33)
223
+
224
+ def _tr_atom_site_aniso_U_12(a, value):
225
+ a.U12 = leading_float(value)
226
+
227
+ _tr_atom_site_aniso_U_12 = staticmethod(_tr_atom_site_aniso_U_12)
228
+
229
+ def _tr_atom_site_aniso_U_13(a, value):
230
+ a.U13 = leading_float(value)
231
+
232
+ _tr_atom_site_aniso_U_13 = staticmethod(_tr_atom_site_aniso_U_13)
233
+
234
+ def _tr_atom_site_aniso_U_23(a, value):
235
+ a.U23 = leading_float(value)
236
+
237
+ _tr_atom_site_aniso_U_23 = staticmethod(_tr_atom_site_aniso_U_23)
238
+
239
+ def _tr_atom_site_aniso_B_11(a, value):
240
+ a.U11 = P_cif.BtoU * leading_float(value)
241
+
242
+ _tr_atom_site_aniso_B_11 = staticmethod(_tr_atom_site_aniso_B_11)
243
+
244
+ def _tr_atom_site_aniso_B_22(a, value):
245
+ a.U22 = P_cif.BtoU * leading_float(value)
246
+
247
+ _tr_atom_site_aniso_B_22 = staticmethod(_tr_atom_site_aniso_B_22)
248
+
249
+ def _tr_atom_site_aniso_B_33(a, value):
250
+ a.U33 = P_cif.BtoU * leading_float(value)
251
+
252
+ _tr_atom_site_aniso_B_33 = staticmethod(_tr_atom_site_aniso_B_33)
253
+
254
+ def _tr_atom_site_aniso_B_12(a, value):
255
+ a.U12 = P_cif.BtoU * leading_float(value)
256
+
257
+ _tr_atom_site_aniso_B_12 = staticmethod(_tr_atom_site_aniso_B_12)
258
+
259
+ def _tr_atom_site_aniso_B_13(a, value):
260
+ a.U13 = P_cif.BtoU * leading_float(value)
261
+
262
+ _tr_atom_site_aniso_B_13 = staticmethod(_tr_atom_site_aniso_B_13)
263
+
264
+ def _tr_atom_site_aniso_B_23(a, value):
265
+ a.U23 = P_cif.BtoU * leading_float(value)
266
+
267
+ _tr_atom_site_aniso_B_23 = staticmethod(_tr_atom_site_aniso_B_23)
268
+
269
+ def _get_atom_setters(cifloop):
270
+ """Static method for finding translators of CifLoop items to data in `Atom` instance.
271
+
272
+ Parameters
273
+ ----------
274
+ cifloop : CifLoop
275
+ Instance of `CifLoop`.
276
+
277
+ Returns
278
+ -------
279
+ list
280
+ List of setter functions in the order of `cifloop.keys()`.
281
+ """
282
+ rv = []
283
+ for p in cifloop.keys():
284
+ lcname = "_tr" + p.lower()
285
+ fncname = P_cif._atom_setters.get(lcname, "_tr_ignore")
286
+ f = getattr(P_cif, fncname)
287
+ rv.append(f)
288
+ return rv
289
+
290
+ _get_atom_setters = staticmethod(_get_atom_setters)
291
+
292
+ # normal methods ---------------------------------------------------------
293
+
294
+ def __init__(self, eps=None):
295
+ StructureParser.__init__(self)
296
+ self.format = "cif"
297
+ self.ciffile = None
298
+ self.stru = None
299
+ self.spacegroup = None
300
+ self.eps = eps
301
+ self.eau = None
302
+ self.asymmetric_unit = None
303
+ self.labelindex = {}
304
+ self.anisotropy = {}
305
+ self.cif_sgname = None
306
+ pass
307
+
308
+ def parse(self, s):
309
+ """Create `Structure` instance from a string in CIF format.
310
+
311
+ Parameters
312
+ ----------
313
+ s : str
314
+ A string in CIF format.
315
+
316
+ Returns
317
+ -------
318
+ Structure
319
+ `Structure` instance.
320
+
321
+ Raises
322
+ ------
323
+ StructureFormatError
324
+ When the data do not constitute a valid CIF format.
325
+ """
326
+ self.ciffile = None
327
+ self.filename = ""
328
+ fp = io.StringIO(s)
329
+ rv = self._parseCifDataSource(fp)
330
+ return rv
331
+
332
+ def parseLines(self, lines):
333
+ """Parse list of lines in CIF format.
334
+
335
+ Parameters
336
+ ----------
337
+ lines : list
338
+ List of strings stripped of line terminator.
339
+
340
+ Returns
341
+ -------
342
+ Structure
343
+ `Structure` instance.
344
+
345
+ Raises
346
+ ------
347
+ StructureFormatError
348
+ When the data do not constitute a valid CIF format.
349
+ """
350
+ s = "\n".join(lines) + "\n"
351
+ return self.parse(s)
352
+
353
+ def parseFile(self, filename):
354
+ """Create Structure from an existing CIF file.
355
+
356
+ Parameters
357
+ ----------
358
+ filename : str
359
+ Path to structure file.
360
+
361
+ Returns
362
+ -------
363
+ Structure
364
+ `Structure` instance.
365
+
366
+ Raises
367
+ ------
368
+ StructureFormatError
369
+ When the data do not constitute a valid CIF format.
370
+ IOError
371
+ When the file cannot be opened.
372
+ """
373
+ self.ciffile = None
374
+ self.filename = filename
375
+ rv = self._parseCifDataSource(filename)
376
+ # all good here
377
+ return rv
378
+
379
+ def _parseCifDataSource(self, datasource):
380
+ """Open and process CIF data from the specified `datasource`.
381
+
382
+ Parameters
383
+ ----------
384
+ datasource : str or a file-like object
385
+ This is used as an argument to the `CifFile` class. The `CifFile`
386
+ instance is stored in `ciffile` attribute of this Parser.
387
+
388
+ Returns
389
+ -------
390
+ Structure
391
+ The `Structure` object loaded from the specified data source.
392
+
393
+ Raises
394
+ ------
395
+ StructureFormatError
396
+ When the data do not constitute a valid CIF format.
397
+ """
398
+ from CifFile import CifFile, StarError
399
+
400
+ self.stru = None
401
+ try:
402
+ with _suppressCifParserOutput():
403
+ # Use `grammar` option to digest values with curly-brackets.
404
+ # Ref: https://bitbucket.org/jamesrhester/pycifrw/issues/19
405
+ self.ciffile = CifFile(datasource, grammar="auto")
406
+ for blockname in self.ciffile.keys():
407
+ self._parseCifBlock(blockname)
408
+ # stop after reading the first structure
409
+ if self.stru is not None:
410
+ break
411
+ except (StarError, ValueError, IndexError) as err:
412
+ exc_type, exc_value, exc_traceback = sys.exc_info()
413
+ emsg = str(err).strip()
414
+ e = StructureFormatError(emsg)
415
+ raise e.with_traceback(exc_traceback)
416
+ return self.stru
417
+
418
+ def _parseCifBlock(self, blockname):
419
+ """Translate CIF file block, skip blocks without `_atom_site_label`.
420
+ Updates data members `stru`, `eau`.
421
+
422
+ Parameters
423
+ ----------
424
+ blockname : str
425
+ Name of top level block in `self.ciffile`.
426
+ """
427
+ block = self.ciffile[blockname]
428
+ if "_atom_site_label" not in block:
429
+ return
430
+ # here block contains structure, initialize output data
431
+ self.stru = Structure()
432
+ self.labelindex.clear()
433
+ self.anisotropy.clear()
434
+ # execute specialized block parsers
435
+ self._parse_lattice(block)
436
+ self._parse_atom_site_label(block)
437
+ self._parse_atom_site_aniso_label(block)
438
+ self._parse_space_group_symop_operation_xyz(block)
439
+ return
440
+
441
+ def _parse_lattice(self, block):
442
+ """Obtain `lattice` parameters from a `CifBlock`.
443
+
444
+ This method updates `self.stru.lattic`e.
445
+
446
+ Parameters
447
+ ----------
448
+ block : CifBlock
449
+ Instance of CifBlock.
450
+ """
451
+ if "_cell_length_a" not in block:
452
+ return
453
+ # obtain lattice parameters
454
+ try:
455
+ latpars = (
456
+ leading_float(block["_cell_length_a"]),
457
+ leading_float(block["_cell_length_b"]),
458
+ leading_float(block["_cell_length_c"]),
459
+ leading_float(block["_cell_angle_alpha"]),
460
+ leading_float(block["_cell_angle_beta"]),
461
+ leading_float(block["_cell_angle_gamma"]),
462
+ )
463
+ except KeyError as err:
464
+ exc_type, exc_value, exc_traceback = sys.exc_info()
465
+ emsg = str(err)
466
+ e = StructureFormatError(emsg)
467
+ raise e.with_traceback(exc_traceback)
468
+ self.stru.lattice = Lattice(*latpars)
469
+ return
470
+
471
+ def _parse_atom_site_label(self, block):
472
+ """Obtain atoms in asymmetric unit from a `CifBlock`.
473
+
474
+ This method inserts `Atom` instances to `self.stru` and
475
+ updates `labelindex` dictionary.
476
+
477
+ Parameters
478
+ ----------
479
+ block : CifBlock
480
+ Instance of `CifBlock`.
481
+ """
482
+ # process _atom_site_label
483
+ atom_site_loop = block.GetLoop("_atom_site_label")
484
+ does_adp_type = (
485
+ "_atom_site_adp_type" in atom_site_loop or "_atom_site_thermal_displace_type" in atom_site_loop
486
+ )
487
+ # get a list of setters for atom_site values
488
+ prop_setters = P_cif._get_atom_setters(atom_site_loop)
489
+ # index of the _atom_site_label item for the labelindex dictionary
490
+ ilb = atom_site_loop.keys().index("_atom_site_label")
491
+ # loop through the values and pass them to the setters
492
+ sitedatalist = zip(*atom_site_loop.values())
493
+ for values in sitedatalist:
494
+ curlabel = values[ilb]
495
+ # skip entries that have invalid label
496
+ if curlabel == "?":
497
+ continue
498
+ self.labelindex[curlabel] = len(self.stru)
499
+ self.stru.addNewAtom()
500
+ a = self.stru.getLastAtom()
501
+ for fset, val in zip(prop_setters, values):
502
+ fset(a, val)
503
+ if does_adp_type:
504
+ self.anisotropy[curlabel] = a.anisotropy
505
+ return
506
+
507
+ def _parse_atom_site_aniso_label(self, block):
508
+ """Obtain value of anisotropic thermal displacements from a `CifBlock`.
509
+
510
+ This method updates `U` members of `Atom` instances in `self.stru`.
511
+ The `labelindex` dictionary has to be defined beforehand.
512
+
513
+ Parameters
514
+ ----------
515
+ block : CifBlock
516
+ Instance of `CifBlock`.
517
+ """
518
+ if "_atom_site_aniso_label" not in block:
519
+ return
520
+ # something to do here:
521
+ adp_loop = block.GetLoop("_atom_site_aniso_label")
522
+ # index of the _atom_site_label column
523
+ ilb = adp_loop.keys().index("_atom_site_aniso_label")
524
+ # get a list of setters for this loop
525
+ prop_setters = P_cif._get_atom_setters(adp_loop)
526
+ sitedatalist = zip(*adp_loop.values())
527
+ for values in sitedatalist:
528
+ lb = values[ilb]
529
+ if lb == "?":
530
+ break
531
+ idx = self.labelindex[lb]
532
+ a = self.stru[idx]
533
+ if lb not in self.anisotropy:
534
+ a.anisotropy = True
535
+ self.anisotropy[lb] = True
536
+ for fset, val in zip(prop_setters, values):
537
+ fset(a, val)
538
+ return
539
+
540
+ def _parse_space_group_symop_operation_xyz(self, block):
541
+ """Process symmetry operations from a CifBlock.
542
+
543
+ The method updates `spacegroup` and `eau` data according to symmetry
544
+ operations defined in `_space_group_symop_operation_xyz` or
545
+ `_symmetry_equiv_pos_as_xyz` items in `CifBlock`.
546
+
547
+ Parameters
548
+ ----------
549
+ block : CifBlock
550
+ Instance of `CifBlock`.
551
+ """
552
+ from diffpy.structure.spacegroups import FindSpaceGroup, GetSpaceGroup, IsSpaceGroupIdentifier, SpaceGroup
553
+
554
+ self.asymmetric_unit = list(self.stru)
555
+ sym_synonyms = ("_space_group_symop_operation_xyz", "_symmetry_equiv_pos_as_xyz")
556
+ sym_loop_name = [n for n in sym_synonyms if n in block]
557
+ # recover explicit list of symmetry operations
558
+ symop_list = []
559
+ if sym_loop_name:
560
+ # sym_loop exists here and we know its cif name
561
+ sym_loop_name = sym_loop_name[0]
562
+ sym_loop = block.GetLoop(sym_loop_name)
563
+ for eqxyz in sym_loop[sym_loop_name]:
564
+ opcif = getSymOp(eqxyz)
565
+ symop_list.append(opcif)
566
+ # determine space group number
567
+ sg_nameHall = block.get("_space_group_name_Hall", "") or block.get("_symmetry_space_group_name_Hall", "")
568
+ sg_nameHM = (
569
+ block.get("_space_group_name_H-M_alt", "")
570
+ or block.get("_space_group_name_H-M_ref", "")
571
+ or block.get("_symmetry_space_group_name_H-M", "")
572
+ )
573
+ self.cif_sgname = sg_nameHall or sg_nameHM or None
574
+ sgid = block.get("_space_group_IT_number", "") or block.get("_symmetry_Int_Tables_number", "") or sg_nameHM
575
+ self.spacegroup = None
576
+ # try to reuse existing space group from symmetry operations
577
+ if symop_list:
578
+ try:
579
+ self.spacegroup = FindSpaceGroup(symop_list)
580
+ except ValueError:
581
+ pass
582
+ # otherwise lookup the space group from its identifier
583
+ if self.spacegroup is None and sgid and IsSpaceGroupIdentifier(sgid):
584
+ self.spacegroup = GetSpaceGroup(sgid)
585
+ # define new spacegroup when symmetry operations were listed, but
586
+ # there is no match to an existing definition
587
+ if symop_list and self.spacegroup is None:
588
+ new_short_name = "CIF " + (sg_nameHall or "data")
589
+ new_crystal_system = (
590
+ block.get("_space_group_crystal_system") or block.get("_symmetry_cell_setting") or "TRICLINIC"
591
+ ).upper()
592
+ self.spacegroup = SpaceGroup(
593
+ short_name=new_short_name, crystal_system=new_crystal_system, symop_list=symop_list
594
+ )
595
+ if self.spacegroup is None:
596
+ emsg = "CIF file has unknown space group identifier {!r}."
597
+ raise StructureFormatError(emsg.format(sgid))
598
+ self._expandAsymmetricUnit(block)
599
+ return
600
+
601
+ def _expandAsymmetricUnit(self, block):
602
+ """Perform symmetry expansion of `self.stru` using `self.spacegroup`.
603
+
604
+ This method updates data in `stru` and `eau`.
605
+
606
+ Parameters
607
+ ----------
608
+ block : CifBlock
609
+ The top-level block containing crystal structure data.
610
+ """
611
+ from diffpy.structure.symmetryutilities import ExpandAsymmetricUnit
612
+
613
+ corepos = [a.xyz for a in self.stru]
614
+ coreUijs = [a.U for a in self.stru]
615
+ self.eau = ExpandAsymmetricUnit(self.spacegroup, corepos, coreUijs, eps=self.eps)
616
+ # setup anisotropy according to symmetry requirements
617
+ # unless it was already explicitly set
618
+ for ca, uisotropy in zip(self.stru, self.eau.Uisotropy):
619
+ if ca.label not in self.anisotropy:
620
+ ca.anisotropy = not uisotropy
621
+ self.anisotropy[ca.label] = ca.anisotropy
622
+ # build a nested list of new atoms:
623
+ newatoms = []
624
+ for i, ca in enumerate(self.stru):
625
+ eca = [] # expanded core atom
626
+ for j in range(self.eau.multiplicity[i]):
627
+ a = Atom(ca)
628
+ a.xyz = self.eau.expandedpos[i][j]
629
+ if j > 0:
630
+ a.label += "_" + str(j + 1)
631
+ if a.anisotropy:
632
+ a.U = self.eau.expandedUijs[i][j]
633
+ eca.append(a)
634
+ newatoms.append(eca)
635
+ # insert new atoms where they belong
636
+ self.stru[:] = sum(newatoms, [])
637
+ return
638
+
639
+ # conversion to CIF ------------------------------------------------------
640
+
641
+ def toLines(self, stru):
642
+ """Convert `Structure` to a list of lines in basic CIF format.
643
+
644
+ Parameters
645
+ ----------
646
+ stru : Structure
647
+ The structure to be converted.
648
+
649
+ Returns
650
+ -------
651
+ list
652
+ List of lines in basic CIF format.
653
+ """
654
+ import time
655
+
656
+ lines = []
657
+ # may be replaced with filtered Structure.title
658
+ # for now, we can add the title as a comment
659
+ if stru.title.strip() != "":
660
+ title_lines = stru.title.split("\n")
661
+ lines.extend(["# " + line.strip() for line in title_lines])
662
+ lines.append("")
663
+ lines.append("data_3D")
664
+ iso_date = "%04i-%02i-%02i" % time.gmtime()[:3]
665
+ lines.extend(
666
+ [
667
+ "%-31s %s" % ("_audit_creation_date", iso_date),
668
+ "%-31s %s" % ("_audit_creation_method", "P_cif.py"),
669
+ "",
670
+ "%-31s %s" % ("_symmetry_space_group_name_H-M", "'P1'"),
671
+ "%-31s %s" % ("_symmetry_Int_Tables_number", "1"),
672
+ "%-31s %s" % ("_symmetry_cell_setting", "triclinic"),
673
+ "",
674
+ ]
675
+ )
676
+ # there should be no need to specify equivalent positions for P1
677
+ # _symmetry_equiv_posi_as_xyz x,y,z
678
+ lines.extend(
679
+ [
680
+ "%-31s %.6g" % ("_cell_length_a", stru.lattice.a),
681
+ "%-31s %.6g" % ("_cell_length_b", stru.lattice.b),
682
+ "%-31s %.6g" % ("_cell_length_c", stru.lattice.c),
683
+ "%-31s %.6g" % ("_cell_angle_alpha", stru.lattice.alpha),
684
+ "%-31s %.6g" % ("_cell_angle_beta", stru.lattice.beta),
685
+ "%-31s %.6g" % ("_cell_angle_gamma", stru.lattice.gamma),
686
+ "",
687
+ ]
688
+ )
689
+ # build a list of site labels and adp (displacement factor) types
690
+ element_count = {}
691
+ a_site_label = []
692
+ a_adp_type = []
693
+ for a in stru:
694
+ cnt = element_count[a.element] = element_count.get(a.element, 0) + 1
695
+ a_site_label.append("%s%i" % (a.element, cnt))
696
+ if numpy.all(a.U == a.U[0, 0] * numpy.identity(3)):
697
+ a_adp_type.append("Uiso")
698
+ else:
699
+ a_adp_type.append("Uani")
700
+ # list all atoms
701
+ lines.extend(
702
+ [
703
+ "loop_",
704
+ " _atom_site_label",
705
+ " _atom_site_type_symbol",
706
+ " _atom_site_fract_x",
707
+ " _atom_site_fract_y",
708
+ " _atom_site_fract_z",
709
+ " _atom_site_U_iso_or_equiv",
710
+ " _atom_site_adp_type",
711
+ " _atom_site_occupancy",
712
+ ]
713
+ )
714
+ for i in range(len(stru)):
715
+ a = stru[i]
716
+ line = " %-5s %-3s %11.6f %11.6f %11.6f %11.6f %-5s %.4f" % (
717
+ a_site_label[i],
718
+ a.element,
719
+ a.xyz[0],
720
+ a.xyz[1],
721
+ a.xyz[2],
722
+ a.Uisoequiv,
723
+ a_adp_type[i],
724
+ a.occupancy,
725
+ )
726
+ lines.append(line)
727
+ # find anisotropic atoms
728
+ idx_aniso = [i for i in range(len(stru)) if a_adp_type[i] != "Uiso"]
729
+ if idx_aniso != []:
730
+ lines.extend(
731
+ [
732
+ "loop_",
733
+ " _atom_site_aniso_label",
734
+ " _atom_site_aniso_U_11",
735
+ " _atom_site_aniso_U_22",
736
+ " _atom_site_aniso_U_33",
737
+ " _atom_site_aniso_U_12",
738
+ " _atom_site_aniso_U_13",
739
+ " _atom_site_aniso_U_23",
740
+ ]
741
+ )
742
+ for i in idx_aniso:
743
+ a = stru[i]
744
+ line = " %-5s %9.6f %9.6f %9.6f %9.6f %9.6f %9.6f" % (
745
+ a_site_label[i],
746
+ a.U[0, 0],
747
+ a.U[1, 1],
748
+ a.U[2, 2],
749
+ a.U[0, 1],
750
+ a.U[0, 2],
751
+ a.U[1, 2],
752
+ )
753
+ lines.append(line)
754
+ return lines
755
+
756
+
757
+ # End of class P_cif
758
+
759
+ # Routines -------------------------------------------------------------------
760
+
761
+ # constant regular expression for leading_float()
762
+ rx_float = re.compile(r"[-+]?(\d+(\.\d*)?|\.\d+)([eE][-+]?\d+)?")
763
+
764
+
765
+ def leading_float(s, d=0.0):
766
+ """Extract the first float from a string and ignore trailing characters.
767
+
768
+ Useful for extracting values from "value(std)" syntax.
769
+
770
+ Parameters
771
+ ----------
772
+ s : str
773
+ The string to be scanned for floating point value.
774
+ d : float, Optional
775
+ The default value when `s` is "." or "?", which in CIF
776
+ format stands for inapplicable and unknown, respectively.
777
+
778
+ Returns
779
+ -------
780
+ float
781
+ The extracted floating point value.
782
+
783
+ Raises
784
+ ------
785
+ ValueError
786
+ When string does not start with a float.
787
+ """
788
+ sbare = s.strip()
789
+ mx = rx_float.match(sbare)
790
+ if mx:
791
+ rv = float(mx.group())
792
+ elif sbare == "." or sbare == "?":
793
+ # CIF files may contain "." or "?" for unknown values
794
+ rv = d
795
+ else:
796
+ rv = float(sbare)
797
+ return rv
798
+
799
+
800
+ # helper dictionary for getSymOp()
801
+ symvec = {
802
+ "x": numpy.array([1, 0, 0], dtype=float),
803
+ "y": numpy.array([0, 1, 0], dtype=float),
804
+ "z": numpy.array([0, 0, 1], dtype=float),
805
+ "-x": numpy.array([-1, 0, 0], dtype=float),
806
+ "-y": numpy.array([0, -1, 0], dtype=float),
807
+ "-z": numpy.array([0, 0, -1], dtype=float),
808
+ }
809
+ symvec["+x"] = symvec["x"]
810
+ symvec["+y"] = symvec["y"]
811
+ symvec["+z"] = symvec["z"]
812
+
813
+
814
+ def getSymOp(s):
815
+ """Create `SpaceGroups.SymOp` instance from a string.
816
+
817
+ Parameters
818
+ ----------
819
+ s : str
820
+ Formula for equivalent coordinates, for example ``'x,1/2-y,1/2+z'``.
821
+
822
+ Returns
823
+ -------
824
+ SymOp
825
+ Instance of `SymOp`.
826
+ """
827
+ from diffpy.structure.spacegroups import SymOp
828
+
829
+ snoblanks = s.replace(" ", "")
830
+ eqlist = snoblanks.split(",")
831
+ R = numpy.zeros((3, 3), dtype=float)
832
+ t = numpy.zeros(3, dtype=float)
833
+ for i in (0, 1, 2):
834
+ eqparts = re.split("(?i)([+-]?[xyz])", eqlist[i])
835
+ for Rpart in eqparts[1::2]:
836
+ R[i, :] += symvec[Rpart.lower()]
837
+ for tpart in eqparts[::2]:
838
+ t[i] += eval("1.0*%s+0" % tpart)
839
+ t -= numpy.floor(t)
840
+ rv = SymOp(R, t)
841
+ return rv
842
+
843
+
844
+ def getParser(eps=None):
845
+ """Return new `parser` object for CIF format.
846
+
847
+ Parameters
848
+ ----------
849
+ eps : float, Optional
850
+ fractional coordinates cutoff for duplicate positions.
851
+ When ``None`` use the default for `ExpandAsymmetricUnit`: ``1.0e-5``.
852
+
853
+ Returns
854
+ -------
855
+ P_cif
856
+ Instance of `P_cif`.
857
+ """
858
+ return P_cif(eps=eps)
859
+
860
+
861
+ # Local Helpers --------------------------------------------------------------
862
+
863
+
864
+ @contextmanager
865
+ def _suppressCifParserOutput():
866
+ """Context manager which suppresses diagnostic messages from CIF parser."""
867
+ from CifFile import yapps3_compiled_rt
868
+
869
+ print_error = yapps3_compiled_rt.print_error
870
+ # replace the print_error function with no-operation
871
+ yapps3_compiled_rt.print_error = lambda *a, **kw: None
872
+ try:
873
+ yield print_error
874
+ finally:
875
+ yapps3_compiled_rt.print_error = print_error
876
+ pass