Note

Run interactively in jupyter notebook.

Spin circuits

In addition to the fermionic setting introduced in the first tutorial, the Qiskit cold atom module supports a framework to describe cold atomic architectures based on high-dimensional spins.

Here, the unit of information, i.e. the individual wires in the quantum circuit, are given as quantum angular momenta or spins of length \(S\), where \(S\) can take positive integer or half-integer values. This can be seen as a generalization of the qubit setting (i.e. a qudit) where the qubit case corresponds to the smallest possible value \(S=\frac{1}{2}\).

Each individual spin has \(2S+1\) internal states which are labeled from \(\left| 0 \right>\) to \(\left| 2S \right>\). In analogy to qubits, we use the convention that each wire is initialized in the \(\left| 0 \right>\) state at the start of a circuit. A measurement of a wire will project that spin to one of the eigenstates and return the measured outcome from \(0\) to \(2S\).

The spin module comes with a SpinSimulator() backend which serves as a general-purpose simulation backend analogous to the FermionSimulator for fermionic circuits.

[1]:
from qiskit_cold_atom.spins import SpinSimulator
from qiskit import QuantumCircuit, QuantumRegister

backend = SpinSimulator()
qc = QuantumCircuit(4)
qc.measure_all()
# confirm that an empty circuit will return '0 0 0 0'
job = backend.run(qc, spin=3)

print(job.result().get_counts())
{'0 0 0 0': 1000}

Spin Gates

Quantum gates define the unitary operations that are carried out on the system. The gate unitary \(U\) can be uniquely identified by the hermitian Hamiltonian \(H\) that generates the evolution of the state (see Qiskit textbook) as \(U = e^{-i H}\). The description of the dynamics of cold atomic quantum simulators commonly takes place on the level of the Hamiltonian which the system implements. We therefore choose a language which defines the spin gates from their generating Hamiltonians.

As a formal language to describe Hamiltonians for spin systems, we utilize the SpinOp from Qiskit Nature. Gates are then defined as instances or subclasses of the SpinGate class which inherits from Qiskit’s original Gate class.

In order for the SpinSimulator to run a circuit, each gate of the circuit needs to have its generating Hamiltonian given as a SpinOp.

As a first example, let’s look at the generalization of a \(\pi\)-rotation around the \(X\)-axis:

[2]:
from qiskit_nature.operators.second_quantization import SpinOp
from qiskit_cold_atom.spins import SpinGate
import numpy as np

Hx = np.pi*SpinOp("X")  # generator of a rotation of angle pi around the x-axis

rx_spin = SpinGate(name="rx_spin", num_modes=1, generator=Hx)

For details on the syntax of how to define a SpinOp please see the SpinOp documentation.

[3]:
qc = QuantumCircuit(QuantumRegister(1, "spin"))

qc.append(rx_spin, [0])
qc.measure_all()
qc.draw(output='mpl')
[3]:
../_images/tutorials_02_spin_circuits_6_0.png
[4]:
job = backend.run(qc, spin=10)
print(job.result().get_counts())
{'20': 1000}

After applying the x-rotation, the spin has been flipped from pointing all the way “up” (0) to pointing all the way “down” to its largest value \(2S = 20\).

Note that for the definition of the gate, the length of the spin \(S\) is not yet given explicitly. Rather, when running the job on the spin simulator backend, \(S\) is given as an additional parameter of the simulation. The same circuit can thus be recycled and simulated with a different spin length:

[5]:
job = backend.run(qc, spin=6)
print(job.result().get_counts())
{'12': 1000}

Example: Superposition

As is the case for qubit systems, superpositions in the initial states can be created by rotating around the x-axis with an angle \(\phi \neq \pi\). Such a parameterized rotation gate is defined in the SpinGateLibrary, it is called the LXGate (as \(L\) usually denotes the angular momentum in the cold atom community). Let’s see what state is created when rotating with an angle of \(\phi= \frac{\pi}{2}\):

[6]:
from qiskit_cold_atom.spins import RLXGate

qc = QuantumCircuit(QuantumRegister(1, "spin"))
qc.append(RLXGate(np.pi/2), [0])
qc.measure_all()
qc.draw(output='mpl')
[6]:
../_images/tutorials_02_spin_circuits_11_0.png
[7]:
# The same circuit can also be built by the following shorthand notation
# which is added to QuantumCircuit upon importing from qiskit_cold_atom.spins
qc = QuantumCircuit(QuantumRegister(1, "spin"))
qc.rlx(np.pi/2, 0)
qc.measure_all()
qc.draw(output='mpl')
[7]:
../_images/tutorials_02_spin_circuits_12_0.png
[8]:
from qiskit.visualization import plot_histogram

job = backend.run(qc, spin=10, shots=1000, seed=123)

counts = job.result().get_counts()
# convert counts to integers for better formatting
plot_histogram({int(k):v for k,v in counts.items()})
[8]:
../_images/tutorials_02_spin_circuits_13_0.png

We see from the above result that a single rotation creates a superposition of all possible values, where the value “in the middle” \(S=10\) is most likely to be measured. The measurement outcomes follows a binomial distribution with \(p=\frac{1}{2}\).

Example: Multi-spin gates

We can also use the language of generating Hamiltonians to create multi-spin gates. As an example, we can use a ZZ-type interaction to create an equal superposition between the different combinations of extremal spin orientations \(0\) and \(2S\):

[9]:
Hzz = np.pi*SpinOp("Z_0 Z_1", register_length=2)  # generating Hamiltonian acting on two spins

zz_gate = SpinGate(name="zz_spin", num_modes=2, generator=Hzz)

qc = QuantumCircuit(QuantumRegister(2, "spin"))

qc.rlx(np.pi/2, [0, 1])
qc.append(zz_gate, [0, 1])
qc.rlx(np.pi/2, [0, 1])
qc.measure_all()

qc.draw(output='mpl')
[9]:
../_images/tutorials_02_spin_circuits_16_0.png

Note that we could have achieved the same result by using the

qc.rlzlz(np.pi, [0, 1])

instruction.

[10]:
job = backend.run(qc, spin=1, shots=1000, seed=1234)

print("counts: ", job.result().get_counts())

plot_histogram(job.result().get_counts())
counts:  {'2 2': 267, '2 0': 231, '0 2': 253, '0 0': 249}
[10]:
../_images/tutorials_02_spin_circuits_18_1.png

The measured counts are returned in a space delimited format, where the first entry corresponds to the last spin in the register, etc. When executing circuits with spin-1/2, this recovers Qiskit’s ordering of bitstrings for qubits.

The spin simulator backend

The SpinSimulator backend can also be used to access the statevector and the unitary of the circuit. Internally, the backend simulates the evolution of the circuit by exact diagonalization. The statevector and unitary of the system can be retrieved in the familiar way of result.get_statevector() and result.get_unitary(). If there is a final measurement in the circuit, the returned state and unitary of the circuit are those just prior to measurement.

Let’s demonstrate this on the above circuit:

[11]:
# access the statevector
print("\nstatevector :", job.result().get_statevector())

# accedd the unitary
print("\ncircuit unitary : \n", job.result().get_unitary())

statevector : [-5.00000000e-01-1.36845553e-48j -1.30736460e-32+1.11022302e-16j
  5.00000000e-01-1.36845553e-48j -1.23259516e-32+1.34015774e-16j
 -6.16297582e-33+6.12323400e-17j  0.00000000e+00+2.29934717e-17j
  5.00000000e-01+3.42113883e-49j  4.35788200e-33+1.77904863e-17j
  5.00000000e-01+3.42113883e-49j]

circuit unitary :
 [[-5.00000000e-01-1.36845553e-48j -1.54074396e-32+1.66533454e-16j
   5.00000000e-01+1.36845553e-48j -1.23259516e-32+1.25886354e-16j
   1.38777878e-17+6.12323400e-17j  6.16297582e-33+7.03752031e-17j
   5.00000000e-01+3.42113883e-49j  6.16297582e-33+3.74166420e-17j
   5.00000000e-01-3.42113883e-49j]
 [-1.30736460e-32+1.11022302e-16j  1.00000000e+00+0.00000000e+00j
   1.30736460e-32+0.00000000e+00j -6.16297582e-33+6.12323400e-17j
   1.23259516e-32-1.37383090e-16j -6.16297582e-33-6.12323400e-17j
   4.35788200e-33+3.74166420e-17j -3.92523115e-17+0.00000000e+00j
  -4.35788200e-33+3.74166420e-17j]
 [ 5.00000000e-01-1.36845553e-48j  1.54074396e-32-5.55111512e-17j
  -5.00000000e-01+1.36845553e-48j  0.00000000e+00+7.03752031e-17j
  -1.38777878e-17-6.12323400e-17j  6.16297582e-33+1.25886354e-16j
   5.00000000e-01+3.42113883e-49j -6.16297582e-33+3.74166420e-17j
   5.00000000e-01-3.42113883e-49j]
 [-1.23259516e-32+1.34015774e-16j  1.38777878e-17+6.12323400e-17j
   6.16297582e-33+5.07490473e-17j  1.00000000e+00+6.84227766e-49j
   9.24446373e-33+0.00000000e+00j  0.00000000e+00-6.84227766e-49j
   1.54074396e-32-8.12941988e-18j -4.62223187e-33-6.12323400e-17j
  -6.16297582e-33+4.73817313e-17j]
 [-6.16297582e-33+6.12323400e-17j  6.16297582e-33-9.81307787e-17j
  -6.16297582e-33-6.12323400e-17j  8.71576399e-33+0.00000000e+00j
  -1.00000000e+00-4.35788200e-33j -8.71576399e-33+5.55111512e-17j
   0.00000000e+00-6.12323400e-17j -6.16297582e-33+3.92523115e-17j
   0.00000000e+00+6.12323400e-17j]
 [ 0.00000000e+00+2.29934717e-17j -1.38777878e-17-6.12323400e-17j
   6.16297582e-33+1.06260199e-16j  0.00000000e+00+6.84227766e-49j
  -9.24446373e-33+0.00000000e+00j  1.00000000e+00-6.84227766e-49j
  -3.08148791e-33+4.73817313e-17j -6.16297582e-33+6.12323400e-17j
  -6.16297582e-33-8.12941988e-18j]
 [ 5.00000000e-01+3.42113883e-49j  3.08148791e-33+3.74166420e-17j
   5.00000000e-01-3.42113883e-49j  1.23259516e-32-8.12941988e-18j
  -4.62223187e-33-6.12323400e-17j -6.16297582e-33+4.73817313e-17j
  -5.00000000e-01+3.42113883e-49j  6.16297582e-33+1.11022302e-16j
   5.00000000e-01-3.42113883e-49j]
 [ 4.35788200e-33+1.77904863e-17j -3.92523115e-17+0.00000000e+00j
  -4.35788200e-33+3.74166420e-17j  0.00000000e+00-6.12323400e-17j
  -6.16297582e-33+3.92523115e-17j  0.00000000e+00+6.12323400e-17j
   4.35788200e-33+1.11022302e-16j  1.00000000e+00+0.00000000e+00j
  -4.35788200e-33+0.00000000e+00j]
 [ 5.00000000e-01+3.42113883e-49j -3.08148791e-33+3.74166420e-17j
   5.00000000e-01-3.42113883e-49j  0.00000000e+00+4.73817313e-17j
  -6.16297582e-33+6.12323400e-17j -6.16297582e-33-8.12941988e-18j
   5.00000000e-01+3.42113883e-49j -6.16297582e-33+0.00000000e+00j
  -5.00000000e-01-3.42113883e-49j]]

For the above circuit with two spins of length \(S=1\), the total dimension of the Hilbert space becomes \((2S+1)^2 = 9\). The state ordering in the basis in this case is (0 0), (1 0), (2 0), (0 1), (1 1), (2 1), (0 2), (1 2), (2 2).

Further remarks

The SpinSimulator is a general simulator backend in the sense that it accepts any SpinGate with a well-defined generator. Similar to the qasm_simulator for qubits, there are no coupling maps or further restrictions posed on the applicable gates.

In order to see how the spin setting introduced here can be used to describe a concrete experimental system of ultracold bosonic atoms with a gateset and coupling maps of a real device, check out the collective spins hardware tutorial.

[12]:
import qiskit.tools.jupyter
%qiskit_version_table
%qiskit_copyright

Version Information

Qiskit SoftwareVersion
qiskit-terra0.23.1
qiskit-aer0.11.2
qiskit-nature0.5.2
System information
Python version3.9.16
Python compilerMSC v.1916 64 bit (AMD64)
Python buildmain, Jan 11 2023 16:16:36
OSWindows
CPUs8
Memory (Gb)63.724937438964844
Wed Feb 22 16:57:25 2023 W. Europe Standard Time

This code is a part of Qiskit

© Copyright IBM 2017, 2023.

This code is licensed under the Apache License, Version 2.0. You may
obtain a copy of this license in the LICENSE.txt file in the root directory
of this source tree or at http://www.apache.org/licenses/LICENSE-2.0.

Any modifications or derivative works of this code must retain this
copyright notice, and modified files need to carry a notice indicating
that they have been altered from the originals.

[ ]: