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

  1"""
  2A collection of :class:`qugrad.QuantumSystem` s for electron spin resonance (ESR)
  3devices.
  4"""
  5
  6from functools import reduce
  7
  8import numpy as np
  9import tensorflow as tf
 10
 11from scipy.linalg import block_diag
 12
 13from ._controls import Controls
 14from ._device import Device
 15from ....hilbert_spaces import QubitSpace
 16from ....hilbert_spaces.fermionic import FermionQuditSpace
 17from ....pulses.composition import concatenate_functions
 18from ....pulses.invertible_functions.scaling import linear_rescaling
 19from ...skeletons.qubits import QubitSystem
 20from ...skeletons.fermionic import FermionicSystem
 21
 22PAULI_X = np.array([[0, 1],
 23                    [1, 0]])
 24"""Pauli-x operator"""
 25
 26PAULI_Z = np.array([[1, 0],
 27                    [0, -1]])
 28"""Pauli-z operator"""
 29
[docs] 30class SpinChain(Controls, QubitSystem): 31 r""" 32 A :class:`qugrad.QuantumSystem` for a spin chain with electron spin 33 resonance (ESR) controls. The Hamiltonian is given by 34 $$ 35 H(t) = \sum_{i=0}^{\texttt{spins}-1} \frac{1}{2} B_i Z_i 36 + g(t) \sum_{i=0}^{\texttt{spins}-1} \frac{1}{2} X_i 37 + \frac{1}{4} \sum_{i=0}^{\texttt{spins}-2} J_i(t) 38 \vec\sigma_i \cdot \vec\sigma_{i+1}, 39 $$ 40 where 41 $\vec\sigma_i\equiv\begin{pmatrix}X_i & Y_i & Z_i\end{pmatrix}^\intercal$ 42 is the vector of the Pauli-x, -y, and -z operators acting on the $i$th spin, 43 $B_i$ are the Zeeman splittings, $J_i(t)$ is the exchange coupling, and 44 $$ 45 g(t) = \sum_{j=0}^{\texttt{spins}-1}\real\left(a_j(t)e^{i\omega_j t}\right), 46 $$ 47 is the Rabi drive with frequency components $\omega_j$ and amplitudes 48 $a_j(t)$. 49 50 See Also 51 -------- 52 * :class:`SpinChainAngledDrive` 53 * :class:`ValleyChain` 54 55 56 - 57 """ 58 59 _ferromagnetic: bool 60 """Whether the exchange coupling is ferromagnetic or antiferromagnetic""" 61
[docs] 62 def __init__(self, 63 spins: int, 64 zeeman_splittings: np.ndarray[float], 65 max_drive_strength: float, 66 J_max: float, 67 J_min: float = 0, 68 feromagnetic: bool = True, 69 use_graph: bool = True): 70 r""" 71 Initialises a spin chain with ESR controls. The Hamiltonian is 72 given by: 73 $$ 74 H(t) = \sum_{i=0}^{\texttt{spins}-1} \frac{1}{2} B_i Z_i 75 + g(t) \sum_{i=0}^{\texttt{spins}-1} \frac{1}{2} X_i 76 + \frac{1}{4} \sum_{i=0}^{\texttt{spins}-2} J_i(t) 77 \vec\sigma_i \cdot \vec\sigma_{i+1}, 78 $$ 79 where 80 $\vec\sigma_i\equiv\begin{pmatrix}X_i&Y_i&Z_i\end{pmatrix}^\intercal$ 81 is the vector of the Pauli-x, -y, and -z operators acting on the $i$th 82 spin, $B_i$ corresponds to `zeeman_splittings`, $J_i(t)$ is the exchange 83 coupling, and 84 $$ 85 g(t) = 86 \sum_{j=0}^{\texttt{spins}-1}\real\left(a_j(t)e^{i\omega_j t}\right), 87 $$ 88 is the Rabi drive with frequency components $\omega_j$ and amplitudes 89 $a_j(t)$. 90 91 Parameters 92 ---------- 93 spins : int 94 The number of spins in the chain 95 zeeman_splittings : NDArray[Shape[spins], float] 96 The Zeeman splitting of each of the spins 97 max_drive_strength : float 98 The maximum drive strength that can be applied at a specific 99 frequency and quadrature. That is if their are ``n_drive_ctrl`` 100 frequencies and both quadratures are used then the maximum amplitude 101 of the drive that can be applied to the device is:: 102 103 np.sqrt(2) * n_drive_ctrl * max_drive_strength 104 J_max : float 105 The minimum value of the exchange coupling $J$ 106 J_min : float 107 The maximum value of the exchange coupling $J$, by default 0 108 feromagnetic : bool 109 If ``True``, the exchange coupling is ferromagnetic. If ``False``, 110 the exchange coupling is antiferromagnetic. By default, ``True``. 111 use_graph : bool 112 Whether to use `TensorFlow <https://www.tensorflow.org>`__ graphs 113 during computation, by default ``True`` 114 """ 115 Controls.__init__(self, 116 zeeman_splittings, 117 max_drive_strength, 118 J_min, 119 J_max) 120 single_qubit_drift_coefficients = \ 121 np.multiply.outer(zeeman_splittings, np.array([0, 0, 0.5])) 122 123 single_qubit_ctrl_coefficients = np.array([[[0.5, 0, 0]]*spins]) 124 125 forward_connectivity = np.einsum("ij,jk->ijk", 126 np.eye(spins, spins, 0), 127 np.eye(spins, spins, 1) 128 )[:-1] 129 backward_connectivity = np.einsum("ij,jk->ijk", 130 np.eye(spins, spins, 1), 131 np.eye(spins, spins, -1) 132 )[:-1] 133 connectivity = forward_connectivity + backward_connectivity 134 J = 0.5**3*np.multiply.outer(connectivity, np.identity(3)) 135 # one factor of 0.5 is for double counting, the other two are the 136 # factors of two differences between spin operators and Pauli 137 # operators 138 if feromagnetic: J *= -1 139 self._ferromagnetic = feromagnetic 140 QubitSystem.__init__(self, 141 QubitSpace(spins), 142 single_qubit_drift_coefficients, 143 np.zeros((spins, spins, 3, 3)), 144 single_qubit_ctrl_coefficients, 145 J, 146 use_graph)
147 @property 148 def ferromagnetic(self) -> bool: 149 """ 150 Whether the exchange coupling is ferromagnetic or antiferromagnetic 151 """ 152 return self._ferromagnetic
153
[docs] 154class SpinChainAngledDrive(Controls, QubitSystem): 155 r""" 156 A :class:`qugrad.QuantumSystem` for a spin chain with electron spin 157 resonance (ESR) controls. The Hamiltonian is given by 158 $$ 159 H(t) = \sum_{i=0}^{\texttt{spins}-1} \frac{1}{2} B_i Z_i 160 + g(t) \sum_{i=0}^{\texttt{spins}-1} \frac{1}{2} 161 \vec v_i \cdot \vec\sigma_i 162 + \frac{1}{4} \sum_{i=0}^{\texttt{spins}-2} J_i(t) 163 \vec\sigma_i \cdot \vec\sigma_{i+1}, 164 $$ 165 where 166 $\vec\sigma_i\equiv\begin{pmatrix}X_i & Y_i & Z_i\end{pmatrix}^\intercal$ 167 is the vector of the Pauli-x, -y, and -z operators acting on the $i$th spin, 168 $B_i$ are the Zeeman splittings, $J_i(t)$ is the exchange coupling, 169 $\vec v_i$ are the drive vectors, and 170 $$ 171 g(t) = \sum_{j=0}^{\texttt{spins}-1}\real\left(a_j(t)e^{i\omega_j t}\right), 172 $$ 173 is the Rabi drive with frequency components $\omega_j$ and amplitudes 174 $a_j(t)$. 175 176 See Also 177 -------- 178 * :class:`SpinChain` 179 * :class:`ValleyChain` 180 181 182 - 183 """ 184 185 _ferromagnetic: bool 186 """Whether the exchange coupling is ferromagnetic or antiferromagnetic""" 187 188 _drive_vectors: np.ndarray[float] 189 """The drive vectors for each of the spins""" 190
[docs] 191 def __init__(self, 192 spins: int, 193 zeeman_splittings: np.ndarray[float], 194 drive_vectors: np.ndarray[float], 195 max_drive_strength: float, 196 J_max: float, 197 J_min: float = 0, 198 feromagnetic: bool = True, 199 use_graph: bool = True): 200 r""" 201 Initialises a spin chain with ESR controls. The Hamiltonian is 202 given by: 203 $$ 204 H(t) = \sum_{i=0}^{\texttt{spins}-1} \frac{1}{2} B_i Z_i 205 + g(t) \sum_{i=0}^{\texttt{spins}-1} \frac{1}{2} 206 \vec v_i \cdot \vec\sigma_i 207 + \frac{1}{4} \sum_{i=0}^{\texttt{spins}-2} J_i(t) 208 \vec\sigma_i \cdot \vec\sigma_{i+1}, 209 $$ 210 where 211 $\vec\sigma_i\equiv\begin{pmatrix}X_i&Y_i&Z_i\end{pmatrix}^\intercal$ 212 is the vector of the Pauli-x, -y, and -z operators acting on the $i$th 213 spin, $B_i$ corresponds to `zeeman_splittings`, $J_i(t)$ is the exchange 214 coupling, $\vec v_i$ corresponds to `drive_vectors`, and 215 $$ 216 g(t) = 217 \sum_{j=0}^{\texttt{spins}-1}\real\left(a_j(t)e^{i\omega_j t}\right), 218 $$ 219 is the Rabi drive with frequency components $\omega_j$ and amplitudes 220 $a_j(t)$. 221 222 Parameters 223 ---------- 224 spins : int 225 The number of spins in the chain 226 zeeman_splittings : NDArray[Shape[spins], float] 227 The Zeeman splitting of each of the spins 228 drive_vectors : NDArray[Shape[spins, 3], float] 229 The drive vectors for each of the spins 230 max_drive_strength : float 231 The maximum drive strength that can be applied at a specific 232 frequency and quadrature. That is if their are ``n_drive_ctrl`` 233 frequencies and both quadratures are used then the maximum amplitude 234 of the drive that can be applied to the device is:: 235 236 np.sqrt(2) * n_drive_ctrl * max_drive_strength 237 J_max : float 238 The minimum value of the exchange coupling $J$ 239 J_min : float 240 The maximum value of the exchange coupling $J$, by default 0 241 feromagnetic : bool 242 If ``True``, the exchange coupling is ferromagnetic. If ``False``, 243 the exchange coupling is antiferromagnetic. By default, ``True``. 244 use_graph : bool 245 Whether to use `TensorFlow <https://www.tensorflow.org>`__ graphs 246 during computation, by default ``True`` 247 """ 248 Controls.__init__(self, 249 zeeman_splittings, 250 max_drive_strength, 251 J_min, 252 J_max) 253 self._drive_vectors = np.array(drive_vectors) 254 self._drive_vectors.flags.writeable = False 255 single_qubit_drift_coefficients = \ 256 np.multiply.outer(zeeman_splittings, np.array([0, 0, 0.5])) 257 258 forward_connectivity = np.einsum("ij,jk->ijk", 259 np.eye(spins, spins, 0), 260 np.eye(spins, spins, 1) 261 )[:-1] 262 backward_connectivity = np.einsum("ij,jk->ijk", 263 np.eye(spins, spins, 1), 264 np.eye(spins, spins, -1) 265 )[:-1] 266 connectivity = forward_connectivity + backward_connectivity 267 J = 0.5**3*np.multiply.outer(connectivity, np.identity(3)) 268 # one factor of 0.5 is for double counting, the other two are the 269 # factors of two differences between spin operators and Pauli 270 # operators 271 if feromagnetic: J *= -1 272 self._ferromagnetic = feromagnetic 273 QubitSystem.__init__(self, 274 QubitSpace(spins), 275 single_qubit_drift_coefficients, 276 np.zeros((spins, spins, 3, 3)), 277 self._drive_vectors, 278 J, 279 use_graph)
280 @property 281 def ferromagnetic(self) -> bool: 282 """ 283 Whether the exchange coupling is ferromagnetic or antiferromagnetic 284 """ 285 return self._ferromagnetic 286 @property 287 def drive_vectors(self) -> np.ndarray[float]: 288 """The drive vectors for each of the spins""" 289 return self._drive_vectors
290
[docs] 291class ValleyChain(Controls, FermionicSystem): 292 r""" 293 A :class:`qugrad.QuantumSystem` for a linear array of silicon electron 294 quantum dots including the valley degree of freedom and electron spin 295 resonance (ESR) controls. The Hamiltonian is given by 296 $$ 297 H(t) = \sum_{\substack{{i,j}\\{\alpha,\beta}}}i_{\alpha}^\dagger j_{\beta} 298 \tilde t^{ij}_{\alpha\beta} 299 +U_0\sum_{i,\left<\alpha,\beta\right>} 300 i_{\alpha}^\dagger i_{\beta}^\dagger i_{\alpha} i_{\beta} 301 +U_1\sum_{i,\alpha,\beta} 302 i_{\alpha}^\dagger i_{\beta}^\dagger i_{\alpha} i_{(\bar\beta_v,\beta_s)}, 303 $$ 304 where $i_{\alpha}^\dagger$ and $i_{\alpha}$ are the creation and annihilation 305 operators for the $i$th dot and $\alpha$ indexes the spin and valley index 306 degrees of freedom, $\left<\alpha,\beta\right>$ represents unique unordered 307 pairs of $\alpha$ and $\beta$, $(\bar\beta_v,\beta_s)$ has the same spin 308 index as $\beta$ but the oposite valley index, U_0$ is the on-site Coulomb 309 no-valley-flip interaction, $U_1$ is the on-site Coulomb valley-flip 310 interaction, the interdot hoppings have the form 311 $\tilde t^{ij}_{\alpha\beta}=h^{ij}(t)\delta_{\alpha\beta}$, and the 312 intradot hoppings take the form 313 $$ 314 \tilde t^{ii}_{\alpha\beta}=\begin{bmatrix} 315 \frac{1}{2}\left(V+Z_i\right)&g(t)^*&&\nu_{\textrm{SO}}^*\\ 316 g(t)&\frac{1}{2}\left(V-Z_i\right)&\nu_{\textrm{SO}}&\\ 317 &\nu_{\textrm{SO}}^*&\frac{1}{2}\left(-V+Z_i\right)&g(t)^*\\ 318 \nu_{\textrm{SO}}&&g(t)&\frac{1}{2}\left(-V-Z_i\right) 319 \end{bmatrix}\begin{matrix}\left(1,\uparrow\right)\\\left(1,\downarrow\right)\\\left(0,\uparrow\right)\\\left(0,\downarrow\right)\end{matrix} 320 $$ 321 where $V$ is the valley splitting, $Z_i$ is the Zeeman splitting on the 322 $i$th dot, $\nu_{\textrm{SO}}$ is the valley-spin-orbit coupling, and 323 $$ 324 g(t) = \sum_{j=0}^{\texttt{dots}-1}\real\left(a_j(t)e^{i\omega_j t}\right), 325 $$ 326 is the Rabi drive with frequency components $\omega_j$ and amplitudes 327 $a_j(t)$. 328 329 See Also 330 -------- 331 * :class:`SpinChain` 332 * :class:`SpinChainAngledDrive` 333 334 335 - 336 """ 337 338 _u: float 339 """The on-site Coulomb no-valley-flip interaction""" 340 341 _u_valley_flip: float 342 """The on-site Coulomb valley-flip interaction""" 343 344 _valley_spin_orbit_coupling: float 345 """The valley-spin-orbit coupling""" 346
[docs] 347 def __init__(self, 348 dots: int, 349 electrons: int, 350 zeeman_splittings: np.ndarray[complex], 351 valley_splitting: float, 352 u: float, 353 u_valley_flip: float, 354 valley_spin_orbit_coupling: float, 355 max_drive_strength: float, 356 J_max: float, 357 J_min: float = 0, 358 use_graph: bool = True): 359 r""" 360 Initialises a linear array of silicon electron quantum dots including 361 the valley degree of freedom and electron spin resonance (ESR) controls. 362 The Hamiltonian is given by 363 $$ 364 H(t) = \sum_{\substack{{i,j}\\{\alpha,\beta}}} 365 i_{\alpha}^\dagger j_{\beta} 366 \tilde t^{ij}_{\alpha\beta} 367 +U_0\sum_{i,\left<\alpha,\beta\right>} 368 i_{\alpha}^\dagger i_{\beta}^\dagger i_{\alpha} i_{\beta} 369 +U_1\sum_{i,\alpha,\beta} 370 i_{\alpha}^\dagger i_{\beta}^\dagger 371 i_{\alpha} i_{(\bar\beta_v,\beta_s)}, 372 $$ 373 where $i_{\alpha}^\dagger$ and $i_{\alpha}$ are the creation and 374 annihilation operators for the $i$th dot and $\alpha$ indexes the spin 375 and valley index degrees of freedom, $\left<\alpha,\beta\right>$ 376 represents unique unordered pairs of $\alpha$ and $\beta$, 377 $(\bar\beta_v,\beta_s)$ has the same spin index as $\beta$ but the 378 oposite valley index, U_0$ is the on-site Coulomb no-valley-flip 379 interaction and corresponds to `u`, $U_1$ is the on-site Coulomb 380 valley-flip interaction and corresponds to `u_valley_flip`, the interdot 381 hoppings have the form 382 $\tilde t^{ij}_{\alpha\beta}=h^{ij}(t)\delta_{\alpha\beta}$, and the 383 intradot hoppings take the form 384 $$ 385 \tilde t^{ii}_{\alpha\beta}=\begin{bmatrix} 386 \frac{1}{2}\left(V+Z_i\right)&g(t)^*&&\nu_{\textrm{SO}}^*\\ 387 g(t)&\frac{1}{2}\left(V-Z_i\right)&\nu_{\textrm{SO}}&\\ 388 &\nu_{\textrm{SO}}^*&\frac{1}{2}\left(-V+Z_i\right)&g(t)^*\\ 389 \nu_{\textrm{SO}}&&g(t)&\frac{1}{2}\left(-V-Z_i\right) 390 \end{bmatrix}\begin{matrix} 391 \left(1,\uparrow\right)\\ 392 \left(1,\downarrow\right)\\ 393 \left(0,\uparrow\right)\\ 394 \left(0,\downarrow\right) 395 \end{matrix} 396 $$ 397 where $V$ corresponds to `valley_splitting`, $Z_i$ is the Zeeman 398 splitting on the $i$th dot and corresponds `zeeman_splittings`, 399 $\nu_{\textrm{SO}}$ corresponds to `valley_spin_orbit_coupling`, and 400 $$ 401 g(t) = \sum_{j=0}^{\texttt{dots}-1}\real\left(a_j(t)e^{i\omega_j t}\right), 402 $$ 403 is the Rabi drive with frequency components $\omega_j$ and amplitudes 404 $a_j(t)$. 405 406 Parameters 407 ---------- 408 dots : int 409 The number of dots in the array 410 electrons : int 411 The number of electrons in the 412 zeeman_splittings : NDArray[Shape[spins], float] 413 The Zeeman splitting of each of the spins 414 valley_splitting : float 415 The valley splitting 416 u : float 417 The on-site Coulomb no-valley-flip interaction 418 u_valley_flip : float 419 The on-site Coulomb valley-flip interaction 420 valley_spin_orbit_coupling : float 421 The valley-spin-orbit coupling 422 max_drive_strength : float 423 The maximum drive strength that can be applied at a specific 424 frequency and quadrature. That is if their are ``n_drive_ctrl`` 425 frequencies and both quadratures are used then the maximum amplitude 426 of the drive that can be applied to the device is:: 427 428 np.sqrt(2) * n_drive_ctrl * max_drive_strength 429 J_max : float 430 The minimum value of the exchange coupling $J$ 431 J_min : float 432 The maximum value of the exchange coupling $J$, by default 0 433 use_graph : bool 434 Whether to use `TensorFlow <https://www.tensorflow.org>`__ graphs 435 during computation, by default ``True`` 436 """ 437 # Defining the control amplitude functions 438 rescale_rabi_drive = \ 439 linear_rescaling.specify_parameters(min=-max_drive_strength, 440 max=max_drive_strength) 441 rescale_J = linear_rescaling.specify_parameters(min=J_min, max=J_max) 442 hopping_amplitude = lambda x: tf.sqrt(rescale_J(x)*self.u)/2 443 self._rescale_and_concatenate = \ 444 concatenate_functions([rescale_rabi_drive, hopping_amplitude]) 445 # Initialising the device parameters 446 Device.__init__(self, 447 zeeman_splittings, 448 max_drive_strength, 449 J_min, 450 J_max) 451 self._u = u 452 self._u_valley_flip = u_valley_flip 453 self._valley_spin_orbit_coupling = valley_spin_orbit_coupling 454 455 # Generating the drift hopping Hamiltonian 456 drift_hopping_blocks = [] 457 for zeeman_splitting in zeeman_splittings: 458 drift_hopping_blocks.append( 459 valley_spin_orbit_coupling * np.kron(PAULI_X, PAULI_X) 460 + valley_splitting/2 * np.kron(PAULI_Z, np.eye(2)) 461 + zeeman_splitting/2 * np.kron(np.eye(2), PAULI_Z)) 462 drift_hoppings = block_diag(*drift_hopping_blocks) 463 464 # Generating the Rabi drive Hamiltonian 465 esr_drives = [0.5*np.kron(np.eye(dots*2), PAULI_X)] 466 467 # Generating the interdot hopping Hamiltonian 468 forward_connectivity = np.einsum("ij,jk->ijk", 469 np.eye(dots, dots, 0), 470 np.eye(dots, dots, 1) 471 )[:-1] 472 backward_connectivity = np.einsum("ij,jk->ijk", 473 np.eye(dots, dots, 1), 474 np.eye(dots, dots, -1) 475 )[:-1] 476 copuling_matrix = np.identity(4) 477 # When hopping in the oposite direction the oposite rotation is picked 478 # up to ensure hermiticity 479 reverse_coupling_matrix = copuling_matrix.T.conj() 480 interdot_hoppings = \ 481 np.kron(forward_connectivity, 482 np.expand_dims(copuling_matrix, axis=0)) \ 483 + np.kron(backward_connectivity, 484 np.expand_dims(reverse_coupling_matrix, axis=0)) 485 486 ctrl_hoppings = np.concatenate([esr_drives, interdot_hoppings], axis=0) 487 488 # Generating the Coulomb interaction Hamiltonian 489 # No spin flip 490 spin_coupling = np.einsum("ij,kl->ikjl", np.identity(2), np.identity(2)) 491 # Valley couplings 492 hartree_coupling = \ 493 np.einsum("ij,kl->ikjl", np.identity(2), np.identity(2)) 494 single_flip_valley_coupling = \ 495 np.einsum("ij,kl,jk->ijkl", np.identity(2), PAULI_X, np.identity(2))\ 496 +np.einsum("ij,kl,jk->ijlk", np.identity(2), PAULI_X, np.identity(2))\ 497 +np.einsum("ij,kl,jk->iljk", np.identity(2), PAULI_X, np.identity(2))\ 498 +np.einsum("ij,kl,jk->lijk", np.identity(2), PAULI_X, np.identity(2)) 499 valley_couplings = -u * hartree_coupling \ 500 -u_valley_flip * single_flip_valley_coupling 501 # On-site Coulomb only 502 dot_couplings = np.einsum("ij,kl,jk->ijkl", 503 np.identity(dots), 504 np.identity(dots), 505 np.identity(dots)) 506 coulomb_integrals = 0.5*reduce(np.kron, [dot_couplings, 507 valley_couplings, 508 spin_coupling]) 509 # The 0.5 accounts for double counting 510 FermionicSystem.__init__(self, 511 FermionQuditSpace(dots, 4, electrons), 512 drift_hoppings, 513 coulomb_integrals, 514 ctrl_hoppings, 515 use_graph)
516 517 @property 518 def u(self) -> float: 519 """The on-site Coulomb no-valley-flip interaction""" 520 return self._u 521 @property 522 def u_valley_flip(self) -> float: 523 """The on-site Coulomb valley-flip interaction""" 524 return self._u_valley_flip 525 @property 526 def valley_spin_orbit_coupling(self) -> float: 527 """The valley-spin-orbit coupling""" 528 return self._valley_spin_orbit_coupling