(
p: Problem,
seed: number,
rubi?: { ce: ComputeEngine; driver: RubiDriver }
)
| 194 | if (Math.hypot(Fp.re - Fm.re, Fp.im - Fm.im) < 1e-9 * fMag) return null; |
| 195 | const d1 = { re: (Fp.re - Fm.re) / (2 * h), im: (Fp.im - Fm.im) / (2 * h) }; |
| 196 | const Fp2 = at(h / 2); |
| 197 | const Fm2 = at(-h / 2); |
| 198 | if (!Fp2 || !Fm2) return d1; |
| 199 | const d2 = { re: (Fp2.re - Fm2.re) / h, im: (Fp2.im - Fm2.im) / h }; |
| 200 | return { re: (4 * d2.re - d1.re) / 3, im: (4 * d2.im - d1.im) / 3 }; |
| 201 | } |
| 202 | |
| 203 | function runProblem( |
| 204 | p: Problem, |
| 205 | seed: number, |
| 206 | rubi?: { ce: ComputeEngine; driver: RubiDriver } |
| 207 | ): ProblemResult { |
| 208 | const start = Date.now(); |
| 209 | const done = ( |
| 210 | outcome: Outcome, |
| 211 | extra: Partial<ProblemResult> = {} |
| 212 | ): ProblemResult => ({ |
| 213 | file: p.file, |
| 214 | index: p.index, |
| 215 | source: p.source, |
| 216 | outcome, |
| 217 | ms: Date.now() - start, |
| 218 | ...extra, |
| 219 | }); |
| 220 | |
| 221 | // Fresh engine per problem (avoids cross-problem state) — except in |
| 222 | // --rubi mode, where compiled patterns are tied to a shared engine. |
| 223 | const ce = rubi?.ce ?? new ComputeEngine(); |
| 224 | |
| 225 | try { |
| 226 | const f = ce.expr(p.integrand as any); |
| 227 | const result = rubi |
| 228 | ? (rubi.driver.int(f, p.variable) ?? |
| 229 | ce.function('Integrate', [f, ce.symbol(p.variable)])) |
| 230 | : ce.expr(['Integrate', p.integrand as any, p.variable]).evaluate(); |
| 231 | |
| 232 | if (containsOperator(result, 'Integrate')) |
| 233 | return done('unsolved', { result: result.toString() }); |
| 234 | |
| 235 | // verification on huge results can grind for minutes — call it |
| 236 | // inconclusive rather than stalling the harness (numeric |
| 237 | // central-difference is per-point subs+N, so the budget is generous) |
| 238 | if (leafCount(result) > 10_000) |
| 239 | return done('inconclusive', { |
| 240 | result: result.toString().slice(0, 400), |
| 241 | detail: 'result too large to verify', |
| 242 | }); |
| 243 | |
| 244 | // Verify: F′(x) ≈ f(x) at sample points via numeric central |
| 245 | // difference — deliberately NOT symbolic D: the engine's |
| 246 | // D→simplify pipeline has an unsound x/√(x²) → 1 rewrite (loses |
| 247 | // sign(x)) that poisons derivative-based checks. (See RUBI.md.) |
| 248 | const params = new Set<string>(); |
| 249 | collectSymbols(p.integrand, params); |
| 250 | params.delete(p.variable); |
| 251 | |
| 252 | const rand = mulberry32(seed ^ hash(`${p.file}#${p.index}`)); |
| 253 | let passes = 0; |
no test coverage detected