| 204 | |
| 205 | // --- numeric reference (the invariant target) ------------------------------ |
| 206 | function refSamples(c: Case): (number | null)[] { |
| 207 | if (c.op === 'solve' || c.op === 'gcd' || c.op === 'resultant') return []; // custom-graded below |
| 208 | const f = ce.expr(c.arg); |
| 209 | if (c.op === 'integrate') return POINTS.map((p) => numAt(ce, f, c.varName, p)); // integrand |
| 210 | if (c.op === 'diff') return POINTS.map((p) => { // central difference |
| 211 | const hi = numAt(ce, f, c.varName, p + H), lo = numAt(ce, f, c.varName, p - H); |
| 212 | return hi == null || lo == null ? null : (hi - lo) / (2 * H); |
| 213 | }); |
| 214 | if (c.op === 'limit') { // near-point estimate |
| 215 | // A limit-at-∞ reference is only trustworthy when (a) no var-containing |
| 216 | // subexpression overflows at the probe point — otherwise a finite result |
| 217 | // is an artifact of `finite/∞ → 0`-style collapse (the Gruntz |
| 218 | // `ln ln(x²+2e^(e^(3x³ln x)))` case probes as 0, not 1/3) — and (b) two |
| 219 | // probe magnitudes agree, mirroring the two-sided check at finite points |
| 220 | // (slow-converging limits like `ln x/(ln x+sin x) → 1` read ~1.026 at |
| 221 | // x=1e6; no float-range probe can resolve an O(1/ln x) tail). When either |
| 222 | // guard trips, return no reference: grading falls back to solved-status |
| 223 | // + the CE-vs-SymPy cross-check. |
| 224 | if (c.point === 'PositiveInfinity' || c.point === 'NegativeInfinity') { |
| 225 | const sgn = c.point === 'PositiveInfinity' ? 1 : -1; |
| 226 | const subtreeOverflows = (e: any): boolean => { |
| 227 | if (!e?.ops) return false; |
| 228 | if (e.has?.(c.varName) && numAt(ce, e, c.varName, sgn * 1e6) == null) return true; |
| 229 | return e.ops.some(subtreeOverflows); |
| 230 | }; |
| 231 | const near = numAt(ce, f, c.varName, sgn * 1e6), far = numAt(ce, f, c.varName, sgn * 1e5); |
| 232 | if (near == null || far == null || Math.abs(near - far) > 1e-3 * (1 + Math.abs(near))) return [null]; |
| 233 | if (subtreeOverflows(f)) return [null]; |
| 234 | return [near]; |
| 235 | } |
| 236 | const a0 = Number(c.point); |