Repository: Qiskit/qiskit

Issue:
If more kwargs/args than required are supplied to `CmdDef` and error is not raised
<!-- ⚠️ If you do not respect this template, your issue will be closed -->
<!-- ⚠️ Make sure to browse the opened and closed issues -->

### Information

- **Qiskit Terra version**: master
- **Python version**: 3.7
- **Operating system**:OSX

### What is the current behavior?
If additional kwargs/args are supplied to `cmd_def.get` no error is raised and a result is still returned if the required parameters are present.


### Steps to reproduce the problem
```python
from qiskit.test.mock import FakeOpenPulse2Q
from qiskit.pulse import CmdDef

backend = FakeOpenPulse2Q()
config = backend.configuration()
defaults = backend.defaults()
cmd_def = CmdDef.from_defaults(defaults.cmd_def,
                               defaults.pulse_library)

print(cmd_def.get_parameters('u1', 0))
print(cmd_def.get('u1', 0, 0))
print(cmd_def.get('u1', 0, P1=0, P2=1, P3=2))
```


### What is the expected behaviour?
An error should be raised if unnecessary args/kwargs are supplied. Similarly for an insufficient number of args/kwargs if partial binding is not supported.


### Suggested solutions
Add error checks. @nkanazawa1989 could you check if your PR #2493 fixes this?




Discussion:
No problem, this will raise an error in PR #2493 .

https://github.com/Qiskit/qiskit-terra/blob/305ae6db5a2c5a0677bb4da168965efff8dbf4a3/qiskit/pulse/parser.py#L127-L128

Resolving patch:
diff --git a/qiskit/pulse/schedule.py b/qiskit/pulse/schedule.py
--- a/qiskit/pulse/schedule.py
+++ b/qiskit/pulse/schedule.py
@@ -372,13 +372,38 @@ def parameters(self) -> Tuple[str]:
         """Schedule parameters."""
         return self._parameters
 
-    def bind_parameters(self, *args: List[float], **kwargs: Dict[str, float]) -> Schedule:
+    def bind_parameters(self,
+                        *args: List[Union[int, float, complex]],
+                        **kwargs: Dict[str, Union[int, float, complex]]) -> Schedule:
         """Generate the Schedule from params to evaluate command expressions"""
         bound_schedule = Schedule(name=self.name)
         schedules = list(self._schedules)
+
+        named_parameters = {}
+        if args:
+            for key, val in zip(self.parameters, args):
+                named_parameters[key] = val
+        if kwargs:
+            for key, val in kwargs.items():
+                if key in self.parameters:
+                    if key not in named_parameters.keys():
+                        named_parameters[key] = val
+                    else:
+                        raise PulseError("%s got multiple values for argument '%s'"
+                                         % (self.__class__.__name__, key))
+                else:
+                    raise PulseError("%s got an unexpected keyword argument '%s'"
+                                     % (self.__class__.__name__, key))
+
         for param_sched in self._parameterized:
             # recursively call until based callable is reached
-            schedules.append(param_sched(*args, **kwargs))
+            if isinstance(param_sched, type(self)):
+                predefined = param_sched.parameters
+            else:
+                # assuming no other parametrized instructions
+                predefined = self.parameters
+            sub_params = {k: v for k, v in named_parameters.items() if k in predefined}
+            schedules.append(param_sched(**sub_params))
 
         # construct evaluated schedules
         for sched in schedules:
@@ -386,5 +411,8 @@ def bind_parameters(self, *args: List[float], **kwargs: Dict[str, float]) -> Sch
 
         return bound_schedule
 
-    def __call__(self, *args: List[float], **kwargs: Dict[str, float]) -> Schedule:
+    def __call__(self,
+                 *args: List[Union[int, float, complex]],
+                 **kwargs: Dict[str, Union[int, float, complex]]) -> Schedule:
+
         return self.bind_parameters(*args, **kwargs)


Test patch:
diff --git a/test/python/pulse/test_cmd_def.py b/test/python/pulse/test_cmd_def.py
--- a/test/python/pulse/test_cmd_def.py
+++ b/test/python/pulse/test_cmd_def.py
@@ -22,6 +22,7 @@
 from qiskit.qobj import PulseQobjInstruction
 from qiskit.pulse import (CmdDef, SamplePulse, Schedule, DeviceSpecification,
                           PulseError, PersistentValue)
+from qiskit.pulse.schedule import ParameterizedSchedule
 
 
 class TestCmdDef(QiskitTestCase):
@@ -89,11 +90,49 @@ def test_parameterized_schedule(self):
         with self.assertRaises(PulseError):
             cmd_def.get('pv_test', 0, 0, P1=-1)
 
+        with self.assertRaises(PulseError):
+            cmd_def.get('pv_test', 0, P1=1, P2=2, P3=3)
+
         sched = cmd_def.pop('pv_test', 0, 0, P2=-1)
         self.assertEqual(sched.instructions[0][-1].command.value, -1)
 
         self.assertFalse(cmd_def.has('pv_test', 0))
 
+    def test_sequenced_parameterized_schedule(self):
+        """Test parametrized schedule consist of multiple instruction. """
+        cmd_def = CmdDef()
+        converter = QobjToInstructionConverter([], buffer=0)
+        qobjs = [PulseQobjInstruction(name='fc', ch='d0', t0=10, phase='P1'),
+                 PulseQobjInstruction(name='fc', ch='d0', t0=20, phase='P2'),
+                 PulseQobjInstruction(name='fc', ch='d0', t0=30, phase='P3')]
+        converted_instruction = [converter(qobj) for qobj in qobjs]
+
+        cmd_def.add('inst_seq', 0, ParameterizedSchedule(*converted_instruction, name='inst_seq'))
+
+        with self.assertRaises(PulseError):
+            cmd_def.get('inst_seq', 0, P1=1, P2=2, P3=3, P4=4, P5=5)
+
+        with self.assertRaises(PulseError):
+            cmd_def.get('inst_seq', 0, P1=1)
+
+        with self.assertRaises(PulseError):
+            cmd_def.get('inst_seq', 0, 1, 2, 3, P1=1)
+
+        sched = cmd_def.get('inst_seq', 0, 1, 2, 3)
+        self.assertEqual(sched.instructions[0][-1].command.phase, 1)
+        self.assertEqual(sched.instructions[1][-1].command.phase, 2)
+        self.assertEqual(sched.instructions[2][-1].command.phase, 3)
+
+        sched = cmd_def.get('inst_seq', 0, P1=1, P2=2, P3=3)
+        self.assertEqual(sched.instructions[0][-1].command.phase, 1)
+        self.assertEqual(sched.instructions[1][-1].command.phase, 2)
+        self.assertEqual(sched.instructions[2][-1].command.phase, 3)
+
+        sched = cmd_def.get('inst_seq', 0, 1, 2, P3=3)
+        self.assertEqual(sched.instructions[0][-1].command.phase, 1)
+        self.assertEqual(sched.instructions[1][-1].command.phase, 2)
+        self.assertEqual(sched.instructions[2][-1].command.phase, 3)
+
     def test_build_cmd_def(self):
         """Test building of parameterized cmd_def from defaults."""
         defaults = self.backend.defaults()