Skip to content

guess()'s default candidate pool has no per-candidate cost budget, letting slow-fitting distributions dominate runtime #1403

Description

@synesenom

Problem

test/guess.js's unfiltered-default-pool test for VonMises ("should include VonMises in the default pool for data it fits well") currently takes ~81 seconds by itself, and is one of the single largest contributors to npm test's ~6-10 minute runtime. This was measured directly via a fresh full-suite run (10,176 tests, 627s wall clock under mocha --parallel with 3 workers on a 4-core container).

Root cause

Issue #1063 diagnosed and PR #1080 fixed a related but narrower problem: DoublyNoncentralBeta/DoublyNoncentralF.fit() chasing a near-flat likelihood ridge at unbounded Powell-search cost. #1080 bounded that single distribution's fit() search budget to ~1-2s/call — a real and correct fix, but it only bounds one candidate's cost, not guess()'s pool-wide iteration cost.

guess()'s default candidate pool has grown to ~144 distributions. For data shaped like VonMises(0, 2) (bounded, real-valued, range ≈ [-π, π]), the support hard-filter does not exclude most real-line-support distributions the way it excludes non-negative-only ones. Issue #1063's own investigation already measured several other distributions as individually slow-but-bounded on comparable data: F (8510ms), DoublyNoncentralT (4285ms), FisherZ (4251ms), QExponential (4262ms), JohnsonSU (2505ms), GeneralizedGamma (1570ms), NoncentralT (1115ms). None of these were addressed by #1080's fix, which was scoped narrowly to DoublyNoncentralBeta/F. When guess() iterates its full pool against VonMises-shaped data, calling fit() serially on every candidate that survives the support filter, these per-candidate costs (1-8s each) sum up — plus additional slowdown from CPU contention across mocha --parallel workers — to the observed ~81s for one test.

This is exactly the scenario issue #1063's own "Suggested investigation" section flagged but explicitly left out of scope for #1080:

"Consider whether ran.dist.guess()'s default candidate pool needs ... a general per-candidate timeout/cost-budget in guess()'s _fitSurvivors ... as a more robust fix than exclusion lists per distribution."

Suggested investigation

Reproduction

const dist = require('ranjs').dist
const data = new dist.VonMises(0, 2).seed(0).sample(500)
const t0 = Date.now()
dist.guess(data) // no candidates override — real unfiltered default pool
console.log(Date.now() - t0) // observed ~81s inside full mocha --parallel suite run

Related

  • Closes the loop left open by Fix DoublyNoncentralF/Beta.fit() severe slowdown #1063 / PR Bound DoublyNoncentralBeta/F.fit() search cost #1080, which fixed one candidate's cost but not the pool-wide iteration cost.
  • See solutions/testing/2026-07-21-1055-guess-default-pool-latent-fit-cliff.md for the original latent-cliff discovery and the test-design workaround (unfilteredPool flag in test/guess.js's FORMERLY_EXCLUDED table) that avoided asserting on the slow path for most distributions but still leaves VonMises's assertion exercising it.

Metadata

Metadata

Assignees

No one assigned

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions