• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

materialsproject / pymatgen / 4075885785

pending completion
4075885785

push

github

Shyue Ping Ong
Merge branch 'master' of github.com:materialsproject/pymatgen

96 of 96 new or added lines in 27 files covered. (100.0%)

81013 of 102710 relevant lines covered (78.88%)

0.79 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

95.54
/pymatgen/phonon/thermal_displacements.py
1
# Copyright (c) Pymatgen Development Team.
2
# Distributed under the terms of the MIT License.
3

4
"""
1✔
5
This module provides classes to handle thermal displacement matrices (anisotropic displacement parameters).
6
"""
7
from __future__ import annotations
1✔
8

9
import re
1✔
10
from functools import partial
1✔
11

12
import numpy as np
1✔
13
from monty.json import MSONable
1✔
14

15
from pymatgen.analysis.structure_matcher import StructureMatcher
1✔
16
from pymatgen.core.structure import Structure
1✔
17
from pymatgen.io.cif import CifFile, CifParser, CifWriter, str2float
1✔
18
from pymatgen.symmetry.groups import SYMM_DATA
1✔
19

20
sub_spgrp = partial(re.sub, r"[\s_]", "")
1✔
21

22
space_groups = {sub_spgrp(k): k for k in SYMM_DATA["space_group_encoding"]}  # type: ignore
1✔
23

24
try:
1✔
25
    import phonopy
1✔
26
except ImportError as ex:
×
27
    print(ex)
×
28
    phonopy = None
×
29

30
__author__ = "J. George"
1✔
31
__copyright__ = "Copyright 2022, The Materials Project"
1✔
32
__version__ = "0.1"
1✔
33
__maintainer__ = "J. George"
1✔
34
__email__ = "janine.george@bam.de"
1✔
35
__status__ = "Testing"
1✔
36
__date__ = "August 09, 2022"
1✔
37

38

39
class ThermalDisplacementMatrices(MSONable):
1✔
40
    """
41
    Class to handle thermal displacement matrices
42
    This class stores thermal displacement matrices in Ucart format
43

44
    An earlier implementation based on Matlab can be found here:
45
    https://github.com/JaGeo/MolecularToolbox
46
    ( J. George, A. Wang, V. L. Deringer, R. Wang, R. Dronskowski, U. Englert, CrystEngComm, 2015, 17, 7414–7422.)
47
    """
48

49
    def __init__(self, thermal_displacement_matrix_cart, structure, temperature, thermal_displacement_matrix_cif=None):
1✔
50
        """
51
        Args:
52
            thermal_displacement_matrix: 2D numpy array including the thermal_displacement matrix Ucart
53
                1st dimension atom types, then compressed thermal displacement matrix will follow
54
                 U11, U22, U33, U23, U13, U12 (xx, yy, zz, yz, xz, xy)
55
                 convention similar to "thermal_displacement_matrices.yaml" in phonopy
56
            structure: A pymatgen Structure object
57
            temperature: temperature at which thermal displacement matrix was determined
58
            thermal_displacement_matrix: 2D numpy array including the thermal_displacement matrix Ucif format
59
                1st dimension atom types, then compressed thermal displacement matrix will follow
60
                 U11, U22, U33, U23, U13, U12 (xx, yy, zz, yz, xz, xy)
61
                 convention similar to "thermal_displacement_matrices.yaml" in phonopy
62
        """
63
        self.thermal_displacement_matrix_cart = np.array(thermal_displacement_matrix_cart)
1✔
64
        self.structure = structure
1✔
65
        self.temperature = temperature
1✔
66
        if thermal_displacement_matrix_cif is not None:
1✔
67
            self.thermal_displacement_matrix_cif = np.array(thermal_displacement_matrix_cif)
1✔
68
        else:
69
            self.thermal_displacement_matrix_cif = None
1✔
70

71
        # convert to matrix form easier handling of the data
72
        self.thermal_displacement_matrix_cart_matrixform = ThermalDisplacementMatrices.get_full_matrix(
1✔
73
            self.thermal_displacement_matrix_cart
74
        )
75

76
        if self.thermal_displacement_matrix_cif is not None:
1✔
77
            self.thermal_displacement_matrix_cif_matrixform = ThermalDisplacementMatrices.get_full_matrix(
1✔
78
                self.thermal_displacement_matrix_cif
79
            )
80

81
    @staticmethod
1✔
82
    def get_full_matrix(thermal_displacement):
1✔
83
        """
84
        Transfers the reduced matrix to the full matrix (order of reduced matrix U11, U22, U33, U23, U13, U12)
85

86
        Args:
87
            thermal_displacement: 2d numpy array, first dimension are the atoms
88

89
        Returns:
90
            3d numpy array including thermal displacements, first dimensions are the atoms
91
        """
92
        matrixform = np.zeros((len(thermal_displacement), 3, 3))
1✔
93
        for imat, mat in enumerate(thermal_displacement):
1✔
94
            # xx, yy, zz, yz, xz, xy
95
            matrixform[imat][0][0] = mat[0]
1✔
96
            matrixform[imat][1][1] = mat[1]
1✔
97
            matrixform[imat][2][2] = mat[2]
1✔
98
            matrixform[imat][1][2] = mat[3]
1✔
99
            matrixform[imat][2][1] = mat[3]
1✔
100
            matrixform[imat][0][2] = mat[4]
1✔
101
            matrixform[imat][2][0] = mat[4]
1✔
102
            matrixform[imat][0][1] = mat[5]
1✔
103
            matrixform[imat][1][0] = mat[5]
1✔
104
        return matrixform
1✔
105

106
    @staticmethod
1✔
107
    def get_reduced_matrix(thermal_displacement):
1✔
108
        """
109
        Transfers the full matrix to reduced matrix (order of reduced matrix U11, U22, U33, U23, U13, U12)
110

111
        Args:
112
            thermal_displacement: 2d numpy array, first dimension are the atoms
113

114
        Returns:
115
            3d numpy array including thermal displacements, first dimensions are the atoms
116
        """
117
        reduced_matrix = np.zeros((len(thermal_displacement), 6))
1✔
118
        for imat, mat in enumerate(thermal_displacement):
1✔
119
            # xx, yy, zz, yz, xz, xy
120
            reduced_matrix[imat][0] = mat[0][0]
1✔
121
            reduced_matrix[imat][1] = mat[1][1]
1✔
122
            reduced_matrix[imat][2] = mat[2][2]
1✔
123
            reduced_matrix[imat][3] = mat[1][2]
1✔
124
            reduced_matrix[imat][4] = mat[0][2]
1✔
125
            reduced_matrix[imat][5] = mat[0][1]
1✔
126
        return reduced_matrix
1✔
127

128
    @property
1✔
129
    def Ustar(self):
1✔
130
        """
131
        Computation as described in R. W. Grosse-Kunstleve, P. D. Adams, J Appl Cryst 2002, 35, 477–480.
132
        Returns: Ustar as a numpy array, first dimension are the atoms in the structure
133
        """
134
        A = self.structure.lattice.matrix.T
1✔
135
        Ainv = np.linalg.inv(A)
1✔
136
        Ustar = []
1✔
137
        for mat in self.thermal_displacement_matrix_cart_matrixform:
1✔
138
            mat_ustar = np.dot(np.dot(Ainv, mat), Ainv.T)
1✔
139
            Ustar.append(mat_ustar)
1✔
140
        return np.array(Ustar)
1✔
141

142
    @property
1✔
143
    def Ucif(self):
1✔
144
        """
145
        Computation as described in R. W. Grosse-Kunstleve, P. D. Adams, J Appl Cryst 2002, 35, 477–480.
146
        Returns: Ucif as a numpy array, first dimension are the atoms in the structure
147
        """
148
        if self.thermal_displacement_matrix_cif is None:
1✔
149
            # computation as described in R. W. Grosse-Kunstleve, P. D. Adams, J Appl Cryst 2002, 35, 477–480.
150
            # will compute Ucif based on Ustar for each atom in the structure
151

152
            A = self.structure.lattice.matrix.T  # check this again?
1✔
153
            N = np.diag([np.linalg.norm(x) for x in np.linalg.inv(A)])
1✔
154
            Ninv = np.linalg.inv(N)
1✔
155
            Ucif = []
1✔
156
            Ustar = self.Ustar
1✔
157
            for mat in Ustar:
1✔
158
                mat_cif = np.dot(np.dot(Ninv, mat), Ninv.T)
1✔
159
                Ucif.append(mat_cif)
1✔
160
            return np.array(Ucif)
1✔
161
        else:
162
            return self.thermal_displacement_matrix_cif_matrixform
×
163

164
    @property
1✔
165
    def B(self):
1✔
166
        """
167
        Computation as described in R. W. Grosse-Kunstleve, P. D. Adams, J Appl Cryst 2002, 35, 477–480.
168
        Returns: B as a numpy array, first dimension are the atoms in the structure
169
        """
170
        B = []
1✔
171
        for mat in self.Ucif:
1✔
172
            mat_B = mat * 8 * np.pi**2
1✔
173
            B.append(mat_B)
1✔
174
        return np.array(B)
1✔
175

176
    @property
1✔
177
    def beta(self):
1✔
178
        """
179
        Computation as described in R. W. Grosse-Kunstleve, P. D. Adams, J Appl Cryst 2002, 35, 477–480.
180
        Returns: beta as a numpy array, first dimension are the atoms in the structure
181
        """
182
        # will compute beta based on Ustar
183
        beta = []
1✔
184
        for mat in self.Ustar:
1✔
185
            mat_beta = mat * 2 * np.pi**2
1✔
186
            beta.append(mat_beta)
1✔
187
        return beta
1✔
188

189
    @property
1✔
190
    def U1U2U3(self):
1✔
191
        """
192
        Computation as described in R. W. Grosse-Kunstleve, P. D. Adams, J Appl Cryst 2002, 35, 477–480.
193
        Returns: numpy array of eigenvalues of Ucart,  first dimension are the atoms in the structure
194
        """
195
        U1U2U3 = []
1✔
196
        for mat in self.thermal_displacement_matrix_cart_matrixform:
1✔
197
            U1U2U3.append(np.linalg.eig(mat)[0])
1✔
198
        return U1U2U3
1✔
199

200
    def write_cif(self, filename):
1✔
201
        """
202
        Writes a cif including thermal displacements
203

204
        Args:
205
            filename: name of the cif file
206
        """
207
        w = CifWriter(self.structure)
1✔
208
        w.write_file(filename)
1✔
209
        # This will simply append the thermal displacement part to the cif from the CifWriter
210
        # In the long run, CifWriter could be extended to handle thermal displacement matrices
211
        with open(filename, "a") as file:
1✔
212
            file.write("loop_ \n")
1✔
213
            file.write("_atom_site_aniso_label\n")
1✔
214
            file.write("_atom_site_aniso_U_11\n")
1✔
215
            file.write("_atom_site_aniso_U_22\n")
1✔
216
            file.write("_atom_site_aniso_U_33\n")
1✔
217
            file.write("_atom_site_aniso_U_23\n")
1✔
218
            file.write("_atom_site_aniso_U_13\n")
1✔
219
            file.write("_atom_site_aniso_U_12\n")
1✔
220
            file.write(f"# Additional Data for U_Aniso: {self.temperature}\n")
1✔
221

222
            count = 0
1✔
223
            for site, matrix in zip(self.structure, self.Ucif):
1✔
224
                file.write(
1✔
225
                    f"{site.specie.symbol}{count} {matrix[0][0]} {matrix[1][1]} {matrix[2][2]}"
226
                    f" {matrix[1][2]} {matrix[0][2]} {matrix[0][1]}\n"
227
                )
228
                count += 1
1✔
229

230
    @staticmethod
1✔
231
    def _angle_dot(a, b):
1✔
232
        dot_product = np.dot(a, b)
1✔
233
        prod_of_norms = np.linalg.norm(a) * np.linalg.norm(b)
1✔
234
        divided = dot_product / prod_of_norms
1✔
235
        angle_rad = np.arccos(np.round(divided, 10))
1✔
236
        angle = np.degrees(angle_rad)
1✔
237
        return angle
1✔
238

239
    def compute_directionality_quality_criterion(self, other):
1✔
240
        """
241
        Will compute directionality of prolate displacement ellipsoids as described in
242
        https://doi.org/10.1039/C9CE00794F with the earlier implementation: https://github.com/damMroz/Angle/
243
        Args:
244
            other: ThermalDisplacementMatrix
245
            please make sure that the order of the atoms in both objects that are compared
246
            is the same. Otherwise, this analysis will deliver wrong results
247

248
        Returns:
249
            will return a list including dicts for each atom that include "vector0"
250
            (largest principal axes of self object),
251
             "vector1" (largest principal axes of the other object), "angle" between both axes,
252
              These vectors can then, for example, be drawn into the structure with VESTA.
253
              Vectors are given in Cartesian coordinates
254
        """
255
        # compare the atoms string at least
256
        for spec1, spec2 in zip(self.structure.species, other.structure.species):
1✔
257
            if spec1 != spec2:
1✔
258
                raise ValueError(
×
259
                    "Species in both structures are not the same! "
260
                    "Please use structures that are similar to each other"
261
                )
262
        # check if structures match
263
        structure_match = StructureMatcher()
1✔
264
        if not structure_match.fit(struct1=self.structure, struct2=other.structure):
1✔
265
            raise ValueError("Structures have to be similar")
×
266

267
        results = []
1✔
268
        for self_Ucart, other_Ucart in zip(
1✔
269
            self.thermal_displacement_matrix_cart_matrixform, other.thermal_displacement_matrix_cart_matrixform
270
        ):
271
            result_dict = {}
1✔
272

273
            # determine eigenvalues and vectors for inverted Ucart
274
            invUcart_eig_self, invUcart_eigv_self = np.linalg.eig(np.linalg.inv(self_Ucart))
1✔
275
            invUcart_eig_other, invUcart_eigv_other = np.linalg.eig(np.linalg.inv(other_Ucart))
1✔
276

277
            argmin_self = np.argmin(invUcart_eig_self)
1✔
278
            vec_self = invUcart_eigv_self.transpose()[argmin_self]
1✔
279
            argmin_other = np.argmin(invUcart_eig_other)
1✔
280
            vec_other = invUcart_eigv_other.transpose()[argmin_other]
1✔
281
            # vector direction does not matter here, smallest angle should be given
282
            result_dict["angle"] = np.min(
1✔
283
                [self._angle_dot(vec_self, vec_other), self._angle_dot(vec_self, vec_other * -1)]
284
            )
285
            result_dict["vector0"] = vec_self
1✔
286
            result_dict["vector1"] = vec_other
1✔
287

288
            results.append(result_dict)
1✔
289

290
        return results
1✔
291

292
    def visualize_directionality_quality_criterion(
1✔
293
        self, other, filename: str = "visualization.vesta", which_structure: int = 0
294
    ):
295
        """
296
        Will create a VESTA file for visualization of the directionality criterion.
297

298
        Args:
299
            other: ThermalDisplacementMatrices
300
            filename:           Filename of the VESTA file
301
            which_structure:    0 means structure of the self object will be used, 1 means structure of the other
302
                                object will be used
303
        """
304
        # will return a VESTA file including vectors to visualize the quality criterion
305
        result = self.compute_directionality_quality_criterion(other=other)
1✔
306
        matrix_cif = (
1✔
307
            self.thermal_displacement_matrix_cif
308
            if self.thermal_displacement_matrix_cif is not None
309
            else self.get_reduced_matrix(self.Ucif)
310
        )
311

312
        if which_structure == 0:
1✔
313
            structure = self.structure
1✔
314
        elif which_structure == 1:
×
315
            structure = other.structure
×
316

317
        with open(filename, "w") as f:
1✔
318
            #
319
            f.write("#VESTA_FORMAT_VERSION 3.5.4\n \n \n")
1✔
320
            f.write("CRYSTAL\n\n")
1✔
321
            f.write("TITLE\n")
1✔
322
            f.write("Directionality Criterion\n\n")
1✔
323
            f.write("GROUP\n")
1✔
324
            f.write("1 1 P 1\n\n")
1✔
325
            f.write("CELLP\n")
1✔
326
            f.write(
1✔
327
                f"{structure.lattice.a} {structure.lattice.b} {structure.lattice.c} "
328
                f"{structure.lattice.alpha} {structure.lattice.beta} {structure.lattice.gamma}\n"
329
            )
330
            f.write("  0.000000   0.000000   0.000000   0.000000   0.000000   0.000000\n")  # error on parameters
1✔
331
            f.write("STRUC\n")
1✔
332

333
            for isite, site in enumerate(structure):
1✔
334
                f.write(
1✔
335
                    f"{isite + 1} {site.species_string} {site.species_string}{isite + 1} 1.0000 {site.frac_coords[0]} "
336
                    f"{site.frac_coords[1]} {site.frac_coords[2]} 1a 1\n"
337
                )
338
                f.write(" 0.000000 0.000000 0.000000 0.00\n")  # error on positions - zero here
1✔
339

340
            # now we iterate over the whole structure and write down the frational coordinates (with errors)
341
            f.write("  0 0 0 0 0 0 0\n")
1✔
342
            f.write("THERT 0\n")
1✔
343
            f.write("THERM\n")
1✔
344
            # print all U11s (make sure they are in the correct order)
345
            counter = 1
1✔
346
            # VESTA order: _U_12    _U_13    _atom_site_aniso_U_23
347
            for atom_therm, site in zip(matrix_cif, structure):
1✔
348
                f.write(
1✔
349
                    f"{counter} {site.species_string}{counter} {atom_therm[0]} "
350
                    f"{atom_therm[1]} {atom_therm[2]} {atom_therm[5]} {atom_therm[4]} {atom_therm[3]}\n"
351
                )
352
                counter += 1
1✔
353
            f.write("  0 0 0 0 0 0 0 0\n")
1✔
354
            f.write("VECTR\n")
1✔
355
            vectorcounter = 1
1✔
356
            sitecounter = 1
1✔
357
            for vectors in result:
1✔
358
                vector0_x = vectors["vector0"][0]
1✔
359
                vector0_y = vectors["vector0"][1]
1✔
360
                vector0_z = vectors["vector0"][2]
1✔
361
                vector1_x = vectors["vector1"][0]
1✔
362
                vector1_y = vectors["vector1"][1]
1✔
363
                vector1_z = vectors["vector1"][2]
1✔
364

365
                f.write(f"    {vectorcounter} {vector0_x} {vector0_y} {vector0_z} 0\n")
1✔
366
                f.write(f"    {sitecounter} 0 0 0 0\n")
1✔
367

368
                f.write(" 0 0 0 0 0\n")
1✔
369
                vectorcounter += 1
1✔
370
                f.write(f"    {vectorcounter} {vector1_x} {vector1_y} {vector1_z} 0\n")
1✔
371
                f.write(f"    {sitecounter} 0 0 0 0\n")
1✔
372
                vectorcounter += 1
1✔
373
                sitecounter += 1
1✔
374
                f.write(" 0 0 0 0 0\n")
1✔
375

376
            f.write(" 0 0 0 0 0\n")
1✔
377
            f.write("VECTT\n")
1✔
378

379
            counter = 1
1✔
380
            # two vectors per atom
381
            for _i in range(len(result)):
1✔
382
                f.write(f"{counter} 0.2 255 0 0 1\n")
1✔
383
                counter += 1
1✔
384
                f.write(f"{counter} 0.2 0 0 255 1\n")
1✔
385
                counter += 1
1✔
386

387
            f.write(" 0 0 0 0 0\n")
1✔
388

389
    @property
1✔
390
    def ratio_prolate(self):
1✔
391
        """
392
        This will compute ratio between largest eigenvalue of Ucart and smallest one
393
        Returns:
394
        """
395
        ratios = []
1✔
396
        for us in self.U1U2U3:
1✔
397
            ratios.append(np.max(us) / np.min(us))
1✔
398

399
        return np.array(ratios)
1✔
400

401
    @staticmethod
1✔
402
    def from_Ucif(thermal_displacement_matrix_cif, structure, temperature):
1✔
403
        """
404
        Starting from a numpy array, it will convert Ucif values into Ucart values and initialize the class
405

406
        Args:
407
            thermal_displacement_matrix_cif: np.array,
408
                first dimension are the atoms,
409
                then reduced form of thermal displacement matrix will follow
410
                Order as above: U11, U22, U33, U23, U13, U12
411
            structure: Structure object
412
            temperature: float
413
                Corresponding temperature
414

415
        Returns:
416
            ThermalDisplacementMatrices
417
        """
418
        # get matrix form
419
        thermal_displacement_matrix_cif_matrix_form = ThermalDisplacementMatrices.get_full_matrix(
1✔
420
            thermal_displacement_matrix_cif
421
        )
422

423
        # convert the parameters for each atom
424
        A = structure.lattice.matrix.T
1✔
425
        N = np.diag([np.linalg.norm(x) for x in np.linalg.inv(A)])
1✔
426
        Ucart = []
1✔
427
        for mat in thermal_displacement_matrix_cif_matrix_form:
1✔
428
            mat_ustar = np.dot(np.dot(N, mat), N.T)
1✔
429
            mat_ucart = np.dot(np.dot(A, mat_ustar), A.T)
1✔
430
            Ucart.append(mat_ucart)
1✔
431

432
        thermal_displacement_matrix_cart = ThermalDisplacementMatrices.get_reduced_matrix(np.array(Ucart))
1✔
433

434
        # get ThermalDisplacementMatrices Object
435

436
        return ThermalDisplacementMatrices(
1✔
437
            thermal_displacement_matrix_cart=thermal_displacement_matrix_cart,
438
            thermal_displacement_matrix_cif=thermal_displacement_matrix_cif,
439
            structure=structure,
440
            temperature=temperature,
441
        )
442

443
    def to_structure_with_site_properties_Ucif(self):
1✔
444
        """
445
        Transfers this object into a structure with site properties (Ucif).
446
        This is useful for sorting the atoms in the structure including site properties.
447
        E.g., with code like this:
448
        def sort_order(site):
449
            return [site.specie.X, site.frac_coords[0], site.frac_coords[1], site.frac_coords[2]]
450
        new_structure0 = Structure.from_sites(sorted(structure0, key=sort_order))
451

452
        Returns: Structure object.
453
        """
454
        site_properties = {"U11_cif": [], "U22_cif": [], "U33_cif": [], "U23_cif": [], "U13_cif": [], "U12_cif": []}
1✔
455
        if self.thermal_displacement_matrix_cif is None:
1✔
456
            cif_matrix = self.get_reduced_matrix(self.Ucif)
1✔
457
        else:
458
            cif_matrix = self.thermal_displacement_matrix_cif
×
459
        for atom_ucif in cif_matrix:
1✔
460
            # U11, U22, U33, U23, U13, U12
461
            site_properties["U11_cif"].append(atom_ucif[0])
1✔
462
            site_properties["U22_cif"].append(atom_ucif[1])
1✔
463
            site_properties["U33_cif"].append(atom_ucif[2])
1✔
464
            site_properties["U23_cif"].append(atom_ucif[3])
1✔
465
            site_properties["U13_cif"].append(atom_ucif[4])
1✔
466
            site_properties["U12_cif"].append(atom_ucif[5])
1✔
467

468
        return self.structure.copy(site_properties=site_properties)
1✔
469

470
    @staticmethod
1✔
471
    def from_structure_with_site_properties_Ucif(structure: Structure, temperature: float | None = None):
1✔
472
        """
473
        Will create this object with the help of a structure with site properties.
474

475
        Args:
476
            structure: Structure object including U11_cif, U22_cif, U33_cif, U23_cif, U13_cif, U12_cif as site
477
            properties
478
            temperature: temperature for Ucif data
479

480
        Returns: ThermalDisplacementMatrices Object.
481

482
        """
483
        Ucif_matrix = []
1✔
484
        # U11, U22, U33, U23, U13, U12
485
        for site in structure:
1✔
486
            Ucif_matrix.append(
1✔
487
                [
488
                    site.properties["U11_cif"],
489
                    site.properties["U22_cif"],
490
                    site.properties["U33_cif"],
491
                    site.properties["U23_cif"],
492
                    site.properties["U13_cif"],
493
                    site.properties["U12_cif"],
494
                ]
495
            )
496

497
        return ThermalDisplacementMatrices.from_Ucif(Ucif_matrix, structure, temperature=temperature)
1✔
498

499
    @staticmethod
1✔
500
    def from_cif_P1(filename: str):
1✔
501
        """
502
        Reads a cif with P1 symmetry including positions and ADPs.
503
        Currently, no check of symmetry is performed as CifParser methods cannot be easily reused
504
        Args:
505
            filename: Filename of the cif
506

507
        Returns: ThermalDisplacementMatrices Object.
508

509
        """
510
        # This code is adapted from the CifParser
511
        # to reuse more code from there, the CifParser would need a major refactoring
512
        # Future plans: add this part to CifParser (i.e., include handling of symmetry for ADPs)
513

514
        cif = CifFile.from_file(filename)
1✔
515
        thermals = []
1✔
516
        for data in cif.data.values():
1✔
517
            # can only handle P1 symmetry for now
518

519
            lattice = CifParser.get_lattice_no_exception(data)
1✔
520

521
            allcoords = []
1✔
522
            allspecies = []
1✔
523
            for i in range(len(data["_atom_site_label"])):
1✔
524
                try:
1✔
525
                    # If site type symbol exists, use it. Otherwise, we use the
526
                    # label.
527
                    symbol = CifParser(filename)._parse_symbol(data["_atom_site_type_symbol"][i])
1✔
528
                except KeyError:
×
529
                    symbol = CifParser(filename)._parse_symbol(data["_atom_site_label"][i])
×
530
                if not symbol:
1✔
531
                    continue
×
532

533
                allspecies.append(symbol)
1✔
534
                x = str2float(data["_atom_site_fract_x"][i])
1✔
535
                y = str2float(data["_atom_site_fract_y"][i])
1✔
536
                z = str2float(data["_atom_site_fract_z"][i])
1✔
537

538
                allcoords.append([x, y, z])
1✔
539

540
            thermals_Ucif = []
1✔
541
            # U11, U22, U33, U23, U13, U12
542
            for i in range(len(data["_atom_site_aniso_label"])):
1✔
543
                thermals_Ucif.append(
1✔
544
                    [
545
                        str2float(data["_atom_site_aniso_U_11"][i]),
546
                        str2float(data["_atom_site_aniso_U_22"][i]),
547
                        str2float(data["_atom_site_aniso_U_33"][i]),
548
                        str2float(data["_atom_site_aniso_U_23"][i]),
549
                        str2float(data["_atom_site_aniso_U_13"][i]),
550
                        str2float(data["_atom_site_aniso_U_12"][i]),
551
                    ]
552
                )
553
            struct = Structure(lattice, allspecies, allcoords)
1✔
554

555
            thermal = ThermalDisplacementMatrices.from_Ucif(
1✔
556
                thermal_displacement_matrix_cif=thermals_Ucif, structure=struct, temperature=None
557
            )
558
            thermals.append(thermal)
1✔
559
        return thermals
1✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc