Skip to content

Commit

Permalink
QNNCircuit circuit added to wrap feature map and ansatz (#569)
Browse files Browse the repository at this point in the history
* 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
david-alber and adekusar-drl authored Apr 5, 2023
1 parent db30139 commit f1793a8
Show file tree
Hide file tree
Showing 5 changed files with 475 additions and 4 deletions.
12 changes: 11 additions & 1 deletion qiskit_machine_learning/circuit/library/__init__.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# This code is part of Qiskit.
#
# (C) Copyright IBM 2020, 2022.
# (C) Copyright IBM 2020, 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
Expand Down Expand Up @@ -28,10 +28,20 @@
RawFeatureVector
Helper Circuits
===============
.. autosummary::
:toctree: ../stubs/
:nosignatures:
QNNCircuit
"""

from .raw_feature_vector import RawFeatureVector
from .qnn_circuit import QNNCircuit

__all__ = [
"RawFeatureVector",
"QNNCircuit",
]
241 changes: 241 additions & 0 deletions qiskit_machine_learning/circuit/library/qnn_circuit.py
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
7 changes: 4 additions & 3 deletions qiskit_machine_learning/utils/adjust_num_qubits.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# This code is part of Qiskit.
#
# (C) Copyright IBM 2022.
# (C) Copyright IBM 2022, 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
Expand Down Expand Up @@ -54,13 +54,14 @@ def derive_num_qubits_feature_map_ansatz(
Raises:
QiskitMachineLearningError: If correct values can not be derived from the parameters.
"""

# check num_qubits, feature_map, and ansatz
if num_qubits is None and feature_map is None and ansatz is None:
if num_qubits in (0, None) and feature_map is None and ansatz is None:
raise QiskitMachineLearningError(
"Need at least one of number of qubits, feature map, or ansatz!"
)

if num_qubits is not None:
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)
Expand Down
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])])
Loading

0 comments on commit f1793a8

Please sign in to comment.