@@ -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 ))
0 commit comments