|
17 | 17 | from typing import Any |
18 | 18 |
|
19 | 19 | from pydantic import ValidationError |
| 20 | +from sympy import Matrix |
20 | 21 |
|
21 | 22 | from jacobian.canonical import format_canonical_integer |
22 | 23 | from jacobian.contracts.plugin_matrices import ( |
@@ -152,91 +153,26 @@ def transform_row_major_capability(request: dict[str, Any]) -> dict[str, Any]: |
152 | 153 | # --------------------------------------------------------------------------- |
153 | 154 |
|
154 | 155 |
|
| 156 | +def _fraction_from_sympy(value: Any) -> Fraction: |
| 157 | + numerator, denominator = value.as_numer_denom() |
| 158 | + return Fraction(int(numerator), int(denominator)) |
| 159 | + |
| 160 | + |
155 | 161 | def _det_fraction(matrix: list[list[int]]) -> Fraction: |
156 | | - """Exact determinant using Fraction Gaussian elimination.""" |
157 | | - n = len(matrix) |
158 | | - if n == 0: |
| 162 | + """Return the exact determinant through SymPy's maintained matrix kernel.""" |
| 163 | + if not matrix: |
159 | 164 | return Fraction(1) |
160 | | - a = [[Fraction(x) for x in row] for row in matrix] |
161 | | - det = Fraction(1) |
162 | | - row = 0 |
163 | | - for col in range(n): |
164 | | - pivot = None |
165 | | - for r in range(row, n): |
166 | | - if a[r][col] != 0: |
167 | | - pivot = r |
168 | | - break |
169 | | - if pivot is None: |
170 | | - return Fraction(0) |
171 | | - if pivot != row: |
172 | | - a[pivot], a[row] = a[row], a[pivot] |
173 | | - det = -det |
174 | | - piv = a[row][col] |
175 | | - det *= piv |
176 | | - for r in range(row + 1, n): |
177 | | - if a[r][col] == 0: |
178 | | - continue |
179 | | - factor = a[r][col] / piv |
180 | | - for c in range(col, n): |
181 | | - a[r][c] -= factor * a[row][c] |
182 | | - row += 1 |
183 | | - return det |
184 | | - |
185 | | - |
186 | | -def _row_echelon( |
187 | | - matrix: list[list[int]], |
188 | | -) -> tuple[list[list[Fraction]], list[int], list[int]]: |
189 | | - """Reduce *matrix* to row echelon form over QQ. |
190 | | -
|
191 | | - Returns the reduced matrix together with the pivot columns and rows. |
192 | | - """ |
193 | | - rows = len(matrix) |
194 | | - cols = len(matrix[0]) if rows else 0 |
195 | | - a = [[Fraction(x) for x in row] for row in matrix] |
196 | | - pivot_cols: list[int] = [] |
197 | | - pivot_rows: list[int] = [] |
198 | | - r = 0 |
199 | | - for c in range(cols): |
200 | | - pivot = None |
201 | | - for i in range(r, rows): |
202 | | - if a[i][c] != 0: |
203 | | - pivot = i |
204 | | - break |
205 | | - if pivot is None: |
206 | | - continue |
207 | | - a[pivot], a[r] = a[r], a[pivot] |
208 | | - pivot_cols.append(c) |
209 | | - pivot_rows.append(r) |
210 | | - for i in range(r + 1, rows): |
211 | | - if a[i][c] == 0: |
212 | | - continue |
213 | | - factor = a[i][c] / a[r][c] |
214 | | - for j in range(c, cols): |
215 | | - a[i][j] -= factor * a[r][j] |
216 | | - r += 1 |
217 | | - return a, pivot_cols, pivot_rows |
| 165 | + return _fraction_from_sympy(Matrix(matrix).det()) |
218 | 166 |
|
219 | 167 |
|
220 | 168 | def _kernel_vector(matrix: list[list[int]]) -> list[Fraction] | None: |
221 | 169 | """Return a non-zero rational vector in the kernel, or None if trivial.""" |
222 | | - rows = len(matrix) |
223 | | - cols = len(matrix[0]) if rows else 0 |
224 | | - a, pivot_cols, pivot_rows = _row_echelon(matrix) |
225 | | - |
226 | | - if len(pivot_cols) == cols: |
| 170 | + if not matrix: |
227 | 171 | return None |
228 | | - |
229 | | - free_cols = [c for c in range(cols) if c not in pivot_cols] |
230 | | - sol = [Fraction(0) for _ in range(cols)] |
231 | | - sol[free_cols[0]] = Fraction(1) |
232 | | - |
233 | | - for r_idx, c_idx in reversed(list(zip(pivot_rows, pivot_cols, strict=True))): |
234 | | - total = Fraction(0) |
235 | | - for j in range(c_idx + 1, cols): |
236 | | - total += a[r_idx][j] * sol[j] |
237 | | - sol[c_idx] = -total / a[r_idx][c_idx] |
238 | | - |
239 | | - return sol |
| 172 | + nullspace = Matrix(matrix).nullspace() |
| 173 | + if not nullspace: |
| 174 | + return None |
| 175 | + return [_fraction_from_sympy(value) for value in nullspace[0]] |
240 | 176 |
|
241 | 177 |
|
242 | 178 | def _is_singular(matrix: list[list[int]]) -> bool: |
|
0 commit comments