Skip to content

Commit f21f3ca

Browse files
Merge pull request #330 from sQUlearn/develop
v0.8.3
2 parents 0252362 + f93d987 commit f21f3ca

9 files changed

Lines changed: 1004 additions & 79 deletions

File tree

docs/modules/classes.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -168,6 +168,7 @@ Quantum Kernel Core
168168

169169
kernel.lowlevel_kernel.FidelityKernel
170170
kernel.lowlevel_kernel.ProjectedQuantumKernel
171+
kernel.lowlevel_kernel.KernelOptimizer
171172

172173
.. automodule:: squlearn.kernel.loss
173174
:no-members:

src/squlearn/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
from .util import Executor
44
from . import observables, encoding_circuit, kernel, optimizers, qnn, util
55

6-
__version__ = "0.8.2"
6+
__version__ = "0.8.3"
77

88
__all__ = [
99
"Executor",

src/squlearn/encoding_circuit/encoding_circuit_base.py

Lines changed: 167 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,9 @@ def get_circuit(
7373
parameters: Union[ParameterVector, np.ndarray],
7474
) -> QuantumCircuit:
7575
"""
76-
Return the circuit encoding circuit (has to be overwritten, otherwise a NotImplementedError is thrown)
76+
Return the circuit encoding circuit
77+
78+
Has to be overwritten, otherwise a NotImplementedError is thrown
7779
7880
Args:
7981
features Union[ParameterVector,np.ndarray]: Input vector of the features
@@ -154,13 +156,78 @@ def set_params(self, **params) -> EncodingCircuitBase:
154156

155157
return self
156158

159+
def inverse(self):
160+
"""
161+
Returns the inverse of the encoding circuit.
162+
163+
Returns:
164+
The inverse of the encoding circuit
165+
"""
166+
167+
class InvertedEncodingCircuit(EncodingCircuitBase):
168+
def __init__(self, encoding_circuit: EncodingCircuitBase):
169+
super().__init__(encoding_circuit.num_qubits, encoding_circuit.num_features)
170+
self._encoding_circuit = encoding_circuit
171+
172+
@property
173+
def num_parameters(self) -> int:
174+
"""Returns the number of trainable parameters of the encoding circuit."""
175+
return self._encoding_circuit.num_parameters
176+
177+
@property
178+
def parameter_bounds(self) -> np.ndarray:
179+
"""Returns the bounds of the trainable parameters of the encoding circuit."""
180+
return self._encoding_circuit.parameter_bounds
181+
182+
@property
183+
def feature_bounds(self) -> np.ndarray:
184+
"""Returns the bounds of the features of the encoding circuit."""
185+
return self._encoding_circuit.feature_bounds
186+
187+
def generate_initial_parameters(self, seed: Union[int, None] = None) -> np.ndarray:
188+
"""
189+
Generates random parameters for the encoding circuit
190+
191+
Args:
192+
seed (Union[int,None]): Seed for the random number generator
193+
194+
Return:
195+
Returns the randomly generated parameters
196+
"""
197+
return self._encoding_circuit.generate_initial_parameters(seed)
198+
199+
def get_circuit(
200+
self,
201+
features: Union[ParameterVector, np.ndarray],
202+
parameters: Union[ParameterVector, np.ndarray],
203+
) -> QuantumCircuit:
204+
"""
205+
Returns the inverse circuit of the encoding circuit
206+
207+
Args:
208+
features Union[ParameterVector,np.ndarray]: Input vector of the features
209+
from which the gate inputs are obtained
210+
param_vec Union[ParameterVector,np.ndarray]: Input vector of the parameters
211+
from which the gate inputs are obtained
212+
213+
Return:
214+
Returns the inverse circuit of the encoding circuit in qiskit QuantumCircuit
215+
format
216+
"""
217+
circ = self._encoding_circuit.get_circuit(features, parameters)
218+
return circ.inverse()
219+
220+
return InvertedEncodingCircuit(self)
221+
157222
def __mul__(self, x):
158223
return self.__add__(x)
159224

160225
def __add__(self, x):
226+
return self.compose(x, concatenate_features=False, concatenate_parameters=True)
227+
228+
def compose(self, x, concatenate_features=True, concatenate_parameters=False):
161229
"""
162-
Overwrites the a + b function, such that the addition of
163-
encoding circuits returns the composition of both encoding circuits.
230+
Composition of encoding circuits with options for handling features and parameters
164231
165232
Number of qubits and features have to be equal in both encoding circuits!
166233
The special function and properties of the both encoding circuits are lost
@@ -182,7 +249,8 @@ class ComposedEncodingCircuit(EncodingCircuitBase):
182249
Special class for composed encoding circuits.
183250
184251
Args:
185-
num_qubits: num qubits for both encoding circuits (necessary for scikit-learn interface)
252+
num_qubits (int): num qubits for both encoding circuits
253+
(necessary for scikit-learn interface)
186254
ec1 (EncodingCircuitBase): right / first encoding circuit
187255
ec2 (EncodingCircuitBase): left / second encoding circuit
188256
"""
@@ -226,17 +294,66 @@ def num_parameters(self) -> int:
226294
227295
Is equal to the sum of both trainable parameters.
228296
"""
229-
return self.ec1.num_parameters + self.ec2.num_parameters
297+
if concatenate_parameters:
298+
return self.ec1.num_parameters + self.ec2.num_parameters
299+
else:
300+
return max(self.ec1.num_parameters, self.ec2.num_parameters)
301+
302+
@property
303+
def num_features(self) -> int:
304+
"""The dimension of the features in the encoding circuit."""
305+
if concatenate_features:
306+
return self.ec1.num_features + self.ec2.num_features
307+
else:
308+
return max(self.ec1.num_features, self.ec2.num_features)
230309

231310
@property
232311
def parameter_bounds(self) -> np.ndarray:
233312
"""Returns the bounds of the trainable parameters of composed encoding circuit.
234313
235314
Is equal to the sum of both bounds.
236315
"""
237-
return np.concatenate(
238-
(self.ec1.parameter_bounds, self.ec2.parameter_bounds), axis=0
239-
)
316+
if concatenate_parameters:
317+
return np.concatenate(
318+
(self.ec1.parameter_bounds, self.ec2.parameter_bounds), axis=0
319+
)
320+
else:
321+
# We compare self.ec1.parameter_bounds and self.ec2.parameter_bounds,
322+
# we return a new array,
323+
# with the shape of the largest array, and the minimum values of the two arrays
324+
# for the first column (lower bound),
325+
# and the maximum values of the two arrays for the second column (upper bound)
326+
327+
# Extend parameter bounds to the shape of the largest array with np.pad
328+
parameter_bounds1_extended = np.pad(
329+
self.ec1.parameter_bounds,
330+
((0, self.num_parameters - self.ec1.num_parameters), (0, 0)),
331+
constant_values=np.nan,
332+
)
333+
parameter_bounds2_extended = np.pad(
334+
self.ec2.parameter_bounds,
335+
((0, self.num_parameters - self.ec2.num_parameters), (0, 0)),
336+
constant_values=np.nan,
337+
)
338+
339+
# Compute the minimum lower bound values for the first column and maximum upper
340+
# bounds for the second column
341+
parameter_bounds3_first_col = np.nanmin(
342+
np.array(
343+
[parameter_bounds1_extended[:, 0], parameter_bounds2_extended[:, 0]]
344+
),
345+
axis=0,
346+
)
347+
parameter_bounds3_second_col = np.nanmax(
348+
np.array(
349+
[parameter_bounds1_extended[:, 1], parameter_bounds2_extended[:, 1]]
350+
),
351+
axis=0,
352+
)
353+
# Stack the results into the final array
354+
return np.column_stack(
355+
(parameter_bounds3_first_col, parameter_bounds3_second_col)
356+
)
240357

241358
@property
242359
def feature_bounds(self) -> np.ndarray:
@@ -274,13 +391,20 @@ def generate_initial_parameters(self, seed: Union[int, None] = None) -> np.ndarr
274391
Returns the randomly generated parameters
275392
"""
276393

277-
return np.concatenate(
278-
(
279-
self.ec1.generate_initial_parameters(seed),
280-
self.ec2.generate_initial_parameters(seed),
281-
),
282-
axis=0,
283-
)
394+
if concatenate_parameters:
395+
return np.concatenate(
396+
(
397+
self.ec1.generate_initial_parameters(seed),
398+
self.ec2.generate_initial_parameters(seed),
399+
),
400+
axis=0,
401+
)
402+
else:
403+
if self.num_parameters == 0:
404+
return np.array([])
405+
r = np.random.RandomState(seed)
406+
bounds = self.parameter_bounds
407+
return r.uniform(low=bounds[:, 0], high=bounds[:, 1])
284408

285409
def get_params(self, deep: bool = True) -> dict:
286410
"""
@@ -291,7 +415,7 @@ def get_params(self, deep: bool = True) -> dict:
291415
292416
Args:
293417
deep (bool): If True, also the parameters for
294-
contained objects are returned (default=True).
418+
contained objects are returned (default=True).
295419
296420
Return:
297421
Dictionary with hyper-parameters and values.
@@ -357,14 +481,35 @@ def get_circuit(
357481
from which the gate inputs are obtained
358482
359483
Return:
360-
Returns the circuit of the composed encoding circuits in qiskit QuantumCircuit format
484+
Returns the circuit of the composed encoding circuits in qiskit QuantumCircuit
485+
format
361486
"""
362-
363-
circ1 = self.ec1.get_circuit(
364-
features[: self.ec1.num_features], parameters[: self.ec1.num_parameters]
365-
)
487+
if concatenate_features:
488+
features_c1, features_c2 = (
489+
features[: self.ec1.num_features],
490+
features[self.ec1.num_features :],
491+
)
492+
else:
493+
features_c1, features_c2 = (
494+
features[: self.ec1.num_features],
495+
features[: self.ec2.num_features],
496+
)
497+
498+
if concatenate_parameters:
499+
parameters_c1, parameters_c2 = (
500+
parameters[: self.ec1.num_parameters],
501+
parameters[self.ec1.num_parameters :],
502+
)
503+
else:
504+
parameters_c1, parameters_c2 = (
505+
parameters[: self.ec1.num_parameters],
506+
parameters[: self.ec1.num_parameters],
507+
)
508+
509+
circ1 = self.ec1.get_circuit(features_c1, parameters_c1)
366510
circ2 = self.ec2.get_circuit(
367-
features[: self.ec2.num_features], parameters[self.ec1.num_parameters :]
511+
features_c2,
512+
parameters_c2, # Only line that changes, to include the new features
368513
)
369514

370515
return circ1.compose(circ2, range(self.ec1.num_qubits))

src/squlearn/encoding_circuit/transpiled_encoding_circuit.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -49,8 +49,8 @@ def __init__(
4949
self._backend = backend
5050
self._transpile_func = transpile_func
5151

52-
self._x = ParameterVector("x", self._encoding_circuit.num_features)
53-
self._p = ParameterVector("p", self._encoding_circuit.num_parameters)
52+
self._x = ParameterVector("x_", self._encoding_circuit.num_features)
53+
self._p = ParameterVector("p_", self._encoding_circuit.num_parameters)
5454

5555
self._circuit = decompose_to_std(self._encoding_circuit.get_circuit(self._x, self._p))
5656

0 commit comments

Comments
 (0)