def test_execute(self): ast = qasm.Qasm(filename=self._get_resource_path('qasm/example.qasm')).parse() dag_circuit = Unroller(ast, DAGBackend()).execute() dag_unroller = DagUnroller(dag_circuit, DAGBackend()) unroller_dag_circuit = dag_unroller.execute() expected_result = """\ OPENQASM 2.0; qreg q[3]; qreg r[3]; creg c[3]; creg d[3]; U(0.5*pi,0,pi) q[2]; CX q[2],r[2]; measure r[2] -> d[2]; U(0.5*pi,0,pi) q[1]; CX q[1],r[1]; measure r[1] -> d[1]; U(0.5*pi,0,pi) q[0]; CX q[0],r[0]; measure r[0] -> d[0]; barrier q[0],q[1],q[2]; measure q[2] -> c[2]; measure q[1] -> c[1]; measure q[0] -> c[0]; """ self.assertEqual(unroller_dag_circuit.qasm(), expected_result)
def test_from_dag_to_json_with_basis(self): ast = qasm.Qasm(filename=self._get_resource_path('qasm/example.qasm')).parse() dag_circuit = Unroller(ast, DAGBackend(["cx", "u1", "u2", "u3"])).execute() dag_unroller = DagUnroller(dag_circuit, JsonBackend(["cx", "u1", "u2", "u3"])) json_circuit = dag_unroller.execute() expected_result = { 'operations': [ {'qubits': [5], 'texparams': ['0', '\\pi'], 'params': [0.0, 3.141592653589793], 'name': 'u2'}, {'qubits': [5, 2], 'texparams': [], 'params': [], 'name': 'cx'}, {'qubits': [2], 'clbits': [2], 'name': 'measure'}, {'qubits': [4], 'texparams': ['0', '\\pi'], 'params': [0.0, 3.141592653589793], 'name': 'u2'}, {'qubits': [4, 1], 'texparams': [], 'params': [], 'name': 'cx'}, {'qubits': [1], 'clbits': [1], 'name': 'measure'}, {'qubits': [3], 'texparams': ['0', '\\pi'], 'params': [0.0, 3.141592653589793], 'name': 'u2'}, {'qubits': [3, 0], 'texparams': [], 'params': [], 'name': 'cx'}, {'qubits': [3, 4, 5], 'name': 'barrier'}, {'qubits': [5], 'clbits': [5], 'name': 'measure'}, {'qubits': [4], 'clbits': [4], 'name': 'measure'}, {'qubits': [3], 'clbits': [3], 'name': 'measure'}, {'qubits': [0], 'clbits': [0], 'name': 'measure'} ], 'header': { 'clbit_labels': [['d', 3], ['c', 3]], 'number_of_qubits': 6, 'qubit_labels': [['r', 0], ['r', 1], ['r', 2], ['q', 0], ['q', 1], ['q', 2]], 'number_of_clbits': 6 } } self.assertEqual(json_circuit, expected_result)
def test_execute_with_basis(self): ast = qasm.Qasm(filename=self._get_resource_path('qasm/example.qasm')).parse() dag_circuit = Unroller(ast, DAGBackend(["cx", "u1", "u2", "u3"])).execute() dag_unroller = DagUnroller(dag_circuit, DAGBackend(["cx", "u1", "u2", "u3"])) unroller_dag_circuit = dag_unroller.execute() expected_result = """\ OPENQASM 2.0; qreg q[3]; qreg r[3]; creg c[3]; creg d[3]; gate u2(phi,lambda) q { U((pi/2),phi,lambda) q; } gate cx c,t { CX c,t; } u2(0,pi) q[2]; cx q[2],r[2]; measure r[2] -> d[2]; u2(0,pi) q[1]; cx q[1],r[1]; measure r[1] -> d[1]; u2(0,pi) q[0]; cx q[0],r[0]; measure r[0] -> d[0]; barrier q[0],q[1],q[2]; measure q[2] -> c[2]; measure q[1] -> c[1]; measure q[0] -> c[0]; """ self.assertEqual(unroller_dag_circuit.qasm(), expected_result)
def test_from_dag_to_json(self): ast = qasm.Qasm(filename=self._get_resource_path('qasm/example.qasm')).parse() dag_circuit = Unroller(ast, DAGBackend()).execute() dag_unroller = DagUnroller(dag_circuit, JsonBackend()) json_circuit = dag_unroller.execute() expected_result = { 'operations': [ {'qubits': [5], 'texparams': ['0.5 \\pi', '0', '\\pi'], 'name': 'U', 'params': [1.5707963267948966, 0.0, 3.141592653589793]}, {'name': 'CX', 'qubits': [5, 2]}, {'clbits': [2], 'name': 'measure', 'qubits': [2]}, {'qubits': [4], 'texparams': ['0.5 \\pi', '0', '\\pi'], 'name': 'U', 'params': [1.5707963267948966, 0.0, 3.141592653589793]}, {'name': 'CX', 'qubits': [4, 1]}, {'clbits': [1], 'name': 'measure', 'qubits': [1]}, {'qubits': [3], 'texparams': ['0.5 \\pi', '0', '\\pi'], 'name': 'U', 'params': [1.5707963267948966, 0.0, 3.141592653589793]}, {'name': 'CX', 'qubits': [3, 0]}, {'name': 'barrier', 'qubits': [3, 4, 5]}, {'clbits': [5], 'name': 'measure', 'qubits': [5]}, {'clbits': [4], 'name': 'measure', 'qubits': [4]}, {'clbits': [3], 'name': 'measure', 'qubits': [3]}, {'clbits': [0], 'name': 'measure', 'qubits': [0]} ], 'header': { 'number_of_clbits': 6, 'qubit_labels': [['r', 0], ['r', 1], ['r', 2], ['q', 0], ['q', 1], ['q', 2]], 'number_of_qubits': 6, 'clbit_labels': [['d', 3], ['c', 3]] } } self.assertEqual(json_circuit, expected_result)
def test_pass_manager_none(self): """Test passing the default (None) pass manager to the transpiler. It should perform the default qiskit flow: unroll, swap_mapper, direction_mapper, cx cancellation, optimize_1q_gates and should be equivalent to using wrapper.compile """ qr = QuantumRegister(2, 'qr') circuit = QuantumCircuit(qr) circuit.h(qr[0]) circuit.h(qr[0]) circuit.cx(qr[0], qr[1]) circuit.cx(qr[0], qr[1]) circuit.cx(qr[0], qr[1]) circuit.cx(qr[0], qr[1]) coupling_map = [[1, 0]] basis_gates = 'u1,u2,u3,cx,id' dag_circuit = DAGCircuit.fromQuantumCircuit(circuit) dag_circuit = transpile_dag(dag_circuit, coupling_map=coupling_map, basis_gates=basis_gates, pass_manager=None) transpiler_json = DagUnroller(dag_circuit, JsonBackend(dag_circuit.basis)).execute() qobj = compile(circuit, backend=Aer.get_backend('qasm_simulator_py'), coupling_map=coupling_map, basis_gates=basis_gates) compiler_json = qobj.experiments[0].as_dict() # Remove extra Qobj header parameters. compiler_json.pop('config') compiler_json['header'].pop('name') compiler_json['header'].pop('compiled_circuit_qasm') self.assertDictEqual(transpiler_json, compiler_json)
def test_pass_manager_none(self): """Test passing the default (None) pass manager to the transpiler. It should perform the default qiskit flow: unroll, swap_mapper, direction_mapper, cx cancellation, optimize_1q_gates and should be equivalent to using wrapper.compile """ q = QuantumRegister(2) circ = QuantumCircuit(q) circ.h(q[0]) circ.h(q[0]) circ.cx(q[0], q[1]) circ.cx(q[0], q[1]) circ.cx(q[0], q[1]) circ.cx(q[0], q[1]) coupling_map = [[1, 0]] basis_gates = 'u1,u2,u3,cx,id' dag_circuit = DAGCircuit.fromQuantumCircuit(circ) dag_circuit = transpile(dag_circuit, coupling_map=coupling_map, basis_gates=basis_gates, pass_manager=None) transpiler_json = DagUnroller(dag_circuit, JsonBackend( dag_circuit.basis)).execute() qobj = wrapper.compile(circ, backend='local_qasm_simulator', coupling_map=coupling_map, basis_gates=basis_gates) compiler_json = qobj['circuits'][0]['compiled_circuit'] self.assertDictEqual(transpiler_json, compiler_json)
def _dags_2_qobj_parallel(dag, config=None, basis_gates=None, coupling_map=None): """Helper function for dags to qobj in parallel (if available). Args: dag (DAGCircuit): DAG to compile config (dict): dictionary of parameters (e.g. noise) used by runner basis_gates (list[str])): basis gates for the experiment coupling_map (list): coupling map (perhaps custom) to target in mapping Returns: Qobj: Qobj to be run on the backends """ json_circuit = DagUnroller(dag, JsonBackend(dag.basis)).execute() # Step 3a: create the Experiment based on json_circuit experiment = QobjExperiment.from_dict(json_circuit) # Step 3b: populate the Experiment configuration and header experiment.header.name = dag.name # TODO: place in header or config? experiment_config = deepcopy(config or {}) experiment_config.update({ 'coupling_map': coupling_map, 'basis_gates': basis_gates, 'layout': dag.layout, 'memory_slots': sum(dag.cregs.values()), # TODO: `n_qubits` is not part of the qobj spec, but needed for the simulator. 'n_qubits': sum(dag.qregs.values())}) experiment.config = QobjItem(**experiment_config) # set eval_symbols=True to evaluate each symbolic expression # TODO: after transition to qobj, we can drop this experiment.header.compiled_circuit_qasm = dag.qasm( qeflag=True, eval_symbols=True) # Step 3c: add the Experiment to the Qobj return experiment
def test_dag_to_dag_expand_gates_custom_basis(self): """Test DagUnroller.expand_gates() to a gate basis.""" ast = qasm.Qasm( filename=self._get_resource_path('qasm/example.qasm')).parse() dag_circuit = Unroller(ast, DAGBackend()).execute() dag_unroller = DagUnroller(dag_circuit, DAGBackend()) expanded_dag_circuit = dag_unroller.expand_gates( basis=["cx", "u1", "u2", "u3"]) expected_result = """\ OPENQASM 2.0; qreg q[3]; qreg r[3]; creg c[3]; creg d[3]; gate u2(phi,lambda) q { U((pi/2),phi,lambda) q; } gate h a { u2(0,pi) a; } gate cx c,t { CX c,t; } u2(0,pi) q[2]; cx q[2],r[2]; measure r[2] -> d[2]; u2(0,pi) q[1]; cx q[1],r[1]; measure r[1] -> d[1]; u2(0,pi) q[0]; cx q[0],r[0]; barrier q[0],q[1],q[2]; measure q[0] -> c[0]; measure q[1] -> c[1]; measure q[2] -> c[2]; measure r[0] -> d[0]; """ self.assertEqual(expanded_dag_circuit.qasm(), expected_result)
def dag2json(dag_circuit, basis_gates='u1,u2,u3,cx,id'): """Make a Json representation of the circuit. Takes a circuit dag and returns json circuit obj. This is an internal function. Args: dag_circuit (QuantumCircuit): a dag representation of the circuit. basis_gates (str): a comma seperated string and are the base gates, which by default are: u1,u2,u3,cx,id Returns: json: the json version of the dag """ return DagUnroller(dag_circuit, JsonBackend(basis_gates)).execute()
def setUp(self): self.seed = 88 self.backend = QasmSimulatorPy() backend_basis = self.backend.configuration().basis_gates qasm_filename = self._get_resource_path('qasm/example.qasm') qasm_ast = Qasm(filename=qasm_filename).parse() qasm_dag = Unroller(qasm_ast, DAGBackend()).execute() qasm_dag = DagUnroller(qasm_dag, DAGBackend(backend_basis)).expand_gates() qasm_json = DagUnroller(qasm_dag, JsonBackend(qasm_dag.basis)).execute() compiled_circuit = QobjExperiment.from_dict(qasm_json) compiled_circuit.header.name = 'test' self.qobj = Qobj( qobj_id='test_sim_single_shot', config=QobjConfig( shots=1024, memory_slots=6, max_credits=3, seed=self.seed ), experiments=[compiled_circuit], header=QobjHeader(backend_name='qasm_simulator_py') )
def _circuit_to_experiment(circuit, config=None, basis_gates=None, coupling_map=None): """Helper function for dags to qobj in parallel (if available). Args: circuit (QuantumCircuit): QuantumCircuit to convert into qobj experiment config (dict): dictionary of parameters (e.g. noise) used by runner basis_gates (list[str])): basis gates for the experiment coupling_map (list): coupling map (perhaps custom) to target in mapping Returns: Qobj: Qobj to be run on the backends """ # pylint: disable=unused-argument # TODO: if arguments are really unused, consider changing the signature # TODO: removed the DAG from this function from qiskit.converters import circuit_to_dag from qiskit.unroll import DagUnroller, JsonBackend dag = circuit_to_dag(circuit) json_circuit = DagUnroller(dag, JsonBackend(dag.basis)).execute() # Step 3a: create the Experiment based on json_circuit experiment = QobjExperiment.from_dict(json_circuit) # Step 3b: populate the Experiment configuration and header experiment.header.name = circuit.name experiment_config = deepcopy(config or {}) experiment_config.update({ 'memory_slots': sum([creg.size for creg in dag.cregs.values()]), 'n_qubits': sum([qreg.size for qreg in dag.qregs.values()]) }) experiment.config = QobjItem(**experiment_config) # set eval_symbols=True to evaluate each symbolic expression # TODO: after transition to qobj, we can drop this experiment.header.compiled_circuit_qasm = circuit.qasm() # Step 3c: add the Experiment to the Qobj return experiment
def optimize_1q_gates(circuit): """Simplify runs of single qubit gates in the QX basis. Return a new circuit that has been optimized. """ qx_basis = ["u1", "u2", "u3", "cx", "id"] dag_unroller = DagUnroller(circuit, DAGBackend(qx_basis)) unrolled = dag_unroller.expand_gates() runs = unrolled.collect_runs(["u1", "u2", "u3", "id"]) for run in runs: qname = unrolled.multi_graph.node[run[0]]["qargs"][0] right_name = "u1" right_parameters = (N(0), N(0), N(0)) # (theta, phi, lambda) for current_node in run: nd = unrolled.multi_graph.node[current_node] assert nd["condition"] is None, "internal error" assert len(nd["qargs"]) == 1, "internal error" assert nd["qargs"][0] == qname, "internal error" left_name = nd["name"] assert left_name in ["u1", "u2", "u3", "id"], "internal error" if left_name == "u1": left_parameters = (N(0), N(0), nd["params"][0]) elif left_name == "u2": left_parameters = (sympy.pi / 2, nd["params"][0], nd["params"][1]) elif left_name == "u3": left_parameters = tuple(nd["params"]) else: left_name = "u1" # replace id with u1 left_parameters = (N(0), N(0), N(0)) # Compose gates name_tuple = (left_name, right_name) if name_tuple == ("u1", "u1"): # u1(lambda1) * u1(lambda2) = u1(lambda1 + lambda2) right_parameters = (N(0), N(0), right_parameters[2] + left_parameters[2]) elif name_tuple == ("u1", "u2"): # u1(lambda1) * u2(phi2, lambda2) = u2(phi2 + lambda1, lambda2) right_parameters = (sympy.pi / 2, right_parameters[1] + left_parameters[2], right_parameters[2]) elif name_tuple == ("u2", "u1"): # u2(phi1, lambda1) * u1(lambda2) = u2(phi1, lambda1 + lambda2) right_name = "u2" right_parameters = (sympy.pi / 2, left_parameters[1], right_parameters[2] + left_parameters[2]) elif name_tuple == ("u1", "u3"): # u1(lambda1) * u3(theta2, phi2, lambda2) = # u3(theta2, phi2 + lambda1, lambda2) right_parameters = (right_parameters[0], right_parameters[1] + left_parameters[2], right_parameters[2]) elif name_tuple == ("u3", "u1"): # u3(theta1, phi1, lambda1) * u1(lambda2) = # u3(theta1, phi1, lambda1 + lambda2) right_name = "u3" right_parameters = (left_parameters[0], left_parameters[1], right_parameters[2] + left_parameters[2]) elif name_tuple == ("u2", "u2"): # Using Ry(pi/2).Rz(2*lambda).Ry(pi/2) = # Rz(pi/2).Ry(pi-2*lambda).Rz(pi/2), # u2(phi1, lambda1) * u2(phi2, lambda2) = # u3(pi - lambda1 - phi2, phi1 + pi/2, lambda2 + pi/2) right_name = "u3" right_parameters = (sympy.pi - left_parameters[2] - right_parameters[1], left_parameters[1] + sympy.pi / 2, right_parameters[2] + sympy.pi / 2) elif name_tuple[1] == "nop": right_name = left_name right_parameters = left_parameters else: # For composing u3's or u2's with u3's, use # u2(phi, lambda) = u3(pi/2, phi, lambda) # together with the qiskit.mapper.compose_u3 method. right_name = "u3" # Evaluate the symbolic expressions for efficiency left_parameters = tuple(map(lambda x: x.evalf(), list(left_parameters))) right_parameters = tuple(map(lambda x: x.evalf(), list(right_parameters))) right_parameters = compose_u3(left_parameters[0], left_parameters[1], left_parameters[2], right_parameters[0], right_parameters[1], right_parameters[2]) # Why evalf()? This program: # OPENQASM 2.0; # include "qelib1.inc"; # qreg q[2]; # creg c[2]; # u3(0.518016983430947*pi,1.37051598592907*pi,1.36816383603222*pi) q[0]; # u3(1.69867232277986*pi,0.371448347747471*pi,0.461117217930936*pi) q[0]; # u3(0.294319836336836*pi,0.450325871124225*pi,1.46804720442555*pi) q[0]; # measure q -> c; # took >630 seconds (did not complete) to optimize without # calling evalf() at all, 19 seconds to optimize calling # evalf() AFTER compose_u3, and 1 second to optimize # calling evalf() BEFORE compose_u3. # 1. Here down, when we simplify, we add f(theta) to lambda to # correct the global phase when f(theta) is 2*pi. This isn't # necessary but the other steps preserve the global phase, so # we continue in that manner. # 2. The final step will remove Z rotations by 2*pi. # 3. Note that is_zero is true only if the expression is exactly # zero. If the input expressions have already been evaluated # then these final simplifications will not occur. # TODO After we refactor, we should have separate passes for # exact and approximate rewriting. # Y rotation is 0 mod 2*pi, so the gate is a u1 if (right_parameters[0] % (2 * sympy.pi)).is_zero \ and right_name != "u1": right_name = "u1" right_parameters = (0, 0, right_parameters[1] + right_parameters[2] + right_parameters[0]) # Y rotation is pi/2 or -pi/2 mod 2*pi, so the gate is a u2 if right_name == "u3": # theta = pi/2 + 2*k*pi if ((right_parameters[0] - sympy.pi / 2) % (2 * sympy.pi)).is_zero: right_name = "u2" right_parameters = (sympy.pi / 2, right_parameters[1], right_parameters[2] + (right_parameters[0] - sympy.pi / 2)) # theta = -pi/2 + 2*k*pi if ((right_parameters[0] + sympy.pi / 2) % (2 * sympy.pi)).is_zero: right_name = "u2" right_parameters = (sympy.pi / 2, right_parameters[1] + sympy.pi, right_parameters[2] - sympy.pi + (right_parameters[0] + sympy.pi / 2)) # u1 and lambda is 0 mod 2*pi so gate is nop (up to a global phase) if right_name == "u1" and (right_parameters[2] % (2 * sympy.pi)).is_zero: right_name = "nop" # Simplify the symbolic parameters right_parameters = tuple(map(sympy.simplify, list(right_parameters))) # Replace the data of the first node in the run new_params = [] if right_name == "u1": new_params = [right_parameters[2]] if right_name == "u2": new_params = [right_parameters[1], right_parameters[2]] if right_name == "u3": new_params = list(right_parameters) nx.set_node_attributes(unrolled.multi_graph, name='name', values={run[0]: right_name}) # params is a list of sympy symbols nx.set_node_attributes(unrolled.multi_graph, name='params', values={run[0]: new_params}) # Delete the other nodes in the run for current_node in run[1:]: unrolled._remove_op_node(current_node) if right_name == "nop": unrolled._remove_op_node(run[0]) return unrolled
def swap_mapper(circuit_graph, coupling_graph, initial_layout=None, basis="cx,u1,u2,u3,id", trials=20, seed=None): """Map a DAGCircuit onto a CouplingGraph using swap gates. Args: circuit_graph (DAGCircuit): input DAG circuit coupling_graph (CouplingGraph): coupling graph to map onto initial_layout (dict): dict from qubits of circuit_graph to qubits of coupling_graph (optional) basis (str): basis string specifying basis of output DAGCircuit trials (int): number of trials. seed (int): initial seed. Returns: DAGCircuit: object containing a circuit equivalent to circuit_graph that respects couplings in coupling_graph, and a layout dict mapping qubits of circuit_graph into qubits of coupling_graph. The layout may differ from the initial_layout if the first layer of gates cannot be executed on the initial_layout. Finally, returned is the final layer qubit permutation that is needed to add measurements back in. Raises: MapperError: if there was any error during the mapping or with the parameters. """ if circuit_graph.width() > coupling_graph.size(): raise MapperError("Not enough qubits in CouplingGraph") # Schedule the input circuit layerlist = list(circuit_graph.layers()) logger.debug("schedule:") for i, v in enumerate(layerlist): logger.debug(" %d: %s", i, v["partition"]) if initial_layout is not None: # Check the input layout circ_qubits = circuit_graph.get_qubits() coup_qubits = coupling_graph.get_qubits() qubit_subset = [] for k, v in initial_layout.items(): qubit_subset.append(v) if k not in circ_qubits: raise MapperError("initial_layout qubit %s[%d] not in input " "DAGCircuit" % (k[0], k[1])) if v not in coup_qubits: raise MapperError("initial_layout qubit %s[%d] not in input " "CouplingGraph" % (v[0], v[1])) else: # Supply a default layout qubit_subset = coupling_graph.get_qubits() qubit_subset = qubit_subset[0:circuit_graph.width()] initial_layout = {a: b for a, b in zip(circuit_graph.get_qubits(), qubit_subset)} # Find swap circuit to preceed to each layer of input circuit layout = initial_layout.copy() layout_max_index = max(map(lambda x: x[1]+1, layout.values())) # Construct an empty DAGCircuit with one qreg "q" # and the same set of cregs as the input circuit dagcircuit_output = DAGCircuit() dagcircuit_output.add_qreg("q", layout_max_index) for name, size in circuit_graph.cregs.items(): dagcircuit_output.add_creg(name, size) # Make a trivial wire mapping between the subcircuits # returned by swap_mapper_layer_update and the circuit # we are building identity_wire_map = {} for j in range(layout_max_index): identity_wire_map[("q", j)] = ("q", j) for name, size in circuit_graph.cregs.items(): for j in range(size): identity_wire_map[(name, j)] = (name, j) first_layer = True # True until first layer is output logger.debug("initial_layout = %s", layout) # Iterate over layers for i, layer in enumerate(layerlist): # Attempt to find a permutation for this layer success_flag, best_circ, best_d, best_layout, trivial_flag \ = layer_permutation(layer["partition"], layout, qubit_subset, coupling_graph, trials, seed) logger.debug("swap_mapper: layer %d", i) logger.debug("swap_mapper: success_flag=%s,best_d=%s,trivial_flag=%s", success_flag, str(best_d), trivial_flag) # If this fails, try one gate at a time in this layer if not success_flag: logger.debug("swap_mapper: failed, layer %d, " "retrying sequentially", i) serial_layerlist = list(layer["graph"].serial_layers()) # Go through each gate in the layer for j, serial_layer in enumerate(serial_layerlist): success_flag, best_circ, best_d, best_layout, trivial_flag \ = layer_permutation(serial_layer["partition"], layout, qubit_subset, coupling_graph, trials, seed) logger.debug("swap_mapper: layer %d, sublayer %d", i, j) logger.debug("swap_mapper: success_flag=%s,best_d=%s," "trivial_flag=%s", success_flag, str(best_d), trivial_flag) # Give up if we fail again if not success_flag: raise MapperError("swap_mapper failed: " + "layer %d, sublayer %d" % (i, j) + ", \"%s\"" % serial_layer["graph"].qasm( no_decls=True, aliases=layout)) # If this layer is only single-qubit gates, # and we have yet to see multi-qubit gates, # continue to the next inner iteration if trivial_flag and first_layer: logger.debug("swap_mapper: skip to next sublayer") continue # Update the record of qubit positions for each inner iteration layout = best_layout # Update the QASM dagcircuit_output.compose_back( swap_mapper_layer_update(j, first_layer, best_layout, best_d, best_circ, serial_layerlist), identity_wire_map) # Update initial layout if first_layer: initial_layout = layout first_layer = False else: # Update the record of qubit positions for each iteration layout = best_layout # Update the QASM dagcircuit_output.compose_back( swap_mapper_layer_update(i, first_layer, best_layout, best_d, best_circ, layerlist), identity_wire_map) # Update initial layout if first_layer: initial_layout = layout first_layer = False # This is the final layout that we need to correctly replace # any measurements that needed to be removed before the swap last_layout = layout # If first_layer is still set, the circuit only has single-qubit gates # so we can use the initial layout to output the entire circuit if first_layer: layout = initial_layout for i, layer in enumerate(layerlist): dagcircuit_output.compose_back(layer["graph"], layout) # Parse openqasm_output into DAGCircuit object dag_unrrolled = DagUnroller(dagcircuit_output, DAGBackend(basis.split(","))) dagcircuit_output = dag_unrrolled.expand_gates() return dagcircuit_output, initial_layout, last_layout
def transpile(dag_circuit, basis_gates='u1,u2,u3,cx,id', coupling_map=None, initial_layout=None, get_layout=False, format='dag', seed=None, pass_manager=None): """Transform a dag circuit into another dag circuit (transpile), through consecutive passes on the dag. Args: dag_circuit (DAGCircuit): dag circuit to transform via transpilation basis_gates (str): a comma seperated string for the target basis gates coupling_map (list): A graph of coupling:: [ [control0(int), target0(int)], [control1(int), target1(int)], ] eg. [[0, 2], [1, 2], [1, 3], [3, 4]} initial_layout (dict): A mapping of qubit to qubit:: { ("q", start(int)): ("q", final(int)), ... } eg. { ("q", 0): ("q", 0), ("q", 1): ("q", 1), ("q", 2): ("q", 2), ("q", 3): ("q", 3) } get_layout (bool): flag for returning the layout format (str): The target format of the compilation: {'dag', 'json', 'qasm'} seed (int): random seed for simulators pass_manager (PassManager): pass manager instance for the tranpilation process If None, a default set of passes are run. Otherwise, the passes defined in it will run. If contains no passes in it, no dag transformations occur. Returns: object: If get_layout == False, the compiled circuit in the specified format. If get_layout == True, a tuple is returned, with the second element being the layout. Raises: TranspilerError: if the format is not valid. """ final_layout = None if pass_manager: # run the passes specified by the pass manager for pass_ in pass_manager.passes(): pass_.run(dag_circuit) else: # default set of passes # TODO: move each step here to a pass, and use a default passmanager below basis = basis_gates.split(',') if basis_gates else [] dag_unroller = DagUnroller(dag_circuit, DAGBackend(basis)) dag_circuit = dag_unroller.expand_gates() # if a coupling map is given compile to the map if coupling_map: logger.info("pre-mapping properties: %s", dag_circuit.property_summary()) # Insert swap gates coupling = Coupling(coupling_list2dict(coupling_map)) logger.info("initial layout: %s", initial_layout) dag_circuit, final_layout = swap_mapper(dag_circuit, coupling, initial_layout, trials=20, seed=seed) logger.info("final layout: %s", final_layout) # Expand swaps dag_unroller = DagUnroller(dag_circuit, DAGBackend(basis)) dag_circuit = dag_unroller.expand_gates() # Change cx directions dag_circuit = direction_mapper(dag_circuit, coupling) # Simplify cx gates cx_cancellation(dag_circuit) # Simplify single qubit gates dag_circuit = optimize_1q_gates(dag_circuit) logger.info("post-mapping properties: %s", dag_circuit.property_summary()) # choose output format # TODO: do we need all of these formats, or just the dag? if format == 'dag': compiled_circuit = dag_circuit elif format == 'json': # FIXME: JsonBackend is wrongly taking an ordered dict as basis, not list dag_unroller = DagUnroller(dag_circuit, JsonBackend(dag_circuit.basis)) compiled_circuit = dag_unroller.execute() elif format == 'qasm': compiled_circuit = dag_circuit.qasm() else: raise TranspilerError('unrecognized circuit format') if get_layout: return compiled_circuit, final_layout return compiled_circuit
def _compile_single_circuit(circuit, backend, config=None, basis_gates=None, coupling_map=None, initial_layout=None, seed=None, pass_manager=None): """Compile a single circuit into a QobjExperiment. Args: circuit (QuantumCircuit): circuit to compile backend (BaseBackend): a backend to compile for config (dict): dictionary of parameters (e.g. noise) used by runner basis_gates (str): comma-separated basis gate set to compile to coupling_map (list): coupling map (perhaps custom) to target in mapping initial_layout (list): initial layout of qubits in mapping seed (int): random seed for simulators pass_manager (PassManager): a pass_manager for the transpiler stage Returns: QobjExperiment: the QobjExperiment to be run on the backends """ # TODO: A better solution is to have options to enable/disable optimizations num_qubits = sum((len(qreg) for qreg in circuit.get_qregs().values())) if num_qubits == 1 or coupling_map == "all-to-all": coupling_map = None # Step 2a: circuit -> dag dag_circuit = DAGCircuit.fromQuantumCircuit(circuit) # TODO: move this inside the mapper pass # pick a good initial layout if coupling_map is not already satisfied # otherwise keep it as q[i]->q[i] if (initial_layout is None and not backend.configuration['simulator'] and not _matches_coupling_map(circuit.data, coupling_map)): initial_layout = _pick_best_layout(backend, num_qubits, circuit.get_qregs()) # Step 2b: transpile (dag -> dag) dag_circuit, final_layout = transpile(dag_circuit, basis_gates=basis_gates, coupling_map=coupling_map, initial_layout=initial_layout, get_layout=True, seed=seed, pass_manager=pass_manager) # Step 2c: dag -> json # the compiled circuit to be run saved as a dag # we assume that transpile() has already expanded gates # to the target basis, so we just need to generate json list_layout = [[k, v] for k, v in final_layout.items()] if final_layout else None json_circuit = DagUnroller(dag_circuit, JsonBackend(dag_circuit.basis)).execute() # Step 3a: create the Experiment based on json_circuit experiment = QobjExperiment.from_dict(json_circuit) # Step 3b: populate the Experiment configuration and header experiment.header.name = circuit.name # TODO: place in header or config? experiment_config = deepcopy(config or {}) experiment_config.update({ 'coupling_map': coupling_map, 'basis_gates': basis_gates, 'layout': list_layout, 'memory_slots': sum(register.size for register in circuit.get_cregs().values()) }) experiment.config = QobjItem(**experiment_config) # set eval_symbols=True to evaluate each symbolic expression # TODO after transition to qobj, we can drop this experiment.header.compiled_circuit_qasm = dag_circuit.qasm( qeflag=True, eval_symbols=True) return experiment
def swap_mapper(circuit_graph, coupling_graph, initial_layout=None, basis="cx,u1,u2,u3,id", trials=20, seed=None): """Map a DAGCircuit onto a CouplingGraph using swap gates. Args: circuit_graph (DAGCircuit): input DAG circuit coupling_graph (CouplingGraph): coupling graph to map onto initial_layout (dict): dict from qubits of circuit_graph to qubits of coupling_graph (optional) basis (str): basis string specifying basis of output DAGCircuit trials (int): number of trials. seed (int): initial seed. Returns: DAGCircuit: object containing a circuit equivalent to circuit_graph that respects couplings in coupling_graph, and a layout dict mapping qubits of circuit_graph into qubits of coupling_graph. The layout may differ from the initial_layout if the first layer of gates cannot be executed on the initial_layout. Raises: MapperError: if there was any error during the mapping or with the parameters. """ if circuit_graph.width() > coupling_graph.size(): raise MapperError("Not enough qubits in CouplingGraph") # Schedule the input circuit layerlist = list(circuit_graph.layers()) logger.debug("schedule:") for i, v in enumerate(layerlist): logger.debug(" %d: %s", i, v["partition"]) if initial_layout is not None: # Check the input layout circ_qubits = circuit_graph.get_qubits() coup_qubits = coupling_graph.get_qubits() qubit_subset = [] for k, v in initial_layout.items(): qubit_subset.append(v) if k not in circ_qubits: raise MapperError("initial_layout qubit %s[%d] not in input " "DAGCircuit" % (k[0], k[1])) if v not in coup_qubits: raise MapperError("initial_layout qubit %s[%d] not in input " "CouplingGraph" % (v[0], v[1])) else: # Supply a default layout qubit_subset = coupling_graph.get_qubits() qubit_subset = qubit_subset[0:circuit_graph.width()] initial_layout = {a: b for a, b in zip(circuit_graph.get_qubits(), qubit_subset)} # Find swap circuit to preceed to each layer of input circuit layout = initial_layout.copy() layout_max_index = max(map(lambda x: x[1]+1, layout.values())) # Construct an empty DAGCircuit with one qreg "q" # and the same set of cregs as the input circuit dagcircuit_output = DAGCircuit() dagcircuit_output.add_qreg("q", layout_max_index) for name, size in circuit_graph.cregs.items(): dagcircuit_output.add_creg(name, size) # Make a trivial wire mapping between the subcircuits # returned by swap_mapper_layer_update and the circuit # we are building identity_wire_map = {} for j in range(layout_max_index): identity_wire_map[("q", j)] = ("q", j) for name, size in circuit_graph.cregs.items(): for j in range(size): identity_wire_map[(name, j)] = (name, j) first_layer = True # True until first layer is output logger.debug("initial_layout = %s", layout) # Iterate over layers for i, layer in enumerate(layerlist): # Attempt to find a permutation for this layer success_flag, best_circ, best_d, best_layout, trivial_flag \ = layer_permutation(layer["partition"], layout, qubit_subset, coupling_graph, trials, seed) logger.debug("swap_mapper: layer %d", i) logger.debug("swap_mapper: success_flag=%s,best_d=%s,trivial_flag=%s", success_flag, str(best_d), trivial_flag) # If this fails, try one gate at a time in this layer if not success_flag: logger.debug("swap_mapper: failed, layer %d, " "retrying sequentially", i) serial_layerlist = list(layer["graph"].serial_layers()) # Go through each gate in the layer for j, serial_layer in enumerate(serial_layerlist): success_flag, best_circ, best_d, best_layout, trivial_flag \ = layer_permutation(serial_layer["partition"], layout, qubit_subset, coupling_graph, trials, seed) logger.debug("swap_mapper: layer %d, sublayer %d", i, j) logger.debug("swap_mapper: success_flag=%s,best_d=%s," "trivial_flag=%s", success_flag, str(best_d), trivial_flag) # Give up if we fail again if not success_flag: raise MapperError("swap_mapper failed: " + "layer %d, sublayer %d" % (i, j) + ", \"%s\"" % serial_layer["graph"].qasm( no_decls=True, aliases=layout)) # If this layer is only single-qubit gates, # and we have yet to see multi-qubit gates, # continue to the next inner iteration if trivial_flag and first_layer: logger.debug("swap_mapper: skip to next sublayer") continue # Update the record of qubit positions for each inner iteration layout = best_layout # Update the QASM dagcircuit_output.compose_back( swap_mapper_layer_update(j, first_layer, best_layout, best_d, best_circ, serial_layerlist), identity_wire_map) # Update initial layout if first_layer: initial_layout = layout first_layer = False else: # Update the record of qubit positions for each iteration layout = best_layout # Update the QASM dagcircuit_output.compose_back( swap_mapper_layer_update(i, first_layer, best_layout, best_d, best_circ, layerlist), identity_wire_map) # Update initial layout if first_layer: initial_layout = layout first_layer = False # If first_layer is still set, the circuit only has single-qubit gates # so we can use the initial layout to output the entire circuit if first_layer: layout = initial_layout for i, layer in enumerate(layerlist): dagcircuit_output.compose_back(layer["graph"], layout) # Parse openqasm_output into DAGCircuit object dag_unrrolled = DagUnroller(dagcircuit_output, DAGBackend(basis.split(","))) dagcircuit_output = dag_unrrolled.expand_gates() return dagcircuit_output, initial_layout
def test_from_dag_to_json(self): ast = qasm.Qasm( filename=self._get_resource_path('qasm/example.qasm')).parse() dag_circuit = Unroller(ast, DAGBackend()).execute() dag_unroller = DagUnroller(dag_circuit, JsonBackend()) json_circuit = dag_unroller.execute() expected_result = { 'operations': [{ 'qubits': [5], 'texparams': ['0.5 \\pi', '0', '\\pi'], 'name': 'U', 'params': [1.5707963267948966, 0.0, 3.141592653589793] }, { 'name': 'CX', 'qubits': [5, 2] }, { 'clbits': [2], 'name': 'measure', 'qubits': [2] }, { 'qubits': [4], 'texparams': ['0.5 \\pi', '0', '\\pi'], 'name': 'U', 'params': [1.5707963267948966, 0.0, 3.141592653589793] }, { 'name': 'CX', 'qubits': [4, 1] }, { 'clbits': [1], 'name': 'measure', 'qubits': [1] }, { 'qubits': [3], 'texparams': ['0.5 \\pi', '0', '\\pi'], 'name': 'U', 'params': [1.5707963267948966, 0.0, 3.141592653589793] }, { 'name': 'CX', 'qubits': [3, 0] }, { 'name': 'barrier', 'qubits': [3, 4, 5] }, { 'clbits': [5], 'name': 'measure', 'qubits': [5] }, { 'clbits': [4], 'name': 'measure', 'qubits': [4] }, { 'clbits': [3], 'name': 'measure', 'qubits': [3] }, { 'clbits': [0], 'name': 'measure', 'qubits': [0] }], 'header': { 'number_of_clbits': 6, 'qubit_labels': [['r', 0], ['r', 1], ['r', 2], ['q', 0], ['q', 1], ['q', 2]], 'number_of_qubits': 6, 'clbit_labels': [['d', 3], ['c', 3]] } } self.assertEqual(json_circuit, expected_result)
def compile(quantum_circuit, basis_gates='u1,u2,u3,cx,id', coupling_map=None, initial_layout=None, get_layout=False, format='dag'): """Compile the circuit. This builds the internal "to execute" list which is list of quantum circuits to run on different backends. Args: quantum_circuit (QuantumCircuit): circuit to compile basis_gates (str): a comma seperated string and are the base gates, which by default are: u1,u2,u3,cx,id coupling_map (list): A graph of coupling:: [ [control0(int), target0(int)], [control1(int), target1(int)], ] eg. [[0, 2], [1, 2], [1, 3], [3, 4]} initial_layout (dict): A mapping of qubit to qubit:: { ("q", start(int)): ("q", final(int)), ... } eg. { ("q", 0): ("q", 0), ("q", 1): ("q", 1), ("q", 2): ("q", 2), ("q", 3): ("q", 3) } get_layout (bool): flag for returning the layout. format (str): The target format of the compilation: {'dag', 'json', 'qasm'} Returns: object: If get_layout == False, the compiled circuit in the specified format. If get_layout == True, a tuple is returned, with the second element being the layout. Raises: QISKitCompilerError: if the format is not valid. """ compiled_dag_circuit = DAGCircuit.fromQuantumCircuit(quantum_circuit) basis = basis_gates.split(',') if basis_gates else [] dag_unroller = DagUnroller(compiled_dag_circuit, DAGBackend(basis)) compiled_dag_circuit = dag_unroller.expand_gates() final_layout = None # if a coupling map is given compile to the map if coupling_map: logger.info("pre-mapping properties: %s", compiled_dag_circuit.property_summary()) # Insert swap gates coupling = mapper.Coupling(mapper.coupling_list2dict(coupling_map)) logger.info("initial layout: %s", initial_layout) compiled_dag_circuit, final_layout = mapper.swap_mapper( compiled_dag_circuit, coupling, initial_layout, trials=20, seed=13) logger.info("final layout: %s", final_layout) # Expand swaps dag_unroller = DagUnroller(compiled_dag_circuit, DAGBackend(basis)) compiled_dag_circuit = dag_unroller.expand_gates() # Change cx directions compiled_dag_circuit = mapper.direction_mapper(compiled_dag_circuit, coupling) # Simplify cx gates mapper.cx_cancellation(compiled_dag_circuit) # Simplify single qubit gates compiled_dag_circuit = mapper.optimize_1q_gates(compiled_dag_circuit) logger.info("post-mapping properties: %s", compiled_dag_circuit.property_summary()) # choose output format if format == 'dag': compiled_circuit = compiled_dag_circuit elif format == 'json': dag_unroller = DagUnroller(compiled_dag_circuit, JsonBackend(list(compiled_dag_circuit.basis.keys()))) compiled_circuit = dag_unroller.execute() elif format == 'qasm': compiled_circuit = compiled_dag_circuit.qasm() else: raise QISKitCompilerError('unrecognized circuit format') if get_layout: return compiled_circuit, final_layout return compiled_circuit
def compile(circuits, backend, config=None, basis_gates=None, coupling_map=None, initial_layout=None, shots=1024, max_credits=10, seed=None, qobj_id=None, hpc=None, pass_manager=None): """Compile a list of circuits into a qobj. Args: circuits (QuantumCircuit or list[QuantumCircuit]): circuits to compile backend (BaseBackend): a backend to compile for config (dict): dictionary of parameters (e.g. noise) used by runner basis_gates (str): comma-separated basis gate set to compile to coupling_map (list): coupling map (perhaps custom) to target in mapping initial_layout (list): initial layout of qubits in mapping shots (int): number of repetitions of each circuit, for sampling max_credits (int): maximum credits to use seed (int): random seed for simulators qobj_id (int): identifier for the generated qobj hpc (dict): HPC simulator parameters pass_manager (PassManager): a pass_manager for the transpiler stage Returns: obj: the qobj to be run on the backends Raises: TranspilerError: in case of bad compile options, e.g. the hpc options. """ if isinstance(circuits, QuantumCircuit): circuits = [circuits] backend_conf = backend.configuration backend_name = backend_conf['name'] qobj = {} # step 1: populate the qobj-level `id` qobj_id = qobj_id or str(uuid.uuid4()) qobj['id'] = qobj_id # step 2: populate the qobj-level `config` qobj['config'] = { 'max_credits': max_credits, 'shots': shots, 'backend_name': backend_name } if hpc is not None and \ not all(key in hpc for key in ('multi_shot_optimization', 'omp_num_threads')): raise TranspilerError('Unknown HPC parameter format!') # step 3: populate the `circuits` in qobj, after compiling each circuit qobj['circuits'] = [] if not basis_gates: basis_gates = backend_conf['basis_gates'] if not coupling_map: coupling_map = backend_conf['coupling_map'] for circuit in circuits: job = {} # step 1: populate the circuit-level `name` job["name"] = circuit.name # step 2: populate the circuit-level `config` if config is None: config = {} job["config"] = copy.deepcopy(config) # TODO: A better solution is to have options to enable/disable optimizations num_qubits = sum((len(qreg) for qreg in circuit.get_qregs().values())) if num_qubits == 1 or coupling_map == "all-to-all": coupling_map = None job["config"]["coupling_map"] = coupling_map job["config"]["basis_gates"] = basis_gates job["config"]["seed"] = seed # step 3: populate the circuit `instructions` after compilation # step 3a: circuit -> dag dag_circuit = DAGCircuit.fromQuantumCircuit(circuit) # TODO: move this inside the mapper pass # pick a good initial layout if coupling_map is not already satisfied # otherwise keep it as q[i]->q[i] if (initial_layout is None and not backend_conf['simulator'] and not _matches_coupling_map(circuit.data, coupling_map)): initial_layout = _pick_best_layout(backend, num_qubits, circuit.get_qregs()) # step 3b: transpile (dag -> dag) dag_circuit, final_layout = transpile(dag_circuit, basis_gates=basis_gates, coupling_map=coupling_map, initial_layout=initial_layout, get_layout=True, seed=seed, pass_manager=pass_manager) # step 3c: dag -> json # TODO: populate the Qobj object when Qobj class exists # the compiled circuit to be run saved as a dag # we assume that transpile() has already expanded gates # to the target basis, so we just need to generate json list_layout = [[k, v] for k, v in final_layout.items() ] if final_layout else None job["config"]["layout"] = list_layout json_circuit = DagUnroller(dag_circuit, JsonBackend(dag_circuit.basis)).execute() job["compiled_circuit"] = json_circuit # set eval_symbols=True to evaluate each symbolic expression # TODO after transition to qobj, we can drop this job["compiled_circuit_qasm"] = dag_circuit.qasm(qeflag=True, eval_symbols=True) # add job to the qobj qobj["circuits"].append(job) return qobj
def transpile(dag, basis_gates='u1,u2,u3,cx,id', coupling_map=None, initial_layout=None, get_layout=False, format='dag', seed=None, pass_manager=None): """Transform a dag circuit into another dag circuit (transpile), through consecutive passes on the dag. Args: dag (DAGCircuit): dag circuit to transform via transpilation basis_gates (str): a comma separated string for the target basis gates coupling_map (list): A graph of coupling:: [ [control0(int), target0(int)], [control1(int), target1(int)], ] eg. [[0, 2], [1, 2], [1, 3], [3, 4]} initial_layout (dict): A mapping of qubit to qubit:: { ("q", start(int)): ("q", final(int)), ... } eg. { ("q", 0): ("q", 0), ("q", 1): ("q", 1), ("q", 2): ("q", 2), ("q", 3): ("q", 3) } get_layout (bool): flag for returning the final layout after mapping format (str): The target format of the compilation: {'dag', 'json', 'qasm'} seed (int): random seed for the swap mapper pass_manager (PassManager): pass manager instance for the transpilation process If None, a default set of passes are run. Otherwise, the passes defined in it will run. If contains no passes in it, no dag transformations occur. Returns: DAGCircuit: transformed dag DAGCircuit, dict: transformed dag along with the final layout on backend qubits Raises: TranspilerError: if the format is not valid. """ # TODO: `basis_gates` will be removed after we have the unroller pass. # TODO: `coupling_map`, `initial_layout`, `get_layout`, `seed` removed after mapper pass. # TODO: move this to the mapper pass num_qubits = sum(dag.qregs.values()) if num_qubits == 1 or coupling_map == "all-to-all": coupling_map = None final_layout = None if pass_manager: # run the passes specified by the pass manager # TODO return the property set too. See #1086 dag = pass_manager.run_passes(dag) else: # default set of passes # TODO: move each step here to a pass, and use a default passmanager below basis = basis_gates.split(',') if basis_gates else [] dag_unroller = DagUnroller(dag, DAGBackend(basis)) dag = dag_unroller.expand_gates() # if a coupling map is given compile to the map if coupling_map: logger.info("pre-mapping properties: %s", dag.property_summary()) # Insert swap gates coupling = Coupling(coupling_list2dict(coupling_map)) removed_meas = remove_last_measurements(dag) logger.info("measurements moved: %s", removed_meas) logger.info("initial layout: %s", initial_layout) dag, final_layout, last_layout = swap_mapper(dag, coupling, initial_layout, trials=20, seed=seed) logger.info("final layout: %s", final_layout) # Expand swaps dag_unroller = DagUnroller(dag, DAGBackend(basis)) dag = dag_unroller.expand_gates() # Change cx directions dag = direction_mapper(dag, coupling) # Simplify cx gates cx_cancellation(dag) # Simplify single qubit gates dag = optimize_1q_gates(dag) return_last_measurements(dag, removed_meas, last_layout) logger.info("post-mapping properties: %s", dag.property_summary()) # choose output format # TODO: do we need all of these formats, or just the dag? if format == 'dag': compiled_circuit = dag elif format == 'json': # FIXME: JsonBackend is wrongly taking an ordered dict as basis, not list dag_unroller = DagUnroller(dag, JsonBackend(dag.basis)) compiled_circuit = dag_unroller.execute() elif format == 'qasm': compiled_circuit = dag.qasm() else: raise TranspilerError('unrecognized circuit format') if get_layout: return compiled_circuit, final_layout return compiled_circuit
def compile(circuits, backend, config=None, basis_gates=None, coupling_map=None, initial_layout=None, shots=1024, max_credits=10, seed=None, qobj_id=None, hpc=None, pass_manager=None): """Compile a list of circuits into a qobj. Args: circuits (QuantumCircuit or list[QuantumCircuit]): circuits to compile backend (BaseBackend): a backend to compile for config (dict): dictionary of parameters (e.g. noise) used by runner basis_gates (str): comma-separated basis gate set to compile to coupling_map (list): coupling map (perhaps custom) to target in mapping initial_layout (list): initial layout of qubits in mapping shots (int): number of repetitions of each circuit, for sampling max_credits (int): maximum credits to use seed (int): random seed for simulators qobj_id (int): identifier for the generated qobj hpc (dict): HPC simulator parameters pass_manager (PassManager): a pass_manager for the transpiler stage Returns: Qobj: the Qobj to be run on the backends Raises: TranspilerError: in case of bad compile options, e.g. the hpc options. """ if isinstance(circuits, QuantumCircuit): circuits = [circuits] backend_conf = backend.configuration backend_name = backend_conf['name'] # Step 1: create the Qobj, with empty experiments. # Copy the configuration: the values in `config` have prefern qobj_config = deepcopy(config or {}) # TODO: "register_slots" is required by the qobj schema in the top-level # qobj.config. In this implementation, is overridden by the individual # experiment.config entries (hence the 0 should never be used). qobj_config.update({ 'shots': shots, 'max_credits': max_credits, 'register_slots': 0 }) qobj = Qobj(id=qobj_id or str(uuid.uuid4()), config=QobjConfig(**qobj_config), experiments=[], header=QobjHeader(backend_name=backend_name)) if seed: qobj.config.seed = seed # Check for valid parameters for the experiments. if hpc is not None and \ not all(key in hpc for key in ('multi_shot_optimization', 'omp_num_threads')): raise TranspilerError('Unknown HPC parameter format!') basis_gates = basis_gates or backend_conf['basis_gates'] coupling_map = coupling_map or backend_conf['coupling_map'] # Step 2 and 3: transpile and populate the circuits for circuit in circuits: # TODO: A better solution is to have options to enable/disable optimizations num_qubits = sum((len(qreg) for qreg in circuit.get_qregs().values())) if num_qubits == 1 or coupling_map == "all-to-all": coupling_map = None # Step 2a: circuit -> dag dag_circuit = DAGCircuit.fromQuantumCircuit(circuit) # TODO: move this inside the mapper pass # pick a good initial layout if coupling_map is not already satisfied # otherwise keep it as q[i]->q[i] if (initial_layout is None and not backend_conf['simulator'] and not _matches_coupling_map(circuit.data, coupling_map)): initial_layout = _pick_best_layout(backend, num_qubits, circuit.get_qregs()) # Step 2b: transpile (dag -> dag) dag_circuit, final_layout = transpile(dag_circuit, basis_gates=basis_gates, coupling_map=coupling_map, initial_layout=initial_layout, get_layout=True, seed=seed, pass_manager=pass_manager) # Step 2c: dag -> json # the compiled circuit to be run saved as a dag # we assume that transpile() has already expanded gates # to the target basis, so we just need to generate json list_layout = [[k, v] for k, v in final_layout.items() ] if final_layout else None json_circuit = DagUnroller(dag_circuit, JsonBackend(dag_circuit.basis)).execute() # Step 3a: create the Experiment based on json_circuit experiment = QobjExperiment.from_dict(json_circuit) # Step 3b: populate the Experiment configuration and header experiment.header.name = circuit.name # TODO: place in header or config? experiment_config = deepcopy(config or {}) experiment_config.update({ 'coupling_map': coupling_map, 'basis_gates': basis_gates, 'layout': list_layout, 'register_slots': sum(register.size for register in circuit.get_cregs().values()) }) experiment.config = QobjItem(**experiment_config) # set eval_symbols=True to evaluate each symbolic expression # TODO after transition to qobj, we can drop this experiment.header.compiled_circuit_qasm = dag_circuit.qasm( qeflag=True, eval_symbols=True) # Step 3c: add the Experiment to the Qobj qobj.experiments.append(experiment) return qobj
def _dags_2_qobj(dags, backend_name, config=None, shots=None, max_credits=None, qobj_id=None, basis_gates=None, coupling_map=None, seed=None): """Convert a list of dags into a qobj. Args: dags (list[DAGCircuit]): dags to compile backend_name (str): name of runner backend config (dict): dictionary of parameters (e.g. noise) used by runner shots (int): number of repetitions of each circuit, for sampling max_credits (int): maximum credits to use qobj_id (int): identifier for the generated qobj basis_gates (list[str])): basis gates for the experiment coupling_map (list): coupling map (perhaps custom) to target in mapping seed (int): random seed for simulators Returns: Qobj: the Qobj to be run on the backends """ # TODO: the following will be removed from qobj and thus removed here: # `basis_gates`, `coupling_map` # Step 1: create the Qobj, with empty experiments. # Copy the configuration: the values in `config` have preference qobj_config = deepcopy(config or {}) # TODO: "memory_slots" is required by the qobj schema in the top-level # qobj.config, and is user-defined. At the moment is set to the maximum # number of *register* slots for the circuits, in order to have `measure` # behave properly until the transition is over; and each circuit stores # its memory_slots in its configuration. qobj_config.update({ 'shots': shots, 'max_credits': max_credits, 'memory_slots': 0 }) qobj = Qobj(qobj_id=qobj_id or str(uuid.uuid4()), config=QobjConfig(**qobj_config), experiments=[], header=QobjHeader(backend_name=backend_name)) if seed: qobj.config.seed = seed for dag in dags: json_circuit = DagUnroller(dag, JsonBackend(dag.basis)).execute() # Step 3a: create the Experiment based on json_circuit experiment = QobjExperiment.from_dict(json_circuit) # Step 3b: populate the Experiment configuration and header experiment.header.name = dag.name # TODO: place in header or config? experiment_config = deepcopy(config or {}) experiment_config.update({ 'coupling_map': coupling_map, 'basis_gates': basis_gates, 'layout': dag.layout, 'memory_slots': sum(dag.cregs.values()), # TODO: `n_qubits` is not part of the qobj spec, but needed for the simulator. 'n_qubits': sum(dag.qregs.values()) }) experiment.config = QobjItem(**experiment_config) # set eval_symbols=True to evaluate each symbolic expression # TODO: after transition to qobj, we can drop this experiment.header.compiled_circuit_qasm = dag.qasm(qeflag=True, eval_symbols=True) # Step 3c: add the Experiment to the Qobj qobj.experiments.append(experiment) # Update the `memory_slots` value. # TODO: remove when `memory_slots` can be provided by the user. qobj.config.memory_slots = max(experiment.config.memory_slots for experiment in qobj.experiments) # Update the `n_qubits` global value. # TODO: num_qubits is not part of the qobj specification, but needed # for the simulator. qobj.config.n_qubits = max(experiment.config.n_qubits for experiment in qobj.experiments) return qobj
def optimize_1q_gates(circuit): """Simplify runs of single qubit gates in the QX basis. Return a new circuit that has been optimized. """ qx_basis = ["u1", "u2", "u3", "cx", "id"] dag_unroller = DagUnroller(circuit, DAGBackend(qx_basis)) unrolled = dag_unroller.expand_gates() runs = unrolled.collect_runs(["u1", "u2", "u3", "id"]) for run in runs: qname = unrolled.multi_graph.node[run[0]]["qargs"][0] right_name = "u1" right_parameters = (N(0), N(0), N(0)) # (theta, phi, lambda) for current_node in run: nd = unrolled.multi_graph.node[current_node] assert nd["condition"] is None, "internal error" assert len(nd["qargs"]) == 1, "internal error" assert nd["qargs"][0] == qname, "internal error" left_name = nd["name"] assert left_name in ["u1", "u2", "u3", "id"], "internal error" if left_name == "u1": left_parameters = (N(0), N(0), nd["params"][0]) elif left_name == "u2": left_parameters = (sympy.pi / 2, nd["params"][0], nd["params"][1]) elif left_name == "u3": left_parameters = tuple(nd["params"]) else: left_name = "u1" # replace id with u1 left_parameters = (N(0), N(0), N(0)) # Compose gates name_tuple = (left_name, right_name) if name_tuple == ("u1", "u1"): # u1(lambda1) * u1(lambda2) = u1(lambda1 + lambda2) right_parameters = (N(0), N(0), right_parameters[2] + left_parameters[2]) elif name_tuple == ("u1", "u2"): # u1(lambda1) * u2(phi2, lambda2) = u2(phi2 + lambda1, lambda2) right_parameters = (sympy.pi / 2, right_parameters[1] + left_parameters[2], right_parameters[2]) elif name_tuple == ("u2", "u1"): # u2(phi1, lambda1) * u1(lambda2) = u2(phi1, lambda1 + lambda2) right_name = "u2" right_parameters = (sympy.pi / 2, left_parameters[1], right_parameters[2] + left_parameters[2]) elif name_tuple == ("u1", "u3"): # u1(lambda1) * u3(theta2, phi2, lambda2) = # u3(theta2, phi2 + lambda1, lambda2) right_parameters = (right_parameters[0], right_parameters[1] + left_parameters[2], right_parameters[2]) elif name_tuple == ("u3", "u1"): # u3(theta1, phi1, lambda1) * u1(lambda2) = # u3(theta1, phi1, lambda1 + lambda2) right_name = "u3" right_parameters = (left_parameters[0], left_parameters[1], right_parameters[2] + left_parameters[2]) elif name_tuple == ("u2", "u2"): # Using Ry(pi/2).Rz(2*lambda).Ry(pi/2) = # Rz(pi/2).Ry(pi-2*lambda).Rz(pi/2), # u2(phi1, lambda1) * u2(phi2, lambda2) = # u3(pi - lambda1 - phi2, phi1 + pi/2, lambda2 + pi/2) right_name = "u3" right_parameters = (sympy.pi - left_parameters[2] - right_parameters[1], left_parameters[1] + sympy.pi / 2, right_parameters[2] + sympy.pi / 2) elif name_tuple[1] == "nop": right_name = left_name right_parameters = left_parameters else: # For composing u3's or u2's with u3's, use # u2(phi, lambda) = u3(pi/2, phi, lambda) # together with the qiskit.mapper.compose_u3 method. right_name = "u3" # Evaluate the symbolic expressions for efficiency left_parameters = tuple(map(lambda x: x.evalf(), list(left_parameters))) right_parameters = tuple(map(lambda x: x.evalf(), list(right_parameters))) right_parameters = compose_u3(left_parameters[0], left_parameters[1], left_parameters[2], right_parameters[0], right_parameters[1], right_parameters[2]) # Why evalf()? This program: # OPENQASM 2.0; # include "qelib1.inc"; # qreg q[2]; # creg c[2]; # u3(0.518016983430947*pi,1.37051598592907*pi,1.36816383603222*pi) q[0]; # u3(1.69867232277986*pi,0.371448347747471*pi,0.461117217930936*pi) q[0]; # u3(0.294319836336836*pi,0.450325871124225*pi,1.46804720442555*pi) q[0]; # measure q -> c; # took >630 seconds (did not complete) to optimize without # calling evalf() at all, 19 seconds to optimize calling # evalf() AFTER compose_u3, and 1 second to optimize # calling evalf() BEFORE compose_u3. # 1. Here down, when we simplify, we add f(theta) to lambda to # correct the global phase when f(theta) is 2*pi. This isn't # necessary but the other steps preserve the global phase, so # we continue in that manner. # 2. The final step will remove Z rotations by 2*pi. # 3. Note that is_zero is true only if the expression is exactly # zero. If the input expressions have already been evaluated # then these final simplifications will not occur. # TODO After we refactor, we should have separate passes for # exact and approximate rewriting. # Y rotation is 0 mod 2*pi, so the gate is a u1 if (right_parameters[0] % (2 * sympy.pi)).is_zero \ and right_name != "u1": right_name = "u1" right_parameters = (0, 0, right_parameters[1] + right_parameters[2] + right_parameters[0]) # Y rotation is pi/2 or -pi/2 mod 2*pi, so the gate is a u2 if right_name == "u3": # theta = pi/2 + 2*k*pi if ((right_parameters[0] - sympy.pi / 2) % (2 * sympy.pi)).is_zero: right_name = "u2" right_parameters = (sympy.pi / 2, right_parameters[1], right_parameters[2] + (right_parameters[0] - sympy.pi / 2)) # theta = -pi/2 + 2*k*pi if ((right_parameters[0] + sympy.pi / 2) % (2 * sympy.pi)).is_zero: right_name = "u2" right_parameters = (sympy.pi / 2, right_parameters[1] + sympy.pi, right_parameters[2] - sympy.pi + (right_parameters[0] + sympy.pi / 2)) # u1 and lambda is 0 mod 2*pi so gate is nop (up to a global phase) if right_name == "u1" and (right_parameters[2] % (2 * sympy.pi)).is_zero: right_name = "nop" # Simplify the symbolic parameters right_parameters = tuple(map(sympy.simplify, list(right_parameters))) # Replace the data of the first node in the run new_params = [] if right_name == "u1": new_params = [right_parameters[2]] if right_name == "u2": new_params = [right_parameters[1], right_parameters[2]] if right_name == "u3": new_params = list(right_parameters) nx.set_node_attributes(unrolled.multi_graph, name='name', values={run[0]: right_name}) # params is a list of sympy symbols nx.set_node_attributes(unrolled.multi_graph, name='params', values={run[0]: new_params}) # Delete the other nodes in the run for current_node in run[1:]: unrolled._remove_op_node(current_node) if right_name == "nop": unrolled._remove_op_node(run[0]) return unrolled
def test_from_dag_to_json_with_basis(self): ast = qasm.Qasm( filename=self._get_resource_path('qasm/example.qasm')).parse() dag_circuit = Unroller(ast, DAGBackend(["cx", "u1", "u2", "u3"])).execute() dag_unroller = DagUnroller(dag_circuit, JsonBackend(["cx", "u1", "u2", "u3"])) json_circuit = dag_unroller.execute() expected_result = { 'operations': [{ 'qubits': [5], 'texparams': ['0', '\\pi'], 'params': [0.0, 3.141592653589793], 'name': 'u2' }, { 'qubits': [5, 2], 'texparams': [], 'params': [], 'name': 'cx' }, { 'qubits': [2], 'clbits': [2], 'name': 'measure' }, { 'qubits': [4], 'texparams': ['0', '\\pi'], 'params': [0.0, 3.141592653589793], 'name': 'u2' }, { 'qubits': [4, 1], 'texparams': [], 'params': [], 'name': 'cx' }, { 'qubits': [1], 'clbits': [1], 'name': 'measure' }, { 'qubits': [3], 'texparams': ['0', '\\pi'], 'params': [0.0, 3.141592653589793], 'name': 'u2' }, { 'qubits': [3, 0], 'texparams': [], 'params': [], 'name': 'cx' }, { 'qubits': [3, 4, 5], 'name': 'barrier' }, { 'qubits': [5], 'clbits': [5], 'name': 'measure' }, { 'qubits': [4], 'clbits': [4], 'name': 'measure' }, { 'qubits': [3], 'clbits': [3], 'name': 'measure' }, { 'qubits': [0], 'clbits': [0], 'name': 'measure' }], 'header': { 'clbit_labels': [['d', 3], ['c', 3]], 'number_of_qubits': 6, 'qubit_labels': [['r', 0], ['r', 1], ['r', 2], ['q', 0], ['q', 1], ['q', 2]], 'number_of_clbits': 6 } } self.assertEqual(json_circuit, expected_result)
def compile(circuits, backend, config=None, basis_gates=None, coupling_map=None, initial_layout=None, shots=1024, max_credits=10, seed=None, qobj_id=None, hpc=None, pass_manager=None): """Compile a list of circuits into a qobj. Args: circuits (QuantumCircuit or list[QuantumCircuit]): circuits to compile backend (BaseBackend): a backend to compile for config (dict): dictionary of parameters (e.g. noise) used by runner basis_gates (str): comma-separated basis gate set to compile to coupling_map (list): coupling map (perhaps custom) to target in mapping initial_layout (list): initial layout of qubits in mapping shots (int): number of repetitions of each circuit, for sampling max_credits (int): maximum credits to use seed (int): random seed for simulators qobj_id (int): identifier for the generated qobj hpc (dict): HPC simulator parameters pass_manager (PassManager): a pass_manager for the transpiler stage Returns: Qobj: the Qobj to be run on the backends Raises: TranspilerError: in case of bad compile options, e.g. the hpc options. """ if isinstance(circuits, QuantumCircuit): circuits = [circuits] backend_conf = backend.configuration backend_name = backend_conf['name'] # Step 1: create the Qobj, with empty circuits qobj = Qobj(id=qobj_id or str(uuid.uuid4()), config=QobjConfig(max_credits=max_credits, shots=shots, backend_name=backend_name), circuits=[]) # Check for valid parameters for the experiments. if hpc is not None and \ not all(key in hpc for key in ('multi_shot_optimization', 'omp_num_threads')): raise TranspilerError('Unknown HPC parameter format!') basis_gates = basis_gates or backend_conf['basis_gates'] coupling_map = coupling_map or backend_conf['coupling_map'] for circuit in circuits: # Step 1: create the experiment configuration. config = config or {} circuit_config = copy.deepcopy(config) # TODO: A better solution is to have options to enable/disable optimizations num_qubits = sum((len(qreg) for qreg in circuit.get_qregs().values())) if num_qubits == 1 or coupling_map == "all-to-all": coupling_map = None circuit_config["coupling_map"] = coupling_map circuit_config["basis_gates"] = basis_gates circuit_config["seed"] = seed circuit_config["layout"] = None # set during step 3. # Step 2: create the QobjExperiment, with empty compiled circuits. experiment = QobjExperiment( name=circuit.name, config=QobjExperimentConfig(**circuit_config), compiled_circuit=None, compiled_circuit_qasm=None) # Step 3: populate the circuit `instructions` after compilation # Step 3a: circuit -> dag dag_circuit = DAGCircuit.fromQuantumCircuit(circuit) # TODO: move this inside the mapper pass # pick a good initial layout if coupling_map is not already satisfied # otherwise keep it as q[i]->q[i] if (initial_layout is None and not backend_conf['simulator'] and not _matches_coupling_map(circuit.data, coupling_map)): initial_layout = _pick_best_layout(backend, num_qubits, circuit.get_qregs()) # Step 3b: transpile (dag -> dag) dag_circuit, final_layout = transpile(dag_circuit, basis_gates=basis_gates, coupling_map=coupling_map, initial_layout=initial_layout, get_layout=True, seed=seed, pass_manager=pass_manager) # Step 3c: dag -> json # the compiled circuit to be run saved as a dag # we assume that transpile() has already expanded gates # to the target basis, so we just need to generate json list_layout = [[k, v] for k, v in final_layout.items() ] if final_layout else None experiment.config.layout = list_layout json_circuit = DagUnroller(dag_circuit, JsonBackend(dag_circuit.basis)).execute() experiment.compiled_circuit = QobjCompiledCircuit.from_dict( json_circuit) # set eval_symbols=True to evaluate each symbolic expression # TODO after transition to qobj, we can drop this experiment.compiled_circuit_qasm = dag_circuit.qasm(qeflag=True, eval_symbols=True) # add job to the qobj qobj.circuits.append(experiment) return qobj