@@ -147,9 +147,14 @@ def test_eigs_lobpcg_failures(par):
147147 when neigs is too large to avoid lobpcg dense solver
148148 """
149149 nx = 10 * par ["nx" ]
150- Op = Diagonal (np .arange (nx , 0 , - 1 ), dtype = par ["dtype" ])
151150
152- # lobpcg cannot be used for non-symmetric operators
151+ # lobpcg cannot be used for non-symmetric explicit operators
152+ Op = MatrixMult (np .diag (np .arange (nx , 0.0 , - 1.0 )), dtype = np .dtype (par ["dtype" ]))
153+ with pytest .raises (ValueError , match = "non real-symmetric or complex-hermitian" ):
154+ Op .eigs (neigs = 2 , symmetric = False , uselobpcg = True , backend = backend )
155+
156+ # lobpcg cannot be used for non-symmetric linear operators
157+ Op = Diagonal (np .arange (nx , 0 , - 1 ), dtype = par ["dtype" ])
153158 with pytest .raises (ValueError , match = "non real-symmetric or complex-hermitian" ):
154159 Op .eigs (neigs = 2 , symmetric = False , uselobpcg = True , backend = backend )
155160
@@ -166,6 +171,36 @@ def test_eigs_lobpcg_failures(par):
166171 backend = backend ,
167172 )
168173
174+ # lobpcg size check for non-square operators (based on number of columns)
175+ Op = VStack ([Op , Zero (par ["ny" ], nx )])
176+ with pytest .raises (ValueError , match = "uselobpcg=True requires the operator size" ):
177+ Op .eigs (neigs = nx // 5 + 1 , uselobpcg = True , backend = backend )
178+ with pytest .raises (ValueError , match = "uselobpcg=True requires the operator size" ):
179+ Op .eigs (
180+ neigs = nx // 5 ,
181+ uselobpcg = True ,
182+ Y = np .eye (nx , 5 , dtype = par ["dtype" ]),
183+ backend = backend ,
184+ )
185+
186+
187+ @pytest .mark .skipif (int (os .environ .get ("TEST_CUPY_PYLOPS" , 0 )) == 0 , reason = "CuPy only" )
188+ @pytest .mark .parametrize ("par" , [(par1 ), (par1j )])
189+ def test_eigs_cupy_nonsymmetric_failures (par ):
190+ """Fail non-symmetric square operators with CuPy backend"""
191+ nx = 10 * par ["nx" ]
192+ diag = np .arange (nx , 0.0 , - 1.0 )
193+
194+ # explicit=True (limited number of eigenvalues)
195+ Op = MatrixMult (np .diag (diag ), dtype = np .dtype (par ["dtype" ]))
196+ with pytest .raises (NotImplementedError , match = "not available with CuPy" ):
197+ Op .eigs (neigs = 3 , symmetric = False , backend = "cupy" )
198+
199+ # explicit=False
200+ Op = Diagonal (diag , dtype = np .dtype (par ["dtype" ]))
201+ with pytest .raises (NotImplementedError , match = "not available with CuPy" ):
202+ Op .eigs (neigs = 3 , symmetric = False , backend = "cupy" )
203+
169204
170205@pytest .mark .parametrize ("par" , [(par1 ), (par1j )])
171206@pytest .mark .parametrize ("symmetric" , [True , False ])
@@ -190,25 +225,43 @@ def test_eigs_square(par, symmetric):
190225 Op = MatrixMult (np .diag (diag ), dtype = np .dtype (par ["dtype" ]))
191226
192227 eigs = Op .eigs (symmetric = symmetric , backend = backend ) # all
193- assert_array_almost_equal (eigs , diag [: eigs . size ] , decimal = 3 )
228+ assert_array_almost_equal (eigs , diag , decimal = 3 )
194229
195230 eigs = Op .eigs (neigs = 3 , symmetric = symmetric , backend = backend ) # top-k
196231 assert_array_almost_equal (eigs , diag [:3 ], decimal = 3 )
197232
233+ # neigs equal to size of operator returns all eigenvalues
234+ eigs = Op .eigs (neigs = nx , symmetric = symmetric , backend = backend )
235+ assert_array_almost_equal (eigs , diag , decimal = 3 )
236+
198237 if backend == "numpy" :
199238 # CuPy cannot compute a single eigenvalue for square, non-symmetric matrix
200- cond = Op .cond (symmetric = symmetric , backend = backend )
239+ cond = Op .cond (backend = backend )
201240 assert_array_almost_equal (np .real (cond ), nx , decimal = 3 )
202241
242+ if symmetric :
243+ # lobpcg
244+ eigs = Op .eigs (
245+ neigs = 3 , symmetric = True , uselobpcg = True , niter = 50 , backend = backend
246+ )
247+ assert_array_almost_equal (eigs , diag [:3 ], decimal = 3 )
248+
203249 # explicit=False
204250 Op = Diagonal (diag , dtype = np .dtype (par ["dtype" ]))
205251
206252 if backend == "numpy" :
207253 # CuPy cannot compute neigs-2 eigenvalues (seems to be
208254 # a bug in CuPy's eigsh)
209255 eigs = Op .eigs (symmetric = symmetric , backend = backend )
256+ assert eigs .size == nx - 2 # neigs=None is clamped to N-2 (no warning)
210257 assert_array_almost_equal (eigs , diag [: eigs .size ], decimal = 3 )
211258
259+ # neigs too large is clamped to N-2 (with warning)
260+ with pytest .warns (UserWarning , match = "exceeds the number of columns" ):
261+ eigs = Op .eigs (neigs = nx , symmetric = symmetric , backend = backend )
262+ assert eigs .size == nx - 2
263+ assert_array_almost_equal (eigs , diag [: nx - 2 ], decimal = 3 )
264+
212265 eigs = Op .eigs (neigs = 3 , symmetric = symmetric , backend = backend ) # top-k
213266 assert_array_almost_equal (eigs , diag [:3 ], decimal = 3 )
214267
@@ -218,8 +271,8 @@ def test_eigs_square(par, symmetric):
218271 cond = Op .cond (backend = backend )
219272 assert_array_almost_equal (np .real (cond ), nx , decimal = 3 )
220273
221- # use lobpcg for symmetric case
222274 if symmetric :
275+ # lobpcg
223276 eigs1 = Op .eigs (
224277 neigs = 3 , symmetric = True , uselobpcg = True , niter = 50 , backend = backend
225278 )
@@ -229,6 +282,53 @@ def test_eigs_square(par, symmetric):
229282 assert_array_almost_equal (np .real (cond ), nx , decimal = 3 )
230283
231284
285+ @pytest .mark .parametrize ("par" , [(par1j )])
286+ @pytest .mark .parametrize ("explicit" , [True , False ])
287+ def test_eigs_square_hermitian (par , explicit ):
288+ """Eigenvalues and conditioning number estimate of complex-valued
289+ hermitian matrix with known (real-valued) eigenvalues
290+ """
291+ nx = 5 * par ["nx" ]
292+
293+ # hermitian matrix A = U diag(eigs) U^H with U unitary
294+ np .random .seed (0 )
295+ U , _ = np .linalg .qr (
296+ np .random .normal (size = (nx , nx )) + 1j * np .random .normal (size = (nx , nx ))
297+ )
298+ eigs_true = np .arange (nx , 0.0 , - 1.0 )
299+ A = (U * eigs_true ) @ np .conj (U .T )
300+ A = (A + np .conj (A .T )) / 2 # remove round-off asymmetry
301+ Op = MatrixMult (A , dtype = np .dtype (par ["dtype" ]))
302+ Op .explicit = explicit
303+
304+ # hermitian operator
305+ assert_array_almost_equal (Op .H .todense (), Op .todense (), decimal = 6 )
306+
307+ # hermitian solver
308+ if explicit :
309+ eigs = Op .eigs (symmetric = True , backend = backend ) # all
310+ assert_array_almost_equal (eigs , eigs_true , decimal = 3 )
311+
312+ eigs = Op .eigs (neigs = 3 , symmetric = True , backend = backend ) # top-k
313+ assert_array_almost_equal (eigs , eigs_true [:3 ], decimal = 3 )
314+
315+ cond = Op .cond (symmetric = True , backend = backend )
316+ assert_array_almost_equal (np .real (cond ), nx , decimal = 3 )
317+
318+ # lobpcg
319+ eigs = Op .eigs (neigs = 3 , symmetric = True , uselobpcg = True , niter = 100 , backend = backend )
320+ assert_array_almost_equal (eigs , eigs_true [:3 ], decimal = 3 )
321+
322+ cond = Op .cond (uselobpcg = True , niter = 200 , backend = backend )
323+ assert_array_almost_equal (np .real (cond ), nx , decimal = 3 )
324+
325+ # general solver (not available for CuPy)
326+ if backend == "numpy" :
327+ eigs = Op .eigs (neigs = 3 , symmetric = False , backend = backend )
328+ assert_array_almost_equal (np .real (eigs ), eigs_true [:3 ], decimal = 3 )
329+ assert_array_almost_equal (np .imag (eigs ), np .zeros (3 ), decimal = 3 )
330+
331+
232332@pytest .mark .parametrize ("par" , [(par2 ), (par2j )])
233333def test_eigs_non_square (par ):
234334 """Eigenvalues and conditioning number estimate of non-square matrix"""
@@ -248,6 +348,9 @@ def test_eigs_non_square(par):
248348 eigs = Op .eigs (neigs = 3 , backend = backend ) # top-k
249349 assert_array_almost_equal (eigs , np .abs (diag )[:3 ], decimal = 3 )
250350
351+ eigs = Op .eigs (neigs = 2 , uselobpcg = True , niter = 100 , backend = backend ) # lobpcg
352+ assert_array_almost_equal (eigs , np .abs (diag )[:2 ], decimal = 3 )
353+
251354 if backend == "numpy" :
252355 # CuPy cannot compute a single eigenvalue for square,
253356 # non-symmetric matrix
@@ -273,6 +376,13 @@ def test_eigs_non_square(par):
273376 cond = Op .cond (backend = backend )
274377 assert_array_almost_equal (np .real (cond ), nx , decimal = 3 )
275378
379+ # use lobpcg
380+ eigs = Op .eigs (neigs = 2 , uselobpcg = True , niter = 100 , backend = backend )
381+ assert_array_almost_equal (eigs , np .abs (diag )[:2 ], decimal = 3 )
382+
383+ cond = Op .cond (uselobpcg = True , niter = 200 , backend = backend )
384+ assert_array_almost_equal (np .real (cond ), nx , decimal = 3 )
385+
276386
277387@pytest .mark .parametrize ("par" , [(par1 ), (par2 ), (par1j ), (par2j )])
278388def test_conj (par ):
0 commit comments