#!/usr/bin/env python3
# -*- coding: utf-8 -*-
""":py:class:`lmfit.Model` model classes and functions for various superconductivity related models."""
# pylint: disable=invalid-name
# This module can be used with Stoner v.0.9.0 asa standalone module
__all__ = [
"RSJ_Noiseless",
"RSJ_Simple",
"Strijkers",
"Ic_B_Airy",
"rsj_noiseless",
"rsj_simple",
"strijkers",
"ic_B_airy",
]
from functools import partial
import numpy as np
from scipy.special import jv
from scipy.constants import physical_constants
from scipy.integrate import quad
from lmfit import Model
from lmfit.models import update_param_vals
hbar = physical_constants["Planck constant over 2 pi"]
kb = physical_constants["Boltzmann constant"]
Phi_0 = physical_constants["mag. flux quantum"][0]
J1 = partial(jv, 1)
try: # numba is an optional dependency
from numba import jit, float64
except ImportError:
from ....compat import _dummy, _jit as jit
float64 = _dummy()
@jit(float64[:](float64[:], float64, float64, float64, float64), nopython=True, parallel=True, nogil=True)
def _strijkers_core(V, omega, delta, P, Z):
"""Implement strijkers Model for point-contact Andreev Reflection Spectroscopy.
Args:
V = bias voltages, params=list of parameter values, imega, delta,P and Z
omega (float): Broadening
delta (float): SC energy Gap
P (float): Interface parameter
Z (float): Current spin polarization through contact
Return:
Conductance vs bias data.
Note:
PCAR fitting Strijkers modified BTK model TK PRB 25 4515 1982, Strijkers PRB 63, 104510 2000
This version only uses 1 delta, not modified for proximity
"""
# Parameters
mv = np.max(np.abs(V)) # Limit for evaluating the integrals
E = np.linspace(-2 * mv, 2 * mv, V.size * 20) # Energy range in meV - we use a mesh 20x denser than data points
gauss = np.exp(-(E**2 / (2 * omega**2)))
gauss /= gauss.sum() # Normalised gaussian for the convolution
# Conductance calculation
# For ease of calculation, epsilon = E/(sqrt(E^2 - delta^2))
# Calculates reflection probabilities when E < or > delta
# A denotes Andreev Reflection probability
# B denotes normal reflection probability
# subscript p for polarised, u for unpolarised
# Ap is always zero as the polarised current has 0 prob for an Andreev
# event
Au1 = (delta**2) / ((E**2) + (((delta**2) - (E**2)) * (1 + 2 * (Z**2)) ** 2))
Au2 = (((np.abs(E) / (np.sqrt((E**2) - (delta**2)))) ** 2) - 1) / (
((np.abs(E) / (np.sqrt((E**2) - (delta**2)))) + (1 + 2 * (Z**2))) ** 2
)
Bu2 = (4 * (Z**2) * (1 + (Z**2))) / (
((np.abs(E) / (np.sqrt((E**2) - (delta**2)))) + (1 + 2 * (Z**2))) ** 2
)
Bp2 = Bu2 / (1 - Au2)
unpolarised_prefactor = (1 - P) * (1 + (Z**2))
polarised_prefactor = 1 * (P) * (1 + (Z**2))
# Optimised for a single use of np.where
G = (
unpolarised_prefactor
+ polarised_prefactor
+ +np.where(
np.abs(E) <= delta,
unpolarised_prefactor * (2 * Au1 - 1) - np.ones_like(E) * polarised_prefactor,
unpolarised_prefactor * (Au2 - Bu2) - Bp2 * polarised_prefactor,
)
)
# Convolve and chop out the central section
cond = np.convolve(G, gauss)
cond = cond[(E.size // 2) : 3 * (E.size // 2)]
# Linear interpolation back onto the V data point
matches = np.searchsorted(E, V)
condl = cond[matches - 1]
condh = cond[matches]
El = E[matches - 1]
Er = E[matches]
cond = (condh - condl) / (Er - El) * (V - El) + condl
return cond
[docs]def strijkers(V, omega, delta, P, Z):
"""Strijkers Model for point-contact Andreev Reflection Spectroscopy.
Args:
V (array):
bias voltages
omega (float):
Broadening
delta (float):
SC energy Gap
P (float):
Interface parameter
Z (float):
Current spin polarization through contact
Return:
Conductance vs bias data.
.. note::
PCAR fitting Strijkers modified BTK model TK PRB 25 4515 1982, Strijkers PRB 63, 104510 2000
This version only uses 1 delta, not modified for proximity
Example:
.. plot:: samples/lmfit_demo.py
:include-source:
:outname: strijkers_func
"""
if isinstance(V, np.ma.MaskedArray):
mask = V.mask
V = np.array(V)
else:
mask = False
ret = _strijkers_core(V, omega, delta, P, Z)
ret = np.ma.MaskedArray(ret)
ret.mask = mask
return ret
[docs]def rsj_noiseless(I, Ic_p, Ic_n, Rn, V_offset):
r"""Implement a simple noiseless RSJ model.
Args:
I (array-like): Current values
Ic_p (foat): Critical current on positive branch
Ic_n (foat): Critical current on negative branch
Rn (float): Normal state resistance
V_offset(float): Offset volage in measurement
Returns:
(array) Calculated voltages
Notes:
Impleemtns a simple form of the RSJ model for a Josephson Junction:
:math:`V(I)=R_N\frac{I}{|I|}\sqrt{I^2-I_c^2}-V_{offset}`
Example:
.. plot:: samples/Fitting/rsj_fit.py
:include-source:
:outname: rsj_noiseless_func
"""
normal_p = np.sign(I) * np.real(np.sqrt(I**2 - Ic_p**2)) * Rn
normal_n = np.sign(I) * np.real(np.sqrt(I**2 - Ic_n**2)) * Rn
p_branch = np.where(I > Ic_p, normal_p, np.zeros_like(I))
n_branch = np.where(I < Ic_n, normal_n, p_branch)
return n_branch + V_offset
[docs]def rsj_simple(I, Ic, Rn, V_offset):
r"""Implement a simple noiseless symmetric RSJ model.
Args:
I (array-like):
Current values
Ic (foat):
Critical current
Rn (float):
Normal state resistance
V_offset(float):
Offset volage in measurement
Returns:
(array):
Calculated voltages
Notes:
Impleemtns a simple form of the RSJ model for a Josephson Junction:
:math:`V(I)=R_N\frac{I}{|I|}\sqrt{I^2-I_c^2}-V_{offset}`
Example:
.. plot:: samples/Fitting/rsj_fit.py
:include-source:
:outname: rsj_simple_func
"""
normal = Rn * np.sign(I) * np.real(np.sqrt(I**2 - Ic**2))
ic_branch = np.zeros_like(I)
return np.where(np.abs(I) < Ic, ic_branch, normal) + V_offset
[docs]def ic_B_airy(B, Ic0, B_offset, A):
r"""Calculate Critical Current for a round Josepshon Junction wrt to Field.
Args:
B (array-like):
Magnetic Field (structly flux density in T)
Ic0 (float):
Maximum critical current
B_offset (float):
Field offset/trapped flux in coils/remanent M in junction
A(fl,oat):
Area of junction in $m^2$
Returns:
(array):
Values of critical current
Notes:
Represents the critical current as:
:math:`I_{c0}\times\left|\frac{2 J_1\left(\frac{\pi\(B-B_{offset}) A}\right)}{\Phi_0}}
{\frac{\pi\(B-B_{offset}) A}){\Phi_0}}\right|`
where :math:`J_1` is a first order Bessel function.
For small ($<1^{-5}$)values of the Bessel function argument, this will return Ic0 to
ensure correct evaluation for 0 flux.
Example:
.. plot:: samples/Fitting/ic_b_airy.py
:include-source:
:outname: ic_b_airy_func
"""
arg = (B - B_offset) * A * np.pi / Phi_0
return Ic0 * np.abs(2 * np.where(np.abs(arg) < 1e-5, np.ones_like(arg), J1(arg) / arg))
def icRN_Clean(d_f, IcRn0, E_x, v_f, d_0):
r"""Critical Current versus ferromagnetic narrier thickness, clean limit.
Args:
d_f (array):
ferromagnetic barrier thickness (nm)
IcRn0 (float):
Characteristic voltage scaling factor
E_x (float):
Exchange energy (eV)
v_f (float):
Fermi velocity (ms^-1)
d_0 (float):
barrier thickness offset
Returns:
(array):
IcRn values
Notes:
Implements
Lmath:`I_cR_N = I_cR_N^0\zfrac{\sin(2E_x (d_f-d_0)/hv_f)}{2E_x(d_f-d_0)/hv_f}`
"""
h = physical_constants["Planck constant in eV s"]
x = (d_f - d_0) * 1e-9
A = 2 * E_x / (v_f * h)
return IcRn0 * np.abs(np.sin(A * x)) / (A * x)
def ic_RN_Dirty(d_f, IcRn0, E_x, v_f, d_0, tau, delta, T):
r"""Critical Current versus ferromagnetic narrier thickness, clean limit.
Args:
d_f (array):
ferromagnetic barrier thickness (nm)
IcRn0 (float):
Characteristic voltage scaling factor
E_x (float):
Exchange energy (eV)
v_f (float):
Fermi velocity (ms^-1)
d_0 (float):
barrier thickness offset
l (float):
mean-free-path (nm)
Returns:
(array):
IcRn values
Notes:
Implements Eq 18 from F.S. Bergeret, A.F. Volkov, and K.B. Efetov, Phys. Rev. B 64, 134506 (2001).
"""
L = v_f * tau * 1e-9
t = tau / hbar
w_m = lambda m: (2 * m + 1) * T * np.pi * kb
k_m = lambda m: (1 + 2 * np.abs(w_m(m)) * t) + (0 - 2j) * E_x * t
integrad = lambda mu, m: np.real(mu / (np.sinh(d_f * k_m(m) / (mu * L))))
prefactor = lambda m: delta**2 / (delta**2 + w_m(m) ** 2)
term = lambda m: prefactor(m) * quad(integrad, -1, 1, (m,)) # pylint: disable=W0612
[docs]class Strijkers(Model):
"""strijkers Model for point-contact Andreev Reflection Spectroscopy.
Args:
V (array): bias voltages
omega (float): Broadening
delta (float): SC energy Gap
P (float): Interface parameter
Z (float): Current spin polarization through contact
Return:
Conductance vs bias data.
.. note::
PCAR fitting Strijkers modified BTK model TK PRB 25 4515 1982, Strijkers PRB 63, 104510 2000
This version only uses 1 delta, not modified for proximity
Example:
.. plot:: samples/lmfit_demo.py
:include-source:
:outname: strijkers_class
"""
display_names = [r"\omega", r"\Delta", "P", "Z"]
def __init__(self, *args, **kwargs):
"""Configure Initial fitting function."""
super().__init__(strijkers, *args, **kwargs)
[docs] def guess(self, data, **kwargs): # pylint: disable=unused-argument
"""Guess starting values for a good Nb contact to a ferromagnet at 4.2K."""
pars = self.make_params(omega=0.5, delta=1.50, P=0.42, Z=0.15)
pars["omega"].min = 0.36
pars["omega"].max = 5.0
pars["delta"].min = 0.5
pars["delta"].max = 2.0
pars["Z"].min = 0.1
pars["P"].min = 0.0
pars["P"].max = 1.0
return update_param_vals(pars, self.prefix, **kwargs)
[docs]class RSJ_Noiseless(Model):
r"""Implement a simple noiseless RSJ model.
Args:
I (array-like): Current values
Ic_p (foat): Critical current on positive branch
Ic_n (foat): Critical current on negative branch
Rn (float): Normal state resistance
V_offset(float): Offset volage in measurement
Returns:
(array) Calculated voltages
Notes:
Impleemtns a simple form of the RSJ model for a Josephson Junction:
:math:`V(I)=R_N\frac{I}{|I|}\sqrt{I^2-I_c^2}-V_{offset}`
Example:
.. plot:: samples/Fitting/rsj_fit.py
:include-source:
:outname: rsj_noiseless_class
"""
display_names = ["I_c^p", "I_c^n", "R_N", "V_{offset}"]
def __init__(self, *args, **kwargs):
"""Configure Initial fitting function."""
super().__init__(rsj_noiseless, *args, **kwargs)
[docs] def guess(self, data, **kwargs):
"""Guess parameters as gamma=2, H_k=0, M_s~(pi.f)^2/(mu_0^2.H)-H."""
x = kwargs.get("x", np.linspace(1, len(data), len(data) + 1))
v_offset_guess = np.mean(data)
v = np.abs(data - v_offset_guess)
x = np.abs(x)
v_low = np.max(v) * 0.05
v_high = np.max(v) * 0.90
ic_index = v < v_low
rn_index = v > v_high
ic_guess = np.max(x[ic_index]) # Guess Ic from a 2% of max V threhsold creiteria
rn_guess = np.mean(v[rn_index] / x[rn_index])
pars = self.make_params(Ic_p=ic_guess, Ic_n=-ic_guess, Rn=rn_guess, V_offset=v_offset_guess)
pars["Ic_p"].min = 0
pars["Ic_n"].max = 0
return update_param_vals(pars, self.prefix, **kwargs)
[docs]class RSJ_Simple(Model):
r"""Implements a simple noiseless symmetric RSJ model.
Args:
I (array-like): Current values
Ic (foat): Critical current
Rn (float): Normal state resistance
V_offset(float): Offset volage in measurement
Returns:
(array) Calculated voltages
Notes:
Impleemtns a simple form of the RSJ model for a Josephson Junction:
:math:`V(I)=R_N\frac{I}{|I|}\sqrt{I^2-I_c^2}-V_{offset}`
Example:
.. plot:: samples/Fitting/rsj_fit.py
:include-source:
:outname: rsj_simple_class
"""
display_names = ["I_c", "R_N", "V_{offset}"]
def __init__(self, *args, **kwargs):
"""Configure Initial fitting function."""
super().__init__(rsj_simple, *args, **kwargs)
[docs] def guess(self, data, **kwargs):
"""Guess parameters as gamma=2, H_k=0, M_s~(pi.f)^2/(mu_0^2.H)-H."""
x = kwargs.get("x", np.linspace(1, len(data), len(data) + 1))
v_offset_guess = np.mean(data)
v = np.abs(data - v_offset_guess)
x = np.abs(x)
v_low = np.max(v) * 0.05
v_high = np.max(v) * 0.90
ic_index = v < v_low
rn_index = v > v_high
ic_guess = np.max(x[ic_index]) # Guess Ic from a 2% of max V threhsold creiteria
rn_guess = np.mean(v[rn_index] / x[rn_index])
pars = self.make_params(Ic=ic_guess, Rn=rn_guess, V_offset=v_offset_guess)
# pars["Ic"].min = 0
return update_param_vals(pars, self.prefix, **kwargs)
[docs]class Ic_B_Airy(Model):
r"""Critical Current for a round Josepshon Junction wrt to Field.
Args:
B (array-like):
Magnetic Field (structly flux density in T)
Ic0 (float):
Maximum critical current
B_offset (float):
Field offset/trapped flux in coils/remanent M in junction
A(fl,oat):
Area of junction in $m^2$
Returns:
(array):
Values of critical current
Notes:
Represents the critical current as:
:math:`I_{c0}\times\left|\frac{2 J_1\left(\frac{\pi\(B-B_{offset}) A}\right)}
{\Phi_0}}{\frac{\pi\(B-B_{offset}) A}){\Phi_0}}\right|`
where `J_1` is a first order Bessel function.
Example:
.. plot:: samples/Fitting/ic_b_airy.py
:include-source:
:outname: ic_b_airy_class
"""
display_names = ["I_{c0}", "B_{offset}"]
def __init__(self, *args, **kwargs):
"""Configure Initial fitting function."""
super().__init__(ic_B_airy, *args, **kwargs)
[docs] def guess(self, data, **kwargs):
"""Guess parameters as max(data), x[argmax(data)] and from FWHM of peak."""
x = kwargs.get("x", np.linspace(-len(data) / 2, len(data) / 2, len(data)))
Ic0_guess = data.max()
B_offset_guess = x[data.argmax()]
tmp = np.abs(data - (data.max() / 2))
x0 = np.abs(x[tmp.argmin()] - B_offset_guess)
A_guess = 2.2 * Phi_0 / (np.pi * x0)
pars = self.make_params(Ic0=Ic0_guess, B_offset=B_offset_guess, A=A_guess)
pars["Ic0"].min = 0
return update_param_vals(pars, self.prefix, **kwargs)