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 QPY serialisation of ControlledGate with open controls (backport #8571) #8578

Merged
merged 1 commit into from
Aug 18, 2022
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
16 changes: 13 additions & 3 deletions qiskit/qpy/binary_io/circuits.py
Original file line number Diff line number Diff line change
Expand Up @@ -320,6 +320,9 @@ def _parse_custom_operation(custom_operations, gate_name, params, version, vecto
base_gate = _read_instruction(
base_gate_obj, None, registers, custom_operations, version, vectors
)
if ctrl_state < 2**num_ctrl_qubits - 1:
# If open controls, we need to discard the control suffix when setting the name.
gate_name = gate_name.rsplit("_", 1)[0]
inst_obj = ControlledGate(
gate_name,
num_qubits,
Expand Down Expand Up @@ -623,14 +626,21 @@ def _write_custom_operation(file_obj, name, operation, custom_operations):
has_definition = True
data = common.data_to_binary(operation, _write_pauli_evolution_gate)
size = len(data)
elif operation.definition is not None:
elif type_key == type_keys.CircuitInstruction.CONTROLLED_GATE:
# For ControlledGate, we have to access and store the private `_definition` rather than the
# public one, because the public one is mutated to include additional logic if the control
# state is open, and the definition setter (during a subsequent read) uses the "fully
# excited" control definition only.
has_definition = True
data = common.data_to_binary(operation.definition, write_circuit)
data = common.data_to_binary(operation._definition, write_circuit)
size = len(data)
if type_key == type_keys.CircuitInstruction.CONTROLLED_GATE:
num_ctrl_qubits = operation.num_ctrl_qubits
ctrl_state = operation.ctrl_state
base_gate = operation.base_gate
elif operation.definition is not None:
has_definition = True
data = common.data_to_binary(operation.definition, write_circuit)
size = len(data)
if base_gate is None:
base_gate_raw = b""
else:
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
fixes:
- |
Fixed QPY serialisation and deserialisation of :class:`.ControlledGate`
with open controls (*i.e.* those whose ``ctrl_state`` is not all ones).
Fixed `#8549 <https://github.com/Qiskit/qiskit-terra/issues/8549>`__.
11 changes: 11 additions & 0 deletions test/python/circuit/test_circuit_load_from_qpy.py
Original file line number Diff line number Diff line change
Expand Up @@ -998,6 +998,17 @@ def test_controlled_gate(self):
new_circuit = load(qpy_file)[0]
self.assertEqual(qc, new_circuit)

def test_controlled_gate_open_controls(self):
"""Test a controlled gate with open controls round-trips exactly."""
qc = QuantumCircuit(3)
controlled_gate = DCXGate().control(1, ctrl_state=0)
qc.append(controlled_gate, [0, 1, 2])
qpy_file = io.BytesIO()
dump(qc, qpy_file)
qpy_file.seek(0)
new_circuit = load(qpy_file)[0]
self.assertEqual(qc, new_circuit)

def test_nested_controlled_gate(self):
"""Test a custom nested controlled gate."""
custom_gate = Gate("black_box", 1, [])
Expand Down
26 changes: 26 additions & 0 deletions test/qpy_compat/test_qpy.py
Original file line number Diff line number Diff line change
Expand Up @@ -488,6 +488,30 @@ def generate_controlled_gates():
return circuits


def generate_open_controlled_gates():
"""Test QPY serialization with custom ControlledGates with open controls."""
circuits = []
qc = QuantumCircuit(3)
controlled_gate = DCXGate().control(1, ctrl_state=0)
qc.append(controlled_gate, [0, 1, 2])
circuits.append(qc)

custom_gate = Gate("black_box", 1, [])
custom_definition = QuantumCircuit(1)
custom_definition.h(0)
custom_definition.rz(1.5, 0)
custom_definition.sdg(0)
custom_gate.definition = custom_definition
nested_qc = QuantumCircuit(3)
nested_qc.append(custom_gate, [0])
controlled_gate = custom_gate.control(2, ctrl_state=1)
nested_qc.append(controlled_gate, [0, 1, 2])
nested_qc.measure_all()
circuits.append(nested_qc)

return circuits


def generate_circuits(version_str=None):
"""Generate reference circuits."""
version_parts = None
Expand Down Expand Up @@ -525,6 +549,8 @@ def generate_circuits(version_str=None):
output_circuits["controlled_gates.qpy"] = generate_controlled_gates()
output_circuits["schedule_blocks.qpy"] = generate_schedule_blocks()
output_circuits["pulse_gates.qpy"] = generate_calibrated_circuits()
if version_parts >= (0, 21, 2):
output_circuits["open_controlled_gates.qpy"] = generate_open_controlled_gates()

return output_circuits

Expand Down