Fix the ``aggregation`` argument of ``SamplingVQE`` (#9221)

* Fix the ``aggregation`` argument of ``SamplingVQE``

The aggregation was not passed to the DiagonalEstimator and had no effect. Also fixed  a bug in the CVaR aggregation function, which overshot the expected value and improved the docs.

Co-authored-by: Takashi Imamichi <31178928+t-imamichi@users.noreply.github.com>

* Update qiskit/algorithms/minimum_eigensolvers/diagonal_estimator.py

Co-authored-by: Takashi Imamichi <31178928+t-imamichi@users.noreply.github.com>

Co-authored-by: Takashi Imamichi <31178928+t-imamichi@users.noreply.github.com>
Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com>
This commit is contained in:
Julien Gacon 2022-12-05 10:11:20 +01:00 committed by GitHub
parent b08c6d6d89
commit 0fe47d201f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 38 additions and 6 deletions

View File

@ -171,7 +171,7 @@ def _get_cvar_aggregation(alpha):
accumulated_percent = 0 # once alpha is reached, stop
cvar = 0
for probability, value in sorted_measurements:
cvar += value * max(probability, alpha - accumulated_percent)
cvar += value * min(probability, alpha - accumulated_percent)
accumulated_percent += probability
if accumulated_percent >= alpha:
break

View File

@ -98,10 +98,12 @@ class SamplingVQE(VariationalAlgorithm, SamplingMinimumEigensolver):
optimizer (Optimizer | Minimizer): A classical optimizer to find the minimum energy. This
can either be a Qiskit :class:`.Optimizer` or a callable implementing the
:class:`.Minimizer` protocol.
aggregation (float | Callable[[list[float]], float] | None): A float or callable to specify
how the objective function evaluated on the basis states should be aggregated. If a
float, this specifies the :math:`\alpha \in [0,1]` parameter for a CVaR expectation
value [1].
aggregation (float | Callable[[list[tuple[float, complex]], float] | None):
A float or callable to specify how the objective function evaluated on the basis states
should be aggregated. If a float, this specifies the :math:`\alpha \in [0,1]` parameter
for a CVaR expectation value [1]. If a callable, it takes a list of basis state
measurements specified as ``[(probability, objective_value)]`` and return an objective
value as float. If None, all an ordinary expectation value is calculated.
callback (Callable[[int, np.ndarray, float, dict[str, Any]], None] | None): A callback that
can access the intermediate data at each optimization step. These data are: the
evaluation count, the optimizer parameters for the ansatz, the evaluated value, and the
@ -292,7 +294,9 @@ class SamplingVQE(VariationalAlgorithm, SamplingMinimumEigensolver):
):
best_measurement["best"] = best_i
estimator = _DiagonalEstimator(sampler=self.sampler, callback=store_best_measurement)
estimator = _DiagonalEstimator(
sampler=self.sampler, callback=store_best_measurement, aggregation=self.aggregation
)
def evaluate_energy(parameters):
nonlocal eval_count

View File

@ -0,0 +1,5 @@
---
fixes:
- |
Fixed a bug in :class:`.SamplingVQE` where the ``aggregation`` did not have an effect.
Now the aggregation function and CVaR expectation value can correctly be specified.

View File

@ -256,6 +256,29 @@ class TestSamplerVQE(QiskitAlgorithmsTestCase):
for params in history["parameters"]:
self.assertTrue(all(isinstance(param, float) for param in params))
def test_aggregation(self):
"""Test the aggregation works."""
# test a custom aggregration that just uses the best measurement
def best_measurement(measurements):
res = min(measurements, key=lambda meas: meas[1])[1]
return res
# test CVaR with alpha of 0.4 (i.e. 40% of the best measurements)
alpha = 0.4
ansatz = RealAmplitudes(1, reps=0)
ansatz.h(0)
for aggregation in [alpha, best_measurement]:
with self.subTest(aggregation=aggregation):
vqe = SamplingVQE(Sampler(), ansatz, _mock_optimizer, aggregation=best_measurement)
result = vqe.compute_minimum_eigenvalue(Pauli("Z"))
# evaluation at x0=0 samples -1 and 1 with 50% probability, and our aggregation
# takes the smallest value
self.assertAlmostEqual(result.optimal_value, -1)
if __name__ == "__main__":
unittest.main()