Source code for qugradlab.systems.semiconducting.esr.rotating_wave_approximation._systems

  1"""
  2A collection of :class:`qugrad.QuantumSystem` s for electron spin resonance (ESR)
  3devices with a linear array of qubits under the rotating wave approximation.
  4"""
  5
  6import numpy as np
  7
  8from ._controls import Controls
  9from .....hilbert_spaces import QubitSpace
 10from ....skeletons.qubits import QubitSystem
 11
[docs] 12class SpinChain(Controls, QubitSystem): 13 r""" 14 A :class:`qugrad.QuantumSystem` for a spin chain with electron spin 15 resonance (ESR) controls with a linear array of qubits under the rotating 16 wave approximation. The Hamiltonian is given by 17 $$ 18 H(t) = \frac{1}{4} \sum_{i=0}^{\texttt{spins}-1} \left[I_i(t) X_i 19 + Q_i(t)Y_i\right] 20 + \frac{1}{4} \sum_{i=0}^{\texttt{spins}-2} J_i(t)\left[Z_iZ_{i+1} 21 +\cos(\omega_i t)\left[X_iX_{i+1}+Y_iY_{i+1}\right] 22 +\sin(\omega_i t)\left[X_iY_{i+1}-Y_iX_{i+1}\right]\right], 23 $$ 24 where 25 $X_i$, $Y_i$, and $Z_i$ are the Pauli-x, -y, and -z operators acting on the 26 $i$th spin, $\omega_i$ are the Zeeman detunings, $J_i(t)$ is the exchange 27 coupling. 28 """ 29 30 _ferromagnetic: bool 31 """Whether the exchange coupling is ferromagnetic or antiferromagnetic""" 32
[docs] 33 def __init__(self, 34 spins: int, 35 zeeman_detunings: np.ndarray[float], 36 max_drive_strength: float, 37 J_max: float, 38 J_min: float = 0, 39 feromagnetic: bool = True, 40 use_graph: bool = True): 41 r""" 42 Initialises a spin chain with ESR controls. The Hamiltonian is 43 given by: 44 $$ 45 H(t) = \frac{1}{4} \sum_{i=0}^{\texttt{spins}-1} \left[I_i(t) X_i 46 + Q_i(t)Y_i\right] 47 + \frac{1}{4} \sum_{i=0}^{\texttt{spins}-2} J_i(t)\left[Z_iZ_{i+1} 48 +\cos(\omega_i t)\left[X_iX_{i+1}+Y_iY_{i+1}\right] 49 +\sin(\omega_i t)\left[X_iY_{i+1}-Y_iX_{i+1}\right]\right], 50 $$ 51 where 52 $X_i$, $Y_i$, and $Z_i$ are the Pauli-x, -y, and -z operators acting on the 53 $i$th spin, $\omega_i$ are the Zeeman detunings, $J_i(t)$ is the exchange 54 coupling. 55 56 Parameters 57 ---------- 58 spins : int 59 The number of spins in the chain 60 zeeman_detunings : NDArray[Shape[spins], float] 61 The Zeeman detuning of each of the spins 62 max_drive_strength : float 63 The maximum drive strength that can be applied at a specific 64 frequency and quadrature. That is if their are ``n_drive_ctrl`` 65 frequencies and both quadratures are used then the maximum amplitude 66 of the drive that can be applied to the device is:: 67 68 np.sqrt(2) * n_drive_ctrl * max_drive_strength 69 J_max : float 70 The minimum value of the exchange coupling $J$ 71 J_min : float 72 The maximum value of the exchange coupling $J$, by default 0 73 feromagnetic : bool 74 If ``True``, the exchange coupling is ferromagnetic. If ``False``, 75 the exchange coupling is antiferromagnetic. By default, ``True``. 76 use_graph : bool 77 Whether to use `TensorFlow <https://www.tensorflow.org>`__ graphs 78 during computation, by default ``True`` 79 """ 80 Controls.__init__(self, 81 zeeman_detunings, 82 max_drive_strength, 83 J_min, 84 J_max) 85 single_qubit_drift_coefficients = np.zeros((spins, 3)) 86 87 single_qubit_ctrl_coefficients = \ 88 np.stack([np.array([[1/4, 0, 0]]*spins), 89 np.array([[0, 1/4, 0]]*spins)]) 90 91 forward_connectivity = np.einsum("ij,jk->ijk", 92 np.eye(spins, spins, 0), 93 np.eye(spins, spins, 1) 94 )[:-1] 95 backward_connectivity = np.einsum("ij,jk->ijk", 96 np.eye(spins, spins, 1), 97 np.eye(spins, spins, -1) 98 )[:-1] 99 connectivity = forward_connectivity + backward_connectivity 100 J_Z = 0.5**3*np.multiply.outer(connectivity, np.diag([0, 0, 1])) 101 J_XX_YY = 0.5**3*np.multiply.outer(connectivity, np.diag([1, 1, 0])) 102 # Check the minus sign convention in the below definition. is it XY-YX 103 # or YX-XY 104 105 J_XY_YX = 0.5**3*np.multiply.outer((np.einsum("ij,j...->ij...", np.eye(spins, spins, 0), np.eye(spins, spins, 1)) 106 -np.einsum("ij,j...->ij...", np.eye(spins, spins, 1), np.eye(spins, spins, -1)))[:-1], 107 np.array([[0, 1, 0], 108 [-1, 0, 0], 109 [0, 0, 0]])) 110 # one factor of 0.5 is for double counting, the other two are the 111 # factors of two differences between spin operators and Pauli 112 # operators 113 J = np.stack([J_Z, J_XX_YY, J_XY_YX]) 114 if feromagnetic: J *= -1 115 self._ferromagnetic = feromagnetic 116 QubitSystem.__init__(self, 117 QubitSpace(spins), 118 single_qubit_drift_coefficients, 119 np.zeros((spins, spins, 3, 3)), 120 single_qubit_ctrl_coefficients, 121 J, 122 use_graph)
123 @property 124 def ferromagnetic(self) -> bool: 125 """ 126 Whether the exchange coupling is ferromagnetic or antiferromagnetic 127 """ 128 return self._ferromagnetic