Skip to content

Commit 241bdfd

Browse files
RobertoFlorezMoritzWillmannDavid-Kreplin
authored
QNN Loss for ODEs (#278)
* included my implementation of QNN odeloss * Included sympy and function input implementation * Improve comments and docs * Improve comments and docs and changed some variable names * Starting writting ode_example * Included tutorial for ode_example and fixed floating boundary method * Changed typo in rzrxrz doc * Changed typo in loss.py doc * modified loss, included kyriienko encoding circuit, modified chebyshev! * removed hardware_efficeint rzrxrx and made a new kyriienko nonlinear implementing chebyshev without calling external class * updated ode_example notebook with new KyriienkoEncodingCircuit * restored chebyshev_tower.py to origin/develop version * Improved comments on loss.py and KyriienkoEncodingCircuit * cleaned docu * polished doku * black * changes by dkr * fixed broken tutorial, added extra example and documentation for loss and for qnnr * black in loss and try to fix sphinx code block error * black in qnnr * black in ode_example * trying to solve long convergence of ode_example * cleaning the doc * changed ode_example to solve an easier ode in shorter time * refactor of comments * black --------- Co-authored-by: Moritz <44642314+MoritzWillmann@users.noreply.github.com> Co-authored-by: David Kreplin <david.kreplin@ipa.fraunhofer.de> Co-authored-by: David Kreplin <106071697+David-Kreplin@users.noreply.github.com>
1 parent 6df1d16 commit 241bdfd

7 files changed

Lines changed: 1303 additions & 11 deletions

File tree

docs/modules/classes.rst

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,9 +65,11 @@ Encoding Circuits
6565
encoding_circuit.MultiControlEncodingCircuit
6666
encoding_circuit.ChebyshevRx
6767
encoding_circuit.ParamZFeatureMap
68+
encoding_circuit.KyriienkoEncodingCircuit
6869
encoding_circuit.QiskitEncodingCircuit
6970
encoding_circuit.QCNNEncodingCircuit
7071

72+
7173
Encoding Circuit Tools
7274
------------------------------------
7375

@@ -198,6 +200,7 @@ QNN Core
198200
qnn.lowlevel_qnn_base.LowLevelQNNBase
199201
qnn.loss.SquaredLoss
200202
qnn.loss.VarianceLoss
203+
qnn.loss.ODELoss
201204
qnn.loss.ParameterRegularizationLoss
202205

203206
Tools for training QNNs

examples/qnn/ode_example.ipynb

Lines changed: 365 additions & 0 deletions
Large diffs are not rendered by default.

src/squlearn/encoding_circuit/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
from .circuit_library.chebyshev_rx import ChebyshevRx
1313
from .circuit_library.param_z_feature_map import ParamZFeatureMap
1414
from .circuit_library.qiskit_encoding_circuit import QiskitEncodingCircuit
15+
from .circuit_library.kyriienko_nonlinear_encoding_circuit import KyriienkoEncodingCircuit
1516

1617
__all__ = [
1718
"PrunedEncodingCircuit",
@@ -30,4 +31,5 @@
3031
"ChebyshevRx",
3132
"ParamZFeatureMap",
3233
"QiskitEncodingCircuit",
34+
"KyriienkoEncodingCircuit",
3335
]
Lines changed: 266 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,266 @@
1+
import numpy as np
2+
from typing import Union
3+
4+
from qiskit.circuit import ParameterVector
5+
from qiskit import QuantumCircuit
6+
7+
from ..encoding_circuit_base import EncodingCircuitBase
8+
from ..layered_encoding_circuit import LayeredEncodingCircuit, Layer
9+
10+
11+
class KyriienkoEncodingCircuit(EncodingCircuitBase):
12+
r"""
13+
Collection of encoding circuits introduced by Kyriienko et al. in reference [1].
14+
15+
The following circuits are implemented:
16+
17+
* ``chebyshev_tower`` encoding (Eq. 15),
18+
* ``chebyshev_sparse`` encoding (Eq. 14)
19+
* ``chebyshev_product`` encoding (Eq. 5).
20+
21+
Each encoding circuit is followed by a variational circuit as defined in reference [1],
22+
RZ-RX-RZ layers followed by entangling layers. Two arrangements are possible:
23+
24+
* ``HEA``: Hardware Efficient Ansatz, with consecutive entangling layers
25+
(See Figure 5a or Section IIIB in [1])
26+
* ``ABA``: Alternating Block Ansatz with consecutive shifted entangling layers in
27+
each block (See Figure 5b or Section IIIB in [1])
28+
29+
**Example: 4 qubits, a 2 dimensional feature vector, 1 encoding layer, 2 variational layers, variational arrangement ABA and Chebyshev tower encoding:**
30+
31+
.. plot::
32+
33+
from squlearn.encoding_circuit import KyriienkoEncodingCircuit
34+
pqc = KyriienkoEncodingCircuit(4, 1, num_encoding_layers=1, num_variational_layers=2,
35+
variational_arrangement="ABA")
36+
pqc.draw(output="mpl", style={'fontsize':15,'subfontsize': 10})
37+
38+
39+
Args:
40+
num_qubits (int): Number of qubits of the encoding circuit
41+
encoding_style (str): Style of the encoding. Options are ``'chebyshev_tower'`` (default),
42+
``'chebyshev_sparse'`` and ``'chebyshev_product'``
43+
(see reference [1], Equation 15, 14 and 5 respectively)
44+
variational_arrangement (str): Arrangement of the variational layers. Options are
45+
``'HEA'`` (default) and ``'ABA'`` (see reference [1],
46+
section IIIB)
47+
num_encoding_layers (int): Number of encoding layers (default: 1)
48+
num_variational_layers (int): Number of variational layers (default: 1)
49+
rotation_gate (str): Rotation gate to use. Either ``'rx'``, ``'ry'`` or
50+
``'rz'`` (default: ``'ry'`` as in reference [1])
51+
num_features (int): Dimension of the feature vector (default: 1)
52+
block_width (int): Only necessary for arrangement ``'ABA'``. Width (vertical) of each
53+
blocks for the ABA arrangement (default: 2), also refered as Nb in
54+
the paper. Must be a divisor of the number of qubits
55+
block_depth (int): Only necessary for arrangement ``'ABA'``. Depth (horizontal) of each
56+
blocks for the ABA arrangement (default: 1), also refered as b in
57+
the paper.
58+
59+
References
60+
----------
61+
[1]: O. Kyriienko et al., "Solving nonlinear differential equations with differentiable
62+
quantum circuits", `arXiv:2011.10395 (2021). <https://arxiv.org/pdf/2011.10395>`_
63+
"""
64+
65+
def __init__(
66+
self,
67+
num_qubits: int,
68+
num_features: int = 1,
69+
encoding_style: str = "chebyshev_tower",
70+
variational_arrangement: str = "HEA",
71+
num_encoding_layers: int = 1,
72+
num_variational_layers: int = 1,
73+
rotation_gate: str = "ry",
74+
block_width: int = 2,
75+
block_depth: int = 1,
76+
) -> None:
77+
super().__init__(num_qubits, num_features)
78+
79+
self.num_encoding_layers = num_encoding_layers
80+
self.num_variational_layers = num_variational_layers
81+
self.variational_arrangement = variational_arrangement
82+
self.encoding_style = encoding_style
83+
self.block_width = block_width
84+
self.block_depth = block_depth
85+
self.alpha = 2.0
86+
self.rotation_gate = rotation_gate
87+
self.num_chebyshev = self.num_qubits
88+
self.tower = True if encoding_style == "chebyshev_tower" else False
89+
90+
if self.variational_arrangement not in ("HEA", "ABA"):
91+
raise ValueError("Arrangement must be either 'HEA' or 'ABA'")
92+
93+
if self.num_qubits < 2:
94+
raise ValueError("Variational circuit HEE_rzrxrz requires at least two qubits.")
95+
96+
if self.rotation_gate not in ("rx", "ry", "rz"):
97+
raise ValueError("Rotation gate must be either 'rx', 'ry' or 'rz'")
98+
99+
@property
100+
def parameter_bounds(self) -> np.ndarray:
101+
"""The bounds of the trainable parameters of the HEE_rzrxrz encoding circuit."""
102+
return np.array([[-2.0 * np.pi, 2.0 * np.pi]] * self.num_parameters)
103+
104+
@property
105+
def num_parameters(self) -> int:
106+
"""The number of trainable parameters of the variational circuit."""
107+
if self.variational_arrangement == "HEA":
108+
num_param = 3 * self.num_qubits * self.num_variational_layers
109+
elif self.variational_arrangement == "ABA":
110+
num_param = 3 * self.num_qubits * self.block_depth * self.num_variational_layers
111+
return num_param
112+
113+
def get_params(self, deep: bool = True) -> dict:
114+
"""
115+
Returns hyper-parameters and their values of the Kyriienko encoding circuit
116+
117+
Args:
118+
deep (bool): If True, also the parameters for
119+
contained objects are returned (default=True).
120+
121+
Return:
122+
Dictionary with hyper-parameters and values.
123+
"""
124+
params = super().get_params()
125+
params["num_encoding_layers"] = self.num_encoding_layers
126+
params["num_variational_layers"] = self.num_variational_layers
127+
params["variational_arrangement"] = self.variational_arrangement
128+
params["encoding_style"] = self.encoding_style
129+
params["block_width"] = self.block_width
130+
params["block_depth"] = self.block_depth
131+
params["rotation_gate"] = self.rotation_gate
132+
133+
return params
134+
135+
def get_circuit(
136+
self,
137+
features: Union[ParameterVector, np.ndarray],
138+
parameters: Union[ParameterVector, np.ndarray],
139+
) -> QuantumCircuit:
140+
"""
141+
Generates and returns the circuit of the Kyriienko encoding circuit
142+
143+
Args:
144+
features (Union[ParameterVector, np.ndarray]): The features to encode
145+
parameters (Union[ParameterVector, np.ndarray]): The parameters of the encoding circuit
146+
147+
Returns:
148+
QuantumCircuit: The encoding circuit
149+
"""
150+
151+
def mapping(x, i):
152+
"""Non-linear mapping for x: alpha*i*arccos(x)"""
153+
return self.alpha * i * np.arccos(x)
154+
155+
def variational_gate_block(
156+
QC,
157+
num_layers,
158+
num_qubits,
159+
shift_parameter=0,
160+
index_offset=0,
161+
variational_arrangement="HEA",
162+
):
163+
"""
164+
Implements an Encoding circuit with layers of RZRXRZ followed by entangling layers.
165+
166+
"""
167+
qubit_starting_index = shift_parameter
168+
num_qubits += shift_parameter
169+
for layer in range(num_layers):
170+
for i in range(qubit_starting_index, num_qubits):
171+
if i >= self.num_qubits:
172+
i -= self.num_qubits
173+
QC.rz(parameters[index_offset], i)
174+
QC.rx(parameters[index_offset + 1], i)
175+
QC.rz(parameters[index_offset + 2], i)
176+
index_offset += 3
177+
if variational_arrangement == "HEA":
178+
for start in (0, 1):
179+
for i in range(start, num_qubits - 1, 2):
180+
QC.cx(i, i + 1)
181+
182+
elif variational_arrangement == "ABA":
183+
for start in (qubit_starting_index, qubit_starting_index + 1):
184+
for i in range(start, num_qubits - 1, 2):
185+
if i + 1 < self.num_qubits:
186+
QC.cx(i, i + 1)
187+
else:
188+
QC.cx(i, 0)
189+
return QC, index_offset
190+
191+
nfeature = len(features)
192+
193+
if self.encoding_style == "chebyshev_product":
194+
QC = LayeredEncodingCircuit(num_qubits=self.num_qubits, num_features=self.num_features)
195+
layer = Layer(QC)
196+
{"rx": layer.Rx, "ry": layer.Ry, "rz": layer.Rz}[self.rotation_gate](
197+
"x", encoding=np.arcsin
198+
)
199+
QC.add_layer(layer, num_layers=self.num_encoding_layers)
200+
201+
elif self.encoding_style in ("chebyshev_tower", "chebyshev_sparse"):
202+
QC = QuantumCircuit(self.num_qubits)
203+
for layer in range(self.num_encoding_layers):
204+
index_offset_encoding = 0
205+
iqubit = 0
206+
icheb = 1
207+
208+
inner = self.num_features
209+
outer = self.num_chebyshev
210+
211+
for outer_ in range(outer):
212+
for inner_ in range(inner):
213+
{"rx": QC.rx, "ry": QC.ry, "rz": QC.rz}[self.rotation_gate](
214+
mapping(features[index_offset_encoding % nfeature], icheb),
215+
iqubit % self.num_qubits,
216+
)
217+
iqubit += 1
218+
index_offset_encoding += 1
219+
icheb = 1 + icheb if self.tower else 1
220+
221+
index_offset_variational = 0
222+
if self.variational_arrangement == "HEA":
223+
QC = variational_gate_block(QC, self.num_variational_layers, self.num_qubits)[0]
224+
elif self.variational_arrangement == "ABA":
225+
if self.num_qubits % self.block_width != 0:
226+
raise ValueError("Block width must be a divisor of the number of qubits.")
227+
228+
number_of_blocks = int(np.ceil(self.num_qubits / self.block_width)) # vertical blocks
229+
shifting_factor = np.floor(self.block_width / 2)
230+
for layer in range(self.num_variational_layers):
231+
if layer % 2 == 0: # even layer
232+
for block in range(number_of_blocks):
233+
QC, index_offset_variational = variational_gate_block(
234+
QC,
235+
self.block_depth,
236+
self.block_width,
237+
int(block * self.block_width),
238+
index_offset_variational,
239+
variational_arrangement="ABA",
240+
)
241+
else:
242+
for block in range(number_of_blocks):
243+
if (
244+
shifting_factor + block * self.block_width + self.block_width
245+
<= self.num_qubits
246+
):
247+
QC, index_offset_variational = variational_gate_block(
248+
QC,
249+
self.block_depth,
250+
self.block_width,
251+
int(shifting_factor + block * self.block_width),
252+
index_offset_variational,
253+
variational_arrangement="ABA",
254+
)
255+
else: # if the block is out of the range of the qubits, we need to wrap around
256+
QC, index_offset_variational = variational_gate_block(
257+
QC,
258+
self.block_depth,
259+
self.block_width,
260+
int(shifting_factor + block * self.block_width),
261+
index_offset_variational,
262+
variational_arrangement="ABA",
263+
)
264+
QC.barrier()
265+
266+
return QC

src/squlearn/qnn/__init__.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
"""QNN module for classification and regression."""
22

3-
from .loss import SquaredLoss, VarianceLoss, ConstantLoss, ParameterRegularizationLoss
3+
from .loss import SquaredLoss, VarianceLoss, ConstantLoss, ParameterRegularizationLoss, ODELoss
44
from .qnnc import QNNClassifier
55
from .qnnr import QNNRegressor
66
from .training import get_variance_fac, get_lr_decay, ShotsFromRSTD
@@ -9,6 +9,7 @@
99
"SquaredLoss",
1010
"VarianceLoss",
1111
"ConstantLoss",
12+
"ODELoss",
1213
"ParameterRegularizationLoss",
1314
"QNNClassifier",
1415
"QNNRegressor",

0 commit comments

Comments
 (0)