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

Cannot create a numpy array of Registers #1898

Closed
Woody1193 opened this issue Mar 6, 2019 · 10 comments · Fixed by #2414
Closed

Cannot create a numpy array of Registers #1898

Woody1193 opened this issue Mar 6, 2019 · 10 comments · Fixed by #2414
Assignees
Labels
status: pending PR It has one or more PRs pending to solve this issue
Milestone

Comments

@Woody1193
Copy link
Contributor

Information

Qiskit Terra version: 0.7.1
Python version: 3.5.6 (Anaconda)
Operating System: Windows 7

What is the current behavior?

Attempting to call append or concatenate with a list of Register objects results in a NumPy array that contains integer values along with the Register objects.

Steps to reproduce the problem

from qiskit import QuantumRegister
import numpy as np

x = np.array([], dtype = QuantumRegister, ndmin = 1)
x = np.append(x, [QuantumRegister(1) for _ in range(5)])
len(x)
x

What is the expected behavior?

This should first print 5 and then print an array with 5 elements, all QuantumRegisters.

Suggested solutions

This is occurring because NumPy calls __len__ when doing an append or concatenate operation. For the time being, I can do x[::2] to remove the integer values but this is a stop-gap approach.

@ajavadia
Copy link
Member

ajavadia commented Mar 6, 2019

Just out of curiosity, why do you want to put QuantumRegisters in a numpy array?

@Woody1193
Copy link
Contributor Author

I was writing code to automate management of ancillary qubits and I was using numpy arrays to store the QuantumRegisters I was managing.

@Woody1193
Copy link
Contributor Author

I expect that quantum software components will be integrated into classical data structures more and more as we move forward so we should ensure that these operate as a programmer would expect them to when that happens.

@ajavadia ajavadia added the good first issue Good for newcomers label Mar 10, 2019
@1ucian0 1ucian0 removed the good first issue Good for newcomers label Apr 4, 2019
@1ucian0
Copy link
Member

1ucian0 commented Apr 4, 2019

I agree this is an issue (not an easy one, so removing good first issue tag). However, I'm not fully sure what "a programmer would expect".

It seems that @Woody1193 would expect this:

class QuantumRegisterDT(QuantumRegister):
    __len__ = property(lambda _: AttributeError)

x = np.array([], dtype = object, ndmin = 1)
x = np.append(x, [QuantumRegisterDT(1) for _ in range(5)])
len(x) -> 5
x -> [QuantumRegisterDT(1, 'q0') QuantumRegisterDT(1, 'q1') QuantumRegisterDT(1, 'q2') QuantumRegisterDT(1, 'q3') QuantumRegisterDT(1, 'q4')]

Nevertheless, I can argue that is also not correct. QuantumRegister (and Register in general) is an iterable. An the problem is that the elements of that iterable are also iterables (tuple specifically). This happens because we dont have a Bit class, but we piggyback tuple to express a Bit combination.

class Bit():
    def __init__(self, register, index):
        self.register = register
        self.index = index

    def __repr__(self):
        return "(%s,%s)" % (self.register, self.index)

class ExtendedQuantumRegister(QuantumRegister):
    def __iter__(self):
        """
        Returns:
            iterator: an iterator over the bits/qubits of the register, in the
                form `tuple (Register, int)`.
        """
        for qubit in zip([self]*self.size, range(self.size)):
            yield Bit(qubit[0],qubit[1])

x = np.array([], dtype = object, ndmin = 1)
x = np.append(x, [ExtendedQuantumRegister(1) for _ in range(5)])
print(len(x))
print(x)
5
[(ExtendedQuantumRegister(1, 'q0'), 0)
 (ExtendedQuantumRegister(1, 'q1'), 0)
 (ExtendedQuantumRegister(1, 'q2'), 0)
 (ExtendedQuantumRegister(1, 'q3'), 0)
 (ExtendedQuantumRegister(1, 'q4'), 0)]

In this way,

np.append(x, [ExtendedQuantumRegister(2) for _ in range(5)])
[(ExtendedQuantumRegister(2, 'q0'),0) (ExtendedQuantumRegister(2, 'q0'),1)
 (ExtendedQuantumRegister(2, 'q1'),0) (ExtendedQuantumRegister(2, 'q1'),1)
 (ExtendedQuantumRegister(2, 'q2'),0) (ExtendedQuantumRegister(2, 'q2'),1)
 (ExtendedQuantumRegister(2, 'q3'),0) (ExtendedQuantumRegister(2, 'q3'),1)
 (ExtendedQuantumRegister(2, 'q4'),0) (ExtendedQuantumRegister(2, 'q4'),1)]

So, go ahead and convince me that the I'm wrong :) @ajavadia @Woody1193

@1ucian0 1ucian0 self-assigned this Apr 4, 2019
@1ucian0
Copy link
Member

1ucian0 commented Apr 4, 2019

CC @nonhermitian , who usually have good insights on what a NumPy user would expect.

@nonhermitian
Copy link
Contributor

I would expect an array of registers to contain registers in the same manner as a list.

The original posters code however is confusing because you cannot have register as a dtype, it would be object, and doing append is super inefficient.

However, our objects should play nice with numpy arrays because it is a fundamental scientific python library.

@1ucian0
Copy link
Member

1ucian0 commented Apr 4, 2019

I would expect an array of registers to contain registers in the same manner as a list.

I'm not fully sure if I understand. You mean this?

np.append(x, [ExtendedQuantumRegister(2) for _ in range(5)])
[(ExtendedQuantumRegister(2, 'q0')
 (ExtendedQuantumRegister(2, 'q1')
 (ExtendedQuantumRegister(2, 'q2')
 (ExtendedQuantumRegister(2, 'q3')
 (ExtendedQuantumRegister(2, 'q4')]

@Woody1193
Copy link
Contributor Author

@1ucian0, the specific issue I'm having is that when you index into a QuantumRegister, you get back a tuple so, when using np.append to add a collection of QuantumRegister objects to an np.array, the append function will attempt to flatten the collection recursively, resulting in a list of QuantumRegisters and ints:

np.array( [QuantumRegister(1, 'q0'), 0, 
    QuantumRegister(1, 'q1'), 0, 
    QuantumRegister(1, 'q2'), 0, 
    QuantumRegister(1, 'q3'), 0, 
    QuantumRegister(1, 'q4'), 0] )

I can get around this by doing np.append(x, [ QuantumRegister(1) for _ in range(5) ] )[::2] but that is fragile. I could also extend QuantumRegister so it has the code you noted but that also won't fix the problem because np.append looks at __len__ to determine whether or not to flatten.

@1ucian0
Copy link
Member

1ucian0 commented Apr 5, 2019

Yes. We agree that returning a tuple is a bad idea. However, if we want to keep QuantumRegister as iterable, the result will be:

[(QuantumRegister(1, 'q0'), 0)
 (QuantumRegister(1, 'q1'), 0)
 (QuantumRegister(1, 'q2'), 0)
 (QuantumRegister(1, 'q3'), 0)
 (QuantumRegister(1, 'q4'), 0)]

I'm just trying to make sure we are all on the same page on what should it be the result.

@1ucian0 1ucian0 removed the status: needs information Further information is requested label Apr 5, 2019
@1ucian0 1ucian0 added this to the 0.9 milestone Apr 5, 2019
@1ucian0
Copy link
Member

1ucian0 commented May 14, 2019

With PR #2414:

from qiskit import QuantumRegister
import numpy as np

x = np.array([], dtype = object, ndmin = 1)
x = np.append(x, [QuantumRegister(2) for _ in range(5)])
print(len(x))
print(x)
10
[QuBit(QuantumRegister(2, 'q0'), 0) QuBit(QuantumRegister(2, 'q0'), 1)
 QuBit(QuantumRegister(2, 'q1'), 0) QuBit(QuantumRegister(2, 'q1'), 1)
 QuBit(QuantumRegister(2, 'q2'), 0) QuBit(QuantumRegister(2, 'q2'), 1)
 QuBit(QuantumRegister(2, 'q3'), 0) QuBit(QuantumRegister(2, 'q3'), 1)
 QuBit(QuantumRegister(2, 'q4'), 0) QuBit(QuantumRegister(2, 'q4'), 1)]

@1ucian0 1ucian0 added the status: pending PR It has one or more PRs pending to solve this issue label May 14, 2019
@kdk kdk closed this as completed in #2414 May 21, 2019
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
status: pending PR It has one or more PRs pending to solve this issue
Projects
None yet
Development

Successfully merging a pull request may close this issue.

4 participants