-
Notifications
You must be signed in to change notification settings - Fork 2.4k
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
Add ObservablesArray.apply_layout()
#12221
base: main
Are you sure you want to change the base?
Conversation
One or more of the the following people are requested to review this:
|
Pull Request Test Coverage Report for Build 9003200844Warning: 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
💛 - Coveralls |
It looks good. |
This needs a bunch of unit tests, but other than that looks fine. @t-imamichi agreed, though as long as we are happy with the function signature here we should add tests and merge, and then we can update its implementation to be more efficient later on when the internal data structure is improved for qubit sparsity. |
@chriseclectic Yes, I agree with you. We can improve the performance as a separate PR. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think the code for this function looks correct; although I'm not super familiar with ObservablesArray
data structure. However, this is missing a release note (never mind this was never public before so there isn't a need to document this as an addition to the class's interface) and also has an unrelated test file change which is causing test job failures.
This commit reverts accidental changes to the test module test_transpile_layout. These changes were causing the test to fail because the exepected layout was no longer complete.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM
I have two questions. The current implementation uses the pauli in the last qubit if there are some duplicate indices. a = ObservablesArray({"XYZ": 1})
print(a.apply_layout([1, 1, 1]))
# ObservablesArray({'IXI': 1.0}, shape=()) I don't get the following behavior. Could you check whether it is correct or not? I thought 5 is out of bounds. a = ObservablesArray({"XYZ": 1})
print(a.apply_layout([0, 1, 5]))
# ObservablesArray({'XYZ': 1.0}, shape=()) |
I don't think |
new_key = n_qubits * ["I"] | ||
for char, qubit in zip(reversed(key), layout): | ||
# Qubit position is from end of string | ||
new_key[n_qubits - 1 - qubit] = char |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
if n_qubits - 1 - qubit
< 0, the second case of my comment #12221 (comment) might occur.
It might be nice to enforce n_qubits - 1 - qubit
is non-negative.
Given the issues flagged by @t-imamichi and that we're already past the release target date I think it's safer if we just defer this to 1.2.0 at this point instead of trying to rush this in at the last minute, so I've retargeted this PR for the next release. |
I checked SparsePauliOp and found that the first case is same as In [11]: op = SparsePauliOp("XYZ")
In [12]: op
Out[12]:
SparsePauliOp(['XYZ'],
coeffs=[1.+0.j])
In [13]: op.apply_layout([1, 1, 1])
Out[13]:
SparsePauliOp(['IXI'],
coeffs=[0.-1.j])
In [14]: op.apply_layout([0, 1, 5])
---------------------------------------------------------------------------
QiskitError Traceback (most recent call last)
Cell In[14], line 1
----> 1 op.apply_layout([0, 1, 5])
File ~/tasks/4_2024/qiskit/terra/qiskit/quantum_info/operators/symplectic/sparse_pauli_op.py:1163, in SparsePauliOp.apply_layout(self, layout, num_qubits)
1161 n_qubits = num_qubits
1162 if layout is not None and any(x >= n_qubits for x in layout):
-> 1163 raise QiskitError("Provided layout contains indices outside the number of qubits.")
1164 if layout is None:
1165 layout = list(range(self.num_qubits))
QiskitError: 'Provided layout contains indices outside the number of qubits.' |
@t-imamichi For the current implementations of Eg these all work: op1 = qi.Pauli("XYZ")
op2 = qi.SparsePauliOp(["XYZ"])
op3 = ObservablesArray(["XYZ"])
for op in [op1, op2, op3]:
print(op.apply_layout([0, 1, 5], num_qubits=6)) If you remove the num_qubits kwarg, the qi ops error, while this current version does nothing, I've pushed a fix so that it will now raise the same errors as the qi ops if you don't do a permutation layout. If we wanted to change this behavior to allow expansion via an integer layout for all apply_layout ops an easy fix would be to implicitly set |
Thank you for the information, Chris. I think it's enough to make the behavior of ObservableArray.apply_layout compatible with that of SparsePauliOp.apply_layout. |
(update) since #12385 was merged, the situation has changed. I will write a new comment below #12221 (comment). Please ignore this comment. I'm checking the behavior of Case 1: negative indices
op = SparsePauliOp("XYZ")
print(op.apply_layout([0, 1, -1])
# SparsePauliOp(['XYZ'],
# coeffs=[1.+0.j])
print(op.apply_layout([0, 1, -2]))
# SparsePauliOp(['IXZ'],
# coeffs=[0.-1.j])
print(op.apply_layout([0, 1, -3]))
# SparsePauliOp(['IYX'],
# coeffs=[1.+0.j])
print(op.apply_layout([0, 1, -4]))
# 328 self._op_shape.compose(other._op_shape, qargs, front)
# 330 if qargs is not None:
#--> 331 x1, z1 = self.paulis.x[:, qargs], self.paulis.z[:, qargs]
# 332 else:
# 333 x1, z1 = self.paulis.x, self.paulis.z
#
#IndexError: index -4 is out of bounds for axis 1 with size 3
a = ObservablesArray({"XYZ": 1})
a.apply_layout([0, 1, -1])
# 170 new_key = n_qubits * ["I"]
# 171 for char, qubit in zip(reversed(key), layout):
# 172 # Qubit position is from end of string
#--> 173 new_key[n_qubits - 1 - qubit] = char
# 174 return "".join(new_key)
#
# IndexError: list assignment index out of range Case 2: resulting coefficients for duplicate indices of layoutIf we give duplicate indices, op = SparsePauliOp("XYZ")
print(op.apply_layout([0, 0, 0,])
# SparsePauliOp(['IIX'],
# coeffs=[0.-1.j])
a = ObservablesArray({"XYZ": 1})
print(a.apply_layout([0, 0, 0])
# ObservablesArray({'IIX': 1.0}, shape=()) Case 3: layout whose size is smaller than the number of qubitsIf we give a list whose size is smaller than the number of qubits as layout (e.g., op = SparsePauliOp("XYZ")
print(op.apply_layout([]))
# File ~/tasks/4_2024/qiskit/terra/qiskit/quantum_info/operators/op_shape.py:477, in OpShape.compose(self, other, qargs, front)
# 475 ret._num_qargs_r = self._num_qargs_r
# 476 if len(qargs) != other._num_qargs_r:
#--> 477 raise QiskitError(
# 478 "Number of qargs does not match ({} != {})".format(
# 479 len(qargs), other._num_qargs_r
# 480 )
# 481 )
# 482 if self._dims_l or other._dims_l:
# 483 if self.dims_l(qargs) != other.dims_r():
#
# QiskitError: 'Number of qargs does not match (0 != 3)'
print(op.apply_layout([0], 1)
# File ~/tasks/4_2024/qiskit/terra/qiskit/quantum_info/operators/symplectic/sparse_pauli_op.py:1157, in SparsePauliOp.apply_layout(self, layout, num_qubits)
# 1155 if num_qubits is not None:
# 1156 if num_qubits < n_qubits:
#-> 1157 raise QiskitError(
# 1158 f"The input num_qubits is too small, a {num_qubits} qubit layout cannot be "
# 1159 f"applied to a {n_qubits} qubit operator"
# 1160 )
# 1161 n_qubits = num_qubits
# 1162 if layout is not None and any(x >= n_qubits for x in layout):
#
# QiskitError: 'The input num_qubits is too small, a 1 qubit layout cannot be applied to a 3 qubit operator'
a = ObservablesArray({"XYZ": 1})
print(a.apply_layout([]))
# ObservablesArray({'': 1.0}, shape=())
print(a.apply_layout([0], 1))
# ObservablesArray({'Z': 1.0}, shape=()) |
I found a corner case of |
I made a PR #12385 so that |
There are some different behaviors between Case 1: negative indices
op = SparsePauliOp("XYZ")
print(op.apply_layout([0, 1, -1])
# QiskitError: 'Provided layout contains indices outside the number of qubits.'
a = ObservablesArray({"XYZ": 1})
a.apply_layout([0, 1, -1])
# 170 new_key = n_qubits * ["I"]
# 171 for char, qubit in zip(reversed(key), layout):
# 172 # Qubit position is from end of string
#--> 173 new_key[n_qubits - 1 - qubit] = char
# 174 return "".join(new_key)
#
# IndexError: list assignment index out of range Case 2: duplicate indices
op = SparsePauliOp("XYZ")
print(op.apply_layout([0, 0, 0])
# QiskitError: 'Provided layout contains duplicate indices.'
a = ObservablesArray({"XYZ": 1})
print(a.apply_layout([0, 0, 0])
# ObservablesArray({'IIX': 1.0}, shape=()) Case 3: layout whose size is smaller than the number of qubitsIf we give a list whose size is smaller than the number of qubits as layout (e.g., op = SparsePauliOp("XYZ")
print(op.apply_layout([]))
# File ~/tasks/4_2024/qiskit/terra/qiskit/quantum_info/operators/op_shape.py:477, in OpShape.compose(self, other, qargs, front)
# 475 ret._num_qargs_r = self._num_qargs_r
# 476 if len(qargs) != other._num_qargs_r:
#--> 477 raise QiskitError(
# 478 "Number of qargs does not match ({} != {})".format(
# 479 len(qargs), other._num_qargs_r
# 480 )
# 481 )
# 482 if self._dims_l or other._dims_l:
# 483 if self.dims_l(qargs) != other.dims_r():
#
# QiskitError: 'Number of qargs does not match (0 != 3)'
print(op.apply_layout([0], 1)
# File ~/tasks/4_2024/qiskit/terra/qiskit/quantum_info/operators/symplectic/sparse_pauli_op.py:1157, in SparsePauliOp.apply_layout(self, layout, num_qubits)
# 1155 if num_qubits is not None:
# 1156 if num_qubits < n_qubits:
#-> 1157 raise QiskitError(
# 1158 f"The input num_qubits is too small, a {num_qubits} qubit layout cannot be "
# 1159 f"applied to a {n_qubits} qubit operator"
# 1160 )
# 1161 n_qubits = num_qubits
# 1162 if layout is not None and any(x >= n_qubits for x in layout):
#
# QiskitError: 'The input num_qubits is too small, a 1 qubit layout cannot be applied to a 3 qubit operator'
a = ObservablesArray({"XYZ": 1})
print(a.apply_layout([]))
# ObservablesArray({'': 1.0}, shape=())
print(a.apply_layout([0], 1))
# ObservablesArray({'Z': 1.0}, shape=()) |
Is this still on-track for 1.2? |
Pushing the milestone to 1.3, there are currently open questions related to the new |
Hi @ihincks, is this PR still a priority for 1.3? I have noticed that there hasn't been a lot of activity recently to address @t-imamichi's concerns. To get into 1.3.0, it should be merged before the rc1 release date (November 7th). |
Summary
This PR adds the method
ObservablesArray.apply_layout()
which applies a transpiler layout to all elements of the array.Details and comments