Skip to content

Logic

This page provides the documentation for the commands encoding undo/redo actions, data serialization, and workers used for running processes on parallel threads.

Brillouin Zone Commands

TiBi.logic.commands.AddBZPointCommand

Bases: QUndoCommand

Add a point to the special points path in the Brillouin zone.

Because this action would invalidate the already-calculated bands, the band structure is reset.

Attributes:

Name Type Description
unit_cell UnitCell

UnitCell to which the point will be addeed

point NDArray[float64]

The point to be added

computation_view ComputationView

UI object containing the computation view

signal Signal

Signal to be emitted to trigger a redraw of the BZ path

Source code in TiBi/logic/commands/bz_commands.py
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
class AddBZPointCommand(QUndoCommand):
    """
    Add a point to the special points path in the Brillouin zone.

    Because this action would invalidate the already-calculated bands,
    the band structure is reset.

    Attributes
    ----------
    unit_cell : UnitCell
        `UnitCell` to which the point will be addeed
    point : NDArray[np.float64]
        The point to be added
    computation_view : ComputationView
        UI object containing the computation view
    signal : Signal
        Signal to be emitted to trigger a redraw of the BZ path
    """

    def __init__(
        self,
        unit_cell: UnitCell,
        point: NDArray[np.float64],
        computation_view: ComputationView,
        signal: Signal,
    ):
        super().__init__("Add BZ Path Point")
        self.unit_cell = unit_cell
        self.point = point
        self.computation_view = computation_view
        self.signal = signal

    def redo(self):

        self.unit_cell.bandstructure.add_point(self.point)
        self.computation_view.bands_panel.remove_last_btn.setEnabled(
            len(self.unit_cell.bandstructure.special_points) > 0
        )
        self.computation_view.bands_panel.clear_path_btn.setEnabled(
            len(self.unit_cell.bandstructure.special_points) > 0
        )
        self.computation_view.bands_panel.compute_bands_btn.setEnabled(
            len(self.unit_cell.bandstructure.special_points) > 1
        )
        self.signal.emit()

    def undo(self):

        self.unit_cell.bandstructure.remove_point()
        self.computation_view.bands_panel.remove_last_btn.setEnabled(
            len(self.unit_cell.bandstructure.special_points) > 0
        )
        self.computation_view.bands_panel.clear_path_btn.setEnabled(
            len(self.unit_cell.bandstructure.special_points) > 0
        )
        self.computation_view.bands_panel.compute_bands_btn.setEnabled(
            len(self.unit_cell.bandstructure.special_points) > 1
        )
        self.signal.emit()

TiBi.logic.commands.ClearBZPathCommand

Bases: QUndoCommand

Clear the special points path in the Brillouin zone.

Because this action would invalidate the already-calculated bands, the band structure is reset.

Attributes:

Name Type Description
unit_cell UnitCell

UnitCell whose path will be cleared

special_points list[NDArray[float64]]

List of special points before clearing the path

computation_view ComputationView

UI object containing the computation view

signal Signal

Signal to be emitted to trigger a redraw of the BZ path

Source code in TiBi/logic/commands/bz_commands.py
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
class ClearBZPathCommand(QUndoCommand):
    """
    Clear the special points path in the Brillouin zone.

    Because this action would invalidate the already-calculated bands,
    the band structure is reset.

    Attributes
    ----------
    unit_cell : UnitCell
        `UnitCell` whose path will be cleared
    special_points : list[NDArray[np.float64]]
        List of special points before clearing the path
    computation_view : ComputationView
        UI object containing the computation view
    signal : Signal
        Signal to be emitted to trigger a redraw of the BZ path
    """

    def __init__(
        self,
        unit_cell: UnitCell,
        computation_view: ComputationView,
        signal: Signal,
    ):
        super().__init__("Add BZ Path Point")
        self.unit_cell = unit_cell
        self.computation_view = computation_view
        self.signal = signal

        self.special_points = copy.deepcopy(
            self.unit_cell.bandstructure.special_points
        )

    def redo(self):
        self.unit_cell.bandstructure.clear()
        self.computation_view.bands_panel.remove_last_btn.setEnabled(
            len(self.unit_cell.bandstructure.special_points) > 0
        )
        self.computation_view.bands_panel.clear_path_btn.setEnabled(
            len(self.unit_cell.bandstructure.special_points) > 0
        )
        self.computation_view.bands_panel.compute_bands_btn.setEnabled(
            len(self.unit_cell.bandstructure.special_points) > 1
        )
        self.signal.emit()

    def undo(self):
        self.unit_cell.bandstructure.clear()
        self.unit_cell.bandstructure.special_points = copy.deepcopy(
            self.special_points
        )
        self.computation_view.bands_panel.remove_last_btn.setEnabled(
            len(self.unit_cell.bandstructure.special_points) > 0
        )
        self.computation_view.bands_panel.clear_path_btn.setEnabled(
            len(self.unit_cell.bandstructure.special_points) > 0
        )
        self.computation_view.bands_panel.compute_bands_btn.setEnabled(
            len(self.unit_cell.bandstructure.special_points) > 1
        )
        self.signal.emit()

TiBi.logic.commands.RemoveBZPointCommand

Bases: QUndoCommand

Remove the last point from the special points path in the Brillouin zone.

Because this action would invalidate the already-calculated bands, the band structure is reset.

Attributes:

Name Type Description
unit_cell UnitCell

UnitCell from which the point will be removed

point NDArray

The point to be removed

computation_view ComputationView

UI object containing the computation view

signal Signal

Signal to be emitted to trigger a redraw of the BZ path

Source code in TiBi/logic/commands/bz_commands.py
 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
class RemoveBZPointCommand(QUndoCommand):
    """
    Remove the last point from the special points path in the Brillouin zone.

    Because this action would invalidate the already-calculated bands,
    the band structure is reset.

    Attributes
    ----------
    unit_cell : UnitCell
        `UnitCell` from which the point will be removed
    point : NDArray
        The point to be removed
    computation_view : ComputationView
        UI object containing the computation view
    signal : Signal
        Signal to be emitted to trigger a redraw of the BZ path
    """

    def __init__(
        self,
        unit_cell: UnitCell,
        computation_view: ComputationView,
        signal: Signal,
    ):
        super().__init__("Remove BZ Path Point")
        self.unit_cell = unit_cell
        self.computation_view = computation_view
        self.signal = signal

        self.point = copy.deepcopy(
            self.unit_cell.bandstructure.special_points[-1]
        )

    def redo(self):
        self.unit_cell.bandstructure.remove_point()
        self.computation_view.bands_panel.remove_last_btn.setEnabled(
            len(self.unit_cell.bandstructure.special_points) > 0
        )
        self.computation_view.bands_panel.clear_path_btn.setEnabled(
            len(self.unit_cell.bandstructure.special_points) > 0
        )
        self.computation_view.bands_panel.compute_bands_btn.setEnabled(
            len(self.unit_cell.bandstructure.special_points) > 1
        )
        self.signal.emit()

    def undo(self):
        self.unit_cell.bandstructure.add_point(self.point)
        self.computation_view.bands_panel.remove_last_btn.setEnabled(
            len(self.unit_cell.bandstructure.special_points) > 0
        )
        self.computation_view.bands_panel.clear_path_btn.setEnabled(
            len(self.unit_cell.bandstructure.special_points) > 0
        )
        self.computation_view.bands_panel.compute_bands_btn.setEnabled(
            len(self.unit_cell.bandstructure.special_points) > 1
        )
        self.signal.emit()

Tree Commands

TiBi.logic.commands.AddSiteCommand

Bases: QUndoCommand

Create a new site in the currently selected unit cell.

Creates a site with default name and coordinates (0,0,0), adds it to the sites dictionary of the selected unit cell and to the tree.

The default site has: - Name: "New Site" - Coordinates (0,0,0) - No states initially - Default radius - Random color

Attributes:

Name Type Description
unit_cells dict[UUID, UnitCell]

Dictionary mapping UUIDs to UnitCell objects

selection Selection

Dictionary containing the current selection

tree_view SystemTree

UI object containing the tree view

uc_id UUID

UUID of the selected UnitCell when the command was issued

site Site

Newly created Site

Source code in TiBi/logic/commands/tree_commands.py
 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
class AddSiteCommand(QUndoCommand):
    """
    Create a new site in the currently selected unit cell.

    Creates a site with default name and coordinates (0,0,0), adds it to
    the sites dictionary of the selected unit cell and to the tree.

    The default site has:
    - Name: "New Site"
    - Coordinates (0,0,0)
    - No states initially
    - Default radius
    - Random color

    Attributes
    ----------
    unit_cells : dict[uuid.UUID, UnitCell]
        Dictionary mapping UUIDs to `UnitCell` objects
    selection : Selection
        Dictionary containing the current selection
    tree_view : SystemTree
        UI object containing the tree view
    uc_id : uuid.UUID
        UUID of the selected `UnitCell` when the command was issued
    site : Site
        Newly created `Site`
    """

    def __init__(
        self,
        unit_cells: dict[uuid.UUID, UnitCell],
        selection: Selection,
        tree_view: SystemTree,
    ):
        super().__init__("Add Site")
        self.unit_cells = unit_cells
        self.selection = selection
        self.tree_view = tree_view
        self.uc_id = self.selection.unit_cell
        self.site = mk_new_site()

    # Add the newly-created site to the dictionary and create a tree item
    # Add entries to the size and colors dictionaries for the site
    def redo(self):
        unit_cell = self.unit_cells[self.uc_id]
        unit_cell.sites[self.site.id] = self.site
        self.tree_view.add_tree_item(self.site.name, self.uc_id, self.site.id)

    # Remove the unit cell from the dictionary and the tree using its id
    # Remove the color and size entries
    def undo(self):
        del self.unit_cells[self.uc_id].sites[self.site.id]
        self.tree_view.remove_tree_item(self.uc_id, self.site.id)

TiBi.logic.commands.AddStateCommand

Bases: QUndoCommand

Create a new state in the currently selected site.

Creates a state with default name, adds it to the states dictionary of the selected site and to the tree.

The default state has: - Name: "New State"

Attributes:

Name Type Description
unit_cells dict[UUID, UnitCell]

Dictionary mapping UUIDs to UnitCell objects

selection Selection

Model containing the current selection

tree_view SystemTree

UI object containing the tree view

signal Signal

Signal emitted when the state is added/removed

uc_id UUID

UUID of the selected UnitCell when the command was issued

site_id UUID

UUID of the selected Site when the command was issued

state State

Newly created State

Source code in TiBi/logic/commands/tree_commands.py
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
class AddStateCommand(QUndoCommand):
    """
    Create a new state in the currently selected site.

    Creates a state with default name, adds it to the
    states dictionary of the selected site and to the tree.

    The default state has:
    - Name: "New State"

    Attributes
    ----------
    unit_cells : dict[uuid.UUID, UnitCell]
        Dictionary mapping UUIDs to `UnitCell` objects
    selection : Selection
        Model containing the current selection
    tree_view : SystemTree
        UI object containing the tree view
    signal : Signal
        Signal emitted when the state is added/removed
    uc_id : uuid.UUID
        UUID of the selected `UnitCell` when the command was issued
    site_id : uuid.UUID
        UUID of the selected `Site` when the command was issued
    state : State
        Newly created `State`
    """

    def __init__(
        self,
        unit_cells: dict[uuid.UUID, UnitCell],
        selection: Selection,
        tree_view: SystemTree,
        signal: Signal,
    ):
        super().__init__("Add State")
        self.unit_cells = unit_cells
        self.selection = selection
        self.tree_view = tree_view
        self.signal = signal
        self.uc_id = self.selection.unit_cell
        self.site_id = self.selection.site
        self.state = mk_new_state()

    # Add the newly-created state to the dictionary and create a tree item
    def redo(self):
        unit_cell = self.unit_cells[self.uc_id]
        site = unit_cell.sites[self.site_id]
        site.states[self.state.id] = self.state
        unit_cell.bandstructure.reset_bands()
        unit_cell.bz_grid.clear()
        self.tree_view.add_tree_item(
            self.state.name, self.uc_id, self.site_id, self.state.id
        )
        self.signal.emit()

    # Remove the site from the dictionary and the tree using its id
    def undo(self):
        del (
            self.unit_cells[self.uc_id]
            .sites[self.site_id]
            .states[self.state.id]
        )
        self.unit_cells[self.uc_id].bandstructure.reset_bands()
        self.unit_cells[self.uc_id].bz_grid.clear()
        self.tree_view.remove_tree_item(
            self.uc_id, self.site_id, self.state.id
        )
        self.signal.emit()

TiBi.logic.commands.AddUnitCellCommand

Bases: QUndoCommand

Create a new unit cell with default properties and add it to the model.

Creates a unit cell with orthogonal basis vectors along the x, y, and z axes, adds it to the unit_cells dictionary and to the tree view.

The default unit cell has: - Name: "New Unit Cell" - Three orthogonal unit vectors along the x, y, and z axes - No periodicity (0D system) - No sites or states initially

Attributes:

Name Type Description
unit_cells dict[UUID, UnitCell]

Dictionary mapping UUIDs to UnitCell objects

tree_view SystemTree

UI object containing the tree view

unit_cell UnitCell

Newly created UnitCell

Source code in TiBi/logic/commands/tree_commands.py
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
class AddUnitCellCommand(QUndoCommand):
    """
    Create a new unit cell with default properties and add it to the model.

    Creates a unit cell with orthogonal basis vectors along
    the x, y, and z axes, adds it to the unit_cells dictionary
    and to the tree view.

    The default unit cell has:
    - Name: "New Unit Cell"
    - Three orthogonal unit vectors along the x, y, and z axes
    - No periodicity (0D system)
    - No sites or states initially

    Attributes
    ----------
    unit_cells : dict[uuid.UUID, UnitCell]
        Dictionary mapping UUIDs to `UnitCell` objects
    tree_view : SystemTree
        UI object containing the tree view
    unit_cell : UnitCell
        Newly created `UnitCell`
    """

    def __init__(
        self, unit_cells: dict[uuid.UUID, UnitCell], tree_view: SystemTree
    ):
        super().__init__("Add Unit Cell")
        self.unit_cells = unit_cells
        self.tree_view = tree_view
        self.unit_cell = mk_new_unit_cell()

    # Add the newly-created unit cell to the dictionary and create a tree item
    def redo(self):
        self.unit_cells[self.unit_cell.id] = self.unit_cell
        self.tree_view.add_tree_item(self.unit_cell.name, self.unit_cell.id)

    # Remove the unit cell from the dictionary and the tree using its id
    def undo(self):
        del self.unit_cells[self.unit_cell.id]
        self.tree_view.remove_tree_item(self.unit_cell.id)

TiBi.logic.commands.DeleteItemCommand

Bases: QUndoCommand

Delete the currently selected item from the model.

This command handles deletion of UnitCells, Sites, and States based on the current selection. It updates both the data model and the tree view to reflect the deletion, and ensures that the selection is updated appropriately.

The deletion follows the containment hierarchy: - Deleting a UnitCell also removes all its Sites and States - Deleting a Site also removes all its States - Deleting a State only removes that specific State

Attributes:

Name Type Description
unit_cells dict[UUID, UnitCell]

Dictionary mapping UUIDs to UnitCell objects

selection Selection

Dictionary containing the current selection

tree_view SystemTree

UI object containing the tree view

signal Signal

Signal emitted when a state is added/removed

uc_id UUID

UUID of the selected UnitCell when the command was issued

site_id UUID

UUID of the selected Site when the command was issued

state_id UUID

UUID of the selected State when the command was issued

Source code in TiBi/logic/commands/tree_commands.py
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
class DeleteItemCommand(QUndoCommand):
    """
    Delete the currently selected item from the model.

    This command handles deletion of `UnitCell`s, `Site`s, and `State`s
    based on the current selection. It updates both the data model and
    the tree view to reflect the deletion, and ensures that
    the selection is updated appropriately.

    The deletion follows the containment hierarchy:
    - Deleting a `UnitCell` also removes all its `Site`s and `State`s
    - Deleting a `Site` also removes all its `State`s
    - Deleting a `State` only removes that specific `State`

    Attributes
    ----------
    unit_cells : dict[uuid.UUID, UnitCell]
        Dictionary mapping UUIDs to `UnitCell` objects
    selection : Selection
        Dictionary containing the current selection
    tree_view : SystemTree
        UI object containing the tree view
    signal : Signal
        Signal emitted when a state is added/removed
    uc_id : uuid.UUID
        UUID of the selected `UnitCell` when the command was issued
    site_id : uuid.UUID
        UUID of the selected `Site` when the command was issued
    state_id : uuid.UUID
        UUID of the selected `State` when the command was issued
    """

    def __init__(
        self,
        unit_cells: dict[uuid.UUID, UnitCell],
        selection: Selection,
        tree_view: SystemTree,
        signal: Signal,
    ):
        super().__init__("Delete Item")
        self.unit_cells = unit_cells
        self.selection = selection
        self.tree_view = tree_view
        self.signal = signal

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

        # Save the item to be deleted for undo
        if self.state_id:
            self.item = copy.deepcopy(
                self.unit_cells[self.uc_id]
                .sites[self.site_id]
                .states[self.state_id]
            )
        # No state selected, therefore remove the site from the unit cell
        elif self.site_id:
            self.item = copy.deepcopy(
                self.unit_cells[self.uc_id].sites[self.site_id]
            )

        # No site selected, therefore remove the unit cell from the model
        elif self.uc_id:
            self.item = copy.deepcopy(self.unit_cells[self.uc_id])

    # Delete the item
    def redo(self):
        if self.state_id:
            # Get the hoppings involving the states, remove them
            # from the hopping dictionary and store them to be used
            # in the undo method
            self.removed_hoppings = {}
            kept_hoppings = {}
            for k, v in self.unit_cells[self.uc_id].hoppings.items():
                if self.state_id in k:
                    self.removed_hoppings[k] = v
                else:
                    kept_hoppings[k] = v
            self.unit_cells[self.uc_id].hoppings = kept_hoppings
            self.unit_cells[self.uc_id].bandstructure.reset_bands()
            self.unit_cells[self.uc_id].bz_grid.clear()
            # Delete the selected state from the site
            del (
                self.unit_cells[self.uc_id]
                .sites[self.site_id]
                .states[self.state_id]
            )
            self.signal.emit()

        # No state selected, therefore remove the site from the unit cell
        elif self.site_id:
            self.removed_hoppings = {}
            kept_hoppings = {}
            for state in (
                self.unit_cells[self.uc_id].sites[self.site_id].states.values()
            ):
                for k, v in self.unit_cells[self.uc_id].hoppings.items():
                    if state.id in k:
                        self.removed_hoppings[k] = v
                    else:
                        kept_hoppings[k] = v
            self.unit_cells[self.uc_id].hoppings = kept_hoppings
            if self.removed_hoppings:
                self.unit_cells[self.uc_id].bandstructure.reset_bands()
                self.unit_cells[self.uc_id].bz_grid.clear()
            # If the site has states, request a redraw of the hopping matrix
            if self.unit_cells[self.uc_id].sites[self.site_id].states:
                del self.unit_cells[self.uc_id].sites[self.site_id]
                self.signal.emit()
            else:
                del self.unit_cells[self.uc_id].sites[self.site_id]
        # No site selected, therefore remove the unit cell from the model
        elif self.uc_id:
            del self.unit_cells[self.uc_id]

        self.tree_view.remove_tree_item(
            self.uc_id, self.site_id, self.state_id
        )

    def undo(self):
        # Reinsert the item into the model
        if self.state_id:
            unit_cell = self.unit_cells[self.uc_id]
            site = unit_cell.sites[self.site_id]
            site.states[self.item.id] = self.item
            unit_cell.bandstructure.reset_bands()
            unit_cell.bz_grid.clear()
            unit_cell.hoppings.update(self.removed_hoppings)
            self.signal.emit()

        elif self.site_id:
            unit_cell = self.unit_cells[self.uc_id]
            unit_cell.sites[self.item.id] = self.item
            if self.removed_hoppings:
                unit_cell.bandstructure.reset_bands()
                unit_cell.bz_grid.clear()
            unit_cell.hoppings.update(self.removed_hoppings)
            # If the site has states, request a redraw of the hopping matrix
            if self.unit_cells[self.uc_id].sites[self.site_id].states:
                self.signal.emit()
        elif self.uc_id:
            self.unit_cells[self.item.id] = self.item

        # Refresh the tree and select the item
        self.tree_view.refresh_tree(self.unit_cells)
        index = self.tree_view.find_item_by_id(
            uc_id=self.uc_id, site_id=self.site_id, state_id=self.state_id
        ).index()
        self.tree_view.selectionModel().setCurrentIndex(
            index, QItemSelectionModel.ClearAndSelect
        )

TiBi.logic.commands.RenameTreeItemCommand

Bases: QUndoCommand

Change the name of a tree item in the unit cells model.

The name is changed by double-clicking on an item in the tree view.

Attributes:

Name Type Description
unit_cells dict[UUID, UnitCell]

Dictionary mapping UUIDs to UnitCell objects

selection Selection

Dictionary containing the current selection

tree_view SystemTree

UI object containing the tree view

signal Signal

Signal to be emitted when the command is executed

item QStandardItem

The item in the tree view that was changed

uc_id UUID

UUID of the selected UnitCell when the command was issued

site_id UUID

UUID of the selected Site when the command was issued

state_id UUID

UUID of the selected State when the command was issued

old_name str

The old name of the item before the change

new_name str

The new name of the item after the change

Source code in TiBi/logic/commands/tree_commands.py
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
class RenameTreeItemCommand(QUndoCommand):
    """
    Change the name of a tree item in the unit cells model.

    The name is changed by double-clicking on an item in the tree view.

    Attributes
    ----------
    unit_cells : dict[uuid.UUID, UnitCell]
        Dictionary mapping UUIDs to `UnitCell` objects
    selection : Selection
        Dictionary containing the current selection
    tree_view : SystemTree
        UI object containing the tree view
    signal : Signal
        Signal to be emitted when the command is executed
    item : QStandardItem
        The item in the tree view that was changed
    uc_id : uuid.UUID
        UUID of the selected `UnitCell` when the command was issued
    site_id : uuid.UUID
        UUID of the selected `Site` when the command was issued
    state_id : uuid.UUID
        UUID of the selected `State` when the command was issued
    old_name : str
        The old name of the item before the change
    new_name : str
        The new name of the item after the change
    """

    def __init__(
        self,
        unit_cells: dict[uuid.UUID, UnitCell],
        selection: Selection,
        tree_view: SystemTree,
        signal: Signal,
        item: QStandardItem,
    ):
        super().__init__("Rename Tree Item")

        self.unit_cells = unit_cells
        self.selection = selection
        self.tree_view = tree_view
        self.signal = signal
        self.new_name = item.text()

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

        # Get the old name
        if self.state_id:
            self.old_name = (
                self.unit_cells[self.uc_id]
                .sites[self.site_id]
                .states[self.state_id]
                .name
            )
        elif self.site_id:
            self.old_name = (
                self.unit_cells[self.uc_id].sites[self.site_id].name
            )
        else:
            self.old_name = self.unit_cells[self.uc_id].name

    def redo(self):
        item = self.tree_view.find_item_by_id(
            self.uc_id, self.site_id, self.state_id
        )
        if self.state_id:
            self.unit_cells[self.uc_id].sites[self.site_id].states[
                self.state_id
            ].name = self.new_name

        elif self.site_id:
            self.unit_cells[self.uc_id].sites[
                self.site_id
            ].name = self.new_name
        else:
            self.unit_cells[self.uc_id].name = self.new_name

        item.setText(self.new_name)
        self.signal.emit()

    def undo(self):
        item = self.tree_view.find_item_by_id(
            self.uc_id, self.site_id, self.state_id
        )
        if self.state_id:
            self.unit_cells[self.uc_id].sites[self.site_id].states[
                self.state_id
            ].name = self.old_name

        elif self.site_id:
            self.unit_cells[self.uc_id].sites[
                self.site_id
            ].name = self.old_name
        else:
            self.unit_cells[self.uc_id].name = self.old_name

        item.setText(self.old_name)
        self.signal.emit()

Hopping Commands

TiBi.logic.commands.SaveHoppingsCommand

Bases: QUndoCommand

Save the hoppings between two states.

Update the entry in the hoppings dictionary of the selected unit for the selected pair of states.

Attributes:

Name Type Description
unit_cells dict[UUID, UnitCell]

Reference to the dictionary mapping UUIDs to UnitCell objects

selection Selection

Reference to the dictionary containing the current selection

uc_id UUID

UUID of the selected UnitCell when the command was issued

site_id UUID

UUID of the selected Site when the command was issued

state_id UUID

UUID of the selected State when the command was issued

pair_selection list[Tuple[str, UUID, str, UUID]]

Reference to the list of selected States

s1, s2 Tuple[str, UUID, str, UUID]

Information tuples for the selected States, containing (site name, site UUID, state name, state UUID) when the command was issued

new_hoppings list[Tuple[Tuple[int, int, int], complex128]]

List of new hoppings to be added to the hoppings dictionary

old_hoppings list[Tuple[Tuple[int, int, int], complex128]]

List of old hoppings to be removed from the hoppings dictionary

signal Signal

Signal to be emitted when the command is executed. The signal carries the information about the selected UnitCell, Site, State, and the selected pair of States.

Source code in TiBi/logic/commands/hopping_commands.py
 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
class SaveHoppingsCommand(QUndoCommand):
    """
    Save the hoppings between two states.

    Update the entry in the `hoppings` dictionary of the selected unit
    for the selected pair of states.

    Attributes
    ----------

    unit_cells : dict[uuid.UUID, UnitCell]
        Reference to the dictionary mapping UUIDs to UnitCell objects
    selection : Selection
        Reference to the dictionary containing the current selection
    uc_id : uuid.UUID
        UUID of the selected `UnitCell` when the command was issued
    site_id : uuid.UUID
        UUID of the selected `Site` when the command was issued
    state_id : uuid.UUID
        UUID of the selected `State` when the command was issued
    pair_selection : list[Tuple[str, uuid.UUID, str, uuid.UUID]]
        Reference to the list of selected `State`s
    s1, s2 : Tuple[str, uuid.UUID, str, uuid.UUID]
        Information tuples for the selected `State`s, containing
        (site name, site UUID, state name, state UUID) when the
        command was issued
    new_hoppings : list[Tuple[Tuple[int, int, int], np.complex128]]
        List of new hoppings to be added to the `hoppings` dictionary
    old_hoppings : list[Tuple[Tuple[int, int, int], np.complex128]]
        List of old hoppings to be removed from the `hoppings` dictionary
    signal : Signal
        Signal to be emitted when the command is executed. The signal
        carries the information about the selected `UnitCell`, `Site`,
        `State`, and the selected pair of `State`s.
    """

    def __init__(
        self,
        unit_cells: dict[uuid.UUID, UnitCell],
        selection: Selection,
        pair_selection: list[Tuple[str, uuid.UUID, str, uuid.UUID]],
        new_hoppings: list[Tuple[Tuple[int, int, int], np.complex128]],
        signal: Signal,
    ):
        super().__init__("Modify Hoppings")
        self.unit_cells = unit_cells
        self.selection = selection
        self.uc_id = self.selection.unit_cell
        self.site_id = self.selection.site
        self.state_id = self.selection.state

        # Selected state UUIDs
        self.pair_selection = pair_selection
        self.s1 = pair_selection[0]
        self.s2 = pair_selection[1]

        self.new_hoppings = new_hoppings
        self.old_hoppings = copy.deepcopy(
            self.unit_cells[self.uc_id].hoppings.get(
                (self.s1[3], self.s2[3]), []
            )
        )
        self.signal = signal

    def redo(self):
        # Insert the hoppings into the unit cell model
        if self.new_hoppings == []:
            self.unit_cells[self.uc_id].hoppings.pop(
                (self.s1[3], self.s2[3]), None
            )
        else:
            self.unit_cells[self.uc_id].hoppings[
                (self.s1[3], self.s2[3])
            ] = self.new_hoppings
        self.unit_cells[self.uc_id].bandstructure.reset_bands()
        self.unit_cells[self.uc_id].bz_grid.clear()
        # Emit the signal with appropriate selection parameters
        self.signal.emit(
            self.uc_id, self.site_id, self.state_id, self.s1, self.s2
        )

    def undo(self):
        # Insert the hoppings into the unit cell model
        if self.old_hoppings == []:
            self.unit_cells[self.uc_id].hoppings.pop(
                (self.s1[3], self.s2[3]), None
            )
        else:
            self.unit_cells[self.uc_id].hoppings[
                (self.s1[3], self.s2[3])
            ] = self.old_hoppings
        self.unit_cells[self.uc_id].bandstructure.reset_bands()
        self.unit_cells[self.uc_id].bz_grid.clear()
        # Emit the signal with appropriate selection parameters
        self.signal.emit(
            self.uc_id, self.site_id, self.state_id, self.s1, self.s2
        )

Unit Cell Commands

TiBi.logic.commands.UpdateUnitCellParameterCommand

Bases: QUndoCommand

Update a parameter of the selected UnitCell.

This command is used to update the basis vectors of the unit cell when the user types in the spinbox.

Attributes:

Name Type Description
unit_cells dict[UUID, UnitCell]

Dictionary mapping UUIDs to UnitCell objects

selection Selection

Model tracking the currently selected unit cell, site, and state

vector str

The vector to be updated (v1, v2, or v3)

coordinate str

The coordinate to be updated (x, y, or z)

spinbox QDoubleSpinBox

The spinbox widget used to input the new value

signal Signal

Signal to be emitted when the command is executed, requesting a plot update

uc_id UUID

UUID of the selected UnitCell when the command was issued

old_value float

The old value of the parameter before the change

new_value float

The new value of the parameter after the change

Source code in TiBi/logic/commands/uc_commands.py
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
class UpdateUnitCellParameterCommand(QUndoCommand):
    """
    Update a parameter of the selected `UnitCell`.

    This command is used to update the basis vectors of the unit cell
    when the user types in the spinbox.

    Attributes
    ----------
    unit_cells : dict[uuid.UUID, UnitCell]
        Dictionary mapping UUIDs to `UnitCell` objects
    selection : Selection
        Model tracking the currently selected unit cell, site, and state
    vector : str
        The vector to be updated (v1, v2, or v3)
    coordinate : str
        The coordinate to be updated (x, y, or z)
    spinbox : QDoubleSpinBox
        The spinbox widget used to input the new value
    signal : Signal
        Signal to be emitted when the command is executed,
        requesting a plot update
    uc_id : uuid.UUID
        UUID of the selected `UnitCell` when the command was issued
    old_value : float
        The old value of the parameter before the change
    new_value : float
        The new value of the parameter after the change
    """

    def __init__(
        self,
        unit_cells: dict[uuid.UUID, UnitCell],
        selection: Selection,
        vector: str,
        coordinate: str,
        spinbox: QDoubleSpinBox,
        signal: Signal,
    ):
        super().__init__("Update Unit Cell Parameter")
        self.unit_cells = unit_cells
        self.selection = selection
        self.vector = vector
        self.coordinate = coordinate
        self.spinbox = spinbox
        self.signal = signal
        self.new_value = self.spinbox.value()

        self.uc_id = self.selection.unit_cell

        self.old_value = getattr(
            getattr(self.unit_cells[self.uc_id], self.vector),
            self.coordinate,
        )

    def redo(self):
        setattr(
            getattr(self.unit_cells[self.uc_id], self.vector),
            self.coordinate,
            self.new_value,
        )
        self.spinbox.setValue(self.new_value)
        self.unit_cells[self.uc_id].bandstructure.clear()
        self.unit_cells[self.uc_id].bz_grid.clear()
        self.signal.emit()

    def undo(self):
        setattr(
            getattr(self.unit_cells[self.uc_id], self.vector),
            self.coordinate,
            self.old_value,
        )
        self.spinbox.setValue(self.old_value)
        self.unit_cells[self.uc_id].bandstructure.clear()
        self.unit_cells[self.uc_id].bz_grid.clear()
        self.signal.emit()

TiBi.logic.commands.ReduceBasisCommand

Bases: QUndoCommand

Reduce the basis vectors of the selected unit cell.

This method applies the Lenstra-Lenstra-Lovász (LLL) lattice reduction algorithm to find a more orthogonal set of basis vectors that spans the same lattice. This is useful for finding a 'nicer' representation of the unit cell with basis vectors that are shorter and more orthogonal to each other.

The method only affects the periodic directions of the unit cell. After reduction, the UI is updated to reflect the new basis vectors.

Attributes:

Name Type Description
unit_cells dict[UUID, UnitCell]

Dictionary mapping UUIDs to UnitCell objects

selection Selection

Model tracking the currently selected unit cell, site, and state

unit_cell_view UnitCellView

UI object containing the unit cell view

signal Signal

Signal to be emitted when the command is executed, requesting a plot update

uc_id UUID

UUID of the selected UnitCell when the command was issued

old_basis list[BasisVector]

The old basis vectors of the unit cell before reduction

new_basis list[BasisVector]

The new basis vectors of the unit cell after reduction

Source code in TiBi/logic/commands/uc_commands.py
 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
class ReduceBasisCommand(QUndoCommand):
    """
    Reduce the basis vectors of the selected unit cell.

    This method applies the Lenstra-Lenstra-Lovász (LLL) lattice
    reduction algorithm to find a more orthogonal set of basis
    vectors that spans the same lattice.
    This is useful for finding a 'nicer' representation of the unit cell
    with basis vectors that are shorter and more orthogonal to each other.

    The method only affects the periodic directions of the unit cell. After
    reduction, the UI is updated to reflect the new basis vectors.

    Attributes
    ----------
    unit_cells : dict[uuid.UUID, UnitCell]
        Dictionary mapping UUIDs to `UnitCell` objects
    selection : Selection
        Model tracking the currently selected unit cell, site, and state
    unit_cell_view : UnitCellView
        UI object containing the unit cell view
    signal : Signal
        Signal to be emitted when the command is executed,
        requesting a plot update
    uc_id : uuid.UUID
        UUID of the selected `UnitCell` when the command was issued
    old_basis : list[BasisVector]
        The old basis vectors of the unit cell before reduction
    new_basis : list[BasisVector]
        The new basis vectors of the unit cell after reduction
    """

    def __init__(
        self,
        unit_cells: dict[uuid.UUID, UnitCell],
        selection: Selection,
        unit_cell_view: UnitCellView,
        signal: Signal,
    ):
        super().__init__("Reduce Basis")
        self.unit_cells = unit_cells
        self.selection = selection
        self.unit_cell_view = unit_cell_view
        self.signal = signal

        self.uc_id = self.selection.unit_cell

        if self.uc_id:
            uc = self.unit_cells[self.uc_id]
            self.old_basis = [uc.v1, uc.v2, uc.v3]
            self.new_basis = uc.reduced_basis()

    def redo(self):
        if self.uc_id:
            uc = self.unit_cells[self.uc_id]
            uc.v1 = self.new_basis[0]
            uc.v2 = self.new_basis[1]
            uc.v3 = self.new_basis[2]

            # Clear focus to avoid conflicts with programmatic
            # filling of the boxes
            focused_widget = QApplication.focusWidget()
            if focused_widget:
                focused_widget.clearFocus()
            QApplication.processEvents()

            self.unit_cell_view.unit_cell_panel.set_basis_vectors(
                uc.v1, uc.v2, uc.v3
            )
            self.unit_cells[self.uc_id].bandstructure.clear()
            self.unit_cells[self.uc_id].bz_grid.clear()
            self.signal.emit()

    def undo(self):
        if self.uc_id:
            uc = self.unit_cells[self.uc_id]
            uc.v1 = self.old_basis[0]
            uc.v2 = self.old_basis[1]
            uc.v3 = self.old_basis[2]

            # Clear focus to avoid conflicts with programmatic
            # filling of the boxes
            focused_widget = QApplication.focusWidget()
            if focused_widget:
                focused_widget.clearFocus()
            QApplication.processEvents()

            self.unit_cell_view.unit_cell_panel.set_basis_vectors(
                uc.v1, uc.v2, uc.v3
            )
            self.unit_cells[self.uc_id].bandstructure.clear()
            self.unit_cells[self.uc_id].bz_grid.clear()
            self.signal.emit()

TiBi.logic.commands.ChangeDimensionalityCommand

Bases: QUndoCommand

Change the dimensionality of the selected UnitCell (0D, 1D, 2D, 3D).

This method is called when the user selects a different dimensionality radio button. It updates the unit cell's periodicity flags and enables/disables appropriate basis vector components based on the selected dimensionality.

For example: - 0D: All directions are non-periodic (isolated system) - 1D: First direction is periodic, others are not - 2D: First and second directions are periodic, third is not - 3D: All directions are periodic (fully periodic crystal)

Attributes:

Name Type Description
unit_cells dict[UUID, UnitCell]

Dictionary mapping UUIDs to UnitCell objects

selection Selection

Model tracking the currently selected unit cell, site, and state

unit_cell_view UnitCellView

UI object containing the unit cell view

signal Signal

Signal to be emitted when the command is executed, requesting a plot update

dim int

The new dimensionality of the unit cell (0, 1, 2, or 3)

buttons list[QRadioButton]

List of radio buttons corresponding to the dimensionality options in the UI

uc_id UUID

UUID of the selected UnitCell when the command was issued

old_v1 BasisVector

The old basis vector 1 of the unit cell before the change

old_v2 BasisVector

The old basis vector 2 of the unit cell before the change

old_v3 BasisVector

The old basis vector 3 of the unit cell before the change

old_dim int

The old dimensionality of the unit cell before the change

new_v1 BasisVector

The new basis vector 1 of the unit cell after the change

new_v2 BasisVector

The new basis vector 2 of the unit cell after the change

new_v3 BasisVector

The new basis vector 3 of the unit cell after the change

Source code in TiBi/logic/commands/uc_commands.py
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
class ChangeDimensionalityCommand(QUndoCommand):
    """
    Change the dimensionality of the selected `UnitCell` (0D, 1D, 2D, 3D).

    This method is called when the user selects a different dimensionality
    radio button.
    It updates the unit cell's periodicity flags and enables/disables
    appropriate basis vector components based on
    the selected dimensionality.

    For example:
    - 0D: All directions are non-periodic (isolated system)
    - 1D: First direction is periodic, others are not
    - 2D: First and second directions are periodic, third is not
    - 3D: All directions are periodic (fully periodic crystal)

    Attributes
    ----------
    unit_cells : dict[uuid.UUID, UnitCell]
        Dictionary mapping UUIDs to `UnitCell` objects
    selection : Selection
        Model tracking the currently selected unit cell, site, and state
    unit_cell_view : UnitCellView
        UI object containing the unit cell view
    signal : Signal
        Signal to be emitted when the command is executed,
        requesting a plot update
    dim : int
        The new dimensionality of the unit cell (0, 1, 2, or 3)
    buttons : list[QRadioButton]
        List of radio buttons corresponding to the dimensionality
        options in the UI
    uc_id : uuid.UUID
        UUID of the selected `UnitCell` when the command was issued
    old_v1 : BasisVector
        The old basis vector 1 of the unit cell before the change
    old_v2 : BasisVector
        The old basis vector 2 of the unit cell before the change
    old_v3 : BasisVector
        The old basis vector 3 of the unit cell before the change
    old_dim : int
        The old dimensionality of the unit cell before the change
    new_v1 : BasisVector
        The new basis vector 1 of the unit cell after the change
    new_v2 : BasisVector
        The new basis vector 2 of the unit cell after the change
    new_v3 : BasisVector
        The new basis vector 3 of the unit cell after the change
    """

    def __init__(
        self,
        unit_cells: dict[uuid.UUID, UnitCell],
        selection: Selection,
        unit_cell_view: UnitCellView,
        signal: Signal,
        dim: int,
        buttons: list[QRadioButton],
    ):
        super().__init__("Change dimensionality")
        self.unit_cells = unit_cells
        self.selection = selection
        self.unit_cell_view = unit_cell_view
        self.signal = signal
        self.new_dim = dim
        self.buttons = buttons

        self.uc_id = self.selection.unit_cell
        uc = self.unit_cells[self.uc_id]
        self.old_v1 = uc.v1
        self.old_v2 = uc.v2
        self.old_v3 = uc.v3
        self.old_dim = (
            uc.v1.is_periodic + uc.v2.is_periodic + uc.v3.is_periodic
        )

        if dim == 0:
            self.new_v1 = BasisVector(1, 0, 0, False)
            self.new_v2 = BasisVector(0, 1, 0, False)
            self.new_v3 = BasisVector(0, 0, 1, False)

        elif dim == 1:
            self.new_v1 = BasisVector(self.old_v1.x, 0, 0, True)
            self.new_v2 = BasisVector(0, self.old_v2.y, 0, False)
            self.new_v3 = BasisVector(0, 0, self.old_v3.z, False)

        elif dim == 2:
            self.new_v1 = BasisVector(self.old_v1.x, self.old_v1.y, 0, True)
            self.new_v2 = BasisVector(self.old_v2.x, self.old_v2.y, 0, True)
            self.new_v3 = BasisVector(0, 0, self.old_v3.z, False)

        else:
            self.new_v1 = BasisVector(
                self.old_v1.x, self.old_v1.y, self.old_v1.z, True
            )
            self.new_v2 = BasisVector(
                self.old_v2.x, self.old_v2.y, self.old_v2.z, True
            )
            self.new_v3 = BasisVector(
                self.old_v3.x, self.old_v3.y, self.old_v3.z, True
            )

    def redo(self):
        self._set_vector_enables(self.new_dim)

        uc = self.unit_cells[self.uc_id]
        uc.v1 = self.new_v1
        uc.v2 = self.new_v2
        uc.v3 = self.new_v3

        self.unit_cell_view.unit_cell_panel.set_basis_vectors(
            uc.v1, uc.v2, uc.v3
        )

        self._set_checked_button(self.new_dim)
        self.unit_cells[self.uc_id].bandstructure.clear()
        self.unit_cells[self.uc_id].bz_grid.clear()
        self.signal.emit()

    def undo(self):

        self._set_vector_enables(self.old_dim)

        uc = self.unit_cells[self.uc_id]
        uc.v1 = self.old_v1
        uc.v2 = self.old_v2
        uc.v3 = self.old_v3

        self.unit_cell_view.unit_cell_panel.set_basis_vectors(
            uc.v1, uc.v2, uc.v3
        )

        self._set_checked_button(self.old_dim)
        self.unit_cells[self.uc_id].bandstructure.clear()
        self.unit_cells[self.uc_id].bz_grid.clear()
        self.signal.emit()

    def _set_vector_enables(self, dim):
        """
        Enable or disable the basis vector components.

        The enabling/disabling is based on the
        dimensionality of the unit cell.

        Parameters
        ----------
        dim : int
            The new dimensionality of the unit cell (0, 1, 2, or 3)
        """
        self.unit_cell_view.unit_cell_panel.v1[0].setEnabled(True)
        self.unit_cell_view.unit_cell_panel.v1[1].setEnabled(dim > 1)
        self.unit_cell_view.unit_cell_panel.v1[2].setEnabled(dim > 2)

        self.unit_cell_view.unit_cell_panel.v2[0].setEnabled(dim > 1)
        self.unit_cell_view.unit_cell_panel.v2[1].setEnabled(True)
        self.unit_cell_view.unit_cell_panel.v2[2].setEnabled(dim > 2)

        self.unit_cell_view.unit_cell_panel.v3[0].setEnabled(dim > 2)
        self.unit_cell_view.unit_cell_panel.v3[1].setEnabled(dim > 2)
        self.unit_cell_view.unit_cell_panel.v3[2].setEnabled(True)

    def _set_checked_button(self, dim):
        """
        Set the radio button corresponding to the dimensionality.

        The radio button is checked and all others are unchecked.
        This is done by blocking signals to avoid triggering
        the button's clicked signal when setting the checked state.

        Parameters
        ----------
        dim : int
            The new dimensionality of the unit cell (0, 1, 2, or 3)
        """
        for btn in self.buttons:
            btn.blockSignals(True)
        self.buttons[dim].setChecked(True)
        for btn in self.buttons:
            btn.blockSignals(False)

TiBi.logic.commands.UpdateSiteParameterCommand

Bases: QUndoCommand

Update a parameter of the selected Site.

This command is used to update the basis vectors of the site when the user types in the spinbox.

Attributes:

Name Type Description
unit_cells dict[UUID, UnitCell]

Dictionary mapping UUIDs to UnitCell objects

selection Selection

Model tracking the currently selected unit cell, site, and state

param str

The parameter to be updated (radius, color, etc.)

spinbox QDoubleSpinBox

The spinbox widget used to input the new value

signal Signal

Signal to be emitted when the command is executed, requesting a plot update

uc_id UUID

UUID of the selected UnitCell when the command was issued

site_id UUID

UUID of the selected Site when the command was issued

old_value float

The old value of the parameter before the change

new_value float

The new value of the parameter after the change

Source code in TiBi/logic/commands/uc_commands.py
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
class UpdateSiteParameterCommand(QUndoCommand):
    """
    Update a parameter of the selected `Site`.

    This command is used to update the basis vectors of the site
    when the user types in the spinbox.

    Attributes
    ----------
    unit_cells : dict[uuid.UUID, UnitCell]
        Dictionary mapping UUIDs to `UnitCell` objects
    selection : Selection
        Model tracking the currently selected unit cell, site, and state
    param : str
        The parameter to be updated (radius, color, etc.)
    spinbox : QDoubleSpinBox
        The spinbox widget used to input the new value
    signal : Signal
        Signal to be emitted when the command is executed,
        requesting a plot update
    uc_id : uuid.UUID
        UUID of the selected `UnitCell` when the command was issued
    site_id : uuid.UUID
        UUID of the selected `Site` when the command was issued
    old_value : float
        The old value of the parameter before the change
    new_value : float
        The new value of the parameter after the change
    """

    def __init__(
        self,
        unit_cells: dict[uuid.UUID, UnitCell],
        selection: Selection,
        param: str,
        spinbox: QDoubleSpinBox,
        signal: Signal,
    ):
        super().__init__("Update Site Parameter")
        self.unit_cells = unit_cells
        self.selection = selection
        self.param = param
        self.spinbox = spinbox
        self.signal = signal
        self.new_value = self.spinbox.value()

        self.uc_id = self.selection.unit_cell
        self.site_id = self.selection.site

        self.old_value = getattr(
            self.unit_cells[self.uc_id].sites[self.site_id],
            self.param,
        )

    def redo(self):
        setattr(
            self.unit_cells[self.uc_id].sites[self.site_id],
            self.param,
            self.new_value,
        )
        self.spinbox.setValue(self.new_value)
        self.signal.emit()

    def undo(self):
        setattr(
            self.unit_cells[self.uc_id].sites[self.site_id],
            self.param,
            self.old_value,
        )
        self.spinbox.setValue(self.old_value)
        self.signal.emit()

TiBi.logic.commands.ChangeSiteColorCommand

Bases: QUndoCommand

Change the color of the selected Site.

Attributes:

Name Type Description
unit_cells dict[UUID, UnitCell]

Dictionary mapping UUIDs to UnitCell objects

selection Selection

Model tracking the currently selected unit cell, site, and state

new_color QColor

The new color to be set for the site

old_color QColor

The old color of the site before the change

unit_cell_view UnitCellView

UI object containing the unit cell view

signal Signal

Signal to be emitted when the command is executed, requesting a plot update

Source code in TiBi/logic/commands/uc_commands.py
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
class ChangeSiteColorCommand(QUndoCommand):
    """
    Change the color of the selected `Site`.

    Attributes
    ----------
    unit_cells : dict[uuid.UUID, UnitCell]
        Dictionary mapping UUIDs to `UnitCell` objects
    selection : Selection
        Model tracking the currently selected unit cell, site, and state
    new_color : QColor
        The new color to be set for the site
    old_color : QColor
        The old color of the site before the change
    unit_cell_view : UnitCellView
        UI object containing the unit cell view
    signal : Signal
        Signal to be emitted when the command is executed,
        requesting a plot update
    """

    def __init__(
        self,
        unit_cells: dict[uuid.UUID, UnitCell],
        selection: Selection,
        new_color: QColor,
        old_color: QColor,
        unit_cell_view: UnitCellView,
        signal: Signal,
    ):
        super().__init__("Change Site Color")
        self.unit_cells = unit_cells
        self.selection = selection
        self.new_color = new_color
        self.old_color = old_color
        self.unit_cell_view = unit_cell_view
        self.signal = signal

    def redo(self):
        self._set_color(self.new_color)
        self.signal.emit()

    def undo(self):
        self._set_color(self.old_color)
        self.signal.emit()

    def _set_color(self, color):
        rgba = (
            f"rgba({color.red()}, "
            f"{color.green()}, "
            f"{color.blue()}, "
            f"{color.alpha()})"
        )
        self.unit_cell_view.site_panel.color_picker_btn.setStyleSheet(
            f"background-color: {rgba};"
        )

        # Update the color in the dictionary (0-1 scale)
        self.unit_cells[self.selection.unit_cell].sites[
            self.selection.site
        ].color = (
            color.redF(),
            color.greenF(),
            color.blueF(),
            color.alphaF(),
        )

Serialization

TiBi.logic.serialization.UnitCellEncoder

Bases: JSONEncoder

Custom JSON encoder for UnitCell objects and their components.

Handles serialization of custom types to JSON-compatible formats: - UUID objects are converted to strings - NumPy complex values are converted to [real, imag] lists - NumPy arrays are converted to lists - BasisVector, State, Site, and UnitCell objects are converted to dicts - Dictionaries with UUID keys are converted to dicts with string keys - Tuples with UUID elements are converted to strings

Source code in TiBi/logic/serialization/serialization.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
class UnitCellEncoder(json.JSONEncoder):
    """
    Custom JSON encoder for UnitCell objects and their components.

    Handles serialization of custom types to JSON-compatible formats:
    - UUID objects are converted to strings
    - NumPy complex values are converted to [real, imag] lists
    - NumPy arrays are converted to lists
    - BasisVector, State, Site, and UnitCell objects are converted to dicts
    - Dictionaries with UUID keys are converted to dicts with string keys
    - Tuples with UUID elements are converted to strings
    """

    def default(self, obj):
        # Handle UUID objects
        if isinstance(obj, uuid.UUID):
            return str(obj)

        # Handle NumPy complex numbers
        if isinstance(obj, complex) or np.issubdtype(
            type(obj), np.complexfloating
        ):
            return [obj.real, obj.imag]

        # Handle NumPy arrays
        if isinstance(obj, np.ndarray):
            return obj.tolist()

        # Handle BandStructure objects
        if isinstance(obj, BandStructure):
            return {
                "type": "BandStructure",
                "path": [x.tolist() for x in obj.path],
                "special_points": [x.tolist() for x in obj.special_points],
                "eigenvalues": [x.tolist() for x in obj.eigenvalues],
                "eigenvectors": [x.tolist() for x in obj.eigenvectors],
            }

        # Handle BrillouinZoneGrid objects
        if isinstance(obj, BrillouinZoneGrid):
            return {
                "type": "BrillouinZoneGrid",
                "is_gamma_centered": obj.is_gamma_centered,
                "grid_divs": list(obj.grid_divs),
                "k_points": [x.tolist() for x in obj.k_points],
                "eigenvalues": [x.tolist() for x in obj.eigenvalues],
                "eigenvectors": [x.tolist() for x in obj.eigenvectors],
            }

        # Handle BasisVector objects
        if isinstance(obj, BasisVector):
            return {
                "type": "BasisVector",
                "x": obj.x,
                "y": obj.y,
                "z": obj.z,
                "is_periodic": obj.is_periodic,
            }

        # Handle State objects
        if isinstance(obj, State):
            return {"type": "State", "name": obj.name, "id": obj.id}

        # Handle Site objects
        if isinstance(obj, Site):
            # Convert states dictionary to have string keys
            states_dict = {str(k): v for k, v in obj.states.items()}
            return {
                "type": "Site",
                "name": obj.name,
                "c1": obj.c1,
                "c2": obj.c2,
                "c3": obj.c3,
                "R": obj.R,
                "color": obj.color,
                "states": states_dict,
                "id": obj.id,
            }

        # Handle UnitCell objects
        if isinstance(obj, UnitCell):
            # Convert sites dictionary to have string keys
            sites_dict = {str(k): v for k, v in obj.sites.items()}

            # Convert hoppings dictionary with tuple keys to string keys
            hoppings_dict = {
                f"{str(k[0])},{str(k[1])}": v for k, v in obj.hoppings.items()
            }

            return {
                "type": "UnitCell",
                "name": obj.name,
                "v1": obj.v1,
                "v2": obj.v2,
                "v3": obj.v3,
                "sites": sites_dict,
                "hoppings": hoppings_dict,
                "bandstructure": obj.bandstructure,
                "bz_grid": obj.bz_grid,
                "id": obj.id,
            }

        # Let the parent class handle all other types
        return super().default(obj)

TiBi.logic.serialization.decode_unit_cell_json(json_obj)

Decode JSON objects into their appropriate custom types.

This is used as the object_hook for json.loads() to deserialize JSON data back into UnitCell, Site, State, and BasisVector objects.

Parameters:

Name Type Description Default
json_obj dict

A dictionary representing a JSON object

required

Returns:

Type Description
The appropriate Python object based on the 'type' field
Source code in TiBi/logic/serialization/serialization.py
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
def decode_unit_cell_json(json_obj: dict[str, Any]) -> Any:
    """
    Decode JSON objects into their appropriate custom types.

    This is used as the object_hook for json.loads() to deserialize
    JSON data back into UnitCell, Site, State, and BasisVector objects.

    Parameters
    ----------
    json_obj : dict
        A dictionary representing a JSON object

    Returns
    -------
    The appropriate Python object based on the 'type' field
    """
    # Check if this is one of our custom types
    if "type" in json_obj:
        obj_type = json_obj["type"]

        # Handle BandStructure
        if obj_type == "BandStructure":
            return BandStructure(
                path=[np.array(x) for x in json_obj["path"]],
                special_points=[
                    np.array(x) for x in json_obj["special_points"]
                ],
                eigenvalues=[np.array(x) for x in json_obj["eigenvalues"]],
                eigenvectors=[
                    np.array(
                        [
                            [
                                complex(real=real, imag=imag)
                                for real, imag in row
                                # Destructure each length-2 list
                            ]
                            for row in mat
                        ],
                        dtype=np.complex128,
                    )
                    for mat in json_obj[
                        "eigenvectors"
                    ]  # Each mat corresponds to a momentum point
                ],
            )
        # Handle BandStructure
        elif obj_type == "BrillouinZoneGrid":
            return BrillouinZoneGrid(
                is_gamma_centered=json_obj["is_gamma_centered"],
                grid_divs=tuple(json_obj["grid_divs"]),
                k_points=[np.array(x) for x in json_obj["k_points"]],
                eigenvalues=[np.array(x) for x in json_obj["eigenvalues"]],
                eigenvectors=[
                    np.array(
                        [
                            [
                                complex(real=real, imag=imag)
                                for real, imag in row
                                # Destructure each length-2 list
                            ]
                            for row in mat
                        ],
                        dtype=np.complex128,
                    )
                    for mat in json_obj[
                        "eigenvectors"
                    ]  # Each mat corresponds to a momentum point
                ],
            )
        # Handle BasisVector
        elif obj_type == "BasisVector":
            return BasisVector(
                x=json_obj["x"],
                y=json_obj["y"],
                z=json_obj["z"],
                is_periodic=json_obj["is_periodic"],
            )

        # Handle State
        elif obj_type == "State":
            return State(name=json_obj["name"], id=uuid.UUID(json_obj["id"]))

        # Handle Site
        elif obj_type == "Site":
            site = Site(
                name=json_obj["name"],
                c1=json_obj["c1"],
                c2=json_obj["c2"],
                c3=json_obj["c3"],
                R=json_obj["R"],
                color=tuple(json_obj["color"]),
                id=uuid.UUID(json_obj["id"]),
            )
            # Convert state dict with string keys back to UUID keys
            for state_id_str, state in json_obj["states"].items():
                site.states[uuid.UUID(state_id_str)] = state
            return site

        # Handle UnitCell
        elif obj_type == "UnitCell":
            unit_cell = UnitCell(
                name=json_obj["name"],
                v1=json_obj["v1"],
                v2=json_obj["v2"],
                v3=json_obj["v3"],
                bandstructure=json_obj["bandstructure"],
                bz_grid=json_obj["bz_grid"],
                id=uuid.UUID(json_obj["id"]),
            )

            # Convert sites dict with string keys back to UUID keys
            for site_id_str, site in json_obj["sites"].items():
                unit_cell.sites[uuid.UUID(site_id_str)] = site

            # Convert hoppings dict with tuple of string keys back to tuple of
            # UUID keys and convert complex values from [real, imag] format
            # back to complex numbers
            for hopping_key_str, hopping_values in json_obj[
                "hoppings"
            ].items():
                # Parse the string key '(uuid1, uuid2)' back to tuple of UUIDs
                k1_str, k2_str = hopping_key_str.split(",")
                key = (uuid.UUID(k1_str), uuid.UUID(k2_str))

                # Convert the hopping values: [(displacement, amplitude), ...]
                converted_values = []
                for displacement_list, amplitude_list in hopping_values:
                    # displacement = (
                    #     displacement_list[0],
                    #     displacement_list[1],
                    #     displacement_list[2],
                    # )
                    displacement = tuple(displacement_list)
                    # Convert [real, imag] list back to complex
                    amplitude = complex(amplitude_list[0], amplitude_list[1])
                    converted_values.append((displacement, amplitude))

                unit_cell.hoppings[key] = converted_values

            return unit_cell

    # If not a custom type, return the object as is
    return json_obj

TiBi.logic.serialization.serialize_unit_cells(unit_cells)

Serialize a dictionary of UnitCell objects to a JSON string.

Parameters:

Name Type Description Default
unit_cells dict[UUID, UnitCell]

Dictionary mapping UUIDs to UnitCell objects

required

Returns:

Type Description
str

JSON string representation of the unit_cells dictionary

Source code in TiBi/logic/serialization/serialization.py
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
def serialize_unit_cells(unit_cells: dict[uuid.UUID, UnitCell]) -> str:
    """
    Serialize a dictionary of `UnitCell` objects to a JSON string.

    Parameters
    ----------
    unit_cells : dict[uuid.UUID, UnitCell]
        Dictionary mapping UUIDs to `UnitCell` objects

    Returns
    -------
    str
        JSON string representation of the unit_cells dictionary
    """
    # Convert dictionary with UUID keys to string keys for JSON serialization
    serializable_dict = {str(k): v for k, v in unit_cells.items()}
    return json.dumps(serializable_dict, cls=UnitCellEncoder, indent=2)

TiBi.logic.serialization.deserialize_unit_cells(json_str)

Deserialize a JSON string back into a dictionary of UnitCell objects.

Parameters:

Name Type Description Default
json_str str

JSON string representation of unit_cells dictionary

required

Returns:

Type Description
dict[UUID, UnitCell]

Dictionary mapping UUIDs to UnitCell objects

Source code in TiBi/logic/serialization/serialization.py
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
def deserialize_unit_cells(json_str: str) -> dict[uuid.UUID, UnitCell]:
    """
    Deserialize a JSON string back into a dictionary of `UnitCell` objects.

    Parameters
    ----------
    json_str : str
        JSON string representation of unit_cells dictionary

    Returns
    -------
    dict[uuid.UUID, UnitCell]
        Dictionary mapping UUIDs to `UnitCell` objects
    """
    # Parse the JSON string with custom object hook
    string_keyed_dict = json.loads(json_str, object_hook=decode_unit_cell_json)

    # Convert string keys back to UUID keys
    return {uuid.UUID(k): v for k, v in string_keyed_dict.items()}

Workers

TiBi.logic.workers.DiagonalizationWorker

Bases: Worker

Worker for diagonalizing a Hamiltonian at multiple k-points.

Attributes:

Name Type Description
hamiltonian_func callable

Function that takes a k-point and returns the Hamiltonian matrix.

k_points list

List of k-points at which to diagonalize the Hamiltonian.

Source code in TiBi/logic/workers/diagonalization_worker.py
 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
class DiagonalizationWorker(Worker):
    """
    Worker for diagonalizing a Hamiltonian at multiple k-points.

    Attributes
    ----------
    hamiltonian_func : callable
        Function that takes a k-point and returns the Hamiltonian matrix.
    k_points : list
        List of k-points at which to diagonalize the Hamiltonian.
    """

    def __init__(self, hamiltonian_func, k_points):
        super().__init__()
        self.hamiltonian_func = hamiltonian_func
        self.k_points = k_points

    def do_work(self):
        """
        Diagonalize the Hamiltonian at the specified k-points.

        At the end of the computation, emit a signal with the results.
        """

        emit_interval = max(len(self.k_points) // 100, 1)

        eigenvalues = []
        eigenvectors = []
        self.progress_updated.emit(0)

        for ii, k in enumerate(self.k_points):
            if self._abort:
                self.task_aborted.emit()
                return

            H = self.hamiltonian_func(k)
            solution = np.linalg.eigh(H)
            eigenvalues.append(solution[0])
            eigenvectors.append(solution[1])

            if ii % emit_interval == 0 or ii == len(self.k_points) - 1:
                self.progress_updated.emit(
                    int((ii + 1) / len(self.k_points) * 100)
                )

        self.task_finished.emit((eigenvalues, eigenvectors, self.k_points))

do_work()

Diagonalize the Hamiltonian at the specified k-points.

At the end of the computation, emit a signal with the results.

Source code in TiBi/logic/workers/diagonalization_worker.py
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
def do_work(self):
    """
    Diagonalize the Hamiltonian at the specified k-points.

    At the end of the computation, emit a signal with the results.
    """

    emit_interval = max(len(self.k_points) // 100, 1)

    eigenvalues = []
    eigenvectors = []
    self.progress_updated.emit(0)

    for ii, k in enumerate(self.k_points):
        if self._abort:
            self.task_aborted.emit()
            return

        H = self.hamiltonian_func(k)
        solution = np.linalg.eigh(H)
        eigenvalues.append(solution[0])
        eigenvectors.append(solution[1])

        if ii % emit_interval == 0 or ii == len(self.k_points) - 1:
            self.progress_updated.emit(
                int((ii + 1) / len(self.k_points) * 100)
            )

    self.task_finished.emit((eigenvalues, eigenvectors, self.k_points))

TiBi.logic.workers.Worker

Bases: QObject

General worker class for performing background tasks.

Attributes:

Name Type Description
_abort bool

Flag indicating whether the worker should abourt its task.

progress_updated Signal

Emitted to update the progress of the task.

task_finished Signal

Emitted when the task is completed successfully.

task_aborted Signal

Emitted when the task is aborted by the user.

Methods:

Name Description
do_work

Abstract method implemented by subclasses to perform the actual work.

request_abort

Set the abort flag to True, signaling the worker to stop its task.

Source code in TiBi/logic/workers/worker.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
class Worker(QObject):
    """
    General worker class for performing background tasks.

    Attributes
    ----------
    _abort : bool
        Flag indicating whether the worker should abourt its task.
    progress_updated : Signal
        Emitted to update the progress of the task.
    task_finished : Signal
        Emitted when the task is completed successfully.
    task_aborted : Signal
        Emitted when the task is aborted by the user.

    Methods
    -------
    do_work() -> None
        Abstract method implemented by subclasses to perform the actual work.
    request_abort() -> None
        Set the abort flag to True, signaling the worker to stop its task.
    """

    progress_updated = Signal(int)
    task_finished = Signal(object)
    task_aborted = Signal()

    def __init__(self):
        super().__init__()
        self._abort = False

    def do_work(self):
        raise NotImplementedError("Subclasses must implement do_work()")

    def request_abort(self):
        """
        Set the abort flag to True, signaling the worker to stop its task.
        """
        self._abort = True

request_abort()

Set the abort flag to True, signaling the worker to stop its task.

Source code in TiBi/logic/workers/worker.py
38
39
40
41
42
def request_abort(self):
    """
    Set the abort flag to True, signaling the worker to stop its task.
    """
    self._abort = True