Open kevinsung opened 2 years ago
I think the right way to go about this is to add a permute
method to the operator/statevector/pauli-op/etc classes in qiskit.quantum_info
and leave other things alone. A lot of combinatorial operations on quantum_info
classes already support this via the qargs
keyword argument (technically permute
can already be achieved inefficiently by composing Operator
with the identity operator, with a suitable qargs
, but obviously that's not ideal). We are missing a single permute
, though. Operator
, DensityMatrix
and Statevector
already have reverse_qargs
, which might even be enough for your use cases.
Qiskit (Terra) internally reliably uses the little-endian convention across the board when crossing API boundaries (like IQX -> Terra, etc), and I don't think it's a good idea to blur that (e.g. I'm not 100% convinced that it's a good idea to add arguments to any Result
functions). Consistency is key there. But permutation of our quantum_info
objects is good, and definitely a nice feature we could/should support.
@jakelishman Permuting quantum objects is a good feature, but it is a separate issue. Permuting a quantum object is a physical operation (i.e. permuting qubits is equivalent to performing a SWAP gate on them). This issue is about requesting information about an object, but in a different convention than the one Qiskit uses by default. Of course, this can be simulated by permuting the objects, but that is only a workaround/hack.
I see this issue is raising its head again.
It was decided several years ago that Qiskit would use the little-endian convention everywhere as it makes quite a bit of sense. I am not sure that this issue will gain much traction given that all agreed that this was the way forward many years ago, and mixing conventions is never a good thing.
I understand the concern about mixing conventions, but I don't think it's such a big deal. I'm not suggesting mixing conventions anywhere within the library, but only providing the option to the end user. Besides, the Qiskit "ecosystem" has already started mixing conventions, considering that both Qiskit Nature and mthree use big-endian.
M3 uses the standard Qiskit convention. Qiskit Nature should as well, save for perhaps some unintentional usage
@nonhermitian
import mthree
from qiskit import QuantumCircuit
from qiskit.providers.aer import AerSimulator
backend = AerSimulator(method='statevector')
mit = mthree.M3Mitigation(backend)
mit.cals_from_system(range(2))
circuit = QuantumCircuit(2)
circuit.x(0)
circuit.measure_all()
counts = backend.run(circuit, shots=100).result().get_counts()
quasis = mit.apply_correction(counts, range(2))
print(f"Expectation of 01: {quasis.expval('01')}")
print(f"Expectation of 10: {quasis.expval('10')}")
prints
Expectation of 01: 0.0
Expectation of 10: 1.0
The developers of Qiskit Nature deliberately chose to use big-endian instead of little-endian throughout the library (as @mrossinek can confirm). When they convert Nature operators to Terra operators, a silent switch to little-endian is performed.
I'm happy to accept a small, self-contained and composable function permute
that is the more general case of reverse_qargs
, but Terra already has a convention for API boundaries (requesting objects), and I don't want to accept a PR muddying those waters because of the associated maintenance cost, in software, documentation and mental model for users.
If you want the returned results to be in a different order when you retrieve them, you can already build the initial quantum circuit using your preferred convention, which doesn't involve any physical operations. You can easily do this without any reference to bit numbers by using the exact Qubit
instances you want as the "indices" when calling circuit methods: it's totally fine to do
from qiskit.circuit import QuantumCircuit, Qubit, Clbit
my_qubits = [Qubit() for _ in [None]*10]
my_clbits = [Clbit() for _ in [None]*10]
my_qlayout = [my_qubits[x] for x in <whatever layout>]
my_clayout = [my_clbits[x] for x in <whatever layout>]
circuit = QuantumCircuit(my_qlayout, my_clayout)
circuit.h(my_qubits[0])
circuit.cx(my_qubits[0], my_qubits[1])
circuit.measure(my_qubits[1], my_clbits[1])
(In fact, internally this is actually the most efficient way to construct a circuit, though the difference is marginal.)
The hypothetical Operator.permute
isn't a physical operation - it'd be implemented just as a Numpy transposition on the matrix representation of an operator, and the quantum_info
module has nothing to do with hardware at all. To be clear, this is a nice feature, and I'd be happy to accept it as a helper method for the quantum_info
classes. All I'm opposed to is adding extra keyword arguments while objects are being construction - the assumption should always be that everything internally was constructed in a little-endian order, and if a user wants to do post-processing on objects after the fact, it's a small, composable function.
For those reasons, I'm actually not a fan of QuantumCircuit.reverse_qubits
because QuantumCircuit
is always under construction, but the ship has sailed on that. Unfortunately, its existence has produced a fairly constant stream of bugs (for example #7415 and #6731, but related issues cause issues in visualisation too), which is where I'm coming from on the maintenance burden side.
@jakelishman I tried to simulate the circuit in your code using AerSimulator but got
Simulation failed and returned the following error message:
ERROR: Failed to load qobj: Invalid Qobj experiment: not enough memory slots.
The hypothetical Operator.permute isn't a physical operation - it'd be implemented just as a Numpy transposition on the matrix representation of an operator, and the quantum_info module has nothing to do with hardware at all.
By "physical operation" I did not mean that it is related to hardware. I meant that it is a physically significant operation. For example, suppose the operator represents the Hamiltonian of a particular physical system being modeled. Enacting a non-trivial permutation on the operator would result in a different operator that does not necessarily model the same physical system any more.
Oh, that's pretty weird about the AerSimulator
error - it should work just fine. Certainly QuantumCircuit
should have absolutely no problems representing that, so it'll be a bug in serialisation to or deserialisation from Qobj
(possibly irredeemably within in the current spec, which requires registers, but that's already known and will be taken care of).
At any rate, if you put the bits in the circuit with
my_qlayout = QuantumRegister(bits=[my_qubits[x] for x in ...])
my_clayout = ClassicalRegister(bits=[my_clbits[x] for x in ...])
circuit = QuantumCircuit(my_qlayout, my_clayout)
it should be fine again (you can still use my_qubits[x]
in the gate calls). My bad - I thought registerless bits weren't a problem for Aer, so I was just lazy typing out my example.
Sure, but you can do all your calculations in a "wrong" convention as long as you're consistent, then just permute them into the "right" convention whenever you want to look at the results. Nothing in there changes the physical meaning. In circuits where the layout demands physical swap gates be inserted, Terra's transpiler will actually do this logical permutation already by mutating any final measure
instructions instead of applying noisy gates. I'm happy to accept a PR that adds this sort of post-processing permutation in the quantum_info
classes, but not one that muddies the waters of Terra's internal convention.
Just to mention it again in case you missed it: it's actually already possible in quantum_info
to compose operators in unusual patterns of the tensor arrangement - that's what the qargs
parameter is for in compose
- and you can swap between big-endian and little-endian conventions in post with the reverse_qargs
method on some (but not all - happy for a PR) quantum_info
classes.
Thanks. As I said above, I am indeed aware that permuting objects can be used to achieve the same effect as reordering labels. But again, this is just a workaround/hack.
If the existing forms can be used for the post-processing you'd like, I'll close this issue, but if you'd like to mentor a project to expand permute
in quantum_info
, I'll happily leave it open.
What should we add?
Qiskit uses the little endian convention for ordering the factors in a tensor product. For example, the statevector for a 3-qubit state would use the ordering
Q2 ⊗ Q1 ⊗ Q0
. It would be useful to add support for specifying the tensor product order. This could be used, for example, to request a state vector using the common big endian ordering.This can be implemented by adding a
qubit_order
argument to all functions that implicitly rely on a choice of tensor product ordering. This argument would be a list of the qubit indices in the desired tensor product order. So for the example above, it would be[2, 1, 0]
to get the state in big-endian order.The following is a non-exhaustive list of functions that would be affected:
Result.get_statevector
Result.get_counts
Operator
SparsePauliOp
Statevector
quantum_info
module