Integrate `integrand` with respect to `variable`. Returns null when * no rule chain applies (caller decides on inert/fallback). * NOTE: the integrand must be canonical but NOT evaluated — evaluate() * expands products like (a+bx)(c+dx), destroying the structure the * rules match on.
(integrand: Expression, variable: string)
| 182 | function hasInexactFloat(e: Expression): boolean { |
| 183 | if (e.isNumberLiteral) return (e as any).isExact === false; |
| 184 | return e.ops?.some(hasInexactFloat) ?? false; |
| 185 | } |
| 186 | |
| 187 | function containsIntegrate(e: Expression): boolean { |
| 188 | if (e.operator === 'Integrate') return true; |
| 189 | return e.ops?.some(containsIntegrate) ?? false; |
| 190 | } |
| 191 | |
| 192 | /** True if the expression tree contains an `Error` node — a rule RHS that |
| 193 | * failed to build a well-formed call (see the reject site in intUncached). */ |
| 194 | function containsError(e: Expression): boolean { |
| 195 | if (e.operator === 'Error') return true; |
| 196 | return e.ops?.some(containsError) ?? false; |
| 197 | } |
| 198 | |
| 199 | /** All operator heads appearing in the (function nodes of the) expression |
| 200 | * tree — the integrand's "skeleton" feature set. Backs the second-level |
| 201 | * dispatch screen: a rule whose compiled `requiredHeads` are not all present |
| 202 | * here provably cannot match, so it is skipped without pattern-matching (see |
| 203 | * `compile.ts` `requiredHeads` for the soundness argument). Cheap: one walk |
| 204 | * per top-level integrand shape, reused across the whole rule scan. */ |
| 205 | function collectHeads(e: Expression, out: Set<string>): void { |
| 206 | if (!e.ops) return; |
| 207 | out.add(e.operator); |
| 208 | for (const op of e.ops) collectHeads(op, out); |
| 209 | } |
| 210 | |
| 211 | /** Bottom-up application of the engine's trig simplifier — folds the |
| 212 | * `sin(θ+π/2) → cos(θ)` cofunction shifts the cosine→sine normalization |
| 213 | * introduces (and other sound trig identities) so results read cleanly. */ |
| 214 | function cleanTrig(ce: ComputeEngine, e: Expression): Expression { |
| 215 | if (!e.ops || e.ops.length === 0) return e; |
| 216 | const newOps = e.ops.map((o) => cleanTrig(ce, o)); |
| 217 | const node = newOps.every((o, i) => o === e.ops![i]) |
| 218 | ? e |
| 219 | : ce.function(e.operator, newOps); |
| 220 | const step = simplifyTrig(node as any); |
| 221 | return (step?.value as Expression) ?? node; |
| 222 | } |
| 223 | |
| 224 | /** |
| 225 | * One recorded integration step, captured by the driver only when a `records` |
| 226 | * accumulator is threaded through `int()` (i.e. under `explain('Integrate')`). |
| 227 | * |
| 228 | * - `node` is an inert `Integrate(g, x)` in the driver's INTERNAL |
| 229 | * (deactivated / Times-Power normal-form) representation — the placeholder |
| 230 | * that this step replaces in the evolving replay state. It is built from the |
| 231 | * integrand as it ENTERS `intRec`, so it matches the placeholder the parent |
| 232 | * step wrote into its own `replacement`. |
| 233 | * - `replacement` is what takes its place: a template that itself contains |
| 234 | * inert sub-`Integrate` placeholders (structural / rule steps), or a final |
| 235 | * sub-result for opaque steps. `null` means "patch me later" (the template |
| 236 | * build failed and the real result is filled in on success). |
| 237 | * - `because` is the step id (a `rubi:<rule-id>` or an `integrate.*` phase id). |
| 238 | */ |
| 239 | export type IntStepRecord = { |
| 240 | node: Expression; |
| 241 | replacement: Expression | null; |
no test coverage detected