Using the HQS Qorrelator App
In this section, we outline how to employ the HQS Qorrelator App to compute time-resolved
correlators for NMR spectroscopy simulations on quantum computers. The HQS Qorrelator App works
together with the HQS struqture and
qoqo libraries. In this regard, we first briefly
show how to create Hamiltonians and open quantum systems using struqture. Then, we discuss the
available qoqo noise models that can be used in the simulation, and we show how to set up device
information. Finally, we illustrate how to use the HQS Qorrelator App to create a qoqo
QuantumProgram
for simulating the dynamics of the correlators.
struqture
struqture is a library enabling compact representation of quantum mechanical operators, Hamiltonians, and open quantum systems. The library supports building spin, fermionic and bosonic objects, as well as combinations thereof (mixed systems). Here, we demonstrate its usage with simple spin systems. For more complicated examples, please check the user documentation of struqture.
Creating Spin Hamiltonians
Let us now create a spin Hamiltonian using the struqture library. Here, arbitrary spin operators are
built from PauliProduct
operators. As the name implies, these are products of single-spin Pauli
operators. Each Pauli operator in the PauliProduct operates on a different qubit. Not all qubits
need to be represented in a PauliProduct (such qubits contribute via identity operators).
For instance, let us create a simple spin-Hamiltonian in the rotating-wave approximation with respect to
a strong external magnetic field in the Z-direction. Such a Hamiltonian is also required as the input
for constructing the QuantumProgram used to calculate the NMR correlation function. All single-spin
terms need to be Z
-terms. All spin-interaction terms need to be at least spin-Z
preserving (only
XX
, YY
or ZZ
terms, with the same coefficient for XX
and YY
) or completely symmetric in X
,
Y
and Z
(same coefficient for XX
, YY
and ZZ
). The second kind of symmetry is required when
setting the b_field_direction
option to a different direction from the input Hamiltonian.
For instance, we can create the following Hamiltonian
\[ H= \sum_{i=0}^9 3\sigma_{i}^z + \sum_{i=0}^8 2(\sigma_{i}^x\sigma_{i+1}^x + \sigma_{i}^y\sigma_{i+1}^y + \sigma_{i}^z\sigma_{i+1}^z) \]
using:
from struqture_py import spins
from struqture_py.spins import (SpinHamiltonianSystem, PauliProduct)
number_spins = 10
chemical_shift = 3.0
spin_coupling = 2.0
hamiltonian = SpinHamiltonianSystem()
for site in range(number_spins):
hamiltonian.add_operator_product(PauliProduct().z(site), chemical_shift)
for site in range(number_spins-1):
hamiltonian.add_operator_product(PauliProduct().x(site).x(site+1), spin_coupling)
hamiltonian.add_operator_product(PauliProduct().y(site).y(site+1), spin_coupling)
hamiltonian.add_operator_product(PauliProduct().z(site).z(site+1), spin_coupling)
Information can be accessed using the following functions:
hamiltonian.keys() # operator keys (Pauli products / strings)
hamiltonian.get("0X1X") # front factor of Pauli product "0X1X"
hamiltonian.number_spins() # number of spins in the system
Creating Spin Lindblad Noise Operators
Let us now create the Lindblad noise operators for a system of spins using struqture. Such noise operators are used to describe the noise part of the the Lindblad master equation
\[ \sum_{i,j}M_{i,j} \left( A_{i}\rho A_{j}^{\dagger} - \frac{1}{2} \lbrace A_j^{\dagger} A_i, \rho \rbrace \right). \]
For more detailed information, please refer to the modeling section. We use struqture
DecoherenceProducts
as the operator basis. The object SpinLindbladNoiseOperator
is given by a
HashMap or Dictionary with the tuple (DecoherenceProduct
, DecoherenceProduct
) as keys and the
entries in the rate matrix \(M_{j,k} \) as values. Similarly to SpinOperators,
SpinLindbladNoiseOperators
have a system equivalent: SpinLindbladNoiseSystem
, with a number of
involved spins defined by the user. For more information on these, see the user
documentation of struqture.
For instance, take \( A_0 = A_1 = \sigma_0^{x} \sigma_2^{z} \) with coefficient 1.0: \( 1.0 \left( A_0 \rho A_1^{\dagger} - \frac{1}{2} \lbrace A_1^{\dagger} A_0, \rho \rbrace \right) \). This is implemented in the following code snippet.
from struqture_py import spins
from struqture_py.spins import SpinLindbladNoiseSystem, DecoherenceProduct
import scipy.sparse as sp
system = spins.SpinLindbladNoiseSystem(3)
dp = spins.DecoherenceProduct().x(0).z(2)
system.add_operator_product((dp, dp), 1.0)
# Accessing information:
system.current_number_spins() # Result: 2
system.get((dp, dp)) # Result: CalculatorFloat(2)
system.keys() # Result: [("0Z1X", "0Z1X")]
dimension = 4**system.number_spins()
matrix = sp.coo_matrix(system.sparse_matrix_superoperator_coo(), shape=(dimension, dimension))
Creating noise models
The current version of the HQS Qorrelator App supports single-qubit physical noise in the form of damping, dephasing, and depolarization, with user-given decoherence rates. We assume that the noise is the same for all gate-types.
We can define a ContinuousDecoherenceModel
noise model with the following types of noise:
- dephasing: using the
add_dephasing_rate
function - depolarisation: using the
add_depolarising_rate
function - damping: using the
add_damping_rate
function - excitations: using the
add_excitation_rate
function
Each of these functions takes a list of qubits to apply the noise to, as well as a noise rate (float), and
returns the modified ContinuousDecoherenceModel
. An example code snippet, which creates a noise model with
damping and dephasing, reads as follows.
from qoqo import noise_models
# Setting up the noise model.
damping = 1e-3
dephasing = 5e-4
noise_model = noise_models.ContinuousDecoherenceModel().add_damping_rate([0, 1, 2], damping).add_dephasing_rate([3, 4], dephasing)
Creating a device
We can define an AllToAllDevice
device with the following settings:
number_of_qubits
: The number of qubits for the device.single_qubit_gates
: The list of single-qubit gates available on the quantum computer.two_qubit_gates
: The list of two-qubit gates available on the quantum computer.default_gate_time
: The default starting gate time.
The option single_qubit_gates
can be any list of single-qubit gates available in
qoqo as long as it contains one of the following
combinations:
RotateX
andRotateZ
RotateY
andRotateZ
RotateX
andRotateY
RotateZ
andSqrtPauliX
andInvSqrtPauliX
The supported choices for two_qubit_gates
are:
CNOT
ControlledPauliZ
ControlledPhaseShift
MolmerSorensenXX
VariableMSXX
An example code snippet of setting device information reads as follows.
from qoqo import devices
# Setting up the device.
number_of_qubits=5
single_qubit_gates = ["RotateX", "RotateZ", "RotateY"]
two_qubit_gates = ["CNOT"]
default_gate_time = 1.0
device = devices.AllToAllDevice(
number_of_qubits, single_qubit_gates, two_qubit_gates, default_gate_time
)
Using the HQS Qorrelator App
The HQS Qorrelator App can be initialized without any arguments:
from hqs_qorrelator_app import NMRCorrelator
correlator = NMRCorrelator()
Additionally, there are settings that have default values and can be set with setter functions with the same name as the setting.
algorithm
: options areParityBased
,QSWAP
,QSWAPMolmerSorensen
,VariableMolmerSorensen
(defaults toQSWAP
).b_field_direction
:Z
(default) andX
are possible options. Determines in which direction of the B-field is used for the calculation of the correlator. Automatically transforms compatible input Hamiltonians (e.g. a input Hamiltonian defined for aZ
b-field if theX
direction is used).noise_mode
: options areactive_qubits_only
(noise added only for qubits involved in operations),all_qubits
(noise added for all qubits at each step, default value) andparallelization_blocks
(noise added after each parallelization block).noise_placement
:before
,after
orsymmetric
- The noise is applied before the gate, after the gate based on gate duration or symmetrically (defaults toafter
)number_measurements
: the number of projective measurements used when measuring observables (defaults to 100000)optimization_level
: the level of optimization when performing the algorithm: either0
where no optimizations are applied, or1
whereSingleQubitGates
are combined (defaults to1
)initialisation
: options areSumOverAllStates
(default),ActiveReset
,NonCorrelatingMeasurement
,GeneralMeasurement
,GeneralDephasing
. See also the chapter on algorithms. We recommend using the default value.decoherence_loops
: This only affects optional initialisation methods and has no effect on the n optional number of loops run after the Hadamards on every qubit and before the main circuit, which are there to let off-diagonal entries of the density matrix decay through decoherence.
The HQS Qorrelator App can create QuantumPrograms to time-propagate a spin state. The
QuantumProgram will initialize a spin state on a quantum computer, time-propagate the spin state
with a quantum algorithm, measure the values of spin observables and compute the NMR correlator as
described in the Quantum Algorithm section. The following example
shows how to obtain the QuantumProgram for a previously defined hamiltonian (hamiltonian
) and
device (device
).
from hqs_qorrelator_app import NMRCorrelator
number_qubits = hamiltonian.number_qubits()
trotter_timestep = 0.005
gyromagnetic_factors = [1.0]*number_qubits
correlator = NMRCorrelator()
program = correlator.spectrum_program_fixed_step(
hamiltonian,
trotter_timestep,
gyromagnetic_factors,
device
)
The QuantumProgram can then be simulated using the backend of the user's choosing. For Linux users,
the qoqo-quest
Backend is recommended for simulations. The following example shows how to run the
obtained QuantumProgram on the backend and get the real and imaginary parts of the NMR correlator
from the resulting dictionary.
import numpy as np
from qoqo_quest import Backend
number_trottersteps = 100
backend = Backend(number_qubits)
correlator_re = np.zeros(number_trottersteps, dtype=float)
correlator_im = np.zeros(number_trottersteps, dtype=float)
for i in range(0, number_trottersteps):
result = backend.run_program(program, [i])
correlator_re[i] = result["correlator_total_re"]
correlator_im[i] = result["correlator_total_im"]
correlator = correlator_re + 1.0j * correlator_im
Considering the effects of noise
Estimating the overall decoherence rate
Physical noise on the quantum computer used to run the QuantumProgram will lead to a decay in the calculated correlation functions. This decay will lead to a broadening in the NMR spectrum (the Fourier transform of the correlation function).
Based on the choice of algorithm, device specification, and noise models, the mean decoherence rate that affects the spin system during the time evolution can be estimated. This estimated decoherence rate gives an expected broadening of the peaks in the NMR spectrum.
Please note that this is just an estimate, and therefore does not require a computationally costly full simulation of the time evolution.
from hqs_qorrelator_app import NMRCorrelator
from struqture_py import spins
from qoqo import devices, noise_models
# define hamiltonian
gyromagnetic = 1.0
number_spins = 2
time = np.pi/4
coupling = 1.0
shift = 1.0
gyromagnetic_factors = [gyromagnetic for _ in range(number_qubits)]
hamiltonian = spins.SpinHamiltonianSystem(number_spins)
for site in range(number_spins):
hamiltonian.set("{}Z".format(site), -shift * site)
# Setting up the device.
single_qubit_gates = ["RotateX", "RotateZ", "RotateY"]
two_qubit_gates = ["CNOT"]
gate_times = 1.0
damping = 1e-3
device = devices.AllToAllDevice(
number_spins, single_qubit_gates, two_qubit_gates, gate_times
)
# While the noise model is not needed to generate the QuantumProgram, it will be required
# when simulating the QuantumProgram.
noise_model = noise_models.ContinuousDecoherenceModel().add_damping_rate([0, 1, 2], damping)
trotter_timestep = 0.01
# Create circuit.
qorrelator_app_sc = NMRCorrelator()
qorrelator_app_sc.algorithm = "QSWAP"
estimated_decoherence_rate = qorrelator_app_sc.estimate_decoherence_rate(hamiltonian, trotter_timestep, device, [noise_model])
print("estimated decoherence rate:", estimated_decoherence_rate)
Extracting the Noisy algorithm model
The noisy algorithm model represents the effective Lindbladian that is being simulated in the presence of noise. Please refer to this paper and to the mapping section for details.
With the HQS Qorrelator App, the noisy algorithm model of a Hamiltonian can be obtained using the
function NMRCorrelator.noisy_algorithm_model
, with input arguments:
hamiltonian
: The Hamiltonian for which the noise algorithm model is created.trotter_timestep
: The simulation time the circuit propagates the simulated system.device
: The device determining the topology.noise_models
: Noise models determining noise properties.
An example is:
from hqs_qorrelator_app import NMRCorrelator
from struqture_py import spins
from struqture_py.spins import (SpinHamiltonianSystem, PauliProduct)
from qoqo import devices, noise_models
# define hamiltonian
number_spins = 4
hamiltonian = spins.SpinHamiltonianSystem(number_spins)
hamiltonian.add_operator_product(PauliProduct().z(0).z(2).z(3), 4.0)
# Setting up the device
single_qubit_gates = ["RotateX", "RotateZ"]
two_qubit_gates = ["CNOT"]
gate_times = 1.0
damping = 1e-3
device = devices.AllToAllDevice(
number_spins, single_qubit_gates, two_qubit_gates, gate_times
)
# While the noise model is not needed to generate the QuantumProgram, it will be required
# when simulating the QuantumProgram.
noise_model = noise_models.ContinuousDecoherenceModel().add_damping_rate([0, 1, 2, 3], damping)
# create inputs
trotter_timestep=0.01
qorrelator_app = NMRCorrelator()
# obtain noisy algorithm model
noisy_model = qorrelator_app.noisy_algorithm_model(hamiltonian, trotter_timestep, device, [noise_model])
print(noisy_model)
noisy_model
is an object of class struqture.spins.SpinLindbladNoiseSystem
.
Backends
The HQS Qorrelator App does not provide an internal simulator, but there are multiple interfaces (such as qoqo-quest, qoqo-for-braket and qoqo-qiskit) which can be used to either simulate the QuantumProgram or run it on quantum hardware.
Should the user wish to run a simulation emulating the device noise, the insert_noise
function in the
HQS Qorrelator App can be used to add in the chosen noise to the QuantumProgram.
import numpy as np
from struqture_py import spins
from struqture_py.spins import (SpinHamiltonianSystem, PauliProduct)
from hqs_qorrelator_app import NMRCorrelator
from qoqo import devices, noise_models
from qoqo_quest import Backend
number_spins = 2
time = np.pi/4
coupling = 1.0
shift = 1.0
gyromagnetic = 1.0
gyromagnetic_factors = [gyromagnetic for _ in range(number_qubits)]
number_trotter_steps = 20
trotter_timestep = 0.005
hamiltonian = spins.SpinHamiltonianSystem(number_spins)
for site in range(number_spins):
hamiltonian.set("{}Z".format(site), -shift * site)
# Setting up the device.
single_qubit_gates = ["RotateX", "RotateZ"]
two_qubit_gates = ["CNOT"]
gate_times = 1.0
device = devices.AllToAllDevice(
number_spins, single_qubit_gates, two_qubit_gates, gate_times
)
# While the noise model is not needed to generate the QuantumProgram, it will be requried
# when simulating the QuantumProgram.
damping = 0.0001
noise_model = noise_models.ContinuousDecoherenceModel().add_damping_rate([0, 1, 2, 3, 4], damping)
qorrelator_app = NMRCorrelator()
quantum_program = qorrelator_app.spectrum_program_fixed_step(hamiltonian, trotter_timestep, gyromagnetic_factors, device)
quantum_program_with_noise = qorrelator_app.insert_noise(quantum_program, device, [noise_model])
backend = Backend(number_spins)
# This is an alternative way of running the QuantumProgram, which allows to substitute one's
# backend of choice in the arguments. It is equivalent to backend.run_program(program, [number_trottersteps])
result = quantum_program_with_noise.run(backend, [number_trottersteps])