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

Commit f5a53c5

Browse files
authored
add broadcasting support to backends (#513)
* adding broadcast_right_multiplication to backends * fix test
1 parent 02e16ee commit f5a53c5

12 files changed

Lines changed: 325 additions & 112 deletions

tensornetwork/backends/base_backend.py

Lines changed: 26 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -354,16 +354,16 @@ def eigs(self,
354354
raise NotImplementedError("Backend '{}' has not implemented eigs.".format(
355355
self.name))
356356

357-
def eigsh_lanczos(self,
358-
A: Callable,
359-
initial_state: Optional[Tensor] = None,
360-
num_krylov_vecs: Optional[int] = 200,
361-
numeig: Optional[int] = 1,
362-
tol: Optional[float] = 1E-8,
363-
delta: Optional[float] = 1E-8,
364-
ndiag: Optional[int] = 20,
365-
reorthogonalize: Optional[bool] = False
366-
) -> Tuple[List, List]:
357+
def eigsh_lanczos(
358+
self,
359+
A: Callable,
360+
initial_state: Optional[Tensor] = None,
361+
num_krylov_vecs: Optional[int] = 200,
362+
numeig: Optional[int] = 1,
363+
tol: Optional[float] = 1E-8,
364+
delta: Optional[float] = 1E-8,
365+
ndiag: Optional[int] = 20,
366+
reorthogonalize: Optional[bool] = False) -> Tuple[List, List]:
367367
"""
368368
Lanczos method for finding the lowest eigenvector-eigenvalue pairs
369369
of `A`.
@@ -444,8 +444,8 @@ def divide(self, tensor1: Tensor, tensor2: Tensor) -> Tensor:
444444
Returns:
445445
Tensor
446446
"""
447-
raise NotImplementedError(
448-
"Backend '{}' has not implemented divide.".format(self.name))
447+
raise NotImplementedError("Backend '{}' has not implemented divide.".format(
448+
self.name))
449449

450450
def index_update(self, tensor: Tensor, mask: Tensor,
451451
assignee: Tensor) -> Tensor:
@@ -471,3 +471,17 @@ def inv(self, matrix: Tensor) -> Tensor:
471471
"""
472472
raise NotImplementedError("Backend '{}' has not implemented `inv`.".format(
473473
self.name))
474+
475+
def broadcast_right_multiplication(self, tensor1: Tensor, tensor2: Tensor):
476+
"""
477+
Perform broadcasting for multiplication of `tensor2` onto `tensor1`, i.e.
478+
`tensor1` * tensor2`.
479+
Args:
480+
tensor1: A tensor.
481+
tensor2: A tensor.
482+
Returns:
483+
Tensor: The result of multiplying `tensor1` onto `tensor2`.
484+
"""
485+
raise NotImplementedError(
486+
"Backend '{}' has not implemented `broadcast_right_multiplication`."
487+
.format(self.name))

tensornetwork/backends/jax/jax_backend_test.py

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -241,8 +241,8 @@ def test_random_uniform_boundaries(dtype):
241241
backend = jax_backend.JaxBackend()
242242
a = backend.random_uniform((4, 4), seed=10, dtype=dtype)
243243
b = backend.random_uniform((4, 4), (lb, ub), seed=10, dtype=dtype)
244-
assert((a >= 0).all() and (a <= 1).all() and
245-
(b >= lb).all() and (b <= ub).all())
244+
assert ((a >= 0).all() and (a <= 1).all() and (b >= lb).all() and
245+
(b <= ub).all())
246246

247247

248248
def test_random_uniform_behavior():
@@ -296,3 +296,20 @@ def test_index_update(dtype):
296296
np_tensor = np.array(tensor)
297297
np_tensor[np_tensor > 0.1] = 0.0
298298
np.testing.assert_allclose(out, np_tensor)
299+
300+
301+
@pytest.mark.parametrize("dtype", [np.float64, np.complex128])
302+
def test_broadcast_right_multiplication(dtype):
303+
backend = jax_backend.JaxBackend()
304+
tensor1 = backend.randn((2, 3), dtype=dtype, seed=10)
305+
tensor2 = backend.randn((3,), dtype=dtype, seed=10)
306+
out = backend.broadcast_right_multiplication(tensor1, tensor2)
307+
np.testing.assert_allclose(out, np.array(tensor1) * np.array(tensor2))
308+
309+
310+
def test_broadcast_right_multiplication_raises():
311+
backend = jax_backend.JaxBackend()
312+
tensor1 = backend.randn((2, 3))
313+
tensor2 = backend.randn((3, 3))
314+
with pytest.raises(ValueError):
315+
backend.broadcast_right_multiplication(tensor1, tensor2)

tensornetwork/backends/numpy/numpy_backend.py

Lines changed: 29 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -44,10 +44,13 @@ def svd_decomposition(self,
4444
max_truncation_error: Optional[float] = None,
4545
relative: Optional[bool] = False
4646
) -> Tuple[Tensor, Tensor, Tensor, Tensor]:
47-
return decompositions.svd_decomposition(self.np, tensor, split_axis,
48-
max_singular_values,
49-
max_truncation_error,
50-
relative=relative)
47+
return decompositions.svd_decomposition(
48+
self.np,
49+
tensor,
50+
split_axis,
51+
max_singular_values,
52+
max_truncation_error,
53+
relative=relative)
5154

5255
def qr_decomposition(
5356
self,
@@ -145,12 +148,12 @@ def random_uniform(self,
145148
dtype = dtype if dtype is not None else self.np.float64
146149
if ((self.np.dtype(dtype) is self.np.dtype(self.np.complex128)) or
147150
(self.np.dtype(dtype) is self.np.dtype(self.np.complex64))):
148-
return self.np.random.uniform(boundaries[0], boundaries[1], shape).astype(
149-
dtype) + 1j * self.np.random.uniform(boundaries[0],
150-
boundaries[1],
151-
shape).astype(dtype)
152-
return self.np.random.uniform(boundaries[0],
153-
boundaries[1], shape).astype(dtype)
151+
return self.np.random.uniform(
152+
boundaries[0], boundaries[1],
153+
shape).astype(dtype) + 1j * self.np.random.uniform(
154+
boundaries[0], boundaries[1], shape).astype(dtype)
155+
return self.np.random.uniform(boundaries[0], boundaries[1],
156+
shape).astype(dtype)
154157

155158
def conj(self, tensor: Tensor) -> Tensor:
156159
return self.np.conj(tensor)
@@ -244,16 +247,16 @@ def eigs(self,
244247
U = U.astype(dtype)
245248
return list(eta), [U[:, n] for n in range(numeig)]
246249

247-
def eigsh_lanczos(self,
248-
A: Callable,
249-
initial_state: Optional[Tensor] = None,
250-
num_krylov_vecs: Optional[int] = 200,
251-
numeig: Optional[int] = 1,
252-
tol: Optional[float] = 1E-8,
253-
delta: Optional[float] = 1E-8,
254-
ndiag: Optional[int] = 20,
255-
reorthogonalize: Optional[bool] = False
256-
) -> Tuple[List, List]:
250+
def eigsh_lanczos(
251+
self,
252+
A: Callable,
253+
initial_state: Optional[Tensor] = None,
254+
num_krylov_vecs: Optional[int] = 200,
255+
numeig: Optional[int] = 1,
256+
tol: Optional[float] = 1E-8,
257+
delta: Optional[float] = 1E-8,
258+
ndiag: Optional[int] = 20,
259+
reorthogonalize: Optional[bool] = False) -> Tuple[List, List]:
257260
"""
258261
Lanczos method for finding the lowest eigenvector-eigenvalue pairs
259262
of a linear operator `A`. If no `initial_state` is provided
@@ -396,3 +399,9 @@ def inv(self, matrix: Tensor) -> Tensor:
396399
raise ValueError("input to numpy backend method `inv` has shape {}."
397400
" Only matrices are supported.".format(matrix.shape))
398401
return self.np.linalg.inv(matrix)
402+
403+
def broadcast_right_multiplication(self, tensor1: Tensor, tensor2: Tensor):
404+
if len(tensor2.shape) != 1:
405+
raise ValueError("only order-1 tensors are allowed for `tensor2`,"
406+
" found `tensor2.shape = {}`".format(tensor2.shape))
407+
return tensor1 * tensor2

tensornetwork/backends/numpy/numpy_backend_test.py

Lines changed: 36 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -239,8 +239,8 @@ def test_random_uniform_boundaries(dtype):
239239
backend = numpy_backend.NumPyBackend()
240240
a = backend.random_uniform((4, 4), seed=10, dtype=dtype)
241241
b = backend.random_uniform((4, 4), (lb, ub), seed=10, dtype=dtype)
242-
assert((a >= 0).all() and (a <= 1).all() and
243-
(b >= lb).all() and (b <= ub).all())
242+
assert ((a >= 0).all() and (a <= 1).all() and (b >= lb).all() and
243+
(b <= ub).all())
244244

245245

246246
def test_random_uniform_behavior():
@@ -329,8 +329,8 @@ def __call__(self, x):
329329
return np.dot(H, x)
330330

331331
mv = LinearOperator(shape=((D,), (D,)), dtype=dtype)
332-
eta1, U1 = backend.eigsh_lanczos(mv, reorthogonalize=True, ndiag=1,
333-
tol=10**(-12), delta=10**(-12))
332+
eta1, U1 = backend.eigsh_lanczos(
333+
mv, reorthogonalize=True, ndiag=1, tol=10**(-12), delta=10**(-12))
334334
eta2, U2 = np.linalg.eigh(H)
335335
v2 = U2[:, 0]
336336
v2 = v2 / sum(v2)
@@ -353,7 +353,7 @@ def test_eigsh_lanczos_raises():
353353
def test_eigsh_lanczos_raises_error_for_incompatible_shapes():
354354
backend = numpy_backend.NumPyBackend()
355355
A = backend.randn((4, 4), dtype=np.float64)
356-
init = backend.randn((3, ), dtype=np.float64)
356+
init = backend.randn((3,), dtype=np.float64)
357357
with pytest.raises(ValueError):
358358
backend.eigsh_lanczos(A, initial_state=init)
359359

@@ -371,7 +371,7 @@ def test_eigsh_lanczos_raises_error_for_untyped_A():
371371
def test_eigsh_lanczos_raises_error_for_bad_initial_state():
372372
backend = numpy_backend.NumPyBackend()
373373
D = 16
374-
init = [1]*D
374+
init = [1] * D
375375
M = backend.randn((D, D), dtype=np.float64)
376376

377377
def mv(x):
@@ -383,9 +383,10 @@ def mv(x):
383383

384384
@pytest.mark.parametrize("a, b, expected", [
385385
pytest.param(1, 1, 2),
386-
pytest.param(1., np.ones((1, 2, 3)), 2*np.ones((1, 2, 3))),
387-
pytest.param(2.*np.ones(()), 1., 3.*np.ones((1, 2, 3))),
388-
pytest.param(2.*np.ones(()), 1.*np.ones((1, 2, 3)), 3.*np.ones((1, 2, 3))),
386+
pytest.param(1., np.ones((1, 2, 3)), 2 * np.ones((1, 2, 3))),
387+
pytest.param(2. * np.ones(()), 1., 3. * np.ones((1, 2, 3))),
388+
pytest.param(2. * np.ones(()), 1. * np.ones((1, 2, 3)), 3. * np.ones(
389+
(1, 2, 3))),
389390
])
390391
def test_addition(a, b, expected):
391392
backend = numpy_backend.NumPyBackend()
@@ -399,7 +400,7 @@ def test_addition(a, b, expected):
399400

400401
@pytest.mark.parametrize("a, b, expected", [
401402
pytest.param(1, 1, 0),
402-
pytest.param(2., 1.*np.ones((1, 2, 3)), 1.*np.ones((1, 2, 3))),
403+
pytest.param(2., 1. * np.ones((1, 2, 3)), 1. * np.ones((1, 2, 3))),
403404
pytest.param(np.ones((1, 2, 3)), 1., np.zeros((1, 2, 3))),
404405
pytest.param(np.ones((1, 2, 3)), np.ones((1, 2, 3)), np.zeros((1, 2, 3))),
405406
])
@@ -415,7 +416,7 @@ def test_subtraction(a, b, expected):
415416

416417
@pytest.mark.parametrize("a, b, expected", [
417418
pytest.param(1, 1, 1),
418-
pytest.param(2., 1.*np.ones((1, 2, 3)), 2.*np.ones((1, 2, 3))),
419+
pytest.param(2., 1. * np.ones((1, 2, 3)), 2. * np.ones((1, 2, 3))),
419420
pytest.param(np.ones((1, 2, 3)), 1., np.ones((1, 2, 3))),
420421
pytest.param(np.ones((1, 2, 3)), np.ones((1, 2, 3)), np.ones((1, 2, 3))),
421422
])
@@ -431,9 +432,10 @@ def test_multiply(a, b, expected):
431432

432433
@pytest.mark.parametrize("a, b, expected", [
433434
pytest.param(2., 2., 1.),
434-
pytest.param(2., 0.5*np.ones((1, 2, 3)), 4.*np.ones((1, 2, 3))),
435-
pytest.param(np.ones(()), 2., 0.5*np.ones((1, 2, 3))),
436-
pytest.param(np.ones(()), 2.*np.ones((1, 2, 3)), 0.5*np.ones((1, 2, 3))),
435+
pytest.param(2., 0.5 * np.ones((1, 2, 3)), 4. * np.ones((1, 2, 3))),
436+
pytest.param(np.ones(()), 2., 0.5 * np.ones((1, 2, 3))),
437+
pytest.param(
438+
np.ones(()), 2. * np.ones((1, 2, 3)), 0.5 * np.ones((1, 2, 3))),
437439
])
438440
def test_divide(a, b, expected):
439441
backend = numpy_backend.NumPyBackend()
@@ -531,7 +533,7 @@ def test_eigs_raises_error_for_unsupported_which(which):
531533
def test_eigs_raises_error_for_incompatible_shapes():
532534
backend = numpy_backend.NumPyBackend()
533535
A = backend.randn((4, 4), dtype=np.float64)
534-
init = backend.randn((3, ), dtype=np.float64)
536+
init = backend.randn((3,), dtype=np.float64)
535537
with pytest.raises(ValueError):
536538
backend.eigs(A, initial_state=init)
537539

@@ -559,7 +561,7 @@ def test_eigs_raises_error_for_untyped_A():
559561
def test_eigs_raises_error_for_bad_initial_state():
560562
backend = numpy_backend.NumPyBackend()
561563
D = 16
562-
init = [1]*D
564+
init = [1] * D
563565
M = backend.randn((D, D), dtype=np.float64)
564566

565567
def mv(x):
@@ -609,3 +611,21 @@ def test_matrix_inv_raises(dtype):
609611
matrix = backend.randn((4, 4, 4), dtype=dtype, seed=10)
610612
with pytest.raises(ValueError):
611613
backend.inv(matrix)
614+
615+
616+
@pytest.mark.parametrize("dtype", [np.float64, np.complex128])
617+
def test_broadcast_right_multiplication(dtype):
618+
backend = numpy_backend.NumPyBackend()
619+
tensor1 = backend.randn((2, 4, 3), dtype=dtype, seed=10)
620+
tensor2 = backend.randn((3,), dtype=dtype, seed=10)
621+
out = backend.broadcast_right_multiplication(tensor1, tensor2)
622+
np.testing.assert_allclose(out, tensor1 * tensor2)
623+
624+
625+
def test_broadcast_right_multiplication_raises():
626+
dtype = np.float64
627+
backend = numpy_backend.NumPyBackend()
628+
tensor1 = backend.randn((2, 4, 3), dtype=dtype, seed=10)
629+
tensor2 = backend.randn((3, 3), dtype=dtype, seed=10)
630+
with pytest.raises(ValueError):
631+
backend.broadcast_right_multiplication(tensor1, tensor2)

tensornetwork/backends/pytorch/pytorch_backend.py

Lines changed: 25 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -52,10 +52,13 @@ def svd_decomposition(self,
5252
max_truncation_error: Optional[float] = None,
5353
relative: Optional[bool] = False
5454
) -> Tuple[Tensor, Tensor, Tensor, Tensor]:
55-
return decompositions.svd_decomposition(self.torch, tensor, split_axis,
56-
max_singular_values,
57-
max_truncation_error,
58-
relative=relative)
55+
return decompositions.svd_decomposition(
56+
self.torch,
57+
tensor,
58+
split_axis,
59+
max_singular_values,
60+
max_truncation_error,
61+
relative=relative)
5962

6063
def qr_decomposition(
6164
self,
@@ -158,16 +161,16 @@ def eigs(self,
158161
raise NotImplementedError("Backend '{}' has not implemented eigs.".format(
159162
self.name))
160163

161-
def eigsh_lanczos(self,
162-
A: Callable,
163-
initial_state: Optional[Tensor] = None,
164-
num_krylov_vecs: Optional[int] = 200,
165-
numeig: Optional[int] = 1,
166-
tol: Optional[float] = 1E-8,
167-
delta: Optional[float] = 1E-8,
168-
ndiag: Optional[int] = 20,
169-
reorthogonalize: Optional[bool] = False
170-
) -> Tuple[List, List]:
164+
def eigsh_lanczos(
165+
self,
166+
A: Callable,
167+
initial_state: Optional[Tensor] = None,
168+
num_krylov_vecs: Optional[int] = 200,
169+
numeig: Optional[int] = 1,
170+
tol: Optional[float] = 1E-8,
171+
delta: Optional[float] = 1E-8,
172+
ndiag: Optional[int] = 20,
173+
reorthogonalize: Optional[bool] = False) -> Tuple[List, List]:
171174
"""
172175
Lanczos method for finding the lowest eigenvector-eigenvalue pairs
173176
of a `LinearOperator` `A`.
@@ -302,3 +305,11 @@ def inv(self, matrix: Tensor) -> Tensor:
302305
"input to pytorch backend method `inv` has shape {}. Only matrices are supported."
303306
.format(matrix.shape))
304307
return matrix.inverse()
308+
309+
def broadcast_right_multiplication(self, tensor1: Tensor, tensor2: Tensor):
310+
if len(tensor2.shape) != 1:
311+
raise ValueError(
312+
"only order-1 tensors are allowed for `tensor2`, found `tensor2.shape = {}`"
313+
.format(tensor2.shape))
314+
315+
return tensor1 * tensor2

0 commit comments

Comments
 (0)