Home / Articles / Calculations You Can Defend HES-04
Solvers That Silently Return Wrong Results
Five different calculation engines on this site, given a problem with no solution, returned a result instead of an error: displacements of order 10¹¹, reactions that break equilibrium. This article explains why, how it was fixed, and how the fixes themselves went wrong.
1. A program that does not crash
A calculation program that crashes is bad; one that shows a wrong number without crashing is far worse. A crash stops the user; a wrong number leads the user to a wrong decision. In audits in October 2026 the same pattern was found in five different engines of archi-civil.com: given a problem with no solution, the engine returned a result instead of an error.
2. Five engines, one pattern
| Function | Found | Input | Output | Root cause |
|---|---|---|---|---|
| dynamics.eigenSolve | v584 | Non-diagonal mass matrix | First eigenvalue 0.879 (correct 0.649, +35%) | The assumption lived in a comment, not in the code |
| statics.gaussElim (truss) | v585 | Two rollers + horizontal load | Plausible member forces; ΣFx = 3, ΣFy = −1.17, ΣM = 8.66 | No singularity check |
| statics.solve3x3 | v585 | Well-conditioned system scaled by 10⁻⁵ | "Singular matrix" — a healthy system rejected | Absolute threshold depended on units |
| fem2d.solveDense | v586 | Unsupported element | ~10¹⁴ displacements | Threshold 10⁻³⁰⁰ caught only exact zero |
| frame-solver.solveLinear | v588 | Inclined member + single pin | 6.3·10¹¹ displacements, unbalanced reactions | Absolute threshold 10⁻¹⁴; caught when axis-parallel, missed when inclined |
What the five share: none of them noticed when it was given an input that genuinely has no solution. The equilibrium equations of an unstable truss, an unsupported finite element model or a frame held by a single pin are singular: such structures have no solution because they undergo unlimited displacement under load. The correct behaviour is to say "this structure is unstable".
3. Why zero is not zero
If a system of equations is singular, a pivot in Gaussian elimination becomes zero. But a computer works not with real numbers but with finite-precision floating-point numbers. A pivot that is theoretically zero comes out as a small round-off residue such as 10⁻⁹ or 10⁻¹². Dividing by it produces huge but finite numbers: 10¹¹, 10¹⁴. The program does not crash, because technically it has not divided by zero.
The frame solver shows this clearly. The same mechanism was caught when the member was axis-parallel: matrix terms were exactly zero and so was the pivot. With the member inclined, products of sines and cosines left a round-off residue and the same mechanism was silently "solved". Simple, axis-parallel test cases could never see this error.
4. An absolute threshold fails both ways
Three engines tested singularity against an absolute number: singular if the pivot is below 10⁻¹⁴, 10⁻¹² or 10⁻³⁰⁰. This fails in both directions at once:
- It misses at large scale. Frame stiffnesses are of order 10⁶–10⁹; round-off grows with that scale and stays far above 10⁻¹⁴. The singular system passes.
- It raises false alarms at small scale. The small equation solver rejected a perfectly healthy system whose entries were of order 10⁻⁵ as "singular", because a determinant shrinks with the cube of the scale.
The fix is to define the threshold relative to the matrix's own scale: singular if the pivot is below a small fraction of the largest entry. The same system then gives the same verdict whether written in metres or millimetres. In the audit this was tested with 200 random systems at five scales from 10⁻⁶ to 10⁶.
5. Physics is an oracle: the equilibrium check
Even a relative threshold may not catch every case. So a second layer was added to the truss and frame solvers: after solving, global equilibrium is checked. A correct static solution must satisfy ΣFx = 0, ΣFy = 0 and ΣM = 0. This check is entirely independent of how the solver is written; it catches a case even if the singularity check misses it. In the two-roller truss example, the old solver's output violated all three equilibrium conditions.
6. Fixes make mistakes too
The most instructive part of these audits is the errors found in the fixes themselves:
- Equilibrium check with rounded numbers: the first equilibrium check for the truss solver used reactions rounded to four decimals in the output. Multiplied by a lever arm, the rounding exceeded the tolerance and stable, correctly solved trusses were rejected. A test with randomly generated trusses caught this. The right fix was not to loosen the tolerance but to check with unrounded values. In its final form, false rejections on 2000 random stable trusses are zero.
- "Fixing" an instructive error: the first guard written for the finite element solver broke a test that deliberately demonstrates the hourglass mode on the site: that demonstration used the shape of the huge displacements of a near-singular system. The solution was to separate the two cases: a clear error in normal use; in reduced integration the solution is returned but flagged as "near-singular" with a warning.
7. Tests that never ran
The same pattern also appears in verification itself. Throughout the audits each round reported "no new failures", comparing against a list of 49 tests that were already failing at the start. Investigation showed that 45 of them crashed on the first line because of a missing library — they never ran at all. Once the library was installed they all passed; but until then, the assurance given for the tools they cover was false. A solver that silently returns a wrong result and a test that silently checks nothing are two faces of the same problem.
8. Rules
- Do not try to solve the unsolvable. A singular system is not a result but a diagnosis: "the structure is unstable."
- Thresholds must be relative. An absolute threshold depends on units and fails in both directions.
- Assumptions must be guarded in code. An assumption in a comment ("M is diagonal") prevents nothing.
- Add a physical oracle. Equilibrium, energy, symmetry: facts independent of the code.
- Test the fix too. Measure the false-alarm rate with random, wide-ranging inputs; do not break deliberate demonstrations.
- Verify that the test runs. Do not report "no new failures" against a failing test whose cause you do not know.
References
- archi-civil.com release notes (README) and docs/MOTOR-DENETIM-KAYDI.md, v584–v588: input, output, fix and tests for each error. docs/TEST-ORTAMI.md (v587).
- Higham, N. J. (2002). Accuracy and Stability of Numerical Algorithms, 2nd ed., Chapter 9 (Gaussian elimination, pivoting). SIAM.
- Golub, G. H. and Van Loan, C. F. (2013). Matrix Computations, 4th ed., Chapters 3 and 5 (condition number, numerical rank). Johns Hopkins University Press.
The numbers in this article were produced by the site's engines; code values are given with their clause numbers.