Skip to content

Commit 793386f

Browse files
committed
Use SymPy for plugin matrix algebra
1 parent 5c179ea commit 793386f

1 file changed

Lines changed: 14 additions & 78 deletions

File tree

src/jacobian/plugins/matrices.py

Lines changed: 14 additions & 78 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
from typing import Any
1818

1919
from pydantic import ValidationError
20+
from sympy import Matrix
2021

2122
from jacobian.canonical import format_canonical_integer
2223
from jacobian.contracts.plugin_matrices import (
@@ -152,91 +153,26 @@ def transform_row_major_capability(request: dict[str, Any]) -> dict[str, Any]:
152153
# ---------------------------------------------------------------------------
153154

154155

156+
def _fraction_from_sympy(value: Any) -> Fraction:
157+
numerator, denominator = value.as_numer_denom()
158+
return Fraction(int(numerator), int(denominator))
159+
160+
155161
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:
159164
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())
218166

219167

220168
def _kernel_vector(matrix: list[list[int]]) -> list[Fraction] | None:
221169
"""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:
227171
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]]
240176

241177

242178
def _is_singular(matrix: list[list[int]]) -> bool:

0 commit comments

Comments
 (0)