Run one engine on one case and grade it.
(engine: ComputeEngine, c: Case)
| 72 | |
| 73 | /** Run one engine on one case and grade it. */ |
| 74 | function runCE(engine: ComputeEngine, c: Case): { v: Verdict; note?: string } { |
| 75 | let roots: any[] | null; |
| 76 | try { |
| 77 | roots = engine.expr(c.ce.mathjson).solve(c.ce.var); |
| 78 | } catch (e: any) { |
| 79 | return { v: 'error', note: e?.message }; |
| 80 | } |
| 81 | roots = roots || []; |
| 82 | const resid = engine.expr(c.ce.mathjson); |
| 83 | |
| 84 | // Keep real roots; check each is sound by substitution. |
| 85 | const sound: number[] = []; |
| 86 | let badReal = 0; |
| 87 | let vacuous = 0; |
| 88 | for (const rawRoot of roots) { |
| 89 | // A validity-guarded root `When(value, guard)` (conditional-values design, |
| 90 | // Phase 2): grade its *value* wherever the guard holds. Substituting the |
| 91 | // `When` itself would thread a guard-wrapped residual (never real). If the |
| 92 | // guard resolves False at this case's (numeric) coefficients the root is |
| 93 | // vacuous — skip it rather than count it wrong. |
| 94 | let root = rawRoot; |
| 95 | if (rawRoot?.operator === 'When') { |
| 96 | let guardSym: string | null = null; |
| 97 | try { |
| 98 | guardSym = rawRoot.op2.evaluate().symbol; |
| 99 | } catch { |
| 100 | guardSym = null; |
| 101 | } |
| 102 | if (guardSym === 'False') { |
| 103 | vacuous++; |