Skip to content

Commit be2f903

Browse files
authored
Merge pull request #346 from AdaWorldAPI/ccr-b2e415d9-4jfvyk-deferred-pillars
Flag deferred pillars; document that Cascade Stroke 1 can drop true hits
2 parents 334dee5 + fcbb506 commit be2f903

18 files changed

Lines changed: 130 additions & 4 deletions

‎.claude/blackboard.md‎

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,17 @@
1+
## 2026-10-08 — `PillarReport::deferred` + `certified()`; `Cascade::query` documents that Stroke 1 can drop true hits
2+
3+
- **Pillars:** Pillar-15/16/17 run no probe but report `passed = true`. A gate on `passed` counted them as certified. `PillarReport` now has `deferred: bool` (true only for those three) and `certified() = passed && !deferred`. `print()` shows `DEFERRED`. `passed` keeps its meaning, so existing callers do not change.
4+
- Breaking for code that builds `PillarReport` with a struct literal: add `deferred: false`. All 18 in-tree literals are updated.
5+
- Pinned by `deferred_pillars_are_not_certified`: 12–14 certified, 15–17 deferred, and `deferred` agrees with `n_paths == n_hops == 0` on every report.
6+
- **Cascade:** `Cascade::query`'s Stroke 1 drops any candidate whose scaled prefix estimate exceeds `threshold + 3σ`. Nothing falls back to an exact check, so a hit within the threshold whose differing bits sit in the prefix is lost. This is how a statistical cascade works, so the behaviour stays. The doc now says recall is not guaranteed and that returned hits are exact.
7+
- Pinned by `stroke1_drops_a_prefix_heavy_true_hit`: 400 bits in the prefix are dropped, the same 400 at the tail are kept, and 127 fillers keep the warm-up σ small.
8+
- **Disable runs, both red, both restored:**
9+
- `certified()` returning `passed`;
10+
- Stroke 1 also admitting `d <= threshold`.
11+
- **Also:** the `SplitMix64::new` doctest failed under `deny(warnings)` because of an unused `mut`. Fixed.
12+
- **Not fixed (pre-existing, unrelated):** clippy errors in `tests/splat3d_correctness.rs` under `--tests`.
13+
- **Source:** lance-graph board `2026-10-08-mexhat-bucket-cascade-probe.md`, which flagged both behaviours.
14+
115
## 2026-10-08 — `PowerSums::checked_affine`, `CrossPowerSums::checked_affine`: power sums of an affine image from the sums alone
216

317
- **New** (both in `src/simd_masking_ops.rs`, reached through `ndarray::simd`):

‎src/hpc/cascade.rs‎

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -266,6 +266,16 @@ impl Cascade {
266266
}
267267

268268
/// Run the full 3-stroke cascade query.
269+
///
270+
/// **Approximate: recall is not guaranteed.** Stroke 1 scales the prefix
271+
/// distance by `vec_bytes / s1_bytes` and drops any candidate whose
272+
/// estimate exceeds `threshold + 3σ`. A candidate whose differing bits sit
273+
/// mostly in the prefix is dropped even when its full distance is within
274+
/// `threshold`; nothing falls through to an exact check. Strokes 2 and 3
275+
/// are exact: every hit returned carries its true distance and is within
276+
/// `threshold`. Vectors shorter than 128 bytes skip Stroke 1 and are exact.
277+
/// For an exact scan, call [`crate::hpc::bitwise::hamming_distance_within`]
278+
/// per candidate.
269279
pub fn query(&self, query: &[u8], database: &[u8], vec_bytes: usize, num_vectors: usize) -> Vec<RankedHit> {
270280
assert_eq!(query.len(), vec_bytes);
271281
assert_eq!(database.len(), vec_bytes * num_vectors);
@@ -790,6 +800,46 @@ mod tests {
790800
.collect()
791801
}
792802

803+
/// Stroke 1 is a statistical prune: a hit whose differences sit in the
804+
/// prefix is dropped although its full distance is within the threshold.
805+
/// The documented contract is "returned hits are exact", not "all hits
806+
/// are returned". A change that makes Stroke 1 exact will flip this test.
807+
#[test]
808+
fn stroke1_drops_a_prefix_heavy_true_hit() {
809+
let vec_bytes = 2048;
810+
let threshold = 600;
811+
let query: Vec<u8> = (0..vec_bytes).map(|i| (i as u8).wrapping_mul(37)).collect();
812+
let mut database = Vec::new();
813+
// Candidate 0: 400 differing bits, all in the stroke-1 prefix.
814+
let mut v = query.clone();
815+
for bit in 0..400 {
816+
v[bit / 8] ^= 1 << (bit % 8);
817+
}
818+
database.extend_from_slice(&v);
819+
// Candidates 1..128: the same 400 bits, all at the tail. They fill the
820+
// 128-row warmup so the population sigma stays small.
821+
let mut w = query.clone();
822+
for bit in 0..400 {
823+
w[vec_bytes - 1 - bit / 8] ^= 1 << (bit % 8);
824+
}
825+
let n = 128;
826+
for _ in 1..n {
827+
database.extend_from_slice(&w);
828+
}
829+
for c in database.chunks(vec_bytes) {
830+
assert_eq!(bitwise::hamming_distance_raw(&query, c), 400);
831+
}
832+
let cascade = Cascade::from_threshold(threshold, vec_bytes);
833+
let mut got: Vec<(usize, u64)> = cascade
834+
.query(&query, &database, vec_bytes, n)
835+
.iter()
836+
.map(|r| (r.index, r.hamming))
837+
.collect();
838+
got.sort_unstable();
839+
let expected: Vec<(usize, u64)> = (1..n).map(|i| (i, 400)).collect();
840+
assert_eq!(got, expected, "Stroke 1 should drop candidate 0 and keep the rest");
841+
}
842+
793843
#[test]
794844
fn query_exact_strokes_respect_the_prefix_budget() {
795845
let (query, database, vec_bytes, expected) = straddling_database();

‎src/hpc/pillar/btsp_unbiased.rs‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,7 @@ pub fn prove_pillar_16() -> PillarReport {
124124
psd_rate: 0.0,
125125
lognorm_concentration: 0.0,
126126
passed: true,
127+
deferred: true,
127128
}
128129
}
129130

‎src/hpc/pillar/cov_high_d.rs‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -417,6 +417,7 @@ pub fn prove_pillar_9() -> PillarReport {
417417
psd_rate: clt_rate, // repurposed: stores CLT convergence rate
418418
lognorm_concentration: clt_rate,
419419
passed,
420+
deferred: false,
420421
}
421422
}
422423

‎src/hpc/pillar/ewa_sandwich_2d.rs‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -226,6 +226,7 @@ pub fn prove_pillar_6() -> PillarReport {
226226
psd_rate,
227227
lognorm_concentration,
228228
passed,
229+
deferred: false,
229230
}
230231
}
231232

‎src/hpc/pillar/ewa_sandwich_3d.rs‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -192,6 +192,7 @@ pub fn prove_pillar_7() -> PillarReport {
192192
psd_rate,
193193
lognorm_concentration,
194194
passed,
195+
deferred: false,
195196
}
196197
}
197198

‎src/hpc/pillar/hhtl_contraction.rs‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -343,6 +343,7 @@ pub fn prove_pillar_13() -> PillarReport {
343343
psd_rate,
344344
lognorm_concentration,
345345
passed,
346+
deferred: false,
346347
}
347348
}
348349

‎src/hpc/pillar/koestenberger.rs‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -285,6 +285,7 @@ pub fn prove_pillar_7_5() -> PillarReport {
285285
psd_rate,
286286
lognorm_concentration: max_err, // repurpose field: tracks max path-parity error
287287
passed,
288+
deferred: false,
288289
}
289290
}
290291

‎src/hpc/pillar/lattice_signature.rs‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -505,6 +505,7 @@ pub fn prove_pillar_11_lattice() -> PillarReport {
505505
psd_rate: 1.0 - f64::from(leg.reduced_merged) / f64::from(leg.reduced_checked.max(1)),
506506
lognorm_concentration: f64::from(leg.false_merge_max_sep_level),
507507
passed,
508+
deferred: false,
508509
}
509510
}
510511

‎src/hpc/pillar/mexican_hat.rs‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,7 @@ pub fn prove_pillar_15() -> PillarReport {
117117
psd_rate: 0.0,
118118
lognorm_concentration: 0.0,
119119
passed: true,
120+
deferred: true,
120121
}
121122
}
122123

0 commit comments

Comments
 (0)