"""
elements.py contains the classes for the circuit elements:
capacitors, inductors, and josephson junctions.
"""
from typing import List, Any, Optional, Union, Callable
import numpy as np
from scipy.special import kn
import SQcircuit.units as unt
[docs]class Capacitor:
"""
Class that contains the capacitor properties.
Parameters
----------
value:
The value of the capacitor.
unit:
The unit of input value. If ``unit`` is "THz", "GHz", and ,etc.,
the value specifies the charging energy of the capacitor. If ``unit``
is "fF", "pF", and ,etc., the value specifies the capacitance in
farad. If ``unit`` is ``None``, the default unit of capacitor is "GHz".
Q:
Quality factor of the dielectric of the capacitor which is one over
tangent loss. It can be either a float number or a Python function of
angular frequency.
error:
The error in fabrication as a percentage.
id_str:
ID string for the capacitor.
"""
def __init__(
self,
value: float,
unit: Optional[str] = None,
Q: Union[Any, Callable[[float], float]] = "default",
error: float = 0,
id_str: Optional[str] = None,
) -> None:
if (unit not in unt.freq_list and
unit not in unt.farad_list and
unit is not None):
error = "The input unit for the capacitor is not correct. " \
"Look at the documentation for the correct input format."
raise ValueError(error)
self.cValue = value
self.error = error
self.type = type(self)
if unit is None:
self.unit = unt.get_unit_cap()
else:
self.unit = unit
if Q == "default":
self.Q = lambda omega: 1e6 * (
2 * np.pi * 6e9 / np.abs(omega)) ** 0.7
elif isinstance(Q, float) or isinstance(Q, int):
self.Q = lambda omega: Q
else:
self.Q = Q
if id_str is None:
self.id_str = "C_{}_{}".format(value, self.unit)
else:
self.id_str = id_str
[docs] def value(self, random: bool = False) -> float:
"""
Return the value of the capacitor in farad units. If `random` is
`True`, it samples from a normal distribution with variance defined
by the fabrication error.
Parameters
----------
random:
A boolean flag which specifies whether the output is
deterministic or random.
"""
if self.unit in unt.farad_list:
cMean = self.cValue * unt.farad_list[self.unit]
else:
E_c = self.cValue * unt.freq_list[self.unit] * (
2 * np.pi * unt.hbar)
cMean = unt.e ** 2 / 2 / E_c
if not random:
return cMean
else:
return np.random.normal(cMean, cMean * self.error / 100, 1)[0]
[docs] def energy(self) -> float:
"""
Return the charging energy of the capacitor in frequency unit of
SQcircuit (gigahertz by default).
"""
if self.unit in unt.freq_list:
return self.cValue * unt.freq_list[
self.unit] / unt.get_unit_freq()
else:
c = self.cValue * unt.farad_list[self.unit]
return unt.e ** 2 / 2 / c / (
2 * np.pi * unt.hbar) / unt.get_unit_freq()
class VerySmallCap(Capacitor):
def __init__(self):
super().__init__(1e-20, "F", Q=None)
class VeryLargeCap(Capacitor):
def __init__(self):
super().__init__(1e20, "F", Q=None)
[docs]class Inductor:
"""
Class that contains the inductor properties.
Parameters
----------
value:
The value of the inductor.
unit:
The unit of input value. If ``unit`` is "THz", "GHz", and ,etc.,
the value specifies the inductive energy of the inductor. If ``unit``
is "fH", "pH", and ,etc., the value specifies the inductance in henry.
If ``unit`` is ``None``, the default unit of inductor is "GHz".
loops:
List of loops in which the inductor resides.
cap:
Capacitor associated to the inductor, necessary for correct
time-dependent external fluxes scheme.
Q:
Quality factor of the inductor needed for inductive loss calculation.
It can be either a float number or a Python function of angular
frequency and temperature.
error:
The error in fabrication as a percentage.
id_str:
ID string for the inductor.
"""
def __init__(
self,
value: float,
unit: str = None,
cap: Optional["Capacitor"] = None,
Q: Union[Any, Callable[[float, float], float]] = "default",
error: float = 0,
loops: Optional[List["Loop"]] = None,
id_str: Optional[str] = None
) -> None:
if (unit not in unt.freq_list and
unit not in unt.henry_list and
unit is not None):
error = "The input unit for the inductor is not correct. " \
"Look at the documentation for the correct input format."
raise ValueError(error)
self.lValue = value
self.error = error
self.type = type(self)
self.id_str = id_str
if unit is None:
self.unit = unt.get_unit_ind()
else:
self.unit = unit
if cap is None:
self.cap = VerySmallCap()
else:
self.cap = cap
if loops is None:
self.loops = []
else:
self.loops = loops
def qInd(omega, T):
alpha = unt.hbar * 2 * np.pi * 0.5e9 / (2 * unt.k_B * T)
beta = unt.hbar * omega / (2 * unt.k_B * T)
return 500e6 * (kn(0, alpha) * np.sinh(alpha)) / (
kn(0, beta) * np.sinh(beta))
if Q == "default":
self.Q = qInd
elif isinstance(Q, float) or isinstance(Q, int):
self.Q = lambda omega, T: Q
else:
self.Q = Q
if id_str is None:
self.id_str = "L_{}_{}".format(value, self.unit)
else:
self.id_str = id_str
[docs] def value(self, random: bool = False) -> float:
"""
Return the value of the inductor in henry units. If `random` is
`True`, it samples from a normal distribution with variance defined
by the fabrication error.
Parameters
----------
random:
A boolean flag which specifies whether the output is
deterministic or random.
"""
if self.unit in unt.henry_list:
lMean = self.lValue * unt.henry_list[self.unit]
else:
E_l = self.lValue * unt.freq_list[self.unit] * (
2 * np.pi * unt.hbar)
lMean = (unt.Phi0 / 2 / np.pi) ** 2 / E_l
if not random:
return lMean
else:
return np.random.normal(lMean, lMean * self.error / 100, 1)[0]
[docs] def energy(self) -> float:
"""
Return the inductive energy of the capacitor in frequency unit of
SQcircuit (gigahertz by default).
"""
if self.unit in unt.freq_list:
return self.lValue * unt.freq_list[
self.unit] / unt.get_unit_freq()
else:
l = self.lValue * unt.henry_list[self.unit]
return (unt.Phi0 / 2 / np.pi) ** 2 / l / (
2 * np.pi * unt.hbar) / unt.get_unit_freq()
[docs]class Junction:
"""
Class that contains the Josephson junction properties.
Parameters
-----------
value:
The value of the Josephson junction.
unit: str
The unit of input value. The ``unit`` can be "THz", "GHz", and ,etc.,
that specifies the junction energy of the inductor. If ``unit`` is
``None``, the default unit of junction is "GHz".
loops:
List of loops in which the Josephson junction reside.
cap:
Capacitor associated to the josephson junction, necessary for the
correct time-dependent external fluxes scheme.
A:
Normalized noise amplitude related to critical current noise.
x:
Quasiparticle density
delta:
Superconducting gap
Y:
Real part of admittance.
error:
The error in fabrication as a percentage.
id_str:
ID string for the junction.
"""
def __init__(
self,
value: float,
unit: Optional[str] = None,
cap: Optional[str] = None,
A: float = 1e-7,
x: float = 3e-06,
delta: float = 3.4e-4,
Y: Union[Any, Callable[[float, float], float]] = "default",
error: float = 0,
loops: Optional[List["Loop"]] = None,
id_str: Optional[str] = None,
) -> None:
if (unit not in unt.freq_list and
unit is not None):
error = "The input unit for the Josephson Junction is not " \
"correct. Look at the documentation for the correct " \
"input format."
raise ValueError(error)
self.jValue = value
self.error = error
self.type = type(self)
self.A = A
self.id_str = id_str
if unit is None:
self.unit = unt.get_unit_JJ()
else:
self.unit = unit
if cap is None:
self.cap = VerySmallCap()
else:
self.cap = cap
if loops is None:
self.loops = []
else:
self.loops = loops
def yQP(omega, T):
alpha = unt.hbar * omega / (2 * unt.k_B * T)
y = np.sqrt(2 / np.pi) * (8 / (delta * 1.6e-19) / (
unt.hbar * 2 * np.pi / unt.e ** 2)) \
* (2 * (delta * 1.6e-19) / unt.hbar / omega) ** 1.5 \
* x * np.sqrt(alpha) * kn(0, alpha) * np.sinh(alpha)
return y
if Y == "default":
self.Y = yQP
else:
self.Y = Y
if id_str is None:
self.id_str = "JJ_{}_{}".format(value, self.unit)
else:
self.id_str = id_str
[docs] def value(self, random: bool = False) -> float:
"""
Return the value of the Josephson Junction in angular frequency.
If `random` is `True`, it samples from a normal distribution with
variance defined by the fabrication error.
Parameters
----------
random:
A boolean flag which specifies whether the output
is deterministic or random.
"""
jMean = self.jValue * unt.freq_list[self.unit] * 2 * np.pi
if not random:
return jMean
else:
return np.random.normal(jMean, jMean * self.error / 100, 1)[0]
[docs]class Loop:
"""
Class that contains the inductive loop properties, closed path of
inductive elements.
Parameters
----------
value:
Value of the external flux at the loop.
A:
Normalized noise amplitude related to flux noise.
id_str:
ID string for the loop.
"""
def __init__(
self,
value: float = 0,
A: float = 1e-6,
id_str: Optional[str] = None
) -> None:
self.lpValue = value * 2 * np.pi
self.A = A * 2 * np.pi
# indices of inductive elements.
self.indices = []
# k1 matrix related to this specific loop
self.K1 = []
if id_str is None:
self.id_str = "loop"
else:
self.id_str = id_str
def reset(self) -> None:
self.K1 = []
self.indices = []
[docs] def value(self, random: bool = False) -> float:
"""
Return the value of the external flux. If `random` is `True`, it
samples from a normal distribution with variance defined by the flux
noise amplitude.
Parameters
----------
random:
A boolean flag which specifies whether the output is
deterministic or random.
"""
if not random:
return self.lpValue
else:
return np.random.normal(self.lpValue, self.A, 1)[0]
[docs] def set_flux(self, value: float) -> None:
"""
Set the external flux associated to the loop.
Parameters
----------
value:
The external flux value
"""
self.lpValue = value * 2 * np.pi
def add_index(self, index):
self.indices.append(index)
def addK1(self, w):
self.K1.append(w)
def getP(self):
K1 = np.array(self.K1)
a = np.zeros_like(K1)
select = np.sum(K1 != a, axis=0) != 0
# eliminate the zero columns
K1 = K1[:, select]
if K1.shape[0] == K1.shape[1]:
K1 = K1[:, 0:-1]
b = np.zeros((1, K1.shape[0]))
b[0, 0] = 1
p = np.linalg.inv(np.concatenate((b, K1.T), axis=0)) @ b.T
return p.T
class Charge:
"""
class that contains the charge island properties.
"""
def __init__(self, value: float = 0, A: float = 1e-4) -> None:
"""
inputs:
-- value: The value of the offset.
-- noise: The amplitude of the charge noise.
"""
self.chValue = value
self.A = A
def value(self, random: bool = False) -> float:
"""
returns the value of charge bias. If random flag is true, it samples
from a normal distribution.
inputs:
-- random: A flag which specifies whether the output is picked
deterministically or randomly.
"""
if not random:
return self.chValue
else:
return np.random.normal(self.chValue, self.noise, 1)[0]
def setOffset(self, value: float) -> None:
self.chValue = value
def setNoise(self, A: float) -> None:
self.A = A