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.MacMahon
|
||||
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.Tournament
|
||||
import org.jeudego.pairgoth.model.getID
|
||||
import org.jeudego.pairgoth.model.historyBefore
|
||||
import org.jeudego.pairgoth.pairing.HistoryHelper
|
||||
import org.jeudego.pairgoth.pairing.solver.MacMahonSolver
|
||||
import kotlin.math.ceil
|
||||
import kotlin.math.floor
|
||||
import kotlin.math.max
|
||||
import kotlin.math.min
|
||||
import kotlin.math.roundToInt
|
||||
|
||||
// TODO CB avoid code redundancy with solvers
|
||||
|
||||
fun Tournament<*>.mmBase(pairable: Pairable): Double {
|
||||
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
|
||||
}
|
||||
|
||||
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")
|
||||
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)
|
||||
}
|
||||
|
||||
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 {
|
||||
it.value.let {
|
||||
pairable ->
|
||||
mmBase(pairable) +
|
||||
it.value.let { pairable ->
|
||||
val mmBase = pairable.mmBase()
|
||||
Pair(
|
||||
mmBase,
|
||||
roundScore(mmBase +
|
||||
(nbW(pairable) ?: 0.0) + // TODO take tournament parameter into account
|
||||
(1..round).map { round ->
|
||||
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,
|
||||
) {
|
||||
|
||||
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 }
|
||||
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
|
||||
// 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>
|
||||
|
||||
// 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.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
|
||||
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
|
||||
val mms by lazy { scores }
|
||||
val mms by lazy { scores.mapValues { it -> it.value.second } }
|
||||
|
||||
// SOS related functions given a score function
|
||||
val sos by lazy {
|
||||
(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 ->
|
||||
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 ->
|
||||
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
|
||||
val sosm1 by lazy {
|
||||
(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 ->
|
||||
Pair(game.white, scores[game.black] ?: 0.0)
|
||||
Pair(game.white, scores[game.black]?.second ?: 0.0)
|
||||
}).groupBy {
|
||||
it.first
|
||||
}.mapValues {
|
||||
@@ -125,9 +131,9 @@ open class HistoryHelper(protected val history: List<List<Game>>, scoresGetter:
|
||||
// sos-2
|
||||
val sosm2 by lazy {
|
||||
(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 ->
|
||||
Pair(game.white, scores[game.black] ?: 0.0)
|
||||
Pair(game.white, scores[game.black]?.second ?: 0.0)
|
||||
}).groupBy {
|
||||
it.first
|
||||
}.mapValues {
|
||||
@@ -141,11 +147,11 @@ open class HistoryHelper(protected val history: List<List<Game>>, scoresGetter:
|
||||
(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] ?: 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 ->
|
||||
game.white != 0 // Remove games against byePlayer
|
||||
}.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 ->
|
||||
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
|
||||
|
||||
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() }) {
|
||||
|
||||
private fun Pairable.asTeam() = this as TeamTournament.Team
|
||||
|
@@ -496,11 +496,11 @@ sealed class BaseSolver(
|
||||
return pairable.rank
|
||||
}
|
||||
|
||||
fun roundScore(score: Double): Int {
|
||||
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.main.roundDownScore) floor(score + epsilon).roundToInt()
|
||||
else ceil(score - epsilon).roundToInt()
|
||||
return if (pairing.main.roundDownScore) floor(score + epsilon)
|
||||
else ceil(score - epsilon)
|
||||
}
|
||||
|
||||
open fun HandicapParams.clamp(input: Int): Int {
|
||||
|
@@ -4,6 +4,7 @@ import org.jeudego.pairgoth.model.*
|
||||
import java.util.*
|
||||
import kotlin.math.max
|
||||
import kotlin.math.min
|
||||
import kotlin.math.roundToInt
|
||||
|
||||
class MacMahonSolver(round: Int,
|
||||
history: List<List<Game>>,
|
||||
@@ -11,31 +12,33 @@ class MacMahonSolver(round: Int,
|
||||
pairingParams: PairingParams,
|
||||
placementParams: PlacementParams,
|
||||
usedTables: BitSet,
|
||||
private val mmFloor: Int, private val mmBar: Int):
|
||||
private val mmFloor: Int, private val mmBar: Int) :
|
||||
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" }
|
||||
val pairing = pairables.map { it.id }.toSet()
|
||||
pairablesMap.mapValues {
|
||||
it.value.let { pairable ->
|
||||
pairable.mmBase +
|
||||
Pair(
|
||||
pairable.mmBase,
|
||||
roundScore(pairable.mmBase +
|
||||
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 {
|
||||
if (useMMS) {
|
||||
return roundScore(pairable.mms + Pairable.MIN_RANK)
|
||||
return pairable.mms.roundToInt()
|
||||
} else {
|
||||
return pairable.rank
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
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
|
||||
|
||||
override val scores by lazy {
|
||||
historyHelper.wins
|
||||
historyHelper.wins.mapValues {
|
||||
Pair(0.0, it.value) }
|
||||
}
|
||||
//
|
||||
// get() by lazy { historyHelper.wins }
|
||||
|
Reference in New Issue
Block a user