Repository: sympy/sympy
Pull request: Add "nqubits" parameter to IntQubit
#### References to other Issues or PRs
Fixes #9136
Fixes #12585

#### Brief description of what is fixed or changed
This PR fixes the ambiguity of `IntQubit` constructor.

```
IntQubit(3).args == Qubit('11').args   # bit pattern '11' (3) of implicit width 2
IntQubit(5, 4).args == Qubit('0101').args # bit pattern '0101' (5) of width 4.
IntQubit(1, 0, 1, 1, 1).args == Qubit('10111').args  # bit pattern '10111' of width 5

# the code below can be '1' (the second parameter is the width)
#  or '11' (the second parameter is another bit ).
#  Current implementation interpret this as '11'
IntQubit(1,1) 
```

This PR adds optional keyword argument `nqubits` to provide a workaround for the last case.
The usage of positional arguments are backward compatible.

```
IntQubit(1,1).args == Qubit('11').args
IntQubit(1, nqubits=1).args == Qubit('1').args
```

#### Other comments
`IntQubit` is a subclass of Qubit and they share most of code.
`args` of `IntQubit` needs to be the same with `Qubit`.

one point I'm not fully confident with this PR:
Is it safe to add keyword argument here?
It seems some old code has keyword argument `assumptions`, so I'm wondering there will be the chance that `Basic` get the keyword args back in some future.

#### Release Notes
<!-- BEGIN RELEASE NOTES -->
* physics.quantum
   * IntQubit now has a keyword argument "nqubit" in its constructor to avoid ambiguity
<!-- END RELEASE NOTES -->


Problem statement:
IntQubit doesn't work if the number of qubits to use is 1
Using Python 3.4 and SymPy 0.7.6, I came through this strange output : 

```
>>> from sympy.physics.quantum.qubit import Qubit, IntQubit
>>> IntQubit(0,1)
|1>
>>> IntQubit(1,1)
|3>
>>> Qubit(IntQubit(0,1))
|01>
>>> Qubit(IntQubit(1,1))
|11>
```

Shouldn't it return the same as `IntQubit(0)` and `IntQubit(1)` (that is, `|0>` and `|1>`) ? Or am I missing something ?


Wrong measurement for one qubit state.
Hi, sympy developers.

    measure_all(qapply(Qubit('0')))

returns [(|01>, 1)] but should be [(|0>, 1)]

best, Vladimir.

Hints from issue discussion:
I believe this really a bug. I think it happened because `IntQubit`  inherit `Qubit`, and `Qubit` accept two representation for qubit. For example, consider `v = |10101>` , you can use `Qubit("10101")`, or `Qubit(1,0,1,0,1)`.

I like this library, but it looks like a forest for me when I was trying to debug the code through winpdb.

---

Edit: I forget to mention that second parameter in IntQubit  refers to how many qbit would you use to encode your information. At this point program fails to distinguish between those two representations.

`>>> Qubit(IntQubit(4,7))`
|0000100>

Note that we currently do not have an active maintainer of the quantum physics modules but if you need help navigating the code, just ask here or on the sympy mailing list.

@moorepants 
I would be glad to help in Quantum modules, particularly those related to quantum computations.   However, I need some time to read  documentation. 
Thank you for informing me.

It seems that if the number of qubits is one, the argument `nqubits` on [this line](https://github.com/sympy/sympy/blob/master/sympy/physics/quantum/qubit.py#L544) will be interpreted as an extra qubit. There should be only `IntQubit(i)` with no `nqubits` argument in case `nqubits == 1`.

Commit message: removed the trailing space missed in the previous commit

Patch to review:
diff --git a/examples/advanced/grover_example.py b/examples/advanced/grover_example.py
index 15b7f441a348..5d52c28408e3 100755
--- a/examples/advanced/grover_example.py
+++ b/examples/advanced/grover_example.py
@@ -12,12 +12,12 @@
 def demo_vgate_app(v):
     for i in range(2**v.nqubits):
         print('qapply(v*IntQubit(%i, %r))' % (i, v.nqubits))
-        pprint(qapply(v*IntQubit(i, v.nqubits)))
-        qapply(v*IntQubit(i, v.nqubits))
+        pprint(qapply(v*IntQubit(i, nqubits=v.nqubits)))
+        qapply(v*IntQubit(i, nqubits=v.nqubits))
 
 
 def black_box(qubits):
-    return True if qubits == IntQubit(1, qubits.nqubits) else False
+    return True if qubits == IntQubit(1, nqubits=qubits.nqubits) else False
 
 
 def main():
diff --git a/sympy/physics/quantum/grover.py b/sympy/physics/quantum/grover.py
index ce060a59422a..1adf9e83555f 100644
--- a/sympy/physics/quantum/grover.py
+++ b/sympy/physics/quantum/grover.py
@@ -55,7 +55,7 @@ def superposition_basis(nqubits):
     """
 
     amp = 1/sqrt(2**nqubits)
-    return sum([amp*IntQubit(n, nqubits) for n in range(2**nqubits)])
+    return sum([amp*IntQubit(n, nqubits=nqubits) for n in range(2**nqubits)])
 
 
 class OracleGate(Gate):
@@ -176,7 +176,7 @@ def _represent_ZGate(self, basis, **options):
         matrixOracle = eye(nbasis)
         # Flip the sign given the output of the oracle function
         for i in range(nbasis):
-            if self.search_function(IntQubit(i, self.nqubits)):
+            if self.search_function(IntQubit(i, nqubits=self.nqubits)):
                 matrixOracle[i, i] = NegativeOne()
         return matrixOracle
 
diff --git a/sympy/physics/quantum/qexpr.py b/sympy/physics/quantum/qexpr.py
index 6aa389f62fd3..db016461c3ea 100644
--- a/sympy/physics/quantum/qexpr.py
+++ b/sympy/physics/quantum/qexpr.py
@@ -129,10 +129,10 @@ def __new__(cls, *args, **old_assumptions):
         """
 
         # First compute args and call Expr.__new__ to create the instance
-        args = cls._eval_args(args)
+        args = cls._eval_args(args, **old_assumptions)
         if len(args) == 0:
-            args = cls._eval_args(tuple(cls.default_args()))
-        inst = Expr.__new__(cls, *args, **old_assumptions)
+            args = cls._eval_args(tuple(cls.default_args()), **old_assumptions)
+        inst = Expr.__new__(cls, *args)
         # Now set the slots on the instance
         inst.hilbert_space = cls._eval_hilbert_space(args)
         return inst
diff --git a/sympy/physics/quantum/qubit.py b/sympy/physics/quantum/qubit.py
index 555493ca1e98..f8908fd16b25 100644
--- a/sympy/physics/quantum/qubit.py
+++ b/sympy/physics/quantum/qubit.py
@@ -282,10 +282,22 @@ class IntQubitState(QubitState):
     """A base class for qubits that work with binary representations."""
 
     @classmethod
-    def _eval_args(cls, args):
+    def _eval_args(cls, args, **extra_args):
         # The case of a QubitState instance
         if len(args) == 1 and isinstance(args[0], QubitState):
             return QubitState._eval_args(args)
+        # otherwise, args should be integer
+        elif not all((isinstance(a, (int, Integer)) for a in args)):
+            raise ValueError('values must be integers, got (%s)' % (tuple(type(a) for a in args),))
+        nqubits = extra_args.get('nqubits')
+        # use nqubits if specified
+        if nqubits is not None:
+            if not isinstance(nqubits, (int, Integer)):
+                raise ValueError('nqubits must be an integer, got (%s)' % type(nqubits))
+            if len(args) != 1:
+                raise ValueError(
+                    'too many positional arguments (%s). should be (number, nqubits=n)' % (args,))
+            return cls._eval_args_with_nqubits(args[0], nqubits)
         # For a single argument, we construct the binary representation of
         # that integer with the minimal number of bits.
         if len(args) == 1 and args[0] > 1:
@@ -296,15 +308,19 @@ def _eval_args(cls, args):
         # For two numbers, the second number is the number of bits
         # on which it is expressed, so IntQubit(0,5) == |00000>.
         elif len(args) == 2 and args[1] > 1:
-            need = bitcount(abs(args[0]))
-            if args[1] < need:
-                raise ValueError(
-                    'cannot represent %s with %s bits' % (args[0], args[1]))
-            qubit_values = [(args[0] >> i) & 1 for i in reversed(range(args[1]))]
-            return QubitState._eval_args(qubit_values)
+            return cls._eval_args_with_nqubits(args[0], args[1])
         else:
             return QubitState._eval_args(args)
 
+    @classmethod
+    def _eval_args_with_nqubits(cls, number, nqubits):
+        need = bitcount(abs(number))
+        if nqubits < need:
+            raise ValueError(
+                'cannot represent %s with %s bits' % (number, nqubits))
+        qubit_values = [(number >> i) & 1 for i in reversed(range(nqubits))]
+        return QubitState._eval_args(qubit_values)
+
     def as_int(self):
         """Return the numerical value of the qubit."""
         number = 0
@@ -341,9 +357,17 @@ class IntQubit(IntQubitState, Qubit):
     values : int, tuple
         If a single argument, the integer we want to represent in the qubit
         values. This integer will be represented using the fewest possible
-        number of qubits. If a pair of integers, the first integer gives the
-        integer to represent in binary form and the second integer gives
-        the number of qubits to use.
+        number of qubits.
+        If a pair of integers and the second value is more than one, the first
+        integer gives the integer to represent in binary form and the second
+        integer gives the number of qubits to use.
+        List of zeros and ones is also accepted to generate qubit by bit pattern.
+
+    nqubits : int
+        The integer that represents the number of qubits.
+        This number should be passed with keyword ``nqubits=N``.
+        You can use this in order to avoid ambiguity of Qubit-style tuple of bits.
+        Please see the example below for more details.
 
     Examples
     ========
@@ -372,6 +396,21 @@ class IntQubit(IntQubitState, Qubit):
 
         >>> Qubit(q)
         |101>
+
+    Please note that ``IntQubit`` also accepts a ``Qubit``-style list of bits.
+    So, the code below yields qubits 3, not a single bit ``1``.
+
+        >>> IntQubit(1, 1)
+        |3>
+
+    To avoid ambiguity, use ``nqubits`` parameter.
+    Use of this keyword is recommended especially when you provide the values by variables.
+
+        >>> IntQubit(1, nqubits=1)
+        |1>
+        >>> a = 1
+        >>> IntQubit(a, nqubits=1)
+        |1>
     """
     @classmethod
     def dual_class(self):
@@ -541,7 +580,7 @@ def measure_all(qubit, format='sympy', normalize=True):
         for i in range(size):
             if m[i] != 0.0:
                 results.append(
-                    (Qubit(IntQubit(i, nqubits)), m[i]*conjugate(m[i]))
+                    (Qubit(IntQubit(i, nqubits=nqubits)), m[i]*conjugate(m[i]))
                 )
         return results
     else:
diff --git a/sympy/physics/quantum/tests/test_grover.py b/sympy/physics/quantum/tests/test_grover.py
index c14d492c4709..2f4f2e69685b 100644
--- a/sympy/physics/quantum/tests/test_grover.py
+++ b/sympy/physics/quantum/tests/test_grover.py
@@ -11,17 +11,17 @@ def return_one_on_two(qubits):
 
 
 def return_one_on_one(qubits):
-    return qubits == IntQubit(1, qubits.nqubits)
+    return qubits == IntQubit(1, nqubits=qubits.nqubits)
 
 
 def test_superposition_basis():
     nbits = 2
-    first_half_state = IntQubit(0, nbits)/2 + IntQubit(1, nbits)/2
+    first_half_state = IntQubit(0, nqubits=nbits)/2 + IntQubit(1, nqubits=nbits)/2
     second_half_state = IntQubit(2, nbits)/2 + IntQubit(3, nbits)/2
     assert first_half_state + second_half_state == superposition_basis(nbits)
 
     nbits = 3
-    firstq = (1/sqrt(8))*IntQubit(0, nbits) + (1/sqrt(8))*IntQubit(1, nbits)
+    firstq = (1/sqrt(8))*IntQubit(0, nqubits=nbits) + (1/sqrt(8))*IntQubit(1, nqubits=nbits)
     secondq = (1/sqrt(8))*IntQubit(2, nbits) + (1/sqrt(8))*IntQubit(3, nbits)
     thirdq = (1/sqrt(8))*IntQubit(4, nbits) + (1/sqrt(8))*IntQubit(5, nbits)
     fourthq = (1/sqrt(8))*IntQubit(6, nbits) + (1/sqrt(8))*IntQubit(7, nbits)
@@ -35,30 +35,30 @@ def test_OracleGate():
 
     nbits = 2
     v = OracleGate(2, return_one_on_two)
-    assert qapply(v*IntQubit(0, nbits)) == IntQubit(0, nbits)
-    assert qapply(v*IntQubit(1, nbits)) == IntQubit(1, nbits)
+    assert qapply(v*IntQubit(0, nbits)) == IntQubit(0, nqubits=nbits)
+    assert qapply(v*IntQubit(1, nbits)) == IntQubit(1, nqubits=nbits)
     assert qapply(v*IntQubit(2, nbits)) == -IntQubit(2, nbits)
     assert qapply(v*IntQubit(3, nbits)) == IntQubit(3, nbits)
 
-    # Due to a bug of IntQubit, this first assertion is buggy
-    # assert represent(OracleGate(1, lambda qubits: qubits == IntQubit(0)), nqubits=1) == \
-    #     Matrix([[-1/sqrt(2), 0], [0, 1/sqrt(2)]])
+    assert represent(OracleGate(1, lambda qubits: qubits == IntQubit(0)), nqubits=1) == \
+           Matrix([[-1, 0], [0, 1]])
     assert represent(v, nqubits=2) == Matrix([[1, 0, 0, 0], [0, 1, 0, 0], [0, 0, -1, 0], [0, 0, 0, 1]])
 
+
 def test_WGate():
     nqubits = 2
     basis_states = superposition_basis(nqubits)
     assert qapply(WGate(nqubits)*basis_states) == basis_states
 
-    expected = ((2/sqrt(pow(2, nqubits)))*basis_states) - IntQubit(1, nqubits)
-    assert qapply(WGate(nqubits)*IntQubit(1, nqubits)) == expected
+    expected = ((2/sqrt(pow(2, nqubits)))*basis_states) - IntQubit(1, nqubits=nqubits)
+    assert qapply(WGate(nqubits)*IntQubit(1, nqubits=nqubits)) == expected
 
 
 def test_grover_iteration_1():
     numqubits = 2
     basis_states = superposition_basis(numqubits)
     v = OracleGate(numqubits, return_one_on_one)
-    expected = IntQubit(1, numqubits)
+    expected = IntQubit(1, nqubits=numqubits)
     assert qapply(grover_iteration(basis_states, v)) == expected
 
 
@@ -83,7 +83,7 @@ def test_grover_iteration_2():
 
 def test_grover():
     nqubits = 2
-    assert apply_grover(return_one_on_one, nqubits) == IntQubit(1, nqubits)
+    assert apply_grover(return_one_on_one, nqubits) == IntQubit(1, nqubits=nqubits)
 
     nqubits = 4
     basis_states = superposition_basis(nqubits)
diff --git a/sympy/physics/quantum/tests/test_qubit.py b/sympy/physics/quantum/tests/test_qubit.py
index cca880a5bdd9..3e91eed50438 100644
--- a/sympy/physics/quantum/tests/test_qubit.py
+++ b/sympy/physics/quantum/tests/test_qubit.py
@@ -53,6 +53,21 @@ def test_QubitBra():
 
 
 def test_IntQubit():
+    # issue 9136
+    iqb = IntQubit(0, nqubits=1)
+    assert qubit_to_matrix(Qubit('0')) == qubit_to_matrix(iqb)
+
+    qb = Qubit('1010')
+    assert qubit_to_matrix(IntQubit(qb)) == qubit_to_matrix(qb)
+
+    iqb = IntQubit(1, nqubits=1)
+    assert qubit_to_matrix(Qubit('1')) == qubit_to_matrix(iqb)
+    assert qubit_to_matrix(IntQubit(1)) == qubit_to_matrix(iqb)
+
+    iqb = IntQubit(7, nqubits=4)
+    assert qubit_to_matrix(Qubit('0111')) == qubit_to_matrix(iqb)
+    assert qubit_to_matrix(IntQubit(7, 4)) == qubit_to_matrix(iqb)
+
     iqb = IntQubit(8)
     assert iqb.as_int() == 8
     assert iqb.qubit_values == (1, 0, 0, 0)
@@ -76,6 +91,9 @@ def test_IntQubit():
     assert iqb._eval_innerproduct_IntQubitBra(iqb_bra) == Integer(0)
     raises(ValueError, lambda: IntQubit(4, 1))
 
+    raises(ValueError, lambda: IntQubit('5'))
+    raises(ValueError, lambda: IntQubit(5, '5'))
+    raises(ValueError, lambda: IntQubit(5, nqubits='5'))
 
 def test_superposition_of_states():
     state = 1/sqrt(2)*Qubit('01') + 1/sqrt(2)*Qubit('10')
@@ -167,6 +185,9 @@ def test_measure_all():
     assert measure_all(state2) == \
         [(Qubit('00'), Rational(4, 5)), (Qubit('11'), Rational(1, 5))]
 
+    # from issue #12585
+    assert measure_all(qapply(Qubit('0'))) == [(Qubit('0'), 1)]
+
 
 def test_eval_trace():
     q1 = Qubit('10110')


Code review comments:
diff_hunk: @@ -282,10 +282,22 @@ class IntQubitState(QubitState):
     """A base class for qubits that work with binary representations."""
 
     @classmethod
-    def _eval_args(cls, args):
+    def _eval_args(cls, args, **extra_args):
path: sympy/physics/quantum/qubit.py
text: @user1:
This should go in the parameter list, 

```suggestion
    def _eval_args(cls, args, nqubits=None):
```

Not only is this better for things like `help()` that show the function signature, but it disallows other keyword arguments (like say a misspelled "nquibits"), which with `**extra_args` would just be silently ignored. 

@author:
That's true! Thank you for the feedback, I will fix it.