Skip to content

Compute the determinant without dividing by its pivots (#992) - #998

Merged
Rafael-SOWNet merged 2 commits into
masterfrom
fix/determinant-division-free-992
Aug 22, 2026
Merged

Compute the determinant without dividing by its pivots (#992)#998
Rafael-SOWNet merged 2 commits into
masterfrom
fix/determinant-division-free-992

Conversation

@Rafael-SOWNet

Copy link
Copy Markdown
Collaborator

Closes #992.

The defect

Matrix.Determinant is computed by Gaussian elimination, which leaves the pivots as literal divisions. The expression it returns is therefore undefined wherever a pivot vanishes — at points where the determinant itself is perfectly well defined.

var m = (Entity.Matrix)"[[x, 1], [2, y]]".ToEntity();
m.Determinant             // was: x * (y * x + -2) / x           now: x * y + -2
m.Determinant.Simplify()  // was: x * y - 2 provided not x = 0   now: x * y - 2
m.Determinant.Substitute("x", 0).Substitute("y", 5).Evaled   // was: NaN   now: -2

x = 0 is an ordinary point of that matrix. At 3×3 it stops being an edge case, because the denominator is a ^ 4 * (a * e - b * d) and two separate conditions have to miss:

substituted into [[a, b, c], [d, e, f], [g, h, i]].Determinant was now
[[0, 1, 2], [3, 4, 5], [6, 7, 8]] NaN 0
[[1, 2, 3], [2, 4, 6], [1, 1, 1]] NaN 0
[[1, 2, 3], [4, 5, 6], [7, 8, 10]] -3 -3
[[2, 1, 0], [1, 2, 1], [0, 1, 2]] 4 4

Two of four ordinary matrices, and the first is the singular example every linear-algebra course opens with. Both wrong answers are silent: the call succeeds and returns NaN, which is exactly what a caller checking for singularity was looking for. AGENTS.md is explicit that NaN means this does not exist, and the determinant does exist.

The fix

The determinant of a matrix over a commutative ring is a polynomial in its entries, so Laplace expansion — which never divides — needs no condition at all. One call site changes.

It is also what the property already claimed. Its <summary> says "Finds the symbolical determinant via Laplace's method" and the comment above it says "We do not need to use Gaussian elimination here since we anyway get N! memory use"; only the call disagreed, and has since #404.

This is not a performance trade

That was the thing worth checking before proposing it, so I measured rather than assumed. Property only — no Simplify on top — both arms built from source on the same machine:

entries Gaussian complexity Laplace complexity Gaussian, numeric Laplace, numeric
2×2 13 9
3×3 79 37
4×4 443 163
5×5 2461 833
6×6 13673 5021 164 ms 126 ms
7×7 35173
8×8 over 120 s 358 ms
10×10 over 120 s 4478 ms

Laplace returns a smaller expression at every size, and is dramatically faster on numeric matrices — the elimination builds a symbolic quotient that InnerSimplified then has to grind down, which is why an 8×8 of small integers does not return inside two minutes today.

Laplace is O(n!), and that is real, but a fully symbolic n × n determinant has n! terms however it is computed, so it is the size of the answer rather than an overhead. The practical ceiling on a numeric matrix moves from 7×7 to 10×10 — 11×11 does not return, where under the elimination 8×8 already did not.

A fraction-free elimination (Bareiss) is O(n^3) and would raise that ceiling further. It is worth having, but it is a separate change and the correctness fix is not blocked on it — I am happy to open an issue for it.

Measured

  • Suite: 7375 passed, 0 failed, 14 skipped.
  • Corpus: 116/119, 0 wrong, 0 error, 0 timeout — and comparing the generated report row by row against the recorded one, no case's verdict or answer changed, only timings.
  • 9 new cases in MatrixTest. Controlled: with the call reverted to DeterminantGaussianSafeDivision they are 5 failed, 4 passed, the four being the controls that should pass either way.
  • BREAKING-CHANGES.md entry with both values measured on a build of each arm.

Against the other open PRs

Derived with git merge-tree --write-tree, not assumed:

against conflicts
#990 fix/bound-name-must-be-symbolic BREAKING-CHANGES.md only
#991 constant-node-984 BREAKING-CHANGES.md only
#997 fix/special-set-membership-995 BREAKING-CHANGES.md only

No source file conflicts with any of them — this touches Entity.Matrix.cs, which none of the others do. Whichever merges last takes the mechanical BREAKING-CHANGES.md round and I will do it.

🤖 Generated with Claude Code

https://claude.ai/code/session_01KbKcbJP266A3EGyQ5kq7Ru

Matrix.Determinant was computed by Gaussian elimination, which leaves the
pivots as literal divisions, so the expression it returned was undefined
wherever a pivot vanishes -- at points where the determinant itself is
perfectly well defined. Substituting [[0, 1, 2], [3, 4, 5], [6, 7, 8]] into
the general 3x3 determinant gave NaN, which asserts the value does not
exist; it is 0.

The determinant of a matrix over a commutative ring is a polynomial in its
entries, so Laplace expansion needs no condition at all. It is also what
the property's own documentation and the comment above it already claimed
was in use.

It is not a performance trade either way. Property only, both arms built
from source: Laplace returns a smaller expression at every size measured
(5021 against 13673 nodes at 6x6), and on numeric matrices it is the faster
of the two by a wide margin -- 8x8 returns in 358 ms where the elimination
does not return inside 120 s. The practical ceiling on a numeric matrix
moves from 7x7 to 10x10. A fraction-free elimination would raise it
further, and is worth having separately.

Suite 7375 passed, 0 failed. Corpus unchanged at 116/119 with 0 wrong, no
case's verdict or answer altered.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KbKcbJP266A3EGyQ5kq7Ru
@Rafael-SOWNet

Copy link
Copy Markdown
Collaborator Author

Filed the follow-up I offered above: #999, for a fraction-free (Bareiss) elimination to raise the practical size ceiling. It carries the same measured table and is explicit that this PR is not blocked on it — #998 makes every size that previously worked work better and adds three usable sizes on top, and #999 is about extending the range rather than repairing it.

…ision-free-992

# Conflicts:
#	BREAKING-CHANGES.md
@Rafael-SOWNet
Rafael-SOWNet merged commit fbc578c into master Aug 22, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

A symbolic determinant is a quotient by its pivots, so it is NaN for ordinary matrices

1 participant