Backends

Simulation of the (original) Noisy Hamiltonian

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

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:

from struqture_py import spins
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 = spins.PauliHamiltonian()
for site in range(number_spins):
    hamiltonian.add_operator_product(spins.PauliProduct().z(site), transverse_field)
for site in range(number_spins-1):
    hamiltonian.add_operator_product(spins.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
# required when simulating the quantum program.
noise_model = noise_models.ContinuousDecoherenceModel().add_damping_rate(
    [0, 1, 2, 3, 4], damping
)

noise_app = HqsNoiseApp()
noise_app.algorithm = "QSWAP"

trotter_timestep=0.01
operators = []
operator_names = []
for i in range(number_spins):
    operator = spins.PauliOperator()
    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 = noise_app.compile_program(quantum_program, device)
quantum_program = noise_app.add_noise(quantum_program, device, [noise_model])

backend = Backend(number_spins)
result = backend.run_program(quantum_program, [number_trottersteps])

Exporting Noisy Algorithm Model as SciPy Sparse Matrix

To run small-scale numerical simulations of the effective model, or to further process the model, 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 a 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(noisy_model.current_number_spins())
dimension = 4**noisy_model.current_number_spins()
matrix = sparse.coo_matrix(coo, shape=(dimension, dimension))
# dense_matrix = matrix.toarray()

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

from struqture_py import spins
import scipy.sparse as sparse

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

# exporting hamiltonian and noise as super-operator matrix
dimension = 4**noisy_system.current_number_spins()
coo = noisy_system.sparse_matrix_superoperator_coo(noisy_system.current_number_spins())
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 is given below.

import numpy as np
from hqs_noise_app import  HqsNoiseApp
from struqture_py import spins
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 = spins.PauliHamiltonian()
for site in range(number_spins):
    hamiltonian.add_operator_product(spins.PauliProduct().z(site), transverse_field)
for site in range(number_spins-1):
    hamiltonian.add_operator_product(spins.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
# required 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()

# 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 = spins.PauliLindbladOpenSystem.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])