From f704f3adb267ecdda57e26e5d053baa82489d617 Mon Sep 17 00:00:00 2001 From: Claude Brisson Date: Thu, 24 Jul 2025 14:14:03 +0200 Subject: [PATCH] Code cleaning: fix previous commit, simplify HistoryHelper creation --- .../org/jeudego/pairgoth/api/ApiTools.kt | 8 +- .../org/jeudego/pairgoth/model/Pairing.kt | 24 +--- .../org/jeudego/pairgoth/model/Tournament.kt | 27 +---- .../pairgoth/pairing/BasePairingHelper.kt | 9 +- .../jeudego/pairgoth/pairing/HistoryHelper.kt | 113 ++++++++++-------- .../org/jeudego/pairgoth/pairing/Random.kt | 1 - .../pairgoth/pairing/solver/MacMahonSolver.kt | 31 +++-- .../solver/{BaseSolver.kt => Solver.kt} | 32 ++++- .../pairgoth/pairing/solver/SwissSolver.kt | 18 ++- api-webapp/src/test/kotlin/BOSP2024Test.kt | 10 +- api-webapp/src/test/kotlin/MalavasiTest.kt | 4 +- api-webapp/src/test/kotlin/PairingTests.kt | 16 +-- 12 files changed, 152 insertions(+), 141 deletions(-) rename api-webapp/src/main/kotlin/org/jeudego/pairgoth/pairing/solver/{BaseSolver.kt => Solver.kt} (95%) diff --git a/api-webapp/src/main/kotlin/org/jeudego/pairgoth/api/ApiTools.kt b/api-webapp/src/main/kotlin/org/jeudego/pairgoth/api/ApiTools.kt index c48e00e..51fdd06 100644 --- a/api-webapp/src/main/kotlin/org/jeudego/pairgoth/api/ApiTools.kt +++ b/api-webapp/src/main/kotlin/org/jeudego/pairgoth/api/ApiTools.kt @@ -27,7 +27,7 @@ fun Tournament<*>.getSortedPairables(round: Int, includePreliminary: Boolean = f return ArrayList(frozen!!.map { it -> it as Json.Object }) } - val history = historyHelper(round) + val history = historyHelper(round + 1) val neededCriteria = ArrayList(pairing.placementParams.criteria) 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 -> roundTeamGames.flatMap { game -> individualGames[game.id]?.toList() ?: listOf() } } - val historyHelper = HistoryHelper(individualHistory) { - pairables.mapValues { - Pair(0.0, wins[it.key] ?: 0.0) - } + val historyHelper = HistoryHelper(individualHistory).apply { + scoresFactory = { wins } } val neededCriteria = mutableListOf(Criterion.NBW, Criterion.RATING) val criteria = neededCriteria.map { crit -> diff --git a/api-webapp/src/main/kotlin/org/jeudego/pairgoth/model/Pairing.kt b/api-webapp/src/main/kotlin/org/jeudego/pairgoth/model/Pairing.kt index e4a3877..6d1ee68 100644 --- a/api-webapp/src/main/kotlin/org/jeudego/pairgoth/model/Pairing.kt +++ b/api-webapp/src/main/kotlin/org/jeudego/pairgoth/model/Pairing.kt @@ -4,7 +4,8 @@ import com.republicate.kson.Json import org.jeudego.pairgoth.api.ApiHandler.Companion.badRequest import org.jeudego.pairgoth.model.MainCritParams.SeedMethod.SPLIT_AND_SLIP 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.SwissSolver import kotlin.math.min @@ -131,7 +132,7 @@ sealed class Pairing( val pairingParams: PairingParams, val placementParams: PlacementParams) { companion object {} - abstract fun solver(tournament: Tournament<*>, round: Int, pairables: List): BaseSolver + abstract fun solver(tournament: Tournament<*>, round: Int, pairables: List): Solver fun pair(tournament: Tournament<*>, round: Int, pairables: List): List { return solver(tournament, round, pairables).pair() } @@ -140,19 +141,6 @@ sealed class Pairing( internal fun Tournament<*>.historyBefore(round: Int) = (1 until min(round, lastRound() + 1)).map { games(it).values.toList() } -/*private fun Tournament<*>.historyBefore(round: Int) : List> { - 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( pairingParams: PairingParams = PairingParams( base = BaseCritParams(), @@ -175,7 +163,7 @@ class Swiss( ): Pairing(SWISS, pairingParams, placementParams) { companion object {} override fun solver(tournament: Tournament<*>, round: Int, pairables: List) = - 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( @@ -203,14 +191,14 @@ class MacMahon( ): Pairing(MAC_MAHON, pairingParams, placementParams) { companion object {} override fun solver(tournament: Tournament<*>, round: Int, pairables: List) = - 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( pairingParams: PairingParams = PairingParams(), placementParams: PlacementParams = PlacementParams(Criterion.NBW, Criterion.RATING) ): Pairing(ROUND_ROBIN, pairingParams, placementParams) { - override fun solver(tournament: Tournament<*>, round: Int, pairables: List): BaseSolver { + override fun solver(tournament: Tournament<*>, round: Int, pairables: List): Solver { TODO("not implemented") } } diff --git a/api-webapp/src/main/kotlin/org/jeudego/pairgoth/model/Tournament.kt b/api-webapp/src/main/kotlin/org/jeudego/pairgoth/model/Tournament.kt index aefbdc3..a7bdaed 100644 --- a/api-webapp/src/main/kotlin/org/jeudego/pairgoth/model/Tournament.kt +++ b/api-webapp/src/main/kotlin/org/jeudego/pairgoth/model/Tournament.kt @@ -225,32 +225,7 @@ sealed class Tournament ( } fun historyHelper(round: Int): HistoryHelper { - return HistoryHelper(historyBefore(round + 1)) { - 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 - ) - } - } - } - } + return pairing.solver(this, round, emptyList()).history } } diff --git a/api-webapp/src/main/kotlin/org/jeudego/pairgoth/pairing/BasePairingHelper.kt b/api-webapp/src/main/kotlin/org/jeudego/pairgoth/pairing/BasePairingHelper.kt index bb309a1..3b829fa 100644 --- a/api-webapp/src/main/kotlin/org/jeudego/pairgoth/pairing/BasePairingHelper.kt +++ b/api-webapp/src/main/kotlin/org/jeudego/pairgoth/pairing/BasePairingHelper.kt @@ -11,14 +11,11 @@ abstract class BasePairingHelper( val placement: PlacementParams, ) { - val scores get() = history.scores - abstract val scoresX: Map - // Extend pairables with members from all rounds // 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 - val Pairable.main: Double get() = scores[id]?.second ?: 0.0 + val Pairable.main: Double get() = score ?: 0.0 abstract val mainLimits: Pair // pairables sorted using overloadable sort function @@ -89,9 +86,9 @@ abstract class BasePairingHelper( 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.sos: Double get() = history.sos[id] ?: 0.0 val Pairable.sosm1: Double get() = history.sosm1[id] ?: 0.0 val Pairable.sosm2: Double get() = history.sosm2[id] ?: 0.0 diff --git a/api-webapp/src/main/kotlin/org/jeudego/pairgoth/pairing/HistoryHelper.kt b/api-webapp/src/main/kotlin/org/jeudego/pairgoth/pairing/HistoryHelper.kt index 68895fa..2cbb595 100644 --- a/api-webapp/src/main/kotlin/org/jeudego/pairgoth/pairing/HistoryHelper.kt +++ b/api-webapp/src/main/kotlin/org/jeudego/pairgoth/pairing/HistoryHelper.kt @@ -2,22 +2,22 @@ package org.jeudego.pairgoth.pairing import org.jeudego.pairgoth.model.* import org.jeudego.pairgoth.model.Game.Result.* -import org.jeudego.pairgoth.model.TeamTournament.Team -import org.jeudego.pairgoth.pairing.solver.MacMahonSolver -import kotlin.math.max -import kotlin.math.min +import org.jeudego.pairgoth.pairing.solver.Solver -/** - * Map from a pairable ID to a pair of (missed rounds increment, main score). - * 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> +typealias ScoreMap = Map +typealias ScoreMapFactory = () -> ScoreMap open class HistoryHelper( - protected val history: List>, - // scoresGetter() returns Pair(sos value for missed rounds, score) where score is nbw for Swiss, mms for MM, ... - scoresGetter: ScoreMapBuilder) { + protected val history: List> +) { + + 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) { BLACK, BOTHWIN -> 1.0 @@ -29,16 +29,6 @@ open class HistoryHelper( 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 open fun playedTogether(p1: Pairable, p2: Pairable) = paired.contains(Pair(p1.id, p2.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> by lazy { - history.map { - it.fold(mutableSetOf()) { acc, next -> - if(next.white != 0) acc.add(next.white) - if (next.black != 0) acc.add(next.black) - acc - } + history.map { roundGames -> + roundGames.flatMap { + game -> listOf(game.white, game.black) + }.filter { id -> + id != ByePlayer.id + }.toSet() } } @@ -110,67 +100,89 @@ open class HistoryHelper( } // 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 { + // SOS for played games against a real opponent or BIP val historySos = (history.flatten().map { game -> Pair( game.black, - if (game.white == 0) scores[game.black]?.first ?: 0.0 - else scores[game.white]?.second?.let { it - game.handicap } ?: 0.0 + if (game.white == 0) missedRoundsSos[game.black] ?: 0.0 + else scores[game.white]?.let { it - game.handicap } ?: 0.0 ) } + history.flatten().map { game -> Pair( game.white, - if (game.black == 0) scores[game.white]?.first ?: 0.0 - else scores[game.black]?.second?.let { it + game.handicap } ?: 0.0 + if (game.black == 0) missedRoundsSos[game.white] ?: 0.0 + else scores[game.black]?.let { it + game.handicap } ?: 0.0 ) }).groupingBy { it.first }.fold(0.0) { acc, next -> acc + next.second } - - scores.mapValues { (id, pair) -> + // plus SOS for missed rounds + missedRoundsSos.mapValues { (id, pseudoSos) -> (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 val sosm1 by lazy { + // SOS for played games against a real opponent or BIP (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 -> - 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 { it.first }.mapValues { (id, pairs) -> val oppScores = pairs.map { it.second }.sortedDescending() + // minus greatest SOS oppScores.sum() - (oppScores.firstOrNull() ?: 0.0) + + // plus SOS for missed rounds playersPerRound.sumOf { players -> if (players.contains(id)) 0.0 - else scores[id]?.first ?: 0.0 + else missedRoundsSos[id] ?: 0.0 } } } // sos-2 val sosm2 by lazy { + // SOS for played games against a real opponent or BIP (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 -> - 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 { it.first }.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 } + + // plus SOS for missed rounds playersPerRound.sumOf { players -> 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 -> game.white != 0 // Remove games against byePlayer }.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 -> game.white != 0 // Remove games against byePlayer }.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 -> acc + next.second } } + // sosos val sosos by lazy { val currentRound = history.size @@ -203,9 +216,9 @@ open class HistoryHelper( acc + next.second } - scores.mapValues { (id, pair) -> + missedRoundsSos.mapValues { (id, missedRoundSos) -> (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 } } diff --git a/api-webapp/src/main/kotlin/org/jeudego/pairgoth/pairing/Random.kt b/api-webapp/src/main/kotlin/org/jeudego/pairgoth/pairing/Random.kt index 35a32b7..a1a2c11 100644 --- a/api-webapp/src/main/kotlin/org/jeudego/pairgoth/pairing/Random.kt +++ b/api-webapp/src/main/kotlin/org/jeudego/pairgoth/pairing/Random.kt @@ -1,7 +1,6 @@ package org.jeudego.pairgoth.pairing import org.jeudego.pairgoth.model.Pairable -import org.jeudego.pairgoth.pairing.solver.BaseSolver fun detRandom(max: Double, p1: Pairable, p2: Pairable, symmetric: Boolean): Double { var inverse = false diff --git a/api-webapp/src/main/kotlin/org/jeudego/pairgoth/pairing/solver/MacMahonSolver.kt b/api-webapp/src/main/kotlin/org/jeudego/pairgoth/pairing/solver/MacMahonSolver.kt index 6030d54..7dba94b 100644 --- a/api-webapp/src/main/kotlin/org/jeudego/pairgoth/pairing/solver/MacMahonSolver.kt +++ b/api-webapp/src/main/kotlin/org/jeudego/pairgoth/pairing/solver/MacMahonSolver.kt @@ -16,19 +16,31 @@ class MacMahonSolver(round: Int, placementParams: PlacementParams, usedTables: BitSet, 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 by lazy { - require (mmBar > mmFloor) { "MMFloor is higher than MMBar" } - allPairablesMap.mapValues { - it.value.let { pairable -> - roundScore(pairable.mmBase + pairable.nbW) + override fun mainScoreMapFactory() = + allPairablesMap.mapValues { (id, pairable) -> + roundScore(pairable.mmBase + + pairable.nbW + + pairable.missedRounds() * pairing.main.mmsValueAbsent) + } + + override fun scoreXMapFactory() = + allPairablesMap.mapValues { (id, pairable) -> + 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{ - return 2*scores[p.id]!!.second + return 2 * p.score } 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 val Pairable.mmBase: Double get() = min(max(rank, mmFloor), mmBar) + mmsZero + mmsCorrection // mms: current Mac-Mahon score of the pairable - val Pairable.mms: Double get() = scores[id]?.second ?: 0.0 - val Pairable.scoreX: Double get() = scoresX[id] ?: 0.0 + val Pairable.mms: Double get() = score // CB TODO - configurable criteria val mainScoreMin = mmFloor + PLA_SMMS_SCORE_MIN - Pairable.MIN_RANK diff --git a/api-webapp/src/main/kotlin/org/jeudego/pairgoth/pairing/solver/BaseSolver.kt b/api-webapp/src/main/kotlin/org/jeudego/pairgoth/pairing/solver/Solver.kt similarity index 95% rename from api-webapp/src/main/kotlin/org/jeudego/pairgoth/pairing/solver/BaseSolver.kt rename to api-webapp/src/main/kotlin/org/jeudego/pairgoth/pairing/solver/Solver.kt index 57d2b2d..85a6c6a 100644 --- a/api-webapp/src/main/kotlin/org/jeudego/pairgoth/pairing/solver/BaseSolver.kt +++ b/api-webapp/src/main/kotlin/org/jeudego/pairgoth/pairing/solver/Solver.kt @@ -19,11 +19,12 @@ import java.text.DecimalFormat import java.util.* import kotlin.math.* -sealed class BaseSolver( +sealed class Solver( round: Int, totalRounds: Int, - history: HistoryHelper, // History of all games played for each round - pairables: List, // All pairables for this round, it may include the bye player + history: HistoryHelper, // Digested history of all games played for each round + pairables: List, // Pairables to pair together + val allPairablesMap: Map, // Map of all known pairables pairing: PairingParams, placement: PlacementParams, val usedTables: BitSet @@ -36,6 +37,27 @@ sealed class BaseSolver( 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 + + /** + * ScoreX map factory (NBW for Swiss, MMSBase + MMS for MacMahon, ...). + */ + abstract fun scoreXMapFactory(): Map + + /** + * SOS for missed rounds factory (0 for Swiss, mmBase or mmBase+rounds/2 for MacMahon depending on pairing option sosValueAbsentUseBase) + */ + abstract fun missedRoundSosMapFactory(): Map + open fun openGothaWeight(p1: Pairable, p2: Pairable) = 1.0 + // 1 is minimum value because 0 means "no matching allowed" pairing.base.apply(p1, p2) + @@ -144,8 +166,8 @@ sealed class BaseSolver( for (p in sortedPairables) { logger.info(String.format("%-20s", p.name.substring(0, min(p.name.length, 18))) + " " + String.format("%-4s", p.id) - + " " + String.format("%-4s", scores[p.id]?.first) - + " " + String.format("%-4s", scores[p.id]?.second) + + " " + String.format("%-4s", history.missedRoundsSos[p.id]) + + " " + String.format("%-4s", history.scores[p.id]) + " " + String.format("%-4s", p.sos) ) } diff --git a/api-webapp/src/main/kotlin/org/jeudego/pairgoth/pairing/solver/SwissSolver.kt b/api-webapp/src/main/kotlin/org/jeudego/pairgoth/pairing/solver/SwissSolver.kt index 5e3891f..20b92d4 100644 --- a/api-webapp/src/main/kotlin/org/jeudego/pairgoth/pairing/solver/SwissSolver.kt +++ b/api-webapp/src/main/kotlin/org/jeudego/pairgoth/pairing/solver/SwissSolver.kt @@ -8,18 +8,28 @@ class SwissSolver(round: Int, totalRounds: Int, history: HistoryHelper, pairables: List, - pairablesMap: Map, + allPairablesMap: Map, pairingParams: PairingParams, placementParams: PlacementParams, usedTables: BitSet ): - BaseSolver(round, totalRounds, history, pairables, pairingParams, placementParams, usedTables) { + Solver(round, totalRounds, history, pairables, allPairablesMap, pairingParams, placementParams, usedTables) { - override val scoresX: Map 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 fun computeWeightForBye(p: Pairable): Double{ - return p.rank + 40*p.main + return p.rank + 40 * p.main } } diff --git a/api-webapp/src/test/kotlin/BOSP2024Test.kt b/api-webapp/src/test/kotlin/BOSP2024Test.kt index a2adf96..50970c9 100644 --- a/api-webapp/src/test/kotlin/BOSP2024Test.kt +++ b/api-webapp/src/test/kotlin/BOSP2024Test.kt @@ -1,14 +1,12 @@ package org.jeudego.pairgoth.test import com.republicate.kson.Json -import org.jeudego.pairgoth.pairing.solver.BaseSolver -import org.jeudego.pairgoth.model.Game +import org.jeudego.pairgoth.pairing.solver.Solver import org.jeudego.pairgoth.test.PairingTests.Companion.compare_weights import org.junit.jupiter.api.Test import java.io.FileWriter import java.io.PrintWriter import java.nio.charset.StandardCharsets -import kotlin.test.assertEquals import kotlin.test.assertNotEquals import kotlin.test.assertNotNull import kotlin.test.assertTrue @@ -22,16 +20,16 @@ class BOSP2024Test: TestBase() { )!!.asObject() val resp = TestAPI.post("/api/tour", tournament).asObject() 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() // compare weights 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")) - BaseSolver.legacy_mode = false + Solver.legacy_mode = false val games = TestAPI.post("/api/tour/$tourId/pair/3", Json.Array("all")).asArray() // Aksut Husrev is ID 18 val solved = games.map { it as Json.Object }.filter { game -> diff --git a/api-webapp/src/test/kotlin/MalavasiTest.kt b/api-webapp/src/test/kotlin/MalavasiTest.kt index 52626a0..61c1bba 100644 --- a/api-webapp/src/test/kotlin/MalavasiTest.kt +++ b/api-webapp/src/test/kotlin/MalavasiTest.kt @@ -1,7 +1,7 @@ package org.jeudego.pairgoth.test 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.junit.jupiter.api.Test import java.io.FileWriter @@ -19,7 +19,7 @@ class MalavasiTest: TestBase() { )!!.asObject() val resp = TestAPI.post("/api/tour", tournament).asObject() 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() // Oceane is ID 548, Valentine 549 val buggy = games.map { it as Json.Object }.filter { game -> diff --git a/api-webapp/src/test/kotlin/PairingTests.kt b/api-webapp/src/test/kotlin/PairingTests.kt index e661cf3..9f1160e 100644 --- a/api-webapp/src/test/kotlin/PairingTests.kt +++ b/api-webapp/src/test/kotlin/PairingTests.kt @@ -2,7 +2,7 @@ package org.jeudego.pairgoth.test import com.republicate.kson.Json 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.lastPlayerId import org.junit.jupiter.api.BeforeEach @@ -59,7 +59,7 @@ class PairingTests: TestBase() { } fun compare_weights(file1: File, file2: File, skipSeeding: Boolean = false):Boolean { - BaseSolver.weightsLogger!!.flush() + Solver.weightsLogger!!.flush() // Maps to store name pairs and costs val map1 = create_weights_map(file1) val map2 = create_weights_map(file2) @@ -172,7 +172,7 @@ class PairingTests: TestBase() { fun test_from_XML_internal(name: String, forcePairing:List, legacy: Boolean) { // Let pairgoth use the legacy asymmetric detRandom() - BaseSolver.legacy_mode = legacy + Solver.legacy_mode = legacy // read tournament with pairing val file = getTestFile("opengotha/pairings/$name.xml") logger.info("read from file $file") @@ -203,7 +203,7 @@ class PairingTests: TestBase() { for (round in 1..tournament.getInt("rounds")!!) { 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 games = TestAPI.post("/api/tour/$id/pair/$round", Json.Array("all")).asArray() logger.info("sumOfWeightOG = " + dec.format(sumOfWeightsOG)) @@ -273,7 +273,7 @@ class PairingTests: TestBase() { @Test fun `SwissTest simpleSwiss`() { - BaseSolver.legacy_mode = true + Solver.legacy_mode = true // read tournament with pairing var file = getTestFile("opengotha/pairings/simpleswiss.xml") logger.info("read from file $file") @@ -315,7 +315,7 @@ class PairingTests: TestBase() { var firstGameID: Int 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() 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") @@ -354,7 +354,7 @@ class PairingTests: TestBase() { @Test fun `SwissTest KPMCSplitbug`() { // Let pairgoth use the legacy asymmetric detRandom() - BaseSolver.legacy_mode = true + Solver.legacy_mode = true // read tournament with pairing val name = "20240921-KPMC-Splitbug" val file = getTestFile("opengotha/pairings/$name.xml") @@ -391,7 +391,7 @@ class PairingTests: TestBase() { for (round in minRound..maxRound) { 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 games = TestAPI.post("/api/tour/$id/pair/$round", Json.Array("all")).asArray()