From a54c1ad04d4f6ef1b752d59c0bec05d10a0152d6 Mon Sep 17 00:00:00 2001 From: Erick Winston Date: Tue, 11 Aug 2020 13:48:23 -0400 Subject: [PATCH 01/17] basicaer handling of global_phase. Also, adapted some transpiler passes to preserve global phase in dag. --- qiskit/assembler/assemble_circuits.py | 3 +++ qiskit/dagcircuit/dagcircuit.py | 3 +++ qiskit/providers/basicaer/unitary_simulator.py | 4 ++++ qiskit/transpiler/passes/basis/basis_translator.py | 5 ++++- .../transpiler/passes/optimization/consolidate_blocks.py | 1 + .../transpiler/passes/optimization/optimize_1q_gates.py | 8 +++++++- test/python/circuit/test_controlled_gate.py | 9 +++++++++ test/python/circuit/test_extensions_standard.py | 4 ++-- 8 files changed, 33 insertions(+), 4 deletions(-) diff --git a/qiskit/assembler/assemble_circuits.py b/qiskit/assembler/assemble_circuits.py index ca5aa5d2087e..9c093a9c21e3 100644 --- a/qiskit/assembler/assemble_circuits.py +++ b/qiskit/assembler/assemble_circuits.py @@ -80,6 +80,9 @@ def _assemble_circuit(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 += 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 a17af95f3476..85736a60c1c2 100644 --- a/qiskit/dagcircuit/dagcircuit.py +++ b/qiskit/dagcircuit/dagcircuit.py @@ -838,6 +838,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/unitary_simulator.py b/qiskit/providers/basicaer/unitary_simulator.py index 08ec408fc6a4..3d5d4ce27f40 100644 --- a/qiskit/providers/basicaer/unitary_simulator.py +++ b/qiskit/providers/basicaer/unitary_simulator.py @@ -118,6 +118,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. @@ -202,6 +203,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 @@ -304,6 +307,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 9fdce5eeead0..b5f1c8ca9a0a 100644 --- a/qiskit/transpiler/passes/basis/basis_translator.py +++ b/qiskit/transpiler/passes/basis/basis_translator.py @@ -150,7 +150,10 @@ def run(self, dag): 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 + if bound_target_dag.global_phase: + dag_op.definition.global_phase = bound_target_dag.global_phase + 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/optimization/consolidate_blocks.py b/qiskit/transpiler/passes/optimization/consolidate_blocks.py index 28eeb8d458de..e44a3e2f2e95 100644 --- a/qiskit/transpiler/passes/optimization/consolidate_blocks.py +++ b/qiskit/transpiler/passes/optimization/consolidate_blocks.py @@ -82,6 +82,7 @@ def run(self, dag): new_dag.add_qreg(qreg) for creg in dag.cregs.values(): new_dag.add_creg(creg) + new_dag._global_phase = dag._global_phase # 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 89a4a4b88fb3..b54e1a177c3f 100644 --- a/qiskit/transpiler/passes/optimization/optimize_1q_gates.py +++ b/qiskit/transpiler/passes/optimization/optimize_1q_gates.py @@ -62,7 +62,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 @@ -79,6 +79,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]) @@ -208,6 +211,9 @@ def run(self, dag): else: raise TranspilerError('It was not possible to use the basis %s' % self.basis) + # safer to keep phase on op instead of dag (e.g. if removed)? + if new_op.definition is not None: + new_op.definition.global_phase = right_global_phase if right_name != 'nop': dag.substitute_node(run[0], new_op, inplace=True) diff --git a/test/python/circuit/test_controlled_gate.py b/test/python/circuit/test_controlled_gate.py index f17ce5bc0e35..39d1076f9eb3 100644 --- a/test/python/circuit/test_controlled_gate.py +++ b/test/python/circuit/test_controlled_gate.py @@ -643,6 +643,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())) + @data(1, 2, 3) def test_open_controlled_unitary_matrix(self, num_ctrl_qubits): """test open controlled unitary matrix""" diff --git a/test/python/circuit/test_extensions_standard.py b/test/python/circuit/test_extensions_standard.py index 50d09b6df71e..ae80d38e0805 100644 --- a/test/python/circuit/test_extensions_standard.py +++ b/test/python/circuit/test_extensions_standard.py @@ -1354,7 +1354,7 @@ def test_to_matrix(self): definition.""" 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__() simulator = BasicAer.get_backend('unitary_simulator') for gate_class in gate_class_list: @@ -1394,7 +1394,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) From 379cc2ea62e642c87e68256bb408b67ba008bbb7 Mon Sep 17 00:00:00 2001 From: Erick Winston Date: Mon, 24 Aug 2020 22:10:32 -0400 Subject: [PATCH 02/17] catch non-float global_phase --- qiskit/assembler/assemble_circuits.py | 4 ++-- qiskit/transpiler/passes/basis/basis_translator.py | 2 +- test/python/circuit/test_squ.py | 1 - 3 files changed, 3 insertions(+), 4 deletions(-) diff --git a/qiskit/assembler/assemble_circuits.py b/qiskit/assembler/assemble_circuits.py index 29894b50f3fd..dc1af04d6728 100644 --- a/qiskit/assembler/assemble_circuits.py +++ b/qiskit/assembler/assemble_circuits.py @@ -46,7 +46,7 @@ def _assemble_circuit(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) @@ -80,7 +80,7 @@ def _assemble_circuit(circuit): 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 += op_context[0].definition.global_phase + 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/transpiler/passes/basis/basis_translator.py b/qiskit/transpiler/passes/basis/basis_translator.py index aa9c19d91857..d4fef5fe260c 100644 --- a/qiskit/transpiler/passes/basis/basis_translator.py +++ b/qiskit/transpiler/passes/basis/basis_translator.py @@ -149,7 +149,7 @@ def run(self, dag): if (len(bound_target_dag.op_nodes()) == 1 and len(bound_target_dag.op_nodes()[0].qargs) == len(node.qargs)): dag_op = bound_target_dag.op_nodes()[0].op - if bound_target_dag.global_phase: + if bound_target_dag.global_phase and dag_op.definition is not None: dag_op.definition.global_phase = bound_target_dag.global_phase dag.substitute_node(node, dag_op, inplace=True) else: diff --git a/test/python/circuit/test_squ.py b/test/python/circuit/test_squ.py index 1ae37a69dbac..9eee6932b51b 100644 --- a/test/python/circuit/test_squ.py +++ b/test/python/circuit/test_squ.py @@ -56,6 +56,5 @@ def test_squ(self): unitary_desired = u self.assertTrue(matrix_equal(unitary_desired, unitary, ignore_phase=True)) - if __name__ == '__main__': unittest.main() From 7380aea54c327a1f497a87733bb6516c40c4e5e1 Mon Sep 17 00:00:00 2001 From: Erick Winston Date: Tue, 25 Aug 2020 01:17:10 -0400 Subject: [PATCH 03/17] update state vector simulator --- qiskit/providers/basicaer/qasm_simulator.py | 3 ++ .../basicaer/test_statevector_simulator.py | 32 +++++++++++++++++++ 2 files changed, 35 insertions(+) diff --git a/qiskit/providers/basicaer/qasm_simulator.py b/qiskit/providers/basicaer/qasm_simulator.py index 1a3c991adf23..931e7282be3f 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 + self._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 * self._global_phase) # Initialize classical memory to all 0 self._classical_memory = 0 self._classical_register = 0 diff --git a/test/python/basicaer/test_statevector_simulator.py b/test/python/basicaer/test_statevector_simulator.py index afa47d1e4805..580f5fcf3f56 100644 --- a/test/python/basicaer/test_statevector_simulator.py +++ b/test/python/basicaer/test_statevector_simulator.py @@ -83,6 +83,38 @@ 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() From b6e9664a60b60af1601addeeec8483b44deaf49d Mon Sep 17 00:00:00 2001 From: Erick Winston Date: Tue, 25 Aug 2020 01:59:49 -0400 Subject: [PATCH 04/17] linting --- qiskit/providers/basicaer/qasm_simulator.py | 4 ++-- test/python/basicaer/test_statevector_simulator.py | 5 ++--- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/qiskit/providers/basicaer/qasm_simulator.py b/qiskit/providers/basicaer/qasm_simulator.py index 931e7282be3f..6d0f25dc8781 100644 --- a/qiskit/providers/basicaer/qasm_simulator.py +++ b/qiskit/providers/basicaer/qasm_simulator.py @@ -456,7 +456,7 @@ def run_experiment(self, experiment): self._classical_memory = 0 self._classical_register = 0 self._sample_measure = False - self._global_phase = experiment.header.global_phase + 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. @@ -487,7 +487,7 @@ def run_experiment(self, experiment): for _ in range(shots): self._initialize_statevector() # apply global_phase - self._statevector *= np.exp(1j * self._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/test/python/basicaer/test_statevector_simulator.py b/test/python/basicaer/test_statevector_simulator.py index 580f5fcf3f56..8ae9ae915e84 100644 --- a/test/python/basicaer/test_statevector_simulator.py +++ b/test/python/basicaer/test_statevector_simulator.py @@ -113,8 +113,7 @@ def test_global_phase_composite(self): 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() From 28ef827e7469f0ba72a30567e4e27a789d9d40ec Mon Sep 17 00:00:00 2001 From: Erick Winston Date: Tue, 25 Aug 2020 10:52:38 -0400 Subject: [PATCH 05/17] blank line needed --- test/python/circuit/test_squ.py | 1 + 1 file changed, 1 insertion(+) diff --git a/test/python/circuit/test_squ.py b/test/python/circuit/test_squ.py index 9eee6932b51b..1ae37a69dbac 100644 --- a/test/python/circuit/test_squ.py +++ b/test/python/circuit/test_squ.py @@ -56,5 +56,6 @@ def test_squ(self): unitary_desired = u self.assertTrue(matrix_equal(unitary_desired, unitary, ignore_phase=True)) + if __name__ == '__main__': unittest.main() From 9e8fb4d68b73837a4454a7dc823da19829538a14 Mon Sep 17 00:00:00 2001 From: Erick Winston Date: Sun, 30 Aug 2020 00:53:46 -0400 Subject: [PATCH 06/17] update Decompose pass for global phase --- qiskit/transpiler/passes/basis/decompose.py | 2 ++ test/python/transpiler/test_decompose.py | 18 ++++++++++++++++++ 2 files changed, 20 insertions(+) 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/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)) From 5b5b35eba2c338cda07784fa2b0f62c9c860e457 Mon Sep 17 00:00:00 2001 From: Erick Winston Date: Thu, 3 Sep 2020 06:54:40 -0400 Subject: [PATCH 07/17] update transpiler passes which create new dagcircuits. This preserves global phase in newly created dagcircuit. --- qiskit/dagcircuit/dagcircuit.py | 13 +++++++++++++ .../passes/basis/basis_translator.py | 5 ++--- .../transpiler/passes/layout/apply_layout.py | 1 + .../passes/optimization/consolidate_blocks.py | 8 +------- qiskit/transpiler/passes/routing/basic_swap.py | 6 +----- qiskit/transpiler/passes/routing/sabre_swap.py | 17 +---------------- .../passes/routing/stochastic_swap.py | 7 +------ .../passes/utils/merge_adjacent_barriers.py | 8 +------- .../passes/utils/remove_final_measurements.py | 9 +-------- .../python/transpiler/test_basis_translator.py | 18 ++++++++++++++++++ 10 files changed, 40 insertions(+), 52 deletions(-) diff --git a/qiskit/dagcircuit/dagcircuit.py b/qiskit/dagcircuit/dagcircuit.py index c10ea4880d02..e828e9ff7a04 100644 --- a/qiskit/dagcircuit/dagcircuit.py +++ b/qiskit/dagcircuit/dagcircuit.py @@ -297,6 +297,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. diff --git a/qiskit/transpiler/passes/basis/basis_translator.py b/qiskit/transpiler/passes/basis/basis_translator.py index d4fef5fe260c..876c717e5113 100644 --- a/qiskit/transpiler/passes/basis/basis_translator.py +++ b/qiskit/transpiler/passes/basis/basis_translator.py @@ -145,12 +145,11 @@ 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_op = bound_target_dag.op_nodes()[0].op - if bound_target_dag.global_phase and dag_op.definition is not None: - dag_op.definition.global_phase = bound_target_dag.global_phase dag.substitute_node(node, dag_op, inplace=True) else: dag.substitute_node_with_dag(node, bound_target_dag) 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 8502aec76002..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,12 +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._global_phase = dag._global_phase + 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/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 3494782e618e..6b60b2f6baf9 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/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)) From c12ddbac367540609fce9d17aa33bd466c98a640 Mon Sep 17 00:00:00 2001 From: Matthew Treinish Date: Tue, 1 Sep 2020 12:12:49 -0400 Subject: [PATCH 08/17] Drop support for python 3.5 (#4926) This commit drops support for running with python 3.5. It marks the minimum supported version of the package as python 3.6, removes python 3.5 package pins, removes the 3.5 CI jobs, and removes the warning on python 3.5. Looking at the PyPI stats since the deprecation period started the number of users on Python 3.5 has diminished significantly, but not disappeared. There were 783 downloads with pip from pypi out of total of 25782 total pip downloads in the last 30 days. Compared to the roughly 10% figure when we deprecated Python 3.5. Merging this means we can not release until after the documented EoL date for Python 3.5 support of September 13. This shouldn't be a problem because with #4767 we will need to coordinate the release of all the qiskit elements and are planning to do that after 09/13/2020. It's worth noting that we should start planning to deprecate python 3.6 support sooner rather than later it goes EoL upstream at the end of next year [1] and some of our other upstream dependencies (mainly numpy et al) are going to remove support before the upstream Python EoL date [2]. [1] https://devguide.python.org/#branchstatus [2] https://numpy.org/neps/nep-0029-deprecation_policy.html Co-authored-by: Kevin Krsulich --- azure-pipelines.yml | 16 +++++----------- qiskit/__init__.py | 8 +------- qiskit/util.py | 7 ------- .../notes/drop-py3.5-86a65daa49903d46.yaml | 7 +++++++ requirements-dev.txt | 2 +- requirements.txt | 3 +-- setup.py | 7 ++----- tox.ini | 2 +- 8 files changed, 18 insertions(+), 34 deletions(-) create mode 100644 releasenotes/notes/drop-py3.5-86a65daa49903d46.yaml diff --git a/azure-pipelines.yml b/azure-pipelines.yml index 4916a5b9ee26..c81724e0afc6 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -28,7 +28,7 @@ stages: variables: python.version: '3.7' CIBW_BEFORE_BUILD: pip install -U Cython - CIBW_SKIP: cp27-* cp34-* pp* + CIBW_SKIP: cp27-* cp34-* cp35-* pp* TWINE_USERNAME: qiskit CIBW_TEST_COMMAND: python {project}/examples/python/stochastic_swap.py steps: @@ -72,7 +72,7 @@ stages: variables: python.version: '3.7' CIBW_BEFORE_BUILD: pip install -U Cython - CIBW_SKIP: cp27-* cp34-* pp* + CIBW_SKIP: cp27-* cp34-* cp35-* pp* TWINE_USERNAME: qiskit CIBW_TEST_COMMAND: python {project}/examples/python/stochastic_swap.py steps: @@ -95,16 +95,16 @@ stages: condition: startsWith(variables['Build.SourceBranch'], 'refs/tags') variables: CIBW_BEFORE_BUILD: pip install -U Cython - CIBW_SKIP: cp27-* cp34-* pp* + CIBW_SKIP: cp27-* cp34-* cp35-* pp* TWINE_USERNAME: qiskit CIBW_TEST_COMMAND: python {project}\examples\python\stochastic_swap.py steps: - - {task: UsePythonVersion@0, inputs: {versionSpec: '3.5', architecture: x86}} - - {task: UsePythonVersion@0, inputs: {versionSpec: '3.5', architecture: x64}} - {task: UsePythonVersion@0, inputs: {versionSpec: '3.6', architecture: x86}} - {task: UsePythonVersion@0, inputs: {versionSpec: '3.6', architecture: x64}} - {task: UsePythonVersion@0, inputs: {versionSpec: '3.7', architecture: x86}} - {task: UsePythonVersion@0, inputs: {versionSpec: '3.7', architecture: x64}} + - {task: UsePythonVersion@0, inputs: {versionSpec: '3.8', architecture: x86}} + - {task: UsePythonVersion@0, inputs: {versionSpec: '3.8', architecture: x64}} - script: choco install vcpython27 -f -y displayName: Install Visual C++ for Python 2.7 - bash: | @@ -349,8 +349,6 @@ stages: pool: {vmImage: 'vs2017-win2016'} strategy: matrix: - Python35: - python.version: '3.5' Python36: python.version: '3.6' Python37: @@ -423,8 +421,6 @@ stages: matrix: Python36: python.version: '3.6' - Python35: - python.version: '3.5' Python38: python.version: '3.8' variables: @@ -494,8 +490,6 @@ stages: matrix: Python36: python.version: '3.6' - Python35: - python.version: '3.5' Python38: python.version: '3.8' variables: diff --git a/qiskit/__init__.py b/qiskit/__init__.py index cbf6d565b3df..dd34d09f4616 100644 --- a/qiskit/__init__.py +++ b/qiskit/__init__.py @@ -15,6 +15,7 @@ """Main Qiskit public functionality.""" + import pkgutil import sys import warnings @@ -82,11 +83,4 @@ from .version import _get_qiskit_versions # noqa -if sys.version_info[0] == 3 and sys.version_info[1] == 5: - warnings.warn( - "Using Qiskit with Python 3.5 is deprecated as of the 0.12.0 release. " - "Support for running Qiskit with Python 3.5 will be removed at the " - "Python 3.5 EoL on 09/13/2020.", DeprecationWarning) - - __qiskit_version__ = _get_qiskit_versions() diff --git a/qiskit/util.py b/qiskit/util.py index 711c4c070c82..4a711d13ed47 100644 --- a/qiskit/util.py +++ b/qiskit/util.py @@ -23,12 +23,6 @@ import psutil -def _check_python_version(): - """Check for Python version 3.5+.""" - if sys.version_info < (3, 5): - raise Exception('Qiskit requires Python version 3.5 or greater.') - - def _filter_deprecation_warnings(): """Apply filters to deprecation warnings. @@ -55,7 +49,6 @@ def _filter_deprecation_warnings(): pass -_check_python_version() _filter_deprecation_warnings() diff --git a/releasenotes/notes/drop-py3.5-86a65daa49903d46.yaml b/releasenotes/notes/drop-py3.5-86a65daa49903d46.yaml new file mode 100644 index 000000000000..6b725a38033c --- /dev/null +++ b/releasenotes/notes/drop-py3.5-86a65daa49903d46.yaml @@ -0,0 +1,7 @@ +--- +upgrade: + - | + The deprecated support for running qiskit-terra with Python 3.5 has been + removed. To use qiskit-terra >=0.16.0 you will now need at least Python 3.6. + If you are using Python 3.5 the last version which will work is Qiskit + 0.15.x. diff --git a/requirements-dev.txt b/requirements-dev.txt index c90083ce42b5..13be85ed5928 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -15,7 +15,7 @@ cython>=0.27.1 pylatexenc>=1.4 ddt>=1.2.0,!=1.4.0 seaborn>=0.9.0 -reno>=3.1.0;python_version>'3.5' +reno>=3.1.0 Sphinx>=1.8.3,<3.1.0 sphinx-rtd-theme>=0.4.0 sphinx-tabs>=1.1.11 diff --git a/requirements.txt b/requirements.txt index dcba1b03daf3..1901497fa822 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,7 +1,6 @@ contextvars>=2.4;python_version<'3.7' jsonschema>=2.6 -networkx>=2.2;python_version>'3.5' -networkx>=2.2,<2.4;python_version=='3.5' +networkx>=2.2 retworkx>=0.4.0 numpy>=1.17 ply>=3.10 diff --git a/setup.py b/setup.py index a400d98de61d..3ec4e5db9ebb 100755 --- a/setup.py +++ b/setup.py @@ -25,9 +25,7 @@ REQUIREMENTS = [ "contextvars>=2.4;python_version<'3.7'", "jsonschema>=2.6", - "networkx>=2.2;python_version>'3.5'", - # Networkx 2.4 is the final version with python 3.5 support. - "networkx>=2.2,<2.4;python_version=='3.5'", + "networkx>=2.2", "retworkx>=0.4.0", "numpy>=1.17", "ply>=3.10", @@ -96,7 +94,6 @@ "Operating System :: MacOS", "Operating System :: POSIX :: Linux", "Programming Language :: Python :: 3 :: Only", - "Programming Language :: Python :: 3.5", "Programming Language :: Python :: 3.6", "Programming Language :: Python :: 3.7", "Programming Language :: Python :: 3.8", @@ -107,7 +104,7 @@ install_requires=REQUIREMENTS, setup_requires=['Cython>=0.27.1'], include_package_data=True, - python_requires=">=3.5", + python_requires=">=3.6", extras_require={ 'visualization': ['matplotlib>=2.1', 'ipywidgets>=7.3.0', 'pydot', "pillow>=4.2.1", "pylatexenc>=1.4", diff --git a/tox.ini b/tox.ini index 343f90cebcba..2319749448ba 100644 --- a/tox.ini +++ b/tox.ini @@ -1,6 +1,6 @@ [tox] minversion = 2.1 -envlist = py35, py36, py37, py38, lint +envlist = py36, py37, py38, lint skipsdist = True [testenv] From ea618cbdc26d9f3819e6d4c0c6b8827ebddc0eb0 Mon Sep 17 00:00:00 2001 From: Matthew Treinish Date: Tue, 1 Sep 2020 20:41:58 -0400 Subject: [PATCH 09/17] Explicity set encoding of release notes via reno config (#5013) The recent reno release 3.2.0 included a new feature [1] for setting the character encoding that reno uses for all it's files. [2] This commit sets this option in the reno config file to make the release note files explicitly utf8. This is important (especially for windows users) because we have literal inlines of the text drawer in some release notes which use utf8 characters. This should avoid issues for users who's system encoding is not compatible with the text drawer output in the release notes. [1] https://opendev.org/openstack/reno/commit/984bcba17e4e0b46763f42015d09680e5c5d5a04 [2] https://docs.openstack.org/reno/latest/user/usage.html#configuring-reno --- releasenotes/config.yaml | 2 ++ requirements-dev.txt | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) create mode 100644 releasenotes/config.yaml diff --git a/releasenotes/config.yaml b/releasenotes/config.yaml new file mode 100644 index 000000000000..fa9f8f379e34 --- /dev/null +++ b/releasenotes/config.yaml @@ -0,0 +1,2 @@ +--- +encoding: utf8 diff --git a/requirements-dev.txt b/requirements-dev.txt index 13be85ed5928..729f2747d76f 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -15,7 +15,7 @@ cython>=0.27.1 pylatexenc>=1.4 ddt>=1.2.0,!=1.4.0 seaborn>=0.9.0 -reno>=3.1.0 +reno>=3.2.0 Sphinx>=1.8.3,<3.1.0 sphinx-rtd-theme>=0.4.0 sphinx-tabs>=1.1.11 From c8b53573989fee8ff7715247cdfe1330d6e8086b Mon Sep 17 00:00:00 2001 From: Kevin Krsulich Date: Wed, 2 Sep 2020 15:18:51 -0400 Subject: [PATCH 10/17] Fix parameterized Gate.definition to have valid ParameterTable. (#4945) Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com> --- qiskit/circuit/instruction.py | 2 +- qiskit/circuit/library/standard_gates/dcx.py | 4 +- qiskit/circuit/library/standard_gates/h.py | 8 +++- .../circuit/library/standard_gates/iswap.py | 4 +- qiskit/circuit/library/standard_gates/ms.py | 5 +-- qiskit/circuit/library/standard_gates/p.py | 2 +- qiskit/circuit/library/standard_gates/r.py | 4 +- qiskit/circuit/library/standard_gates/rx.py | 8 +++- qiskit/circuit/library/standard_gates/rxx.py | 4 +- qiskit/circuit/library/standard_gates/ry.py | 8 +++- qiskit/circuit/library/standard_gates/ryy.py | 4 +- qiskit/circuit/library/standard_gates/rz.py | 8 +++- qiskit/circuit/library/standard_gates/rzx.py | 4 +- qiskit/circuit/library/standard_gates/rzz.py | 4 +- qiskit/circuit/library/standard_gates/s.py | 8 +++- qiskit/circuit/library/standard_gates/swap.py | 8 +++- qiskit/circuit/library/standard_gates/t.py | 8 +++- qiskit/circuit/library/standard_gates/u1.py | 11 +++-- qiskit/circuit/library/standard_gates/u2.py | 4 +- qiskit/circuit/library/standard_gates/u3.py | 4 +- qiskit/circuit/library/standard_gates/x.py | 30 +++++++++---- qiskit/circuit/library/standard_gates/y.py | 8 +++- qiskit/circuit/library/standard_gates/z.py | 8 +++- qiskit/converters/ast_to_dag.py | 3 +- qiskit/converters/circuit_to_gate.py | 3 +- qiskit/converters/circuit_to_instruction.py | 3 +- ...plete-ParameterTable-a9becfb8aff7d906.yaml | 8 ++++ test/python/circuit/test_gate_definitions.py | 42 ++++++++++++++++++- 28 files changed, 169 insertions(+), 48 deletions(-) create mode 100644 releasenotes/notes/4863-Parameterized-gate-definitions-built-with-incomplete-ParameterTable-a9becfb8aff7d906.yaml diff --git a/qiskit/circuit/instruction.py b/qiskit/circuit/instruction.py index a1a45767941b..10767cdabd6d 100644 --- a/qiskit/circuit/instruction.py +++ b/qiskit/circuit/instruction.py @@ -383,6 +383,6 @@ def repeat(self, n): qc.add_register(qargs) if cargs: qc.add_register(cargs) - qc._data = [(self, qargs[:], cargs[:])] * n + qc.data = [(self, qargs[:], cargs[:])] * n instruction.definition = qc return instruction diff --git a/qiskit/circuit/library/standard_gates/dcx.py b/qiskit/circuit/library/standard_gates/dcx.py index f1f26b345706..24fcd6babffe 100644 --- a/qiskit/circuit/library/standard_gates/dcx.py +++ b/qiskit/circuit/library/standard_gates/dcx.py @@ -61,7 +61,9 @@ def _define(self): (CXGate(), [q[0], q[1]], []), (CXGate(), [q[1], q[0]], []) ] - qc._data = rules + for instr, qargs, cargs in rules: + qc._append(instr, qargs, cargs) + self.definition = qc def to_matrix(self): diff --git a/qiskit/circuit/library/standard_gates/h.py b/qiskit/circuit/library/standard_gates/h.py index 55213072987f..23585b88a354 100644 --- a/qiskit/circuit/library/standard_gates/h.py +++ b/qiskit/circuit/library/standard_gates/h.py @@ -63,7 +63,9 @@ def _define(self): rules = [ (U2Gate(0, pi), [q[0]], []) ] - qc._data = rules + for instr, qargs, cargs in rules: + qc._append(instr, qargs, cargs) + self.definition = qc def control(self, num_ctrl_qubits=1, label=None, ctrl_state=None): @@ -194,7 +196,9 @@ def _define(self): (HGate(), [q[1]], []), (SdgGate(), [q[1]], []) ] - qc._data = rules + for instr, qargs, cargs in rules: + qc._append(instr, qargs, cargs) + self.definition = qc def inverse(self): diff --git a/qiskit/circuit/library/standard_gates/iswap.py b/qiskit/circuit/library/standard_gates/iswap.py index 83b1127d911a..b20ce83c988b 100644 --- a/qiskit/circuit/library/standard_gates/iswap.py +++ b/qiskit/circuit/library/standard_gates/iswap.py @@ -105,7 +105,9 @@ def _define(self): (CXGate(), [q[1], q[0]], []), (HGate(), [q[1]], []) ] - qc._data = rules + for instr, qargs, cargs in rules: + qc._append(instr, qargs, cargs) + self.definition = qc def to_matrix(self): diff --git a/qiskit/circuit/library/standard_gates/ms.py b/qiskit/circuit/library/standard_gates/ms.py index 0918bfeaecda..e310f27412e6 100644 --- a/qiskit/circuit/library/standard_gates/ms.py +++ b/qiskit/circuit/library/standard_gates/ms.py @@ -40,9 +40,8 @@ def _define(self): theta = self.params[0] q = QuantumRegister(self.num_qubits, 'q') qc = QuantumCircuit(q, name=self.name) - rules = [] for i in range(self.num_qubits): for j in range(i + 1, self.num_qubits): - rules += [(RXXGate(theta), [q[i], q[j]], [])] - qc._data = rules + qc._append(RXXGate(theta), [q[i], q[j]], []) + self.definition = qc diff --git a/qiskit/circuit/library/standard_gates/p.py b/qiskit/circuit/library/standard_gates/p.py index c1596000555c..13702bfcf23d 100644 --- a/qiskit/circuit/library/standard_gates/p.py +++ b/qiskit/circuit/library/standard_gates/p.py @@ -259,7 +259,7 @@ def _define(self): scaled_lam = self.params[0] / (2 ** (self.num_ctrl_qubits - 1)) bottom_gate = CPhaseGate(scaled_lam) definition = _gray_code_chain(q, self.num_ctrl_qubits, bottom_gate) - qc._data = definition + qc.data = definition self.definition = qc def control(self, num_ctrl_qubits=1, label=None, ctrl_state=None): diff --git a/qiskit/circuit/library/standard_gates/r.py b/qiskit/circuit/library/standard_gates/r.py index 46613269f3d9..4b0adedb1b97 100644 --- a/qiskit/circuit/library/standard_gates/r.py +++ b/qiskit/circuit/library/standard_gates/r.py @@ -61,7 +61,9 @@ def _define(self): rules = [ (U3Gate(theta, phi - pi / 2, -phi + pi / 2), [q[0]], []) ] - qc._data = rules + for instr, qargs, cargs in rules: + qc._append(instr, qargs, cargs) + self.definition = qc def inverse(self): diff --git a/qiskit/circuit/library/standard_gates/rx.py b/qiskit/circuit/library/standard_gates/rx.py index 3bf6364b525c..dae8cf805d07 100644 --- a/qiskit/circuit/library/standard_gates/rx.py +++ b/qiskit/circuit/library/standard_gates/rx.py @@ -60,7 +60,9 @@ def _define(self): rules = [ (RGate(self.params[0], 0), [q[0]], []) ] - qc._data = rules + for instr, qargs, cargs in rules: + qc._append(instr, qargs, cargs) + self.definition = qc def control(self, num_ctrl_qubits=1, label=None, ctrl_state=None): @@ -181,7 +183,9 @@ def _define(self): (CXGate(), [q[0], q[1]], []), (U3Gate(self.params[0] / 2, -pi / 2, 0), [q[1]], []) ] - qc._data = rules + for instr, qargs, cargs in rules: + qc._append(instr, qargs, cargs) + self.definition = qc def inverse(self): diff --git a/qiskit/circuit/library/standard_gates/rxx.py b/qiskit/circuit/library/standard_gates/rxx.py index 3f162061e7d8..16ed34fdb110 100644 --- a/qiskit/circuit/library/standard_gates/rxx.py +++ b/qiskit/circuit/library/standard_gates/rxx.py @@ -89,7 +89,9 @@ def _define(self): (HGate(), [q[1]], []), (HGate(), [q[0]], []), ] - qc._data = rules + for instr, qargs, cargs in rules: + qc._append(instr, qargs, cargs) + self.definition = qc def inverse(self): diff --git a/qiskit/circuit/library/standard_gates/ry.py b/qiskit/circuit/library/standard_gates/ry.py index 9bbb007276dd..682f1a1143d1 100644 --- a/qiskit/circuit/library/standard_gates/ry.py +++ b/qiskit/circuit/library/standard_gates/ry.py @@ -60,7 +60,9 @@ def _define(self): rules = [ (RGate(self.params[0], pi / 2), [q[0]], []) ] - qc._data = rules + for instr, qargs, cargs in rules: + qc._append(instr, qargs, cargs) + self.definition = qc def control(self, num_ctrl_qubits=1, label=None, ctrl_state=None): @@ -176,7 +178,9 @@ def _define(self): (U3Gate(-self.params[0] / 2, 0, 0), [q[1]], []), (CXGate(), [q[0], q[1]], []) ] - qc._data = rules + for instr, qargs, cargs in rules: + qc._append(instr, qargs, cargs) + self.definition = qc def inverse(self): diff --git a/qiskit/circuit/library/standard_gates/ryy.py b/qiskit/circuit/library/standard_gates/ryy.py index e1953ef8ef39..fda1322b6110 100644 --- a/qiskit/circuit/library/standard_gates/ryy.py +++ b/qiskit/circuit/library/standard_gates/ryy.py @@ -91,7 +91,9 @@ def _define(self): (RXGate(-np.pi / 2), [q[0]], []), (RXGate(-np.pi / 2), [q[1]], []), ] - qc._data = rules + for instr, qargs, cargs in rules: + qc._append(instr, qargs, cargs) + self.definition = qc def inverse(self): diff --git a/qiskit/circuit/library/standard_gates/rz.py b/qiskit/circuit/library/standard_gates/rz.py index 05e8a3f7a929..fb08516afce4 100644 --- a/qiskit/circuit/library/standard_gates/rz.py +++ b/qiskit/circuit/library/standard_gates/rz.py @@ -71,7 +71,9 @@ def _define(self): rules = [ (U1Gate(theta), [q[0]], []) ] - qc._data = rules + for instr, qargs, cargs in rules: + qc._append(instr, qargs, cargs) + self.definition = qc def control(self, num_ctrl_qubits=1, label=None, ctrl_state=None): @@ -193,7 +195,9 @@ def _define(self): (U1Gate(-self.params[0] / 2), [q[1]], []), (CXGate(), [q[0], q[1]], []) ] - qc._data = rules + for instr, qargs, cargs in rules: + qc._append(instr, qargs, cargs) + self.definition = qc def inverse(self): diff --git a/qiskit/circuit/library/standard_gates/rzx.py b/qiskit/circuit/library/standard_gates/rzx.py index 8f0c6283d2c9..cfe9d7c5558a 100644 --- a/qiskit/circuit/library/standard_gates/rzx.py +++ b/qiskit/circuit/library/standard_gates/rzx.py @@ -135,7 +135,9 @@ def _define(self): (CXGate(), [q[0], q[1]], []), (HGate(), [q[1]], []) ] - qc._data = rules + for instr, qargs, cargs in rules: + qc._append(instr, qargs, cargs) + self.definition = qc def inverse(self): diff --git a/qiskit/circuit/library/standard_gates/rzz.py b/qiskit/circuit/library/standard_gates/rzz.py index 1ae5525db0e4..1c17685a382a 100644 --- a/qiskit/circuit/library/standard_gates/rzz.py +++ b/qiskit/circuit/library/standard_gates/rzz.py @@ -99,7 +99,9 @@ def _define(self): (RZGate(theta), [q[1]], []), (CXGate(), [q[0], q[1]], []) ] - qc._data = rules + for instr, qargs, cargs in rules: + qc._append(instr, qargs, cargs) + self.definition = qc def inverse(self): diff --git a/qiskit/circuit/library/standard_gates/s.py b/qiskit/circuit/library/standard_gates/s.py index f707c6739dae..9313265b4c4f 100644 --- a/qiskit/circuit/library/standard_gates/s.py +++ b/qiskit/circuit/library/standard_gates/s.py @@ -61,7 +61,9 @@ def _define(self): rules = [ (U1Gate(pi / 2), [q[0]], []) ] - qc._data = rules + for instr, qargs, cargs in rules: + qc._append(instr, qargs, cargs) + self.definition = qc def inverse(self): @@ -117,7 +119,9 @@ def _define(self): rules = [ (U1Gate(-pi / 2), [q[0]], []) ] - qc._data = rules + for instr, qargs, cargs in rules: + qc._append(instr, qargs, cargs) + self.definition = qc def inverse(self): diff --git a/qiskit/circuit/library/standard_gates/swap.py b/qiskit/circuit/library/standard_gates/swap.py index ac3abb5d6f26..74c92e202878 100644 --- a/qiskit/circuit/library/standard_gates/swap.py +++ b/qiskit/circuit/library/standard_gates/swap.py @@ -68,7 +68,9 @@ def _define(self): (CXGate(), [q[1], q[0]], []), (CXGate(), [q[0], q[1]], []) ] - qc._data = rules + for instr, qargs, cargs in rules: + qc._append(instr, qargs, cargs) + self.definition = qc def control(self, num_ctrl_qubits=1, label=None, ctrl_state=None): @@ -217,7 +219,9 @@ def _define(self): (CCXGate(), [q[0], q[1], q[2]], []), (CXGate(), [q[2], q[1]], []) ] - qc._data = rules + for instr, qargs, cargs in rules: + qc._append(instr, qargs, cargs) + self.definition = qc def inverse(self): diff --git a/qiskit/circuit/library/standard_gates/t.py b/qiskit/circuit/library/standard_gates/t.py index 6ceeba9d8eb2..648852249d70 100644 --- a/qiskit/circuit/library/standard_gates/t.py +++ b/qiskit/circuit/library/standard_gates/t.py @@ -62,7 +62,9 @@ def _define(self): rules = [ (U1Gate(pi / 4), [q[0]], []) ] - qc._data = rules + for instr, qargs, cargs in rules: + qc._append(instr, qargs, cargs) + self.definition = qc def inverse(self): @@ -118,7 +120,9 @@ def _define(self): rules = [ (U1Gate(-pi / 4), [q[0]], []) ] - qc._data = rules + for instr, qargs, cargs in rules: + qc._append(instr, qargs, cargs) + self.definition = qc def inverse(self): diff --git a/qiskit/circuit/library/standard_gates/u1.py b/qiskit/circuit/library/standard_gates/u1.py index b76db3e27fb4..267bca34a1cd 100644 --- a/qiskit/circuit/library/standard_gates/u1.py +++ b/qiskit/circuit/library/standard_gates/u1.py @@ -86,7 +86,9 @@ def _define(self): rules = [ (U3Gate(0, 0, self.params[0]), [q[0]], []) ] - qc._data = rules + for instr, qargs, cargs in rules: + qc._append(instr, qargs, cargs) + self.definition = qc def control(self, num_ctrl_qubits=1, label=None, ctrl_state=None): @@ -184,7 +186,9 @@ def _define(self): (CXGate(), [q[0], q[1]], []), (U1Gate(self.params[0] / 2), [q[1]], []) ] - qc._data = rules + for instr, qargs, cargs in rules: + qc._append(instr, qargs, cargs) + self.definition = qc def control(self, num_ctrl_qubits=1, label=None, ctrl_state=None): @@ -273,7 +277,8 @@ def _define(self): scaled_lam = self.params[0] / (2 ** (self.num_ctrl_qubits - 1)) bottom_gate = CU1Gate(scaled_lam) definition = _gray_code_chain(q, self.num_ctrl_qubits, bottom_gate) - qc._data = definition + for instr, qargs, cargs in definition: + qc._append(instr, qargs, cargs) self.definition = qc def control(self, num_ctrl_qubits=1, label=None, ctrl_state=None): diff --git a/qiskit/circuit/library/standard_gates/u2.py b/qiskit/circuit/library/standard_gates/u2.py index 1f901a88e40e..2a58677fc179 100644 --- a/qiskit/circuit/library/standard_gates/u2.py +++ b/qiskit/circuit/library/standard_gates/u2.py @@ -69,7 +69,9 @@ def _define(self): q = QuantumRegister(1, 'q') qc = QuantumCircuit(q, name=self.name) rules = [(U3Gate(pi / 2, self.params[0], self.params[1]), [q[0]], [])] - qc._data = rules + for instr, qargs, cargs in rules: + qc._append(instr, qargs, cargs) + self.definition = qc def inverse(self): diff --git a/qiskit/circuit/library/standard_gates/u3.py b/qiskit/circuit/library/standard_gates/u3.py index 3e4d1c7771d0..c26bf010ba1b 100644 --- a/qiskit/circuit/library/standard_gates/u3.py +++ b/qiskit/circuit/library/standard_gates/u3.py @@ -189,7 +189,9 @@ def _define(self): (CXGate(), [q[0], q[1]], []), (U3Gate(self.params[0] / 2, self.params[1], 0), [q[1]], []) ] - qc._data = rules + for instr, qargs, cargs in rules: + qc._append(instr, qargs, cargs) + self.definition = qc def inverse(self): diff --git a/qiskit/circuit/library/standard_gates/x.py b/qiskit/circuit/library/standard_gates/x.py index 1c3883c65757..39b26e28d8dc 100644 --- a/qiskit/circuit/library/standard_gates/x.py +++ b/qiskit/circuit/library/standard_gates/x.py @@ -84,7 +84,9 @@ def _define(self): rules = [ (U3Gate(pi, 0, pi), [q[0]], []) ] - qc._data = rules + for instr, qargs, cargs in rules: + qc._append(instr, qargs, cargs) + self.definition = qc def control(self, num_ctrl_qubits=1, label=None, ctrl_state=None): @@ -317,7 +319,9 @@ def _define(self): (TdgGate(), [q[1]], []), (CXGate(), [q[0], q[1]], []) ] - qc._data = rules + for instr, qargs, cargs in rules: + qc._append(instr, qargs, cargs) + self.definition = qc def control(self, num_ctrl_qubits=1, label=None, ctrl_state=None): @@ -396,7 +400,9 @@ def _define(self): (U1Gate(-pi / 4), [q[2]], []), # inverse T gate (U2Gate(0, pi), [q[2]], []), # H gate ] - qc._data = rules + for instr, qargs, cargs in rules: + qc._append(instr, qargs, cargs) + self.definition = qc def to_matrix(self): @@ -487,7 +493,9 @@ def _define(self): (HGate(), [q[3]], []) ] qc = QuantumCircuit(q) - qc._data = rules + for instr, qargs, cargs in rules: + qc._append(instr, qargs, cargs) + self.definition = qc def control(self, num_ctrl_qubits=1, label=None, ctrl_state=None): @@ -596,7 +604,9 @@ def _define(self): (U1Gate(-pi / 4), [q[3]], []), (U2Gate(0, pi), [q[3]], []), ] - qc._data = rules + for instr, qargs, cargs in rules: + qc._append(instr, qargs, cargs) + self.definition = qc def to_matrix(self): @@ -677,7 +687,9 @@ def _define(self): (C3XGate(), [q[0], q[1], q[2], q[3]], []), (C3XGate(numpy.pi / 8), [q[0], q[1], q[2], q[4]], []), ] - qc._data = rules + for instr, qargs, cargs in rules: + qc._append(instr, qargs, cargs) + self.definition = qc def control(self, num_ctrl_qubits=1, label=None, ctrl_state=None): @@ -841,8 +853,9 @@ def _define(self): qc._append(C4XGate(), q[:], []) self.definition = qc else: + for instr, qargs, cargs in self._recurse(q[:-1], q_ancilla=q[-1]): + qc._append(instr, qargs, cargs) self.definition = qc - self.definition._data = self._recurse(q[:-1], q_ancilla=q[-1]) def _recurse(self, q, q_ancilla=None): # recursion stop @@ -954,5 +967,6 @@ def _define(self): definition.append( (RCCXGate(), [q_controls[j], q_ancillas[i], q_ancillas[i + 1]], [])) - qc._data = definition + for instr, qargs, cargs in definition: + qc._append(instr, qargs, cargs) self.definition = qc diff --git a/qiskit/circuit/library/standard_gates/y.py b/qiskit/circuit/library/standard_gates/y.py index 84fe4f8a5c66..222eb718841b 100644 --- a/qiskit/circuit/library/standard_gates/y.py +++ b/qiskit/circuit/library/standard_gates/y.py @@ -76,7 +76,9 @@ def _define(self): rules = [ (U3Gate(pi, pi / 2, pi / 2), [q[0]], []) ] - qc._data = rules + for instr, qargs, cargs in rules: + qc._append(instr, qargs, cargs) + self.definition = qc def control(self, num_ctrl_qubits=1, label=None, ctrl_state=None): @@ -192,7 +194,9 @@ def _define(self): (CXGate(), [q[0], q[1]], []), (SGate(), [q[1]], []) ] - qc._data = rules + for instr, qargs, cargs in rules: + qc._append(instr, qargs, cargs) + self.definition = qc def inverse(self): diff --git a/qiskit/circuit/library/standard_gates/z.py b/qiskit/circuit/library/standard_gates/z.py index 4d4b86d547b3..66a1ce8bfe38 100644 --- a/qiskit/circuit/library/standard_gates/z.py +++ b/qiskit/circuit/library/standard_gates/z.py @@ -75,7 +75,9 @@ def _define(self): rules = [ (U1Gate(pi), [q[0]], []) ] - qc._data = rules + for instr, qargs, cargs in rules: + qc._append(instr, qargs, cargs) + self.definition = qc def control(self, num_ctrl_qubits=1, label=None, ctrl_state=None): @@ -159,7 +161,9 @@ def _define(self): (CXGate(), [q[0], q[1]], []), (HGate(), [q[1]], []) ] - qc._data = rules + for instr, qargs, cargs in rules: + qc._append(instr, qargs, cargs) + self.definition = qc def inverse(self): diff --git a/qiskit/converters/ast_to_dag.py b/qiskit/converters/ast_to_dag.py index 8c77ce208385..b99edfad1cc5 100644 --- a/qiskit/converters/ast_to_dag.py +++ b/qiskit/converters/ast_to_dag.py @@ -388,7 +388,8 @@ def _gate_rules_to_qiskit_circuit(self, node, params): op = self._create_op(child_op.name, params=eparams) rules.append((op, qparams, [])) circ = QuantumCircuit(qreg) - circ._data = rules + for instr, qargs, cargs in rules: + circ._append(instr, qargs, cargs) return circ def _create_dag_op(self, name, params, qargs): diff --git a/qiskit/converters/circuit_to_gate.py b/qiskit/converters/circuit_to_gate.py index 666d8838b9d7..9008e90fc089 100644 --- a/qiskit/converters/circuit_to_gate.py +++ b/qiskit/converters/circuit_to_gate.py @@ -101,6 +101,7 @@ def find_bit_position(bit): []), rules)) qc = QuantumCircuit(q, name=gate.name, global_phase=target.global_phase) - qc._data = rules + for instr, qargs, cargs in rules: + qc._append(instr, qargs, cargs) gate.definition = qc return gate diff --git a/qiskit/converters/circuit_to_instruction.py b/qiskit/converters/circuit_to_instruction.py index 33c1c7dec0f6..d564e4323428 100644 --- a/qiskit/converters/circuit_to_instruction.py +++ b/qiskit/converters/circuit_to_instruction.py @@ -121,7 +121,8 @@ def find_bit_position(bit): 'multiple classical registers to instruction') qc = QuantumCircuit(*regs, name=instruction.name) - qc._data = definition + for instr, qargs, cargs in definition: + qc._append(instr, qargs, cargs) if circuit.global_phase: qc.global_phase = circuit.global_phase diff --git a/releasenotes/notes/4863-Parameterized-gate-definitions-built-with-incomplete-ParameterTable-a9becfb8aff7d906.yaml b/releasenotes/notes/4863-Parameterized-gate-definitions-built-with-incomplete-ParameterTable-a9becfb8aff7d906.yaml new file mode 100644 index 000000000000..869808790afa --- /dev/null +++ b/releasenotes/notes/4863-Parameterized-gate-definitions-built-with-incomplete-ParameterTable-a9becfb8aff7d906.yaml @@ -0,0 +1,8 @@ +--- +fixes: + - | + When accessing the ``definition`` attribute of a parameterized ``Gate`` + instance, the generated ``QuantumCircuit`` had been generated with an invalid + ``ParameterTable``, such that reading from ``QuantumCircuit.parameters`` or + calling ``QuantumCircuit.bind_parameters`` would incorrectly report the + unbound parameters. This has been resolved. diff --git a/test/python/circuit/test_gate_definitions.py b/test/python/circuit/test_gate_definitions.py index e5e6ca4c26b1..6fa89dbf3965 100644 --- a/test/python/circuit/test_gate_definitions.py +++ b/test/python/circuit/test_gate_definitions.py @@ -13,15 +13,17 @@ """Test hardcoded decomposition rules and matrix definitions for standard gates.""" +import inspect + import numpy as np -from ddt import ddt, data +from ddt import ddt, data, unpack from qiskit import QuantumCircuit from qiskit.quantum_info import Operator from qiskit.test import QiskitTestCase from qiskit.circuit import ParameterVector, Gate, ControlledGate - +from qiskit.circuit.library import standard_gates from qiskit.circuit.library import ( HGate, CHGate, IGate, RGate, RXGate, CRXGate, RYGate, CRYGate, RZGate, CRZGate, SGate, SdgGate, CSwapGate, TGate, TdgGate, U1Gate, CU1Gate, @@ -115,6 +117,42 @@ def test_cx_definition(self): self.assertTrue(Operator(circ).equiv(Operator(decomposed_circ))) +@ddt +class TestStandardGates(QiskitTestCase): + """Standard Extension Test.""" + + @unpack + @data( + *inspect.getmembers( + standard_gates, + predicate=lambda value: (inspect.isclass(value) + and issubclass(value, Gate))) + ) + def test_definition_parameters(self, class_name, gate_class): + """Verify definitions from standard library include correct parameters.""" + + free_params = _get_free_params(gate_class) + n_params = len(free_params) + param_vector = ParameterVector('th', n_params) + + if class_name in ('MCPhaseGate', 'MCU1Gate'): + param_vector = param_vector[:-1] + gate = gate_class(*param_vector, num_ctrl_qubits=2) + elif class_name in ('MCXGate', 'MCXGrayCode', 'MCXRecursive', 'MCXVChain'): + num_ctrl_qubits = 2 + param_vector = param_vector[:-1] + gate = gate_class(num_ctrl_qubits, *param_vector) + elif class_name == 'MSGate': + num_qubits = 2 + param_vector = param_vector[:-1] + gate = gate_class(num_qubits, *param_vector) + else: + gate = gate_class(*param_vector) + + if gate.definition is not None: + self.assertEqual(gate.definition.parameters, set(param_vector)) + + class TestGateEquivalenceEqual(QiskitTestCase): """Test the decomposition of a gate in terms of other gates yields the same matrix as the hardcoded matrix definition.""" From 5338dbf049dc2e32e7e098a578440fd3d2dfa245 Mon Sep 17 00:00:00 2001 From: Andrew Meyer Date: Wed, 2 Sep 2020 14:53:23 -0700 Subject: [PATCH 11/17] Allow ParameterExpressions to be cast to ints (#5001) - Resolves #4978 Co-authored-by: Kevin Krsulich --- qiskit/circuit/parameterexpression.py | 6 +++ test/python/circuit/test_parameters.py | 57 ++++++++++++++++++++++---- 2 files changed, 54 insertions(+), 9 deletions(-) diff --git a/qiskit/circuit/parameterexpression.py b/qiskit/circuit/parameterexpression.py index 8ea7d6e196ed..9146e8065692 100644 --- a/qiskit/circuit/parameterexpression.py +++ b/qiskit/circuit/parameterexpression.py @@ -249,6 +249,12 @@ def __float__(self): 'cannot be cast to a float.'.format(self.parameters)) return float(self._symbol_expr) + def __int__(self): + if self.parameters: + raise TypeError('ParameterExpression with unbound parameters ({}) ' + 'cannot be cast to an int.'.format(self.parameters)) + return int(self._symbol_expr) + def __copy__(self): return self diff --git a/test/python/circuit/test_parameters.py b/test/python/circuit/test_parameters.py index cfd286a32ba3..6613624818f5 100644 --- a/test/python/circuit/test_parameters.py +++ b/test/python/circuit/test_parameters.py @@ -13,26 +13,24 @@ """Test circuits with variable parameters.""" import pickle -from operator import add, sub, mul, truediv - +from operator import add, mul, sub, truediv from test import combine -import numpy -from ddt import ddt, data +import numpy +from ddt import data, ddt import qiskit import qiskit.circuit.library as circlib -from qiskit import BasicAer -from qiskit import QuantumRegister, ClassicalRegister, QuantumCircuit -from qiskit.circuit import Gate, Instruction -from qiskit.circuit import Parameter, ParameterVector, ParameterExpression +from qiskit import BasicAer, ClassicalRegister, QuantumCircuit, QuantumRegister +from qiskit.circuit import (Gate, Instruction, Parameter, ParameterExpression, + ParameterVector) from qiskit.circuit.exceptions import CircuitError from qiskit.compiler import assemble, transpile from qiskit.execute import execute +from qiskit.quantum_info import Operator from qiskit.test import QiskitTestCase from qiskit.test.mock import FakeOurense from qiskit.tools import parallel_map -from qiskit.quantum_info import Operator def raise_if_parameter_table_invalid(circuit): # pylint: disable=invalid-name @@ -794,6 +792,47 @@ class TestParameterExpressions(QiskitTestCase): supported_operations = [add, sub, mul, truediv] + def test_cast_to_float_when_bound(self): + """Verify expression can be cast to a float when fully bound.""" + + x = Parameter('x') + bound_expr = x.bind({x: 2.3}) + self.assertEqual(float(bound_expr), 2.3) + + def test_raise_if_cast_to_float_when_not_fully_bound(self): + """Verify raises if casting to float and not fully bound.""" + + x = Parameter('x') + y = Parameter('y') + bound_expr = (x + y).bind({x: 2.3}) + with self.assertRaisesRegex(TypeError, 'unbound parameters'): + float(bound_expr) + + def test_cast_to_int_when_bound(self): + """Verify expression can be cast to an int when fully bound.""" + + x = Parameter('x') + bound_expr = x.bind({x: 2.3}) + self.assertEqual(int(bound_expr), 2) + + def test_cast_to_int_when_bound_truncates_after_evaluation(self): + """Verify expression can be cast to an int when fully bound, but + truncated only after evaluation.""" + + x = Parameter('x') + y = Parameter('y') + bound_expr = (x + y).bind({x: 2.3, y: 0.8}) + self.assertEqual(int(bound_expr), 3) + + def test_raise_if_cast_to_int_when_not_fully_bound(self): + """Verify raises if casting to int and not fully bound.""" + + x = Parameter('x') + y = Parameter('y') + bound_expr = (x + y).bind({x: 2.3}) + with self.assertRaisesRegex(TypeError, 'unbound parameters'): + int(bound_expr) + def test_raise_if_sub_unknown_parameters(self): """Verify we raise if asked to sub a parameter not in self.""" x = Parameter('x') From 319f527d4febc94c1e02f1adeaa470b1443594ed Mon Sep 17 00:00:00 2001 From: Matthew Treinish Date: Wed, 2 Sep 2020 18:47:51 -0400 Subject: [PATCH 12/17] Add tutorials job to CI (#4907) * Add tutorials job to CI This commit adds a new job to run the qiskit tutorials in CI. The tutorials are used as a form of upgrade testing to ensure that for key examples Qiskit as a whole is N and N+1 releases. This caused friction during the qiskit 0.20.0 release because the tutorials were never updated to stop using deprecated code (or in the case of other elements by merging backwards incompatible changes). To ensure there aren't any surprises at the last minute when we run the tutorials with a proposed new metapackage this commit adds a job to CI to ensure that they always run with terra changes. It means for PRs that change an api (after a deprecation cycle) the tutorial will need to be updated first. This will ensure that users will have an upgrade path because CI in qiskit/qiskit-tutorials runs with the metapackage. * Install pandoc in tutorials job * Remove aqua tutorials from ci job for run time * Fix rm path and speed up tutorials clone * Try pinning matplotlib * Try fixing archive path * Add back aqua tutorials Co-authored-by: Luciano Bello Co-authored-by: Kevin Krsulich --- azure-pipelines.yml | 50 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 50 insertions(+) diff --git a/azure-pipelines.yml b/azure-pipelines.yml index c81724e0afc6..551d0fde74e4 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -552,3 +552,53 @@ stages: inputs: testResultsFiles: '**/test-*.xml' testRunTitle: 'Test results for macOS Python $(python.version)' + - job: 'Tutorials' + pool: {vmImage: 'ubuntu-latest'} + strategy: + matrix: + Python38: + python.version: '3.8' + variables: + QISKIT_SUPPRESS_PACKAGING_WARNINGS: Y + PIP_CACHE_DIR: $(Pipeline.Workspace)/.pip + steps: + - task: UsePythonVersion@0 + inputs: + versionSpec: '$(python.version)' + displayName: 'Use Python $(python.version)' + - task: Cache@2 + inputs: + key: 'pip | "$(Agent.OS)" | "$(python.version)"' + restoreKeys: | + pip | "$(Agent.OS)" + pip + path: $(PIP_CACHE_DIR) + displayName: Cache pip + - bash: | + set -e + git clone https://github.com/Qiskit/qiskit-tutorials --depth=1 + python -m pip install --upgrade pip + pip install -U -r requirements.txt -r requirements-dev.txt -c constraints.txt + pip install -c constraints.txt -e . + pip install "qiskit-ibmq-provider" "qiskit-aer" "z3-solver" "qiskit-ignis" "qiskit-aqua" "pyscf<1.7.4" "matplotlib<3.3.0" sphinx nbsphinx sphinx_rtd_theme cvxpy -c constraints.txt + python setup.py build_ext --inplace + sudo apt install -y graphviz pandoc + pip check + displayName: 'Install dependencies' + - bash: | + set -e + cd qiskit-tutorials + sphinx-build -b html . _build/html + - task: ArchiveFiles@2 + inputs: + rootFolderOrFile: 'qiskit-tutorials/_build/html' + archiveType: tar + archiveFile: '$(Build.ArtifactStagingDirectory)/html_tutorials.tar.gz' + verbose: true + - task: PublishBuildArtifacts@1 + displayName: 'Publish docs' + inputs: + pathtoPublish: '$(Build.ArtifactStagingDirectory)' + artifactName: 'html_tutorials' + Parallel: true + ParallelCount: 8 From 503eecec958cf6cbc699d2641bc524edfc618c9a Mon Sep 17 00:00:00 2001 From: Raul Otaolea Date: Thu, 3 Sep 2020 01:53:57 +0200 Subject: [PATCH 13/17] Fix/5015 QuantumCircuit __eq__ should always return a Bool (#5016) * Check if circuit is an instance of QuantumObject before comparing it Added a few tests * Fixed lint error Co-authored-by: Kevin Krsulich Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com> --- qiskit/circuit/quantumcircuit.py | 3 ++ .../python/circuit/test_circuit_operations.py | 32 +++++++++++++++++++ 2 files changed, 35 insertions(+) diff --git a/qiskit/circuit/quantumcircuit.py b/qiskit/circuit/quantumcircuit.py index be7c16795398..73ee2ef8531d 100644 --- a/qiskit/circuit/quantumcircuit.py +++ b/qiskit/circuit/quantumcircuit.py @@ -220,6 +220,9 @@ def __str__(self): return str(self.draw(output='text')) def __eq__(self, other): + if not isinstance(other, QuantumCircuit): + return False + # TODO: remove the DAG from this function from qiskit.converters import circuit_to_dag return circuit_to_dag(self) == circuit_to_dag(other) diff --git a/test/python/circuit/test_circuit_operations.py b/test/python/circuit/test_circuit_operations.py index 1ab87ef09206..c9a7217e4fb5 100644 --- a/test/python/circuit/test_circuit_operations.py +++ b/test/python/circuit/test_circuit_operations.py @@ -587,6 +587,38 @@ def test_inverse(self): expected.global_phase = -0.5 self.assertEqual(qc.inverse(), expected) + def test_compare_two_equal_circuits(self): + """Test to compare that 2 circuits are equal. + """ + qc1 = QuantumCircuit(2, 2) + qc1.h(0) + + qc2 = QuantumCircuit(2, 2) + qc2.h(0) + + self.assertTrue(qc1 == qc2) + + def test_compare_two_different_circuits(self): + """Test to compare that 2 circuits are different. + """ + qc1 = QuantumCircuit(2, 2) + qc1.h(0) + + qc2 = QuantumCircuit(2, 2) + qc2.x(0) + + self.assertFalse(qc1 == qc2) + + def test_compare_a_circuit_with_none(self): + """Test to compare that a circuit is different to None. + """ + qc1 = QuantumCircuit(2, 2) + qc1.h(0) + + qc2 = None + + self.assertFalse(qc1 == qc2) + class TestCircuitBuilding(QiskitTestCase): """QuantumCircuit tests.""" From 9977e7f81f6bfe84bd4f64c2446f8e02aa8a8ceb Mon Sep 17 00:00:00 2001 From: Tanya Garg <62295887+tgag17@users.noreply.github.com> Date: Thu, 3 Sep 2020 06:37:25 +0530 Subject: [PATCH 14/17] fixed issue 4936 by updating the docstring (#5014) Co-authored-by: Luciano Bello Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com> --- qiskit/transpiler/passes/routing/sabre_swap.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/qiskit/transpiler/passes/routing/sabre_swap.py b/qiskit/transpiler/passes/routing/sabre_swap.py index 6b60b2f6baf9..59954b86bf88 100644 --- a/qiskit/transpiler/passes/routing/sabre_swap.py +++ b/qiskit/transpiler/passes/routing/sabre_swap.py @@ -100,8 +100,8 @@ def __init__(self, coupling_map, heuristic='basic', seed=None): .. math:: - H_{decay} = \frac{1}{\abs{F}} \sum_{gate \in F} D[\pi(gate.q_1)][\pi(gate.q2)] - + W * \frac{1}{\abs{E}} \sum_{gate \in E} D[\pi(gate.q_1)][\pi(gate.q2)] + H_{decay}=\frac{1}{\left|{F}\right|}\sum_{gate \in F} D[\pi(gate.q_1)][\pi(gate.q2)] + + W*\frac{1}{\left|{E}\right|} \sum_{gate \in E} D[\pi(gate.q_1)][\pi(gate.q2)] - 'decay': @@ -112,8 +112,8 @@ def __init__(self, coupling_map, heuristic='basic', seed=None): .. math:: H_{decay} = max(decay(SWAP.q_1), decay(SWAP.q_2)) { - \frac{1}{\abs{F}} \sum_{gate \in F} D[\pi(gate.q_1)][\pi(gate.q2)] - + W * \frac{1}{\abs{E}} \sum_{gate \in E} D[\pi(gate.q_1)][\pi(gate.q2)] + \frac{1}{\left|{F}\right|} \sum_{gate \in F} D[\pi(gate.q_1)][\pi(gate.q2)]\\ + + W *\frac{1}{\left|{E}\right|} \sum_{gate \in E} D[\pi(gate.q_1)][\pi(gate.q2)] } """ From c595cda6359015d60fa3f7df7fffb1cf9a1039ec Mon Sep 17 00:00:00 2001 From: E W Date: Tue, 22 Sep 2020 13:23:52 -0400 Subject: [PATCH 15/17] fix bux in optimize_1q_gates --- .../passes/optimization/optimize_1q_gates.py | 5 ++--- test/python/transpiler/test_optimize_1q_gates.py | 13 +++++++++++++ 2 files changed, 15 insertions(+), 3 deletions(-) diff --git a/qiskit/transpiler/passes/optimization/optimize_1q_gates.py b/qiskit/transpiler/passes/optimization/optimize_1q_gates.py index d624bb822a7c..2f1af8428606 100644 --- a/qiskit/transpiler/passes/optimization/optimize_1q_gates.py +++ b/qiskit/transpiler/passes/optimization/optimize_1q_gates.py @@ -209,9 +209,8 @@ def run(self, dag): else: raise TranspilerError('It was not possible to use the basis %s' % self.basis) - # safer to keep phase on op instead of dag (e.g. if removed)? - if new_op.definition is not None: - new_op.definition.global_phase = right_global_phase + dag.global_phase += right_global_phase + if right_name != 'nop': dag.substitute_node(run[0], new_op, inplace=True) diff --git a/test/python/transpiler/test_optimize_1q_gates.py b/test/python/transpiler/test_optimize_1q_gates.py index 3944643bfefb..bd9bf29875da 100644 --- a/test/python/transpiler/test_optimize_1q_gates.py +++ b/test/python/transpiler/test_optimize_1q_gates.py @@ -221,6 +221,19 @@ def test_parameterized_expressions_in_circuits(self): self.assertEqual(circuit_to_dag(expected), after) + def test_global_phase_u3_on_left(self): + 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(dag.global_phase, 5 * np.pi / 6, 8) class TestOptimize1qGatesParamReduction(QiskitTestCase): """Test for 1q gate optimizations parameter reduction, reduce n in Un """ From b0cdc4cbe61437ca85154e95f9343fb170e3d05b Mon Sep 17 00:00:00 2001 From: E W Date: Tue, 22 Sep 2020 15:20:06 -0400 Subject: [PATCH 16/17] minor linting --- qiskit/transpiler/passes/optimization/optimize_1q_gates.py | 2 +- test/python/transpiler/test_optimize_1q_gates.py | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/qiskit/transpiler/passes/optimization/optimize_1q_gates.py b/qiskit/transpiler/passes/optimization/optimize_1q_gates.py index 2f1af8428606..0b6bb8af831a 100644 --- a/qiskit/transpiler/passes/optimization/optimize_1q_gates.py +++ b/qiskit/transpiler/passes/optimization/optimize_1q_gates.py @@ -210,7 +210,7 @@ def run(self, dag): 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/test/python/transpiler/test_optimize_1q_gates.py b/test/python/transpiler/test_optimize_1q_gates.py index bd9bf29875da..e3b32f5cc313 100644 --- a/test/python/transpiler/test_optimize_1q_gates.py +++ b/test/python/transpiler/test_optimize_1q_gates.py @@ -235,6 +235,7 @@ def test_global_phase_u3_on_left(self): after = Optimize1qGates().run(dag) self.assertAlmostEqual(dag.global_phase, 5 * np.pi / 6, 8) + class TestOptimize1qGatesParamReduction(QiskitTestCase): """Test for 1q gate optimizations parameter reduction, reduce n in Un """ From 6d3c050c1ead5438f0252b476098ce8a20517d59 Mon Sep 17 00:00:00 2001 From: E W Date: Wed, 23 Sep 2020 05:46:13 -0400 Subject: [PATCH 17/17] fix test --- test/python/transpiler/test_optimize_1q_gates.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/test/python/transpiler/test_optimize_1q_gates.py b/test/python/transpiler/test_optimize_1q_gates.py index e3b32f5cc313..4d8d010f5012 100644 --- a/test/python/transpiler/test_optimize_1q_gates.py +++ b/test/python/transpiler/test_optimize_1q_gates.py @@ -222,6 +222,7 @@ 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) @@ -233,7 +234,7 @@ def test_global_phase_u3_on_left(self): dag = circuit_to_dag(qc) after = Optimize1qGates().run(dag) - self.assertAlmostEqual(dag.global_phase, 5 * np.pi / 6, 8) + self.assertAlmostEqual(after.global_phase, 5 * np.pi / 6, 8) class TestOptimize1qGatesParamReduction(QiskitTestCase):