(
integrand: Expression,
variable: string,
depth: number
)
| 401 | // Bail if this is a re-entrant call triggered while building a display-only |
| 402 | // rule template (a `Subst` template can `.evaluate()` an inert `Integrate`, |
| 403 | // which re-enters the provider): pretend we can't integrate rather than |
| 404 | // clobber the outer call's deadline/memo. |
| 405 | if (this.templateBuilding) return null; |
| 406 | // This method can be re-entered while a call is in flight: the |
| 407 | // native-rational fallback goes through `ce.Integrate.evaluate()` (see |
| 408 | // nativeRationalFallback), and `build()` can `.evaluate()` an expression |
| 409 | // containing an inert `Integrate` (a With-binding whose `Int[…]` didn't |
| 410 | // close, the general `Subst` path) — both land back here via the |
| 411 | // provider. A re-entrant call must NOT clobber the outer call's |
| 412 | // per-call state: resetting the deadline would grant the outer |
| 413 | // integration a fresh time budget (violating the interruptible- |
| 414 | // evaluation contract), clearing the memo would wipe the outer call's |
| 415 | // in-flight cycle-guard entries (re-opening infinite recursion on |
| 416 | // cyclic subproblems), and resetting `trigActive` (and the caches) |
| 417 | // would leak the subproblem's state back into the outer recursion — a |
| 418 | // trig integrand whose subproblem hits the fallback could otherwise |
| 419 | // emit inert lowercase trig heads. So: a re-entrant call — detected by |
| 420 | // the in-flight counter, NOT just the native-fallback flag — inherits |
| 421 | // the outer deadline and memo, and its clobber of `trigActive`/caches |
| 422 | // is snapshotted and restored on the way out. |
| 423 | const reentrant = this.activeCalls > 0; |
| 424 | const savedTrig = this.trigActive; |
| 425 | const savedCaches = getActiveCaches(); |
| 426 | // Step-tracing accumulator (only when `explain('Integrate')` threads one |
| 427 | // in). A re-entrant call passes no `records`, so the outer's accumulator is |
| 428 | // left in place; recording is suppressed for its duration — a re-entrant |
| 429 | // subproblem is internal to whatever expression the outer step records. |
| 430 | const savedRecords = this.records; |
| 431 | if (records !== undefined) this.records = records; |
| 432 | if (reentrant) this.suppressRecording++; |
| 433 | this.activeCalls++; |
| 434 | if (!reentrant) { |
| 435 | this.deadline = Date.now() + (this.options.timeLimitMs ?? 30_000); |
| 436 | // Bound the memo to a single top-level call: it is a per-call cache + |
| 437 | // cycle guard, not a cross-call one (rules don't consult assumptions |
| 438 | // today, but if they did, a stale pre-assumption result could be |
| 439 | // served verbatim). Clearing here caps its growth and sidesteps that |
| 440 | // staleness. Not cleared on re-entry — that would wipe the outer |
| 441 | // call's in-flight cycle-guard entries. |
| 442 | this.memo.clear(); |
| 443 | } |
| 444 | // fresh predicate caches per call (zeroQ/simplify results recur heavily |
| 445 | // across the rule scan); the outer call's caches are restored below when |
| 446 | // this is a re-entrant call. |
| 447 | installCaches({ zeroQ: new Map(), simplify: new Map() }); |
| 448 | // Chapter-4 rules match against inert trig (`cos`/`sin`); detect active |
| 449 | // trig once so intRec can deactivate the integrand (and its recursive |
| 450 | // subproblems). Results are re-activated on the way out. |
| 451 | this.trigActive = hasActiveTrig(integrand); |
| 452 | // Re-activate inert heads on the way out, then normalize the trig form: |
| 453 | // the cosine→sine cofunction shift (unifyInertTrig) leaves `sin(θ+π/2)` |
| 454 | // etc. in the result; `simplifyTrig` folds those back to `cos(θ)` so the |
| 455 | // answer reads cleanly (Rubi relies on Mathematica's auto-simplification |
| 456 | // for the same step). Sound identities only — never changes the value. |
| 457 | const activate = (e: Expression | null): Expression | null => |
| 458 | e !== null && this.trigActive |
| 459 | ? cleanTrig(this.ce, activateTrig(this.ce, e)) |
| 460 | : e; |
no test coverage detected