Fractal

In this tutorial, we will take the Sierpiński carpet as an example and show the procedure of constructing fractals. Generally, fractals can be constructed in two approaches, namely bottom-up and top-down, as demonstrated in Fig. 1. The bottom-up approach builds the fractal by iteratively replicating the fractal of low iteration number following some specific pattern. On the contrary, the top-down approach builds a large model at first, then recursively removes unnecessary orbitals and hopping terms following the pattern. Both approaches can be implemented with TBPLaS, while the top-down approach is faster.

../../_images/fractal1.png

Schematic plot of constructing Sierpiński carpet with \(S=2\), \(L=3\) and \(I=2\) in (a)-(c) bottom-up and (d)-(f) top-down approaches. The dashed squares in (a)-(c) and filled squares in (d)-(f) indicate the void areas in the fractal.

The scripts of this tutorial are located at examples/advanced/fractal. We begin with importing the necessary packages:

import numpy as np

import tbplas as tb

Top-down approach

For the top-down approach, we also need to import the Box and Mask classes from the mask module located at the same directory as the scripts:

from mask import Box, Mask

The Box class represents a rectangular area spanning from \([i_0, j_0]\) to \((i_1, j_1)\). If the box is marked as void, then the orbitals inside it will be removed. The Mask class is a collection of boxes, which recursively partitions them into smaller boxes and marks the central boxes as void. It offers the etch_prim_cell function to produce the fractal by removing orbitals falling into void boxes at the PrimitiveCell level, and the etch_super_cell at SuperCell level. In this tutorial, we will build the fractal at PrimitiveCell level. The usage of etch_super_cell can be found in the example scripts.

We define the following function to build the fractal in top-down approach:

 1def top_down(prim_cell: tb.PrimitiveCell, start_width: int,
 2             iteration: int, extension: int) -> tb.PrimitiveCell:
 3    """
 4    Build fractal in top-down approach.
 5
 6    :param prim_cell: primitive cell of square lattice
 7    :param start_width: starting width of the sample
 8    :param iteration: iteration number of sample
 9    :param extension: extension of the sample width
10    :return: fractal
11    """
12    # Create the extended cell
13    final_width = start_width * extension**iteration
14    extended_cell = tb.extend_prim_cell(prim_cell,
15                                        dim=(final_width, final_width, 1))
16    extended_cell.apply_pbc((False, False, False))
17
18    # Create the mask
19    start_box = Box(0, 0, final_width - 1, final_width - 1)
20    mask = Mask(start_box, num_grid=extension, num_iter=iteration)
21
22    # Remove orbitals
23    mask.etch_prim_cell(extended_cell, final_width)
24    return extended_cell

The Sierpiński carpet is characterized by 3 parameters: the starting width \(S\), the extension \(L\) which controls the pattern, and the iteration number \(I\), as shown in Fig. 1. We extend the square primitive cell to the final width of the carpet in line 13-16, which is determined as \(D = S \cdot L^I\). Then we create a box covering the whole extended cell and a mask from the box in line 19-20. The bottom-left corner of the box is located at \([0, 0]\), while the top-right corner is at \((D-1, D-1)\). Then we call the etch_prim_cell function to remove the orbitals falling into void boxes of the mask in line 23. Finally, the fractal is returned.

Bottom-up approach

We define the following function to build the fractal in bottom-up approach:

 1def bottom_up(prim_cell: tb.PrimitiveCell, start_width: int,
 2              iteration: int, extension: int) -> tb.PrimitiveCell:
 3    """
 4    Build fractal in bottom-up approach.
 5
 6    :param prim_cell: primitive cell of square lattice
 7    :param start_width: starting width of the sample
 8    :param iteration: iteration number of sample
 9    :param extension: extension of the sample width
10    :return: fractal
11    """
12    # Create the extended cell
13    final_width = start_width * extension**iteration
14    extended_cell = tb.extend_prim_cell(prim_cell,
15                                        dim=(final_width, final_width, 1))
16    extended_cell.apply_pbc((False, False, False))
17
18    # Build 0-th order fractal
19    fractal = [(ia, ib)
20               for ia in range(start_width)
21               for ib in range(start_width)]
22
23    # Build pattern for replication
24    pattern = [(ia, ib)
25               for ia in range(extension)
26               for ib in range(extension)
27               if not (1 <= ia < extension-1 and 1 <= ib < extension-1)]
28
29    # Build n-th order fractal by replicating (n-1)-th order according to
30    # pattern, which is a direct product mathematically
31    for i in range(iteration):
32        fractal_new = []
33        width = start_width * extension**i
34        for entry in pattern:
35            di = width * entry[0]
36            dj = width * entry[1]
37            replica = [(grid[0] + di, grid[1] + dj) for grid in fractal]
38            fractal_new.extend(replica)
39        fractal = fractal_new
40
41    # Get grid coordinates of vacancies
42    full_sites = [(ia, ib)
43                  for ia in range(final_width)
44                  for ib in range(final_width)]
45    vacancies = list(set(full_sites).difference(set(fractal)))
46    vacancies = [(grid[0], grid[1]) for grid in vacancies]
47
48    # Create the model
49    masked_id_pc = [i[0] * final_width + i[1] for i in vacancies]
50    masked_id_pc = sorted(masked_id_pc)
51    extended_cell.remove_orbitals(masked_id_pc)
52    return extended_cell

Similar to the top-down approach, we also need to extend the primitive cell in line 13-16. Then we build the 0-th order of fractal and the replication pattern in line 19-27. After that, we replicate the fractal according to the pattern iteratively to get the final fractal, which is a list of tuples containing the grid coordinates of the reserved orbitals. Then we get the grid coordinates of the orbitals to remove using set operations. Finally, the orbitals are removed by calling remove_orbitals method of PrimitiveCell and the fractal is returned.

Build the model

We demonstrate the usage of top-down and bottom-up approaches by:

 1def main():
 2    # Create a square lattice
 3    lattice = np.eye(3, dtype=np.float64)
 4    prim_cell = tb.PrimitiveCell(lattice)
 5    prim_cell.add_orbital((0, 0))
 6    prim_cell.add_hopping((1, 0), 0, 0, 1.0)
 7    prim_cell.add_hopping((0, 1), 0, 0, 1.0)
 8    prim_cell.add_hopping((1, 1), 0, 0, 1.0)
 9    prim_cell.add_hopping((1, -1), 0, 0, 1.0)
10
11    # Create fractal using top-down approach
12    fractal = top_down(prim_cell, 2, 3, 3)
13    fractal.plot(with_cells=False, with_orbitals=False, hop_as_arrows=False)
14
15    # Create fractal using bottom-up approach
16    fractal = bottom_up(prim_cell, 2, 3, 3)
17    fractal.plot(with_cells=False, with_orbitals=False, hop_as_arrows=False)
18
19
20if __name__ == "__main__":
21    main()

The output is shown in the following figure:

../../_images/sierpinski.png

Sierpiński carpet with \(S=2\), \(L=3\) and \(I=3\).