Tackle solver reeng
This commit is contained in:
@@ -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
|
||||||
|
@@ -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
|
||||||
|
@@ -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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@@ -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]!!
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@@ -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 {
|
||||||
|
Reference in New Issue
Block a user