Skip to content

Lattice

Lattice

A lattice is specified by it's lattice vectors. This class can then be used to create PeriodicStructure instances which are filled with a given motif. The usage flow for this class is:

1) Define your lattice by specifying the lattice vectors and instantiating this class 2) Define a motif of sites, which is a dictionary mapping each site class to the basis vectors that point to the site locations 3) Use build_from to generate a periodic structure by repeating the unit cell defined by this lattice in every direction.

Attributes

np.ndarray

The lattice vectors defining the unit cell of this lattice.

Source code in pylattica/core/lattice.py
 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
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
class Lattice:
    """A lattice is specified by it's lattice vectors. This class can then
    be used to create PeriodicStructure instances which are filled with
    a given motif. The usage flow for this class is:

    1) Define your lattice by specifying the lattice vectors and instantiating this class
    2) Define a motif of sites, which is a dictionary mapping each site class to the
    basis vectors that point to the site locations
    3) Use build_from to generate a periodic structure by repeating the unit cell
    defined by this lattice in every direction.

    Attributes
    ----------

    vecs : np.ndarray
        The lattice vectors defining the unit cell of this lattice.
    """

    @classmethod
    def from_dict(cls, d):
        return cls(d["vectors"], d["periodic"])

    def __init__(self, vecs: List[Tuple[float]], periodic=True):
        """Initializes a lattice with the vectors defining its unit cell provided.
        The dimension of the lattice is inferred from the dimension of the lattice vectors.

        Parameters
        ----------
        vecs : List[Tuple[float]]
            A list of vectors establishing the unit cell of the lattice. Any dimension is accepted.
        """

        # This set up of matrix and inversion matrix is taken from pymatgen
        # I would prefer to use the pymatgen lattice directly, but it is hardcoded
        # to utilize 3 dimensions - i.e. no game of life, 2D Ising, etc

        self.vecs = np.array(vecs)

        if not isinstance(periodic, tuple) and not isinstance(periodic, list):
            self.periodic = tuple(periodic for _ in vecs)
        else:
            self.periodic = tuple(periodic)

        self._periodic_bool = np.array(periodic, dtype=int)

        dim = int(math.sqrt(len(np.array(self.vecs).flatten())))
        mat = np.array(self.vecs, dtype=np.float64).reshape((dim, dim))
        mat.setflags(write=False)

        self._matrix: np.ndarray = mat
        self._inv_matrix: np.ndarray | None = None

        self.dim = len(vecs[0])
        self.vec_lengths = [np.linalg.norm(np.array(vec)) for vec in vecs]
        assert (
            len(list(set(len(v) for v in vecs))) == 1
        ), "Lattice instantiated with vectors of unequal dimension"

    def as_dict(self):
        return {"vectors": self.vecs.tolist(), "periodic": self.periodic}

    @property
    def matrix(self) -> np.ndarray:
        """Copy of matrix representing the Lattice. (Taken from pymatgen)"""
        return self._matrix

    @property
    def inv_matrix(self) -> np.ndarray:
        """Inverse of lattice matrix. (Taken from pymatgen)"""
        if self._inv_matrix is None:
            self._inv_matrix = np.linalg.inv(self._matrix)
            self._inv_matrix.setflags(write=False)
        return self._inv_matrix

    def get_cartesian_coords(self, fractional_coords: ArrayLike) -> np.ndarray:
        """Returns the Cartesian coordinates given fractional coordinates. (taken from pymatgen)

        Parameters
        ----------
        fractional_coords : ArrayLike
            Fractional coords

        Returns
        -------
        np.ndarray
            The equivalent Cartesian coordinates
        """
        return np.dot(fractional_coords, self._matrix)

    def get_fractional_coords(self, cart_coords: ArrayLike) -> np.ndarray:
        """Returns the fractional coordinates given Cartesian coordinates. (taken from pymatgen)

        Parameters
        ----------
        cart_coords : ArrayLike
            Cartesian coords.

        Returns
        -------
        np.ndarray
            Fractional coordinates.
        """
        return np.dot(cart_coords, self.inv_matrix)

    def get_periodized_cartesian_coords(self, cart_coords: ArrayLike) -> np.ndarray:
        """Given a set of Cartesian coordinates, returns the periodized version
        within this lattice.

        Parameters
        ----------
        cart_coords : ArrayLike
            The coordinates to periodize

        Returns
        -------
        np.ndarray
            The periodized coordinates
        """
        frac = self.get_fractional_coords(cart_coords)
        return self.get_cartesian_coords(periodize(frac, self.periodic))

    def get_scaled_lattice(self, num_cells: ArrayLike) -> Lattice:
        """Return a copy of this lattice with a unit cell scaled to the dimensions provided.
        If the num_cells parameter was [3, 3, 3], this would return a lattice 3 times larger
        in the direction of each basis vector.

        Parameters
        ----------
        num_cells : ArrayLike
            The extent of scaling along each basis direciton

        Returns
        -------
        Lattice
            The new, scaled Lattice instance.
        """
        return Lattice(
            np.array([v * amt for amt, v in zip(num_cells, self.vecs)]), self.periodic
        )

    def cartesian_periodic_distance(self, loc1: ArrayLike, loc2: ArrayLike) -> float:
        """Returns the Cartesian distance between two coordinates after periodizing them.

        Parameters
        ----------
        loc1 : ArrayLike
            The first location.
        loc2 : ArrayLike
            The second location.

        Returns
        -------
        float
            The distance between the locations.
        """
        return pbc_diff_cart(
            loc1,
            loc2,
            self,
        )

__init__(vecs, periodic=True)

Initializes a lattice with the vectors defining its unit cell provided. The dimension of the lattice is inferred from the dimension of the lattice vectors.

Parameters
List[Tuple[float]]

A list of vectors establishing the unit cell of the lattice. Any dimension is accepted.

Source code in pylattica/core/lattice.py
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
137
138
139
140
141
142
143
def __init__(self, vecs: List[Tuple[float]], periodic=True):
    """Initializes a lattice with the vectors defining its unit cell provided.
    The dimension of the lattice is inferred from the dimension of the lattice vectors.

    Parameters
    ----------
    vecs : List[Tuple[float]]
        A list of vectors establishing the unit cell of the lattice. Any dimension is accepted.
    """

    # This set up of matrix and inversion matrix is taken from pymatgen
    # I would prefer to use the pymatgen lattice directly, but it is hardcoded
    # to utilize 3 dimensions - i.e. no game of life, 2D Ising, etc

    self.vecs = np.array(vecs)

    if not isinstance(periodic, tuple) and not isinstance(periodic, list):
        self.periodic = tuple(periodic for _ in vecs)
    else:
        self.periodic = tuple(periodic)

    self._periodic_bool = np.array(periodic, dtype=int)

    dim = int(math.sqrt(len(np.array(self.vecs).flatten())))
    mat = np.array(self.vecs, dtype=np.float64).reshape((dim, dim))
    mat.setflags(write=False)

    self._matrix: np.ndarray = mat
    self._inv_matrix: np.ndarray | None = None

    self.dim = len(vecs[0])
    self.vec_lengths = [np.linalg.norm(np.array(vec)) for vec in vecs]
    assert (
        len(list(set(len(v) for v in vecs))) == 1
    ), "Lattice instantiated with vectors of unequal dimension"

cartesian_periodic_distance(loc1, loc2)

Returns the Cartesian distance between two coordinates after periodizing them.

Parameters
ArrayLike

The first location.

ArrayLike

The second location.

Returns

float The distance between the locations.

Source code in pylattica/core/lattice.py
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
def cartesian_periodic_distance(self, loc1: ArrayLike, loc2: ArrayLike) -> float:
    """Returns the Cartesian distance between two coordinates after periodizing them.

    Parameters
    ----------
    loc1 : ArrayLike
        The first location.
    loc2 : ArrayLike
        The second location.

    Returns
    -------
    float
        The distance between the locations.
    """
    return pbc_diff_cart(
        loc1,
        loc2,
        self,
    )

get_cartesian_coords(fractional_coords)

Returns the Cartesian coordinates given fractional coordinates. (taken from pymatgen)

Parameters
ArrayLike

Fractional coords

Returns

np.ndarray The equivalent Cartesian coordinates

Source code in pylattica/core/lattice.py
161
162
163
164
165
166
167
168
169
170
171
172
173
174
def get_cartesian_coords(self, fractional_coords: ArrayLike) -> np.ndarray:
    """Returns the Cartesian coordinates given fractional coordinates. (taken from pymatgen)

    Parameters
    ----------
    fractional_coords : ArrayLike
        Fractional coords

    Returns
    -------
    np.ndarray
        The equivalent Cartesian coordinates
    """
    return np.dot(fractional_coords, self._matrix)

get_fractional_coords(cart_coords)

Returns the fractional coordinates given Cartesian coordinates. (taken from pymatgen)

Parameters
ArrayLike

Cartesian coords.

Returns

np.ndarray Fractional coordinates.

Source code in pylattica/core/lattice.py
176
177
178
179
180
181
182
183
184
185
186
187
188
189
def get_fractional_coords(self, cart_coords: ArrayLike) -> np.ndarray:
    """Returns the fractional coordinates given Cartesian coordinates. (taken from pymatgen)

    Parameters
    ----------
    cart_coords : ArrayLike
        Cartesian coords.

    Returns
    -------
    np.ndarray
        Fractional coordinates.
    """
    return np.dot(cart_coords, self.inv_matrix)

get_periodized_cartesian_coords(cart_coords)

Given a set of Cartesian coordinates, returns the periodized version within this lattice.

Parameters
ArrayLike

The coordinates to periodize

Returns

np.ndarray The periodized coordinates

Source code in pylattica/core/lattice.py
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
def get_periodized_cartesian_coords(self, cart_coords: ArrayLike) -> np.ndarray:
    """Given a set of Cartesian coordinates, returns the periodized version
    within this lattice.

    Parameters
    ----------
    cart_coords : ArrayLike
        The coordinates to periodize

    Returns
    -------
    np.ndarray
        The periodized coordinates
    """
    frac = self.get_fractional_coords(cart_coords)
    return self.get_cartesian_coords(periodize(frac, self.periodic))

get_scaled_lattice(num_cells)

Return a copy of this lattice with a unit cell scaled to the dimensions provided. If the num_cells parameter was [3, 3, 3], this would return a lattice 3 times larger in the direction of each basis vector.

Parameters
ArrayLike

The extent of scaling along each basis direciton

Returns

Lattice The new, scaled Lattice instance.

Source code in pylattica/core/lattice.py
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
def get_scaled_lattice(self, num_cells: ArrayLike) -> Lattice:
    """Return a copy of this lattice with a unit cell scaled to the dimensions provided.
    If the num_cells parameter was [3, 3, 3], this would return a lattice 3 times larger
    in the direction of each basis vector.

    Parameters
    ----------
    num_cells : ArrayLike
        The extent of scaling along each basis direciton

    Returns
    -------
    Lattice
        The new, scaled Lattice instance.
    """
    return Lattice(
        np.array([v * amt for amt, v in zip(num_cells, self.vecs)]), self.periodic
    )

inv_matrix() property

Inverse of lattice matrix. (Taken from pymatgen)

Source code in pylattica/core/lattice.py
153
154
155
156
157
158
159
@property
def inv_matrix(self) -> np.ndarray:
    """Inverse of lattice matrix. (Taken from pymatgen)"""
    if self._inv_matrix is None:
        self._inv_matrix = np.linalg.inv(self._matrix)
        self._inv_matrix.setflags(write=False)
    return self._inv_matrix

matrix() property

Copy of matrix representing the Lattice. (Taken from pymatgen)

Source code in pylattica/core/lattice.py
148
149
150
151
@property
def matrix(self) -> np.ndarray:
    """Copy of matrix representing the Lattice. (Taken from pymatgen)"""
    return self._matrix

pbc_diff_cart(cart_coords1, cart_coords2, lattice)

Returns the Cartesian distance between two coordinates taking into account periodic boundary conditions. (from pymatgen)

Parameters

ArrayLike

First set of Cartesian coordinates

ArrayLike

Second set of Cartesian coordinates

Lattice

The Lattice within which the distance should be calculated

Returns

type description

Source code in pylattica/core/lattice.py
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
def pbc_diff_cart(cart_coords1: ArrayLike, cart_coords2: ArrayLike, lattice: Lattice):
    """Returns the Cartesian distance between two coordinates taking into
    account periodic boundary conditions. (from pymatgen)

    Parameters
    ----------
    cart_coords1 : ArrayLike
        First set of Cartesian coordinates
    cart_coords2 : ArrayLike
        Second set of Cartesian coordinates
    lattice : Lattice
        The Lattice within which the distance should be calculated

    Returns
    -------
    _type_
        _description_
    """
    fcoords1 = lattice.get_fractional_coords(cart_coords1)
    fcoords2 = lattice.get_fractional_coords(cart_coords2)
    frac_dist = pbc_diff_frac_vec(fcoords1, fcoords2, lattice.periodic)
    return np.round(
        np.linalg.norm(lattice.get_cartesian_coords(frac_dist)), OFFSET_PRECISION
    )

pbc_diff_frac_vec(fcoords1, fcoords2, periodic)

Returns the 'fractional distance' between two coordinates taking into account periodic boundary conditions. (from pymatgen)

Parameters

ArrayLike

First set of fractional coordinates. e.g., [0.5, 0.6, 0.7] or [[1.1, 1.2, 4.3], [0.5, 0.6, 0.7]]. It can be a single coord or any array of coords.

ArrayLike

Second set of fractional coordinates.

ArrayLike

a tuple defining the periodic boundary conditions along the three axis of the lattice.

Returns

float Fractional distance. Each coordinate must have the operty that abs(a) <= 0.5. Examples: pbc_diff([0.1, 0.1, 0.1], [0.3, 0.5, 0.9]) = [-0.2, -0.4, 0.2] pbc_diff([0.9, 0.1, 1.01], [0.3, 0.5, 0.9]) = [-0.4, -0.4, 0.11]

Source code in pylattica/core/lattice.py
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
def pbc_diff_frac_vec(fcoords1: ArrayLike, fcoords2: ArrayLike, periodic):
    """Returns the 'fractional distance' between two coordinates taking into
    account periodic boundary conditions. (from pymatgen)

    Parameters
    ----------
    fcoords1 : ArrayLike
        First set of fractional coordinates. e.g., [0.5, 0.6,
        0.7] or [[1.1, 1.2, 4.3], [0.5, 0.6, 0.7]]. It can be a single
        coord or any array of coords.
    fcoords2 : ArrayLike
        Second set of fractional coordinates.
    periodic : ArrayLike
        a tuple defining the periodic boundary conditions along the three
        axis of the lattice.

    Returns
    -------
    float
        Fractional distance. Each coordinate must have the  operty that
        abs(a) <= 0.5. Examples:
        pbc_diff([0.1, 0.1, 0.1], [0.3, 0.5, 0.9]) = [-0.2, -0.4, 0.2]
        pbc_diff([0.9, 0.1, 1.01], [0.3, 0.5, 0.9]) = [-0.4, -0.4, 0.11]
    """
    fdist = np.subtract(fcoords1, fcoords2)
    return fdist - np.round(fdist) * periodic

periodize(frac_coords, periodic=True)

Moves fractional coordinates into the unit cell.

Parameters

ArrayLike

The fractional coordinates to periodize.

bool, optional

Either a single boolean or a tuple of booleans indicating the periodicity of each dimension, by default True

Returns

ArrayLike The periodized coordinates

Source code in pylattica/core/lattice.py
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
def periodize(frac_coords, periodic: Union[Tuple[bool], bool] = True):
    """Moves fractional coordinates into the unit cell.

    Parameters
    ----------
    frac_coords : ArrayLike
        The fractional coordinates to periodize.
    periodic : bool, optional
        Either a single boolean or a tuple of booleans indicating the periodicity
        of each dimension, by default True

    Returns
    -------
    ArrayLike
        The periodized coordinates
    """
    if not isinstance(periodic, tuple):
        periodic = [periodic for _ in frac_coords]

    return frac_coords - np.floor(frac_coords) * np.array(periodic, dtype=int)