Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion .claude/board/entries/2026-10-20-u1-jc-batch.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
- A reversed pair swaps the sides.
- Statistics: mean, variance, one-way ANOVA, covariance, Pearson, R², and the simple regression line in both directions.
- Both paths refuse a request built for another version (`BatchError::VersionMismatch`).
- Both paths refuse a selection that names a resident mask plane (`BatchError::ResidentPlane`). Resident planes are U2.
- `Cost` is counted from the programs that actually run: programs, selection passes, sum passes, rows traversed, values read, mask bytes.
- `examples/bench.rs` (`BENCH_N`, `BENCH_REPS`).

Expand Down Expand Up @@ -38,13 +39,16 @@ The time ratio is below the values-read ratio because a cross pass folds five su
- At `i32::MIN`/`i32::MAX` values, `Σx²` passes `u64` and `Σxy` passes `i64`; the test asserts both. Mean and variance still equal the exact `i128` values.
- At an offset of 10⁹, the naive one-pass f64 Pearson is off by more than 10⁻³. The exact path is within 10⁻¹² of the two-pass value.

**Tests and disables.** 8 tests pass. Six disable runs each turned red, with every disable patch's anchor asserted:
**Tests and disables.** 10 tests pass. Nine disable runs each turned red, with every disable patch's anchor asserted:
- swap the reversed pair;
- read the `y` side as `x`;
- drop the pair dedup;
- skip `normalize`;
- merge only the first group;
- drop the version check.
- drop the resident-plane refusal (`Rel::Plane` selections are refused, Codex on #1643);
- stop that check at `Not`.
- check planes before versions in one pass (a version mismatch must win wherever a plane appears, Codex on #1645).

## OPEN

Expand Down
29 changes: 25 additions & 4 deletions crates/jc-batch-probe/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,10 @@ pub enum BatchError {
/// The selection was decided from its description alone (all rows or
/// none). The probe runs only selections that need a pass.
SymbolicSelection { request: usize },
/// The selection names a resident mask plane (`Rel::Plane`). A batch
/// reads only the table's lanes: resident planes are U2's let-planes, and
/// [`Cost`] does not count plane reads.
ResidentPlane { request: usize },
/// relalg refused the selection.
Relalg(RelError),
/// mask-risc refused a pass.
Expand Down Expand Up @@ -211,7 +215,21 @@ fn selection_lanes(rel: &Rel, out: &mut Vec<u16>) {
}
}

fn check_versions(t: &Table<'_>, reqs: &[Request]) -> Result<(), BatchError> {
/// Whether a selection names a resident mask plane anywhere.
fn names_plane(rel: &Rel) -> bool {
match rel {
Rel::Plane(_) => true,
Rel::All | Rel::Empty | Rel::Atom(_) => false,
Rel::And(xs) | Rel::Or(xs) => xs.iter().any(names_plane),
Rel::Not(x) => names_plane(x),
}
}

/// Refuses a request for another version, or one whose selection names a
/// resident plane, before anything runs.
fn check_requests(t: &Table<'_>, reqs: &[Request]) -> Result<(), BatchError> {
// Versions first, over the whole batch: a batch with a stale request is
// refused as `VersionMismatch` wherever a resident plane appears.
for (i, r) in reqs.iter().enumerate() {
if r.version != t.version {
return Err(BatchError::VersionMismatch {
Expand All @@ -221,7 +239,10 @@ fn check_versions(t: &Table<'_>, reqs: &[Request]) -> Result<(), BatchError> {
});
}
}
Ok(())
match reqs.iter().position(|r| names_plane(&r.select)) {
Some(i) => Err(BatchError::ResidentPlane { request: i }),
None => Ok(()),
}
}

/// Evaluates `select` into a mask (one relalg program).
Expand Down Expand Up @@ -412,7 +433,7 @@ pub fn answer_separately(
t: &Table<'_>,
reqs: &[Request],
) -> Result<(Vec<Answer>, Cost), BatchError> {
check_versions(t, reqs)?;
check_requests(t, reqs)?;
let mut cost = Cost::default();
let mut out = Vec::with_capacity(reqs.len());
for (i, r) in reqs.iter().enumerate() {
Expand Down Expand Up @@ -455,7 +476,7 @@ pub fn answer_batched(
t: &Table<'_>,
reqs: &[Request],
) -> Result<(Vec<Answer>, Cost, Reduction), BatchError> {
check_versions(t, reqs)?;
check_requests(t, reqs)?;
let mut cost = Cost::default();
let mut red = Reduction::default();

Expand Down
43 changes: 43 additions & 0 deletions crates/jc-batch-probe/tests/batch.rs
Original file line number Diff line number Diff line change
Expand Up @@ -430,3 +430,46 @@ fn a_lane_only_univariate_requests_need_gets_one_pass() {
assert_eq!((red.cross_passes, red.power_passes), (1, 1));
assert_eq!(cost.sum_passes, 2);
}

/// FAILS IF: a selection that names a resident mask plane, alone or inside a
/// conjunction, is run instead of refused. Neither path has resident planes,
/// so running it could only fail later inside the fold.
#[test]
fn a_selection_naming_a_resident_plane_is_refused() {
let fx = Fixture::new(500, 0x91A7);
let lanes = fx.lanes();
let t = fx.table(&lanes);
for (k, select) in [
Rel::Plane(0),
Rel::And(vec![low(), Rel::Not(Box::new(Rel::Plane(1)))]),
]
.into_iter()
.enumerate()
{
let mut reqs = requests();
reqs[5 + k].select = select;
let want = Err(BatchError::ResidentPlane { request: 5 + k });
assert_eq!(answer_batched(&t, &reqs).map(|_| ()), want);
assert_eq!(answer_separately(&t, &reqs).map(|_| ()), want);
}
}

/// FAILS IF: a resident-plane refusal takes precedence over a version
/// mismatch. The plane comes first (request 2) and the stale request later
/// (request 7), so a single in-order pass would report the plane.
#[test]
fn a_version_mismatch_wins_over_a_resident_plane_anywhere_in_the_batch() {
let fx = Fixture::new(500, 0x0DE7);
let lanes = fx.lanes();
let t = fx.table(&lanes);
let mut reqs = requests();
reqs[2].select = Rel::Plane(0);
reqs[7].version = VERSION + 1;
let want = Err(BatchError::VersionMismatch {
request: 7,
request_version: VERSION + 1,
table_version: VERSION,
});
assert_eq!(answer_batched(&t, &reqs).map(|_| ()), want);
assert_eq!(answer_separately(&t, &reqs).map(|_| ()), want);
}
Loading