From 1ca25220bf7ad644589de87c425d17a9030c5b06 Mon Sep 17 00:00:00 2001 From: Theo Barollet Date: Mon, 12 Jun 2023 16:53:28 +0200 Subject: [PATCH] Seeding with split fold/split/random --- .../org/jeudego/pairgoth/model/Pairing.kt | 28 ++++----- .../org/jeudego/pairgoth/pairing/Solver.kt | 62 +++++++++++++++---- 2 files changed, 64 insertions(+), 26 deletions(-) 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 d4e9aea..fcce2d4 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 @@ -25,13 +25,13 @@ private const val BA_MAX_AVOIDDUPLGAME: Long = 500000000000000L // 5e14 private const val BA_MAX_RANDOM: Long = 1000000000L // 2e9 private const val BA_MAX_BALANCEWB: Long = 1000000L // 1e6 -private const val MA_MAX_AVOID_MIXING_CATEGORIES: Double = 2e13 +private const val MA_MAX_AVOID_MIXING_CATEGORIES: Long = 20000000000000L // 2e13 // Ratio between MA_MAX_MINIMIZE_SCORE_DIFFERENCE and MA_MAX_AVOID_MIXING_CATEGORIES should stay below 1/ nbcat^2 -private const val MA_MAX_MINIMIZE_SCORE_DIFFERENCE: Double = 1e11 -private const val MA_MAX_DUDD_WEIGHT: Double = MA_MAX_MINIMIZE_SCORE_DIFFERENCE / 1000; // Draw-ups Draw-downs +private const val MA_MAX_MINIMIZE_SCORE_DIFFERENCE: Long = 100000000000L // 1e11 +private const val MA_MAX_DUDD_WEIGHT: Long = MA_MAX_MINIMIZE_SCORE_DIFFERENCE / 1000; // Draw-ups Draw-downs enum class MA_DUDD {TOP, MID, BOT} -private const val MA_MAX_MAXIMIZE_SEEDING: Double = MA_MAX_MINIMIZE_SCORE_DIFFERENCE / 20000; +private const val MA_MAX_MAXIMIZE_SEEDING: Long = MA_MAX_MINIMIZE_SCORE_DIFFERENCE / 20000; enum class SeedMethod { SPLIT_AND_FOLD, SPLIT_AND_RANDOM, SPLIT_AND_SLIP } @@ -50,14 +50,14 @@ sealed class Pairing(val type: PairingType, val pairingParams: PairingParams = P // Main criteria // TODO move avoidmixingcategories to swiss with category //val maAvoidMixingCategories: Double = MA_MAX_AVOID_MIXING_CATEGORIES, - val mainMinimizeScoreDifference: Double = MA_MAX_MINIMIZE_SCORE_DIFFERENCE, + val mainMinimizeScoreDifference: Long = MA_MAX_MINIMIZE_SCORE_DIFFERENCE, - val maDUDDWeight: Double = MA_MAX_DUDD_WEIGHT, + val maDUDDWeight: Long = MA_MAX_DUDD_WEIGHT, val maCompensateDUDD: Boolean = true, val maDUDDUpperMode: MA_DUDD = MA_DUDD.MID, val maDUDDLowerMode: MA_DUDD = MA_DUDD.MID, - val maMaximizeSeeding: Double = MA_MAX_MAXIMIZE_SEEDING, // 5 *10^6 + val maMaximizeSeeding: Long = MA_MAX_MAXIMIZE_SEEDING, // 5 *10^6 val maLastRoundForSeedSystem1: Int = 1, val maSeedSystem1: SeedMethod = SeedMethod.SPLIT_AND_RANDOM, val maSeedSystem2: SeedMethod = SeedMethod.SPLIT_AND_FOLD, @@ -68,7 +68,7 @@ sealed class Pairing(val type: PairingType, val pairingParams: PairingParams = P val seBarThresholdActive: Boolean = true, // Do not apply secondary criteria for players above bar val seRankThreshold: Int = 0, // Do not apply secondary criteria above 1D rank val seNbWinsThresholdActive: Boolean = true, // Do not apply secondary criteria when nbWins >= nbRounds / 2 - val seDefSecCrit: Double = MA_MAX_AVOID_MIXING_CATEGORIES, // Should be MA_MAX_MINIMIZE_SCORE_DIFFERENCE for MM, MA_MAX_AVOID_MIXING_CATEGORIES for others + val seDefSecCrit: Long = MA_MAX_AVOID_MIXING_CATEGORIES, // Should be MA_MAX_MINIMIZE_SCORE_DIFFERENCE for MM, MA_MAX_AVOID_MIXING_CATEGORIES for others // Geographical params val geo: GeographicalParams = GeographicalParams(avoidSameGeo = seDefSecCrit), @@ -82,19 +82,19 @@ sealed class Pairing(val type: PairingType, val pairingParams: PairingParams = P } data class GeographicalParams( - val avoidSameGeo: Double, // Should be SeDefSecCrit for SwCat and MM, 0 for Swiss + val avoidSameGeo: Long, // Should be SeDefSecCrit for SwCat and MM, 0 for Swiss val preferMMSDiffRatherThanSameCountry: Int = 1, // Typically = 1 val preferMMSDiffRatherThanSameClubsGroup: Int = 2, // Typically = 2 val preferMMSDiffRatherThanSameClub: Int = 3, // Typically = 3 ) { companion object { - fun disabled() = GeographicalParams(avoidSameGeo = 0.0) + fun disabled() = GeographicalParams(avoidSameGeo = 0L) } } data class HandicapParams( // minimizeHandicap is a secondary criteria but moved here - val minimizeHandicap: Double, // Should be paiSeDefSecCrit for SwCat, 0 for others + val minimizeHandicap: Long, // Should be paiSeDefSecCrit for SwCat, 0 for others val basedOnMMS: Boolean = true, // if hdBasedOnMMS is false, hd will be based on rank // When one player in the game has a rank of at least hdNoHdRankThreshold, // then the game will be without handicap @@ -104,7 +104,7 @@ data class HandicapParams( ) { companion object { fun disabled() = HandicapParams( - minimizeHandicap = 0.0, + minimizeHandicap = 0L, basedOnMMS = false, noHdRankThreshold=-30, // 30k ceiling=0) @@ -161,7 +161,7 @@ fun HandicapParams.toJson() = Json.Object( "ceiling" to ceiling, ) fun HandicapParams.fromJson(json: Json.Object) = HandicapParams( - minimizeHandicap=json.getDouble("minimize_hd")!!, + minimizeHandicap=json.getLong("minimize_hd")!!, basedOnMMS=json.getBoolean("mms_based")!!, noHdRankThreshold=json.getInt("no_hd_thresh")!!, correction=json.getInt("correction")!!, @@ -175,7 +175,7 @@ fun GeographicalParams.toJson() = Json.Object( "club" to preferMMSDiffRatherThanSameClub,) fun GeographicalParams.fromJson(json: Json.Object) = GeographicalParams( - avoidSameGeo=json.getDouble("avoid_same_geo")!!, + avoidSameGeo=json.getLong("avoid_same_geo")!!, preferMMSDiffRatherThanSameCountry=json.getInt("country")!!, preferMMSDiffRatherThanSameClubsGroup=json.getInt("club_group")!!, preferMMSDiffRatherThanSameClub=json.getInt("club")!!, diff --git a/api-webapp/src/main/kotlin/org/jeudego/pairgoth/pairing/Solver.kt b/api-webapp/src/main/kotlin/org/jeudego/pairgoth/pairing/Solver.kt index 0ec22f0..049ea87 100644 --- a/api-webapp/src/main/kotlin/org/jeudego/pairgoth/pairing/Solver.kt +++ b/api-webapp/src/main/kotlin/org/jeudego/pairgoth/pairing/Solver.kt @@ -180,11 +180,17 @@ sealed class Solver( // Main criterion 2 minimize score difference score += minimizeScoreDifference(p1, p2) + // Main criterion 3 If different groups, make a directed Draw-up/Draw-down + // TODO + + // Main criterion 4 seeding + score += applySeeding(p1, p2) + return score } open fun minimizeScoreDifference(p1: Pairable, p2: Pairable): Long { - var score: Long = 0 + var score = 0L val scoreRange: Int = numberGroups // TODO check category equality if category are used in SwissCat val x = abs(p1.group - p2.group) as Double / scoreRange.toDouble() @@ -194,6 +200,44 @@ sealed class Solver( return score } + fun applySeeding(p1: Pairable, p2: Pairable): Long { + var score = 0L + // Apply seeding for players in the same group + if (p1.group == p2.group) { + val (cla1, groupSize) = p1.placeInGroup + val cla2 = p2.placeInGroup.first + val maxSeedingWeight = pairingParams.maMaximizeSeeding + + val currentSeedSystem: SeedMethod = if (round <= pairingParams.maLastRoundForSeedSystem1) + pairingParams.maSeedSystem1 else pairingParams.maSeedSystem2 + + score += when(currentSeedSystem) { + // The best is to get 2 * |Cla1 - Cla2| - groupSize close to 0 + SeedMethod.SPLIT_AND_SLIP -> { + val x = 2 * abs(cla1 - cla2) - groupSize + maxSeedingWeight - maxSeedingWeight * x / groupSize * x / groupSize + } + + // The best is to get cla1 + cla2 - (groupSize - 1) close to 0 + SeedMethod.SPLIT_AND_FOLD -> { + val x = cla1 + cla2 - (groupSize - 1) + maxSeedingWeight - maxSeedingWeight * x / (groupSize - 1) * x / (groupSize - 1) + } + + SeedMethod.SPLIT_AND_RANDOM -> { + if ((2 * cla1 < groupSize && 2 * cla2 >= groupSize) || (2 * cla1 >= groupSize && 2 * cla2 < groupSize)) { + val randRange = (maxSeedingWeight * 0.2).toLong() + val rand = detRandom(randRange, p1, p2) + maxSeedingWeight - rand + } else { + 0L + } + } + } + } + return score + } + // Handicap functions // Has to be overridden if handicap is not based on rank open fun handicap(p1: Pairable, p2: Pairable): Int { @@ -237,19 +281,13 @@ sealed class Solver( else HistoryHelper(history, standingScore) + // Decide each pairable group based on the main criterion - private fun computeGroups(): Pair, Int> { + private val numberGroups by lazy { val (mainScoreMin, mainScoreMax) = mainCriterionMinMax() - - // TODO categories - val groups: Map = pairables.associate { pairable -> Pair(pairable.id, mainCriterion(pairable)) } - - return Pair(groups, mainScoreMax - mainScoreMin) + mainScoreMax - mainScoreMin } - - private val groupsResult = computeGroups() - private val _groups = groupsResult.first - private val numberGroups = groupsResult.second + private val _groups = pairables.associate { pairable -> Pair(pairable.id, mainCriterion(pairable)) } // pairables sorted using overloadable sort function private val sortedPairables by lazy { @@ -265,7 +303,7 @@ sealed class Solver( } // placeInGroup (of same score) : Pair(place, groupSize) - val Pairable.placeInGroup: Pair get() = _placeInGroup[id]!! + private val Pairable.placeInGroup: Pair get() = _placeInGroup[id]!! private val _placeInGroup by lazy { sortedPairables.groupBy { it.group