Skip to content

Commit 33e881a

Browse files
committed
Fix regressions in superconductivity models
1 parent 422b0e7 commit 33e881a

1 file changed

Lines changed: 71 additions & 43 deletions

File tree

Stoner/analysis/fitting/models/superconductivity.py

Lines changed: 71 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
#!/usr/bin/env python3
22
# -*- coding: utf-8 -*-
33
""":py:class:`lmfit.Model` model classes and functions for various superconductivity related models."""
4+
45
# pylint: disable=invalid-name
56
# This module can be used with Stoner v.0.9.0 asa standalone module
67
__all__ = [
@@ -59,36 +60,41 @@ def _strijkers_core(V, omega, delta, P, Z):
5960
This version only uses 1 delta, not modified for proximity
6061
"""
6162
# Parameters
62-
E = np.linspace(-2 * np.max(np.abs(V)), 2 * np.max(np.abs(V)), V.size * 20)
63-
gauss = np.exp(-(E**2) / (2 * omega**2))
64-
gauss /= gauss.sum()
65-
66-
absE = np.abs(E)
67-
E2 = E**2
68-
delta2 = delta**2
69-
Z2 = Z**2
70-
Zfac = 1 + 2 * Z2
71-
denom = np.sqrt(E2 - delta2)
72-
eps = absE / denom
73-
74-
Au1 = delta2 / (E2 + (delta2 - E2) * Zfac**2)
75-
eps2 = eps**2
76-
Au2 = (eps2 - 1) / (eps + Zfac) ** 2
77-
Bu2 = 4 * Z2 * (1 + Z2) / (eps + Zfac) ** 2
78-
Bp2 = Bu2 / (1 - Au2)
79-
80-
up = (1 - P) * (1 + Z2)
81-
pp = P * (1 + Z2)
82-
83-
G = up + pp + np.where(absE <= delta, up * (2 * Au1 - 1) - pp, up * (Au2 - Bu2) - pp * Bp2)
84-
85-
conv = np.convolve(G, gauss)
86-
conv = conv[(E.size // 2) : (3 * E.size // 2)]
87-
88-
idx = np.searchsorted(E, V)
89-
El, Er = E[idx - 1], E[idx]
90-
Gl, Gr = conv[idx - 1], conv[idx]
91-
return (Gr - Gl) / (Er - El) * (V - El) + Gl
63+
E = np.linspace(
64+
-2 * np.max(np.abs(V)), 2 * np.max(np.abs(V)), V.size * 20
65+
) # Energy range in meV - we use a mesh 20x denser than data points
66+
gauss = np.exp(-(E**2 / (2 * omega**2)))
67+
gauss /= gauss.sum() # Normalised gaussian for the convolution
68+
69+
U = (
70+
(delta**2) / ((E**2) + (((delta**2) - (E**2)) * (1 + 2 * (Z**2)) ** 2)),
71+
(((np.abs(E) / (np.sqrt((E**2) - (delta**2)))) ** 2) - 1)
72+
/ (((np.abs(E) / (np.sqrt((E**2) - (delta**2)))) + (1 + 2 * (Z**2))) ** 2),
73+
(4 * (Z**2) * (1 + (Z**2))) / (((np.abs(E) / (np.sqrt((E**2) - (delta**2)))) + (1 + 2 * (Z**2))) ** 2),
74+
)
75+
76+
# Optimised for a single use of np.where
77+
G = (
78+
(1 - P) * (1 + (Z**2))
79+
+ 1 * (P) * (1 + (Z**2))
80+
+ +np.where(
81+
np.abs(E) <= delta,
82+
(1 - P) * (1 + (Z**2)) * (2 * U[0] - 1) - np.ones_like(E) * 1 * (P) * (1 + (Z**2)),
83+
(1 - P) * (1 + (Z**2)) * (U[1] - U[2]) - (U[2] / (1 - U[1])) * 1 * (P) * (1 + (Z**2)),
84+
)
85+
)
86+
87+
# Convolve and chop out the central section
88+
cond = np.convolve(G, gauss)
89+
cond = cond[(E.size // 2) : 3 * (E.size // 2)]
90+
# Linear interpolation back onto the V data point
91+
matches = np.searchsorted(E, V)
92+
condl = cond[matches - 1]
93+
condh = cond[matches]
94+
El = E[matches - 1]
95+
Er = E[matches]
96+
cond = (condh - condl) / (Er - El) * (V - El) + condl
97+
return cond
9298

9399

94100
def strijkers(V, omega, delta, P, Z):
@@ -259,7 +265,7 @@ def icRN_Clean(d_f, IcRn0, E_x, v_f, d_0):
259265
return IcRn0 * np.abs(np.sin(A * x)) / (A * x)
260266

261267

262-
def ic_RN_Dirty(d_f, IcRn0, E_x, v_f, d_0, tau, delta, T):
268+
def ic_RN_Dirty(d_f, IcRn0, E_x, v_f, d_0, tau, delta, T): # pylint: disable=unused-argument
263269
r"""Critical Current versus ferromagnetic narrier thickness, clean limit.
264270
265271
Args:
@@ -285,12 +291,21 @@ def ic_RN_Dirty(d_f, IcRn0, E_x, v_f, d_0, tau, delta, T):
285291
"""
286292
L = v_f * tau * 1e-9
287293
t = tau / hbar
288-
w_m = lambda m: (2 * m + 1) * T * np.pi * kb
289-
k_m = lambda m: (1 + 2 * np.abs(w_m(m)) * t) + (0 - 2j) * E_x * t
290-
integrad = lambda mu, m: np.real(mu / (np.sinh(d_f * k_m(m) / (mu * L))))
291-
prefactor = lambda m: delta**2 / (delta**2 + w_m(m) ** 2)
292294

293-
term = lambda m: prefactor(m) * quad(integrad, -1, 1, (m,)) # pylint: disable=W0612
295+
def _w_m(m):
296+
return (2 * m + 1) * T * np.pi * kb
297+
298+
def _k_m(m):
299+
return (1 + 2 * np.abs(_w_m(m)) * t) + (0 - 2j) * E_x * t
300+
301+
def _integrad(mu, m):
302+
return np.real(mu / (np.sinh(d_f * _k_m(m) / (mu * L))))
303+
304+
def _prefactor(m):
305+
return delta**2 / (delta**2 + _w_m(m) ** 2)
306+
307+
def term(m): # pylint: disable=unused-variable
308+
return _prefactor(m) * quad(_integrad, -1, 1, (m,)) # pylint
294309

295310

296311
class Strijkers(Model):
@@ -324,7 +339,7 @@ def __init__(self, *args, **kwargs):
324339
"""Configure Initial fitting function."""
325340
super().__init__(strijkers, *args, **kwargs)
326341

327-
def guess(self, data, **kwargs): # pylint: disable=unused-argument
342+
def guess(self, data, x=None, **kwargs): # pylint: disable=unused-argument
328343
"""Guess starting values for a good Nb contact to a ferromagnet at 4.2K."""
329344
pars = self.make_params(omega=0.5, delta=1.50, P=0.42, Z=0.15)
330345
pars["omega"].min = 0.36
@@ -336,6 +351,10 @@ def guess(self, data, **kwargs): # pylint: disable=unused-argument
336351
pars["P"].max = 1.0
337352
return update_param_vals(pars, self.prefix, **kwargs)
338353

354+
def copy(self, **kwargs):
355+
"""Make a new copy of the model."""
356+
return self.__class__(**kwargs)
357+
339358

340359
class RSJ_Noiseless(Model):
341360
r"""Implement a simple noiseless RSJ model.
@@ -367,9 +386,8 @@ def __init__(self, *args, **kwargs):
367386
"""Configure Initial fitting function."""
368387
super().__init__(rsj_noiseless, *args, **kwargs)
369388

370-
def guess(self, data, **kwargs):
389+
def guess(self, data, x=None, **kwargs):
371390
"""Guess parameters as gamma=2, H_k=0, M_s~(pi.f)^2/(mu_0^2.H)-H."""
372-
x = kwargs.get("x", np.linspace(1, len(data), len(data) + 1))
373391

374392
v_offset_guess = np.mean(data)
375393
v = np.abs(data - v_offset_guess)
@@ -389,6 +407,10 @@ def guess(self, data, **kwargs):
389407
pars["Ic_n"].max = 0
390408
return update_param_vals(pars, self.prefix, **kwargs)
391409

410+
def copy(self, **kwargs):
411+
"""Make a new copy of the model."""
412+
return self.__class__(**kwargs)
413+
392414

393415
class RSJ_Simple(Model):
394416
r"""Implements a simple noiseless symmetric RSJ model.
@@ -420,9 +442,8 @@ def __init__(self, *args, **kwargs):
420442
"""Configure Initial fitting function."""
421443
super().__init__(rsj_simple, *args, **kwargs)
422444

423-
def guess(self, data, **kwargs):
445+
def guess(self, data, x=None, **kwargs):
424446
"""Guess parameters as gamma=2, H_k=0, M_s~(pi.f)^2/(mu_0^2.H)-H."""
425-
x = kwargs.get("x", np.linspace(1, len(data), len(data) + 1))
426447

427448
v_offset_guess = np.mean(data)
428449
v = np.abs(data - v_offset_guess)
@@ -441,6 +462,10 @@ def guess(self, data, **kwargs):
441462
# pars["Ic"].min = 0
442463
return update_param_vals(pars, self.prefix, **kwargs)
443464

465+
def copy(self, **kwargs):
466+
"""Make a new copy of the model."""
467+
return self.__class__(**kwargs)
468+
444469

445470
class Ic_B_Airy(Model):
446471
r"""Critical Current for a round Josepshon Junction wrt to Field.
@@ -478,9 +503,8 @@ def __init__(self, *args, **kwargs):
478503
"""Configure Initial fitting function."""
479504
super().__init__(ic_B_airy, *args, **kwargs)
480505

481-
def guess(self, data, **kwargs):
506+
def guess(self, data, x=None, **kwargs):
482507
"""Guess parameters as max(data), x[argmax(data)] and from FWHM of peak."""
483-
x = kwargs.get("x", np.linspace(-len(data) / 2, len(data) / 2, len(data)))
484508

485509
Ic0_guess = data.max()
486510
B_offset_guess = x[data.argmax()]
@@ -491,3 +515,7 @@ def guess(self, data, **kwargs):
491515
pars = self.make_params(Ic0=Ic0_guess, B_offset=B_offset_guess, A=A_guess)
492516
pars["Ic0"].min = 0
493517
return update_param_vals(pars, self.prefix, **kwargs)
518+
519+
def copy(self, **kwargs):
520+
"""Make a new copy of the model."""
521+
return self.__class__(**kwargs)

0 commit comments

Comments
 (0)