The purpose of this notebook is to illustrate how to use Qubiter to simulate ( i.e., predict the outcome of) a simple quantum circuit with a few basic gates
Below, we won't always give the precise definition of each gate. You can find the
precise analytical/numerical definition of all gates implemented by Qubiter in the document entitled qubiter_rosetta_stone.pdf
included with the Qubiter distribution.
$\newcommand{\bra}[1]{\left\langle{#1}\right|}$ $\newcommand{\ket}[1]{\left|{#1}\right\rangle}$ test: $\bra{\psi}M\ket{\phi}$
First change your working directory to the Qubiter directory in your computer, and add its path to the path environment variable.
import os
import sys
print(os.getcwd())
os.chdir('../../')
print(os.getcwd())
sys.path.insert(0,os.getcwd())
/home/rrtucci/PycharmProjects/qubiter/qubiter/jupyter_notebooks /home/rrtucci/PycharmProjects/qubiter
Suppose you are anywhere in your home ~ directory, and qubiter has been installed somewhere accessible via the path environmental variable. You can find where qubiter is installed like this, in case you want to cd there.
from qubiter.utilities_gen import find_path_to_qubiter
# this method returns the absolute path to the py file where the method is defined
path = find_path_to_qubiter()
print(path)
/home/rrtucci/PycharmProjects/qubiter/qubiter/utilities_gen.py
from qubiter.SEO_writer import *
from qubiter.SEO_simulator import *
from qubiter.StateVec import *
from qubiter.SEO_MatrixProduct import *
import numpy as np
loaded OneQubitGate, WITHOUT autograd.numpy
Sometimes, we use the word "bit" to denote both qbits (qubits, quantum bits) or cbits (classical bits).
Number of qubits is 4.
num_qbits = 4
Use a trivial circuit embedder that embeds 4 qubits into same 4 qubits.
emb = CktEmbedder(num_qbits, num_qbits)
Open a writer, tell it where to write to. We will use zero bit last (ZL) convention, which is the default for SEO_writer.
file_prefix = 'hello_world_test'
wr = SEO_writer(file_prefix, emb)
Write Pauli matrices $\sigma_X, \sigma_Y,\sigma_Z$ at position 2.
wr.write_X(2)
wr.write_Y(2)
wr.write_Z(2)
# old way of doing it, still works
# wr.write_one_qbit_gate(2, OneQubitGate.sigx)
# wr.write_one_qbit_gate(2, OneQubitGate.sigy)
# wr.write_one_qbit_gate(2, OneQubitGate.sigz)
Write 1 qubit Hadamard matrix at position 3.
wr.write_H(3)
# old way of doing it, still works
# wr.write_one_qbit_gate(3, OneQubitGate.had2)
Rotate qubit 2 by $\pi$ along directions x, y, z successively.
Note: We define $Ra(\theta) = exp(i\theta\sigma_a)$ for $a=X,Y,Z$. Others use
$Ra(\theta) = exp(-i\frac{\theta}{2}\sigma_a)$ instead.
Note: $\theta$ in $Ra(\theta)$ is inserted in radians, but shows
up in the English File in degrees.
wr.write_Rx(2, np.pi)
wr.write_Ry(2, np.pi)
wr.write_Rz(2, np.pi)
# old way of doing it, still works dir=1,2,3
# wr.write_one_qbit_gate(2, OneQubitGate.rot_ax,[np.pi, dir])
Rotate qubit 1 along a non-axis direction $\hat{n}$ characterized by a list of 3 angles. $R(\theta_1, \theta_2, \theta_3) = \exp(i[\theta_1 \sigma_X +\theta_2\sigma_Y+\theta_3\sigma_Z])$
wr.write_Rn(1, [np.pi, np.pi/2, np.pi/3])
Definitions of S and T
$S = diag[1, i] = diag[1, e^{i\frac{\pi}{2}}]$
$T = \sqrt{S}= diag[1, e^{i\frac{\pi}{4}}]$
Write $S, S^\dagger, T, T^\dagger$ at position=2.
These operations show up in the English File as
P1PH
and in the
Picture File as @P
. That is because $P_1 = n =\ket{1}\bra{1} = diag(0, 1)$ and the operation
P1PH
(i.e. $P_1$ Phase) by a phase angle $\theta$ equals the diagonal matrix $diag(1, e^{i\theta})$
wr.write_S(2)
wr.write_S(2, herm=True)
wr.write_T(2)
wr.write_T(2, herm=True)
Write $CNOT = sigx(target\_pos)^{n(control\_pos)}$ with control_pos=3 and target_pos=1
wr.write_cnot(3, 1)
# old way of doing it, still works
# control_pos = 3
# target_pos = 1
# trols = Controls.new_single_trol(num_qbits, control_pos, kind=True)
# wr.write_controlled_one_qbit_gate(
# target_pos, trols, OneQubitGate.sigx)
At any point in the circuit, you can use a PRINT statement. This will print on the console, immediately after you create an object of the class SEO_simulator, a description of the state vector at that point in the circuit. Various styles of description are pre-canned for your convenience, or you can write your own. See use_PRINT() method of SEO_simulator class. Let's use a PRINT statement now in the pre-canned style "ALL".
wr.write_PRINT("ALL")
Swap qubits 1 and 3
wr.write_qbit_swap(1, 3)
Recall that $P_1 = n = \ket{1}\bra{1}=diag(0, 1)$ and a P1 phase (P1PH) by $\theta$ is $diag(1, e^{i\theta})$. Write a singly controlled P1PH with control=c=3, target=t=1 and rads = pi/3. This gate equals $e^{i*rads*n(t) n(c)}$.
wr.write_c_P1PH(3, 1, rads=np.pi/3)
If rads=pi, c_P1PH equals $(-1)^{n(t)n(c)} = \sigma_Z(t)^{n(c)}$, which is commonly called a controlled Z and denoted by Cz. Write a Cz with c=3 and t=1.
wr.write_c_P1PH(3, 1) # rads=np.pi is default
Write a controlled rotation at qubit 0 in the Y direction, with a True (@) control at qubit 1, and a False (0) control at qubits 2 and 3.
target_pos = 0
rads = 30*np.pi/180
ax = 2 # y axis
trols = Controls(num_qbits, {1:True, 2:False, 3:False})
wr.write_controlled_one_qbit_gate(
target_pos, trols, OneQubitGate.rot_ax, [rads, ax])
Close English and Picture files.
wr.close_files()
Look in files
to see the quantum circuit that was generated.
Once the English and Picture files are generated, you can ask the writer object wr to print them for you on screen
wr.print_eng_file(jup=True)
1 | SIGX AT 2 | 2 | SIGY AT 2 | 3 | SIGZ AT 2 | 4 | HAD2 AT 3 | 5 | ROTX 180.000000 AT 2 | 6 | ROTY 180.000000 AT 2 | 7 | ROTZ 180.000000 AT 2 | 8 | ROTN 180.000000 90.000000 60.000000 AT 1 | 9 | P1PH 90.000000 AT 2 | 10 | P1PH -90.000000 AT 2 | 11 | P1PH 45.000000 AT 2 | 12 | P1PH -45.000000 AT 2 | 13 | SIGX AT 1 IF 3T | 14 | PRINT ALL | 15 | SWAP 3 1 | 16 | P1PH 60.000000 AT 1 IF 3T | 17 | P1PH 180.000000 AT 1 IF 3T | 18 | ROTY 30.000000 AT 0 IF 3F 2F 1T |
wr.print_pic_file(jup=True)
1 | | X | | | 2 | | Y | | | 3 | | Z | | | 4 | H | | | | 5 | | Rx | | | 6 | | Ry | | | 7 | | Rz | | | 8 | | | R | | 9 | | @P | | | 10 | | @P | | | 11 | | @P | | | 12 | | @P | | | 13 | @---+---X | | 14 | PRINT ALL | 15 | <---+---> | | 16 | @---+---@P | | 17 | @---+---@P | | 18 | O---O---@---Ry |
You can ask wr for the path to the English and Picture files
print(wr.get_eng_file_path())
/home/rrtucci/PycharmProjects/qubiter/qubiter/io_folder/hello_world_test_4_eng.txt
print(wr.get_pic_file_path())
/home/rrtucci/PycharmProjects/qubiter/qubiter/io_folder/hello_world_test_4_ZLpic.txt
You can generate a log file with an inventory of the English file by creating
an object of the SEO_reader class with the flag write_log
set to True
rdr = SEO_reader(file_prefix, num_qbits, write_log=True)
The following file was just created
Once the log file is generated, you can ask the reader object rdr to print it for you on screen
rdr.print_log_file()
Number of lines in file = 18 Number of Elem. Ops = 17 Number of CNOTS (SIGX with single control) = 1 List of distinct variable numbers encountered (length=0)= [] List of distinct function names encountered (length=0)= []
You can ask rdr for the path to the log file
print(rdr.get_log_file_path())
/home/rrtucci/PycharmProjects/qubiter/qubiter/io_folder/hello_world_test_4_log.txt
Occasionally, especially for debugging purposes, you might want to display the
product of a SEO (sequence of elementary operations, sequence of gates) as a 2^num_qbits dimensional
unitary matrix. This can be done with the class SEO_MatrixProduct. Simply
creating an object of this class multiplies the SEO and stores the result
in its attribute self.prod_arr
. Next we print that array for our example
mp = SEO_MatrixProduct(file_prefix, num_qbits)
print('product array=')
print(np.array_str(mp.prod_arr,
precision=2, suppress_small=True))
product array= [[ 0.1 -0.61j 0. +0.j 0.3 -0.15j 0. +0.j -0. +0.j 0. +0.j -0. -0.j 0. +0.j 0.1 -0.61j 0. +0.j 0.3 -0.15j 0. +0.j -0. +0.j 0. +0.j -0. -0.j 0. +0.j ] [ 0. +0.j 0.1 -0.61j 0. +0.j 0.3 -0.15j 0. +0.j -0. +0.j 0. +0.j -0. -0.j 0. +0.j 0.1 -0.61j 0. +0.j 0.3 -0.15j 0. +0.j -0. +0.j 0. +0.j -0. -0.j ] [ 0.26+0.13j 0.15+0.08j -0.09-0.53j -0.05-0.31j -0. -0.j -0. -0.j -0. +0.j -0. +0.j -0.26-0.13j -0.15-0.08j 0.09+0.53j 0.05+0.31j 0. +0.j 0. +0.j 0. -0.j 0. -0.j ] [-0.15-0.08j 0.26+0.13j 0.05+0.31j -0.09-0.53j 0. +0.j -0. -0.j 0. -0.j -0. +0.j 0.15+0.08j -0.26-0.13j -0.05-0.31j 0.09+0.53j -0. -0.j 0. +0.j -0. +0.j 0. -0.j ] [-0. -0.j 0. +0.j 0. -0.j 0. +0.j 0.1 -0.61j 0. +0.j 0.3 -0.15j 0. +0.j -0. -0.j 0. +0.j 0. -0.j 0. +0.j 0.1 -0.61j 0. +0.j 0.3 -0.15j 0. +0.j ] [ 0. +0.j -0. -0.j 0. +0.j 0. -0.j 0. +0.j 0.1 -0.61j 0. +0.j 0.3 -0.15j 0. +0.j -0. -0.j 0. +0.j 0. -0.j 0. +0.j 0.1 -0.61j 0. +0.j 0.3 -0.15j] [ 0. -0.j 0. +0.j -0. -0.j 0. +0.j 0.3 +0.15j 0. +0.j -0.1 -0.61j 0. +0.j -0. +0.j 0. +0.j 0. +0.j 0. +0.j -0.3 -0.15j 0. +0.j 0.1 +0.61j 0. +0.j ] [ 0. +0.j 0. -0.j 0. +0.j -0. -0.j 0. +0.j 0.3 +0.15j 0. +0.j -0.1 -0.61j 0. +0.j -0. +0.j 0. +0.j 0. +0.j 0. +0.j -0.3 -0.15j 0. +0.j 0.1 +0.61j] [ 0.3 +0.15j 0. +0.j -0.1 -0.61j 0. +0.j -0. -0.j 0. +0.j -0. +0.j 0. +0.j 0.3 +0.15j 0. +0.j -0.1 -0.61j 0. +0.j -0. -0.j 0. +0.j -0. +0.j 0. +0.j ] [ 0. +0.j 0.3 +0.15j 0. +0.j -0.1 -0.61j 0. +0.j -0. -0.j 0. +0.j -0. +0.j 0. +0.j 0.3 +0.15j 0. +0.j -0.1 -0.61j 0. +0.j -0. -0.j 0. +0.j -0. +0.j ] [-0.58+0.22j 0. +0.j -0.28-0.19j 0. +0.j 0. +0.j 0. +0.j 0. +0.j 0. +0.j 0.58-0.22j 0. +0.j 0.28+0.19j 0. +0.j -0. -0.j 0. +0.j -0. -0.j 0. +0.j ] [ 0. +0.j -0.58+0.22j 0. +0.j -0.28-0.19j 0. +0.j 0. +0.j 0. +0.j 0. +0.j 0. +0.j 0.58-0.22j 0. +0.j 0.28+0.19j 0. +0.j -0. -0.j 0. +0.j -0. -0.j ] [ 0. -0.j 0. +0.j -0. -0.j 0. +0.j 0.3 +0.15j 0. +0.j -0.1 -0.61j 0. +0.j 0. -0.j 0. +0.j -0. -0.j 0. +0.j 0.3 +0.15j 0. +0.j -0.1 -0.61j 0. +0.j ] [ 0. +0.j 0. -0.j 0. +0.j -0. -0.j 0. +0.j 0.3 +0.15j 0. +0.j -0.1 -0.61j 0. +0.j 0. -0.j 0. +0.j -0. -0.j 0. +0.j 0.3 +0.15j 0. +0.j -0.1 -0.61j] [-0. +0.j 0. +0.j -0. +0.j 0. +0.j -0.58+0.22j 0. +0.j -0.28-0.19j 0. +0.j 0. -0.j 0. +0.j 0. -0.j 0. +0.j 0.58-0.22j 0. +0.j 0.28+0.19j 0. +0.j ] [ 0. +0.j -0. +0.j 0. +0.j -0. +0.j 0. +0.j -0.58+0.22j 0. +0.j -0.28-0.19j 0. +0.j 0. -0.j 0. +0.j 0. -0.j 0. +0.j 0.58-0.22j 0. +0.j 0.28+0.19j]]
Specify initial state vector for simulation. This example corresponds to $\ket{0}\ket{0}\ket{1}\ket{1}$. In ZL convention, last ket corresponds to bit 0.
init_st_vec = StateVec.get_standard_basis_st_vec([0, 0, 1, 1], ZL=True)
Open a simulator. This automatically calculates the final state vector for the quantum circuit in the English file subject to the initial state vector that you give as input to the SEO_simulator constructor. Note that the PRINT statement that we inserted into the English file, prints, as promised, immediately after creating the SEO_simulator object.
sim = SEO_simulator(file_prefix, num_qbits, init_st_vec)
*************************beginning PRINT output PRINT line number=14 *********branch= pure state vector: ZL convention (Zero bit Last in state tuple) (0001)ZL ( 0.303046 - 0.151523j) prob=0.114796 (1001)ZL (-0.101015 - 0.612372j) prob=0.385204 (0011)ZL (-0.101015 - 0.612372j) prob=0.385204 (1011)ZL ( 0.303046 - 0.151523j) prob=0.114796 total probability of state vector (=one if no measurements)= 1.000000 dictionary with key=qubit, value=(Prob(0), Prob(1)) {0: (0.0, 1.0), 1: (0.5, 0.5), 2: (1.0, 0.0), 3: (0.5, 0.5)} ****************************ending PRINT output
Ask sim to print a description of final state vector
sim.describe_st_vec_dict(print_st_vec=True, do_pp=True,
omit_zero_amps=True, show_pp_probs=True)
*********branch= pure state vector: ZL convention (Zero bit Last in state tuple) (0010)ZL (-0.050508 - 0.306186j) prob=0.096301 (0001)ZL ( 0.303046 - 0.151523j) prob=0.114796 (1001)ZL (-0.101015 - 0.612372j) prob=0.385204 (0011)ZL (-0.087482 - 0.530330j) prob=0.288903 (1011)ZL (-0.282746 - 0.186684j) prob=0.114796 total probability of state vector (=one if no measurements)= 1.000000 dictionary with key=qubit, value=(Prob(0), Prob(1)) {0: (0.096301, 0.903699), 1: (0.5, 0.5), 2: (1.0, 0.0), 3: (0.5, 0.5)}
The object sim of SEO_simulator, holds, at this point, the final state vector for the evolution of the circuit subject to the initial state vector chosen. You might want to sample the probability distribution defined by that final state vector, and obtain counts of each observed multi-qubit state for a given number of shots. This is the type of output that a real qc device gives, albeit our counts have no extrinsic noise. One can ask sim to simulate such counts as follows:
counts = sim.get_counts(num_shots=100)
print(counts)
OrderedDict([('0001ZL', 15), ('0010ZL', 11), ('0011ZL', 33), ('1001ZL', 29), ('1011ZL', 12)])
And you can ask the Plotter class to plot those counts as follows:
Plotter.plot_counts(counts)