• 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

80.15
/pymatgen/phonon/bandstructure.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 define a phonon band structure.
6
"""
7

8
from __future__ import annotations
1✔
9

10
import numpy as np
1✔
11
from monty.json import MSONable
1✔
12

13
from pymatgen.core.lattice import Lattice
1✔
14
from pymatgen.core.structure import Structure
1✔
15
from pymatgen.electronic_structure.bandstructure import Kpoint
1✔
16

17

18
def get_reasonable_repetitions(n_atoms: int) -> tuple[int, int, int]:
1✔
19
    """
20
    Choose the number of repetitions in a supercell
21
    according to the number of atoms in the system
22
    """
23
    if n_atoms < 4:
1✔
24
        return (3, 3, 3)
1✔
25
    if 4 <= n_atoms < 15:
×
26
        return (2, 2, 2)
×
27
    if 15 <= n_atoms < 50:
×
28
        return (2, 2, 1)
×
29

30
    return (1, 1, 1)
×
31

32

33
def eigenvectors_from_displacements(disp, masses):
1✔
34
    """
35
    Calculate the eigenvectors from the atomic displacements
36
    """
37
    sqrt_masses = np.sqrt(masses)
×
38
    return np.einsum("nax,a->nax", disp, sqrt_masses)
×
39

40

41
def estimate_band_connection(prev_eigvecs, eigvecs, prev_band_order):
1✔
42
    """
43
    A function to order the phonon eigenvectors taken from phonopy
44
    """
45
    metric = np.abs(np.dot(prev_eigvecs.conjugate().T, eigvecs))
×
46
    connection_order = []
×
47
    for overlaps in metric:
×
48
        max_val = 0
×
49
        for i in reversed(range(len(metric))):
×
50
            val = overlaps[i]
×
51
            if i in connection_order:
×
52
                continue
×
53
            if val > max_val:
×
54
                max_val = val
×
55
                max_idx = i
×
56
        connection_order.append(max_idx)
×
57

58
    band_order = [connection_order[x] for x in prev_band_order]
×
59

60
    return band_order
×
61

62

63
class PhononBandStructure(MSONable):
1✔
64
    """
65
    This is the most generic phonon band structure data possible
66
    it's defined by a list of qpoints + frequencies for each of them.
67
    Additional information may be given for frequencies at Gamma, where
68
    non-analytical contribution may be taken into account.
69
    """
70

71
    def __init__(
1✔
72
        self,
73
        qpoints: list[Kpoint],
74
        frequencies: np.ndarray,
75
        lattice: Lattice,
76
        nac_frequencies=None,
77
        eigendisplacements=None,
78
        nac_eigendisplacements=None,
79
        labels_dict=None,
80
        coords_are_cartesian=False,
81
        structure: Structure | None = None,
82
    ):
83
        """
84
        Args:
85
            qpoints: list of qpoint as numpy arrays, in frac_coords of the
86
                given lattice by default
87
            frequencies: list of phonon frequencies in THz as a numpy array with shape
88
                (3*len(structure), len(qpoints)). The First index of the array
89
                refers to the band and the second to the index of the qpoint.
90
            lattice: The reciprocal lattice as a pymatgen Lattice object.
91
                Pymatgen uses the physics convention of reciprocal lattice vectors
92
                WITH a 2*pi coefficient.
93
            nac_frequencies: Frequencies with non-analytical contributions at Gamma in THz.
94
                A list of tuples. The first element of each tuple should be a list
95
                defining the direction (not necessarily a versor, will be normalized
96
                internally). The second element containing the 3*len(structure)
97
                phonon frequencies with non-analytical correction for that direction.
98
            eigendisplacements: the phonon eigendisplacements associated to the
99
                frequencies in Cartesian coordinates. A numpy array of complex
100
                numbers with shape (3*len(structure), len(qpoints), len(structure), 3).
101
                he First index of the array refers to the band, the second to the index
102
                of the qpoint, the third to the atom in the structure and the fourth
103
                to the Cartesian coordinates.
104
            nac_eigendisplacements: the phonon eigendisplacements associated to the
105
                non-analytical frequencies in nac_frequencies in Cartesian coordinates.
106
                A list of tuples. The first element of each tuple should be a list
107
                defining the direction. The second element containing a numpy array of
108
                complex numbers with shape (3*len(structure), len(structure), 3).
109
            labels_dict: (dict) of {} this links a qpoint (in frac coords or
110
                Cartesian coordinates depending on the coords) to a label.
111
            coords_are_cartesian: Whether the qpoint coordinates are Cartesian.
112
            structure: The crystal structure (as a pymatgen Structure object)
113
                associated with the band structure. This is needed if we
114
                provide projections to the band structure
115
        """
116
        self.lattice_rec = lattice
1✔
117
        self.qpoints = []
1✔
118
        self.labels_dict = {}
1✔
119
        self.structure = structure
1✔
120
        if eigendisplacements is None:
1✔
121
            eigendisplacements = np.array([])
1✔
122
        self.eigendisplacements = eigendisplacements
1✔
123
        if labels_dict is None:
1✔
124
            labels_dict = {}
1✔
125

126
        for q_pt in qpoints:
1✔
127
            # let see if this qpoint has been assigned a label
128
            label = None
1✔
129
            for c in labels_dict:
1✔
130
                if np.linalg.norm(q_pt - np.array(labels_dict[c])) < 0.0001:
1✔
131
                    label = c
1✔
132
                    self.labels_dict[label] = Kpoint(
1✔
133
                        q_pt,
134
                        lattice,
135
                        label=label,
136
                        coords_are_cartesian=coords_are_cartesian,
137
                    )
138
            self.qpoints.append(Kpoint(q_pt, lattice, label=label, coords_are_cartesian=coords_are_cartesian))
1✔
139
        self.bands = frequencies
1✔
140
        self.nb_bands = len(self.bands)
1✔
141
        self.nb_qpoints = len(self.qpoints)
1✔
142

143
        # normalize directions for nac_frequencies and nac_eigendisplacements
144
        self.nac_frequencies = []
1✔
145
        self.nac_eigendisplacements = []
1✔
146
        if nac_frequencies is not None:
1✔
147
            for t in nac_frequencies:
×
148
                self.nac_frequencies.append(([i / np.linalg.norm(t[0]) for i in t[0]], t[1]))
×
149
        if nac_eigendisplacements is not None:
1✔
150
            for t in nac_eigendisplacements:
×
151
                self.nac_eigendisplacements.append(([i / np.linalg.norm(t[0]) for i in t[0]], t[1]))
×
152

153
    def min_freq(self) -> tuple[Kpoint, float]:
1✔
154
        """
155
        Returns the point where the minimum frequency is reached and its value
156
        """
157
        i = np.unravel_index(np.argmin(self.bands), self.bands.shape)
1✔
158

159
        return self.qpoints[i[1]], self.bands[i]
1✔
160

161
    def has_imaginary_freq(self, tol: float = 1e-5) -> bool:
1✔
162
        """
163
        True if imaginary frequencies are present in the BS.
164
        """
165
        return self.min_freq()[1] + tol < 0
1✔
166

167
    @property
1✔
168
    def has_nac(self) -> bool:
1✔
169
        """
170
        True if nac_frequencies are present.
171
        """
172
        return len(self.nac_frequencies) > 0
1✔
173

174
    @property
1✔
175
    def has_eigendisplacements(self) -> bool:
1✔
176
        """
177
        True if eigendisplacements are present.
178
        """
179
        return len(self.eigendisplacements) > 0
1✔
180

181
    def get_nac_frequencies_along_dir(self, direction):
1✔
182
        """
183
        Returns the nac_frequencies for the given direction (not necessarily a versor).
184
        None if the direction is not present or nac_frequencies has not been calculated.
185

186
        Args:
187
            direction: the direction as a list of 3 elements
188
        Returns:
189
            the frequencies as a numpy array o(3*len(structure), len(qpoints)).
190
            None if not found.
191
        """
192
        versor = [i / np.linalg.norm(direction) for i in direction]
1✔
193
        for d, f in self.nac_frequencies:
1✔
194
            if np.allclose(versor, d):
1✔
195
                return f
1✔
196

197
        return None
1✔
198

199
    def get_nac_eigendisplacements_along_dir(self, direction):
1✔
200
        """
201
        Returns the nac_eigendisplacements for the given direction (not necessarily a versor).
202
        None if the direction is not present or nac_eigendisplacements has not been calculated.
203

204
        Args:
205
            direction: the direction as a list of 3 elements
206
        Returns:
207
            the eigendisplacements as a numpy array of complex numbers with shape
208
            (3*len(structure), len(structure), 3). None if not found.
209
        """
210
        versor = [i / np.linalg.norm(direction) for i in direction]
1✔
211
        for d, e in self.nac_eigendisplacements:
1✔
212
            if np.allclose(versor, d):
1✔
213
                return e
1✔
214

215
        return None
1✔
216

217
    def asr_breaking(self, tol_eigendisplacements=1e-5):
1✔
218
        """
219
        Returns the breaking of the acoustic sum rule for the three acoustic modes,
220
        if Gamma is present. None otherwise.
221
        If eigendisplacements are available they are used to determine the acoustic
222
        modes: selects the bands corresponding  to the eigendisplacements that
223
        represent to a translation within tol_eigendisplacements. If these are not
224
        identified or eigendisplacements are missing the first 3 modes will be used
225
        (indices [0:3]).
226
        """
227
        for i in range(self.nb_qpoints):
1✔
228
            if np.allclose(self.qpoints[i].frac_coords, (0, 0, 0)):
1✔
229
                if self.has_eigendisplacements:
1✔
230
                    acoustic_modes_index = []
1✔
231
                    for j in range(self.nb_bands):
1✔
232
                        eig = self.eigendisplacements[j][i]
1✔
233
                        if np.max(np.abs(eig[1:] - eig[:1])) < tol_eigendisplacements:
1✔
234
                            acoustic_modes_index.append(j)
×
235
                    # if acoustic modes are not correctly identified return use
236
                    # the first three modes
237
                    if len(acoustic_modes_index) != 3:
1✔
238
                        acoustic_modes_index = [0, 1, 2]
1✔
239
                    return self.bands[acoustic_modes_index, i]
1✔
240

241
                return self.bands[:3, i]
×
242

243
        return None
×
244

245
    def as_dict(self):
1✔
246
        """
247
        :return: MSONable dict
248
        """
249
        d = {
1✔
250
            "@module": type(self).__module__,
251
            "@class": type(self).__name__,
252
            "lattice_rec": self.lattice_rec.as_dict(),
253
            "qpoints": [],
254
        }
255
        # qpoints are not Kpoint objects dicts but are frac coords.Tthis makes
256
        # the dict smaller and avoids the repetition of the lattice
257
        for q in self.qpoints:
1✔
258
            d["qpoints"].append(q.as_dict()["fcoords"])
1✔
259
        d["bands"] = self.bands.tolist()
1✔
260
        d["labels_dict"] = {}
1✔
261
        for kpoint_letter, kpoint_object in self.labels_dict.items():
1✔
262
            d["labels_dict"][kpoint_letter] = kpoint_object.as_dict()["fcoords"]
1✔
263

264
        # split the eigendisplacements to real and imaginary part for serialization
265
        d["eigendisplacements"] = dict(
1✔
266
            real=np.real(self.eigendisplacements).tolist(),
267
            imag=np.imag(self.eigendisplacements).tolist(),
268
        )
269
        d["nac_eigendisplacements"] = [
1✔
270
            (direction, dict(real=np.real(e).tolist(), imag=np.imag(e).tolist()))
271
            for direction, e in self.nac_eigendisplacements
272
        ]
273
        d["nac_frequencies"] = [(direction, f.tolist()) for direction, f in self.nac_frequencies]
1✔
274

275
        if self.structure:
1✔
276
            d["structure"] = self.structure.as_dict()
1✔
277

278
        return d
1✔
279

280
    @classmethod
1✔
281
    def from_dict(cls, d):
1✔
282
        """
283
        :param d: Dict representation
284
        :return: PhononBandStructure
285
        """
286
        lattice_rec = Lattice(d["lattice_rec"]["matrix"])
×
287
        eigendisplacements = np.array(d["eigendisplacements"]["real"]) + np.array(d["eigendisplacements"]["imag"]) * 1j
×
288
        nac_eigendisplacements = [
×
289
            (direction, np.array(e["real"]) + np.array(e["imag"]) * 1j) for direction, e in d["nac_eigendisplacements"]
290
        ]
291
        nac_frequencies = [(direction, np.array(f)) for direction, f in d["nac_frequencies"]]
×
292
        structure = Structure.from_dict(d["structure"]) if "structure" in d else None
×
293
        return cls(
×
294
            d["qpoints"],
295
            np.array(d["bands"]),
296
            lattice_rec,
297
            nac_frequencies,
298
            eigendisplacements,
299
            nac_eigendisplacements,
300
            d["labels_dict"],
301
            structure=structure,
302
        )
303

304

305
class PhononBandStructureSymmLine(PhononBandStructure):
1✔
306
    r"""
307
    This object stores phonon band structures along selected (symmetry) lines in the
308
    Brillouin zone. We call the different symmetry lines (ex: \\Gamma to Z)
309
    "branches".
310
    """
311

312
    def __init__(
1✔
313
        self,
314
        qpoints,
315
        frequencies,
316
        lattice,
317
        has_nac=False,
318
        eigendisplacements=None,
319
        labels_dict=None,
320
        coords_are_cartesian=False,
321
        structure=None,
322
    ):
323
        """
324
        Args:
325
            qpoints: list of qpoints as numpy arrays, in frac_coords of the
326
                given lattice by default
327
            frequencies: list of phonon frequencies in eV as a numpy array with shape
328
                (3*len(structure), len(qpoints))
329
            lattice: The reciprocal lattice as a pymatgen Lattice object.
330
                Pymatgen uses the physics convention of reciprocal lattice vectors
331
                WITH a 2*pi coefficient
332
            has_nac: specify if the band structure has been produced taking into account
333
                non-analytical corrections at Gamma. If True frequenciens at Gamma from
334
                diffent directions will be stored in naf. Default False.
335
            eigendisplacements: the phonon eigendisplacements associated to the
336
                frequencies in Cartesian coordinates. A numpy array of complex
337
                numbers with shape (3*len(structure), len(qpoints), len(structure), 3).
338
                he First index of the array refers to the band, the second to the index
339
                of the qpoint, the third to the atom in the structure and the fourth
340
                to the Cartesian coordinates.
341
            labels_dict: (dict) of {} this links a qpoint (in frac coords or
342
                Cartesian coordinates depending on the coords) to a label.
343
            coords_are_cartesian: Whether the qpoint coordinates are cartesian.
344
            structure: The crystal structure (as a pymatgen Structure object)
345
                associated with the band structure. This is needed if we
346
                provide projections to the band structure
347
        """
348
        super().__init__(
1✔
349
            qpoints=qpoints,
350
            frequencies=frequencies,
351
            lattice=lattice,
352
            nac_frequencies=None,
353
            eigendisplacements=eigendisplacements,
354
            nac_eigendisplacements=None,
355
            labels_dict=labels_dict,
356
            coords_are_cartesian=coords_are_cartesian,
357
            structure=structure,
358
        )
359
        self._reuse_init(eigendisplacements, frequencies, has_nac, qpoints)
1✔
360

361
    def _reuse_init(self, eigendisplacements, frequencies, has_nac, qpoints):
1✔
362
        self.distance = []
1✔
363
        self.branches = []
1✔
364
        one_group = []
1✔
365
        branches_tmp = []
1✔
366
        # get labels and distance for each qpoint
367
        previous_qpoint = self.qpoints[0]
1✔
368
        previous_distance = 0.0
1✔
369
        previous_label = self.qpoints[0].label
1✔
370
        for i in range(self.nb_qpoints):
1✔
371
            label = self.qpoints[i].label
1✔
372
            if label is not None and previous_label is not None:
1✔
373
                self.distance.append(previous_distance)
1✔
374
            else:
375
                self.distance.append(
1✔
376
                    np.linalg.norm(self.qpoints[i].cart_coords - previous_qpoint.cart_coords) + previous_distance
377
                )
378
            previous_qpoint = self.qpoints[i]
1✔
379
            previous_distance = self.distance[i]
1✔
380
            if label:
1✔
381
                if previous_label:
1✔
382
                    if len(one_group) != 0:
1✔
383
                        branches_tmp.append(one_group)
1✔
384
                    one_group = []
1✔
385
            previous_label = label
1✔
386
            one_group.append(i)
1✔
387
        if len(one_group) != 0:
1✔
388
            branches_tmp.append(one_group)
1✔
389
        for b in branches_tmp:
1✔
390
            self.branches.append(
1✔
391
                {
392
                    "start_index": b[0],
393
                    "end_index": b[-1],
394
                    "name": str(self.qpoints[b[0]].label) + "-" + str(self.qpoints[b[-1]].label),
395
                }
396
            )
397
        # extract the frequencies with non-analytical contribution at gamma
398
        if has_nac:
1✔
399
            naf = []
1✔
400
            nac_eigendisplacements = []
1✔
401
            for i in range(self.nb_qpoints):
1✔
402
                # get directions with nac irrespectively of the label_dict. NB: with labels
403
                # the gamma point is expected to appear twice consecutively.
404
                if np.allclose(qpoints[i], (0, 0, 0)):
1✔
405
                    if i > 0 and not np.allclose(qpoints[i - 1], (0, 0, 0)):
1✔
406
                        q_dir = self.qpoints[i - 1]
1✔
407
                        direction = q_dir.frac_coords / np.linalg.norm(q_dir.frac_coords)
1✔
408
                        naf.append((direction, frequencies[:, i]))
1✔
409
                        if self.has_eigendisplacements:
1✔
410
                            nac_eigendisplacements.append((direction, eigendisplacements[:, i]))
1✔
411
                    if i < len(qpoints) - 1 and not np.allclose(qpoints[i + 1], (0, 0, 0)):
1✔
412
                        q_dir = self.qpoints[i + 1]
1✔
413
                        direction = q_dir.frac_coords / np.linalg.norm(q_dir.frac_coords)
1✔
414
                        naf.append((direction, frequencies[:, i]))
1✔
415
                        if self.has_eigendisplacements:
1✔
416
                            nac_eigendisplacements.append((direction, eigendisplacements[:, i]))
1✔
417

418
            self.nac_frequencies = np.array(naf, dtype=object)
1✔
419
            self.nac_eigendisplacements = np.array(nac_eigendisplacements, dtype=object)
1✔
420

421
    def get_equivalent_qpoints(self, index):
1✔
422
        """
423
        Returns the list of qpoint indices equivalent (meaning they are the
424
        same frac coords) to the given one.
425

426
        Args:
427
            index: the qpoint index
428

429
        Returns:
430
            a list of equivalent indices
431

432
        TODO: now it uses the label we might want to use coordinates instead
433
        (in case there was a mislabel)
434
        """
435
        # if the qpoint has no label it can"t have a repetition along the band
436
        # structure line object
437

438
        if self.qpoints[index].label is None:
1✔
439
            return [index]
1✔
440

441
        list_index_qpoints = []
×
442
        for i in range(self.nb_qpoints):
×
443
            if self.qpoints[i].label == self.qpoints[index].label:
×
444
                list_index_qpoints.append(i)
×
445

446
        return list_index_qpoints
×
447

448
    def get_branch(self, index):
1✔
449
        r"""
450
        Returns in what branch(es) is the qpoint. There can be several
451
        branches.
452

453
        Args:
454
            index: the qpoint index
455

456
        Returns:
457
            A list of dictionaries [{"name","start_index","end_index","index"}]
458
            indicating all branches in which the qpoint is. It takes into
459
            account the fact that one qpoint (e.g., \\Gamma) can be in several
460
            branches
461
        """
462
        to_return = []
1✔
463
        for i in self.get_equivalent_qpoints(index):
1✔
464
            for b in self.branches:
1✔
465
                if b["start_index"] <= i <= b["end_index"]:
1✔
466
                    to_return.append(
1✔
467
                        {
468
                            "name": b["name"],
469
                            "start_index": b["start_index"],
470
                            "end_index": b["end_index"],
471
                            "index": i,
472
                        }
473
                    )
474
        return to_return
1✔
475

476
    def write_phononwebsite(self, filename):
1✔
477
        """
478
        Write a json file for the phononwebsite:
479
        http://henriquemiranda.github.io/phononwebsite
480
        """
481
        import json
1✔
482

483
        with open(filename, "w") as f:
1✔
484
            json.dump(self.as_phononwebsite(), f)
1✔
485

486
    def as_phononwebsite(self):
1✔
487
        """
488
        Return a dictionary with the phononwebsite format:
489
        http://henriquemiranda.github.io/phononwebsite
490
        """
491
        d = {}
1✔
492

493
        # define the lattice
494
        d["lattice"] = self.structure.lattice._matrix.tolist()
1✔
495

496
        # define atoms
497
        atom_pos_car = []
1✔
498
        atom_pos_red = []
1✔
499
        atom_types = []
1✔
500
        for site in self.structure.sites:
1✔
501
            atom_pos_car.append(site.coords.tolist())
1✔
502
            atom_pos_red.append(site.frac_coords.tolist())
1✔
503
            atom_types.append(site.species_string)
1✔
504

505
        # default for now
506
        d["repetitions"] = get_reasonable_repetitions(len(atom_pos_car))
1✔
507

508
        d["natoms"] = len(atom_pos_car)
1✔
509
        d["atom_pos_car"] = atom_pos_car
1✔
510
        d["atom_pos_red"] = atom_pos_red
1✔
511
        d["atom_types"] = atom_types
1✔
512
        d["atom_numbers"] = self.structure.atomic_numbers
1✔
513
        d["formula"] = self.structure.formula
1✔
514
        d["name"] = self.structure.formula
1✔
515

516
        # get qpoints
517
        qpoints = []
1✔
518
        for q in self.qpoints:
1✔
519
            qpoints.append(list(q.frac_coords))
1✔
520
        d["qpoints"] = qpoints
1✔
521

522
        # get labels
523
        hsq_dict = {}
1✔
524
        for nq, q in enumerate(self.qpoints):
1✔
525
            if q.label is not None:
1✔
526
                hsq_dict[nq] = q.label
1✔
527

528
        # get distances
529
        dist = 0
1✔
530
        nqstart = 0
1✔
531
        distances = [dist]
1✔
532
        line_breaks = []
1✔
533
        for nq in range(1, len(qpoints)):
1✔
534
            q1 = np.array(qpoints[nq])
1✔
535
            q2 = np.array(qpoints[nq - 1])
1✔
536
            # detect jumps
537
            if (nq in hsq_dict) and (nq - 1 in hsq_dict):
1✔
538
                if hsq_dict[nq] != hsq_dict[nq - 1]:
1✔
539
                    hsq_dict[nq - 1] += "|" + hsq_dict[nq]
1✔
540
                del hsq_dict[nq]
1✔
541
                line_breaks.append((nqstart, nq))
1✔
542
                nqstart = nq
1✔
543
            else:
544
                dist += np.linalg.norm(q1 - q2)
1✔
545
            distances.append(dist)
1✔
546
        line_breaks.append((nqstart, len(qpoints)))
1✔
547
        d["distances"] = distances
1✔
548
        d["line_breaks"] = line_breaks
1✔
549
        d["highsym_qpts"] = list(hsq_dict.items())
1✔
550

551
        # eigenvalues
552
        thz2cm1 = 33.35641
1✔
553
        bands = self.bands.copy() * thz2cm1
1✔
554
        d["eigenvalues"] = bands.T.tolist()
1✔
555

556
        # eigenvectors
557
        eigenvectors = self.eigendisplacements.copy()
1✔
558
        eigenvectors /= np.linalg.norm(eigenvectors[0, 0])
1✔
559
        eigenvectors = eigenvectors.swapaxes(0, 1)
1✔
560
        eigenvectors = np.array([eigenvectors.real, eigenvectors.imag])
1✔
561
        eigenvectors = np.rollaxis(eigenvectors, 0, 5)
1✔
562
        d["vectors"] = eigenvectors.tolist()
1✔
563

564
        return d
1✔
565

566
    def band_reorder(self):
1✔
567
        """
568
        Re-order the eigenvalues according to the similarity of the eigenvectors
569
        """
570
        eiv = self.eigendisplacements
×
571
        eig = self.bands
×
572

573
        nphonons, nqpoints = self.bands.shape
×
574
        order = np.zeros([nqpoints, nphonons], dtype=int)
×
575
        order[0] = np.array(range(nphonons))
×
576

577
        # get the atomic masses
578
        atomic_masses = [site.specie.atomic_mass for site in self.structure.sites]
×
579

580
        # get order
581
        for nq in range(1, nqpoints):
×
582
            old_eiv = eigenvectors_from_displacements(eiv[:, nq - 1], atomic_masses)
×
583
            new_eiv = eigenvectors_from_displacements(eiv[:, nq], atomic_masses)
×
584
            order[nq] = estimate_band_connection(
×
585
                old_eiv.reshape([nphonons, nphonons]).T,
586
                new_eiv.reshape([nphonons, nphonons]).T,
587
                order[nq - 1],
588
            )
589

590
        # reorder
591
        for nq in range(1, nqpoints):
×
592
            eivq = eiv[:, nq]
×
593
            eigq = eig[:, nq]
×
594
            eiv[:, nq] = eivq[order[nq]]
×
595
            eig[:, nq] = eigq[order[nq]]
×
596

597
    def as_dict(self):
1✔
598
        """
599
        Returns: MSONable dict
600
        """
601
        d = super().as_dict()
1✔
602
        # remove nac_frequencies and nac_eigendisplacements as they are reconstructed
603
        # in the __init__ when the dict is deserialized
604
        nac_frequencies = d.pop("nac_frequencies")
1✔
605
        d.pop("nac_eigendisplacements")
1✔
606
        d["has_nac"] = len(nac_frequencies) > 0
1✔
607
        return d
1✔
608

609
    @classmethod
1✔
610
    def from_dict(cls, d):
1✔
611
        """
612
        Args:
613
            d: Dict representation
614

615
        Returns: PhononBandStructureSummLine
616
        """
617
        lattice_rec = Lattice(d["lattice_rec"]["matrix"])
1✔
618
        eigendisplacements = np.array(d["eigendisplacements"]["real"]) + np.array(d["eigendisplacements"]["imag"]) * 1j
1✔
619
        structure = Structure.from_dict(d["structure"]) if "structure" in d else None
1✔
620
        return cls(
1✔
621
            d["qpoints"],
622
            np.array(d["bands"]),
623
            lattice_rec,
624
            d["has_nac"],
625
            eigendisplacements,
626
            d["labels_dict"],
627
            structure=structure,
628
        )
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