Skip to content

Add K-index answer-copying statistic (CopyDetect-faithful port) - #260

Merged
seonghobae merged 2 commits into
seonghobae-wollack-omegafrom
seonghobae-k-index
Jul 26, 2026
Merged

Add K-index answer-copying statistic (CopyDetect-faithful port)#260
seonghobae merged 2 commits into
seonghobae-wollack-omegafrom
seonghobae-k-index

Conversation

@seonghobae

@seonghobae seonghobae commented Jul 25, 2026

Copy link
Copy Markdown
Contributor

Adds the K-index of matching incorrect answers as a faithful port of the CRAN CopyDetect package's internal k().

Stacked on #259 (Wollack omega; seonghobae-wollack-omega).

Scope (adversarial spec-verify: REDUCED-SCOPE APPROVED)

  • Implemented as "K-index as implemented by CopyDetect R/similarity1.r" (READ; corroborated by R/similarity2.r). NOT claimed as a transcription of Holland (1996, ETS RR-96-07) or Sotaridona & Meijer (2002, JEM 39(2)) — neither was read. Sotaridona & Meijer (2001, RR-01-07, ERIC ED467373) READ for background corroboration only. aberrance checked: no K-index.
  • Number-incorrect subgroup keyed on the copier's score, INCLUDING the copier and (when scores match) the source — CopyDetect convention; self-inclusion bias documented on all surfaces.
  • p = mean(emp_agg)/ws, K = P(Bin(ws, p) >= m) with the upper tail summed directly in log space (log-term recurrence + max-shifted exponential sum; no factorial overflow, no extreme-p underflow, no complement cancellation; [0,1] clamp; explicit p==0/p==1 branches). ws == 0 → error.

Surfaces

  • Rust: mlsirm_core::security::k_index + KIndexResult
  • PyO3: py_k_index
  • Python: fast_mlsirm.k_index / KIndexResult (pre-cast validation: complex/bool/non-binary rejected)

Evidence

  • Pinned oracle (independent exact math.comb Python oracle): wc=6, ws=5, m=2, subgroup=[1,2,5], emp_agg=[3,2,3], p=0.5333…, K=0.85139489711934158 (1e-12), all 7 fields asserted from crate output.
  • Structural invariants: copier always in subgroup; source included iff score matches (both directions tested).
  • Degenerate branches: m==0→K=1, p==1→K=1; full error-path coverage.
  • Extreme-p regression: P(Bin(1000, 0.99) >= 990) = 0.58304080330109709 pinned at 1e-12 against an exact fractions.Fraction reference.
  • MC-500 #[ignore]: null size < 0.10 (K discrete/conservative), power > 0.5 under 90% full-response copying. (Copying only incorrect answers inflates wc into a sparse subgroup where self-inclusion makes K conservative — documented in the test.)
  • 3 EXECUTED mutation kills: tail flip → FAIL; p denominator wc-for-ws → FAIL; subgroup keyed on ws → FAIL; restored green.

Adversarial implementation review

  • Round 1: 1 MAJOR — the original linear-space binomial recurrence started at (1-p)^n, underflowing at extreme p / large n (n=1000, p=0.99, m=990 returned K=0). Fixed in 721ae19 by log-space upper-tail summation + pinned regression.
  • Round 2: CLEAN — hostile numeric spot-checks vs exact Fraction oracles (max rel err ~3.4e-11), edge branches, citation governance, and all surfaces verified.

seonghobae and others added 2 commits July 26, 2026 02:39
Implement the K-index of matching incorrect answers exactly as in the
CRAN CopyDetect package's internal k() (R/similarity1.r, READ;
corroborated by R/similarity2.r): number-incorrect subgroup keyed on the
copier's score (including the copier and, when scores match, the source
- CopyDetect convention, source exclusion NOT applied), p =
mean(emp_agg)/ws, and the binomial upper tail P(Bin(ws, p) >= m)
computed with a coefficient-free f64 term recurrence (no factorial
overflow, smaller-tail summation, [0,1] clamp).

Holland (1996, ETS RR-96-07) and Sotaridona & Meijer (2002, JEM 39(2))
were NOT read; the statistic is cited only as implemented by CopyDetect.
Sotaridona & Meijer (2001, RR-01-07, ERIC ED467373) was read for
background corroboration of the binomial framework only. The aberrance
package was checked and contains no K-index.

Rust core mlsirm_core::security::k_index + KIndexResult; PyO3
py_k_index; Python fast_mlsirm.k_index with pre-cast validation
(complex/bool/non-binary rejected, ws==0 degenerate error). Tests:
pinned oracle (independent exact-binomial Python oracle, all 7 result
fields), subgroup-membership invariants (copier always included; source
included when its score matches), degenerate branches (m==0 -> K=1,
p==1 -> K=1), error paths, and a 500-rep Monte Carlo (#[ignore])
checking null size < 0.10 and power > 0.5 under 90% full-response
copying. Three executed mutation kills documented: tail flip, p
denominator wc-for-ws, subgroup keyed on ws.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
The linear-space term recurrence started from (1-p)^n, which underflows
to zero for cases like n=1000, p=0.99 (K wrongly returned 0 instead of
0.58304080330109709). Sum the upper-tail terms directly in log space
(log-term recurrence + max-shifted exponential sum): no complement
subtraction, no cancellation, no underflow. Regression test pins the
extreme-p case against an exact fractions.Fraction reference computed
on the exact binary value of 0.99, plus the moderate oracle case.

Found by adversarial implementation review.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
@seonghobae
seonghobae merged commit 0bced3c into seonghobae-wollack-omega Jul 26, 2026
5 checks passed
@seonghobae
seonghobae deleted the seonghobae-k-index branch July 26, 2026 09:06
seonghobae added a commit that referenced this pull request Jul 26, 2026
* Add K-index answer-copying statistic (CopyDetect-faithful port)

Implement the K-index of matching incorrect answers exactly as in the
CRAN CopyDetect package's internal k() (R/similarity1.r, READ;
corroborated by R/similarity2.r): number-incorrect subgroup keyed on the
copier's score (including the copier and, when scores match, the source
- CopyDetect convention, source exclusion NOT applied), p =
mean(emp_agg)/ws, and the binomial upper tail P(Bin(ws, p) >= m)
computed with a coefficient-free f64 term recurrence (no factorial
overflow, smaller-tail summation, [0,1] clamp).

Holland (1996, ETS RR-96-07) and Sotaridona & Meijer (2002, JEM 39(2))
were NOT read; the statistic is cited only as implemented by CopyDetect.
Sotaridona & Meijer (2001, RR-01-07, ERIC ED467373) was read for
background corroboration of the binomial framework only. The aberrance
package was checked and contains no K-index.

Rust core mlsirm_core::security::k_index + KIndexResult; PyO3
py_k_index; Python fast_mlsirm.k_index with pre-cast validation
(complex/bool/non-binary rejected, ws==0 degenerate error). Tests:
pinned oracle (independent exact-binomial Python oracle, all 7 result
fields), subgroup-membership invariants (copier always included; source
included when its score matches), degenerate branches (m==0 -> K=1,
p==1 -> K=1), error paths, and a 500-rep Monte Carlo (#[ignore])
checking null size < 0.10 and power > 0.5 under 90% full-response
copying. Three executed mutation kills documented: tail flip, p
denominator wc-for-ws, subgroup keyed on ws.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

* Fix binomial tail underflow in k_index at extreme p / large n

The linear-space term recurrence started from (1-p)^n, which underflows
to zero for cases like n=1000, p=0.99 (K wrongly returned 0 instead of
0.58304080330109709). Sum the upper-tail terms directly in log space
(log-term recurrence + max-shifted exponential sum): no complement
subtraction, no cancellation, no underflow. Regression test pins the
extreme-p case against an exact fractions.Fraction reference computed
on the exact binary value of 0.99, plus the moderate oracle case.

Found by adversarial implementation review.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

---------

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
seonghobae added a commit that referenced this pull request Jul 26, 2026
* Add K-index answer-copying statistic (CopyDetect-faithful port)

Implement the K-index of matching incorrect answers exactly as in the
CRAN CopyDetect package's internal k() (R/similarity1.r, READ;
corroborated by R/similarity2.r): number-incorrect subgroup keyed on the
copier's score (including the copier and, when scores match, the source
- CopyDetect convention, source exclusion NOT applied), p =
mean(emp_agg)/ws, and the binomial upper tail P(Bin(ws, p) >= m)
computed with a coefficient-free f64 term recurrence (no factorial
overflow, smaller-tail summation, [0,1] clamp).

Holland (1996, ETS RR-96-07) and Sotaridona & Meijer (2002, JEM 39(2))
were NOT read; the statistic is cited only as implemented by CopyDetect.
Sotaridona & Meijer (2001, RR-01-07, ERIC ED467373) was read for
background corroboration of the binomial framework only. The aberrance
package was checked and contains no K-index.

Rust core mlsirm_core::security::k_index + KIndexResult; PyO3
py_k_index; Python fast_mlsirm.k_index with pre-cast validation
(complex/bool/non-binary rejected, ws==0 degenerate error). Tests:
pinned oracle (independent exact-binomial Python oracle, all 7 result
fields), subgroup-membership invariants (copier always included; source
included when its score matches), degenerate branches (m==0 -> K=1,
p==1 -> K=1), error paths, and a 500-rep Monte Carlo (#[ignore])
checking null size < 0.10 and power > 0.5 under 90% full-response
copying. Three executed mutation kills documented: tail flip, p
denominator wc-for-ws, subgroup keyed on ws.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

* Fix binomial tail underflow in k_index at extreme p / large n

The linear-space term recurrence started from (1-p)^n, which underflows
to zero for cases like n=1000, p=0.99 (K wrongly returned 0 instead of
0.58304080330109709). Sum the upper-tail terms directly in log space
(log-term recurrence + max-shifted exponential sum): no complement
subtraction, no cancellation, no underflow. Regression test pins the
extreme-p case against an exact fractions.Fraction reference computed
on the exact binary value of 0.99, plus the moderate oracle case.

Found by adversarial implementation review.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

---------

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
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