Solve for a parameter
64-bitThe value of the named parameter for which the expression equals the target.
secant: c = b − f(b) × (b − a) ÷ (f(b) − f(a)); accept when |f| ≤ 1e-9 × max(1, |target|, |x₀|)
The first value that meets the residual tolerance is returned, so the expression matches the target to about 9 digits. A linear expression is solved by the first secant step, to rounding. The secant step divides by f(b) − f(a), which the loop requires to be non-zero; near a flat spot the step can be large and the bracket search takes over.
/**
* Solve f(x) = target for one variable: the value of `name` that makes the expression give `target`,
* the other values held. Starts at the variable's current value; secant steps, then a bracketing
* search outward, then bisection. → { ok, value } | { ok: false, error }.
*/
export function solveFor(ast, values, name, target, { tol = 1e-9 } = {}) {
const base = values instanceof Map ? Object.fromEntries(values) : { ...values };
const x0 = Number.isFinite(base[name]) ? base[name] : 1;
const f = (x) => { base[name] = x; try { return evaluate(ast, base) - target; } catch { return NaN; } };
const scale = Math.max(1, Math.abs(target), Math.abs(x0));
const close = (y) => Number.isFinite(y) && Math.abs(y) <= tol * scale;
let a = x0, fa = f(a);
if (close(fa)) return { ok: true, value: a };
// secant from the current value (linear expressions land in one step)
let b = x0 === 0 ? 1e-3 : x0 * (1 + 1e-4), fb = f(b);
for (let k = 0; k < 60 && Number.isFinite(fa) && Number.isFinite(fb) && fb !== fa; k++) {
const c = b - fb * (b - a) / (fb - fa), fc = f(c);
if (close(fc)) return { ok: true, value: polish(c) };
a = b; fa = fb; b = c; fb = fc;
}
// bracket outward from x0, then bisect
const g0 = f(x0);
for (let step = Math.max(1e-6, Math.abs(x0) * 1e-3); step < 1e9; step *= 2) {
for (const s of [x0 + step, x0 - step]) {
const gs = f(s);
if (close(gs)) return { ok: true, value: polish(s) };
if (Number.isFinite(g0) && Number.isFinite(gs) && Math.sign(gs) !== Math.sign(g0)) {
let lo = x0, hi = s, glo = g0;
for (let k = 0; k < 200; k++) {
const mid = (lo + hi) / 2, gm = f(mid);
if (close(gm) || Math.abs(hi - lo) <= 1e-15 * Math.max(1, Math.abs(mid))) return { ok: true, value: polish(mid) };
if (Math.sign(gm) === Math.sign(glo)) { lo = mid; glo = gm; } else hi = mid;
}
}
}
}
return { ok: false, error: tr('expr.no_value_gives', { name, target: formatNumber(target) }) };
}