-
Notifications
You must be signed in to change notification settings - Fork 337
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
QNNCircuit circuit added to wrap feature map and ansatz (#569)
* local env set up print vars working version of QNNCircuit adding unittests for QNNCircuit add feature releas note for QNNCircuit black, lint, compyright add CNNCircuit to wrap feature map and ansatz correct spelling mistakes spell check resolve pylint unused variable 'circuit' update type hints, copyright & correct release notes formating derive property config. whenever circuit is build, add respective tests and detailed release note Update qiskit_machine_learning/circuit/library/qnn_circuit.py typo correction: adding missing space. Co-authored-by: Steve Wood <[email protected]> extended argument description valid parametersets at all time, adjust tests & docu. respectively * more explicit documentation and test cases for invalid construction * add class ref. in docs., rm attribute-, value-error tests and docs. --------- Co-authored-by: Anton Dekusar <[email protected]>
- Loading branch information
1 parent
db30139
commit f1793a8
Showing
5 changed files
with
475 additions
and
4 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,241 @@ | ||
# This code is part of Qiskit. | ||
# | ||
# (C) Copyright IBM 2023. | ||
# | ||
# 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 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.library import BlueprintCircuit | ||
from qiskit_machine_learning.utils import derive_num_qubits_feature_map_ansatz | ||
from qiskit_machine_learning import QiskitMachineLearningError | ||
|
||
|
||
class QNNCircuit(BlueprintCircuit): | ||
""" | ||
The QNN circuit is a blueprint circuit that wraps feature map and ansatz circuits. | ||
It can be used to simplify the composition of these two. | ||
If only the number of qubits is provided the :class:`~qiskit.circuit.library.RealAmplitudes` | ||
ansatz and the :class:`~qiskit.circuit.library.ZZFeatureMap` feature map are used. If the | ||
number of qubits is 1 the :class:`~qiskit.circuit.library.ZFeatureMap` is used. If only a | ||
feature map is provided, the :class:`~qiskit.circuit.library.RealAmplitudes` ansatz with the | ||
corresponding number of qubits is used. If only an ansatz is provided the | ||
:class:`~qiskit.circuit.library.ZZFeatureMap` with the corresponding number of qubits is used. | ||
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. | ||
In case number of qubits is provided along with either a feature map, an ansatz or both, a | ||
potential mismatch between the three inputs with respect to the number of qubits is resolved by | ||
constructing the :class:`~qiskit_machine_learning.circuit.library.QNNCircuit` with the given | ||
number of qubits. If one of the :class:`~qiskit_machine_learning.circuit.library.QNNCircuit` | ||
properties is set after the class construction, the circuit is adjusted to incorporate the | ||
changes. This means, a new valid configuration that considers the latest property update will be | ||
derived. This ensures that the classes properties are consistent at all times. | ||
Example: | ||
.. code-block:: python | ||
from qiskit_machine_learning.circuit.library import QNNCircuit | ||
qnn_qc = QNNCircuit(2) | ||
print(qnn_qc) | ||
# prints: | ||
# ┌──────────────────────────┐» | ||
# q_0: ┤0 ├» | ||
# │ ZZFeatureMap(x[0],x[1]) │» | ||
# q_1: ┤1 ├» | ||
# └──────────────────────────┘» | ||
# « ┌──────────────────────────────────────────────────────────┐ | ||
# «q_0: ┤0 ├ | ||
# « │ RealAmplitudes(θ[0],θ[1],θ[2],θ[3],θ[4],θ[5],θ[6],θ[7]) │ | ||
# «q_1: ┤1 ├ | ||
# « └──────────────────────────────────────────────────────────┘ | ||
print(qnn_qc.num_qubits) | ||
# prints: 2 | ||
print(qnn_qc.input_parameters) | ||
# prints: ParameterView([ParameterVectorElement(x[0]), ParameterVectorElement(x[1])]) | ||
print(qnn_qc.weight_parameters) | ||
# prints: ParameterView([ParameterVectorElement(θ[0]), ParameterVectorElement(θ[1]), | ||
# ParameterVectorElement(θ[2]), ParameterVectorElement(θ[3]), | ||
# ParameterVectorElement(θ[4]), ParameterVectorElement(θ[5]), | ||
# ParameterVectorElement(θ[6]), ParameterVectorElement(θ[7])]) | ||
""" | ||
|
||
def __init__( | ||
self, | ||
num_qubits: int | None = None, | ||
feature_map: QuantumCircuit | None = None, | ||
ansatz: QuantumCircuit | None = None, | ||
) -> None: | ||
""" | ||
Although all parameters default to None at least one parameter must be provided, to determine | ||
the number of qubits from it, when the instance is created. | ||
If more than one parameter is passed: | ||
1) If num_qubits is provided the feature map and/or ansatz supplied will be overridden to | ||
circuits with num_qubits, as long as the respective circuit supports updating its 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 ansatz. | ||
feature_map: A feature map. Optional if num_qubits or ansatz is provided, otherwise | ||
required. If not provided defaults to | ||
:class:`~qiskit.circuit.library.ZZFeatureMap` or | ||
:class:`~qiskit.circuit.library.ZFeatureMap` 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 | ||
:class:`~qiskit.circuit.library.RealAmplitudes`. | ||
Returns: | ||
The composed feature map and ansatz circuit. | ||
Raises: | ||
QiskitMachineLearningError: If a valid number of qubits cannot be derived from the \ | ||
provided input arguments. | ||
""" | ||
|
||
super().__init__() | ||
self._feature_map = feature_map | ||
self._ansatz = ansatz | ||
# Check if circuit is constructed with valid configuration and set properties accordingly. | ||
self.num_qubits, self._feature_map, self._ansatz = derive_num_qubits_feature_map_ansatz( | ||
num_qubits, feature_map, ansatz | ||
) | ||
|
||
def _build(self): | ||
super()._build() | ||
self.compose(self.feature_map, inplace=True) | ||
self.compose(self.ansatz, inplace=True) | ||
|
||
def _check_configuration(self, raise_on_failure=True): | ||
try: | ||
self.num_qubits, self.feature_map, self.ansatz = derive_num_qubits_feature_map_ansatz( | ||
self.num_qubits, self.feature_map, self.ansatz | ||
) | ||
except QiskitMachineLearningError as qml_ex: | ||
if raise_on_failure: | ||
raise qml_ex | ||
|
||
@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. If num_qubits is set | ||
the feature map and ansatz are adjusted to circuits with num_qubits qubits. | ||
Args: | ||
num_qubits: The number of qubits, a positive integer. | ||
""" | ||
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")] | ||
( | ||
self.num_qubits, | ||
self._feature_map, | ||
self._ansatz, | ||
) = derive_num_qubits_feature_map_ansatz( | ||
num_qubits, self._feature_map, self._ansatz | ||
) | ||
|
||
@property | ||
def feature_map(self) -> QuantumCircuit: | ||
"""Returns feature_map. | ||
Returns: | ||
The feature map. | ||
""" | ||
return self._feature_map | ||
|
||
@feature_map.setter | ||
def feature_map(self, feature_map: QuantumCircuit) -> None: | ||
"""Set the feature map. If the feature map is updated the ``QNNCircuit`` is adjusted | ||
according to the feature map being passed. This includes: | ||
1) The num_qubits is adjusted to the feature map number of qubits. | ||
2) The ansatz is adjusted to a circuit with the feature_map number of qubits. | ||
Args: | ||
feature_map: The feature map. | ||
""" | ||
if self.feature_map != feature_map: | ||
# invalidate the circuit | ||
self._invalidate() | ||
self.num_qubits = feature_map.num_qubits | ||
self.num_qubits, self._feature_map, self._ansatz = derive_num_qubits_feature_map_ansatz( | ||
self.num_qubits, feature_map, self.ansatz | ||
) | ||
|
||
@property | ||
def ansatz(self) -> QuantumCircuit: | ||
"""Returns ansatz. | ||
Returns: | ||
The ansatz. | ||
""" | ||
return self._ansatz | ||
|
||
@ansatz.setter | ||
def ansatz(self, ansatz: QuantumCircuit) -> None: | ||
"""Set the ansatz. If the ansatz is updated the ``QNNCircuit`` is adapted | ||
according to the ansatz being passed. This includes: | ||
1) The num_qubits is adjusted to the ansatz number of qubits. | ||
2) The feature_map is adjusted to a circuit with the ansatz number of qubits. | ||
Args: | ||
ansatz: The ansatz. | ||
""" | ||
if self.ansatz != ansatz: | ||
# invalidate the circuit | ||
self._invalidate() | ||
self.num_qubits = ansatz.num_qubits | ||
self.num_qubits, self._feature_map, self._ansatz = derive_num_qubits_feature_map_ansatz( | ||
self.num_qubits, self.feature_map, ansatz | ||
) | ||
|
||
@property | ||
def input_parameters(self) -> ParameterView: | ||
"""Returns the parameters of the feature map. | ||
Returns: | ||
The parameters of the feature map. | ||
""" | ||
return self._feature_map.parameters | ||
|
||
@property | ||
def weight_parameters(self) -> ParameterView: | ||
"""Returns the parameters of the ansatz. These corresponding to the trainable weights. | ||
Returns: | ||
The parameters of the ansatz. | ||
""" | ||
return self._ansatz.parameters |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
56 changes: 56 additions & 0 deletions
56
releasenotes/notes/add-quantum-neural-network-circuit-f2e593c13f3e37d5.yaml
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,56 @@ | ||
--- | ||
features: | ||
- | | ||
Added a new :class:`~qiskit_machine_learning.circuit.library.QNNCircuit` class that | ||
composes a Quantum Circuit from a feature map and an ansatz. | ||
At least one parameter, i.e. number of qubits, feature map, ansatz, has to be provided. | ||
If only the number of qubits is provided the resulting quantum circuit is a composition | ||
of the :class:`~qiskit.circuit.library.ZZFeatureMap` and the | ||
:class:`~qiskit.circuit.library.RealAmplitudes` ansatz. | ||
If the number of qubits is 1 the :class:`~qiskit.circuit.library.ZFeatureMap` is used per | ||
default. If only a feature map is provided, the :class:`~qiskit.circuit.library.RealAmplitudes` | ||
ansatz with the corresponding number of qubits is used. If only an ansatz is provided the | ||
:class:`~qiskit.circuit.library.ZZFeatureMap` with the corresponding number of qubits is used. | ||
In case number of qubits is provided along with either a feature map, an ansatz or both, a | ||
potential mismatch between the three inputs with respect to the number of qubits is | ||
resolved by constructing the :class:`~qiskit_machine_learning.circuit.library.QNNCircuit` with | ||
the given number of qubits. If one of the | ||
:class:`~qiskit_machine_learning.circuit.library.QNNCircuit` properties is set after the class | ||
construction, the circuit is is adjusted to incorporate the changes. This is, a new valid | ||
configuration that considers the latest property update will be derived. This ensures that the | ||
classes properties are consistent at all times. | ||
An example of using this class is as follows: | ||
.. code-block:: python | ||
from qiskit_machine_learning.circuit.library import QNNCircuit | ||
qnn_qc = QNNCircuit(2) | ||
print(qnn_qc) | ||
# prints: | ||
# ┌──────────────────────────┐» | ||
# q_0: ┤0 ├» | ||
# │ ZZFeatureMap(x[0],x[1]) │» | ||
# q_1: ┤1 ├» | ||
# └──────────────────────────┘» | ||
# « ┌──────────────────────────────────────────────────────────┐ | ||
# «q_0: ┤0 ├ | ||
# « │ RealAmplitudes(θ[0],θ[1],θ[2],θ[3],θ[4],θ[5],θ[6],θ[7]) │ | ||
# «q_1: ┤1 ├ | ||
# « └──────────────────────────────────────────────────────────┘ | ||
print(qnn_qc.num_qubits) | ||
# prints: 2 | ||
print(qnn_qc.input_parameters) | ||
# prints: ParameterView([ParameterVectorElement(x[0]), ParameterVectorElement(x[1])]) | ||
print(qnn_qc.weight_parameters) | ||
# prints: ParameterView([ParameterVectorElement(θ[0]), ParameterVectorElement(θ[1]), | ||
# ParameterVectorElement(θ[2]), ParameterVectorElement(θ[3]), | ||
# ParameterVectorElement(θ[4]), ParameterVectorElement(θ[5]), | ||
# ParameterVectorElement(θ[6]), ParameterVectorElement(θ[7])]) | ||
Oops, something went wrong.