File: lattice.py

package info (click to toggle)
python-dynasor 2.1-2
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 22,008 kB
  • sloc: python: 5,263; sh: 20; makefile: 3
file content (136 lines) | stat: -rw-r--r-- 4,689 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
import warnings
import numpy as np
from numpy.typing import NDArray


from dynasor.qpoints.tools import get_P_matrix, get_commensurate_lattice_points, find_on_line


class Lattice:

    def __init__(self, primitive: NDArray, supercell: NDArray):
        """Representation of a crystal supercell

        The supercell S is given by the primitive cell p and a repetition
        matrix P such that:

            dot(P, p) = S

        In this convention the cell vectors are row vectors of p and S as in
        ASE. An inverse cell is defined as:

            c_inv = inv(c).T

        and the reciprocal cell is defined as:

            c_rec = 2*pi*inv(c).T

        Notice that the inverse cell here is defined with the tranpose so that
        the lattic vectors of the inverse/reciprocal lattice are also row
        vectors. The above also implies:

            dot(P.T, S_inv) = p_inv

        The inverse supercell S_inv defines a new lattice in reciprocal space.
        Those inverse lattice points which resides inside the inverse primitive
        cell p_inv are called commensurate lattice points. These are typically
        the only points of interest in MD simulations from a crystallographic
        and lattice dynamics point of view.

        The convention taken here is that the reciprocal cell carries the 2pi
        factor onto the cartesian q-points. This is consistent with e.g.
        Kittel. The reduced coordinates are always with respect to the
        reciprocal primitive cell.

        Parameters
        ----------
        primitive
            cell metric of the primitive cell with lattice vectors as rows.
        supercell
            cell metric of the supercell with lattice vectors as rows
        """

        self._primitive = np.array(primitive)
        self._supercell = np.array(supercell)

        self._P = get_P_matrix(self.primitive, self.supercell)

        # As stated in the doc the P matrix relating the inverse cells are just P.T
        com = get_commensurate_lattice_points(self.P.T)
        self._qpoints = com @ self.reciprocal_supercell

    @property
    def primitive(self):
        """Returns the primitive cell with lattice vectors as rows"""
        return self._primitive

    @property
    def supercell(self):
        """Returns the supercell with lattice vectors as rows"""
        return self._supercell

    @property
    def reciprocal_primitive(self):
        """Returns inv(primitive).T so that the rows are the inverse lattice vectors"""
        return 2*np.pi * np.linalg.inv(self.primitive.T)  # inverse lattice as rows

    @property
    def reciprocal_supercell(self):
        """Returns inv(super).T so that the rows are the inverse lattice vectors"""
        return 2*np.pi * np.linalg.inv(self.supercell.T)  # reciprocal lattice as rows

    @property
    def P(self):
        """The P-matrix for this system, P @ primitive = supercell"""
        return self._P

    def __repr__(self):
        rep = (f'{self.__class__.__name__}('
               f'primitive={self.primitive.tolist()}, '
               f'supercell={self.supercell.tolist()})')
        return rep

    @property
    def qpoints(self):
        """Cartesian commensurate q-points"""
        return self._qpoints

    def __len__(self):
        return len(self._qpoints)

    def reduced_to_cartesian(self, qpoints):
        """Convert from reduced to cartesian coordinates"""
        return qpoints @ self.reciprocal_primitive

    def cartesian_to_reduced(self, qpoints):
        """Convert from Cartesian to reduced coordinates."""
        return np.linalg.solve(self.reciprocal_primitive.T, qpoints.T).T

    def make_path(self, start, stop):
        """Takes qpoints in reduced coordinates and returns all points in between

        Parameters
        ----------
        start
            coordinate of starting point in reduced inverse coordinates.
            e.g. a zone mode is given as (0.5, 0, 0), (0,0,0) == (1,0,0) etc.
        stop
            stop position

        Returns
        -------
        qpoints
            coordinates of commensurate points along path in cartesian reciprocals
        dists
            fractional distance along path
        """
        start = np.array(start)
        stop = np.array(stop)
        fracs = find_on_line(start, stop, self.P.T)
        if not len(fracs):
            warnings.warn('No q-points along path!')
            return np.zeros(shape=(0, 3)), np.zeros(shape=(0,))
        points = np.array([start + float(f) * (stop - start) for f in fracs])
        qpoints = self.reduced_to_cartesian(points)
        dists = np.array([float(f) for f in fracs])
        return qpoints, dists