Odd-parity magnets toy model

Image 1

考虑一条一维链,原始晶格常数为 ,每个格点包含一个轨道和两个真实自旋自由度。模型的磁性周期为四个原始格点,因此一个磁性单胞包含四个子格

标记磁性单胞,用 标记单胞内子格,即

物理格点编号为$j=4R+s$,相应电子算符记为

磁性单胞长度为$a_{\rm M}=4a$,模型的四个子格局域磁矩取为

它们满足

也就是$\mathbf m_{s+2}=-\mathbf m_s$。因此,平移两个原始格点会使局域磁矩整体反号,这是该模型最关键的实空间结构。

模型哈密顿量写为$H=H_t+H_J$,最近邻自旋无关 hopping 为

用磁性单胞表示,

前三项是磁性单胞内部跃迁,最后一项是跨磁性单胞跃迁。局域交换项为

其中

于是四个子格上的 onsite exchange matrix 分别为

由于磁性周期为 ,动量空间应使用磁性 Brillouin zone。定义无量纲磁性单胞动量

采用元胞周期规范

这个规范只把磁性单胞编号 放入 Fourier 相位,而不把子格内部位置 放入指数中。因此单胞内 hopping 不带动量相位,只有跨单胞 hopping 带

以单胞内 hopping 为例,

利用

得到

因此单胞内 hopping 给出

跨单胞 hopping 为

代入 Bloch 变换:

于是

因此跨单胞项给出

其厄米共轭给出

取 Bloch 基矢

动量空间哈密顿量为

这里每一个矩阵元都是 自旋矩阵,因此 是一个 矩阵。对角化

即可得到能带。第 条能带的自旋期望值定义为

其中完整子格-自旋空间中的自旋算符为$\mathcal S_\alpha
=
I_4\otimes\sigma_\alpha$,因此

如果某个 点存在严格简并,则单条能带的自旋期望值可能依赖于简并子空间中本征矢的选取。此时更严格的做法是在简并子空间内重新对角化自旋算符,或者只讨论非简并区域的自旋纹理。

该模型最重要的对称性有两个。第一是半磁性周期平移与时间反演的组合。定义

由于

单独的 不是磁结构的对称性。真实时间反演 使电子自旋反号:

因此交换项在 下变为

再经过 后,自旋矩阵反号,于是重新回到

所以组合反幺正操作

是体系的对称性,并满足

因为 是半个磁性单胞平移,所以$\tau_2^2=\tau_4$,其中 是完整磁性单胞平移。在 Bloch 态上,完整磁性单胞平移给出相位 ,具体符号取决于 Bloch 变换约定。由于自旋 电子满足

于是

或者等价地写为

物理结论是

因此有 Kramers-like 简并;而

因此不再强制 Kramers-like 简并。也就是说,真实时间反演已经被磁序破坏,但组合反幺正对称性仍然可以在特定动量点保护简并。另一些 TRIM 点则允许简并被打开,这可以理解为 hidden Zeeman field 的能带表现。

第二个关键对称性是空间反演与自旋旋转的组合。取一维反演 ,使其在四子格之间实现

再考虑绕 轴的自旋 旋转$U_z(\pi)$,它对自旋分量的作用为

对磁矩向量也相应有

逐个检查四个磁矩:

因此 交换子格, 旋转自旋,二者组合后保持磁结构不变。这个组合对称性可以写为$\{U_z(\pi)\Vert P\}$,它在动量空间中使$K\rightarrow -K$,同时使自旋分量变换为

因此它给出约束

另一方面, 包含时间反演,因此它使自旋反号,并把 变为 。所以它给出

合并两个对称性的约束。对于 分量,两者都允许

但对于 分量,一个对称性要求

另一个要求

二者同时成立只能给出

因此该模型的动量空间自旋纹理满足

局域磁序本身是非共面的,因为四个 不在同一个平面内;但动量空间能带的自旋纹理被对称性限制在 平面内。因此该 toy model 体现的是

简言之,四子格磁序破坏真实时间反演,但保留 ,使动量空间自旋纹理具有 odd parity;同时保留 ,进一步强制 。因此,该模型不是普通反铁磁链,而是由磁性半平移和自旋-空间组合对称性共同约束的 odd-parity magnetic chain。

代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
#!/usr/bin/env python3
# -*- coding: utf-8 -*-

"""
Band structure of the 1D odd-parity magnets toy model.

Magnetic unit cell:
A, B, C, D

Local moments:
m_A = ( 1, 1, 1) / sqrt(3)
m_B = ( 1, 1, -1) / sqrt(3)
m_C = (-1, -1, -1) / sqrt(3)
m_D = (-1, -1, 1) / sqrt(3)

Hamiltonian:
H = t sum_<ij>,s c^dag_{i s} c_{j s}
+ J sum_i c^dag_i [m_i · sigma] c_i

Momentum:
K = 4 k_x a

Bloch Hamiltonian:
h(K) =
[ M_A t I 0 t e^{-iK} I ]
[ t I M_B t I 0 ]
[ 0 t I M_C t I ]
[ t e^{iK} I 0 t I M_D ]

Outputs:
output_opm_toy_band/data/bands_and_spin.npz
output_opm_toy_band/figures/band_plain.png
output_opm_toy_band/figures/band_spin_x.png
output_opm_toy_band/figures/band_spin_y.png
output_opm_toy_band/figures/band_spin_z.png
"""

import os
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.collections import LineCollection


# ============================================================
# 0. User parameters
# ============================================================

RUN_CALCULATION = True
RUN_PLOTTING = True

# Model parameters
T_HOP = 1.0
JEX = 0.5

# k mesh in magnetic Brillouin zone K in [-pi, pi]
NK = 1001

# Optional chemical-potential guide line
DRAW_MU_LINE = True
MU = -1.0

# Output folders
OUTPUT_DIR = "output_opm_toy_band"
DATA_DIR = os.path.join(OUTPUT_DIR, "data")
FIG_DIR = os.path.join(OUTPUT_DIR, "figures")

SAVE_FIGURES = True
SHOW_FIGURES = True
DPI = 300

# Plot windows
E_WINDOW = (-2.4, 2.4)

# Spin-colored band plot controls
SPIN_CMAP = "bwr"
SPIN_VMIN = -1.0
SPIN_VMAX = 1.0


# ============================================================
# 1. Pauli matrices
# ============================================================

sigma_0 = np.array([[1, 0], [0, 1]], dtype=complex)
sigma_x = np.array([[0, 1], [1, 0]], dtype=complex)
sigma_y = np.array([[0, -1j], [1j, 0]], dtype=complex)
sigma_z = np.array([[1, 0], [0, -1]], dtype=complex)

SIGMA = {
"x": sigma_x,
"y": sigma_y,
"z": sigma_z,
}


# ============================================================
# 2. Magnetic texture
# ============================================================

def local_moments():
"""
Return local magnetic moments in one 4-site magnetic unit cell.

Order:
A, B, C, D
"""
q = 1.0 / np.sqrt(3.0)

moments = np.array([
[ q, q, q], # A
[ q, q, -q], # B
[-q, -q, -q], # C
[-q, -q, q], # D
], dtype=float)

return moments


# ============================================================
# 3. Bloch Hamiltonian
# ============================================================

def bloch_hamiltonian(K, t_hop, jex):
"""
Construct the 8 x 8 Bloch Hamiltonian h(K).

Basis:
(A up, A down,
B up, B down,
C up, C down,
D up, D down)

K = 4 k_x a.
"""
moments = local_moments()
n_sub = len(moments)
dim = 2 * n_sub

H = np.zeros((dim, dim), dtype=complex)

# Onsite exchange: M_s = J m_s · sigma
for s, m in enumerate(moments):
onsite = jex * (
m[0] * sigma_x
+ m[1] * sigma_y
+ m[2] * sigma_z
)

sl = slice(2 * s, 2 * s + 2)
H[sl, sl] += onsite

# Intra-cell hopping: A-B, B-C, C-D
for s in range(n_sub - 1):
sl = slice(2 * s, 2 * s + 2)
sr = slice(2 * (s + 1), 2 * (s + 1) + 2)

H[sl, sr] += t_hop * sigma_0
H[sr, sl] += t_hop * sigma_0

# Inter-cell hopping: D_R -> A_{R+1}
sl_first = slice(0, 2)
sl_last = slice(2 * (n_sub - 1), 2 * n_sub)

H[sl_last, sl_first] += t_hop * np.exp(1j * K) * sigma_0
H[sl_first, sl_last] += t_hop * np.exp(-1j * K) * sigma_0

if not np.allclose(H, H.conj().T, atol=1.0e-12):
raise RuntimeError("Bloch Hamiltonian is not Hermitian.")

return H


def spin_operator(component):
"""
Full spin operator I_4 tensor sigma_component.
"""
n_sub = 4
return np.kron(np.eye(n_sub, dtype=complex), SIGMA[component])


# ============================================================
# 4. Calculation
# ============================================================

def calculate_bands_and_spin():
"""
Calculate eigenvalues and spin expectation values.
"""
K_grid = np.linspace(-np.pi, np.pi, NK)

n_band = 8
energies = np.zeros((NK, n_band), dtype=float)

sx = np.zeros((NK, n_band), dtype=float)
sy = np.zeros((NK, n_band), dtype=float)
sz = np.zeros((NK, n_band), dtype=float)

Sx = spin_operator("x")
Sy = spin_operator("y")
Sz = spin_operator("z")

for ik, K in enumerate(K_grid):
H = bloch_hamiltonian(K, T_HOP, JEX)
evals, evecs = np.linalg.eigh(H)

energies[ik, :] = evals

for ib in range(n_band):
psi = evecs[:, ib]

sx[ik, ib] = np.real(np.vdot(psi, Sx @ psi))
sy[ik, ib] = np.real(np.vdot(psi, Sy @ psi))
sz[ik, ib] = np.real(np.vdot(psi, Sz @ psi))

data = {
"K_grid": K_grid,
"energies": energies,
"sx": sx,
"sy": sy,
"sz": sz,
"T_HOP": np.array([T_HOP]),
"JEX": np.array([JEX]),
"MU": np.array([MU]),
}

return data


# ============================================================
# 5. Data I/O
# ============================================================

def ensure_dirs():
os.makedirs(DATA_DIR, exist_ok=True)
os.makedirs(FIG_DIR, exist_ok=True)


def save_data(data):
ensure_dirs()
fname = os.path.join(DATA_DIR, "bands_and_spin.npz")
np.savez(fname, **data)
print(f"Saved data: {fname}")


def load_data():
fname = os.path.join(DATA_DIR, "bands_and_spin.npz")

if not os.path.exists(fname):
raise FileNotFoundError(
f"Data file not found: {fname}\n"
f"Please set RUN_CALCULATION = True first."
)

raw = np.load(fname)
data = {key: raw[key] for key in raw.files}
return data


# ============================================================
# 6. Plotting style
# ============================================================

def set_plot_style():
plt.rcParams.update({
"font.family": "Times New Roman",
"mathtext.fontset": "stix",
"font.size": 14,
"axes.linewidth": 1.1,
"xtick.direction": "in",
"ytick.direction": "in",
"xtick.top": True,
"ytick.right": True,
"xtick.major.size": 4.5,
"ytick.major.size": 4.5,
"xtick.major.width": 1.0,
"ytick.major.width": 1.0,
"legend.frameon": False,
})


def save_or_show(fig, filename):
if SAVE_FIGURES:
ensure_dirs()
path = os.path.join(FIG_DIR, filename)
fig.savefig(path, dpi=DPI, bbox_inches="tight")
print(f"Saved figure: {path}")

if SHOW_FIGURES:
plt.show()
else:
plt.close(fig)


def colored_band_line(ax, x, y, c, lw=1.2):
"""
Plot one band as a spin-colored line.
"""
points = np.array([x, y]).T.reshape(-1, 1, 2)
segments = np.concatenate([points[:-1], points[1:]], axis=1)

cseg = 0.5 * (c[:-1] + c[1:])

lc = LineCollection(
segments,
array=cseg,
cmap=SPIN_CMAP,
norm=plt.Normalize(vmin=SPIN_VMIN, vmax=SPIN_VMAX),
linewidth=lw,
)

ax.add_collection(lc)
return lc


# ============================================================
# 7. Plot functions
# ============================================================

def setup_band_axis(ax):
ax.set_xlim(-np.pi, np.pi)
ax.set_ylim(*E_WINDOW)

ax.set_xticks([-np.pi, 0.0, np.pi])
ax.set_xticklabels([r"$-\pi$", r"$0$", r"$\pi$"])

ax.set_yticks([-2.0, 0.0, 2.0])

ax.set_xlabel(r"$K=4k_xa$")
ax.set_ylabel(r"$E$")

if DRAW_MU_LINE:
ax.axhline(MU, color="black", lw=0.9, ls="--")

# Helpful high-symmetry markers
ax.axvline(0.0, color="gray", lw=0.7, ls=":", alpha=0.8)
ax.axvline(np.pi, color="gray", lw=0.7, ls=":", alpha=0.8)
ax.axvline(-np.pi, color="gray", lw=0.7, ls=":", alpha=0.8)


def plot_plain_band(data):
K = data["K_grid"]
E = data["energies"]

fig, ax = plt.subplots(figsize=(4.6, 3.5))

for ib in range(E.shape[1]):
ax.plot(K, E[:, ib], color="black", lw=1.2)

setup_band_axis(ax)
ax.set_title(r"Band structure")

fig.tight_layout()
save_or_show(fig, "band_plain.png")


def plot_spin_colored_band(data, component):
K = data["K_grid"]
E = data["energies"]
S = data[f"s{component}"]

fig, ax = plt.subplots(figsize=(4.8, 3.6))

last_lc = None

for ib in range(E.shape[1]):
last_lc = colored_band_line(
ax,
K,
E[:, ib],
S[:, ib],
lw=1.2,
)

setup_band_axis(ax)
ax.set_title(rf"Band structure colored by $s_{component}$")

cbar = fig.colorbar(last_lc, ax=ax, pad=0.025, shrink=0.88)
cbar.set_ticks([-1, 0, 1])
cbar.set_label(rf"$s_{component}$", rotation=90)

fig.tight_layout()
save_or_show(fig, f"band_spin_{component}.png")


def plot_spin_components_near_mu(data):
"""
Plot spin expectation values of the band closest to MU.
Useful for checking odd-parity spin texture.
"""
K = data["K_grid"]
E = data["energies"]

sx = data["sx"]
sy = data["sy"]
sz = data["sz"]

closest = np.argmin(np.abs(E - MU), axis=1)

sx_mu = np.array([sx[i, closest[i]] for i in range(len(K))])
sy_mu = np.array([sy[i, closest[i]] for i in range(len(K))])
sz_mu = np.array([sz[i, closest[i]] for i in range(len(K))])

fig, ax = plt.subplots(figsize=(5.2, 3.5))

ax.plot(K, sx_mu, lw=1.5, label=r"$s_x$")
ax.plot(K, sy_mu, lw=1.5, label=r"$s_y$")
ax.plot(K, sz_mu, lw=1.5, label=r"$s_z$")

ax.axhline(0.0, color="black", lw=0.8)
ax.axvline(0.0, color="gray", lw=0.7, ls=":", alpha=0.8)

ax.set_xlim(-np.pi, np.pi)
ax.set_ylim(-1.05, 1.05)

ax.set_xticks([-np.pi, 0.0, np.pi])
ax.set_xticklabels([r"$-\pi$", r"$0$", r"$\pi$"])

ax.set_yticks([-1.0, 0.0, 1.0])

ax.set_xlabel(r"$K=4k_xa$")
ax.set_ylabel(r"$s_\alpha$")
ax.set_title(r"Spin expectation of the band closest to $\mu$")

ax.legend(loc="best")

fig.tight_layout()
save_or_show(fig, "spin_texture_closest_to_mu.png")


# ============================================================
# 8. Main
# ============================================================

def main():
if RUN_CALCULATION:
print("====================================================")
print("Calculating odd-parity magnets toy-model bands")
print("====================================================")
print(f"T_HOP = {T_HOP}")
print(f"JEX = {JEX}")
print(f"MU = {MU}")
print(f"NK = {NK}")
print("====================================================")

data = calculate_bands_and_spin()
save_data(data)

if RUN_PLOTTING:
set_plot_style()
data = load_data()

plot_plain_band(data)

for comp in ["x", "y", "z"]:
plot_spin_colored_band(data, comp)

plot_spin_components_near_mu(data)


if __name__ == "__main__":
main()
Image 1 Image 1

参考文献

  1. Hidden Zeeman Field in Odd-Parity Magnets: An Ideal Platform for Topological Superconductivity

鉴于该网站分享的大都是学习笔记,作者水平有限,若发现有问题可以发邮件给我

  • yxliphy@gmail.com

也非常欢迎喜欢分享的小伙伴投稿