(envCap: number)
| 475 | // rules don't yet cover. Bounded by the same wall-clock budget. This |
| 476 | // rules+native coexistence is exactly how `loadIntegrationRules` is |
| 477 | // meant to ship. |
| 478 | this.suppressRecording++; |
| 479 | let nf: Expression | null; |
| 480 | try { |
| 481 | nf = this.nativeRationalFallback(integrand, variable); |
| 482 | } finally { |
| 483 | this.suppressRecording--; |
| 484 | } |
| 485 | if (nf !== null) |
| 486 | this.record( |
| 487 | () => this.ce._fn('Integrate', [integrand, this.ce.symbol(variable)]), |
| 488 | () => nf, |
| 489 | 'integrate.partial-fractions' |
| 490 | ); |
| 491 | return nf; |
| 492 | } catch (e) { |
| 493 | // An engine deadline firing inside evaluate()/simplify(), or the |
| 494 | // matcher's own deadline (see match.ts), surfaces as a |
| 495 | // CancellationError — deadline exhaustion, not a crash: report "no |
| 496 | // rule chain applied" like any other timeout. Detect it via the |
| 497 | // `name` instance property (set as a string literal in the |
| 498 | // constructor): `instanceof CancellationError` fails across the |
| 499 | // host/plugin bundle split, and `constructor.name` is mangled by |
| 500 | // minification. |
| 501 | if (e instanceof Error && e.name === 'CancellationError') return null; |
| 502 | throw e; |
| 503 | } finally { |
| 504 | // Restore the outer call's state clobbered by this re-entry (see the |
| 505 | // header comment); a genuine top-level call leaves its state in place. |
| 506 | this.activeCalls--; |
| 507 | if (reentrant) { |
| 508 | this.suppressRecording--; |
| 509 | this.trigActive = savedTrig; |
| 510 | installCaches(savedCaches); |
| 511 | } |
| 512 | this.records = savedRecords; |
| 513 | } |
| 514 | } |
| 515 | |
| 516 | /** Engine-native antiderivative fallback for a rational integrand the Rubi |
| 517 | * rule set didn't close (see int()). Returns null when the integrand is |
| 518 | * not a rational function of `variable`, the wall-clock budget is spent, |
| 519 | * or the native integrator can't close it either (result still inert). */ |
| 520 | private nativeRationalFallback( |
| 521 | integrand: Expression, |
| 522 | variable: string |
| 523 | ): Expression | null { |
| 524 | if (NO_NATIVE_RATIONAL) return null; |
| 525 | if (this.inNativeFallback) return null; // re-entry guard (see field) |
| 526 | if (!rationalFnQ(integrand, variable)) return null; |
| 527 | // Numeric-coefficient integrands only. Symbolic-parameter rationals |
| 528 | // (e.g. 1/((a+b·x)(c+d·x)(e+f·x))) need symbolic polynomial factoring |
| 529 | // the native integrator can't do — it can't close them, and its |
| 530 | // factoring path doesn't poll the deadline, so it overruns the budget |
| 531 | // badly (observed 80 s+). Restricting to free-of-parameters integrands |
| 532 | // keeps the wins and bounds the cost. |
| 533 | if (integrand.unknowns.some((u) => u !== variable)) return null; |
| 534 | const ce = this.ce; |
nothing calls this directly
no test coverage detected