Repository navigation
Apply missing_data='exclude' to every statistic in the fused engines - #332
Conversation
Under 'exclude', windowed_analysis sent only Garud-only requests to the fused engine. Every other request went to the per-window fallback, which does not know zns, omega, mu_ld, mu_var, mu_sfs, daf_hist, the snp_dist_* and dist_* statistics, or mean_nsl, so those requests raised "Unknown statistic". theta_h, singletons, fay_wu_h and max_daf raised the same way when requested with a Garud statistic, or with two populations and no two-population statistic. Called directly, windowed_statistics_fused and windowed_statistics_fused_chunked silently ignored 'exclude' for every statistic except the Garud family and omega. Both fused engines now restrict the matrix to its complete sites before they index windows. A site's completeness does not depend on the window, so this equals dropping each window's incomplete sites, and the window grid, which comes from the bin edges, does not move. Completeness is judged over the rows the request reads: both populations when a two-population statistic is requested, else the single population, else every row. After the filter the engines run in 'include' mode, so the chunked engine's hand-off to the unchunked one does not filter again over a different set of rows. A Garud-only request skips the filter, because garud_h_windows drops incomplete columns itself without a copy. Under 'exclude', windowed_analysis now uses the fused engine for any request that contains a statistic the fallback cannot compute. That set comes from the fallback's own statistic tables, not from a separate list. Every request that the change routes differently raised on main. exclude_missing_sites finds incomplete columns with column_has_missing (one pass, one byte per site) instead of a matrix-sized boolean. The filter still copies the complete sites, so under 'exclude' an engine holds one extra copy of the matrix next to the caller's. The five copies of the two-population statistic names in windowed_analysis.py now share one constant. Fixes #330
nspope
left a comment
There was a problem hiding this comment.
looks good! couple minor points
| Otherwise the filter copies the complete sites, so under 'exclude' an | ||
| engine holds one extra copy of the matrix next to the caller's. | ||
| """ | ||
| if missing_data != 'exclude' or set(statistics) <= set(_FUSED_GARUD_STATS): |
There was a problem hiding this comment.
Hmm ... so if statistics contains something that's not a Garud stat, then this doesn't fire, and the call goes through to the exclude_missing_sites below? That doesn't seem quite right?
There was a problem hiding this comment.
for example,
import numpy as np
from pg_gpu import HaplotypeMatrix
from pg_gpu.windowed_analysis import windowed_analysis
N_HAP, N_VAR, SPACING, WIN = 40, 40, 10, 200
rng = np.random.default_rng(0)
hap = (rng.random((N_HAP, N_VAR)) < 0.3).astype(np.int8)
hap[:, 5] = np.where(np.arange(N_HAP) < 20, hap[:, 5], -1) # site 5 missing in rows 20-39 only
pos = np.arange(N_VAR, dtype=np.int64) * SPACING
hm = HaplotypeMatrix(hap.copy(), pos)
df_garud_only = windowed_analysis(
hm, window_size=WIN, step_size=WIN,
statistics=["garud_h1"],
missing_data="exclude",
)
df_garud_plus = windowed_analysis(
hm, window_size=WIN, step_size=WIN,
statistics=["garud_h1", "zns"],
missing_data="exclude",
)
print("n_variants garud-only:", df_garud_only["n_variants"].to_numpy())
print("n_variants garud+zns: ", df_garud_plus["n_variants"].to_numpy())
n_variants garud-only: [20 20]
n_variants garud+zns: [19 20]There was a problem hiding this comment.
ah-- you are right. fixed this so the fused engine now counts the complete sites when it builds the window columns and the fallback sets the complete count on the window, so the n_variants column and the n_variants statistic agree.
with the latest commit your example gives
n_variants garud-only: [19 20] │
n_variants garud+zns: [19 20]
| if haplotype_matrix.device == 'CPU': | ||
| haplotype_matrix.transfer_to_gpu() | ||
|
|
||
| haplotype_matrix, missing_data = _apply_exclude( |
There was a problem hiding this comment.
this is happening before the pop1/pop2 parameter check, so we don't raise the right error if pops aren't supplied to a raw call of the engine:
import numpy as np
from pg_gpu import HaplotypeMatrix
from pg_gpu.windowed_analysis import windowed_statistics_fused
hap = np.zeros((40, 40), dtype=np.int8)
hm = HaplotypeMatrix(hap, np.arange(40, dtype=np.int64) * 10)
edges = np.arange(0, 401, 200)
windowed_statistics_fused(hm, bp_bins=edges, statistics=("fst",), missing_data="exclude")
# TypeError: 'NoneType' object is not iterable(should raise the value error indicating pops are required for Fst)
There was a problem hiding this comment.
Good catch. Fixed this so pop1/pop2 check now runs at the start of _apply_exclude before the filter
Under missing_data='exclude', a Garud-only request and the per-window fallback reported n_variants as every site in the window, while the other fused requests and the scatter engine counted the complete sites only. A Garud-only request skips the filtered copy, so each fused engine now counts its complete sites when it builds the window columns. The fallback sets the complete count on the window, so the n_variants column and the n_variants statistic read the same value. The fused engines checked pop1 and pop2 after the exclude filter, which raised TypeError when they were missing. The check now runs first, in _apply_exclude, and raises the same ValueError as under 'include'.
|
Looks good, merging |
Fixes #330.
Problem
Under
missing_data='exclude',windowed_analysissent only Garud-only requests to the fused engine. Every other request went to the per-window fallback, which does not know these statistics, so the call raisedUnknown statistic:zns,omega,mu_ld,mu_var,mu_sfs,daf_hist, thesnp_dist_*anddist_*statistics, andmean_nsl, alone or mixed;theta_h,singletons,fay_wu_handmax_dafwhen requested with a Garud statistic, or with two populations and no two-population statistic.windowed_analysisrejects the diploS/HIC statistics andmean_nslundermissing_data='exclude'#330 did not list these four.Called directly,
windowed_statistics_fusedandwindowed_statistics_fused_chunkedsilently ignored'exclude'for every statistic except the Garud family andomega. The values andn_variantswere the same as under'include'.Changes
'exclude', both engines restrict the matrix to its complete sites before they index windows (_apply_exclude).'include'mode. So the chunked engine's hand-off to the unchunked engine does not filter again over a different set of rows.garud_h_windowsdrops incomplete columns itself without a copy. That path, including itsn_variants, is unchanged.'exclude',windowed_analysisuses the fused engine for any request that contains a statistic the fallback cannot compute. The set comes from the fallback's own statistic tables (_fallback_stats), not from a separate list. Every request that this routes differently raised onmain; requests that worked onmainkeep their engine.exclude_missing_sitesfinds incomplete columns withcolumn_has_missing: one pass and one byte per site, with no matrix-sized boolean.windowed_analysis.pynow share_TWO_POP_STATS.Known limits
'exclude'an engine holds one extra copy of the matrix next to the caller's. At large sizes the chunked engine can run out of memory under'exclude'where'include'works. Before this PR those requests raised at every size. Measured on an 80 GB A100 withpi+mu_sfs:'exclude'works up to a 46.6 GiB matrix and runs out of memory at 51.3 GiB, while'include'works at 55.9 GiB. Fused engines copy the matrix under missing_data='exclude', which defeats the chunked engine's memory bound #333 tracks the fix: carry a keep index instead of copying.'exclude', addingznsto['pi', 'fst']changespi. The scatter engine judges completeness per statistic; the fused engine uses one site set for the whole request, as its allele-cap filter already does. This is part of Engines disagree on the undefined-FST sentinel and on single-population columns in mixed requests #200.Tests
tests/test_windowed_exclude.py, 25 tests:'exclude'against the scalar function on the window's complete sites, withn_variantsand the window starts.piagainst the complete sites.daf_histagainstdiversity.daf_histogram.theta_h,singletons,fay_wu_handmax_dafwith a Garud statistic and with two populations.'include'on the complete sites.24 of the 25 tests fail on
main. The chunked-against-unchunked test passes onmainonly because both engines ignore'exclude'there.The full suite passes: 1957 passed, 81 skipped, 15 xfailed.
ruff check pg_gpu/ tests/ examples/passes.