Using the HQS Noise App
The objective is to start from a system-bath model (spin-boson or spin-fermion) that one wishes to simulate. This system-bath model is a mixed-system object (see also the struqture documentation) and can have either a dense spectrum of bath modes or a few broad bath modes. It is important the bath can be described by a spectral function according to normal Bloch-Redfield theory, as the bath will later be coarse-grained by the provided fitting tools. The output of the HQS Noise App will be a coarse-grained spin-boson system with optimized parameters. This system can then be turned into a mixed spin-spin system. The final total system is ideally small enough to be simulated on a quantum computer.
In this process, the fitting tool will also provide the best choice of Trotter time step when converting the spin-spin system into a quantum circuit that implements the time propagation under the spin-spin Hamiltonian. Choosing the right Trotter time step gives the best possible fit between the time evolution of the open system and the time propagation on the quantum computer under the influence of physical noise.
Coarse-Graining Mixed Hamiltonians
More detailed, the goal of using the tool is to convert either a spin-boson system (MixedLindbladOpenSystem
) into a spin-spin system (MixedHamiltonian
).
The spin-boson system corresponds to a Hamiltonian of type:
\[ H_\textrm{input} = H_\textrm{system} + \frac{1}{2}\sum_{i} \sigma^i_x X_i + \sum_{m} \omega_{m} a^\dagger(\omega_m) a(\omega_m) \nobreakspace , \]
where the coupling between the system and the bath is described by spin-boson operators, where the spin parts are single-spin operators, and the bosonic part is a linear operator of the form:
\[ X_i = \sum_{m} g_{im} \left[a(\omega_m) + a^\dagger(\omega_m)\right] \nobreakspace . \]
The bosonic bath is non-interacting and has discretized frequencies \( \omega_m \) and widths \(d\omega_m\equiv \gamma_m\). The influence of such a bosonic bath on the system can be described by a spectral function. The widths are identified as boson-mode damping rates, corresponding to Lorentzian spectral peaks at their frequencies. All of the bosonic parts can also be given by a spectral function:
\[ S_{ij}(\omega) = 2\pi \sum_{m} g_{im}g_{jm} \delta(\omega-\omega_m + \textrm{i}\gamma_m) \nobreakspace , \]
added with the information of the interaction type, which, in this example, is \(\sigma_x \).
The final objective is to create a Hamiltonian of type MixedHamiltonian
,
with system spins and bath spins of the form:
\[ H_\textrm{output} = H_\textrm{system} + \sum_{i\in\textrm{system}}\sum_{j\in\textrm{bath}} \frac{g_{ij}}{2} \sigma_x^i\sigma_x^j + \sum_{j\in\textrm{bath}} \frac{\epsilon_j}{2} \sigma_z^j \nobreakspace . \]
The first part of the mixed system is the original spin-system. The second part is the effective spin-bath, which approximates the behaviour (spectral function) of the original bosonic bath. The broadening of the spin-bath modes is implemented by intrinsic noise of the qubits during the quantum simulation. This can be due to qubit damping and dephasing.
A more detailed technical description of the main functions used in the coarse-graining can be found in the simulation examples.
Creating Mixed Hamiltonians and Open Systems
The system-bath model is defined in the interface as a mixed system. The mixed system can have either two spin subsystems (a spin system and a spin bath), or one spin system and several bosonic subsystems. The bosonic subsystems are used to define the spin-boson problem. Note that only non-interacting baths are allowed, as explained in the section above. The corresponding system-bath couplings are limited to products of two Pauli operators.
In the following we create a spin-boson Hamiltonian with struqture_py. Struqture is a library that supports the building of spin objects, bosonic objects, fermionic objects and mixed system objects that contain arbitrarily many spin, fermionic and bosonic subsystems. The struqture documentation contains several examples of how to define different kinds of Hamiltonians.
The spin-boson Hamiltonian we create has \(2\) system spins and \(3\) bath bosons. Each of the system spins is coupled to all of the bosons with the coupling matrix system_bath_couplings
. The first part of the script inserts the system energies and the inner-system hopping. The next lines define the boson energies and the noise of the bosons. The last step is to insert the coupling between the spins and bosons, which are coupled through Pauli-z
matrix.
from struqture_py import mixed_systems, spins, bosons
from hqs_noise_app import coupling_to_spectral_function
import numpy as np
# Set number of system spins.
system_spins = 2
# Set number of boson.
number_bosons = 3
system_energies = [0.1, 0.2]
# Hopping within the system spins.
system_hopping = 0.1
# Use three boson baths.
bath_energies = [0, 1, 2]
bath_broadenings = [0.1, 0.2, 0.3]
# System bath coupling matrix.
system_bath_couplings = [[0.3, 0.1, 0.3], [0.2, 0.4, 0.2]]
# Create a new mixed system with one spin and one boson subsystem.
spin_boson_hamiltonian = mixed_systems.MixedLindbladOpenSystem(
[system_spins],
[number_bosons],
[],
)
# Insert system energies.
for (system, system_energy) in enumerate(system_energies):
index = mixed_systems.HermitianMixedProduct(
[spins.PauliProduct().z(system)], [bosons.BosonProduct([], [])], []
)
spin_boson_hamiltonian.system_add_operator_product(index, 0.5 * system_energy)
# inserting inner-system hopping (0.5*xx + 0.5*yy)
for system in range(system_spins - 1):
index_xx = mixed_systems.HermitianMixedProduct(
[spins.PauliProduct().x(system).x(system + 1)],
[bosons.BosonProduct([], [])],
[],
)
index_yy = mixed_systems.HermitianMixedProduct(
[spins.PauliProduct().y(system).y(system + 1)],
[bosons.BosonProduct([], [])],
[],
)
spin_boson_hamiltonian.system_add_operator_product(index_xx, 0.5 * system_hopping)
spin_boson_hamiltonian.system_add_operator_product(index_yy, 0.5 * system_hopping)
# Set bath energies
for (bath_index, bath_energy) in enumerate(bath_energies):
index = mixed_systems.HermitianMixedProduct(
# Identity spin operator
[spins.PauliProduct()],
# Create a Boson occupation operator
[bosons.BosonProduct([bath_index], [bath_index])],
[],
)
spin_boson_hamiltonian.system_add_operator_product(index, bath_energy)
# Set bath noise
for (bath_index, bath_broadening) in enumerate(bath_broadenings):
# create the index for the Lindblad terms.
# We have pure damping
index = mixed_systems.MixedDecoherenceProduct(
# Identity spin operator
[spins.DecoherenceProduct()],
# Create a Boson occupation operator
[bosons.BosonProduct([], [bath_index])],
[],
)
spin_boson_hamiltonian.noise_add_operator_product((index, index), bath_broadening)
# Set couplings, use longitudinal (Z) coupling
for spin_index in range(system_spins):
for (bath_index, system_bath_coupling) in enumerate(
system_bath_couplings[spin_index]
):
index = mixed_systems.HermitianMixedProduct(
# Identity spin operator
[spins.PauliProduct().z(spin_index)],
# Create a Boson coupling operator (always a + a^dagger)
[bosons.BosonProduct([], [bath_index])],
[],
)
spin_boson_hamiltonian.system_add_operator_product(index, system_bath_coupling)
print("Newly created system:")
print(spin_boson_hamiltonian)
Having created this spin-bath Hamiltonian, we can use the HQS Noise App to obtain the spectral function representing the broadened bath modes. For more information on the theory behind this, please see the theory section below.
# We can then create a spectral function from spin-bath Hamiltonian, using the
# `coupling_from_spectral_function` function. This needs a list of frequencies
# for which to build the spectral function as an input:
min_frequency = -2
max_frequency = 4
number_frequencies = 1000
frequencies = np.linspace(min_frequency, max_frequency, number_frequencies)
# Obtaining the corresponding spectral function
spectral_function = coupling_to_spectral_function(spin_boson_hamiltonian, frequencies)
print("Spectral function frequencies")
print(spectral_function.frequencies())
print("Spectral function for 0, 0 spin indices with Z coupling")
print(spectral_function.get(("0Z", "0Z")))
print("Spectral function for 0, 1 spin indices with Z coupling")
print(spectral_function.get(("0Z", "1Z")))
In this snippet, we have built
System-Bath Circuits
We also provide algorithms specifically tailored for system-bath type problems, which take into account that the bath is non-interacting and that large-angle rotations of the bath qubits should be avoided. System-bath interactions can be optimally generated for two types of hardware connectivity/architecture.
Top: System-bath interactions generated using system to all-bath connectivity. Bottom: System-bath interactions generated in 2D architecture using nearest-neighbour connectivity and a special SWAP algorithm.
Ideally, all system qubits are connected to all bath qubits.
For system-bath problems connectivity between bath qubits is not needed, since the bath is usually assumed to be non-interacting.
This approach is used when using the ParityBased
algorithm (see also the description in the interface) section.
On the other hand, if only nearest-neighbour interactions are possible,
we offer the user an automatic circuit generation based on a SWAP algorithm specifically optimized for system-bath type problems and 2D hardware-architecture.
Here, the quantum state(s) of the system spin(s) are moved (or swapped) in a system-qubit network, located between the bath qubits.
In this modification, one avoids performing SWAPs between bath qubits, which can cause major modifications in the effective noise model.
In the example shown above, the state of the spin is stored by two system qubits, one per time.
This architecture most optimally stores a system consisting of \(n\) spins coupled to \(2n\) bath modes.
This approach is used when using the QSWAP
algorithm.
The HQS Noise App System-Bath Functionality
The HQS Noise App allows to set properties of the noise model and several other features. A simple example that initializes the HQS Noise App with the noise mode and the algorithm is:
from hqs_noise_app import HqsNoiseApp
# Initialize noise app.
algorithm = "VariableMolmerSorensen"
noise_app = HqsNoiseApp()
noise_app.algorithm = algorithm
# Print all available methods of the HqsNoiseApp
print(dir(HqsNoiseApp))
# Print docstring of algorithm
help(HqsNoiseApp.algorithm)
The available algorithms are:
ParityBased
: An algorithm that implements the time evolution under a product of PauliOperators via the total parity of the set of involved qubits.QSWAP
: Terms in the Hamiltonian involving two qubits are implemented using a swap network that reorders the terms.QSWAPMolmerSorensen
: The QSWAP algorithm using variable angle Mølmer Sørensen XX interaction gates.VariableMolmerSorensen
: An algorithm that implements terms in the Hamiltonian involving two qubits with a basis change and variable-angle Mølmer-Sørensen gates.
Besides thealgorithm
, there are several other parameters that can be set with the HqsNoiseApp
. A complete list of methods can be obtained from the Python API documentation or within a Python session via print(dir(HqsNoiseApp))
. Information about the methods can then be obtained with help(HqsNoiseApp.algorithm)
for example.
Device and Noise Model
Some of the functions of the HQS Noise App require that we define a device and/or a noise model to run the algorithm. These are defined in qoqo. The documentation contains several examples about how to use qoqo. Important for the HQS Noise App is mainly how to set up a device and create a noise model.
from qoqo import devices, noise_models
# Number of qubits.
number_qubits = 3
# Setting up the device.
single_qubit_gates = ["RotateX", "RotateZ"]
two_qubit_gates = ["VariableMSXX"]
# Same time for all qubits.
gate_time = 1.0
# Setup device with all-to-all connectivity.
device = devices.AllToAllDevice(
number_qubits, single_qubit_gates, two_qubit_gates, gate_time
)
# Set up a noise model
noise_model = noise_models.ContinuousDecoherenceModel()
# Damping and dephasing of qubits.
qubit_damping_rates = [0.01, 0.02, 0.15]
qubit_dephasing_rates = [0.004, 0.006, 0.12]
# Add damping and dephasing to qubits.
for ii in range(number_qubits):
noise_model = noise_model.add_damping_rate([ii], qubit_damping_rates[ii])
noise_model = noise_model.add_dephasing_rate([ii], qubit_dephasing_rates[ii])
# Set single qubit gate times.
for qubit in range(number_qubits):
device.set_single_qubit_gate_time('RotateX', qubit, 0.03)
device.set_single_qubit_gate_time('RotateZ', qubit, 0.01)
The above noise model has different rates for each qubit. We also set the gate times for single-qubit gates to be faster than the gate times of the two-qubit gate.