Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix creation of registers in synthesis methods #13086

Merged
merged 6 commits into from
Sep 10, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
53 changes: 39 additions & 14 deletions qiskit/circuit/quantumcircuit.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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)
Expand All @@ -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 = {
Expand Down
2 changes: 1 addition & 1 deletion qiskit/synthesis/clifford/clifford_decompose_bm.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ def synth_clifford_bm(clifford: Clifford) -> QuantumCircuit:
`arXiv:2003.09412 [quant-ph] <https://arxiv.org/abs/2003.09412>`_
"""
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
2 changes: 1 addition & 1 deletion qiskit/synthesis/clifford/clifford_decompose_greedy.py
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ def synth_clifford_greedy(clifford: Clifford) -> QuantumCircuit:
`arXiv:2105.02291 [quant-ph] <https://arxiv.org/abs/2105.02291>`_
"""
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
2 changes: 1 addition & 1 deletion qiskit/synthesis/linear/cnot_synth.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
4 changes: 3 additions & 1 deletion qiskit/synthesis/linear_phase/cz_depth_lnn.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
)
3 changes: 2 additions & 1 deletion qiskit/synthesis/one_qubit/one_qubit_decompose.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
4 changes: 2 additions & 2 deletions qiskit/synthesis/permutation/permutation_full.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down Expand Up @@ -75,4 +75,4 @@ def synth_permutation_acg(pattern: list[int] | np.ndarray[int]) -> QuantumCircui
*Routing Permutations on Graphs Via Matchings.*,
`(Full paper) <https://www.cs.tau.ac.il/~nogaa/PDFS/r.pdf>`_
"""
return QuantumCircuit._from_circuit_data(_synth_permutation_acg(pattern))
return QuantumCircuit._from_circuit_data(_synth_permutation_acg(pattern), add_regs=True)
4 changes: 3 additions & 1 deletion qiskit/synthesis/permutation/permutation_lnn.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
)
4 changes: 3 additions & 1 deletion qiskit/synthesis/permutation/permutation_reverse_lnn.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
)
4 changes: 2 additions & 2 deletions qiskit/synthesis/two_qubit/two_qubit_decompose.py
Original file line number Diff line number Diff line change
Expand Up @@ -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."""
Expand Down Expand Up @@ -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),
Expand Down
7 changes: 7 additions & 0 deletions releasenotes/notes/fix-synth-qregs-7662681c0ff02511.yaml
Original file line number Diff line number Diff line change
@@ -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 <https://github.com/Qiskit/qiskit/issues/13041>`__.
17 changes: 17 additions & 0 deletions test/python/quantum_info/operators/symplectic/test_clifford.py
Original file line number Diff line number Diff line change
Expand Up @@ -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"""
Expand Down
Loading