diff --git a/qiskit/assembler/assemble_circuits.py b/qiskit/assembler/assemble_circuits.py index b91a269fa23a..6b85ccc29043 100644 --- a/qiskit/assembler/assemble_circuits.py +++ b/qiskit/assembler/assemble_circuits.py @@ -76,7 +76,7 @@ def _assemble_circuit( memory_slots=memory_slots, creg_sizes=creg_sizes, name=circuit.name, - global_phase=circuit.global_phase) + global_phase=float(circuit.global_phase)) # TODO: why do we need n_qubits and memory_slots in both the header and the config config = QasmQobjExperimentConfig(n_qubits=num_qubits, memory_slots=memory_slots) @@ -113,6 +113,9 @@ def _assemble_circuit( # measurement result may be needed for a conditional gate. if instruction.name == "measure" and is_conditional_experiment: instruction.register = clbit_indices + if op_context[0].definition is not None and op_context[0].definition.global_phase: + # pylint: disable=no-member + header.global_phase += float(op_context[0].definition.global_phase) # To convert to a qobj-style conditional, insert a bfunc prior # to the conditional instruction to map the creg ?= val condition diff --git a/qiskit/dagcircuit/dagcircuit.py b/qiskit/dagcircuit/dagcircuit.py index 623291cd37ca..d943ff2d87c4 100644 --- a/qiskit/dagcircuit/dagcircuit.py +++ b/qiskit/dagcircuit/dagcircuit.py @@ -321,6 +321,19 @@ def _add_op_node(self, op, qargs, cargs): new_node._node_id = node_index return node_index + def _copy_circuit_metadata(self): + """Return a copy of source_dag with metadata but empty.""" + target_dag = DAGCircuit() + target_dag.name = self.name + target_dag._global_phase = self._global_phase + + for qreg in self.qregs.values(): + target_dag.add_qreg(qreg) + for creg in self.cregs.values(): + target_dag.add_creg(creg) + + return target_dag + def apply_operation_back(self, op, qargs=None, cargs=None, condition=None): """Apply an operation to the output of the circuit. @@ -883,6 +896,9 @@ def substitute_node_with_dag(self, node, input_dag, wires=None): in_dag.apply_operation_back(replay_node.op, replay_node.qargs, replay_node.cargs) + if in_dag.global_phase: + self.global_phase += in_dag.global_phase + if wires is None: wires = in_dag.wires diff --git a/qiskit/providers/basicaer/qasm_simulator.py b/qiskit/providers/basicaer/qasm_simulator.py index 1a3c991adf23..6d0f25dc8781 100644 --- a/qiskit/providers/basicaer/qasm_simulator.py +++ b/qiskit/providers/basicaer/qasm_simulator.py @@ -456,6 +456,7 @@ def run_experiment(self, experiment): self._classical_memory = 0 self._classical_register = 0 self._sample_measure = False + global_phase = experiment.header.global_phase # Validate the dimension of initial statevector if set self._validate_initial_statevector() # Get the seed looking in circuit, qobj, and then random. @@ -485,6 +486,8 @@ def run_experiment(self, experiment): shots = self._shots for _ in range(shots): self._initialize_statevector() + # apply global_phase + self._statevector *= np.exp(1j * global_phase) # Initialize classical memory to all 0 self._classical_memory = 0 self._classical_register = 0 diff --git a/qiskit/providers/basicaer/unitary_simulator.py b/qiskit/providers/basicaer/unitary_simulator.py index 37cbddce5671..b38aa11370eb 100644 --- a/qiskit/providers/basicaer/unitary_simulator.py +++ b/qiskit/providers/basicaer/unitary_simulator.py @@ -116,6 +116,7 @@ def __init__(self, configuration=None, provider=None): self._number_of_qubits = 0 self._initial_unitary = None self._chop_threshold = 1e-15 + self._global_phase = 0 def _add_unitary(self, gate, qubits): """Apply an N-qubit unitary matrix. @@ -200,6 +201,8 @@ def _initialize_unitary(self): def _get_unitary(self): """Return the current unitary""" unitary = np.reshape(self._unitary, 2 * [2 ** self._number_of_qubits]) + if self._global_phase: + unitary *= np.exp(1j * float(self._global_phase)) unitary[abs(unitary) < self._chop_threshold] = 0.0 return unitary @@ -302,6 +305,7 @@ def run_experiment(self, experiment): """ start = time.time() self._number_of_qubits = experiment.header.n_qubits + self._global_phase = experiment.header.global_phase # Validate the dimension of initial unitary if set self._validate_initial_unitary() diff --git a/qiskit/transpiler/passes/basis/basis_translator.py b/qiskit/transpiler/passes/basis/basis_translator.py index 5f322e012fab..752fabc29d7d 100644 --- a/qiskit/transpiler/passes/basis/basis_translator.py +++ b/qiskit/transpiler/passes/basis/basis_translator.py @@ -159,10 +159,12 @@ def run(self, dag): bound_target_dag = circuit_to_dag(target_circuit) else: bound_target_dag = target_dag - + if bound_target_dag.global_phase: + dag.global_phase += bound_target_dag.global_phase if (len(bound_target_dag.op_nodes()) == 1 and len(bound_target_dag.op_nodes()[0].qargs) == len(node.qargs)): - dag.substitute_node(node, bound_target_dag.op_nodes()[0].op, inplace=True) + dag_op = bound_target_dag.op_nodes()[0].op + dag.substitute_node(node, dag_op, inplace=True) else: dag.substitute_node_with_dag(node, bound_target_dag) else: diff --git a/qiskit/transpiler/passes/basis/decompose.py b/qiskit/transpiler/passes/basis/decompose.py index 4627764233b9..444d73268b6b 100644 --- a/qiskit/transpiler/passes/basis/decompose.py +++ b/qiskit/transpiler/passes/basis/decompose.py @@ -46,6 +46,8 @@ def run(self, dag: DAGCircuit) -> DAGCircuit: # opaque or built-in gates are not decomposable if not node.op.definition: continue + if node.op.definition.global_phase: + dag.global_phase += node.op.definition.global_phase # TODO: allow choosing among multiple decomposition rules rule = node.op.definition.data diff --git a/qiskit/transpiler/passes/layout/apply_layout.py b/qiskit/transpiler/passes/layout/apply_layout.py index 6a9e6bb27b44..97c9abf6a75d 100644 --- a/qiskit/transpiler/passes/layout/apply_layout.py +++ b/qiskit/transpiler/passes/layout/apply_layout.py @@ -57,5 +57,6 @@ def run(self, dag): if node.type == 'op': qargs = [q[layout[qarg]] for qarg in node.qargs] new_dag.apply_operation_back(node.op, qargs, node.cargs) + new_dag._global_phase = dag._global_phase return new_dag diff --git a/qiskit/transpiler/passes/optimization/consolidate_blocks.py b/qiskit/transpiler/passes/optimization/consolidate_blocks.py index 4ca44f2be771..cf904dedbf68 100644 --- a/qiskit/transpiler/passes/optimization/consolidate_blocks.py +++ b/qiskit/transpiler/passes/optimization/consolidate_blocks.py @@ -16,7 +16,6 @@ from qiskit.circuit import QuantumRegister, ClassicalRegister, QuantumCircuit -from qiskit.dagcircuit import DAGCircuit from qiskit.quantum_info.operators import Operator from qiskit.quantum_info.synthesis import TwoQubitBasisDecomposer from qiskit.extensions import UnitaryGate @@ -75,11 +74,7 @@ def run(self, dag): if self.decomposer is None: return dag - new_dag = DAGCircuit() - for qreg in dag.qregs.values(): - new_dag.add_qreg(qreg) - for creg in dag.cregs.values(): - new_dag.add_creg(creg) + new_dag = dag._copy_circuit_metadata() # compute ordered indices for the global circuit wires global_index_map = {wire: idx for idx, wire in enumerate(dag.qubits)} diff --git a/qiskit/transpiler/passes/optimization/optimize_1q_gates.py b/qiskit/transpiler/passes/optimization/optimize_1q_gates.py index 61fbc38933e7..0b6bb8af831a 100644 --- a/qiskit/transpiler/passes/optimization/optimize_1q_gates.py +++ b/qiskit/transpiler/passes/optimization/optimize_1q_gates.py @@ -60,7 +60,7 @@ def run(self, dag): for run in runs: right_name = "u1" right_parameters = (0, 0, 0) # (theta, phi, lambda) - + right_global_phase = 0 for current_node in run: left_name = current_node.name if (current_node.condition is not None @@ -77,6 +77,9 @@ def run(self, dag): else: left_name = "u1" # replace id with u1 left_parameters = (0, 0, 0) + if (current_node.op.definition is not None and + current_node.op.definition.global_phase): + right_global_phase += current_node.op.definition.global_phase # If there are any sympy objects coming from the gate convert # to numpy. left_parameters = tuple([float(x) for x in left_parameters]) @@ -206,6 +209,8 @@ def run(self, dag): else: raise TranspilerError('It was not possible to use the basis %s' % self.basis) + dag.global_phase += right_global_phase + if right_name != 'nop': dag.substitute_node(run[0], new_op, inplace=True) diff --git a/qiskit/transpiler/passes/routing/basic_swap.py b/qiskit/transpiler/passes/routing/basic_swap.py index 8eb49b0e4eff..2641163d0d84 100644 --- a/qiskit/transpiler/passes/routing/basic_swap.py +++ b/qiskit/transpiler/passes/routing/basic_swap.py @@ -49,11 +49,7 @@ def run(self, dag): TranspilerError: if the coupling map or the layout are not compatible with the DAG. """ - new_dag = DAGCircuit() - for qreg in dag.qregs.values(): - new_dag.add_qreg(qreg) - for creg in dag.cregs.values(): - new_dag.add_creg(creg) + new_dag = dag._copy_circuit_metadata() if len(dag.qregs) != 1 or dag.qregs.get('q', None) is None: raise TranspilerError('Basic swap runs on physical circuits only') diff --git a/qiskit/transpiler/passes/routing/sabre_swap.py b/qiskit/transpiler/passes/routing/sabre_swap.py index 32ac0de6d219..59954b86bf88 100644 --- a/qiskit/transpiler/passes/routing/sabre_swap.py +++ b/qiskit/transpiler/passes/routing/sabre_swap.py @@ -17,7 +17,6 @@ from itertools import cycle import numpy as np -from qiskit.dagcircuit import DAGCircuit from qiskit.circuit.library.standard_gates import SwapGate from qiskit.transpiler.basepasses import TransformationPass from qiskit.transpiler.exceptions import TranspilerError @@ -145,7 +144,7 @@ def run(self, dag): rng = np.random.default_rng(self.seed) # Preserve input DAG's name, regs, wire_map, etc. but replace the graph. - mapped_dag = _copy_circuit_metadata(dag) + mapped_dag = dag._copy_circuit_metadata() # Assume bidirectional couplings, fixing gate direction is easy later. self.coupling_map.make_symmetric() @@ -335,20 +334,6 @@ def _score_heuristic(self, heuristic, front_layer, extended_set, layout, swap_qu raise TranspilerError('Heuristic %s not recognized.' % heuristic) -def _copy_circuit_metadata(source_dag): - """Return a copy of source_dag with metadata but empty. - """ - target_dag = DAGCircuit() - target_dag.name = source_dag.name - - for qreg in source_dag.qregs.values(): - target_dag.add_qreg(qreg) - for creg in source_dag.cregs.values(): - target_dag.add_creg(creg) - - return target_dag - - def _transform_gate_for_layout(op_node, layout): """Return node implementing a virtual op on given layout.""" mapped_op_node = deepcopy(op_node) diff --git a/qiskit/transpiler/passes/routing/stochastic_swap.py b/qiskit/transpiler/passes/routing/stochastic_swap.py index be98d51b72a4..095a0bd2723f 100644 --- a/qiskit/transpiler/passes/routing/stochastic_swap.py +++ b/qiskit/transpiler/passes/routing/stochastic_swap.py @@ -299,12 +299,7 @@ def _mapper(self, circuit_graph, coupling_graph, trials=20): # Construct an empty DAGCircuit with the same set of # qregs and cregs as the input circuit - dagcircuit_output = DAGCircuit() - dagcircuit_output.name = circuit_graph.name - for qreg in circuit_graph.qregs.values(): - dagcircuit_output.add_qreg(qreg) - for creg in circuit_graph.cregs.values(): - dagcircuit_output.add_creg(creg) + dagcircuit_output = circuit_graph._copy_circuit_metadata() logger.debug("trivial_layout = %s", layout) diff --git a/qiskit/transpiler/passes/utils/merge_adjacent_barriers.py b/qiskit/transpiler/passes/utils/merge_adjacent_barriers.py index 93fa091b6958..56310311af07 100644 --- a/qiskit/transpiler/passes/utils/merge_adjacent_barriers.py +++ b/qiskit/transpiler/passes/utils/merge_adjacent_barriers.py @@ -13,7 +13,6 @@ """Return a circuit with any adjacent barriers merged together.""" from qiskit.transpiler.basepasses import TransformationPass -from qiskit.dagcircuit import DAGCircuit from qiskit.circuit.barrier import Barrier @@ -59,12 +58,7 @@ def run(self, dag): return dag # add the merged barriers to a new DAG - new_dag = DAGCircuit() - - for qreg in dag.qregs.values(): - new_dag.add_qreg(qreg) - for creg in dag.cregs.values(): - new_dag.add_creg(creg) + new_dag = dag._copy_circuit_metadata() # go over current nodes, and add them to the new dag for node in dag.topological_op_nodes(): diff --git a/qiskit/transpiler/passes/utils/remove_final_measurements.py b/qiskit/transpiler/passes/utils/remove_final_measurements.py index 8c755cd47622..377ba784946d 100644 --- a/qiskit/transpiler/passes/utils/remove_final_measurements.py +++ b/qiskit/transpiler/passes/utils/remove_final_measurements.py @@ -13,7 +13,6 @@ """Remove final measurements and barriers at the end of a circuit.""" from qiskit.transpiler.basepasses import TransformationPass -from qiskit.dagcircuit import DAGCircuit class RemoveFinalMeasurements(TransformationPass): @@ -54,8 +53,6 @@ def run(self, dag): if not final_ops: return dag - new_dag = DAGCircuit() - for node in final_ops: for carg in node.cargs: # Add the clbit that was attached to the measure we are removing @@ -75,11 +72,7 @@ def run(self, dag): if val in cregs_to_remove and cregs_to_remove[val] == val.size: del dag.cregs[key] - # Fill new DAGCircuit - for qreg in dag.qregs.values(): - new_dag.add_qreg(qreg) - for creg in dag.cregs.values(): - new_dag.add_creg(creg) + new_dag = dag._copy_circuit_metadata() for node in dag.topological_op_nodes(): # copy the condition over too diff --git a/test/python/basicaer/test_statevector_simulator.py b/test/python/basicaer/test_statevector_simulator.py index afa47d1e4805..8ae9ae915e84 100644 --- a/test/python/basicaer/test_statevector_simulator.py +++ b/test/python/basicaer/test_statevector_simulator.py @@ -83,6 +83,37 @@ def test_unitary(self): fidelity = state_fidelity(psi_target, psi_out) self.assertGreater(fidelity, 0.999) + def test_global_phase(self): + """Test global_phase""" + n_qubits = 4 + qr = QuantumRegister(n_qubits) + circ = QuantumCircuit(qr) + circ.x(qr) + circ.global_phase = 0.5 + self.circuit = circ + result = super().test_run_circuit() + actual = result.get_statevector(self.circuit) + expected = np.exp(1j * circ.global_phase) * np.repeat([[0], [1]], [n_qubits**2-1, 1]) + self.assertTrue(np.allclose(actual, expected)) + + def test_global_phase_composite(self): + """Test global_phase""" + n_qubits = 4 + qr = QuantumRegister(n_qubits) + circ = QuantumCircuit(qr) + circ.x(qr) + circ.global_phase = 0.5 + gate = circ.to_gate() + + comp = QuantumCircuit(qr) + comp.append(gate, qr) + comp.global_phase = 0.1 + self.circuit = comp + result = super().test_run_circuit() + actual = result.get_statevector(self.circuit) + expected = np.exp(1j * 0.6) * np.repeat([[0], [1]], [n_qubits**2-1, 1]) + self.assertTrue(np.allclose(actual, expected)) + if __name__ == '__main__': unittest.main() diff --git a/test/python/circuit/test_controlled_gate.py b/test/python/circuit/test_controlled_gate.py index 2161bf45f7f7..e8615a885227 100644 --- a/test/python/circuit/test_controlled_gate.py +++ b/test/python/circuit/test_controlled_gate.py @@ -658,6 +658,15 @@ def test_open_controlled_unitary_z(self, num_ctrl_qubits, ctrl_state): ref_mat = _compute_control_matrix(umat, num_ctrl_qubits, ctrl_state=ctrl_state) self.assertEqual(Operator(cugate), Operator(ref_mat)) + def test_controlled_controlled_rz(self): + """Test that UnitaryGate with control returns params.""" + qc = QuantumCircuit(1) + qc.rz(0.2, 0) + controlled = QuantumCircuit(2) + controlled.compose(qc.control(), inplace=True) + self.assertEqual(Operator(controlled), Operator(CRZGate(0.2))) + self.assertEqual(Operator(controlled), Operator(RZGate(0.2).control())) + def test_controlled_controlled_unitary(self): """Test that global phase in iso decomposition of unitary is handled.""" umat = np.array([[1, 0], [0, -1]]) diff --git a/test/python/circuit/test_extensions_standard.py b/test/python/circuit/test_extensions_standard.py index aab7b5ffd2a7..14a64f0acc7e 100644 --- a/test/python/circuit/test_extensions_standard.py +++ b/test/python/circuit/test_extensions_standard.py @@ -1381,7 +1381,7 @@ def test_to_matrix_op(self): from qiskit.quantum_info import Operator from qiskit.circuit.library.standard_gates.ms import MSGate - params = [0.1 * i for i in range(10)] + params = [0.1 * i for i in range(1, 11)] gate_class_list = Gate.__subclasses__() + ControlledGate.__subclasses__() for gate_class in gate_class_list: sig = signature(gate_class) diff --git a/test/python/transpiler/test_basis_translator.py b/test/python/transpiler/test_basis_translator.py index 5709f0a831c8..dcdf7325145e 100644 --- a/test/python/transpiler/test_basis_translator.py +++ b/test/python/transpiler/test_basis_translator.py @@ -643,3 +643,21 @@ def test_cx_bell_to_iswap(self): expected_dag = circuit_to_dag(expected) self.assertEqual(out_dag, expected_dag) + + def test_global_phase(self): + """Verify global phase preserved in basis translation""" + circ = QuantumCircuit(1) + gate_angle = pi / 5 + circ_angle = pi / 3 + circ.rz(gate_angle, 0) + circ.global_phase = circ_angle + in_dag = circuit_to_dag(circ) + out_dag = BasisTranslator(std_eqlib, ['u1']).run(in_dag) + + qr = QuantumRegister(1, 'q') + expected = QuantumCircuit(qr) + expected.u1(gate_angle, qr) + expected.global_phase = circ_angle - gate_angle / 2 + expected_dag = circuit_to_dag(expected) + self.assertEqual(out_dag, expected_dag) + self.assertEqual(float(out_dag.global_phase), float(expected_dag.global_phase)) diff --git a/test/python/transpiler/test_decompose.py b/test/python/transpiler/test_decompose.py index 4899e7dee02c..7950f50c97cc 100644 --- a/test/python/transpiler/test_decompose.py +++ b/test/python/transpiler/test_decompose.py @@ -19,6 +19,7 @@ from qiskit.converters import circuit_to_dag from qiskit.circuit.library import HGate from qiskit.circuit.library import CCXGate +from qiskit.quantum_info.operators import Operator from qiskit.test import QiskitTestCase @@ -100,3 +101,20 @@ def test_decompose_oversized_instruction(self): output = qc2.decompose() self.assertEqual(qc1, output) + + def test_decompose_global_phase_1q(self): + """Test decomposition of circuit with global phase""" + qc = QuantumCircuit(1) + qc.rz(0.1, 0) + qc.ry(0.5, 0) + qc.global_phase += pi/4 + qcd = qc.decompose() + self.assertEqual(Operator(qc), Operator(qcd)) + + def test_decompose_global_phase_2q(self): + """Test decomposition of circuit with global phase""" + qc = QuantumCircuit(2, global_phase=pi/4) + qc.rz(0.1, 0) + qc.rxx(0.2, 0, 1) + qcd = qc.decompose() + self.assertEqual(Operator(qc), Operator(qcd)) diff --git a/test/python/transpiler/test_optimize_1q_gates.py b/test/python/transpiler/test_optimize_1q_gates.py index 3944643bfefb..4d8d010f5012 100644 --- a/test/python/transpiler/test_optimize_1q_gates.py +++ b/test/python/transpiler/test_optimize_1q_gates.py @@ -221,6 +221,21 @@ def test_parameterized_expressions_in_circuits(self): self.assertEqual(circuit_to_dag(expected), after) + def test_global_phase_u3_on_left(self): + """Check proper phase accumulation with instruction with no definition.""" + from qiskit.circuit.library.standard_gates import U1Gate + qr = QuantumRegister(1) + qc = QuantumCircuit(qr) + u1 = U1Gate(0.1) + u1.definition.global_phase = np.pi / 2 + qc.append(u1, [0]) + qc.global_phase = np.pi / 3 + qc.u3(0.1, 0.2, 0.3, 0) + + dag = circuit_to_dag(qc) + after = Optimize1qGates().run(dag) + self.assertAlmostEqual(after.global_phase, 5 * np.pi / 6, 8) + class TestOptimize1qGatesParamReduction(QiskitTestCase): """Test for 1q gate optimizations parameter reduction, reduce n in Un """