( expr: Expression, x: string, roots: ReadonlyArray<Expression>, trace?: RuleSteps )
| 2569 | // decidable guard for the narrative (a numeric ratio collapses to the |
| 2570 | // bare root, matching the pre-Phase-2 trace; a False guard drops the |
| 2571 | // step). An undecidable guard is displayed as the `When`. |
| 2572 | let value: Expression | null = m.value; |
| 2573 | if (isFunction(value, 'When')) |
| 2574 | value = conditionalRoot(ce, value.op1, value.op2); |
| 2575 | if (value === null) continue; |
| 2576 | traceStep( |
| 2577 | trace, |
| 2578 | m.because !== '' ? m.because : 'solve.template', |
| 2579 | rootsAsEquations(ce, x, [value]) |
| 2580 | ); |
| 2581 | } |
| 2582 | }; |
| 2583 | |
| 2584 | // FAST PATH: a univariate polynomial of degree ≥ 2 is solved directly from |
| 2585 | // its coefficients (quadratic formula / rational-root + numeric), skipping |
| 2586 | // the commutative pattern-matcher whose operand-permutation search dominates |
| 2587 | // polynomial solving (P1-2). Non-polynomial shapes — and linear equations, |
| 2588 | // whose rule is already cheap — fall through to the rule templates below. |
| 2589 | // `originalExpr` is preferred (clean); when radical-clearing turned a |
| 2590 | // non-polynomial original into a sqrt-free polynomial (`√(1−x²) = x²` → |
| 2591 | // `1 − x² − x⁴`), the transformed `expr` is used and `validateRoots` drops |
| 2592 | // any roots the squaring introduced. |
| 2593 | const polyExpr = |
| 2594 | polynomialDegree(originalExpr, x) >= 0 |
| 2595 | ? originalExpr |
| 2596 | : polynomialDegree(expr, x) >= 0 |
| 2597 | ? expr |
| 2598 | : null; |
| 2599 | if (polyExpr !== null && polynomialDegree(polyExpr, x) >= 2) |
| 2600 | result = solvePolynomialByCoefficients(polyExpr, x, trace); |
| 2601 | |
| 2602 | if (result.length === 0) { |
| 2603 | for (const e of exprs) { |
| 2604 | const matches = matchRootsSteps(e); |
| 2605 | traceMatches(matches); |
| 2606 | result.push(...matches.map((s) => s.value)); |
| 2607 | } |
| 2608 | } |
| 2609 | |
| 2610 | // If we didn't find a solution yet, try modifying the expression |
| 2611 | //expr. |
| 2612 | // Note: @todo we can try different heuristics here: |
| 2613 | // Collection: reduce the numbers of occurrences of the unknown |
| 2614 | // Attraction: bring the occurrences of the unknown closer together |
| 2615 | // Function Swapping: replacing function with ones easier to solve |
| 2616 | // - square roots: square both sides |
| 2617 | // - logs: exponentiate both sides |
| 2618 | // - trig functions: use inverse trig functions |
| 2619 | // Homogenization: replace a function of the unknown by a new variable, |
| 2620 | // e.g. exp(x) -> y, then solve for y |
| 2621 | |
| 2622 | let harmonized: Expression[] = []; |
| 2623 | if (result.length === 0) { |
| 2624 | harmonized = exprs.flatMap((expr) => harmonize(expr)); |
| 2625 | for (const h of harmonized) { |
| 2626 | const matches = matchRootsSteps(h); |
| 2627 | traceMatches(matches, { because: 'solve.harmonize', form: h }); |
| 2628 | result.push(...matches.map((s) => s.value)); |
no test coverage detected