Path: blob/main/notebooks/intro/hello_quantum.py
3855 views
#!/usr/bin/env python312import copy3from io import BytesIO45from qiskit import BasicAer as Aer6from qiskit import ClassicalRegister, QuantumRegister, QuantumCircuit7from qiskit import execute8from qiskit.visualization import plot_bloch_multivector9from qiskit.quantum_info import Statevector1011from qiskit.quantum_info import Statevector12import numpy as np13import matplotlib.pyplot as plt14from matplotlib.patches import Circle, Rectangle, FancyBboxPatch15from ipywidgets import widgets16from IPython.display import display1718from qiskit_textbook.widgets._helpers import _img1920darker_purple = (105/255, 41/255, 196/255)21dark_purple = (165/255,110/255,255/255)22purple = (190/255, 149/255, 255/255)23light_purple = (212/255,187/255,255/255)2425white = (242/255,244/255,248/255)26light_gray = (221/255,225/255,230/255)27dark_gray = (193/255,199/255,205/255)28black = (52/255,58/255,63/255)293031# background color32background_color = white33# circle colors for normal and Y circles34circle_color = [white,white]35# colors for 0 and 1 parts of line and divider36line_color = [light_gray,purple,dark_purple]37# box colors for qubit boxes and correlation boxes38box_color = [light_purple,darker_purple]39# outer and inner colors of Bloch point40point_color = [dark_gray,dark_gray]414243class run_game():44# Implements a puzzle, which is defined by the given inputs.4546def __init__(self,initialize, success_condition, allowed_gates, vi, qubit_names={'0':'q[0]', '1':'q[1]'}, eps=0.1, backend=None, shots=1024,mode='line',verbose=False):47"""48initialize49List of gates applied to the initial 00 state to get the starting state of the puzzle.50Supported single qubit gates (applied to qubit '0' or '1') are 'x', 'y', 'z', 'h', 'ry(pi/4)'.51Supported two qubit gates are 'cz' and 'cx'. Specify only the target qubit.52success_condition53Values for pauli observables that must be obtained for the puzzle to declare success.54allowed_gates55For each qubit, specify which operations are allowed in this puzzle. 'both' should be used only for operations that don't need a qubit to be specified ('cz' and 'unbloch').56Gates are expressed as a dict with an int as value. If this is non-zero, it specifies the number of times the gate is must be used (no more or less) for the puzzle to be successfully solved. If the value is zero, the player can use the gate any number of times.57vi58Some visualization information as a three element list. These specify:59* which qubits are hidden (empty list if both shown).60* whether both circles shown for each qubit (use True for qubit puzzles and False for bit puzzles).61* whether the correlation circles (the four in the middle) are shown.62qubit_names63The two qubits are always called '0' and '1' from the programming side. But for the player, we can display different names.64eps=0.165How close the expectation values need to be to the targets for success to be declared.66backend=Aer.get_backend('qasm_simulator')67Backend to be used by Qiskit to calculate expectation values (defaults to local simulator).68shots=102469Number of shots used to to calculate expectation values.70mode='circle'71Either the standard 'Hello Quantum' visualization can be used (with mode='circle'), or the extended one (mode='y') or the alternative line based one (mode='line').72y_boxes = False73Whether to show expectation values involving y.74verbose=False75"""7677def get_total_gate_list():78# Get a text block describing allowed gates.7980total_gate_list = ""81for qubit in allowed_gates:82gate_list = ""83for gate in allowed_gates[qubit]:84if required_gates[qubit][gate] > 0 :85gate_list += ' ' + gate+" (use "+str(required_gates[qubit][gate])+" time"+"s"*(required_gates[qubit][gate]>1)+")"86elif allowed_gates[qubit][gate]==0:87gate_list += ' '+gate + ' '88if gate_list!="":89if qubit=="both" :90gate_list = "\nAllowed symmetric operations:" + gate_list91else :92gate_list = "\nAllowed operations for " + qubit_names[qubit] + ":\n" + " "*10 + gate_list93total_gate_list += gate_list +"\n"94return total_gate_list9596def get_success(required_gates):97# Determine whether the success conditions are satisfied, both for expectation values, and the number of gates to be used.9899success = True100grid.get_rho()101if verbose:102print(grid.rho)103for pauli in success_condition:104success = success and (abs(success_condition[pauli] - grid.rho[pauli])<eps)105for qubit in required_gates:106for gate in required_gates[qubit]:107success = success and (required_gates[qubit][gate]==0)108return success109110def show_circuit():111gates = get_total_gate_list112113def get_command(gate,qubit):114# For a given gate and qubit, return the string describing the corresponding Qiskit string.115116if qubit=='both':117qubit = '1'118qubit_name = qubit_names[qubit]119for name in qubit_names.values():120if name!=qubit_name:121other_name = name122# then make the command (both for the grid, and for printing to screen)123if gate in ['x','y','z','h']:124real_command = 'grid.qc.'+gate+'(grid.qr['+qubit+'])'125clean_command = 'qc.'+gate+'('+qubit_name+')'126elif gate in ['ry(pi/4)','ry(-pi/4)']:127real_command = 'grid.qc.ry('+'-'*(gate=='ry(-pi/4)')+'np.pi/4,grid.qr['+qubit+'])'128clean_command = 'qc.ry('+'-'*(gate=='ry(-pi/4)')+'np.pi/4,'+qubit_name+')'129elif gate in ['rx(pi/4)','rx(-pi/4)']:130real_command = 'grid.qc.rx('+'-'*(gate=='rx(-pi/4)')+'np.pi/4,grid.qr['+qubit+'])'131clean_command = 'qc.rx('+'-'*(gate=='rx(-pi/4)')+'np.pi/4,'+qubit_name+')'132elif gate in ['cz','cx','swap']:133real_command = 'grid.qc.'+gate+'(grid.qr['+'0'*(qubit=='1')+'1'*(qubit=='0')+'],grid.qr['+qubit+'])'134clean_command = 'qc.'+gate+'('+other_name+','+qubit_name+')'135return [real_command,clean_command]136137bloch = [None]138139# set up initial state and figure140if mode=='y':141grid = pauli_grid(backend=backend,shots=shots,mode='line',y_boxes=True)142else:143grid = pauli_grid(backend=backend,shots=shots,mode=mode)144145self.initializer = []146for gate in initialize:147command = get_command(gate[0],gate[1])148eval(command[0])149self.initializer.append(command[1])150151required_gates = copy.deepcopy(allowed_gates)152153# determine which qubits to show in figure154if allowed_gates['0']=={} : # if no gates are allowed for qubit 0, we know to only show qubit 1155shown_qubit = 1156elif allowed_gates['1']=={} : # and vice versa157shown_qubit = 0158else :159shown_qubit = 2160161# show figure162grid_view = _img()163grid.update_grid(bloch=bloch[0],hidden=vi[0],qubit=vi[1],corr=vi[2],message=get_total_gate_list(),output=grid_view)164display(grid_view.widget)165166167168description = {'gate':['Choose gate'],'qubit':['Choose '+'qu'*vi[1]+'bit'],'action':['Make it happen!']}169170all_allowed_gates_raw = []171for q in ['0','1','both']:172all_allowed_gates_raw += list(allowed_gates[q])173all_allowed_gates_raw = list(set(all_allowed_gates_raw))174175all_allowed_gates = []176for g in ['bloch']:177if g in all_allowed_gates_raw:178all_allowed_gates.append( g )179for g in ['x','y','z','h','cz','cx']:180if g in all_allowed_gates_raw:181all_allowed_gates.append( g )182for g in all_allowed_gates_raw:183if g not in all_allowed_gates:184all_allowed_gates.append( g )185186gate = widgets.ToggleButtons(options=description['gate']+all_allowed_gates)187qubit = widgets.ToggleButtons(options=[''])188action = widgets.ToggleButtons(options=[''])189190boxes = widgets.VBox([gate,qubit,action])191display(boxes)192self.program = []193self.qubit_names = qubit_names194195def given_gate(a):196# Action to be taken when gate is chosen. This sets up the system to choose a qubit.197198if gate.value:199if gate.value in allowed_gates['both']:200qubit.options = description['qubit'] + ["not required"]201qubit.value = "not required"202else:203allowed_qubits = []204for q in ['1','0']:205if (gate.value in allowed_gates[q]) or (gate.value in allowed_gates['both']):206allowed_qubits.append(q)207allowed_qubit_names = []208for q in allowed_qubits:209allowed_qubit_names += [qubit_names[q]]210qubit.options = description['qubit'] + allowed_qubit_names211212def given_qubit(b):213# Action to be taken when qubit is chosen. This sets up the system to choose an action.214215if qubit.value not in ['',description['qubit'][0],'Success!']:216action.options = description['action']+['Apply operation']217218def given_action(c):219# Action to be taken when user confirms their choice of gate and qubit.220# This applied the command, updates the visualization and checks whether the puzzle is solved.221222if action.value not in ['',description['action'][0]]:223# apply operation224if action.value=='Apply operation':225if qubit.value not in ['',description['qubit'][0],'Success!']:226# translate bit gates to qubit gates227if gate.value=='NOT':228q_gate = 'x'229elif gate.value=='CNOT':230q_gate = 'cx'231else:232q_gate = gate.value233if qubit.value=="not required":234q = qubit_names['1']235else:236q = qubit.value237q01 = '0'*(qubit.value==qubit_names['0']) + '1'*(qubit.value==qubit_names['1']) + 'both'*(qubit.value=="not required")238if q_gate in ['bloch']:239if q01 != bloch[0]:240bloch[0] = q01241else:242bloch[0] = None243else:244command = get_command(q_gate,q01)245eval(command[0])246self.program.append( command[1] )247if required_gates[q01][gate.value]>0:248required_gates[q01][gate.value] -= 1249250grid.update_grid(bloch=bloch[0],hidden=vi[0],qubit=vi[1],corr=vi[2],message=get_total_gate_list(),output=grid_view)251252success = get_success(required_gates)253if success:254gate.options = ['Success!']255qubit.options = ['Success!']256action.options = ['Success!']257plt.close(grid.fig)258else:259gate.value = description['gate'][0]260qubit.options = ['']261action.options = ['']262263gate.observe(given_gate)264qubit.observe(given_qubit)265action.observe(given_action)266267def get_circuit(self, use_initializer=False):268269q = QuantumRegister(2,'q')270b = ClassicalRegister(2,'b')271qc = QuantumCircuit(q,b)272273if '[' not in self.qubit_names['0']+self.qubit_names['0']:274exec(self.qubit_names['0']+' = q[0]')275exec(self.qubit_names['1']+' = q[1]')276277if use_initializer:278for line in self.initializer:279eval(line)280281for line in self.program:282eval(line)283284return qc285286def plot_spheres(self):287return plot_bloch_multivector(Statevector(self.get_circuit(use_initializer=True)),reverse_bits=True)288289class pauli_grid():290# Allows a quantum circuit to be created, modified and implemented, and visualizes the output in the style of 'Hello Quantum'.291292def __init__(self,backend=Aer.get_backend('qasm_simulator'),shots=1024,mode='circle',y_boxes=False):293"""294backend=Aer.get_backend('qasm_simulator')295Backend to be used by Qiskit to calculate expectation values (defaults to local simulator).296shots=1024297Number of shots used to to calculate expectation values.298mode='circle'299Either the standard 'Hello Quantum' visualization can be used (with mode='circle') or the alternative line based one (mode='line').300y_boxes=True301Whether to display full grid that includes Y expectation values.302"""303304self.backend = backend305self.shots = shots306307self.y_boxes = y_boxes308if self.y_boxes:309self.box = {'IZ':(-1, 2), 'IX':(-3, 4), 'IY':(-2, 3),310'XI':( 3, 4), 'YI':( 2,3 ), 'ZI':( 1, 2),311'XZ':( 2, 5), 'YZ':( 1, 4), 'ZZ':( 0, 3),312'XX':( 0, 7), 'YX':(-1, 6), 'ZX':(-2, 5),313'XY':( 1, 6), 'YY':( 0, 5), 'ZY':(-1, 4)}314else:315self.box = {'IZ':(-1, 2),'IX':(-2, 3),316'ZI':( 1, 2),'XI':( 2, 3),317'XZ':( 1, 4),'ZZ':( 0, 3),318'ZX':(-1, 4),'XX':( 0, 5)}319320self.rho = {}321for pauli in self.box:322self.rho[pauli] = 0.0323for pauli in ['ZI','IZ','ZZ']:324self.rho[pauli] = 1.0325326self.qr = QuantumRegister(2)327self.cr = ClassicalRegister(2)328self.qc = QuantumCircuit(self.qr, self.cr)329330self.mode = mode331332if self.mode!='y':333figsize=(5,5)334else:335figsize=(6,6)336337self.fig = plt.figure(figsize=(6,6),facecolor=background_color)338self.ax = self.fig.add_subplot(111)339plt.axis('off')340341self.bottom = self.ax.text(-3,1,"",size=10,va='top',color='black')342343self.points = {}344for pauli in self.box:345self.points[pauli] = [ self.ax.add_patch( Circle(self.box[pauli], 0.0, color=point_color[0], zorder=10) ) ]346self.points[pauli].append( self.ax.add_patch( Circle(self.box[pauli], 0.0, color=point_color[1], zorder=10) ) )347348self.initial = True349350def get_rho(self):351# Runs the circuit specified by self.qc and determines the expectation values for 'ZI', 'IZ', 'ZZ', 'XI', 'IX', 'XX', 'ZX' and 'XZ' (and the ones with Ys too if needed).352353if self.y_boxes:354corr = ['ZZ','ZX','XZ','XX','YY','YX','YZ','XY','ZY']355ps = ['X','Y','Z']356else:357corr = ['ZZ','ZX','XZ','XX']358ps = ['X','Z']359360361self.rho = {}362363results = {}364for basis in corr:365temp_qc = copy.deepcopy(self.qc)366for j in range(2):367if basis[j]=='X':368temp_qc.h(self.qr[j])369elif basis[j]=='Y':370temp_qc.sdg(self.qr[j])371temp_qc.h(self.qr[j])372373if self.backend==None:374ket = Statevector([1,0,0,0])375ket = ket.from_instruction(temp_qc)376results[basis] = ket.probabilities_dict()377else:378temp_qc.barrier(self.qr)379temp_qc.measure(self.qr,self.cr)380job = execute(temp_qc, backend=self.backend, shots=self.shots)381results[basis] = job.result().get_counts()382for string in results[basis]:383results[basis][string] = results[basis][string]/self.shots384385prob = {}386# prob of expectation value -1 for single qubit observables387for j in range(2):388389for p in ps:390pauli = {}391for pp in ['I']+ps:392pauli[pp] = (j==1)*pp + p + (j==0)*pp393prob[pauli['I']] = 0394for ppp in ps:395basis = pauli[ppp]396for string in results[basis]:397if string[(j+1)%2]=='1':398prob[pauli['I']] += results[basis][string]/(2+self.y_boxes)399400# prob of expectation value -1 for two qubit observables401for basis in corr:402prob[basis] = 0403for string in results[basis]:404if string[0]!=string[1]:405prob[basis] += results[basis][string]406407for pauli in prob:408self.rho[pauli] = 1-2*prob[pauli]409410411412413414def update_grid(self,rho=None,labels=False,bloch=None,hidden=[],qubit=True,corr=True,message="",output=None):415"""416rho = None417Dictionary of expectation values for 'ZI', 'IZ', 'ZZ', 'XI', 'IX', 'XX', 'ZX' and 'XZ'. If supplied, this will be visualized instead of the results of running self.qc.418labels = False419Determines whether basis labels are printed in the corresponding boxes.420bloch = None421If a qubit name is supplied, and if mode='line', Bloch circles are displayed for this qubit422hidden = []423Which qubits have their circles hidden (empty list if both shown).424qubit = True425Whether both circles shown for each qubit (use True for qubit puzzles and False for bit puzzles).426corr = True427Whether the correlation circles (the four in the middle) are shown.428message429A string of text that is displayed below the grid.430"""431432def see_if_unhidden(pauli):433# For a given Pauli, see whether its circle should be shown.434435unhidden = True436# first: does it act non-trivially on a qubit in `hidden`437for j in hidden:438unhidden = unhidden and (pauli[j]=='I')439# second: does it contain something other than 'I' or 'Z' when only bits are shown440if qubit==False:441for j in range(2):442unhidden = unhidden and (pauli[j] in ['I','Z'])443# third: is it a correlation pauli when these are not allowed444if corr==False:445unhidden = unhidden and ((pauli[0]=='I') or (pauli[1]=='I'))446# finally: is it actually in rho447unhidden = unhidden and (pauli in self.rho)448return unhidden449450def add_line(line,pauli_pos,pauli):451"""452For mode='line', add in the line.453454line = the type of line to be drawn (X, Z or the other one)455pauli = the box where the line is to be drawn456expect = the expectation value that determines its length457"""458459delta = 0.07460461unhidden = see_if_unhidden(pauli)462p = (1-self.rho[pauli])/2 # prob of 1 output463# in the following, white lines goes from a to b, and black from b to c464if unhidden:465if line=='X':466467a = ( self.box[pauli_pos][0]-length/2, self.box[pauli_pos][1]-width/2 )468c = ( self.box[pauli_pos][0]+length/2, self.box[pauli_pos][1]-width/2 )469b = ( p*a[0] + (1-p)*c[0] , p*a[1] + (1-p)*c[1] )470471self.ax.add_patch( Rectangle( a, length*(1-p), width, angle=0, color=line_color[0]) )472self.ax.add_patch( Rectangle( b, length*p, width, angle=0, color=line_color[2]) )473if length*p>delta:474self.ax.add_patch( Rectangle( (b[0]+delta/2,b[1]+delta*0.6), length*p-delta, width-delta, angle=0, color=line_color[1]) )475476elif line=='Z':477478a = ( self.box[pauli_pos][0]-width/2, self.box[pauli_pos][1]-length/2 )479c = ( self.box[pauli_pos][0]-width/2, self.box[pauli_pos][1]+length/2 )480b = ( p*a[0] + (1-p)*c[0] , p*a[1] + (1-p)*c[1] )481482self.ax.add_patch( Rectangle( a, width, length*(1-p), angle=0, color=line_color[0]) )483self.ax.add_patch( Rectangle( b, width, length*p, angle=0, color=line_color[2]) )484if length*p>delta:485self.ax.add_patch( Rectangle( (b[0]+delta/2,b[1]+delta/2), width-delta, length*p-delta, angle=0, color=line_color[1]) )486487else:488489490a = ( self.box[pauli_pos][0]-length/(2*np.sqrt(2)), self.box[pauli_pos][1]-length/(2*np.sqrt(2)) )491c = ( self.box[pauli_pos][0]+length/(2*np.sqrt(2)), self.box[pauli_pos][1]+length/(2*np.sqrt(2)) )492b = ( p*a[0] + (1-p)*c[0] , p*a[1] + (1-p)*c[1] )493494self.ax.add_patch( Rectangle( a, width, length*(1-p), angle=-45, color=line_color[0]) )495self.ax.add_patch( Rectangle( b, width, length*p, angle=-45, color=line_color[2]) )496if length*p>delta:497self.ax.add_patch( Rectangle( (b[0]+delta*np.sqrt(2)/2,b[1]+delta*0.1*np.sqrt(2)/2), width-delta, length*p-delta, angle=-45, color=line_color[1]) )498499return p500501L = 0.98*np.sqrt(2) # box height and width502length = 0.75*L # line length503width = 0.12*L # line width504r = 0.6 # circle radius505506# set the state507self.rho = rho508if self.rho=={} or self.rho==None:509self.get_rho()510511# draw boxes512if self.initial:513for pauli in self.box:514if 'I' in pauli:515color = box_color[0]516else:517color = box_color[1]518self.ax.add_patch( Rectangle( (self.box[pauli][0],self.box[pauli][1]-1), L, L, angle=45, color=color) )519520# draw circles521for pauli in self.box:522unhidden = see_if_unhidden(pauli)523if unhidden:524if self.mode!='line':525prob = (1-self.rho[pauli])/2526color=(prob,prob,prob)527self.ax.add_patch( Circle(self.box[pauli], r, color=color) )528else:529if 'Y' in pauli:530self.ax.add_patch( Circle(self.box[pauli], r, color=circle_color[1]) )531else:532self.ax.add_patch( Circle(self.box[pauli], r, color=circle_color[0]) )533534# hide points535for pauli in self.points:536for point in self.points[pauli]:537point.radius = 0538539# update bars if required540if self.mode=='line':541if bloch in ['0','1']:542for other in 'IXZ':543px = other*(bloch=='1') + 'X' + other*(bloch=='0')544pz = other*(bloch=='1') + 'Z' + other*(bloch=='0')545prob_z = add_line('Z',pz,pz)546prob_x = add_line('X',pz,px)547for j,point in enumerate(self.points[pz]):548if see_if_unhidden(pz):549point.center = (self.box[pz][0]-(prob_x-0.5)*length, self.box[pz][1]-(prob_z-0.5)*length)550point.radius = (j==0)*0.05 + (j==1)*0.04551px = 'I'*(bloch=='0') + 'X' + 'I'*(bloch=='1')552pz = 'I'*(bloch=='0') + 'Z' + 'I'*(bloch=='1')553add_line('Z',pz,pz)554add_line('X',px,px)555if self.y_boxes:556for pauli in ['IY','YI','XY','YX','ZY','YZ','YY']:557add_line('ZXY',pauli,pauli)558else:559for pauli in self.box:560for point in self.points[pauli]:561point.radius = 0.0562if pauli in ['ZI','IZ','ZZ']:563add_line('Z',pauli,pauli)564if pauli in ['XI','IX','XX']:565add_line('X',pauli,pauli)566if pauli in ['XZ','ZX']+self.y_boxes*['IY','YI','XY','YX','ZY','YZ','YY']:567add_line('ZXY',pauli,pauli)568569self.bottom.set_text(message)570571if labels:572for pauli in self.box:573plt.text(self.box[pauli][0]-0.18,self.box[pauli][1]-0.85, pauli)574575if self.y_boxes:576self.ax.set_xlim([-4,4])577self.ax.set_ylim([0,8])578else:579self.ax.set_xlim([-3,3])580self.ax.set_ylim([0,6])581582if output is None:583self.fig.canvas.draw()584else:585plt.close() # prevent the graphic from showing inline586output.value = self.fig587588self.initial = False589590591