Repository: Qiskit/qiskit

Issue:
casting parametric parameters into int in addition to floats
<!-- ⚠️ If you do not respect this template, your issue will be closed -->
<!-- ⚠️ Make sure to browse the opened and closed issues -->

### Informations

- **Qiskit version**: 0.20.0
- **Python version**: 3.8.5
- **Operating system**: OSX

### What is the current behavior?

When trying to bind an integer for a circuit.Parameter() into a qiskit.pulse.library.Waveform(), the parameter needs to be casted into a float, then into an integer before it can be bound.

references:
https://qiskit.org/documentation/tutorials/pulse/6_pulse_scheduler.html

### Steps to reproduce the problem

def gs_pulse(duration):
    duration = int(float(duration)) # hacky solution needed ATM... TODO?
    return qiskit.pulse.library.GaussianSquare(duration,0.5,64,64)

def random_2q_gate(x):
    sched = qiskit.pulse.Schedule()
    sched = sched.insert(0,Play(gs_pulse(x), DriveChannel(1)))
    return sched

my_random_2q_gate = ParameterizedSchedule(random_2q_gate, parameters=['x'])

### What is the expected behavior?

binding a parameter should maintain the datatype through to the parameterExpression

### Suggested solutions

Talk to @lcapelluto , @taalexander , or #4940 



Discussion:
@lcapelluto Is the problem here that if you bind a `ParameterExpression` to an int value, it currently can only be cast to a float? It seems like it'd be fairly simple to add an additional casting operator to `ParameterExpression` to support ints. I'll give a quick PR a shot.
Actually, I may roll it into #4940 

Resolving patch:
diff --git a/qiskit/circuit/parameterexpression.py b/qiskit/circuit/parameterexpression.py
--- a/qiskit/circuit/parameterexpression.py
+++ b/qiskit/circuit/parameterexpression.py
@@ -249,6 +249,12 @@ def __float__(self):
                             'cannot be cast to a float.'.format(self.parameters))
         return float(self._symbol_expr)
 
+    def __int__(self):
+        if self.parameters:
+            raise TypeError('ParameterExpression with unbound parameters ({}) '
+                            'cannot be cast to an int.'.format(self.parameters))
+        return int(self._symbol_expr)
+
     def __copy__(self):
         return self
 


Test patch:
diff --git a/test/python/circuit/test_parameters.py b/test/python/circuit/test_parameters.py
--- a/test/python/circuit/test_parameters.py
+++ b/test/python/circuit/test_parameters.py
@@ -13,26 +13,24 @@
 """Test circuits with variable parameters."""
 
 import pickle
-from operator import add, sub, mul, truediv
-
+from operator import add, mul, sub, truediv
 from test import combine
-import numpy
 
-from ddt import ddt, data
+import numpy
+from ddt import data, ddt
 
 import qiskit
 import qiskit.circuit.library as circlib
-from qiskit import BasicAer
-from qiskit import QuantumRegister, ClassicalRegister, QuantumCircuit
-from qiskit.circuit import Gate, Instruction
-from qiskit.circuit import Parameter, ParameterVector, ParameterExpression
+from qiskit import BasicAer, ClassicalRegister, QuantumCircuit, QuantumRegister
+from qiskit.circuit import (Gate, Instruction, Parameter, ParameterExpression,
+                            ParameterVector)
 from qiskit.circuit.exceptions import CircuitError
 from qiskit.compiler import assemble, transpile
 from qiskit.execute import execute
+from qiskit.quantum_info import Operator
 from qiskit.test import QiskitTestCase
 from qiskit.test.mock import FakeOurense
 from qiskit.tools import parallel_map
-from qiskit.quantum_info import Operator
 
 
 def raise_if_parameter_table_invalid(circuit):  # pylint: disable=invalid-name
@@ -794,6 +792,47 @@ class TestParameterExpressions(QiskitTestCase):
 
     supported_operations = [add, sub, mul, truediv]
 
+    def test_cast_to_float_when_bound(self):
+        """Verify expression can be cast to a float when fully bound."""
+
+        x = Parameter('x')
+        bound_expr = x.bind({x: 2.3})
+        self.assertEqual(float(bound_expr), 2.3)
+
+    def test_raise_if_cast_to_float_when_not_fully_bound(self):
+        """Verify raises if casting to float and not fully bound."""
+
+        x = Parameter('x')
+        y = Parameter('y')
+        bound_expr = (x + y).bind({x: 2.3})
+        with self.assertRaisesRegex(TypeError, 'unbound parameters'):
+            float(bound_expr)
+
+    def test_cast_to_int_when_bound(self):
+        """Verify expression can be cast to an int when fully bound."""
+
+        x = Parameter('x')
+        bound_expr = x.bind({x: 2.3})
+        self.assertEqual(int(bound_expr), 2)
+
+    def test_cast_to_int_when_bound_truncates_after_evaluation(self):
+        """Verify expression can be cast to an int when fully bound, but
+        truncated only after evaluation."""
+
+        x = Parameter('x')
+        y = Parameter('y')
+        bound_expr = (x + y).bind({x: 2.3, y: 0.8})
+        self.assertEqual(int(bound_expr), 3)
+
+    def test_raise_if_cast_to_int_when_not_fully_bound(self):
+        """Verify raises if casting to int and not fully bound."""
+
+        x = Parameter('x')
+        y = Parameter('y')
+        bound_expr = (x + y).bind({x: 2.3})
+        with self.assertRaisesRegex(TypeError, 'unbound parameters'):
+            int(bound_expr)
+
     def test_raise_if_sub_unknown_parameters(self):
         """Verify we raise if asked to sub a parameter not in self."""
         x = Parameter('x')