20
20
from scipy import sparse as scisparse
21
21
22
22
from qiskit .opflow import PauliSumOp
23
+ from qiskit .quantum_info import SparsePauliOp , Statevector
23
24
from qiskit .quantum_info .operators .base_operator import BaseOperator
24
- from qiskit .quantum_info import Statevector
25
25
from qiskit .utils .validation import validate_min
26
26
27
27
from .eigensolver import Eigensolver , EigensolverResult
28
+ from ..exceptions import AlgorithmError
28
29
from ..list_or_dict import ListOrDict
29
30
30
31
logger = logging .getLogger (__name__ )
@@ -53,12 +54,12 @@ def __init__(
53
54
) -> None :
54
55
"""
55
56
Args:
56
- k: number of eigenvalues are to be computed, with a min. value of 1.
57
- filter_criterion: callable that allows to filter eigenvalues/eigenstates. Only feasible
57
+ k: Number of eigenvalues are to be computed, with a minimum value of 1.
58
+ filter_criterion: Callable that allows to filter eigenvalues/eigenstates. Only feasible
58
59
eigenstates are returned in the results. The callable has the signature
59
60
``filter(eigenstate, eigenvalue, aux_values)`` and must return a boolean to indicate
60
61
whether to keep this value in the final returned result or not. If the number of
61
- elements that satisfies the criterion is smaller than `k `, then the returned list will
62
+ elements that satisfies the criterion is smaller than ``k` `, then the returned list will
62
63
have fewer elements and can even be empty.
63
64
"""
64
65
validate_min ("k" , k , 1 )
@@ -69,8 +70,6 @@ def __init__(
69
70
70
71
self ._filter_criterion = filter_criterion
71
72
72
- self ._ret = NumPyEigensolverResult ()
73
-
74
73
@property
75
74
def k (self ) -> int :
76
75
"""Return k (number of eigenvalues requested)."""
@@ -109,69 +108,64 @@ def _check_set_k(self, operator: BaseOperator | PauliSumOp) -> None:
109
108
else :
110
109
self ._k = self ._in_k
111
110
112
- def _solve (self , operator : BaseOperator | PauliSumOp ) -> None :
111
+ def _solve (self , operator : BaseOperator | PauliSumOp ) -> tuple [ np . ndarray , np . ndarray ] :
113
112
if isinstance (operator , PauliSumOp ):
114
- sp_mat = operator .to_spmatrix ()
113
+ op_matrix = operator .to_spmatrix ()
114
+ else :
115
+ try :
116
+ op_matrix = operator .to_matrix (sparse = True )
117
+ except TypeError :
118
+ logger .debug (
119
+ "WARNING: operator of type `%s` does not support sparse matrices. "
120
+ "Trying dense computation" ,
121
+ type (operator ),
122
+ )
123
+ try :
124
+ op_matrix = operator .to_matrix ()
125
+ except AttributeError as ex :
126
+ raise AlgorithmError (f"Unsupported operator type `{ type (operator )} `." ) from ex
127
+
128
+ if isinstance (op_matrix , scisparse .csr_matrix ):
115
129
# If matrix is diagonal, the elements on the diagonal are the eigenvalues. Solve by sorting.
116
- if scisparse .csr_matrix (sp_mat .diagonal ()).nnz == sp_mat .nnz :
117
- diag = sp_mat .diagonal ()
130
+ if scisparse .csr_matrix (op_matrix .diagonal ()).nnz == op_matrix .nnz :
131
+ diag = op_matrix .diagonal ()
118
132
indices = np .argsort (diag )[: self ._k ]
119
133
eigval = diag [indices ]
120
- eigvec = np .zeros ((sp_mat .shape [0 ], self ._k ))
134
+ eigvec = np .zeros ((op_matrix .shape [0 ], self ._k ))
121
135
for i , idx in enumerate (indices ):
122
136
eigvec [idx , i ] = 1.0
123
137
else :
124
138
if self ._k >= 2 ** operator .num_qubits - 1 :
125
139
logger .debug (
126
140
"SciPy doesn't support to get all eigenvalues, using NumPy instead."
127
141
)
128
- if operator .is_hermitian ():
129
- eigval , eigvec = np .linalg .eigh (operator .to_matrix ())
130
- else :
131
- eigval , eigvec = np .linalg .eig (operator .to_matrix ())
142
+ eigval , eigvec = self ._solve_dense (operator .to_matrix ())
132
143
else :
133
- if operator .is_hermitian ():
134
- eigval , eigvec = scisparse .linalg .eigsh (sp_mat , k = self ._k , which = "SA" )
135
- else :
136
- eigval , eigvec = scisparse .linalg .eigs (sp_mat , k = self ._k , which = "SR" )
137
-
138
- indices = np .argsort (eigval )[: self ._k ]
139
- eigval = eigval [indices ]
140
- eigvec = eigvec [:, indices ]
144
+ eigval , eigvec = self ._solve_sparse (op_matrix , self ._k )
141
145
else :
142
- logger .debug ("SciPy not supported, using NumPy instead." )
143
-
144
- if operator .data .all () == operator .data .conj ().T .all ():
145
- eigval , eigvec = np .linalg .eigh (operator .data )
146
- else :
147
- eigval , eigvec = np .linalg .eig (operator .data )
148
-
149
- indices = np .argsort (eigval )[: self ._k ]
150
- eigval = eigval [indices ]
151
- eigvec = eigvec [:, indices ]
152
-
153
- self ._ret .eigenvalues = eigval
154
- self ._ret .eigenstates = eigvec .T
146
+ # Sparse SciPy matrix not supported, use dense NumPy computation.
147
+ eigval , eigvec = self ._solve_dense (operator .to_matrix ())
155
148
156
- def _get_ground_state_energy (self , operator : BaseOperator | PauliSumOp ) -> None :
157
- if self ._ret .eigenvalues is None or self ._ret .eigenstates is None :
158
- self ._solve (operator )
149
+ indices = np .argsort (eigval )[: self ._k ]
150
+ eigval = eigval [indices ]
151
+ eigvec = eigvec [:, indices ]
152
+ return eigval , eigvec .T
159
153
160
- def _get_energies (
161
- self ,
162
- operator : BaseOperator | PauliSumOp ,
163
- aux_operators : ListOrDict [ BaseOperator | PauliSumOp ] | None ,
164
- ) -> None :
165
- if self . _ret . eigenvalues is None or self . _ret . eigenstates is None :
166
- self . _solve ( operator )
154
+ @ staticmethod
155
+ def _solve_sparse ( op_matrix : scisparse . csr_matrix , k : int ) -> tuple [ np . ndarray , np . ndarray ]:
156
+ if ( op_matrix != op_matrix . H ). nnz == 0 :
157
+ # Operator is Hermitian
158
+ return scisparse . linalg . eigsh ( op_matrix , k = k , which = "SA" )
159
+ else :
160
+ return scisparse . linalg . eigs ( op_matrix , k = k , which = "SR" )
167
161
168
- if aux_operators is not None :
169
- aux_op_vals = []
170
- for i in range ( self . _k ):
171
- aux_op_vals . append (
172
- self . _eval_aux_operators ( aux_operators , self . _ret . eigenstates [ i ] )
173
- )
174
- self . _ret . aux_operators_evaluated = aux_op_vals
162
+ @ staticmethod
163
+ def _solve_dense ( op_matrix : np . ndarray ) -> tuple [ np . ndarray , np . ndarray ]:
164
+ if op_matrix . all () == op_matrix . conj (). T . all ( ):
165
+ # Operator is Hermitian
166
+ return np . linalg . eigh ( op_matrix )
167
+ else :
168
+ return np . linalg . eig ( op_matrix )
175
169
176
170
@staticmethod
177
171
def _eval_aux_operators (
@@ -190,25 +184,42 @@ def _eval_aux_operators(
190
184
else :
191
185
values = {}
192
186
key_op_iterator = aux_operators .items ()
187
+
193
188
for key , operator in key_op_iterator :
194
189
if operator is None :
195
190
continue
196
- value = 0.0
191
+
192
+ if operator .num_qubits is None or operator .num_qubits < 1 :
193
+ logger .info (
194
+ "The number of qubits of the %s operator must be greater than zero." , key
195
+ )
196
+ continue
197
+
198
+ op_matrix = None
197
199
if isinstance (operator , PauliSumOp ):
198
200
if operator .coeff != 0 :
199
- mat = operator .to_spmatrix ()
200
- # Terra doesn't support sparse yet, so do the matmul directly if so
201
- # This is necessary for the particle_hole and other chemistry tests because the
202
- # pauli conversions are 2^12th large and will OOM error if not sparse.
203
- if isinstance (mat , scisparse .spmatrix ):
204
- value = mat .dot (wavefn ).dot (np .conj (wavefn ))
205
- else :
206
- value = (
207
- Statevector (wavefn ).expectation_value (operator .primitive )
208
- * operator .coeff
209
- )
201
+ op_matrix = operator .to_spmatrix ()
210
202
else :
203
+ try :
204
+ op_matrix = operator .to_matrix (sparse = True )
205
+ except TypeError :
206
+ logger .debug (
207
+ "WARNING: operator of type `%s` does not support sparse matrices. "
208
+ "Trying dense computation" ,
209
+ type (operator ),
210
+ )
211
+ try :
212
+ op_matrix = operator .to_matrix ()
213
+ except AttributeError as ex :
214
+ raise AlgorithmError (f"Unsupported operator type { type (operator )} ." ) from ex
215
+
216
+ if isinstance (op_matrix , scisparse .csr_matrix ):
217
+ value = op_matrix .dot (wavefn ).dot (np .conj (wavefn ))
218
+ elif isinstance (op_matrix , np .ndarray ):
211
219
value = Statevector (wavefn ).expectation_value (operator )
220
+ else :
221
+ value = 0.0
222
+
212
223
value = value if np .abs (value ) > threshold else 0.0
213
224
# The value gets wrapped into a tuple: (mean, metadata).
214
225
# The metadata includes variance (and, for other eigensolvers, shots).
@@ -225,8 +236,12 @@ def compute_eigenvalues(
225
236
226
237
super ().compute_eigenvalues (operator , aux_operators )
227
238
239
+ if operator .num_qubits is None or operator .num_qubits < 1 :
240
+ raise AlgorithmError ("The number of qubits of the operator must be greater than zero." )
241
+
228
242
self ._check_set_k (operator )
229
- zero_op = PauliSumOp .from_list ([("I" , 1 )]).tensorpower (operator .num_qubits ) * 0.0
243
+
244
+ zero_op = SparsePauliOp (["I" * operator .num_qubits ], coeffs = [0.0 ])
230
245
if isinstance (aux_operators , list ) and len (aux_operators ) > 0 :
231
246
# For some reason Chemistry passes aux_ops with 0 qubits and paulis sometimes.
232
247
aux_operators = [zero_op if op == 0 else op for op in aux_operators ]
@@ -244,49 +259,51 @@ def compute_eigenvalues(
244
259
# need to consider all elements if a filter is set
245
260
self ._k = 2 ** operator .num_qubits
246
261
247
- self ._ret = NumPyEigensolverResult ()
248
- self ._solve (operator )
262
+ eigvals , eigvecs = self ._solve (operator )
249
263
250
264
# compute energies before filtering, as this also evaluates the aux operators
251
- self ._get_energies (operator , aux_operators )
265
+ if aux_operators is not None :
266
+ aux_op_vals = [
267
+ self ._eval_aux_operators (aux_operators , eigvecs [i ]) for i in range (self ._k )
268
+ ]
269
+ else :
270
+ aux_op_vals = None
252
271
253
272
# if a filter is set, loop over the given values and only keep
254
273
if self ._filter_criterion :
255
-
256
- eigvecs = []
257
- eigvals = []
258
- aux_ops = []
259
- cnt = 0
260
- for i in range (len (self ._ret .eigenvalues )):
261
- eigvec = self ._ret .eigenstates [i ]
262
- eigval = self ._ret .eigenvalues [i ]
263
- if self ._ret .aux_operators_evaluated is not None :
264
- aux_op = self ._ret .aux_operators_evaluated [i ]
274
+ filt_eigvals = []
275
+ filt_eigvecs = []
276
+ filt_aux_op_vals = []
277
+ count = 0
278
+ for i , (eigval , eigvec ) in enumerate (zip (eigvals , eigvecs )):
279
+ if aux_op_vals is not None :
280
+ aux_op_val = aux_op_vals [i ]
265
281
else :
266
- aux_op = None
267
- if self ._filter_criterion (eigvec , eigval , aux_op ):
268
- cnt += 1
269
- eigvecs += [eigvec ]
270
- eigvals += [eigval ]
271
- if self ._ret .aux_operators_evaluated is not None :
272
- aux_ops += [aux_op ]
273
- if cnt == k_orig :
282
+ aux_op_val = None
283
+
284
+ if self ._filter_criterion (eigvec , eigval , aux_op_val ):
285
+ count += 1
286
+ filt_eigvecs .append (eigvec )
287
+ filt_eigvals .append (eigval )
288
+ if aux_op_vals is not None :
289
+ filt_aux_op_vals .append (aux_op_val )
290
+
291
+ if count == k_orig :
274
292
break
275
293
276
- self ._ret .eigenstates = np .array (eigvecs )
277
- self ._ret .eigenvalues = np .array (eigvals )
278
- # conversion to np.array breaks in case of aux_ops
279
- self ._ret .aux_operators_evaluated = aux_ops
294
+ eigvals = np .array (filt_eigvals )
295
+ eigvecs = np .array (filt_eigvecs )
296
+ aux_op_vals = filt_aux_op_vals
280
297
281
298
self ._k = k_orig
282
299
283
- # evaluate ground state after filtering (in case a filter is set )
284
- self . _get_ground_state_energy ( operator )
285
- if self . _ret . eigenstates is not None :
286
- self . _ret . eigenstates = [ Statevector ( vec ) for vec in self . _ret . eigenstates ]
300
+ result = NumPyEigensolverResult ( )
301
+ result . eigenvalues = eigvals
302
+ result . eigenstates = [ Statevector ( vec ) for vec in eigvecs ]
303
+ result . aux_operators_evaluated = aux_op_vals
287
304
288
- logger .debug ("NumpyEigensolverResult:\n %s" , self . _ret )
289
- return self . _ret
305
+ logger .debug ("NumpyEigensolverResult:\n %s" , result )
306
+ return result
290
307
291
308
292
309
class NumPyEigensolverResult (EigensolverResult ):
0 commit comments