|
| 1 | +# This code is part of Qiskit. |
| 2 | +# |
| 3 | +# (C) Copyright IBM 2023, 2024. |
| 4 | +# |
| 5 | +# This code is licensed under the Apache License, Version 2.0. You may |
| 6 | +# obtain a copy of this license in the LICENSE.txt file in the root directory |
| 7 | +# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. |
| 8 | +# |
| 9 | +# Any modifications or derivative works of this code must retain this |
| 10 | +# copyright notice, and modified files need to carry a notice indicating |
| 11 | +# that they have been altered from the originals. |
| 12 | +""" |
| 13 | +Estimator class |
| 14 | +""" |
| 15 | + |
| 16 | +from __future__ import annotations |
| 17 | + |
| 18 | +from collections.abc import Iterable |
| 19 | + |
| 20 | +import numpy as np |
| 21 | + |
| 22 | +from qiskit.quantum_info import SparsePauliOp, Statevector |
| 23 | + |
| 24 | +from .base import BaseEstimatorV2 |
| 25 | +from .containers import EstimatorPub, EstimatorPubLike, PrimitiveResult, PubResult |
| 26 | +from .primitive_job import PrimitiveJob |
| 27 | +from .utils import bound_circuit_to_instruction |
| 28 | + |
| 29 | + |
| 30 | +class StatevectorEstimator(BaseEstimatorV2): |
| 31 | + """ |
| 32 | + Simple implementation of :class:`BaseEstimatorV2` with full state vector simulation. |
| 33 | +
|
| 34 | + This class is implemented via :class:`~.Statevector` which turns provided circuits into |
| 35 | + pure state vectors. These states are subsequently acted on by :class:~.SparsePauliOp`, |
| 36 | + which implies that, at present, this implementation is only compatible with Pauli-based |
| 37 | + observables. |
| 38 | + """ |
| 39 | + |
| 40 | + def __init__( |
| 41 | + self, *, default_precision: float = 0.0, seed: np.random.Generator | int | None = None |
| 42 | + ): |
| 43 | + """ |
| 44 | + Args: |
| 45 | + default_precision: The default precision for the estimator if not specified during run. |
| 46 | + seed: The seed or Generator object for random number generation. |
| 47 | + If None, a random seeded default RNG will be used. |
| 48 | + """ |
| 49 | + self._default_precision = default_precision |
| 50 | + self._seed = seed |
| 51 | + |
| 52 | + @property |
| 53 | + def default_precision(self) -> int: |
| 54 | + """Return the default shots""" |
| 55 | + return self._default_precision |
| 56 | + |
| 57 | + @property |
| 58 | + def seed(self) -> np.random.Generator | int | None: |
| 59 | + """Return the seed or Generator object for random number generation.""" |
| 60 | + return self._seed |
| 61 | + |
| 62 | + def run( |
| 63 | + self, pubs: Iterable[EstimatorPubLike], *, precision: float | None = None |
| 64 | + ) -> PrimitiveJob[PrimitiveResult[PubResult]]: |
| 65 | + if precision is None: |
| 66 | + precision = self._default_precision |
| 67 | + coerced_pubs = [EstimatorPub.coerce(pub, precision) for pub in pubs] |
| 68 | + |
| 69 | + job = PrimitiveJob(self._run, coerced_pubs) |
| 70 | + job._submit() |
| 71 | + return job |
| 72 | + |
| 73 | + def _run(self, pubs: list[EstimatorPub]) -> PrimitiveResult[PubResult]: |
| 74 | + return PrimitiveResult([self._run_pub(pub) for pub in pubs]) |
| 75 | + |
| 76 | + def _run_pub(self, pub: EstimatorPub) -> PubResult: |
| 77 | + rng = np.random.default_rng(self._seed) |
| 78 | + circuit = pub.circuit |
| 79 | + observables = pub.observables |
| 80 | + parameter_values = pub.parameter_values |
| 81 | + precision = pub.precision |
| 82 | + bound_circuits = parameter_values.bind_all(circuit) |
| 83 | + bc_circuits, bc_obs = np.broadcast_arrays(bound_circuits, observables) |
| 84 | + evs = np.zeros_like(bc_circuits, dtype=np.float64) |
| 85 | + stds = np.zeros_like(bc_circuits, dtype=np.float64) |
| 86 | + for index in np.ndindex(*bc_circuits.shape): |
| 87 | + bound_circuit = bc_circuits[index] |
| 88 | + observable = bc_obs[index] |
| 89 | + final_state = Statevector(bound_circuit_to_instruction(bound_circuit)) |
| 90 | + paulis, coeffs = zip(*observable.items()) |
| 91 | + obs = SparsePauliOp(paulis, coeffs) # TODO: support non Pauli operators |
| 92 | + expectation_value = np.real_if_close(final_state.expectation_value(obs)) |
| 93 | + if precision != 0: |
| 94 | + if not np.isreal(expectation_value): |
| 95 | + raise ValueError("Given operator is not Hermitian and noise cannot be added.") |
| 96 | + expectation_value = rng.normal(expectation_value, precision) |
| 97 | + evs[index] = expectation_value |
| 98 | + data_bin_cls = self._make_data_bin(pub) |
| 99 | + data_bin = data_bin_cls(evs=evs, stds=stds) |
| 100 | + return PubResult(data_bin, metadata={"precision": precision}) |
0 commit comments