From d1ad476ced6f7da217f6a07d368184c74eb8e5c0 Mon Sep 17 00:00:00 2001 From: 92meharali Date: Sat, 16 May 2026 20:14:58 +0500 Subject: [PATCH] Remove deprecated qubit auto-alignment (#1024) Drop num_qubits argument and circuit padding from derive_num_qubits_feature_map_ansatz, qnn_circuit, VQC, VQR, and BaseKernel. Require matching qubit counts on feature_map and ansatz. --- .../algorithms/classifiers/vqc.py | 15 +- .../algorithms/regressors/vqr.py | 15 +- .../circuit/library/qnn_circuit.py | 49 ++--- .../kernels/base_kernel.py | 22 +- .../neural_networks/estimator_qnn.py | 2 +- .../neural_networks/sampler_qnn.py | 2 +- .../utils/adjust_num_qubits.py | 189 +++--------------- .../remove-qubit-auto-alignment-1024.yaml | 10 + test/algorithms/classifiers/test_vqc.py | 44 ++-- test/algorithms/regressors/test_vqr.py | 4 +- test/circuit/test_qnn_circuit.py | 13 +- .../test_fidelity_statevector_kernel.py | 10 +- test/neural_networks/test_estimator_qnn.py | 2 +- test/neural_networks/test_sampler_qnn.py | 2 +- test/utils/test_adjust_num_qubits.py | 33 +-- 15 files changed, 94 insertions(+), 318 deletions(-) create mode 100644 releasenotes/notes/remove-qubit-auto-alignment-1024.yaml diff --git a/qiskit_machine_learning/algorithms/classifiers/vqc.py b/qiskit_machine_learning/algorithms/classifiers/vqc.py index e6e435cb3..5a80b4b1c 100644 --- a/qiskit_machine_learning/algorithms/classifiers/vqc.py +++ b/qiskit_machine_learning/algorithms/classifiers/vqc.py @@ -49,7 +49,6 @@ class VQC(NeuralNetworkClassifier): # pylint: disable=too-many-positional-arguments def __init__( self, - num_qubits: int | None = None, feature_map: QuantumCircuit | None = None, ansatz: QuantumCircuit | None = None, loss: str | Loss = "cross_entropy", @@ -65,11 +64,6 @@ def __init__( ) -> None: """ Args: - num_qubits: The number of qubits for the underlying QNN. - If ``None`` is given, the number of qubits is derived from the - feature map or ansatz. If neither of those is given, raises an exception. - The number of qubits in the feature map and ansatz are adjusted to this - number if required and possible (such adjustment is deprecated). feature_map: The (parametrized) circuit to be used as a feature map for the underlying QNN. If ``None`` is given, the :meth:`~qiskit.circuit.library.zz_feature_map` is used if the number of qubits is larger than 1. For a single qubit @@ -102,14 +96,11 @@ def __init__( output_shape: The output shape for the underlying neural network, generally equals to number of classes. Defaults to binary classification, 2. Raises: - QiskitMachineLearningError: Needs at least one out of ``num_qubits``, ``feature_map`` or - ``ansatz`` to be given. Or the number of qubits in the feature map and/or ansatz - can't be adjusted to ``num_qubits``. + QiskitMachineLearningError: At least one of ``feature_map`` or ``ansatz`` must be + given, or the number of qubits in the feature map and ansatz must match. """ - num_qubits, feature_map, ansatz = derive_num_qubits_feature_map_ansatz( - num_qubits, feature_map, ansatz - ) + num_qubits, feature_map, ansatz = derive_num_qubits_feature_map_ansatz(feature_map, ansatz) if output_shape is None: self.output_shape = 2 diff --git a/qiskit_machine_learning/algorithms/regressors/vqr.py b/qiskit_machine_learning/algorithms/regressors/vqr.py index f38b9c093..8ef366e1c 100644 --- a/qiskit_machine_learning/algorithms/regressors/vqr.py +++ b/qiskit_machine_learning/algorithms/regressors/vqr.py @@ -35,7 +35,6 @@ class VQR(NeuralNetworkRegressor): # pylint: disable=too-many-positional-arguments def __init__( self, - num_qubits: int | None = None, feature_map: QuantumCircuit | None = None, ansatz: QuantumCircuit | None = None, observable: BaseOperator | None = None, @@ -50,11 +49,6 @@ def __init__( ) -> None: r""" Args: - num_qubits: The number of qubits for the underlying QNN. - If ``None`` then the number of qubits is derived from the - feature map or ansatz, but if neither of these are given an error is raised. - The number of qubits in the feature map and ansatz are adjusted to this - number if required and possible (such adjustment is deprecated). feature_map: The (parametrized) circuit to be used as a feature map for the underlying QNN. If ``None`` the :meth:`~qiskit.circuit.library.zz_feature_map` is used if the number of qubits is larger than 1. For a single qubit regression @@ -82,9 +76,8 @@ 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: - QiskitMachineLearningError: Needs at least one out of ``num_qubits``, ``feature_map`` or - ``ansatz`` to be given. Or the number of qubits in the feature map and/or ansatz - can't be adjusted to ``num_qubits``. + QiskitMachineLearningError: At least one of ``feature_map`` or ``ansatz`` must be + given, or the number of qubits in the feature map and ansatz must match. ValueError: if the type of the observable is not compatible with ``estimator``. """ if observable is not None and not isinstance(observable, BaseOperator): @@ -95,9 +88,7 @@ def __init__( self._estimator = estimator - num_qubits, feature_map, ansatz = derive_num_qubits_feature_map_ansatz( - num_qubits, feature_map, ansatz - ) + num_qubits, feature_map, ansatz = derive_num_qubits_feature_map_ansatz(feature_map, ansatz) # construct circuit self._feature_map = feature_map diff --git a/qiskit_machine_learning/circuit/library/qnn_circuit.py b/qiskit_machine_learning/circuit/library/qnn_circuit.py index 87f6dbf2b..14080d7e4 100644 --- a/qiskit_machine_learning/circuit/library/qnn_circuit.py +++ b/qiskit_machine_learning/circuit/library/qnn_circuit.py @@ -21,7 +21,6 @@ def qnn_circuit( - num_qubits: int | None = None, feature_map: QuantumCircuit | None = None, ansatz: QuantumCircuit | None = None, ): @@ -31,12 +30,11 @@ def qnn_circuit( use for inputs and weights as needed for a neural network, such as :class:`~qiskit-machine-learning.neural_networks.SamplerQNN`. - If only the number of qubits is provided the :meth:`~qiskit.circuit.library.real_amplitudes` - ansatz and the :meth:`~qiskit.circuit.library.zz_feature_map` feature map are used. If the - number of qubits is 1 the :meth:`~qiskit.circuit.library.z_feature_map` is used. If only a - feature map is provided, the :meth:`~qiskit.circuit.library.real_amplitudes` ansatz with the - corresponding number of qubits is used. If only an ansatz is provided the + If only a feature map is provided, the :meth:`~qiskit.circuit.library.real_amplitudes` + ansatz with the corresponding number of qubits is used. If only an ansatz is provided the :meth:`~qiskit.circuit.library.zz_feature_map` with the corresponding number of qubits is used. + For a single qubit, :meth:`~qiskit.circuit.library.z_feature_map` is used as the default feature + map. At least one parameter has to be provided. If a feature map and an ansatz is provided, the number of qubits must be the same. @@ -45,8 +43,9 @@ def qnn_circuit( .. code-block:: python + from qiskit.circuit.library import zz_feature_map from qiskit_machine_learning.circuit.library import qnn_circuit - qnn_qc, fm_params, anz_params = qnn_circuit(2) + qnn_qc, fm_params, anz_params = qnn_circuit(feature_map=zz_feature_map(2)) qnn_qc.draw(fold=60) # ┌───┐┌─────────────┐ » # q_0: ┤ H ├┤ P(2.0*x[0]) ├──■──» @@ -81,29 +80,13 @@ def qnn_circuit( # ParameterVectorElement(θ[4]), ParameterVectorElement(θ[5]), # ParameterVectorElement(θ[6]), ParameterVectorElement(θ[7])]) - Although all arguments to qnn_circuit default to None at least one must be provided, - to determine the number of qubits from. - - If more than one is passed: - - 1) If num_qubits is provided the feature map and/or ansatz circuits supplied must be the - same number of qubits. - - 2) If num_qubits is not provided the feature_map and ansatz must be set to the same number - of qubits. - Args: - num_qubits: Number of qubits, a positive integer. Optional if feature_map or ansatz is - provided, otherwise required. If not provided num_qubits defaults from the - sizes of feature_map and/or ansatz. - feature_map: A feature map. Optional if num_qubits or ansatz is provided, otherwise - required. If not provided defaults to - :meth:`~qiskit.circuit.library.zz_feature_map` or - :meth:`~qiskit.circuit.library.z_feature_map` if num_qubits is determined - to be 1. - ansatz: An ansatz. Optional if num_qubits or feature_map is provided, otherwise - required. If not provided defaults to - :meth:`~qiskit.circuit.library.real_amplitudes`. + feature_map: A feature map. At least one of ``feature_map`` or ``ansatz`` must be + provided. If not provided defaults to + :meth:`~qiskit.circuit.library.zz_feature_map` or + :meth:`~qiskit.circuit.library.z_feature_map` for a single qubit. + ansatz: An ansatz. At least one of ``feature_map`` or ``ansatz`` must be provided. + If not provided defaults to :meth:`~qiskit.circuit.library.real_amplitudes`. Returns: The composed feature map and ansatz circuit, the feature map parameters and the @@ -111,12 +94,10 @@ def qnn_circuit( Raises: QiskitMachineLearningError: If a valid number of qubits cannot be derived from the \ - provided input arguments. + provided input arguments, or if ``feature_map`` and ``ansatz`` have different numbers of \ + qubits. """ - # 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 - ) + num_qubits, feature_map, ansatz = derive_num_qubits_feature_map_ansatz(feature_map, ansatz) qc = QuantumCircuit(num_qubits) qc.compose(feature_map, inplace=True) qc.compose(ansatz, inplace=True) diff --git a/qiskit_machine_learning/kernels/base_kernel.py b/qiskit_machine_learning/kernels/base_kernel.py index e458394c3..ae8d6448f 100644 --- a/qiskit_machine_learning/kernels/base_kernel.py +++ b/qiskit_machine_learning/kernels/base_kernel.py @@ -47,10 +47,8 @@ def __init__(self, *, feature_map: QuantumCircuit = None, enforce_psd: bool = Tr """ Args: feature_map: Parameterized circuit to be used as the feature map. This is required: if - ``None`` is given, a :class:`~QiskitMachineLearningError` is raised. 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. + ``None`` is given, a :class:`~QiskitMachineLearningError` is raised. The number of + features in the input data must match the number of qubits in the feature map. enforce_psd: Project to the closest positive semidefinite matrix if ``x = y``. Default ``True``. """ @@ -111,16 +109,12 @@ def _validate_input( x_vec = np.reshape(x_vec, (-1, len(x_vec))) if x_vec.shape[1] != self._num_features: - # before raising an error we try to adjust the feature map - # to the required number of qubit. - try: - self._feature_map.num_qubits = x_vec.shape[1] - except AttributeError as a_e: - raise ValueError( - f"x_vec and class feature map have incompatible dimensions.\n" - f"x_vec has {x_vec.shape[1]} dimensions, " - f"but feature map has {self._num_features}." - ) from a_e + raise ValueError( + f"x_vec and feature map have incompatible dimensions.\n" + f"x_vec has {x_vec.shape[1]} features, but the feature map expects " + f"{self._num_features} parameters. " + "Construct the feature map with a matching feature dimension." + ) if y_vec is not None: y_vec = np.asarray(y_vec) diff --git a/qiskit_machine_learning/neural_networks/estimator_qnn.py b/qiskit_machine_learning/neural_networks/estimator_qnn.py index 88e15d8f1..77bfe2837 100644 --- a/qiskit_machine_learning/neural_networks/estimator_qnn.py +++ b/qiskit_machine_learning/neural_networks/estimator_qnn.py @@ -67,7 +67,7 @@ class EstimatorQNN(NeuralNetwork): # Using the qnn_circuit: # Create a parametrized 2 qubit circuit composed of the default zz_feature_map feature map # and real_amplitudes ansatz. - qnn_qc, fm_params, anz_params = qnn_circuit(num_qubits) + qnn_qc, fm_params, anz_params = qnn_circuit(feature_map=zz_feature_map(num_qubits)) qnn = EstimatorQNN( circuit=qnn_qc, diff --git a/qiskit_machine_learning/neural_networks/sampler_qnn.py b/qiskit_machine_learning/neural_networks/sampler_qnn.py index 275eab72b..9b80c7918 100644 --- a/qiskit_machine_learning/neural_networks/sampler_qnn.py +++ b/qiskit_machine_learning/neural_networks/sampler_qnn.py @@ -102,7 +102,7 @@ def parity(x): # Example 1: Using the qnn_circuit # qnn_circuit automatically combines a feature map and an ansatz into a single circuit - qnn_qc, fm_params, anz_params = qnn_circuit(num_qubits) + qnn_qc, fm_params, anz_params = qnn_circuit(feature_map=zz_feature_map(num_qubits)) qnn = SamplerQNN( circuit=qnn_qc, diff --git a/qiskit_machine_learning/utils/adjust_num_qubits.py b/qiskit_machine_learning/utils/adjust_num_qubits.py index dd74176f6..acfd0d11d 100644 --- a/qiskit_machine_learning/utils/adjust_num_qubits.py +++ b/qiskit_machine_learning/utils/adjust_num_qubits.py @@ -10,67 +10,33 @@ # 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. -"""Helper functions to adjust number of qubits.""" +"""Helper functions to derive feature map and ansatz circuits.""" from __future__ import annotations -import warnings from qiskit import QuantumCircuit from qiskit.circuit.library import real_amplitudes, z_feature_map, zz_feature_map from ..exceptions import QiskitMachineLearningError -from ..utils.deprecation import issue_deprecation_msg -# pylint: disable=invalid-name def derive_num_qubits_feature_map_ansatz( - num_qubits: int | None = None, feature_map: QuantumCircuit | None = None, ansatz: QuantumCircuit | None = None, ) -> tuple[int, QuantumCircuit, QuantumCircuit]: """ Derives a correct number of qubits, feature map, and ansatz from the parameters. - 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 :func:`~qiskit.circuit.library.zz_feature_map` - and :func:`~qiskit.circuit.library.real_amplitudes` are created respectively. If there's just + If the 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 - :func:`~qiskit.circuit.library.zz_feature_map` or :func:`~qiskit.circuit.library.real_amplitudes` - are created respectively. - - 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 :meth:`~qiskit.circuit.library.zz_feature_map` - and :meth:`~qiskit.circuit.library.real_amplitudes` are created respectively. If there's just - one qubit, :meth:`~qiskit.circuit.library.z_feature_map` is created instead. + If both the feature map and ansatz are provided, they must have the same number of qubits. If all the parameters are ``None`` an error is raised. - .. warning:: - - The ``num_qubits`` argument and automatic qubit alignment (padding or resizing of - ``feature_map`` and ``ansatz`` circuits) are **deprecated** as of version ``0.9.1`` and - will be removed after a 6-month deprecation period. - - In a future release, this function will require that ``feature_map`` and ``ansatz`` are - explicitly provided with the **same number of qubits**, and passing ``num_qubits`` or - relying on automatic circuit alignment will raise an error. - - To ensure forward compatibility, remove the ``num_qubits`` argument (or set it to - ``None``) and construct ``feature_map`` and ``ansatz`` with matching numbers of qubits - before calling this function. - - See https://github.com/qiskit-community/qiskit-machine-learning/issues/1010 for details. - - Args: - num_qubits: Number of qubits (deprecated). feature_map: A feature map. ansatz: An ansatz. @@ -80,143 +46,34 @@ def derive_num_qubits_feature_map_ansatz( Raises: QiskitMachineLearningError: If correct values can not be derived from the parameters. """ - candidates = {} - - 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: - candidates["num_qubits"] = num_qubits - - issue_deprecation_msg( - msg=( - "The num_qubits argument and the qubit auto-alignment of circuits are " - "deprecated and will be removed. " - "See https://github.com/qiskit-community/qiskit-machine-learning/issues/1010 " - "for more details." - ), - version="0.10.0", - remedy=( - "Remove the num_qubits argument (or set to None), and make sure that the " - "feature_map and ansatz have the same number of qubits before passing " - "them as arguments." - ), - period="6 months", - ) - - if not candidates: + if feature_map is None and ansatz is None: raise QiskitMachineLearningError( "Unable to determine number of qubits: " - "provide `num_qubits` (int), `feature_map` (QuantumCircuit), " - "or `ansatz` (QuantumCircuit)." + "provide `feature_map` (QuantumCircuit) and/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, - ) - issue_deprecation_msg( - msg=( - "Qubit auto-alignment of circuits is deprecated and will be removed. " - "In the future, an error will be raised if the number of qubits in " - "feature_map and ansatz are note the same. " - "See https://github.com/qiskit-community/qiskit-machine-learning/issues/1010 " - "for more details." - ), - version="0.10.0", - remedy=( - "Ensure that the feature_map and ansatz have the same number of qubits " - "before passing them as arguments." - ), - period="6 months", - ) - - # Final resolved number of qubits - resolved_num_qubits = max(unique_vals) + if feature_map is not None and ansatz is not None: + if feature_map.num_qubits != ansatz.num_qubits: + raise QiskitMachineLearningError( + f"Inconsistent qubit numbers detected between the feature map " + f"({feature_map.num_qubits}) and the ansatz ({ansatz.num_qubits}). " + "Construct both circuits with the same number of qubits before passing them." + ) + resolved_num_qubits = feature_map.num_qubits + elif feature_map is not None: + resolved_num_qubits = feature_map.num_qubits + else: + resolved_num_qubits = ansatz.num_qubits - def default_feature_map(n: int) -> QuantumCircuit: - return z_feature_map(n) if n == 1 else zz_feature_map(n) + def default_feature_map(num_qubits: int) -> QuantumCircuit: + return z_feature_map(num_qubits) if num_qubits == 1 else zz_feature_map(num_qubits) - def default_ansatz(n: int) -> QuantumCircuit: - return real_amplitudes(n) + def default_ansatz(num_qubits: int) -> QuantumCircuit: + return real_amplitudes(num_qubits) if feature_map is None: feature_map = default_feature_map(resolved_num_qubits) - candidates["feature_map"] = feature_map.num_qubits - else: - 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: - """ - .. warning:: - - This function is **deprecated** as of version ``0.9.1``. - See https://github.com/qiskit-community/qiskit-machine-learning/issues/1010 for details. - """ - 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. - - Args: - circuit: A circuit to adjust. - circuit_name: A circuit name, used in the error description. - num_qubits: A number of qubits to set. - - Raises: - QiskitMachineLearningError: if number of qubits can't be adjusted. - - """ - issue_deprecation_msg( - msg="No longer in use", - version="0.9.0", - remedy="Check ", - period="0 months", - ) diff --git a/releasenotes/notes/remove-qubit-auto-alignment-1024.yaml b/releasenotes/notes/remove-qubit-auto-alignment-1024.yaml new file mode 100644 index 000000000..8a88cf2e7 --- /dev/null +++ b/releasenotes/notes/remove-qubit-auto-alignment-1024.yaml @@ -0,0 +1,10 @@ +--- +upgrade: + - | + Removed the deprecated ``num_qubits`` argument and automatic qubit alignment (padding or + resizing) from :func:`~qiskit_machine_learning.utils.derive_num_qubits_feature_map_ansatz`, + :func:`~qiskit_machine_learning.circuit.library.qnn_circuit`, :class:`~.VQC`, and + :class:`~.VQR`. ``feature_map`` and ``ansatz`` must now be constructed with the same number + of qubits before being passed to these APIs. Quantum kernels no longer attempt to resize the + feature map when the input feature dimension does not match. See + `#1024 `_. diff --git a/test/algorithms/classifiers/test_vqc.py b/test/algorithms/classifiers/test_vqc.py index b6392e9ad..dbab4a3a7 100644 --- a/test/algorithms/classifiers/test_vqc.py +++ b/test/algorithms/classifiers/test_vqc.py @@ -36,7 +36,6 @@ 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] ANSATZES = ["real_amplitudes", None] OPTIMIZERS = ["cobyla", None] @@ -94,11 +93,9 @@ def setUp(self): } # pylint: disable=too-many-positional-arguments - @idata( - itertools.product(NUM_QUBITS_LIST, FEATURE_MAPS, ANSATZES, OPTIMIZERS, DATASETS, SAMPLERS) - ) + @idata(itertools.product(FEATURE_MAPS, ANSATZES, OPTIMIZERS, DATASETS, SAMPLERS)) @unpack - def test_VQC(self, num_qubits, f_m, ans, opt, d_s, smplr): + def test_VQC(self, f_m, ans, opt, d_s, smplr): """ Test VQC with binary and multiclass data using a range of quantum instances, numbers of qubits, feature maps, and optimizers. @@ -108,10 +105,8 @@ def test_VQC(self, num_qubits, f_m, ans, opt, d_s, smplr): else: pm = None - if num_qubits is None and f_m is None and ans is None: - self.skipTest( - "At least one of num_qubits, feature_map, or ansatz must be set by the user." - ) + if f_m is None and ans is None: + self.skipTest("At least one of feature_map or ansatz must be set by the user.") feature_map = self.properties.get(f_m) optimizer = self.properties.get(opt) @@ -130,7 +125,6 @@ def test_VQC(self, num_qubits, f_m, ans, opt, d_s, smplr): initial_point = np.array([0.5] * ansatz.num_parameters) if ansatz is not None else None classifier = VQC( - num_qubits=num_qubits, feature_map=feature_map, ansatz=ansatz, optimizer=optimizer, @@ -152,7 +146,7 @@ def test_VQC_non_parameterized(self): Test VQC without an optimizer set. """ classifier = VQC( - num_qubits=2, + feature_map=self.properties.get("zz_feature_map"), optimizer=None, ) dataset = self.properties.get("binary") @@ -231,7 +225,7 @@ def test_multilabel_targets_raise_an_error(self): x = algorithm_globals.random.random((3, 2)) y = np.asarray([[1, 1, 0], [1, 0, 1], [0, 1, 1]]) - classifier = VQC(num_qubits=2) + classifier = VQC(feature_map=self.properties.get("zz_feature_map")) with self.assertRaises(QiskitMachineLearningError): classifier.fit(x, y) @@ -243,7 +237,7 @@ def test_changing_classes_raises_error(self): features1 = algorithm_globals.random.random((len(targets1), 2)) features2 = algorithm_globals.random.random((len(targets2), 2)) - classifier = VQC(num_qubits=2, warm_start=True) + classifier = VQC(feature_map=self.properties.get("zz_feature_map"), warm_start=True) classifier.fit(features1, targets1) with self.assertRaises(QiskitMachineLearningError): classifier.fit(features2, targets2) @@ -252,7 +246,7 @@ def test_changing_classes_raises_error(self): def test_sparse_arrays(self, loss): """Tests VQC on sparse features and labels.""" - classifier = VQC(num_qubits=2, loss=loss) + classifier = VQC(feature_map=self.properties.get("zz_feature_map"), loss=loss) x = scipy.sparse.csr_matrix([[0, 0], [1, 1]]) y = scipy.sparse.csr_matrix([[1, 0], [0, 1]]) @@ -265,7 +259,7 @@ def test_categorical(self): """Test VQC on categorical labels.""" classifier = VQC( - num_qubits=2, + feature_map=self.properties.get("zz_feature_map"), optimizer=COBYLA(25), ) dataset = self.properties.get("no_one_hot") @@ -281,26 +275,12 @@ def test_categorical(self): predict = classifier.predict(features[0, :]) self.assertIn(predict, ["A", "B"]) - @unittest.skip("Skip for now") - def test_circuit_extensions(self): - """Test VQC when the number of qubits is different compared to the feature map/ansatz.""" - - # Note: This first part is using the deprecated BlueprintCircuit based classes to - # ensure the auto-adjust continues to work as expected until its gone/removed. - num_qubits = 2 - classifier = VQC( - num_qubits=num_qubits, - 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) - + def test_mismatched_feature_map_and_ansatz(self): + """Test VQC raises when feature map and ansatz have different numbers of qubits.""" with self.assertRaises(QiskitMachineLearningError): _ = VQC( - num_qubits=num_qubits, feature_map=z_feature_map(1), - ansatz=real_amplitudes(1), + ansatz=real_amplitudes(2), ) diff --git a/test/algorithms/regressors/test_vqr.py b/test/algorithms/regressors/test_vqr.py index eadc2204a..5c64e3ef2 100644 --- a/test/algorithms/regressors/test_vqr.py +++ b/test/algorithms/regressors/test_vqr.py @@ -103,7 +103,7 @@ def test_vqr(self, config): def test_properties(self): """Test properties of VQR.""" - vqr = VQR(num_qubits=2) + vqr = VQR(feature_map=QuantumCircuit(2)) self.assertIsNotNone(vqr.feature_map) self.assertIsInstance(vqr.feature_map, QuantumCircuit) self.assertEqual(vqr.feature_map.num_qubits, 2) @@ -117,7 +117,7 @@ def test_properties(self): def test_incorrect_observable(self): """Test VQR with a wrong observable.""" with self.assertRaises(ValueError): - _ = VQR(num_qubits=2, observable=QuantumCircuit(2)) + _ = VQR(feature_map=QuantumCircuit(2), observable=QuantumCircuit(2)) @data( # optimizer, has ansatz diff --git a/test/circuit/test_qnn_circuit.py b/test/circuit/test_qnn_circuit.py index 554d46b54..e0f1abd66 100644 --- a/test/circuit/test_qnn_circuit.py +++ b/test/circuit/test_qnn_circuit.py @@ -19,6 +19,7 @@ from qiskit.circuit.library import ( pauli_feature_map, real_amplitudes, + z_feature_map, zz_feature_map, ) from qiskit_machine_learning import QiskitMachineLearningError @@ -31,7 +32,7 @@ class TestQNNCircuitFunction(QiskitMachineLearningTestCase): def test_construction(self): """Test construction of ``qnn_circuit``.""" - circuit, fm_params, anz_params = qnn_circuit(num_qubits=2) + circuit, fm_params, anz_params = qnn_circuit(feature_map=zz_feature_map(2)) with self.subTest("check resultant circuit built"): self.assertEqual(circuit.num_qubits, 2) @@ -41,21 +42,17 @@ def test_construction(self): def test_construction_fails(self): """Test the faulty construction""" - # If no argument is passed a QiskitMachineLearningError is raised with self.assertRaises(QiskitMachineLearningError): qnn_circuit(feature_map=zz_feature_map(2), ansatz=real_amplitudes(1)) - # If no argument is passed a QiskitMachineLearningError is raised with self.assertRaises(QiskitMachineLearningError): qnn_circuit() def test_num_qubit_construction(self): - """Test building the ``qnn_circuit`` with number of qubits.""" + """Test building the ``qnn_circuit`` with a single-qubit feature map.""" - circuit, fm_params, anz_params = qnn_circuit(1) + circuit, fm_params, anz_params = qnn_circuit(feature_map=z_feature_map(1)) - # If not otherwise specified, the defaults are a ZFeatureMap/ZZFeatureMap and a - # RealAmplitudes ansatz. with self.subTest("check input configuration after the circuit is build"): self.assertEqual(circuit.num_qubits, 1) self.assertEqual(type(circuit), QuantumCircuit) @@ -77,4 +74,4 @@ def test_construction_for_input_mismatch(self): """Test the construction of ``qnn_circuit`` for input that does not match fails.""" with self.assertRaises(QiskitMachineLearningError): - qnn_circuit(num_qubits=4, feature_map=zz_feature_map(3), ansatz=real_amplitudes(2)) + qnn_circuit(feature_map=zz_feature_map(3), ansatz=real_amplitudes(2)) diff --git a/test/kernels/test_fidelity_statevector_kernel.py b/test/kernels/test_fidelity_statevector_kernel.py index 6d54d7f37..0c81accdb 100644 --- a/test/kernels/test_fidelity_statevector_kernel.py +++ b/test/kernels/test_fidelity_statevector_kernel.py @@ -312,14 +312,12 @@ def test_validate_input(self): x_vec = np.asarray([]) self.assertRaises(ValueError, kernel.evaluate, x_vec) - with self.subTest("Adjust the number of qubits in the feature map"): + with self.subTest("Reject feature dimension mismatch in the feature map"): kernel = FidelityStatevectorKernel(feature_map=z_feature_map(feature_dimension=3)) - x_vec = np.asarray([[1, 2, 3]]) - kernel.evaluate(x_vec) - - self.assertEqual(kernel.feature_map.num_qubits, 3) + x_vec = np.asarray([[1, 2]]) + self.assertRaises(ValueError, kernel.evaluate, x_vec) - with self.subTest("Fail to adjust the number of qubits in the feature map"): + with self.subTest("Reject feature maps that cannot match feature dimension"): qc = QuantumCircuit(1) kernel = FidelityStatevectorKernel(feature_map=qc) diff --git a/test/neural_networks/test_estimator_qnn.py b/test/neural_networks/test_estimator_qnn.py index 679d45214..1f81f9060 100644 --- a/test/neural_networks/test_estimator_qnn.py +++ b/test/neural_networks/test_estimator_qnn.py @@ -492,7 +492,7 @@ def test_qnn_qc_circuit_construction(self): ) qnn_qc, feature_map_params, ansatz_params = qnn_circuit( - num_qubits=num_qubits, feature_map=feature_map, ansatz=ansatz + feature_map=feature_map, ansatz=ansatz ) isa_qnn_qc = self.pass_manager.run(qnn_qc) estimator_qnn_qc = EstimatorQNN( diff --git a/test/neural_networks/test_sampler_qnn.py b/test/neural_networks/test_sampler_qnn.py index 51bb39585..d5629909a 100644 --- a/test/neural_networks/test_sampler_qnn.py +++ b/test/neural_networks/test_sampler_qnn.py @@ -393,7 +393,7 @@ def parity(x): return f"{bin(x)}".count("1") % 2 qnn_qc, feature_map_params, ansatz_params = qnn_circuit( - num_qubits=num_qubits, feature_map=feature_map, ansatz=ansatz + feature_map=feature_map, ansatz=ansatz ) qc = QuantumCircuit(num_qubits) qc.compose(feature_map, inplace=True) diff --git a/test/utils/test_adjust_num_qubits.py b/test/utils/test_adjust_num_qubits.py index 1cf84a4ac..1b1c207e1 100644 --- a/test/utils/test_adjust_num_qubits.py +++ b/test/utils/test_adjust_num_qubits.py @@ -13,7 +13,6 @@ """Tests for adjusting number of qubits in a feature map / ansatz.""" from test import QiskitMachineLearningTestCase -import itertools from ddt import ddt, idata, unpack from qiskit import QuantumCircuit from qiskit.circuit.library import real_amplitudes, z_feature_map @@ -37,7 +36,7 @@ def setUp(self) -> None: def test_all_none(self): """Test when all parameters are ``None``.""" self.assertRaises( - QiskitMachineLearningError, derive_num_qubits_feature_map_ansatz, None, None, None + QiskitMachineLearningError, derive_num_qubits_feature_map_ansatz, None, None ) def test_incompatible_feature_map_ansatz(self): @@ -45,41 +44,19 @@ def test_incompatible_feature_map_ansatz(self): self.assertRaises( QiskitMachineLearningError, derive_num_qubits_feature_map_ansatz, - None, self.properties["z1"], self.properties["ra2"], ) - @idata( - itertools.chain( - itertools.product([1], [None, "z1"], [None, "ra1"]), - itertools.product([2], [None, "z2"], [None, "ra2"]), - ) - ) - @unpack - def test_num_qubits_is_set(self, num_qubits, feature_map, ansatz): - """Test when the number of qubits is set.""" - feature_map = self.properties.get(feature_map) - ansatz = self.properties.get(ansatz) - - # derived objects - num_qubits_der, feature_map_der, ansatz_der = derive_num_qubits_feature_map_ansatz( - num_qubits, feature_map, ansatz - ) - self.assertEqual(num_qubits_der, num_qubits) - - self._test_feature_map(feature_map_der, feature_map, num_qubits) - self._test_ansatz(ansatz_der, num_qubits) - - @idata([(None, "ra1"), (None, "ra2"), ("z1", None), ("z1", "ra1"), ("z2", None), ("z2", "ra2")]) + @idata([(None, "z1"), (None, "z2"), ("z1", None), ("z1", "ra1"), ("z2", None), ("z2", "ra2")]) @unpack - def test_num_qubits_isnot_set(self, feature_map, ansatz): - """Test when the number of qubits is not set.""" + def test_derive_from_circuits(self, feature_map, ansatz): + """Test deriving defaults when one or both circuits are provided.""" ansatz = self.properties.get(ansatz) feature_map = self.properties.get(feature_map) num_qubits_der, feature_map_der, ansatz_der = derive_num_qubits_feature_map_ansatz( - None, feature_map, ansatz + feature_map, ansatz ) num_qubits = feature_map.num_qubits if feature_map is not None else ansatz.num_qubits