From ccd9cd8b343332421884186f2b399886e1a3b665 Mon Sep 17 00:00:00 2001 From: "google-labs-jules[bot]" <161369871+google-labs-jules[bot]@users.noreply.github.com> Date: Wed, 26 Aug 2026 14:01:07 +0000 Subject: [PATCH 1/2] refactor: extract helper functions from evalGoalSeek Extracts `solveLinear`, `findBisectionBounds`, and `solveBisection` helper functions to improve the readability and maintainability of `evalGoalSeek` within `src/engine/evaluator.ts`. Co-authored-by: Horizontal42 <75383519+Horizontal42@users.noreply.github.com> --- src/engine/evaluator.ts | 128 ++++++++++++++++++++++++---------------- 1 file changed, 76 insertions(+), 52 deletions(-) diff --git a/src/engine/evaluator.ts b/src/engine/evaluator.ts index 4f36f0a..e77cf94 100644 --- a/src/engine/evaluator.ts +++ b/src/engine/evaluator.ts @@ -475,77 +475,74 @@ function evalDateArith(op: string, l: Value, r: Value): Value { return { ...date, ms, hasTime }; } -function evalGoalSeek(lhsNode: Node, rhsNode: Node, ctx: EvalCtx): Value { - const evalF = (x: Decimal): Decimal => { - const vars = new Map(ctx.vars); - vars.set("__x__", qty(x)); - const innerCtx: EvalCtx = { ...ctx, vars }; - const lv = evaluate(lhsNode, innerCtx); - const rv = evaluate(rhsNode, innerCtx); - const toD = (v: Value) => v.kind === "quantity" ? toBase(v) : DEC_ZERO; - return toD(lv).minus(toD(rv)); - }; - - const safeEval = (x: Decimal): Decimal | null => { - try { - return evalF(x); - } catch { - return null; - } - }; +type SafeEvalFn = (x: Decimal) => Decimal | null; +function solveLinear(safeEval: SafeEvalFn): Decimal | null { const f0 = safeEval(DEC_ZERO); const f1 = safeEval(DEC_ONE); - if (f0 !== null && f1 !== null) { const slope = f1.minus(f0); if (!slope.isZero()) { const root = f0.neg().div(slope); const fRoot = safeEval(root); - if (fRoot !== null && fRoot.abs().lt(new Decimal("1e-9"))) - return qty(root); + if (fRoot !== null && fRoot.abs().lt(new Decimal("1e-9"))) return root; } } + return null; +} - // Bisection fallback for nonlinear cases +interface Bounds { + lo: Decimal; + hi: Decimal; + flo: Decimal | null; + fhi: Decimal | null; +} + +function findBisectionBounds(safeEval: SafeEvalFn): Bounds { let lo = new Decimal(-1e9); let hi = new Decimal(1e9); let flo = safeEval(lo); let fhi = safeEval(hi); - if (flo === null || fhi === null || flo.mul(fhi).gt(0)) { - const probes = [ - 1, 2, 5, 10, 100, 1000, 10000, -1, -2, -5, -10, -100, -1000, -10000, - ]; - let found = false; - for (const p of probes) { - const pDec = new Decimal(p); - const fp = safeEval(pDec); - if (fp === null) continue; - if (flo === null) { - lo = pDec; - flo = fp; - } else if (fhi === null) { - hi = pDec; - fhi = fp; - } else if (flo.mul(fp).lte(0)) { - hi = pDec; - fhi = fp; - } else if (fhi.mul(fp).lte(0)) { - lo = pDec; - flo = fp; - } else continue; - if (flo !== null && fhi !== null && flo.mul(fhi).lte(0)) { - found = true; - break; - } - } - if (!found) throw new EvalError("no solution"); + if (flo !== null && fhi !== null && flo.mul(fhi).lte(0)) { + return { lo, hi, flo, fhi }; } + const probes = [ + 1, 2, 5, 10, 100, 1000, 10000, -1, -2, -5, -10, -100, -1000, -10000, + ]; + let found = false; + for (const p of probes) { + const pDec = new Decimal(p); + const fp = safeEval(pDec); + if (fp === null) continue; + if (flo === null) { + lo = pDec; + flo = fp; + } else if (fhi === null) { + hi = pDec; + fhi = fp; + } else if (flo.mul(fp).lte(0)) { + hi = pDec; + fhi = fp; + } else if (fhi.mul(fp).lte(0)) { + lo = pDec; + flo = fp; + } else continue; + if (flo !== null && fhi !== null && flo.mul(fhi).lte(0)) { + found = true; + break; + } + } + if (!found) throw new EvalError("no solution"); + return { lo, hi, flo, fhi }; +} + +function solveBisection(safeEval: SafeEvalFn, initialBounds: Bounds): Decimal { + let { lo, hi, flo, fhi } = initialBounds; for (let i = 0; i < 100; i++) { const mid = lo.plus(hi).div(2); - if (hi.minus(lo).abs().lt(new Decimal("1e-10"))) return qty(mid); + if (hi.minus(lo).abs().lt(new Decimal("1e-10"))) return mid; const fm = safeEval(mid); if (fm === null) { lo = mid.plus("1e-9"); @@ -559,7 +556,34 @@ function evalGoalSeek(lhsNode: Node, rhsNode: Node, ctx: EvalCtx): Value { flo = fm; } } - return qty(lo.plus(hi).div(2)); + return lo.plus(hi).div(2); +} + +function evalGoalSeek(lhsNode: Node, rhsNode: Node, ctx: EvalCtx): Value { + const evalF = (x: Decimal): Decimal => { + const vars = new Map(ctx.vars); + vars.set("__x__", qty(x)); + const innerCtx: EvalCtx = { ...ctx, vars }; + const lv = evaluate(lhsNode, innerCtx); + const rv = evaluate(rhsNode, innerCtx); + const toD = (v: Value) => (v.kind === "quantity" ? toBase(v) : DEC_ZERO); + return toD(lv).minus(toD(rv)); + }; + + const safeEval: SafeEvalFn = (x: Decimal) => { + try { + return evalF(x); + } catch { + return null; + } + }; + + const linearRoot = solveLinear(safeEval); + if (linearRoot !== null) return qty(linearRoot); + + const bounds = findBisectionBounds(safeEval); + const bisectionRoot = solveBisection(safeEval, bounds); + return qty(bisectionRoot); } /** Lines whose unit shares the first line's dimension (mixed-dimension blocks skip the rest). */ From a593254eedfc70b34a8eb651adb0ec3e86bc17d1 Mon Sep 17 00:00:00 2001 From: "google-labs-jules[bot]" <161369871+google-labs-jules[bot]@users.noreply.github.com> Date: Wed, 26 Aug 2026 14:08:53 +0000 Subject: [PATCH 2/2] refactor: extract helper functions from evalGoalSeek Extracts `solveLinear`, `findBisectionBounds`, and `solveBisection` helper functions to improve the readability and maintainability of `evalGoalSeek` within `src/engine/evaluator.ts`. Fixed a CI build failure by removing the unused `fhi` variable in `solveBisection`. Co-authored-by: Horizontal42 <75383519+Horizontal42@users.noreply.github.com> --- src/engine/evaluator.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/engine/evaluator.ts b/src/engine/evaluator.ts index e77cf94..6cfed8e 100644 --- a/src/engine/evaluator.ts +++ b/src/engine/evaluator.ts @@ -539,7 +539,7 @@ function findBisectionBounds(safeEval: SafeEvalFn): Bounds { } function solveBisection(safeEval: SafeEvalFn, initialBounds: Bounds): Decimal { - let { lo, hi, flo, fhi } = initialBounds; + let { lo, hi, flo } = initialBounds; for (let i = 0; i < 100; i++) { const mid = lo.plus(hi).div(2); if (hi.minus(lo).abs().lt(new Decimal("1e-10"))) return mid; @@ -550,7 +550,6 @@ function solveBisection(safeEval: SafeEvalFn, initialBounds: Bounds): Decimal { } if (flo!.mul(fm).lte(0)) { hi = mid; - fhi = fm; } else { lo = mid; flo = fm;