Skip to content

Commit c6b19c7

Browse files
committed
Make hnf_decomposition return the reduced HNF rather than the HNF.
1 parent 3e9e7d4 commit c6b19c7

1 file changed

Lines changed: 28 additions & 22 deletions

File tree

src/torchjd/sparse/_linalg.py

Lines changed: 28 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -42,15 +42,19 @@ def extended_gcd(a: int, b: int) -> tuple[int, int, int]:
4242

4343
def hnf_decomposition(A: Tensor) -> tuple[Tensor, Tensor, Tensor]:
4444
"""
45-
Computes the Hermite Normal Form decomposition using PyTorch.
45+
Computes the reduced Hermite Normal Form decomposition using PyTorch. For a matrix A (m x n) of
46+
rank r, computes the matrices H (m x r), U (n x r) and V (r x n) such that
47+
V U = I_r
48+
A = H V
49+
H = A U
4650
4751
Args:
4852
A: (m x n) torch.Tensor (dtype=torch.long)
4953
5054
Returns:
51-
H: (m x n) Canonical Lower Triangular HNF
52-
U: (n x n) Unimodular transform (A @ U = H)
53-
V: (n x n) Inverse Unimodular transform (H @ V = A)
55+
H: (m x r) Canonical Lower Triangular HNF
56+
U: (n x r) Unimodular transform (A @ U = H)
57+
V: (r x n) Right inverse Unimodular transform (H @ V = A)
5458
"""
5559

5660
H = A.clone().to(dtype=torch.long)
@@ -143,28 +147,41 @@ def hnf_decomposition(A: Tensor) -> tuple[Tensor, Tensor, Tensor]:
143147
row += 1
144148
col += 1
145149

146-
return H, U, V
150+
col_magnitudes = torch.sum(torch.abs(H), dim=0)
151+
non_zero_indices = torch.nonzero(col_magnitudes, as_tuple=True)[0]
152+
153+
if len(non_zero_indices) == 0:
154+
rank = 0
155+
else:
156+
rank = non_zero_indices.max().item() + 1
157+
158+
reduced_H = H[:, :rank]
159+
reduced_U = U[:, :rank]
160+
reduced_V = V[:rank, :]
161+
162+
return reduced_H, reduced_U, reduced_V
147163

148164

149165
def compute_gcd(S1: Tensor, S2: Tensor) -> tuple[Tensor, Tensor, Tensor]:
150166
"""
151167
Computes the GCD and the projection factors. i.e.
152168
S1 = G @ K1
153169
S2 = G @ K2
170+
with G having minimal rank.
154171
155172
Args:
156173
S1, S2: torch.Tensors (m x n1), (m x n2)
157174
158175
Returns:
159-
G: (m x m) The Matrix GCD (Canonical Base)
160-
K1: (m x n1) Factors for S1
161-
K2: (m x n2) Factors for S2
176+
G: (m x r) The Matrix GCD (Canonical Base)
177+
K1: (r x n1) Factors for S1
178+
K2: (r x n2) Factors for S2
162179
"""
163180
assert S1.shape[0] == S2.shape[0], "Virtual dimension mismatch"
164181
m, n1 = S1.shape
165182

166183
A = torch.cat([S1, S2], dim=1)
167-
H, U, V = hnf_decomposition(A)
184+
G, U, V = hnf_decomposition(A)
168185

169186
# H = [S1 | S2] @ U
170187
# [S1 | S2] = H @ V
@@ -182,19 +199,8 @@ def compute_gcd(S1: Tensor, S2: Tensor) -> tuple[Tensor, Tensor, Tensor]:
182199
# SLT(p1, S1) = SLT(SLT(p1, K1), G)
183200
# SLT(p2, S2) = SLT(SLT(p2, K2), G)
184201

185-
col_magnitudes = torch.sum(torch.abs(H), dim=0)
186-
non_zero_indices = torch.nonzero(col_magnitudes, as_tuple=True)[0]
187-
188-
if len(non_zero_indices) == 0:
189-
rank = 0
190-
else:
191-
rank = non_zero_indices.max().item() + 1
192-
193-
G = H[:, :rank]
194-
V_active = V[:rank, :]
195-
196-
K1 = V_active[:, :n1]
197-
K2 = V_active[:, n1:]
202+
K1 = V[:, :n1]
203+
K2 = V[:, n1:]
198204

199205
return G, K1, K2
200206

0 commit comments

Comments
 (0)