Early rounding of MMS; use player base score for non-played rounds SOS
This commit is contained in:
@@ -4,33 +4,48 @@ import com.republicate.kson.Json
|
|||||||
import org.jeudego.pairgoth.model.Criterion
|
import org.jeudego.pairgoth.model.Criterion
|
||||||
import org.jeudego.pairgoth.model.MacMahon
|
import org.jeudego.pairgoth.model.MacMahon
|
||||||
import org.jeudego.pairgoth.model.Pairable
|
import org.jeudego.pairgoth.model.Pairable
|
||||||
|
import org.jeudego.pairgoth.model.Pairable.Companion.MIN_RANK
|
||||||
import org.jeudego.pairgoth.model.PairingType
|
import org.jeudego.pairgoth.model.PairingType
|
||||||
import org.jeudego.pairgoth.model.Tournament
|
import org.jeudego.pairgoth.model.Tournament
|
||||||
import org.jeudego.pairgoth.model.getID
|
import org.jeudego.pairgoth.model.getID
|
||||||
import org.jeudego.pairgoth.model.historyBefore
|
import org.jeudego.pairgoth.model.historyBefore
|
||||||
import org.jeudego.pairgoth.pairing.HistoryHelper
|
import org.jeudego.pairgoth.pairing.HistoryHelper
|
||||||
import org.jeudego.pairgoth.pairing.solver.MacMahonSolver
|
import org.jeudego.pairgoth.pairing.solver.MacMahonSolver
|
||||||
|
import kotlin.math.ceil
|
||||||
|
import kotlin.math.floor
|
||||||
import kotlin.math.max
|
import kotlin.math.max
|
||||||
import kotlin.math.min
|
import kotlin.math.min
|
||||||
|
import kotlin.math.roundToInt
|
||||||
|
|
||||||
// TODO CB avoid code redundancy with solvers
|
// TODO CB avoid code redundancy with solvers
|
||||||
|
|
||||||
fun Tournament<*>.mmBase(pairable: Pairable): Double {
|
fun Tournament<*>.getSortedPairables(round: Int): List<Json.Object> {
|
||||||
|
|
||||||
|
fun Pairable.mmBase(): Double {
|
||||||
if (pairing !is MacMahon) throw Error("invalid call: tournament is not Mac Mahon")
|
if (pairing !is MacMahon) throw Error("invalid call: tournament is not Mac Mahon")
|
||||||
return min(max(pairable.rank, pairing.mmFloor), pairing.mmBar) + MacMahonSolver.mmsZero + pairable.mmsCorrection
|
return min(max(rank, pairing.mmFloor), pairing.mmBar) + MacMahonSolver.mmsZero + mmsCorrection
|
||||||
|
}
|
||||||
|
|
||||||
|
fun roundScore(score: Double): Double {
|
||||||
|
val epsilon = 0.00001
|
||||||
|
// Note: this works for now because we only have .0 and .5 fractional parts
|
||||||
|
return if (pairing.pairingParams.main.roundDownScore) floor(score + epsilon)
|
||||||
|
else ceil(score - epsilon)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun Tournament<*>.getSortedPairables(round: Int): List<Json.Object> {
|
|
||||||
val historyHelper = HistoryHelper(historyBefore(round + 1)) {
|
val historyHelper = HistoryHelper(historyBefore(round + 1)) {
|
||||||
if (pairing.type == PairingType.SWISS) wins
|
if (pairing.type == PairingType.SWISS) wins.mapValues { Pair(0.0, it.value) }
|
||||||
else pairables.mapValues {
|
else pairables.mapValues {
|
||||||
it.value.let {
|
it.value.let { pairable ->
|
||||||
pairable ->
|
val mmBase = pairable.mmBase()
|
||||||
mmBase(pairable) +
|
Pair(
|
||||||
|
mmBase,
|
||||||
|
roundScore(mmBase +
|
||||||
(nbW(pairable) ?: 0.0) + // TODO take tournament parameter into account
|
(nbW(pairable) ?: 0.0) + // TODO take tournament parameter into account
|
||||||
(1..round).map { round ->
|
(1..round).map { round ->
|
||||||
if (playersPerRound.getOrNull(round - 1)?.contains(pairable.id) == true) 0 else 1
|
if (playersPerRound.getOrNull(round - 1)?.contains(pairable.id) == true) 0 else 1
|
||||||
}.sum() * pairing.pairingParams.main.mmsValueAbsent
|
}.sum() * pairing.pairingParams.main.mmsValueAbsent)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -11,7 +11,7 @@ abstract class BasePairingHelper(
|
|||||||
val placement: PlacementParams,
|
val placement: PlacementParams,
|
||||||
) {
|
) {
|
||||||
|
|
||||||
abstract val scores: Map<ID, Double>
|
abstract val scores: Map<ID, Pair<Double, Double>>
|
||||||
val historyHelper = if (pairables.first().let { it is TeamTournament.Team && it.teamOfIndividuals }) TeamOfIndividualsHistoryHelper(history) { scores }
|
val historyHelper = if (pairables.first().let { it is TeamTournament.Team && it.teamOfIndividuals }) TeamOfIndividualsHistoryHelper(history) { scores }
|
||||||
else HistoryHelper(history) { scores }
|
else HistoryHelper(history) { scores }
|
||||||
|
|
||||||
@@ -19,7 +19,7 @@ abstract class BasePairingHelper(
|
|||||||
|
|
||||||
// The main criterion that will be used to define the groups should be defined by subclasses
|
// The main criterion that will be used to define the groups should be defined by subclasses
|
||||||
// SOS and variants will be computed based on this score
|
// SOS and variants will be computed based on this score
|
||||||
val Pairable.main: Double get() = scores[id] ?: 0.0
|
val Pairable.main: Double get() = scores[id]?.second ?: 0.0
|
||||||
abstract val mainLimits: Pair<Double, Double>
|
abstract val mainLimits: Pair<Double, Double>
|
||||||
|
|
||||||
// pairables sorted using overloadable sort function
|
// pairables sorted using overloadable sort function
|
||||||
|
@@ -3,7 +3,7 @@ package org.jeudego.pairgoth.pairing
|
|||||||
import org.jeudego.pairgoth.model.*
|
import org.jeudego.pairgoth.model.*
|
||||||
import org.jeudego.pairgoth.model.Game.Result.*
|
import org.jeudego.pairgoth.model.Game.Result.*
|
||||||
|
|
||||||
open class HistoryHelper(protected val history: List<List<Game>>, scoresGetter: HistoryHelper.()-> Map<ID, Double>) {
|
open class HistoryHelper(protected val history: List<List<Game>>, scoresGetter: HistoryHelper.()-> Map<ID, Pair<Double, Double>>) {
|
||||||
|
|
||||||
// List of all the pairables ID present in the history
|
// List of all the pairables ID present in the history
|
||||||
val allPairables = history.flatten()
|
val allPairables = history.flatten()
|
||||||
@@ -95,25 +95,31 @@ open class HistoryHelper(protected val history: List<List<Game>>, scoresGetter:
|
|||||||
}
|
}
|
||||||
|
|
||||||
// define mms to be a synonym of scores
|
// define mms to be a synonym of scores
|
||||||
val mms by lazy { scores }
|
val mms by lazy { scores.mapValues { it -> it.value.second } }
|
||||||
|
|
||||||
// SOS related functions given a score function
|
// SOS related functions given a score function
|
||||||
val sos by lazy {
|
val sos by lazy {
|
||||||
(history.flatten().map { game ->
|
(history.flatten().map { game ->
|
||||||
Pair(game.black, scores[game.white] ?: 0.0)
|
Pair(game.black, scores[game.white]?.second ?: 0.0)
|
||||||
} + history.flatten().map { game ->
|
} + history.flatten().map { game ->
|
||||||
Pair(game.white, scores[game.black] ?: 0.0)
|
Pair(game.white, scores[game.black]?.second ?: 0.0)
|
||||||
}).groupingBy { it.first }.fold(0.0) { acc, next ->
|
}).groupingBy { it.first }.fold(0.0) { acc, next ->
|
||||||
acc + next.second
|
acc + next.second
|
||||||
|
}.mapValues { (id, score) ->
|
||||||
|
// "If the player does not participate in a round, the opponent's score is replaced by the starting score of the player himself."
|
||||||
|
score + playersPerRound.map { players ->
|
||||||
|
if (players.contains(id)) 0.0
|
||||||
|
else scores[id]?.first ?: 0.0
|
||||||
|
}.sum()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// sos-1
|
// sos-1
|
||||||
val sosm1 by lazy {
|
val sosm1 by lazy {
|
||||||
(history.flatten().map { game ->
|
(history.flatten().map { game ->
|
||||||
Pair(game.black, scores[game.white] ?: 0.0)
|
Pair(game.black, scores[game.white]?.second ?: 0.0)
|
||||||
} + history.flatten().map { game ->
|
} + history.flatten().map { game ->
|
||||||
Pair(game.white, scores[game.black] ?: 0.0)
|
Pair(game.white, scores[game.black]?.second ?: 0.0)
|
||||||
}).groupBy {
|
}).groupBy {
|
||||||
it.first
|
it.first
|
||||||
}.mapValues {
|
}.mapValues {
|
||||||
@@ -125,9 +131,9 @@ open class HistoryHelper(protected val history: List<List<Game>>, scoresGetter:
|
|||||||
// sos-2
|
// sos-2
|
||||||
val sosm2 by lazy {
|
val sosm2 by lazy {
|
||||||
(history.flatten().map { game ->
|
(history.flatten().map { game ->
|
||||||
Pair(game.black, scores[game.white] ?: 0.0)
|
Pair(game.black, scores[game.white]?.second ?: 0.0)
|
||||||
} + history.flatten().map { game ->
|
} + history.flatten().map { game ->
|
||||||
Pair(game.white, scores[game.black] ?: 0.0)
|
Pair(game.white, scores[game.black]?.second ?: 0.0)
|
||||||
}).groupBy {
|
}).groupBy {
|
||||||
it.first
|
it.first
|
||||||
}.mapValues {
|
}.mapValues {
|
||||||
@@ -141,11 +147,11 @@ open class HistoryHelper(protected val history: List<List<Game>>, scoresGetter:
|
|||||||
(history.flatten().filter { game ->
|
(history.flatten().filter { game ->
|
||||||
game.white != 0 // Remove games against byePlayer
|
game.white != 0 // Remove games against byePlayer
|
||||||
}.map { game ->
|
}.map { game ->
|
||||||
Pair(game.black, if (game.result == Game.Result.BLACK) scores[game.white] ?: 0.0 else 0.0)
|
Pair(game.black, if (game.result == Game.Result.BLACK) scores[game.white]?.second ?: 0.0 else 0.0)
|
||||||
} + history.flatten().filter { game ->
|
} + history.flatten().filter { game ->
|
||||||
game.white != 0 // Remove games against byePlayer
|
game.white != 0 // Remove games against byePlayer
|
||||||
}.map { game ->
|
}.map { game ->
|
||||||
Pair(game.white, if (game.result == Game.Result.WHITE) scores[game.black] ?: 0.0 else 0.0)
|
Pair(game.white, if (game.result == Game.Result.WHITE) scores[game.black]?.second ?: 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
|
||||||
}
|
}
|
||||||
@@ -202,7 +208,7 @@ open class HistoryHelper(protected val history: List<List<Game>>, scoresGetter:
|
|||||||
|
|
||||||
// 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<List<Game>>, scoresGetter: () -> Map<ID, Double>):
|
class TeamOfIndividualsHistoryHelper(history: List<List<Game>>, scoresGetter: () -> Map<ID, Pair<Double, Double>>):
|
||||||
HistoryHelper(history, { scoresGetter() }) {
|
HistoryHelper(history, { scoresGetter() }) {
|
||||||
|
|
||||||
private fun Pairable.asTeam() = this as TeamTournament.Team
|
private fun Pairable.asTeam() = this as TeamTournament.Team
|
||||||
|
@@ -496,11 +496,11 @@ sealed class BaseSolver(
|
|||||||
return pairable.rank
|
return pairable.rank
|
||||||
}
|
}
|
||||||
|
|
||||||
fun roundScore(score: Double): Int {
|
fun roundScore(score: Double): Double {
|
||||||
val epsilon = 0.00001
|
val epsilon = 0.00001
|
||||||
// Note: this works for now because we only have .0 and .5 fractional parts
|
// Note: this works for now because we only have .0 and .5 fractional parts
|
||||||
return if (pairing.main.roundDownScore) floor(score + epsilon).roundToInt()
|
return if (pairing.main.roundDownScore) floor(score + epsilon)
|
||||||
else ceil(score - epsilon).roundToInt()
|
else ceil(score - epsilon)
|
||||||
}
|
}
|
||||||
|
|
||||||
open fun HandicapParams.clamp(input: Int): Int {
|
open fun HandicapParams.clamp(input: Int): Int {
|
||||||
|
@@ -4,6 +4,7 @@ import org.jeudego.pairgoth.model.*
|
|||||||
import java.util.*
|
import java.util.*
|
||||||
import kotlin.math.max
|
import kotlin.math.max
|
||||||
import kotlin.math.min
|
import kotlin.math.min
|
||||||
|
import kotlin.math.roundToInt
|
||||||
|
|
||||||
class MacMahonSolver(round: Int,
|
class MacMahonSolver(round: Int,
|
||||||
history: List<List<Game>>,
|
history: List<List<Game>>,
|
||||||
@@ -14,28 +15,30 @@ class MacMahonSolver(round: Int,
|
|||||||
private val mmFloor: Int, private val mmBar: Int) :
|
private val mmFloor: Int, private val mmBar: Int) :
|
||||||
BaseSolver(round, history, pairables, pairingParams, placementParams, usedTables) {
|
BaseSolver(round, history, pairables, pairingParams, placementParams, usedTables) {
|
||||||
|
|
||||||
override val scores: Map<ID, Double> by lazy {
|
override val scores: Map<ID, Pair<Double, Double>> by lazy {
|
||||||
require (mmBar > mmFloor) { "MMFloor is higher than MMBar" }
|
require (mmBar > mmFloor) { "MMFloor is higher than MMBar" }
|
||||||
val pairing = pairables.map { it.id }.toSet()
|
val pairing = pairables.map { it.id }.toSet()
|
||||||
pairablesMap.mapValues {
|
pairablesMap.mapValues {
|
||||||
it.value.let { pairable ->
|
it.value.let { pairable ->
|
||||||
pairable.mmBase +
|
Pair(
|
||||||
|
pairable.mmBase,
|
||||||
|
roundScore(pairable.mmBase +
|
||||||
pairable.nbW + // TODO take tournament parameter into account
|
pairable.nbW + // TODO take tournament parameter into account
|
||||||
pairable.missedRounds(round, pairing) * pairingParams.main.mmsValueAbsent
|
pairable.missedRounds(round, pairing) * pairingParams.main.mmsValueAbsent))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun HandicapParams.pseudoRank(pairable: Pairable): Int {
|
override fun HandicapParams.pseudoRank(pairable: Pairable): Int {
|
||||||
if (useMMS) {
|
if (useMMS) {
|
||||||
return roundScore(pairable.mms + Pairable.MIN_RANK)
|
return pairable.mms.roundToInt()
|
||||||
} else {
|
} else {
|
||||||
return pairable.rank
|
return pairable.rank
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
val Pairable.mmBase: Double get() = min(max(rank, mmFloor), mmBar) + mmsZero + mmsCorrection
|
val Pairable.mmBase: Double get() = min(max(rank, mmFloor), mmBar) + mmsZero + mmsCorrection
|
||||||
val Pairable.mms: Double get() = scores[id] ?: 0.0
|
val Pairable.mms: Double get() = scores[id]?.second ?: 0.0
|
||||||
|
|
||||||
// CB TODO - configurable criteria
|
// CB TODO - configurable criteria
|
||||||
val mainScoreMin = mmFloor + PLA_SMMS_SCORE_MIN - Pairable.MIN_RANK
|
val mainScoreMin = mmFloor + PLA_SMMS_SCORE_MIN - Pairable.MIN_RANK
|
||||||
|
@@ -15,7 +15,8 @@ class SwissSolver(round: Int,
|
|||||||
// In a Swiss tournament the main criterion is the number of wins and already computed
|
// In a Swiss tournament the main criterion is the number of wins and already computed
|
||||||
|
|
||||||
override val scores by lazy {
|
override val scores by lazy {
|
||||||
historyHelper.wins
|
historyHelper.wins.mapValues {
|
||||||
|
Pair(0.0, it.value) }
|
||||||
}
|
}
|
||||||
//
|
//
|
||||||
// get() by lazy { historyHelper.wins }
|
// get() by lazy { historyHelper.wins }
|
||||||
|
Reference in New Issue
Block a user