Coverage for src/pqlattice/linalg/_linalg.py: 84%
104 statements
« prev ^ index » next coverage.py v7.11.0, created at 2026-01-12 21:36 +0100
« prev ^ index » next coverage.py v7.11.0, created at 2026-01-12 21:36 +0100
1from functools import reduce
3import numpy as np
5from .._utils import as_integer
6from ..typing import Matrix, SquareMatrix, Vector, validate_aliases
7from ._utils import row_add, row_scale, row_swap
10@validate_aliases
11def hnf(A: Matrix) -> tuple[Matrix, SquareMatrix]:
12 """_summary_
14 Parameters
15 ----------
16 A : Matrix
17 _description_
19 Returns
20 -------
21 tuple[Matrix, SquareMatrix]
22 _description_
23 """
24 H, U, _ = _hnf(A)
25 return H, U
28@validate_aliases
29def _hnf(a: Matrix) -> tuple[Matrix, SquareMatrix, int]:
30 H = np.array(a, dtype=object)
31 m, n = H.shape
32 U = np.eye(m, dtype=object)
33 pivot_row = 0
34 pivot_col = 0
35 det_U = 1
37 while pivot_row < m and pivot_col < n:
38 # pivot selection
39 if np.all(H[pivot_row:, pivot_col] == 0):
40 pivot_col += 1
41 continue
43 candidates = [(abs(H[i, pivot_col]), i) for i in range(pivot_row, m) if H[i, pivot_col] != 0]
44 _, best_row = min(candidates)
46 row_swap(H, pivot_row, best_row)
47 row_swap(U, pivot_row, best_row)
48 det_U *= -1
50 # clear below pivot
51 for i in range(pivot_row + 1, m):
52 while H[i, pivot_col] != 0:
53 factor = H[i, pivot_col] // H[pivot_row, pivot_col]
55 row_add(H, i, pivot_row, -factor)
56 row_add(U, i, pivot_row, -factor)
58 if H[i, pivot_col] != 0:
59 row_swap(H, pivot_row, i)
60 row_swap(U, pivot_row, i)
61 det_U *= -1
63 if H[pivot_row, pivot_col] < 0:
64 row_scale(H, pivot_row, -1)
65 row_scale(U, pivot_row, -1)
66 det_U *= -1
68 pivot_val = H[pivot_row, pivot_col]
70 for i in range(pivot_row):
71 factor = H[i, pivot_col] // pivot_val
72 row_add(H, i, pivot_row, -factor)
73 row_add(U, i, pivot_row, -factor)
75 pivot_row += 1
76 pivot_col += 1
78 return H, U, det_U
81@validate_aliases
82def right_image(A: Matrix) -> Matrix:
83 """
84 _summary_
86 Parameters
87 ----------
88 A : Matrix
89 _description_
91 Returns
92 -------
93 Matrix
94 _description_
95 """
96 return left_image(A.T).T
99@validate_aliases
100def left_image(A: Matrix) -> Matrix:
101 """
102 _summary_
104 Parameters
105 ----------
106 A : Matrix
107 _description_
109 Returns
110 -------
111 Matrix
112 _description_
113 """
114 H, _ = hnf(A)
116 m, _ = H.shape
117 k = 0
118 for i in range(m):
119 if np.all(H[i] == 0):
120 k = i
121 break
123 return as_integer(H[:k])
126@validate_aliases
127def left_kernel(A: Matrix):
128 """
129 {x : xA = 0}
131 Parameters
132 ----------
133 A : Matrix
134 _description_
136 Returns
137 -------
138 _type_
139 _description_
140 """
141 H, U = hnf(A)
142 kernel_basis: list[Vector] = []
144 m, _ = H.shape
145 for i in range(m):
146 if np.all(H[i] == 0):
147 kernel_basis.append(U[i])
149 return np.array(kernel_basis, dtype=object)
152@validate_aliases
153def right_kernel(A: Matrix) -> Matrix:
154 """
155 {x : Ax = 0}
157 Parameters
158 ----------
159 A : Matrix
160 _description_
162 Returns
163 -------
164 Matrix
165 _description_
166 """
167 return left_kernel(A.T)
170@validate_aliases
171def left_nullity(a: Matrix) -> int:
172 """_summary_
174 Parameters
175 ----------
176 a : Matrix
177 _description_
179 Returns
180 -------
181 int
182 _description_
183 """
184 kernel = left_kernel(a)
185 return kernel.shape[0]
188@validate_aliases
189def right_nullity(a: Matrix) -> int:
190 """_summary_
192 Parameters
193 ----------
194 a : Matrix
195 _description_
197 Returns
198 -------
199 int
200 _description_
201 """
202 kernel = right_kernel(a)
203 return kernel.shape[0]
206def rank(a: Matrix) -> int:
207 """_summary_
209 Parameters
210 ----------
211 a : Matrix
212 _description_
214 Returns
215 -------
216 int
217 _description_
218 """
219 m, n = a.shape
220 l_rank = m - left_nullity(a)
221 r_rank = n - right_nullity(a)
222 assert l_rank == r_rank
223 return l_rank
226@validate_aliases
227def det(A: SquareMatrix) -> int:
228 """_summary_
230 Parameters
231 ----------
232 A : SquareMatrix
233 _description_
235 Returns
236 -------
237 int
238 _description_
239 """
240 H, _, det_U = _hnf(A)
242 return reduce(lambda a, b: a * b, np.diagonal(H), 1) * det_U
245@validate_aliases
246def minor(A: SquareMatrix, i: int, j: int) -> int:
247 """_summary_
249 Parameters
250 ----------
251 A : SquareMatrix
252 _description_
253 i : int
254 _description_
255 j : int
256 _description_
258 Returns
259 -------
260 int
261 _description_
262 """
263 return det(np.delete(np.delete(A, i, axis=0), j, axis=1))
266@validate_aliases
267def cofactor(A: SquareMatrix, i: int, j: int) -> int:
268 """_summary_
270 Parameters
271 ----------
272 A : SquareMatrix
273 _description_
274 i : int
275 _description_
276 j : int
277 _description_
279 Returns
280 -------
281 int
282 _description_
283 """
284 return minor(A, i, j) * ((-1) ** (i + 1 + j + 1))
287@validate_aliases
288def cofactor_matrix(A: SquareMatrix) -> SquareMatrix:
289 """_summary_
291 Parameters
292 ----------
293 A : SquareMatrix
294 _description_
296 Returns
297 -------
298 SquareMatrix
299 _description_
300 """
301 n = A.shape[0]
302 C = np.zeros((n, n), dtype=object)
303 for i in range(n):
304 for j in range(n):
305 C[i, j] = cofactor(A, i, j)
306 return C