Skip to content

Add interoperation between TranspileLayout and PropertySet#14826

Merged
mtreinish merged 5 commits into
Qiskit:mainfrom
jakelishman:transpilelayout-interop-propertyset
Aug 22, 2025
Merged

Add interoperation between TranspileLayout and PropertySet#14826
mtreinish merged 5 commits into
Qiskit:mainfrom
jakelishman:transpilelayout-interop-propertyset

Conversation

@jakelishman

Copy link
Copy Markdown
Member

Summary

This adds a constructor and a "destructor" to TranspileLayout to cleanly centralise how the current ad-hoc PropertySet properties are combined into a TranspileLayout, and lets them be written back out again. This lets passes in the middle of an execution pipeline construct a complete TranspileLayout structure, based on the current state.

This is a step along the road to centralising TranspileLayout into a core part of the IR; we can now add methods to it, write transpiler passes against TranspileLayout, and start transitioning the logic, without breaking backwards compatibility. The idea is that passes will opt in to creating a TranspileLayout, mutate it, then write it back out again. In the future, we can have the pass manager itself manage legacy passes by extracting the TranspileLayout into the PropertySet before executing a legacy pass.

Details and comments

Based on #14825.

@jakelishman jakelishman added this to the 2.2.0 milestone Aug 5, 2025
@jakelishman jakelishman added the Changelog: Added Add an "Added" entry in the GitHub Release changelog. label Aug 5, 2025
@jakelishman jakelishman requested a review from a team as a code owner August 5, 2025 17:43
@jakelishman jakelishman added the mod: transpiler Issues and PRs related to Transpiler label Aug 5, 2025
@qiskit-bot

Copy link
Copy Markdown
Collaborator

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

  • @Qiskit/terra-core

@coveralls

coveralls commented Aug 5, 2025

Copy link
Copy Markdown

Pull Request Test Coverage Report for Build 17153389326

Warning: This coverage report may be inaccurate.

This pull request's base commit is no longer the HEAD commit of its target branch. This means it includes changes from outside the original pull request, including, potentially, unrelated coverage changes.

Details

  • 47 of 47 (100.0%) changed or added relevant lines in 2 files are covered.
  • 2889 unchanged lines in 63 files lost coverage.
  • Overall coverage increased (+0.8%) to 88.416%

Files with Coverage Reduction New Missed Lines %
crates/accelerate/src/circuit_library/pauli_evolution.rs 1 98.98%
crates/cext/src/transpiler/passes/elide_permutations.rs 1 95.24%
crates/circuit/src/lib.rs 1 94.57%
crates/transpiler/src/passes/check_map.rs 1 98.82%
qiskit/circuit/commutation_checker.py 1 94.74%
qiskit/transpiler/passes/layout/sabre_layout.py 1 93.33%
qiskit/transpiler/passes/scheduling/scheduling/base_scheduler.py 1 97.3%
qiskit/transpiler/passmanager.py 1 95.04%
crates/synthesis/src/clifford/bm_synthesis.rs 2 99.15%
qiskit/transpiler/passes/optimization/inverse_cancellation.py 2 95.12%
Totals Coverage Status
Change from base Build 16758738403: 0.8%
Covered Lines: 89826
Relevant Lines: 101595

💛 - Coveralls

This adds a constructor and a "destructor" to `TranspileLayout` to
cleanly centralise how the current ad-hoc `PropertySet` properties are
combined into a `TranspileLayout`, and lets them be written back out
again.  This lets passes in the middle of an execution pipeline
construct a complete `TranspileLayout` structure, based on the current
state.

This is a step along the road to centralising `TranspileLayout` into a
core part of the IR; we can now add methods to it, write transpiler
passes against `TranspileLayout`, and start transitioning the logic,
without breaking backwards compatibility.  The idea is that passes will
opt in to creating a `TranspileLayout`, mutate it, then write it back
out again.  In the future, we can have the pass manager itself manage
legacy passes by extracting the `TranspileLayout` into the `PropertySet`
before executing a legacy pass.
@jakelishman jakelishman force-pushed the transpilelayout-interop-propertyset branch from f3f7d63 to 66197c7 Compare August 5, 2025 20:39
Comment thread qiskit/transpiler/layout.py Outdated
Comment on lines +831 to +858
# Throughout the rest of this, we will speak about index permutations as lists that mean:
#
# qubit `permutation[i]` goes to new index `i`
#
# or in alternative langauge,
#
# after the permutation, qubit `i` contains qubit `permutation[i]`.
#
# This is to match the convention that `PermutationGate` uses, but beware: it might not be
# the way you think about permutations (it's not my preferred convention---Jake).
#
# Now, we'll step through the tranpsilation process. At each point, we'll relate the
# objects we have back to a 3-tuple of abstract objects, which are applied in order:
#
# (relabelling, explicit instructions, implicit instructions)
#
# The "explicit instructions" are always just the DAG itself. The "relabelling" is
# generally associated with the "initial layout" and the metadata linking the original
# virtual qubit objects and their indices. The "implicit instructions" is where all the
# interesting stuff happens; at the moment, in Qiskit, we only track an implicit final
# permutation, though you could imagine a world where we allow a lot more things to be
# tracked, such as necessary classical post-processing steps.
#
# We will attempt to always have in hand an "undoing" permutation, such that doing
# `qc.append(PermutationGate(permutation), qc.qubits)` to the output of the transpiler, were
# it to terminate at any given step, would be enough to precisely recreate the unitary of
# the circuit (with due hand-waviness around measurements/resets), up to the qubit
# relabelling of the initial layout.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

This discussion probably actually wants to get moved into proper documentation in qiskit.transpiler, and the formalism completed. Creating this documentation and the pedagogy of the rest of this function was actually one of the main points of the PR - it went hand-in-hand with creating the method itself, since I wanted to use both in the new Rust-y ApplyLayout, but I couldn't understand what everything meant.

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.

I find the above paragraph confusing. When you say "to the output of the transpiler", do you mean the circuit returned by the transpiler (without the output permutation, which is implicit), or the circuit with the output permutation (as above you argue that the transpiler really returns a triple that includes this output permutation as part of the implicit stuff)?

I presume you mean the circuit without the output permutation, and, under the assumption that the initial layout is trivial, appending the PermutationGate(permutation) gives us exactly the unitary operator of the original circuit (assuming it only consists of unitary operations). However I don't understand why this is called "undoing" rather than "doing". The way I think about this say after the ElidePermutationPass, is that we have moved the explicit permutation gate at the end of the circuit to an implicit object, so to get the original operator we need to add it back (and not to undo it again).

I am fine keeping the word "undo", but could you please rephrase the paragraph to avoid any potential confusion?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

I did a bit more of a rewrite in 150d4a7 - hopefully that's a bit clearer.

I presume you mean the circuit without the output permutation, and, under the assumption that the initial layout is trivial, appending the PermutationGate(permutation) gives us exactly the unitary operator of the original circuit.

Yes, but also in my world, you still do qc.append(PermutationGate(permutation), qc.qubits) first if you do have an initial layout, while the qubits are still the relabelled physical ones, and then you undo the initial-layout relabelling to get back to the original unitary matrix. The reason for it is that things that modify the final permutation shouldn't ever need to care what the original relabelling was (and they don't, in any current Qiskit pass). In my 3-tuple form, they only deal with the (_, explicit, implicit) part, not the first (relabelling, _, _) bit, which means there's less to sync up.

However I don't understand why this is called "undoing" rather than "doing". The way I think about this say after the ElidePermutationPass, is that we have moved the explicit permutation gate at the end of the circuit to an implicit object, so to get the original operator we need to add it back (and not to undo it again).

ElidePermutations is one of the few that reduces the number of swaps/permutations in the circuit explicitly at the moment. Imo most things (like routing passes, which almost always run) add swaps in that you'd need to "remove" to get back, which is why I used "undoing". To me, you "undo" the effects of an elision by re-adding them whereas "doing" them again wouldn't imply an inverse. But I tried to avoid using "undo" without greater context in the rewrite to help out.

@alexanderivrii

Copy link
Copy Markdown
Member

Thanks Jake for starting this. The following message is more of a high-level discussion/questions rather than a review.

First of all, I very-very much like the long-term vision described in this PR's summary. This is how I understand it (so please tell me if there are discrepancies between this and how you view it). Each of the transpiler passes takes a DAGCircuit as input, and produces a DAGCircuit as output. However, in the future, the DAGCircuit also explicitly contains a TranspileLayout. So we can think of this as a tuple (explicit instructions in the DAG, TranspileLayout), and think of each pass as taking a tuple (explicit instructions in the DAG, TranspileLayout) as input, and producing a new tuple (explicit instructions in the DAG, TranspileLayout) as output. The important point being that such tuples alone are sufficient to fully describe the state of the current partially transpiled circuit, and in particular to construct a unitary operator in the case that of all the circuit instructions are unitary. For comparison, just looking at explicit instructions in the DAG alone does not suffice as this misses crucial information such as the mapping from the virtual qubits to the physical qubits and the final permutation of the output qubits (which are currently kept in the PropertySet separately from the DAGCircuit). In the code comments, this is actually described as a triple (relabelling, explicit instructions, implicit instructions), where explicit instructions are the same as before while relabeling and implicit instructions are both part of the TranspileLayout. Jake, did I get this completely right, or your implicit instructions are more general than TranspileLayout (in particular, would the hypothetical "final clifford" be a part of TranspileLayout or of another object also sitting on the DAG)? The future TranspileLayout will be in rust and will be finalized by #14778, once we agree on all of the technical details.

One immediate question: this PR adds the interop between PropertySet and python-space TranspileLayout, while #14778 aims to define a Rust-space TranspileLayout. Which of these two PRs should be merged first? (Thinking through the interop here may have consequences as to how the rust-space TranspileLayout will eventually look like).

Currently, this PR replaces the _finalize_layouts method by

layout := TranspileLayout.from_property_set(passmanager_ir, self.property_set)
# Write the canonicalized form back out. This is for backwards compatibility.
layout.write_into_property_set(self.property_set)

Currently this is done at the very end of the run and in particular canonicalizes away the horrible "virtual_permutation_layout" that we have introduced in the past, and from what I can judge this should work correctly. In addition, based on the release notes it seems that converting the property set into a transpile layout and back should also work after every pass in the transpilation pipeline, but I am confused as whether this already happens. I do want this to be the case after this PR is merged. There are two things that confuse me.

  • Where in the transpiler pipeline, the associated (python-space) TranspileLayout is assumed/required to be None? For the rust-space, can we have TranspileLayout to be always non-None with initial_layout being an Option (I think I saw both your and Matthew comments to this extent)? Does going from property set to transpile layout and back (or from transpile layout to property set and back) work correctly when TranspileLayout is None?
  • Most of the layout passes (e.g. VF2Layout) are analysis passes which compute how the mapping from virtual to physical qubits should look like, but do not yet apply this mapping. Should TranspileLayout know whether the layout has already been applied? (This would be easy with the Rust-space structure when it's an optional). Should from_property_set and to_property_set consider the cases "before the layout was applied" and "after the layout was applied" separately? Hmm, let's say we have just run the VF2Analysis pass and that we have a layout that we want to apply to the circuit. At this point the layout is not applied, so we do not want the TranspileLayout to change (which is consistent with VF2Layout being only an analysis pass). How should we keep the computed layout so that it does not get lost when converted from and to property set.

@jakelishman

Copy link
Copy Markdown
Member Author

In the code comments, this is actually described as a triple (relabelling, explicit instructions, implicit instructions), where explicit instructions are the same as before while relabeling and implicit instructions are both part of the TranspileLayout. Jake, did I get this completely right, or your implicit instructions are more general than TranspileLayout (in particular, would the hypothetical "final clifford" be a part of TranspileLayout or of another object also sitting on the DAG)?

Yeah, this is correct with some subtlety: the (relabelling, explicit, implicit) 3-tuple is the abstract model of the transpilation state ("relabelling" might need some thought, but it's fine for now). I wanted to define that outside a reference to any concrete objects.

Then we have to choose the concrete parts. Obviously we have the DAG for "explicit". We currently have a Layout object for "relabelling" (plus some extra metadata - bit order, etc), and a permutation for "implicit". We then bundled all that up into a single object, TranspileLayout.

In my abstract model, we can choose to represent the 3 items however we like. The actual choice of representation in any version of Qiskit is making a choice of the user API we offer, the backwards compatibility from the previous version, and the expressibility of the compiler. We don't have to keep TranspileLayout, but we probably want to do so and to evolve it in place to make backwards compatibility easier.

The "implicit" bit at the end is perhaps the most subtle. We could store implicit as a separate DAG, and put whatever arbitrary operations we like on it (permutations, Cliffords, etc, etc). The reason we don't want to do that is that it becomes near-impossible to derive anything useful from it after the fact, without running another compilation on it. We deliberately choose to restrict what we track as "implicit" to make it easier to use the output; it's always a trade-off between expression and ease-of-use. I think there's a good argument to be made soon that a final Clifford, or the observable (as a Pauli measurement) of EstimatorPub, could and should also be considered in "implicit" in some way, and the abstract 3-tuple is supposed to be a way of formalising why the transpiler is allowed to do it. The mechanics of "how" are an API choice we then make.

One immediate question: this PR adds the interop between PropertySet and python-space TranspileLayout, while #14778 aims to define a Rust-space TranspileLayout. Which of these two PRs should be merged first? (Thinking through the interop here may have consequences as to how the rust-space TranspileLayout will eventually look like).

Matt and I (unfortunately) don't think we can merge the Python and Rust-space TranspileLayout objects together in a backwards-compatible way for the 2.x series, so they're going to co-exist. After both merge, we'll have a PR that adds interop between the two classes as well, to enable Rust-space passes to start using Matt's class immediately too.

Where in the transpiler pipeline, the associated (python-space) TranspileLayout is assumed/required to be None?

We can't really break backwards compatibility of this object, so for Qiskit 2.x, we're kind of stuck with the current state of affairs: if virtual_permutation_layout exists and something needs to examine the "layout", it'll see a dummy (and incorrect) initial_layout too. We'll fix that in Qiskit 3.0. If neither virtual_permutation_layout and initial_layout are set, this object is None.

Most of the layout passes (e.g. VF2Layout) are analysis passes which compute how the mapping from virtual to physical qubits should look like, but do not yet apply this mapping. Should TranspileLayout know whether the layout has already been applied?

Once we transition to TranspileLayout being on DAGCircuit directly, there'll still be a layout key in the PropertySet, so VF2Layout can still be an analysis pass. ApplyLayout will be able to read in the property-set keys to know what layout it needs to apply, and will be able to examine the DAGCircuit (via its TranspileLayout attribute) to know how to apply the futher remapping.

@jakelishman

Copy link
Copy Markdown
Member Author

The short version of it:

This PR isn't intended to change any behaviour in Qiskit 2.x. It's just laying the groundwork of the conversion functions that will be necessary in the upgrade path to Qiskit 3.x.

@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.

Overall this LGTM, it's really nice to have a clear description of all the moving pieces here and how it all fits together. I agree with your inline comment we should bubble this up to the transpile module docs to make it clear for people (especially as we now have docs for pass authors) I just had a few small questions inline but otherwise I think I"m happy to merge this.

Comment thread qiskit/transpiler/layout.py Outdated
Comment thread qiskit/transpiler/layout.py
Comment thread qiskit/transpiler/layout.py
Comment thread releasenotes/notes/transpilelayout-from_property_set-97eb505156c13230.yaml Outdated

@alexanderivrii alexanderivrii 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.

This looks good to me as well. Thanks for adding the detailed description of what each field in the property set means, our assumptions about them, and how they interact which each other. I have asked to rephrase one of the paragraphs, since I believe there might be still room for confusion left.

One other question. This PR allows to "canonicalize" the property set by calling from_property_set followed by write_into_property_set. In practice, this PR calls these once at the very end of transpilation instead of _finalize_layouts, where I believe everything works perfectly. Hence I am happy to merge this as is. However, is it true that we can currently "canonicalize" the property set after every transpiler pass and we would still get correct results?

I am afraid this might not be true (for instance, would this work right after ElidePermutations or after one of the analysis passes the compute but do not apply the layout?), I would be happy to be wrong, but if not - can you please add an explicit warning to this extent?

Comment thread qiskit/transpiler/layout.py Outdated
Comment thread qiskit/transpiler/layout.py Outdated
Comment on lines +831 to +858
# Throughout the rest of this, we will speak about index permutations as lists that mean:
#
# qubit `permutation[i]` goes to new index `i`
#
# or in alternative langauge,
#
# after the permutation, qubit `i` contains qubit `permutation[i]`.
#
# This is to match the convention that `PermutationGate` uses, but beware: it might not be
# the way you think about permutations (it's not my preferred convention---Jake).
#
# Now, we'll step through the tranpsilation process. At each point, we'll relate the
# objects we have back to a 3-tuple of abstract objects, which are applied in order:
#
# (relabelling, explicit instructions, implicit instructions)
#
# The "explicit instructions" are always just the DAG itself. The "relabelling" is
# generally associated with the "initial layout" and the metadata linking the original
# virtual qubit objects and their indices. The "implicit instructions" is where all the
# interesting stuff happens; at the moment, in Qiskit, we only track an implicit final
# permutation, though you could imagine a world where we allow a lot more things to be
# tracked, such as necessary classical post-processing steps.
#
# We will attempt to always have in hand an "undoing" permutation, such that doing
# `qc.append(PermutationGate(permutation), qc.qubits)` to the output of the transpiler, were
# it to terminate at any given step, would be enough to precisely recreate the unitary of
# the circuit (with due hand-waviness around measurements/resets), up to the qubit
# relabelling of the initial layout.

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.

I find the above paragraph confusing. When you say "to the output of the transpiler", do you mean the circuit returned by the transpiler (without the output permutation, which is implicit), or the circuit with the output permutation (as above you argue that the transpiler really returns a triple that includes this output permutation as part of the implicit stuff)?

I presume you mean the circuit without the output permutation, and, under the assumption that the initial layout is trivial, appending the PermutationGate(permutation) gives us exactly the unitary operator of the original circuit (assuming it only consists of unitary operations). However I don't understand why this is called "undoing" rather than "doing". The way I think about this say after the ElidePermutationPass, is that we have moved the explicit permutation gate at the end of the circuit to an implicit object, so to get the original operator we need to add it back (and not to undo it again).

I am fine keeping the word "undo", but could you please rephrase the paragraph to avoid any potential confusion?

@alexanderivrii

Copy link
Copy Markdown
Member

After some experimenting, I can indeed see that "canonicalizing" the transpile layout right after the elide permutations pass leads to incorrect results. This is expected (due to the deficiency of our python-based transpile layout class) and the reason behind introducing the (arguably very ugly) virtual_permutation_layout in the first place. This should not affect #14904 because when ApplyLayout finally runs we have full information (including the mapping from virtual to physical qubits) to be able to transfer the permutation from virtual to physical qubits. I don't know if we can do anything with this, as the Python-based TranspileLayout class does not have virtual_permutation_layout, so we should probably just document this clearly in the code that canonicalizations works after ApplyLayout or SabreLayout (when that explicitly calls ApplyLayout). What do you think?

@jakelishman

Copy link
Copy Markdown
Member Author

Sasha: can you give a code example showing that this is wrong after ElidePermutations?

@alexanderivrii

alexanderivrii commented Aug 21, 2025

Copy link
Copy Markdown
Member

Jake:

The following code (with your PR applied)

qc = QuantumCircuit(3)
qc.h(0)
qc.swap(0, 2)
qc.cx(0, 2)
qc.swap(1, 0)
qc.h(1)
print(qc.draw(idle_wires=True))

coupling_map = CouplingMap([(0, 1), (1, 0), (1, 2), (2, 1)])
target = Target.from_configuration(basis_gates=["u", "cx"], num_qubits=coupling_map.size(), coupling_map=coupling_map)

# the coupling map + the initial layout ensure that we will have to deal both with the permutation of virtual qubits
# (virtual permutation layout) and with the mapping from virtual to physical qubits (initial_layout)
pm = generate_preset_pass_manager(optimization_level=3, seed_transpiler=42, target = target, initial_layout=[1, 2, 0])
qct = pm.run(qc)
print(qct.draw(idle_wires=True))

eq = (Operator.from_circuit(qct).equiv(Operator(qc)))
print(f"{eq =  }")

prints True (as it should).

Adding

from qiskit.transpiler.layout import TranspileLayout 
if (layout := TranspileLayout.from_property_set(new_dag, self.property_set)) is not None:
    layout.write_into_property_set(self.property_set)

to the ElidePermutations pass just before returning new_dag outputs False.

@jakelishman

Copy link
Copy Markdown
Member Author

Have you got #14919 included in your branch when you try that?

@alexanderivrii

alexanderivrii commented Aug 21, 2025

Copy link
Copy Markdown
Member

Not explicitly (let me try).

Update: yeah unfortunately the problem persists even with #14919 applied (I was pretty sure this would happen, but wanted to double-check).

@jakelishman

Copy link
Copy Markdown
Member Author

Thanks, I'll look into it - it might be because of the effective double-set of the initial layout (due to how we have to handle it with ElidePermutations right now).

@jakelishman

jakelishman commented Aug 22, 2025

Copy link
Copy Markdown
Member Author

As to Sasha's comment above about the potential bug (#14826 (comment)): actually this is because our logic in the default layout plugin construction is pretty incompatible with layout or final_layout being set before the layout plugin runs. We can update that in a follow-up, when we want to start using this method in earlier passes, I think. (edit: actually there is a bug in this PR currently in that case too, but I'll fix it tomorrow now.)

There is a different problem with using the TranspileLayout.write_into_property_set normalisation indiscriminately, though, which is that if a pass that sets virtual_permutation_layout does it, and then another pass that also updates virtual_permutation_layout runs, the first pass's permutation will have been normalised into final_layout, which the normalisation assumes got filled in later than virtual_permutation_layout, which will result in incorrect results. I don't see a neat way around this (other than adding extra temporary tracking crap to the property set), and since (I'm pretty sure) all passes that set virtual_permutation_layout already fail to update virtual_permutation_layout in a consistent way, I think we can get away with it. We just need to be careful in the upgrade path, but we were always going to need to take care with the ordering of updates.

If a virtual-permutation setting pass uses
`TranspileLayout.write_into_property_set` to normalise its virtual
permutation into a final layout, we would have subsequently failed to
detect the permutation if the initial layout was (validly) reset to
`None`.  This corrects the logic.
@jakelishman

Copy link
Copy Markdown
Member Author

Ok, so to speak to Sasha's bug, there's about 3 things going on:

  • the most immediate problem is that the preset-passmanager logic uses final_layout as a proxy for "did we reach SabreLayout+Routing and the routing bit ran?". If so, it doesn't run ApplyLayout. Normalising into final_layout tricks that conditional. We can tighten up that check later, I think.
  • this PR alone doesn't make ApplyLayout use this code yet (you need Port ApplyLayout to Rust #14904 for that), and that makes ApplyLayout properly aware of how to normalise things.
  • if you deal with both of those things, there was a bug in this logic where it wouldn't handle the case of (initial_layout, virtual_permutation_layout, final_layout) matching (None, None, not-None). That's fixed in 30f40de.

With all three things fixed, Sasha's example with the modified ElidePermutations works correctly for me.

I think there'll be a tail of issues to work through as we transition the transpiler pipeline over to using TranspileLayout on DAGCircuit itself - I think I've done everything we can do in this part of the issue now, though.

@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.

This LGTM now, thanks for the updates.

@mtreinish mtreinish added this pull request to the merge queue Aug 22, 2025
Merged via the queue into Qiskit:main with commit 9fd3dd7 Aug 22, 2025
26 checks passed
@jakelishman jakelishman deleted the transpilelayout-interop-propertyset branch August 22, 2025 14:14
littlebullGit pushed a commit to littlebullGit/qiskit that referenced this pull request Sep 5, 2025
…t#14826)

* Add interoperation between `TranspileLayout` and `PropertySet`

This adds a constructor and a "destructor" to `TranspileLayout` to
cleanly centralise how the current ad-hoc `PropertySet` properties are
combined into a `TranspileLayout`, and lets them be written back out
again.  This lets passes in the middle of an execution pipeline
construct a complete `TranspileLayout` structure, based on the current
state.

This is a step along the road to centralising `TranspileLayout` into a
core part of the IR; we can now add methods to it, write transpiler
passes against `TranspileLayout`, and start transitioning the logic,
without breaking backwards compatibility.  The idea is that passes will
opt in to creating a `TranspileLayout`, mutate it, then write it back
out again.  In the future, we can have the pass manager itself manage
legacy passes by extracting the `TranspileLayout` into the `PropertySet`
before executing a legacy pass.

* Remove unused import

* Be more commital about 3.0

* Make comment more explicit about state

* Handle case of `final_layout` without `layout`

If a virtual-permutation setting pass uses
`TranspileLayout.write_into_property_set` to normalise its virtual
permutation into a final layout, we would have subsequently failed to
detect the permutation if the initial layout was (validly) reset to
`None`.  This corrects the logic.
aaryav-3 pushed a commit to aaryav-3/qiskit that referenced this pull request Oct 21, 2025
…t#14826)

* Add interoperation between `TranspileLayout` and `PropertySet`

This adds a constructor and a "destructor" to `TranspileLayout` to
cleanly centralise how the current ad-hoc `PropertySet` properties are
combined into a `TranspileLayout`, and lets them be written back out
again.  This lets passes in the middle of an execution pipeline
construct a complete `TranspileLayout` structure, based on the current
state.

This is a step along the road to centralising `TranspileLayout` into a
core part of the IR; we can now add methods to it, write transpiler
passes against `TranspileLayout`, and start transitioning the logic,
without breaking backwards compatibility.  The idea is that passes will
opt in to creating a `TranspileLayout`, mutate it, then write it back
out again.  In the future, we can have the pass manager itself manage
legacy passes by extracting the `TranspileLayout` into the `PropertySet`
before executing a legacy pass.

* Remove unused import

* Be more commital about 3.0

* Make comment more explicit about state

* Handle case of `final_layout` without `layout`

If a virtual-permutation setting pass uses
`TranspileLayout.write_into_property_set` to normalise its virtual
permutation into a final layout, we would have subsequently failed to
detect the permutation if the initial layout was (validly) reset to
`None`.  This corrects the logic.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Changelog: Added Add an "Added" entry in the GitHub Release changelog. mod: transpiler Issues and PRs related to Transpiler

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants