Skip to content

Models

This page provides the documentation for the data models used by the Application, as well as relevant factories.

TiBi.models.BandStructure dataclass

A UnitCell attribute containing a system's band structure.

Attributes:

Name Type Description
path list[NDArray[float64]]

A list of point coordinates along which the bands are calculated.

special_points list[NDArray[float64]]

A list of high-symmetry point coordinates used for the path.

eigenvalues list[NDArray[float64]]

A list of arrays, where each array contains eigenvalues (energies) corresponding to each point on the path.

eigenvectors list[NDArray[float64]]

A list of square 2D arrays, where each array contains the eigenvectors corresponding to each point on the path. The eigenvectors are the columns of the 2D arrays.

Methods:

Name Description
clear

Reset the BandStructure to the initial state.

reset_bands

Reset the BandStructure by clearing the path, eigenvalues, and eigenvectors, but keeping the special points.

add_point

Add a point to the special points path. Reset all other fields.

remove_point

Remove the last point from the special points path. Reset all other fields.

Source code in TiBi/models/band_structure.py
 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
@dataclass
class BandStructure:
    """
    A `UnitCell` attribute containing a system's band structure.

    Attributes
    ----------
    path : list[NDArray[np.float64]]
        A list of point coordinates along which the bands are calculated.
    special_points : list[NDArray[np.float64]]
        A list of high-symmetry point coordinates used for the path.
    eigenvalues : list[NDArray[np.float64]]
        A list of arrays, where each array contains eigenvalues (energies)
        corresponding to each point on the path.
    eigenvectors : list[NDArray[np.float64]]
        A list of square 2D arrays, where each array contains the eigenvectors
        corresponding to each point on the path. The
        eigenvectors are the columns of the 2D arrays.

    Methods
    -------
    clear()
        Reset the `BandStructure` to the initial state.
    reset_bands()
        Reset the `BandStructure` by clearing the path, eigenvalues, and
        eigenvectors, but keeping the special points.
    add_point(point: NDArray[np.float64])
        Add a point to the special points path. Reset all other fields.
    remove_point()
        Remove the last point from the special points path. Reset all other
        fields.
    """

    path: list[NDArray[np.float64]] = field(default_factory=list)
    special_points: list[NDArray[np.float64]] = field(default_factory=list)
    eigenvalues: list[NDArray[np.float64]] = field(default_factory=list)
    eigenvectors: list[NDArray[np.float64]] = field(default_factory=list)

    def clear(self):
        """Reset the `BandStructure` to the initial state."""
        self.special_points.clear()
        self.reset_bands()

    def reset_bands(self):
        """
        Reset the `BandStructure` by clearing the path, eigenvalues, and
        eigenvectors, but keeping the special points.
        """
        self.path.clear()
        self.eigenvalues.clear()
        self.eigenvectors.clear()

    def add_point(self, point: NDArray[np.float64]):
        """
        Add a point to the special points path. Reset all other fields.

        Parameters
        ----------
        point : NDArray[np.float64]
            The point to be added to the special points path.
        """
        self.reset_bands()
        self.special_points.append(point)

    def remove_point(self):
        """
        Remove the last point from the special points path. Reset all other
        fields.
        """
        if self.special_points:
            self.special_points.pop(-1)
            self.reset_bands()

add_point(point)

Add a point to the special points path. Reset all other fields.

Parameters:

Name Type Description Default
point NDArray[float64]

The point to be added to the special points path.

required
Source code in TiBi/models/band_structure.py
58
59
60
61
62
63
64
65
66
67
68
def add_point(self, point: NDArray[np.float64]):
    """
    Add a point to the special points path. Reset all other fields.

    Parameters
    ----------
    point : NDArray[np.float64]
        The point to be added to the special points path.
    """
    self.reset_bands()
    self.special_points.append(point)

clear()

Reset the BandStructure to the initial state.

Source code in TiBi/models/band_structure.py
44
45
46
47
def clear(self):
    """Reset the `BandStructure` to the initial state."""
    self.special_points.clear()
    self.reset_bands()

remove_point()

Remove the last point from the special points path. Reset all other fields.

Source code in TiBi/models/band_structure.py
70
71
72
73
74
75
76
77
def remove_point(self):
    """
    Remove the last point from the special points path. Reset all other
    fields.
    """
    if self.special_points:
        self.special_points.pop(-1)
        self.reset_bands()

reset_bands()

Reset the BandStructure by clearing the path, eigenvalues, and eigenvectors, but keeping the special points.

Source code in TiBi/models/band_structure.py
49
50
51
52
53
54
55
56
def reset_bands(self):
    """
    Reset the `BandStructure` by clearing the path, eigenvalues, and
    eigenvectors, but keeping the special points.
    """
    self.path.clear()
    self.eigenvalues.clear()
    self.eigenvectors.clear()

TiBi.models.BasisVector dataclass

A basis vector in 3D space for a crystalline unit cell.

Attributes:

Name Type Description
x float

x-component in Cartesian coordinates

y float

y-component in Cartesian coordinates

z float

z-component in Cartesian coordinates

is_periodic bool

Flag denoting whether the crystal repeats in this direction

Methods:

Name Description
as_array

Convert the BasisVector to a NumPy array with 3 elements.

Source code in TiBi/models/basis_vector.py
 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
@dataclass
class BasisVector:
    """
    A basis vector in 3D space for a crystalline unit cell.

    Attributes
    ----------
    x : float
        x-component in Cartesian coordinates
    y : float
        y-component in Cartesian coordinates
    z : float
        z-component in Cartesian coordinates
    is_periodic : bool
        Flag denoting whether the crystal repeats in this direction

    Methods
    -------
    as_array()
        Convert the `BasisVector` to a NumPy array with 3 elements.
    """

    x: float
    y: float
    z: float
    is_periodic: bool = False

    def as_array(self) -> NDArray[np.float64]:
        """
        Convert the `BasisVector` to a NumPy array.

        Returns
        -------
        NDArray[np.float64]
            3D vector as a NumPy array [x, y, z]
        """
        return np.array([self.x, self.y, self.z], dtype=np.float64)

as_array()

Convert the BasisVector to a NumPy array.

Returns:

Type Description
NDArray[float64]

3D vector as a NumPy array [x, y, z]

Source code in TiBi/models/basis_vector.py
33
34
35
36
37
38
39
40
41
42
def as_array(self) -> NDArray[np.float64]:
    """
    Convert the `BasisVector` to a NumPy array.

    Returns
    -------
    NDArray[np.float64]
        3D vector as a NumPy array [x, y, z]
    """
    return np.array([self.x, self.y, self.z], dtype=np.float64)

TiBi.models.BrillouinZoneGrid dataclass

A UnitCell attribute containing a system's computed Brillouin zone grid.

Attributes:

Name Type Description
is_gamma_centered bool

A boolean marking whether the grid is Gamma centered or Monkhorst-Pack

grid_divs tuple[int, int, int]

Number of divisions along each reciprocal basis vector

k_points list[NDArray[float64]]

The coordinates of the grid points.

eigenvalues list[NDArray[float64]]

A list of arrays, where each array contains eigenvalues (energies) corresponding to each grid point.

eigenvectors list[NDArray[float64]]

A list of square 2D arrays, where each array contains the eigenvectors corresponding to each grid point. The eigenvectors are the columns of the 2D arrays.

Methods:

Name Description
clear

Reset the grid to the initial state.

Source code in TiBi/models/bz_grid.py
 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
@dataclass
class BrillouinZoneGrid:
    """
    A `UnitCell` attribute containing a system's computed Brillouin zone grid.

    Attributes
    ----------
    is_gamma_centered : bool
        A boolean marking whether the grid is Gamma centered or Monkhorst-Pack
    grid_divs : tuple[int, int, int]
        Number of divisions along each reciprocal basis vector
    k_points : list[NDArray[np.float64]]
        The coordinates of the grid points.
    eigenvalues : list[NDArray[np.float64]]
        A list of arrays, where each array contains eigenvalues (energies)
        corresponding to each grid point.
    eigenvectors : list[NDArray[np.float64]]
        A list of square 2D arrays, where each array contains the eigenvectors
        corresponding to each grid point. The
        eigenvectors are the columns of the 2D arrays.

    Methods
    -------
    clear()
        Reset the grid to the initial state.
    """

    is_gamma_centered: bool = True
    grid_divs: tuple[int, int, int] = (30, 30, 30)
    k_points: list[NDArray[np.float64]] = field(default_factory=list)
    eigenvalues: list[NDArray[np.float64]] = field(default_factory=list)
    eigenvectors: list[NDArray[np.float64]] = field(default_factory=list)

    def clear(self):
        """
        Reset the grid to the initial state.

        The k points, eigenvalues, and eigenvectors are cleared,
        while the gamma-centered flag and the grid divisions remain unchanged.
        """
        self.k_points.clear()
        self.eigenvalues.clear()
        self.eigenvectors.clear()

clear()

Reset the grid to the initial state.

The k points, eigenvalues, and eigenvectors are cleared, while the gamma-centered flag and the grid divisions remain unchanged.

Source code in TiBi/models/bz_grid.py
39
40
41
42
43
44
45
46
47
48
def clear(self):
    """
    Reset the grid to the initial state.

    The k points, eigenvalues, and eigenvectors are cleared,
    while the gamma-centered flag and the grid divisions remain unchanged.
    """
    self.k_points.clear()
    self.eigenvalues.clear()
    self.eigenvectors.clear()

TiBi.models.Selection

Bases: QObject

Currently selected item.

The item is characterized by a series of uuid.UUID's that can be viewed as a hierarchical address.

Attributes:

Name Type Description
unit_cell UUID | None

ID of the selected UnitCell

site UUID | None

ID of the selected Site

state UUID | None

ID of the selected State

unit_cell_updated Signal

Emitted when a new UnitCell is selected.

site_updated Signal

Emitted when a new Site is selected.

state_updated Signal

Emitted when a new State is selected.

selection_changed Signal

Emitted when the selection changes, in addition to the specific signal.

Methods:

Name Description
set_selection

Update the selection and emit an appropriate signal.

Source code in TiBi/models/selection.py
 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
class Selection(QObject):
    """
    Currently selected item.

    The item is characterized by a series of uuid.UUID's
    that can be viewed as a hierarchical address.

    Attributes
    ----------
    unit_cell : uuid.UUID | None
        ID of the selected `UnitCell`
    site : uuid.UUID | None
        ID of the selected `Site`
    state : uuid.UUID | None
        ID of the selected `State`
    unit_cell_updated : Signal
        Emitted when a new `UnitCell` is selected.
    site_updated : Signal
        Emitted when a new `Site` is selected.
    state_updated : Signal
        Emitted when a new `State` is selected.
    selection_changed : Signal
        Emitted when the selection changes, in addition
        to the specific signal.

    Methods
    -------
    set_selection(uc_id : uuid.UUID | None, site_id : uuid.UUID | None,\
          state_id : uuid.UUID | None)
        Update the selection and emit an appropriate signal.
    """

    unit_cell_updated = Signal()
    site_updated = Signal()
    state_updated = Signal()
    selection_changed = Signal()

    def __init__(self):
        super().__init__()

        self.unit_cell = None
        self.site = None
        self.state = None

    def set_selection(self, uc_id, site_id, state_id):
        """
        Update the selection and emit an appropriate signal.

        Parameters
        ----------
        uc_id : uuid.UUID | None
            New `UnitCell` id.
        site_id : uuid.UUID | None
            New `Site` id.
        state_id : uuid.UUID | None
            New `State` id.
        """
        current_uc = self.unit_cell
        current_site = self.site
        current_state = self.state

        self.unit_cell = uc_id
        self.site = site_id
        self.state = state_id

        if current_uc != uc_id:
            self.unit_cell_updated.emit()
            self.selection_changed.emit()
        elif current_site != site_id:
            self.site_updated.emit()
            self.selection_changed.emit()
        elif current_state != state_id:
            self.state_updated.emit()
            self.selection_changed.emit()

set_selection(uc_id, site_id, state_id)

Update the selection and emit an appropriate signal.

Parameters:

Name Type Description Default
uc_id UUID | None

New UnitCell id.

required
site_id UUID | None

New Site id.

required
state_id UUID | None

New State id.

required
Source code in TiBi/models/selection.py
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
def set_selection(self, uc_id, site_id, state_id):
    """
    Update the selection and emit an appropriate signal.

    Parameters
    ----------
    uc_id : uuid.UUID | None
        New `UnitCell` id.
    site_id : uuid.UUID | None
        New `Site` id.
    state_id : uuid.UUID | None
        New `State` id.
    """
    current_uc = self.unit_cell
    current_site = self.site
    current_state = self.state

    self.unit_cell = uc_id
    self.site = site_id
    self.state = state_id

    if current_uc != uc_id:
        self.unit_cell_updated.emit()
        self.selection_changed.emit()
    elif current_site != site_id:
        self.site_updated.emit()
        self.selection_changed.emit()
    elif current_state != state_id:
        self.state_updated.emit()
        self.selection_changed.emit()

TiBi.models.Site dataclass

A physical site (like an atom) within a UnitCell.

Sites are positioned using fractional coordinates relative to the UnitCell's basis vectors, where each coordinate ranges from 0 to 1. Each Site can contain multiple States.

Attributes:

Name Type Description
name str

Name of the Site (e.g., atom name like "C", "Fe", etc.)

c1, c2, c3 float

Fractional coordinates (0 ≤ c ≤ 1) along the unit cell's BasisVectors

R float

Site radius used for plotting (in arbitrary units)

color tuple[float, float, float, float]

Site RGBA color used for plotting (0-1 range for each channel)

states dict[UUID, State]

Dictionary mapping state UUIDs to State objects

id UUID

Unique identifier for the Site

Source code in TiBi/models/site.py
 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
@dataclass
class Site:
    """
    A physical site (like an atom) within a `UnitCell`.

    `Site`s are positioned using fractional coordinates relative to
    the `UnitCell`'s basis vectors, where each coordinate ranges from 0 to 1.
    Each `Site` can contain multiple `State`s.

    Attributes
    ----------
    name : str
        Name of the `Site` (e.g., atom name like "C", "Fe", etc.)
    c1, c2, c3 : float
        Fractional coordinates (0 ≤ c ≤ 1) along the unit cell's `BasisVector`s
    R : float
        `Site` radius used for plotting (in arbitrary units)
    color : tuple[float, float, float, float]
        `Site` RGBA color used for plotting (0-1 range for each channel)
    states : dict[uuid.UUID, State]
        Dictionary mapping state UUIDs to `State` objects
    id : uuid.UUID
        Unique identifier for the `Site`
    """

    name: str
    c1: float
    c2: float
    c3: float
    R: float
    color: tuple[float, float, float, float]
    states: dict[uuid.UUID, State] = field(default_factory=dict)
    id: uuid.UUID = field(default_factory=uuid.uuid4)

TiBi.models.State dataclass

A quantum state (orbital) within a Site.

Each State has a name and belongs to a Site in the UnitCell. States are the fundamental entities between which hopping can occur.

Attributes:

Name Type Description
name str

Name of the State (e.g., "s", "px", "py", etc.)

id UUID

Unique identifier for the State

Source code in TiBi/models/state.py
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
@dataclass
class State:
    """
    A quantum state (orbital) within a `Site`.

    Each `State` has a `name` and belongs to a `Site` in the `UnitCell`.
    `State`s are the fundamental entities between which hopping can occur.

    Attributes
    ----------
    name : str
        Name of the `State` (e.g., "s", "px", "py", etc.)
    id : uuid.UUID
        Unique identifier for the `State`
    """

    name: str
    id: uuid.UUID = field(default_factory=uuid.uuid4)

TiBi.models.UnitCell dataclass

The funtamental object describing a crystal.

The UnitCell is defined by three BasisVectors and contains Sites and hopping terms between States. Additionally, UnitCell carries the calculated BandStructure and BrillouinZoneGrid objects.

Attributes:

Name Type Description
name str

Name of the unit cell

v1, v2, v3 BasisVector

Basis vectors

sites dict[UUID, Site]

Dictionary mapping site UUIDs to Site objects

hoppings dict[tuple[UUID, UUID], list[tuple[tuple[int, int, int], complex128]]]

Dictionary of hopping terms between states. Keys are pairs of state UUIDs (destination_state_id, source_state_id). Values are lists of (displacement, amplitude) pairs where:

  • displacement is a tuple of three integers (n1,n2,n3) indicating which periodic image of the unit cell is involved (0,0,0 means within the same unit cell)
  • amplitude is a complex number
bandstructure BandStructure

Band structure object for the UnitCell

bz_grid BrillouinZoneGrid

Grid of points in the Brillouin zone

id UUID

Unique identifier for the UnitCell

Methods:

Name Description
volume

Compute the volume of the UnitCell using the scalar triple product.

is_hermitian

Check whether the hoppings are Hermitian.

reciprocal_vectors

Compute the reciprocal lattice vectors for the periodic directions.

reduced_basis

Return a reduced set of periodic BasisVectors using LLL algorithm.

get_states

Extract all States from a UnitCell along with their identifying information.

get_BZ

Compute the Brillouin zone vertices and faces.

get_hamiltonian_function

Generate a function that computes the Hamiltonian matrix for a given k-point.

Source code in TiBi/models/unit_cell.py
 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
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
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
@dataclass
class UnitCell:
    """
    The funtamental object describing a crystal.

    The `UnitCell` is defined by three `BasisVector`s and contains `Site`s
    and hopping terms between `State`s. Additionally, `UnitCell` carries
    the calculated `BandStructure` and `BrillouinZoneGrid` objects.

    Attributes
    ----------
    name : str
        Name of the unit cell
    v1, v2, v3 : BasisVector
        Basis vectors
    sites : dict[uuid.UUID, Site]
        Dictionary mapping site UUIDs to `Site` objects
    hoppings : dict[tuple[uuid.UUID, uuid.UUID], \
        list[tuple[tuple[int, int, int], np.complex128]]]
        Dictionary of hopping terms between states.
        Keys are pairs of state UUIDs (destination_state_id,
        source_state_id).
        Values are lists of (displacement, amplitude) pairs where:

        - displacement is a tuple of three integers (n1,n2,n3) indicating
        which periodic image of the unit cell is involved
        (0,0,0 means within the same unit cell)
        - amplitude is a complex number
    bandstructure : BandStructure
        Band structure object for the `UnitCell`
    bz_grid: BrillouinZoneGrid
        Grid of points in the Brillouin zone
    id : uuid.UUID
        Unique identifier for the `UnitCell`

    Methods
    -------
    volume()
        Compute the volume of the `UnitCell` using the scalar triple
        product.
    is_hermitian()
        Check whether the hoppings are Hermitian.
    reciprocal_vectors()
        Compute the reciprocal lattice vectors for the periodic directions.
    reduced_basis()
        Return a reduced set of periodic `BasisVector`s using LLL
        algorithm.
    get_states()
        Extract all `State`s from a `UnitCell` along with their identifying
        information.
    get_BZ()
        Compute the Brillouin zone vertices and faces.
    get_hamiltonian_function()
        Generate a function that computes the Hamiltonian matrix
        for a given k-point.
    """

    name: str
    v1: BasisVector
    v2: BasisVector
    v3: BasisVector
    sites: dict[uuid.UUID, Site] = field(default_factory=dict)
    hoppings: dict[
        tuple[uuid.UUID, uuid.UUID],
        list[tuple[tuple[int, int, int], np.complex128]],
    ] = field(default_factory=dict)
    bandstructure: BandStructure = field(default_factory=BandStructure)
    bz_grid: BrillouinZoneGrid = field(default_factory=BrillouinZoneGrid)
    id: uuid.UUID = field(default_factory=uuid.uuid4)

    def volume(self) -> np.float64:
        """
        Compute the volume of the `UnitCell` using the scalar triple product.

        Returns
        -------
        np.float64
            Volume of the unit cell in arbitrary units
        """
        a1, a2, a3 = [v.as_array() for v in [self.v1, self.v2, self.v3]]
        return np.abs(np.dot(a1, np.cross(a2, a3)))

    def is_hermitian(self) -> bool:
        """
        Check whether the hoppings are Hermitian.

        For each key (destination_state_id, source_state_id) in the
        hoppings dictionary, check if there is a key
        (source_state_id, destination_state_id). If so, check that the entries
        are related by Hermitian conjugation.

        Returns
        -------
        bool
            `True` if Hermitian; `False` if not.
        """
        hermitian = True

        for key, val in self.hoppings.items():
            s1 = key[0]
            s2 = key[1]

            hop = set(val)
            hop_transpose = set(self.hoppings.get((s2, s1), []))

            hop_neg_conj = set(
                ((-d1, -d2, -d3), np.conj(x)) for ((d1, d2, d3), x) in hop
            )

            if hop_neg_conj != hop_transpose:
                hermitian = False
                break
        return hermitian

    def reciprocal_vectors(self) -> list[NDArray[np.float64]]:
        """
        Compute the reciprocal lattice vectors for the periodic directions.

        Calculates the reciprocal lattice vectors corresponding to the
        periodic directions in the `UnitCell`.
        The number of reciprocal vectors depends on the number of
        periodic dimensions (0-3).

        Returns
        -------
        list[NDArray[np.float64]]
            List of 3D reciprocal vectors (0 to 3 items depending on
            periodicity)
        """
        basis_vectors = [
            v for v in [self.v1, self.v2, self.v3] if v.is_periodic
        ]
        num_periodic = len(basis_vectors)

        if num_periodic == 0:
            return []

        elif num_periodic == 1:
            a1 = basis_vectors[0].as_array()
            g1 = 2 * np.pi * a1 / np.dot(a1, a1)
            return [g1]

        elif num_periodic == 2:
            a1, a2 = [v.as_array() for v in basis_vectors]
            normal = np.cross(a1, a2)
            g1 = (
                2
                * np.pi
                * np.cross(normal, a2)
                / np.dot(a1, np.cross(a2, normal))
            )
            g2 = (
                2
                * np.pi
                * np.cross(a1, normal)
                / np.dot(a2, np.cross(normal, a1))
            )
            return [g1, g2]

        elif num_periodic == 3:
            a1, a2, a3 = [v.as_array() for v in basis_vectors]
            volume = np.dot(a1, np.cross(a2, a3))
            g1 = 2 * np.pi * np.cross(a2, a3) / volume
            g2 = 2 * np.pi * np.cross(a3, a1) / volume
            g3 = 2 * np.pi * np.cross(a1, a2) / volume
            return [g1, g2, g3]

        else:
            raise ValueError("Invalid number of periodic vectors.")

    def reduced_basis(self, scale: float = 1e6) -> list[BasisVector]:
        """
        Return a reduced set of periodic `BasisVector`s using LLL algorithm.

        Applies the Lenstra-Lenstra-Lovász (LLL) lattice reduction algorithm
        to find a more orthogonal set of basis vectors that spans the same
        lattice.

        Only the periodic `BasisVector`s are reduced.
        Non-periodic vectors are left unchanged.

        Parameters
        ----------
        scale : float = 1e6
            A float to scale the vectors for integer reduction.
            Used because the LLL algorithm works with integer matrices.

        Returns
        -------
        list[BasisVector]
            A list of BasisVector objects representing the reduced basis
        """
        vs = [self.v1, self.v2, self.v3]
        # Determine which vectors are periodic
        periodic_flags = [v.is_periodic for v in vs]
        # Extract the periodic vectors. Scale them to be used in reduction
        periodic_vectors = [
            np.round(scale * vs[ii].as_array()).astype(int)
            for ii in range(3)
            if periodic_flags[ii]
        ]

        # If there are fewer than 2 periodic vectors,
        # LLL reduction isn't meaningful
        if len(periodic_vectors) < 2:
            return vs  # Return unchanged

        # Reduced vectors
        reduced = DM(periodic_vectors, ZZ).lll().to_list()
        # Rebuild full list with reduced periodic vectors in original order
        reduced_basis = []
        # Rescale
        reduced_vectors = [
            np.array(vec, dtype=float) / scale for vec in reduced
        ]

        jj = 0  # Index for reduced_vectors
        for ii in range(3):
            if periodic_flags[ii]:
                vec = reduced_vectors[jj]
                reduced_basis.append(BasisVector(*vec, is_periodic=True))
                jj += 1
            else:
                reduced_basis.append(vs[ii])  # Unchanged
        return reduced_basis

    def get_states(self):
        """
        Extract all `State`s and their information from a `UnitCell` .

        This is a helper function used by UI components to get a flattened
        list of all states in the unit cell, regardless of which site they
        belong to. It makes it easier to display states in UI components
        like dropdown menus or lists.

        Returns
        -------
        tuple[list[State], list[tuple[str, uuid.UUID, str, uuid.UUID]]]
            A tuple containing a list of `State` objects and a list of
            tuples (site_name, site_id, state_name, state_id)
            providing context for each state (which site it belongs to).
        """
        states = []
        state_info = []
        for site_id, site in self.sites.items():
            for state_id, state in site.states.items():
                states.append(state)
                state_info.append((site.name, site_id, state.name, state_id))
        return (states, state_info)

    def get_BZ(self):
        """
        Compute the Brillouin zone vertices and faces.

        The Brillouin zone is the Wigner-Seitz cell of the reciprocal lattice.
        This method calculates the vertices and faces of the Brillouin zone
        using Voronoi construction. The dimensionality of the BZ depends on
        the number of periodic dimensions in the unit cell (0-3).

        For 1D, bz_vertices contains two points defining the BZ boundary.
        For 2D, bz_faces contains the edges of the 2D BZ polygon.
        For 3D, bz_faces contains the polygonal faces of the 3D BZ polyhedron.

        Returns
        -------
        tuple[NDArray[NDArray[np.float64]],\
            NDArray[NDArray[NDArray[np.float64]]]]
            The first element of the tuple gives the BZ vertex coordinates.
            The second element gives a list of faces, where each face is
            defined by vertex points. In 2D, the "faces" are edges.
        """
        n_neighbors = (
            1  # Number of neighboring reciprocal lattice points to consider
        )
        ranges = range(-n_neighbors, n_neighbors + 1)

        reciprocal_vectors = self.reciprocal_vectors()
        dim = len(reciprocal_vectors)

        if dim == 0:
            bz_vertices = np.array([])
            bz_faces = np.array([])

        elif dim == 1:
            g1 = reciprocal_vectors[0]
            bz_vertices = np.array([[g1[0] / 2], [-g1[0] / 2]])
            bz_faces = np.array([])

        else:

            if dim == 2:
                g1 = reciprocal_vectors[0][0:2]
                g2 = reciprocal_vectors[1][0:2]
                points = [
                    ii * g1 + jj * g2
                    for ii, jj in itertools.product(ranges, ranges)
                    if (ii != 0 or jj != 0)
                ]
                all_points = np.vstack([np.zeros(2), points])
            else:
                g1 = reciprocal_vectors[0]
                g2 = reciprocal_vectors[1]
                g3 = reciprocal_vectors[2]
                points = [
                    ii * g1 + jj * g2 + kk * g3
                    for ii, jj, kk in itertools.product(ranges, ranges, ranges)
                    if (ii != 0 or jj != 0 or kk != 0)
                ]
                all_points = np.vstack([np.zeros(3), points])

            vor = Voronoi(all_points)

            # Start by getting the vertices of the Brillouin zone
            # The first Brillouin zone is the Voronoi cell around
            # the origin (index 0)
            origin_region = vor.point_region[
                0
            ]  # Tells which Voronoi cell corresponds to origin point
            vertex_indices = vor.regions[
                origin_region
            ]  # Tells which vertices defining Voronoi cell bound the region

            # Extract vertices
            bz_vertices = vor.vertices[
                vertex_indices
            ]  # Get the coordinates of the vertices

            # Next, get a list of lists that defines the faces of the BZ
            bz_faces = []
            # ridge_points is a list of tuples corresponding to
            # pairs of elements of all_points that are separated by
            # a Voronoi boundary
            for num, p in enumerate(vor.ridge_points):
                # If one of the elements in the pair is the origin (index 0),
                # this ridge separates the BZ from its neighbors
                if p[0] == 0 or p[1] == 0:
                    # ridge_vertices[num] contains the indices of the vertices
                    # bounding the ridge
                    ridge_vertices = vor.ridge_vertices[num]
                    # finally, get the coordinates of the ridge vertices from
                    # their indices
                    face = vor.vertices[ridge_vertices]
                    bz_faces.append(face)
            bz_faces = np.array(bz_faces)
        return bz_vertices, bz_faces

    def get_hamiltonian_function(self):
        """
        Generate a function that computes the Hamiltonian matrix.

        This method creates a closure that precomputes all k-independent
        data needed for the Hamiltonian, and returns a function that builds
        the Hamiltonian matrix for any k-point in the Brillouin zone.

        The dimension of the k-point must match the number of periodic
        dimensions in the unit cell (1D, 2D, or 3D).

        Returns
        -------
        function
            A function that takes k-points (numpy array) and returns a
            complex Hamiltonian matrix.
        """
        # Get the list of all states in the unit cell for
        # determining the Hamiltonian size
        states, state_info = self.get_states()

        # Get the reciprocal lattice vectors
        reciprocal_vectors = self.reciprocal_vectors()
        num_periodic = len(reciprocal_vectors)

        # Create a mapping from state IDs to indices in the Hamiltonian matrix
        # state_id identifies the state, idx is its index in the Hamiltonian
        # matrix (to keep track of rows/columns)
        state_to_idx = {
            state_id: idx for idx, (_, _, _, state_id) in enumerate(state_info)
        }

        # Store the total number of states for matrix size
        n_states = len(states)
        # Basis vectors as arrays
        v1 = self.v1.as_array() if self.v1.is_periodic else np.zeros(3)
        v2 = self.v2.as_array() if self.v2.is_periodic else np.zeros(3)
        v3 = self.v3.as_array() if self.v3.is_periodic else np.zeros(3)

        # Define the Hamiltonian function that will be returned
        def hamiltonian(k):
            """
            Compute the Hamiltonian matrix for a given k-point.

            This function constructs the Hamiltonian matrix in k-space
            according to the tight-binding model defined by the `UnitCell`
            and its hopping parameters.
            The matrix elements include phase factors exp(-i k·R) for hoppings
            between different unit cells, as required by Bloch's theorem.

            Parameters
            ----------
            k : NDArray[np.float64]
                k-point vector in the basis of reciprocal lattice vectors
                If the system has n periodic directions, k should be an
                n-dimensional vector

            Returns
            -------
            NDArray[np.float64]
                Complex Hamiltonian matrix of size (n_states, n_states)
            """
            # Validate the k-point dimension matches the number of
            # periodic directions
            if len(k) != num_periodic:
                raise ValueError("Momentum does not match system periodicity")

            # Initialize the Hamiltonian matrix with zeros
            H = np.zeros((n_states, n_states), dtype=np.complex128)

            # Fill the Hamiltonian matrix
            for (dest_id, source_id), hoppings in self.hoppings.items():
                dest_idx = state_to_idx[dest_id]  # Destination state index
                source_idx = state_to_idx[source_id]  # Source state index

                for displacement, amplitude in hoppings:
                    d1, d2, d3 = displacement

                    # Calculate the real-space displacement vector
                    # This is the sum of the periodic vectors scaled by
                    # the displacement
                    R = d1 * v1 + d2 * v2 + d3 * v3

                    # Apply Bloch phase factor: exp(-i k·R)
                    if num_periodic == 0:
                        phase = 1.0
                    else:
                        phase = np.exp(1j * np.dot(k, R[0:num_periodic]))

                    # Add the term to the Hamiltonian
                    H[dest_idx, source_idx] += amplitude * phase

            return H

        return hamiltonian

get_BZ()

Compute the Brillouin zone vertices and faces.

The Brillouin zone is the Wigner-Seitz cell of the reciprocal lattice. This method calculates the vertices and faces of the Brillouin zone using Voronoi construction. The dimensionality of the BZ depends on the number of periodic dimensions in the unit cell (0-3).

For 1D, bz_vertices contains two points defining the BZ boundary. For 2D, bz_faces contains the edges of the 2D BZ polygon. For 3D, bz_faces contains the polygonal faces of the 3D BZ polyhedron.

Returns:

Type Description
tuple[NDArray[NDArray[float64]], NDArray[NDArray[NDArray[float64]]]]

The first element of the tuple gives the BZ vertex coordinates. The second element gives a list of faces, where each face is defined by vertex points. In 2D, the "faces" are edges.

Source code in TiBi/models/unit_cell.py
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
def get_BZ(self):
    """
    Compute the Brillouin zone vertices and faces.

    The Brillouin zone is the Wigner-Seitz cell of the reciprocal lattice.
    This method calculates the vertices and faces of the Brillouin zone
    using Voronoi construction. The dimensionality of the BZ depends on
    the number of periodic dimensions in the unit cell (0-3).

    For 1D, bz_vertices contains two points defining the BZ boundary.
    For 2D, bz_faces contains the edges of the 2D BZ polygon.
    For 3D, bz_faces contains the polygonal faces of the 3D BZ polyhedron.

    Returns
    -------
    tuple[NDArray[NDArray[np.float64]],\
        NDArray[NDArray[NDArray[np.float64]]]]
        The first element of the tuple gives the BZ vertex coordinates.
        The second element gives a list of faces, where each face is
        defined by vertex points. In 2D, the "faces" are edges.
    """
    n_neighbors = (
        1  # Number of neighboring reciprocal lattice points to consider
    )
    ranges = range(-n_neighbors, n_neighbors + 1)

    reciprocal_vectors = self.reciprocal_vectors()
    dim = len(reciprocal_vectors)

    if dim == 0:
        bz_vertices = np.array([])
        bz_faces = np.array([])

    elif dim == 1:
        g1 = reciprocal_vectors[0]
        bz_vertices = np.array([[g1[0] / 2], [-g1[0] / 2]])
        bz_faces = np.array([])

    else:

        if dim == 2:
            g1 = reciprocal_vectors[0][0:2]
            g2 = reciprocal_vectors[1][0:2]
            points = [
                ii * g1 + jj * g2
                for ii, jj in itertools.product(ranges, ranges)
                if (ii != 0 or jj != 0)
            ]
            all_points = np.vstack([np.zeros(2), points])
        else:
            g1 = reciprocal_vectors[0]
            g2 = reciprocal_vectors[1]
            g3 = reciprocal_vectors[2]
            points = [
                ii * g1 + jj * g2 + kk * g3
                for ii, jj, kk in itertools.product(ranges, ranges, ranges)
                if (ii != 0 or jj != 0 or kk != 0)
            ]
            all_points = np.vstack([np.zeros(3), points])

        vor = Voronoi(all_points)

        # Start by getting the vertices of the Brillouin zone
        # The first Brillouin zone is the Voronoi cell around
        # the origin (index 0)
        origin_region = vor.point_region[
            0
        ]  # Tells which Voronoi cell corresponds to origin point
        vertex_indices = vor.regions[
            origin_region
        ]  # Tells which vertices defining Voronoi cell bound the region

        # Extract vertices
        bz_vertices = vor.vertices[
            vertex_indices
        ]  # Get the coordinates of the vertices

        # Next, get a list of lists that defines the faces of the BZ
        bz_faces = []
        # ridge_points is a list of tuples corresponding to
        # pairs of elements of all_points that are separated by
        # a Voronoi boundary
        for num, p in enumerate(vor.ridge_points):
            # If one of the elements in the pair is the origin (index 0),
            # this ridge separates the BZ from its neighbors
            if p[0] == 0 or p[1] == 0:
                # ridge_vertices[num] contains the indices of the vertices
                # bounding the ridge
                ridge_vertices = vor.ridge_vertices[num]
                # finally, get the coordinates of the ridge vertices from
                # their indices
                face = vor.vertices[ridge_vertices]
                bz_faces.append(face)
        bz_faces = np.array(bz_faces)
    return bz_vertices, bz_faces

get_hamiltonian_function()

Generate a function that computes the Hamiltonian matrix.

This method creates a closure that precomputes all k-independent data needed for the Hamiltonian, and returns a function that builds the Hamiltonian matrix for any k-point in the Brillouin zone.

The dimension of the k-point must match the number of periodic dimensions in the unit cell (1D, 2D, or 3D).

Returns:

Type Description
function

A function that takes k-points (numpy array) and returns a complex Hamiltonian matrix.

Source code in TiBi/models/unit_cell.py
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
def get_hamiltonian_function(self):
    """
    Generate a function that computes the Hamiltonian matrix.

    This method creates a closure that precomputes all k-independent
    data needed for the Hamiltonian, and returns a function that builds
    the Hamiltonian matrix for any k-point in the Brillouin zone.

    The dimension of the k-point must match the number of periodic
    dimensions in the unit cell (1D, 2D, or 3D).

    Returns
    -------
    function
        A function that takes k-points (numpy array) and returns a
        complex Hamiltonian matrix.
    """
    # Get the list of all states in the unit cell for
    # determining the Hamiltonian size
    states, state_info = self.get_states()

    # Get the reciprocal lattice vectors
    reciprocal_vectors = self.reciprocal_vectors()
    num_periodic = len(reciprocal_vectors)

    # Create a mapping from state IDs to indices in the Hamiltonian matrix
    # state_id identifies the state, idx is its index in the Hamiltonian
    # matrix (to keep track of rows/columns)
    state_to_idx = {
        state_id: idx for idx, (_, _, _, state_id) in enumerate(state_info)
    }

    # Store the total number of states for matrix size
    n_states = len(states)
    # Basis vectors as arrays
    v1 = self.v1.as_array() if self.v1.is_periodic else np.zeros(3)
    v2 = self.v2.as_array() if self.v2.is_periodic else np.zeros(3)
    v3 = self.v3.as_array() if self.v3.is_periodic else np.zeros(3)

    # Define the Hamiltonian function that will be returned
    def hamiltonian(k):
        """
        Compute the Hamiltonian matrix for a given k-point.

        This function constructs the Hamiltonian matrix in k-space
        according to the tight-binding model defined by the `UnitCell`
        and its hopping parameters.
        The matrix elements include phase factors exp(-i k·R) for hoppings
        between different unit cells, as required by Bloch's theorem.

        Parameters
        ----------
        k : NDArray[np.float64]
            k-point vector in the basis of reciprocal lattice vectors
            If the system has n periodic directions, k should be an
            n-dimensional vector

        Returns
        -------
        NDArray[np.float64]
            Complex Hamiltonian matrix of size (n_states, n_states)
        """
        # Validate the k-point dimension matches the number of
        # periodic directions
        if len(k) != num_periodic:
            raise ValueError("Momentum does not match system periodicity")

        # Initialize the Hamiltonian matrix with zeros
        H = np.zeros((n_states, n_states), dtype=np.complex128)

        # Fill the Hamiltonian matrix
        for (dest_id, source_id), hoppings in self.hoppings.items():
            dest_idx = state_to_idx[dest_id]  # Destination state index
            source_idx = state_to_idx[source_id]  # Source state index

            for displacement, amplitude in hoppings:
                d1, d2, d3 = displacement

                # Calculate the real-space displacement vector
                # This is the sum of the periodic vectors scaled by
                # the displacement
                R = d1 * v1 + d2 * v2 + d3 * v3

                # Apply Bloch phase factor: exp(-i k·R)
                if num_periodic == 0:
                    phase = 1.0
                else:
                    phase = np.exp(1j * np.dot(k, R[0:num_periodic]))

                # Add the term to the Hamiltonian
                H[dest_idx, source_idx] += amplitude * phase

        return H

    return hamiltonian

get_states()

Extract all States and their information from a UnitCell .

This is a helper function used by UI components to get a flattened list of all states in the unit cell, regardless of which site they belong to. It makes it easier to display states in UI components like dropdown menus or lists.

Returns:

Type Description
tuple[list[State], list[tuple[str, UUID, str, UUID]]]

A tuple containing a list of State objects and a list of tuples (site_name, site_id, state_name, state_id) providing context for each state (which site it belongs to).

Source code in TiBi/models/unit_cell.py
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
def get_states(self):
    """
    Extract all `State`s and their information from a `UnitCell` .

    This is a helper function used by UI components to get a flattened
    list of all states in the unit cell, regardless of which site they
    belong to. It makes it easier to display states in UI components
    like dropdown menus or lists.

    Returns
    -------
    tuple[list[State], list[tuple[str, uuid.UUID, str, uuid.UUID]]]
        A tuple containing a list of `State` objects and a list of
        tuples (site_name, site_id, state_name, state_id)
        providing context for each state (which site it belongs to).
    """
    states = []
    state_info = []
    for site_id, site in self.sites.items():
        for state_id, state in site.states.items():
            states.append(state)
            state_info.append((site.name, site_id, state.name, state_id))
    return (states, state_info)

is_hermitian()

Check whether the hoppings are Hermitian.

For each key (destination_state_id, source_state_id) in the hoppings dictionary, check if there is a key (source_state_id, destination_state_id). If so, check that the entries are related by Hermitian conjugation.

Returns:

Type Description
bool

True if Hermitian; False if not.

Source code in TiBi/models/unit_cell.py
 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
def is_hermitian(self) -> bool:
    """
    Check whether the hoppings are Hermitian.

    For each key (destination_state_id, source_state_id) in the
    hoppings dictionary, check if there is a key
    (source_state_id, destination_state_id). If so, check that the entries
    are related by Hermitian conjugation.

    Returns
    -------
    bool
        `True` if Hermitian; `False` if not.
    """
    hermitian = True

    for key, val in self.hoppings.items():
        s1 = key[0]
        s2 = key[1]

        hop = set(val)
        hop_transpose = set(self.hoppings.get((s2, s1), []))

        hop_neg_conj = set(
            ((-d1, -d2, -d3), np.conj(x)) for ((d1, d2, d3), x) in hop
        )

        if hop_neg_conj != hop_transpose:
            hermitian = False
            break
    return hermitian

reciprocal_vectors()

Compute the reciprocal lattice vectors for the periodic directions.

Calculates the reciprocal lattice vectors corresponding to the periodic directions in the UnitCell. The number of reciprocal vectors depends on the number of periodic dimensions (0-3).

Returns:

Type Description
list[NDArray[float64]]

List of 3D reciprocal vectors (0 to 3 items depending on periodicity)

Source code in TiBi/models/unit_cell.py
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
def reciprocal_vectors(self) -> list[NDArray[np.float64]]:
    """
    Compute the reciprocal lattice vectors for the periodic directions.

    Calculates the reciprocal lattice vectors corresponding to the
    periodic directions in the `UnitCell`.
    The number of reciprocal vectors depends on the number of
    periodic dimensions (0-3).

    Returns
    -------
    list[NDArray[np.float64]]
        List of 3D reciprocal vectors (0 to 3 items depending on
        periodicity)
    """
    basis_vectors = [
        v for v in [self.v1, self.v2, self.v3] if v.is_periodic
    ]
    num_periodic = len(basis_vectors)

    if num_periodic == 0:
        return []

    elif num_periodic == 1:
        a1 = basis_vectors[0].as_array()
        g1 = 2 * np.pi * a1 / np.dot(a1, a1)
        return [g1]

    elif num_periodic == 2:
        a1, a2 = [v.as_array() for v in basis_vectors]
        normal = np.cross(a1, a2)
        g1 = (
            2
            * np.pi
            * np.cross(normal, a2)
            / np.dot(a1, np.cross(a2, normal))
        )
        g2 = (
            2
            * np.pi
            * np.cross(a1, normal)
            / np.dot(a2, np.cross(normal, a1))
        )
        return [g1, g2]

    elif num_periodic == 3:
        a1, a2, a3 = [v.as_array() for v in basis_vectors]
        volume = np.dot(a1, np.cross(a2, a3))
        g1 = 2 * np.pi * np.cross(a2, a3) / volume
        g2 = 2 * np.pi * np.cross(a3, a1) / volume
        g3 = 2 * np.pi * np.cross(a1, a2) / volume
        return [g1, g2, g3]

    else:
        raise ValueError("Invalid number of periodic vectors.")

reduced_basis(scale=1000000.0)

Return a reduced set of periodic BasisVectors using LLL algorithm.

Applies the Lenstra-Lenstra-Lovász (LLL) lattice reduction algorithm to find a more orthogonal set of basis vectors that spans the same lattice.

Only the periodic BasisVectors are reduced. Non-periodic vectors are left unchanged.

Parameters:

Name Type Description Default
scale float = 1e6

A float to scale the vectors for integer reduction. Used because the LLL algorithm works with integer matrices.

1000000.0

Returns:

Type Description
list[BasisVector]

A list of BasisVector objects representing the reduced basis

Source code in TiBi/models/unit_cell.py
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
def reduced_basis(self, scale: float = 1e6) -> list[BasisVector]:
    """
    Return a reduced set of periodic `BasisVector`s using LLL algorithm.

    Applies the Lenstra-Lenstra-Lovász (LLL) lattice reduction algorithm
    to find a more orthogonal set of basis vectors that spans the same
    lattice.

    Only the periodic `BasisVector`s are reduced.
    Non-periodic vectors are left unchanged.

    Parameters
    ----------
    scale : float = 1e6
        A float to scale the vectors for integer reduction.
        Used because the LLL algorithm works with integer matrices.

    Returns
    -------
    list[BasisVector]
        A list of BasisVector objects representing the reduced basis
    """
    vs = [self.v1, self.v2, self.v3]
    # Determine which vectors are periodic
    periodic_flags = [v.is_periodic for v in vs]
    # Extract the periodic vectors. Scale them to be used in reduction
    periodic_vectors = [
        np.round(scale * vs[ii].as_array()).astype(int)
        for ii in range(3)
        if periodic_flags[ii]
    ]

    # If there are fewer than 2 periodic vectors,
    # LLL reduction isn't meaningful
    if len(periodic_vectors) < 2:
        return vs  # Return unchanged

    # Reduced vectors
    reduced = DM(periodic_vectors, ZZ).lll().to_list()
    # Rebuild full list with reduced periodic vectors in original order
    reduced_basis = []
    # Rescale
    reduced_vectors = [
        np.array(vec, dtype=float) / scale for vec in reduced
    ]

    jj = 0  # Index for reduced_vectors
    for ii in range(3):
        if periodic_flags[ii]:
            vec = reduced_vectors[jj]
            reduced_basis.append(BasisVector(*vec, is_periodic=True))
            jj += 1
        else:
            reduced_basis.append(vs[ii])  # Unchanged
    return reduced_basis

volume()

Compute the volume of the UnitCell using the scalar triple product.

Returns:

Type Description
float64

Volume of the unit cell in arbitrary units

Source code in TiBi/models/unit_cell.py
86
87
88
89
90
91
92
93
94
95
96
def volume(self) -> np.float64:
    """
    Compute the volume of the `UnitCell` using the scalar triple product.

    Returns
    -------
    np.float64
        Volume of the unit cell in arbitrary units
    """
    a1, a2, a3 = [v.as_array() for v in [self.v1, self.v2, self.v3]]
    return np.abs(np.dot(a1, np.cross(a2, a3)))

TiBi.models.factories.bz_point_selection_init()

Initialize the selection dictonary for points in the Brillouin zone.

Each key corresponds to a point type (vertex, edge, face), while the entries give the cardinal indices of the selected points.

Returns:

Type Description
dict

A dictionary with keys 'vertex', 'edge', and 'face', each initialized to None. During use, the entries are set to the indices of the selected points.

Source code in TiBi/models/factories.py
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
def bz_point_selection_init():
    """
    Initialize the selection dictonary for points in the Brillouin zone.

    Each key corresponds to a point type (vertex, edge, face), while
    the entries give the cardinal indices of the selected points.

    Returns
    -------
    dict
        A dictionary with keys 'vertex', 'edge', and 'face', each initialized
        to None. During use, the entries are set to the indices
        of the selected points.
    """
    return {
        "vertex": None,
        "edge": None,
        "face": None,
    }  # Indices of the selected high-symmetry points in the BZ

TiBi.models.factories.bz_point_lists_init()

Initialize the dictonary of points in the Brillouin zone.

Each key corresponds to a point type (vertex, edge, face), while the entries are lists of high-symmetry points in the Brillouin zone.

Returns:

Type Description
dict

A dictionary with keys 'vertex', 'edge', and 'face', each initialized to empty lists. During use, the entries are set to the lists of of the high-symmetry points.

Source code in TiBi/models/factories.py
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
def bz_point_lists_init():
    """
    Initialize the dictonary of points in the Brillouin zone.

    Each key corresponds to a point type (vertex, edge, face), while
    the entries are lists of high-symmetry points in the Brillouin zone.

    Returns
    -------
    dict
        A dictionary with keys 'vertex', 'edge', and 'face', each initialized
        to empty lists. During use, the entries are set to the lists of
        of the high-symmetry points.
    """
    return {
        "vertex": [],
        "edge": [],
        "face": [],
    }  # Lists of the high-symmetry points in the BZ, grouped by type

TiBi.models.factories.mk_new_site()

Create a new site with default values.

Returns:

Type Description
Site

A new Site object with default values.

Source code in TiBi/models/factories.py
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
def mk_new_site():
    """
    Create a new site with default values.

    Returns
    -------
    Site
        A new `Site` object with default values.
    """
    name = "New Site"
    c1 = 0  # Fractional coordinate along first basis vector
    c2 = 0  # Fractional coordinate along second basis vector
    c3 = 0  # Fractional coordinate along third basis vector
    R = DEFAULT_SITE_SIZE
    color = (
        random.uniform(0, 1),
        random.uniform(0, 1),
        random.uniform(0, 1),
        1.0,
    )
    return Site(name, c1, c2, c3, R, color)

TiBi.models.factories.mk_new_state()

Create a new state with default values.

Returns:

Type Description
State

A new State object with default values.

Source code in TiBi/models/factories.py
89
90
91
92
93
94
95
96
97
98
99
def mk_new_state():
    """
    Create a new state with default values.

    Returns
    -------
    State
        A new `State` object with default values.
    """
    name = "New State"
    return State(name)

TiBi.models.factories.mk_new_unit_cell()

Create a new unit cell with default values.

Returns:

Type Description
UnitCell

A new UnitCell object with default values.

Source code in TiBi/models/factories.py
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
def mk_new_unit_cell():
    """
    Create a new unit cell with default values.

    Returns
    -------
    UnitCell
        A new `UnitCell` object with default values.
    """
    name = "New Unit Cell"
    v1 = BasisVector(1, 0, 0)  # Unit vector along x-axis
    v2 = BasisVector(0, 1, 0)  # Unit vector along y-axis
    v3 = BasisVector(0, 0, 1)  # Unit vector along z-axis

    return UnitCell(name, v1, v2, v3)