Repository: Qiskit/qiskit

Issue:
Strange things about `ParameterVector`
### Environment

- **Qiskit version**: main
- **Python version**: 
- **Operating system**: 


### What is happening?

There are some weird things I have noticed while staring at the `ParameterVector` code.  One design choice is that it stores previous elements even if an array has been down-sized:

https://github.com/Qiskit/qiskit/blob/03d107e1f68f2a47ffdcf5599f906120e4efba63/qiskit/circuit/parametervector.py#L99-L104

Some methods, such as `__iter__` and `__str__` and a few others, correctly account for this:

https://github.com/Qiskit/qiskit/blob/03d107e1f68f2a47ffdcf5599f906120e4efba63/qiskit/circuit/parametervector.py#L87-L88
https://github.com/Qiskit/qiskit/blob/03d107e1f68f2a47ffdcf5599f906120e4efba63/qiskit/circuit/parametervector.py#L93-L94

However, other methods do not account for this, such as `index` and the `params` property:

https://github.com/Qiskit/qiskit/blob/03d107e1f68f2a47ffdcf5599f906120e4efba63/qiskit/circuit/parametervector.py#L69-L76

It appears that it may be intentional for `params` to return everything.  If so, this should be documented.  Also, it might be worth changing `index` to return only elements that currently exist.

### How can we reproduce the issue?

```python
pv = ParameterVector("theta", 20)
last = pv[-1]
pv.resize(3)
pv.index(last)  # returns 19, which is greater than len(pv) - 1
pv.params  # is a list of 20 elements, which is greater than len(pv)
```

### What should happen?

Up for discussion

### Any suggestions?

_No response_


Discussion:
The resizing feature is something used in blueprint circuits, e.g. the `RawFeatureVector` which is essentially a parameterized `Initialize` instruction. Until these objects exists we should keep this feature. The `params` and `index` methods seem to not handle this correctly, after resizing the available parameters should correspond to the new size.
> The resizing feature is something used in blueprint circuits, e.g. the `RawFeatureVector` which is essentially a parameterized `Initialize` instruction. Until these objects exists we should keep this feature. The `params` and `index` methods seem to not handle this correctly, after resizing the available parameters should correspond to the new size.

OK great, this is a very reasonable outcome.  I will open a PR that does this.

Resolving patch:
diff --git a/qiskit/circuit/parametervector.py b/qiskit/circuit/parametervector.py
--- a/qiskit/circuit/parametervector.py
+++ b/qiskit/circuit/parametervector.py
@@ -50,11 +50,10 @@ def __setstate__(self, state):
 class ParameterVector:
     """ParameterVector class to quickly generate lists of parameters."""
 
-    __slots__ = ("_name", "_params", "_size", "_root_uuid")
+    __slots__ = ("_name", "_params", "_root_uuid")
 
     def __init__(self, name, length=0):
         self._name = name
-        self._size = length
         self._root_uuid = uuid4()
         root_uuid_int = self._root_uuid.int
         self._params = [
@@ -76,32 +75,38 @@ def index(self, value):
         return self._params.index(value)
 
     def __getitem__(self, key):
-        if isinstance(key, slice):
-            start, stop, step = key.indices(self._size)
-            return self.params[start:stop:step]
-
-        if key > self._size:
-            raise IndexError(f"Index out of range: {key} > {self._size}")
         return self.params[key]
 
     def __iter__(self):
-        return iter(self.params[: self._size])
+        return iter(self.params)
 
     def __len__(self):
-        return self._size
+        return len(self._params)
 
     def __str__(self):
-        return f"{self.name}, {[str(item) for item in self.params[: self._size]]}"
+        return f"{self.name}, {[str(item) for item in self.params]}"
 
     def __repr__(self):
         return f"{self.__class__.__name__}(name={self.name}, length={len(self)})"
 
     def resize(self, length):
-        """Resize the parameter vector.
-
-        If necessary, new elements are generated. If length is smaller than before, the
-        previous elements are cached and not re-generated if the vector is enlarged again.
+        """Resize the parameter vector.  If necessary, new elements are generated.
+
+        Note that the UUID of each :class:`.Parameter` element will be generated
+        deterministically given the root UUID of the ``ParameterVector`` and the index
+        of the element.  In particular, if a ``ParameterVector`` is resized to
+        be smaller and then later resized to be larger, the UUID of the later
+        generated element at a given index will be the same as the UUID of the
+        previous element at that index.
         This is to ensure that the parameter instances do not change.
+
+        >>> from qiskit.circuit import ParameterVector
+        >>> pv = ParameterVector("theta", 20)
+        >>> elt_19 = pv[19]
+        >>> rv.resize(10)
+        >>> rv.resize(20)
+        >>> pv[19] == elt_19
+        True
         """
         if length > len(self._params):
             root_uuid_int = self._root_uuid.int
@@ -111,4 +116,5 @@ def resize(self, length):
                     for i in range(len(self._params), length)
                 ]
             )
-        self._size = length
+        else:
+            del self._params[length:]
diff --git a/qiskit/qpy/binary_io/value.py b/qiskit/qpy/binary_io/value.py
--- a/qiskit/qpy/binary_io/value.py
+++ b/qiskit/qpy/binary_io/value.py
@@ -45,7 +45,7 @@ def _write_parameter_vec(file_obj, obj):
         struct.pack(
             formats.PARAMETER_VECTOR_ELEMENT_PACK,
             len(name_bytes),
-            obj._vector._size,
+            len(obj._vector),
             obj.uuid.bytes,
             obj._index,
         )


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
@@ -1368,10 +1368,8 @@ def test_parametervector_resize(self):
         with self.subTest("enlargen"):
             vec.resize(3)
             self.assertEqual(len(vec), 3)
-            # ensure we still have the same instance not a copy with the same name
-            # this is crucial for adding parameters to circuits since we cannot use the same
-            # name if the instance is not the same
-            self.assertIs(element, vec[1])
+            # ensure we still have an element with the same uuid
+            self.assertEqual(element, vec[1])
             self.assertListEqual([param.name for param in vec], _paramvec_names("x", 3))
 
     def test_raise_if_sub_unknown_parameters(self):