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 operationsall_qubits
: noise added for all qubits at each stepparallelization_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 methodnumber_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 toFalse
).Using
bath_qubits_as_control_qubits
is the preferred way to get and set this property. The old methoduse_bath_as_control
will be deprecated in a future release. -
noise_placement
:before
,after
orsymmetric
- The noise is applied before the gate, after the gate based on gate duration or symmetrically (defaults toafter
).Using
noise_insertion_placement
is the preferred way to get and set this property. The old methodnoise_placement
will be deprecated in a future release. -
algorithm
: options are:ParityBased
,QSWAP
,QSWAPMolmerSorensen
,VariableMolmerSorensen
, (defaults toParityBased
).Using
time_evolution_algorithm
is the preferred way to get and set this property. The old methodalgorithm
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 tofalse
.Using
use_noise_symmetrization
is the preferred way to get and set this property. The old methodnoise_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 toNone
, for which no wait time is applied.Using
wait_time_after_trotterstep
is the preferred way to get and set this property. The old methodafter_trotter_wait_time
will be deprecated in a future release. no optimizations are applied, or1
whereSingleQubitGates
are combined (defaults to1
) -
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 methodtrotterization_order
will be deprecated in a future release. -
optimization_level
: the level of optimization when building the circuit: either0
where no optimizations are applied, or1
whereSingleQubitGates
are combined (defaults to1
)Using
level_of_optimization
is the preferred way to get and set this property. The old methodoptimization_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 devicesingle_qubit_gates
: The list of single-qubit gates available on the quantum computertwo_qubit_gates
: The list of two-qubit gates available on the quantum computerdefault_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
andRotateZ
RotateY
andRotateZ
RotateX
andRotateY
RotateZ
andSqrtPauliX
andInvSqrtPauliX
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 QuantumProgram
s can then be simulated using the Backend
class of the qoqo-QuEST package.
CNOT algorithm
The QuantumProgram
s 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 thePauliProduct
- 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 createdtrotter_timestep
: The simulation time the circuit propagates the simulated systemdevice
: 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 createdtrotter_timestep
: The simulation time the circuit propagates the simulated systemdevice
: 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])