| 115 | } |
| 116 | if (res !== null && Math.abs(res) < TOL) sound.push(rv); |
| 117 | else badReal++; |
| 118 | } |
| 119 | return judge(c, roots.length - vacuous, sound, badReal); |
| 120 | } |
| 121 | |
| 122 | /** Grade SymPy's precomputed outcome the same way (its roots are real numeric, |
| 123 | * sourced from solve()/mpmath — trusted sound). */ |
| 124 | function runSymPy(c: Case): { v: Verdict } { |
| 125 | const r = c.sympy.result; |
| 126 | if (r.status !== 'ok' || r.roots.length === 0) |
| 127 | return { v: c.verify.cardinality === 'empty' ? 'correct' : 'unsolved' }; |
| 128 | const got = r.roots.map(Number); |
| 129 | return judge(c, got.length, got, 0); |
| 130 | } |
| 131 | |
| 132 | // --- Wolfram / Mathematica (the reference baseline) ------------------------ |
| 133 | // One `wolframscript` kernel solves every case (`Solve[expr == 0, x]`), via the |
| 134 | // shared batch runner. Each case's `ce` MathJSON becomes the Wolfram residual; |
| 135 | // the runner returns the real roots and the |residual| at each, so we grade |
| 136 | // soundness ourselves (like CE) rather than trusting the roots blind. |
| 137 | function runWolframAll(): Record<string, any> { |
| 138 | const by: Record<string, any> = {}; |