Repository: Qiskit/qiskit

Issue:
Doc request: Alphabetical sorting of parameters in QuantumCircuit
### Environment

qiskit-terra: 0.20.0

### What is happening?

`QuantumCircuit` objects sort their list of parameters alphabetically by the parameter names, and AFAICT this is not documented. I think it would help to point it out on the `assign_parameters` and `bind_parameters` documentation, and maybe on the `QuantumCircuit` page.

### How can we reproduce the issue?

https://qiskit.org/documentation/stubs/qiskit.circuit.QuantumCircuit.assign_parameters.html

### What should happen?

I'm bringing this up because the behavior seems surprising given that `circuit.parameters` kind of looks like a list, but does not preserve order of insertion like a list. Clueless users like me then assume `assign_parameters()` will, when given a list of values, bind those values to the parameters in order of insertion. This assumption leads to trouble that's tricky to notice, diagnose, and fix. (E.g., `ParameterVector('angles_11')` gets bound with the values intended for `ParameterVector('angles_2')` )

### Any suggestions?

_No response_


Discussion:
That's a very good idea! We should also add it in `QuantumCircuit.parameters` while at it 🙂 

Resolving patch:
diff --git a/qiskit/circuit/quantumcircuit.py b/qiskit/circuit/quantumcircuit.py
--- a/qiskit/circuit/quantumcircuit.py
+++ b/qiskit/circuit/quantumcircuit.py
@@ -2499,7 +2499,63 @@ def global_phase(self, angle: ParameterValueType):
 
     @property
     def parameters(self) -> ParameterView:
-        """Convenience function to get the parameters defined in the parameter table."""
+        """The parameters defined in the circuit.
+
+        This attribute returns the :class:`.Parameter` objects in the circuit sorted
+        alphabetically. Note that parameters instantiated with a :class:`.ParameterVector`
+        are still sorted numerically.
+
+        Examples:
+
+            The snippet below shows that insertion order of parameters does not matter.
+
+            .. code-block:: python
+
+                >>> from qiskit.circuit import QuantumCircuit, Parameter
+                >>> a, b, elephant = Parameter("a"), Parameter("b"), Parameter("elephant")
+                >>> circuit = QuantumCircuit(1)
+                >>> circuit.rx(b, 0)
+                >>> circuit.rz(elephant, 0)
+                >>> circuit.ry(a, 0)
+                >>> circuit.parameters  # sorted alphabetically!
+                ParameterView([Parameter(a), Parameter(b), Parameter(elephant)])
+
+            Bear in mind that alphabetical sorting might be unituitive when it comes to numbers.
+            The literal "10" comes before "2" in strict alphabetical sorting.
+
+            .. code-block:: python
+
+                >>> from qiskit.circuit import QuantumCircuit, Parameter
+                >>> angles = [Parameter("angle_1"), Parameter("angle_2"), Parameter("angle_10")]
+                >>> circuit = QuantumCircuit(1)
+                >>> circuit.u(*angles, 0)
+                >>> circuit.draw()
+                   ┌─────────────────────────────┐
+                q: ┤ U(angle_1,angle_2,angle_10) ├
+                   └─────────────────────────────┘
+                >>> circuit.parameters
+                ParameterView([Parameter(angle_1), Parameter(angle_10), Parameter(angle_2)])
+
+            To respect numerical sorting, a :class:`.ParameterVector` can be used.
+
+            .. code-block:: python
+
+            >>> from qiskit.circuit import QuantumCircuit, Parameter, ParameterVector
+            >>> x = ParameterVector("x", 12)
+            >>> circuit = QuantumCircuit(1)
+            >>> for x_i in x:
+            ...     circuit.rx(x_i, 0)
+            >>> circuit.parameters
+            ParameterView([
+                ParameterVectorElement(x[0]), ParameterVectorElement(x[1]),
+                ParameterVectorElement(x[2]), ParameterVectorElement(x[3]),
+                ..., ParameterVectorElement(x[11])
+            ])
+
+
+        Returns:
+            The sorted :class:`.Parameter` objects in the circuit.
+        """
         # parameters from gates
         if self._parameters is None:
             unsorted = self._unsorted_parameters()
@@ -2510,7 +2566,7 @@ def parameters(self) -> ParameterView:
 
     @property
     def num_parameters(self) -> int:
-        """Convenience function to get the number of parameter objects in the circuit."""
+        """The number of parameter objects in the circuit."""
         return len(self._unsorted_parameters())
 
     def _unsorted_parameters(self) -> Set[Parameter]:
@@ -2528,18 +2584,20 @@ def assign_parameters(
     ) -> Optional["QuantumCircuit"]:
         """Assign parameters to new parameters or values.
 
-        The keys of the parameter dictionary must be Parameter instances in the current circuit. The
-        values of the dictionary can either be numeric values or new parameter objects.
+        If ``parameters`` is passed as a dictionary, the keys must be :class:`.Parameter`
+        instances in the current circuit. The values of the dictionary can either be numeric values
+        or new parameter objects.
+
+        If ``parameters`` is passed as a list or array, the elements are assigned to the
+        current parameters in the order of :attr:`parameters` which is sorted
+        alphabetically (while respecting the ordering in :class:`.ParameterVector` objects).
+
         The values can be assigned to the current circuit object or to a copy of it.
 
         Args:
-            parameters (dict or iterable): Either a dictionary or iterable specifying the new
-                parameter values. If a dict, it specifies the mapping from ``current_parameter`` to
-                ``new_parameter``, where ``new_parameter`` can be a new parameter object or a
-                numeric value. If an iterable, the elements are assigned to the existing parameters
-                in the order of ``QuantumCircuit.parameters``.
-            inplace (bool): If False, a copy of the circuit with the bound parameters is
-                returned. If True the circuit instance itself is modified.
+            parameters: Either a dictionary or iterable specifying the new parameter values.
+            inplace: If False, a copy of the circuit with the bound parameters is returned.
+                If True the circuit instance itself is modified.
 
         Raises:
             CircuitError: If parameters is a dict and contains parameters not present in the
@@ -2548,8 +2606,7 @@ def assign_parameters(
                 parameters in the circuit.
 
         Returns:
-            Optional(QuantumCircuit): A copy of the circuit with bound parameters, if
-            ``inplace`` is False, otherwise None.
+            A copy of the circuit with bound parameters, if ``inplace`` is False, otherwise None.
 
         Examples:
 
@@ -2572,7 +2629,7 @@ def assign_parameters(
                 print('Assigned in-place:')
                 print(circuit.draw())
 
-            Bind the values out-of-place and get a copy of the original circuit.
+            Bind the values out-of-place by list and get a copy of the original circuit.
 
             .. jupyter-execute::
 
@@ -2583,7 +2640,7 @@ def assign_parameters(
                 circuit.ry(params[0], 0)
                 circuit.crx(params[1], 0, 1)
 
-                bound_circuit = circuit.assign_parameters({params[0]: 1, params[1]: 2})
+                bound_circuit = circuit.assign_parameters([1, 2])
                 print('Bound circuit:')
                 print(bound_circuit.draw())
 
@@ -2638,18 +2695,21 @@ def bind_parameters(
     ) -> "QuantumCircuit":
         """Assign numeric parameters to values yielding a new circuit.
 
+        If the values are given as list or array they are bound to the circuit in the order
+        of :attr:`parameters` (see the docstring for more details).
+
         To assign new Parameter objects or bind the values in-place, without yielding a new
         circuit, use the :meth:`assign_parameters` method.
 
         Args:
-            values (dict or iterable): {parameter: value, ...} or [value1, value2, ...]
+            values: ``{parameter: value, ...}`` or ``[value1, value2, ...]``
 
         Raises:
             CircuitError: If values is a dict and contains parameters not present in the circuit.
             TypeError: If values contains a ParameterExpression.
 
         Returns:
-            QuantumCircuit: copy of self with assignment substitution.
+            Copy of self with assignment substitution.
         """
         if isinstance(values, dict):
             if any(isinstance(value, ParameterExpression) for value in values.values()):


Test patch: