Skip to content

Commit

Permalink
Fix the aggregation argument of SamplingVQE (Qiskit/qiskit#9221)
Browse files Browse the repository at this point in the history
* 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 <[email protected]>

* Update qiskit/algorithms/minimum_eigensolvers/diagonal_estimator.py

Co-authored-by: Takashi Imamichi <[email protected]>

Co-authored-by: Takashi Imamichi <[email protected]>
Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com>
  • Loading branch information
3 people authored Dec 5, 2022
1 parent dfa6300 commit d8e1dc5
Show file tree
Hide file tree
Showing 3 changed files with 33 additions and 6 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -171,7 +171,7 @@ def aggregate(measurements):
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
Expand Down
14 changes: 9 additions & 5 deletions qiskit_algorithms/minimum_eigensolvers/sampling_vqe.py
Original file line number Diff line number Diff line change
Expand Up @@ -98,10 +98,12 @@ def my_minimizer(fun, x0, jac=None, bounds=None) -> OptimizerResult:
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
Expand Down Expand Up @@ -292,7 +294,9 @@ def store_best_measurement(best):
):
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
Expand Down
23 changes: 23 additions & 0 deletions test/minimum_eigensolvers/test_sampling_vqe.py
Original file line number Diff line number Diff line change
Expand Up @@ -256,6 +256,29 @@ def store_intermediate_result(eval_count, parameters, mean, metadata):
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()

0 comments on commit d8e1dc5

Please sign in to comment.