Repository: qutech/qupulse
Issue: parameter_names does not include constrained parameters in some non-atomic pulse template

The property `parameter_names` should include all parameters that are needed to instaniate a pulse template. This is currently not the case for
```python
SequencePulseTemplate
RepetitionPulseTemplate
ForLoopPulseTemplate
```

Changed files:
tests/pulses/loop_pulse_template_tests.py, tests/pulses/sequence_pulse_template_tests.py, tests/pulses/repetition_pulse_template_tests.py, qctoolkit/pulses/repetition_pulse_template.py, qctoolkit/pulses/sequence_pulse_template.py, qctoolkit/pulses/loop_pulse_template.py

diff --git a/qctoolkit/pulses/loop_pulse_template.py b/qctoolkit/pulses/loop_pulse_template.py
index 5828089f..9a22d97f 100644
--- a/qctoolkit/pulses/loop_pulse_template.py
+++ b/qctoolkit/pulses/loop_pulse_template.py
@@ -178,7 +178,7 @@ class ForLoopPulseTemplate(LoopPulseTemplate, MeasurementDefiner, ParameterConst
     def parameter_names(self) -> Set[str]:
         parameter_names = self.body.parameter_names.copy()
         parameter_names.remove(self._loop_index)
-        return parameter_names | self._loop_range.parameter_names
+        return parameter_names | self._loop_range.parameter_names | self.constrained_parameters
 
     def _body_parameter_generator(self, parameters: Dict[str, Parameter], forward=True) -> Generator:
         loop_range_parameters = dict((parameter_name, parameters[parameter_name].get_value())
diff --git a/qctoolkit/pulses/repetition_pulse_template.py b/qctoolkit/pulses/repetition_pulse_template.py
index 44b531dd..250cc64d 100644
--- a/qctoolkit/pulses/repetition_pulse_template.py
+++ b/qctoolkit/pulses/repetition_pulse_template.py
@@ -123,7 +123,7 @@ class RepetitionPulseTemplate(LoopPulseTemplate, ParameterConstrainer, Measureme
 
     @property
     def parameter_names(self) -> Set[str]:
-        return self.body.parameter_names | set(self.repetition_count.variables)
+        return self.body.parameter_names | set(self.repetition_count.variables) | self.constrained_parameters
 
     @property
     def measurement_names(self) -> Set[str]:
diff --git a/qctoolkit/pulses/sequence_pulse_template.py b/qctoolkit/pulses/sequence_pulse_template.py
index b22b8ff3..2a6fa6bb 100644
--- a/qctoolkit/pulses/sequence_pulse_template.py
+++ b/qctoolkit/pulses/sequence_pulse_template.py
@@ -169,7 +169,7 @@ class SequencePulseTemplate(PulseTemplate, ParameterConstrainer, MeasurementDefi
 
     @property
     def parameter_names(self) -> Set[str]:
-        return set.union(*(st.parameter_names for st in self.__subtemplates))
+        return self.constrained_parameters.union(*(st.parameter_names for st in self.__subtemplates))
 
     @property
     def subtemplates(self) -> List[MappingPulseTemplate]:
diff --git a/tests/pulses/loop_pulse_template_tests.py b/tests/pulses/loop_pulse_template_tests.py
index b8e3e16f..4e3563f7 100644
--- a/tests/pulses/loop_pulse_template_tests.py
+++ b/tests/pulses/loop_pulse_template_tests.py
@@ -148,6 +148,11 @@ class ForLoopPulseTemplateTest(unittest.TestCase):
 
         self.assertEqual(flt.parameter_names, {'k', 'a', 'b', 'c'})
 
+    def test_parameter_names_param_only_in_constraint(self) -> None:
+        flt = ForLoopPulseTemplate(body=DummyPulseTemplate(parameter_names={'k', 'i'}), loop_index='i',
+                                   loop_range=('a', 'b', 'c',), parameter_constraints=['k<=f'])
+        self.assertEqual(flt.parameter_names, {'k', 'a', 'b', 'c', 'f'})
+
     def test_build_sequence(self):
         dt = DummyPulseTemplate(parameter_names={'i'})
         flt = ForLoopPulseTemplate(body=dt, loop_index='i', loop_range=('a', 'b', 'c'),
diff --git a/tests/pulses/repetition_pulse_template_tests.py b/tests/pulses/repetition_pulse_template_tests.py
index bdfa8b7d..c6763670 100644
--- a/tests/pulses/repetition_pulse_template_tests.py
+++ b/tests/pulses/repetition_pulse_template_tests.py
@@ -204,7 +204,6 @@ class RepetitionPulseTemplateSequencingTests(unittest.TestCase):
             template.build_sequence(self.sequencer, parameters, conditions, measurement_mapping, channel_mapping,
                                      self.block)
 
-
     def test_build_sequence_declaration_exceeds_bounds(self) -> None:
         parameters = dict(foo=ConstantParameter(9))
         conditions = dict(foo=DummyCondition(requires_stop=True))
@@ -226,6 +225,10 @@ class RepetitionPulseTemplateSequencingTests(unittest.TestCase):
             self.template.build_sequence(self.sequencer, parameters, conditions, {}, {}, self.block)
         self.assertFalse(self.sequencer.sequencing_stacks)
 
+    def test_parameter_names_param_only_in_constraint(self) -> None:
+        pt = RepetitionPulseTemplate(DummyPulseTemplate(parameter_names={'a'}), 'n', parameter_constraints=['a<c'])
+        self.assertEqual(pt.parameter_names, {'a','c', 'n'})
+
 
 class RepetitionPulseTemplateSerializationTests(unittest.TestCase):
 
diff --git a/tests/pulses/sequence_pulse_template_tests.py b/tests/pulses/sequence_pulse_template_tests.py
index 21b7e06c..ed1b95dd 100644
--- a/tests/pulses/sequence_pulse_template_tests.py
+++ b/tests/pulses/sequence_pulse_template_tests.py
@@ -127,6 +127,10 @@ class SequencePulseTemplateTest(unittest.TestCase):
                                    DummyPulseTemplate(duration='b'))
         self.assertEqual(pt.duration, Expression('a+a+b'))
 
+    def test_parameter_names_param_only_in_constraint(self) -> None:
+        pt = SequencePulseTemplate(DummyPulseTemplate(parameter_names={'a'}), DummyPulseTemplate(parameter_names={'b'}), parameter_constraints=['a==b', 'a<c'])
+        self.assertEqual(pt.parameter_names, {'a','b','c'})
+
     def test_build_waveform(self):
         wfs = [DummyWaveform(), DummyWaveform()]
         pts = [DummyPulseTemplate(waveform=wf) for wf in wfs]