Skip to content

feat(train): tiered (position-filtered) keep-set .sa build for Design-Z - #53

Merged
nh13 merged 5 commits into
mainfrom
feat/z-tiered-sa
Jun 24, 2026
Merged

feat(train): tiered (position-filtered) keep-set .sa build for Design-Z#53
nh13 merged 5 commits into
mainfrom
feat/z-tiered-sa

Conversation

@nh13

@nh13 nh13 commented Jun 22, 2026

Copy link
Copy Markdown

Design-Z stack 2/6 — base: main.

Tiered (position-filtered) keep-set .sa build mode: when --keep-bed is given, the on-disk suffix array retains only entries whose forward reference coordinate lies in the keep-set, applied RC-symmetrically over the doubled text (keep_doubled_pos). The full genome text/.pac is unchanged, so match_len and SA positions stay native; only the entry count shrinks. Includes forward-progress guards for the tiered zigzag walks, byte-identity/guard-firing tests vs the full index, and a single-contig build_halo_bed keep-set example.

Dual-reviewed (coderabbit --agent + local CR); fixes applied (contig name in build_halo_bed; present_anchor docstring). cargo build/clippy/test green.

Summary by CodeRabbit

  • New Features
    • Added prmi build --keep-bed for RC-symmetric, position-filtered tiered indexes that shrink the on-disk suffix array while preserving genome coordinate interpretation (incompatible with --with-isa).
    • Extended .sa sidecar metadata with optional tiering/keep-BED fields and updated tiered validation.
    • Added a Rust CLI example to generate merged “exome ∪ halo” BED intervals from FASTA + exome BED.
  • Robustness
    • Improved tiered traversal termination using a forward-progress guard (instrumented in test/coverage builds).
  • Tests
    • Added integration tests for correctness, metadata constraints, and invalid/empty keep-BED inputs.
  • CI
    • Added a spectrum-probe-count feature-gated test run.

@coderabbitai

coderabbitai Bot commented Jun 22, 2026

Copy link
Copy Markdown

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: c3224778-e5f1-4313-bb5b-786305f96432

📥 Commits

Reviewing files that changed from the base of the PR and between 7ef4814 and cce75fe.

📒 Files selected for processing (11)
  • .github/workflows/ci.yml
  • prmi/examples/build_halo_bed.rs
  • prmi/src/cli.rs
  • prmi/src/index/collect.rs
  • prmi/src/sidecar/meta.rs
  • prmi/src/train/mask.rs
  • prmi/src/train/mod.rs
  • prmi/src/train/training_set.rs
  • prmi/tests/mask.rs
  • prmi/tests/sidecar_meta.rs
  • prmi/tests/z_keep_mask.rs

Walkthrough

Adds keep-bed tiered suffix-array build support, query-time sparse-SA guards, a halo BED generator, and tests plus CI coverage for the new tiered paths.

Changes

Tiered Keep-Mask SA Build

Layer / File(s) Summary
Keep-bed mask contract
prmi/src/train/mask.rs
MaskConfig gains keep-bed fields, and keep_doubled_pos maps doubled SA coordinates to forward-reference coverage checks.
Tiered sidecar metadata
prmi/src/sidecar/meta.rs, prmi/tests/sidecar_meta.rs
Sa metadata gains tiered; validation accepts reduced entry counts only for tiered sidecars, and tests cover the accepted and rejected cases.
Tiered training-set construction
prmi/src/train/training_set.rs
keep_masked_training_set filters retained SA positions, assigns compacted ranks, and preserves prior weighting and masking behavior for the tiered path.
Tiered build-sidecar wiring
prmi/src/train/mod.rs, prmi/tests/mask.rs
Training and sidecar writing now derive retained SA cardinality from keep-bed filtering, skip ISA output under tiering, emit tiered metadata, and parse keep-bed CLI input into mask config.
Keep-bed CLI option
prmi/src/cli.rs
Cmd::Build accepts --keep-bed, rejects the with-isa combination, and threads the new argument into mask configuration.
Sparse-SA query guards
prmi/src/index/collect.rs
CollectContext adds present_anchor, feature-gated stall counting, and forward-progress termination checks in both zigzag loops.
Halo BED builder
prmi/examples/build_halo_bed.rs
build_halo_bed reads a single-contig FASTA and exome BED, counts canonical k-mers, builds a halo keep-set, expands intervals by flank, merges overlaps, and writes BED output.
Integration and CI coverage
prmi/tests/z_keep_mask.rs, .github/workflows/ci.yml
New keep-mask tests cover anchor presence, SMEM identity, termination, metadata constraints, and empty-keep rejection; CI adds spectrum-probe-count test coverage.

Sequence Diagram(s)

sequenceDiagram
  participant CLI as Cmd::Build
  participant Train as build_sidecar_from_pac_with_config
  participant Meta as Meta::validate_with_file
  participant Index as CollectContext

  CLI->>Train: --keep-bed path.bed
  Train->>Train: filter SA entries and build tiered training set
  Train->>Meta: write tiered .sa metadata
  Index->>Index: present_anchor and zigzag stall checks
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~75 minutes

Possibly related PRs

  • fg-labs/prmi#5: Extends the Sa TOML schema and validation that this PR updates with tiered and reduced entry-count handling.
  • fg-labs/prmi#6: Adds the BED interval parsing and mask infrastructure that the keep-bed path builds on.
  • fg-labs/prmi#42: Changes the same prmi/src/index/collect.rs reseed traversal that this PR updates with forward-progress guards.
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately captures the main change: a tiered, position-filtered keep-set .sa build for Design-Z.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/z-tiered-sa

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@nh13

nh13 commented Jun 22, 2026

Copy link
Copy Markdown
Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jun 22, 2026

Copy link
Copy Markdown
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@nh13

nh13 commented Jun 22, 2026

Copy link
Copy Markdown
Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jun 22, 2026

Copy link
Copy Markdown
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 10

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
prmi/src/sidecar/meta.rs (1)

370-398: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Reject tiered=true when sa.l_pac is missing.

Validation currently skips all tiered-entry invariants if l_pac is absent. For tiered sidecars, that permits metadata that later derives the wrong forward length from sa_num.

Proposed fix
-        if let Some(l_pac) = self.sa.l_pac {
+        if self.sa.tiered == Some(true) && self.sa.l_pac.is_none() {
+            return Err(Error::SizeMismatch {
+                file: file.to_path_buf(),
+                detail: "tiered=true requires sa.l_pac to be set".to_string(),
+            });
+        }
+        if let Some(l_pac) = self.sa.l_pac {
             let expected_entries = l_pac
                 .checked_mul(2)
                 .and_then(|v| v.checked_add(1))
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@prmi/src/sidecar/meta.rs` around lines 370 - 398, The validation logic for
tiered sidecars currently skips all checks when l_pac is missing because all
validation is gated within the if let Some(l_pac) block. Add an additional check
after this block (in the else case or as a separate condition) to explicitly
reject tiered sidecars when sa.l_pac is None by returning an Error::SizeMismatch
with an appropriate detail message indicating that tiered sidecars require l_pac
to be present. This ensures that metadata with tiered=true but missing l_pac is
caught and prevents downstream issues with forward length derivation from
sa_num.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@prmi/examples/build_halo_bed.rs`:
- Around line 47-52: The FASTA parsing logic in the strip_prefix section
silently continues when encountering multiple FASTA headers, causing the first
contig name to be used for all sequences. Additionally, the BED contig
processing around lines 74-79 ignores the contig field from the BED file
entirely. To fix this, add a check in the FASTA header parsing block to reject
if contig is already set (preventing multiple headers), and add a validation in
the BED processing section to ensure the BED contig matches the FASTA contig
before processing. This will cause the program to fail with clear error messages
instead of silently producing misaligned coordinates.
- Around line 36-37: The flank variable is parsed without validation, allowing
negative values that cause invalid interval bounds in arithmetic operations. Add
an assertion after parsing flank (where `let flank: i64 =
a[5].parse().expect("flank");` is located) to validate that flank is within
acceptable bounds, similar to the pattern used for the k variable validation.
This assertion should reject negative or otherwise invalid flank values.

In `@prmi/src/cli.rs`:
- Around line 273-277: The validation guard that checks for the incompatibility
between keep_bed and with_isa currently uses anyhow::bail! for error handling,
but this is a user-input validation error that should use the crate's
Error::InvalidInput categorization instead. Replace the anyhow::bail! call in
the condition block (where keep_bed.is_some() && with_isa is checked) with a
return statement that constructs Error::InvalidInput with a detail field
containing the descriptive error message about the --keep-bed and --with-isa
incompatibility, ensuring consistency with other CLI input validation errors in
the crate.

In `@prmi/src/index/collect.rs`:
- Around line 275-283: The while loop condition at the start of the loop and the
slice operation in the mem_search call both perform unchecked arithmetic by
adding K to the start index, which can overflow or panic when slicing the
caller-provided read buffer. Replace the direct addition operations with
checked_add to safely compute start + K, and return false immediately if the
checked_add returns None (indicating overflow). This ensures fail-closed
behavior before any buffer slicing occurs, protecting against integer overflow
when working with caller-derived indices and buffer lengths.

In `@prmi/src/sidecar/meta.rs`:
- Around line 563-613: The tiered metadata tests
(tiered_sa_allows_fewer_entries_than_full, non_tiered_rejects_fewer_entries,
num_entries_above_max_rejected_even_when_tiered) only exercise mode "2" with
bytes_per_entry = 13, but this on-disk format change should be validated across
all memory modes. Create an additional test helper function similar to
tiered_toml that generates test TOML for mode "1" (with the appropriate
bytes_per_entry value for mode 1), then add corresponding test cases that verify
the tiered validation logic works correctly for mode "1" as well, ensuring the
same validation constraints are enforced consistently across all modes.

In `@prmi/src/train/mask.rs`:
- Around line 159-167: In the `keep_doubled_pos` function, the current logic
treats any position greater than 2*l_pac as a sentinel and returns true, which
is too permissive. Change the condition that handles coordinates beyond the
valid range to check for the exact sentinel position (pos == 2*l_pac) and return
true only for that specific case. For any other out-of-range positions (pos >
2*l_pac), add a fallback that returns false to reject malformed coordinates
instead of retaining them.

In `@prmi/src/train/mod.rs`:
- Around line 205-230: The issue is that while num_sa_entries correctly captures
the retained SA entry count when a keep-bed filter is applied, the .kmt writer
path still uses the full sa.len() instead of the retained count. Find where the
.kmt metadata is being written (likely in a writer or builder that takes sa_num
as a parameter) and replace the sa.len() call with num_sa_entries to ensure both
.sa/.meta and .kmt files use the same SA cardinality, which will keep k-mer
table bounds valid at read time.

In `@prmi/src/train/training_set.rs`:
- Around line 411-446: The keep_masked_training_set function currently ignores
mask and prior weighting configurations that are applied in masked_training_set,
such as mask_n_runs, homopolymer filtering, and mask_bed, causing inconsistent
behavior when using --keep-bed with mask options. To fix this, either add
MaskConfig, NBitmap, and Prior parameters to the keep_masked_training_set
function signature and apply the same filtering and weighting transformations
that masked_training_set applies to the keys and sa_indices before returning the
TrainingSet, or alternatively add validation at the CLI or build setup level to
explicitly reject incompatible combinations of --keep-bed with masking/prior
options.

In `@prmi/tests/z_keep_mask.rs`:
- Around line 273-291: The test currently only verifies self-consistency (occ_Z
<= occ_full) and that the call returns without hanging, but does not
independently verify that the stall guard path (search_pivot <=
entry_search_pivot) actually fires during execution. Add instrumentation to
track when the guard fires (through the smem_mns function or by adding a counter
in the search logic), then add an assertion after the loop in the test to verify
that the guard count is non-zero, providing independent oracle coverage for the
new search behavior as required by test conventions.
- Around line 390-399: The current assertion using assert_ne!(f, zz) is
insufficient because it only checks that the vectors differ overall, but zz
might still contain the full-span off-keep SMEM that should have been rejected.
Instead of comparing the full vectors, filter the SMEM entries from f to
identify those matching the full-span pattern (where m=0 and n=read_len-1), then
assert that none of these specific full-span SMEM entries are present in zz.
This ensures the keep-masked index properly rejected the off-keep full-span
match rather than just having additional or different entries.

---

Outside diff comments:
In `@prmi/src/sidecar/meta.rs`:
- Around line 370-398: The validation logic for tiered sidecars currently skips
all checks when l_pac is missing because all validation is gated within the if
let Some(l_pac) block. Add an additional check after this block (in the else
case or as a separate condition) to explicitly reject tiered sidecars when
sa.l_pac is None by returning an Error::SizeMismatch with an appropriate detail
message indicating that tiered sidecars require l_pac to be present. This
ensures that metadata with tiered=true but missing l_pac is caught and prevents
downstream issues with forward length derivation from sa_num.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 9f3a5958-b675-4228-a58c-82cf7e5e7ad1

📥 Commits

Reviewing files that changed from the base of the PR and between f7eda69 and 4dfd02d.

📒 Files selected for processing (10)
  • prmi/examples/build_halo_bed.rs
  • prmi/src/cli.rs
  • prmi/src/index/collect.rs
  • prmi/src/sidecar/meta.rs
  • prmi/src/train/mask.rs
  • prmi/src/train/mod.rs
  • prmi/src/train/training_set.rs
  • prmi/tests/mask.rs
  • prmi/tests/sidecar_meta.rs
  • prmi/tests/z_keep_mask.rs

Comment thread prmi/examples/build_halo_bed.rs Outdated
Comment thread prmi/examples/build_halo_bed.rs
Comment thread prmi/src/cli.rs
Comment thread prmi/src/index/collect.rs Outdated
Comment thread prmi/src/sidecar/meta.rs
Comment thread prmi/src/train/mask.rs
Comment thread prmi/src/train/mod.rs
Comment thread prmi/src/train/training_set.rs
Comment thread prmi/tests/z_keep_mask.rs
Comment thread prmi/tests/z_keep_mask.rs
@nh13
nh13 force-pushed the feat/z-tiered-sa branch from 4dfd02d to 3c3e8f5 Compare June 23, 2026 05:04
@nh13

nh13 commented Jun 23, 2026

Copy link
Copy Markdown
Author

Addressed all 10 inline comments plus the outside-diff comment. Two notes on the less-mechanical ones:

  • keep_masked_training_set masks/prior (training_set.rs): chose to compose rather than reject. The keep filter (which .sa entries exist) is orthogonal to the mask/prior options (which retained entries are training targets and how they are weighted), so keep_masked_training_set now threads MaskConfig/doubled NBitmap/Prior and applies the same mask_n_runs/homopolymer/mask_bed/prior logic as masked_training_set. A masked entry is still written to the .sa (only keep_doubled_pos filters the write), so the compacted rank still advances for it — it is dropped only as a training target, exactly like a short-window suffix. New keep_masked_composes_with_mask_and_prior proves sa_num is mask-invariant while the training-pair set shrinks, the retained indices stay a subset, and a non-uniform prior yields aligned weights.

  • Stall-guard fire proof (z_keep_mask.rs): added a thread-local stall_guard fire counter in collect.rs (feature-gated behind spectrum-probe-count, no-op in production), mirroring the existing attrib probe-count idiom. The repetitive-terminates test now asserts the guard fires > 0 on the tiered SA and exactly 0 on the full SA (the documented strict no-op). One caveat worth flagging: these feature-gated coverage gates (the new one and the pre-existing attrib rs_left>0 gate at collect.rs) are compiled out of the default cargo test, so I added a cargo test -p prmi --features spectrum-probe-count step to CI to actually run them.

@nh13

nh13 commented Jun 23, 2026

Copy link
Copy Markdown
Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jun 23, 2026

Copy link
Copy Markdown
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@nh13

nh13 commented Jun 23, 2026

Copy link
Copy Markdown
Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jun 23, 2026

Copy link
Copy Markdown
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 6

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
prmi/src/train/mod.rs (1)

248-292: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Fail fast when --keep-bed leaves zero trainable 32-mers.

An empty/comment-only or out-of-range keep BED can retain only the sentinel, producing an empty TrainingSet that is sent to the trainer. Return Error::InvalidInput before training.

Proposed fail-closed guard
     } else {
         // Materialized path. The SA is in doubled coordinates over `text`
         // (length 2*l_pac+1), so the training set indexes bases/n_positions in
         // those same coordinates: the 0..=3 base array from `text` (1..=4,
@@
         let n_positions_2x = doubled_n_bitmap(&n_positions, &mask, text.len());
         masked_training_set(&sa, &text_bases, &n_positions_2x, &mask, &config.prior)
     };
+    if keep.is_some() && ts.is_empty() {
+        return Err(Error::InvalidInput {
+            detail: "--keep-bed retained no trainable 32-mers".into(),
+        });
+    }
     let model = crate::train::trainer::train_with_config(&ts, l2_leaf_count, &config)?;

Based on learnings, user-provided keep BED failures should use Error::InvalidInput rather than surfacing as internal trainer failures.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@prmi/src/train/mod.rs` around lines 248 - 292, When the keep option is
provided and keep_masked_training_set() is called to generate the TrainingSet,
add a validation check immediately after its return to ensure the TrainingSet is
not empty. If the TrainingSet contains zero trainable 32-mers (which can happen
when a keep BED is empty, contains only comments, or specifies out-of-range
intervals), return Error::InvalidInput with a descriptive message before passing
the training set to train_with_config(). This prevents empty training sets from
reaching the trainer and surfacing as internal failures.

Source: Learnings

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@prmi/examples/build_halo_bed.rs`:
- Around line 36-38: The flank variable is parsed as i64 and only checked for
non-negative values, but large positive values can still cause overflow in
subsequent interval arithmetic operations. Parse flank as usize instead of i64
(which automatically prevents negative values), remove or update the assertion
that checks flank >= 0 since usize is unsigned, and update all downstream
interval arithmetic operations that use flank (in the range around lines
178-209) to use saturating arithmetic methods to prevent overflow.
- Around line 116-141: The k-mer counting logic currently uses only the forward
k-mer code as the key, but should use the canonical form to handle
reverse-complement matches. Replace all k-mer key references with the minimum of
the forward code and its reverse-complement: use min(code, revcomp(code, k))
instead of just code when calling count.entry(code), in_ex_kmer.insert(code,
true), and count.get(kmer) in the filter. Apply the same canonicalization
pattern to the second pass mentioned at lines 149-165 to ensure consistent halo
set membership across both the forward and reverse-complement representations.
- Around line 75-103: The BED file parsing logic in the lines iteration
(starting with for line in bf.lines()) needs to be more strict about input
validation to fail closed on malformed data. After reading each line, trim
whitespace from the input. Expand the skip conditions to include lines starting
with "browser" in addition to the existing checks for empty lines, comments, and
"track". After parsing the start and end values from c[1] and c[2], add
validation to ensure start < end and reject the row if this condition is not
met. Finally, instead of silently truncating intervals that extend past the
reference using e.min(n) in the bitmap fill loop, add a check to reject any
interval where end > n and emit an error before processing.

In `@prmi/src/index/collect.rs`:
- Around line 604-610: Replace the assignment `pivot = next_pivot` with a
checked arithmetic operation that advances from entry_search_pivot instead. Use
entry_search_pivot.checked_add(1) and bound the result to next_pivot to continue
the pass-1 scan from the next position rather than terminating early.
Additionally, add an oracle assertion after the stall guard fires to verify that
SMEM completeness is maintained and no rightward positions are skipped when
served reads are involved, such as by cross-validating against a full-SA
baseline.

In `@prmi/src/train/mod.rs`:
- Around line 495-496: The predicate used to determine whether to materialize
the n_positions_2x bitmap in the materialise_n assignment unnecessarily
allocates and scans a full bitmap by checking n_positions.is_some() even when no
Ns are present in the FASTA. Replace the n_positions.is_some() check with a
predicate that verifies n_positions actually contains data (such as checking if
the bitmap has a non-zero count or non-empty content) to avoid materialization
when the bitmap is empty and respect the documented empty-bitmap fast path.

In `@prmi/src/train/training_set.rs`:
- Around line 770-779: The test assertions in the training_set.rs file verify
that weights are either 3.5 or 1.0 and that some pairs have the BED weight of
3.5, but there is no assertion confirming that RC-half pairs actually receive
the 1.0 weight. This allows the test to pass even if all pairs are incorrectly
weighted as 3.5. Add a third assertion w.contains(&1.0) after the existing
assertions to verify that at least some RC-half pairs receive the expected 1.0
weight, tightening the test coverage.

---

Outside diff comments:
In `@prmi/src/train/mod.rs`:
- Around line 248-292: When the keep option is provided and
keep_masked_training_set() is called to generate the TrainingSet, add a
validation check immediately after its return to ensure the TrainingSet is not
empty. If the TrainingSet contains zero trainable 32-mers (which can happen when
a keep BED is empty, contains only comments, or specifies out-of-range
intervals), return Error::InvalidInput with a descriptive message before passing
the training set to train_with_config(). This prevents empty training sets from
reaching the trainer and surfacing as internal failures.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 3c1bbefc-78df-4aec-b768-9240a92563f5

📥 Commits

Reviewing files that changed from the base of the PR and between 3c3e8f5 and 256b24f.

📒 Files selected for processing (11)
  • .github/workflows/ci.yml
  • prmi/examples/build_halo_bed.rs
  • prmi/src/cli.rs
  • prmi/src/index/collect.rs
  • prmi/src/sidecar/meta.rs
  • prmi/src/train/mask.rs
  • prmi/src/train/mod.rs
  • prmi/src/train/training_set.rs
  • prmi/tests/mask.rs
  • prmi/tests/sidecar_meta.rs
  • prmi/tests/z_keep_mask.rs

Comment thread prmi/examples/build_halo_bed.rs Outdated
Comment thread prmi/examples/build_halo_bed.rs
Comment thread prmi/examples/build_halo_bed.rs
Comment thread prmi/src/index/collect.rs
Comment thread prmi/src/train/mod.rs Outdated
Comment thread prmi/src/train/training_set.rs
@nh13
nh13 force-pushed the feat/z-tiered-sa branch from 256b24f to 5ed52fa Compare June 23, 2026 23:20
@nh13

nh13 commented Jun 24, 2026

Copy link
Copy Markdown
Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jun 24, 2026

Copy link
Copy Markdown
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@nh13

nh13 commented Jun 24, 2026

Copy link
Copy Markdown
Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jun 24, 2026

Copy link
Copy Markdown
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
prmi/src/train/mod.rs (1)

248-292: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Reject zero-pair tiered training before model fitting.

An empty or out-of-reference --keep-bed can retain only the sentinel, yielding ts.is_empty() and pushing a user-input failure into train_with_config. One-line fix: fail closed with Error::InvalidInput before training.

Proposed guard
         masked_training_set(&sa, &text_bases, &n_positions_2x, &mask, &config.prior)
     };
+    if ts.is_empty() {
+        return Err(Error::InvalidInput {
+            detail: "training set is empty; relax --keep-bed/--mask-* or use a reference with at least one 32-mer".into(),
+        });
+    }
     let model = crate::train::trainer::train_with_config(&ts, l2_leaf_count, &config)?;

Based on learnings, user-provided BED/config validation failures should use Error::InvalidInput { detail: ... }.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@prmi/src/train/mod.rs` around lines 248 - 292, After the conditional block
that creates the training set `ts` (the if/else chain involving
keep_masked_training_set, streamed_training_set, and masked_training_set), add a
validation guard that checks if the training set is empty using `ts.is_empty()`.
If the training set is empty, return an `Error::InvalidInput` with a descriptive
detail message before the call to `train_with_config`. This ensures user-input
validation failures from empty or out-of-reference `--keep-bed` files are caught
early and reported as input errors rather than propagating into the model
fitting logic.

Source: Learnings

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Outside diff comments:
In `@prmi/src/train/mod.rs`:
- Around line 248-292: After the conditional block that creates the training set
`ts` (the if/else chain involving keep_masked_training_set,
streamed_training_set, and masked_training_set), add a validation guard that
checks if the training set is empty using `ts.is_empty()`. If the training set
is empty, return an `Error::InvalidInput` with a descriptive detail message
before the call to `train_with_config`. This ensures user-input validation
failures from empty or out-of-reference `--keep-bed` files are caught early and
reported as input errors rather than propagating into the model fitting logic.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: ce6db9af-74f7-440b-a3bc-88dd61c66f0d

📥 Commits

Reviewing files that changed from the base of the PR and between 256b24f and 5ed52fa.

📒 Files selected for processing (11)
  • .github/workflows/ci.yml
  • prmi/examples/build_halo_bed.rs
  • prmi/src/cli.rs
  • prmi/src/index/collect.rs
  • prmi/src/sidecar/meta.rs
  • prmi/src/train/mask.rs
  • prmi/src/train/mod.rs
  • prmi/src/train/training_set.rs
  • prmi/tests/mask.rs
  • prmi/tests/sidecar_meta.rs
  • prmi/tests/z_keep_mask.rs

@nh13
nh13 force-pushed the feat/z-tiered-sa branch from 5ed52fa to 5ecd3ef Compare June 24, 2026 02:00
@nh13

nh13 commented Jun 24, 2026

Copy link
Copy Markdown
Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jun 24, 2026

Copy link
Copy Markdown
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@prmi/examples/build_halo_bed.rs`:
- Around line 166-170: The halo membership counting needs to account for both
orientations in the `[Fwd||RC]` representation and properly check exome
intersection. First, when incrementing the count for canonical k-mers in the
saturation logic (at the `c.saturating_add(1)` call), check if the k-mer is
palindromic (where code equals revcomp_code(code, k)) and increment by 2 instead
of 1 to reflect both orientations. Second, replace the simple `if
in_exome[start]` check with logic that verifies the entire k-mer window
(spanning from start to start+k-1) intersects with exome regions, rather than
only checking if the start position is exonic, to correctly capture
boundary-spanning seeds.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 3edb0c34-5eaa-409b-a8ef-1932dee18f29

📥 Commits

Reviewing files that changed from the base of the PR and between 5ed52fa and 5ecd3ef.

📒 Files selected for processing (11)
  • .github/workflows/ci.yml
  • prmi/examples/build_halo_bed.rs
  • prmi/src/cli.rs
  • prmi/src/index/collect.rs
  • prmi/src/sidecar/meta.rs
  • prmi/src/train/mask.rs
  • prmi/src/train/mod.rs
  • prmi/src/train/training_set.rs
  • prmi/tests/mask.rs
  • prmi/tests/sidecar_meta.rs
  • prmi/tests/z_keep_mask.rs

Comment thread prmi/examples/build_halo_bed.rs Outdated
@nh13
nh13 force-pushed the feat/z-tiered-sa branch from 5ecd3ef to 1632d28 Compare June 24, 2026 03:25
@nh13

nh13 commented Jun 24, 2026

Copy link
Copy Markdown
Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jun 24, 2026

Copy link
Copy Markdown
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@prmi/src/index/collect.rs`:
- Line 333: In the mem_search call on line 333, remove the unsafe narrowing cast
from match_len (u64) to usize before comparing with K. Instead, compare
match_len directly as u64 by widening the constant K to u64 in the comparison,
ensuring the full 64-bit value is preserved on all platforms including 32-bit
systems.

In `@prmi/src/train/mask.rs`:
- Around line 42-44: The `keep_bed_path` field is documented as being for
`.meta` provenance and is preserved by `mask_config_from_cli`, but it is not
being written to the metadata that gets stored in the sidecar (which currently
only records `tiered` and `masked_bed`). Either add an optional `keep_bed_path`
field to the metadata structure and ensure it is populated from
`mask.keep_bed_path` when recording metadata, or remove the `keep_bed_path`
field from the struct and update its documentation if provenance tracking is not
actually needed. Ensure the implementation aligns with the documented intent.

In `@prmi/src/train/mod.rs`:
- Around line 205-218: Move the validation of the keep-bed to immediately after
genome_len is determined, before the build_gsa function is called. Add a check
to detect when the keep-bed has no intervals or when all intervals start at or
after genome_len (which would only retain the sentinel and cause failure later).
Return an Error::InvalidInput with appropriate detail message for these invalid
keep-bed cases, preventing unnecessary construction of the full doubled GSA when
the build is guaranteed to fail.

In `@prmi/tests/z_keep_mask.rs`:
- Around line 120-135: The current test for present_anchor only covers cases
where reads have no N's, which means implementations that incorrectly check only
read[0..32] or scan later kept 32-mers could still pass. Add two additional test
cases after the existing assertions: first, create a read where the first window
contains N characters but later 32-mers are N-free (and the first N-free 32-mer
is in a kept region), and verify that both z and full correctly identify it as
present; second, create a read where the first N-free 32-mer is in an off-keep
region even though a later 32-mer falls in the kept region, and verify that z
correctly rejects it while full accepts it. These additions ensure
present_anchor properly validates against its first-N-free contract.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: cc53ce5a-7a00-4a37-b690-d37b3e9a1b2c

📥 Commits

Reviewing files that changed from the base of the PR and between 5ed52fa and 1632d28.

📒 Files selected for processing (11)
  • .github/workflows/ci.yml
  • prmi/examples/build_halo_bed.rs
  • prmi/src/cli.rs
  • prmi/src/index/collect.rs
  • prmi/src/sidecar/meta.rs
  • prmi/src/train/mask.rs
  • prmi/src/train/mod.rs
  • prmi/src/train/training_set.rs
  • prmi/tests/mask.rs
  • prmi/tests/sidecar_meta.rs
  • prmi/tests/z_keep_mask.rs

Comment thread prmi/src/index/collect.rs Outdated
Comment thread prmi/src/train/mask.rs
Comment thread prmi/src/train/mod.rs
Comment thread prmi/tests/z_keep_mask.rs
@nh13
nh13 force-pushed the feat/z-tiered-sa branch from 1632d28 to 7ef4814 Compare June 24, 2026 04:35
@nh13

nh13 commented Jun 24, 2026

Copy link
Copy Markdown
Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jun 24, 2026

Copy link
Copy Markdown
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@prmi/examples/build_halo_bed.rs`:
- Around line 78-86: The FASTA parsing loop in build_halo_bed is pushing raw
bytes from line.bytes(), so CRLF inputs leave a trailing carriage return that
becomes a phantom sentinel base and shifts coordinates. Trim each sequence line
before iterating its bytes in the sequence-reading path, matching the existing
BED line handling, so only real nucleotide characters are converted and
appended.

In `@prmi/tests/sidecar_meta.rs`:
- Around line 64-79: The new tiered keep_bed round-trip test only exercises the
sample() mode 1 metadata, so add a second round-trip case that uses the valid
mode 2 field combination (bytes_per_entry = 13, encoding =
"packed_lo8_hi32_key64", stored_keys = Some(true)) to cover the same
keep_bed/tiered provenance path. Update the sidecar metadata tests in
roundtrip_tiered_keep_bed_provenance, using the existing Meta and sample
helpers, so the new on-disk fields are verified across both memory modes.

In `@prmi/tests/z_keep_mask.rs`:
- Around line 303-315: The occ-subset oracle in z_keep_mask.rs is too weak
because it only asserts when smem_mns(&full, ...) already emitted the same
(m,n), which lets Z-only spans bypass the no-inflation contract. Update the test
around smem_mns and z_set so every Z span computes its full-index occurrence
oracle directly from full using the existing search helpers (for example
full.mem_search on the read slice for that span) and compare s_z against that
value instead of skipping missing entries. Keep the assertion tied to the same
(m, n) span variables so the coverage remains independent and exact.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 4f8d82d7-0f9d-48b2-9a91-6e379cd0e79f

📥 Commits

Reviewing files that changed from the base of the PR and between 1632d28 and 7ef4814.

📒 Files selected for processing (11)
  • .github/workflows/ci.yml
  • prmi/examples/build_halo_bed.rs
  • prmi/src/cli.rs
  • prmi/src/index/collect.rs
  • prmi/src/sidecar/meta.rs
  • prmi/src/train/mask.rs
  • prmi/src/train/mod.rs
  • prmi/src/train/training_set.rs
  • prmi/tests/mask.rs
  • prmi/tests/sidecar_meta.rs
  • prmi/tests/z_keep_mask.rs

Comment thread prmi/examples/build_halo_bed.rs Outdated
Comment thread prmi/tests/sidecar_meta.rs
Comment thread prmi/tests/z_keep_mask.rs
nh13 added 5 commits June 24, 2026 09:18
Build the .sa + RMI model over only keep-set suffix positions while retaining
the full genome text/.pac, so served reads' SMEMs are byte-identical to the
whole-genome index and positions stay native genome coordinates (no translation
table). The full build is unchanged when no keep-set is given.

- MaskConfig::keep_bed + RC-symmetric keep_doubled_pos (keeps a forward position
  and its reverse-complement image together, as the RC-span/zigzag logic needs).
- SA-write keep-filter in build_sidecar_core; keep_masked_training_set trains the
  RMI on compacted ranks 0..N_kept matching the filtered .sa write order;
  meta.sa.l_pac stays the full genome length; .isa skipped under a keep-mask.
- meta.sa.tiered flag authorizes num_entries < 2*l_pac+1 (a full SA stays exact).
- --keep-bed CLI flag (incompatible with --with-isa).
The reseed/zigzag walks (zz_step1, zz_step1_reseed) assumed the full-genome SA,
where the walk always advances. A position-filtered (tiered) SA can stall them:
the left RC span and the right extension disagree over partially-present copies,
leaving search_pivot stationary while right > 0, so the existing right==0 stall
guard never fires and the walk loops forever. Break when search_pivot does not
advance past its value at the top of the iteration. This is a strict no-op on the
full SA (the walk always advances there), so full-genome byte-identity is
preserved (the oracle/lockstep proptests stay green); it only terminates the
tiered-SA stall.

Also adds present_anchor: a one-probe tiered-dispatch pre-reject (the read's first
32-mer present in this index?), far cheaper than a full collect_smems whose failed
reseeds on an absent read cost hundreds of probes.
…ject tests

Synthetic proofs for Design Z (no committed fixtures; sequence built in-test):
served reads are byte-identical (m,n,s) and off-keep reads diverge; the
compacted-rank model is well-fit; a repetitive low-copy case exercises the
forward-progress guard's firing path (it hangs without the guard); served SMEM
genome positions match the full index end-to-end; and present_anchor
discriminates served from off-keep reads.
…et builder

Measurement tool: emits the Design Z keep-set BED (exome targets plus the
genome-count-capped low-copy homology halo, each flanked) for a single-contig
reference. Two passes over the reference count k-mers and recover the positions
of in-exome low-copy k-mers. Not shipped (in-RAM; chr22-scale).
…or docstring

Address review: build_halo_bed wrote a hardcoded 'chr22' contig in its output
BED regardless of the input reference; take the contig name from the first
FASTA header instead (the tool is single-contig). Clarify present_anchor's
docstring — it inspects only the FIRST N-free 32-mer window, not 'some' window.
@nh13
nh13 force-pushed the feat/z-tiered-sa branch from 7ef4814 to cce75fe Compare June 24, 2026 16:19
@nh13

nh13 commented Jun 24, 2026

Copy link
Copy Markdown
Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jun 24, 2026

Copy link
Copy Markdown
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant