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 NMR Hamiltonian using the struqture library. Struqture uses Pauli Hamiltonians, the only difference to a Spin Hamiltonian from physics are missing factors of (\frac{1}{2}) for the X, Y and Z operators. Arbitrary Pauli Hamiltonians 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 spin. Not all spins need to be represented in a PauliProduct (such spins contribute via identity operators).

For instance, let us create a simple 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 PauliHamiltonian, PauliProduct

number_spins = 10
chemical_shift = 3.0
spin_coupling = 2.0

hamiltonian = PauliHamiltonian()
for site in range(number_spins):
    # Accounting for the factor of 1/2
    hamiltonian.set(f"{site}Z", chemical_shift * 0.5)
for site in range(number_spins - 1):
    hamiltonian.set(f"{site}Z{site+1}Z", spin_coupling*0.5**2)
    hamiltonian.set(f"{site}X{site+1}Y", spin_coupling*0.5**2)
    hamiltonian.set(f"{site}X{site+1}Y", spin_coupling*0.5**2)

Information can be accessed using the following functions:

hamiltonian.keys()                  # operator keys (Pauli products / strings)
hamiltonian.get("0X1X")             # prefactor of Pauli product "0X1X"
hamiltonian.current_number_spins()  # number of spins in the system

Creating a device

To construct a QuantumProgram to calculate a correlation function we need to specify what kind of device should be used for the calculation. Devices can be specified with the help of the open-source qoqo library. We will use the simple AllToAllDevice device as an example. For more information about setting up more complicated devices, please have a look at qoqo. We can define an AllToAllDevice device with the following settings:

  • number_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 and RotateZ
  • RotateY and RotateZ
  • RotateX and RotateY
  • RotateZ and SqrtPauliX and InvSqrtPauliX

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_qubits = 10
single_qubit_gates = ["RotateX", "RotateZ", "RotateY"]
two_qubit_gates = ["CNOT"]
default_gate_time = 1.0
device = devices.AllToAllDevice(
    number_qubits, single_qubit_gates, two_qubit_gates, default_gate_time
)

Using the HQS Qorrelator App for calculating NMR correlators

QuantumPrograms for NMR correlation functions are best obtained from the NMRCorrelator class. The NMRCorrelator class 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 are ParityBased, QSWAP, QSWAPMolmerSorensen, VariableMolmerSorensen (defaults to QSWAP).
  • b_field_direction: Z (default) and X 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 a Z b-field if the X direction is used).
  • parallelization_blocks : the way noise is added to a quantum circuit. By default, this is set to false, so the noise will be inserted on all qubits after each operation, according to the user-specified NoiseModels (see the section on Creating a noise model, below). If the parallelization_blocks option is set to true, the noise mode is changed to ParallelizationBlocks. In this case, the quantum circuits created are automatically parallelized into blocks that can be executed simultaniously and the noise model adds noise on the qubits that are involved in a parallel block after each block. These noise is added after a PragmaStopParallelBlock that notifies the end of the parallel operations in a qoqo circuit.
  • number_measurements : the number of projective measurements used when measuring observables (defaults to 100000)
  • initialisation: At the moment only SumOverAllStates (default) is available. This will change in the future. See also the chapter on algorithms.

The NMRCorrelator 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

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. 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):
    # Time is allways propagated in units of the trotter timestep
    # chosen as an input for the quantum program creation
    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

In the NISQ era quantum computers all suffer from physical background noise. In the calculation of correlation functions this leads to an overall decay of the correlation with time and in the resulting spectral function to a broadening of the spectral peaks. Depending on the level of noise these effects can still be acceptable. To better understand the effects of noise and to judge if it is too high to have a chance to obtain good results the HQS Qorrelator app provides methods to calculate the influence of noise on the QuantumProgram running on the noisy quantum computer.

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.

Just like the devices, the noise models are defined with the help of the qoqo library, for more detailed information about available noise models also see the qoqo repository. We can define a ContinuousDecoherenceModel a noise model for noise that is always on in the background during the quantum operations. This noise model supports 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)

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.PauliHamiltonian()
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 PauliHamiltonian, PauliProduct
from qoqo import devices, noise_models

# define hamiltonian
number_spins = 4
hamiltonian = spins.PauliHamiltonian()
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 PauliHamiltonian, 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.PauliHamiltonian()
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])