Skip to content

Fix composition of final_layout#14919

Merged
mtreinish merged 1 commit into
Qiskit:mainfrom
jakelishman:fix-final-layout
Aug 15, 2025
Merged

Fix composition of final_layout#14919
mtreinish merged 1 commit into
Qiskit:mainfrom
jakelishman:fix-final-layout

Conversation

@jakelishman

Copy link
Copy Markdown
Member

Summary

The previous implementation had it backwards. There are two ways to think about the final_layout, which is a Layout object mapping "virtual" objects (qubits at the start of the circuit) to "physical" indices (qubits at the end of the circuit):

  • It is a "goes to" permutation that you would prepend to the unrouted circuit to have the same effect as routing.

  • It is a "comes from" permutation that you would append to the routed circuit to undo the effects of routing.

If you have permutation A that is already applied, and you're newly applying permutation B, then in the first interpretation, you want to transform
A and then (B and then circuit)
into
(A and then B) and then circuit.

In the second interpretation, you instead want to turn
(circuit then "undo B") then "undo A"
into
circuit then ("undo B" then "undo A")

The composition "undo B then undo A" is the same as "undo (A then B)" by standard inverse rules.

In both cases, the logic is implemented as previous.compose(new) in terms of Layout.compose.

Details and comments

The commit message is just part of me iterating towards a set of words I'm happy with to describe the full situation. I still don't think I'm quite yet there. Either way, the point is that the test is enforcing an invariant that I think we can agree on (up to whether we like the convention of PermutationGate, but we don't have freedom to choose that).

Technically this is stable for backport if we were to choose to.

This fixes the remaining bug of #14904. The bug certainly exists in Qiskit 2.1 (and probably for as long as final_layout existed), but #14904 causes it to surface because it normalises virtual_permutation_layout into final_layout earlier in the transpiler process, causing layout/routing to now actually have to deal with the update in some cases.

The previous implementation had it backwards.  There are two ways to
think about the `final_layout`, which is a `Layout` object mapping
"virtual" objects (qubits at the start of the circuit) to "physical"
indices (qubits at the end of the circuit):

- It is a "goes to" permutation that you would _prepend_ to the
  _unrouted_ circuit to have the same effect as routing.

- It is a "comes from" permutation that you would _append_ to the
  _routed_ circuit to undo the effects of routing.

If you have permutation `A` that is already applied, and you're newly
applying permutation `B`, then in the first interpretation, you want to
transform
    A and then (B and then circuit)
into
    (A and then B) and then circuit.

In the second interpretation, you instead want to turn
    (circuit then "undo B") then "undo A"
into
    circuit then ("undo B" then "undo A")

The composition "undo B then undo A" is the same as "undo (A then B)"
by standard inverse rules.

In both cases, the logic is implemented as `previous.compose(new)` in
terms of `Layout.compose`.
@jakelishman jakelishman added this to the 2.2.0 milestone Aug 15, 2025
@jakelishman jakelishman requested a review from a team as a code owner August 15, 2025 14:56
@jakelishman jakelishman added Changelog: Fixed Add a "Fixed" entry in the GitHub Release changelog. mod: transpiler Issues and PRs related to Transpiler labels Aug 15, 2025
@qiskit-bot

Copy link
Copy Markdown
Collaborator

One or more of the following people are relevant to this code:

  • @Qiskit/terra-core

@coveralls

Copy link
Copy Markdown

Pull Request Test Coverage Report for Build 16992684590

Details

  • 2 of 4 (50.0%) changed or added relevant lines in 3 files are covered.
  • 12 unchanged lines in 3 files lost coverage.
  • Overall coverage increased (+0.002%) to 88.282%

Changes Missing Coverage Covered Lines Changed/Added Lines %
qiskit/transpiler/passes/layout/sabre_layout.py 0 1 0.0%
qiskit/transpiler/passes/routing/basic_swap.py 1 2 50.0%
Files with Coverage Reduction New Missed Lines %
crates/qasm2/src/expr.rs 1 93.63%
crates/qasm2/src/lex.rs 5 91.75%
crates/qasm2/src/parse.rs 6 97.56%
Totals Coverage Status
Change from base Build 16988585462: 0.002%
Covered Lines: 87901
Relevant Lines: 99568

💛 - Coveralls

@mtreinish mtreinish left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The fix looks good to me, do you think it's worth backporting for 2.1.2?

@mtreinish mtreinish added this pull request to the merge queue Aug 15, 2025
@mtreinish mtreinish added the stable backport potential Make Mergify open a backport PR to the most recent stable branch on merge. label Aug 15, 2025
@jakelishman

Copy link
Copy Markdown
Member Author

Yeah, it's eligible for backport, it just shouldn't really matter in practice in 2.1. We can backport it.

Merged via the queue into Qiskit:main with commit dfcc5c6 Aug 15, 2025
28 checks passed
mergify Bot pushed a commit that referenced this pull request Aug 15, 2025
The previous implementation had it backwards.  There are two ways to
think about the `final_layout`, which is a `Layout` object mapping
"virtual" objects (qubits at the start of the circuit) to "physical"
indices (qubits at the end of the circuit):

- It is a "goes to" permutation that you would _prepend_ to the
  _unrouted_ circuit to have the same effect as routing.

- It is a "comes from" permutation that you would _append_ to the
  _routed_ circuit to undo the effects of routing.

If you have permutation `A` that is already applied, and you're newly
applying permutation `B`, then in the first interpretation, you want to
transform
    A and then (B and then circuit)
into
    (A and then B) and then circuit.

In the second interpretation, you instead want to turn
    (circuit then "undo B") then "undo A"
into
    circuit then ("undo B" then "undo A")

The composition "undo B then undo A" is the same as "undo (A then B)"
by standard inverse rules.

In both cases, the logic is implemented as `previous.compose(new)` in
terms of `Layout.compose`.

(cherry picked from commit dfcc5c6)

# Conflicts:
#	qiskit/transpiler/passes/layout/sabre_layout.py
#	qiskit/transpiler/passes/routing/sabre_swap.py
@jakelishman jakelishman deleted the fix-final-layout branch August 15, 2025 16:51
jakelishman added a commit that referenced this pull request Aug 18, 2025
The previous implementation had it backwards.  There are two ways to
think about the `final_layout`, which is a `Layout` object mapping
"virtual" objects (qubits at the start of the circuit) to "physical"
indices (qubits at the end of the circuit):

- It is a "goes to" permutation that you would _prepend_ to the
  _unrouted_ circuit to have the same effect as routing.

- It is a "comes from" permutation that you would _append_ to the
  _routed_ circuit to undo the effects of routing.

If you have permutation `A` that is already applied, and you're newly
applying permutation `B`, then in the first interpretation, you want to
transform
    A and then (B and then circuit)
into
    (A and then B) and then circuit.

In the second interpretation, you instead want to turn
    (circuit then "undo B") then "undo A"
into
    circuit then ("undo B" then "undo A")

The composition "undo B then undo A" is the same as "undo (A then B)"
by standard inverse rules.

In both cases, the logic is implemented as `previous.compose(new)` in
terms of `Layout.compose`.
github-merge-queue Bot pushed a commit that referenced this pull request Aug 18, 2025
The previous implementation had it backwards.  There are two ways to
think about the `final_layout`, which is a `Layout` object mapping
"virtual" objects (qubits at the start of the circuit) to "physical"
indices (qubits at the end of the circuit):

- It is a "goes to" permutation that you would _prepend_ to the
  _unrouted_ circuit to have the same effect as routing.

- It is a "comes from" permutation that you would _append_ to the
  _routed_ circuit to undo the effects of routing.

If you have permutation `A` that is already applied, and you're newly
applying permutation `B`, then in the first interpretation, you want to
transform
    A and then (B and then circuit)
into
    (A and then B) and then circuit.

In the second interpretation, you instead want to turn
    (circuit then "undo B") then "undo A"
into
    circuit then ("undo B" then "undo A")

The composition "undo B then undo A" is the same as "undo (A then B)"
by standard inverse rules.

In both cases, the logic is implemented as `previous.compose(new)` in
terms of `Layout.compose`.

Co-authored-by: Jake Lishman <jake.lishman@ibm.com>
littlebullGit pushed a commit to littlebullGit/qiskit that referenced this pull request Sep 5, 2025
The previous implementation had it backwards.  There are two ways to
think about the `final_layout`, which is a `Layout` object mapping
"virtual" objects (qubits at the start of the circuit) to "physical"
indices (qubits at the end of the circuit):

- It is a "goes to" permutation that you would _prepend_ to the
  _unrouted_ circuit to have the same effect as routing.

- It is a "comes from" permutation that you would _append_ to the
  _routed_ circuit to undo the effects of routing.

If you have permutation `A` that is already applied, and you're newly
applying permutation `B`, then in the first interpretation, you want to
transform
    A and then (B and then circuit)
into
    (A and then B) and then circuit.

In the second interpretation, you instead want to turn
    (circuit then "undo B") then "undo A"
into
    circuit then ("undo B" then "undo A")

The composition "undo B then undo A" is the same as "undo (A then B)"
by standard inverse rules.

In both cases, the logic is implemented as `previous.compose(new)` in
terms of `Layout.compose`.
aaryav-3 pushed a commit to aaryav-3/qiskit that referenced this pull request Oct 21, 2025
The previous implementation had it backwards.  There are two ways to
think about the `final_layout`, which is a `Layout` object mapping
"virtual" objects (qubits at the start of the circuit) to "physical"
indices (qubits at the end of the circuit):

- It is a "goes to" permutation that you would _prepend_ to the
  _unrouted_ circuit to have the same effect as routing.

- It is a "comes from" permutation that you would _append_ to the
  _routed_ circuit to undo the effects of routing.

If you have permutation `A` that is already applied, and you're newly
applying permutation `B`, then in the first interpretation, you want to
transform
    A and then (B and then circuit)
into
    (A and then B) and then circuit.

In the second interpretation, you instead want to turn
    (circuit then "undo B") then "undo A"
into
    circuit then ("undo B" then "undo A")

The composition "undo B then undo A" is the same as "undo (A then B)"
by standard inverse rules.

In both cases, the logic is implemented as `previous.compose(new)` in
terms of `Layout.compose`.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Changelog: Fixed Add a "Fixed" entry in the GitHub Release changelog. mod: transpiler Issues and PRs related to Transpiler stable backport potential Make Mergify open a backport PR to the most recent stable branch on merge.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants