Python-реализация алгоритмов и инструментарий для моделирования динамики сверхпроводникого квантового интерферометра с двумя джозефсоновскими переходами (Вычисление зависимости величины тока возврата и модуляции напряжения от величины внешнего магнитного поля)#

1. Описание модели#

Эффект Джозефсона и джозефсоновский переход:

Связь двух сверхпроводящих слоев посредством тонкого слоя несверхпроводящего барьера образует структуру называемую джозефсоновским переходом (в честь Английского ученого Брайана Джозефсона). При пропускании электрического тока через джозефсоновский переход в зависимости от величины тока наблюдается стационарный и нестационарный эффект Джозефсона.

Стационарный эффект Джозефсона. При пропускании тока ниже критического значения (\(I<I_{c}\)) в джозефсоновском переходе (ДП) отсутсвует напряжение и через переход течет сверхпроводящий ток. Данный ток пропорционален синусу разности фаз параметров порядка сверхпроводящих слоев образующих ДП.

\[ \begin{eqnarray} I_{s}=I_{c}\sin\varphi. \label{eq1} \tag{1} \end{eqnarray} \]

Нестационарный эффект Джозефсона. При увеличении тока выше критического значения (\(I>I_{c}\)) возникает переменное напряжение в ДП и оно пропорционально производной разности фаз

\[ \begin{eqnarray} V=\frac{\hbar}{2e}\frac{d\varphi}{dt}. \label{eq2} \tag{2} \end{eqnarray} \]

Сверхпроводниковые квантовые интерференционные устройства (СКВИД)

Системы содержащие джозефсоновские переходы давно нашли свое применение в различных отраслях науки и техники. Одним из таких примеров является так называемые сверхпроводниковые квантовые интерференционные устройства. В литературе эти устройства известны как СКВИД (от английского SQUID - Superconducting Quantum Interference Device). Различают двух типов СКВИДа: СКВИД постоянного тока (DC-SQUID) и СКВИД переменного тока (RF-SQUID). СКВИД постоянного тока представляет собой сверхпроводниковое кольцо, состоящее из двух параллельно соединенных джозефсоновских переходов, и широко применяется как устройство для измерения очень слабых магнитных полей. А СКВИД переменного тока применяются как чувствительный прибор слабого переменного поля.

Рассмотрим, СКВИД постоянного тока (DC-SQUID), схематический вид которого представлен на Рис.1. Плоскость на которой лежат джозефсоновские переходы и ограничено соединяющими сверхпроводящими проводниками будем называть поверхностью СКВИДа. Предположим, что перпендикулярно к поверхности СКВИДа приложено постоянное магнитное поле \(B\) направленное снизу вверх и оно приведет к потоку \(\Phi_{ext}=BS\), где \(S\) площадь поверхности СКВИДа.

Рис.1 Схематический вид СКВИДа

Рис.2 Эквивалентная схема СКВИДа.

Динамика перехода описывается Resitively Capasitevily Shunted Junction (RCSJ) моделью\cite{r}. В рамках этой модели ДП моделируется как параллельное соединение конденсатора, резистора и сверхпроводника, через которые протекают, соответственно, ток смещения, квазичастичный и сверхпроводящий токи. Учитывая индуктивности сверхпроводящих проводников соединяющие джозефсоновские переходы 1 и 2 Можно написать эквивалентную схему СКВИДа, которая представлена на Рис. 2. Индексами 1 и 2 обозначены, соответсвенно левый и правый и правый ДП. Через СКВИД пропускается внешный ток \(I\), которая распределясь на \(I_{1}\) и \(I_{2}\) течет через джозефсоновские переходы 1 и 2 и в резистивном состоянии ДП-ов приводит к среднему напряжению \(V\). Кроме того из за наличия потока внешнего магнитного поля \(\Phi_{ext}\) возникает циркулирующий сверхпроводящий ток \(J\). Здесь \(L_{1}\) и \(L_{2}\) обозначают индуктивность сверхпроводящих проводников соединяющих джозефсоновские переходы и \(C_{1,2}\), \( R_{1,2}\), \( I_{c,1,2}\) емкость, сопротивление и критический ток ДП, соответвенно. Согласно RCSJ-модели токи \(I_{1}\) и \(I_{2}\) определяются выражениями

\[\begin{split} \label{currents} \left\{\begin{array}{l} \displaystyle I_{1}=C\frac{dV_{1}}{dt}+\frac{V_{1}}{R_{1}} +I_{c1}\sin\varphi_{1}, \\ \displaystyle I_{2}=C\frac{dV_{2}}{dt}+\frac{V_{2}}{R_{2}} +I_{c2}\sin\varphi_{2}, \end{array}\right. \end{split}\]

где \(V_{i}=\frac{\Phi_{0}}{2\pi}\frac{d\varphi_{i}}{dt}\) напряжение в \(i\)-ом переходе, \(\Phi_{0}=\frac{\hbar}{2e}\)-квант магнитного потока. С другой стороны, согласно правила Кирхгофа для токов можно записать

\[\begin{split} \label{currents1} \left\{\begin{array}{l} \displaystyle I_{1}=\frac{I}{2}+J, \\ \displaystyle I_{2}=\frac{I}{2}-J. \end{array}\right. \end{split}\]

Далее для простоты предполагаем, что индуктивности проводников равны друг другу и ДП СКВИДа одинаковы, т.е. полагаем

\[C_{1}=C_{2}=C,\hspace{0.5cm} R_{1}=R_{2}=R,\hspace{0.5cm} L_{1}=L_{2}=L,\hspace{0.5cm} I_{c1}=I_{c2}=I_{c}.\]

Магнитный поток через СКВИД складывается из потока создаваемого внешним полем \(\Phi_{ext}\) и циркулирующим током \(J\) и записывается в виде

\[ \label{flux1} \Phi=\Phi_{ext}+L(I_{1}-I_{2})=\Phi_{ext}+2LJ. \]

Хорошо известно, что магнитный поток через сверхпроводниковые контуры квантуется, т.е. принимает дискретное значения кратное к \(\Phi_{0}\). Выражение для квантования магнитного потока для рассматриваемой системы можно записать

\[ \label{flux2} \varphi_{2}-\varphi_{1}+2\pi n=\frac{2\pi}{\Phi_{0}}\Phi=\frac{2\pi}{\Phi_{0}}(\Phi_{ext}+2LJ), \]

или

\[ \label{flux3} \varphi_{2}-\varphi_{1}+2\pi n=2\pi\varphi_{ext}+\frac{4\pi L}{\Phi_{0}}J, \]

где \(\varphi_{ext}\) безразмерный поток внешнего магнитного поля нормированный на \(\Phi_{0}\). Отсюда можно получить выражению для циркулирующего тока

\[ \label{circulating_current} J=\frac{\Phi_{0}}{4\pi L}[\varphi_{2}-\varphi_{1}+2\pi(n-\varphi_{ext})]. \]

Тогда с учетом (2)-(5) можно получить замкнутую систему уравнений для описания СКВИДа:

\[\begin{split} \label{currents2} \left\{\begin{array}{l} \displaystyle C\frac{dV_{1}}{dt}+\frac{V_{1}}{R}+I_{c}\sin\varphi_{1}=\frac{I}{2}+\frac{\Phi_{0}}{4\pi L}[\varphi_{2}-\varphi_{1}+2\pi(n-\varphi_{ext})],\\ \displaystyle \frac{\Phi_{0}}{2\pi}\frac{d\varphi_{1}}{dt}=V_{1}\\ \displaystyle C\frac{dV_{2}}{dt}+\frac{V_{2}}{R}+I_{c}\sin\varphi_{2}=\frac{I}{2}-\frac{\Phi_{0}}{4\pi L}[\varphi_{2}-\varphi_{1}+2\pi(n-\varphi_{ext})], \\ \displaystyle \frac{\Phi_{0}}{2\pi}\frac{d\varphi_{2}}{dt}=V_{2} \end{array},\right. \end{split}\]

Переходя к безразмерным величинам можно написать

\[\begin{split} \label{eqsystem} \left\{\begin{array}{l} \displaystyle \frac{dV_{1}}{dt}=\frac{1}{\beta_{c}}\bigg\{\frac{I}{2}-\frac{d\varphi_{1}}{dt}-\sin(\varphi_{1})+\frac{1}{2\beta_{L}}[\varphi_{2}-\varphi_{1}+2\pi(n-\varphi_{ext})]\bigg\}\\ \displaystyle \frac{d\varphi_{1}}{dt}=V_{1}\\ \displaystyle \frac{dV_{2}}{dt}=\frac{1}{\beta_{c}}\bigg\{\frac{I}{2}-\frac{d\varphi_{2}}{dt}-\sin(\varphi_{2})-\frac{1}{2\beta_{L}}[\varphi_{2}-\varphi_{1}+2\pi(n-\varphi_{ext})]\bigg\}\\ \displaystyle \frac{d\varphi_{2}}{dt}=V_{2} \end{array},\right. \end{split}\]

где \(\beta_{c}=C\Phi_{0}\omega_{c}^{2}/(2\pi I_{c})\) - параметр МакКамбера, \(\omega_{c}=2\pi R I_{c}/\Phi_{0}\)- характеристическая частота Джозефсона, \(\beta_{L}=2\pi L I_{c}/\Phi_{0}\)- обезразмеренная индуктивность, \(\Phi_{0}=h/(2e)\) - квант магнитного потока, \(\varphi_{ext}=\Phi_{ext}/\Phi_{0}\) - безразмерный поток внешнего магнитного поля. Здесь напряжение нормировано на \(V_{c}=I_{c}R\), ток на \(I_{c}\) и время \(t\) на \(\omega^{-1}_{c}\), где \(\omega_{c}=2\pi V_{c}/\Phi_{0}\). Начальные условия для системы уравнений \eqref{eqsystem} заданы в виде \(V_1(0)=V_2(0)=0\) и \(\varphi_1(0)=\varphi_2(0)=0\).

Постановка задачи: Необходимо вычислить:

  1. Вольт-амперную характеристику (ВАХ) СКВИДа

  2. Зависимость критического тока от потока внешнего магнитного поля.

Алгоритм рассчета

Ниже опишем алгоритм вычисления ВАХ и зависимости критического тока от внешнего магнитного поля. При заданном значений параметров и начальных условий при фиксированном значении тока \(I\) численно решается СДУ (\ref{eqsystem}) в интервале времени [0, Tmax] с шагом \(\Delta t\). Полученная временная зависимость напряжения усредняется по времени по формуле

\[\bar{V}=\frac{1}{T_{\max}-T_{\min}} \int_{T_{\min}}^{T_{\max}}V(t)dt\]

и в результате вычисляется одна точка ВАХ для заданного значения \(I\). Отметим, что при усреднении интегрирования начинается от времени \(Tmin\), после которого решение стабилизируется. Далее меняется значение тока на δI и повторяется вышеописанные действия. При этом вычисленные φ(Tmax) и V (Tmax) для предыдущего значения тока будут использоваться в качестве начального условия для текущего значения тока. Таким образом увеличивая ток до Imax и уменьшая его обратно до нуля получаем ВАХ ДП.

Для определения критического тока в процессе вычисления ВАХ фиксируется значения внешнего тока при которой выполняется условие V>0, т е значение тока при которой возникает напряжение, считается критическим током.

Подключаем библиотеки#

import numpy as np
import matplotlib.pyplot as plt
import math as m
from scipy.integrate import solve_ivp
from functools import partial
from scipy.integrate import odeint
import time

from numbalsoda import lsoda_sig, lsoda, dop853
from numba import njit, cfunc

import seaborn as sns
sns.set()
sns.set(style="whitegrid")

%matplotlib inline

1. Численное решение задачи Коши для системы уравнений#

Функция для вычисления значений правых частей уравнений (Numba version)#

@cfunc(lsoda_sig)
def rhs(t, S, dS, p):
    ''' Функция определяющая правую часть систему уравнений.
        Возвращает значения правой части уравнений при заданных значениях
        параметров.
        S - массив математических функций
        dS - массив производных от математических функций по времени
        p - массив параметров
        '''
    # Задаем параметров модели
    beta_c = p[0]
    beta_L = p[1]
    Pi = p[2]
    n = p[3]
    Icurr = p[4]
    ph_ext = p[5]
    # S массив которая содержит значения функций в начале (начальное условие)
    ph1 = S[0]
    V1 = S[1]
    ph2 = S[2]
    V2 = S[3]
    # Задаем правую часть системы уравнений
    dph1 = V1
    dV1 = (1./beta_c) * ((Icurr/2.) - V1 - np.sin(ph1)
                         + (1./(2.*beta_L)) * (ph2-ph1+2*Pi*(n-ph_ext)))
    dph2 = V2
    dV2 = (1./beta_c) * ((Icurr/2.) - V2 - np.sin(ph2)
                         - (1./(2.*beta_L)) * (ph2-ph1+2*Pi*(n-ph_ext)))
    # Передаем правые части уравнений в массив dS
    dS[0] = dph1
    dS[1] = dV1
    dS[2] = dph2
    dS[3] = dV2
funcptr = rhs.address  # address to ODE function

Задаем значения параметров модели для численного решения системы уравнений#

Важно: необходимо соблюдать типы переменных, когда задаем начальные условия и параметры

beta_c = 0.1  # Параметр диссипации
beta_L = 1.0  # Амплитуда внешнего излучения
Pi = 3.14159265358979323
n = 0  # Период внешнего излучения
Icurr = 2.7
ph_ext = 0.5
Tmax = 800  # Максимальное значение времени
deltat = 0.05
t0 = 0
nt = int(Tmax / deltat)
Imax = 2.7

Создаем массив с значениями времени для решения уравнения и задаем начальные условия

time_array = np.linspace(0, Tmax, num=nt)
s0 = np.array([0, 0, 0, 0])  # Initial conditions

Создаем массив с значениями параметров

# beta_c, beta_L, Pi, n, Icurr, ph_ext
data = np.array([beta_c, beta_L, Pi, n, Icurr, ph_ext])
data
array([0.1       , 1.        , 3.14159265, 0.        , 2.7       ,
       0.5       ])

Численно решаем систему уравнений методом LSODA

    %%time
# integrate with lsoda method
usol, success = lsoda(funcptr, s0, time_array, rtol = 1e-8, atol = 1e-8, data = data)
CPU times: user 154 ms, sys: 3.94 ms, total: 158 ms
Wall time: 158 ms

Извлекаем из массива решений значения напряжения V1 и V2

V1time = usol[:, 1]
V2time = usol[:, 3]

Временная зависимость V1 и V2

fig = plt.figure(figsize=(12, 10))
plt.plot(time_array, V1time, label='Временная зависимость', linewidth=2)
plt.xlabel('time', size=12)
plt.ylabel('V1', size=12)
plt.legend(loc='upper right')
plt.show()
_images/8d0ec96a4dd994422d25ada10302bf09dec025fc89c451eaa59a13af84cbbeb5.png
fig = plt.figure(figsize=(12, 10))
plt.plot(time_array, V2time, label='Временная зависимость', linewidth=2)
plt.xlabel('time', size=12)
plt.ylabel('V2', size=12)
plt.legend(loc='upper right')
plt.show()
_images/78eb8524f602aadffc23199e1c4a3a920f21a6818a9c62575ef2a86c1ab161ed.png

2. Алгоритм для нахождения вольт-амперной характеристики (ВАХ) СКВИДа#

Для нахождения ВАХ нам нужно полученную временную зависимость усреднить. Для этого необходимо усреднить по отдельности V1 и V2 и затем вычислить среднее от полученных < V1 > и < V2 >. Этот шаг можно реализовать в виде функции которая принимая временные зависимости V1 и V2 возвращает среднее значения < V >

@njit
def averageV_new(ntmin, nt, deltat, V1, V2):
    ''' Функция для вычисления среднего значения напряжения.
        Возвращает среднее значение напряжения для заданной
            временной зависимости напряжений в первом и втором ДП
        ntmin-номер шага по времени соответсвующая
                нижней границе интегрирования,
        nt - количество точек по времени, в которых найдены решения,
        V1 - временная зависимость напряжения первого ДП,
        V2 - временная зависимость напряжения второго ДП,
        '''
    V1av = np.sum(V1[ntmin: nt])/((nt-ntmin))
    V2av = np.sum(V2[ntmin: nt])/((nt-ntmin))
    Vav = (V1av + V2av)/2
    return Vav

Функция для вычисления одной точки ВАХ

Создаем функцию, которая вычисляет одну точку ВАХ, т.е. выполняет следующие шаги:

  • принимает начальные условия и значения параметров модели

  • решает численно уравнения

  • используя ранее созданную функцию averageV_new усредняет напряжение

  • возвращает среднее напряжение и начальные условия для вычисления следующей точки ВАХ

@njit
def cvcpoint_new(s0, beta_c, beta_L, Pi, n,
                 Icurr, ph_ext, nt, ntmin, t0, deltat):
    ''' Функция для вычисления одной точки ВАХ.
        Возвращает для заданного значения тока вычисленную среднее значение
            напряжения при заданных значений параметров.
        s0 - массив начальных условий,
        beta_c, beta_L, Pi, n, Icurr, ph_ext,-параметры модели
        nt - количество точек по времени, в которых найдены решения,
        ntmin - номер шага по времени соответсвующее
                нижней границе интегрирования,
        t0 - начальное значение времени,
        deltat - шаг по времени
        '''
    data = np.array([beta_c, beta_L, Pi, n, Icurr, ph_ext])
    t_e = np.linspace(t0, nt*deltat, nt)
    # integrate with lsoda method
    usol, success = lsoda(funcptr, s0, t_e, rtol=1e-8, atol=1e-8, data=data)
    # получение результатов из массива usol
    ph1time = usol[:, 0]
    V1time = usol[:, 1]
    ph2time = usol[:, 2]
    V2time = usol[:, 3]
    s0 = np.array([ph1time[nt-1], V1time[nt-1], ph2time[nt-1], V2time[nt-1]])
    Vav = averageV_new(ntmin, nt, deltat, V1time, V2time)
    return np.array([Vav, ph1time[nt-1], V1time[nt-1],
                     ph2time[nt-1], V2time[nt-1]])

Функция для вычисления ВАХ

@njit
def comput_vax(s0, beta_c, beta_L, Pi, n,
               Icurr, ph_ext, nt, ntmin, t0, deltat, a, deltaI):
    ''' Функция для вычисления однопетлевой ВАХ.
        Возвращает для заданного значения параметров массива
            с значениями тока и вычисленного напряжения
        s0 - массив начальных условий,
        beta_c, beta_L, Pi, n, Icurr, ph_ext,-параметры модели
        nt - количество точек по времени, в которых найдены решения,
        ntmin - номер шага по времени соответсвующее
                нижней границе интегрирования,
        t0 - начальное значение времени,
        deltat - шаг по времени
        deltaI - шаг по току
        '''
    Vplot = []
    Iplot = []
    while Icurr < 100:
        res = cvcpoint_new(s0, beta_c, beta_L, Pi, n,
                           Icurr, ph_ext, nt, ntmin, t0, deltat)
        Vav = res[0]
        s0 = res[1:]
        Vplot.append(Vav)
        Iplot.append(Icurr)
        Icurr += a*deltaI
        if(Icurr > Imax):
            a = -1
        if ((Icurr < 0) and (a == -1)):
            break
    return np.column_stack((np.array(Iplot), np.array(Vplot)))

Задаем значения параметров для вычисления ВАХ

Tmin = 50.0  # Начало интервала для интегрирования для усреднения
ntmin = int(Tmin/deltat)
deltaI = 0.001
Icurr = 0.0
a = 1
Imax = 2.2
s0 = np.array([0.0, 0.0, 0.0, 0.0])
res = cvcpoint_new(s0, beta_c, beta_L, Pi, n,
                   Icurr, ph_ext, nt, ntmin, t0, deltat)
print(res)
[-7.81324927e-13 -8.31711194e-01 -2.12310771e-13  8.31711194e-01
 -2.02754355e-13]
print(res[0])
-7.813249272940803e-13
print(res[1:])
[-8.31711194e-01 -2.12310771e-13  8.31711194e-01 -2.02754355e-13]

Вычислим ВАХ

start = time.time()
res = comput_vax(s0, beta_c, beta_L, Pi, n,
                 Icurr, ph_ext, nt, ntmin, t0, deltat, a, deltaI)
end = time.time()

print(end-start)
27.4161536693573

График ВАХ

fig = plt.figure(figsize=(12, 10))
plt.plot(res[:, 0], res[:, 1], label='ВАХ', linewidth=3.0)
plt.xlabel('I', size=12)
plt.ylabel('V', size=12)
plt.legend(loc='upper left')
plt.show()
_images/0ee39fa0b9ad044bcf4574fca11a1eb485a7c609bf29bac4aa1f01302df089cd.png

Описание ВАХ#

Как видно из ВАХ с увеличением тока начиная от нуля до 0.8 значения напряжение равна нулю (стационарный эффект Джозефсона). При достижении значения тока 0.8 появляется напряжение и далее с ростом тока растет и напряжение. Как было отмечено в описании модели ток при котором появляется напряжение назывется критическим током. Теперь рассмотрим как критический ток меняется с изменением внешнего потока.

Исследование влияния \(\varphi_{ext}\) на ВАХ#

Проведем вычисления при различных значений \(\varphi_{ext}\), например при \(\varphi_{ext}=0\), \(\varphi_{ext}=0.25\) и \(\varphi_{ext}=0.5\). Численное моделирование проводилось в интервале времени [0, 1000] с шагом 0.05.

ph_ext = 0
start = time.time()
res0 = comput_vax(s0, beta_c, beta_L, Pi, n,
                  Icurr, ph_ext, nt, ntmin, t0, deltat, a, deltaI)
end = time.time()
print(end-start)
6.295086145401001
ph_ext = 0.25
start = time.time()
res1 = comput_vax(s0, beta_c, beta_L, Pi, n,
                  Icurr, ph_ext, nt, ntmin, t0, deltat, a, deltaI)
end = time.time()
print(end-start)
13.635685443878174
ph_ext = 0.5
start = time.time()
res2 = comput_vax(s0, beta_c, beta_L, Pi, n,
                  Icurr, ph_ext, nt, ntmin, t0, deltat, a, deltaI)
end = time.time()
print(end-start)
26.87863850593567

График ВАХ#

    fig = plt.figure(figsize=(9, 8))
    plt.plot(res0[:, 0], res0[:, 1], label='ВАХ phi_ext=0',  linewidth=3.0)
    plt.plot(res1[:, 0], res1[:, 1], label='ВАХ phi_ext=0.25',  linewidth=3.0)
    plt.plot(res2[:, 0], res2[:, 1], label='ВАХ phi_ext=0.5',  linewidth=3.0)
    plt.xlabel('I', size=12)
    plt.ylabel('V', size=12)
    plt.legend(loc='upper left')
    plt.savefig('cvcsquid.png')
    plt.show()
_images/d8cd627f81b93ebbdc0b5412bfa4b38dde31304d3becbee512860bd01a294b60.png

Как видно из графика ВАХ при \(\varphi_{ext}=0\) критический ток равен \(I_{c}=2\), при \(\varphi_{ext}=0.25\) - \(I_{c}=1.58\), а при \(\varphi_{ext}=0.5\) - \(I_{c}=0.8\), т.е. с изменением \(\varphi_{ext}\) меняется величина критического тока.

Исследование зависимости тока возврата от внешнего магнитного поля#

Для этого вводим следующие параметры:

  • npoint - количество точек в интервале значений внешнего поля

  • ph_extmin - минимальное значение внешнего поля

  • ph_extmax - максимальное значение внешнего поля

  • deltaph_ext - шаг изменения внешнего поля

npoint = 51
ph_extmin = 0.0
ph_extmax = 2.0
deltaph_ext = (ph_extmax - ph_extmin) / (npoint-1)
print(deltaph_ext)
0.04

Создаем массив значений внешнего поля для которых нужно вычислить ток возврата

ph_ext_array = np.linspace(ph_extmin, ph_extmax, num=npoint)
ph_ext_array.shape
(51,)

Создаем пустой массив для значений тока возврата

Ireturn_array = np.zeros(npoint)
Ireturn_array.shape
(51,)

Ток возврата - это ток при которой в ходе уменьшения внешнего тока напряжение приравнивается к нулю.
Поэтому рассчет начинаем от максимального тока (Imax).

Imax = 2.2
@njit
def get_Ireturn(s0, beta_c, beta_L, Pi, n, ph_ext,
                nt, ntmin, t0, deltat, deltaI, epsilon):
    ''' Функция для вычисления значении тока возврата при заданных параметров.
        Возвращает для заданного значения параметров значение тока возврата.
        s0 - массив начальных условий,
        beta_c, beta_L, Pi, n, Icurr, ph_ext,-параметры модели
        nt - количество точек по времени, в которых найдены решение,
        ntmin - номер шага по времени соответсвующее
                нижней границе интегрирования,
        t0-начальное значение времени,
        deltat - шаг по времени
        deltaI - шаг по току
        epsilon -  точность нахождения тока возврата
        '''
    Icurr = Imax
    a = -1
    while Icurr < 100:
        res = cvcpoint_new(s0, beta_c, beta_L, Pi, n,
                           Icurr, ph_ext, nt, ntmin, t0, deltat)
        Vav = res[0]
        s0 = res[1:]
        # print(Icurr)
        if (Vav <= epsilon):
            Ireturn = Icurr
            break
        Icurr -= deltaI
    return Ireturn
@njit
def funk_numba_return(j, ph_extmin, deltaph_ext, s0, beta_c, beta_L,
                      Pi, n, ph_ext, nt, ntmin, t0, deltat, deltaI, epsilon):
    ''' Функция для вычисления значении тока возврата при заданных параметров и
        фиксированном значении потока внешнего магнитного поля с номером j
        на отрезке [ph_extmin, j*deltaph_ext].
        Возвращает для заданного значения ph_ext значение тока возврата.
        s0 - массив начальных условий,
        beta_c, beta_L, Pi, n, Icurr, ph_ext,-параметры модели
        nt - количество точек по времени, в которых найдены решение,
        ntmin - номер шага по времени соответсвующее
                нижней границе интегрирования,
        t0-начальное значение времени,
        deltat - шаг по времени
        deltaI - шаг по току
        epsilon - точность критического тока
        '''
    ph_ext = ph_extmin + deltaph_ext * j
    Ireturn = get_Ireturn(s0, beta_c, beta_L, Pi, n, ph_ext,
                          nt, ntmin, t0, deltat, deltaI, epsilon)
    return Ireturn
epsilon = 0.1
%%time
for i in range(0, npoint):
    Ireturn = funk_numba_return(i, ph_extmin, deltaph_ext, s0, beta_c, beta_L, Pi, n, ph_ext,
                       nt, ntmin, t0, deltat, deltaI, epsilon)
    Ireturn_array[i] = Ireturn
CPU times: user 5min 7s, sys: 412 ms, total: 5min 8s
Wall time: 5min 8s
fig = plt.figure(figsize=(6, 6))
plt.plot(ph_ext_array, Ireturn_array,
         label='Зависимость $I_{return}$ от phi_ext', linewidth=3.0)
plt.xlabel('$phi_{ext}$', size=12)
plt.ylabel('$I_{return}$', size=12)
plt.legend(loc='upper right')
plt.savefig('Icfield.png')
plt.show()
_images/b496ef285a03dcae12e17719b09e7d99c706ce9999539fafd447c677f7dc79d2.png

4. Вычисление модуляции напряжения при фиксированном токе от внешнего магнитного поля#

  • npoint - количество точек в интервале значений внешнего поля

  • ph_extmin - минимальное значение внешнего поля

  • ph_extmax - максимальное значение внешнего поля

  • deltaph_ext - шаг изменения внешнего поля

  • I_curr - значение тока

  • I_fix_min - минимальное значение тока

  • I_fix_max - максимальное значение тока

  • delta_I_fix - шаг на отрезке по току.

  • n_point - кол-во точек по внешнему полю.

  • n_fix_point - кол-во точек по току.

ph_ext = 0.0
Icurr = 2.7
n_fix_point = 20
n_point = 300
ph_extmin = 0.0
ph_extmax = 2
delta_ph_ext = (ph_extmax - ph_extmin) / (n_point-1)
I_fix_min = 0.2
delta_I_fix = 0.1
I_fix_max =I_fix_min + n_fix_point *delta_I_fix 
  • I_fix_array - массив точек по току.

  • ph_ext_array - массив точек по внешнему полю.

I_fix_array = np.linspace(I_fix_min, I_fix_max, num = n_fix_point)
I_fix_array.shape
(100,)
ph_ext_array = np.linspace(ph_extmin, ph_extmax, num=n_point)
ph_ext_array.shape
print(ph_ext_array)
[0.         0.10526316 0.21052632 0.31578947 0.42105263 0.52631579
 0.63157895 0.73684211 0.84210526 0.94736842 1.05263158 1.15789474
 1.26315789 1.36842105 1.47368421 1.57894737 1.68421053 1.78947368
 1.89473684 2.        ]
Vmod_array = []
@njit
def get_Vmod(s0, beta_c, beta_L, Pi, n, ph_ext,
           nt, ntmin, t0, deltat, deltaI):
    ''' Функция для вычисления значении критического тока
            при заданных параметров. Возвращает для заданного
            значения параметров значение критического тока.
        s0 - массив начальных условий,
        beta_c, beta_L, Pi, n, Icurr, ph_ext,-параметры модели
        nt - количество точек по времени, в которых найдены решение,
        ntmin - номер шага по времени соответсвующее
                нижней границе интегрирования,
        t0-начальное значение времени,
        deltat - шаг по времени
        deltaI - шаг по току
        epsilonV -  точность нахождения модулированного напряжения
        '''
    a = 1    
    res = comput_vax(s0, beta_c, beta_L, Pi, n, Icurr, ph_ext, nt, ntmin, t0, deltat, a, deltaI)
#     print(ph_ext)
    return res
    
@njit
def numba_V_mod (j, ph_extmin, deltaph_ext, s0, beta_c, beta_L, Pi, n, ph_ext,
               nt, ntmin, t0, deltat, deltaI, I_fix_array, n_fix_point):

        ''' Функция для вычисления значении модуляции напряжения от величины внешнего поля
        при заданных параметров. Возвращает для заданного значения параметров и величин внешнего поля
        массивы значений напряжения, извлеченных из соответсВАХ.
        s0 - массив начальных условий,
        beta_c, beta_L, Pi, n, Icurr, ph_ext,-параметры модели
        nt - количество точек по времени, в которых найдены решение,
        ntmin - номер шага по времени соответсвующее нижней границе интегрирования,
        t0-начальное значение времени,
        deltat - шаг по времени
        deltaI - шаг по току
        '''
        
        V_I = []
        ph_ext = ph_extmin + j* deltaph_ext
        cvc = get_Vmod(s0, beta_c, beta_L, Pi, n, ph_ext,
        nt, ntmin, t0, deltat, deltaI)
        
        cvc_0 = cvc[:, 0]
        
        for i in I_fix_array:
        # Находим индекс минимального значения
                min_index = np.argmin(np.abs(cvc_0 - i))
                V_I.append(cvc[min_index][1])
        return V_I

Создаем массив, в который будем заполнять значения напряжений при определенных значений тока.

%%time
Vmod_array = np.empty((n_point, n_fix_point), dtype=np.float64)

for i in range(0, n_point):
    Vmod = numba_V_mod(i,  ph_extmin, delta_ph_ext, s0, beta_c, beta_L, Pi, n,
                    ph_ext, nt, ntmin, t0, deltat, deltaI, I_fix_array, n_fix_point)
    
    Vmod_array[i] = Vmod
CPU times: user 4min 19s, sys: 404 ms, total: 4min 19s
Wall time: 4min 19s

Обозначим np.array(Vmod_array) = data и вычислим значение num - кол-во кривых модуляции

data = np.array(Vmod_array)
print(len(data[:,0]))
num = len(data[0])
print(len(ph_ext_array))
20
20

Построим зависимости напряжений от внешнего поля при различных значениях тока

fig = plt.figure(figsize=(10, 10))
for a in range (0, num):
    plt.plot(ph_ext_array, data[:, a], linewidth=3.0)
plt.xlabel('$phi_{ext}$', size=12)
plt.ylabel('$I$', size=12)
plt.legend('beta_C = {beta_C}', 'beta_L = {beta_L}')
plt.show()
/tmp/ipykernel_407029/1188417558.py:6: UserWarning: Legend does not support handles for str instances.
A proxy artist may be used instead.
See: https://matplotlib.org/stable/users/explain/axes/legend_guide.html#controlling-the-legend-entries
  plt.legend('beta_C = {beta_C}', 'beta_L = {beta_L}')
_images/e013b2697ebaf4f2fdbb481659ccf08e0cc1f7caa9124f5f28737529bdf690b8.png

Параллелизация с numba#

Материалы документации Numba

Setting the Number of Threads
The number of threads used by numba is based on the number of CPU cores available (see numba.config.NUMBA_DEFAULT_NUM_THREADS), but it can be overridden with the NUMBA_NUM_THREADS environment variable.

https://numba.readthedocs.io/en/stable/developer/threading_implementation.html

from numba import config, njit, threading_layer
from numba import set_num_threads, get_num_threads
# set the threading layer before any parallel target compilation
config.THREADING_LAYER = 'threadsafe'
config.NUMBA_DEFAULT_NUM_THREADS
80
config.THREADING_LAYER = 'omp'
import os
print('NUMBA NUM THREADS')
try:
    print(os.environ['NUMBA_NUM_THREADS'])
except:
    print(config.NUMBA_DEFAULT_NUM_THREADS)
NUMBA NUM THREADS
80
import numba as nb
from numba import prange

Параллелизация вычисления зависимости тока возврата от величины внешнего поля#

npoint = 51
ph_extmin = 0.0
ph_extmax = 2.0
deltaph_ext = (ph_extmax - ph_extmin) / (npoint-1)
print(deltaph_ext)
ph_ext_array = np.linspace(ph_extmin, ph_extmax, num=npoint)
ph_ext_array.shape
0.04
(51,)
@njit(parallel=True)
def comput_parallel_I_return(npoint, ph_extmin, deltaph_ext, s0, beta_c, beta_L, Pi, n,
                                ph_ext, nt, ntmin, t0, deltat, deltaI, epsilon):
    ''' Функция для вычисления зависимости тока возврата
            от потока внешнего магнитного поля при заданных параметров.
            Возвращает массив значений токов возврата для заданного
            интервала значений ph_ext.
        npoint - количество точек ph_ext в интервале
        ph_extmin - начало интервала потока внешнего магнитного поля,
        deltaph_ext - шаг потока внешнего магнитного поля,
        s0 - массив начальных условий,
        beta_c, beta_L, Pi, n, Icurr, ph_ext,-параметры модели
        nt - количество точек по времени, в которых найдены решение,
        ntmin - номер шага по времени соответсвующее
                нижней границе интегрирования,
        t0-начальное значение времени,
        deltat - шаг по времени
        deltaI - шаг по току
        epsilon - точность тока возврата
        '''
    Ireturn_array = np.empty(npoint)
    Ireturn_array[:] = 0
    for i in nb.prange(npoint):
        Ireturn = funk_numba_return(i, ph_extmin, deltaph_ext, s0, beta_c,
                                    beta_L, Pi, n, ph_ext, nt, ntmin, t0, deltat, deltaI, epsilon)
        Ireturn_array[i] = Ireturn
    return Ireturn_array
%%time
Ireturn_array  = comput_parallel_I_return(npoint, ph_extmin, deltaph_ext, s0, beta_c, beta_L, Pi, n, ph_ext, nt, ntmin, t0, deltat, deltaI, epsilon)
CPU times: user 5min 51s, sys: 813 ms, total: 5min 52s
Wall time: 14.1 s
fig = plt.figure(figsize=(6, 6))
plt.plot(ph_ext_array, Ireturn_array,
         label='Зависимость I_{return} от phi_ext', linewidth=3.0)
plt.xlabel('$phi_{ext}$', size=12)
plt.ylabel('$I_{return}$', size=12)
plt.legend(loc='upper right')
plt.show()
_images/99817339ea562e7482f874e465ff8501215ed87ed362815a53144a8a52e82647.png

Параллелизация вычисления зависимости модуляции напряжения от величины внешнего поля#

ph_ext-внешнее поле, ph_ext_min- минимальное значение внешнего поля, ph_extmax - максимальное значение внешнего поля, delta_ph_ext - шаг изменения внешнего поля, I_fix_min - минимальное значение тока для вычисления модуляции напряжения, I_fix_max - максимальное значение тока для вычисления модуляции напряжения, delta_I_fix - шаг изменения тока для вычисления модуляции напряжения. n_point - количество точек по внешнему полю. n_fix_point - количество точек по току.

ph_ext = 0.0
Icurr = 2.7
n_fix_point = 20
n_point = 300
ph_extmin = 0.0
ph_extmax = 2
delta_ph_ext = (ph_extmax - ph_extmin) / (n_point-1)
I_fix_min = 0.2
delta_I_fix = 0.1
I_fix_max =I_fix_min + n_fix_point *delta_I_fix 
  • I_fix_array - массив точек по току.

  • ph_ext_array - массив точек по внешнему полю.

I_fix_array = np.linspace(I_fix_min, I_fix_max, num = n_fix_point)
I_fix_array.shape
(20,)
ph_ext_array = np.linspace(ph_extmin, ph_extmax, num=n_point)
ph_ext_array.shape
(4,)
Vmod_array = []
@njit(parallel=True)
def comput_parallel_V_mod(n_point, ph_extmin, delta_ph_ext, s0, beta_c, beta_L, Pi, n,
                    ph_ext, nt, ntmin, t0, deltat, deltaI, I_fix_array, n_fix_point):
    ''' Функция для вычисления значении модуляции напряжения от величины внешнего поля
        при заданных параметров. Возвращает для заданного значения параметров и величин внешнего поля
        массивы значений напряжения, извлеченных из соответсВАХ.
        s0 - массив начальных условий,
        beta_c, beta_L, Pi, n, Icurr, ph_ext,-параметры модели
        nt - количество точек по времени, в которых найдены решение,
        ntmin - номер шага по времени соответсвующее нижней границе интегрирования,
        t0-начальное значение времени,
        deltat - шаг по времени
        deltaI - шаг по току
        '''
    Vmod_array = np.empty((n_point, n_fix_point), dtype=np.float64)
    # new_arr = []
    for i in nb.prange(n_point):

        Vmod = numba_V_mod(i,  ph_extmin, delta_ph_ext, s0, beta_c, beta_L, Pi, n,
                        ph_ext, nt, ntmin, t0, deltat, deltaI, I_fix_array, n_fix_point)

        Vmod_array[i]=Vmod
    return Vmod_array

Создаем массив, в который будем заполнять рассчитанные значений напряжения с изменением внешнего поля.

%%time
Vmod_array  = comput_parallel_V_mod(n_point, ph_extmin, delta_ph_ext, s0, beta_c, beta_L, Pi, n, ph_ext, nt, ntmin, t0, deltat, deltaI, I_fix_array, n_fix_point)
CPU times: user 1min 2s, sys: 553 ms, total: 1min 3s
Wall time: 18.7 s

Обозначим np.array(Vmod_array) = data и вычислим значение num - количество кривых модуляции напряжения

print(Vmod_array)
[[ 1.69499714e-12 -1.86973683e-12  5.33284545e-13 -8.31354652e-12
  -8.14992602e-13 -2.05586823e-13 -2.68358034e-12 -1.05219235e-11
   1.03259674e-11 -5.79752522e-12 -1.67572572e-11 -4.99187715e-12
  -1.69603867e-12 -1.45710002e-11  3.72771595e-12  1.22817770e-11
  -1.12167560e-11 -3.43144085e-08  3.15166109e-01  4.60495652e-01]
 [ 1.34547695e-12 -3.57796643e-12 -7.83029998e-12  2.78705096e-13
  -3.30862512e-12  1.48667530e-11  3.89322628e-12 -4.26815458e-12
  -3.85719161e-12 -3.52771299e-12 -3.62327779e-11  1.81252656e-01
   3.26185752e-01  4.17454839e-01  4.92111290e-01  5.60845840e-01
   6.23947085e-01  6.84899670e-01  7.44958889e-01  8.02487914e-01]
 [ 4.26700346e-12 -1.34912346e-12 -8.60547155e-12 -7.79911861e-12
   2.74682069e-12  5.21910454e-12  7.85144255e-12 -7.10788519e-14
  -1.39098533e-11  1.96622643e-11 -6.11951302e-12  1.83864680e-01
   3.26210348e-01  4.15999693e-01  4.92004809e-01  5.61114560e-01
   6.22873613e-01  6.85952233e-01  7.45231310e-01  8.03268375e-01]
 [-2.07231558e-12  1.74848465e-12 -2.98256500e-13 -2.15779507e-12
   1.16095386e-11  3.97401380e-13 -1.95437942e-12  5.81752350e-12
  -4.71583515e-13 -8.69916250e-12 -2.76726320e-12  5.69774767e-13
  -9.18000367e-12 -2.46355351e-11  4.00687426e-12  1.27242513e-11
  -1.79850490e-11 -3.39905668e-08  3.10487736e-01  4.56978494e-01]]
data = np.array(Vmod_array)
num = len(data[0]) 
fig = plt.figure(figsize=(10, 10))
for a in range(0, num):
    plt.plot(ph_ext_array, data[:, a], label = a, linewidth=3.0)
plt.xlabel('$phi_{ext}$', size=12)
plt.ylabel('$V$', size=12)
plt.show()
_images/c1d76b7332fd5a5e9bf16dd473896ecd519aad0b4496f589df63499fcbba01f0.png

Вычисления при разных чисел потоков.

На этом этапе реализована параллелизация вычислении критического тока. На графике представлена рассчитанная зависимость критического тока СКВИДа от потока \(\varphi_{ext}\) в интервале потока [0, 2]. Данная зависимость демонстрирует модулирование критического тока с изменением \(\varphi_{ext}\), что согласуется с известными результатами. Теперь можно вычислить зависимость времени вычисления от числа вычислительных потоков.

num_treads = [1, 5, 10, 15, 20, 25, 30, 35, 40, 45, 50, 55, 60, 65, 70, 75, 80]
time_stamp = []
for i in num_treads:
    set_num_threads(i)
    t_start = time.time()
    Vmod_array  = comput_parallel_V_mod(n_point, ph_extmin, delta_ph_ext, s0, beta_c, beta_L, Pi, n, ph_ext, nt, ntmin, t0, deltat, deltaI, I_fix_array, n_fix_point)
    t_finish = time.time()
    time_stamp.append(t_finish - t_start)
    print(f'Execution time {t_finish - t_start} s')
fig = plt.figure(figsize=(10, 10))
# for a in range (0, len(res)):
plt.plot(num_treads, time_stamp, label = a, linewidth=3.0)
plt.xlabel('$Threads number$', size=12)
plt.ylabel('$Time (s)$', size=12)
plt.savefig('Time_Num_threads.png')
plt.show()

total_time - массив времени расчетов

total_time = [3702.65714263916, 942.7957887649536, 531.9059028625488, 356.6171684265137, 266.47338223457336, 217.4221169948578, 182.3952293395996, 164.5790901184082, 149.1030695438385, 141.55036878585815, 135.79208898544312, 135.71493935585022, 123.3793740272522, 127.7741801738739, 121.7995765209198, 111.03557324409485, 112.71054816246033]
ax = []
for i in time_stamp:
    local_ax = time_stamp[0] / i
    ax.append(local_ax)
---------------------------------------------------------------------------
NameError                                 Traceback (most recent call last)
Cell In[71], line 1
----> 1 for i in time_stamp:
      2     local_ax = time_stamp[0] / i
      3     ax.append(local_ax)

NameError: name 'time_stamp' is not defined
fig = plt.figure(figsize=(10, 10))
# for a in range (0, len(res)):
plt.plot(num_treads, ax, label = a, linewidth=3.0)
plt.xlabel('Количество потоков', size=12)
plt.ylabel('Ускорение вычислений', size=12)
plt.savefig('Ax_Num_threads.png')
plt.show()
_images/04838d6bb4f8c438ba9ef02be8da126eeb2417db3db6776911e447435a8ed2ae.png