Repository: Qiskit/qiskit

Issue:
VQD with primitives for k > 2 fails to validate parameter values
### Environment

- **Qiskit Terra version**:  0.22.0
- **Python version**: 3.9.6
- **Operating system**: Fedora 36


### What is happening?

When running `VQD` with primitives, the program fails to validate parameter values when calculating fidelities for k > 2. The ground and first excited states are found successfully, but when the algorithm gets to the second excited state, the sampler used for the fidelity fails to validate the parameters when `VQD` passes the parameters to  the `run()` method of `ComputeUncompute`. This seems to have slipped past unit tests because those for `VQD` only test for k=1 and k=2.

### How can we reproduce the issue?

The following code reproduces the issue:

```
from qiskit.algorithms.eigensolvers import VQD
from qiskit.algorithms.state_fidelities import ComputeUncompute
from qiskit.primitives import Estimator, Sampler
from qiskit.algorithms.optimizers import COBYLA
from qiskit.circuit.library import RealAmplitudes
from qiskit.quantum_info import Pauli
from qiskit_aer import AerSimulator

backend = AerSimulator(method='statevector')
qubit_op = Pauli('ZZ')
ansatz = RealAmplitudes(2)

cobyla = COBYLA()
estimator = Estimator()
sampler = Sampler()
fidelity = ComputeUncompute(sampler=sampler)
betas = [2, 2]

def print_intermediate_result(eval_count, params, estimated_value, estimation_metadata, current_step):

    print(f'eval count: {eval_count}, energy: {estimated_value}, step: {current_step}')

vqd_instance = VQD(estimator=estimator,
                   fidelity=fidelity,
                   ansatz=ansatz,
                   optimizer=cobyla,
                   k=3,
                   betas=betas,
                   initial_point=None,
                   callback=print_intermediate_result)

result = vqd_instance.compute_eigenvalues(operator=qubit_op)

print(result.eigenvalues)
```

This produces the following traceback:
```
capi_return is NULL
Call-back cb_calcfc_in__cobyla__user__routines failed.
Traceback (most recent call last):
  File "/home/joel/Desktop/electronic-structure-methods/test/VQD/VQD_ bugtest.py", line 32, in <module>
    result = vqd_instance.compute_eigenvalues(operator=qubit_op)
  File "/home/joel/miniconda3/envs/Qiskit-0390/lib/python3.9/site-packages/qiskit/algorithms/eigensolvers/vqd.py", line 256, in compute_eigenvalues
    opt_result = self.optimizer.minimize(
  File "/home/joel/miniconda3/envs/Qiskit-0390/lib/python3.9/site-packages/qiskit/algorithms/optimizers/scipy_optimizer.py", line 148, in minimize
    raw_result = minimize(
  File "/home/joel/miniconda3/envs/Qiskit-0390/lib/python3.9/site-packages/scipy/optimize/_minimize.py", line 705, in minimize
    res = _minimize_cobyla(fun, x0, args, constraints, callback=callback,
  File "/home/joel/miniconda3/envs/Qiskit-0390/lib/python3.9/site-packages/scipy/optimize/_cobyla_py.py", line 34, in wrapper
    return func(*args, **kwargs)
  File "/home/joel/miniconda3/envs/Qiskit-0390/lib/python3.9/site-packages/scipy/optimize/_cobyla_py.py", line 273, in _minimize_cobyla
    xopt, info = cobyla.minimize(calcfc, m=m, x=np.copy(x0), rhobeg=rhobeg,
  File "/home/joel/miniconda3/envs/Qiskit-0390/lib/python3.9/site-packages/scipy/optimize/_cobyla_py.py", line 261, in calcfc
    f = fun(np.copy(x), *args)
  File "/home/joel/miniconda3/envs/Qiskit-0390/lib/python3.9/site-packages/qiskit/algorithms/eigensolvers/vqd.py", line 363, in evaluate_energy
    costs = fidelity_job.result().fidelities
  File "/home/joel/miniconda3/envs/Qiskit-0390/lib/python3.9/site-packages/qiskit/primitives/primitive_job.py", line 50, in result
    return self._future.result()
  File "/home/joel/miniconda3/envs/Qiskit-0390/lib/python3.9/concurrent/futures/_base.py", line 438, in result
    return self.__get_result()
  File "/home/joel/miniconda3/envs/Qiskit-0390/lib/python3.9/concurrent/futures/_base.py", line 390, in __get_result
    raise self._exception
  File "/home/joel/miniconda3/envs/Qiskit-0390/lib/python3.9/concurrent/futures/thread.py", line 52, in run
    result = self.fn(*self.args, **self.kwargs)
  File "/home/joel/miniconda3/envs/Qiskit-0390/lib/python3.9/site-packages/qiskit/algorithms/state_fidelities/compute_uncompute.py", line 141, in _run
    job = self._sampler.run(circuits=circuits, parameter_values=values, **opts.__dict__)
  File "/home/joel/miniconda3/envs/Qiskit-0390/lib/python3.9/site-packages/qiskit/primitives/base/base_sampler.py", line 172, in run
    parameter_values = self._validate_parameter_values(
  File "/home/joel/miniconda3/envs/Qiskit-0390/lib/python3.9/site-packages/qiskit/primitives/base/base_primitive.py", line 108, in _validate_parameter_values
    raise TypeError("Invalid parameter values, expected Sequence[Sequence[float]].")
TypeError: Invalid parameter values, expected Sequence[Sequence[float]].
```

### What should happen?

Algorithm completes successfully.

### Any suggestions?

I am still looking into why this occurs, but my guess is that because the fidelity for the first excited state is computed successfully, there might be some error in the arguments that `VQD` passes to the `run()` method of `ComputeUncompute` on line 357 of `vqd.py` that is only problematic when `len(prev_circs)` > 1.


Discussion:
@ElePT 

Resolving patch:
diff --git a/qiskit/algorithms/eigensolvers/vqd.py b/qiskit/algorithms/eigensolvers/vqd.py
--- a/qiskit/algorithms/eigensolvers/vqd.py
+++ b/qiskit/algorithms/eigensolvers/vqd.py
@@ -91,13 +91,16 @@ class VQD(VariationalAlgorithm, Eigensolver):
             optimizer(Optimizer): A classical optimizer. Can either be a Qiskit optimizer or a callable
                 that takes an array as input and returns a Qiskit or SciPy optimization result.
             k (int): the number of eigenvalues to return. Returns the lowest k eigenvalues.
-            betas (list[float]): beta parameters in the VQD paper.
+            betas (list[float]): Beta parameters in the VQD paper.
                 Should have length k - 1, with k the number of excited states.
                 These hyper-parameters balance the contribution of each overlap term to the cost
                 function and have a default value computed as the mean square sum of the
                 coefficients of the observable.
+            initial point (list[float]): An optional initial point (i.e. initial parameter values)
+                for the optimizer. If ``None`` then VQD will look to the ansatz for a preferred
+                point and if not will simply compute a random one.
             callback (Callable[[int, np.ndarray, float, dict[str, Any]], None] | None):
-                a callback that can access the intermediate data
+                A callback that can access the intermediate data
                 during the optimization. Four parameter values are passed to the callback as
                 follows during each evaluation by the optimizer: the evaluation count,
                 the optimizer parameters for the ansatz, the estimated value, the estimation
@@ -124,16 +127,16 @@ def __init__(
             ansatz: A parameterized circuit used as ansatz for the wave function.
             optimizer: A classical optimizer. Can either be a Qiskit optimizer or a callable
                 that takes an array as input and returns a Qiskit or SciPy optimization result.
-            k: the number of eigenvalues to return. Returns the lowest k eigenvalues.
-            betas: beta parameters in the VQD paper.
+            k: The number of eigenvalues to return. Returns the lowest k eigenvalues.
+            betas: Beta parameters in the VQD paper.
                 Should have length k - 1, with k the number of excited states.
                 These hyperparameters balance the contribution of each overlap term to the cost
                 function and have a default value computed as the mean square sum of the
                 coefficients of the observable.
-            initial_point: an optional initial point (i.e. initial parameter values)
+            initial_point: An optional initial point (i.e. initial parameter values)
                 for the optimizer. If ``None`` then VQD will look to the ansatz for a preferred
                 point and if not will simply compute a random one.
-            callback: a callback that can access the intermediate data
+            callback: A callback that can access the intermediate data
                 during the optimization. Four parameter values are passed to the callback as
                 follows during each evaluation by the optimizer: the evaluation count,
                 the optimizer parameters for the ansatz, the estimated value,
@@ -238,11 +241,19 @@ def compute_eigenvalues(
         if aux_operators is not None:
             aux_values = []
 
+        # We keep a list of the bound circuits with optimal parameters, to avoid re-binding
+        # the same parameters to the ansatz if we do multiple steps
+        prev_states = []
+
         for step in range(1, self.k + 1):
 
+            # update list of optimal circuits
+            if step > 1:
+                prev_states.append(self.ansatz.bind_parameters(result.optimal_points[-1]))
+
             self._eval_count = 0
             energy_evaluation = self._get_evaluate_energy(
-                step, operator, betas, prev_states=result.optimal_parameters
+                step, operator, betas, prev_states=prev_states
             )
 
             start_time = time()
@@ -304,7 +315,7 @@ def _get_evaluate_energy(
         step: int,
         operator: BaseOperator | PauliSumOp,
         betas: Sequence[float],
-        prev_states: list[np.ndarray] | None = None,
+        prev_states: list[QuantumCircuit] | None = None,
     ) -> Callable[[np.ndarray], float | list[float]]:
         """Returns a function handle to evaluate the ansatz's energy for any given parameters.
             This is the objective function to be passed to the optimizer that is used for evaluation.
@@ -312,7 +323,7 @@ def _get_evaluate_energy(
         Args:
             step: level of energy being calculated. 0 for ground, 1 for first excited state...
             operator: The operator whose energy to evaluate.
-            prev_states: List of parameters from previous rounds of optimization.
+            prev_states: List of optimal circuits from previous rounds of optimization.
 
         Returns:
             A callable that computes and returns the energy of the hamiltonian
@@ -336,10 +347,6 @@ def _get_evaluate_energy(
 
         self._check_operator_ansatz(operator)
 
-        prev_circs = []
-        for state in range(step - 1):
-            prev_circs.append(self.ansatz.bind_parameters(prev_states[state]))
-
         def evaluate_energy(parameters: np.ndarray) -> np.ndarray | float:
 
             try:
@@ -355,10 +362,9 @@ def evaluate_energy(parameters: np.ndarray) -> np.ndarray | float:
             if step > 1:
                 # Compute overlap cost
                 fidelity_job = self.fidelity.run(
-                    [self.ansatz] * len(prev_circs),
-                    prev_circs,
-                    [parameters] * len(prev_circs),
-                    [prev_states[:-1]],
+                    [self.ansatz] * (step - 1),
+                    prev_states,
+                    [parameters] * (step - 1),
                 )
                 costs = fidelity_job.result().fidelities
 


Test patch:
diff --git a/test/python/algorithms/eigensolvers/test_vqd.py b/test/python/algorithms/eigensolvers/test_vqd.py
--- a/test/python/algorithms/eigensolvers/test_vqd.py
+++ b/test/python/algorithms/eigensolvers/test_vqd.py
@@ -60,7 +60,7 @@ def setUp(self):
         algorithm_globals.random_seed = self.seed
 
         self.h2_energy = -1.85727503
-        self.h2_energy_excited = [-1.85727503, -1.24458455]
+        self.h2_energy_excited = [-1.85727503, -1.24458455, -0.88272215, -0.22491125]
 
         self.ryrz_wavefunction = TwoLocal(
             rotation_blocks=["ry", "rz"], entanglement_blocks="cz", reps=1
@@ -88,7 +88,7 @@ def test_basic_operator(self, op):
 
         with self.subTest(msg="test eigenvalue"):
             np.testing.assert_array_almost_equal(
-                result.eigenvalues.real, self.h2_energy_excited, decimal=1
+                result.eigenvalues.real, self.h2_energy_excited[:2], decimal=1
             )
 
         with self.subTest(msg="test dimension of optimal point"):
@@ -108,6 +108,14 @@ def test_basic_operator(self, op):
             )
             np.testing.assert_array_almost_equal(job.result().values, result.eigenvalues, 6)
 
+    def test_full_spectrum(self):
+        """Test obtaining all eigenvalues."""
+        vqd = VQD(self.estimator, self.fidelity, self.ryrz_wavefunction, optimizer=L_BFGS_B(), k=4)
+        result = vqd.compute_eigenvalues(H2_PAULI)
+        np.testing.assert_array_almost_equal(
+            result.eigenvalues.real, self.h2_energy_excited, decimal=2
+        )
+
     @data(H2_PAULI, H2_OP)
     def test_mismatching_num_qubits(self, op):
         """Ensuring circuit and operator mismatch is caught"""
@@ -198,7 +206,7 @@ def test_vqd_optimizer(self, op):
         def run_check():
             result = vqd.compute_eigenvalues(operator=op)
             np.testing.assert_array_almost_equal(
-                result.eigenvalues.real, self.h2_energy_excited, decimal=3
+                result.eigenvalues.real, self.h2_energy_excited[:2], decimal=3
             )
 
         run_check()
@@ -226,7 +234,7 @@ def test_aux_operators_list(self, op):
         # Start with an empty list
         result = vqd.compute_eigenvalues(op, aux_operators=[])
         np.testing.assert_array_almost_equal(
-            result.eigenvalues.real, self.h2_energy_excited, decimal=2
+            result.eigenvalues.real, self.h2_energy_excited[:2], decimal=2
         )
         self.assertIsNone(result.aux_operators_evaluated)
 
@@ -236,7 +244,7 @@ def test_aux_operators_list(self, op):
         aux_ops = [aux_op1, aux_op2]
         result = vqd.compute_eigenvalues(op, aux_operators=aux_ops)
         np.testing.assert_array_almost_equal(
-            result.eigenvalues.real, self.h2_energy_excited, decimal=2
+            result.eigenvalues.real, self.h2_energy_excited[:2], decimal=2
         )
         self.assertEqual(len(result.aux_operators_evaluated), 2)
         # expectation values
@@ -250,7 +258,7 @@ def test_aux_operators_list(self, op):
         extra_ops = [*aux_ops, None, 0]
         result = vqd.compute_eigenvalues(op, aux_operators=extra_ops)
         np.testing.assert_array_almost_equal(
-            result.eigenvalues.real, self.h2_energy_excited, decimal=2
+            result.eigenvalues.real, self.h2_energy_excited[:2], decimal=2
         )
         self.assertEqual(len(result.aux_operators_evaluated), 2)
         # expectation values
@@ -278,7 +286,7 @@ def test_aux_operators_dict(self, op):
         # Start with an empty dictionary
         result = vqd.compute_eigenvalues(op, aux_operators={})
         np.testing.assert_array_almost_equal(
-            result.eigenvalues.real, self.h2_energy_excited, decimal=2
+            result.eigenvalues.real, self.h2_energy_excited[:2], decimal=2
         )
         self.assertIsNone(result.aux_operators_evaluated)