@@ -42,15 +42,19 @@ def extended_gcd(a: int, b: int) -> tuple[int, int, int]:
4242
4343def 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
149165def 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