Author: Boxi Li (etamin1201@gmail.com)

In this example, we demonstrate how to simulate simple quantum algorithms on a qauntum hardware with QuTiP. The simulators are defined in the class `Processor`

(and its sub-classes). `Processor`

represents a general quantum device. The interaction of the quantum systems such as qubits is defined by the control Hamiltonian. For a general introduction of pulse-level simulation, please refer to the user guide.

In the following, we compile a simple three-qubit quantum circuit into control pulses on different Hamiltonian model.

The Deutsch–Jozsa algorithm is the simplest quantum algorithm that offers an exponential speed-up compared to the classical one. It assumes that we have a function $f:\{0,1\}^n \rightarrow \{0,1\}$ which is either balanced or constant. Constant means that $f(x)$ is either 1 or 0 for all inputs while balanced means that $f(x)$ is 1 for half of the input domain and 0 for the other half. A more rigorous definition can be found at https://en.wikipedia.org/wiki/Deutsch-Jozsa_algorithm.

The implementation of the Deutsch–Jozsa algorithm includes $n$ input qubits and 1 ancilla initialised in state $1$. At the end of the algorithm, the first $n$ qubits are measured on the computational basis. If the function is constant, the result will be $0$ for all $n$ qubits. If balanced, $\left|00...0\right\rangle$ will never be measured. The following example is implemented for the balanced function $f:\{00,01,10,11\} \rightarrow \{0,1\}$, where $f(00)=f(11)=0$ and $f(01)=f(10)=1$. This function is balanced, so the probability of measuring state $\left|00\right\rangle$ should be 0.

In [1]:

```
import numpy as np
from qutip import basis, ptrace
from qutip_qip.circuit import QubitCircuit
from qutip_qip.device import (LinearSpinChain, OptPulseProcessor, SCQubits,
SpinChainModel)
from qutip.ipynbtools import version_table
import qutip_qip
```

In [2]:

```
qc = QubitCircuit(N=3)
qc.add_gate("X", targets=2)
qc.add_gate("SNOT", targets=0)
qc.add_gate("SNOT", targets=1)
qc.add_gate("SNOT", targets=2)
# function f(x)
qc.add_gate("CNOT", controls=0, targets=2)
qc.add_gate("CNOT", controls=1, targets=2)
qc.add_gate("SNOT", targets=0)
qc.add_gate("SNOT", targets=1)
```

In [3]:

```
qc
```

/home/runner/work/qutip-tutorials/qutip-tutorials/qutip-qip/src/qutip_qip/circuit/circuit_latex.py:95: UserWarning: Could not locate system 'pdfcrop': image output may have additional margins. warnings.warn(

Out[3]:

First, we simulate the quantum circuit using the Hamiltonian model `LinearSpinChain`

. The control Hamiltonians are defined in `SpinChainModel`

.

In [4]:

```
processor = LinearSpinChain(3)
processor.load_circuit(qc);
```

`Processor`

has a method called `plot_pulses`

. In the figure bellow, each colour represents the pulse sequence of one control Hamiltonian in the system as a function of time. In each time interval, the pulse remains constant.

In [5]:

```
processor.plot_pulses(title="Control pulse of Spin chain",
figsize=(8, 4), dpi=100);
```

In [6]:

```
basis00 = basis([2, 2], [0, 0])
psi0 = basis([2, 2, 2], [0, 0, 0])
result = processor.run_state(init_state=psi0)
print("Probability of measuring state 00:")
print(np.real((basis00.dag() * ptrace(result.states[-1], [0, 1]) * basis00)))
```

Probability of measuring state 00: [[1.54116069e-08]]

In [7]:

```
processor.t1 = 100
processor.t2 = 30
psi0 = basis([2, 2, 2], [0, 0, 0])
result = processor.run_state(init_state=psi0)
print("Probability of measuring state 00:")
print(np.real((basis00.dag() * ptrace(result.states[-1], [0, 1]) * basis00)))
```

Probability of measuring state 00: [[0.13730233]]

This feature integrated into the sub-class `OptPulseProcessor`

which use methods in the optimal control module to find the optimal pulse sequence for the desired gates. It can find the optimal pulse either for the whole unitary evolution or for each gate. Here we choose the second option.

In [8]:

```
setting_args = {
"SNOT": {"num_tslots": 6, "evo_time": 2},
"X": {"num_tslots": 1, "evo_time": 0.5},
"CNOT": {"num_tslots": 12, "evo_time": 5},
}
# Use the control Hamiltonians of the spin chain model.
processor = OptPulseProcessor(
num_qubits=3, model=SpinChainModel(3, setup="linear")
)
processor.load_circuit( # Provide parameters for the algorithm
qc,
setting_args=setting_args,
merge_gates=False,
verbose=True,
amp_ubound=5,
amp_lbound=0,
);
```

In [9]:

```
processor.plot_pulses(
title="Control pulse of OptPulseProcessor", figsize=(8, 4), dpi=100
);
```

In [10]:

```
basis00 = basis([2, 2], [0, 0])
psi0 = basis([2, 2, 2], [0, 0, 0])
result = processor.run_state(init_state=psi0)
print("Probability of measuring state 00:")
print(np.real((basis00.dag() * ptrace(result.states[-1], [0, 1]) * basis00)))
```

Probability of measuring state 00: [[0.94500042]]

In [11]:

```
processor.t1 = 100
processor.t2 = 30
psi0 = basis([2, 2, 2], [0, 0, 0])
result = processor.run_state(init_state=psi0)
print("Probability of measuring state 00:")
print(np.real((basis00.dag() * ptrace(result.states[-1], [0, 1]) * basis00)))
```

Probability of measuring state 00: [[0.52465557]]

We can see that under noisy evolution their is a none zero probability of measuring state 00.

Below, we simulate the same quantum circuit using one sub-class `LinearSpinChain`

. It will find the pulse based on the Hamiltonian available on a quantum computer of the linear spin chain system.
Please refer to the notebook of the spin chain model for more details.

In [12]:

```
processor = SCQubits(num_qubits=3)
processor.load_circuit(qc);
```

In [13]:

```
processor.plot_pulses(title="Control pulse of SCQubits",
figsize=(8, 4), dpi=100);
```

In [14]:

```
basis00 = basis([3, 3], [0, 0])
psi0 = basis([3, 3, 3], [0, 0, 0])
result = processor.run_state(init_state=psi0)
print("Probability of measuring state 00:")
print(np.real((basis00.dag() * ptrace(result.states[-1], [0, 1]) * basis00)))
```

Probability of measuring state 00: [[3.0313567e-07]]

In [15]:

```
processor.t1 = 50.0e3
processor.t2 = 20.0e3
psi0 = basis([3, 3, 3], [0, 0, 0])
result = processor.run_state(init_state=psi0)
print("Probability of measuring state 00:")
print(np.real((basis00.dag() * ptrace(result.states[-1], [0, 1]) * basis00)))
```

Probability of measuring state 00: [[0.06044433]]

In [16]:

```
print("qutip-qip version:", qutip_qip.version.version)
version_table()
```

qutip-qip version: 0.3.0.dev0+fa929ff

Out[16]:

Software | Version |
---|---|

QuTiP | 4.7.1 |

Numpy | 1.22.4 |

SciPy | 1.8.1 |

matplotlib | 3.5.2 |

Cython | 0.29.33 |

Number of CPUs | 2 |

BLAS Info | Generic |

IPython | 8.11.0 |

Python | 3.10.4 | packaged by conda-forge | (main, Mar 24 2022, 17:39:04) [GCC 10.3.0] |

OS | posix [linux] |

Wed Mar 15 08:10:45 2023 UTC |