Skip to content

Commit 12ff9c2

Browse files
authored
Skip redundant full-rank decompositions (#52)
1 parent a0c03a5 commit 12ff9c2

4 files changed

Lines changed: 44 additions & 2 deletions

File tree

‎geotorch/lowrank.py‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -149,6 +149,10 @@ def sample(self, init_=torch.nn.init.xavier_normal_, factorized=False):
149149
with torch.no_grad():
150150
X = self[0].base.new_empty(self.tensorial_size + (self.n, self.k))
151151
init_(X)
152+
if self.rank == self.k and not factorized:
153+
if self.transposed:
154+
X = X.transpose(-2, -1)
155+
return X
152156
U, S, Vt = torch.linalg.svd(X, full_matrices=False)
153157
U, S, Vt = U[..., : self.rank], S[..., : self.rank], Vt[..., : self.rank, :]
154158
if factorized:

‎geotorch/symmetric.py‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -218,6 +218,8 @@ def sample(self, init_=torch.nn.init.xavier_normal_, factorized=False):
218218
X = self[0].base.new_empty(self.tensorial_size + (self.n, self.n))
219219
init_(X)
220220
X = X @ X.transpose(-2, -1)
221+
if self.rank == self.n and not factorized:
222+
return X
221223
L, Q = torch.linalg.eigh(X)
222224
L = L[..., -self.rank :]
223225
Q = Q[..., -self.rank :]

‎test/test_lowrank.py‎

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
from unittest import TestCase
1+
from unittest import TestCase, mock
22

33
import torch
44

@@ -35,3 +35,24 @@ def test_inverse_softplus_is_stable(self):
3535
inverse = inv_softplus_epsilon(x)
3636
self.assertTrue(torch.isfinite(inverse).all())
3737
self.assertTrue(torch.allclose(softplus_epsilon(inverse), x))
38+
39+
def test_full_rank_sample_skips_svd(self):
40+
for size in [(3, 3), (5, 3), (3, 5), (2, 5, 3)]:
41+
manifold = LowRank(size=size, rank=min(size[-2:])).double()
42+
43+
def init_(X):
44+
return X.copy_(
45+
torch.arange(X.numel(), dtype=X.dtype, device=X.device).view_as(X)
46+
)
47+
48+
canonical_size = size[:-2] + (max(size[-2:]), min(size[-2:]))
49+
expected = torch.empty(canonical_size, dtype=torch.float64)
50+
expected = torch.arange(expected.numel(), dtype=expected.dtype).view_as(
51+
expected
52+
)
53+
if size[-2] < size[-1]:
54+
expected = expected.mT
55+
56+
with mock.patch("torch.linalg.svd", side_effect=AssertionError):
57+
sample = manifold.sample(init_)
58+
self.assertTrue(torch.equal(sample, expected))

‎test/test_symmetric.py‎

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,13 @@
11
# Tests for the Sphere
2-
from unittest import TestCase
2+
from unittest import TestCase, mock
33
import itertools
44

55
import torch
66
import torch.nn as nn
77
from torch.nn.utils import parametrize
88

99
from geotorch.symmetric import Symmetric, SymF
10+
from geotorch.pssd import PSSD
1011

1112

1213
class TestSymmetric(TestCase):
@@ -64,3 +65,17 @@ def test_construction(self):
6465

6566
def test_repr(self):
6667
print(Symmetric())
68+
69+
def test_full_rank_sample_skips_eigendecomposition(self):
70+
manifold = PSSD(size=(2, 4, 4)).double()
71+
72+
def init_(X):
73+
return X.copy_(
74+
torch.arange(X.numel(), dtype=X.dtype, device=X.device).view_as(X)
75+
)
76+
77+
X = torch.arange(32, dtype=torch.float64).view(2, 4, 4)
78+
expected = X @ X.mT
79+
with mock.patch("torch.linalg.eigh", side_effect=AssertionError):
80+
sample = manifold.sample(init_)
81+
self.assertTrue(torch.equal(sample, expected))

0 commit comments

Comments
 (0)