many corrections to compile circuits.

add new test example test16_obj.py.
update teleport_obj and rippleadd_obj examples.
major changes to _circuit and _mapping.
add print statement to compile_circuits() in _quantumprogram.
minor bug in _qasmparser.py.
This commit is contained in:
Andrew Cross 2017-05-12 00:05:07 -04:00
parent 7a3ff0765f
commit 8d1ae2a497
7 changed files with 243 additions and 230 deletions

View File

@ -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

View File

@ -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

View File

@ -0,0 +1,80 @@
"""
Ripple adder example based on OPENQASM example.
Author: Andrew Cross
Jesus Perez <jesusper@us.ibm.com>
"""
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

View File

@ -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

View File

@ -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

View File

@ -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()

View File

@ -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):