Add Plackett-Luce rankings LSR/I-LSR estimators (choix lsr_rankings/ilsr_rankings) - #294
Merged
Merged
Conversation
…lsr_rankings) Rust cores scaling::lsr_rankings (one-shot) and scaling::ilsr_rankings (iterative MLE) for full/partial rankings in CSR layout, porting choix 0.4.1 exactly (source READ; Maystre & Grossglauser 2015 NOT READ, cited as-cited per choix docstrings). Python wrappers accept lists of rankings, validate before unsigned casts (negatives, non-integers, length<2 rejected), and return LsrResult. Documented divergences from choix: length<2 rankings rejected (choix no-ops), within-ranking duplicates rejected (choix accepts if connected), negative indices rejected (Python would wrap). Tests: exact rational anchors from an executed exact-Fraction/mpmath oracle (full + partial fixtures; the partial fixture is the only one that can see a wrong all-items denominator), bit-exact length-2 equivalence with lsr_pairwise, I-LSR fixed-point pins (atol 1e-7 = oracle-measured margin) + iteration-count pins (8/11 at tol=1e-8) + weights==exp_transform(params) invariant, full error contract incl. disconnected graph and alpha overflow, MC-500 recovery (#[ignore], bound 0.2 vs measured worst 0.1440). Five mutation kills executed (MU1 stale denominator, MU2 transpose, MU3 full-ranking losers, MU4 all-items sum, MU5 uniform I-LSR worths). Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Contributor
|
Important Review skippedAuto reviews are disabled on base/target branches other than the default branch. Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Pro Plus Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
✨ Finishing Touches🧪 Generate unit tests (beta)
Comment |
- Cap n at 10000 in rankings_validate: the dense O(n^2) chain would otherwise attempt terabyte allocations and abort the process on tiny inputs like lsr_rankings([[0,1]], 1_000_000) (finding 1, High). - Reject np.bool_ items alongside Python bool (finding 2, Medium). - Catch OverflowError from int(x) so infinite items raise ValueError per the wrapper contract (finding 3, Low). Regression tests added on both the Rust error contract and the Python validation test. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
3 tasks
Contributor
Author
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Implements Plackett-Luce ranking estimation via Luce Spectral Ranking —
lsr_rankings(one-shot) andilsr_rankings(iterative MLE) for full and partial rankings, porting choix 0.4.1 exactly.Stacked on #293 (Rank Centrality); base
seonghobae-rank-centrality.Source status
lsr.pylsr_rankingslines 272-317,ilsr_rankings320-363,_init_lsr,_ilsr;utils.pystatdist/log_transform). Every formula traceable.Algorithm
Each ranking (best first, length >= 2) is a sequence of Luce choices: position i accrues rate
1/(sum of remaining ranked worths)on every loser->winner edge (suffix losers only), plusalpharegularization everywhere. Params = centered log stationary distribution. I-LSR feeds each pass the previous params viaexp_transformuntil L1 change <= tol*n.Documented divergences from choix (all tested)
Spec-verify (adversarial, BEFORE implementation)
Verdict: APPROVED-WITH-CHANGES — all 6 mandatory changes adopted: (1) MU2 rationale corrected (RA kills transpose, probe maxdiff 1.67); (2) divergences documented + tested; (3) iteration pins 8/11 at tol=1e-8 are choix-equivalent probes, not dps-50 artifacts; (4) atol 1e-7 is oracle-MEASURED margin (converged iterates 8.6e-11 / 1.7e-9 from fixed points); (5) weights==exp_transform(params) invariant pinned (kills stale-weights mutants); (6) MC-500 uses local test Lcg with sequential-Luce categorical draws, centered truth.
Oracle (EXECUTED, exact Fraction + mpmath dps=50)
Mutation kills (all EXECUTED)
s -= w[winner](stale denominator)Baseline re-verified clean after restore (25 scaling tests).
Validation
mlsirm-core --lib: 754 passed (6 new); MC-500--ignoredpasses with measured worst MAE 0.1440 vs bound 0.2.tests/test_paper_features.py: 278 passed (3 new).Adversarial impl-review outcome
Round 1: FINDINGS (3) — (1) High: public API could abort the process on oversized
n(dense O(n^2) chain attempted terabyte allocations); (2) Medium:np.bool_items bypassed the boolean rejection; (3) Low: infinite items leakedOverflowErrorinstead ofValueError.Fixed in 37f6b88:
rankings_validatenow rejectsn > 10000in the shared Rust core (covers PyO3 callers too, documented as a dense-chain ceiling);_rankings_to_csrrejectsnp.bool_alongsidebooland catchesOverflowError; regression tests added on both the Rust error contract and the Python validation test.Round 2: CLEAN — reviewer re-ran all round-1 break-it probes in child processes (huge n,
np.bool_item, inf item → allValueError, no abort), confirmed the fix layers, cargo scaling 25 pass, pytest PlackettLuce 3 pass. Full review: session filespl_rankings_impl_review.md.