Code cleaning: fix previous commit, simplify HistoryHelper creation

This commit is contained in:
Claude Brisson
2025-07-24 14:14:03 +02:00
parent ecec6556d1
commit f704f3adb2
12 changed files with 152 additions and 141 deletions

View File

@@ -27,7 +27,7 @@ fun Tournament<*>.getSortedPairables(round: Int, includePreliminary: Boolean = f
return ArrayList(frozen!!.map { it -> it as Json.Object }) return ArrayList(frozen!!.map { it -> it as Json.Object })
} }
val history = historyHelper(round) val history = historyHelper(round + 1)
val neededCriteria = ArrayList(pairing.placementParams.criteria) val neededCriteria = ArrayList(pairing.placementParams.criteria)
if (!neededCriteria.contains(Criterion.NBW)) neededCriteria.add(Criterion.NBW) if (!neededCriteria.contains(Criterion.NBW)) neededCriteria.add(Criterion.NBW)
@@ -162,10 +162,8 @@ fun TeamTournament.getSortedTeamMembers(round: Int, includePreliminary: Boolean
val individualHistory = teamGames.map { roundTeamGames -> val individualHistory = teamGames.map { roundTeamGames ->
roundTeamGames.flatMap { game -> individualGames[game.id]?.toList() ?: listOf() } roundTeamGames.flatMap { game -> individualGames[game.id]?.toList() ?: listOf() }
} }
val historyHelper = HistoryHelper(individualHistory) { val historyHelper = HistoryHelper(individualHistory).apply {
pairables.mapValues { scoresFactory = { wins }
Pair(0.0, wins[it.key] ?: 0.0)
}
} }
val neededCriteria = mutableListOf(Criterion.NBW, Criterion.RATING) val neededCriteria = mutableListOf(Criterion.NBW, Criterion.RATING)
val criteria = neededCriteria.map { crit -> val criteria = neededCriteria.map { crit ->

View File

@@ -4,7 +4,8 @@ import com.republicate.kson.Json
import org.jeudego.pairgoth.api.ApiHandler.Companion.badRequest import org.jeudego.pairgoth.api.ApiHandler.Companion.badRequest
import org.jeudego.pairgoth.model.MainCritParams.SeedMethod.SPLIT_AND_SLIP import org.jeudego.pairgoth.model.MainCritParams.SeedMethod.SPLIT_AND_SLIP
import org.jeudego.pairgoth.model.PairingType.* import org.jeudego.pairgoth.model.PairingType.*
import org.jeudego.pairgoth.pairing.solver.BaseSolver import org.jeudego.pairgoth.pairing.HistoryHelper
import org.jeudego.pairgoth.pairing.solver.Solver
import org.jeudego.pairgoth.pairing.solver.MacMahonSolver import org.jeudego.pairgoth.pairing.solver.MacMahonSolver
import org.jeudego.pairgoth.pairing.solver.SwissSolver import org.jeudego.pairgoth.pairing.solver.SwissSolver
import kotlin.math.min import kotlin.math.min
@@ -131,7 +132,7 @@ sealed class Pairing(
val pairingParams: PairingParams, val pairingParams: PairingParams,
val placementParams: PlacementParams) { val placementParams: PlacementParams) {
companion object {} companion object {}
abstract fun solver(tournament: Tournament<*>, round: Int, pairables: List<Pairable>): BaseSolver abstract fun solver(tournament: Tournament<*>, round: Int, pairables: List<Pairable>): Solver
fun pair(tournament: Tournament<*>, round: Int, pairables: List<Pairable>): List<Game> { fun pair(tournament: Tournament<*>, round: Int, pairables: List<Pairable>): List<Game> {
return solver(tournament, round, pairables).pair() return solver(tournament, round, pairables).pair()
} }
@@ -140,19 +141,6 @@ sealed class Pairing(
internal fun Tournament<*>.historyBefore(round: Int) = internal fun Tournament<*>.historyBefore(round: Int) =
(1 until min(round, lastRound() + 1)).map { games(it).values.toList() } (1 until min(round, lastRound() + 1)).map { games(it).values.toList() }
/*private fun Tournament<*>.historyBefore(round: Int) : List<List<Game>> {
println("Welcome to tournament.historyBefore !")
println("lastround and round = "+lastRound().toString()+" "+round.toString())
println((1 until round).map { it })
println((1 until round).map { games(it).values.toList() })
if (lastRound() == 1){
return emptyList()
}
else {
return (1 until round).map { games(it).values.toList() }
}
}*/
class Swiss( class Swiss(
pairingParams: PairingParams = PairingParams( pairingParams: PairingParams = PairingParams(
base = BaseCritParams(), base = BaseCritParams(),
@@ -175,7 +163,7 @@ class Swiss(
): Pairing(SWISS, pairingParams, placementParams) { ): Pairing(SWISS, pairingParams, placementParams) {
companion object {} companion object {}
override fun solver(tournament: Tournament<*>, round: Int, pairables: List<Pairable>) = override fun solver(tournament: Tournament<*>, round: Int, pairables: List<Pairable>) =
SwissSolver(round, tournament.rounds, tournament.historyHelper(round), pairables, tournament.pairables, pairingParams, placementParams, tournament.usedTables(round)) SwissSolver(round, tournament.rounds, HistoryHelper(tournament.historyBefore(round)), pairables, tournament.pairables, pairingParams, placementParams, tournament.usedTables(round))
} }
class MacMahon( class MacMahon(
@@ -203,14 +191,14 @@ class MacMahon(
): Pairing(MAC_MAHON, pairingParams, placementParams) { ): Pairing(MAC_MAHON, pairingParams, placementParams) {
companion object {} companion object {}
override fun solver(tournament: Tournament<*>, round: Int, pairables: List<Pairable>) = override fun solver(tournament: Tournament<*>, round: Int, pairables: List<Pairable>) =
MacMahonSolver(round, tournament.rounds, tournament.historyHelper(round), pairables, tournament.pairables, pairingParams, placementParams, tournament.usedTables(round), mmFloor, mmBar) MacMahonSolver(round, tournament.rounds, HistoryHelper(tournament.historyBefore(round)), pairables, tournament.pairables, pairingParams, placementParams, tournament.usedTables(round), mmFloor, mmBar)
} }
class RoundRobin( class RoundRobin(
pairingParams: PairingParams = PairingParams(), pairingParams: PairingParams = PairingParams(),
placementParams: PlacementParams = PlacementParams(Criterion.NBW, Criterion.RATING) placementParams: PlacementParams = PlacementParams(Criterion.NBW, Criterion.RATING)
): Pairing(ROUND_ROBIN, pairingParams, placementParams) { ): Pairing(ROUND_ROBIN, pairingParams, placementParams) {
override fun solver(tournament: Tournament<*>, round: Int, pairables: List<Pairable>): BaseSolver { override fun solver(tournament: Tournament<*>, round: Int, pairables: List<Pairable>): Solver {
TODO("not implemented") TODO("not implemented")
} }
} }

View File

@@ -225,32 +225,7 @@ sealed class Tournament <P: Pairable>(
} }
fun historyHelper(round: Int): HistoryHelper { fun historyHelper(round: Int): HistoryHelper {
return HistoryHelper(historyBefore(round + 1)) { return pairing.solver(this, round, emptyList()).history
if (pairing.type == PairingType.SWISS) {
pairables.mapValues {
// In a Swiss tournament the main criterion is the number of wins
Pair(0.0, wins[it.key] ?: 0.0)
}
}
else {
pairables.mapValues {
// In a MacMahon tournament the main criterion is the mms
it.value.let { pairable ->
val mmBase = pairable.mmBase()
val score = roundScore(mmBase +
(nbW(pairable) ?: 0.0) +
(1..round).sumOf { round ->
if (playersPerRound.getOrNull(round - 1)?.contains(pairable.id) == true) 0.0 else 1.0
} * pairing.pairingParams.main.mmsValueAbsent)
Pair(
if (pairing.pairingParams.main.sosValueAbsentUseBase) mmBase
else roundScore(mmBase + round/2),
score
)
}
}
}
}
} }
} }

View File

@@ -11,14 +11,11 @@ abstract class BasePairingHelper(
val placement: PlacementParams, val placement: PlacementParams,
) { ) {
val scores get() = history.scores
abstract val scoresX: Map<ID, Double>
// Extend pairables with members from all rounds // Extend pairables with members from all rounds
// The main criterion that will be used to define the groups should be defined by subclasses // The main criterion that will be used to define the groups should be defined by subclasses
// SOS and variants will be computed based on this score // SOS and variants will be computed based on this score
val Pairable.main: Double get() = scores[id]?.second ?: 0.0 val Pairable.main: Double get() = score ?: 0.0
abstract val mainLimits: Pair<Double, Double> abstract val mainLimits: Pair<Double, Double>
// pairables sorted using overloadable sort function // pairables sorted using overloadable sort function
@@ -89,9 +86,9 @@ abstract class BasePairingHelper(
protected val Pairable.nbBye: Int get() = history.nbPlayedWithBye(this) ?: 0 protected val Pairable.nbBye: Int get() = history.nbPlayedWithBye(this) ?: 0
// score (number of wins) val Pairable.score: Double get() = history.scores[id] ?: 0.0
val Pairable.scoreX: Double get() = history.scoresX[id] ?: 0.0
val Pairable.nbW: Double get() = history.nbW(this) ?: 0.0 val Pairable.nbW: Double get() = history.nbW(this) ?: 0.0
val Pairable.sos: Double get() = history.sos[id] ?: 0.0 val Pairable.sos: Double get() = history.sos[id] ?: 0.0
val Pairable.sosm1: Double get() = history.sosm1[id] ?: 0.0 val Pairable.sosm1: Double get() = history.sosm1[id] ?: 0.0
val Pairable.sosm2: Double get() = history.sosm2[id] ?: 0.0 val Pairable.sosm2: Double get() = history.sosm2[id] ?: 0.0

View File

@@ -2,22 +2,22 @@ package org.jeudego.pairgoth.pairing
import org.jeudego.pairgoth.model.* import org.jeudego.pairgoth.model.*
import org.jeudego.pairgoth.model.Game.Result.* import org.jeudego.pairgoth.model.Game.Result.*
import org.jeudego.pairgoth.model.TeamTournament.Team import org.jeudego.pairgoth.pairing.solver.Solver
import org.jeudego.pairgoth.pairing.solver.MacMahonSolver
import kotlin.math.max
import kotlin.math.min
/** typealias ScoreMap = Map<ID, Double>
* Map from a pairable ID to a pair of (missed rounds increment, main score). typealias ScoreMapFactory = () -> ScoreMap
* The missed rounds increment is 0 for Swiss, and a function of the MMS base of the pairable for MacMahon.
* The main score is the NBW for the Swiss, the MMS for MacMahon.
*/
typealias ScoreMapBuilder = HistoryHelper.()-> Map<ID, Pair<Double, Double>>
open class HistoryHelper( open class HistoryHelper(
protected val history: List<List<Game>>, protected val history: List<List<Game>>
// scoresGetter() returns Pair(sos value for missed rounds, score) where score is nbw for Swiss, mms for MM, ... ) {
scoresGetter: ScoreMapBuilder) {
lateinit var scoresFactory: ScoreMapFactory
lateinit var scoresXFactory: ScoreMapFactory
lateinit var missedRoundsSosFactory: ScoreMapFactory
val scores by lazy { scoresFactory() }
val scoresX by lazy { scoresXFactory() }
val missedRoundsSos by lazy { missedRoundsSosFactory() }
private val Game.blackScore get() = when (result) { private val Game.blackScore get() = when (result) {
BLACK, BOTHWIN -> 1.0 BLACK, BOTHWIN -> 1.0
@@ -29,16 +29,6 @@ open class HistoryHelper(
else -> 0.0 else -> 0.0
} }
val scores by lazy {
scoresGetter()
}
val scoresX by lazy {
scoresGetter().mapValues { entry ->
entry.value.first + (wins[entry.key] ?: 0.0)
}
}
// Generic helper functions // Generic helper functions
open fun playedTogether(p1: Pairable, p2: Pairable) = paired.contains(Pair(p1.id, p2.id)) open fun playedTogether(p1: Pairable, p2: Pairable) = paired.contains(Pair(p1.id, p2.id))
open fun colorBalance(p: Pairable) = colorBalance[p.id] open fun colorBalance(p: Pairable) = colorBalance[p.id]
@@ -82,14 +72,14 @@ open class HistoryHelper(
} }
} }
// Set of all implied players for each round (warning: does comprise games with BIP) // Set of all implied players for each round
val playersPerRound: List<Set<ID>> by lazy { val playersPerRound: List<Set<ID>> by lazy {
history.map { history.map { roundGames ->
it.fold(mutableSetOf<ID>()) { acc, next -> roundGames.flatMap {
if(next.white != 0) acc.add(next.white) game -> listOf(game.white, game.black)
if (next.black != 0) acc.add(next.black) }.filter { id ->
acc id != ByePlayer.id
} }.toSet()
} }
} }
@@ -110,67 +100,89 @@ open class HistoryHelper(
} }
// define mms to be a synonym of scores // define mms to be a synonym of scores
val mms by lazy { scores.mapValues { it -> it.value.second } } val mms by lazy { scores }
val sos by lazy { val sos by lazy {
// SOS for played games against a real opponent or BIP
val historySos = (history.flatten().map { game -> val historySos = (history.flatten().map { game ->
Pair( Pair(
game.black, game.black,
if (game.white == 0) scores[game.black]?.first ?: 0.0 if (game.white == 0) missedRoundsSos[game.black] ?: 0.0
else scores[game.white]?.second?.let { it - game.handicap } ?: 0.0 else scores[game.white]?.let { it - game.handicap } ?: 0.0
) )
} + history.flatten().map { game -> } + history.flatten().map { game ->
Pair( Pair(
game.white, game.white,
if (game.black == 0) scores[game.white]?.first ?: 0.0 if (game.black == 0) missedRoundsSos[game.white] ?: 0.0
else scores[game.black]?.second?.let { it + game.handicap } ?: 0.0 else scores[game.black]?.let { it + game.handicap } ?: 0.0
) )
}).groupingBy { }).groupingBy {
it.first it.first
}.fold(0.0) { acc, next -> }.fold(0.0) { acc, next ->
acc + next.second acc + next.second
} }
// plus SOS for missed rounds
scores.mapValues { (id, pair) -> missedRoundsSos.mapValues { (id, pseudoSos) ->
(historySos[id] ?: 0.0) + playersPerRound.sumOf { (historySos[id] ?: 0.0) + playersPerRound.sumOf {
if (it.contains(id)) 0.0 else pair.first if (it.contains(id)) 0.0 else pseudoSos
} }
} }
} }
// sos-1 // sos-1
val sosm1 by lazy { val sosm1 by lazy {
// SOS for played games against a real opponent or BIP
(history.flatten().map { game -> (history.flatten().map { game ->
Pair(game.black, scores[game.white]?.second?.let { it - game.handicap } ?: 0.0) Pair(
game.black,
if (game.white == 0) missedRoundsSos[game.black] ?: 0.0
else scores[game.white]?.let { it - game.handicap } ?: 0.0
)
} + history.flatten().map { game -> } + history.flatten().map { game ->
Pair(game.white, scores[game.black]?.second?.let { it + game.handicap } ?: 0.0) Pair(
game.white,
if (game.black == 0) missedRoundsSos[game.white] ?: 0.0
else scores[game.black]?.let { it + game.handicap } ?: 0.0
)
}).groupBy { }).groupBy {
it.first it.first
}.mapValues { (id, pairs) -> }.mapValues { (id, pairs) ->
val oppScores = pairs.map { it.second }.sortedDescending() val oppScores = pairs.map { it.second }.sortedDescending()
// minus greatest SOS
oppScores.sum() - (oppScores.firstOrNull() ?: 0.0) + oppScores.sum() - (oppScores.firstOrNull() ?: 0.0) +
// plus SOS for missed rounds
playersPerRound.sumOf { players -> playersPerRound.sumOf { players ->
if (players.contains(id)) 0.0 if (players.contains(id)) 0.0
else scores[id]?.first ?: 0.0 else missedRoundsSos[id] ?: 0.0
} }
} }
} }
// sos-2 // sos-2
val sosm2 by lazy { val sosm2 by lazy {
// SOS for played games against a real opponent or BIP
(history.flatten().map { game -> (history.flatten().map { game ->
Pair(game.black, scores[game.white]?.second?.let { it - game.handicap } ?: 0.0) Pair(
game.black,
if (game.white == 0) missedRoundsSos[game.black] ?: 0.0
else scores[game.white]?.let { it - game.handicap } ?: 0.0
)
} + history.flatten().map { game -> } + history.flatten().map { game ->
Pair(game.white, scores[game.black]?.second?.let { it + game.handicap } ?: 0.0) Pair(
game.white,
if (game.black == 0) missedRoundsSos[game.white] ?: 0.0
else scores[game.black]?.let { it + game.handicap } ?: 0.0
)
}).groupBy { }).groupBy {
it.first it.first
}.mapValues { (id, pairs) -> }.mapValues { (id, pairs) ->
val oppScores = pairs.map { it.second }.sorted() val oppScores = pairs.map { it.second }.sortedDescending()
// minus two greatest SOS
oppScores.sum() - oppScores.getOrElse(0) { 0.0 } - oppScores.getOrElse(1) { 0.0 } + oppScores.sum() - oppScores.getOrElse(0) { 0.0 } - oppScores.getOrElse(1) { 0.0 } +
// plus SOS for missed rounds
playersPerRound.sumOf { players -> playersPerRound.sumOf { players ->
if (players.contains(id)) 0.0 if (players.contains(id)) 0.0
else scores[id]?.first ?: 0.0 else missedRoundsSos[id] ?: 0.0
} }
} }
} }
@@ -180,16 +192,17 @@ open class HistoryHelper(
(history.flatten().filter { game -> (history.flatten().filter { game ->
game.white != 0 // Remove games against byePlayer game.white != 0 // Remove games against byePlayer
}.map { game -> }.map { game ->
Pair(game.black, if (game.result == Game.Result.BLACK) scores[game.white]?.second?.let { it - game.handicap } ?: 0.0 else 0.0) Pair(game.black, if (game.result == Game.Result.BLACK) scores[game.white]?.let { it - game.handicap } ?: 0.0 else 0.0)
} + history.flatten().filter { game -> } + history.flatten().filter { game ->
game.white != 0 // Remove games against byePlayer game.white != 0 // Remove games against byePlayer
}.map { game -> }.map { game ->
Pair(game.white, if (game.result == Game.Result.WHITE) scores[game.black]?.second?.let { it + game.handicap } ?: 0.0 else 0.0) Pair(game.white, if (game.result == Game.Result.WHITE) scores[game.black]?.let { it + game.handicap } ?: 0.0 else 0.0)
}).groupingBy { it.first }.fold(0.0) { acc, next -> }).groupingBy { it.first }.fold(0.0) { acc, next ->
acc + next.second acc + next.second
} }
} }
// sosos // sosos
val sosos by lazy { val sosos by lazy {
val currentRound = history.size val currentRound = history.size
@@ -203,9 +216,9 @@ open class HistoryHelper(
acc + next.second acc + next.second
} }
scores.mapValues { (id, pair) -> missedRoundsSos.mapValues { (id, missedRoundSos) ->
(historySosos[id] ?: 0.0) + playersPerRound.sumOf { (historySosos[id] ?: 0.0) + playersPerRound.sumOf {
if (it.contains(id)) 0.0 else pair.first * currentRound if (it.contains(id)) 0.0 else missedRoundSos * currentRound
} }
} }

View File

@@ -1,7 +1,6 @@
package org.jeudego.pairgoth.pairing package org.jeudego.pairgoth.pairing
import org.jeudego.pairgoth.model.Pairable import org.jeudego.pairgoth.model.Pairable
import org.jeudego.pairgoth.pairing.solver.BaseSolver
fun detRandom(max: Double, p1: Pairable, p2: Pairable, symmetric: Boolean): Double { fun detRandom(max: Double, p1: Pairable, p2: Pairable, symmetric: Boolean): Double {
var inverse = false var inverse = false

View File

@@ -16,19 +16,31 @@ class MacMahonSolver(round: Int,
placementParams: PlacementParams, placementParams: PlacementParams,
usedTables: BitSet, usedTables: BitSet,
private val mmFloor: Int, private val mmBar: Int) : private val mmFloor: Int, private val mmBar: Int) :
BaseSolver(round, totalRounds, history, pairables, pairingParams, placementParams, usedTables) { Solver(round, totalRounds, history, pairables, allPairablesMap, pairingParams, placementParams, usedTables) {
override val scoresX: Map<ID, Double> by lazy { override fun mainScoreMapFactory() =
require (mmBar > mmFloor) { "MMFloor is higher than MMBar" } allPairablesMap.mapValues { (id, pairable) ->
allPairablesMap.mapValues { roundScore(pairable.mmBase +
it.value.let { pairable -> pairable.nbW +
pairable.missedRounds() * pairing.main.mmsValueAbsent)
}
override fun scoreXMapFactory() =
allPairablesMap.mapValues { (id, pairable) ->
roundScore(pairable.mmBase + pairable.nbW) roundScore(pairable.mmBase + pairable.nbW)
} }
override fun missedRoundSosMapFactory() =
allPairablesMap.mapValues { (id, pairable) ->
if (pairing.main.sosValueAbsentUseBase) {
pairable.mmBase
} else {
roundScore(pairable.mmBase + round/2)
} }
} }
override fun computeWeightForBye(p: Pairable): Double{ override fun computeWeightForBye(p: Pairable): Double{
return 2*scores[p.id]!!.second return 2 * p.score
} }
override fun SecondaryCritParams.apply(p1: Pairable, p2: Pairable): Double { override fun SecondaryCritParams.apply(p1: Pairable, p2: Pairable): Double {
@@ -69,8 +81,7 @@ class MacMahonSolver(round: Int,
// mmBase: starting Mac-Mahon score of the pairable // mmBase: starting Mac-Mahon score of the pairable
val Pairable.mmBase: Double get() = min(max(rank, mmFloor), mmBar) + mmsZero + mmsCorrection val Pairable.mmBase: Double get() = min(max(rank, mmFloor), mmBar) + mmsZero + mmsCorrection
// mms: current Mac-Mahon score of the pairable // mms: current Mac-Mahon score of the pairable
val Pairable.mms: Double get() = scores[id]?.second ?: 0.0 val Pairable.mms: Double get() = score
val Pairable.scoreX: Double get() = scoresX[id] ?: 0.0
// CB TODO - configurable criteria // CB TODO - configurable criteria
val mainScoreMin = mmFloor + PLA_SMMS_SCORE_MIN - Pairable.MIN_RANK val mainScoreMin = mmFloor + PLA_SMMS_SCORE_MIN - Pairable.MIN_RANK

View File

@@ -19,11 +19,12 @@ import java.text.DecimalFormat
import java.util.* import java.util.*
import kotlin.math.* import kotlin.math.*
sealed class BaseSolver( sealed class Solver(
round: Int, round: Int,
totalRounds: Int, totalRounds: Int,
history: HistoryHelper, // History of all games played for each round history: HistoryHelper, // Digested history of all games played for each round
pairables: List<Pairable>, // All pairables for this round, it may include the bye player pairables: List<Pairable>, // Pairables to pair together
val allPairablesMap: Map<ID, Pairable>, // Map of all known pairables
pairing: PairingParams, pairing: PairingParams,
placement: PlacementParams, placement: PlacementParams,
val usedTables: BitSet val usedTables: BitSet
@@ -36,6 +37,27 @@ sealed class BaseSolver(
var legacy_mode = false var legacy_mode = false
} }
init {
history.scoresFactory = this::mainScoreMapFactory
history.scoresXFactory = this::scoreXMapFactory
history.missedRoundsSosFactory = this::missedRoundSosMapFactory
}
/**
* Main score map factory (NBW for Swiss, MMS for MacMahon, ...).
*/
abstract fun mainScoreMapFactory(): Map<ID, Double>
/**
* ScoreX map factory (NBW for Swiss, MMSBase + MMS for MacMahon, ...).
*/
abstract fun scoreXMapFactory(): Map<ID, Double>
/**
* SOS for missed rounds factory (0 for Swiss, mmBase or mmBase+rounds/2 for MacMahon depending on pairing option sosValueAbsentUseBase)
*/
abstract fun missedRoundSosMapFactory(): Map<ID, Double>
open fun openGothaWeight(p1: Pairable, p2: Pairable) = open fun openGothaWeight(p1: Pairable, p2: Pairable) =
1.0 + // 1 is minimum value because 0 means "no matching allowed" 1.0 + // 1 is minimum value because 0 means "no matching allowed"
pairing.base.apply(p1, p2) + pairing.base.apply(p1, p2) +
@@ -144,8 +166,8 @@ sealed class BaseSolver(
for (p in sortedPairables) { for (p in sortedPairables) {
logger.info(String.format("%-20s", p.name.substring(0, min(p.name.length, 18))) logger.info(String.format("%-20s", p.name.substring(0, min(p.name.length, 18)))
+ " " + String.format("%-4s", p.id) + " " + String.format("%-4s", p.id)
+ " " + String.format("%-4s", scores[p.id]?.first) + " " + String.format("%-4s", history.missedRoundsSos[p.id])
+ " " + String.format("%-4s", scores[p.id]?.second) + " " + String.format("%-4s", history.scores[p.id])
+ " " + String.format("%-4s", p.sos) + " " + String.format("%-4s", p.sos)
) )
} }

View File

@@ -8,18 +8,28 @@ class SwissSolver(round: Int,
totalRounds: Int, totalRounds: Int,
history: HistoryHelper, history: HistoryHelper,
pairables: List<Pairable>, pairables: List<Pairable>,
pairablesMap: Map<ID, Pairable>, allPairablesMap: Map<ID, Pairable>,
pairingParams: PairingParams, pairingParams: PairingParams,
placementParams: PlacementParams, placementParams: PlacementParams,
usedTables: BitSet usedTables: BitSet
): ):
BaseSolver(round, totalRounds, history, pairables, pairingParams, placementParams, usedTables) { Solver(round, totalRounds, history, pairables, allPairablesMap, pairingParams, placementParams, usedTables) {
override val scoresX: Map<ID, Double> get() = scores.mapValues { it.value.second } override fun mainScoreMapFactory() =
allPairablesMap.mapValues { (id, pairable) ->
history.wins[id] ?: 0.0
}
override fun scoreXMapFactory() = mainScoreMapFactory()
override fun missedRoundSosMapFactory() =
allPairablesMap.mapValues { (id, pairable) ->
0.0
}
override val mainLimits = Pair(0.0, round - 1.0) override val mainLimits = Pair(0.0, round - 1.0)
override fun computeWeightForBye(p: Pairable): Double{ override fun computeWeightForBye(p: Pairable): Double{
return p.rank + 40*p.main return p.rank + 40 * p.main
} }
} }

View File

@@ -1,14 +1,12 @@
package org.jeudego.pairgoth.test package org.jeudego.pairgoth.test
import com.republicate.kson.Json import com.republicate.kson.Json
import org.jeudego.pairgoth.pairing.solver.BaseSolver import org.jeudego.pairgoth.pairing.solver.Solver
import org.jeudego.pairgoth.model.Game
import org.jeudego.pairgoth.test.PairingTests.Companion.compare_weights import org.jeudego.pairgoth.test.PairingTests.Companion.compare_weights
import org.junit.jupiter.api.Test import org.junit.jupiter.api.Test
import java.io.FileWriter import java.io.FileWriter
import java.io.PrintWriter import java.io.PrintWriter
import java.nio.charset.StandardCharsets import java.nio.charset.StandardCharsets
import kotlin.test.assertEquals
import kotlin.test.assertNotEquals import kotlin.test.assertNotEquals
import kotlin.test.assertNotNull import kotlin.test.assertNotNull
import kotlin.test.assertTrue import kotlin.test.assertTrue
@@ -22,16 +20,16 @@ class BOSP2024Test: TestBase() {
)!!.asObject() )!!.asObject()
val resp = TestAPI.post("/api/tour", tournament).asObject() val resp = TestAPI.post("/api/tour", tournament).asObject()
val tourId = resp.getInt("id") val tourId = resp.getInt("id")
BaseSolver.weightsLogger = PrintWriter(FileWriter(getOutputFile("bosp2024-weights.txt"))) Solver.weightsLogger = PrintWriter(FileWriter(getOutputFile("bosp2024-weights.txt")))
BaseSolver.legacy_mode = true Solver.legacy_mode = true
TestAPI.post("/api/tour/$tourId/pair/3", Json.Array("all")).asArray() TestAPI.post("/api/tour/$tourId/pair/3", Json.Array("all")).asArray()
// compare weights // compare weights
assertTrue(compare_weights(getOutputFile("bosp2024-weights.txt"), getTestFile("opengotha/bosp2024/bosp2024_weights_R3.txt")), "Not matching opengotha weights for BOSP test") assertTrue(compare_weights(getOutputFile("bosp2024-weights.txt"), getTestFile("opengotha/bosp2024/bosp2024_weights_R3.txt")), "Not matching opengotha weights for BOSP test")
TestAPI.delete("/api/tour/$tourId/pair/3", Json.Array("all")) TestAPI.delete("/api/tour/$tourId/pair/3", Json.Array("all"))
BaseSolver.legacy_mode = false Solver.legacy_mode = false
val games = TestAPI.post("/api/tour/$tourId/pair/3", Json.Array("all")).asArray() val games = TestAPI.post("/api/tour/$tourId/pair/3", Json.Array("all")).asArray()
// Aksut Husrev is ID 18 // Aksut Husrev is ID 18
val solved = games.map { it as Json.Object }.filter { game -> val solved = games.map { it as Json.Object }.filter { game ->

View File

@@ -1,7 +1,7 @@
package org.jeudego.pairgoth.test package org.jeudego.pairgoth.test
import com.republicate.kson.Json import com.republicate.kson.Json
import org.jeudego.pairgoth.pairing.solver.BaseSolver import org.jeudego.pairgoth.pairing.solver.Solver
import org.jeudego.pairgoth.test.PairingTests.Companion.compare_weights import org.jeudego.pairgoth.test.PairingTests.Companion.compare_weights
import org.junit.jupiter.api.Test import org.junit.jupiter.api.Test
import java.io.FileWriter import java.io.FileWriter
@@ -19,7 +19,7 @@ class MalavasiTest: TestBase() {
)!!.asObject() )!!.asObject()
val resp = TestAPI.post("/api/tour", tournament).asObject() val resp = TestAPI.post("/api/tour", tournament).asObject()
val tourId = resp.getInt("id") val tourId = resp.getInt("id")
BaseSolver.weightsLogger = PrintWriter(FileWriter(getOutputFile("malavasi-weights.txt"))) Solver.weightsLogger = PrintWriter(FileWriter(getOutputFile("malavasi-weights.txt")))
val games = TestAPI.post("/api/tour/$tourId/pair/2", Json.Array("all")).asArray() val games = TestAPI.post("/api/tour/$tourId/pair/2", Json.Array("all")).asArray()
// Oceane is ID 548, Valentine 549 // Oceane is ID 548, Valentine 549
val buggy = games.map { it as Json.Object }.filter { game -> val buggy = games.map { it as Json.Object }.filter { game ->

View File

@@ -2,7 +2,7 @@ package org.jeudego.pairgoth.test
import com.republicate.kson.Json import com.republicate.kson.Json
import org.jeudego.pairgoth.model.* import org.jeudego.pairgoth.model.*
import org.jeudego.pairgoth.pairing.solver.BaseSolver import org.jeudego.pairgoth.pairing.solver.Solver
import org.jeudego.pairgoth.store.MemoryStore import org.jeudego.pairgoth.store.MemoryStore
import org.jeudego.pairgoth.store.lastPlayerId import org.jeudego.pairgoth.store.lastPlayerId
import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.BeforeEach
@@ -59,7 +59,7 @@ class PairingTests: TestBase() {
} }
fun compare_weights(file1: File, file2: File, skipSeeding: Boolean = false):Boolean { fun compare_weights(file1: File, file2: File, skipSeeding: Boolean = false):Boolean {
BaseSolver.weightsLogger!!.flush() Solver.weightsLogger!!.flush()
// Maps to store name pairs and costs // Maps to store name pairs and costs
val map1 = create_weights_map(file1) val map1 = create_weights_map(file1)
val map2 = create_weights_map(file2) val map2 = create_weights_map(file2)
@@ -172,7 +172,7 @@ class PairingTests: TestBase() {
fun test_from_XML_internal(name: String, forcePairing:List<Int>, legacy: Boolean) { fun test_from_XML_internal(name: String, forcePairing:List<Int>, legacy: Boolean) {
// Let pairgoth use the legacy asymmetric detRandom() // Let pairgoth use the legacy asymmetric detRandom()
BaseSolver.legacy_mode = legacy Solver.legacy_mode = legacy
// read tournament with pairing // read tournament with pairing
val file = getTestFile("opengotha/pairings/$name.xml") val file = getTestFile("opengotha/pairings/$name.xml")
logger.info("read from file $file") logger.info("read from file $file")
@@ -203,7 +203,7 @@ class PairingTests: TestBase() {
for (round in 1..tournament.getInt("rounds")!!) { for (round in 1..tournament.getInt("rounds")!!) {
val sumOfWeightsOG = compute_sumOfWeight_OG(getTestFile("opengotha/$name/$name" + "_weights_R$round.txt"), pairingsOG[round-1], players) val sumOfWeightsOG = compute_sumOfWeight_OG(getTestFile("opengotha/$name/$name" + "_weights_R$round.txt"), pairingsOG[round-1], players)
BaseSolver.weightsLogger = PrintWriter(FileWriter(getOutputFile("weights.txt"))) Solver.weightsLogger = PrintWriter(FileWriter(getOutputFile("weights.txt")))
// Call Pairgoth pairing solver to generate games // Call Pairgoth pairing solver to generate games
games = TestAPI.post("/api/tour/$id/pair/$round", Json.Array("all")).asArray() games = TestAPI.post("/api/tour/$id/pair/$round", Json.Array("all")).asArray()
logger.info("sumOfWeightOG = " + dec.format(sumOfWeightsOG)) logger.info("sumOfWeightOG = " + dec.format(sumOfWeightsOG))
@@ -273,7 +273,7 @@ class PairingTests: TestBase() {
@Test @Test
fun `SwissTest simpleSwiss`() { fun `SwissTest simpleSwiss`() {
BaseSolver.legacy_mode = true Solver.legacy_mode = true
// read tournament with pairing // read tournament with pairing
var file = getTestFile("opengotha/pairings/simpleswiss.xml") var file = getTestFile("opengotha/pairings/simpleswiss.xml")
logger.info("read from file $file") logger.info("read from file $file")
@@ -315,7 +315,7 @@ class PairingTests: TestBase() {
var firstGameID: Int var firstGameID: Int
for (round in 1..7) { for (round in 1..7) {
BaseSolver.weightsLogger = PrintWriter(FileWriter(getOutputFile("weights.txt"))) Solver.weightsLogger = PrintWriter(FileWriter(getOutputFile("weights.txt")))
games = TestAPI.post("/api/tour/$id/pair/$round", Json.Array("all")).asArray() games = TestAPI.post("/api/tour/$id/pair/$round", Json.Array("all")).asArray()
logger.info("games for round $round: {}", games.toString().slice(0..50) + "...") logger.info("games for round $round: {}", games.toString().slice(0..50) + "...")
assertTrue(compare_weights(getOutputFile("weights.txt"), getTestFile("opengotha/simpleswiss/simpleswiss_weights_R$round.txt")), "Not matching opengotha weights for round $round") assertTrue(compare_weights(getOutputFile("weights.txt"), getTestFile("opengotha/simpleswiss/simpleswiss_weights_R$round.txt")), "Not matching opengotha weights for round $round")
@@ -354,7 +354,7 @@ class PairingTests: TestBase() {
@Test @Test
fun `SwissTest KPMCSplitbug`() { fun `SwissTest KPMCSplitbug`() {
// Let pairgoth use the legacy asymmetric detRandom() // Let pairgoth use the legacy asymmetric detRandom()
BaseSolver.legacy_mode = true Solver.legacy_mode = true
// read tournament with pairing // read tournament with pairing
val name = "20240921-KPMC-Splitbug" val name = "20240921-KPMC-Splitbug"
val file = getTestFile("opengotha/pairings/$name.xml") val file = getTestFile("opengotha/pairings/$name.xml")
@@ -391,7 +391,7 @@ class PairingTests: TestBase() {
for (round in minRound..maxRound) { for (round in minRound..maxRound) {
val sumOfWeightsOG = compute_sumOfWeight_OG(getTestFile("opengotha/$name/$name" + "_weights_R$round.txt"), pairingsOG[round - minRound], players) val sumOfWeightsOG = compute_sumOfWeight_OG(getTestFile("opengotha/$name/$name" + "_weights_R$round.txt"), pairingsOG[round - minRound], players)
BaseSolver.weightsLogger = PrintWriter(FileWriter(getOutputFile("weights.txt"))) Solver.weightsLogger = PrintWriter(FileWriter(getOutputFile("weights.txt")))
// Call Pairgoth pairing solver to generate games // Call Pairgoth pairing solver to generate games
games = TestAPI.post("/api/tour/$id/pair/$round", Json.Array("all")).asArray() games = TestAPI.post("/api/tour/$id/pair/$round", Json.Array("all")).asArray()