Matching markets for MoonBit: two-sided stable matching with machine-checkable stability certificates.
๐ง WIP โ 2026 MoonBit Hackathon (September edition) entry, developed in the open.
$ moon run cli -- check "1,0,2|0,1,2|0,1,2" "2,1,0|0,2,1|0,1,2" "0,1,2"
P0 -> R0
P1 -> R1
P2 -> R2
certificate: UNSTABLE, 4 blocking pair(s): (P0, R1) (P1, R0) (P2, R0) (P2, R1)$ moon run cli -- cert "1,0,2|0,1,2|0,1,2" "2,1,0|0,2,1|0,1,2"
certificate: {"proposer_prefs":[[1,0,2],[0,1,2],[0,1,2]],"receiver_prefs":[[2,1,0],[0,2,1],[0,1,2]],"claim":[1,2,0],"witnesses":[{"proposer":1,"receiver":0,"holder":2},{"proposer":1,"receiver":1,"holder":0}]}
$ moon run cli -- verify '{"proposer_prefs":...,"witnesses":[]}'
verify: REJECTED: proposer 1 prefers receiver 0 to its partner, but no witness vouches for the refusal| n | deferred_acceptance | blocking_pairs scan | certificate | verify | top_trading_cycles |
|---|---|---|---|---|---|
| 100 | 43 | 94 | 11 | 118 | 33 |
| 300 | 332 | 773 | 38 | 775 | 176 |
| 1000 | 3,439 | 12,548 | 224 | 8,618 | 1,184 |
| Neighbour | What it actually has | What it does not have |
|---|---|---|
| pk9993/moonbit-auction/src/matching | DA, TTC, TTC-with-chains, capacitated matching; audit_matching counts duplicate proposers, invalid references, capacity overflow | any stability check โ the repo has no blocking-pair logic at all; no exportable certificate |
| bobzhang/loop_invariants_graph/gale_shapley_stable_matching (2026-09-22) | square-table DA + is_stable -> Bool, with loop-invariant documentation | one-to-one only (no HR quotas, no TTC); the check is in-library trust, not an artifact a third party can audit |
| hutingyu-nuist/moonballot | IRV vote tabulation | two-sided matching of any kind |
| SUIKKKA/shiftweave | shift-scheduling constraint rules | game-theoretic stability |
// proposers and receivers are row indices; each row ranks the other
// side best-to-worst and must be a complete permutation
let market = Preferences::new(
[[0, 1], [1, 0]], // proposers' rankings
[[0, 1], [0, 1]], // receivers' rankings
)
let m = market.deferred_acceptance()
m.is_stable() // true โ verified by a blocking-pair scan, not trustedpub suberror CertError {
InvalidWitness(Int, Int)
WitnessNotDominating(Int, Int)
HolderMismatch(Int, Int, Int, Int)
MissingWitness(Int, Int)
} derive(Debug)pub suberror CertificateError {
ClaimLength(Int, Int)
PartnerOutOfRange(Int, Int)
ReceiverHeldTwice(Int)
} derive(Debug)impl Show for CertificateErrorpub suberror MarketError {
Empty
RowNotPerm(Int)
EndowmentLength(Int, Int)
EndowmentNotPerm
} derive(Debug)impl Show for MarketErrorimpl Show for ProfileErrorimpl Show for QuotaErrorpub struct Matching {
preferences : Preferences
proposer_to_receiver : Array[Int]
receiver_to_proposer : Array[Int]
} derive(Eq, Debug)fn Preferences::hospital_residents(self : Preferences, quotas : Array[Int]) -> HRAssignment raise QuotaErrorfn Preferences::matching_from(self : Preferences, proposer_to_receiver : Array[Int]) -> Matching raise CertificateErrorfn Preferences::new(proposer_prefs : Array[Array[Int]], receiver_prefs : Array[Array[Int]]) -> Preferences raise ProfileErrorInstall
Download zipMatching markets for MoonBit: two-sided stable matching with machine-checkable stability certificates.