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

Added V2 and ISA support #197

Draft
wants to merge 11 commits into
base: main
Choose a base branch
from
1 change: 1 addition & 0 deletions .pylintdict
Original file line number Diff line number Diff line change
Expand Up @@ -362,6 +362,7 @@ trainability
transpilation
transpile
transpiled
transpiler
trotterization
trotterized
uncompute
Expand Down
59 changes: 36 additions & 23 deletions qiskit_algorithms/amplitude_amplifiers/grover.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,16 +18,14 @@
from typing import Any

import numpy as np

from qiskit import ClassicalRegister, QuantumCircuit
from qiskit.primitives import BaseSampler
from qiskit.quantum_info import Statevector
from qiskit.primitives import BaseSamplerV2

from qiskit_algorithms.exceptions import AlgorithmError
from qiskit_algorithms.utils import algorithm_globals

from .amplification_problem import AmplificationProblem
from .amplitude_amplifier import AmplitudeAmplifier, AmplitudeAmplifierResult
from ..custom_types import Transpiler


class Grover(AmplitudeAmplifier):
Expand Down Expand Up @@ -116,7 +114,9 @@ def __init__(
iterations: list[int] | Iterator[int] | int | None = None,
growth_rate: float | None = None,
sample_from_iterations: bool = False,
sampler: BaseSampler | None = None,
sampler: BaseSamplerV2 | None = None,
transpiler: Transpiler | None = None,
transpiler_options: dict[str, Any] | None = None,
) -> None:
r"""
Args:
Expand All @@ -136,6 +136,11 @@ def __init__(
powers of the Grover operator, a random integer sample between 0 and smaller value
than the iteration is used as a power, see [1], Section 4.
sampler: A Sampler to use for sampling the results of the circuits.
transpiler: An optional object with a `run` method allowing to transpile the circuits
that are produced within this algorithm. If set to `None`, these won't be
transpiled.
transpiler_options: A dictionary of options to be passed to the transpiler's `run`
method as keyword arguments.

Raises:
ValueError: If ``growth_rate`` is a float but not larger than 1.
Expand Down Expand Up @@ -165,9 +170,11 @@ def __init__(
self._sampler = sampler
self._sample_from_iterations = sample_from_iterations
self._iterations_arg = iterations
self._transpiler = transpiler
self._transpiler_options = transpiler_options if transpiler_options is not None else {}

@property
def sampler(self) -> BaseSampler | None:
def sampler(self) -> BaseSamplerV2 | None:
"""Get the sampler.

Returns:
Expand All @@ -176,7 +183,7 @@ def sampler(self) -> BaseSampler | None:
return self._sampler

@sampler.setter
def sampler(self, sampler: BaseSampler) -> None:
def sampler(self, sampler: BaseSamplerV2) -> None:
"""Set the sampler.

Args:
Expand Down Expand Up @@ -234,23 +241,29 @@ def amplify(self, amplification_problem: AmplificationProblem) -> "GroverResult"
# sample from [0, power) if specified
if self._sample_from_iterations:
power = algorithm_globals.random.integers(power)

# Run a grover experiment for a given power of the Grover operator.
if self._sampler is not None:
qc = self.construct_circuit(amplification_problem, power, measurement=True)
job = self._sampler.run([qc])

try:
results = job.result()
except Exception as exc:
raise AlgorithmError("Sampler job failed.") from exc

num_bits = len(amplification_problem.objective_qubits)
circuit_results: dict[str, Any] | Statevector | np.ndarray = {
np.binary_repr(k, num_bits): v for k, v in results.quasi_dists[0].items()
}
top_measurement, max_probability = max(
circuit_results.items(), key=lambda x: x[1] # type: ignore[union-attr]
)
qc = self.construct_circuit(amplification_problem, power, measurement=True)

if self._transpiler is not None:
qc = self._transpiler.run(qc, **self._transpiler_options)

job = self._sampler.run([qc])

try:
results = job.result()
except Exception as exc:
raise AlgorithmError("Sampler job failed.") from exc

circuit_results = getattr(results[0].data, qc.cregs[0].name)
circuit_results = {
label: value / circuit_results.num_shots
for label, value in circuit_results.get_counts().items()
}

top_measurement, max_probability = max(
circuit_results.items(), key=lambda x: x[1] # type: ignore[union-attr]
)

all_circuit_results.append(circuit_results)

Expand Down
28 changes: 28 additions & 0 deletions qiskit_algorithms/custom_types.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
# This code is part of a Qiskit project.
#
# (C) Copyright IBM 2024.
#
# This code is licensed under the Apache License, Version 2.0. You may
# obtain a copy of this license in the LICENSE.txt file in the root directory
# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0.
#
# Any modifications or derivative works of this code must retain this
# copyright notice, and modified files need to carry a notice indicating
# that they have been altered from the originals.

"""Types used by the qiskit-algorithms package."""
from __future__ import annotations

from typing import Any, Protocol, Union

from qiskit import QuantumCircuit

_Circuits = Union[list[QuantumCircuit], QuantumCircuit]


class Transpiler(Protocol):
"""A Generic type to represent a transpiler."""

def run(self, circuits: _Circuits, **options: Any) -> _Circuits:
"""Transpile a circuit or a list of quantum circuits."""
pass
19 changes: 8 additions & 11 deletions qiskit_algorithms/eigensolvers/vqd.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@
import numpy as np

from qiskit.circuit import QuantumCircuit
from qiskit.primitives import BaseEstimator
from qiskit.primitives import BaseEstimatorV2
from qiskit.quantum_info.operators.base_operator import BaseOperator
from qiskit.quantum_info import SparsePauliOp

Expand Down Expand Up @@ -88,7 +88,7 @@ class VQD(VariationalAlgorithm, Eigensolver):
updated once the VQD object has been constructed.

Attributes:
estimator (BaseEstimator): The primitive instance used to perform the expectation
estimator (BaseEstimatorV2): The primitive instance used to perform the expectation
estimation as indicated in the VQD paper.
fidelity (BaseStateFidelity): The fidelity class instance used to compute the
overlap estimation as indicated in the VQD paper.
Expand All @@ -112,7 +112,7 @@ class VQD(VariationalAlgorithm, Eigensolver):

def __init__(
self,
estimator: BaseEstimator,
estimator: BaseEstimatorV2,
fidelity: BaseStateFidelity,
ansatz: QuantumCircuit,
optimizer: Optimizer | Minimizer | Sequence[Optimizer | Minimizer],
Expand Down Expand Up @@ -389,9 +389,7 @@ def evaluate_energy(parameters: np.ndarray) -> float | np.ndarray:
parameters = np.reshape(parameters, (-1, num_parameters))
batch_size = len(parameters)

estimator_job = self.estimator.run(
batch_size * [self.ansatz], batch_size * [operator], parameters
)
estimator_job = self.estimator.run([(self.ansatz, operator, parameters)])

total_cost = np.zeros(batch_size)

Expand All @@ -410,18 +408,17 @@ def evaluate_energy(parameters: np.ndarray) -> float | np.ndarray:
total_cost += np.real(betas[state] * cost)

try:
estimator_result = estimator_job.result()
estimator_result = estimator_job.result()[0]

except Exception as exc:
raise AlgorithmError("The primitive job to evaluate the energy failed!") from exc

values = estimator_result.values + total_cost
values = estimator_result.data.evs + total_cost

if self.callback is not None:
metadata = estimator_result.metadata
for params, value, meta in zip(parameters, values, metadata):
for params, value in zip(parameters, values):
self._eval_count += 1
self.callback(self._eval_count, params, value, meta, step)
self.callback(self._eval_count, params, value, estimator_result.metadata, step)
else:
self._eval_count += len(values)

Expand Down
16 changes: 7 additions & 9 deletions qiskit_algorithms/observables_evaluator.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,15 +20,15 @@

from qiskit import QuantumCircuit
from qiskit.quantum_info import SparsePauliOp
from qiskit.primitives import BaseEstimator
from qiskit.primitives import BaseEstimatorV2
from qiskit.quantum_info.operators.base_operator import BaseOperator

from .exceptions import AlgorithmError
from .list_or_dict import ListOrDict


def estimate_observables(
estimator: BaseEstimator,
estimator: BaseEstimatorV2,
quantum_state: QuantumCircuit,
observables: ListOrDict[BaseOperator],
parameter_values: Sequence[float] | None = None,
Expand Down Expand Up @@ -63,21 +63,19 @@ def estimate_observables(

if len(observables_list) > 0:
observables_list = _handle_zero_ops(observables_list)
quantum_state = [quantum_state] * len(observables)
parameter_values_: Sequence[float] | Sequence[Sequence[float]] | None = parameter_values
if parameter_values is not None:
parameter_values_ = [parameter_values] * len(observables)
try:
estimator_job = estimator.run(quantum_state, observables_list, parameter_values_)
expectation_values = estimator_job.result().values
estimator_job = estimator.run([(quantum_state, observables_list, parameter_values_)])
estimator_result = estimator_job.result()[0]
expectation_values = estimator_result.data.evs
except Exception as exc:
raise AlgorithmError("The primitive job failed!") from exc

metadata = estimator_job.result().metadata
metadata = estimator_result.metadata
# Discard values below threshold
observables_means = expectation_values * (np.abs(expectation_values) > threshold)
# zip means and metadata into tuples
observables_results = list(zip(observables_means, metadata))
observables_results = list(zip(observables_means, [metadata] * len(observables_means)))
else:
observables_results = []

Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# This code is part of a Qiskit project.
#
# (C) Copyright IBM 2020, 2023.
# (C) Copyright IBM 2020, 2024.
#
# This code is licensed under the Apache License, Version 2.0. You may
# obtain a copy of this license in the LICENSE.txt file in the root directory
Expand All @@ -14,16 +14,16 @@

from __future__ import annotations


from qiskit import QuantumCircuit
from qiskit.circuit.library import PauliEvolutionGate
from qiskit.primitives import BaseSampler
from qiskit.primitives import BaseSamplerV2
from qiskit.quantum_info import SparsePauliOp, Statevector, Pauli
from qiskit.synthesis import EvolutionSynthesis

from .phase_estimation import PhaseEstimation
from .hamiltonian_phase_estimation_result import HamiltonianPhaseEstimationResult
from .phase_estimation import PhaseEstimation
from .phase_estimation_scale import PhaseEstimationScale
from ..custom_types import Transpiler


class HamiltonianPhaseEstimation:
Expand Down Expand Up @@ -83,17 +83,22 @@ class HamiltonianPhaseEstimation:
def __init__(
self,
num_evaluation_qubits: int,
sampler: BaseSampler | None = None,
sampler: BaseSamplerV2 | None = None,
transpiler: Transpiler | None = None,
) -> None:
r"""
Args:
num_evaluation_qubits: The number of qubits used in estimating the phase. The phase will
be estimated as a binary string with this many bits.
sampler: The sampler primitive on which the circuit will be sampled.
transpiler: An optional object with a `run` method allowing to transpile the circuits
that are produced within this algorithm. If set to `None`, these won't be
transpiled.
"""
self._phase_estimation = PhaseEstimation(
num_evaluation_qubits=num_evaluation_qubits,
sampler=sampler,
transpiler=transpiler,
)

def _get_scale(self, hamiltonian, bound=None) -> PhaseEstimationScale:
Expand Down
22 changes: 18 additions & 4 deletions qiskit_algorithms/phase_estimators/ipe.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# This code is part of a Qiskit project.
#
# (C) Copyright IBM 2021, 2023.
# (C) Copyright IBM 2021, 2024.
#
# This code is licensed under the Apache License, Version 2.0. You may
# obtain a copy of this license in the LICENSE.txt file in the root directory
Expand All @@ -19,12 +19,13 @@

from qiskit.circuit import QuantumCircuit, QuantumRegister
from qiskit.circuit.classicalregister import ClassicalRegister
from qiskit.primitives import BaseSampler
from qiskit.primitives import BaseSamplerV2

from qiskit_algorithms.exceptions import AlgorithmError

from .phase_estimator import PhaseEstimator
from .phase_estimator import PhaseEstimatorResult
from ..custom_types import Transpiler


class IterativePhaseEstimation(PhaseEstimator):
Expand All @@ -40,12 +41,16 @@ class IterativePhaseEstimation(PhaseEstimator):
def __init__(
self,
num_iterations: int,
sampler: BaseSampler | None = None,
sampler: BaseSamplerV2 | None = None,
transpiler: Transpiler | None = None,
) -> None:
r"""
Args:
num_iterations: The number of iterations (rounds) of the phase estimation to run.
sampler: The sampler primitive on which the circuit will be sampled.
transpiler: An optional object with a `run` method allowing to transpile the circuits
that are produced within this algorithm. If set to `None`, these won't be
transpiled.

Raises:
ValueError: if num_iterations is not greater than zero.
Expand All @@ -58,6 +63,7 @@ def __init__(
raise ValueError("`num_iterations` must be greater than zero.")
self._num_iterations = num_iterations
self._sampler = sampler
self._pass_manager = transpiler

def construct_circuit(
self,
Expand Down Expand Up @@ -125,9 +131,17 @@ def _estimate_phase_iteratively(self, unitary, state_preparation):
qc = self.construct_circuit(
unitary, state_preparation, k, -2 * numpy.pi * omega_coef, True
)

if self._pass_manager is not None:
qc = self._pass_manager.run(qc)

try:
sampler_job = self._sampler.run([qc])
result = sampler_job.result().quasi_dists[0]
result = sampler_job.result()[0].data.c
result = {
label: value / result.num_shots
for label, value in result.get_int_counts().items()
}
except Exception as exc:
raise AlgorithmError("The primitive job failed!") from exc
x = 1 if result.get(1, 0) > result.get(0, 0) else 0
Expand Down
Loading
Loading