vignettes/glowpipeline-overview.Rmd
glowpipeline-overview.RmdGLOWpipeline is the orchestration-and-summary layer
of the GLOW software family. The methods layer, GLOWr, provides the
single-region variant-set test (GLOWr::glow_region_test());
GLOWpipeline scales that single-region test up to a genome-wide scan and
turns the per-region output into genome-wide tables and diagnostics. It
is the STAARpipeline analog in the GLOWpipeline -> GLOWr ->
GFisher stack.
A genome-wide GLOW scan has four conceptual phases:
prepare_scan_run()).define_genome_chunks()).glow_scan_chunk() / run_scan_unit()),
typically one compute-cluster array task per chunk.aggregate_scan_results()) with
diagnostics (summarize_qq_and_lambdas()).GLOWpipeline is deliberately cohort- and cluster-agnostic: resolved objects go in, tables come out. A workflow script binds it to a dataset and a scheduler. This vignette shows the call shapes; the data-dependent steps are not evaluated here (they need a GDS file and a fitted null model), while the two self-contained steps run for real.
prepare_scan_run() resolves the parts of a run that
every chunk task shares – the fitted null model, the optimal-weight
inputs, and the output directories – so the work is done once rather
than per task. It takes a resolved config list and an
output_dir.
spine <- prepare_scan_run(
config = config, # resolved analysis config (cohort + method knobs)
output_dir = "scan_out",
chroms = 1:22
)The returned setup carries the null model and weight inputs that the scan consumes in phase 3.
define_genome_chunks() splits a set of chromosome spans
into contiguous sliding-window chunks. Each row is one chunk – one
compute-cluster array task – mapping an array index to a chromosome and
a contiguous run of window start positions. The boundary rule guarantees
the union of all chunks’ windows equals one full-span slide with no
duplicates and no gaps.
This step is self-contained, so we run it for real on a small two-chromosome example:
chrom_spans <- data.frame(
chr = c("21", "22"),
start = c(5010000L, 10510000L),
end = c(48120000L, 50810000L)
)
chunks <- define_genome_chunks(
chrom_spans,
window_size = 2000,
step_size = 1000,
chunk_strategy = "fixed_mb",
chunk_size_mb = 5
)
nrow(chunks)
#> [1] 18
head(chunks)
#> chunk_id chr chunk_start chunk_end span_end est_n_windows
#> 1 1 21 5010000 10009999 48120000 5000
#> 2 2 21 10010000 15009999 48120000 5000
#> 3 3 21 15010000 20009999 48120000 5000
#> 4 4 21 20010000 25009999 48120000 5000
#> 5 5 21 25010000 30009999 48120000 5000
#> 6 6 21 30010000 35009999 48120000 5000Each chunk’s chunk_id is the value an array task reads
(e.g. from SLURM_ARRAY_TASK_ID) to learn which chromosome
and window range it owns.
An output policy declares which artifacts the scan writes. The flat per-window results table is always on; the heavier per-window evidence, STAAR detail, and result objects default off. This constructor is self-contained:
policy <- glow_output_policy()
policy
#> <glow_output_policy>
#> flat_table : on (always; the load-bearing output)
#> evidence : off
#> staar_detail : off (needs a staar_context at the engine)
#> result_objects : offWith the spine, a chunk’s window range, and the policy in hand,
run_scan_unit() builds the chunk’s regions and filters and
runs glow_scan_chunk() over them, persisting the results.
In a cluster job this is the per-array-task call:
# `gds_path` and the null-model / weight inputs come from the shared setup;
# one of spine$b_func and spine$b_model is NULL, the other the B source.
k <- 1L # this task's chunk (SLURM_ARRAY_TASK_ID)
run_scan_unit(
gds_path = gds_path,
null_model = spine$null_model,
pi_models = spine$pi_models,
pi_features = spine$pi_features,
reference_medians = spine$reference_medians,
b_func = spine$b_func,
b_model = spine$b_model,
region_type = "window",
config = config,
chunk = chunks[k, ], # one row of define_genome_chunks()
results_path = file.path("scan_out", "results", sprintf("scan_chunk_%d.csv", k)),
output_dir = "scan_out",
output_policy = policy
)The gene-centric non-coding masks are the same call with
region_type = "noncoding" and a chromosome in place of a
chunk; config$noncoding_categories selects the masks
(default all eight) and n_cores the forked workers:
run_scan_unit(
gds_path = gds_path,
null_model = spine$null_model,
pi_models = spine$pi_models,
pi_features = spine$pi_features,
reference_medians = spine$reference_medians,
b_func = spine$b_func,
b_model = spine$b_model,
region_type = "noncoding",
config = config,
chr = 22,
results_path = file.path("scan_out", "results-glow", "glow_noncoding_chr22.csv"),
output_dir = "scan_out",
output_policy = policy,
n_cores = 4
)glow_scan_chunk() returns a list whose
results element is the flat per-window table (one row per
non-skipped window, with the window coordinates, variant counts, and the
GLOW test p-values).
region_type selects how the unit’s SNV sets are formed,
and the same call serves all four types. A unit holds many regions, and
GLOW tests one set per region: the region says which variants are
candidates, and the filter (config$filter_spec: QC label,
variant type, MAF and MAC cutoffs, minimum set size) says which
candidates stay.
"gene": one set per gene of the bundled gene table (or
of config$resolved_gene_list), every record in the gene’s
span. A gene with more records than config$gene_max_records
is tiled into segments."window": one set per sliding window of a genome chunk
(chunk), every record in the window."coding": one set per gene and coding mask
(config$categories), the records in the gene’s span whose
coding annotation matches the mask."noncoding": one set per gene and gene-centric
non-coding mask (config$noncoding_categories, the eight
masks of GLOWr::noncoding_masks()), the records anywhere on
the chromosome whose annotation links them to the gene.
build_scan_regions() groups the chromosome’s records once
per mask with GLOWr::define_regions_noncoding(), which
needs the aGDS path. The rows of the per-unit table carry the mask as
category.With n_cores above 1 the unit’s regions run on forked
workers, scheduled by their record counts. A region’s cost is its
squared record count. A region whose cost reaches the unit’s total cost
divided by 8 times n_cores runs alone, the rest are packed
into blocks of at most 25 regions whose summed cost stays within that
threshold, and the blocks run heaviest first. The written table is the
single-core one.
After all chunk tasks finish, aggregate_scan_results()
concatenates the per-chunk result files into a single genome-wide table
(ordered by chromosome then position), flags significant windows against
a Bonferroni-style threshold, and merges overlapping significant windows
into loci.
agg <- aggregate_scan_results(
chunk_files = chunk_files, # the per-chunk result files written in phase 3
primary_test = "GLOW_Omni",
alpha = 0.05
)
agg$scan_results_all # genome-wide flat table (all tested windows)
agg$scan_sig_windows # the significant windowsFor a gene-centric coding scan,
aggregate_coding_results() is the analog. It reads
results-glow/glow_coding_chr<N>.csv under
output_dir. The gene-centric non-coding masks use it too:
result_prefix = "glow_noncoding" reads their per-chromosome
tables, type_label = "noncoding" names the returned tables,
gene_anchor (from noncoding_gene_anchor())
orders a gene’s masks together although their spans differ, and
family_alpha sets the across-mask cutoff, which is
family_alpha divided by the number of tested (gene, mask)
cells. Its thresholds table lists every cutoff with its
denominator, and its completeness_table one row per
requested chromosome. staar_modes = character(0) reads no
native STAARpipeline tables; the comparator columns inside the GLOW
tables are kept either way.
tbl <- do.call(rbind, lapply(1:22, function(k) read.csv(
sprintf("scan_out/results-glow/glow_noncoding_chr%d.csv", k),
stringsAsFactors = FALSE, colClasses = c(chr = "character"))))
nc <- aggregate_coding_results(
output_dir = "scan_out",
chroms = 1:22,
gene_num_in_array = 500,
staar_modes = character(0),
result_prefix = "glow_noncoding",
type_label = "noncoding",
family_alpha = 0.05,
gene_anchor = noncoding_gene_anchor(tbl)
)
nc$noncoding_merged
nc$thresholds
nc$completeness_tableGenomic-control diagnostics come from
summarize_qq_and_lambdas(), which computes per-test lambda
values and writes QQ plots:
summarize_qq_and_lambdas(
df = agg$scan_results_all,
label = "genome",
output_dir = "scan_out",
test_columns = c("GLOW_Burden", "GLOW_SKAT", "GLOW_Omni"),
omni_test = "GLOW_Omni"
)For the gene and window routes, run completeness, run-log parsing and
per-error variant drill-downs are available via
check_scan_completeness() (chunks),
parse_chr_log() / parse_chunk_log() and
write_error_drilldowns(). The non-coding route writes its
errors and its per-mask tallies beside each per-chromosome table instead
(<stem>_errors.csv,
<stem>_summary.csv), which
aggregate_coding_results() reads into its
completeness_table; a candidate list has no interval for a
drill-down.
?define_genome_chunks,
?glow_scan_chunk, ?run_scan_unit,
?aggregate_scan_results,
?prepare_scan_run.?GLOWr::glow_region_test) and its vignettes for fitting
the null model and estimating the optimal-weight inputs.