diff --git a/.pylintdict b/.pylintdict index 68d55842c..d07542965 100644 --- a/.pylintdict +++ b/.pylintdict @@ -357,6 +357,7 @@ nfevs nft nielsen njev +nlocal nlopt nn noancilla @@ -511,7 +512,7 @@ scipy sdg seealso semidefinite -sep +sep seperate seperable serializable diff --git a/qiskit_machine_learning/algorithm_job.py b/qiskit_machine_learning/algorithm_job.py index abd6def46..ec4a0648b 100644 --- a/qiskit_machine_learning/algorithm_job.py +++ b/qiskit_machine_learning/algorithm_job.py @@ -1,6 +1,6 @@ # This code is part of a Qiskit project. # -# (C) Copyright IBM 2022, 2024. +# (C) Copyright IBM 2022, 2025. # # 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 @@ -29,17 +29,7 @@ def submit(self) -> None: """ Submit the job for execution. - For V1 primitives, Qiskit ``PrimitiveJob`` subclassed JobV1 and defined ``submit()``. - ``PrimitiveJob`` was updated for V2 primitives, no longer subclasses ``JobV1``, and - now has a private ``_submit()`` method, with ``submit()`` being deprecated as of - Qiskit version 0.46. This maintains the ``submit()`` for ``AlgorithmJob`` here as - it's called in many places for such a job. An alternative could be to make - 0.46 the required minimum version and alter all algorithm's call sites to use - ``_submit()`` and make this an empty class again as it once was. For now this - way maintains compatibility with the current min version of 0.44. + Since the library has been migrated to Qiskit v2.1, it is no longer necessary to + keep the :meth:``JobV1.submit()`` for the exception handling. """ - # TODO: Considering changing this in the future - see above docstring. - try: - super()._submit() - except AttributeError: - super().submit() # pylint: disable=no-member + super()._submit() diff --git a/qiskit_machine_learning/algorithms/classifiers/__init__.py b/qiskit_machine_learning/algorithms/classifiers/__init__.py index ce4e21c6e..cd44fdc17 100644 --- a/qiskit_machine_learning/algorithms/classifiers/__init__.py +++ b/qiskit_machine_learning/algorithms/classifiers/__init__.py @@ -13,8 +13,8 @@ """Classifiers Package""" from .neural_network_classifier import NeuralNetworkClassifier -from .qsvc import QSVC from .pegasos_qsvc import PegasosQSVC +from .qsvc import QSVC from .vqc import VQC __all__ = ["NeuralNetworkClassifier", "QSVC", "PegasosQSVC", "VQC"] diff --git a/qiskit_machine_learning/algorithms/classifiers/vqc.py b/qiskit_machine_learning/algorithms/classifiers/vqc.py index 159659401..83877587f 100644 --- a/qiskit_machine_learning/algorithms/classifiers/vqc.py +++ b/qiskit_machine_learning/algorithms/classifiers/vqc.py @@ -12,19 +12,18 @@ """An implementation of variational quantum classifier.""" from __future__ import annotations + from typing import Callable import numpy as np - from qiskit import QuantumCircuit -from qiskit.primitives import BaseSampler +from qiskit.primitives import BaseSamplerV2 from qiskit.transpiler.passmanager import BasePassManager from ...neural_networks import SamplerQNN -from ...optimizers import Optimizer, OptimizerResult, Minimizer +from ...optimizers import Minimizer, Optimizer, OptimizerResult from ...utils import derive_num_qubits_feature_map_ansatz from ...utils.loss_functions import Loss - from .neural_network_classifier import NeuralNetworkClassifier @@ -58,7 +57,7 @@ def __init__( initial_point: np.ndarray | None = None, callback: Callable[[np.ndarray, float], None] | None = None, *, - sampler: BaseSampler | None = None, + sampler: BaseSamplerV2 | None = None, # change: BaseSampler is migrated to BaseSamplerV2 interpret: Callable[[int], int | tuple[int, ...]] | None = None, output_shape: int | None = None, pass_manager: BasePassManager | None = None, @@ -108,7 +107,7 @@ def __init__( """ num_qubits, feature_map, ansatz = derive_num_qubits_feature_map_ansatz( - num_qubits, feature_map, ansatz, use_methods=True + num_qubits, feature_map, ansatz ) if output_shape is None: diff --git a/qiskit_machine_learning/algorithms/inference/qbayesian.py b/qiskit_machine_learning/algorithms/inference/qbayesian.py index 4e8d02255..5ebf412ed 100644 --- a/qiskit_machine_learning/algorithms/inference/qbayesian.py +++ b/qiskit_machine_learning/algorithms/inference/qbayesian.py @@ -14,17 +14,18 @@ from __future__ import annotations import copy -from typing import Tuple, Dict, Set, List +from typing import Dict, Set -from qiskit import QuantumCircuit, ClassicalRegister -from qiskit.quantum_info import Statevector +from qiskit import ClassicalRegister, QuantumCircuit from qiskit.circuit import Qubit from qiskit.circuit.library import grover_operator -from qiskit.primitives import BaseSampler, Sampler, BaseSamplerV2, BaseSamplerV1 -from qiskit.transpiler.passmanager import BasePassManager +from qiskit.primitives import ( + BaseSamplerV2, + StatevectorSampler, +) +from qiskit.quantum_info import Statevector from qiskit.result import QuasiDistribution - -from ...utils.deprecation import issue_deprecation_msg +from qiskit.transpiler.passmanager import BasePassManager class QBayesian: @@ -67,7 +68,7 @@ def __init__( *, limit: int = 10, threshold: float = 0.9, - sampler: BaseSampler | BaseSamplerV2 | None = None, + sampler: BaseSamplerV2 | None = None, pass_manager: BasePassManager | None = None, ): """ @@ -96,15 +97,7 @@ def __init__( self._limit = limit self._threshold = threshold if sampler is None: - sampler = Sampler() - - if isinstance(sampler, BaseSamplerV1): - issue_deprecation_msg( - msg="V1 Primitives are deprecated", - version="0.8.0", - remedy="Use V2 primitives for continued compatibility and support.", - period="4 months", - ) + sampler = StatevectorSampler() self._sampler = sampler @@ -167,40 +160,28 @@ def _run_circuit(self, circuit: QuantumCircuit) -> Dict[str, float]: """Run the quantum circuit with the sampler.""" counts = {} - if isinstance(self._sampler, BaseSampler): - # Sample from circuit - job = self._sampler.run(circuit) - result = job.result() - - # Get the counts of quantum state results - counts = result.quasi_dists[0].nearest_probability_distribution().binary_probabilities() - - elif isinstance(self._sampler, BaseSamplerV2): - # Sample from circuit - if self._pass_manager is not None: - circuit = self._pass_manager.run(circuit) - job = self._sampler.run([circuit]) - result = job.result() - - bit_array = list(result[0].data.values())[0] - bitstring_counts = bit_array.get_counts() + # Sample from circuit + if self._pass_manager is not None: + circuit = self._pass_manager.run(circuit) + job = self._sampler.run([circuit]) + result = job.result() - # Normalize the counts to probabilities - total_shots = sum(bitstring_counts.values()) - probabilities = {k: v / total_shots for k, v in bitstring_counts.items()} - # Convert to quasi-probabilities - quasi_dist = QuasiDistribution(probabilities) - binary_prob = quasi_dist.nearest_probability_distribution().binary_probabilities() - counts = {k: v for k, v in binary_prob.items() if int(k) < 2**self.num_virtual_qubits} + bit_array = list(result[0].data.values())[0] + bitstring_counts = bit_array.get_counts() - # counts = QuasiDistribution(probabilities) - # counts = {k: v for k, v in counts.items()} + # Normalize the counts to probabilities + total_shots = sum(bitstring_counts.values()) + probabilities = {k: v / total_shots for k, v in bitstring_counts.items()} + # Convert to quasi-probabilities + quasi_dist = QuasiDistribution(probabilities) + binary_prob = quasi_dist.nearest_probability_distribution().binary_probabilities() + counts = {k: v for k, v in binary_prob.items() if int(k) < 2**self.num_virtual_qubits} return counts def __power_grover( self, grover_op: QuantumCircuit, evidence: Dict[str, int], k: int - ) -> Tuple[QuantumCircuit, Set[Tuple[Qubit, int]]]: + ) -> tuple[QuantumCircuit, Set[tuple[Qubit, int]]]: """ Applies the Grover operator to the quantum circuit 2^k times, measures the evidence qubits, and returns a tuple containing the updated quantum circuit and a set of the measured @@ -246,7 +227,7 @@ def __power_grover( } return qc, e_meas - def _format_samples(self, samples: Dict[str, float], evidence: List[str]) -> Dict[str, float]: + def _format_samples(self, samples: Dict[str, float], evidence: list[str]) -> Dict[str, float]: """Transforms samples keys back to their variables names.""" f_samples: Dict[str, float] = {} for smpl_key, smpl_val in samples.items(): @@ -295,7 +276,7 @@ def rejection_sampling( grover_op = self._get_grover_op(evidence) # Amplitude amplification true_e = {(self._label2qubit[e_key], e_val) for e_key, e_val in evidence.items()} - meas_e: Set[Tuple[str, int]] = set() + meas_e: Set[tuple[str, int]] = set() best_qc, best_inter = QuantumCircuit(), -1 self._converged = False k = -1 @@ -412,12 +393,12 @@ def limit(self, limit: int): self._limit = limit @property - def sampler(self) -> BaseSampler | BaseSamplerV2: + def sampler(self) -> BaseSamplerV2: """Returns the sampler primitive used to compute the samples.""" return self._sampler @sampler.setter - def sampler(self, sampler: BaseSampler | BaseSamplerV2): + def sampler(self, sampler: BaseSamplerV2): """Set the sampler primitive used to compute the samples.""" self._sampler = sampler diff --git a/qiskit_machine_learning/algorithms/regressors/__init__.py b/qiskit_machine_learning/algorithms/regressors/__init__.py index ddc8729fb..7c52e189e 100644 --- a/qiskit_machine_learning/algorithms/regressors/__init__.py +++ b/qiskit_machine_learning/algorithms/regressors/__init__.py @@ -12,8 +12,8 @@ """Regressors Package""" -from .qsvr import QSVR from .neural_network_regressor import NeuralNetworkRegressor +from .qsvr import QSVR from .vqr import VQR __all__ = ["QSVR", "VQR", "NeuralNetworkRegressor"] diff --git a/qiskit_machine_learning/algorithms/regressors/vqr.py b/qiskit_machine_learning/algorithms/regressors/vqr.py index 94b72b5b6..cfe17b541 100644 --- a/qiskit_machine_learning/algorithms/regressors/vqr.py +++ b/qiskit_machine_learning/algorithms/regressors/vqr.py @@ -16,15 +16,15 @@ import numpy as np from qiskit import QuantumCircuit -from qiskit.primitives import BaseEstimator +from qiskit.primitives import BaseEstimatorV2 from qiskit.quantum_info.operators.base_operator import BaseOperator from qiskit.transpiler.passmanager import BasePassManager -from .neural_network_regressor import NeuralNetworkRegressor from ...neural_networks import EstimatorQNN -from ...optimizers import Optimizer, Minimizer +from ...optimizers import Minimizer, Optimizer from ...utils import derive_num_qubits_feature_map_ansatz from ...utils.loss_functions import Loss +from .neural_network_regressor import NeuralNetworkRegressor class VQR(NeuralNetworkRegressor): @@ -43,7 +43,7 @@ def __init__( initial_point: np.ndarray | None = None, callback: Callable[[np.ndarray, float], None] | None = None, *, - estimator: BaseEstimator | None = None, + estimator: BaseEstimatorV2 | None = None, pass_manager: BasePassManager | None = None, ) -> None: r""" @@ -94,7 +94,7 @@ def __init__( self._estimator = estimator num_qubits, feature_map, ansatz = derive_num_qubits_feature_map_ansatz( - num_qubits, feature_map, ansatz, use_methods=True + num_qubits, feature_map, ansatz ) # construct circuit diff --git a/qiskit_machine_learning/circuit/library/qnn_circuit.py b/qiskit_machine_learning/circuit/library/qnn_circuit.py index 5658c3dd2..9f376af9e 100644 --- a/qiskit_machine_learning/circuit/library/qnn_circuit.py +++ b/qiskit_machine_learning/circuit/library/qnn_circuit.py @@ -12,11 +12,10 @@ """The QNN circuit.""" from __future__ import annotations -from typing import List -from qiskit.circuit import QuantumRegister, QuantumCircuit -from qiskit.circuit.parametertable import ParameterView +from qiskit.circuit import QuantumCircuit, QuantumRegister from qiskit.circuit.library import BlueprintCircuit +from qiskit.circuit.parametertable import ParameterView from qiskit_machine_learning import QiskitMachineLearningError @@ -119,7 +118,7 @@ def qnn_circuit( """ # Check if circuit is constructed with valid configuration and set properties accordingly. num_qubits, feature_map, ansatz = derive_num_qubits_feature_map_ansatz( - num_qubits, feature_map, ansatz, use_methods=True + num_qubits, feature_map, ansatz ) qc = QuantumCircuit(num_qubits) qc.compose(feature_map, inplace=True) @@ -274,7 +273,7 @@ def num_qubits(self, num_qubits: int) -> None: if self.num_qubits != num_qubits: # invalidate the circuit self._invalidate() - self.qregs: List[QuantumRegister] = [] + self.qregs: list[QuantumRegister] = [] if num_qubits is not None and num_qubits > 0: self.qregs = [QuantumRegister(num_qubits, name="q")] ( diff --git a/qiskit_machine_learning/circuit/library/raw_feature_vector.py b/qiskit_machine_learning/circuit/library/raw_feature_vector.py index 25e4586f0..3edc98c97 100644 --- a/qiskit_machine_learning/circuit/library/raw_feature_vector.py +++ b/qiskit_machine_learning/circuit/library/raw_feature_vector.py @@ -12,7 +12,7 @@ """The raw feature vector circuit.""" -from typing import Optional, List +from typing import Optional import numpy as np from qiskit.exceptions import QiskitError from qiskit.circuit import ( @@ -201,7 +201,7 @@ def num_qubits(self, num_qubits: int) -> None: if self.num_qubits != num_qubits: # invalidate the circuit self._invalidate() - self.qregs: List[QuantumRegister] = [] + self.qregs: list[QuantumRegister] = [] if num_qubits is not None and num_qubits > 0: self.qregs = [QuantumRegister(num_qubits, name="q")] diff --git a/qiskit_machine_learning/datasets/ad_hoc.py b/qiskit_machine_learning/datasets/ad_hoc.py index 36fd7b05b..8a3a76645 100644 --- a/qiskit_machine_learning/datasets/ad_hoc.py +++ b/qiskit_machine_learning/datasets/ad_hoc.py @@ -46,7 +46,7 @@ def ad_hoc_data( r""" Generates a dataset that can be fully separated by :class:`~qiskit.circuit.library.ZZFeatureMap` according to the procedure - outlined in [1]. First, vectors :math:`\vec{x} \in (0, 2\pi]^{n}` are generated from a + outlined in [1]. First, vectors :math:`\vec{x} \in (0, 2\pi]^{n}` are generated from a uniform distribution, using a sampling method determined by the ``sampling_method`` argument. Next, a feature map is applied: @@ -67,7 +67,7 @@ def ad_hoc_data( \begin{cases}\phi_{\{i, j\}} = (\pi - x_i)(\pi - x_j) \\ \phi_{\{i\}} = x_i \end{cases} - The choice of second-order terms :math:`Z_i Z_j` in the above summation depends + The choice of second-order terms :math:`Z_i Z_j` in the above summation depends on the ``entanglement`` argument (``"linear"``, ``"circular"``, or ``"full"``). See arguments for more information. @@ -79,8 +79,8 @@ def ad_hoc_data( where :math:`V` is a randomly generated unitary matrix. Depending on the ``labelling_method``, if ``"expectation"`` is used, the expectation value :math:`\langle \Phi(\vec{x})| O |\Phi(\vec{x})\rangle` is compared to the - gap parameter :math:`\Delta` (from ``gap``) to assign :math:`\pm 1` labels. - if ``"measurement"`` is used, a simple measurement in the computational + gap parameter :math:`\Delta` (from ``gap``) to assign :math:`\pm 1` labels. + if ``"measurement"`` is used, a simple measurement in the computational basis is performed to assign labels. **References:** @@ -96,10 +96,10 @@ def ad_hoc_data( n : Number of qubits (dimension of the feature space). gap : Separation gap :math:`\Delta` used when ``labelling_method="expectation"``. Default is 0. - plot_data : If True, plots the sampled data (disabled automatically if + plot_data : If True, plots the sampled data (disabled automatically if ``n > 3``). Default is False. one_hot : If True, returns labels in one-hot format. Default is True. - include_sample_total : If True, the function also returns the total number + include_sample_total : If True, the function also returns the total number of accepted samples. Default is False. entanglement : Determines which second-order terms :math:`Z_i Z_j` appear in :math:`U_{\Phi(\vec{x})}`. The options are: @@ -117,16 +117,16 @@ def ad_hoc_data( * ``"sobol"``: Uses Sobol sequences Default is ``"grid"``. - divisions : Must be specified if ``sampling_method="hypercube"``. This parameter - determines the number of stratifications along each dimension. Recommended - to be chosen close to ``training_size``. + divisions : Must be specified if ``sampling_method="hypercube"``. This parameter + determines the number of stratifications along each dimension. Recommended + to be chosen close to ``training_size``. labelling_method : Method for assigning labels. The options are: * ``"expectation"``: Uses the expectation value of the observable. * ``"measurement"``: Performs a measurement in the computational basis. Default is ``"expectation"``. - class_labels : Custom labels for the two classes when one-hot is not enabled. + class_labels : Custom labels for the two classes when one-hot is not enabled. If not provided, the labels default to ``-1`` and ``+1`` Returns: @@ -176,7 +176,7 @@ def ad_hoc_data( if sampling_method == "grid" and (training_size + test_size) > 4000: warnings.warn( - """Grid Sampling for large number of samples is not recommended + """Grid Sampling for large number of samples is not recommended and can lead to samples repeating in the training and testing sets""", UserWarning, ) @@ -449,7 +449,7 @@ def _loop_sampling(n, n_samples, z_diags, zz_diags, psi_0, h_n, lab_fn, samp_fn, ("grid", "hypercube", or "sobol"). Returns: - Tuple[np.ndarray, np.ndarray]: + tuple[np.ndarray, np.ndarray]: Two arrays of shape `(n_samples, n)`, each containing the sampled feature vectors belonging to class A and class B, respectively. """ @@ -575,7 +575,7 @@ def _grid_sampling(n, n_samples, z_diags, zz_diags, psi_0, h_n, lab_fn): measurement-based). Returns: - Tuple[np.ndarray, np.ndarray]: + tuple[np.ndarray, np.ndarray]: Two arrays of shape `(n_samples, n)`, each containing the sampled feature vectors belonging to class A and class B, respectively. This code is incomplete and references variables not defined above, diff --git a/qiskit_machine_learning/gradients/base/base_estimator_gradient.py b/qiskit_machine_learning/gradients/base/base_estimator_gradient.py index 2bb0c6735..13e8849c9 100644 --- a/qiskit_machine_learning/gradients/base/base_estimator_gradient.py +++ b/qiskit_machine_learning/gradients/base/base_estimator_gradient.py @@ -1,6 +1,6 @@ # This code is part of a Qiskit project. # -# (C) Copyright IBM 2022, 2024. +# (C) Copyright IBM 2022, 2025. # # 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 @@ -21,26 +21,22 @@ from copy import copy import numpy as np - from qiskit.circuit import Parameter, ParameterExpression, QuantumCircuit -from qiskit.primitives import BaseEstimator, BaseEstimatorV1 -from qiskit.primitives.base import BaseEstimatorV2 -from qiskit.primitives.utils import _circuit_key +from qiskit.primitives import BaseEstimatorV2 from qiskit.providers import Options from qiskit.quantum_info.operators.base_operator import BaseOperator from qiskit.transpiler.passes import TranslateParameterizedGates from qiskit.transpiler.passmanager import BasePassManager -from .estimator_gradient_result import EstimatorGradientResult +from ...algorithm_job import AlgorithmJob from ..utils import ( DerivativeType, GradientCircuit, _assign_unique_parameters, - _make_gradient_parameters, _make_gradient_parameter_values, + _make_gradient_parameters, ) -from ...utils.deprecation import issue_deprecation_msg -from ...algorithm_job import AlgorithmJob +from .estimator_gradient_result import EstimatorGradientResult class BaseEstimatorGradient(ABC): @@ -48,7 +44,7 @@ class BaseEstimatorGradient(ABC): def __init__( self, - estimator: BaseEstimator | BaseEstimatorV2, + estimator: BaseEstimatorV2, options: Options | None = None, derivative_type: DerivativeType = DerivativeType.REAL, pass_manager: BasePassManager | None = None, @@ -73,14 +69,7 @@ def __init__( pass_manager: The pass manager to transpile the circuits if necessary. Defaults to ``None``, as some primitives do not need transpiled circuits. """ - if isinstance(estimator, BaseEstimatorV1): - issue_deprecation_msg( - msg="V1 Primitives are deprecated", - version="0.8.0", - remedy="Use V2 primitives for continued compatibility and support.", - period="4 months", - ) - self._estimator: BaseEstimator = estimator + self._estimator: BaseEstimatorV2 = estimator self._pass_manager = pass_manager self._default_options = Options() if options is not None: @@ -88,7 +77,7 @@ def __init__( self._derivative_type = derivative_type self._gradient_circuit_cache: dict[ - tuple, + int | tuple, GradientCircuit, ] = {} @@ -204,7 +193,7 @@ def _preprocess( g_parameter_values: list[Sequence[float]] = [] g_parameters: list[Sequence[Parameter]] = [] for circuit, parameter_value_, parameters_ in zip(circuits, parameter_values, parameters): - circuit_key = _circuit_key(circuit) + circuit_key = hash(circuit) if circuit_key not in self._gradient_circuit_cache: unrolled = translator(circuit) self._gradient_circuit_cache[circuit_key] = _assign_unique_parameters(unrolled) @@ -249,7 +238,7 @@ def _postprocess( ): # If the derivative type is complex, cast the gradient to complex. gradient = gradient.astype("complex") - gradient_circuit = self._gradient_circuit_cache[_circuit_key(circuit)] + gradient_circuit = self._gradient_circuit_cache[hash(circuit)] g_parameters = _make_gradient_parameters(gradient_circuit, parameters_) # Make a map from the gradient parameter to the respective index in the gradient. g_parameter_indices = {param: i for i, param in enumerate(g_parameters)} diff --git a/qiskit_machine_learning/gradients/base/base_sampler_gradient.py b/qiskit_machine_learning/gradients/base/base_sampler_gradient.py index ea8ad98e4..1f1b83ff8 100644 --- a/qiskit_machine_learning/gradients/base/base_sampler_gradient.py +++ b/qiskit_machine_learning/gradients/base/base_sampler_gradient.py @@ -1,6 +1,6 @@ # This code is part of a Qiskit project. # -# (C) Copyright IBM 2022, 2024. +# (C) Copyright IBM 2022, 2025. # # 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 @@ -20,23 +20,22 @@ from collections import defaultdict from collections.abc import Sequence from copy import copy +from typing import Any from qiskit.circuit import Parameter, ParameterExpression, QuantumCircuit -from qiskit.primitives import BaseSampler, BaseSamplerV1 -from qiskit.primitives.utils import _circuit_key +from qiskit.primitives import BaseSamplerV2 from qiskit.providers import Options from qiskit.transpiler.passes import TranslateParameterizedGates from qiskit.transpiler.passmanager import BasePassManager -from .sampler_gradient_result import SamplerGradientResult +from ...algorithm_job import AlgorithmJob from ..utils import ( GradientCircuit, _assign_unique_parameters, - _make_gradient_parameters, _make_gradient_parameter_values, + _make_gradient_parameters, ) -from ...utils.deprecation import issue_deprecation_msg -from ...algorithm_job import AlgorithmJob +from .sampler_gradient_result import SamplerGradientResult class BaseSamplerGradient(ABC): @@ -44,7 +43,7 @@ class BaseSamplerGradient(ABC): def __init__( self, - sampler: BaseSampler, + sampler: BaseSamplerV2, options: Options | None = None, pass_manager: BasePassManager | None = None, ): @@ -58,19 +57,12 @@ def __init__( pass_manager: The pass manager to transpile the circuits if necessary. Defaults to ``None``, as some primitives do not need transpiled circuits. """ - if isinstance(sampler, BaseSamplerV1): - issue_deprecation_msg( - msg="V1 Primitives are deprecated", - version="0.8.0", - remedy="Use V2 primitives for continued compatibility and support.", - period="4 months", - ) - self._sampler: BaseSampler = sampler + self._sampler: BaseSamplerV2 = sampler self._pass_manager = pass_manager self._default_options = Options() if options is not None: self._default_options.update_options(**options) - self._gradient_circuit_cache: dict[tuple, GradientCircuit] = {} + self._gradient_circuit_cache: dict[int | tuple[Any, ...], GradientCircuit] = {} def run( self, @@ -164,7 +156,7 @@ def _preprocess( g_parameter_values: list[Sequence[float]] = [] g_parameters: list[Sequence[Parameter]] = [] for circuit, parameter_value_, parameters_ in zip(circuits, parameter_values, parameters): - circuit_key = _circuit_key(circuit) + circuit_key = hash(circuit) if circuit_key not in self._gradient_circuit_cache: unrolled = translator(circuit) self._gradient_circuit_cache[circuit_key] = _assign_unique_parameters(unrolled) @@ -202,7 +194,7 @@ def _postprocess( for idx, (circuit, parameter_values_, parameters_) in enumerate( zip(circuits, parameter_values, parameters) ): - gradient_circuit = self._gradient_circuit_cache[_circuit_key(circuit)] + gradient_circuit = self._gradient_circuit_cache[hash(circuit)] g_parameters = _make_gradient_parameters(gradient_circuit, parameters_) # Make a map from the gradient parameter to the respective index in the gradient. g_parameter_indices = {param: i for i, param in enumerate(g_parameters)} diff --git a/qiskit_machine_learning/gradients/lin_comb/lin_comb_estimator_gradient.py b/qiskit_machine_learning/gradients/lin_comb/lin_comb_estimator_gradient.py index 53d8649ef..c4c73a4e0 100644 --- a/qiskit_machine_learning/gradients/lin_comb/lin_comb_estimator_gradient.py +++ b/qiskit_machine_learning/gradients/lin_comb/lin_comb_estimator_gradient.py @@ -17,21 +17,21 @@ from collections.abc import Sequence import numpy as np - from qiskit.circuit import Parameter, QuantumCircuit -from qiskit.primitives.base import BaseEstimatorV2 -from qiskit.primitives import BaseEstimator, BaseEstimatorV1 -from qiskit.transpiler.passmanager import BasePassManager - -from qiskit.primitives.utils import init_observable, _circuit_key +from qiskit.primitives import BaseEstimatorV2 from qiskit.providers import Options +from qiskit.quantum_info import SparsePauliOp from qiskit.quantum_info.operators.base_operator import BaseOperator +from qiskit.transpiler.passmanager import BasePassManager +from ...exceptions import AlgorithmError from ..base.base_estimator_gradient import BaseEstimatorGradient from ..base.estimator_gradient_result import EstimatorGradientResult -from ..utils import DerivativeType, _make_lin_comb_gradient_circuit, _make_lin_comb_observables - -from ...exceptions import AlgorithmError +from ..utils import ( + DerivativeType, + _make_lin_comb_gradient_circuit, + _make_lin_comb_observables, +) class LinCombEstimatorGradient(BaseEstimatorGradient): @@ -68,7 +68,7 @@ class LinCombEstimatorGradient(BaseEstimatorGradient): def __init__( self, - estimator: BaseEstimator, + estimator: BaseEstimatorV2, derivative_type: DerivativeType = DerivativeType.REAL, options: Options | None = None, pass_manager: BasePassManager | None = None, @@ -91,7 +91,7 @@ def __init__( pass_manager: The pass manager to transpile the circuits if necessary. Defaults to ``None``, as some primitives do not need transpiled circuits. """ - self._lin_comb_cache: dict[tuple, dict[Parameter, QuantumCircuit]] = {} + self._lin_comb_cache: dict[int | tuple, dict[Parameter, QuantumCircuit]] = {} super().__init__( estimator, options=options, derivative_type=derivative_type, pass_manager=pass_manager ) @@ -134,7 +134,7 @@ def _run_unique( ): # Prepare circuits for the gradient of the specified parameters. meta = {"parameters": parameters_} - circuit_key = _circuit_key(circuit) + circuit_key = hash(circuit) if circuit_key not in self._lin_comb_cache: # Cache the circuits for the linear combination of unitaries. # We only cache the circuits for the specified parameters in the future. @@ -148,7 +148,7 @@ def _run_unique( n = len(gradient_circuits) # Make the observable as :class:`~qiskit.quantum_info.SparsePauliOp` and # add an ancillary operator to compute the gradient. - observable = init_observable(observable) + observable = SparsePauliOp(observable) observable_1, observable_2 = _make_lin_comb_observables( observable, self._derivative_type ) @@ -168,81 +168,42 @@ def _run_unique( job_param_values.extend([parameter_values_] * n) all_n.append(n) - if isinstance(self._estimator, BaseEstimatorV1): - # Run the single job with all circuits. - job = self._estimator.run( - job_circuits, - job_observables, - job_param_values, - **options, - ) - try: - results = job.result() - except Exception as exc: - raise AlgorithmError("Estimator job failed.") from exc - # Compute the gradients. - gradients = [] - partial_sum_n = 0 - for n in all_n: - # this disable is needed as Pylint does not understand derivative_type is a property if - # it is only defined in the base class and the getter is in the child - # pylint: disable=comparison-with-callable - if self.derivative_type == DerivativeType.COMPLEX: - gradient = np.zeros(n // 2, dtype="complex") - gradient.real = results.values[partial_sum_n : partial_sum_n + n // 2] - gradient.imag = results.values[partial_sum_n + n // 2 : partial_sum_n + n] - - else: - gradient = np.real(results.values[partial_sum_n : partial_sum_n + n]) - partial_sum_n += n - gradients.append(gradient) - - opt = self._get_local_options(options) - elif isinstance(self._estimator, BaseEstimatorV2): - if self._pass_manager is None: - circs = job_circuits - observables = job_observables + if self._pass_manager is None: + circs = job_circuits + observables = job_observables + else: + circs = self._pass_manager.run(job_circuits) + observables = [op.apply_layout(circs[i].layout) for i, op in enumerate(job_observables)] + # Prepare circuit-observable-parameter tuples (PUBs) + circuit_observable_params = [] + for pub in zip(circs, observables, job_param_values): + circuit_observable_params.append(pub) + + # Run the estimator using PUBs and specified precision + job = self._estimator.run(circuit_observable_params) + try: + results = job.result() + except Exception as exc: + raise AlgorithmError("Estimator job failed.") from exc + results = np.array([float(r.data.evs) for r in results]) + opt = Options(**options) + # Compute the gradients. + gradients = [] + partial_sum_n = 0 + for n in all_n: + # this disable is needed as Pylint does not understand derivative_type is a property if + # it is only defined in the base class and the getter is in the child + # pylint: disable=comparison-with-callable + if self.derivative_type == DerivativeType.COMPLEX: + gradient = np.zeros(n // 2, dtype="complex") + gradient.real = results[partial_sum_n : partial_sum_n + n // 2] + gradient.imag = results[partial_sum_n + n // 2 : partial_sum_n + n] + else: - circs = self._pass_manager.run(job_circuits) - observables = [ - op.apply_layout(circs[i].layout) for i, op in enumerate(job_observables) - ] - # Prepare circuit-observable-parameter tuples (PUBs) - circuit_observable_params = [] - for pub in zip(circs, observables, job_param_values): - circuit_observable_params.append(pub) - - # For BaseEstimatorV2, run the estimator using PUBs and specified precision - job = self._estimator.run(circuit_observable_params) - try: - results = job.result() - except Exception as exc: - raise AlgorithmError("Estimator job failed.") from exc - results = np.array([float(r.data.evs) for r in results]) - opt = Options(**options) - # Compute the gradients. - gradients = [] - partial_sum_n = 0 - for n in all_n: - # this disable is needed as Pylint does not understand derivative_type is a property if - # it is only defined in the base class and the getter is in the child - # pylint: disable=comparison-with-callable - if self.derivative_type == DerivativeType.COMPLEX: - gradient = np.zeros(n // 2, dtype="complex") - gradient.real = results[partial_sum_n : partial_sum_n + n // 2] - gradient.imag = results[partial_sum_n + n // 2 : partial_sum_n + n] - - else: - gradient = np.real( - results[partial_sum_n : partial_sum_n + n] - ) # type: ignore[assignment, unused-ignore] - partial_sum_n += n - gradients.append(gradient) + gradient = np.real( + results[partial_sum_n : partial_sum_n + n] + ) # type: ignore[assignment, unused-ignore] + partial_sum_n += n + gradients.append(gradient) - else: - raise AlgorithmError( - "The accepted estimators are BaseEstimatorV1 and BaseEstimatorV2; got " - + f"{type(self._estimator)} instead. Note that BaseEstimatorV1 is deprecated in" - + "Qiskit and removed in Qiskit IBM Runtime." - ) return EstimatorGradientResult(gradients=gradients, metadata=metadata, options=opt) diff --git a/qiskit_machine_learning/gradients/lin_comb/lin_comb_sampler_gradient.py b/qiskit_machine_learning/gradients/lin_comb/lin_comb_sampler_gradient.py index 27fa978a8..0c209c1f3 100644 --- a/qiskit_machine_learning/gradients/lin_comb/lin_comb_sampler_gradient.py +++ b/qiskit_machine_learning/gradients/lin_comb/lin_comb_sampler_gradient.py @@ -1,6 +1,6 @@ # This code is part of a Qiskit project. # -# (C) Copyright IBM 2022, 2024. +# (C) Copyright IBM 2022, 2025. # # 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 @@ -19,20 +19,16 @@ from collections.abc import Sequence from qiskit.circuit import Parameter, QuantumCircuit -from qiskit.primitives.utils import _circuit_key - -from qiskit.primitives import BaseSampler, BaseSamplerV1 -from qiskit.primitives.base import BaseSamplerV2 -from qiskit.result import QuasiDistribution +from qiskit.primitives import BaseSamplerV2 from qiskit.providers import Options +from qiskit.result import QuasiDistribution from qiskit.transpiler.passmanager import BasePassManager +from ...exceptions import AlgorithmError from ..base.base_sampler_gradient import BaseSamplerGradient from ..base.sampler_gradient_result import SamplerGradientResult from ..utils import _make_lin_comb_gradient_circuit -from ...exceptions import AlgorithmError - class LinCombSamplerGradient(BaseSamplerGradient): """Compute the gradients of the sampling probability. @@ -68,7 +64,7 @@ class LinCombSamplerGradient(BaseSamplerGradient): def __init__( self, - sampler: BaseSampler, + sampler: BaseSamplerV2, options: Options | None = None, pass_manager: BasePassManager | None = None, ): @@ -82,7 +78,7 @@ def __init__( pass_manager: The pass manager to transpile the circuits if necessary. Defaults to ``None``, as some primitives do not need transpiled circuits. """ - self._lin_comb_cache: dict[tuple, dict[Parameter, QuantumCircuit]] = {} + self._lin_comb_cache: dict[int | tuple, dict[Parameter, QuantumCircuit]] = {} super().__init__(sampler, options, pass_manager=pass_manager) def _run( @@ -113,7 +109,7 @@ def _run_unique( # Prepare circuits for the gradient of the specified parameters. # TODO: why is this not wrapped into another list level like it is done elsewhere? metadata.append({"parameters": parameters_}) - circuit_key = _circuit_key(circuit) + circuit_key = hash(circuit) if circuit_key not in self._lin_comb_cache: # Cache the circuits for the linear combination of unitaries. # We only cache the circuits for the specified parameters in the future. @@ -132,23 +128,14 @@ def _run_unique( opt = options # Run the single job with all circuits. - if isinstance(self._sampler, BaseSamplerV1): - job = self._sampler.run(job_circuits, job_param_values, **options) - opt = self._get_local_options(options) - elif isinstance(self._sampler, BaseSamplerV2): - if self._pass_manager is None: - circs = job_circuits - _len_quasi_dist = 2 ** job_circuits[0].num_qubits - else: - circs = self._pass_manager.run(job_circuits) - _len_quasi_dist = 2 ** circs[0].layout._input_qubit_count - circ_params = [(circs[i], job_param_values[i]) for i in range(len(job_param_values))] - job = self._sampler.run(circ_params) + if self._pass_manager is None: + circs = job_circuits + _len_quasi_dist = 2 ** job_circuits[0].num_qubits else: - raise AlgorithmError( - "The accepted estimators are BaseSamplerV1 (deprecated) and BaseSamplerV2; got " - + f"{type(self._sampler)} instead." - ) + circs = self._pass_manager.run(job_circuits) + _len_quasi_dist = 2 ** circs[0].layout._input_qubit_count + circ_params = [(circs[i], job_param_values[i]) for i in range(len(job_param_values))] + job = self._sampler.run(circ_params) try: results = job.result() except Exception as exc: @@ -159,25 +146,17 @@ def _run_unique( partial_sum_n = 0 for i, n in enumerate(all_n): gradient = [] - if isinstance(self._sampler, BaseSamplerV1): - result = results.quasi_dists[partial_sum_n : partial_sum_n + n] - - elif isinstance(self._sampler, BaseSamplerV2): - result = [] - for x in range(partial_sum_n, partial_sum_n + n): - if hasattr(results[x].data, "meas"): - bitstring_counts = results[x].data.meas.get_counts() - else: - # Fallback to 'c' if 'meas' is not available. - bitstring_counts = results[x].data.c.get_counts() + result = [] + for x in range(partial_sum_n, partial_sum_n + n): + bitstring_counts = results[x].join_data().get_counts() - # Normalize the counts to probabilities - total_shots = sum(bitstring_counts.values()) - probabilities = {k: v / total_shots for k, v in bitstring_counts.items()} + # Normalize the counts to probabilities + total_shots = sum(bitstring_counts.values()) + probabilities = {k: v / total_shots for k, v in bitstring_counts.items()} - # Convert to quasi-probabilities - counts = QuasiDistribution(probabilities) - result.append({k: v for k, v in counts.items() if int(k) < _len_quasi_dist}) + # Convert to quasi-probabilities + counts = QuasiDistribution(probabilities) + result.append({k: v for k, v in counts.items() if int(k) < _len_quasi_dist}) m = 2 ** circuits[i].num_qubits for dist in result: grad_dist: dict[int, float] = defaultdict(float) diff --git a/qiskit_machine_learning/gradients/spsa/spsa_estimator_gradient.py b/qiskit_machine_learning/gradients/spsa/spsa_estimator_gradient.py index 801e48182..5abf7f08a 100644 --- a/qiskit_machine_learning/gradients/spsa/spsa_estimator_gradient.py +++ b/qiskit_machine_learning/gradients/spsa/spsa_estimator_gradient.py @@ -1,6 +1,6 @@ # This code is part of a Qiskit project. # -# (C) Copyright IBM 2022, 2024. +# (C) Copyright IBM 2022, 2025. # # 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 @@ -17,19 +17,16 @@ from collections.abc import Sequence import numpy as np - from qiskit.circuit import Parameter, QuantumCircuit +from qiskit.primitives import BaseEstimatorV2 from qiskit.providers import Options from qiskit.quantum_info.operators.base_operator import BaseOperator -from qiskit.primitives.base import BaseEstimatorV2 -from qiskit.primitives import BaseEstimator, BaseEstimatorV1 from qiskit.transpiler.passmanager import BasePassManager +from ...exceptions import AlgorithmError from ..base.base_estimator_gradient import BaseEstimatorGradient from ..base.estimator_gradient_result import EstimatorGradientResult -from ...exceptions import AlgorithmError - class SPSAEstimatorGradient(BaseEstimatorGradient): """ @@ -45,7 +42,7 @@ class SPSAEstimatorGradient(BaseEstimatorGradient): # pylint: disable=too-many-positional-arguments def __init__( self, - estimator: BaseEstimator, + estimator: BaseEstimatorV2, epsilon: float = 1e-6, batch_size: int = 1, seed: int | None = None, @@ -107,84 +104,40 @@ def _run( job_observables.extend([observable] * 2 * self._batch_size) job_param_values.extend(plus + minus) all_n.append(2 * self._batch_size) - if isinstance(self._estimator, BaseEstimatorV1): - # Run the single job with all circuits. - job = self._estimator.run( - job_circuits, - job_observables, - job_param_values, - **options, - ) - try: - results = job.result() - except Exception as exc: - raise AlgorithmError("Estimator job failed.") from exc - # Compute the gradients. - gradients = [] - partial_sum_n = 0 - for i, n in enumerate(all_n): - result = results.values[partial_sum_n : partial_sum_n + n] - partial_sum_n += n - n = len(result) // 2 - diffs = (result[:n] - result[n:]) / (2 * self._epsilon) - # Calculate the gradient for each batch. - # Note that (``diff`` / ``offset``) is the gradient - # since ``offset`` is a perturbation vector of 1s and -1s. - batch_gradients = np.array( - [diff / offset for diff, offset in zip(diffs, offsets[i])] - ) - # Take the average of the batch gradients. - gradient = np.mean(batch_gradients, axis=0) - indices = [circuits[i].parameters.data.index(p) for p in metadata[i]["parameters"]] - gradients.append(gradient[indices]) - opt = self._get_local_options(options) - elif isinstance(self._estimator, BaseEstimatorV2): - if self._pass_manager is None: - circs = job_circuits - observables = job_observables - else: - circs = self._pass_manager.run(job_circuits) - observables = [ - op.apply_layout(circs[x].layout) for x, op in enumerate(job_observables) - ] - # Prepare circuit-observable-parameter tuples (PUBs) - circuit_observable_params = [] - for pub in zip(circs, observables, job_param_values): - circuit_observable_params.append(pub) - - # For BaseEstimatorV2, run the estimator using PUBs and specified precision - job = self._estimator.run(circuit_observable_params) - try: - results = job.result() - except Exception as exc: - raise AlgorithmError("Estimator job failed.") from exc - results = np.array([float(r.data.evs) for r in results]) - opt = Options(**options) - - # Compute the gradients. - gradients = [] - partial_sum_n = 0 - for i, n in enumerate(all_n): - result = results[partial_sum_n : partial_sum_n + n] - partial_sum_n += n - n = len(result) // 2 - diffs = (result[:n] - result[n:]) / (2 * self._epsilon) - # Calculate the gradient for each batch. - # Note that (``diff`` / ``offset``) is the gradient - # since ``offset`` is a perturbation vector of 1s and -1s. - batch_gradients = np.array( - [diff / offset for diff, offset in zip(diffs, offsets[i])] - ) - # Take the average of the batch gradients. - gradient = np.mean(batch_gradients, axis=0) - indices = [circuits[i].parameters.data.index(p) for p in metadata[i]["parameters"]] - gradients.append(gradient[indices]) - + if self._pass_manager is None: + circs = job_circuits + observables = job_observables else: - raise AlgorithmError( - "The accepted estimators are BaseEstimatorV1 and BaseEstimatorV2; got " - + f"{type(self._estimator)} instead. Note that BaseEstimatorV1 is deprecated in" - + "Qiskit and removed in Qiskit IBM Runtime." - ) - + circs = self._pass_manager.run(job_circuits) + observables = [op.apply_layout(circs[x].layout) for x, op in enumerate(job_observables)] + # Prepare circuit-observable-parameter tuples (PUBs) + circuit_observable_params = [] + for pub in zip(circs, observables, job_param_values): + circuit_observable_params.append(pub) + + # For BaseEstimatorV2, run the estimator using PUBs and specified precision + job = self._estimator.run(circuit_observable_params) + try: + results = job.result() + except Exception as exc: + raise AlgorithmError("Estimator job failed.") from exc + results = np.array([float(r.data.evs) for r in results]) + opt = Options(**options) + + # Compute the gradients. + gradients = [] + partial_sum_n = 0 + for i, n in enumerate(all_n): + result = results[partial_sum_n : partial_sum_n + n] + partial_sum_n += n + n = len(result) // 2 + diffs = (result[:n] - result[n:]) / (2 * self._epsilon) + # Calculate the gradient for each batch. + # Note that (``diff`` / ``offset``) is the gradient + # since ``offset`` is a perturbation vector of 1s and -1s. + batch_gradients = np.array([diff / offset for diff, offset in zip(diffs, offsets[i])]) + # Take the average of the batch gradients. + gradient = np.mean(batch_gradients, axis=0) + indices = [circuits[i].parameters.data.index(p) for p in metadata[i]["parameters"]] + gradients.append(gradient[indices]) return EstimatorGradientResult(gradients=gradients, metadata=metadata, options=opt) diff --git a/qiskit_machine_learning/gradients/spsa/spsa_sampler_gradient.py b/qiskit_machine_learning/gradients/spsa/spsa_sampler_gradient.py index 574cab9ea..35b305ce4 100644 --- a/qiskit_machine_learning/gradients/spsa/spsa_sampler_gradient.py +++ b/qiskit_machine_learning/gradients/spsa/spsa_sampler_gradient.py @@ -1,6 +1,6 @@ # This code is part of a Qiskit project. # -# (C) Copyright IBM 2022, 2024. +# (C) Copyright IBM 2022, 2025. # # 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 @@ -18,20 +18,16 @@ from collections.abc import Sequence import numpy as np - from qiskit.circuit import Parameter, QuantumCircuit - -from qiskit.primitives import BaseSampler, BaseSamplerV1 -from qiskit.primitives.base import BaseSamplerV2 -from qiskit.result import QuasiDistribution +from qiskit.primitives import BaseSamplerV2 from qiskit.providers import Options +from qiskit.result import QuasiDistribution from qiskit.transpiler.passmanager import BasePassManager +from ...exceptions import AlgorithmError from ..base.base_sampler_gradient import BaseSamplerGradient from ..base.sampler_gradient_result import SamplerGradientResult -from ...exceptions import AlgorithmError - class SPSASamplerGradient(BaseSamplerGradient): """ @@ -47,7 +43,7 @@ class SPSASamplerGradient(BaseSamplerGradient): # pylint: disable=too-many-positional-arguments def __init__( self, - sampler: BaseSampler, + sampler: BaseSamplerV2, epsilon: float = 1e-6, batch_size: int = 1, seed: int | None = None, @@ -110,23 +106,14 @@ def _run( opt = options # Run the single job with all circuits. - if isinstance(self._sampler, BaseSamplerV1): - job = self._sampler.run(job_circuits, job_param_values, **options) - opt = self._get_local_options(options) - elif isinstance(self._sampler, BaseSamplerV2): - if self._pass_manager is None: - _circs = job_circuits - _len_quasi_dist = 2 ** job_circuits[0].num_qubits - else: - _circs = self._pass_manager.run(job_circuits) - _len_quasi_dist = 2 ** _circs[0].layout._input_qubit_count - _circ_params = [(_circs[i], job_param_values[i]) for i in range(len(job_param_values))] - job = self._sampler.run(_circ_params) + if self._pass_manager is None: + _circs = job_circuits + _len_quasi_dist = 2 ** job_circuits[0].num_qubits else: - raise AlgorithmError( - "The accepted estimators are BaseSamplerV1 (deprecated) and BaseSamplerV2; got " - + f"{type(self._sampler)} instead." - ) + _circs = self._pass_manager.run(job_circuits) + _len_quasi_dist = 2 ** _circs[0].layout._input_qubit_count + _circ_params = [(_circs[i], job_param_values[i]) for i in range(len(job_param_values))] + job = self._sampler.run(_circ_params) try: results = job.result() except Exception as exc: @@ -137,24 +124,17 @@ def _run( result = [] partial_sum_n = 0 for i, n in enumerate(all_n): - dist_diffs = {} - if isinstance(self._sampler, BaseSamplerV1): - result = results.quasi_dists[partial_sum_n : partial_sum_n + n] - elif isinstance(self._sampler, BaseSamplerV2): - _result = [] - for m in range(partial_sum_n, partial_sum_n + n): - if hasattr(results[i].data, "meas"): - _bitstring_counts = results[m].data.meas.get_counts() - else: - # Fallback to 'c' if 'meas' is not available. - _bitstring_counts = results[m].data.c.get_counts() - # Normalize the counts to probabilities - _total_shots = sum(_bitstring_counts.values()) - _probabilities = {k: v / _total_shots for k, v in _bitstring_counts.items()} - # Convert to quasi-probabilities - _counts = QuasiDistribution(_probabilities) - _result.append({k: v for k, v in _counts.items() if int(k) < _len_quasi_dist}) - result = [{key: d[key] for key in sorted(d)} for d in _result] + dist_diffs: dict[int | str, dict[int, float]] = {} + _result = [] + for m in range(partial_sum_n, partial_sum_n + n): + _bitstring_counts = results[m].join_data().get_counts() + # Normalize the counts to probabilities + _total_shots = sum(_bitstring_counts.values()) + _probabilities = {k: v / _total_shots for k, v in _bitstring_counts.items()} + # Convert to quasi-probabilities + _counts = QuasiDistribution(_probabilities) + _result.append({k: v for k, v in _counts.items() if int(k) < _len_quasi_dist}) + result = [{key: d[key] for key in sorted(d)} for d in _result] for j, (dist_plus, dist_minus) in enumerate(zip(result[: n // 2], result[n // 2 :])): dist_diff: dict[int, float] = defaultdict(float) diff --git a/qiskit_machine_learning/kernels/base_kernel.py b/qiskit_machine_learning/kernels/base_kernel.py index 8417913b6..fb5558a96 100644 --- a/qiskit_machine_learning/kernels/base_kernel.py +++ b/qiskit_machine_learning/kernels/base_kernel.py @@ -14,11 +14,11 @@ from __future__ import annotations -from abc import abstractmethod, ABC +from abc import ABC, abstractmethod import numpy as np from qiskit import QuantumCircuit -from qiskit.circuit.library import ZZFeatureMap +from qiskit.circuit.library import zz_feature_map from ..utils.deprecation import issue_deprecation_msg @@ -48,7 +48,7 @@ def __init__(self, *, feature_map: QuantumCircuit = None, enforce_psd: bool = Tr """ Args: feature_map: Parameterized circuit to be used as the feature map. If ``None`` is given, - :class:`~qiskit.circuit.library.ZZFeatureMap` is used with two qubits. If there's + :func:`~qiskit.circuit.library.zz_feature_map` is used with two qubits. If there's a mismatch in the number of qubits of the feature map and the number of features in the dataset, then the kernel will try to adjust the feature map to reflect the number of features. @@ -64,11 +64,11 @@ def __init__(self, *, feature_map: QuantumCircuit = None, enforce_psd: bool = Tr remedy="Pass a feature map with the required number of qubits to match " "the features. Adjusting the number of qubits after instantiation will be " "removed from Qiskit as circuits based on BlueprintCircuit, " - "like ZZFeatureMap to which this defaults, which could do this, " + "like zz_feature_map to which this defaults, which could do this, " "have been deprecated.", period="4 months", ) - feature_map = ZZFeatureMap(2) + feature_map = zz_feature_map(2) self._num_features = feature_map.num_parameters self._feature_map = feature_map diff --git a/qiskit_machine_learning/kernels/fidelity_quantum_kernel.py b/qiskit_machine_learning/kernels/fidelity_quantum_kernel.py index 212c32acd..b37565ef6 100644 --- a/qiskit_machine_learning/kernels/fidelity_quantum_kernel.py +++ b/qiskit_machine_learning/kernels/fidelity_quantum_kernel.py @@ -1,6 +1,6 @@ # This code is part of a Qiskit project. # -# (C) Copyright IBM 2022, 2024. +# (C) Copyright IBM 2022, 2025. # # 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 @@ -14,16 +14,14 @@ from __future__ import annotations from collections.abc import Sequence -from typing import List, Tuple - import numpy as np from qiskit import QuantumCircuit -from qiskit.primitives import Sampler -from ..state_fidelities import BaseStateFidelity, ComputeUncompute +from qiskit.primitives import StatevectorEstimator +from ..state_fidelities import BaseStateFidelity, ComputeUncompute from .base_kernel import BaseKernel -KernelIndices = List[Tuple[int, int]] +KernelIndices = list[tuple[int, int]] class FidelityQuantumKernel(BaseKernel): @@ -51,7 +49,7 @@ def __init__( """ Args: feature_map: Parameterized circuit to be used as the feature map. If ``None`` is given, - :class:`~qiskit.circuit.library.ZZFeatureMap` is used with two qubits. If there's + :func:`~qiskit.circuit.library.zz_feature_map` is used with two qubits. If there's a mismatch in the number of qubits of the feature map and the number of features in the dataset, then the kernel will try to adjust the feature map to reflect the number of features. @@ -59,7 +57,7 @@ def __init__( :class:`~qiskit_machine_learning.state_fidelities.BaseStateFidelity` primitive to be used to compute fidelity between states. Default is :class:`~qiskit_machine_learning.state_fidelities.ComputeUncompute` which is created on - top of the reference sampler defined by :class:`~qiskit.primitives.Sampler`. + top of the reference sampler defined by :class:`~qiskit.primitives.BaseSamplerV2`. enforce_psd: Project to the closest positive semidefinite matrix if ``x = y``. Default ``True``. evaluate_duplicates: Defines a strategy how kernel matrix elements are evaluated if @@ -83,12 +81,10 @@ def __init__( eval_duplicates = evaluate_duplicates.lower() if eval_duplicates not in ("all", "off_diagonal", "none"): - raise ValueError( - f"Unsupported value passed as evaluate_duplicates: {evaluate_duplicates}" - ) + raise ValueError(f"Unsupported value passed as evaluate_duplicates: {eval_duplicates}") self._evaluate_duplicates = eval_duplicates if fidelity is None: - fidelity = ComputeUncompute(sampler=Sampler()) + fidelity = ComputeUncompute(sampler=StatevectorEstimator()) self._fidelity = fidelity if max_circuits_per_job is not None: if max_circuits_per_job < 1: diff --git a/qiskit_machine_learning/neural_networks/effective_dimension.py b/qiskit_machine_learning/neural_networks/effective_dimension.py index 1840bdca2..cfc97de6a 100644 --- a/qiskit_machine_learning/neural_networks/effective_dimension.py +++ b/qiskit_machine_learning/neural_networks/effective_dimension.py @@ -13,7 +13,7 @@ import logging import time -from typing import Any, Union, List, Tuple +from typing import Any, Union import numpy as np from scipy.special import logsumexp @@ -129,7 +129,7 @@ def input_samples(self, input_samples: Union[np.ndarray, int]) -> None: self._num_input_samples = len(self._input_samples) - def run_monte_carlo(self) -> Tuple[np.ndarray, np.ndarray]: + def run_monte_carlo(self) -> tuple[np.ndarray, np.ndarray]: """ This method computes the model's Monte Carlo sampling for a set of input samples and weight samples. @@ -215,7 +215,7 @@ def get_fisher_information( return fisher_information - def get_normalized_fisher(self, normalized_fisher: np.ndarray) -> Tuple[np.ndarray, float]: + def get_normalized_fisher(self, normalized_fisher: np.ndarray) -> tuple[np.ndarray, float]: """ This method computes the normalized Fisher Information Matrix and extracts its trace. @@ -254,7 +254,7 @@ def get_normalized_fisher(self, normalized_fisher: np.ndarray) -> Tuple[np.ndarr def _get_effective_dimension( self, normalized_fisher: np.ndarray, - dataset_size: Union[List[int], np.ndarray, int], + dataset_size: Union[list[int], np.ndarray, int], ) -> Union[np.ndarray, int]: if not isinstance(dataset_size, int) and len(dataset_size) > 1: # expand dims for broadcasting @@ -282,7 +282,7 @@ def _get_effective_dimension( return np.squeeze(effective_dims) def get_effective_dimension( - self, dataset_size: Union[List[int], np.ndarray, int] + self, dataset_size: Union[list[int], np.ndarray, int] ) -> Union[np.ndarray, int]: """ This method computes the effective dimension for a dataset of size ``dataset_size``. If an diff --git a/qiskit_machine_learning/neural_networks/estimator_qnn.py b/qiskit_machine_learning/neural_networks/estimator_qnn.py index 4a70731a0..1710c71a3 100644 --- a/qiskit_machine_learning/neural_networks/estimator_qnn.py +++ b/qiskit_machine_learning/neural_networks/estimator_qnn.py @@ -17,24 +17,22 @@ import logging from copy import copy from typing import Sequence -import numpy as np +import numpy as np from qiskit.circuit import Parameter, QuantumCircuit +from qiskit.primitives import StatevectorEstimator from qiskit.primitives.base import BaseEstimatorV2 -from qiskit.primitives import BaseEstimator, BaseEstimatorV1, Estimator, EstimatorResult from qiskit.quantum_info import SparsePauliOp from qiskit.quantum_info.operators.base_operator import BaseOperator from qiskit.transpiler.passmanager import BasePassManager - +from ..circuit.library import QNNCircuit +from ..exceptions import QiskitMachineLearningError from ..gradients import ( BaseEstimatorGradient, EstimatorGradientResult, ParamShiftEstimatorGradient, ) - -from ..circuit.library import QNNCircuit -from ..exceptions import QiskitMachineLearningError from ..utils.deprecation import issue_deprecation_msg from .neural_network import NeuralNetwork @@ -96,7 +94,6 @@ class EstimatorQNN(NeuralNetwork): qnn.forward(input_data=[1, 2], weights=[1, 2, 3, 4, 5, 6, 7, 8]) - The following attributes can be set via the constructor but can also be read and updated once the EstimatorQNN object has been constructed. @@ -111,7 +108,7 @@ def __init__( self, *, circuit: QuantumCircuit, - estimator: BaseEstimator | BaseEstimatorV2 | None = None, + estimator: BaseEstimatorV2 | None = None, observables: Sequence[BaseOperator] | BaseOperator | None = None, input_params: Sequence[Parameter] | None = None, weight_params: Sequence[Parameter] | None = None, @@ -129,14 +126,15 @@ def __init__( :class:`~qiskit_machine_learning.circuit.library.QNNCircuit` (DEPRECATED). estimator: The estimator used to compute neural network's results. If ``None``, a default instance of the reference estimator, - :class:`~qiskit.primitives.Estimator`, will be used. + :class:`~qiskit.primitives.StatevectorEstimator`, will be used. # change: Estimator is + replaced by StatevectorEstimator .. warning:: The assignment ``estimator=None`` defaults to using - :class:`~qiskit.primitives.Estimator`, which points to a deprecated estimator V1 - (as of Qiskit 1.2). ``EstimatorQNN`` will adopt Estimator V2 as default no later than - Qiskit Machine Learning 0.9. + :class:`~qiskit.primitives.StatevectorEstimator`, which points to a deprecated + estimator V1 (as of Qiskit 1.2). ``EstimatorQNN`` will adopt Estimator V2 as + default no later than Qiskit Machine Learning 0.9. observables: The observables for outputs of the neural network. If ``None``, use the default :math:`Z^{\otimes n}` observable, where :math:`n` @@ -166,15 +164,8 @@ def __init__( QiskitMachineLearningError: Invalid parameter values. """ if estimator is None: - estimator = Estimator() + estimator = StatevectorEstimator() - if isinstance(estimator, BaseEstimatorV1): - issue_deprecation_msg( - msg="V1 Primitives are deprecated", - version="0.8.0", - remedy="Use V2 primitives for continued compatibility and support.", - period="4 months", - ) self.estimator = estimator if hasattr(circuit.layout, "_input_qubit_count"): @@ -218,17 +209,14 @@ def __init__( # set gradient if gradient is None: - if isinstance(estimator, BaseEstimatorV1): - gradient = ParamShiftEstimatorGradient(estimator=self.estimator) - else: - if pass_manager is None: - logger.warning( - "No gradient function provided, creating a gradient function." - " If your Estimator requires transpilation, please provide a pass manager." - ) - gradient = ParamShiftEstimatorGradient( - estimator=self.estimator, pass_manager=pass_manager + if pass_manager is None: + logger.warning( + "No gradient function provided, creating a gradient function." + " If your Estimator requires transpilation, please provide a pass manager." ) + gradient = ParamShiftEstimatorGradient( + estimator=self.estimator, pass_manager=pass_manager + ) self._default_precision = default_precision self.gradient = gradient self._input_gradients = input_gradients @@ -279,7 +267,7 @@ def default_precision(self) -> float: """Return the default precision""" return self._default_precision - def _forward_postprocess(self, num_samples: int, result: EstimatorResult) -> np.ndarray: + def _forward_postprocess(self, num_samples: int, result: list) -> np.ndarray: """Post-processing during forward pass of the network.""" return np.reshape(result, (-1, num_samples)).T @@ -289,31 +277,14 @@ def _forward( """Forward pass of the neural network.""" parameter_values_, num_samples = self._preprocess_forward(input_data, weights) - # Determine how to run the estimator based on its version - if isinstance(self.estimator, BaseEstimatorV1): - job = self.estimator.run( - [self._circuit] * num_samples * self.output_shape[0], - [op for op in self._observables for _ in range(num_samples)], - np.tile(parameter_values_, (self.output_shape[0], 1)), - ) - results = job.result().values - - elif isinstance(self.estimator, BaseEstimatorV2): - - # Prepare circuit-observable-parameter tuples (PUBs) - circuit_observable_params = [] - for observable in self._observables: - circuit_observable_params.append((self._circuit, observable, parameter_values_)) + # Prepare circuit-observable-parameter tuples (PUBs) + circuit_observable_params = [] + for observable in self._observables: + circuit_observable_params.append((self._circuit, observable, parameter_values_)) - # For BaseEstimatorV2, run the estimator using PUBs and specified precision - job = self.estimator.run(circuit_observable_params, precision=self._default_precision) - results = [result.data.evs for result in job.result()] - else: - raise QiskitMachineLearningError( - "The accepted estimators are BaseEstimatorV1 and BaseEstimatorV2; got " - + f"{type(self.estimator)} instead. Note that BaseEstimatorV1 is deprecated in" - + "Qiskit and removed in Qiskit IBM Runtime." - ) + # For BaseEstimatorV2, run the estimator using PUBs and specified precision + job = self.estimator.run(circuit_observable_params, precision=self._default_precision) + results = [result.data.evs for result in job.result()] return self._forward_postprocess(num_samples, results) def _backward_postprocess( diff --git a/qiskit_machine_learning/neural_networks/sampler_qnn.py b/qiskit_machine_learning/neural_networks/sampler_qnn.py index dc4947f8f..d206236c7 100644 --- a/qiskit_machine_learning/neural_networks/sampler_qnn.py +++ b/qiskit_machine_learning/neural_networks/sampler_qnn.py @@ -13,32 +13,34 @@ """A Neural Network implementation based on the Sampler primitive.""" from __future__ import annotations + import logging from numbers import Integral -from typing import Callable, cast, Iterable, Sequence -import numpy as np - -from qiskit.primitives import BaseSamplerV1 -from qiskit.primitives.base import BaseSamplerV2 +from typing import Callable, Iterable, Sequence, cast +import numpy as np from qiskit.circuit import Parameter, QuantumCircuit -from qiskit.primitives import BaseSampler, SamplerResult, Sampler +from qiskit.primitives import ( + BaseSamplerV2, + PrimitiveResult, + SamplerPubResult, + StatevectorSampler, +) from qiskit.result import QuasiDistribution from qiskit.transpiler.passmanager import BasePassManager import qiskit_machine_learning.optionals as _optionals +from ..circuit.library import QNNCircuit +from ..exceptions import QiskitMachineLearningError from ..gradients import ( BaseSamplerGradient, ParamShiftSamplerGradient, SamplerGradientResult, ) -from ..circuit.library import QNNCircuit -from ..exceptions import QiskitMachineLearningError from ..utils.deprecation import issue_deprecation_msg from .neural_network import NeuralNetwork - if _optionals.HAS_SPARSE: # pylint: disable=import-error from sparse import SparseArray @@ -159,7 +161,7 @@ def __init__( self, *, circuit: QuantumCircuit, - sampler: BaseSampler | None = None, + sampler: BaseSamplerV2 | None = None, input_params: Sequence[Parameter] | None = None, weight_params: Sequence[Parameter] | None = None, sparse: bool = False, @@ -218,15 +220,8 @@ def __init__( """ # Set primitive, provide default if sampler is None: - sampler = Sampler() + sampler = StatevectorSampler() - if isinstance(sampler, BaseSamplerV1): - issue_deprecation_msg( - msg="V1 Primitives are deprecated", - version="0.8.0", - remedy="Use V2 primitives for continued compatibility and support.", - period="4 months", - ) self.sampler = sampler if hasattr(circuit.layout, "_input_qubit_count"): self.num_virtual_qubits = circuit.layout._input_qubit_count @@ -264,17 +259,12 @@ def __init__( # Set gradient if gradient is None: - if isinstance(sampler, BaseSamplerV1): - gradient = ParamShiftSamplerGradient(sampler=self.sampler) - else: - if pass_manager is None: - logger.warning( - "No gradient function provided, creating a gradient function." - " If your Sampler requires transpilation, please provide a pass manager." - ) - gradient = ParamShiftSamplerGradient( - sampler=self.sampler, pass_manager=pass_manager + if pass_manager is None: + logger.warning( + "No gradient function provided, creating a gradient function." + " If your Sampler requires transpilation, please provide a pass manager." ) + gradient = ParamShiftSamplerGradient(sampler=self.sampler, pass_manager=pass_manager) self.gradient = gradient self._input_gradients = input_gradients @@ -366,7 +356,9 @@ def _compute_output_shape( output_shape_ = (2**self.num_virtual_qubits,) return output_shape_ - def _postprocess(self, num_samples: int, result: SamplerResult) -> np.ndarray | SparseArray: + def _postprocess( + self, num_samples: int, result: PrimitiveResult[SamplerPubResult] + ) -> np.ndarray | SparseArray: """ Post-processing during forward pass of the network. """ @@ -379,41 +371,34 @@ def _postprocess(self, num_samples: int, result: SamplerResult) -> np.ndarray | else: prob = np.zeros((num_samples, *self._output_shape)) - for i in range(num_samples): - if isinstance(self.sampler, BaseSamplerV1): - counts = result.quasi_dists[i] - - elif isinstance(self.sampler, BaseSamplerV2): - if hasattr(result[i].data, "meas"): - bitstring_counts = result[i].data.meas.get_counts() - else: - # Fallback to 'c' if 'meas' is not available. - bitstring_counts = result[i].data.c.get_counts() - - # Normalize the counts to probabilities - total_shots = sum(bitstring_counts.values()) - probabilities = {k: v / total_shots for k, v in bitstring_counts.items()} - - # Convert to quasi-probabilities - counts = QuasiDistribution(probabilities) - counts = {k: v for k, v in counts.items() if int(k) < 2**self.num_virtual_qubits} + # Get the counts from the result + bitstring_counts = result[0].join_data().get_counts() + + # Normalize the counts to probabilities + total_shots = sum(bitstring_counts.values()) + probabilities = {k: v / total_shots for k, v in bitstring_counts.items()} + + # Convert to quasi-probabilities + counts = QuasiDistribution(probabilities) + counts = {k: v for k, v in counts.items() if int(k) < 2**self.num_virtual_qubits} + + # Precompute interpreted keys + interpreted_keys: list = [] + for b in counts: + key = self._interpret(b) + if isinstance(key, Integral): + key = (cast(int, key),) + interpreted_keys.append(key) + + # Populate probabilities + for key_suffix, value in zip(interpreted_keys, counts.values()): + if self._sparse: + for i in range(num_samples): + prob[(i, *key_suffix)] += value else: - raise QiskitMachineLearningError( - "The accepted estimators are BaseSamplerV1 (deprecated) and BaseSamplerV2; " - + f"got {type(self.sampler)} instead." - ) - # evaluate probabilities - for b, v in counts.items(): - key = self._interpret(b) - if isinstance(key, Integral): - key = (cast(int, key),) - key = (i, *key) # type: ignore - prob[key] += v + prob[(slice(None), *key_suffix)] += value - if self._sparse: - return prob.to_coo() - else: - return prob + return prob.to_coo() if self._sparse else prob def _postprocess_gradient( self, num_samples: int, results: SamplerGradientResult @@ -491,17 +476,7 @@ def _forward( """ parameter_values, num_samples = self._preprocess_forward(input_data, weights) - if isinstance(self.sampler, BaseSamplerV1): - job = self.sampler.run([self._circuit] * num_samples, parameter_values) - elif isinstance(self.sampler, BaseSamplerV2): - job = self.sampler.run( - [(self._circuit, parameter_values[i]) for i in range(num_samples)] - ) - else: - raise QiskitMachineLearningError( - "The accepted estimators are BaseSamplerV1 (deprecated) and BaseSamplerV2; " - + f"got {type(self.sampler)} instead." - ) + job = self.sampler.run([(self._circuit, parameter_values[:num_samples])]) try: results = job.result() except Exception as exc: diff --git a/qiskit_machine_learning/optimizers/qnspsa.py b/qiskit_machine_learning/optimizers/qnspsa.py index 9ffbd9edb..5ef019f24 100644 --- a/qiskit_machine_learning/optimizers/qnspsa.py +++ b/qiskit_machine_learning/optimizers/qnspsa.py @@ -19,11 +19,10 @@ import numpy as np from qiskit.circuit import QuantumCircuit +from qiskit.primitives import BaseSamplerV2 -from qiskit.primitives import BaseSampler from ..state_fidelities import ComputeUncompute - -from .spsa import SPSA, CALLBACK, TERMINATIONCHECKER, _batch_evaluate +from .spsa import CALLBACK, SPSA, TERMINATIONCHECKER, _batch_evaluate # the function to compute the fidelity FIDELITY = Callable[[np.ndarray, np.ndarray], float] @@ -51,7 +50,7 @@ class QNSPSA(SPSA): This component has some function that is normally random. If you want to reproduce behavior then you should set the random number generator seed in the algorithm_globals - (``qiskit_machine_learning.utils.algorithm_globals.random_seed = seed``). + (`` qiskit_machine_learning.utils.algorithm_globals.random_seed = seed``). Examples: @@ -233,7 +232,7 @@ def settings(self) -> dict[str, Any]: def get_fidelity( circuit: QuantumCircuit, *, - sampler: BaseSampler | None = None, + sampler: BaseSamplerV2 | None = None, ) -> Callable[[np.ndarray, np.ndarray], float]: r"""Get a function to compute the fidelity of ``circuit`` with itself. diff --git a/qiskit_machine_learning/optimizers/umda.py b/qiskit_machine_learning/optimizers/umda.py index 55af590cb..a2b7a8d69 100644 --- a/qiskit_machine_learning/optimizers/umda.py +++ b/qiskit_machine_learning/optimizers/umda.py @@ -1,6 +1,6 @@ # This code is part of a Qiskit project. # -# (C) Copyright IBM 2018, 2024. +# (C) Copyright IBM 2018, 2025. # # 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 @@ -74,7 +74,7 @@ class UMDA(Optimizer): from qiskit_machine_learning.optimizers import UMDA from qiskit_machine_learning import QAOA from qiskit.quantum_info import Pauli - from qiskit.primitives import Sampler + from qiskit.primitives import StatevectorSampler X = Pauli("X") I = Pauli("I") diff --git a/qiskit_machine_learning/state_fidelities/base_state_fidelity.py b/qiskit_machine_learning/state_fidelities/base_state_fidelity.py index 7355ce2b8..4b22114ad 100644 --- a/qiskit_machine_learning/state_fidelities/base_state_fidelity.py +++ b/qiskit_machine_learning/state_fidelities/base_state_fidelity.py @@ -1,6 +1,6 @@ # This code is part of a Qiskit project. # -# (C) Copyright IBM 2022, 2024. +# (C) Copyright IBM 2022, 2025. # # 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 @@ -14,14 +14,14 @@ """ from __future__ import annotations + from abc import ABC, abstractmethod from collections.abc import MutableMapping -from typing import cast, Sequence, List -import numpy as np +from typing import Sequence, cast +import numpy as np from qiskit import QuantumCircuit from qiskit.circuit import ParameterVector -from qiskit.primitives.utils import _circuit_key from ..algorithm_job import AlgorithmJob @@ -95,11 +95,11 @@ def _preprocess_values( # ensure 2d if len(values) > 0 and not isinstance(values[0], Sequence) or len(values) == 0: - values = [cast(List[float], values)] + values = [cast(list[float], values)] # we explicitly cast the type here because mypy appears to be unable to understand the # above few lines where we ensure that values are 2d - return cast(Sequence[List[float]], values) + return cast(Sequence[list[float]], values) def _check_qubits_match(self, circuit_1: QuantumCircuit, circuit_2: QuantumCircuit) -> None: """ @@ -170,8 +170,8 @@ def _construct_circuits( circuits = [] for circuit_1, circuit_2 in zip(circuits_1, circuits_2): - # Use the same key for circuits as qiskit.primitives use. - circuit = self._circuit_cache.get((_circuit_key(circuit_1), _circuit_key(circuit_2))) + # Use the same key for circuits as qiskit.primitives use in 2.0+ + circuit = self._circuit_cache.get((hash(circuit_1), hash(circuit_2))) if circuit is not None: circuits.append(circuit) @@ -190,7 +190,7 @@ def _construct_circuits( ) circuits.append(circuit) # update cache - self._circuit_cache[_circuit_key(circuit_1), _circuit_key(circuit_2)] = circuit + self._circuit_cache[hash(circuit_1), hash(circuit_2)] = circuit return circuits diff --git a/qiskit_machine_learning/state_fidelities/compute_uncompute.py b/qiskit_machine_learning/state_fidelities/compute_uncompute.py index 5cd9ebe81..5651a5140 100644 --- a/qiskit_machine_learning/state_fidelities/compute_uncompute.py +++ b/qiskit_machine_learning/state_fidelities/compute_uncompute.py @@ -1,6 +1,6 @@ # This code is part of a Qiskit project. # -# (C) Copyright IBM 2022, 2024. +# (C) Copyright IBM 2022, 2025. # # 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 @@ -14,22 +14,21 @@ """ from __future__ import annotations + from collections.abc import Sequence from copy import copy from qiskit import QuantumCircuit -from qiskit.primitives import BaseSampler, BaseSamplerV1, SamplerResult -from qiskit.primitives.base import BaseSamplerV2 -from qiskit.transpiler.passmanager import PassManager -from qiskit.result import QuasiDistribution +from qiskit.primitives import BaseSamplerV2, PrimitiveResult, SamplerPubResult from qiskit.primitives.primitive_job import PrimitiveJob from qiskit.providers import Options +from qiskit.result import QuasiDistribution +from qiskit.transpiler.passmanager import PassManager -from ..exceptions import AlgorithmError, QiskitMachineLearningError -from ..utils.deprecation import issue_deprecation_msg +from ..algorithm_job import AlgorithmJob +from ..exceptions import AlgorithmError from .base_state_fidelity import BaseStateFidelity from .state_fidelity_result import StateFidelityResult -from ..algorithm_job import AlgorithmJob class ComputeUncompute(BaseStateFidelity): @@ -57,7 +56,7 @@ class ComputeUncompute(BaseStateFidelity): def __init__( self, - sampler: BaseSampler | BaseSamplerV2, + sampler: BaseSamplerV2, *, options: Options | None = None, local: bool = False, @@ -84,22 +83,10 @@ def __init__( pass_manager: The pass manager to transpile the circuits, if necessary. Defaults to ``None``, as some primitives do not need transpiled circuits. Raises: - ValueError: If the sampler is not an instance of ``BaseSampler``. + ValueError: If the sampler is not an instance of ``BaseSamplerV2``. """ - if (not isinstance(sampler, BaseSampler)) and (not isinstance(sampler, BaseSamplerV2)): - raise ValueError( - f"The sampler should be an instance of BaseSampler or BaseSamplerV2, " - f"but got {type(sampler)}" - ) - if isinstance(sampler, BaseSamplerV1): - issue_deprecation_msg( - msg="V1 Primitives are deprecated", - version="0.8.0", - remedy="Use V2 primitives for continued compatibility and support.", - period="4 months", - ) - self._sampler: BaseSampler = sampler + self._sampler: BaseSamplerV2 = sampler self._pass_manager = pass_manager self._local = local self._default_options = Options() @@ -162,7 +149,7 @@ def _run( ValueError: At least one pair of circuits must be defined. AlgorithmError: If the sampler job is not completed successfully. QiskitMachineLearningError: If the sampler is not an instance - of ``BaseSamplerV1`` or ``BaseSamplerV2``. + of ``BaseSamplerV2``. """ circuits = self._construct_circuits(circuits_1, circuits_2) if len(circuits) == 0: @@ -177,33 +164,19 @@ def _run( opts = copy(self._default_options) opts.update_options(**options) - if isinstance(self._sampler, BaseSamplerV1): - sampler_job = self._sampler.run( - circuits=circuits, parameter_values=values, **opts.__dict__ - ) - _len_quasi_dist = circuits[0].num_qubits - local_opts = self._get_local_options(opts.__dict__) - elif isinstance(self._sampler, BaseSamplerV2): - sampler_job = self._sampler.run( - [(circuits[i], values[i]) for i in range(len(circuits))], **opts.__dict__ - ) - if hasattr(circuits[0].layout, "_input_qubit_count"): - _len_quasi_dist = circuits[0].layout._input_qubit_count - else: - _len_quasi_dist = circuits[0].num_qubits - local_opts = opts.__dict__ + sampler_job = self._sampler.run( + [(circuits[i], values[i]) for i in range(len(circuits))], **opts.__dict__ + ) + if hasattr(circuits[0].layout, "_input_qubit_count"): + _len_quasi_dist = circuits[0].layout._input_qubit_count else: - raise QiskitMachineLearningError( - "The accepted estimators are BaseSamplerV1 (deprecated) and BaseSamplerV2; got" - + f" {type(self._sampler)} instead." - ) + _len_quasi_dist = circuits[0].num_qubits + local_opts = opts.__dict__ return AlgorithmJob( ComputeUncompute._call, sampler_job, - circuits, self._local, local_opts, - self._sampler, self._post_process_v2, _len_quasi_dist, ) @@ -211,10 +184,8 @@ def _run( @staticmethod def _call( job: PrimitiveJob, - circuits: Sequence[QuantumCircuit], local: bool, local_opts: Options = None, - _sampler=None, _post_process_v2=None, num_virtual_qubits=None, ) -> StateFidelityResult: @@ -223,22 +194,12 @@ def _call( except Exception as exc: raise AlgorithmError("Sampler job failed!") from exc - if isinstance(_sampler, BaseSamplerV1): - quasi_dists = result.quasi_dists - elif isinstance(_sampler, BaseSamplerV2): - quasi_dists = _post_process_v2(result, num_virtual_qubits) + quasi_dists = _post_process_v2(result, num_virtual_qubits) if local: raw_fidelities = [ - ComputeUncompute._get_local_fidelity( - prob_dist, - ( - num_virtual_qubits - if isinstance(_sampler, BaseSamplerV2) - else circuit.num_qubits - ), - ) - for prob_dist, circuit in zip(quasi_dists, circuits) + ComputeUncompute._get_local_fidelity(prob_dist, num_virtual_qubits) + for prob_dist in quasi_dists ] else: raw_fidelities = [ @@ -289,10 +250,10 @@ def _get_local_options(self, options: Options) -> Options: opts.update_options(**options) return opts - def _post_process_v2(self, result: SamplerResult, num_virtual_qubits: int): + def _post_process_v2(self, results: PrimitiveResult[SamplerPubResult], num_virtual_qubits: int): quasis = [] - for i in range(len(result)): - bitstring_counts = result[i].data.meas.get_counts() + for result in results: + bitstring_counts = result.join_data().get_counts() # Normalize the counts to probabilities total_shots = sum(bitstring_counts.values()) diff --git a/qiskit_machine_learning/utils/adjust_num_qubits.py b/qiskit_machine_learning/utils/adjust_num_qubits.py index 614ff98e1..cb98bdcf2 100644 --- a/qiskit_machine_learning/utils/adjust_num_qubits.py +++ b/qiskit_machine_learning/utils/adjust_num_qubits.py @@ -12,10 +12,8 @@ """Helper functions to adjust number of qubits.""" from __future__ import annotations -from typing import Tuple - +import warnings from qiskit import QuantumCircuit -from qiskit.circuit.library import RealAmplitudes, ZFeatureMap, ZZFeatureMap from qiskit.circuit.library import real_amplitudes, z_feature_map, zz_feature_map from ..exceptions import QiskitMachineLearningError @@ -27,24 +25,23 @@ def derive_num_qubits_feature_map_ansatz( num_qubits: int | None = None, feature_map: QuantumCircuit | None = None, ansatz: QuantumCircuit | None = None, - use_methods: bool = False, -) -> Tuple[int, QuantumCircuit, QuantumCircuit]: + use_methods: bool = True, +) -> tuple[int, QuantumCircuit, QuantumCircuit]: """ Derives a correct number of qubits, feature map, and ansatz from the parameters. - With `use_methods` set False (default): - If the number of qubits is not ``None``, then the feature map and ansatz are adjusted to this number of qubits if required. If such an adjustment fails, an error is raised. Also, if the - feature map or ansatz or both are ``None``, then :class:`~qiskit.circuit.library.ZZFeatureMap` - and :class:`~qiskit.circuit.library.RealAmplitudes` are created respectively. If there's just - one qubit, :class:`~qiskit.circuit.library.ZFeatureMap` is created instead. + feature map or ansatz or both are ``None``, then :func:`~qiskit.circuit.library.zz_feature_map` + and :func:`~qiskit.circuit.library.real_amplitudes` are created respectively. If there's just + one qubit, :func:`~qiskit.circuit.library.z_feature_map` is created instead. If the number of qubits is ``None``, then the number of qubits is derived from the feature map or ansatz. Both the feature map and ansatz in this case must have the same number of qubits. If the number of qubits of the feature map is not the same as the number of qubits of the ansatz, an error is raised. If only one of the feature map and ansatz are ``None``, then - :class:`~qiskit.circuit.library.ZZFeatureMap` or :class:`~qiskit.circuit.library.RealAmplitudes` + + :func:`~qiskit.circuit.library.zz_feature_map` or :func:`~qiskit.circuit.library.real_amplitudes` are created respectively. With `use_methods` set True: @@ -68,9 +65,8 @@ def derive_num_qubits_feature_map_ansatz( num_qubits: Number of qubits. feature_map: A feature map. ansatz: An ansatz. - use_methods: True (default) use deprecated BlueprintBased circuits such - as ZZFeatureMap, ZFeatureMap and RealAmplitudes. When False uses the - method "replacements" that provide back immutable circuits. + use_methods: weather to use the method implementation of circuits (Qiskit >=2) or the class + implementation (deprecated in Qiskit 2 and will be removed in Qiskit 3). Returns: A tuple of number of qubits, feature map, and ansatz. All are not none. @@ -91,62 +87,90 @@ def derive_num_qubits_feature_map_ansatz( "have been deprecated.", period="4 months", ) + candidates = {} - # check num_qubits, feature_map, and ansatz - if num_qubits in (0, None) and feature_map is None and ansatz is None: + if feature_map is not None: + candidates["feature_map"] = feature_map.num_qubits + if ansatz is not None: + candidates["ansatz"] = ansatz.num_qubits + if num_qubits is not None and feature_map is None and ansatz is None: + candidates["num_qubits"] = num_qubits + + if not candidates: raise QiskitMachineLearningError( - "Need at least one of number of qubits, feature map, or ansatz!" + "Unable to determine number of qubits: " + "provide `num_qubits` (int), `feature_map` (QuantumCircuit), " + "or `ansatz` (QuantumCircuit)." + ) + + # Check consensus on num_qubits + unique_vals = set(candidates.values()) + if len(unique_vals) > 1: + conflicts = ", ".join(f"{k}={v}" for k, v in candidates.items()) + warnings.warn( + ( + f"Inconsistent qubit numbers detected: {conflicts}. " + "Ensure all inputs agree on the number of qubits." + ), + UserWarning, ) - if num_qubits not in (0, None): - if feature_map is not None: - if feature_map.num_qubits != num_qubits: - _adjust_num_qubits(feature_map, "feature map", num_qubits) - else: - if use_methods: - feature_map = ( - z_feature_map(num_qubits) if num_qubits == 1 else zz_feature_map(num_qubits) - ) - else: - feature_map = ( - ZFeatureMap(num_qubits) if num_qubits == 1 else ZZFeatureMap(num_qubits) - ) - if ansatz is not None: - if ansatz.num_qubits != num_qubits: - _adjust_num_qubits(ansatz, "ansatz", num_qubits) - else: - if use_methods: - ansatz = real_amplitudes(num_qubits) - else: - ansatz = RealAmplitudes(num_qubits) + # Final resolved number of qubits + resolved_num_qubits = unique_vals.pop() + + def default_feature_map(n: int) -> QuantumCircuit: + return z_feature_map(n) if n == 1 else zz_feature_map(n) + + def default_ansatz(n: int) -> QuantumCircuit: + return real_amplitudes(n) + + if feature_map is None: + feature_map = default_feature_map(resolved_num_qubits) + candidates["feature_map"] = feature_map.num_qubits else: - if feature_map is not None and ansatz is not None: - if feature_map.num_qubits != ansatz.num_qubits: - raise QiskitMachineLearningError( - f"Mismatching number of qubits in the feature map ({feature_map.num_qubits}) " - f"and the ansatz ({ansatz.num_qubits})!" - ) - num_qubits = feature_map.num_qubits - elif feature_map is not None: - num_qubits = feature_map.num_qubits - if use_methods: - ansatz = real_amplitudes(num_qubits) - else: - ansatz = RealAmplitudes(num_qubits) - else: - num_qubits = ansatz.num_qubits - if use_methods: - feature_map = ( - z_feature_map(num_qubits) if num_qubits == 1 else zz_feature_map(num_qubits) - ) - else: - feature_map = ( - ZFeatureMap(num_qubits) if num_qubits == 1 else ZZFeatureMap(num_qubits) - ) - - return num_qubits, feature_map, ansatz + feature_map = _pad_if_needed(feature_map, resolved_num_qubits) + if ansatz is None: + ansatz = default_ansatz(resolved_num_qubits) + candidates["ansatz"] = ansatz.num_qubits + else: + ansatz = _pad_if_needed(ansatz, resolved_num_qubits) + # Mismatch in the circuits' num_qubits is unacceptable + if candidates["feature_map"] != candidates["ansatz"]: + raise QiskitMachineLearningError( + f"Inconsistent qubit numbers detected between the feature map ({candidates['feature_map']}) " + f"and the ansatz ({candidates['ansatz']}). These must match at all times." + ) + + return resolved_num_qubits, feature_map, ansatz + + +def _pad_if_needed(circ: QuantumCircuit, requested_num_qubits: int) -> QuantumCircuit | None: + circ_nq = circ.num_qubits + + if requested_num_qubits == circ_nq: + return circ + + if requested_num_qubits < circ_nq: + raise QiskitMachineLearningError( + f"Requesting num_qubits={requested_num_qubits} to a circuit with {circ_nq} qubits. " + f"Circuit cutting is not supported by default. Please, remove qubit registers manually." + ) + + warnings.warn( + ( + f"Requesting num_qubits={requested_num_qubits} to a circuit with {circ_nq} qubits. " + f"Padding with {requested_num_qubits - circ_nq} idle qubits." + ), + UserWarning, + ) + padded = QuantumCircuit(requested_num_qubits, circ.num_clbits, name=circ.name) + padded.compose(circ, inplace=True) + return padded + + +# pylint: disable=unused-argument def _adjust_num_qubits(circuit: QuantumCircuit, circuit_name: str, num_qubits: int) -> None: """ Tries to adjust the number of qubits of the circuit by trying to set ``num_qubits`` properties. @@ -160,11 +184,9 @@ def _adjust_num_qubits(circuit: QuantumCircuit, circuit_name: str, num_qubits: i QiskitMachineLearningError: if number of qubits can't be adjusted. """ - try: - circuit.num_qubits = num_qubits - except AttributeError as ex: - raise QiskitMachineLearningError( - f"The number of qubits {circuit.num_qubits} of the {circuit_name} does not match " - f"the number of qubits {num_qubits}, and the {circuit_name} does not allow setting " - "the number of qubits using `num_qubits`." - ) from ex + issue_deprecation_msg( + msg="No longer in use", + version="0.9.0", + remedy="Check ", + period="0 months", + ) diff --git a/qiskit_machine_learning/utils/validate_initial_point.py b/qiskit_machine_learning/utils/validate_initial_point.py index ad7f8ba5b..e4bf26a15 100644 --- a/qiskit_machine_learning/utils/validate_initial_point.py +++ b/qiskit_machine_learning/utils/validate_initial_point.py @@ -1,6 +1,6 @@ # This code is part of a Qiskit project. # -# (C) Copyright IBM 2022, 2024. +# (C) Copyright IBM 2022, 2025. # # 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 @@ -15,8 +15,8 @@ from __future__ import annotations import numpy as np - from qiskit.circuit import QuantumCircuit + from .algorithm_globals import algorithm_globals @@ -42,6 +42,9 @@ def validate_initial_point(point: np.ndarray | None | None, circuit: QuantumCirc if point is None: # get bounds if circuit has them set, otherwise use [-2pi, 2pi] for each parameter + # This attribute "parameter_bounds" is available in classes derived from NLocal + # class like RealAmplitudes, EfficientSU2, etc. This class is deprecated in Qiskit 2.1 + # and will be removed in Qiskit 3, so it would be better to removed after that version. bounds = getattr(circuit, "parameter_bounds", None) if bounds is None: bounds = [(-2 * np.pi, 2 * np.pi)] * expected_size diff --git a/requirements-dev.txt b/requirements-dev.txt index 6414e1a2b..9daebcb83 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -12,10 +12,10 @@ sphinx-design>=0.4.0 sphinxcontrib-spelling jupyter-sphinx discover -qiskit-aer>=0.11.2 +qiskit-aer>=0.17.1 mypy>=0.981 mypy-extensions>=0.4.3 nbsphinx qiskit_sphinx_theme~=1.16.0 -qiskit-ibm-runtime>=0.21 +qiskit-ibm-runtime>=0.40.0 scikit-learn!=1.7.1 diff --git a/requirements.txt b/requirements.txt index 811a007a0..580f988c2 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,4 +1,4 @@ -qiskit>=1.0,<2.0 +qiskit>=2.0 numpy>=2.0 scipy>=1.4 scikit-learn>=1.2 diff --git a/test/algorithms/classifiers/test_vqc.py b/test/algorithms/classifiers/test_vqc.py index d93ccb518..ee36a42a4 100644 --- a/test/algorithms/classifiers/test_vqc.py +++ b/test/algorithms/classifiers/test_vqc.py @@ -14,28 +14,26 @@ from __future__ import annotations -from dataclasses import dataclass - -from test import QiskitMachineLearningTestCase import functools import itertools import unittest +from dataclasses import dataclass +from test import QiskitMachineLearningTestCase -from ddt import ddt, idata, unpack import numpy as np import scipy +from ddt import ddt, idata, unpack from sklearn.datasets import make_classification from sklearn.preprocessing import MinMaxScaler, OneHotEncoder - -from qiskit.circuit.library import RealAmplitudes, ZFeatureMap -from qiskit.circuit.library import real_amplitudes, zz_feature_map, z_feature_map +from qiskit.circuit.library import real_amplitudes, z_feature_map, zz_feature_map from qiskit.providers.fake_provider import GenericBackendV2 from qiskit.transpiler.preset_passmanagers import generate_preset_pass_manager -from qiskit_ibm_runtime import Session, SamplerV2 -from qiskit_machine_learning.optimizers import COBYLA -from qiskit_machine_learning.utils import algorithm_globals +from qiskit_ibm_runtime import SamplerV2, Session from qiskit_machine_learning.algorithms import VQC from qiskit_machine_learning.exceptions import QiskitMachineLearningError +from qiskit_machine_learning.optimizers import COBYLA +from qiskit_machine_learning.utils import algorithm_globals + NUM_QUBITS_LIST = [2, None] FEATURE_MAPS = ["zz_feature_map", None] @@ -43,7 +41,7 @@ OPTIMIZERS = ["cobyla", None] DATASETS = ["binary", "multiclass", "no_one_hot"] LOSSES = ["squared_error", "absolute_error", "cross_entropy"] -SAMPLERS = ["samplerv1"] +SAMPLERS = ["samplerv2"] @dataclass(frozen=True) @@ -78,8 +76,6 @@ def setUp(self): self.num_classes_by_batch = [] self.backend = GenericBackendV2( num_qubits=3, - calibrate_instructions=None, - pulse_channels=False, noise_info=False, seed=123, ) @@ -92,7 +88,6 @@ def setUp(self): "binary": _create_dataset(6, 2), "multiclass": _create_dataset(10, 3), "no_one_hot": _create_dataset(6, 2, one_hot=False), - "samplerv1": None, "samplerv2": SamplerV2(mode=self.session), } @@ -117,10 +112,7 @@ def test_VQC(self, num_qubits, f_m, ans, opt, d_s, smplr): dataset = self.properties.get(d_s) sampler = self.properties.get(smplr) - if smplr == "samplerv2": - pm = generate_preset_pass_manager(optimization_level=0, backend=self.backend) - else: - pm = None + pm = generate_preset_pass_manager(optimization_level=0, backend=self.backend) unique_labels = np.unique(dataset.y, axis=0) # we want to have labels as a column array, either 1D or 2D(one hot) @@ -332,8 +324,8 @@ def test_circuit_extensions(self): num_qubits = 2 classifier = VQC( num_qubits=num_qubits, - feature_map=ZFeatureMap(1), - ansatz=RealAmplitudes(1), + feature_map=z_feature_map(1), + ansatz=real_amplitudes(1), ) self.assertEqual(classifier.feature_map.num_qubits, num_qubits) self.assertEqual(classifier.ansatz.num_qubits, num_qubits) diff --git a/test/algorithms/inference/test_qbayesian.py b/test/algorithms/inference/test_qbayesian.py index 5c874c832..6edad8941 100644 --- a/test/algorithms/inference/test_qbayesian.py +++ b/test/algorithms/inference/test_qbayesian.py @@ -16,18 +16,15 @@ from test import QiskitMachineLearningTestCase import numpy as np - from qiskit import QuantumCircuit from qiskit.circuit import QuantumRegister -from qiskit.primitives import Sampler +from qiskit.primitives import StatevectorSampler from qiskit.providers.fake_provider import GenericBackendV2 from qiskit.transpiler.preset_passmanagers import generate_preset_pass_manager - -from qiskit_ibm_runtime import Session, SamplerV2 +from qiskit_ibm_runtime import SamplerV2, Session from qiskit_ibm_runtime.options import SamplerOptions, SimulatorOptions - -from qiskit_machine_learning.utils import algorithm_globals from qiskit_machine_learning.algorithms import QBayesian +from qiskit_machine_learning.utils import algorithm_globals class TestQBayesianInference(QiskitMachineLearningTestCase): @@ -174,7 +171,7 @@ def test_parameter(self): self.assertTrue(self.qbayesian.converged) self.assertTrue(self.qbayesian.limit == 1) # Test sampler - sampler = Sampler() + sampler = StatevectorSampler() # change: Sampler is migrated to StatevectorSampler self.qbayesian.sampler = sampler self.qbayesian.inference(query={"B": 1}, evidence={"A": 0, "C": 0}) self.assertTrue(self.qbayesian.sampler == sampler) @@ -217,8 +214,6 @@ def test_trivial_circuit_V2(self): backend = GenericBackendV2( num_qubits=2, - calibrate_instructions=None, - pulse_channels=False, noise_info=False, seed=123, ) diff --git a/test/algorithms/regressors/test_fidelity_quantum_kernel_qsvr.py b/test/algorithms/regressors/test_fidelity_quantum_kernel_qsvr.py index f9fbde184..4b57f6167 100644 --- a/test/algorithms/regressors/test_fidelity_quantum_kernel_qsvr.py +++ b/test/algorithms/regressors/test_fidelity_quantum_kernel_qsvr.py @@ -1,156 +1,153 @@ -# This code is part of a Qiskit project. -# -# (C) Copyright IBM 2021, 2025. -# -# 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. -"""Test QSVR on fidelity quantum kernel.""" - -import os -import tempfile -import unittest - -from test import QiskitMachineLearningTestCase - -import numpy as np -from sklearn.metrics import mean_squared_error - -from qiskit.primitives import Sampler -from qiskit.circuit.library import zz_feature_map - -from qiskit_machine_learning.utils import algorithm_globals -from qiskit_machine_learning.algorithms import QSVR, SerializableModelMixin -from qiskit_machine_learning.exceptions import QiskitMachineLearningWarning -from qiskit_machine_learning.kernels import FidelityQuantumKernel - - -class TestQSVR(QiskitMachineLearningTestCase): - """Test QSVR Algorithm on fidelity quantum kernel.""" - - def setUp(self): - super().setUp() - - algorithm_globals.random_seed = 10598 - - self.sampler = Sampler() - self.feature_map = zz_feature_map(feature_dimension=2, reps=2) - - self.sample_train = np.asarray( - [ - [-0.36572221, 0.90579879], - [-0.41816432, 0.03011426], - [-0.48806982, 0.87208714], - [-0.67078436, -0.91017876], - [-0.12980588, 0.98475113], - [0.78335453, 0.49721604], - [0.78158498, 0.78689328], - [0.03771672, -0.3681419], - [0.54402486, 0.32332253], - [-0.25268454, -0.81106666], - ] - ) - self.label_train = np.asarray( - [ - 0.07045477, - 0.80047778, - 0.04493319, - -0.30427998, - -0.02430856, - 0.17224315, - -0.26474769, - 0.83097582, - 0.60943777, - 0.31577759, - ] - ) - - self.sample_test = np.asarray( - [ - [-0.60713067, -0.37935265], - [0.55480968, 0.94365285], - [0.00148237, -0.71220499], - [-0.97212742, -0.54068794], - ] - ) - self.label_test = np.asarray([0.45066614, -0.18052862, 0.4549451, -0.23674218]) - - def test_qsvr(self): - """Test QSVR""" - qkernel = FidelityQuantumKernel(feature_map=self.feature_map) - qsvr = QSVR(quantum_kernel=qkernel) - qsvr.fit(self.sample_train, self.label_train) - - predictions = qsvr.predict(self.sample_test) - mse = mean_squared_error(self.label_test, predictions) - self.assertAlmostEqual(mse, 0.04964456790383482, places=4) - - def test_change_kernel(self): - """Test QSVR with QuantumKernel later""" - qkernel = FidelityQuantumKernel(feature_map=self.feature_map) - - qsvr = QSVR() - qsvr.quantum_kernel = qkernel - qsvr.fit(self.sample_train, self.label_train) - - predictions = qsvr.predict(self.sample_test) - mse = mean_squared_error(self.label_test, predictions) - self.assertAlmostEqual(mse, 0.04964456790383482, places=4) - - def test_qsvr_parameters(self): - """Test QSVR with extra constructor parameters""" - qkernel = FidelityQuantumKernel(feature_map=self.feature_map) - - qsvr = QSVR(quantum_kernel=qkernel, tol=1e-3, C=1.0) - qsvr.fit(self.sample_train, self.label_train) - - predictions = qsvr.predict(self.sample_test) - mse = mean_squared_error(self.label_test, predictions) - self.assertAlmostEqual(mse, 0.04964456790383482, places=4) - - def test_qsvc_to_string(self): - """Test QSVR print works when no *args passed in""" - qsvr = QSVR() - _ = str(qsvr) - - def test_with_kernel_parameter(self): - """Test QSVC with the `kernel` argument.""" - with self.assertWarns(QiskitMachineLearningWarning): - QSVR(kernel=1) - - def test_save_load(self): - """Tests save and load models.""" - features = np.array([[0, 0], [0.1, 0.1], [0.4, 0.4], [1, 1]]) - labels = np.array([0, 0.1, 0.4, 1]) - - quantum_kernel = FidelityQuantumKernel(feature_map=zz_feature_map(2)) - regressor = QSVR(quantum_kernel=quantum_kernel) - regressor.fit(features, labels) - - test_features = np.array([[0.5, 0.5]]) - original_predicts = regressor.predict(test_features) - - with tempfile.TemporaryDirectory() as dir_name: - file_name = os.path.join(dir_name, "qsvr.model") - regressor.to_dill(file_name) - - regressor_load = QSVR.from_dill(file_name) - loaded_model_predicts = regressor_load.predict(test_features) - - np.testing.assert_array_almost_equal(original_predicts, loaded_model_predicts) - - class FakeModel(SerializableModelMixin): - """Fake model class for test purposes.""" - - pass - - with self.assertRaises(TypeError): - FakeModel.from_dill(file_name) - - -if __name__ == "__main__": - unittest.main() +# This code is part of a Qiskit project. +# +# (C) Copyright IBM 2021, 2025. +# +# 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. +"""Test QSVR on fidelity quantum kernel.""" + +import os +import tempfile +import unittest +from test import QiskitMachineLearningTestCase + +import numpy as np +from sklearn.metrics import mean_squared_error +from qiskit.circuit.library import zz_feature_map +from qiskit.primitives import StatevectorEstimator +from qiskit_machine_learning.algorithms import QSVR, SerializableModelMixin +from qiskit_machine_learning.exceptions import QiskitMachineLearningWarning +from qiskit_machine_learning.kernels import FidelityQuantumKernel +from qiskit_machine_learning.utils import algorithm_globals + + +class TestQSVR(QiskitMachineLearningTestCase): + """Test QSVR Algorithm on fidelity quantum kernel.""" + + def setUp(self): + super().setUp() + + algorithm_globals.random_seed = 10598 + + self.sampler = StatevectorEstimator() + self.feature_map = zz_feature_map(feature_dimension=2, reps=2) + + self.sample_train = np.asarray( + [ + [-0.36572221, 0.90579879], + [-0.41816432, 0.03011426], + [-0.48806982, 0.87208714], + [-0.67078436, -0.91017876], + [-0.12980588, 0.98475113], + [0.78335453, 0.49721604], + [0.78158498, 0.78689328], + [0.03771672, -0.3681419], + [0.54402486, 0.32332253], + [-0.25268454, -0.81106666], + ] + ) + self.label_train = np.asarray( + [ + 0.07045477, + 0.80047778, + 0.04493319, + -0.30427998, + -0.02430856, + 0.17224315, + -0.26474769, + 0.83097582, + 0.60943777, + 0.31577759, + ] + ) + + self.sample_test = np.asarray( + [ + [-0.60713067, -0.37935265], + [0.55480968, 0.94365285], + [0.00148237, -0.71220499], + [-0.97212742, -0.54068794], + ] + ) + self.label_test = np.asarray([0.45066614, -0.18052862, 0.4549451, -0.23674218]) + + def test_qsvr(self): + """Test QSVR""" + qkernel = FidelityQuantumKernel(feature_map=self.feature_map) + qsvr = QSVR(quantum_kernel=qkernel) + qsvr.fit(self.sample_train, self.label_train) + + predictions = qsvr.predict(self.sample_test) + mse = mean_squared_error(self.label_test, predictions) + self.assertAlmostEqual(mse, 0.04964456790383482, places=4) + + def test_change_kernel(self): + """Test QSVR with QuantumKernel later""" + qkernel = FidelityQuantumKernel(feature_map=self.feature_map) + + qsvr = QSVR() + qsvr.quantum_kernel = qkernel + qsvr.fit(self.sample_train, self.label_train) + + predictions = qsvr.predict(self.sample_test) + mse = mean_squared_error(self.label_test, predictions) + self.assertAlmostEqual(mse, 0.04964456790383482, places=4) + + def test_qsvr_parameters(self): + """Test QSVR with extra constructor parameters""" + qkernel = FidelityQuantumKernel(feature_map=self.feature_map) + + qsvr = QSVR(quantum_kernel=qkernel, tol=1e-3, C=1.0) + qsvr.fit(self.sample_train, self.label_train) + + predictions = qsvr.predict(self.sample_test) + mse = mean_squared_error(self.label_test, predictions) + self.assertAlmostEqual(mse, 0.04964456790383482, places=4) + + def test_qsvc_to_string(self): + """Test QSVR print works when no *args passed in""" + qsvr = QSVR() + _ = str(qsvr) + + def test_with_kernel_parameter(self): + """Test QSVC with the `kernel` argument.""" + with self.assertWarns(QiskitMachineLearningWarning): + QSVR(kernel=1) + + def test_save_load(self): + """Tests save and load models.""" + features = np.array([[0, 0], [0.1, 0.1], [0.4, 0.4], [1, 1]]) + labels = np.array([0, 0.1, 0.4, 1]) + + quantum_kernel = FidelityQuantumKernel(feature_map=zz_feature_map(2)) + regressor = QSVR(quantum_kernel=quantum_kernel) + regressor.fit(features, labels) + + test_features = np.array([[0.5, 0.5]]) + original_predicts = regressor.predict(test_features) + + with tempfile.TemporaryDirectory() as dir_name: + file_name = os.path.join(dir_name, "qsvr.model") + regressor.to_dill(file_name) + + regressor_load = QSVR.from_dill(file_name) + loaded_model_predicts = regressor_load.predict(test_features) + + np.testing.assert_array_almost_equal(original_predicts, loaded_model_predicts) + + class FakeModel(SerializableModelMixin): + """Fake model class for test purposes.""" + + pass + + with self.assertRaises(TypeError): + FakeModel.from_dill(file_name) + + +if __name__ == "__main__": + unittest.main() diff --git a/test/algorithms/regressors/test_qsvr.py b/test/algorithms/regressors/test_qsvr.py index cc6a4d764..7037c70c8 100644 --- a/test/algorithms/regressors/test_qsvr.py +++ b/test/algorithms/regressors/test_qsvr.py @@ -1,151 +1,149 @@ -# This code is part of a Qiskit project. -# -# (C) Copyright IBM 2021, 2025. -# -# 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. - -"""Test QSVR""" -import os -import tempfile -import unittest - -from test import QiskitMachineLearningTestCase - -import numpy as np -from sklearn.metrics import mean_squared_error - -from qiskit.circuit.library import zz_feature_map -from qiskit_machine_learning.utils import algorithm_globals -from qiskit_machine_learning.algorithms import QSVR, SerializableModelMixin -from qiskit_machine_learning.exceptions import QiskitMachineLearningWarning -from qiskit_machine_learning.kernels import FidelityQuantumKernel - - -class TestQSVR(QiskitMachineLearningTestCase): - """Test QSVR Algorithm""" - - def setUp(self): - super().setUp() - - algorithm_globals.random_seed = 10598 - - self.feature_map = zz_feature_map(feature_dimension=2, reps=2) - - self.sample_train = np.asarray( - [ - [-0.36572221, 0.90579879], - [-0.41816432, 0.03011426], - [-0.48806982, 0.87208714], - [-0.67078436, -0.91017876], - [-0.12980588, 0.98475113], - [0.78335453, 0.49721604], - [0.78158498, 0.78689328], - [0.03771672, -0.3681419], - [0.54402486, 0.32332253], - [-0.25268454, -0.81106666], - ] - ) - self.label_train = np.asarray( - [ - 0.07045477, - 0.80047778, - 0.04493319, - -0.30427998, - -0.02430856, - 0.17224315, - -0.26474769, - 0.83097582, - 0.60943777, - 0.31577759, - ] - ) - - self.sample_test = np.asarray( - [ - [-0.60713067, -0.37935265], - [0.55480968, 0.94365285], - [0.00148237, -0.71220499], - [-0.97212742, -0.54068794], - ] - ) - self.label_test = np.asarray([0.45066614, -0.18052862, 0.4549451, -0.23674218]) - - def test_qsvr(self): - """Test QSVR""" - qkernel = FidelityQuantumKernel(feature_map=self.feature_map, enforce_psd=False) - - qsvr = QSVR(quantum_kernel=qkernel) - qsvr.fit(self.sample_train, self.label_train) - predictions = qsvr.predict(self.sample_test) - mse = mean_squared_error(self.label_test, predictions) - self.assertAlmostEqual(mse, 0.04964456790383482, places=4) - - def test_change_kernel(self): - """Test QSVR with QuantumKernel set later""" - qkernel = FidelityQuantumKernel(feature_map=self.feature_map, enforce_psd=False) - - qsvr = QSVR() - qsvr.quantum_kernel = qkernel - qsvr.fit(self.sample_train, self.label_train) - predictions = qsvr.predict(self.sample_test) - mse = mean_squared_error(self.label_test, predictions) - self.assertAlmostEqual(mse, 0.04964456790383482, places=4) - - def test_qsvr_parameters(self): - """Test QSVR with extra constructor parameters""" - qkernel = FidelityQuantumKernel(feature_map=self.feature_map) - - qsvr = QSVR(quantum_kernel=qkernel, tol=1e-3, C=1.0) - qsvr.fit(self.sample_train, self.label_train) - predictions = qsvr.predict(self.sample_test) - mse = mean_squared_error(self.label_test, predictions) - self.assertAlmostEqual(mse, 0.04964456790383482, places=4) - - def test_qsvr_to_string(self): - """Test QSVR string representation""" - qsvr = QSVR() - _ = str(qsvr) - - def test_with_kernel_parameter(self): - """Test QSVR with the `kernel` argument""" - with self.assertWarns(QiskitMachineLearningWarning): - QSVR(kernel=1) - - def test_save_load(self): - """Tests save and load functionality""" - features = np.array([[0, 0], [0.1, 0.1], [0.4, 0.4], [1, 1]]) - labels = np.array([0, 0.1, 0.4, 1]) - - quantum_kernel = FidelityQuantumKernel(feature_map=zz_feature_map(2)) - regressor = QSVR(quantum_kernel=quantum_kernel) - regressor.fit(features, labels) - - test_features = np.array([[0.5, 0.5]]) - original_predicts = regressor.predict(test_features) - - with tempfile.TemporaryDirectory() as dir_name: - file_name = os.path.join(dir_name, "qsvr.model") - regressor.to_dill(file_name) - - regressor_load = QSVR.from_dill(file_name) - loaded_model_predicts = regressor_load.predict(test_features) - - np.testing.assert_array_almost_equal(original_predicts, loaded_model_predicts) - - class FakeModel(SerializableModelMixin): - """Fake model class for test purposes""" - - pass - - with self.assertRaises(TypeError): - FakeModel.from_dill(file_name) - - -if __name__ == "__main__": - unittest.main() +# This code is part of a Qiskit project. +# +# (C) Copyright IBM 2021, 2025. +# +# 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. + +"""Test QSVR""" +import os +import tempfile +import unittest +from test import QiskitMachineLearningTestCase + +import numpy as np +from sklearn.metrics import mean_squared_error +from qiskit.circuit.library import zz_feature_map +from qiskit_machine_learning.algorithms import QSVR, SerializableModelMixin +from qiskit_machine_learning.exceptions import QiskitMachineLearningWarning +from qiskit_machine_learning.kernels import FidelityQuantumKernel +from qiskit_machine_learning.utils import algorithm_globals + + +class TestQSVR(QiskitMachineLearningTestCase): + """Test QSVR Algorithm""" + + def setUp(self): + super().setUp() + + algorithm_globals.random_seed = 10598 + + self.feature_map = zz_feature_map(feature_dimension=2, reps=2) + + self.sample_train = np.asarray( + [ + [-0.36572221, 0.90579879], + [-0.41816432, 0.03011426], + [-0.48806982, 0.87208714], + [-0.67078436, -0.91017876], + [-0.12980588, 0.98475113], + [0.78335453, 0.49721604], + [0.78158498, 0.78689328], + [0.03771672, -0.3681419], + [0.54402486, 0.32332253], + [-0.25268454, -0.81106666], + ] + ) + self.label_train = np.asarray( + [ + 0.07045477, + 0.80047778, + 0.04493319, + -0.30427998, + -0.02430856, + 0.17224315, + -0.26474769, + 0.83097582, + 0.60943777, + 0.31577759, + ] + ) + + self.sample_test = np.asarray( + [ + [-0.60713067, -0.37935265], + [0.55480968, 0.94365285], + [0.00148237, -0.71220499], + [-0.97212742, -0.54068794], + ] + ) + self.label_test = np.asarray([0.45066614, -0.18052862, 0.4549451, -0.23674218]) + + def test_qsvr(self): + """Test QSVR""" + qkernel = FidelityQuantumKernel(feature_map=self.feature_map, enforce_psd=False) + + qsvr = QSVR(quantum_kernel=qkernel) + qsvr.fit(self.sample_train, self.label_train) + predictions = qsvr.predict(self.sample_test) + mse = mean_squared_error(self.label_test, predictions) + self.assertAlmostEqual(mse, 0.04964456790383482, places=4) + + def test_change_kernel(self): + """Test QSVR with QuantumKernel set later""" + qkernel = FidelityQuantumKernel(feature_map=self.feature_map, enforce_psd=False) + + qsvr = QSVR() + qsvr.quantum_kernel = qkernel + qsvr.fit(self.sample_train, self.label_train) + predictions = qsvr.predict(self.sample_test) + mse = mean_squared_error(self.label_test, predictions) + self.assertAlmostEqual(mse, 0.04964456790383482, places=4) + + def test_qsvr_parameters(self): + """Test QSVR with extra constructor parameters""" + qkernel = FidelityQuantumKernel(feature_map=self.feature_map) + + qsvr = QSVR(quantum_kernel=qkernel, tol=1e-3, C=1.0) + qsvr.fit(self.sample_train, self.label_train) + predictions = qsvr.predict(self.sample_test) + mse = mean_squared_error(self.label_test, predictions) + self.assertAlmostEqual(mse, 0.04964456790383482, places=4) + + def test_qsvr_to_string(self): + """Test QSVR string representation""" + qsvr = QSVR() + _ = str(qsvr) + + def test_with_kernel_parameter(self): + """Test QSVR with the `kernel` argument""" + with self.assertWarns(QiskitMachineLearningWarning): + QSVR(kernel=1) + + def test_save_load(self): + """Tests save and load functionality""" + features = np.array([[0, 0], [0.1, 0.1], [0.4, 0.4], [1, 1]]) + labels = np.array([0, 0.1, 0.4, 1]) + + quantum_kernel = FidelityQuantumKernel(feature_map=zz_feature_map(2)) + regressor = QSVR(quantum_kernel=quantum_kernel) + regressor.fit(features, labels) + + test_features = np.array([[0.5, 0.5]]) + original_predicts = regressor.predict(test_features) + + with tempfile.TemporaryDirectory() as dir_name: + file_name = os.path.join(dir_name, "qsvr.model") + regressor.to_dill(file_name) + + regressor_load = QSVR.from_dill(file_name) + loaded_model_predicts = regressor_load.predict(test_features) + + np.testing.assert_array_almost_equal(original_predicts, loaded_model_predicts) + + class FakeModel(SerializableModelMixin): + """Fake model class for test purposes""" + + pass + + with self.assertRaises(TypeError): + FakeModel.from_dill(file_name) + + +if __name__ == "__main__": + unittest.main() diff --git a/test/algorithms/regressors/test_vqr.py b/test/algorithms/regressors/test_vqr.py index 5c87d0bb3..d4f997622 100644 --- a/test/algorithms/regressors/test_vqr.py +++ b/test/algorithms/regressors/test_vqr.py @@ -17,16 +17,14 @@ import numpy as np from ddt import data, ddt from qiskit.circuit import Parameter, QuantumCircuit -from qiskit.primitives import Estimator +from qiskit.primitives import StatevectorEstimator from qiskit.providers.fake_provider import GenericBackendV2 from qiskit.transpiler.preset_passmanagers import generate_preset_pass_manager - -from qiskit_ibm_runtime import Session, EstimatorV2 +from qiskit_ibm_runtime import EstimatorV2, Session +from qiskit_machine_learning.algorithms import VQR from qiskit_machine_learning.optimizers import COBYLA, L_BFGS_B from qiskit_machine_learning.utils import algorithm_globals -from qiskit_machine_learning.algorithms import VQR - @ddt class TestVQR(QiskitMachineLearningTestCase): @@ -38,7 +36,9 @@ def setUp(self): # specify quantum instances algorithm_globals.random_seed = 12345 - self.estimator = Estimator() + self.estimator = ( + StatevectorEstimator() + ) # change: Estimator is migrated to StatevectorEstimator num_samples = 20 eps = 0.2 @@ -143,8 +143,6 @@ def test_vqr_v2(self, config): backend = GenericBackendV2( num_qubits=2, - calibrate_instructions=None, - pulse_channels=False, noise_info=False, seed=123, ) diff --git a/test/circuit/library/test_qnn_circuit.py b/test/circuit/library/test_qnn_circuit.py index 2f6b0abab..4e0837880 100644 --- a/test/circuit/library/test_qnn_circuit.py +++ b/test/circuit/library/test_qnn_circuit.py @@ -14,13 +14,17 @@ import unittest from test import QiskitMachineLearningTestCase + from qiskit.circuit import QuantumCircuit -from qiskit.circuit.library import ZFeatureMap, ZZFeatureMap, RealAmplitudes -from qiskit.circuit.library import PauliFeatureMap, EfficientSU2 -from qiskit.circuit.library import zz_feature_map, real_amplitudes -from qiskit.circuit.library import pauli_feature_map +from qiskit.circuit.library import ( + ZFeatureMap, + efficient_su2, + pauli_feature_map, + real_amplitudes, + z_feature_map, + zz_feature_map, +) from qiskit_machine_learning import QiskitMachineLearningError - from qiskit_machine_learning.circuit.library import QNNCircuit, qnn_circuit @@ -90,9 +94,13 @@ def test_construction_before_build(self): # The properties of the QNNCircuit are set when the class is instantiated. with self.subTest("check input configuration before circuit is build"): self.assertEqual(circuit.num_qubits, 2) - self.assertEqual(type(circuit.feature_map), ZZFeatureMap) + self.assertEqual( + type(circuit.feature_map), zz_feature_map + ) # change: ZZFeatureMap is replaced by zz_feature_map self.assertEqual(circuit.feature_map.num_qubits, 2) - self.assertEqual(type(circuit.ansatz), RealAmplitudes) + self.assertEqual( + type(circuit.ansatz), real_amplitudes + ) # change: RealAmplitudes is replaced by real_amplitudes self.assertEqual(circuit.ansatz.num_qubits, 2) self.assertEqual(circuit.num_input_parameters, 2) self.assertEqual(circuit.num_weight_parameters, 8) @@ -103,7 +111,7 @@ def test_construction_fails(self): # If no argument is passed a QiskitMachineLearningError is raised # when the class is attempted to be instantiated (before the circuit is built). with self.assertRaises(QiskitMachineLearningError): - QNNCircuit(feature_map=ZZFeatureMap(2), ansatz=RealAmplitudes(1)) + QNNCircuit(feature_map=zz_feature_map(2), ansatz=real_amplitudes(1)) # If no argument is passed a QiskitMachineLearningError is raised # when the class is attempted to be instantiated (before the circuit is built). @@ -122,7 +130,9 @@ def test_num_qubit_construction(self): self.assertEqual(circuit.num_qubits, 1) self.assertEqual(type(circuit.feature_map), ZFeatureMap) self.assertEqual(circuit.feature_map.num_qubits, 1) - self.assertEqual(type(circuit.ansatz), RealAmplitudes) + self.assertEqual( + type(circuit.ansatz), real_amplitudes + ) # change: RealAmplitudes is replaced by real_amplitudes self.assertEqual(circuit.ansatz.num_qubits, 1) self.assertEqual(circuit.num_input_parameters, 1) self.assertEqual(circuit.num_weight_parameters, 4) @@ -130,7 +140,7 @@ def test_num_qubit_construction(self): def test_feature_map_construction(self): """Test building the ``QNNCircuit`` with a feature map""" - feature_map = PauliFeatureMap(3) + feature_map = pauli_feature_map(3) circuit = QNNCircuit(feature_map=feature_map) circuit._build() @@ -138,7 +148,9 @@ def test_feature_map_construction(self): self.assertEqual(circuit.num_qubits, 3) with self.subTest("check feature map type"): - self.assertEqual(type(circuit.feature_map), PauliFeatureMap) + self.assertEqual( + type(circuit.feature_map), pauli_feature_map + ) # change: PauliFeatureMap is replaced by pauli_feature_map with self.subTest("check number of qubits for feature map"): self.assertEqual(circuit.feature_map.num_qubits, 3) @@ -147,12 +159,14 @@ def test_feature_map_construction(self): self.assertEqual(circuit.ansatz.num_qubits, 3) with self.subTest("check ansatz type"): - self.assertEqual(type(circuit.ansatz), RealAmplitudes) + self.assertEqual( + type(circuit.ansatz), real_amplitudes + ) # change: RealAmplitudes is replaced by real_amplitudes def test_construction_for_input_missmatch(self): """Test the construction of ``QNNCircuit`` for input that does not match.""" - circuit = QNNCircuit(num_qubits=4, feature_map=ZZFeatureMap(3), ansatz=RealAmplitudes(2)) + circuit = QNNCircuit(num_qubits=4, feature_map=zz_feature_map(3), ansatz=real_amplitudes(2)) # If the number of qubits is provided, it overrules the feature map # and ansatz settings. @@ -183,9 +197,11 @@ def test_num_qubit_setter(self): def test_ansatz_setter(self): """Test the properties after the ansatz is updated.""" # Instantiate QNNCircuit 2 qubits a PauliFeatureMap the default ansatz RealAmplitudes - circuit = QNNCircuit(2, feature_map=PauliFeatureMap(2)) + circuit = QNNCircuit( + 2, feature_map=pauli_feature_map(2) + ) # change: PauliFeatureMap is replaced by pauli_feature_map # Update the ansatz to a 3 qubit "EfficientSU2" - circuit.ansatz = EfficientSU2(3) + circuit.ansatz = efficient_su2(3) # change: EfficientSU2 is replaced by efficient_su2 with self.subTest("check number of qubits"): self.assertEqual(circuit.num_qubits, 3) @@ -194,8 +210,12 @@ def test_ansatz_setter(self): self.assertEqual(circuit.num_input_parameters, 3) self.assertEqual(circuit.num_weight_parameters, 24) with self.subTest("check updated ansatz"): - self.assertEqual(type(circuit.feature_map), PauliFeatureMap) - self.assertEqual(type(circuit.ansatz), EfficientSU2) + self.assertEqual( + type(circuit.feature_map), pauli_feature_map + ) # change: PauliFeatureMap is replaced by pauli_feature_map + self.assertEqual( + type(circuit.ansatz), efficient_su2 + ) # change: EfficientSU2 is replaced by efficient_su2 def test_feature_map_setter(self): """Test that the number of qubits cannot be updated by a new ansatz.""" @@ -204,7 +224,7 @@ def test_feature_map_setter(self): # RealAmplitudes circuit = QNNCircuit(3) # Update the feature_map to a 1 qubit "EfficientSU2" - circuit.feature_map = ZFeatureMap(1) + circuit.feature_map = z_feature_map(1) # change: ZFeatureMap is replaced by z_feature_map with self.subTest("check number of qubits"): self.assertEqual(circuit.num_qubits, 1) @@ -213,7 +233,9 @@ def test_feature_map_setter(self): self.assertEqual(circuit.num_input_parameters, 1) self.assertEqual(circuit.num_weight_parameters, 4) with self.subTest("check updated ansatz"): - self.assertEqual(type(circuit.feature_map), ZFeatureMap) + self.assertEqual( + type(circuit.feature_map), z_feature_map + ) # change: ZFeatureMap is replaced by z_feature_map def test_copy(self): """Test copy operation for ``QNNCircuit``.""" diff --git a/test/circuit/library/test_raw_feature_vector.py b/test/circuit/library/test_raw_feature_vector.py index de312a143..a221b2121 100644 --- a/test/circuit/library/test_raw_feature_vector.py +++ b/test/circuit/library/test_raw_feature_vector.py @@ -13,20 +13,18 @@ """Test the ``RawFeatureVector`` circuit.""" import unittest - from test import QiskitMachineLearningTestCase import numpy as np import qiskit from qiskit.circuit import QuantumCircuit -from qiskit.circuit.library import RealAmplitudes, real_amplitudes +from qiskit.circuit.library import real_amplitudes from qiskit.exceptions import QiskitError from qiskit.quantum_info import Statevector -from qiskit_machine_learning.optimizers import COBYLA -from qiskit_machine_learning.utils import algorithm_globals - from qiskit_machine_learning.algorithms import VQC from qiskit_machine_learning.circuit.library import RawFeatureVector, raw_feature_vector +from qiskit_machine_learning.optimizers import COBYLA +from qiskit_machine_learning.utils import algorithm_globals class TestRawFeatureVectorFunction(QiskitMachineLearningTestCase): @@ -210,7 +208,9 @@ def test_usage_in_vqc(self): y = np.array([y, 1 - y]).transpose() feature_map = RawFeatureVector(feature_dimension=num_inputs) - ansatz = RealAmplitudes(feature_map.num_qubits, reps=1) + ansatz = real_amplitudes( + feature_map.num_qubits, reps=1 + ) # change: RealAmplitudes is migrated to real_amplitudes vqc = VQC( feature_map=feature_map, diff --git a/test/connectors/test_torch_connector.py b/test/connectors/test_torch_connector.py index ec0707d6f..5e5f5cded 100644 --- a/test/connectors/test_torch_connector.py +++ b/test/connectors/test_torch_connector.py @@ -12,7 +12,7 @@ """Test Torch Connector.""" import itertools -from typing import cast, Union, List, Tuple, Any +from typing import cast, Union, Any from test.connectors.test_torch import TestTorch @@ -293,7 +293,7 @@ def __init__( @staticmethod def build_circuit( num_weights: int, num_input: int, num_qubits: int = 3 - ) -> Tuple[QuantumCircuit, List[Parameter], List[Parameter]]: + ) -> tuple[QuantumCircuit, list[Parameter], list[Parameter]]: """ Build the quantum circuit for the convolutional layer. @@ -304,7 +304,7 @@ def build_circuit( Defaults to 3. Returns: - Tuple[QuantumCircuit, List[Parameter], List[Parameter]]: Quantum circuit, + tuple[QuantumCircuit, list[Parameter], list[Parameter]]: Quantum circuit, list of weight parameters, list of input parameters. """ qc = QuantumCircuit(num_qubits) diff --git a/test/datasets/test_entanglement_concentration.py b/test/datasets/test_entanglement_concentration.py index 47a433a00..834abb689 100644 --- a/test/datasets/test_entanglement_concentration.py +++ b/test/datasets/test_entanglement_concentration.py @@ -19,12 +19,12 @@ import numpy as np from ddt import ddt, unpack, idata - from qiskit.quantum_info import Statevector, partial_trace from qiskit_machine_learning.utils import algorithm_globals from qiskit_machine_learning.datasets import entanglement_concentration_data +# pylint: disable=invalid-name def _compute_ce(sv): """Computing CE using Mathematical Expression due to Beckey, J. L. et al. (alternatively SWAP test can be used if done in a Quantum Circuit)""" diff --git a/test/gradients/logging_primitives.py b/test/gradients/logging_primitives.py index f5ffeae0e..6455d824e 100644 --- a/test/gradients/logging_primitives.py +++ b/test/gradients/logging_primitives.py @@ -1,6 +1,6 @@ # This code is part of a Qiskit project. # -# (C) Copyright IBM 2022, 2024. +# (C) Copyright IBM 2022, 2025. # # 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 @@ -12,31 +12,31 @@ """Test primitives that check what kind of operations are in the circuits they execute.""" -from qiskit.primitives import Estimator, Sampler +from qiskit.primitives import BaseEstimatorV2, BaseSamplerV2 -class LoggingEstimator(Estimator): +class LoggingEstimator(BaseEstimatorV2): """An estimator checking what operations were in the circuits it executed.""" - def __init__(self, options=None, operations_callback=None): - super().__init__(options=options) + def __init__(self, operations_callback=None): + super().__init__(default_precision=0.0, seed=None) self.operations_callback = operations_callback - def _run(self, circuits, observables, parameter_values, **run_options): + def run(self, pubs, **run_options): if self.operations_callback is not None: - ops = [circuit.count_ops() for circuit in circuits] + ops = [pub[0].count_ops() for pub in pubs] self.operations_callback(ops) - return super()._run(circuits, observables, parameter_values, **run_options) + return super().run(pubs, **run_options) -class LoggingSampler(Sampler): +class LoggingSampler(BaseSamplerV2): """A sampler checking what operations were in the circuits it executed.""" def __init__(self, operations_callback): super().__init__() self.operations_callback = operations_callback - def _run(self, circuits, parameter_values, **run_options): - ops = [circuit.count_ops() for circuit in circuits] + def run(self, pubs, **run_options): + ops = [pub[0].count_ops() for pub in pubs] self.operations_callback(ops) - return super()._run(circuits, parameter_values, **run_options) + return super().run(pubs, **run_options) diff --git a/test/gradients/test_estimator_gradient.py b/test/gradients/test_estimator_gradient.py index a36e39de7..97656e814 100644 --- a/test/gradients/test_estimator_gradient.py +++ b/test/gradients/test_estimator_gradient.py @@ -14,29 +14,28 @@ """Test Estimator Gradients""" import unittest +from math import sqrt from test import QiskitAlgorithmsTestCase import numpy as np -from ddt import ddt, data - +from ddt import data, ddt from qiskit import QuantumCircuit from qiskit.circuit import Parameter from qiskit.circuit.library import efficient_su2, real_amplitudes from qiskit.circuit.library.standard_gates import RXXGate, RYYGate, RZXGate, RZZGate -from qiskit.primitives import Estimator -from qiskit.quantum_info import SparsePauliOp +from qiskit.primitives import StatevectorEstimator from qiskit.providers.fake_provider import GenericBackendV2 +from qiskit.quantum_info import SparsePauliOp from qiskit.transpiler.preset_passmanagers import generate_preset_pass_manager - -from qiskit_ibm_runtime import Session, EstimatorV2 +from qiskit_ibm_runtime import EstimatorV2, Session from qiskit_ibm_runtime.options import EstimatorOptions, SimulatorOptions - from qiskit_machine_learning.gradients import ( LinCombEstimatorGradient, ParamShiftEstimatorGradient, SPSAEstimatorGradient, ) + from .logging_primitives import LoggingEstimator gradient_factories = [ @@ -50,7 +49,7 @@ class TestEstimatorGradient(QiskitAlgorithmsTestCase): """Test Estimator Gradient""" def __init__(self, TestCase): - self.estimator = Estimator() + self.estimator = StatevectorEstimator() super().__init__(TestCase) @data(*gradient_factories) @@ -373,7 +372,8 @@ def test_options(self, grad): qc = QuantumCircuit(1) qc.rx(a, 0) op = SparsePauliOp.from_list([("Z", 1)]) - estimator = Estimator(options={"shots": 100}) + precision = 1 / sqrt(100) + estimator = StatevectorEstimator(default_precision=precision) with self.subTest("estimator"): if grad is SPSAEstimatorGradient: gradient = grad(estimator, epsilon=1e-6) diff --git a/test/gradients/test_sampler_gradient.py b/test/gradients/test_sampler_gradient.py index f5149b0eb..f3b7e97fa 100644 --- a/test/gradients/test_sampler_gradient.py +++ b/test/gradients/test_sampler_gradient.py @@ -15,21 +15,18 @@ import unittest from test import QiskitAlgorithmsTestCase -from typing import List -import numpy as np -from ddt import ddt, data +import numpy as np +from ddt import data, ddt from qiskit import QuantumCircuit from qiskit.circuit import Parameter from qiskit.circuit.library import efficient_su2, real_amplitudes from qiskit.circuit.library.standard_gates import RXXGate -from qiskit.primitives import Sampler -from qiskit.result import QuasiDistribution +from qiskit.primitives import StatevectorSampler from qiskit.providers.fake_provider import GenericBackendV2 +from qiskit.result import QuasiDistribution from qiskit.transpiler.preset_passmanagers import generate_preset_pass_manager - -from qiskit_ibm_runtime import Session, SamplerV2 - +from qiskit_ibm_runtime import SamplerV2, Session from qiskit_machine_learning.gradients import ( LinCombSamplerGradient, ParamShiftSamplerGradient, @@ -49,7 +46,7 @@ class TestSamplerGradient(QiskitAlgorithmsTestCase): """Test Sampler Gradient""" def __init__(self, TestCase): - self.sampler = Sampler() + self.sampler = StatevectorSampler() super().__init__(TestCase) @data(*gradient_factories) @@ -1169,7 +1166,7 @@ def operations_callback(op): np.testing.assert_allclose(array1, array2, atol=1e-5) -def _quasi2array(quasis: List[QuasiDistribution], num_qubits: int) -> np.ndarray: +def _quasi2array(quasis: list[QuasiDistribution], num_qubits: int) -> np.ndarray: ret = np.zeros((len(quasis), 2**num_qubits)) for i, quasi in enumerate(quasis): ret[i, list(quasi.keys())] = list(quasi.values()) diff --git a/test/kernels/test_fidelity_qkernel.py b/test/kernels/test_fidelity_qkernel.py index d2cb685f1..74d6c7535 100644 --- a/test/kernels/test_fidelity_qkernel.py +++ b/test/kernels/test_fidelity_qkernel.py @@ -22,21 +22,20 @@ import numpy as np from ddt import ddt, idata, unpack -from sklearn.svm import SVC - from qiskit import QuantumCircuit from qiskit.circuit import Parameter from qiskit.circuit.library import z_feature_map -from qiskit.primitives import Sampler +from qiskit.primitives import StatevectorSampler +from sklearn.svm import SVC from qiskit_machine_learning.algorithm_job import AlgorithmJob -from qiskit_machine_learning.utils import algorithm_globals +from qiskit_machine_learning.kernels import FidelityQuantumKernel from qiskit_machine_learning.state_fidelities import ( - ComputeUncompute, BaseStateFidelity, + ComputeUncompute, StateFidelityResult, ) -from qiskit_machine_learning.kernels import FidelityQuantumKernel +from qiskit_machine_learning.utils import algorithm_globals @ddt @@ -63,7 +62,7 @@ def setUp(self): self.sample_test = np.asarray([[2.199114860, 5.15221195], [0.50265482, 0.06283185]]) self.label_test = np.asarray([0, 1]) - self.sampler = Sampler() + self.sampler = StatevectorSampler() self.fidelity = ComputeUncompute(self.sampler) self.properties = { @@ -359,7 +358,7 @@ def test_properties(self): """Test properties of the base (abstract) class and fidelity based kernel.""" qc = QuantumCircuit(1) qc.ry(Parameter("w"), 0) - fidelity = ComputeUncompute(sampler=Sampler()) + fidelity = ComputeUncompute(sampler=StatevectorSampler()) kernel = FidelityQuantumKernel( feature_map=qc, fidelity=fidelity, enforce_psd=False, evaluate_duplicates="none" ) @@ -386,7 +385,7 @@ def setUp(self) -> None: "y_vec": np.array([[0, 1], [1, 2]]), } - counting_sampler = Sampler() + counting_sampler = StatevectorSampler() counting_sampler.run = self.count_circuits(counting_sampler.run) self.counting_sampler = counting_sampler self.circuit_counts = 0 diff --git a/test/neural_networks/test_estimator_qnn_v1.py b/test/neural_networks/test_estimator_qnn_v1.py index f6baaa4a3..56d52fe7d 100644 --- a/test/neural_networks/test_estimator_qnn_v1.py +++ b/test/neural_networks/test_estimator_qnn_v1.py @@ -13,18 +13,17 @@ """Test EstimatorQNN""" import unittest - from test import QiskitMachineLearningTestCase import numpy as np from qiskit.circuit import Parameter, QuantumCircuit -from qiskit.circuit.library import zz_feature_map, real_amplitudes, z_feature_map +from qiskit.circuit.library import real_amplitudes, z_feature_map, zz_feature_map from qiskit.quantum_info import SparsePauliOp - from qiskit_machine_learning.circuit.library import QNNCircuit from qiskit_machine_learning.neural_networks.estimator_qnn import EstimatorQNN from qiskit_machine_learning.utils import algorithm_globals + CASE_DATA = { "shape_1_1": { "test_data": [1, [1], [[1], [2]], [[[1], [2]], [[3], [4]]]], diff --git a/test/neural_networks/test_estimator_qnn_v2.py b/test/neural_networks/test_estimator_qnn_v2.py index 5d81b41be..657a5825f 100644 --- a/test/neural_networks/test_estimator_qnn_v2.py +++ b/test/neural_networks/test_estimator_qnn_v2.py @@ -13,23 +13,20 @@ """Test EstimatorQNN""" import unittest - from test import QiskitMachineLearningTestCase import numpy as np - from qiskit.circuit import Parameter, QuantumCircuit -from qiskit.circuit.library import zz_feature_map, real_amplitudes, z_feature_map -from qiskit.quantum_info import SparsePauliOp +from qiskit.circuit.library import real_amplitudes, z_feature_map, zz_feature_map from qiskit.providers.fake_provider import GenericBackendV2 +from qiskit.quantum_info import SparsePauliOp from qiskit.transpiler.preset_passmanagers import generate_preset_pass_manager -from qiskit_ibm_runtime import Session, EstimatorV2 - +from qiskit_ibm_runtime import EstimatorV2, Session from qiskit_machine_learning.circuit.library import QNNCircuit +from qiskit_machine_learning.gradients import ParamShiftEstimatorGradient from qiskit_machine_learning.neural_networks.estimator_qnn import EstimatorQNN from qiskit_machine_learning.utils import algorithm_globals -from qiskit_machine_learning.gradients import ParamShiftEstimatorGradient algorithm_globals.random_seed = 52 diff --git a/test/neural_networks/test_sampler_qnn.py b/test/neural_networks/test_sampler_qnn.py index ba3d12203..fab79cf4c 100644 --- a/test/neural_networks/test_sampler_qnn.py +++ b/test/neural_networks/test_sampler_qnn.py @@ -13,29 +13,26 @@ """Test Sampler QNN with Terra primitives.""" from __future__ import annotations -from test import QiskitMachineLearningTestCase - import itertools import unittest -import numpy as np +from test import QiskitMachineLearningTestCase +import numpy as np from ddt import ddt, idata - from qiskit.circuit import Parameter, QuantumCircuit -from qiskit.primitives import Sampler +from qiskit.circuit.library import real_amplitudes, zz_feature_map +from qiskit.primitives import StatevectorSampler from qiskit.providers.fake_provider import GenericBackendV2 from qiskit.transpiler.preset_passmanagers import generate_preset_pass_manager -from qiskit.circuit.library import real_amplitudes, zz_feature_map - -from qiskit_ibm_runtime import Session, SamplerV2 - -from qiskit_machine_learning.utils import algorithm_globals -from qiskit_machine_learning.circuit.library import QNNCircuit -from qiskit_machine_learning.neural_networks.sampler_qnn import SamplerQNN +from qiskit_ibm_runtime import SamplerV2, Session +import qiskit_machine_learning.optionals as _optionals +from qiskit_machine_learning.circuit.library import qnn_circuit from qiskit_machine_learning.gradients.param_shift.param_shift_sampler_gradient import ( ParamShiftSamplerGradient, ) -import qiskit_machine_learning.optionals as _optionals +from qiskit_machine_learning.neural_networks.sampler_qnn import SamplerQNN +from qiskit_machine_learning.utils import algorithm_globals + if _optionals.HAS_SPARSE: # pylint: disable=import-error @@ -100,8 +97,8 @@ def interpret_2d(x): ) # 1st dim. takes values in {0, 1} 2nd dim in {0, 1, 2} # define sampler primitives - self.sampler = Sampler() - self.sampler_shots = Sampler(options={"shots": 100, "seed": 42}) + self.sampler = StatevectorSampler() + self.sampler_shots = StatevectorSampler(default_shots=100, seed=42) self.backend = GenericBackendV2(num_qubits=8) self.session = Session(backend=self.backend) self.sampler_v2 = SamplerV2(mode=self.session) @@ -226,6 +223,7 @@ def test_sampler_qnn(self, config): sparse, sampler_type, interpret_type, batch_size, input_grads = config # Test QNN with input and weight params + qnn = self._get_qnn( sparse, sampler_type, @@ -385,11 +383,14 @@ def test_qnn_qc_circuit_construction(self): num_qubits = 2 feature_map = zz_feature_map(feature_dimension=num_qubits) ansatz = real_amplitudes(num_qubits=num_qubits, reps=1) + pm = generate_preset_pass_manager(backend=self.backend) def parity(x): return f"{bin(x)}".count("1") % 2 - qnn_qc = QNNCircuit(num_qubits=num_qubits, feature_map=feature_map, ansatz=ansatz) + qnn_qc, feature_map_params, ansatz_params = qnn_circuit( + num_qubits=num_qubits, feature_map=feature_map, ansatz=ansatz + ) qc = QuantumCircuit(num_qubits) qc.compose(feature_map, inplace=True) qc.compose(ansatz, inplace=True) @@ -401,9 +402,16 @@ def parity(x): interpret=parity, output_shape=2, input_gradients=True, + pass_manager=pm, ) sampler_qnn_qc = SamplerQNN( - circuit=qnn_qc, interpret=parity, output_shape=2, input_gradients=True + circuit=qnn_qc, + input_params=feature_map_params, + weight_params=ansatz_params, + interpret=parity, + output_shape=2, + input_gradients=True, + pass_manager=pm, ) input_data = [1, 2] diff --git a/test/optimizers/test_nlopts.py b/test/optimizers/test_nlopts.py index 474837e70..42937f0a5 100644 --- a/test/optimizers/test_nlopts.py +++ b/test/optimizers/test_nlopts.py @@ -1,6 +1,6 @@ # This code is part of a Qiskit project. # -# (C) Copyright IBM 2018, 2024. +# (C) Copyright IBM 2018, 2025. # # 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 @@ -14,9 +14,16 @@ import unittest from test import QiskitAlgorithmsTestCase + import numpy as np from qiskit.exceptions import MissingOptionalLibraryError -from qiskit_machine_learning.optimizers.nlopts import CRS, DIRECT_L, DIRECT_L_RAND, ESCH, ISRES +from qiskit_machine_learning.optimizers.nlopts import ( + CRS, + DIRECT_L, + DIRECT_L_RAND, + ESCH, + ISRES, +) from qiskit_machine_learning.utils import algorithm_globals @@ -152,6 +159,9 @@ def quadratic_function(params): return np.sum((params - 2) ** 2) initial_point = np.array([10.0, -10.0]) + initial_point = np.clip( + initial_point, [l for l, _ in self.bounds], [u for _, u in self.bounds] + ) try: optimizers = [ diff --git a/test/optimizers/test_optimizer_aqgd.py b/test/optimizers/test_optimizer_aqgd.py index 00cd67616..74dfc073f 100644 --- a/test/optimizers/test_optimizer_aqgd.py +++ b/test/optimizers/test_optimizer_aqgd.py @@ -1,6 +1,6 @@ # This code is part of a Qiskit project. # -# (C) Copyright IBM 2019, 2024. +# (C) Copyright IBM 2019, 2025. # # 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 @@ -14,11 +14,11 @@ import unittest from test import QiskitAlgorithmsTestCase + import numpy as np from ddt import ddt -from qiskit.primitives import Estimator +from qiskit.primitives import StatevectorEstimator from qiskit.quantum_info import SparsePauliOp - from qiskit_machine_learning import AlgorithmError from qiskit_machine_learning.gradients import LinCombEstimatorGradient from qiskit_machine_learning.optimizers import AQGD @@ -41,7 +41,7 @@ def setUp(self): ("XX", 0.18093119978423156), ] ) - self.estimator = Estimator() + self.estimator = StatevectorEstimator() self.gradient = LinCombEstimatorGradient(self.estimator) def test_raises_exception(self): diff --git a/test/optimizers/test_optimizers.py b/test/optimizers/test_optimizers.py index 1d77a243b..90f9fade4 100644 --- a/test/optimizers/test_optimizers.py +++ b/test/optimizers/test_optimizers.py @@ -15,14 +15,14 @@ import unittest from test import QiskitAlgorithmsTestCase -from typing import Optional, List, Tuple +from typing import Optional from ddt import ddt, data, unpack import numpy as np from scipy.optimize import rosen, rosen_der from qiskit.circuit.library import real_amplitudes from qiskit.exceptions import MissingOptionalLibraryError -from qiskit.primitives import Sampler +from qiskit.primitives import StatevectorSampler from qiskit_machine_learning.optimizers import ( ADAM, @@ -62,7 +62,7 @@ def run_optimizer( optimizer: Optimizer, max_nfev: int, grad: bool = False, - bounds: Optional[List[Tuple[float, float]]] = None, + bounds: Optional[list[tuple[float, float]]] = None, ): """Test the optimizer. @@ -391,7 +391,7 @@ def steps(): def test_qnspsa(self): """Test QN-SPSA optimizer is serializable.""" ansatz = real_amplitudes(1) - fidelity = QNSPSA.get_fidelity(ansatz, sampler=Sampler()) + fidelity = QNSPSA.get_fidelity(ansatz, sampler=StatevectorSampler()) options = { "fidelity": fidelity, "maxiter": 100, diff --git a/test/optimizers/test_spsa.py b/test/optimizers/test_spsa.py index a201d80ea..411360c6c 100644 --- a/test/optimizers/test_spsa.py +++ b/test/optimizers/test_spsa.py @@ -11,17 +11,14 @@ # that they have been altered from the originals. """Tests for the SPSA optimizer.""" - from test import QiskitAlgorithmsTestCase -from ddt import ddt, data import numpy as np - +from ddt import data, ddt from qiskit.circuit.library import pauli_two_design -from qiskit.primitives import Estimator, Sampler +from qiskit.primitives import StatevectorEstimator, StatevectorSampler from qiskit.quantum_info import SparsePauliOp, Statevector - -from qiskit_machine_learning.optimizers import SPSA, QNSPSA +from qiskit_machine_learning.optimizers import QNSPSA, SPSA from qiskit_machine_learning.utils import algorithm_globals @@ -57,7 +54,7 @@ def objective(x): settings["regularization"] = 0.01 expected_nfev = settings["maxiter"] * 5 + 1 elif method == "qnspsa": - settings["fidelity"] = QNSPSA.get_fidelity(circuit, sampler=Sampler()) + settings["fidelity"] = QNSPSA.get_fidelity(circuit, sampler=StatevectorSampler()) settings["regularization"] = 0.001 settings["learning_rate"] = 0.05 settings["perturbation"] = 0.05 @@ -204,7 +201,7 @@ def test_qnspsa_fidelity_primitives(self): initial_point = np.random.random(ansatz.num_parameters) with self.subTest(msg="pass as kwarg"): - fidelity = QNSPSA.get_fidelity(ansatz, sampler=Sampler()) + fidelity = QNSPSA.get_fidelity(ansatz, sampler=StatevectorEstimator()) result = fidelity(initial_point, initial_point) self.assertAlmostEqual(result[0], 1) @@ -215,7 +212,7 @@ def test_qnspsa_max_evals_grouped(self): num_parameters = circuit.num_parameters obs = SparsePauliOp("ZZI") # Z^Z^I - estimator = Estimator(options={"seed": 12}) + estimator = StatevectorEstimator(seed=12) initial_point = np.array( [0.82311034, 0.02611798, 0.21077064, 0.61842177, 0.09828447, 0.62013131] @@ -223,10 +220,9 @@ def test_qnspsa_max_evals_grouped(self): def objective(x): x = np.reshape(x, (-1, num_parameters)).tolist() - n = len(x) - return estimator.run(n * [circuit], n * [obs], x).result().values.real + return estimator.run((circuit, obs, x)).result().values.real - fidelity = QNSPSA.get_fidelity(circuit, sampler=Sampler()) + fidelity = QNSPSA.get_fidelity(circuit, sampler=StatevectorEstimator()) optimizer = QNSPSA(fidelity) optimizer.maxiter = 1 optimizer.learning_rate = 0.05 diff --git a/test/state_fidelities/test_compute_uncompute.py b/test/state_fidelities/test_compute_uncompute.py index e777b0a92..e942b576d 100644 --- a/test/state_fidelities/test_compute_uncompute.py +++ b/test/state_fidelities/test_compute_uncompute.py @@ -16,11 +16,9 @@ from test import QiskitAlgorithmsTestCase import numpy as np - -from qiskit.circuit import QuantumCircuit, ParameterVector +from qiskit.circuit import ParameterVector, QuantumCircuit from qiskit.circuit.library import real_amplitudes -from qiskit.primitives import Sampler - +from qiskit.primitives import StatevectorSampler from qiskit_machine_learning.state_fidelities import ComputeUncompute @@ -49,7 +47,7 @@ def setUp(self): rx_rotation.h(1) self._circuit = [rx_rotations, ry_rotations, plus, zero, rx_rotation] - self._sampler = Sampler() + self._sampler = StatevectorSampler() self._left_params = np.array([[0, 0], [np.pi / 2, 0], [0, np.pi / 2], [np.pi, np.pi]]) self._right_params = np.array([[0, 0], [0, 0], [np.pi / 2, 0], [0, 0]]) @@ -219,7 +217,7 @@ def test_input_measurements(self): def test_options(self): """Test fidelity's run options""" - sampler_shots = Sampler(options={"shots": 1024}) + sampler_shots = StatevectorSampler(default_shots=1024) with self.subTest("sampler"): # Only options in sampler diff --git a/test/state_fidelities/test_compute_uncompute_v2.py b/test/state_fidelities/test_compute_uncompute_v2.py index 0fc68d43a..bf4dbf323 100644 --- a/test/state_fidelities/test_compute_uncompute_v2.py +++ b/test/state_fidelities/test_compute_uncompute_v2.py @@ -16,15 +16,12 @@ from test import QiskitMachineLearningTestCase import numpy as np - -from qiskit.circuit import QuantumCircuit, ParameterVector +from qiskit.circuit import ParameterVector, QuantumCircuit from qiskit.circuit.library import real_amplitudes -from qiskit.primitives import Sampler +from qiskit.primitives import StatevectorSampler from qiskit.providers.fake_provider import GenericBackendV2 from qiskit.transpiler.preset_passmanagers import generate_preset_pass_manager - -from qiskit_ibm_runtime import Session, SamplerV2 - +from qiskit_ibm_runtime import SamplerV2, Session from qiskit_machine_learning.state_fidelities import ComputeUncompute @@ -56,8 +53,6 @@ def setUp(self): self.backend = GenericBackendV2( num_qubits=4, - calibrate_instructions=None, - pulse_channels=False, noise_info=False, seed=123, ) @@ -267,7 +262,7 @@ def test_input_measurements(self): def test_options(self): """Test fidelity's run options""" - sampler_shots = Sampler(options={"shots": 1024}) + sampler_shots = StatevectorSampler(default_shots=1024) with self.subTest("sampler"): # Only options in sampler diff --git a/test/utils/test_adjust_num_qubits.py b/test/utils/test_adjust_num_qubits.py index 13920157a..5e3b3949a 100644 --- a/test/utils/test_adjust_num_qubits.py +++ b/test/utils/test_adjust_num_qubits.py @@ -10,14 +10,11 @@ # copyright notice, and modified files need to carry a notice indicating # that they have been altered from the originals. """Tests for adjusting number of qubits in a feature map / ansatz.""" -import itertools - from test import QiskitMachineLearningTestCase - -from ddt import idata, unpack, ddt +import itertools +from ddt import ddt, idata, unpack from qiskit import QuantumCircuit -from qiskit.circuit.library import ZFeatureMap, RealAmplitudes - +from qiskit.circuit.library import real_amplitudes, z_feature_map from qiskit_machine_learning import QiskitMachineLearningError from qiskit_machine_learning.utils import derive_num_qubits_feature_map_ansatz @@ -29,10 +26,10 @@ class TestAdjustNumQubits(QiskitMachineLearningTestCase): def setUp(self) -> None: super().setUp() self.properties = { - "z1": ZFeatureMap(1), - "z2": ZFeatureMap(2), - "ra1": RealAmplitudes(1), - "ra2": RealAmplitudes(2), + "z1": z_feature_map(1), + "z2": z_feature_map(2), + "ra1": real_amplitudes(1), + "ra2": real_amplitudes(2), } def test_all_none(self): @@ -51,24 +48,12 @@ def test_incompatible_feature_map_ansatz(self): self.properties["ra2"], ) - def test_no_adjustment(self): - """Test when no adjustment can be made.""" - self.assertRaises( - QiskitMachineLearningError, - derive_num_qubits_feature_map_ansatz, - 2, - QuantumCircuit(1), - None, + @idata( + itertools.chain( + itertools.product([1], [None, "z1"], [None, "ra1"]), + itertools.product([2], [None, "z2"], [None, "ra2"]), ) - self.assertRaises( - QiskitMachineLearningError, - derive_num_qubits_feature_map_ansatz, - 2, - None, - QuantumCircuit(1), - ) - - @idata(itertools.product([1, 2], [None, "z1", "z2"], [None, "ra1", "ra2"])) + ) @unpack def test_num_qubits_is_set(self, num_qubits, feature_map, ansatz): """Test when the number of qubits is set."""