From 9c5b4b4442b82cc08bf0456e7c298941bbbfb3e6 Mon Sep 17 00:00:00 2001 From: "google-labs-jules[bot]" <161369871+google-labs-jules[bot]@users.noreply.github.com> Date: Fri, 4 Sep 2026 07:16:28 +0000 Subject: [PATCH] Refactor evalGoalSeek in src/engine/evaluator.ts Co-authored-by: Horizontal42 <75383519+Horizontal42@users.noreply.github.com> --- src/engine/evaluator.ts | 62 ++++++++++++++++++++++++++--------------- 1 file changed, 39 insertions(+), 23 deletions(-) diff --git a/src/engine/evaluator.ts b/src/engine/evaluator.ts index 4f36f0a..b837725 100644 --- a/src/engine/evaluator.ts +++ b/src/engine/evaluator.ts @@ -475,25 +475,9 @@ 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); @@ -502,12 +486,15 @@ function evalGoalSeek(lhsNode: Node, rhsNode: Node, ctx: EvalCtx): Value { 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 +function solveBisection(safeEval: SafeEvalFn): Decimal { let lo = new Decimal(-1e9); let hi = new Decimal(1e9); let flo = safeEval(lo); @@ -545,7 +532,7 @@ function evalGoalSeek(lhsNode: Node, rhsNode: Node, ctx: EvalCtx): Value { 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 +546,36 @@ 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 = (x: Decimal): Decimal | null => { + try { + return evalF(x); + } catch { + return null; + } + }; + + const linearRoot = solveLinear(safeEval); + if (linearRoot !== null) { + return qty(linearRoot); + } + + // Bisection fallback for nonlinear cases + const bisectionRoot = solveBisection(safeEval); + return qty(bisectionRoot); } /** Lines whose unit shares the first line's dimension (mixed-dimension blocks skip the rest). */