Documentation
¶
Overview ¶
pairing/dubov/dubov.go
Package dubov implements the Dubov Swiss pairing system (C.04.4.1).
The Dubov system is an ARO-equalization Swiss variant that splits score groups by colour preference (G1=White-seekers, G2=Black-seekers), sorts G1 by ascending ARO, and uses transposition-based matching. It has 10 criteria (C1-C10) and tracks MaxT upfloater limits.
Key differences from Dutch:
- No S1/S2 half-split; instead G1/G2 by colour preference
- S1 (=G1) sorted by ascending ARO, not descending score then TPN
- Transposition-only matching (no exchanges)
- MaxT upfloater limit = 2 + floor(Rnds/5)
- 10 criteria (C1-C10), not 13 (C8-C21)
- Distinct 5-rule colour allocation
- Distinct bye selection (Art. 2.3)
Index ¶
- Constants
- Variables
- func AllocateColor(a, b *swisslib.PlayerState, boardNumber int, topSeedColor *swisslib.Color) (string, string)
- func BalanceG1G2(g1, g2 []*swisslib.PlayerState, ratings map[string]int) ([]*swisslib.PlayerState, []*swisslib.PlayerState)
- func BuildRatingMap(players []swisslib.PlayerState) map[string]int
- func C1NoRematches(a, b *swisslib.PlayerState) bool
- func C3NoAbsoluteColorConflict(a, b *swisslib.PlayerState) bool
- func ComputeARO(player *swisslib.PlayerState, ratings map[string]int) float64
- func CriterionC4(floaters []*swisslib.PlayerState) int
- func CriterionC5(floaters []*swisslib.PlayerState) float64
- func CriterionC6(pairs []proposedPairing) int
- func CriterionC7(floaters []*swisslib.PlayerState, maxT int) int
- func CriterionC8(floaters []*swisslib.PlayerState) int
- func CriterionC9(pairs []proposedPairing, floaterIDs map[string]bool, maxT int) int
- func CriterionC10(floaters []*swisslib.PlayerState, maxT int) int
- func MatchBracketDubov(bracket swisslib.Bracket, ctx *matchContext) (*bracketResult, error)
- func MaxT(completedRounds int) int
- func SatisfiesAbsoluteCriteria(a, b *swisslib.PlayerState, forbidden map[[2]string]bool) bool
- func SortByAROAscending(players []*swisslib.PlayerState, ratings map[string]int)
- func SplitG1G2(players []*swisslib.PlayerState, isRound1 bool) (g1, g2 []*swisslib.PlayerState)
- func UpfloatCount(p *swisslib.PlayerState) int
- type DubovByeSelector
- type DubovCandidateScore
- type Options
- type Pairer
Constants ¶
const ( IdxC4 = 0 // upfloater count IdxC5 = 1 // upfloater score sum (negated: higher = better -> stored as deficit) IdxC6 = 2 // colour preference violations IdxC7 = 3 // MaxT upfloater violations IdxC8 = 4 // consecutive-round upfloaters IdxC9 = 5 // MaxT upfloater-opponent violations IdxC10 = 6 // consecutive-round MaxT violations )
Violation index constants for DubovCandidateScore.Violations.
const NumDubovViolations = 7
NumDubovViolations is the number of quality criteria (C4-C10).
Variables ¶
var ErrNoPairingPossible = errors.New("no valid pairing exists for the remaining players")
ErrNoPairingPossible is returned when no valid pairing can be found.
var ErrTooFewPlayers = errors.New("dubov pairing requires at least 1 active player")
ErrTooFewPlayers is returned when there aren't enough active players.
Functions ¶
func AllocateColor ¶
func AllocateColor(a, b *swisslib.PlayerState, boardNumber int, topSeedColor *swisslib.Color) (string, string)
AllocateColor decides which player gets White and which gets Black for a Dubov pairing. Returns (whiteID, blackID).
Per Art. 5, the 5 rules are equivalent to swisslib.AllocateColor with topScorerRules=false. Dubov does not have topscorer-specific colour rules.
func BalanceG1G2 ¶
func BalanceG1G2(g1, g2 []*swisslib.PlayerState, ratings map[string]int) ([]*swisslib.PlayerState, []*swisslib.PlayerState)
BalanceG1G2 ensures |G1| = floor(total/2) and |G2| = ceil(total/2) by shifting players between groups per Art. 3.2.4. Shifted players are chosen per Art. 4.3 (shifter sorting).
func BuildRatingMap ¶
func BuildRatingMap(players []swisslib.PlayerState) map[string]int
BuildRatingMap creates a player ID -> rating lookup from a PlayerState slice.
func C1NoRematches ¶
func C1NoRematches(a, b *swisslib.PlayerState) bool
C1NoRematches returns true if the two players have NOT already played each other.
func C3NoAbsoluteColorConflict ¶
func C3NoAbsoluteColorConflict(a, b *swisslib.PlayerState) bool
C3NoAbsoluteColorConflict returns true if the two players do NOT both have the same absolute colour preference. If either player has no absolute preference, there is no conflict.
func ComputeARO ¶
func ComputeARO(player *swisslib.PlayerState, ratings map[string]int) float64
ComputeARO returns the Average Rating of Opponents for a player. Per Dubov Art. 1.7: arithmetic mean of the ratings of all opponents the player has played. Returns 0 if the player has no opponents.
The opponents list comes from PlayerState.Opponents, which already excludes forfeits (see swisslib.BuildPlayerStates).
func CriterionC4 ¶
func CriterionC4(floaters []*swisslib.PlayerState) int
CriterionC4 returns the number of upfloaters in the candidate.
func CriterionC5 ¶
func CriterionC5(floaters []*swisslib.PlayerState) float64
CriterionC5 returns the sum of upfloater scores. Higher is better, so callers should compare reversed.
func CriterionC6 ¶
func CriterionC6(pairs []proposedPairing) int
CriterionC6 returns the number of players not receiving their colour preference.
func CriterionC7 ¶
func CriterionC7(floaters []*swisslib.PlayerState, maxT int) int
CriterionC7 returns the number of upfloaters who have reached or exceeded MaxT.
func CriterionC8 ¶
func CriterionC8(floaters []*swisslib.PlayerState) int
CriterionC8 returns the number of upfloaters who also upfloated in the previous round.
func CriterionC9 ¶
CriterionC9 returns the number of upfloater opponents who are at/above MaxT.
func CriterionC10 ¶
func CriterionC10(floaters []*swisslib.PlayerState, maxT int) int
CriterionC10 returns the number of consecutive-round upfloaters at/above MaxT.
func MatchBracketDubov ¶
MatchBracketDubov attempts to pair all players in a bracket using the Dubov algorithm.
Algorithm:
- Split into G1 (white-seekers) and G2 (black-seekers/neutral)
- Balance G1/G2 so |G1| = floor(n/2)
- Sort G1 by ascending ARO (Art. 3.2.5)
- Try sequential pairing with each G2 transposition
- Pick the best candidate per criteria scoring
- Unmatched players become floaters
func MaxT ¶
MaxT returns the maximum number of times a player may be upfloated. Per Dubov Art. 1.8: MaxT = 2 + floor(Rnds/5).
func SatisfiesAbsoluteCriteria ¶
func SatisfiesAbsoluteCriteria(a, b *swisslib.PlayerState, forbidden map[[2]string]bool) bool
SatisfiesAbsoluteCriteria checks C1, C3, and forbidden pairs for a proposed pair.
func SortByAROAscending ¶
func SortByAROAscending(players []*swisslib.PlayerState, ratings map[string]int)
SortByAROAscending sorts players by ascending ARO, breaking ties by ascending TPN (Art. 3.2.5). This is the G1/S1 sorting rule.
func SplitG1G2 ¶
func SplitG1G2(players []*swisslib.PlayerState, isRound1 bool) (g1, g2 []*swisslib.PlayerState)
SplitG1G2 splits a score group into G1 (White-preference) and G2 (Black-preference/neutral).
Round 1 (isRound1=true): G1 = first floor(n/2) players by TPN, G2 = rest. Later rounds: G1 = players preferring White, G2 = players preferring Black or no preference.
func UpfloatCount ¶
func UpfloatCount(p *swisslib.PlayerState) int
UpfloatCount returns how many times a player has been upfloated across all completed rounds.
Types ¶
type DubovByeSelector ¶
type DubovByeSelector struct{}
DubovByeSelector selects the bye player per Dubov system rules (Art. 2.3): lowest score -> most games played -> highest TPN (lowest-ranked).
func (DubovByeSelector) SelectBye ¶
func (s DubovByeSelector) SelectBye(players []*swisslib.PlayerState) *swisslib.PlayerState
SelectBye returns the player to receive the PAB, or nil if all have already received one.
type DubovCandidateScore ¶
type DubovCandidateScore struct {
UpfloaterCount int // C4: number of upfloaters (lower = better)
UpfloaterScoreSum float64 // C5: sum of upfloater scores (higher = better)
Violations [NumDubovViolations]int // C4-C10 violation counts
TranspositionIdx int // Transposition sequence number (lower = better)
}
DubovCandidateScore holds the quality evaluation of a complete bracket pairing.
func (*DubovCandidateScore) Compare ¶
func (s *DubovCandidateScore) Compare(other *DubovCandidateScore) int
Compare returns -1 if s is better than other, +1 if worse, 0 if equal. Comparison order per FIDE C.04.4.1:
- C4: fewer upfloaters is better
- C5: higher upfloater score sum is better
- C6-C10: fewer violations is better (lexicographic)
- Lower transposition index is better
func (*DubovCandidateScore) IsPerfect ¶
func (s *DubovCandidateScore) IsPerfect() bool
IsPerfect returns true if there are no upfloaters and no violations.
type Options ¶
type Options struct {
// TopSeedColor forces the top seed's color in round 1.
// Values: "auto" (default), "white", "black".
TopSeedColor *string `json:"topSeedColor,omitempty"`
// ForbiddenPairs lists player ID pairs that must not be paired together.
ForbiddenPairs [][]string `json:"forbiddenPairs,omitempty"`
// TotalRounds is the planned number of rounds in the tournament.
// If nil, derived from state.
TotalRounds *int `json:"totalRounds,omitempty"`
}
Options holds Dubov-specific pairing configuration. All pointer fields use nil = use default.
func ParseOptions ¶
ParseOptions converts a generic map[string]any into typed Options.
func (Options) WithDefaults ¶
WithDefaults returns a copy of options with defaults applied for nil fields.
type Pairer ¶
type Pairer struct {
// contains filtered or unexported fields
}
Pairer implements the chesspairing.Pairer interface for the Dubov Swiss system.
func NewFromMap ¶
NewFromMap creates a new Dubov pairer from a generic options map.
func (*Pairer) Pair ¶
func (p *Pairer) Pair(_ context.Context, state *chesspairing.TournamentState) (*chesspairing.PairingResult, error)
Pair generates pairings for the next round using the Dubov system (C.04.4.1).
Algorithm:
- Build PlayerState for all active players
- Handle single-player edge case (bye only)
- PAB bye selection via DubovByeSelector if odd count
- Build score groups and brackets
- Match each bracket using Dubov G1/G2 algorithm
- Allocate colours per Art. 5
- Board ordering and final assembly