Skip to content
This repository was archived by the owner on Nov 7, 2024. It is now read-only.

Commit 6b02b0c

Browse files
mganahlChase Roberts
andauthored
API change for eigs (#593)
* fix call signature * revert * ??? * ???? * fix test * fix argument order in matvec * fix linter * fix doc * fix API * linting * linting * tests fixed * fix test * fixing a bunch of things * fix code * check * check added * remove [] * remove [] Co-authored-by: Chase Roberts <chaseriley@google.com>
1 parent 0aa710f commit 6b02b0c

10 files changed

Lines changed: 160 additions & 168 deletions

File tree

tensornetwork/backends/backend_test.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -316,7 +316,7 @@ def test_base_backend_eigs_not_implemented():
316316
def test_base_backend_eigs_lanczos_not_implemented():
317317
backend = BaseBackend()
318318
with pytest.raises(NotImplementedError):
319-
backend.eigsh_lanczos(lambda x: x, [], np.ones((2)))
319+
backend.eigsh_lanczos(lambda x: x, np.ones((2)))
320320

321321

322322
def test_base_backend_addition_not_implemented():
@@ -401,6 +401,8 @@ def test_base_backend_broadcast_left_multiplication_not_implemented():
401401
backend = BaseBackend()
402402
with pytest.raises(NotImplementedError):
403403
backend.broadcast_left_multiplication(np.ones((2, 2)), np.ones((2, 2)))
404+
405+
404406
def test_backend_instantiation(backend):
405407
backend1 = backend_factory.get_backend(backend)
406408
backend2 = backend_factory.get_backend(backend)

tensornetwork/backends/base_backend.py

Lines changed: 20 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -334,24 +334,31 @@ def eigh(self, matrix: Tensor):
334334

335335
def eigs(self,
336336
A: Callable,
337+
args: Optional[List[Tensor]] = None,
337338
initial_state: Optional[Tensor] = None,
338-
num_krylov_vecs: Optional[int] = 200,
339-
numeig: Optional[int] = 1,
340-
tol: Optional[float] = 1E-8,
341-
which: Optional[Text] = 'LR',
342-
maxiter: Optional[int] = None,
343-
dtype: Optional[Type] = None) -> List[Tensor]:
344-
"""Arnoldi method for finding the lowest eigenvector-eigenvalue pairs of a
345-
linear operator `A`. `A` can be either a linear operator type object or a
346-
regular callable. If no `initial_state` is provided then `A` has to have an
347-
attribute `shape` so that a suitable initial state can be randomly
348-
generated.
339+
shape: Optional[Tuple[int, ...]] = None,
340+
dtype: Optional[Type[np.number]] = None,
341+
num_krylov_vecs: int = 50,
342+
numeig: int = 1,
343+
tol: float = 1E-8,
344+
which: Text = 'LR',
345+
maxiter: Optional[int] = None) -> List[Tensor]:
346+
"""Arnoldi method for finding the lowest eigenvector-eigenvalue pairs
347+
of a linear operator `A`. `A` is a callable implementing the
348+
matrix-vector product. If no `initial_state` is provided then
349+
`shape` and `dtype` have to be passed so that a suitable initial
350+
state can be randomly generated.
349351
350352
Args:
351353
A: A (sparse) implementation of a linear operator
352-
initial_state: An initial vector for the Lanczos algorithm. If `None`,
354+
arsg: A list of arguments to `A`. `A` will be called as
355+
`res = A(initial_state, *args)`.
356+
initial_state: An initial vector for the algorithm. If `None`,
353357
a random initial `Tensor` is created using the `numpy.random.randn`
354358
method.
359+
shape: The shape of the input-dimension of `A`.
360+
dtype: The dtype of the input `A`. If both no `initial_state` is provided,
361+
a random initial state with shape `shape` and dtype `dtype` is created.
355362
num_krylov_vecs: The number of iterations (number of krylov vectors).
356363
numeig: The nummber of eigenvector-eigenvalue pairs to be computed.
357364
If `numeig > 1`, `reorthogonalize` has to be `True`.
@@ -374,7 +381,7 @@ def eigs(self,
374381

375382
def eigsh_lanczos(self,
376383
A: Callable,
377-
args: List,
384+
args: Optional[List[Tensor]] = None,
378385
initial_state: Optional[Tensor] = None,
379386
shape: Optional[Tuple[int, ...]] = None,
380387
dtype: Optional[Type[np.number]] = None,

tensornetwork/backends/jax/jax_backend.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -230,7 +230,7 @@ def cmplx_random_uniform(complex_dtype, real_dtype):
230230
def eigsh_lanczos(
231231
self,
232232
A: Callable,
233-
args: List[Tensor],
233+
args: Optional[List[Tensor]] = None,
234234
initial_state: Optional[Tensor] = None,
235235
shape: Optional[Tuple] = None,
236236
dtype: Optional[Type[np.number]] = None,
@@ -307,6 +307,8 @@ def A(H,x):
307307
eigvals: A list of `numeig` lowest eigenvalues
308308
eigvecs: A list of `numeig` lowest eigenvectors
309309
"""
310+
if args is None:
311+
args = []
310312
if num_krylov_vecs < numeig:
311313
raise ValueError('`num_krylov_vecs` >= `numeig` required!')
312314

tensornetwork/backends/jax/jax_backend_test.py

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -414,30 +414,30 @@ def test_eigsh_lanczos_raises():
414414
backend = jax_backend.JaxBackend()
415415
with pytest.raises(
416416
ValueError, match='`num_krylov_vecs` >= `numeig` required!'):
417-
backend.eigsh_lanczos(lambda x: x, [], numeig=10, num_krylov_vecs=9)
417+
backend.eigsh_lanczos(lambda x: x, numeig=10, num_krylov_vecs=9)
418418
with pytest.raises(
419419
ValueError,
420420
match="Got numeig = 2 > 1 and `reorthogonalize = False`. "
421421
"Use `reorthogonalize=True` for `numeig > 1`"):
422-
backend.eigsh_lanczos(lambda x: x, [], numeig=2, reorthogonalize=False)
422+
backend.eigsh_lanczos(lambda x: x, numeig=2, reorthogonalize=False)
423423
with pytest.raises(
424424
ValueError,
425425
match="if no `initial_state` is passed, then `shape` and"
426426
"`dtype` have to be provided"):
427-
backend.eigsh_lanczos(lambda x: x, [], shape=(10,), dtype=None)
427+
backend.eigsh_lanczos(lambda x: x, shape=(10,), dtype=None)
428428
with pytest.raises(
429429
ValueError,
430430
match="if no `initial_state` is passed, then `shape` and"
431431
"`dtype` have to be provided"):
432-
backend.eigsh_lanczos(lambda x: x, [], shape=None, dtype=np.float64)
432+
backend.eigsh_lanczos(lambda x: x, shape=None, dtype=np.float64)
433433
with pytest.raises(
434434
ValueError,
435435
match="if no `initial_state` is passed, then `shape` and"
436436
"`dtype` have to be provided"):
437-
backend.eigsh_lanczos(lambda x: x, [])
437+
backend.eigsh_lanczos(lambda x: x)
438438
with pytest.raises(
439439
TypeError, match="Expected a `jax.array`. Got <class 'list'>"):
440-
backend.eigsh_lanczos(lambda x: x, [], initial_state=[1, 2, 3])
440+
backend.eigsh_lanczos(lambda x: x, initial_state=[1, 2, 3])
441441

442442

443443
@pytest.mark.parametrize("dtype", np_dtypes)

tensornetwork/backends/numpy/numpy_backend.py

Lines changed: 36 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -178,13 +178,15 @@ def eigh(self, matrix: Tensor) -> Tuple[Tensor, Tensor]:
178178

179179
def eigs(self,
180180
A: Callable,
181+
args: Optional[List] = None,
181182
initial_state: Optional[Tensor] = None,
182-
num_krylov_vecs: Optional[int] = 200,
183-
numeig: Optional[int] = 6,
184-
tol: Optional[float] = 1E-8,
185-
which: Optional[Text] = 'LR',
186-
maxiter: Optional[int] = None,
187-
dtype: Optional[Type[np.number]] = None) -> Tuple[List, List]:
183+
shape: Optional[Tuple[int, ...]] = None,
184+
dtype: Optional[Type[np.number]] = None,
185+
num_krylov_vecs: int = 50,
186+
numeig: int = 6,
187+
tol: float = 1E-8,
188+
which: Text = 'LR',
189+
maxiter: Optional[int] = None) -> Tuple[List, List]:
188190
"""
189191
Arnoldi method for finding the lowest eigenvector-eigenvalue pairs
190192
of a linear operator `A`. `A` can be either a
@@ -196,9 +198,14 @@ def eigs(self,
196198
197199
Args:
198200
A: A (sparse) implementation of a linear operator
199-
initial_state: An initial vector for the Lanczos algorithm. If `None`,
201+
args: A list of arguments to `A`. `A` will be called as
202+
`res = A(initial_state, *args)`.
203+
initial_state: An initial vector for the algorithm. If `None`,
200204
a random initial `Tensor` is created using the `numpy.random.randn`
201205
method.
206+
shape: The shape of the input-dimension of `A`.
207+
dtype: The dtype of the input `A`. If both no `initial_state` is provided,
208+
a random initial state with shape `shape` and dtype `dtype` is created.
202209
num_krylov_vecs: The number of iterations (number of krylov vectors).
203210
numeig: The nummber of eigenvector-eigenvalue pairs to be computed.
204211
If `numeig > 1`, `reorthogonalize` has to be `True`.
@@ -217,38 +224,37 @@ def eigs(self,
217224
`np.ndarray`: An array of `numeig` lowest eigenvalues
218225
`np.ndarray`: An array of `numeig` lowest eigenvectors
219226
"""
227+
if args is None:
228+
args = []
220229
if which == 'SI':
221230
raise ValueError('which = SI is currently not supported.')
222231
if which == 'LI':
223232
raise ValueError('which = LI is currently not supported.')
224233

225-
if (initial_state is not None) and hasattr(A, 'shape'):
226-
if initial_state.shape != A.shape[1]:
227-
raise ValueError(
228-
"A.shape[1]={} and initial_state.shape={} are incompatible.".format(
229-
A.shape[1], initial_state.shape))
234+
if numeig + 1 >= num_krylov_vecs:
235+
raise ValueError('`num_krylov_vecs` > `numeig + 1` required!')
230236

231237
if initial_state is None:
232-
if not hasattr(A, 'shape'):
233-
raise AttributeError("`A` has no attribute `shape`. Cannot initialize "
234-
"lanczos. Please provide a valid `initial_state`")
235-
if not hasattr(A, 'dtype'):
236-
raise AttributeError(
237-
"`A` has no attribute `dtype`. Cannot initialize "
238-
"lanczos. Please provide a valid `initial_state` with "
239-
"a `dtype` attribute")
240-
241-
initial_state = self.randn(A.shape[1], A.dtype)
238+
if (shape is None) or (dtype is None):
239+
raise ValueError("if no `initial_state` is passed, then `shape` and"
240+
"`dtype` have to be provided")
241+
initial_state = self.randn(shape, dtype)
242242

243243
if not isinstance(initial_state, np.ndarray):
244-
raise TypeError("Expected a `np.array`. Got {}".format(
244+
raise TypeError("Expected a `np.ndarray`. Got {}".format(
245245
type(initial_state)))
246+
247+
shape = initial_state.shape
248+
249+
def matvec(vector):
250+
return np.ravel(A(np.reshape(vector, shape), *args))
251+
246252
#initial_state is an np.ndarray of rank 1, so we can
247253
#savely deduce the shape from it
248254
lop = sp.sparse.linalg.LinearOperator(
249255
dtype=initial_state.dtype,
250-
shape=(initial_state.shape[0], initial_state.shape[0]),
251-
matvec=A)
256+
shape=(np.prod(initial_state.shape), np.prod(initial_state.shape)),
257+
matvec=matvec)
252258
eta, U = sp.sparse.linalg.eigs(
253259
A=lop,
254260
k=numeig,
@@ -260,11 +266,11 @@ def eigs(self,
260266
if dtype:
261267
eta = eta.astype(dtype)
262268
U = U.astype(dtype)
263-
return list(eta), [U[:, n] for n in range(numeig)]
269+
return list(eta), [np.reshape(U[:, n], shape) for n in range(numeig)]
264270

265271
def eigsh_lanczos(self,
266272
A: Callable,
267-
args: List,
273+
args: Optional[List[Tensor]] = None,
268274
initial_state: Optional[Tensor] = None,
269275
shape: Optional[Tuple] = None,
270276
dtype: Optional[Type[np.number]] = None,
@@ -309,6 +315,9 @@ def eigsh_lanczos(self,
309315
eigvals: A list of `numeig` lowest eigenvalues
310316
eigvecs: A list of `numeig` lowest eigenvectors
311317
"""
318+
if args is None:
319+
args = []
320+
312321
if num_krylov_vecs < numeig:
313322
raise ValueError('`num_krylov_vecs` >= `numeig` required!')
314323

0 commit comments

Comments
 (0)