Skip to content

Commit 4278d25

Browse files
Cryorismanoelmarquesdongreenberg
authored
* make compatible with current Terra means no num_parameters keyword * use circuit params as var_form_params and prepare VQC for circuits * make VQC work with circuits * overwrite varform params on setter * move patching to setter * more tests, don't allow no parameters if the varform has no parameters it breaks VQE since we divide by the number of parameters * update docstrings * add warning if feature dim is 0 * add test for feature dim = 0 * fix unused variable * use params provided in circuit don't reassign * begin adding circuit to qsvm * num_parameters is now circuit attribute * adjust to new circuit methods num_parameters and assign_parameters is now available in the circuit base class * run tests for varform and QC * remove unused imports * add VQC tests for circuits * make QSVM run on circuits * sort parameters in wine test for reproducibility * adress Steve's comments * add changelog, obey PEP and dont use `None or ..` * remove the op+varform check to construct_circuit Co-authored-by: Manoel Marques <[email protected]> Co-authored-by: Donny Greenberg <[email protected]>
1 parent 7aa44a1 commit 4278d25

File tree

4 files changed

+368
-80
lines changed

4 files changed

+368
-80
lines changed

qiskit/aqua/algorithms/classifiers/qsvm/qsvm.py

Lines changed: 38 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -41,8 +41,7 @@
4141

4242

4343
class QSVM(QuantumAlgorithm):
44-
"""
45-
The Quantum SVM algorithm.
44+
"""Quantum SVM algorithm.
4645
4746
A key concept in classification methods is that of a kernel. Data cannot typically be
4847
separated by a hyperplane in its original space. A common technique used to find such a
@@ -76,7 +75,7 @@ class QSVM(QuantumAlgorithm):
7675

7776
BATCH_SIZE = 1000
7877

79-
def __init__(self, feature_map: FeatureMap,
78+
def __init__(self, feature_map: Union[QuantumCircuit, FeatureMap],
8079
training_dataset: Optional[Dict[str, np.ndarray]] = None,
8180
test_dataset: Optional[Dict[str, np.ndarray]] = None,
8281
datapoints: Optional[np.ndarray] = None,
@@ -122,6 +121,17 @@ def __init__(self, feature_map: FeatureMap,
122121
self.feature_map = feature_map
123122
self.num_qubits = self.feature_map.num_qubits
124123

124+
if isinstance(feature_map, QuantumCircuit):
125+
# patch the feature dimension attribute to the circuit
126+
self.feature_map.feature_dimension = len(feature_map.parameters)
127+
if not hasattr(feature_map, 'ordered_parameters'):
128+
self.feature_map.ordered_parameters = list(feature_map.parameters)
129+
self.feature_map_params_x = ParameterVector('x', self.feature_map.feature_dimension)
130+
self.feature_map_params_y = ParameterVector('y', self.feature_map.feature_dimension)
131+
else:
132+
self.feature_map_params_x = ParameterVector('x', feature_map.feature_dimension)
133+
self.feature_map_params_y = ParameterVector('y', feature_map.feature_dimension)
134+
125135
if multiclass_extension is None:
126136
qsvm_instance = _QSVM_Binary(self)
127137
else:
@@ -132,8 +142,7 @@ def __init__(self, feature_map: FeatureMap,
132142

133143
@staticmethod
134144
def _construct_circuit(x, feature_map, measurement, is_statevector_sim=False):
135-
"""
136-
If `is_statevector_sim` is True, we only build the circuits for Psi(x1)|0> rather than
145+
"""If `is_statevector_sim` is True, we only build the circuits for Psi(x1)|0> rather than
137146
Psi(x2)^dagger Psi(x1)|0>.
138147
"""
139148
x1, x2 = x
@@ -145,9 +154,20 @@ def _construct_circuit(x, feature_map, measurement, is_statevector_sim=False):
145154
qc = QuantumCircuit(q, c)
146155

147156
# write input state from sample distribution
148-
qc += feature_map.construct_circuit(x1, q)
157+
if isinstance(feature_map, FeatureMap):
158+
qc += feature_map.construct_circuit(x1, q)
159+
else:
160+
psi_x1 = _assign_parameters(feature_map, x1)
161+
qc.append(psi_x1.to_instruction(), qc.qubits)
162+
149163
if not is_statevector_sim:
150-
qc += feature_map.construct_circuit(x2, q).inverse()
164+
# write input state from sample distribution
165+
if isinstance(feature_map, FeatureMap):
166+
qc += feature_map.construct_circuit(x2, q).inverse()
167+
else:
168+
psi_x2_dag = _assign_parameters(feature_map, x2)
169+
qc.append(psi_x2_dag.to_instruction().inverse(), qc.qubits)
170+
151171
if measurement:
152172
qc.barrier(q)
153173
qc.measure(q, c)
@@ -207,7 +227,10 @@ def get_kernel_matrix(quantum_instance, feature_map, x1_vec, x2_vec=None):
207227
numpy.ndarray: 2-D matrix, N1xN2
208228
"""
209229

210-
use_parameterized_circuits = feature_map.support_parameterized_circuit
230+
if isinstance(feature_map, QuantumCircuit):
231+
use_parameterized_circuits = True
232+
else:
233+
use_parameterized_circuits = feature_map.support_parameterized_circuit
211234

212235
if x2_vec is None:
213236
is_symmetric = True
@@ -488,3 +511,10 @@ def setup_datapoint(self, datapoints):
488511
if not isinstance(datapoints, np.ndarray):
489512
datapoints = np.asarray(datapoints)
490513
self.datapoints = datapoints
514+
515+
516+
def _assign_parameters(circuit, params):
517+
if not hasattr(circuit, 'ordered_parameters'):
518+
raise AttributeError('Circuit needs the attribute `ordered_parameters`.')
519+
param_dict = dict(zip(circuit.ordered_parameters, params))
520+
return circuit.assign_parameters(param_dict)

qiskit/aqua/algorithms/classifiers/vqc.py

Lines changed: 110 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -12,16 +12,17 @@
1212
# copyright notice, and modified files need to carry a notice indicating
1313
# that they have been altered from the originals.
1414

15-
""" The Variational Quantum Classifier algorithm """
15+
"""The Variational Quantum Classifier algorithm."""
1616

1717
from typing import Optional, Callable, Dict, Union
18+
import warnings
1819
import logging
1920
import math
2021
import numpy as np
2122

2223
from sklearn.utils import shuffle
2324
from qiskit import ClassicalRegister, QuantumCircuit, QuantumRegister
24-
from qiskit.circuit import ParameterVector
25+
from qiskit.circuit import ParameterVector, ParameterExpression
2526

2627
from qiskit.providers import BaseBackend
2728
from qiskit.aqua import QuantumInstance, AquaError
@@ -38,8 +39,7 @@
3839

3940

4041
class VQC(VQAlgorithm):
41-
"""
42-
The Variational Quantum Classifier algorithm.
42+
"""The Variational Quantum Classifier algorithm.
4343
4444
Similar to :class:`QSVM`, the VQC algorithm also applies to classification problems.
4545
VQC uses the variational method to solve such problems in a quantum processor. Specifically,
@@ -50,8 +50,8 @@ class VQC(VQAlgorithm):
5050
def __init__(
5151
self,
5252
optimizer: Optimizer,
53-
feature_map: FeatureMap,
54-
var_form: VariationalForm,
53+
feature_map: Union[QuantumCircuit, FeatureMap],
54+
var_form: Union[QuantumCircuit, VariationalForm],
5555
training_dataset: Dict[str, np.ndarray],
5656
test_dataset: Optional[Dict[str, np.ndarray]] = None,
5757
datapoints: Optional[np.ndarray] = None,
@@ -75,11 +75,24 @@ def __init__(
7575
These are: the evaluation count, parameters of the variational form,
7676
the evaluated value, the index of data batch.
7777
quantum_instance: Quantum Instance or Backend
78+
7879
Note:
7980
We use `label` to denotes numeric results and `class` the class names (str).
81+
8082
Raises:
81-
AquaError: invalid input
83+
AquaError: Missing feature map or missing training dataset.
8284
"""
85+
# VariationalForm is not deprecated on level of the VQAlgorithm yet as UCCSD still
86+
# derives from there, therefore we're adding a warning here
87+
if isinstance(var_form, VariationalForm):
88+
warnings.warn('The qiskit.aqua.components.variational_form.VariationalForm object as '
89+
'input for the VQC is deprecated as of 0.7.0 and will be removed no '
90+
'earlier than 3 months after the release. You should pass a '
91+
'QuantumCircuit object instead. '
92+
'See also qiskit.circuit.library.n_local for a collection of '
93+
'suitable circuits.',
94+
DeprecationWarning, stacklevel=2)
95+
8396
super().__init__(
8497
var_form=var_form,
8598
optimizer=optimizer,
@@ -120,41 +133,61 @@ def __init__(
120133

121134
self._eval_count = 0
122135
self._ret = {}
123-
self._feature_map = feature_map
124-
self._num_qubits = feature_map.num_qubits
125-
self._var_form_params = ParameterVector('θ', self._var_form.num_parameters)
126-
self._feature_map_params = ParameterVector('x', self._feature_map.feature_dimension)
127136
self._parameterized_circuits = None
128137

138+
self.feature_map = feature_map
139+
129140
def construct_circuit(self, x, theta, measurement=False):
130-
"""
131-
Construct circuit based on data and parameters in variational form.
141+
"""Construct circuit based on data and parameters in variational form.
132142
133143
Args:
134144
x (numpy.ndarray): 1-D array with D dimension
135145
theta (list[numpy.ndarray]): list of 1-D array, parameters sets for variational form
136146
measurement (bool): flag to add measurement
147+
137148
Returns:
138149
QuantumCircuit: the circuit
150+
151+
Raises:
152+
AquaError: If ``x`` and ``theta`` share parameters with the same name.
139153
"""
154+
# check x and theta do not have parameters of the same name
155+
x_names = [param.name for param in x if isinstance(param, ParameterExpression)]
156+
theta_names = [param.name for param in theta if isinstance(param, ParameterExpression)]
157+
if any(x_name in theta_names for x_name in x_names):
158+
raise AquaError('Variational form and feature map are not allowed to share parameters '
159+
'with the same name!')
160+
140161
qr = QuantumRegister(self._num_qubits, name='q')
141162
cr = ClassicalRegister(self._num_qubits, name='c')
142163
qc = QuantumCircuit(qr, cr)
143-
qc += self._feature_map.construct_circuit(x, qr)
144-
qc += self._var_form.construct_circuit(theta, qr)
164+
165+
if isinstance(self.feature_map, QuantumCircuit):
166+
param_dict = dict(zip(self._feature_map_params, x))
167+
circuit = self._feature_map.assign_parameters(param_dict, inplace=False)
168+
qc.append(circuit.to_instruction(), qr)
169+
else:
170+
qc += self._feature_map.construct_circuit(x, qr)
171+
172+
if isinstance(self.var_form, QuantumCircuit):
173+
param_dict = dict(zip(self._var_form_params, theta))
174+
circuit = self._var_form.assign_parameters(param_dict, inplace=False)
175+
qc.append(circuit.to_instruction(), qr)
176+
else:
177+
qc += self._var_form.construct_circuit(theta, qr)
145178

146179
if measurement:
147180
qc.barrier(qr)
148181
qc.measure(qr, cr)
149182
return qc
150183

151184
def _get_prediction(self, data, theta):
152-
"""
153-
Make prediction on data based on each theta.
185+
"""Make prediction on data based on each theta.
154186
155187
Args:
156188
data (numpy.ndarray): 2-D array, NxD, N data points, each with D dimension
157189
theta (list[numpy.ndarray]): list of 1-D array, parameters sets for variational form
190+
158191
Returns:
159192
Union(numpy.ndarray or [numpy.ndarray], numpy.ndarray or [numpy.ndarray]):
160193
list of NxK array, list of Nx1 array
@@ -165,10 +198,12 @@ def _get_prediction(self, data, theta):
165198
theta_sets = np.split(theta, num_theta_sets)
166199

167200
def _build_parameterized_circuits():
168-
if self._var_form.support_parameterized_circuit and \
169-
self._feature_map.support_parameterized_circuit and \
170-
self._parameterized_circuits is None:
201+
var_form_support = isinstance(self._var_form, QuantumCircuit) \
202+
or self._var_form.support_parameterized_circuit
203+
feat_map_support = isinstance(self._feature_map, QuantumCircuit) \
204+
or self._feature_map.support_parameterized_circuit
171205

206+
if var_form_support and feat_map_support and self._parameterized_circuits is None:
172207
parameterized_circuits = self.construct_circuit(
173208
self._feature_map_params, self._var_form_params,
174209
measurement=not self._quantum_instance.is_statevector)
@@ -179,9 +214,9 @@ def _build_parameterized_circuits():
179214
for thet in theta_sets:
180215
for datum in data:
181216
if self._parameterized_circuits is not None:
182-
curr_params = {self._feature_map_params: datum,
183-
self._var_form_params: thet}
184-
circuit = self._parameterized_circuits.bind_parameters(curr_params)
217+
curr_params = dict(zip(self._feature_map_params, datum))
218+
curr_params.update(dict(zip(self._var_form_params, thet)))
219+
circuit = self._parameterized_circuits.assign_parameters(curr_params)
185220
else:
186221
circuit = self.construct_circuit(
187222
datum, thet, measurement=not self._quantum_instance.is_statevector)
@@ -302,8 +337,10 @@ def train(self, data, labels, quantum_instance=None, minibatch_size=-1):
302337
# temporary fix: this code should be unified with the gradient api in optimizer.py
303338
def _gradient_function_wrapper(self, theta):
304339
"""Compute and return the gradient at the point theta.
340+
305341
Args:
306342
theta (numpy.ndarray): 1-d array
343+
307344
Returns:
308345
numpy.ndarray: 1-d array with the same shape as theta. The gradient computed
309346
"""
@@ -352,6 +389,7 @@ def test(self, data, labels, quantum_instance=None, minibatch_size=-1, params=No
352389
quantum_instance (QuantumInstance): quantum backend with all setting
353390
minibatch_size (int): the size of each minibatched accuracy evaluation
354391
params (list): list of parameters to populate in the variational form
392+
355393
Returns:
356394
float: classification accuracy
357395
"""
@@ -391,6 +429,7 @@ def predict(self, data, quantum_instance=None, minibatch_size=-1, params=None):
391429
quantum_instance (QuantumInstance): quantum backend with all setting
392430
minibatch_size (int): the size of each minibatched accuracy evaluation
393431
params (list): list of parameters to populate in the variational form
432+
394433
Returns:
395434
list: for each data point, generates the predicted probability for each class
396435
list: for each data point, generates the predicted label (that with the highest prob)
@@ -445,6 +484,9 @@ def get_optimal_circuit(self):
445484
if 'opt_params' not in self._ret:
446485
raise AquaError("Cannot find optimal circuit before running "
447486
"the algorithm to find optimal params.")
487+
if isinstance(self._var_form, QuantumCircuit):
488+
param_dict = dict(zip(self._var_form_params, self._ret['opt_params']))
489+
return self._var_form.assign_parameters(param_dict)
448490
return self._var_form.construct_circuit(self._ret['opt_params'])
449491

450492
def get_optimal_vector(self):
@@ -469,6 +511,44 @@ def get_optimal_vector(self):
469511
self._ret['min_vector'] = ret.get_counts(qc)
470512
return self._ret['min_vector']
471513

514+
@property
515+
def feature_map(self) -> Optional[Union[FeatureMap, QuantumCircuit]]:
516+
"""Return the feature map."""
517+
return self._feature_map
518+
519+
@feature_map.setter
520+
def feature_map(self, feature_map: Union[FeatureMap, QuantumCircuit]):
521+
"""Set the feature map.
522+
523+
Also sets the number of qubits, the internally stored feature map parameters and,
524+
if the feature map is a circuit, the order of the parameters.
525+
"""
526+
if isinstance(feature_map, QuantumCircuit):
527+
# patch the feature dimension to the circuit
528+
feature_map.feature_dimension = len(feature_map.parameters)
529+
530+
# store the parameters
531+
self._num_qubits = feature_map.num_qubits
532+
self._feature_map_params = list(feature_map.parameters)
533+
self._feature_map = feature_map
534+
elif isinstance(feature_map, FeatureMap):
535+
warnings.warn('The qiskit.aqua.components.feature_maps.FeatureMap object is deprecated '
536+
'as of 0.7.0 and will be removed no earlier than 3 months after the '
537+
'release. You should pass a QuantumCircuit object instead. '
538+
'See also qiskit.circuit.library.data_preparation for a collection of '
539+
'suitable circuits.',
540+
DeprecationWarning, stacklevel=2)
541+
542+
self._num_qubits = feature_map.num_qubits
543+
self._feature_map_params = ParameterVector('x', length=feature_map.feature_dimension)
544+
self._feature_map = feature_map
545+
else:
546+
raise ValueError('Unsupported type {} of feature_map.'.format(type(feature_map)))
547+
548+
if self._feature_map and self._feature_map.feature_dimension == 0:
549+
warnings.warn('The feature map has no parameters that can be optimized to represent '
550+
'the data. This will most likely cause the VQC to fail.')
551+
472552
@property
473553
def optimal_params(self):
474554
""" returns optimal parameters """
@@ -570,15 +650,18 @@ def assign_label(measured_key, num_classes):
570650

571651

572652
def cost_estimate(probs, gt_labels, shots=None): # pylint: disable=unused-argument
573-
"""Calculate cross entropy
574-
# shots is kept since it may be needed in future.
653+
"""Calculate cross entropy.
575654
576655
Args:
577656
shots (int): the number of shots used in quantum computing
578657
probs (numpy.ndarray): NxK array, N is the number of data and K is the number of class
579658
gt_labels (numpy.ndarray): Nx1 array
659+
580660
Returns:
581661
float: cross entropy loss between estimated probs and gt_labels
662+
663+
Note:
664+
shots is kept since it may be needed in future.
582665
"""
583666
mylabels = np.zeros(probs.shape)
584667
for i in range(gt_labels.shape[0]):
@@ -603,6 +686,7 @@ def cost_estimate_sigmoid(shots, probs, gt_labels):
603686
shots (int): the number of shots used in quantum computing
604687
probs (numpy.ndarray): NxK array, N is the number of data and K is the number of class
605688
gt_labels (numpy.ndarray): Nx1 array
689+
606690
Returns:
607691
float: sigmoid cross entropy loss between estimated probs and gt_labels
608692
"""
@@ -619,6 +703,7 @@ def return_probabilities(counts, num_classes):
619703
Args:
620704
counts (list[dict]): N data and each with a dict recording the counts
621705
num_classes (int): number of classes
706+
622707
Returns:
623708
numpy.ndarray: NxK array
624709
"""

0 commit comments

Comments
 (0)