#!/usr/bin/env python
# coding: utf-8
# ## Say "Hello World" With Qubiter
# 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.
# In[1]:
import os
import sys
print(os.getcwd())
os.chdir('../../')
print(os.getcwd())
sys.path.insert(0,os.getcwd())
# 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.
# In[2]:
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)
# In[3]:
from qubiter.SEO_writer import *
from qubiter.SEO_simulator import *
from qubiter.StateVec import *
from qubiter.SEO_MatrixProduct import *
import numpy as np
# Sometimes, we use the word "bit" to denote both qbits (qubits, quantum bits) or cbits (classical bits).
#
# Number of qubits is 4.
# In[4]:
num_qbits = 4
# Use a trivial circuit embedder that embeds 4 qubits into same 4 qubits.
# In[5]:
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.
# In[6]:
file_prefix = 'hello_world_test'
wr = SEO_writer(file_prefix, emb)
# Write Pauli matrices $\sigma_X, \sigma_Y,\sigma_Z$ at position 2.
# In[7]:
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.
# In[8]:
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.
# In[9]:
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])$
# In[10]:
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})$
# In[11]:
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
# In[12]:
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".
# In[13]:
wr.write_PRINT("ALL")
# Swap qubits 1 and 3
# In[14]:
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)}$.
# In[15]:
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.
# In[16]:
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.
# In[17]:
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.
# In[18]:
wr.close_files()
# Look in files
#
# * ../io_folder/hello_world_test_4_eng.txt
# * ../io_folder/hello_world_test_4_ZLpic.txt
#
# 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
# In[19]:
wr.print_eng_file(jup=True)
# In[20]:
wr.print_pic_file(jup=True)
# You can ask wr for the path to the English and Picture files
# In[21]:
print(wr.get_eng_file_path())
# In[22]:
print(wr.get_pic_file_path())
# 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
# In[23]:
rdr = SEO_reader(file_prefix, num_qbits, write_log=True)
# The following file was just created
#
# * ../io_folder/hello_world_test_4_log.txt
# Once the log file is generated, you can ask the reader object rdr to print it for you on screen
# In[24]:
rdr.print_log_file()
# You can ask rdr for the path to the log file
# In[25]:
print(rdr.get_log_file_path())
# 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
# In[26]:
mp = SEO_MatrixProduct(file_prefix, num_qbits)
print('product array=')
print(np.array_str(mp.prod_arr,
precision=2, suppress_small=True))
# 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.
# In[27]:
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.
# In[28]:
sim = SEO_simulator(file_prefix, num_qbits, init_st_vec)
# Ask sim to print a description of final state vector
# In[29]:
sim.describe_st_vec_dict(print_st_vec=True, do_pp=True,
omit_zero_amps=True, show_pp_probs=True)
# 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:
# In[30]:
counts = sim.get_counts(num_shots=100)
print(counts)
# And you can ask the Plotter class to plot those counts as follows:
# In[31]:
Plotter.plot_counts(counts)
# In[ ]: