Source code for qiskit_machine_learning.circuit.library.raw_feature_vector

# This code is part of a Qiskit project.
#
# (C) Copyright IBM 2020, 2024.
#
# This code is licensed under the Apache License, Version 2.0. You may
# obtain a copy of this license in the LICENSE.txt file in the root directory
# 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.

"""The raw feature vector circuit."""

from typing import Optional, List
import numpy as np
from qiskit.exceptions import QiskitError
from qiskit.circuit import (
    QuantumRegister,
    QuantumCircuit,
    ParameterVector,
    Instruction,
    ParameterExpression,
)
from qiskit.circuit.library import BlueprintCircuit


[docs] class RawFeatureVector(BlueprintCircuit): """The raw feature vector circuit. This circuit acts as parameterized initialization for statevectors with ``feature_dimension`` dimensions, thus with ``log2(feature_dimension)`` qubits. The circuit contains a placeholder instruction that can only be synthesized/defined when all parameters are bound. In ML, this circuit can be used to load the training data into qubit amplitudes. It does not apply an kernel transformation (therefore, it is a "raw" feature vector). Since initialization is implemented via a ``QuantumCircuit.initialize()`` call, this circuit can't be used with gradient based optimizers, one can see a warning that gradients can't be computed. Examples: .. code-block:: from qiskit_machine_learning.circuit.library import RawFeatureVector circuit = RawFeatureVector(4) print(circuit.num_qubits) # prints: 2 print(circuit.draw(output='text')) # prints: # ┌───────────────────────────────────────────────┐ # q_0: ┤0 ├ # │ PARAMETERIZEDINITIALIZE(x[0],x[1],x[2],x[3]) │ # q_1: ┤1 ├ # └───────────────────────────────────────────────┘ print(circuit.ordered_parameters) # prints: [Parameter(p[0]), Parameter(p[1]), Parameter(p[2]), Parameter(p[3])] import numpy as np state = np.array([1, 0, 0, 1]) / np.sqrt(2) bound = circuit.assign_parameters(state) print(bound.draw()) # prints: # ┌───────────────────────────────────────────────┐ # q_0: ┤0 ├ # │ PARAMETERIZEDINITIALIZE(0.70711,0,0,0.70711) │ # q_1: ┤1 ├ # └───────────────────────────────────────────────┘ """ def __init__(self, feature_dimension: Optional[int]) -> None: """ Args: feature_dimension: The feature dimension from which the number of qubits is inferred as ``n_qubits = log2(feature_dim)`` """ super().__init__() self._ordered_parameters = ParameterVector("x") if feature_dimension is not None: self.feature_dimension = feature_dimension def _build(self): super()._build() placeholder = ParameterizedInitialize(self._ordered_parameters[:]) self.append(placeholder, self.qubits) def _unsorted_parameters(self): if self.data is None: self._build() return super()._unsorted_parameters() def _check_configuration(self, raise_on_failure=True): if isinstance(self._ordered_parameters, ParameterVector): self._ordered_parameters.resize(self.feature_dimension) elif len(self._ordered_parameters) != self.feature_dimension: if raise_on_failure: raise ValueError("Mismatching number of parameters and feature dimension.") return False return True @property def num_qubits(self) -> int: """Returns the number of qubits in this circuit. Returns: The number of qubits. """ return super().num_qubits @num_qubits.setter def num_qubits(self, num_qubits: int) -> None: """Set the number of qubits for the n-local circuit. Args: The new number of qubits. """ if self.num_qubits != num_qubits: # invalidate the circuit self._invalidate() self.qregs: List[QuantumRegister] = [] if num_qubits is not None and num_qubits > 0: self.qregs = [QuantumRegister(num_qubits, name="q")] @property def feature_dimension(self) -> int: """Return the feature dimension. Returns: The feature dimension, which is ``2 ** num_qubits``. """ return 2**self.num_qubits @feature_dimension.setter def feature_dimension(self, feature_dimension: int) -> None: """Set the feature dimension. Args: feature_dimension: The new feature dimension. Must be a power of 2. Raises: ValueError: If ``feature_dimension`` is not a power of 2. """ num_qubits = np.log2(feature_dimension) if int(num_qubits) != num_qubits: raise ValueError("feature_dimension must be a power of 2!") if num_qubits != self.num_qubits: self._invalidate() self.num_qubits = int(num_qubits)
class ParameterizedInitialize(Instruction): """A normalized parameterized initialize instruction.""" def __init__(self, amplitudes): num_qubits = np.log2(len(amplitudes)) if int(num_qubits) != num_qubits: raise ValueError("feature_dimension must be a power of 2!") super().__init__("ParameterizedInitialize", int(num_qubits), 0, amplitudes) def _define(self): # cast ParameterExpressions that are fully bound to numbers cleaned_params = [] for param in self.params: if not isinstance(param, ParameterExpression) or len(param.parameters) == 0: cleaned_params.append(complex(param)) else: raise QiskitError("Cannot define a ParameterizedInitialize with unbound parameters") # normalize norm = np.linalg.norm(cleaned_params) normalized = cleaned_params if np.isclose(norm, 1) else cleaned_params / norm circuit = QuantumCircuit(self.num_qubits) circuit.initialize(normalized, range(self.num_qubits)) self.definition = circuit