test_solver.py 9.70 KiB
# -*- coding:utf-8; eval: (blacken-mode) -*-
import os
import pytest
import numpy as np
from numpy.testing import assert_allclose
from numpy import pi
from scipy.special import zeta
from scipy.linalg import expm
from usadelndsoc.solver import *
from usadelndsoc.bcs import BCS_Delta
from usadelndsoc.matsubara import get_matsubara_sum
try:
import usadel1
except ImportError:
usadel1 = None
def example_sns_1d(phi, L, nx=500, ny=1, Delta0=1.0, eta=0.0):
solver = Solver(nx=nx, ny=ny)
solver.mask[...] = MASK_NONE
solver.mask[0, :] = MASK_TERMINAL
solver.mask[-1, :] = MASK_TERMINAL
solver.Lx = L
solver.Ly = L
solver.eta = eta
solver.Omega[...] = 0
solver.Delta[0, :] = Delta0 * np.exp(-1j * phi / 2) * np.eye(2)
solver.Delta[-1, :] = Delta0 * np.exp(+1j * phi / 2) * np.eye(2)
return solver
def test_example_sns_1d():
# Test against analytic solution
omega = 30
Lx = 10
phi = pi / 2
sol = example_sns_1d(phi, Lx)
res = sol.solve(omega=omega)
x = np.linspace(0, Lx, sol.shape[0])
theta0 = np.arctan(1 / omega)
theta_a = 4 * np.arctan(np.exp(-np.sqrt(2 * omega) * x) * np.tan(theta0 / 4))
theta_b = 4 * np.arctan(np.exp(-np.sqrt(2 * omega) * (Lx - x)) * np.tan(theta0 / 4))
F = np.sin(theta_a) * np.exp(-1j * phi / 2) + np.sin(theta_b) * np.exp(1j * phi / 2)
rF = res.F[:, :, 0, 0]
assert_allclose(rF.squeeze(), F, rtol=0, atol=5e-3 * abs(F).max())
@pytest.mark.skipif(usadel1 is None, reason="usadel1 not installed")
def test_example_sns_1d_J_usadel1():
Lx = 10
phi = pi / 2
sol = example_sns_1d(phi, Lx)
sol.Ly = 1
T = 0.2
Delta, I0, _, success = sol.self_consistency(T=T, T_c0=0, tol=1e-10)
tau3 = np.diag([1, 1, -1, -1])
I0 = tr(I0[:-1, 0, 0] @ tau3)
I0_mean = I0[1:-1].real.mean()
# Current is real-valued and conserved
assert_allclose(I0.imag, 0, atol=1e-8)
assert_allclose(I0[1:-1].real, I0_mean, rtol=1e-5)
# Compare to Usadel1
g = usadel1.Geometry(1, 2)
g.t_type = [usadel1.NODE_CLEAN_S_TERMINAL, usadel1.NODE_CLEAN_S_TERMINAL]
g.w_type = usadel1.WIRE_TYPE_N
g.w_ends[0, :] = [0, 1]
g.w_length = Lx
g.w_conductance = 1
g.t_delta = [1, 1]
g.t_phase = [-phi / 2, phi / 2]
g.t_t = T
g.t_mu = 0
s = usadel1.CurrentSolver(g, maxE=4, ne=2000)
s.solve()
I1, _ = s.get_currents()
I1_mean = I1.mean()
assert_allclose(I0_mean, I1_mean, rtol=1e-3)
@pytest.mark.skipif(usadel1 is None, reason="usadel1 not installed")
def test_example_sss_1d_J_usadel1():
Lx = 10
phi = pi / 2
sol = example_sns_1d(phi, 30 * Lx / (30 - 2), nx=30)
sol.Ly = 1
T_c0 = np.exp(np.euler_gamma) / np.pi
T = 0.2
Delta, I0, _, success = sol.self_consistency(T=T, T_c0=T_c0, workers=1)
tau3 = np.diag([1, 1, -1, -1])
I0 = tr(I0[:-1, 0, 0] @ tau3)
I0_mean = I0.real.mean()
# Current is real-valued and conserved
assert_allclose(I0.imag, 0, atol=1e-8)
assert_allclose(I0.real, I0_mean, rtol=5e-3)
# Compare to Usadel1
g = usadel1.Geometry(1, 2)
g.t_type = [usadel1.NODE_CLEAN_S_TERMINAL, usadel1.NODE_CLEAN_S_TERMINAL]
g.w_type = usadel1.WIRE_TYPE_S
g.w_ends[0, :] = [0, 1]
g.w_delta = 1.0
g.w_phase = np.linspace(-phi / 2, phi / 2, len(g.x))
g.w_length = Lx
g.w_conductance = 1
g.t_delta = [1, 1]
g.t_phase = [-phi / 2, phi / 2]
g.t_t = T
g.t_mu = 0
g.omega_D = 100
g.coupling_lambda = 1 / np.arccosh(g.omega_D / 1.0)
for k, d, v in usadel1.self_consistent_matsubara_iteration(g):
if d.relative_residual_norm() < 1e-6:
break
s = usadel1.CurrentSolver(g, ne=2000)
s.solve()
I1, _ = s.get_currents()
I1_mean = I1.mean()
assert_allclose(I0_mean, I1_mean, rtol=5e-2)
OMEGAS = [30, 5, (0.3 + 0.001j) / 1j]
@pytest.mark.skipif(usadel1 is None, reason="usadel1 not installed")
@pytest.mark.parametrize("omega", OMEGAS)
def test_example_sns_usadel1(omega):
# Test against another numerical solution
Lx = 10
phi = pi / 2
sol = example_sns_1d(phi, Lx)
res = sol.solve(omega=omega)
g = usadel1.Geometry(1, 2)
g.t_type = [usadel1.NODE_CLEAN_S_TERMINAL, usadel1.NODE_CLEAN_S_TERMINAL]
g.w_type = usadel1.WIRE_TYPE_N
g.w_ends[0, :] = [0, 1]
g.w_length = Lx
g.w_conductance = 1
g.t_delta = [1, 1]
g.t_phase = [-phi / 2, phi / 2]
g.t_t = 1
g.t_mu = 0
s = usadel1.Solver()
s.set_geometry(g)
x_scl = np.linspace(0, 1, sol.shape[0])
res2 = s.sp_solve(np.array([1j * omega]), x_scl)
F2 = 2j * res2.a / (1 - res2.a * res2.b)
F2c = 2j * res2.b / (1 - res2.a * res2.b)
assert_allclose(
res.F[..., 0, 0].squeeze(), F2.squeeze(), rtol=1e-2, atol=1e-3 * abs(F2).max()
)
assert_allclose(
res.Fc[..., 0, 0].squeeze(),
F2c.squeeze(),
rtol=1e-2,
atol=1e-3 * abs(F2c).max(),
)
@pytest.mark.slow
@pytest.mark.parametrize("T", [0.28, 0.53, 0.8])
def test_selfcons_kupriyanov(request, T):
fns_k = {
0.28: "kupriyanov1981-cpr-a1.dat",
0.53: "kupriyanov1981-cpr-a2.dat",
0.8: "kupriyanov1981-cpr-a3.dat",
}
fns_u = {
0.28: "L3.989_T0.28_TcR1_TcW1_gamma1e-06_r0_ns7.dat",
0.53: "L3.989_T0.53_TcR1_TcW1_gamma1e-06_r0_ns7.dat",
0.8: "L3.989_T0.8_TcR1_TcW1_gamma1e-06_r0_ns7.dat",
}
fn_k = os.path.join(os.path.dirname(__file__), "data", fns_k[T])
fn_u = os.path.join(os.path.dirname(__file__), "data", fns_u[T])
L = 3.989
Tc0 = 1.0
Delta0 = BCS_Delta(T, Tc0)
Delta00 = BCS_Delta(0.05, Tc0)
# L is the distance between terminals, but Lx the system size where
# the two outermost layers are terminals
nx = 40
Lp = (L / (nx - 2)) * nx
sol = example_sns_1d(0, Lp, nx=nx, ny=1, Delta0=Delta0)
sol.Ly = 1
phase_mask = (sol.mask == MASK_TERMINAL) & (sol.x[:, None] > 0)
dn = request.config.cache.makedir("test_selfcons_kupriyanov")
cache_fn = os.path.join(dn, f"T-{T}.npz")
res = cpr(
sol,
T=T,
T_c0=Tc0,
phase_mask=phase_mask,
filename=cache_fn,
workers=1,
ds=0.4,
)
phis, Deltas, Js = res
tau3 = np.diag([1, 1, -1, -1])
Js = tr(Js @ tau3)
Is = Js[:, :-1, 0, 0].squeeze().mean(axis=1)
m = ~np.isnan(Is)
Is = Is[m]
phis = phis[m]
phis_k, Is_k = np.loadtxt(fn_k).T
phis_u, Is_u = np.loadtxt(fn_u).T
Is_k *= Delta00 / L
Imax = abs(Is_u).max()
assert curve_separation(phis, Is, phis_u, Is_u) < 0.075 * Imax
assert curve_separation(phis, Is, phis_k, Is_k) < 0.11 * Imax
def curve_separation(x1, y1, x2, y2):
# Resample
k1 = np.arange(len(x1))
k1p = np.linspace(k1[0], k1[-1], 2000)
k2 = np.arange(len(x2))
k2p = np.linspace(k2[0], k2[-1], 2000)
x1 = np.interp(k1p, k1, x1)
y1 = np.interp(k1p, k1, y1)
x2 = np.interp(k2p, k2, x2)
y2 = np.interp(k2p, k2, y2)
# Min-max separation
s = np.hypot(abs(x1[:, None] - x2[None, :]), abs(y1[:, None] - y2[None, :]))
return s.min(axis=1).max()
@pytest.mark.parametrize(
"T,n,alpha_soc", [(0.1, 10, 0.01), (0.4, 10, 0.01), (0.4, 5, 0.0123)]
)
def test_soc_analytic(T, n, alpha_soc):
sx = np.array([[0, 1], [1, 0]])
sy = np.array([[0, -1j], [1j, 0]])
sz = np.array([[1, 0], [0, -1]])
s0 = np.array([[1, 0], [0, 1]])
def get_sol(soc_alpha, eta, L=10, h=0.5, D=1.0):
sol = Solver(nx=n, ny=n)
sol.mask[...] = MASK_NONE
sol.Lx = L
sol.Ly = L
sol.Omega[...] = 0
sol.Delta[:, :] = np.eye(2)
sol.D = D
sol.eta = eta
nx, ny = sol.shape
dx = sol.Lx / nx
dy = sol.Ly / ny
Ax = 2 * soc_alpha * np.kron(s0, sy)
Ay = -2 * soc_alpha * np.kron(s0, sx)
sol.Ux[...] = expm(1j * dx * Ax)
sol.Uy[...] = expm(1j * dy * Ay)
sol.Omega[...] += h * np.kron(sz, sx)
return sol
# Solver without SOC
h = 0.5
sol0 = get_sol(soc_alpha=0, eta=0, h=h)
# Solver for evaluating current perturbatively in SOC
eta = 1.0
sol = get_sol(soc_alpha=alpha_soc, eta=eta, h=h, D=0)
E_typical = 10 + 10 * T
w, a = get_matsubara_sum(T, E_typical)
t3 = np.kron(sz, s0)
Phis = []
Fs = []
Gs = []
Js = []
Jans = []
dd = (sol.Lx / sol.shape[0], sol.Ly / sol.shape[1])
Jtot = 0
Jantot = 0
for ww, aa in zip(w, a):
res0 = sol0.solve(omega=ww)
sol.omega = ww
J = sol._core.grad_A(res0.Phi.copy()).transpose(0, 1, 2, 4, 3) * (1 / 16)
Jtot += -1j * pi * aa * J
gp = (ww - 1j * h) / np.sqrt((ww - 1j * h) ** 2 + 1)
gm = (ww + 1j * h) / np.sqrt((ww + 1j * h) ** 2 + 1)
fp = 1 / np.sqrt((ww - 1j * h) ** 2 + 1)
fm = 1 / np.sqrt((ww + 1j * h) ** 2 + 1)
# XXX: check the 8*eta
Jy_an = (gp - gm) * (1 + gp * gm + fp * fm) * (-1j * alpha_soc**3) * (8 * eta)
Jy_an *= dd[1] # multiply current density by the width of a single cell
Jantot += pi * aa * Jy_an
Jy = -1j * tr(J @ t3)[n // 2, n // 2, 1]
assert_allclose(Jy, Jy_an, rtol=1e-2)
Fs.append(res0.F.copy())
Gs.append(res0.G.copy())
Phis.append(res0.Phi.copy())
Js.append(J)
Jans.append(Jy_an)
def test_selfcons_h():
T = 0.4
h = 0.5
solver = Solver(nx=5, ny=2)
solver.mask[...] = MASK_NONE
solver.Lx = 5
solver.Ly = 5
solver.eta = 0
solver.Delta[...] = 1.0
solver.Omega[...] += h * np.kron(S_z, S_y)
T_c0 = np.exp(np.euler_gamma) / np.pi
Delta, I0, _, success = solver.self_consistency(T=T, T_c0=T_c0, workers=1)
Delta0 = BCS_Delta(T, T_c0, h=h)
Deltam = np.broadcast_to((Delta0 * S_0)[None, None, :, :], Delta.shape)
assert_allclose(Delta.copy(), Deltam, rtol=1e-3)