* Handle Element-based indexing sets by extracting finite domains * and iterating over their values. * * Returns a detailed result to distinguish between: * - Success: domain was enumerated and reduced * - Non-enumerable: domain is valid but cannot be enumerated (keep expression symbolic) * -
( body: Expression, indexes: ReadonlyArray<Expression>, fn: (acc: T, x: Expression) => T | null, initial: T, returnReason = false // Yields only accumulator values (`T | undefined`) between iterations; the // detailed `ReduceElementResult` classification is delivered as the *return* // value. Splitting yield/return types lets `reduceBigOp` re-yield each // accumulator (for deadline checks) without widening its own yield type. )
| 663 | return undefined; |
| 664 | if (!isFunction(body, 'Power')) return undefined; |
| 665 | const base = body.op1; |
| 666 | const exp = body.op2; |
| 667 | if (!(isSymbol(base) && base.symbol === index)) return undefined; |
| 668 | if (!isNumber(exp) || exp.im !== 0) return undefined; |
| 669 | const r = exp.re; |
| 670 | // s = −exp must be a real > 1 for absolute convergence (s = 1 is the |
| 671 | // harmonic/ζ(1) pole; s ≤ 1 diverges). |
| 672 | if (!(Number.isFinite(r) && r < -1)) return undefined; |
| 673 | const s = exp.neg(); |
| 674 | const z = ce.function('Zeta', [s]).evaluate(); |
| 675 | if (lowerValue === 1) return asReadableFraction(z, ce); |
| 676 | |
| 677 | const terms: Expression[] = [z]; |
| 678 | for (let k = 1; k < lowerValue; k++) |
| 679 | terms.push( |
| 680 | body |
| 681 | .subs({ [index]: k }) |
| 682 | .evaluate() |
| 683 | .neg() |
| 684 | ); |
| 685 | return asReadableFraction(ce.function('Add', terms).evaluate(), ce); |
| 686 | } |
| 687 | |
| 688 | /** |
| 689 | * Closed form of a geometric series `Σ_{k=n₀}^∞ c·rᵏ = c·r^{n₀}/(1 − r)`, valid |
| 690 | * for `|r| < 1` (conditional-values design, Phase 3a). `r` must be free of the |
| 691 | * index `k`; `n₀` is an integer-literal lower bound; an index-free constant |
| 692 | * factor `c` is allowed (`c·rᵏ`). |
| 693 | * |
| 694 | * The convergence condition `|r| < 1` is routed through the `conditionalValue` |
| 695 | * chokepoint: |
| 696 | * - numeric `r` with `|r| < 1` → the bare exact value (per the exactness |
| 697 | * contract: `Σ(1/2)ᵏ → 2`, not `2.`); |
| 698 | * - numeric `r` with `|r| ≥ 1` → `undefined` (decidable-divergent: caller |
| 699 | * keeps the sum symbolic, mirroring the p-series entry); |
| 700 | * - symbolic `r` → `When(c·r^{n₀}/(1 − r), |r| < 1)`. |
| 701 | * |
| 702 | * Scope is deliberately just this family: no x-dependent ratios (`Σ n·xⁿ`), |
| 703 | * symbolic start indices, or derivative-of-geometric shapes. |
| 704 | */ |
| 705 | function geometricSumClosedForm( |
| 706 | body: Expression, |
| 707 | index: string, |
| 708 | lower: Expression, |
| 709 | ce: ComputeEngine |
| 710 | ): Expression | undefined { |
| 711 | if (!lower.isInteger) return undefined; |
| 712 | const n0 = lower.re; |
| 713 | if (!Number.isSafeInteger(n0)) return undefined; |
| 714 | |
| 715 | // Separate an optional index-free constant factor `c` from the `rᵏ` power. |
| 716 | let coeff: Expression = ce.One; |
| 717 | let power: Expression = body; |
| 718 | if (isFunction(body, 'Multiply')) { |
| 719 | const consts: Expression[] = []; |
| 720 | const varying: Expression[] = []; |
| 721 | for (const f of body.ops) (f.has(index) ? varying : consts).push(f); |
| 722 | if (varying.length !== 1) return undefined; |
no test coverage detected