Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Qulacs general estimator #319

Merged
merged 5 commits into from
Mar 12, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
82 changes: 45 additions & 37 deletions packages/core/quri_parts/core/estimator/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,7 @@
from quri_parts.core.state import (
CircuitQuantumState,
ParametricCircuitQuantumState,
ParametricQuantumStateT,
ParametricQuantumStateVector,
QuantumStateT,
QuantumStateVector,
)

Expand Down Expand Up @@ -462,25 +460,25 @@ def concurrent_estimator(


@dataclass
class GeneralQuantumEstimator(Generic[QuantumStateT, ParametricQuantumStateT]):
class GeneralQuantumEstimator(Generic[_StateT, _ParametricStateT]):
r"""A callable dataclass that holds :class:`QuantumEstimator`,
:class:`ConcurrentQuantumEstimator`, :class:`ParametricQuantumEstimator`,
or :class:`ConcurrentParametricEstimator`. When it is used as a callable function,
it allows generic inputs for expectation value estimation. The allowed inputs for
using it as a callable function are:

- Act as :class:`QuantumEstimator`:
- Estimatable, QuantumStateT -> Estimate
- Estimatable, _StateT -> Estimate
- Act as :class:`ConcurrentQuantumEstimator`:
- Estimatable, [QuantumStateT, ...] -> [Estimate, ...]
- [Estimatable], [QuantumStateT, ...] -> [Estimate, ...]
- [Estimatable, ...], QuantumStateT -> [Estimate, ...]
- [Estimatable, ...], [QuantumStateT] -> [Estimate, ...]
- [Estimatable, ...], [QuantumStateT, ...] -> [Estimate, ...]
- Estimatable, [_StateT, ...] -> [Estimate, ...]
- [Estimatable], [_StateT, ...] -> [Estimate, ...]
- [Estimatable, ...], _StateT -> [Estimate, ...]
- [Estimatable, ...], [_StateT] -> [Estimate, ...]
- [Estimatable, ...], [_StateT, ...] -> [Estimate, ...]
- Act as :class:`ParametricQuantumEstimator`:
- Estimatable, ParametricQuantumStateT, [float, ...] -> Estimate
- Estimatable, _ParametricStateT, [float, ...] -> Estimate
- Act as :class:`ConcurrentParametricQuantumEstimator`:
- Estimatable, ParametricQuantumStateT, [[float, ...], ...] -> [Estimate, ...]
- Estimatable, _ParametricStateT, [[float, ...], ...] -> [Estimate, ...]

When a :class:`GeneralEstimator` is called directly with one of the combinations
above, it needs to parse the input arguments to figure out which of
Expand All @@ -490,35 +488,45 @@ class GeneralQuantumEstimator(Generic[QuantumStateT, ParametricQuantumStateT]):
retrieve the desired estimator as a property directly.
"""

estimator: QuantumEstimator[QuantumStateT]
concurrent_estimator: ConcurrentQuantumEstimator[QuantumStateT]
parametric_estimator: ParametricQuantumEstimator[ParametricQuantumStateT]
estimator: QuantumEstimator[_StateT]
concurrent_estimator: ConcurrentQuantumEstimator[_StateT]
parametric_estimator: ParametricQuantumEstimator[_ParametricStateT]
concurrent_parametric_estimator: ConcurrentParametricQuantumEstimator[
ParametricQuantumStateT
_ParametricStateT
]

@overload
def __call__(self, op: Estimatable, state: QuantumStateT) -> Estimate[complex]:
def __call__( # type: ignore[overload-overlap]
self,
op: Estimatable,
state: _StateT,
) -> Estimate[complex]:
"""A :class:`QuantumEstimator`"""
...

@overload
def __call__(
self, op: Sequence[Estimatable], state: Sequence[QuantumStateT]
self,
op: Sequence[Estimatable],
state: Sequence[_StateT],
) -> Iterable[Estimate[complex]]:
"""A :class:`ConcurrentQuantumEstimator`"""
...

@overload
def __call__(
self, op: Estimatable, state: Sequence[QuantumStateT]
self,
op: Estimatable,
state: Sequence[_StateT],
) -> Iterable[Estimate[complex]]:
"""A :class:`ConcurrentQuantumEstimator`"""
...

@overload
def __call__(
self, op: Sequence[Estimatable], state: QuantumStateT
self,
op: Sequence[Estimatable],
state: _StateT,
) -> Iterable[Estimate[complex]]:
"""A :class:`ConcurrentQuantumEstimator`"""
...
Expand All @@ -527,8 +535,8 @@ def __call__(
def __call__(
self,
op: Estimatable,
state: ParametricQuantumStateT,
param: Sequence[float],
state: _ParametricStateT,
param: Iterable[float],
) -> Estimate[complex]:
"""A :class:`ParametricQuantumEstimator`"""
...
Expand All @@ -537,35 +545,35 @@ def __call__(
def __call__(
self,
op: Estimatable,
state: ParametricQuantumStateT,
param: Sequence[Sequence[float]],
state: _ParametricStateT,
param: Iterable[Iterable[float]],
) -> Iterable[Estimate[complex]]:
"""A :class:`ConcurrentParametricQuantumEstimator`"""
...

def __call__(
self,
op: Union[Estimatable, Sequence[Estimatable]],
state: Union[QuantumStateT, Sequence[QuantumStateT], ParametricQuantumStateT],
param: Optional[Union[Sequence[float], Sequence[Sequence[float]]]] = None,
state: Union[_StateT, Sequence[_StateT], _ParametricStateT],
param: Optional[Union[Iterable[float], Iterable[Iterable[float]]]] = None,
) -> Union[Estimate[complex], Iterable[Estimate[complex]]]:
if param is None:
if isinstance(op, Operator) or isinstance(op, PauliLabel):
if isinstance(state, Sequence):
return self.concurrent_estimator([op], state)
state = cast(QuantumStateT, state)
state = cast(_StateT, state)
return self.estimator(op, state)

if isinstance(state, Sequence):
return self.concurrent_estimator(op, state)
state = cast(QuantumStateT, state)
state = cast(_StateT, state)
return self.concurrent_estimator(op, [state])

assert not isinstance(state, Sequence)
assert isinstance(op, Operator) or isinstance(op, PauliLabel)

state = cast(ParametricQuantumStateT, state)
if isinstance(param[0], Sequence):
state = cast(_ParametricStateT, state)
if isinstance(next(iter(param)), Iterable):
param = cast(Sequence[Sequence[float]], param)
return self.concurrent_parametric_estimator(op, state, param)
param = cast(Sequence[float], param)
Expand All @@ -587,8 +595,8 @@ def create_general_estimator_from_estimator(


def create_general_estimator_from_estimator(
estimator: QuantumEstimator[QuantumStateT],
) -> GeneralQuantumEstimator[QuantumStateT, ParametricQuantumStateT]:
estimator: QuantumEstimator[_StateT],
) -> GeneralQuantumEstimator[_StateT, _ParametricStateT]:
"""Creates a :class:`GeneralEstimator` from a :class:`QuantumEstimator`.

Note:
Expand All @@ -602,11 +610,11 @@ def create_general_estimator_from_estimator(
"""
concurrent_estimator = create_concurrent_estimator_from_estimator(estimator)
parametric_estimator: ParametricQuantumEstimator[
ParametricQuantumStateT
_ParametricStateT
] = create_parametric_estimator_from_concurrent_estimator(concurrent_estimator)

concurrent_parametric_estimator: ConcurrentParametricQuantumEstimator[
ParametricQuantumStateT
_ParametricStateT
] = create_concurrent_parametric_estimator_from_concurrent_estimator(
concurrent_estimator
)
Expand Down Expand Up @@ -635,8 +643,8 @@ def create_general_estimator_from_concurrent_estimator(


def create_general_estimator_from_concurrent_estimator(
concurrent_estimator: ConcurrentQuantumEstimator[QuantumStateT],
) -> GeneralQuantumEstimator[QuantumStateT, ParametricQuantumStateT]:
concurrent_estimator: ConcurrentQuantumEstimator[_StateT],
) -> GeneralQuantumEstimator[_StateT, _ParametricStateT]:
"""Creates a :class:`GeneralEstimator` from a
:class:`ConcurrentQuantumEstimator`.

Expand All @@ -648,11 +656,11 @@ def create_general_estimator_from_concurrent_estimator(
"""
estimator = create_estimator_from_concurrent_estimator(concurrent_estimator)
parametric_estimator: ParametricQuantumEstimator[
ParametricQuantumStateT
_ParametricStateT
] = create_parametric_estimator_from_concurrent_estimator(concurrent_estimator)

concurrent_parametric_estimator: ConcurrentParametricQuantumEstimator[
ParametricQuantumStateT
_ParametricStateT
] = create_concurrent_parametric_estimator_from_concurrent_estimator(
concurrent_estimator
)
Expand Down
68 changes: 68 additions & 0 deletions packages/core/tests/core/estimator/test_estimator_creation.py
Original file line number Diff line number Diff line change
Expand Up @@ -307,11 +307,29 @@ def test_with_circuit_state(self) -> None:
assert general_estimator(self.op_0, param_state, [3, 4, 5]) == _Estimate(
value=12 + 0j
)
assert general_estimator(
self.op_0, param_state, np.array([0, 1, 2])
) == _Estimate(value=3 + 0j)
assert general_estimator(
self.op_0, param_state, np.array([3, 4, 5])
) == _Estimate(value=12 + 0j)

assert general_estimator(self.op_0, param_state, [[0, 1, 2], [3, 4, 5]]) == [
_Estimate(value=3 + 0j),
_Estimate(value=12 + 0j),
]
assert general_estimator(
self.op_0, param_state, [np.array([0, 1, 2]), np.array([3, 4, 5])]
) == [
_Estimate(value=3 + 0j),
_Estimate(value=12 + 0j),
]
assert general_estimator(
self.op_0, param_state, np.array([[0, 1, 2], [3, 4, 5]])
) == [
_Estimate(value=3 + 0j),
_Estimate(value=12 + 0j),
]

def test_with_vector(self) -> None:
general_estimator = create_general_estimator_from_estimator(
Expand Down Expand Up @@ -362,12 +380,28 @@ def test_with_vector(self) -> None:
assert np.isclose(estimate.value, 3 * np.sqrt(2))
estimate = general_estimator(self.op_0, param_state, [3, 4, 5])
assert np.isclose(estimate.value, 12 * np.sqrt(2))
estimate = general_estimator(self.op_0, param_state, np.array([0, 1, 2]))
assert np.isclose(estimate.value, 3 * np.sqrt(2))
estimate = general_estimator(self.op_0, param_state, np.array([3, 4, 5]))
assert np.isclose(estimate.value, 12 * np.sqrt(2))

estimates = list(
general_estimator(self.op_0, param_state, [[0, 1, 2], [3, 4, 5]])
)
assert np.isclose(estimates[0].value, 3 * np.sqrt(2))
assert np.isclose(estimates[1].value, 12 * np.sqrt(2))
estimates = list(
general_estimator(
self.op_0, param_state, [np.array([0, 1, 2]), np.array([3, 4, 5])]
)
)
assert np.isclose(estimates[0].value, 3 * np.sqrt(2))
assert np.isclose(estimates[1].value, 12 * np.sqrt(2))
estimates = list(
general_estimator(self.op_0, param_state, np.array([[0, 1, 2], [3, 4, 5]]))
)
assert np.isclose(estimates[0].value, 3 * np.sqrt(2))
assert np.isclose(estimates[1].value, 12 * np.sqrt(2))

def test_concurrent_with_circuit_state(self) -> None:
general_estimator = create_general_estimator_from_concurrent_estimator(
Expand Down Expand Up @@ -410,11 +444,29 @@ def test_concurrent_with_circuit_state(self) -> None:
assert general_estimator(self.op_0, param_state, [3, 4, 5]) == _Estimate(
value=12 + 0j
)
assert general_estimator(
self.op_0, param_state, np.array([0, 1, 2])
) == _Estimate(value=3 + 0j)
assert general_estimator(
self.op_0, param_state, np.array([3, 4, 5])
) == _Estimate(value=12 + 0j)

assert general_estimator(self.op_0, param_state, [[0, 1, 2], [3, 4, 5]]) == [
_Estimate(value=3 + 0j),
_Estimate(value=12 + 0j),
]
assert general_estimator(
self.op_0, param_state, [np.array([0, 1, 2]), np.array([3, 4, 5])]
) == [
_Estimate(value=3 + 0j),
_Estimate(value=12 + 0j),
]
assert general_estimator(
self.op_0, param_state, np.array([[0, 1, 2], [3, 4, 5]])
) == [
_Estimate(value=3 + 0j),
_Estimate(value=12 + 0j),
]

def test_concurrent_with_vector(self) -> None:
general_estimator = create_general_estimator_from_concurrent_estimator(
Expand Down Expand Up @@ -465,9 +517,25 @@ def test_concurrent_with_vector(self) -> None:
assert np.isclose(estimate.value, 3 * np.sqrt(2))
estimate = general_estimator(self.op_0, param_state, [3, 4, 5])
assert np.isclose(estimate.value, 12 * np.sqrt(2))
estimate = general_estimator(self.op_0, param_state, np.array([0, 1, 2]))
assert np.isclose(estimate.value, 3 * np.sqrt(2))
estimate = general_estimator(self.op_0, param_state, np.array([3, 4, 5]))
assert np.isclose(estimate.value, 12 * np.sqrt(2))

estimates = list(
general_estimator(self.op_0, param_state, [[0, 1, 2], [3, 4, 5]])
)
assert np.isclose(estimates[0].value, 3 * np.sqrt(2))
assert np.isclose(estimates[1].value, 12 * np.sqrt(2))
estimates = list(
general_estimator(
self.op_0, param_state, [np.array([0, 1, 2]), np.array([3, 4, 5])]
)
)
assert np.isclose(estimates[0].value, 3 * np.sqrt(2))
assert np.isclose(estimates[1].value, 12 * np.sqrt(2))
estimates = list(
general_estimator(self.op_0, param_state, np.array([[0, 1, 2], [3, 4, 5]]))
)
assert np.isclose(estimates[0].value, 3 * np.sqrt(2))
assert np.isclose(estimates[1].value, 12 * np.sqrt(2))
37 changes: 37 additions & 0 deletions packages/qulacs/quri_parts/qulacs/estimator.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
ConcurrentQuantumEstimator,
Estimatable,
Estimate,
GeneralQuantumEstimator,
ParametricQuantumEstimator,
QuantumEstimator,
create_parametric_estimator,
Expand Down Expand Up @@ -344,3 +345,39 @@ def estimator(
)

return estimator


def create_qulacs_general_vector_estimator(
executor: Optional["Executor"] = None, concurrency: int = 1
) -> GeneralQuantumEstimator[QulacsStateT, QulacsParametricStateT]:
"""Creates a Qulacs general vector estimator."""
concurrent_param_estimator = create_qulacs_vector_concurrent_parametric_estimator(
executor, concurrency
)
return GeneralQuantumEstimator(
estimator=create_qulacs_vector_estimator(),
concurrent_estimator=create_qulacs_vector_concurrent_estimator(
executor, concurrency
),
parametric_estimator=create_qulacs_vector_parametric_estimator(),
concurrent_parametric_estimator=concurrent_param_estimator,
)


def create_qulacs_general_density_matrix_estimator(
model: NoiseModel, executor: Optional["Executor"] = None, concurrency: int = 1
) -> GeneralQuantumEstimator[QulacsStateT, QulacsParametricStateT]:
"""Creates a Qulacs general density estimator."""
# cp_estimator: abbreviation of concurrent parametric estimator
cp_estimator = create_qulacs_density_matrix_concurrent_parametric_estimator(
model, executor, concurrency
)

return GeneralQuantumEstimator(
estimator=create_qulacs_density_matrix_estimator(model),
concurrent_estimator=create_qulacs_density_matrix_concurrent_estimator(
model, executor, concurrency
),
parametric_estimator=create_qulacs_density_matrix_parametric_estimator(model),
concurrent_parametric_estimator=cp_estimator,
)
Loading
Loading