Using the HQS Noise App

In this section, we outline how to employ the HQS Noise App to simulate quantum systems with noisy quantum computers. The HQS Noise App works together with the HQS struqture library. In this regard, we first briefly show how to create Hamiltonians and open quantum systems using struqture. Then, we illustrate how to study noise models using the HQS Noise App. Furthermore, we discuss the different backends that the HQS Noise App offers for doing numerical simulations.

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 transverse-field Ising model,

\[ H= \sum_{i=0}^9 3\sigma_{i}^z + \sum_{i=0}^8 2\sigma_{i}^x\sigma_{i+1}^x , , \]

which is created by:


from struqture_py import spins
from struqture_py.spins import (SpinHamiltonianSystem, PauliProduct)

number_spins = 10
transverse_field = 3.0
spin_coupling = 2.0

hamiltonian = SpinHamiltonianSystem()
for site in range(number_spins):
    hamiltonian.add_operator_product(PauliProduct().z(site), transverse_field)
for site in range(number_spins-1):
    hamiltonian.add_operator_product(PauliProduct().x(site).x(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 with the aid of struqture. Such noise operators are important to describe the noise part of the the Lindblad master equation, see the modeling chapter, for more information.

To describe the pure noise part of the Lindblad 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)\), we use DecoherenceProducts as the operator base. 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) \)


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 physical noise models

Supported noise mechanisms in the present release

The current version of the HQS Noise 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.

Setting up the HQS Noise App

The HQS Noise App has to be initialized with :

  • noise_mode : the way noise is added to a quantum circuit

The available options for noise_mode are:

  • active_qubits_only : noise added only for qubits involved in operations
  • all_qubits : noise added for all qubits at each step
  • parallelization_blocks : noise added after each parallelization block

Additionally, there are settings that have default values and can be set with setter functions with the same name as the setting. Since hqs-noise-app 0.16.2, using getters and setters is the preferred way of accessing these settings.

  • number_measurements : the number of projective measurements used when measuring observables (defaults to 100000).

    Using number_of_measurements is the preferred way to get and set this property. The old method number_measurements will be deprecated in a future release.

  • use_bath_as_control: True/False to use/not use bath qubits as control when solving system-bath problems (defaults to False).

    Using bath_qubits_as_control_qubits is the preferred way to get and set this property. The old method use_bath_as_control will be deprecated in a future release.

  • noise_placement : before, after or symmetric- The noise is applied before the gate, after the gate based on gate duration or symmetrically (defaults to after).

    Using noise_insertion_placement is the preferred way to get and set this property. The old method noise_placement will be deprecated in a future release.

  • algorithm : options are: ParityBased, QSWAP, QSWAPMolmerSorensen, VariableMolmerSorensen, (defaults to ParityBased).

    Using time_evolution_algorithm is the preferred way to get and set this property. The old method algorithm will be deprecated in a future release.

  • noise_symmetrization : whether or not to apply noise symmetrization, which controls whether the Trotterstep is symmetrized with respect to damping noise. When the overall noise has the tendency to favor one state (|0> or |1>), the Trotterstep can be symmetrized by doubling the Trotter circuit and flipping the definition of |0> and |1> for the second circuit. This process will bring the effective noise closer to a balanced noise at the cost of larger decoherence overall. This option defaults to false.

    Using use_noise_symmetrization is the preferred way to get and set this property. The old method noise_symmetrization will be deprecated in a future release.

  • after_trotter_wait_time : the optional additional physical wait time after the Trotter circuit has been applied. This can be used to introduce additional decoherence when the decoherence in the Trotter step is too low or is dominated by the wrong type of decoherence compared to the background decoherence processes that are active during wait time. This defaults to None, for which no wait time is applied.

    Using wait_time_after_trotterstep is the preferred way to get and set this property. The old method after_trotter_wait_time will be deprecated in a future release. no optimizations are applied, or 1 where SingleQubitGates are combined (defaults to 1)

  • trotterization_order : trotterization order of time-evolution operator, 1 or 2 (defaults to 1)

    Using order_of_trotterization is the preferred way to get and set this property. The old method trotterization_order will be deprecated in a future release.

  • optimization_level : the level of optimization when building the circuit: either 0 where no optimizations are applied, or 1 where SingleQubitGates are combined (defaults to 1)

    Using level_of_optimization is the preferred way to get and set this property. The old method optimization_level will be deprecated in a future release.

Creating 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 the HQS qoqo library 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 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
)

Creating noise model

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 and a noise rate (float), and returns the modified ContinuousDecoherenceModel. An example code of setting noise model information, including the damping of qubits, 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 QuantumPrograms

The HQS Noise App can create quantum programs 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 and measure the values of spin observables. The initialization and measured operators are defined by the user. The creation of quantum programs specifically for mixed systems is discussed more detailed on System Bath.

The QuantumPrograms can then be simulated using the Backend class of the qoqo-QuEST package.

CNOT algorithm

The QuantumPrograms can be constructed by different algorithms. One choice the HQS Noise App offers is a parity-based algorithm, which we call the CNOT algorithm. It trotterizes the time evolution by separating a Hamiltonian into a sum over Pauli-products. The time evolution under each Pauli-product is simulated by

  • Rotating all involved qubits into the \(Z\) basis (by applying the Hadamard gate for \(X\) and a RotateX for \(Y\)
  • Encoding the total parity of the involved qubits in the last involved qubits with the help of a chain of CNOT operations
  • Applying a RotateZ operation on the last qubit, rotating by twice the prefactor of the PauliProduct
  • Undoing the CNOT chain and basis rotations

For the example of simulating \(\exp(-\textrm{i} g \sigma^x_0\sigma^x_2)\), the circuit looks like:


 0 -H---o-------------------o----H--------
 1 -----|-------------------|-------------
 2 -H---x----RotateZ(2g)----x----H--------

An example of creating a circuit of a previously defined Hamiltonian and device is:


from hqs_noise_app import HqsNoiseApp
from struqture_py import spins
from qoqo import devices, noise_models

# define hamiltonian (transverse Ising Hamiltonian, with spin_coupling=0)
number_spins = 3
hamiltonian = spins.SpinHamiltonianSystem(number_spins)
for site in range(number_spins):
    hamiltonian.set("{}Z".format(site), 1.0)

# 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 quantum program, it will be required
# when simulating the quantum program.
noise_model = noise_models.ContinuousDecoherenceModel().add_damping_rate([0, 1, 2], damping)

# Create the quantum program. The function HqsNoiseApp() uses the CNOT algorithm by default
trotter_timestep=0.01
operators = []
operator_names = []
for i in range(number_spins):
    operator = spins.SpinSystem(number_spins)
    operator.set(spins.PauliProduct().z(i), -0.5)
    operator.set(spins.PauliProduct(), 0.5)
    operators.append(operator)
    operator_names.append("population_site_{}".format(i))
initialisation = [0.0 for _ in range(number_spins)]

noise_app_sc = HqsNoiseApp("all_qubits")

quantum_program = noise_app_sc.quantum_program(hamiltonian, trotter_timestep, initialisation, operators, operator_names, device)
# Printing the circuits of the QuantumProgram:
print(quantum_program.measurement().circuits()) 

QSWAP algorithm

The QSWAP algorithm trotterizes the time evolution into sequences of nearest-neighbour interactions and quantum-state swaps. Using this approach, a quantum simulation of arbitrary two-body interactions can always be generated with linear depth. For the example of simulating arbitrary operation \(O_{02} = \exp(-\textrm{i} I_0I_2)\), the circuit looks like:


0 -----x-------------------x-----
      SWAP                SWAP
1 -----x--------x----------x-----
           Operation 02
2 --------------x----------------

An example of creating a circuit of a previously defined Hamiltonian and device is:

from hqs_noise_app import HqsNoiseApp
from struqture_py import spins
from qoqo import devices, noise_models

# define hamiltonian (transverse Ising Hamiltonian, with spin_coupling=0)
number_spins = 3
hamiltonian = spins.SpinHamiltonianSystem(number_spins)
for site in range(number_spins):
    hamiltonian.set("{}Z".format(site), 1.0)

#noise_app_sc = HqsNoiseApp("all_qubits", ["RotateX", "RotateZ"], "CNOT")

# 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 quantum program, it will be required
# when simulating the quantum program.
noise_model = noise_models.ContinuousDecoherenceModel().add_damping_rate([0, 1, 2], damping)


# Create circuit. The function HqsNoiseApp() uses the CNOT algorithm per default
trotter_timestep=0.01
operators = []
operator_names = []
for i in range(number_spins):
    operator = spins.SpinSystem(number_spins)
    operator.set(spins.PauliProduct().z(i), -0.5)
    operator.set(spins.PauliProduct(), 0.5)
    operators.append(operator)
    operator_names.append("population_site_{}".format(i))
initialisation = [0.0 for _ in range(number_spins)]

noise_app_sc = HqsNoiseApp("all_qubits")
noise_app_sc.time_evolution_algorithm = "QSWAP"

quantum_program = noise_app_sc.quantum_program(hamiltonian, trotter_timestep, initialisation, operators, operator_names, device)
# Printing the circuits of the QuantumProgram:
print(quantum_program.measurement().circuits()) 

System-bath CNOT algorithm

This is used for system-bath Hamiltonians. It assumes all-to-all connectivity. The creation of Hamiltonians and circuits specific for mixed systems is discussed more in the System Bath chapter. The system-bath version CNOT algorithm tries to miminize bath qubit rotations when performing XX or ZX type interactions between system and bath qubits. The reasoning is to minimize large-angle rotations on bath qubits which we assume have stronger noise then system qubits. This is possible for the relevant cases of XX and ZX type of interaction between the system and the bath. Here, for simulating a XX interaction between spin qubit s and bath qubit b1, \(\exp(-\textrm{i} g \sigma^x_{s}\sigma^x_{b1})\), the circuit is chosen to be


s  -----o----RotateX(2g)----o-----
b0 -----|-------------------|-----
b1 -----x-------------------x-----

An example of creating a quantum program of a previously defined Hamiltonian (hamiltonian) and a given device is:

logical_to_physical_system = {0: 0, 1: 1, 2: 3}
logical_to_physical_bath = {0: 4, 1: 5, 2: 7}
bath_qubit_connectivity = {0: [4, 5], 1: [5, 6], 3: [7, 8]}

noise_app_sc = HqsNoiseApp("all_qubits")
noise_app_sc.bath_qubits_as_control_qubits(False)

quantum_program = noise_app_sc.system_bath_quantum_program(
    hamiltonian,
    trotter_timestep,
    initialisation,
    operators,
    operator_names,
    device,
    logical_to_physical_system,
    logical_to_physical_bath,
    bath_qubit_connectivity,
)

The use_bath_as_control option is to use bath qubits as control when solving system-bath problems and interacting between system and bath qubits with CNOT operations as shown in the above circuit example (affects the noise model, see examples).

System-bath QSWAP algorithm

The system-bath QSWAP algorithm trotterizes the time evolution into sequence of nearest-neighbour interactions in the system and following quantum-state SWAPs. Assumption is made that each system spin can have a nearest-neighbour bath qubit below and above (assuming 2d architecture). The bath spins are not swapped. When creating the circuit, new system qubits are added to the circuit, if needed. So if we have an input of mixed system with 1 system and 4 bath spins, the final circuit has 6 qubits ordered like [bath0, bath1, system0, system1, bath3, bath4]. Here, simulating the original model cross-coupling between spin-qubit s and bath qubit b1, \(\exp(-\textrm{i} g \sigma^x_{s}\sigma^x_{b1})\), looks like


     s0 -x---------------------------x-
        SWAP                        SWAP
     s1 -x---o----RotateX(2g)----o---x-
             |                   |
     b1 -----x-------------------x-----

An example of creating a quantum program of a Hamiltonian (H_mixed) and a given device is:


trotter_timestep=0.01
quantum_program_mixed = noise_app.system_bath_quantum_program(
    H_mixed, trotter_timestep, initialisation, operators, operator_names, device, None, None, None
)
quantum_program_mixed # printing the circuit

Noisy algorithm model

Extracting the noise model

The noisy algorithm model of a hamiltonian is generated by the function HqsNoiseApp.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: The noise models determining noise properties.

The noisy algorithm model represents the effective Lindblad noise that the system simulated with a given hamiltonian is exposed to due to qubit noise during a single Trotter step, see Mapping chapter for more details about effective noise mapping.

An example is:


from hqs_noise_app import HqsNoiseApp
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 quantum program, it will be required
# when simulating the quantum program.
noise_model = noise_models.ContinuousDecoherenceModel().add_damping_rate([0, 1, 2, 3], damping)

# create inputs
trotter_timestep=0.01
noise_app = HqsNoiseApp("all_qubits")

# obtain noisy algorithm model
noisy_model = noise_app.noisy_algorithm_model(hamiltonian, trotter_timestep, device, [noise_model])
noisy_model

noisy_model is an object of class 'struqture.spins.SpinLindbladNoiseSystem'.

Printing and modifying a noisy algorithm model

Information on the created noisy algorithm model can be obtained by


noisy_model                            # noise terms and rates
noisy_model.keys()                     # strings of the Lindbladian noise matrix M
noisy_model.get(("0Z", "0Z"))          # accessing the rate of a specific noise term

In creating the effective model, we can encounter terms that are effectively zero but appear as non-zero due to the finite numerical accuracy. To eliminate these terms one can use the truncate function:


# absolute values below the given threshold will be set to zero
truncated_model = noisy_model.truncate(1e-4)

Furthermore, when studying the effect of individual contributions, it may be useful to turn "off" or "on" individual noise channels. For this one can use add_operator_product function:


noisy_model.add_operator_product(("2Z", "2Z"), 0.5)
noisy_model.add_operator_product(("0Z2Z3Z", "0Z2Z3Z"), 0)

System-bath noisy algorithm model

The noisy algorithm model of a hamiltonian is generated by the function HqsNoiseApp.system_bath_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: The noise models determining noise properties.
  • logical_to_physical_system: an optional mapping from logical to physical qubits for the system.
  • logical_to_physical_bath: an optional mapping from logical to physical qubits for the bath.
  • bath_qubit_connectivity - optional connectivity between physical system and bath qubits.

An example is detailed below. For this example, we use the following physical device:

0 - 1 - 2 - 3
|   |   |   |
4 - 5 - 6 - 7

where we choose the system qubits to be qubits [4, 5, 6] and the bath qubits to be qubits [0, 1, 2].


from hqs_noise_app import HqsNoiseApp
from struqture_py import spins
from struqture_py.spins import (SpinHamiltonianSystem, PauliProduct)
from qoqo import devices, noise_models

# define hamiltonian on the logical indices
hamiltonian = MixedHamiltonianSystem([3, 3], [], [])
# A XX interaction between spins 0 and 1 and spins 1 and 0
hamiltonian.set(HermitianMixedProduct(["0X1X", ""], [], []), 1.0)
# A XX interaction between spins 1 and 2 and spins 1 and 0
hamiltonian.set(HermitianMixedProduct(["1X2X", ""], [], []), 1.0)
hamiltonian.set(HermitianMixedProduct(["0Z", "0Z"], [], []), 0.1)
hamiltonian.set(HermitianMixedProduct(["1Z", "1Z"], [], []), 0.1)
hamiltonian.set(HermitianMixedProduct(["2Z", "2Z"], [], []), 0.1)

# Setting up the device
single_qubit_gates = ["RotateX", "RotateZ"]
two_qubit_gates = ["CNOT"]
gate_times = 1.0
damping = 1e-3
device = devices.AllToAllDevice(
    8, single_qubit_gates, two_qubit_gates, gate_times
)
# While the noise model is not needed to generate the quantum program, it will be required
# when simulating the quantum program.
noise_model = noise_models.ContinuousDecoherenceModel().add_damping_rate([0, 1, 2, 3, 4, 5, 6, 7], damping)
# The logical-to-physical mappings would be as follows:
logical_to_physical_system = {0: 4, 1: 5, 2: 6}
logical_to_physical_bath = {0: 0, 1: 1, 2: 2}
# The corresponding bath connectivity is:
connectivity = {4: [0], 5: [1], 6: [2]}

# create inputs
trotter_timestep=0.01
noise_app = HqsNoiseApp("all_qubits")

# obtain noisy algorithm model
noisy_model = noise_app.system_bath_noisy_algorithm_model(
    hamiltonian,
    trotter_timestep,
    device,
    [noise_model],
    logical_to_physical_system,
    logical_to_physical_bath,
    connectivity    
)
noisy_model

noisy_model is an object of class 'struqture.spins.SpinLindbladNoiseSystem'.

Backends

Simulation of the (original) noisy hamiltonian

The QuantumProgram objects created from the input hamiltonians can be simulated by the user using one of the qoqo interface packages, such as qoqo-QuEST, qoqo-for-braket or qoqo-qiskit.

NOTE: For Linux users, the function HqsNoiseApp.simulate_quantum_program numerically solves the time-evolution starting in the state defined by initial_density_matrix. The noise is added automatically to the QuantumProgram. The solver returns the density matrix at the end of the simulation.

One can use the HQS Noise App to add the noise to a quantum program and simulate the corresponding circuit using the qoqo-QuEST simulator (for instance), as follows


import numpy as np
from struqture_py import spins
from struqture_py.spins import (SpinHamiltonianSystem, PauliProduct)
from hqs_noise_app import HqsNoiseApp
from qoqo import devices, noise_models
from qoqo_quest import Backend


# define hamiltonian (transverse Ising Hamiltonian)
number_spins = 5
transverse_field = 3.0
spin_coupling = 2.0
hamiltonian = SpinHamiltonianSystem(number_spins)
for site in range(number_spins):
    hamiltonian.add_operator_product(PauliProduct().z(site), transverse_field)
for site in range(number_spins-1):
    hamiltonian.add_operator_product(PauliProduct().x(site).x(site+1), spin_coupling)    


# Setting up the device.
single_qubit_gates = ["RotateX", "RotateZ"]
two_qubit_gates = ["CNOT"]
gate_times = 1.0
damping = 0.0001
device = devices.AllToAllDevice(
    number_spins, single_qubit_gates, two_qubit_gates, gate_times
)
# While the noise model is not needed to generate the quantum program, it will be requried
# when simulating the quantum program.
noise_model = noise_models.ContinuousDecoherenceModel().add_damping_rate([0, 1, 2, 3, 4], damping)

noise_app = HqsNoiseApp("all_qubits")
noise_app.time_evolution_algorithm = "QSWAP"

trotter_timestep=0.01
operators = []
operator_names = []
for i in range(number_spins):
    operator = spins.SpinSystem(number_spins)
    operator.set(spins.PauliProduct().z(i), -0.5)
    operator.set(spins.PauliProduct(), 0.5)
    operators.append(operator)
    operator_names.append("population_site_{}".format(i))
initialisation = [0.0 for _ in range(number_spins)]

number_trottersteps = 20
quantum_program = noise_app.quantum_program(hamiltonian, trotter_timestep, initialisation, operators, operator_names, device)
quantum_program_with_noise = noise_app.add_noise(quantum_program, device, [noise_model])

backend = Backend(number_spins)
result = quantum_program_with_noise.run(backend, [number_trottersteps])

Exporting noisy algorithm model as Scipy sparse matrix

To run small-scale numerical simulations of the effective model, or to process the model further, one can export the superoperator as a Scipy sparse-matrix (acting on a density-matrix vector). The density matrix is flattened to a vector in row-major fashion [[1,2],[3,4]] -> [1,2,3,4]). A compact example:


import scipy.sparse as sparse

noisy_model = noisy_model.truncate(1e-4)
coo = noisy_model.sparse_matrix_superoperator_coo()
dimension = 4**noisy_model.number_spins()
matrix = sparse.coo_matrix(coo, shape=(dimension, dimension))
#dense_matrix = matrix.toarray()

Alternatively, one can export both the Hamiltonian and the noise model together as one Lindblad superoperator:


from struqture_py.spins import SpinLindbladOpenSystem
import scipy.sparse as sparse

# for exporting, combining the effective model and Hamiltonian into struqture.SpinLindbladOpenSystem
noisy_system = SpinLindbladOpenSystem(number_spins)
noisy_system  = SpinLindbladOpenSystem().group(hamiltonian, noisy_model)

# exporting hamiltonian and noise as super-operator matrix
dimension = 4**number_spins
coo = noisy_system.sparse_matrix_superoperator_coo()
sparse_matrix = sparse.coo_matrix(coo, shape=(dimension, dimension))
#lindblad_matrix = sparse_matrix.toarray()  # to dense matrix

Several examples of combining this with Scipy numerical methods are given in the Jupyter notebooks distributed with the HQS Noise App.

Exporting the noisy algorithm model to QuTiP

The effective model can also be exported to QuTip. For this, one can use the HQS package struqture_qutip_interface. Central functions here are SpinQutipInterface.pauli_product_to_qutip and SpinOpenSystemQutipInterface.open_system_to_qutip. With the QuTiP package, one can efficiently solve the time evolution of small-scale systems, for example, using the qutip.mesolve solver. An example of doing this is:

import numpy as np
from hqs_noise_app import  HqsNoiseApp
from struqture_py.spins import SpinHamiltonianSystem, SpinLindbladOpenSystem, PauliProduct
from struqture_qutip_interface import SpinQutipInterface, SpinOpenSystemQutipInterface
import qutip as qt
from qoqo import devices, noise_models

# constructing Hamiltonian
number_spins = 4
transverse_field = 1.0
spin_coupling = 1.0
hamiltonian = SpinHamiltonianSystem(number_spins)
for site in range(number_spins):
    hamiltonian.add_operator_product(PauliProduct().z(site), transverse_field)
for site in range(number_spins-1):
    hamiltonian.add_operator_product(PauliProduct().x(site).x(site+1), spin_coupling)


# Setting up the device.
single_qubit_gates = ["RotateX", "RotateZ"]
two_qubit_gates = ["CNOT"]
gate_times = 1.0
damping = 0.0001
device = devices.AllToAllDevice(
    number_spins, single_qubit_gates, two_qubit_gates, gate_times
)
# While the noise model is not needed to generate the quantum program, it will be requried
# when simulating the quantum program.
noise_model = noise_models.ContinuousDecoherenceModel().add_damping_rate([0, 1, 2, 3], damping)

# setting up the HQS Noise App
noise_app = HqsNoiseApp("all_qubits")

# automatic generation of the circuit corresponding to one Trotter step
trotter_timestep = 0.01
number_timesteps = 500

# extracting the effective noise model, corresponding to circuit of one trotterstep
noisy_model = noise_app.noisy_algorithm_model(hamiltonian, trotter_timestep, device, [noise_model])

# transforming Struqture open system (hamiltonian+noise) into QuTiP superoperator
noisy_system = SpinLindbladOpenSystem(number_spins)
noisy_system  = SpinLindbladOpenSystem().group(hamiltonian, noisy_model)

sqi = SpinOpenSystemQutipInterface()
(coherent_part, noisy_part) = sqi.open_system_to_qutip(noisy_system)
liouFull = coherent_part + noisy_part

# desired struqture operators to QuTiP
qi = SpinQutipInterface()
op_Z0X1 = PauliProduct().set_pauli(0, "Z").set_pauli(1, "X")
qt_Z0X1 = qi.pauli_product_to_qutip(op_Z0X1, number_spins, endianess="little")

# setting up an initial state
init_spin = [qt.basis(2, 1)] # first spin initially excited
for i in range(number_spins - 1):
    init_spin.append(qt.basis(2, 0)) # other spins at ground
init_spin_tensor = qt.tensor(list(reversed(init_spin)))
psi0 = init_spin_tensor * init_spin_tensor.dag()  # ro_init = |init> <init|

# QuTiP master-equation solver
time_axis = np.linspace(0, trotter_timestep * number_timesteps, number_timesteps + 1)
result = qt.mesolve(liouFull,
                    psi0,
                    time_axis,
                    [], # c_op_list is left empty, since noise is already in liouFull
                    [qt_Z0X1] # operator(s) to be measured
                    ).expect
time_evolution_Z0X1 = np.real(result[0])