(expr: Expression)
| 72 | // beat the single `⁴√12` (8). |
| 73 | // |
| 74 | // The premium is calibrated so a radical LITERAL costs about what the |
| 75 | // equivalent expression costs: `Sqrt(y)` prices at 6, and `√3` now prices |
| 76 | // at 6 too. |
| 77 | const radical = (n as { radical?: number }).radical; |
| 78 | if (radical !== undefined && radical > 1) |
| 79 | return numericCostFunction(n.re) + 3 + numericCostFunction(radical); |
| 80 | |
| 81 | return numericCostFunction(n.re); |
| 82 | } |
| 83 | |
| 84 | /** |
| 85 | * The default cost function, used to determine if a new expression is simpler |
| 86 | * than the old one. |
| 87 | * |
| 88 | * To change the cost function used by the engine, set the |
| 89 | * `ce.costFunction` property of the engine or pass a custom cost function |
| 90 | * to the `simplify` function. |
| 91 | * |
| 92 | */ |
| 93 | /** |
| 94 | * Price a `Power(base, exp)`. Extracted so the alias heads that canonicalize |
| 95 | * to a Power (`Square`, `Exp`) price identically to the canonical form — the |
| 96 | * cost function must not depend on which representation it is handed. |
| 97 | * |
| 98 | * The base is mostly ignored so that `2q^2` beats `2qq`, except when the base |
| 99 | * is a `Negate` (so `(-x)^n` is not cheaper than `-x^n`) or a `Multiply` (so |
| 100 | * `(ab)^n` is not artificially cheaper than the distributed `a^n b^n`). |
| 101 | */ |
| 102 | function powerCost(base: Expression, exp: Expression): number { |
| 103 | const expCost = costFunction(exp); |
| 104 | // Count a negated base too. This used to be a flat `expCost + 4`, which |
| 105 | // discarded everything under the sign — the same defect as the removed |
| 106 | // `Negate(Power(...))` shortcut, just on the other side. Once `Power` began |
| 107 | // counting its base it became load-bearing: `(-sin x)^2` scored 5 against |
| 108 | // `sin(x)^2` at 12, so the gate REJECTED the rewrite and left the negation |
| 109 | // in place. A symbol base hid it (`(-x)^2 -> x^2` still worked). |
| 110 | if (base.operator === 'Negate') return expCost + costFunction(base); |
| 111 | if (isFunction(base, 'Multiply')) { |
| 112 | // A negative coefficient under a fractional exponent must factor its sign |
| 113 | // out for a correct real result, so make the unfactored form expensive. |
| 114 | const hasNegativeCoef = base.ops.some( |
| 115 | (f) => isNumber(f) && f.isNegative === true |
| 116 | ); |
| 117 | if (hasNegativeCoef && exp.isRational === true && !exp.isInteger) |
| 118 | return expCost + costFunction(base) + 15; |
| 119 | return expCost + costFunction(base); |
| 120 | } |
| 121 | // Count the base. It used to be discarded for every base that was not a |
| 122 | // `Negate` or `Multiply`, which priced `(a+b+c+d)^20` at 2 — the same as |
| 123 | // `x^20`, and barely above `x^2` — even though it expands to 1,771 terms. |
| 124 | // The stated goal ("`2q^2` should beat `2qq`") survives: a power still costs |
| 125 | // far less than the repeated multiplication it replaces. |
| 126 | return expCost + costFunction(base); |
| 127 | } |
| 128 | |
| 129 | export function costFunction(expr: Expression): number { |
| 130 | // Special-case: Encourage the "exp/log separation" rewrite used by |
| 131 | // `simplifyLog()` for base-10 logs: |
no test coverage detected