diff --git a/examples/Python/rippleadd_obj.py b/examples/Python/rippleadd_obj.py index 578c228ca1..66c28361f4 100644 --- a/examples/Python/rippleadd_obj.py +++ b/examples/Python/rippleadd_obj.py @@ -15,7 +15,9 @@ import os sys.path.append(os.path.join(os.path.dirname(__file__), '../..')) from qiskit import QuantumProgram -n = 4 +import Qconfig + +n = 3 QPS_SPECS = { "name": "Program", @@ -46,28 +48,6 @@ cout = QP_program.quantum_registers("cout") ans = QP_program.classical_registers("ans") -def print_2x8(layout): - """Print a 2x8 layout.""" - rev_layout = {b: a for a, b in layout.items()} - print("") - for i in range(8): - qubit = ("q", i) - if qubit in rev_layout: - print("%s[%d] " % (rev_layout[qubit][0], rev_layout[qubit][1]), - end="") - else: - print("XXXX ", end="") - print("") - for i in range(8, 16): - qubit = ("q", i) - if qubit in rev_layout: - print("%s[%d] " % (rev_layout[qubit][0], rev_layout[qubit][1]), - end="") - else: - print("XXXX ", end="") - print("") - - def majority(p, a, b, c): """Majority gate.""" p.cx(c, b) @@ -101,121 +81,24 @@ for j in range(n): qc.measure(b[j], ans[j]) # Measure the output register qc.measure(cout[0], ans[n]) -###################################################################### -# Work in progress to map the QuantumCircuit to the 2x8 device -###################################################################### - -print("QuantumCircuit OPENQASM") -print("-----------------------") -print(qc.qasm()) - -###################################################################### -# First pass: expand subroutines to a basis of 1 and 2 qubit gates. -###################################################################### - -source, C = QP_program.unroller_code(qc) - -print("Unrolled OPENQASM to [u1, u2, u3, cx] basis") -print("-------------------------------------------") -print(C.qasm(qeflag=True)) - -print("") -print("size = %d" % C.size()) -print("depth = %d" % C.depth()) -print("width = %d" % C.width()) -print("bits = %d" % C.num_cbits()) -print("factors = %d" % C.num_tensor_factors()) - -###################################################################### -# Second pass: choose a layout on the coupling graph and add SWAPs. -###################################################################### - -# This is the 2 by 8 array of 16 qubits -couplingdict = {0: [1, 8], 1: [2, 9], 2: [3, 10], 3: [4, 11], 4: [5, 12], +# 2x8 array +coupling_map = {0: [1, 8], 1: [2, 9], 2: [3, 10], 3: [4, 11], 4: [5, 12], 5: [6, 13], 6: [7, 14], 7: [15], 8: [9], 9: [10], 10: [11], 11: [12], 12: [13], 13: [14], 14: [15]} +result = QP_program.set_api(Qconfig.APItoken, Qconfig.config["url"]) +if not result: + print("Error setting API") + sys.exit(1) -# QP_program.compile(couplingdict) +# First version: not compiled +result = QP_program.execute(device='simulator', coupling_map=None, shots=1024) +# print(result["compiled_circuits"][0]["qasm"]) +print(result["compiled_circuits"][0]["result"]["data"]["counts"]) -coupling = QP_program.mapper.Coupling(couplingdict) -C_mapped, layout = QP_program.mapper.swap_mapper(C, coupling) +# Second version: compiled to 2x8 array coupling graph +result = QP_program.execute(device='simulator', coupling_map=coupling_map, shots=1024) +# print(result["compiled_circuits"][0]["qasm"]) +print(result["compiled_circuits"][0]["result"]["data"]["counts"]) -print("") -print("Initial qubit positions on 2x8 layout") -print("-------------------------------------------") -print_2x8(layout) - -print("") -print("Inserted SWAP gates for 2x8 layout") -print("-------------------------------------------") -print(C_mapped.qasm(qeflag=True)) - -print("") -print("size = %d" % C_mapped.size()) -print("depth = %d" % C_mapped.depth()) -print("width = %d" % C_mapped.width()) -print("bits = %d" % C_mapped.num_cbits()) -print("factors = %d" % C_mapped.num_tensor_factors()) - -###################################################################### -# Third pass: expand SWAP subroutines and adjust cx gate orientations. -###################################################################### - - -source, C_mapped_unrolled = QP_program.unroller_code(C_mapped) - -C_directions = QP_program.mapper.direction_mapper(C_mapped_unrolled, coupling) - -print("") -print("Changed CNOT directions") -print("-------------------------------------------") -print(C_directions.qasm(qeflag=True)) - -print("") -print("size = %d" % C_directions.size()) -print("depth = %d" % C_directions.depth()) -print("width = %d" % C_directions.width()) -print("bits = %d" % C_directions.num_cbits()) -print("factors = %d" % C_directions.num_tensor_factors()) - -###################################################################### -# Fourth pass: collect runs of cx gates and cancel them. -###################################################################### -# We assume there are no double edges in the connectivity graph, so -# we don't need to check the direction of the cx gates in a run. -runs = C_directions.collect_runs(["cx"]) -for r in runs: - if len(r) % 2 == 0: - for n in r: - C_directions._remove_op_node(n) - else: - for j in range(len(r) - 1): - C_directions._remove_op_node(r[j]) - -print("") -print("Cancelled redundant CNOT gates") -print("-------------------------------------------") -print(C_directions.qasm(qeflag=True)) - -print("") -print("size = %d" % C_directions.size()) -print("depth = %d" % C_directions.depth()) -print("width = %d" % C_directions.width()) -print("bits = %d" % C_directions.num_cbits()) -print("factors = %d" % C_directions.num_tensor_factors()) - -###################################################################### -# Fifth pass: expand single qubit gates to u1, u2, u3 and simplify. -###################################################################### - -source, C_directions_unrolled = QP_program.unroller_code(C_directions) - -runs = C_directions_unrolled.collect_runs(["u1", "u2", "u3"]) -print(runs) - -# TODO: complete this pass -# TODO: add Circuit method to tally each type of gate so we can see costs -# TODO: test on examples using simulator - -# TODO: simple unit tests for qasm, mapper, unroller, circuit +# Both versions should give the same distribution diff --git a/examples/Python/teleport_obj.py b/examples/Python/teleport_obj.py index e4a4aee001..de8bd80282 100644 --- a/examples/Python/teleport_obj.py +++ b/examples/Python/teleport_obj.py @@ -15,6 +15,8 @@ import os sys.path.append(os.path.join(os.path.dirname(__file__), '../..')) from qiskit import QuantumProgram +import Qconfig + QPS_SPECS = { "name": "Program", "circuits": [{ @@ -54,56 +56,28 @@ qc.z(q[2]).c_if(c0, 1) qc.x(q[2]).c_if(c1, 1) qc.measure(q[2], c2[0]) -###################################################################### +# qx5qv2 coupling +coupling_map = {0: [1, 2], + 1: [2], + 2: [], + 3: [2, 4], + 4: [2]} -print("QuantumCircuit OPENQASM") -print("-----------------------") -print(qc.qasm()) +# Experiment does not support feedback, so we use the simulator -QASM, C = QP_program.unroller_code(qc) +result = QP_program.set_api(Qconfig.APItoken, Qconfig.config["url"]) +if not result: + print("Error setting API") + sys.exit(1) -print("") -print("size = %d" % C.size()) -print("depth = %d" % C.depth()) -print("width = %d" % C.width()) -print("bits = %d" % C.num_cbits()) -print("factors = %d" % C.num_tensor_factors()) +# First version: not compiled +result = QP_program.execute(device='simulator', coupling_map=None, shots=1024) +# print(result["compiled_circuits"][0]["qasm"]) +print(result["compiled_circuits"][0]["result"]["data"]["counts"]) -print("") -print("Unrolled OPENQASM") -print("-----------------------") -print(QASM) +# Second version: compiled to 2x8 array coupling graph +result = QP_program.execute(device='simulator', coupling_map=coupling_map, shots=1024) +# print(result["compiled_circuits"][0]["qasm"]) +print(result["compiled_circuits"][0]["result"]["data"]["counts"]) -# This is the 2 by 8 -couplingdict = {0: [1, 8], 1: [2, 9], 2: [3, 10], 3: [4, 11], 4: [5, 12], - 5: [6, 13], 6: [7, 14], 7: [15], 8: [9], 9: [10], 10: [11], - 11: [12], 12: [13], 13: [14], 14: [15]} - -coupling = QP_program.mapper.Coupling(couplingdict) -print("") -print("2x8 coupling graph = \n%s" % coupling) - -C_mapped, layout = QP_program.mapper.swap_mapper(C, coupling) -rev_layout = {b: a for a, b in layout.items()} - -print("") -print("2x8 layout:") -for i in range(8): - qubit = ("q", i) - if qubit in rev_layout: - print("%s[%d] " % (rev_layout[qubit][0], rev_layout[qubit][1]), end="") - else: - print("XXXX ", end="") -print("") -for i in range(8, 16): - qubit = ("q", i) - if qubit in rev_layout: - print("%s[%d] " % (rev_layout[qubit][0], rev_layout[qubit][1]), end="") - else: - print("XXXX ", end="") -print("") - -print("") -print("Mapped OPENQASM") -print("-----------------------") -print(C_mapped.qasm(qeflag=True)) +# Both versions should give the same distribution diff --git a/examples/Python/test16_obj.py b/examples/Python/test16_obj.py new file mode 100644 index 0000000000..a5d6c1874c --- /dev/null +++ b/examples/Python/test16_obj.py @@ -0,0 +1,80 @@ +""" +Ripple adder example based on OPENQASM example. + +Author: Andrew Cross + Jesus Perez +""" + +import sys +import os + +# We don't know from where the user is running the example, +# so we need a relative position from this file path. +# TODO: Relative imports for intra-package imports are highly discouraged. +# http://stackoverflow.com/a/7506006 +sys.path.append(os.path.join(os.path.dirname(__file__), '../..')) +from qiskit import QuantumProgram + +import Qconfig + + +def swap(qc, q0, q1): + """Swap gate.""" + qc.cx(q0, q1) + qc.cx(q1, q0) + qc.cx(q0, q1) + +n = 3 + +QPS_SPECS = { + "name": "Program", + "circuits": [{ + "name": "test16", + "quantum_registers": [ + {"name": "q", + "size": n}, + {"name": "r", + "size": n} + ], + "classical_registers": [ + {"name": "ans", + "size": 2*n}, + ]}] +} + +QP_program = QuantumProgram(specs=QPS_SPECS) +qc = QP_program.circuit("test16") +q = QP_program.quantum_registers("q") +r = QP_program.quantum_registers("r") +ans = QP_program.classical_registers("ans") + +qc.x(q[0]) # Set input q = 1...0000 + +swap(qc, q[0], q[n-1]) +swap(qc, q[n-1], r[n-1]) + +for j in range(n): + qc.measure(q[j], ans[j]) + qc.measure(r[j], ans[j+n]) + +# 2x8 array +coupling_map = {0: [1, 8], 1: [2, 9], 2: [3, 10], 3: [4, 11], 4: [5, 12], + 5: [6, 13], 6: [7, 14], 7: [15], 8: [9], 9: [10], 10: [11], + 11: [12], 12: [13], 13: [14], 14: [15]} + +result = QP_program.set_api(Qconfig.APItoken, Qconfig.config["url"]) +if not result: + print("Error setting API") + sys.exit(1) + +# First version: not compiled +result = QP_program.execute(device='simulator', coupling_map=None, shots=1024) +# print(result["compiled_circuits"][0]["qasm"]) +print(result["compiled_circuits"][0]["result"]["data"]["counts"]) + +# Second version: compiled to 2x8 array coupling graph +result = QP_program.execute(device='simulator', coupling_map=coupling_map, shots=1024) +# print(result["compiled_circuits"][0]["qasm"]) +print(result["compiled_circuits"][0]["result"]["data"]["counts"]) + +# Both versions should give the same distribution diff --git a/qiskit/_quantumprogram.py b/qiskit/_quantumprogram.py index a589818232..e85ab00a36 100644 --- a/qiskit/_quantumprogram.py +++ b/qiskit/_quantumprogram.py @@ -236,10 +236,13 @@ class QuantumProgram(object): for circuit in circuits: qasm_source, circuit_unrolled = self.unroller_code(circuit) if coupling_map: + print("pre-mapping properties: %s" + % circuit_unrolled.property_summary()) # Insert swap gates coupling = self.mapper.Coupling(coupling_map) circuit_unrolled, final_layout = self.mapper.swap_mapper( circuit_unrolled, coupling) + print("layout: %s" % final_layout) # Expand swaps qasm_source, circuit_unrolled = self.unroller_code( circuit_unrolled) @@ -251,6 +254,8 @@ class QuantumProgram(object): # Simplify single qubit gates circuit_unrolled = mapper.optimize_1q_gates(circuit_unrolled) qasm_source = circuit_unrolled.qasm(qeflag=True) + print("post-mapping properties: %s" + % circuit_unrolled.property_summary()) qasm_circuits.append({'qasm': qasm_source}) unrolled_circuits.append({'circuit_unrolled': circuit_unrolled}) return qasm_circuits, unrolled_circuits diff --git a/qiskit/circuit/_circuit.py b/qiskit/circuit/_circuit.py index 430cd216c4..7d4df8c209 100644 --- a/qiskit/circuit/_circuit.py +++ b/qiskit/circuit/_circuit.py @@ -1251,3 +1251,13 @@ class Circuit: else: op_dict[name] += 1 return op_dict + + def property_summary(self): + """Return a dictionary of circuit properties.""" + summary = {"size": self.size(), + "depth": self.depth(), + "width": self.width(), + "bits": self.num_cbits(), + "factors": self.num_tensor_factors(), + "operations": self.count_ops()} + return summary diff --git a/qiskit/mapper/_mapping.py b/qiskit/mapper/_mapping.py index 0eb157c3ee..896d580c9c 100644 --- a/qiskit/mapper/_mapping.py +++ b/qiskit/mapper/_mapping.py @@ -12,6 +12,14 @@ from qiskit import QISKitException from qiskit.qasm import Qasm import qiskit.unroll as unroll +# Notes: +# Measurements may occur and be followed by swaps that result in repeated +# measurement of the same qubit. Near-term experiments cannot implement +# these circuits, so we may need to modify the algorithm. +# It can happen that a swap in a deeper layer can be removed by permuting +# qubits in the layout. We don't do this. +# It can happen that initial swaps can be removed or partly simplified +# because the initial state is zero. We don't do this. def layer_permutation(layer_partition, layout, qubit_subset, coupling, trials): """Find a swap circuit that implements a permutation for this layer. @@ -19,7 +27,7 @@ def layer_permutation(layer_partition, layout, qubit_subset, coupling, trials): The goal is to swap qubits such that qubits in the same two qubit gates are adjacent. - Based on Sergey Bravyi's MATLAB code. + Based on Sergey Bravyi's algorithm. The layer_partition is a list of (qu)bit lists and each qubit is a tuple (qreg, index). @@ -30,12 +38,13 @@ def layer_permutation(layer_partition, layout, qubit_subset, coupling, trials): The coupling is a CouplingGraph. TRIALS is the number of attempts the randomized algorithm makes. - Returns: success_flag, best_circ, best_d, best_layout + Returns: success_flag, best_circ, best_d, best_layout, trivial_flag If success_flag is True, then best_circ contains an OPENQASM string with the swap circuit, best_d contains the depth of the swap circuit, and best_layout contains the new positions of the data qubits after the - swap circuit has been applied. + swap circuit has been applied. The trivial_flag is set if the layer + has no multi-qubit gates. """ rev_layout = {b: a for a, b in layout.items()} gates = [] @@ -45,6 +54,12 @@ def layer_permutation(layer_partition, layout, qubit_subset, coupling, trials): elif len(layer) == 2: gates.append(tuple(layer)) + # Can we already apply the gates? + dist = sum([coupling.distance(layout[g[0]], + layout[g[1]]) for g in gates]) + if dist == len(gates): + return True, "", 0, layout, len(gates) == 0 + # Begin loop over trials of randomized algorithm n = coupling.size() best_d = sys.maxsize # initialize best depth @@ -54,7 +69,7 @@ def layer_permutation(layer_partition, layout, qubit_subset, coupling, trials): trial_layout = copy.deepcopy(layout) rev_trial_layout = copy.deepcopy(rev_layout) - trial_circ = "" + trial_circ = "" # circuit produced in this trial # Compute Sergey's randomized distance xi = {} @@ -67,8 +82,9 @@ def layer_permutation(layer_partition, layout, qubit_subset, coupling, trials): xi[j][i] = xi[i][j] # Loop over depths d up to a max depth of 2n+1 - for d in range(1, 2 * n + 1): - circ = "" + d = 1 + circ = "" # circuit for this swap slice + while d < 2*n+1: # Set of available qubits qubit_set = set(qubit_subset) # While there are still qubits available @@ -120,9 +136,12 @@ def layer_permutation(layer_partition, layout, qubit_subset, coupling, trials): # If all gates can be applied now, we are finished # Otherwise we need to consider a deeper swap circuit if dist == len(gates): - trial_circ = circ + trial_circ += circ break + # Increment the depth + d += 1 + # Either we have succeeded at some depth d < dmax or failed dist = sum([coupling.distance(trial_layout[g[0]], trial_layout[g[1]]) for g in gates]) @@ -133,9 +152,9 @@ def layer_permutation(layer_partition, layout, qubit_subset, coupling, trials): best_d = min(best_d, d) if best_circ is None: - return False, None, None, None + return False, None, None, None, False else: - return True, best_circ, best_d, best_layout + return True, best_circ, best_d, best_layout, False def direction_mapper(circuit_graph, coupling_graph, verbose=False): @@ -193,7 +212,7 @@ def direction_mapper(circuit_graph, coupling_graph, verbose=False): def swap_mapper(circuit_graph, coupling_graph, initial_layout=None, - basis="cx,u1,u2,u3", verbose=False): + basis="cx,u1,u2,u3,id", verbose=False): """Map a Circuit onto a CouplingGraph using swap gates. circuit_graph = input Circuit @@ -232,7 +251,7 @@ def swap_mapper(circuit_graph, coupling_graph, "in input Circuit" % (k[0], k[1])) if v not in coup_qubits: raise QISKitException("initial_layout qubit %s[%d] not " + - " in input CouplingGraph" % (k[0], k[1])) + " in input CouplingGraph" % (v[0], v[1])) else: # Supply a default layout qubit_subset = coupling_graph.get_qubits() @@ -243,20 +262,27 @@ def swap_mapper(circuit_graph, coupling_graph, # Find swap circuit to preceed to each layer of input circuit layout = copy.deepcopy(initial_layout) openqasm_output = "" - first_layer = True + first_layer = True # True until first layer is output + first_swapping_layer = True # True until first swap layer is output + # Iterate over layers for i in range(len(layerlist)): - success_flag, best_circ, best_d, best_layout \ + # Attempt to find a permutation for this layer + success_flag, best_circ, best_d, best_layout, trivial_flag \ = layer_permutation(layerlist[i]["partition"], layout, qubit_subset, coupling_graph, 20) + # If this fails, try one gate at a time in this layer if not success_flag: if verbose: print("swap_mapper: failed, layer %d, " % i, - " contention? retrying sequentially") + " retrying sequentially") serial_layerlist = layerlist[i]["graph"].serial_layers() + # Go through each gate in the layer for j in range(len(serial_layerlist)): - success_flag, best_circ, best_d, best_layout \ + success_flag, best_circ, best_d, best_layout, trivial_flag \ = layer_permutation(serial_layerlist[j]["partition"], - layout, qubit_subset, coupling_graph, 20) + layout, qubit_subset, coupling_graph, + 20) + # Give up if we fail again if not success_flag: raise QISKitException("swap_mapper failed: " + "layer %d, sublayer %d" % (i, j) + @@ -265,42 +291,77 @@ def swap_mapper(circuit_graph, coupling_graph, no_decls=True, aliases=layout)) else: + # Update the qubit positions each iteration layout = best_layout - if first_layer: - initial_layout = layout - openqasm_output += circuit_graph.qasm(add_swap=True, - decls_only=True, - aliases=layout) - openqasm_output += serial_layerlist[j]["graph"].qasm( - no_decls=True, - aliases=layout) - first_layer = False + if best_d == 0: + # Output qasm without swaps + if first_layer: + openqasm_output += circuit_graph.qasm( + add_swap=True, + decls_only=True, + aliases=layout) + first_layer = False + if not trivial_flag and first_swapping_layer: + initial_layout = layout + first_swapping_layer = False else: - if verbose: - print("swap_mapper: layer %d (%d), depth %d" - % (i, j, best_d)) - if best_circ != "": - openqasm_output += best_circ + # Output qasm with swaps + if first_layer: + openqasm_output += circuit_graph.qasm( + add_swap=True, + decls_only=True, + aliases=layout) + first_layer = False + initial_layout = layout + first_swapping_layer = False + else: + if not first_swapping_layer: + if verbose: + print("swap_mapper: layer %d (%d), depth %d" + % (i, j, best_d)) + openqasm_output += best_circ + else: + initial_layout = layout + first_swapping_layer = False openqasm_output += serial_layerlist[j]["graph"].qasm( - no_decls=True, - aliases=layout) + no_decls=True, + aliases=layout) else: + # Update the qubit positions each iteration layout = best_layout - if first_layer: - initial_layout = layout - openqasm_output += circuit_graph.qasm(add_swap=True, - decls_only=True, - aliases=layout) - openqasm_output += layerlist[i]["graph"].qasm(no_decls=True, - aliases=layout) - first_layer = False + if best_d == 0: + # Output qasm without swaps + if first_layer: + openqasm_output += circuit_graph.qasm( + add_swap=True, + decls_only=True, + aliases=layout) + first_layer = False + if not trivial_flag and first_swapping_layer: + initial_layout = layout + first_swapping_layer = False else: - if verbose: - print("swap_mapper: layer %s, depth %d" % (i, best_d)) - if best_circ != "": - openqasm_output += best_circ - openqasm_output += layerlist[i]["graph"].qasm(no_decls=True, - aliases=layout) + # Output qasm with swaps + if first_layer: + openqasm_output += circuit_graph.qasm( + add_swap=True, + decls_only=True, + aliases=layout) + first_layer = False + initial_layout = layout + first_swapping_layer = False + else: + if not first_swapping_layer: + if verbose: + print("swap_mapper: layer %d, depth %d" + % (i, best_d)) + openqasm_output += best_circ + else: + initial_layout = layout + first_swapping_layer = False + openqasm_output += layerlist[i]["graph"].qasm( + no_decls=True, + aliases=layout) # Parse openqasm_output into Circuit object basis += ",swap" ast = Qasm(data=openqasm_output).parse() diff --git a/qiskit/qasm/_qasmparser.py b/qiskit/qasm/_qasmparser.py index e77278e636..d931fdb0a2 100644 --- a/qiskit/qasm/_qasmparser.py +++ b/qiskit/qasm/_qasmparser.py @@ -475,7 +475,7 @@ class QasmParser(object): ''' if program[2] != '}': raise QasmException("Missing '}' in gate definition; received'" - + str(pprogram[2].value) + "'") + + str(program[2].value) + "'") program[0] = node.GateBody(None) def p_gate_body_1(self, program):