( ce: IComputeEngine, operator: string, arg: Expression )
| 195 | } |
| 196 | |
| 197 | /** |
| 198 | * Eligibility gate for **real-only** simplification rewrites (policy D4). |
| 199 | * |
| 200 | * Rewrites that are valid only on the reals — `√(x²) → |x|`, `|x²| → x²`, |
| 201 | * `|x|² → x²`, `ln(x^{2k}) → 2k·ln|x|` — must not fire when the operand was |
| 202 | * *declared* (or provably inferred) a complex or imaginary type. Although |
| 203 | * `complex ⊇ real`, an explicit `complex`/`imaginary` declaration signals a |
| 204 | * *general* complex value, so these rewrites bail on it (e.g. |
| 205 | * `z: complex ⇒ √(z²)` stays `√(z²)`, not `|z|`, since `√(i²) = i ≠ 1`). |
| 206 | * |
| 207 | * *Unconstrained* symbols (type `unknown`) — and composite expressions of them |
| 208 | * (`x·y`, `x+1`, which the engine types `number`) — keep the |
| 209 | * documented generic-real convention and remain eligible. |
| 210 | * |
| 211 | * Detection is by **type**, not by `isExtendedReal`. That predicate is |
| 212 | * three-valued for functions as well as symbols, so a composite expression of |
| 213 | * unconstrained symbols answers `undefined` rather than `true`: `x·y` and |
| 214 | * `ln(x)` both type `number`, which is not below the extended real line. |
| 215 | * Gating on it would therefore need a decision about the |
| 216 | * `undefined` case, and treating `undefined` as "bail" would drop the |
| 217 | * generic-real convention on `ln(x)+ln(y) → ln(xy)` and friends. A type that |
| 218 | * matches `complex` but not `real` is a two-valued test that reliably |
| 219 | * identifies declared/inferred complex operands, which is what D4 targets. |
| 220 | * |
| 221 | * @returns `false` (bail) when `x`'s type admits genuinely non-real complex |
| 222 | * values — that is, `complex` or `imaginary`; `true` otherwise. |
| 223 | */ |
| 224 | export function isEligibleRealRewrite(x: Expression): boolean { |
| 225 | return !isNonRealNumber(x.type.type); |
| 226 | } |
| 227 | |
| 228 | // Operators that carry at least one Poles record — a cheap gate so the numeric |
| 229 | // evaluator only does membership work for functions that can have poles. |
| 230 | const POLE_OPERATORS: ReadonlySet<string> = new Set( |
| 231 | Object.keys(DATA.operators).filter((op) => |
| 232 | DATA.operators[op].some((r) => r.property === 'Poles') |
| 233 | ) |
| 234 | ); |
| 235 | |
| 236 | function isOnPoleSet( |
no test coverage detected