超导磁电效应

超导线性磁电效应,也叫超导Edelstein效应,描述的是超流驱动产生自旋磁化。超流可以用 Cooper pair 的有限动量 $\mathbf q$ 表示。对于小 $\mathbf q$,自旋磁化可以展开为

其中线性磁电效应定义为

若进一步用超流密度 $\mathbf J_s$ 表示,由于$J_{s,b}=\nu_s q_b$,也可以写成

考虑一般正常态 Bloch哈密顿量$H_{\mathbf k}$,它可以包含轨道、自旋、子格等内部自由度。超导配对矩阵记为$\hat\Delta_{\mathbf k}$,若超导序参量携带空间相位$\Delta(\mathbf r)=\Delta_0 e^{i\mathbf q\cdot \mathbf r}$,则 Cooper对的总动量为 $\mathbf q$。配对的两个电子动量可以写成

于是定义 Nambu 基底

在显式自旋基底下,可以写为

有限 $\mathbf q$ 的 BdG Hamiltonian 为

这里暂时假设 $\hat\Delta_{\mathbf k}$ 不显式依赖 $\mathbf q$。这个近似适用于弱超流,也就是 $\mathbf q$ 足够小的情况。

磁化的热力学定义

为了计算自旋磁化,引入一个辅助 Zeeman 场 $\mathbf h$。电子的 Zeeman 耦合为

其中 $s_a$ 是 Pauli 矩阵,真正的自旋算符是 $s_a/2$。在 Nambu 空间中,相应的广义自旋矩阵定义为

这里空穴块前面的负号来自粒子—-空穴结构。含辅助 Zeeman 场的格林函数写成

Gor’kov 格林函数为

辅助场 $\mathbf h$ 最后要取零。磁化由配分函数对 $\mathbf h$ 求导得到

由于 Nambu 表象中有一半的 double counting,配分函数中出现因子 $1/2$。对 $\mathrm{Tr}\log G^{-1}$ 求导,使用

得到

现在对$\mathcal H_{\rm BdG}(\mathbf k,\mathbf q)$在 $\mathbf q=0$ 附近展开,首先定义没有超流态的 BdG 哈密顿量

正常态速度矩阵定义为

由于电子块是 $H_{\mathbf k+\mathbf q/2}$,所以一阶展开为

空穴块是$-H^*_{-\mathbf k+\mathbf q/2}$,因此一阶展开为

所以 BdG Hamiltonian 的一阶展开可以写成

其中 Nambu 空间速度矩阵为

接下来对格林函数进行展开,定义没有超流态的格林函数

由方程$\eqref{eq:m1}$可得

因此

使用矩阵恒等式

得到

将其代入方程$\eqref{eq:m2}$得到

利用迹的循环不变性

因此

其中

而线性磁电张量为

这就是超导线性磁电效应的 Kubo-like格林函数形式。现在将上式改写成 BdG 能带表象,对零超流态 BdG哈密顿量对角化

这里 $n$ 是 BdG 能带指标。由于 BdG 哈密顿量具有粒子—-空穴对称性,能谱通常成对出现$E_{n\mathbf k}
\leftrightarrow
-E_{n,-\mathbf k}$,但是在下面的推导中,我们直接对所有 BdG 本征态求和,不需要额外区分正能和负能分支。

格林函数可以谱分解为

定义能带表象中的矩阵元

将格林函数的谱分解代入$\mathrm{Tr}
\left[
\eta_aG_0\hat v_bG_0
\right]$中得到

因此线性磁电张量系数为

下面对松原频率求和,定义

当 $n\neq m$ 时,用部分分式分解

结合标准松原频率求和公式

因此

于是

等价的可以表示为

由于 $\alpha_{ab}$ 是实物理量,通常可显式写成

当$E_{n\mathbf k}=E_{m\mathbf k}$时上式需要取极限,特别是$n=m$时

因此对角项为

注意 $f’(E)<0$。低温下,$f’(E)$ 只在 $E\approx 0$ 附近有明显贡献。因此,如果 BdG 谱完全打开能隙,那么这个对角项在 $T\to 0$ 时通常会被指数压低。

能带表象的表达式可以自然分成两部分

对角部分,即 $n=m$,为

这个部分反映 BdG 准粒子能带在有限 $\mathbf q$ 下的占据变化。它类似金属正常态响应中的费米面贡献。

非对角部分,即 $n\neq m$,为

这个部分来自不同 BdG 本征态之间的虚跃迁,类似带间极化率。也可以利用 $n,m$ 的对称性写成 $n<m$ 的形式

Rashba 超导

对于二维 Rashba 超导,正常态哈密顿量可写成

配对取常规的$s$波配对

速度矩阵为

由于 Rashba SOC 使自旋和动量锁定,超流沿 $x$ 方向时,主要诱导 $y$ 方向磁化

超流沿 $y$ 方向时,主要诱导 $x$ 方向磁化

在连续旋转对称的 Rashba 系统中

因此整体上有

由于

也就是

对称性约束

线性磁电效应

对称性上要求反演对称性破缺。因为$\mathbf{q}$是极矢量,在空间反演 $P$ 下变号

而磁化$\mathbf M$是轴矢量,在空间反演下不变

因此如果体系有反演对称性,那么$\mathbf M\propto \mathbf q$这一线性关系被禁止$(\alpha_{ab}=0)$。但是时间反演 $T$ 本身不禁止线性磁电效应,因为

所以$\mathbf M\propto\mathbf q$在时间反演下是允许的。所以Rashba 超导可以在保持时间反演对称性的情况下存在超导Edelstein效应。

程序代码

  • 计算代码

    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
    #!/usr/bin/env julia

    # ============================================================
    # Square-lattice Rashba superconductor
    # Superconducting Edelstein effect
    #
    # MPI parallel version.
    #
    # Output:
    # data/rashba_lattice_edelstein_mpi.dat
    #
    # Tensor:
    # delta M_a = alpha_ab q_b
    #
    # Main Rashba Edelstein component:
    # alpha_yx : q_x -> M_y
    # ============================================================

    using MPI
    using LinearAlgebra
    using Printf
    using Dates

    BLAS.set_num_threads(1)

    # ============================================================
    # 0. MPI initialization
    # ============================================================

    MPI.Init()

    const comm = MPI.COMM_WORLD
    const rank = MPI.Comm_rank(comm)
    const nprocs = MPI.Comm_size(comm)

    # ============================================================
    # 1. Parameters
    # ============================================================

    const t_hop = 1.0
    const mu = -3.0
    const Delta0 = 0.10
    const temperature = 0.03

    # k mesh
    const Nk = 251

    # Rashba SOC scan
    const lambda_min = 0.0
    const lambda_max = 0.60
    const lambda_num = 61

    # Constants, dimensionless units
    const gs = 2.0
    const muB = 1.0

    # Degeneracy tolerance for K_nm
    const degeneracy_eps = 1.0e-10

    # Output
    const data_dir = "data"
    const output_file = joinpath(data_dir, "rashba_lattice_edelstein_mpi.dat")

    # ============================================================
    # 2. Pauli matrices
    # ============================================================

    const s0 = ComplexF64[
    1.0 + 0im 0.0 + 0im
    0.0 + 0im 1.0 + 0im
    ]

    const sx = ComplexF64[
    0.0 + 0im 1.0 + 0im
    1.0 + 0im 0.0 + 0im
    ]

    const sy = ComplexF64[
    0.0 + 0im 0.0 - 1im
    0.0 + 1im 0.0 + 0im
    ]

    const sz = ComplexF64[
    1.0 + 0im 0.0 + 0im
    0.0 + 0im -1.0 + 0im
    ]

    const zero2 = zeros(ComplexF64, 2, 2)

    # ============================================================
    # 3. Small matrix helpers
    # ============================================================

    function block2x2(A::Matrix{ComplexF64}, B::Matrix{ComplexF64},
    C::Matrix{ComplexF64}, D::Matrix{ComplexF64})
    M = zeros(ComplexF64, 4, 4)
    @views begin
    M[1:2, 1:2] .= A
    M[1:2, 3:4] .= B
    M[3:4, 1:2] .= C
    M[3:4, 3:4] .= D
    end
    return M
    end

    # ============================================================
    # 4. Normal-state Rashba lattice Hamiltonian
    # ============================================================

    function h_normal(kx::Float64, ky::Float64, lambda_R::Float64)
    xi = -2.0 * t_hop * (cos(kx) + cos(ky)) - mu
    H = xi .* s0 .+
    lambda_R .* (sin(kx) .* sy .- sin(ky) .* sx)
    return Matrix{ComplexF64}(H)
    end

    function dh_dk(kx::Float64, ky::Float64, lambda_R::Float64, direction::Symbol)
    if direction == :x
    V = 2.0 * t_hop * sin(kx) .* s0 .+
    lambda_R * cos(kx) .* sy
    elseif direction == :y
    V = 2.0 * t_hop * sin(ky) .* s0 .-
    lambda_R * cos(ky) .* sx
    else
    error("direction must be :x or :y")
    end

    return Matrix{ComplexF64}(V)
    end

    # ============================================================
    # 5. BdG Hamiltonian and Nambu operators
    # ============================================================

    function bdg_hamiltonian(kx::Float64, ky::Float64, lambda_R::Float64)
    Hk = h_normal(kx, ky, lambda_R)
    Hmk = h_normal(-kx, -ky, lambda_R)

    Delta = Delta0 .* (1im .* sy)

    HBdG = block2x2(
    Hk,
    Matrix{ComplexF64}(Delta),
    Matrix{ComplexF64}(Delta'),
    -conj.(Hmk),
    )

    return HBdG
    end

    function nambu_velocity(kx::Float64, ky::Float64, lambda_R::Float64, direction::Symbol)
    Ve = dh_dk(kx, ky, lambda_R, direction)
    Vh = -conj.(dh_dk(-kx, -ky, lambda_R, direction))

    Vbdg = block2x2(
    Ve,
    zero2,
    zero2,
    Vh,
    )

    return Vbdg
    end

    function nambu_spin(direction::Symbol)
    s = if direction == :x
    sx
    elseif direction == :y
    sy
    elseif direction == :z
    sz
    else
    error("spin direction must be :x, :y, or :z")
    end

    eta = block2x2(
    Matrix{ComplexF64}(s),
    zero2,
    zero2,
    -conj.(s),
    )

    return eta
    end

    const eta_ops = (
    nambu_spin(:x),
    nambu_spin(:y),
    nambu_spin(:z),
    )

    # ============================================================
    # 6. Fermi function and response kernel
    # ============================================================

    @inline function fermi(E::Float64, beta::Float64)
    x = clamp(beta * E, -700.0, 700.0)
    return 1.0 / (exp(x) + 1.0)
    end

    @inline function fermi_derivative(E::Float64, beta::Float64)
    f = fermi(E, beta)
    return -beta * f * (1.0 - f)
    end

    function response_kernel!(K::Matrix{Float64}, E::Vector{Float64}, beta::Float64)
    nb = length(E)

    @inbounds for n in 1:nb
    En = E[n]
    fn = fermi(En, beta)

    for m in 1:nb
    Em = E[m]
    fm = fermi(Em, beta)

    denom = En - Em

    if abs(denom) > degeneracy_eps
    K[n, m] = (fn - fm) / denom
    else
    K[n, m] = fermi_derivative(0.5 * (En + Em), beta)
    end
    end
    end

    return nothing
    end

    # ============================================================
    # 7. k mesh distribution over MPI ranks
    # ============================================================

    @inline function linear_index_to_k(idx::Int)
    # idx = 1, ..., Nk*Nk
    ix = div(idx - 1, Nk) + 1
    iy = mod(idx - 1, Nk) + 1

    dk = 2.0 * pi / Nk

    # midpoint mesh on [-pi, pi)
    kx = -pi + (ix - 0.5) * dk
    ky = -pi + (iy - 0.5) * dk

    return kx, ky
    end

    function lambda_grid()
    if lambda_num == 1
    return [lambda_min]
    end

    return collect(range(lambda_min, lambda_max, length=lambda_num))
    end

    # ============================================================
    # 8. Local calculation for one lambda_R
    # ============================================================

    function local_accumulate_for_lambda(lambda_R::Float64)
    beta = 1.0 / temperature

    # alpha accumulator: 3 spin components x 2 q directions
    acc_total = zeros(Float64, 3, 2)
    acc_intra = zeros(Float64, 3, 2)
    acc_inter = zeros(Float64, 3, 2)

    nb = 4
    K = zeros(Float64, nb, nb)

    total_k = Nk * Nk

    # MPI distribution over k points
    for idx in (rank + 1):nprocs:total_k
    kx, ky = linear_index_to_k(idx)

    HBdG = bdg_hamiltonian(kx, ky, lambda_R)

    eig = eigen(Hermitian(HBdG))
    E = Vector{Float64}(eig.values)
    U = Matrix{ComplexF64}(eig.vectors)

    response_kernel!(K, E, beta)

    Udag = U'

    eta_band = (
    Udag * eta_ops[1] * U,
    Udag * eta_ops[2] * U,
    Udag * eta_ops[3] * U,
    )

    vx_op = nambu_velocity(kx, ky, lambda_R, :x)
    vy_op = nambu_velocity(kx, ky, lambda_R, :y)

    v_band = (
    Udag * vx_op * U,
    Udag * vy_op * U,
    )

    @inbounds for ia in 1:3
    eta_b = eta_band[ia]

    for ib in 1:2
    v_b = v_band[ib]

    total_val = 0.0
    intra_val = 0.0
    inter_val = 0.0

    for n in 1:nb
    for m in 1:nb
    # eta_nm * v_mn * K_nm
    val = real(eta_b[n, m] * v_b[m, n]) * K[n, m]

    total_val += val

    if n == m
    intra_val += val
    else
    inter_val += val
    end
    end
    end

    acc_total[ia, ib] += total_val
    acc_intra[ia, ib] += intra_val
    acc_inter[ia, ib] += inter_val
    end
    end
    end

    return acc_total, acc_intra, acc_inter
    end

    # ============================================================
    # 9. MPI allreduce helper
    # ============================================================

    function allreduce_sum_matrix(local_mat::Matrix{Float64})
    global_mat = similar(local_mat)
    MPI.Allreduce!(local_mat, global_mat, +, comm)
    return global_mat
    end

    # ============================================================
    # 10. Main calculation
    # ============================================================

    function main()
    lambdas = lambda_grid()

    if rank == 0
    mkpath(data_dir)

    @printf("============================================================\n")
    @printf("Rashba lattice superconducting Edelstein effect\n")
    @printf("Julia MPI calculation\n")
    @printf("============================================================\n")
    @printf("MPI processes = %d\n", nprocs)
    @printf("t = %.8f\n", t_hop)
    @printf("mu = %.8f\n", mu)
    @printf("Delta0 = %.8f\n", Delta0)
    @printf("T = %.8f\n", temperature)
    @printf("Nk = %d x %d\n", Nk, Nk)
    @printf("lambda_R range = [%.8f, %.8f], N = %d\n",
    lambda_min, lambda_max, lambda_num)
    @printf("output = %s\n", output_file)
    @printf("============================================================\n")
    flush(stdout)

    open(output_file, "w") do io
    println(io, "# Square-lattice Rashba superconductor")
    println(io, "# H(k) = xi(k) s0 + lambda_R [sin(kx) sy - sin(ky) sx]")
    println(io, "# xi(k) = -2t [cos(kx)+cos(ky)] - mu")
    println(io, "# Delta = Delta0 i sy")
    println(io, "# Response: delta M_a = alpha_ab q_b")
    println(io, "# Units: a = hbar = kB = muB = 1")
    println(io, "#")
    @printf(io, "# t = %.16e\n", t_hop)
    @printf(io, "# mu = %.16e\n", mu)
    @printf(io, "# Delta0 = %.16e\n", Delta0)
    @printf(io, "# temperature = %.16e\n", temperature)
    @printf(io, "# Nk = %d\n", Nk)
    @printf(io, "# nprocs = %d\n", nprocs)
    println(io, "#")
    println(io, "# columns:")
    println(io, "# lambda_R alpha_xx alpha_xy alpha_yx alpha_yy alpha_zx alpha_zy alpha_yx_intra alpha_yx_inter")
    end
    end

    MPI.Barrier(comm)

    t0 = time()

    for (iλ, λ) in enumerate(lambdas)
    local_total, local_intra, local_inter = local_accumulate_for_lambda(λ)

    global_total_raw = allreduce_sum_matrix(local_total)
    global_intra_raw = allreduce_sum_matrix(local_intra)
    global_inter_raw = allreduce_sum_matrix(local_inter)

    # For BZ = [-pi, pi]^2,
    # ∫BZ d^2k/(2π)^2 equals uniform average over k mesh.
    #
    # Formula:
    # alpha_ab = (gs*muB/8) * average_k sum_nm Re[eta_nm v_mn] K_nm
    prefactor = gs * muB / 8.0
    norm = 1.0 / (Nk * Nk)

    alpha_total = prefactor .* norm .* global_total_raw
    alpha_intra = prefactor .* norm .* global_intra_raw
    alpha_inter = prefactor .* norm .* global_inter_raw

    if rank == 0
    αxx = alpha_total[1, 1]
    αxy = alpha_total[1, 2]
    αyx = alpha_total[2, 1]
    αyy = alpha_total[2, 2]
    αzx = alpha_total[3, 1]
    αzy = alpha_total[3, 2]

    αyx_intra = alpha_intra[2, 1]
    αyx_inter = alpha_inter[2, 1]

    open(output_file, "a") do io
    @printf(io,
    "%.16e %.16e %.16e %.16e %.16e %.16e %.16e %.16e %.16e\n",
    λ, αxx, αxy, αyx, αyy, αzx, αzy, αyx_intra, αyx_inter
    )
    end

    @printf("[%4d/%4d] lambda_R = %.8f | alpha_yx = %+ .8e | intra = %+ .8e | inter = %+ .8e\n",
    iλ, length(lambdas), λ, αyx, αyx_intra, αyx_inter)
    flush(stdout)
    end
    end

    MPI.Barrier(comm)

    if rank == 0
    elapsed = time() - t0
    @printf("============================================================\n")
    @printf("Finished. Elapsed time = %.2f s\n", elapsed)
    @printf("Data saved to: %s\n", output_file)
    @printf("============================================================\n")
    end
    end

    main()

    MPI.Finalize()
  • 绘图代码

    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
    #!/usr/bin/env python3
    # -*- coding: utf-8 -*-

    """
    Plot superconducting Edelstein response from Julia MPI dat output.

    Input:
    data/rashba_lattice_edelstein_mpi.dat

    Output:
    figures/rashba_lattice_edelstein_alpha_tensor.png
    figures/rashba_lattice_edelstein_alpha_tensor.pdf
    figures/rashba_lattice_edelstein_alpha_yx_intra_inter.png
    figures/rashba_lattice_edelstein_alpha_yx_intra_inter.pdf
    """

    import os
    import numpy as np
    import matplotlib.pyplot as plt
    from matplotlib.ticker import MaxNLocator


    # ============================================================
    # 0. Settings
    # ============================================================

    DATA_FILE = "data/rashba_lattice_edelstein_mpi.dat"
    FIG_DIR = "figures"

    SAVE_PREFIX = "rashba_lattice_edelstein"

    PLOT_ONLY_MAIN_COMPONENTS = False

    # If True, show the figures interactively.
    SHOW_FIG = True


    # ============================================================
    # 1. Plot style
    # ============================================================

    def setup_style():
    plt.rcParams["font.family"] = "Times New Roman"
    plt.rcParams["mathtext.fontset"] = "stix"
    plt.rcParams["font.size"] = 18
    plt.rcParams["axes.linewidth"] = 1.2
    plt.rcParams["xtick.direction"] = "in"
    plt.rcParams["ytick.direction"] = "in"
    plt.rcParams["xtick.top"] = True
    plt.rcParams["ytick.right"] = True
    plt.rcParams["legend.frameon"] = False


    def format_axes(ax):
    """
    Common axis formatting:
    1. Square plotting box.
    2. At most 3 major tick labels on x and y axes.
    """
    ax.set_box_aspect(1)

    # nbins=2 usually gives at most 3 major ticks.
    ax.xaxis.set_major_locator(MaxNLocator(nbins=2, min_n_ticks=2))
    ax.yaxis.set_major_locator(MaxNLocator(nbins=2, min_n_ticks=2))

    ax.tick_params(width=1.2, length=5)


    # ============================================================
    # 2. Load data
    # ============================================================

    def load_data(path):
    if not os.path.exists(path):
    raise FileNotFoundError(f"Cannot find data file: {path}")

    data = np.loadtxt(path, comments="#")

    lam = data[:, 0]

    alpha_xx = data[:, 1]
    alpha_xy = data[:, 2]
    alpha_yx = data[:, 3]
    alpha_yy = data[:, 4]
    alpha_zx = data[:, 5]
    alpha_zy = data[:, 6]

    alpha_yx_intra = data[:, 7]
    alpha_yx_inter = data[:, 8]

    return {
    "lambda": lam,
    "alpha_xx": alpha_xx,
    "alpha_xy": alpha_xy,
    "alpha_yx": alpha_yx,
    "alpha_yy": alpha_yy,
    "alpha_zx": alpha_zx,
    "alpha_zy": alpha_zy,
    "alpha_yx_intra": alpha_yx_intra,
    "alpha_yx_inter": alpha_yx_inter,
    }


    # ============================================================
    # 3. Plot tensor components
    # ============================================================

    def plot_alpha_tensor(d):
    lam = d["lambda"]

    fig, ax = plt.subplots(figsize=(5.8, 5.8))

    ax.plot(
    lam,
    d["alpha_yx"],
    marker="o",
    markersize=4,
    linewidth=1.8,
    label=r"$\alpha_{yx}$",
    )

    ax.plot(
    lam,
    d["alpha_xy"],
    marker="s",
    markersize=4,
    linewidth=1.8,
    linestyle="--",
    label=r"$\alpha_{xy}$",
    )

    if not PLOT_ONLY_MAIN_COMPONENTS:
    ax.plot(
    lam,
    d["alpha_xx"],
    linewidth=1.3,
    linestyle=":",
    label=r"$\alpha_{xx}$",
    )

    ax.plot(
    lam,
    d["alpha_yy"],
    linewidth=1.3,
    linestyle="-.",
    label=r"$\alpha_{yy}$",
    )

    ax.plot(
    lam,
    d["alpha_zx"],
    linewidth=1.1,
    linestyle=(0, (5, 2)),
    label=r"$\alpha_{zx}$",
    )

    ax.plot(
    lam,
    d["alpha_zy"],
    linewidth=1.1,
    linestyle=(0, (3, 2, 1, 2)),
    label=r"$\alpha_{zy}$",
    )

    ax.axhline(0.0, linewidth=1.0, color="black", alpha=0.5)

    ax.set_xlabel(r"Rashba SOC $\lambda_R/t$")
    ax.set_ylabel(r"Edelstein response $\alpha_{ab}$")

    ax.set_xlim(lam.min(), lam.max())

    format_axes(ax)

    ax.legend(fontsize=12, loc="best")

    fig.tight_layout()

    png_path = os.path.join(FIG_DIR, f"{SAVE_PREFIX}_alpha_tensor.png")
    pdf_path = os.path.join(FIG_DIR, f"{SAVE_PREFIX}_alpha_tensor.pdf")

    fig.savefig(png_path, dpi=300, bbox_inches="tight", pad_inches=0.02)
    fig.savefig(pdf_path, bbox_inches="tight", pad_inches=0.02)

    print(f"Saved figure: {png_path}")
    print(f"Saved figure: {pdf_path}")

    return fig, ax


    # ============================================================
    # 4. Plot intra/inter decomposition
    # ============================================================

    def plot_alpha_yx_intra_inter(d):
    lam = d["lambda"]

    fig, ax = plt.subplots(figsize=(5.8, 5.8))

    ax.plot(
    lam,
    d["alpha_yx"],
    marker="o",
    markersize=4,
    linewidth=1.8,
    label=r"total $\alpha_{yx}$",
    )

    ax.plot(
    lam,
    d["alpha_yx_intra"],
    linewidth=1.8,
    linestyle="--",
    label=r"intra-band",
    )

    ax.plot(
    lam,
    d["alpha_yx_inter"],
    linewidth=1.8,
    linestyle="-.",
    label=r"inter-band",
    )

    ax.axhline(0.0, linewidth=1.0, color="black", alpha=0.5)

    ax.set_xlabel(r"Rashba SOC $\lambda_R/t$")
    ax.set_ylabel(r"$\alpha_{yx}$")

    ax.set_xlim(lam.min(), lam.max())

    format_axes(ax)

    ax.legend(fontsize=12, loc="best")

    fig.tight_layout()

    png_path = os.path.join(FIG_DIR, f"{SAVE_PREFIX}_alpha_yx_intra_inter.png")
    pdf_path = os.path.join(FIG_DIR, f"{SAVE_PREFIX}_alpha_yx_intra_inter.pdf")

    fig.savefig(png_path, dpi=300, bbox_inches="tight", pad_inches=0.02)
    fig.savefig(pdf_path, bbox_inches="tight", pad_inches=0.02)

    print(f"Saved figure: {png_path}")
    print(f"Saved figure: {pdf_path}")

    return fig, ax


    # ============================================================
    # 5. Main
    # ============================================================

    def main():
    setup_style()

    os.makedirs(FIG_DIR, exist_ok=True)

    d = load_data(DATA_FILE)

    print("=" * 80)
    print("Loaded data")
    print("=" * 80)
    print(f"Data file = {DATA_FILE}")
    print(f"Number of SOCs = {len(d['lambda'])}")
    print(f"lambda range = [{d['lambda'].min():.6f}, {d['lambda'].max():.6f}]")
    print("=" * 80)

    plot_alpha_tensor(d)
    plot_alpha_yx_intra_inter(d)

    if SHOW_FIG:
    plt.show()
    else:
    plt.close("all")


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

完整程序点击这里下载

参考文献

  1. Nonlinear Superconducting Magnetoelectric Effect

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

  • yxliphy@gmail.com

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