Coverage for tbkit/graphene.py: 100%
99 statements
« prev ^ index » next coverage.py v7.17.0, created at 2026-10-30 11:04 +0000
« prev ^ index » next coverage.py v7.17.0, created at 2026-10-30 11:04 +0000
1from __future__ import annotations
3from tbkit.lattice import *
4from tbkit.plot import *
5from tbkit.system import *
6from math import sqrt
8PI = np.pi
9ATOL = 1e-3
10DX = 0.5 * sqrt(3)
11DY = 0.5
14#################################
15# CLASS GRAPHENE
16#################################
19class GrapheneLattice(Lattice):
20 r'''
21 Honeycomb lattice of graphene, with flake shapes.
23 Two sublattices, 'a' at :math:`(0, 0)` and 'b' at
24 :math:`(\sqrt3/2, 1/2)`, and primitive vectors
25 :math:`\mathbf{a}_1 = (\sqrt3, 0)` and
26 :math:`\mathbf{a}_2 = (\sqrt3/2, 3/2)`, so the nearest-neighbour
27 distance is 1. It is a :class:`Lattice` with methods that cut flakes
28 of the usual shapes: *triangle_zigzag*, *hexagon_zigzag*,
29 *triangle_armchair*, *hexagon_armchair*, *square* and *circle*.
31 *butterfly* and *betas* are empty here; *GrapheneSystem.get_butterfly*
32 fills the ones of the system.
34 Examples
35 --------
36 Example usage::
38 from tbkit.graphene import GrapheneLattice, GrapheneSystem
40 lat = GrapheneLattice()
41 lat.hexagon_zigzag(n=4)
42 sys = GrapheneSystem(lat)
43 sys.set_hopping([{'n': 1, 't': 1.}])
44 '''
46 def __init__(self) -> None:
47 unit_cell = [{'tag': 'a', 'r0': (0, 0)},
48 {'tag': 'b', 'r0': (DX, DY)}]
49 prim_vec = [(2*DX, 0.), (DX, 1.5)]
50 Lattice.__init__(self, unit_cell=unit_cell, prim_vec=prim_vec)
51 self.butterfly = np.array([])
52 self.betas = np.array([])
54 def triangle_zigzag(self, n: int) -> None:
55 '''
56 Triangular flake with zigzag terminations.
58 Parameters
59 ----------
60 n : Int
61 Number of plaquettes along the edges.
62 '''
63 error_handling.positive_int(n, 'n')
64 self.get_lattice(n1=n+2, n2=n+2)
65 self.boundary_line(cx=-sqrt(3), cy=-1, co=-3*n+2)
67 def hexagon_zigzag(self, n: int) -> None:
68 '''
69 Hexagonal flake with zigzag terminations.
71 Parameters
72 ----------
73 n : Int
74 Number of plaquettes along the edges.
75 '''
76 error_handling.positive_int(n, 'n')
77 self.get_lattice(n1=2*n, n2=2*n)
78 self.boundary_line(cx=sqrt(3), cy=1, co=3*(n-1))
79 self.boundary_line(cx=-sqrt(3), cy=-1, co=-9*n+2.5)
81 def triangle_armchair(self, n: int) -> None:
82 '''
83 Triangular flake with armchair terminations.
85 Parameters
86 ----------
87 n : Int
88 Number of plaquettes along the edges.
89 '''
90 error_handling.positive_int(n, 'n')
91 self.get_lattice(n1=2*n, n2=2*n)
92 self.boundary_line(cx=1, cy=0, co=sqrt(3)/2*(2*n-1)-0.1)
93 self.boundary_line(cx=-1/sqrt(3), cy=1, co=-n-0.1)
94 self.boundary_line(cx=-1/sqrt(3), cy=-1, co=-4*n+0.1)
96 def hexagon_armchair(self, n: int) -> None:
97 '''
98 Hexagonal flake with armchair terminations.
100 Parameters
101 ----------
102 n : Int
103 Number of plaquettes along each edge.
104 '''
105 error_handling.positive_int(n, 'n')
106 nn = 3 * n - 2
107 self.get_lattice(n1=2*nn, n2=2*nn)
108 self.boundary_line(cx=1, cy=0, co=sqrt(3)/2* (2*nn-1)-.1)
109 self.boundary_line(cx=-1/sqrt(3), cy=1, co=-nn -.1)
110 self.boundary_line(cx=-1/sqrt(3), cy=-1, co=-4*nn +1-.1)
111 self.coor['x'] -= self.coor['x'].min()
112 self.boundary_line(cx=-1, cy=0, co=-DX * 2*nn-0.1)
113 self.boundary_line(cx=1/sqrt(3), cy=1, co=3*n -3.)
114 self.boundary_line(cx=1/sqrt(3), cy=-1, co=-6*n+4)
116 def square(self, n: int) -> None:
117 '''
118 Squared flake.
120 Parameters
121 ----------
122 n : Int
123 Number of plaquettes along x.
124 '''
125 error_handling.positive_int(n, 'n')
126 n2 = int(1.5*DX*n)
127 self.get_lattice(n1=2*n, n2=n2)
128 self.boundary_line(cx=1, cy=0, co=DX*(2*n-2)+0.1)
129 self.boundary_line(cx=-1, cy=0, co=-DX*(4*n)+0.5)
131 def circle(self, n: int) -> None:
132 '''
133 Circular flake.
135 Parameters
136 ----------
137 n : Int
138 Number of plaquettes along the diameter.
139 '''
140 error_handling.positive_int(n, 'n')
141 self.get_lattice(n1=2*n, n2=2*n)
142 self.sites = len(self.coor)
143 self.center()
144 if n % 2 == 0:
145 self.shift_x(shift=-DX)
146 self.ellipse_in(rx=DX*(n+1), ry=DX*(n+1), x0=0., y0=0.)
147 self.remove_dangling()
149class GrapheneSystem(System):
150 r'''
151 Graphene :class:`System`, with triaxial strain.
153 Adds to *System* the nearest-neighbour hoppings of a linear triaxial
154 strain (*set_hop_linear_strain*), which acts as a uniform
155 pseudo-magnetic field, the range of strains that keeps every hopping
156 positive (*get_beta_lims*), and the spectrum as a function of the strain
157 (*get_butterfly*).
159 Parameters
160 ----------
161 lat : Lattice, usually a :class:`GrapheneLattice` flake
163 Examples
164 --------
165 Example usage::
167 from tbkit.graphene import GrapheneLattice, GrapheneSystem
169 lat = GrapheneLattice()
170 lat.hexagon_zigzag(n=10)
171 lat.center()
172 sys = GrapheneSystem(lat)
173 sys.set_hop_linear_strain(t=1., beta=0.1)
174 sys.get_ham()
175 '''
177 def __init__(self, lat: Lattice) -> None:
178 System.__init__(self, lat)
180 def _strain_projection(
181 self,
182 ) -> tuple[NDArray[np.int64], NDArray[np.int64], NDArray[np.float64], NDArray[np.float64]]:
183 r'''
184 Private method.
186 Get, for every nearest-neighbor bond, the projection
188 .. math::
190 s_{ij} = \hat{\boldsymbol\delta}_{ij}\cdot\mathbf{r}_{ij}
192 of its midpoint :math:`\mathbf{r}_{ij}` on its direction
193 :math:`\hat{\boldsymbol\delta}_{ij}`, oriented from its 'b' site to
194 its 'a' site (the same orientation for all three bond families), so
195 that the strained hopping is :math:`t_{ij} = t(1 + \tfrac14\beta s_{ij})`.
197 Returns
198 -------
199 i, j : Integer ndarrays
200 Bond end points, oriented as in
201 *System.get_bonds*.
202 ang : Real ndarray
203 Bond angles (from *i* to *j*), in degrees,
204 in [0, 180).
205 s : Real ndarray
206 The projections above.
207 '''
208 self.get_distances()
209 i, j, ang = self.get_bonds(1)
210 # direction from the 'b' end to the 'a' end of each bond
211 ang_ba = np.where(self.lat.coor['tag'][i] == 'a', ang - 180., ang)
212 x_center = .5 * (self.lat.coor['x'][i] + self.lat.coor['x'][j])
213 y_center = .5 * (self.lat.coor['y'][i] + self.lat.coor['y'][j])
214 s = (np.cos(PI / 180 * ang_ba) * x_center + np.sin(PI / 180 * ang_ba) * y_center)
215 return i, j, ang, s
217 def set_hop_linear_strain(self, t: complex, beta: float) -> None:
218 r'''
219 Set nearest-neighbor hoppings according to a linear triaxial strain:
221 .. math::
223 t_{ij} = t\left(1 + \tfrac14\beta\,
224 \hat{\boldsymbol\delta}_{ij}\cdot\mathbf{r}_{ij}\right)
226 with :math:`\hat{\boldsymbol\delta}_{ij}` the bond direction (from its
227 'b' site to its 'a' site) and
228 :math:`\mathbf{r}_{ij}` its midpoint. The strain is measured from the
229 coordinate origin, so centre the flake on it (see *lattice.center*)
230 before calling this.
232 Parameters
233 ----------
234 t
235 Hopping value without strain.
236 beta
237 Strength of the strain. See *get_beta_lims* for the
238 range that keeps every hopping positive.
239 '''
240 error_handling.number(t, 't')
241 error_handling.real_number(beta, 'beta')
242 i, j, ang, s = self._strain_projection()
243 self.clear_hopping()
244 self.hop = np.zeros(len(i), dtype=HOP_DTYPE)
245 self.hop['n'] = 1
246 self.hop['i'] = i
247 self.hop['j'] = j
248 self.hop['ang'] = ang
249 self.hop['tag'] = npc.add(self.lat.coor['tag'][i], self.lat.coor['tag'][j])
250 self.hop['t'] = t * (1. + 0.25 * beta * s)
252 def get_butterfly(self, t: complex, N: int) -> None:
253 '''
254 Get the spectrum as a function of the strain *beta* (see
255 *set_hop_linear_strain*), stored in *sys.butterfly*, shape
256 (N, sites), with the strain values in *sys.betas*.
258 Parameters
259 ----------
260 t
261 Unstrained hopping value.
262 N : Positive integer
263 Number of strain values between the
264 minimal and maximal strains given by *get_beta_lims*.
265 '''
266 error_handling.number(t, 't')
267 error_handling.positive_int(N, 'N')
268 beta_lims = self.get_beta_lims()
269 self.betas = np.linspace(beta_lims[0], beta_lims[1], N)
270 self.butterfly = np.zeros((N, self.lat.sites))
271 for i, beta in enumerate(self.betas):
272 self.set_hop_linear_strain(t=t, beta=beta)
273 self.get_ham()
274 self.butterfly[i] = LA.eigvalsh(self.ham.toarray())
276 def get_beta_lims(self) -> NDArray[np.float64]:
277 r'''
278 Get the extremal strain values keeping every hopping positive.
280 Under *set_hop_linear_strain* a bond's amplitude is
281 :math:`t(1+\tfrac14\beta s_{ij})`, so it stays positive for every bond
282 iff :math:`-4/\max_{ij} s_{ij} < \beta < -4/\min_{ij} s_{ij}`.
284 Returns
285 -------
286 beta_lims : Real ndarray of length 2, ``[beta_min, beta_max]`` (ascending)
287 A bound is infinite if the corresponding
288 :math:`s_{ij}` never takes that sign.
289 '''
290 _, _, _, s = self._strain_projection()
291 eps = 1e-6
292 s_min, s_max = s.min(), s.max()
293 beta_min = -4. / s_max + eps if s_max > 0 else -np.inf
294 beta_max = -4. / s_min - eps if s_min < 0 else np.inf
295 return np.array([beta_min, beta_max])
298# Backward-compatible camelCase aliases (pre-0.2 API).
299grapheneLat = GrapheneLattice
300grapheneSys = GrapheneSystem