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 extraction of controlled parametric gates (backport #13067) #13080

Merged
merged 1 commit into from
Sep 3, 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
12 changes: 9 additions & 3 deletions crates/circuit/src/circuit_instruction.rs
Original file line number Diff line number Diff line change
Expand Up @@ -547,12 +547,18 @@ impl<'py> FromPyObject<'py> for OperationFromPython {
// mapping to avoid an `isinstance` check on `ControlledGate` - a standard gate has
// nonzero `num_ctrl_qubits` iff it is a `ControlledGate`.
//
// `ControlledGate` also has a `base_gate` attribute, and we don't track enough in Rust
// space to handle the case that that was mutated away from a standard gate.
// `ControlledGate` also has a `base_gate` attribute related to its historical
// implementation, which technically allows mutations from Python space. The only
// mutation of a standard gate's `base_gate` that wouldn't have already broken the
// Python-space data model is setting a label, so we just catch that case and default
// back to non-standard-gate handling in that case.
if standard.num_ctrl_qubits() != 0
&& ((ob.getattr(intern!(py, "ctrl_state"))?.extract::<usize>()?
!= (1 << standard.num_ctrl_qubits()) - 1)
|| ob.getattr(intern!(py, "mutable"))?.extract()?)
|| !ob
.getattr(intern!(py, "base_gate"))?
.getattr(intern!(py, "label"))?
.is_none())
{
break 'standard;
}
Expand Down
57 changes: 32 additions & 25 deletions qiskit/circuit/library/standard_gates/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -54,45 +54,51 @@ def get_standard_gate_name_mapping():
from qiskit.circuit.delay import Delay
from qiskit.circuit.reset import Reset

lambda_ = Parameter("λ")
theta = Parameter("ϴ")
phi = Parameter("φ")
gamma = Parameter("γ")
beta = Parameter("β")
time = Parameter("t")

# Standard gates library mapping, multicontrolled gates not included since they're
# variable width
gates = [
IGate(),
SXGate(),
XGate(),
CXGate(),
RZGate(Parameter("λ")),
RGate(Parameter("ϴ"), Parameter("φ")),
Reset(),
RZGate(lambda_),
RGate(theta, phi),
C3SXGate(),
CCXGate(),
DCXGate(),
CHGate(),
CPhaseGate(Parameter("ϴ")),
CRXGate(Parameter("ϴ")),
CRYGate(Parameter("ϴ")),
CRZGate(Parameter("ϴ")),
CPhaseGate(theta),
CRXGate(theta),
CRYGate(theta),
CRZGate(theta),
CSwapGate(),
CSXGate(),
CUGate(Parameter("ϴ"), Parameter("φ"), Parameter("λ"), Parameter("γ")),
CU1Gate(Parameter("λ")),
CU3Gate(Parameter("ϴ"), Parameter("φ"), Parameter("λ")),
CUGate(theta, phi, lambda_, gamma),
CU1Gate(lambda_),
CU3Gate(theta, phi, lambda_),
CYGate(),
CZGate(),
CCZGate(),
GlobalPhaseGate(Parameter("ϴ")),
GlobalPhaseGate(theta),
HGate(),
PhaseGate(Parameter("ϴ")),
PhaseGate(theta),
RCCXGate(),
RC3XGate(),
RXGate(Parameter("ϴ")),
RXXGate(Parameter("ϴ")),
RYGate(Parameter("ϴ")),
RYYGate(Parameter("ϴ")),
RZZGate(Parameter("ϴ")),
RZXGate(Parameter("ϴ")),
XXMinusYYGate(Parameter("ϴ"), Parameter("β")),
XXPlusYYGate(Parameter("ϴ"), Parameter("β")),
RXGate(theta),
RXXGate(theta),
RYGate(theta),
RYYGate(theta),
RZZGate(theta),
RZXGate(theta),
XXMinusYYGate(theta, beta),
XXPlusYYGate(theta, beta),
ECRGate(),
SGate(),
SdgGate(),
Expand All @@ -103,13 +109,14 @@ def get_standard_gate_name_mapping():
SXdgGate(),
TGate(),
TdgGate(),
UGate(Parameter("ϴ"), Parameter("φ"), Parameter("λ")),
U1Gate(Parameter("λ")),
U2Gate(Parameter("φ"), Parameter("λ")),
U3Gate(Parameter("ϴ"), Parameter("φ"), Parameter("λ")),
UGate(theta, phi, lambda_),
U1Gate(lambda_),
U2Gate(phi, lambda_),
U3Gate(theta, phi, lambda_),
YGate(),
ZGate(),
Delay(Parameter("t")),
Delay(time),
Reset(),
Measure(),
]
name_mapping = {gate.name: gate for gate in gates}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
---
fixes:
- |
Parametric controlled standard-library gates (such as :class:`.CRXGate`) will now get correctly
extracted to a Rust-space standard gate when using :meth:`.QuantumCircuit.append` and the gate
object. Previously there was a discrepancy where using the :meth:`.QuantumCircuit.crx` method
would cause a correct extraction in Rust space, but the :meth:`~.QuantumCirucit.append` form
would not. The bug should generally not have caused any unsoundness from Python.
17 changes: 17 additions & 0 deletions test/python/circuit/test_rust_equivalence.py
Original file line number Diff line number Diff line change
Expand Up @@ -227,3 +227,20 @@ def test_non_default_controls(self):
circuit.append(gate, [0, 1, 2])
self.assertIsNotNone(getattr(gate, "_standard_gate", None))
np.testing.assert_almost_equal(Operator(circuit.data[0].operation).to_matrix(), op)

def test_extracted_as_standard_gate(self):
"""Test that every gate in the standard library gets correctly extracted as a Rust-space
`StandardGate` in its default configuration when passed through `append`."""
standards = set()
qc = QuantumCircuit(4)
for name, gate in get_standard_gate_name_mapping().items():
if gate._standard_gate is None:
# Not a standard gate.
continue
standards.add(name)
qc.append(gate, qc.qubits[: gate.num_qubits], [])
# Sanity check: the test should have found at least one standard gate in the mapping.
self.assertNotEqual(standards, set())

extracted = {inst.name for inst in qc.data if inst.is_standard_gate()}
self.assertEqual(standards, extracted)
Loading