Skip to content

Fused windowed engine: boundary variants are assigned to the wrong window by bin_idx #166

Description

@nspope

Boundary membership

In windowed_statistics_fused, the per-variant scatter statistics assign each variant to a window with bin_idx = cp.searchsorted(we_gpu, positions), where we_gpu is the window end coordinates. This counts how many window ends lie strictly left of a position, so a variant whose position falls exactly on a window boundary (pos equal to a window end, which for contiguous windows is also the next window's start) is assigned to the lower window. Everything else in the same engine treats a window as the half-open interval [start, end): win_start / win_stop come from searchsorted(positions, [ws, we], side='left'), so n_variants = win_stop - win_start counts a boundary variant in the next window, and the dedicated scatter engines (_build_scatter_indices) use contains = pos < win_stop, also right-open. So a boundary-coincident variant lands in window w for the bin_idx-scattered stats but in window w+1 for n_variants and the theta / divergence stats.

Concrete example: positions 0, 100, ..., 11900 (120 sites), windows of 3000 bp with no overlap, so window ends are 3000, 6000, 9000, 12000. The site at pos == 3000 is reported in n_variants for the second window ([3000, 6000)), but bin_idx = searchsorted([3000, 6000, 9000, 12000], 3000) = 0 puts it in the first window's daf_hist / mu_sfs / mean_nsl. So those features for the first window include a variant that n_variants (and pi, theta, dxy) attribute to the second.

Overlapping windows

bin_idx = searchsorted(...) assigns each variant to exactly one window. When step_size < window_size a variant belongs to up to ceil(window_size / step_size) windows, and bin_idx drops it from all but one, so daf_hist / mu_sfs / mean_nsl undercount. The theta / divergence stats do not have this problem: they go through _compute_window_ranges, which builds n_per_var candidate windows per variant (its docstring: "scatter over (possibly overlapping) windows ... each variant falls inside up to n_per_var windows"). So within a single overlapping-window call, pi is correct while daf_hist / mu_sfs are wrong.

Measured: window_size=2000, step_size=1000 over 40 sites at pos 0, 100, ..., 3900 gives 3 of 4 windows whose daf_hist disagrees with diversity.daf_histogram over each window's [start, end) slice. It is silent, with no error or warning. It only errors if a non-fusable stat is co-requested, which reroutes to the WindowedAnalyzer and raises Unknown statistic, and that is incidental.

Affected statistics

The per-site bin_idx scatter path covers daf_hist, mu_sfs, and mean_nsl (via _windowed_mean). The broader scatter_stats set (snp_dist_*, mu_var, zns, omega, mu_ld, dist_var / dist_skew / dist_kurt) should be audited for the same single-window / boundary bin_idx pattern when this is picked up. GenotypeMatrix is unaffected; this is HaplotypeMatrix windowing.

Fix direction

Route the bin_idx scatter stats through the same window-membership machinery the theta / divergence stats use, _compute_window_ranges / _build_scatter_indices (n_per_var candidate windows per variant, right-open [start, end) containment), instead of the single-window searchsorted. That fixes both the boundary membership (right-open, matching n_variants) and the overlapping-window undercount in one place, and keeps all fused stats on one assignment.

If the full fix is deferred, a stopgap is to reject or warn on overlapping windows (step_size < window_size) for these stats, so they stop returning silently wrong values.

Validation

For every window, daf_hist / mu_sfs / mean_nsl should equal the scalar computed over that window's own [start, end) variant slice, checked across a multi-window grid (not just the whole-region single window the current parity tests use), and across both regimes:

  • non-overlapping windows with a variant sitting exactly on an interior boundary (pos equal to a window end, which is also the next window's start): it must land in the same window as n_variants;
  • overlapping windows (step_size < window_size): a variant in k windows must contribute to all k.

The removed multi-window test (test_multi_window_boundary_matches_scalar) covered the boundary case for non-overlapping windows and can be restored and extended when the fix lands.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions