Tackle solver reeng

This commit is contained in:
Claude Brisson
2023-06-20 08:17:56 +02:00
parent 2901394052
commit e2cf4bb0fd
5 changed files with 170 additions and 159 deletions

View File

@@ -8,7 +8,7 @@ import org.jeudego.pairgoth.pairing.MacMahonSolver
import org.jeudego.pairgoth.pairing.SwissSolver import org.jeudego.pairgoth.pairing.SwissSolver
// base pairing parameters // base pairing parameters
data class BasePairingParams( data class BaseCritParams(
// standard NX1 factor for concavity curves // standard NX1 factor for concavity curves
val nx1: Double = 0.5, val nx1: Double = 0.5,
val dupWeight: Double = MAX_AVOIDDUPGAME, val dupWeight: Double = MAX_AVOIDDUPGAME,
@@ -27,7 +27,7 @@ data class BasePairingParams(
const val MAX_AVOIDDUPGAME = 500000000000000.0 // 5e14 const val MAX_AVOIDDUPGAME = 500000000000000.0 // 5e14
const val MAX_RANDOM = 1000000000.0 // 1e9 const val MAX_RANDOM = 1000000000.0 // 1e9
const val MAX_COLOR_BALANCE = 1000000.0 // 1e6 const val MAX_COLOR_BALANCE = 1000000.0 // 1e6
val default = BasePairingParams() val default = BaseCritParams()
} }
} }
@@ -106,7 +106,7 @@ data class HandicapParams(
enum class PairingType { SWISS, MAC_MAHON, ROUND_ROBIN } enum class PairingType { SWISS, MAC_MAHON, ROUND_ROBIN }
data class PairingParams( data class PairingParams(
val base: BasePairingParams = BasePairingParams(), val base: BaseCritParams = BaseCritParams(),
val main: MainCritParams = MainCritParams(), val main: MainCritParams = MainCritParams(),
val secondary: SecondaryCritParams = SecondaryCritParams(), val secondary: SecondaryCritParams = SecondaryCritParams(),
val geo: GeographicalParams = GeographicalParams(), val geo: GeographicalParams = GeographicalParams(),
@@ -123,11 +123,11 @@ sealed class Pairing(
private fun Tournament<*>.historyBefore(round: Int) = private fun Tournament<*>.historyBefore(round: Int) =
if (lastRound() == 0) emptyList() if (lastRound() == 0) emptyList()
else (0 until round).flatMap { games(round).values } else (0 until round).map { games(round).values.toList() }
class Swiss( class Swiss(
pairingParams: PairingParams = PairingParams( pairingParams: PairingParams = PairingParams(
base = BasePairingParams(), base = BaseCritParams(),
main = MainCritParams( main = MainCritParams(
seedSystem1 = SPLIT_AND_SLIP, seedSystem1 = SPLIT_AND_SLIP,
seedSystem2 = SPLIT_AND_SLIP seedSystem2 = SPLIT_AND_SLIP
@@ -153,7 +153,7 @@ class Swiss(
class MacMahon( class MacMahon(
pairingParams: PairingParams = PairingParams( pairingParams: PairingParams = PairingParams(
base = BasePairingParams(), base = BaseCritParams(),
main = MainCritParams(), main = MainCritParams(),
secondary = SecondaryCritParams( secondary = SecondaryCritParams(
defSecCrit = MainCritParams.MAX_SCORE_WEIGHT defSecCrit = MainCritParams.MAX_SCORE_WEIGHT
@@ -184,7 +184,7 @@ class RoundRobin(
// Serialization // Serialization
fun BasePairingParams.Companion.fromJson(json: Json.Object) = BasePairingParams( fun BaseCritParams.Companion.fromJson(json: Json.Object) = BaseCritParams(
nx1 = json.getDouble("nx1") ?: default.nx1, nx1 = json.getDouble("nx1") ?: default.nx1,
dupWeight = json.getDouble("dupWeight") ?: default.dupWeight, dupWeight = json.getDouble("dupWeight") ?: default.dupWeight,
random = json.getDouble("random") ?: default.random, random = json.getDouble("random") ?: default.random,
@@ -192,7 +192,7 @@ fun BasePairingParams.Companion.fromJson(json: Json.Object) = BasePairingParams(
colorBalance = json.getDouble("colorBalanceWeight") ?: default.colorBalance colorBalance = json.getDouble("colorBalanceWeight") ?: default.colorBalance
) )
fun BasePairingParams.toJson() = Json.Object( fun BaseCritParams.toJson() = Json.Object(
"nx1" to nx1, "nx1" to nx1,
"dupWeight" to dupWeight, "dupWeight" to dupWeight,
"random" to random, "random" to random,
@@ -281,7 +281,7 @@ fun Pairing.Companion.fromJson(json: Json.Object): Pairing {
MAC_MAHON -> MacMahon() MAC_MAHON -> MacMahon()
ROUND_ROBIN -> RoundRobin() ROUND_ROBIN -> RoundRobin()
} }
val base = json.getObject("base")?.let { BasePairingParams.fromJson(it) } ?: defaultParams.pairingParams.base val base = json.getObject("base")?.let { BaseCritParams.fromJson(it) } ?: defaultParams.pairingParams.base
val main = json.getObject("main")?.let { MainCritParams.fromJson(it) } ?: defaultParams.pairingParams.main val main = json.getObject("main")?.let { MainCritParams.fromJson(it) } ?: defaultParams.pairingParams.main
val secondary = json.getObject("secondary")?.let { SecondaryCritParams.fromJson(it) } ?: defaultParams.pairingParams.secondary val secondary = json.getObject("secondary")?.let { SecondaryCritParams.fromJson(it) } ?: defaultParams.pairingParams.secondary
val geo = json.getObject("geo")?.let { GeographicalParams.fromJson(it) } ?: defaultParams.pairingParams.geo val geo = json.getObject("geo")?.let { GeographicalParams.fromJson(it) } ?: defaultParams.pairingParams.geo

View File

@@ -1,8 +1,19 @@
package org.jeudego.pairgoth.pairing package org.jeudego.pairgoth.pairing
import org.jeudego.pairgoth.model.* import org.jeudego.pairgoth.model.*
import org.jeudego.pairgoth.model.Game.Result.*
open class HistoryHelper(protected val history: List<Game>, computeScore: () -> Map<ID, Double>) { open class HistoryHelper(protected val history: List<List<Game>>, computeScore: () -> Map<ID, Double>) {
private val Game.blackScore get() = when (result) {
BLACK, BOTHWIN -> 1.0
else -> 0.0
}
private val Game.whiteScore get() = when (result) {
WHITE, BOTHWIN -> 1.0
else -> 0.0
}
fun getCriterionValue(p: Pairable, crit: Criterion): Double { fun getCriterionValue(p: Pairable, crit: Criterion): Double {
// Returns generic criterion // Returns generic criterion
@@ -23,13 +34,12 @@ open class HistoryHelper(protected val history: List<Game>, computeScore: () ->
// 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]
open fun nbW(p: Pairable) = numberWins[p.id] open fun nbW(p: Pairable) = wins[p.id]
protected val paired: Set<Pair<ID, ID>> by lazy { protected val paired: Set<Pair<ID, ID>> by lazy {
(history.map { game -> (history.flatten().map { game ->
Pair(game.black, game.white) Pair(game.black, game.white)
} + history.map { game -> } + history.flatten().map { game ->
Pair(game.white, game.black) Pair(game.white, game.black)
}).toSet() }).toSet()
} }
@@ -37,25 +47,26 @@ open class HistoryHelper(protected val history: List<Game>, computeScore: () ->
// Returns the number of games played as white // Returns the number of games played as white
// Only count games without handicap // Only count games without handicap
private val colorBalance: Map<ID, Int> by lazy { private val colorBalance: Map<ID, Int> by lazy {
history.flatMap { game -> if (game.handicap == 0) { history.flatten().filter { game ->
game.handicap == 0
}.flatMap { game ->
listOf(Pair(game.white, +1), Pair(game.black, -1)) listOf(Pair(game.white, +1), Pair(game.black, -1))
} else { }.groupingBy {
listOf(Pair(game.white, 0), Pair(game.black, 0)) it.first
} }.fold(0) { acc, next ->
}.groupingBy { it.first }.fold(0) { acc, next ->
acc + next.second acc + next.second
} }
} }
val numberWins: Map<ID, Double> by lazy { val wins: Map<ID, Double> by lazy {
mutableMapOf<ID, Double>().apply { mutableMapOf<ID, Double>().apply {
history.forEach { game -> history.flatten().forEach { game ->
when (game.result) { when (game.result) {
Game.Result.BLACK -> put(game.black, getOrDefault(game.black, 0.0) + 1.0) Game.Result.BLACK -> put(game.black, getOrDefault(game.black, 0.0) + 1.0)
Game.Result.WHITE -> put(game.white, getOrDefault(game.white, 0.0) + 1.0) Game.Result.WHITE -> put(game.white, getOrDefault(game.white, 0.0) + 1.0)
Game.Result.BOTHWIN -> { Game.Result.BOTHWIN -> {
put(game.black, getOrDefault(game.black, 0.0) + 0.5) put(game.black, getOrDefault(game.black, 0.0) + 1.0)
put(game.white, getOrDefault(game.white, 0.0) + 0.5) put(game.white, getOrDefault(game.white, 0.0) + 1.0)
} }
else -> {} else -> {}
} }
@@ -70,9 +81,9 @@ open class HistoryHelper(protected val history: List<Game>, computeScore: () ->
// SOS related functions given a score function // SOS related functions given a score function
val sos by lazy { val sos by lazy {
(history.map { game -> (history.flatten().map { game ->
Pair(game.black, score[game.white] ?: 0.0) Pair(game.black, score[game.white] ?: 0.0)
} + history.map { game -> } + history.flatten().map { game ->
Pair(game.white, score[game.black] ?: 0.0) Pair(game.white, score[game.black] ?: 0.0)
}).groupingBy { it.first }.fold(0.0) { acc, next -> }).groupingBy { it.first }.fold(0.0) { acc, next ->
acc + next.second acc + next.second
@@ -80,20 +91,38 @@ open class HistoryHelper(protected val history: List<Game>, computeScore: () ->
} }
// sos-1 // sos-1
val sosm1: Map<ID, Double> by lazy { val sosm1 by lazy {
TODO() (history.flatten().map { game ->
Pair(game.black, score[game.white] ?: 0.0)
} + history.flatten().map { game ->
Pair(game.white, score[game.black] ?: 0.0)
}).groupBy {
it.first
}.mapValues {
val scores = it.value.map { it.second }.sorted()
scores.sum() - (scores.firstOrNull() ?: 0.0)
}
} }
// sos-2 // sos-2
val sosm2: Map<ID, Double> by lazy { val sosm2 by lazy {
TODO() (history.flatten().map { game ->
Pair(game.black, score[game.white] ?: 0.0)
} + history.flatten().map { game ->
Pair(game.white, score[game.black] ?: 0.0)
}).groupBy {
it.first
}.mapValues {
val scores = it.value.map { it.second }.sorted()
scores.sum() - scores.getOrElse(0) { 0.0 } - scores.getOrElse(1) { 0.0 }
}
} }
// sodos // sodos
val sodos by lazy { val sodos by lazy {
(history.map { game -> (history.flatten().map { game ->
Pair(game.black, if (game.result == Game.Result.BLACK) score[game.white] ?: 0.0 else 0.0) Pair(game.black, if (game.result == Game.Result.BLACK) score[game.white] ?: 0.0 else 0.0)
} + history.map { game -> } + history.flatten().map { game ->
Pair(game.white, if (game.result == Game.Result.WHITE) score[game.black] ?: 0.0 else 0.0) Pair(game.white, if (game.result == Game.Result.WHITE) score[game.black] ?: 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
@@ -102,9 +131,9 @@ open class HistoryHelper(protected val history: List<Game>, computeScore: () ->
// sosos // sosos
val sosos by lazy { val sosos by lazy {
(history.map { game -> (history.flatten().map { game ->
Pair(game.black, sos[game.white] ?: 0.0) Pair(game.black, sos[game.white] ?: 0.0)
} + history.map { game -> } + history.flatten().map { game ->
Pair(game.white, sos[game.black] ?: 0.0) Pair(game.white, sos[game.black] ?: 0.0)
}).groupingBy { it.first }.fold(0.0) { acc, next -> }).groupingBy { it.first }.fold(0.0) { acc, next ->
acc + next.second acc + next.second
@@ -112,14 +141,25 @@ open class HistoryHelper(protected val history: List<Game>, computeScore: () ->
} }
// cumulative score // cumulative score
val cumscore: Map<ID, Double> by lazy { val cumScore by lazy {
TODO() history.map { games ->
(games.groupingBy { it.black }.fold(0.0) { acc, next ->
acc + next.blackScore
}) +
(games.groupingBy { it.white }.fold(0.0) { acc, next ->
acc + next.whiteScore
})
}.reduce { acc, map ->
(acc.keys + map.keys).associate<ID, ID, Double> { id ->
Pair(id, acc.getOrDefault(id, 0.0) + acc.getOrDefault(id, 0.0) + map.getOrDefault(id, 0.0))
}.toMap()
}
} }
} }
// CB TODO - a big problem with the current naive implementation is that the team score is -for now- the sum of team members individual scores // CB TODO - a big problem with the current naive implementation is that the team score is -for now- the sum of team members individual scores
class TeamOfIndividualsHistoryHelper(history: List<Game>, computeScore: () -> Map<ID, Double>): class TeamOfIndividualsHistoryHelper(history: List<List<Game>>, computeScore: () -> Map<ID, Double>):
HistoryHelper(history, computeScore) { HistoryHelper(history, computeScore) {
private fun Pairable.asTeam() = this as TeamTournament.Team private fun Pairable.asTeam() = this as TeamTournament.Team

View File

@@ -2,7 +2,12 @@ package org.jeudego.pairgoth.pairing
import org.jeudego.pairgoth.model.* import org.jeudego.pairgoth.model.*
class MacMahonSolver(round: Int, history: List<Game>, pairables: List<Pairable>, pairingParams: PairingParams, placementParams: PlacementParams): Solver(round, history, pairables, pairingParams, placementParams) { class MacMahonSolver(round: Int,
history: List<List<Game>>,
pairables: List<Pairable>,
pairingParams: PairingParams,
placementParams: PlacementParams):
Solver(round, history, pairables, pairingParams, placementParams) {
// val Pairable.mms get() = mmBase + nbW // TODO real calculation // val Pairable.mms get() = mmBase + nbW // TODO real calculation
@@ -32,5 +37,4 @@ class MacMahonSolver(round: Int, history: List<Game>, pairables: List<Pairable>,
else -> -1.0 else -> -1.0
} }
} }
} }

View File

@@ -17,23 +17,15 @@ val DEBUG_EXPORT_WEIGHT = true
private fun detRandom(max: Double, p1: Pairable, p2: Pairable): Double { private fun detRandom(max: Double, p1: Pairable, p2: Pairable): Double {
var inverse = false var inverse = false
var name1 = p1.nameSeed()
val seed1 = p1.nameSeed() var name2 = p2.nameSeed()
val seed2 = p2.nameSeed()
var name1 = seed1
var name2 = seed2
if (name1 > name2) { if (name1 > name2) {
name1 = name2.also { name2 = name1 } name1 = name2.also { name2 = name1 }
inverse = true inverse = true
} }
val s = name1 + name2 var nR = "$name1$name2".mapIndexed { i, c ->
var nR = 0.0 c.code.toDouble() * (i + 1)
for (i in s.indices) { }.sum() * 1234567 % (max + 1)
val c = s[i]
nR += (c.code * (i + 1)).toDouble()
}
nR = nR * 1234567 % (max + 1)
if (inverse) nR = max - nR if (inverse) nR = max - nR
return nR return nR
} }
@@ -43,18 +35,22 @@ private fun nonDetRandom(max: Double) =
else Math.random() * (max + 1.0) else Math.random() * (max + 1.0)
sealed class Solver( sealed class Solver(
val round: Int, val round: Int,
history: List<Game>, history: List<List<Game>>,
val pairables: List<Pairable>, val pairables: List<Pairable>,
val pairingParams: PairingParams, val pairing: PairingParams,
val placementParams: PlacementParams) { val placement: PlacementParams
) {
companion object { companion object {
val rand = Random(/* seed from properties - TODO */) val rand = Random(/* seed from properties - TODO */)
} }
val historyHelper = if (pairables.first().let { it is TeamTournament.Team && it.teamOfIndividuals }) TeamOfIndividualsHistoryHelper(history, ::computeStandingScore)
else HistoryHelper(history, ::computeStandingScore)
open fun sort(p: Pairable, q: Pairable): Int { open fun sort(p: Pairable, q: Pairable): Int {
for (criterion in placementParams.criteria) { for (criterion in placement.criteria) {
val criterionP = getCriterionValue(p, criterion) val criterionP = getCriterionValue(p, criterion)
val criterionQ = getCriterionValue(q, criterion) val criterionQ = getCriterionValue(q, criterion)
if (criterionP != criterionQ) { if (criterionP != criterionQ) {
@@ -65,9 +61,10 @@ sealed class Solver(
} }
open fun weight(p1: Pairable, p2: Pairable) = open fun weight(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"
applyBaseCriteria(p1, p2) + pairing.base.apply(p1, p2) +
applyMainCriteria(p1, p2) + pairing.main.apply(p1, p2) +
applySecondaryCriteria(p1, p2) pairing.secondary.apply(p1, p2) +
pairing.geo.apply(p1, p2)
// 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
abstract fun mainCriterion(p1: Pairable): Int abstract fun mainCriterion(p1: Pairable): Int
@@ -114,22 +111,48 @@ sealed class Solver(
return result return result
} }
open fun applyBaseCriteria(p1: Pairable, p2: Pairable): Double { // base criteria
var score = 0.0
open fun BaseCritParams.apply(p1: Pairable, p2: Pairable): Double {
var score = 0.0
// Base Criterion 1 : Avoid Duplicating Game // Base Criterion 1 : Avoid Duplicating Game
// Did p1 and p2 already play ? // Did p1 and p2 already play ?
score += avoidDuplicatingGames(p1, p2) score += avoidDuplicatingGames(p1, p2)
// Base Criterion 2 : Random // Base Criterion 2 : Random
score += applyRandom(p1, p2) score += applyRandom(p1, p2)
// Base Criterion 3 : Balance W and B // Base Criterion 3 : Balance W and B
score += applyBalanceBW(p1, p2) score += applyColorBalance(p1, p2)
return score return score
} }
// Weight score computation details
open fun BaseCritParams.avoidDuplicatingGames(p1: Pairable, p2: Pairable): Double {
return if (p1.played(p2)) 0.0 // We get no score if pairables already played together
else dupWeight
}
open fun BaseCritParams.applyRandom(p1: Pairable, p2: Pairable): Double {
return if (deterministic) detRandom(random, p1, p2)
else nonDetRandom(random)
}
open fun BaseCritParams.applyColorBalance(p1: Pairable, p2: Pairable): Double {
// This cost is never applied if potential Handicap != 0
// It is fully applied if wbBalance(sP1) and wbBalance(sP2) are strictly of different signs
// It is half applied if one of wbBalance is 0 and the other is >=2
val potentialHd: Int = pairing.handicap.handicap(p1, p2)
if (potentialHd == 0) {
val wb1: Int = p1.colorBalance
val wb2: Int = p2.colorBalance
if (wb1 * wb2 < 0) return colorBalance
else if (wb1 == 0 && abs(wb2) >= 2 || wb2 == 0 && abs(wb1) >= 2) return colorBalance / 2
}
return 0.0
}
// Main criteria // Main criteria
open fun applyMainCriteria(p1: Pairable, p2: Pairable): Double { open fun MainCritParams.apply(p1: Pairable, p2: Pairable): Double {
var score = 0.0 var score = 0.0
// Main criterion 1 avoid mixing category is moved to Swiss with category // Main criterion 1 avoid mixing category is moved to Swiss with category
@@ -147,76 +170,26 @@ sealed class Solver(
return score return score
} }
open fun applySecondaryCriteria(p1: Pairable, p2: Pairable): Double { open fun MainCritParams.minimizeScoreDifference(p1: Pairable, p2: Pairable): Double {
var score = 0.0
// See Swiss with category for minimizing handicap criterion
// TODO understand where opengotha test if need to be applied
// Geographical criterion
score += avoidSameGeo(p1, p2)
return score
}
// Weight score computation details
// Base criteria
open fun avoidDuplicatingGames(p1: Pairable, p2: Pairable): Double {
if (p1.played(p2)) {
return 0.0 // We get no score if pairables already played together
} else {
return pairingParams.base.dupWeight
}
}
open fun applyRandom(p1: Pairable, p2: Pairable): Double {
if (pairingParams.base.deterministic) {
return detRandom(pairingParams.base.random, p1, p2)
} else {
return nonDetRandom(pairingParams.base.random)
}
}
open fun applyBalanceBW(p1: Pairable, p2: Pairable): Double {
// This cost is never applied if potential Handicap != 0
// It is fully applied if wbBalance(sP1) and wbBalance(sP2) are strictly of different signs
// It is half applied if one of wbBalance is 0 and the other is >=2
val potentialHd: Int = handicap(p1, p2)
if (potentialHd == 0) {
val wb1: Int = p1.colorBalance
val wb2: Int = p2.colorBalance
if (wb1 * wb2 < 0) {
return pairingParams.base.colorBalance
} else if (wb1 == 0 && abs(wb2) >= 2) {
return pairingParams.base.colorBalance / 2
} else if (wb2 == 0 && abs(wb1) >= 2) {
return pairingParams.base.colorBalance / 2
}
}
return 0.0
}
open fun minimizeScoreDifference(p1: Pairable, p2: Pairable): Double {
var score = 0.0 var score = 0.0
val scoreRange: Int = numberGroups val scoreRange: Int = numberGroups
// TODO check category equality if category are used in SwissCat // TODO check category equality if category are used in SwissCat
val x = abs(p1.group - p2.group) as Double / scoreRange.toDouble() val x = abs(p1.group - p2.group).toDouble() / scoreRange.toDouble()
val k: Double = pairingParams.base.nx1 val k: Double = pairing.base.nx1
score = pairingParams.main.scoreWeight * (1.0 - x) * (1.0 + k * x) score = scoreWeight * (1.0 - x) * (1.0 + k * x)
return score return score
} }
fun applySeeding(p1: Pairable, p2: Pairable): Double { fun MainCritParams.applySeeding(p1: Pairable, p2: Pairable): Double {
var score = 0.0 var score = 0.0
// Apply seeding for players in the same group // Apply seeding for players in the same group
if (p1.group == p2.group) { if (p1.group == p2.group) {
val (cla1, groupSize) = p1.placeInGroup val (cla1, groupSize) = p1.placeInGroup
val cla2 = p2.placeInGroup.first val cla2 = p2.placeInGroup.first
val maxSeedingWeight = pairingParams.main.seedingWeight val maxSeedingWeight = seedingWeight
val currentSeedSystem: MainCritParams.SeedMethod = if (round <= pairingParams.main.lastRoundForSeedSystem1) val currentSeedSystem= if (round <= lastRoundForSeedSystem1) seedSystem1 else seedSystem2
pairingParams.main.seedSystem1 else pairingParams.main.seedSystem2
score += when(currentSeedSystem) { score += when(currentSeedSystem) {
// The best is to get 2 * |Cla1 - Cla2| - groupSize close to 0 // The best is to get 2 * |Cla1 - Cla2| - groupSize close to 0
@@ -245,21 +218,30 @@ sealed class Solver(
return score return score
} }
open fun doNeedToApplySecondaryCriteria(p1: Pairable, p2: Pairable) { open fun SecondaryCritParams.apply(p1: Pairable, p2: Pairable): Double {
var score = 0.0
// See Swiss with category for minimizing handicap criterion
// TODO understand where opengotha test if need to be applied
return score
}
open fun SecondaryCritParams.notNeeded(p1: Pairable, p2: Pairable) {
// secCase = 0 : No player is above thresholds // secCase = 0 : No player is above thresholds
// secCase = 1 : One player is above thresholds // secCase = 1 : One player is above thresholds
// secCase = 2 : Both players are above thresholds // secCase = 2 : Both players are above thresholds
// TODO understand where it is used // TODO understand where it is used
} }
fun avoidSameGeo(p1: Pairable, p2: Pairable): Double { fun GeographicalParams.apply(p1: Pairable, p2: Pairable): Double {
val placementScoreRange = numberGroups val placementScoreRange = numberGroups
val geoMaxCost = pairingParams.geo.avoidSameGeo val geoMaxCost = avoidSameGeo
val countryFactor = pairingParams.geo.preferMMSDiffRatherThanSameCountry val countryFactor = preferMMSDiffRatherThanSameCountry
val clubFactor: Int = pairingParams.geo.preferMMSDiffRatherThanSameClub val clubFactor: Int = preferMMSDiffRatherThanSameClub
//val groupFactor: Int = pairingParams.geo.preferMMSDiffRatherThanSameClubsGroup //val groupFactor: Int = preferMMSDiffRatherThanSameClubsGroup
// Same country // Same country
val countryRatio = if (p1.country != p2.country && countryFactor != 0) { val countryRatio = if (p1.country != p2.country && countryFactor != 0) {
@@ -298,12 +280,12 @@ sealed class Solver(
var geoRatio = mainPart + secPart / 2.0 var geoRatio = mainPart + secPart / 2.0
if (geoRatio > 0.0) { if (geoRatio > 0.0) {
geoRatio += 0.5 / placementScoreRange as Double geoRatio += 0.5 / placementScoreRange.toDouble()
} }
// The concavity function is applied to geoRatio to get geoCost // The concavity function is applied to geoRatio to get geoCost
val dbGeoCost: Double = geoMaxCost.toDouble() * (1.0 - geoRatio) * (1.0 + pairingParams.base.nx1 * geoRatio) val dbGeoCost: Double = geoMaxCost.toDouble() * (1.0 - geoRatio) * (1.0 + pairing.base.nx1 * geoRatio)
var score = pairingParams.main.scoreWeight - dbGeoCost var score = pairing.main.scoreWeight - dbGeoCost
score = min(score, geoMaxCost) score = min(score, geoMaxCost)
return score return score
@@ -311,48 +293,37 @@ sealed class Solver(
// Handicap functions // Handicap functions
// Has to be overridden if handicap is not based on rank // Has to be overridden if handicap is not based on rank
open fun handicap(p1: Pairable, p2: Pairable): Int { open fun HandicapParams.handicap(p1: Pairable, p2: Pairable): Int {
var hd = 0 var hd = 0
var pseudoRank1: Int = p1.rank var pseudoRank1: Int = p1.rank
var pseudoRank2: Int = p2.rank var pseudoRank2: Int = p2.rank
pseudoRank1 = min(pseudoRank1, pairingParams.handicap.rankThreshold) pseudoRank1 = min(pseudoRank1, rankThreshold)
pseudoRank2 = min(pseudoRank2, pairingParams.handicap.rankThreshold) pseudoRank2 = min(pseudoRank2, rankThreshold)
hd = pseudoRank1 - pseudoRank2 hd = pseudoRank1 - pseudoRank2
return clampHandicap(hd) return clamp(hd)
} }
open fun clampHandicap(inputHd: Int): Int { open fun HandicapParams.clamp(input: Int): Int {
var hd = inputHd var hd = input
if (hd > 0) { if (hd >= correction) hd -= correction
hd -= pairingParams.handicap.correction if (hd < 0) hd = max(hd + correction, 0)
hd = min(hd, 0)
}
if (hd < 0) {
hd += pairingParams.handicap.correction
hd = max(hd, 0)
}
// Clamp handicap with ceiling // Clamp handicap with ceiling
hd = min(hd, pairingParams.handicap.ceiling) hd = min(hd, ceiling)
hd = max(hd, -pairingParams.handicap.ceiling) hd = max(hd, -ceiling)
return hd return hd
} }
open fun games(black: Pairable, white: Pairable): List<Game> { open fun games(black: Pairable, white: Pairable): List<Game> {
// CB TODO team of individuals pairing // CB TODO team of individuals pairing
return listOf(Game(id = Store.nextGameId, black = black.id, white = white.id, handicap = handicap(black, white))) return listOf(Game(id = Store.nextGameId, black = black.id, white = white.id, handicap = pairing.handicap.handicap(black, white)))
} }
// Generic parameters calculation // Generic parameters calculation
//private val standingScore by lazy { computeStandingScore() } //private val standingScore by lazy { computeStandingScore() }
val historyHelper = if (pairables.first().let { it is TeamTournament.Team && it.teamOfIndividuals }) TeamOfIndividualsHistoryHelper(history, ::computeStandingScore)
else HistoryHelper(history, ::computeStandingScore)
// Decide each pairable group based on the main criterion // Decide each pairable group based on the main criterion
private val numberGroups by lazy { private val numberGroups by lazy {
val (mainScoreMin, mainScoreMax) = mainCriterionMinMax() val (mainScoreMin, mainScoreMax) = mainCriterionMinMax()
@@ -401,9 +372,5 @@ sealed class Solver(
val Pairable.sosm2: Double get() = historyHelper.sosm2[id]!! val Pairable.sosm2: Double get() = historyHelper.sosm2[id]!!
val Pairable.sosos: Double get() = historyHelper.sosos[id]!! val Pairable.sosos: Double get() = historyHelper.sosos[id]!!
val Pairable.sodos: Double get() = historyHelper.sodos[id]!! val Pairable.sodos: Double get() = historyHelper.sodos[id]!!
val Pairable.cums: Double get() = historyHelper.cumscore[id]!! val Pairable.cums: Double get() = historyHelper.cumScore[id]!!
} }

View File

@@ -3,7 +3,7 @@ package org.jeudego.pairgoth.pairing
import org.jeudego.pairgoth.model.* import org.jeudego.pairgoth.model.*
class SwissSolver(round: Int, class SwissSolver(round: Int,
history: List<Game>, history: List<List<Game>>,
pairables: List<Pairable>, pairables: List<Pairable>,
pairingParams: PairingParams, pairingParams: PairingParams,
placementParams: PlacementParams): placementParams: PlacementParams):
@@ -19,7 +19,7 @@ class SwissSolver(round: Int,
} }
override fun computeStandingScore(): Map<ID, Double> { override fun computeStandingScore(): Map<ID, Double> {
return historyHelper.numberWins return historyHelper.wins
} }
override fun getSpecificCriterionValue(p: Pairable, criterion: Criterion): Double { override fun getSpecificCriterionValue(p: Pairable, criterion: Criterion): Double {