diff --git a/qiskit/circuit/quantumcircuit.py b/qiskit/circuit/quantumcircuit.py index ebc63511ca7b..566a6fef9f70 100644 --- a/qiskit/circuit/quantumcircuit.py +++ b/qiskit/circuit/quantumcircuit.py @@ -1152,17 +1152,41 @@ def __init__( """The unit that :attr:`duration` is specified in.""" self.metadata = {} if metadata is None else metadata """Arbitrary user-defined metadata for the circuit. - + Qiskit will not examine the content of this mapping, but it will pass it through the transpiler and reattach it to the output, so you can track your own metadata.""" @classmethod - def _from_circuit_data(cls, data: CircuitData) -> typing.Self: + def _from_circuit_data(cls, data: CircuitData, add_regs: bool = False) -> typing.Self: """A private constructor from rust space circuit data.""" out = QuantumCircuit() + + if data.num_qubits > 0: + if add_regs: + qr = QuantumRegister(name="q", bits=data.qubits) + out.qregs = [qr] + out._qubit_indices = { + bit: BitLocations(index, [(qr, index)]) for index, bit in enumerate(data.qubits) + } + else: + out._qubit_indices = { + bit: BitLocations(index, []) for index, bit in enumerate(data.qubits) + } + + if data.num_clbits > 0: + if add_regs: + cr = ClassicalRegister(name="c", bits=data.clbits) + out.cregs = [cr] + out._clbit_indices = { + bit: BitLocations(index, [(cr, index)]) for index, bit in enumerate(data.clbits) + } + else: + out._clbit_indices = { + bit: BitLocations(index, []) for index, bit in enumerate(data.clbits) + } + out._data = data - out._qubit_indices = {bit: BitLocations(index, []) for index, bit in enumerate(data.qubits)} - out._clbit_indices = {bit: BitLocations(index, []) for index, bit in enumerate(data.clbits)} + return out @staticmethod @@ -3013,16 +3037,7 @@ def add_register(self, *regs: Register | int | Sequence[Bit]) -> None: self._ancillas.append(bit) if isinstance(register, QuantumRegister): - self.qregs.append(register) - - for idx, bit in enumerate(register): - if bit in self._qubit_indices: - self._qubit_indices[bit].registers.append((register, idx)) - else: - self._data.add_qubit(bit) - self._qubit_indices[bit] = BitLocations( - self._data.num_qubits - 1, [(register, idx)] - ) + self._add_qreg(register) elif isinstance(register, ClassicalRegister): self.cregs.append(register) @@ -3041,6 +3056,16 @@ def add_register(self, *regs: Register | int | Sequence[Bit]) -> None: else: raise CircuitError("expected a register") + def _add_qreg(self, qreg: QuantumRegister) -> None: + self.qregs.append(qreg) + + for idx, bit in enumerate(qreg): + if bit in self._qubit_indices: + self._qubit_indices[bit].registers.append((qreg, idx)) + else: + self._data.add_qubit(bit) + self._qubit_indices[bit] = BitLocations(self._data.num_qubits - 1, [(qreg, idx)]) + def add_bits(self, bits: Iterable[Bit]) -> None: """Add Bits to the circuit.""" duplicate_bits = { diff --git a/qiskit/synthesis/clifford/clifford_decompose_bm.py b/qiskit/synthesis/clifford/clifford_decompose_bm.py index ac4923446bcd..5ead6945eba2 100644 --- a/qiskit/synthesis/clifford/clifford_decompose_bm.py +++ b/qiskit/synthesis/clifford/clifford_decompose_bm.py @@ -41,7 +41,7 @@ def synth_clifford_bm(clifford: Clifford) -> QuantumCircuit: `arXiv:2003.09412 [quant-ph] `_ """ circuit = QuantumCircuit._from_circuit_data( - synth_clifford_bm_inner(clifford.tableau.astype(bool)) + synth_clifford_bm_inner(clifford.tableau.astype(bool)), add_regs=True ) circuit.name = str(clifford) return circuit diff --git a/qiskit/synthesis/clifford/clifford_decompose_greedy.py b/qiskit/synthesis/clifford/clifford_decompose_greedy.py index 0a679a8a7a6f..9766efe4aa8f 100644 --- a/qiskit/synthesis/clifford/clifford_decompose_greedy.py +++ b/qiskit/synthesis/clifford/clifford_decompose_greedy.py @@ -51,7 +51,7 @@ def synth_clifford_greedy(clifford: Clifford) -> QuantumCircuit: `arXiv:2105.02291 [quant-ph] `_ """ circuit = QuantumCircuit._from_circuit_data( - synth_clifford_greedy_inner(clifford.tableau.astype(bool)) + synth_clifford_greedy_inner(clifford.tableau.astype(bool)), add_regs=True ) circuit.name = str(clifford) return circuit diff --git a/qiskit/synthesis/linear/cnot_synth.py b/qiskit/synthesis/linear/cnot_synth.py index f900ff86d17e..bb3d9c6896ff 100644 --- a/qiskit/synthesis/linear/cnot_synth.py +++ b/qiskit/synthesis/linear/cnot_synth.py @@ -66,4 +66,4 @@ def synth_cnot_count_full_pmh( circuit_data = fast_pmh(normalized, section_size) # construct circuit from the data - return QuantumCircuit._from_circuit_data(circuit_data) + return QuantumCircuit._from_circuit_data(circuit_data, add_regs=True) diff --git a/qiskit/synthesis/linear_phase/cz_depth_lnn.py b/qiskit/synthesis/linear_phase/cz_depth_lnn.py index 419aec806f2e..d7dd071956e0 100644 --- a/qiskit/synthesis/linear_phase/cz_depth_lnn.py +++ b/qiskit/synthesis/linear_phase/cz_depth_lnn.py @@ -53,4 +53,6 @@ def synth_cz_depth_line_mr(mat: np.ndarray) -> QuantumCircuit: """ # Call Rust implementaton - return QuantumCircuit._from_circuit_data(synth_cz_depth_line_mr_inner(mat.astype(bool))) + return QuantumCircuit._from_circuit_data( + synth_cz_depth_line_mr_inner(mat.astype(bool)), add_regs=True + ) diff --git a/qiskit/synthesis/one_qubit/one_qubit_decompose.py b/qiskit/synthesis/one_qubit/one_qubit_decompose.py index f60f20f9524e..60da6ed6e82b 100644 --- a/qiskit/synthesis/one_qubit/one_qubit_decompose.py +++ b/qiskit/synthesis/one_qubit/one_qubit_decompose.py @@ -224,7 +224,8 @@ def _decompose(self, unitary, simplify=True, atol=DEFAULT_ATOL): return QuantumCircuit._from_circuit_data( euler_one_qubit_decomposer.unitary_to_circuit( unitary, [self.basis], 0, None, simplify, atol - ) + ), + add_regs=True, ) @property diff --git a/qiskit/synthesis/permutation/permutation_full.py b/qiskit/synthesis/permutation/permutation_full.py index 2fd892a0427e..7dd5ae99dc4c 100644 --- a/qiskit/synthesis/permutation/permutation_full.py +++ b/qiskit/synthesis/permutation/permutation_full.py @@ -42,7 +42,7 @@ def synth_permutation_basic(pattern: list[int] | np.ndarray[int]) -> QuantumCirc Returns: The synthesized quantum circuit. """ - return QuantumCircuit._from_circuit_data(_synth_permutation_basic(pattern)) + return QuantumCircuit._from_circuit_data(_synth_permutation_basic(pattern), add_regs=True) def synth_permutation_acg(pattern: list[int] | np.ndarray[int]) -> QuantumCircuit: @@ -75,4 +75,4 @@ def synth_permutation_acg(pattern: list[int] | np.ndarray[int]) -> QuantumCircui *Routing Permutations on Graphs Via Matchings.*, `(Full paper) `_ """ - return QuantumCircuit._from_circuit_data(_synth_permutation_acg(pattern)) + return QuantumCircuit._from_circuit_data(_synth_permutation_acg(pattern), add_regs=True) diff --git a/qiskit/synthesis/permutation/permutation_lnn.py b/qiskit/synthesis/permutation/permutation_lnn.py index d5da6929463c..5f1bfbeaa1a8 100644 --- a/qiskit/synthesis/permutation/permutation_lnn.py +++ b/qiskit/synthesis/permutation/permutation_lnn.py @@ -49,4 +49,6 @@ def synth_permutation_depth_lnn_kms(pattern: list[int] | np.ndarray[int]) -> Qua # In the permutation synthesis code below the notation is opposite: # [2, 4, 3, 0, 1] means that 0 maps to 2, 1 to 3, 2 to 3, 3 to 0, and 4 to 1. # This is why we invert the pattern. - return QuantumCircuit._from_circuit_data(_synth_permutation_depth_lnn_kms(pattern)) + return QuantumCircuit._from_circuit_data( + _synth_permutation_depth_lnn_kms(pattern), add_regs=True + ) diff --git a/qiskit/synthesis/permutation/permutation_reverse_lnn.py b/qiskit/synthesis/permutation/permutation_reverse_lnn.py index f214fd7ce294..ccc820d97e62 100644 --- a/qiskit/synthesis/permutation/permutation_reverse_lnn.py +++ b/qiskit/synthesis/permutation/permutation_reverse_lnn.py @@ -88,4 +88,6 @@ def synth_permutation_reverse_lnn_kms(num_qubits: int) -> QuantumCircuit: """ # Call Rust implementation - return QuantumCircuit._from_circuit_data(synth_permutation_reverse_lnn_kms_inner(num_qubits)) + return QuantumCircuit._from_circuit_data( + synth_permutation_reverse_lnn_kms_inner(num_qubits), add_regs=True + ) diff --git a/qiskit/synthesis/two_qubit/two_qubit_decompose.py b/qiskit/synthesis/two_qubit/two_qubit_decompose.py index 633de3a64f78..0e3427773387 100644 --- a/qiskit/synthesis/two_qubit/two_qubit_decompose.py +++ b/qiskit/synthesis/two_qubit/two_qubit_decompose.py @@ -233,7 +233,7 @@ def circuit( circuit_data = self._inner_decomposition.circuit( euler_basis=euler_basis, simplify=simplify, atol=atol ) - return QuantumCircuit._from_circuit_data(circuit_data) + return QuantumCircuit._from_circuit_data(circuit_data, add_regs=True) def actual_fidelity(self, **kwargs) -> float: """Calculates the actual fidelity of the decomposed circuit to the input unitary.""" @@ -672,7 +672,7 @@ def __call__( approximate, _num_basis_uses=_num_basis_uses, ) - return QuantumCircuit._from_circuit_data(circ_data) + return QuantumCircuit._from_circuit_data(circ_data, add_regs=True) else: sequence = self._inner_decomposer( np.asarray(unitary, dtype=complex), diff --git a/releasenotes/notes/fix-synth-qregs-7662681c0ff02511.yaml b/releasenotes/notes/fix-synth-qregs-7662681c0ff02511.yaml new file mode 100644 index 000000000000..d838032ffaf9 --- /dev/null +++ b/releasenotes/notes/fix-synth-qregs-7662681c0ff02511.yaml @@ -0,0 +1,7 @@ +--- +fixes: + - | + Fixed a bug where various synthesis methods created circuits without quantum or + classical registers. This also affected functions that internally used the synthesis + methods, such as :meth:`.Clifford.to_circuit`. + Fixed `#13041 `__. diff --git a/test/python/quantum_info/operators/symplectic/test_clifford.py b/test/python/quantum_info/operators/symplectic/test_clifford.py index 061348c78cdb..e78feaf3b78f 100644 --- a/test/python/quantum_info/operators/symplectic/test_clifford.py +++ b/test/python/quantum_info/operators/symplectic/test_clifford.py @@ -598,6 +598,23 @@ def test_to_circuit(self, num_qubits): # Convert back to clifford and check it is the same self.assertEqual(Clifford(decomp), target) + def test_to_circuit_manual(self): + """Test a manual comparison to a known circuit. + + This also tests whether the resulting Clifford circuit has quantum registers, thereby + regression testing #13041. + """ + # this is set to a circuit that remains the same under Clifford reconstruction + circuit = QuantumCircuit(2) + circuit.z(0) + circuit.h(0) + circuit.cx(0, 1) + + cliff = Clifford(circuit) + reconstructed = cliff.to_circuit() + + self.assertEqual(circuit, reconstructed) + @combine(num_qubits=[1, 2, 3, 4, 5]) def test_to_instruction(self, num_qubits): """Test to_instruction method"""