Skip to content

Fix open controls on controlled gates with multiple target qubits - #2463

Open
Eduardo Alcantara (edubraqd) wants to merge 1 commit into
Qiskit:mainfrom
edubraqd:fix/2342-ctrl-state-multi-target
Open

Eduardo Alcantara (edubraqd) wants to merge 1 commit into
Qiskit:mainfrom
edubraqd:fix/2342-ctrl-state-multi-target

Conversation

@edubraqd

Copy link
Copy Markdown

Summary

Fixes #2342.

SwapGate().control(1, ctrl_state=0) (and any other controlled gate with more than one target qubit and a non-default ctrl_state) was simulated incorrectly: the X gates that implement the open controls were also applied to the first target qubit.

Details and comments

In _assemble_op, the number of control qubits was derived as len(qubits) - 1, which is only correct for single-target gates. For cswap_o0 (1 control, 2 targets) this loop covers qubits 0 and 1, so the open control bit is applied to the control and to the first target, before and after the gate. This is exactly the extra X on a described in the issue, and it affects every method that goes through the Python assembler (statevector, unitary, matrix_product_state, ...).

The fix uses the operation's num_ctrl_qubits instead, falling back to the previous behaviour if the attribute is missing.

Verification:

  • Added a cswap with ctrl_state=0 case to test_controlled_gates (test/terra/backends/aer_simulator/test_circuit.py). It fails on main with the statevector [0, 0.5, 0.5, 0.5, 0, ...] instead of [0.5, 0.5, 0, 0.5, 0.5, ...], and passes with this change.
  • The reproducer from the issue now gives |sv[0]| = 1.0 for statevector, unitary and matrix_product_state (was 0.5 for all three).
  • test_circuit.py, test_cliffords.py, test_auto_method.py, test_multiplexer.py and test_standard_gates.py pass (516 tests), black --check clean.

The Python-side change was tested against the 0.17.2 binary extension; no C++ changes are involved.

Disclosure: I used Claude (Anthropic) as an assistant to investigate this issue and draft the change; I reviewed and ran everything above locally.

🤖 Generated with Claude Code

When a controlled gate with a non-default ctrl_state is assembled, the
X gates implementing the open controls were applied to the first
len(qubits) - 1 qubits. For gates with more than one target qubit
(cswap, mcswap) this includes a target qubit, so
SwapGate().control(1, ctrl_state=0) was simulated with an extra X on
the first target before and after the swap.

Use the operation's num_ctrl_qubits instead of deriving the count from
the number of qubits.

Fixes Qiskit#2342

Co-Authored-By: Claude Opus 5 <[email protected]>
@CLAassistant

CLAassistant commented Sep 19, 2026 •

Copy link
Copy Markdown

CLA assistant check
All committers have signed the CLA.

@CLAassistant

Copy link
Copy Markdown

CLA assistant check
Thank you for your submission! We really appreciate it. Like many open source projects, we ask that you sign our Contributor License Agreement before we can accept your contribution.
You have signed the CLA already but the status is still pending? Let us recheck it.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Incorrect results from simulation of inverse controlled swap gate

2 participants