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

Final layout is not invoked in the transpiler unless the swap mapper is called. #10818

Closed
jaygambetta opened this issue Sep 12, 2023 · 2 comments · Fixed by #10835
Closed

Final layout is not invoked in the transpiler unless the swap mapper is called. #10818

jaygambetta opened this issue Sep 12, 2023 · 2 comments · Fixed by #10835
Labels
bug Something isn't working

Comments

@jaygambetta
Copy link
Member

Environment

  • Qiskit Terra version:
  • Python version:
  • Operating system:

What is happening?

In the following code

theta = Parameter('theta')
qc_example = QuantumCircuit(3)
qc_example.h(0) # generate superposition
qc_example.p(theta, 0) # add quantum phase
qc_example.cx(0, 1) # condition 1st qubit on 0th qubit
qc_example.cx(0, 2) # condition 2nd qubit on 0th qubit

qc_ibm = transpile(qc_example, basis_gates = ['cz', 'sx', 'rz'],  coupling_map =[[0, 1], [1, 2]], optimization_level=3)
print(qc_ibm)

The transpiler performs a mapping but only initial_layout is updated not the final_laytout is set to none

qc_ibm.layout

gives

TranspileLayout(initial_layout=Layout({
1: Qubit(QuantumRegister(3, 'q'), 0),
2: Qubit(QuantumRegister(3, 'q'), 1),
0: Qubit(QuantumRegister(3, 'q'), 2)
}), input_qubit_mapping={Qubit(QuantumRegister(3, 'q'), 0): 0, Qubit(QuantumRegister(3, 'q'), 1): 1, Qubit(QuantumRegister(3, 'q'), 2): 2}, final_layout=None)

This should give

final_layout=Layout({
1: Qubit(QuantumRegister(3, 'q'), 0),
2: Qubit(QuantumRegister(3, 'q'), 1),
0: Qubit(QuantumRegister(3, 'q'), 2)

How can we reproduce the issue?

see code above

What should happen?

This should give

final_layout=Layout({
1: Qubit(QuantumRegister(3, 'q'), 0),
2: Qubit(QuantumRegister(3, 'q'), 1),
0: Qubit(QuantumRegister(3, 'q'), 2)

Any suggestions?

In the pass_manager after choosing the initial_layout to the swap mapper set the final_layout and only update it after the final layout.

Personally I find the notation very confusing. It took me a while (actually I never did @nonhermitian explained it to me) to realize initial_layout is not the initial_layout but the initial_layout to the swap mapper and the final_layout is the layout after the swap mapper (also no idea what 'input_qubit_mapping' is). Personally I think layout should just be the final one but if the user wants the history of how the layout changed then the should have meta data that correlates passes used with layout changes. But I think this would be a longer issue. My suggestion is to perform a quick update to set final_layout for this example and then in the future create an issue for what should be in the layout after the transpiler and how a user can get meta data that includes how the layout changes.

@jaygambetta jaygambetta added the bug Something isn't working label Sep 12, 2023
@mtreinish
Copy link
Member

Actually None is equivalent to a trivial final layout. So it would be:

Layout({
0: Qubit(QuantumRegister(3, 'q'), 0),
1: Qubit(QuantumRegister(3, 'q'), 1),
2: Qubit(QuantumRegister(3, 'q'), 2)

The final layout is mapping the qubit object in it's position in qc_ibm.qubits at the start of the circuit (after the initial layout) to the output position after routing; not the original circuit's position to the final position like the name implies. So if qc_ibm.layout.final_layout is None that means routing wasn't need and there is no output permutation caused by swap mapping. That being said we can update the default in the transpiler output to always populate final_layout even if routing doesn't run, that probably would make it easier for people as you don't need to explicitly handle the None case.

The input_qubit_mapping object attribute is the positions for the virtual qubit objects in the input circuit, that information is lost from the transpiler's output circuit and if we want to reverse the permutations caused by the transpiler we need to know where the qubits started in the input circuit. The best example to see how all these attributes are used is in Operator.from_circuit (

qargs = None
# If there was a layout specified (either from the circuit
# or via user input) use that to set qargs to permute qubits
# based on that layout
if layout is not None:
physical_to_virtual = layout.initial_layout.get_physical_bits()
qargs = [
layout.input_qubit_mapping[physical_to_virtual[physical_bit]]
for physical_bit in range(len(physical_to_virtual))
]
# Convert circuit to an instruction
instruction = circuit.to_instruction()
op._append_instruction(instruction, qargs=qargs)
# If final layout is set permute output indices based on layout
if final_layout is not None:
perm_pattern = [final_layout._v2p[v] for v in circuit.qubits]
op = op.apply_permutation(perm_pattern, front=False)
) which reverses the permutation tracked in qc_ibm.layout so that it's operator is the same as the input circuit qc's.

The TranspileLayout object is basically already what you're describing where each stage tracks the permutation it causes to the circuit (per pass is too fine grained as earlier permutations may be changed later in the pipeline). That being said the names are confusing (I also don't like the structures we use for this as I would just like arrays of integers), but it's an interface that people are relying on and while not super ergonomic it is workable. For me the path forward here is to improve the documentation, right now we have some description in: https://qiskit.org/documentation/stubs/qiskit.transpiler.TranspileLayout.html#qiskit.transpiler.TranspileLayout and I feel like we can improve that and also include better examples of how to work with it.

@jaygambetta
Copy link
Member Author

jaygambetta commented Sep 12, 2023

@mtreinish while I agree with all you views and why I just started an issue on this. I still think there is a bug as whatever pass is used in optimization permuted the qubits so in this case final_layout should be changed as it is different from the initial layout I used in the circuit. I agree that it is none as the assumption is initial_layout= final_layout in this case so this is my fix I am proposing that if a initial_layout is set before the swapper we copy that into final and if the swapper changes it great but this implies final is the same as the circuit I made which it is not. The transpiler picked a better set.

mtreinish added a commit to mtreinish/qiskit-core that referenced this issue Sep 13, 2023
This commit updates the TranspileLayout class to make it easier to work
with. The TranspileLayout class was orginally just an a container class
that took the information returned from the transpiler needed to reverse
the transpiler layout permutation (in the absence of ancillas) so that
`QuantumCircuit.from_circuit()` could compare Operator instances across
a `transpile()`. However, the internal data structures used by the
transpiler are hard to work with, and don't map well to the normal
reasoning around how the transpiler transforms the circuit. To improve
the usability of the class this commit adds 4 new methods to the class:

 - initial_layout_list() and final_layout_list() which compute a list
   form of the initial_layout and final_layout respectively
 - full_layout() which generates a list that maps the input circuits
   qubit positions in the input circuit to the final position at
   the end of the circuit (which is what most people think of when they
   hear "final layout")
 - permute_sparse_pauli_op() which takes in a SparsePauliOp object and
   permutes it based on the full layout. This is especially useful when
   combined with the Estimator primitive

To implement these functions a few extra pieces of information are
needed to fully implement them. The first is we need to know the number
of original circuit qubits. This is used to account for any ancillas
that are added in the circuit, once we have the input circuit count we
can use the original_qubit_indices attribute to discern which qubits in
the initial layout are from the original circuit and which are ancillas.
The second piece of information needed is the list of qubits in the
output circuit. as this is needed to look up the position of the virtual
qubit in the final_layout. These are both added as optional private
attributes to the TranspileLayout class and there are some small changes
to the pass manager and QPY to accomodate them.

Similarly the change in the TranspileLayout class requires a new QPY
version to include the missing details that were not being serialized
in QPY and weren't representable in the previous payload format.

Fixes Qiskit#10826
Fixes Qiskit#10818
github-merge-queue bot pushed a commit that referenced this issue Oct 3, 2023
* Improve the ergonomics of the TranspileLayout class

This commit updates the TranspileLayout class to make it easier to work
with. The TranspileLayout class was orginally just an a container class
that took the information returned from the transpiler needed to reverse
the transpiler layout permutation (in the absence of ancillas) so that
`QuantumCircuit.from_circuit()` could compare Operator instances across
a `transpile()`. However, the internal data structures used by the
transpiler are hard to work with, and don't map well to the normal
reasoning around how the transpiler transforms the circuit. To improve
the usability of the class this commit adds 4 new methods to the class:

 - initial_layout_list() and final_layout_list() which compute a list
   form of the initial_layout and final_layout respectively
 - full_layout() which generates a list that maps the input circuits
   qubit positions in the input circuit to the final position at
   the end of the circuit (which is what most people think of when they
   hear "final layout")
 - permute_sparse_pauli_op() which takes in a SparsePauliOp object and
   permutes it based on the full layout. This is especially useful when
   combined with the Estimator primitive

To implement these functions a few extra pieces of information are
needed to fully implement them. The first is we need to know the number
of original circuit qubits. This is used to account for any ancillas
that are added in the circuit, once we have the input circuit count we
can use the original_qubit_indices attribute to discern which qubits in
the initial layout are from the original circuit and which are ancillas.
The second piece of information needed is the list of qubits in the
output circuit. as this is needed to look up the position of the virtual
qubit in the final_layout. These are both added as optional private
attributes to the TranspileLayout class and there are some small changes
to the pass manager and QPY to accomodate them.

Similarly the change in the TranspileLayout class requires a new QPY
version to include the missing details that were not being serialized
in QPY and weren't representable in the previous payload format.

Fixes #10826
Fixes #10818

* Handle None for the private TranspileLayout fields in QPY

* Add test for full_layout with ancillas

* Rework interface to be more consistent

This commit reworks the names of the new methods added in this API to be
more self consistent. It uses the format '*_index_layout' to replace the
`*_layout_list` name. Similarly new methods `*_virtual_layout` are added
for methods that return layout objects.

* Apply suggestions from code review

Co-authored-by: Jake Lishman <[email protected]>

* Remove permute_sparse_pauli_op()

This commit remove the TranspileLayout.permute_sparse_pauli_op() method.
This was a bit out of place on the TranspileLayout class, and it will
instead be replaced in a follow-up PR by a apply_layout() method on the
SparePauliOp class that takes in a TranspileLayout object.

* Update docs

* Fix docs build post-rebase

* Fix docs issues

---------

Co-authored-by: Jake Lishman <[email protected]>
rupeshknn pushed a commit to rupeshknn/qiskit that referenced this issue Oct 9, 2023
* Improve the ergonomics of the TranspileLayout class

This commit updates the TranspileLayout class to make it easier to work
with. The TranspileLayout class was orginally just an a container class
that took the information returned from the transpiler needed to reverse
the transpiler layout permutation (in the absence of ancillas) so that
`QuantumCircuit.from_circuit()` could compare Operator instances across
a `transpile()`. However, the internal data structures used by the
transpiler are hard to work with, and don't map well to the normal
reasoning around how the transpiler transforms the circuit. To improve
the usability of the class this commit adds 4 new methods to the class:

 - initial_layout_list() and final_layout_list() which compute a list
   form of the initial_layout and final_layout respectively
 - full_layout() which generates a list that maps the input circuits
   qubit positions in the input circuit to the final position at
   the end of the circuit (which is what most people think of when they
   hear "final layout")
 - permute_sparse_pauli_op() which takes in a SparsePauliOp object and
   permutes it based on the full layout. This is especially useful when
   combined with the Estimator primitive

To implement these functions a few extra pieces of information are
needed to fully implement them. The first is we need to know the number
of original circuit qubits. This is used to account for any ancillas
that are added in the circuit, once we have the input circuit count we
can use the original_qubit_indices attribute to discern which qubits in
the initial layout are from the original circuit and which are ancillas.
The second piece of information needed is the list of qubits in the
output circuit. as this is needed to look up the position of the virtual
qubit in the final_layout. These are both added as optional private
attributes to the TranspileLayout class and there are some small changes
to the pass manager and QPY to accomodate them.

Similarly the change in the TranspileLayout class requires a new QPY
version to include the missing details that were not being serialized
in QPY and weren't representable in the previous payload format.

Fixes Qiskit#10826
Fixes Qiskit#10818

* Handle None for the private TranspileLayout fields in QPY

* Add test for full_layout with ancillas

* Rework interface to be more consistent

This commit reworks the names of the new methods added in this API to be
more self consistent. It uses the format '*_index_layout' to replace the
`*_layout_list` name. Similarly new methods `*_virtual_layout` are added
for methods that return layout objects.

* Apply suggestions from code review

Co-authored-by: Jake Lishman <[email protected]>

* Remove permute_sparse_pauli_op()

This commit remove the TranspileLayout.permute_sparse_pauli_op() method.
This was a bit out of place on the TranspileLayout class, and it will
instead be replaced in a follow-up PR by a apply_layout() method on the
SparePauliOp class that takes in a TranspileLayout object.

* Update docs

* Fix docs build post-rebase

* Fix docs issues

---------

Co-authored-by: Jake Lishman <[email protected]>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working
Projects
None yet
Development

Successfully merging a pull request may close this issue.

2 participants