Repository: Qiskit/qiskit

Issue:
Method validate_parameter() of GaussianSquare raises PulseError if you call it on a created pulse
### Environment

- **Qiskit Terra version**: 0.19.2
- **Python version**: 3.10
- **Operating system**: MacOS


### What is happening?

The validate_parameter() method of the GaussianSquare pulse will raise an error if you call it after the pulse is created. This happen because the `width` and the `risefall_sigma_ratio` are both set.

### How can we reproduce the issue?

```python
import qiskit.pulse as ps

# For any given acceptable pulse it will raise an error.
out_pulse = ps.GaussianSquare(100, 0.3+0.3j, 2, 10). # Or any other param, that is not the focus.
out_pulse.validate_parameters()
```

### What should happen?

After looking at the source code, I figured this was an internal function that is called before the pulse if completed. It should be expected that you can't call this function for any given pulse (Drag or Gaussian too for exemple.)
Perhaps raise a different error in this case (the case of having a completed pulse.)

### Any suggestions?

Perhaps two solutions that are both equivalent, one that I don't really know what it represent to change the name of this particular function.
- Make this function private.
- Document the function to explain that it is not client-side code.


Discussion:
This is mostly a question of how we design interfaces for subclassing.  In this case, it perhaps would have been better to make this internal hook function private, as you mention, but since it's now out there in the wild, it's hard for us to walk that back.  The documentation route you mentioned is a good solution, though - we can just add some text to the docstring of `ParametricPulse.validate_parameters` (which should largely be inherited by the other classes) to say that it's an internal check for subclasses, and not part of the user-facing API.

Resolving patch:
diff --git a/qiskit/pulse/library/parametric_pulses.py b/qiskit/pulse/library/parametric_pulses.py
--- a/qiskit/pulse/library/parametric_pulses.py
+++ b/qiskit/pulse/library/parametric_pulses.py
@@ -242,6 +242,9 @@ def __init__(
             limit_amplitude: If ``True``, then limit the amplitude of the
                              waveform to 1. The default is ``True`` and the
                              amplitude is constrained to 1.
+
+        Raises:
+            PulseError: If the parameters passed are not valid.
         """
         if not _is_parameterized(amp):
             amp = complex(amp)
@@ -249,6 +252,13 @@ def __init__(
         self._sigma = sigma
         self._risefall_sigma_ratio = risefall_sigma_ratio
         self._width = width
+
+        if self.width is not None and self.risefall_sigma_ratio is not None:
+            raise PulseError(
+                "Either the pulse width or the risefall_sigma_ratio parameter can be specified"
+                " but not both."
+            )
+
         super().__init__(duration=duration, name=name, limit_amplitude=limit_amplitude)
 
     @property
@@ -284,11 +294,6 @@ def validate_parameters(self) -> None:
             )
         if not _is_parameterized(self.sigma) and self.sigma <= 0:
             raise PulseError("Sigma must be greater than 0.")
-        if self.width is not None and self.risefall_sigma_ratio is not None:
-            raise PulseError(
-                "Either the pulse width or the risefall_sigma_ratio parameter can be specified"
-                " but not both."
-            )
         if self.width is None and self.risefall_sigma_ratio is None:
             raise PulseError(
                 "Either the pulse width or the risefall_sigma_ratio parameter must be specified."


Test patch:
diff --git a/test/python/pulse/test_pulse_lib.py b/test/python/pulse/test_pulse_lib.py
--- a/test/python/pulse/test_pulse_lib.py
+++ b/test/python/pulse/test_pulse_lib.py
@@ -162,6 +162,13 @@ def test_gauss_square_extremes(self):
             gaus_square.get_waveform().samples[2:-2], const.get_waveform().samples[2:-2]
         )
 
+    def test_gauss_square_passes_validation_after_construction(self):
+        """Test that parameter validation is consistent before and after construction.
+
+        This previously used to raise an exception: see gh-7882."""
+        pulse = GaussianSquare(duration=125, sigma=4, amp=0.5j, width=100)
+        pulse.validate_parameters()
+
     def test_drag_pulse(self):
         """Test that the Drag sample pulse matches the pulse library."""
         drag = Drag(duration=25, sigma=4, amp=0.5j, beta=1)