Qiskit-terra: qiskit ordering convention

Created on 27 Oct 2018  Β·  18Comments  Β·  Source: Qiskit/qiskit-terra

What is the expected enhancement?

There are a few things that we have been doing that have confused people for a while. We need to documeent this and make sure all the parts of qiskit obey our convention.

  • [x] documentation #1241

Lets start for a single register.

Tensor order

The tensor order goes

Qn x .. Q1 x Q0

This is what we have in qiskit now. This means that the a circuit that has an X on the q0 makes the state |01>

classical bits

The classical bits are ordered so that the MSB is to the left and the LSB is to the right. This is the standard binary sequence order. For the case 01 the MSB is 0 and the LSB is 1. This means that if we map the state |01> to a classical register then it is 01 and has a 1 to 1 relationship with the basis states of the quantum system. This is why in qiskit we use the non-standard tensor product order.

As a summary

binary   | int  | qubit 
0..00  <-> 0     |0..00 >
0..01  <-> 1     |0..01 >
0..10  <-> 2     |0..10 >
0..11  <-> 3     |0..11 >
1..00
1..01
1..10
1..11

visualization

@ajavadia would like this to go from the highest qubit at the top and go to the lowest at the bottom (this is not what we have in qiskit now).

qn --
.. --
.. --
q1 --
q0 --

@ajavadia prefers this as if every qubit is maped to a classical register and you read the outcome it is more natural to read top to bottom and this is the binary number the register would be in.

- [x] update the visualizations (since we like @ajavadia we can do this ) #1242

Multiple registers

This is where qiskit is the most confused. Imagine we two two regsters Q and V and that Q is declared
first.

Ie

circ = QuantumCircuit()
q = QuantumRegister(5)
v = QuantumRegister(6)
circ.add_register(q)
circ.add_register(v)

this should be the same as

q = QuantumRegister(5)
v = QuantumRegister(6)
circ = QuantumCircuit(q,v)

Note the order in the list is the order they are declared (Not tensor product)

  • [x] check qiskit does this (issue #1243)

Tensor order

The tensor product order for these registers is

Vn x ... V1 x V0 x Qn x .. Q1 x Q0
  • [x] check the simulators do this #1244

classical bits

The classical bits are order with V being the most signifcant register and Q the least significant. That way the the mapping stays 1:1 between basis states and classical bits.

  • [x] check the measurements do this if i have two classical registers. We will not have any spaced between the registers. So if the outcome is for register V is 1...01 and for Q is 0..11 then the bit string returned is 1...010..11. Currently qiskit was suppost to return it 1...01 0..11 (issue #1243)

visualization

@ajavadia would like this to go from the highest register to lowest register and if we add a new register it adds it to the top.

vn --
.. --
.. --
.. --
v1 --
v0 --
qn --
.. --
.. --
q1 --
q0 --

Reverse simply reverses everything (to the way i prefer)

q0 --
q1 --
.. --
.. --
qn --
v0 --
v1 --
.. --
.. --
.. --
vn --
  • [x] update the visualizations #1242
epic

Most helpful comment

Suppose that a user defines a custom gate that implements CNOT. The canonical, textbook form of CNOT, when qubit 0 is the control and qubit 1 is the target, is:

[[1, 0, 0, 0], [0, 1, 0, 0], [0, 0, 0, 1], [0, 0, 1, 0]]

The user will be surprised that the CNOT gate that she's defined has qubit 1 as the control.

All 18 comments

@ajavadia and @chriseclectic does this represent the summary.

Yes this is what we discussed.

I think the main point that needs to be clearly documented is that QuantumCircuit(q, v) means "add q, then add v" (v is more significant, and the left tensor). It does NOT mean q otimes v.

One minor point: why don't you want a space between classical bits? I thought we would do this in the final step in Result (obviously not in the backends, who treat the whole thing as a flat register).

I don’t mind for the space. I thought you or Chris were against it.

If you dont agree with this convention, raise your concerns asap. Otherwise, tomorrow we will break this epic in individual issues to implement this approach around the code.

Suppose that a user defines a custom gate that implements CNOT. The canonical, textbook form of CNOT, when qubit 0 is the control and qubit 1 is the target, is:

[[1, 0, 0, 0], [0, 1, 0, 0], [0, 0, 0, 1], [0, 0, 1, 0]]

The user will be surprised that the CNOT gate that she's defined has qubit 1 as the control.

This is blocking #1187. Remove the tag from #1187 once this is agreed.

@yaelbh i agree that it is different but this is why we need good documentation.

So, let's considered this as "decided". I'm creating issues for each of the subtasks.

Recently I’ve been writing a simulator based on manipulations of the density matrix. In this context, I’ve gained some insights regarding the experience of working with Qiskit qubit ordering. I expect that developers of add-ons, which are external simulators wrapped by Qiskit interface, will have similar experience. I assume that most external simulators work with the standard qubit ordering, which is different from Qiskit’s. I’m sorry for not sharing my insights earlier, this was impossible because this is really recent work. I’m not trying to take back a decision that has already been made, and with good arguments. I simply find that I have relevant and useful information so I should not keep it to myself.

The bottom line is that, when wrapping a simulator with Qiskit interface, one needs to constantly cope with the different qubit ordering. This makes the wrapper more difficult to write, resulting in not-so-nice code, and sometimes also costs some runtime. Here are a couple of concrete examples.

  1. State vectors: the external simulator provides a state vector with the amplitudes of the basis states. The first entry of the vector stores the amplitude of state 0000, the second of state 0001, etc., where, unlike Qiksit, 0001 means that qubit (n-1) is 1 while the other qubits are 0 (n denotes the number of qubits). The wrapper needs to translate this vector to a new vector, which conforms with the Qiskit qubit ordering. This dictates the insertion of the following lines to the wrapper:
def state_num2str(basis_state_as_num, nqubits):
    return '{0:b}'.format(basis_state_as_num).zfill(nqubits)

def state_str2num(basis_state_as_str):
    return int(basis_state_as_str, 2)

def state_reverse(basis_state_as_num, nqubits):
    basis_state_as_str = state_num2str(basis_state_as_num, nqubits)
    new_str = basis_state_as_str[::-1]
    return state_str2num(new_str)

adjusted_state = np.zeros(2**nqubits, dtype=complex)
for basis_state in range(2**nqubits):
     adjusted_state[state_reverse(basis_state, nqubits)] = state[basis_state]
  1. Matrices provided by the user in the input: this happens with custom gates, observables, and noise operators. If the matrix is said to apply on a list of qubits, say [4, 2, 6], then the external simulator must be fed with the inverse list of qubits: [6, 2, 4]. This facilitates bugs in the wrapper, and requires extra documentation in the wrapper.

At some point I considered getting rid of all the in-and-out conversions by writing the internals of my simulator using the Qiskit qubit ordering. I began to do it, but discovered that the conversions did not disappear but entered the simulator. For example, suppose that the simulator internally contains an array as a data structure, of length n, where each array entry corresponds to a qubit. Then entry i of the array corresponds to qubit (n-1-i). I therefore decided to keep the internals of the simulator nice and clean, and have the conversions externally, in the point where the simulator meets Qiskit.

Hi all, I'm just a qiskit beginner,

it is strange to me that the order of the qubits are reversed of the typical orders in the literature.

qr = QuantumRegister(2)
cr = ClassicalRegister(2)
circ = QuantumCircuit(qr,cr)
circ.x(qr[0])

cx_op = Operator([[1, 0, 0, 0],
                  [0, 1, 0, 0],
                  [0, 0, 0, 1],
                  [0, 0, 1, 0]])

circ.unitary(cx_op, [qr[1],qr[0]], label='cx')        # the order is reversed

One can possibly change the CX matrix as following:

cx_op = Operator([[1, 0, 0, 0],
                  [0, 0, 0, 1],
                  [0, 0, 1, 0],
                  [0, 1, 0, 0]])

But in the literature this matrix is used when you want to have CNOT with second qubit as the control one.

Anyhow I believe this is not the most straight forward way of ordering the qubits.

Google qiskit qubit order give this issue as the first result. We should add documentation to explain this convention. The best I've found in on the qiskit tutorials, terra/fundamentals/3_summary_of_quantum_operations.ipynb, Basis vector ordering in Qiskit section:

https://github.com/Qiskit/qiskit-tutorials/blob/d6fac46815c7fe6a132f0699526f999b55b76eeb/tutorials/terra/fundamentals/3_summary_of_quantum_operations.ipynb

Just leaving my opinion about the ordering convention. I found it very unnatural that the classical output bits are ordered in reverse compared to the qubits.

In my opinion, particularly when writing QuantumCircuit.measure([0,1,2], [0,1,2]), the only natural way to represent the output bits is in the same order as the qubits.

I am writing some content aimed at high school students, and I suspect students will find this aspect confusing.

@ardroc92 If it helps, the right most bit is the least significant bit. This is the bit at is multiplied by 2^0 when converting a bitstring to integer. The next bit multiplied by 2^1 and so on. So the exponent corresponds to the qubit number in the circuit.

Thanks. I understand binary notation, but I don't see why it would be desirable here.

It seems very unnatural to specify the qubits to measure as [0,1,2] and representing the outcome bits in the order [2,1,0]. I doubt any quantum computing theorist would ever write outcomes strings on paper in this order. I'd be happy to be convinced that it helps in the programming enough to offset the inconvenience that most people will reverse the list of classical bits so that the outcome bits are displayed with the qubits ordering.

@ardroc92

I found it very unnatural that the classical output bits are ordered in reverse compared to the qubits.

Not sure what you mean by this. One of the nice things about this convention is precisely that qubits and classical bits have the same order (little endian). This means q1 is more significant than q0, and c1 is more significant than c0. So if you prepare a quantum state |10> and measure q1->c1 and q0->c0, you will read out 10. And this converts to decimal nicely too: |2> -> 2.

import qiskit as qk

qc = qk.QuantumCircuit(2,2)
qc.x(1)  # excite the most significant qubit
qc.measure([0, 1], [0, 1])   # measure in the same order

sim = qk.providers.aer.QasmSimulator()
counts = qk.execute(qc, sim).result().get_counts()
print(counts)  # the MSB is 1
{'10': 1024}

By the way it's awesome you are writing high school content using Qiskit.

@ajavadia Thanks for the detailed reply!

The part that I find unnatural is the fact that if you write qc.measure([0,1], [0,1]), then as in your example, the outcome is {'10': 1024}. I would have expected it to be '01'.

What I am not a fan of is that when I draw the circuit qc using qc.draw the qubits are represented with the |0> qubit at the top and |1> qubit at the bottom. The typical convention is that the state represented by this circuit is the state |01>. So, I would expect the outcome of measuring both qubits in the same order [0,1] to be '01', not '10'. Does that make sense? Thanks.

Ok I think in your case the confusion comes from this syntax qc.measure([0,1], [0,1]).
This just means qc.measure(qubits=[0,1], clbits=[0,1]). And it measures q0 to c0 and q1 to c1. It would be exactly the same if you wrote qc.measure([1,0], [1,0]).
If this shorthand one-liner is confusing, expand it to multiple lines:

qc.measure(0, 0)  # measure q0 to c0
qc.measure(1, 1)   # measure q1 to c1

As for drawing, yes as you see in the origin of this issue I was a fan of drawing the most-significant-bit at the top. However there is an option to do that in the drawers if you prefer:

qc.draw() (default drawing)

          β”Œβ”€β”   
q_0: ──────Mβ”œβ”€β”€β”€
     β”Œβ”€β”€β”€β”β””β•₯β”˜β”Œβ”€β”
q_1: ─ X β”œβ”€β•«β”€β”€Mβ”œ
     β””β”€β”€β”€β”˜ β•‘ β””β•₯β”˜
c_0: ══════╩══╬═
              β•‘ 
c_1: ═════════╩═

qc.draw(reverse_bits=True)

     β”Œβ”€β”€β”€β”   β”Œβ”€β”
q_1: ─ X β”œβ”€β”€β”€β”€Mβ”œ
     β””β”€β”€β”€β”˜β”Œβ”€β”β””β•₯β”˜
q_0: ──────Mβ”œβ”€β•«β”€
          β””β•₯β”˜ β•‘ 
c_1: ══════╬══╩═
           β•‘    
c_0: ══════╩════

Thanks for the patience. I was clear about the sintax of qc.measure, and I understand that everything can be fixed with a "reverse" somewhere. Maybe one way to precisely frame my "complaint" is:

the choice of default ordering for the representation of the outcome string is inconsistent with the choice of default ordering for qubits in a drawn circuit.

And, I have to admit, even if the "reverse_bits = True" were to be made default I would still be slightly unsatisfied, because I believe the prevalent convention throughout quantum information for ordering and referring to qubits in a circuit is to start from the top and proceed in ascending order (i.e. modulo the Pythonic zero, the "second qubit" or "second register" is the second one from the top - this is not the case when selecting reverse_bits=True).

To recap, call the prevalent convention for ordering and referring to qubits in a circuit "a". Then the prevalent convention for ordering outcome strings is also "a". So "a,a". What qiskit currently does as default is "a, b". What the reverse_bits=True implements is "b,b" (which is an improvement over "a,b"). What I suggest be the default in qiskit is: "a,a".

Was this page helpful?
0 / 5 - 0 ratings