# -*- coding: utf-8 -*- import math def ClearScreen () : # DESCRIPTION: # Prints a whole bunch of space to screen print("\n"*200) def ColouredString (message,colour) : # DESCRIPTION: # Prints in a colour specified by the colour in ["r","b","p"] and then sets what follows back to white # first the colour coloured_message = "" if colour=="r": # red coloured_message += "\x1b[1;31m" elif colour=="g": # green coloured_message += "\x1b[1;32m" elif colour=="b": # blue coloured_message += "\x1b[1;34m" elif colour=="p": # purple coloured_message += "\x1b[1;35m" else: # black (which should actually be the default text colour) coloured_message += "\x1b[0m" # then the message coloured_message += message # and back to black coloured_message += "\x1b[0m" return coloured_message def Expect2Prob ( expect ) : # DESCRIPTION: # Converts expectation value to a probability of getting the outcome 1. return (1-expect)/2 def Expect2Polar ( boxes ) : # DESCRIPTION: # Takes the contents of an X and Z box and turns it into polar coordinates for the Bloch circle. # INPUT: # {String: Float} boxes Should have entries for "X" and "Z", which serve as the horizontal and vertical boxes respectively. # OUTPUT: # [Float] [ degrees, radius ] degrees is between 0 and 1. It is the angle clockwise from the top as a fraction of 2*Pi. Radius is how far out the point is. Will be 1 for pure states and 0 for maximally mixed. radius = math.sqrt( boxes["X"]**2 + boxes["Z"]**2) degrees = 0 # default value of 0 if radius>0.0: degrees = math.acos( -boxes["Z"] / radius ) / (2*math.pi) if boxes["X"]>=0.0: degrees = 1 - degrees return [ degrees, radius ] def Polar2Expect ( polar_coords ) : # DESCRIPTION: # As Boxes2Polar, but with inputs and outputs reversed boxes = {} boxes["X"] = -polar_coords[1] * math.sin( 2*math.pi*polar_coords[0]) boxes["Z"] = -polar_coords[1] * math.cos( 2*math.pi*polar_coords[0]) return boxes def ExchangeBoxes ( state, box1, box2 ) : # DESCRIPTION: # Given a state and two of its boxes, the values for these boxes are exchanged output_state = state temp = output_state[box1] output_state[box1] = output_state[box2] output_state[box2] = temp return output_state def Swap ( state ) : # DESCRIPTION: # Determines the state after a swap gate # INPUT: # {String: Float} state Two qubit state. # OUTPUT: # {String: Float} state Transformed version of input state. swapped_state = {} for box in state.keys(): swapped_state[box[1]+box[0]] = state[box] return swapped_state def ApplyGate ( state, gate, qubit ) : # DESCRIPTION: # Transforms the given state according to the given gate. # INPUT: # {String: Float} state Full two qubit state. Needs entries for XI, ZI, IZ, IX, XX, XZ, ZX, ZZ and YY # String gate QISKit style str to specify a gate (can be x, z, h, q, qdg, or cz) # Int qubit Qubit on which single qubit gate is applied. Unused for CZ. # OUTPUT: # {String: Float} state Transformed version of input state. # We begin constructing the output state by copying the input state. output_state = state # Single qubit gates require special treatment, so we deal with these separately. if gate in ["x","z","h","q","qdg"] : # Single qubit gates act on pairs of boxes. These are the pairs that form Bloch circles. # For qubit 0, these pairs are (XI, ZI), (XX,ZX) and (XZ,ZZ). # For qubit 2 they are (IX, IZ), (XX,XZ) and (ZX,ZZ). # Here we loop over and construct the three pairs, which in each case will be (p[0],p[1]). for rc in ["I","X","Z"] : box_name = {"X":rc,"Z":rc} for p in ["X","Z"] : if qubit=="0" : box_name[p] = p + box_name[p] else : box_name[p] = box_name[p] + p # What we do to the pairs depends on the gate we apply. if gate=="x": # invert Z box and invert YY output_state[box_name["Z"]] = -output_state[box_name["Z"]] output_state["YY"] = -output_state["YY"] elif gate=="z" :# invert X box and invert YY output_state[box_name["X"]] = -output_state[box_name["X"]] output_state["YY"] = -output_state["YY"] elif gate=="h" : # exchange X and Z boxes and invert YY output_state = ExchangeBoxes( output_state, box_name["X"], box_name["Z"]) output_state["YY"] = -output_state["YY"] elif gate in ["q","qdg"] : polar_coords = Expect2Polar( { "X":output_state[box_name["X"]], "Z":output_state[box_name["Z"]] } ) # convert to polar coords if gate=="q" : # change angle according to the rotation polar_coords[0] += 1/8 else : polar_coords[0] -= 1/8 # convert back to boxes output_boxes = Polar2Expect(polar_coords) for p in ["X","Z"] : output_state[box_name[p]] = output_boxes[p] else : print("Error: Unknown single qubit gate") # Now for the two qubit gates elif gate=="cz" : # exchange contents of XI and XZ output_state = ExchangeBoxes( output_state, "XI", "XZ") # exchange contents of IX and ZX output_state = ExchangeBoxes( output_state, "IX", "ZX") # exchange contents of XX and YY output_state = ExchangeBoxes( output_state, "XX", "YY") elif gate=="cx" : if qubit=="1" : # exchange contents of XI and XX output_state = ExchangeBoxes( output_state, "XI", "XX") # exchange contents of IZ and ZZ output_state = ExchangeBoxes( output_state, "IZ", "ZZ") # exchange contents of XZ and YY output_state = ExchangeBoxes( output_state, "XZ", "YY") # invert XZ output_state["XZ"] = -output_state["XZ"] elif qubit=="0" : # exchange contents of ZI and ZZ output_state = ExchangeBoxes( output_state, "ZI", "ZZ") # exchange contents of IX and XX output_state = ExchangeBoxes( output_state, "IX", "XX") # exchange contents of ZX and YY output_state = ExchangeBoxes( output_state, "ZX", "YY") # invert ZX output_state["ZX"] = -output_state["ZX"] output_state["YY"] = -output_state["YY"] # invert YY else : print("Error: Unknown gate") return output_state def MakeState ( initial_state, gates ) : # DESCRIPTION: # Constructs a state given an initial state and list of gates # Can also be used to construct an initial state such that the target state and solution are as specified by the input # In this case, note that the inverse of the desired solution must be supplied # Put simply, this means that the first gate should be on the right, and the substition q <-> qdg should be used state = initial_state for gate in gates : state = ApplyGate( state, gate[0], gate[1] ) return state def MakeCell ( cell_state ) : # DESCRIPTION: # Prints a single box or Bloch circle, depending on the input state. # INPUT: # {String: Float} cell_state With key "X", value is the expectation value for horizontal red box. # Similarly for "Z" and the vertical blue box, and "Y" for a diagonal purple box. # Note that a cell that uses "Y" will correspond to XZ or ZZ boxes. Not a Y box. # OUTPUT: # [String] lines List of 12 lines, which when printed sequentially will display the cell. # PROCESS: # When a single key is supplied, the corresponding box is printed. # When both "X" and "Z" are supplied, the boxes are combined into a Bloch circle. # In all cases, the level on the box is first converted from the expectation value to the probability. reso = {"X":17, "Z":9, "Y":13} # number of characters used for each type of box (including center) bottom = "───────────────────────" # bottom border right = "|" # right border # The 8 points around the circle and one in the middle. Default to a dot c = [] c.append("˙") c.append(".") c.append("·") c.append("˙") c.append(".") c.append("˙") c.append("·") c.append(".") c.append(" ") # When a Bloch sphere is displayed, the point corresponding to the state is denoted by "*" # This is displayed only for pretty mixed states (radius <0.25) and pretty pure ones (radius>0.75) if ( "X" in cell_state.keys() and "Z" in cell_state.keys() ) : # both X and Z boxes need to be present in the cell if ( Expect2Polar( cell_state )[1]<0.25 ) : # if state is pretty mixed, point is shown in the center c[8] = "*" elif Expect2Polar( cell_state )[1]>0.75 : # if state is pretty pure, it is one of the 8 around the edge point = 0 degree = Expect2Polar( cell_state )[0] for eighth in range(1,8) : if degree>(float(eighth)/8 - 1.0/16) and degree<=(float(eighth)/8 + 1.0/16 ) : point = eighth if point in [1,4,7] : c[ point ] = "⁎" else : c[ point ] = "*" # Strings for the three boxes. Blank if unused, but filled with █ and ░ if used to represent how 'filled' they are. b = {"X":[], "Z":[], "Y":[] } for box in [ "X", "Z", "Y" ] : this_b = [] if box not in cell_state.keys() : for _ in range(1,reso[box]+1) : this_b.append(" ") else : prob = Expect2Prob( cell_state[box] ) # convert from expectation value to prob of a 1 outcome fill = int( float(reso[box]) * prob ) if (prob>0.5 and fill != reso[box]) : # if over half filled, but not completely filled, round up (for clear visuals) fill += 1 for _ in range(fill) : if box == "X" : this_b.append( ColouredString( "█" , "r" ) ) elif box == "Z" : this_b.append( ColouredString( "█" , "b" ) ) elif box == "Y" : this_b.append( ColouredString( "█" , "p" ) ) for _ in range(fill,reso[box]) : if box == "X" : this_b.append( ColouredString( "░" , "r" ) ) elif box == "Z" : this_b.append( ColouredString( "░" , "b" ) ) elif box == "Y" : this_b.append( ColouredString( "░" , "p" ) ) b[box] = this_b # center is X with the colour of the cell, unless the cell is empty if "Y" in cell_state.keys() : c[8] = ColouredString( b["Y"][int(reso["Y"]/2)], "p" ) elif "X" in cell_state.keys() : c[8] = ColouredString( b["X"][int(reso["X"]/2)], "r" ) elif "Z" in cell_state.keys() : c[8] = ColouredString( b["Z"][int(reso["Z"]/2)], "b" ) b["X"][int(reso["X"]/2)] = c[8] # The center will be the ninth element of c instead # Declare and construct the lines. lines = [] if cell_state=={"II":1.0} : for _ in range(11) : lines.append( " "+right ) else : lines.append( " .· "+c[0]+" ·. "+right ) lines.append( " "+c[7]+"˙ "+b["Z"][8]+" ˙"+c[1]+" "+right ) lines.append( " · "+b["Z"][7]+" "+b["Y"][11]+b["Y"][12]+" · "+right ) lines.append( " · "+b["Z"][6]+" "+b["Y"][9]+b["Y"][10]+" · "+right ) lines.append( " "+b["Z"][5]+""+b["Y"][7]+b["Y"][8]+" "+right ) lines.append( ""+c[6]+" "+"".join(b["X"])+" "+c[2]+" "+right ) lines.append( " "+b["Y"][4]+b["Y"][5]+""+b["Z"][3]+" "+right ) lines.append( " ˙ "+b["Y"][2]+b["Y"][3]+" "+b["Z"][2]+" ˙ "+right ) lines.append( " · "+b["Y"][0]+b["Y"][1]+" "+b["Z"][1]+" · "+right ) lines.append( " "+c[5]+". "+b["Z"][0]+" ."+c[3]+" "+right ) lines.append( " ˙· "+c[4]+" ·˙ "+right ) lines.append( bottom + right ) return lines def MakeGrid ( state, shown_qubit, active_qubit, bloch ) : # DESCRIPTION: # The inputs describe what the grid is doing. This function puts all that information together to actually make the grid. # INPUT: # {String: Float} state Full two qubit state. Needs entries for XI, ZI, IZ, IX, XX, XZ, ZX, ZZ and YY # Int shown_qubit If this is 0 or 1, only the two boxes of that qubit are shown. Otherwise, all are shown. # Int active_qubit If this is 0 or 1, the diagonals are straightened according to their function for this qubit. Otherwise they remain diagonal. # Bool bloch Whether or not to display Bloch circles for the qubit specified above # OUTPUT: # [String] grid_lines An array of strs that represent the grid cells = { "XI":[], "ZI":[], "XX":[],"XZ":[],"ZX":[],"ZZ":[],"YY":[],"II":[], "IX":[], "IZ":[] } # determine which cells behave in which way (I is blank, B is Bloch circle, X and Z are horizontal and vertical boxes and Y are diagonal) grid_lines = [] cell_types = {} if bloch : if shown_qubit==0 : cell_types = {"I":["II","ZI","XX","XZ","ZX","ZZ","YY","IX","IZ"],"X":[],"Y":[],"Z":[],"B":["XI"]} # shown_qubit is used as active qubit elif shown_qubit==1 : cell_types = {"I":["II","XI","ZI","XX","XZ","ZX","ZZ","YY","IZ"],"X":[],"Y":[],"Z":[],"B":["IX"]} # shown_qubit is used as active qubit else : if active_qubit==0 : cell_types = {"I":["II","ZI","ZX","ZZ"],"X":["IX"],"Y":[],"Z":["IZ"],"B":["XI","XX","XZ"]} elif active_qubit==1 : cell_types = {"I":["II","IZ","XZ","ZZ"],"X":["XI"],"Y":[],"Z":["ZI"],"B":["IX","XX","ZX"]} # these are the same as above but with strs reversed else : print("Error: A valid qubit must be chosen to display a Bloch circle") else : if shown_qubit==0 : cell_types = {"I":["II","XX","XZ","ZX","ZZ","YY","IX","IZ"],"X":["XI"],"Y":[],"Z":["ZI"],"B":[]} elif shown_qubit==1 : cell_types = {"I":["II","XI","ZI","XX","XZ","ZX","ZZ","YY","IX","IZ"],"X":["IX"],"Y":[],"Z":["IZ"],"B":[]} else : # Y for diagonal boxes, since there is no active qubit cell_types = {"I":["II"],"X":["XI","IX","XX"],"Y":["XZ","ZX"],"Z":["ZI","IZ","ZZ"],"B":[]} # make blank cell for cell in cell_types["I"] : if cell=="II" : cells[cell] = MakeCell( {"II":1.0} ) else : cells[cell] = MakeCell( {} ) # make cells with horizontal and vertical boxes for cell_type in ["X","Z"] : for cell in cell_types[cell_type] : cells[cell] = MakeCell( { cell_type: state[cell] } ) # make cells with diagonal boxes for cell in cell_types["Y"] : if active_qubit in [0,1] : index = active_qubit cells[cell] = MakeCell( { str(cell[index]):state[cell] } ) # qubit dependent cell type is used else : cells[cell] = MakeCell( { "Y":state[cell] } ) # same as in X,Z loop above # make cells with Bloch circle for cell in cell_types["B"] : index = (1-active_qubit) if active_qubit==0 : cells[cell] = MakeCell( { "X":state["X"+str(cell[index])], "Z":state["Z"+str(cell[index])] } ) else : cells[cell] = MakeCell( { "X":state[str(cell[index])+"X"], "Z":state[str(cell[index])+"Z"] } ) for l in range(12) : grid_lines.append( cells["II"][l] + cells["ZI"][l] + cells["XI"][l] ) for l in range(12) : grid_lines.append( cells["IZ"][l] + cells["ZZ"][l] + cells["XZ"][l] ) for l in range(12) : grid_lines.append( cells["IX"][l] + cells["ZX"][l] + cells["XX"][l] ) return grid_lines def PrintScreen ( message, level, intro, program, state, shown_qubit, active_qubit, bloch, allowed_gates, required_gates ) : print("\n"*3) # set up the screen: job 1 # text at the top ClearScreen () print("\nLevel "+str(level+1)+"\n") for line in intro[level] : print("> " + line + "...") if program != [] : print("\nYour QISKit program so far\n") for line in program : print(line) print("\n") # set up the screen: job 2 # the grid print("\n") grid_lines = MakeGrid( state, shown_qubit, active_qubit, bloch ) for line in grid_lines : print(" "+line) # set up the screen: job 3 # state allowed gates for qubit in allowed_gates.keys() : gate_list = "" for gate in allowed_gates[qubit].keys() : if required_gates[qubit][gate] > 0 : gate_list += " "+gate+" (use exactly "+str(required_gates[qubit][gate])+" times)" elif allowed_gates[qubit][gate]==0: gate_list += " "+gate if gate_list!="" : if qubit=="both" : print("\nAllowed two qubit operations:") else : print("\nAllowed operations for qubit " + qubit + ":") print(gate_list) return input("\n"+message+"\n") def WriteQISKit( gate, qubit, qubits_used, program, mode): if mode=="5": regL = "" regR = "" else: regL = "qubit[" regR = "]" if gate=="cz" : program.append("program.cz( "+regL+qubits_used[0]+regR+", "+regL+qubits_used[1]+regR+" )") elif gate=="cx" : if qubit==qubits_used[0] : program.append("program.cx( "+regL+qubits_used[1]+regR+", "+regL+qubits_used[0]+" )") else : program.append("program.cx( "+regL+qubits_used[0]+regR+", "+regL+qubits_used[1]+" )") elif gate in ["q","qdg"]: sign = "-"*(gate=="qdg") program.append("program.u3( "+sign+"math.pi/4,0,0, "+regL+qubit+regR+" )") else : program.append("program."+gate+"( "+regL+qubit+regR+" )") def MakeTarget ( initial_state, gates ): if gates=='swap': target = Swap( initial_state ) else: target = MakeState( initial_state, gates ) target_string = "\n" for line in MakeGrid(target,2,2,False): target_string += " " + line + "\n" return target, target_string