Refactoring criterion computation
This commit is contained in:
@@ -35,7 +35,7 @@ private const val MA_MAX_MAXIMIZE_SEEDING: Double = MA_MAX_MINIMIZE_SCORE_DIFFER
|
|||||||
|
|
||||||
enum class SeedMethod { SPLIT_AND_FOLD, SPLIT_AND_RANDOM, SPLIT_AND_SLIP }
|
enum class SeedMethod { SPLIT_AND_FOLD, SPLIT_AND_RANDOM, SPLIT_AND_SLIP }
|
||||||
|
|
||||||
sealed class Pairing(val type: PairingType, val pairingParams: PairingParams = PairingParams()) {
|
sealed class Pairing(val type: PairingType, val pairingParams: PairingParams = PairingParams(), val placementParams: PlacementParams) {
|
||||||
companion object {}
|
companion object {}
|
||||||
enum class PairingType { SWISS, MAC_MAHON, ROUND_ROBIN }
|
enum class PairingType { SWISS, MAC_MAHON, ROUND_ROBIN }
|
||||||
data class PairingParams(
|
data class PairingParams(
|
||||||
@@ -61,9 +61,8 @@ sealed class Pairing(val type: PairingType, val pairingParams: PairingParams = P
|
|||||||
val maLastRoundForSeedSystem1: Int = 1,
|
val maLastRoundForSeedSystem1: Int = 1,
|
||||||
val maSeedSystem1: SeedMethod = SeedMethod.SPLIT_AND_RANDOM,
|
val maSeedSystem1: SeedMethod = SeedMethod.SPLIT_AND_RANDOM,
|
||||||
val maSeedSystem2: SeedMethod = SeedMethod.SPLIT_AND_FOLD,
|
val maSeedSystem2: SeedMethod = SeedMethod.SPLIT_AND_FOLD,
|
||||||
// TODO get these parameters from Placement parameters
|
val maAdditionalPlacementCritSystem1: PlacementCriterion = PlacementCriterion.RATING,
|
||||||
//val maAdditionalPlacementCritSystem1: Int = PlacementParameterSet.PLA_CRIT_RATING,
|
val maAdditionalPlacementCritSystem2: PlacementCriterion = PlacementCriterion.NULL,
|
||||||
//val maAdditionalPlacementCritSystem2: Int = PlacementParameterSet.PLA_CRIT_NUL,
|
|
||||||
|
|
||||||
// Secondary criteria
|
// Secondary criteria
|
||||||
val seBarThresholdActive: Boolean = true, // Do not apply secondary criteria for players above bar
|
val seBarThresholdActive: Boolean = true, // Do not apply secondary criteria for players above bar
|
||||||
@@ -127,9 +126,9 @@ class Swiss(): Pairing(SWISS, PairingParams(
|
|||||||
|
|
||||||
geo = GeographicalParams.disabled(),
|
geo = GeographicalParams.disabled(),
|
||||||
hd = HandicapParams.disabled(),
|
hd = HandicapParams.disabled(),
|
||||||
)) {
|
), PlacementParams(PlacementCriterion.NBW, PlacementCriterion.SOSW, PlacementCriterion.SOSOSW)) {
|
||||||
override fun pair(tournament: Tournament<*>, round: Int, pairables: List<Pairable>): List<Game> {
|
override fun pair(tournament: Tournament<*>, round: Int, pairables: List<Pairable>): List<Game> {
|
||||||
return SwissSolver(tournament.historyBefore(round), pairables, pairingParams).pair()
|
return SwissSolver(round, tournament.historyBefore(round), pairables, pairingParams, placementParams).pair()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -137,15 +136,16 @@ class MacMahon(
|
|||||||
var bar: Int = 0,
|
var bar: Int = 0,
|
||||||
var minLevel: Int = -30,
|
var minLevel: Int = -30,
|
||||||
var reducer: Int = 1
|
var reducer: Int = 1
|
||||||
): Pairing(MAC_MAHON, PairingParams(seDefSecCrit = MA_MAX_MINIMIZE_SCORE_DIFFERENCE)) {
|
): Pairing(MAC_MAHON, PairingParams(seDefSecCrit = MA_MAX_MINIMIZE_SCORE_DIFFERENCE),
|
||||||
|
PlacementParams(PlacementCriterion.MMS, PlacementCriterion.SOSM, PlacementCriterion.SOSOSM)) {
|
||||||
val groups = mutableListOf<Int>()
|
val groups = mutableListOf<Int>()
|
||||||
|
|
||||||
override fun pair(tournament: Tournament<*>, round: Int, pairables: List<Pairable>): List<Game> {
|
override fun pair(tournament: Tournament<*>, round: Int, pairables: List<Pairable>): List<Game> {
|
||||||
return MacMahonSolver(tournament.historyBefore(round), pairables, pairingParams, mmBase = minLevel, mmBar = bar, reducer = reducer).pair()
|
return MacMahonSolver(round, tournament.historyBefore(round), pairables, pairingParams, placementParams, mmBase = minLevel, mmBar = bar, reducer = reducer).pair()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class RoundRobin: Pairing(ROUND_ROBIN) {
|
class RoundRobin: Pairing(ROUND_ROBIN, PairingParams(), PlacementParams(PlacementCriterion.NBW, PlacementCriterion.RATING)) {
|
||||||
override fun pair(tournament: Tournament<*>, round: Int, pairables: List<Pairable>): List<Game> {
|
override fun pair(tournament: Tournament<*>, round: Int, pairables: List<Pairable>): List<Game> {
|
||||||
TODO()
|
TODO()
|
||||||
}
|
}
|
||||||
|
@@ -0,0 +1,58 @@
|
|||||||
|
package org.jeudego.pairgoth.model
|
||||||
|
|
||||||
|
enum class PlacementCriterion {
|
||||||
|
NULL, // No ranking/tie-break
|
||||||
|
|
||||||
|
CATEGORY,
|
||||||
|
RANK,
|
||||||
|
RATING,
|
||||||
|
NBW, // Number win
|
||||||
|
MMS, // Macmahon score
|
||||||
|
STS, // Strasbourg score
|
||||||
|
CPS, // Cup score
|
||||||
|
|
||||||
|
SOSW, // Sum of opponents NBW
|
||||||
|
SOSWM1, //-1
|
||||||
|
SOSWM2, //-2
|
||||||
|
SODOSW, // Sum of defeated opponents NBW
|
||||||
|
SOSOSW, // Sum of opponenent SOS
|
||||||
|
CUSSW, // Cumulative sum of scores (NBW)
|
||||||
|
|
||||||
|
SOSM, // Sum of opponents McMahon score
|
||||||
|
SOSMM1, // Same as previous group but with McMahon score
|
||||||
|
SOSMM2,
|
||||||
|
SODOSM,
|
||||||
|
SOSOSM,
|
||||||
|
CUSSM,
|
||||||
|
|
||||||
|
SOSTS, // Sum of opponnents Strasbourg score
|
||||||
|
|
||||||
|
EXT, // Exploits tentes
|
||||||
|
EXR, // Exploits reussis
|
||||||
|
|
||||||
|
// For the two criteria below see the user documentation
|
||||||
|
SDC, // Simplified direct confrontation
|
||||||
|
DC, // Direct confrontation
|
||||||
|
}
|
||||||
|
|
||||||
|
class PlacementParams(vararg criteria: PlacementCriterion) {
|
||||||
|
companion object {
|
||||||
|
const val MAX_NUMBER_OF_CRITERIA: Int = 6
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun addNullCriteria(criteria: Array<out PlacementCriterion>): ArrayList<PlacementCriterion> {
|
||||||
|
var criteria = arrayListOf(*criteria)
|
||||||
|
while (criteria.size < MAX_NUMBER_OF_CRITERIA) {
|
||||||
|
criteria.add(PlacementCriterion.NULL)
|
||||||
|
}
|
||||||
|
return criteria
|
||||||
|
}
|
||||||
|
|
||||||
|
val criteria = addNullCriteria(criteria)
|
||||||
|
|
||||||
|
open fun checkWarnings(): String {
|
||||||
|
// Returns a warning message if criteria are incoherent
|
||||||
|
// TODO
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
}
|
@@ -1,17 +1,30 @@
|
|||||||
package org.jeudego.pairgoth.pairing
|
package org.jeudego.pairgoth.pairing
|
||||||
|
|
||||||
import org.jeudego.pairgoth.model.Game
|
import org.jeudego.pairgoth.model.*
|
||||||
import org.jeudego.pairgoth.model.Pairable
|
|
||||||
import org.jeudego.pairgoth.model.TeamTournament
|
|
||||||
|
|
||||||
open class HistoryHelper(protected val history: List<Game>) {
|
open class HistoryHelper(protected val history: List<Game>, score: Map<ID, Double>) {
|
||||||
|
|
||||||
|
fun getCriterionValue(p: Pairable, crit: PlacementCriterion): Int {
|
||||||
|
// Returns generic criterion
|
||||||
|
// Specific criterion are computed by solvers directly
|
||||||
|
return when (crit) {
|
||||||
|
PlacementCriterion.NULL -> 0
|
||||||
|
PlacementCriterion.CATEGORY -> TODO()
|
||||||
|
PlacementCriterion.RANK -> p.rank
|
||||||
|
PlacementCriterion.RATING -> p.rating
|
||||||
|
|
||||||
|
PlacementCriterion.EXT -> TODO()
|
||||||
|
PlacementCriterion.EXR -> TODO()
|
||||||
|
PlacementCriterion.SDC -> TODO()
|
||||||
|
PlacementCriterion.DC -> TODO()
|
||||||
|
else -> -1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// 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 score(p: Pairable) = score[p.id]
|
open fun nbW(p: Pairable) = numberWins[p.id]
|
||||||
open fun sos(p: Pairable) = sos[p.id]
|
|
||||||
open fun sosos(p: Pairable) = sosos[p.id]
|
|
||||||
open fun sodos(p: Pairable) = sodos[p.id]
|
|
||||||
|
|
||||||
protected val paired: Set<Pair<Int, Int>> by lazy {
|
protected val paired: Set<Pair<Int, Int>> by lazy {
|
||||||
(history.map { game ->
|
(history.map { game ->
|
||||||
@@ -34,7 +47,7 @@ open class HistoryHelper(protected val history: List<Game>) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private val score: Map<Int, Double> by lazy {
|
val numberWins: Map<Int, Double> by lazy {
|
||||||
mutableMapOf<Int, Double>().apply {
|
mutableMapOf<Int, Double>().apply {
|
||||||
history.forEach { game ->
|
history.forEach { game ->
|
||||||
when (game.result) {
|
when (game.result) {
|
||||||
@@ -50,7 +63,8 @@ open class HistoryHelper(protected val history: List<Game>) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private val sos by lazy {
|
// SOS related functions given a score function
|
||||||
|
val sos by lazy {
|
||||||
(history.map { game ->
|
(history.map { game ->
|
||||||
Pair(game.black, score[game.white] ?: 0.0)
|
Pair(game.black, score[game.white] ?: 0.0)
|
||||||
} + history.map { game ->
|
} + history.map { game ->
|
||||||
@@ -60,17 +74,18 @@ open class HistoryHelper(protected val history: List<Game>) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private val sosos by lazy {
|
// sos-1
|
||||||
(history.map { game ->
|
val sosm1: Map<ID, Double> by lazy {
|
||||||
Pair(game.black, sos[game.white] ?: 0.0)
|
TODO()
|
||||||
} + history.map { game ->
|
|
||||||
Pair(game.white, sos[game.black] ?: 0.0)
|
|
||||||
}).groupingBy { it.first }.fold(0.0) { acc, next ->
|
|
||||||
acc + next.second
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private val sodos by lazy {
|
// sos-2
|
||||||
|
val sosm2: Map<ID, Double> by lazy {
|
||||||
|
TODO()
|
||||||
|
}
|
||||||
|
|
||||||
|
// sodos
|
||||||
|
val sodos by lazy {
|
||||||
(history.map { game ->
|
(history.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.map { game ->
|
||||||
@@ -80,12 +95,27 @@ open class HistoryHelper(protected val history: List<Game>) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// sosos
|
||||||
|
val sosos by lazy {
|
||||||
|
(history.map { game ->
|
||||||
|
Pair(game.black, sos[game.white] ?: 0.0)
|
||||||
|
} + history.map { game ->
|
||||||
|
Pair(game.white, sos[game.black] ?: 0.0)
|
||||||
|
}).groupingBy { it.first }.fold(0.0) { acc, next ->
|
||||||
|
acc + next.second
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// cumulative score
|
||||||
|
val cumscore: Map<ID, Double> by lazy {
|
||||||
|
TODO()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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>): HistoryHelper(history) {
|
class TeamOfIndividualsHistoryHelper(history: List<Game>, score: Map<ID, Double>):
|
||||||
|
HistoryHelper(history, score) {
|
||||||
|
|
||||||
private fun Pairable.asTeam() = this as TeamTournament.Team
|
private fun Pairable.asTeam() = this as TeamTournament.Team
|
||||||
|
|
||||||
@@ -93,8 +123,8 @@ class TeamOfIndividualsHistoryHelper(history: List<Game>): HistoryHelper(history
|
|||||||
(p2.asTeam()).playerIds.map {Pair(it, id) }
|
(p2.asTeam()).playerIds.map {Pair(it, id) }
|
||||||
}.toSet()).isNotEmpty()
|
}.toSet()).isNotEmpty()
|
||||||
|
|
||||||
override fun score(p: Pairable) = p.asTeam().teamPlayers.map { super.score(it) ?: throw Error("unknown player id: #${it.id}") }.sum()
|
override fun nbW(p: Pairable) = p.asTeam().teamPlayers.map { super.nbW(it) ?: throw Error("unknown player id: #${it.id}") }.sum()
|
||||||
override fun sos(p:Pairable) = p.asTeam().teamPlayers.map { super.sos(it) ?: throw Error("unknown player id: #${it.id}") }.sum()
|
//override fun sos(p:Pairable) = p.asTeam().teamPlayers.map { super.sos(it) ?: throw Error("unknown player id: #${it.id}") }.sum()
|
||||||
override fun sosos(p:Pairable) = p.asTeam().teamPlayers.map { super.sosos(it) ?: throw Error("unknown player id: #${it.id}") }.sum()
|
//override fun sosos(p:Pairable) = p.asTeam().teamPlayers.map { super.sosos(it) ?: throw Error("unknown player id: #${it.id}") }.sum()
|
||||||
override fun sodos(p:Pairable) = p.asTeam().teamPlayers.map { super.sodos(it) ?: throw Error("unknown player id: #${it.id}") }.sum()
|
//override fun sodos(p:Pairable) = p.asTeam().teamPlayers.map { super.sodos(it) ?: throw Error("unknown player id: #${it.id}") }.sum()
|
||||||
}
|
}
|
||||||
|
@@ -1,16 +1,14 @@
|
|||||||
package org.jeudego.pairgoth.pairing
|
package org.jeudego.pairgoth.pairing
|
||||||
|
|
||||||
import org.jeudego.pairgoth.model.Game
|
import org.jeudego.pairgoth.model.*
|
||||||
import org.jeudego.pairgoth.model.Pairable
|
|
||||||
import org.jeudego.pairgoth.model.Pairing
|
|
||||||
import kotlin.math.abs
|
import kotlin.math.abs
|
||||||
import kotlin.math.max
|
import kotlin.math.max
|
||||||
import kotlin.math.roundToInt
|
import kotlin.math.roundToInt
|
||||||
import kotlin.math.sign
|
import kotlin.math.sign
|
||||||
|
|
||||||
class MacMahonSolver(history: List<Game>, pairables: List<Pairable>, pairingParams: Pairing.PairingParams, val mmBase: Int, val mmBar: Int, val reducer: Int): Solver(history, pairables, pairingParams) {
|
class MacMahonSolver(round: Int, history: List<Game>, pairables: List<Pairable>, pairingParams: Pairing.PairingParams, placementParams: PlacementParams, val mmBase: Int, val mmBar: Int, val reducer: Int): Solver(round, history, pairables, pairingParams, placementParams) {
|
||||||
|
|
||||||
val Pairable.mms get() = mmBase + score
|
val Pairable.mms get() = mmBase + nbW // TODO real calculation
|
||||||
|
|
||||||
// CB TODO - configurable criteria
|
// CB TODO - configurable criteria
|
||||||
override fun mainCriterion(p1: Pairable): Int {
|
override fun mainCriterion(p1: Pairable): Int {
|
||||||
@@ -20,10 +18,23 @@ class MacMahonSolver(history: List<Game>, pairables: List<Pairable>, pairingPara
|
|||||||
override fun mainCriterionMinMax(): Pair<Int, Int> {
|
override fun mainCriterionMinMax(): Pair<Int, Int> {
|
||||||
TODO("Not yet implemented")
|
TODO("Not yet implemented")
|
||||||
}
|
}
|
||||||
override fun sort(p: Pairable, q: Pairable): Int =
|
|
||||||
if (p.mms != q.mms) ((q.mms - p.mms) * 1000).toInt()
|
override fun computeStandingScore(): Map<ID, Double> {
|
||||||
else if (p.sos != q.sos) ((q.sos - p.sos) * 1000).toInt()
|
TODO("Not yet implemented")
|
||||||
else if (p.sosos != q.sosos) ((q.sosos - p.sosos) * 1000).toInt()
|
}
|
||||||
else 0
|
|
||||||
|
override fun getSpecificCriterionValue(p: Pairable, criterion: PlacementCriterion): Int {
|
||||||
|
// TODO solve this double/int conflict
|
||||||
|
return when (criterion) {
|
||||||
|
PlacementCriterion.MMS -> TODO()
|
||||||
|
PlacementCriterion.SOSM -> p.sos.toInt()
|
||||||
|
PlacementCriterion.SOSMM1 -> p.sosm1.toInt()
|
||||||
|
PlacementCriterion.SOSMM2 -> p.sosm2.toInt()
|
||||||
|
PlacementCriterion.SODOSM -> p.sodos.toInt()
|
||||||
|
PlacementCriterion.SOSOSM -> p.sosos.toInt()
|
||||||
|
PlacementCriterion.CUSSM -> p.cums.toInt()
|
||||||
|
else -> -1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@@ -1,9 +1,6 @@
|
|||||||
package org.jeudego.pairgoth.pairing
|
package org.jeudego.pairgoth.pairing
|
||||||
|
|
||||||
import org.jeudego.pairgoth.model.Game
|
import org.jeudego.pairgoth.model.*
|
||||||
import org.jeudego.pairgoth.model.Pairable
|
|
||||||
import org.jeudego.pairgoth.model.Pairing
|
|
||||||
import org.jeudego.pairgoth.model.TeamTournament
|
|
||||||
import org.jeudego.pairgoth.store.Store
|
import org.jeudego.pairgoth.store.Store
|
||||||
import org.jgrapht.alg.matching.blossom.v5.KolmogorovWeightedPerfectMatching
|
import org.jgrapht.alg.matching.blossom.v5.KolmogorovWeightedPerfectMatching
|
||||||
import org.jgrapht.alg.matching.blossom.v5.ObjectiveSense
|
import org.jgrapht.alg.matching.blossom.v5.ObjectiveSense
|
||||||
@@ -47,30 +44,88 @@ private fun nonDetRandom(max: Long): Long {
|
|||||||
return r.toLong()
|
return r.toLong()
|
||||||
}
|
}
|
||||||
|
|
||||||
sealed class Solver(history: List<Game>, val pairables: List<Pairable>, val pairingParams: Pairing.PairingParams) {
|
sealed class Solver(
|
||||||
|
val round: Int,
|
||||||
|
history: List<Game>,
|
||||||
|
val pairables: List<Pairable>,
|
||||||
|
val pairingParams: Pairing.PairingParams,
|
||||||
|
val placementParams: PlacementParams) {
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
val rand = Random(/* seed from properties - TODO */)
|
val rand = Random(/* seed from properties - TODO */)
|
||||||
}
|
}
|
||||||
|
|
||||||
open fun sort(p: Pairable, q: Pairable): Int = 0 // no sort by default
|
open fun sort(p: Pairable, q: Pairable): Int {
|
||||||
|
for (criterion in placementParams.criteria) {
|
||||||
|
val criterionP = getCriterionValue(p, criterion)
|
||||||
|
val criterionQ = getCriterionValue(q, criterion)
|
||||||
|
if (criterionP != criterionQ) {
|
||||||
|
return criterionP - criterionQ
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
}
|
||||||
open fun weight(p1: Pairable, p2: Pairable): Double {
|
open fun weight(p1: Pairable, p2: Pairable): Double {
|
||||||
var score = 1L // 1 is minimum value because 0 means "no matching allowed"
|
var score = 1L // 1 is minimum value because 0 means "no matching allowed"
|
||||||
|
|
||||||
score += applyBaseCriteria(p1, p2)
|
score += applyBaseCriteria(p1, p2)
|
||||||
|
|
||||||
|
score += applyMainCriteria(p1, p2)
|
||||||
|
|
||||||
return score as Double
|
return score as Double
|
||||||
}
|
}
|
||||||
// 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
|
||||||
abstract fun mainCriterionMinMax(): Pair<Int, Int>
|
abstract fun mainCriterionMinMax(): Pair<Int, Int>
|
||||||
|
// SOS and variants will be computed based on this score
|
||||||
|
abstract fun computeStandingScore(): Map<ID, Double>
|
||||||
|
// This function needs to be overridden for criterion specific to the current pairing mode
|
||||||
|
open fun getSpecificCriterionValue(p1: Pairable, criterion: PlacementCriterion): Int {
|
||||||
|
return -1
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getCriterionValue(p1: Pairable, criterion: PlacementCriterion): Int {
|
||||||
|
val genericCritVal = historyHelper.getCriterionValue(p1, criterion)
|
||||||
|
// If the value from the history helper is > 0 it means that it is a generic criterion
|
||||||
|
// Just returns the value
|
||||||
|
if (genericCritVal != -1) {
|
||||||
|
return genericCritVal
|
||||||
|
}
|
||||||
|
// Otherwise we have to delegate it to the solver
|
||||||
|
val critVal = getSpecificCriterionValue(p1, criterion)
|
||||||
|
if (critVal == -1) throw Error("Couldn't compute criterion value")
|
||||||
|
return critVal
|
||||||
|
}
|
||||||
|
|
||||||
|
fun pair(): List<Game> {
|
||||||
|
// check that at this stage, we have an even number of pairables
|
||||||
|
if (pairables.size % 2 != 0) throw Error("expecting an even number of pairables")
|
||||||
|
val builder = GraphBuilder(SimpleDirectedWeightedGraph<Pairable, DefaultWeightedEdge>(DefaultWeightedEdge::class.java))
|
||||||
|
for (i in sortedPairables.indices) {
|
||||||
|
for (j in i + 1 until pairables.size) {
|
||||||
|
val p = pairables[i]
|
||||||
|
val q = pairables[j]
|
||||||
|
weight(p, q).let { if (it != Double.NaN) builder.addEdge(p, q, it) }
|
||||||
|
weight(q, p).let { if (it != Double.NaN) builder.addEdge(q, p, it) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
val graph = builder.build()
|
||||||
|
val matching = KolmogorovWeightedPerfectMatching(graph, ObjectiveSense.MINIMIZE)
|
||||||
|
val solution = matching.matching
|
||||||
|
|
||||||
|
val result = solution.flatMap {
|
||||||
|
games(black = graph.getEdgeSource(it) , white = graph.getEdgeTarget(it))
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
// Weight score computation details
|
||||||
// Base criteria
|
// Base criteria
|
||||||
open fun avoidDuplicatingGames(p1: Pairable, p2: Pairable): Long {
|
open fun avoidDuplicatingGames(p1: Pairable, p2: Pairable): Long {
|
||||||
if (historyHelper.playedTogether(p1, p2)) {
|
if (p1.played(p2)) {
|
||||||
return pairingParams.baseAvoidDuplGame
|
return 0 // We get no score if pairables already played together
|
||||||
} else {
|
} else {
|
||||||
return 0
|
return pairingParams.baseAvoidDuplGame
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -129,17 +184,18 @@ sealed class Solver(history: List<Game>, val pairables: List<Pairable>, val pair
|
|||||||
}
|
}
|
||||||
|
|
||||||
open fun minimizeScoreDifference(p1: Pairable, p2: Pairable): Long {
|
open fun minimizeScoreDifference(p1: Pairable, p2: Pairable): Long {
|
||||||
var scoCost: Long = 0
|
var score: Long = 0
|
||||||
val scoRange: 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(groups[p1.id]!! - groups[p2.id]!!) as Double / scoRange.toDouble()
|
val x = abs(p1.group - p2.group) as Double / scoreRange.toDouble()
|
||||||
val k: Double = pairingParams.standardNX1Factor
|
val k: Double = pairingParams.standardNX1Factor
|
||||||
scoCost = (pairingParams.mainMinimizeScoreDifference * (1.0 - x) * (1.0 + k * x)) as Long
|
score = (pairingParams.mainMinimizeScoreDifference * (1.0 - x) * (1.0 + k * x)) as Long
|
||||||
|
|
||||||
return scoCost
|
return score
|
||||||
}
|
}
|
||||||
|
|
||||||
// Handicap functions
|
// Handicap functions
|
||||||
|
// Has to be overridden if handicap is not based on rank
|
||||||
open fun handicap(p1: Pairable, p2: Pairable): Int {
|
open fun handicap(p1: Pairable, p2: Pairable): Int {
|
||||||
var hd = 0
|
var hd = 0
|
||||||
var pseudoRank1: Int = p1.rank
|
var pseudoRank1: Int = p1.rank
|
||||||
@@ -152,8 +208,8 @@ sealed class Solver(history: List<Game>, val pairables: List<Pairable>, val pair
|
|||||||
return clampHandicap(hd)
|
return clampHandicap(hd)
|
||||||
}
|
}
|
||||||
|
|
||||||
open fun clampHandicap(input_hd: Int): Int {
|
open fun clampHandicap(inputHd: Int): Int {
|
||||||
var hd = input_hd
|
var hd = inputHd
|
||||||
if (hd > 0) {
|
if (hd > 0) {
|
||||||
hd -= pairingParams.hd.correction
|
hd -= pairingParams.hd.correction
|
||||||
hd = min(hd, 0)
|
hd = min(hd, 0)
|
||||||
@@ -174,47 +230,25 @@ sealed class Solver(history: List<Game>, val pairables: List<Pairable>, val pair
|
|||||||
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 = handicap(black, white)))
|
||||||
}
|
}
|
||||||
|
|
||||||
fun pair(): List<Game> {
|
// Generic parameters calculation
|
||||||
// check that at this stage, we have an even number of pairables
|
private val standingScore = computeStandingScore()
|
||||||
if (pairables.size % 2 != 0) throw Error("expecting an even number of pairables")
|
val historyHelper =
|
||||||
val builder = GraphBuilder(SimpleDirectedWeightedGraph<Pairable, DefaultWeightedEdge>(DefaultWeightedEdge::class.java))
|
if (pairables.first().let { it is TeamTournament.Team && it.teamOfIndividuals }) TeamOfIndividualsHistoryHelper(history, standingScore)
|
||||||
for (i in sortedPairables.indices) {
|
else HistoryHelper(history, standingScore)
|
||||||
for (j in i + 1 until n) {
|
|
||||||
val p = pairables[i]
|
|
||||||
val q = pairables[j]
|
|
||||||
weight(p, q).let { if (it != Double.NaN) builder.addEdge(p, q, it) }
|
|
||||||
weight(q, p).let { if (it != Double.NaN) builder.addEdge(q, p, it) }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
val graph = builder.build()
|
|
||||||
val matching = KolmogorovWeightedPerfectMatching(graph, ObjectiveSense.MINIMIZE)
|
|
||||||
val solution = matching.matching
|
|
||||||
|
|
||||||
val result = solution.flatMap {
|
|
||||||
games(black = graph.getEdgeSource(it) , white = graph.getEdgeTarget(it))
|
|
||||||
}
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun computeGroups(): Pair<Map<Int, Int>, Int> {
|
// Decide each pairable group based on the main criterion
|
||||||
|
private fun computeGroups(): Pair<Map<ID, Int>, Int> {
|
||||||
val (mainScoreMin, mainScoreMax) = mainCriterionMinMax()
|
val (mainScoreMin, mainScoreMax) = mainCriterionMinMax()
|
||||||
|
|
||||||
// TODO categories
|
// TODO categories
|
||||||
val groups: Map<Int, Int> = pairables.associate { pairable -> Pair(pairable.id, mainCriterion(pairable)) }
|
val groups: Map<ID, Int> = pairables.associate { pairable -> Pair(pairable.id, mainCriterion(pairable)) }
|
||||||
|
|
||||||
return Pair(groups, mainScoreMax - mainScoreMin)
|
return Pair(groups, mainScoreMax - mainScoreMin)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Calculation parameters
|
|
||||||
|
|
||||||
val n = pairables.size
|
|
||||||
|
|
||||||
private val historyHelper =
|
|
||||||
if (pairables.first().let { it is TeamTournament.Team && it.teamOfIndividuals }) TeamOfIndividualsHistoryHelper(history)
|
|
||||||
else HistoryHelper(history)
|
|
||||||
|
|
||||||
private val groupsResult = computeGroups()
|
private val groupsResult = computeGroups()
|
||||||
private val groups = groupsResult.first
|
private val _groups = groupsResult.first
|
||||||
private val numberGroups = groupsResult.second
|
private val numberGroups = groupsResult.second
|
||||||
|
|
||||||
// pairables sorted using overloadable sort function
|
// pairables sorted using overloadable sort function
|
||||||
@@ -234,7 +268,7 @@ sealed class Solver(history: List<Game>, val pairables: List<Pairable>, val pair
|
|||||||
val Pairable.placeInGroup: Pair<Int, Int> get() = _placeInGroup[id]!!
|
val Pairable.placeInGroup: Pair<Int, Int> get() = _placeInGroup[id]!!
|
||||||
private val _placeInGroup by lazy {
|
private val _placeInGroup by lazy {
|
||||||
sortedPairables.groupBy {
|
sortedPairables.groupBy {
|
||||||
it.score
|
it.group
|
||||||
}.values.flatMap { group ->
|
}.values.flatMap { group ->
|
||||||
group.mapIndexed { index, pairable ->
|
group.mapIndexed { index, pairable ->
|
||||||
Pair(pairable.id, Pair(index, group.size))
|
Pair(pairable.id, Pair(index, group.size))
|
||||||
@@ -243,20 +277,25 @@ sealed class Solver(history: List<Game>, val pairables: List<Pairable>, val pair
|
|||||||
}
|
}
|
||||||
|
|
||||||
// already paired players map
|
// already paired players map
|
||||||
fun Pairable.played(other: Pairable) = historyHelper.playedTogether(this, other)
|
private fun Pairable.played(other: Pairable) = historyHelper.playedTogether(this, other)
|
||||||
|
|
||||||
// color balance (nw - nb)
|
// color balance (nw - nb)
|
||||||
val Pairable.colorBalance: Int get() = historyHelper.colorBalance(this) ?: 0
|
private val Pairable.colorBalance: Int get() = historyHelper.colorBalance(this) ?: 0
|
||||||
|
|
||||||
|
private val Pairable.group: Int get() = _groups[id]!!
|
||||||
|
|
||||||
// score (number of wins)
|
// score (number of wins)
|
||||||
val Pairable.score: Double get() = historyHelper.score(this) ?: 0.0
|
val Pairable.nbW: Double get() = historyHelper.nbW(this) ?: 0.0
|
||||||
|
|
||||||
|
val Pairable.sos: Double get() = historyHelper.sos[id]!!
|
||||||
|
|
||||||
|
val Pairable.sosm1: Double get() = historyHelper.sosm1[id]!!
|
||||||
|
val Pairable.sosm2: Double get() = historyHelper.sosm2[id]!!
|
||||||
|
val Pairable.sosos: Double get() = historyHelper.sosos[id]!!
|
||||||
|
val Pairable.sodos: Double get() = historyHelper.sodos[id]!!
|
||||||
|
val Pairable.cums: Double get() = historyHelper.cumscore[id]!!
|
||||||
|
|
||||||
|
|
||||||
// sos
|
|
||||||
val Pairable.sos: Double get() = historyHelper.sos(this) ?: 0.0
|
|
||||||
|
|
||||||
// sosos
|
|
||||||
val Pairable.sosos: Double get() = historyHelper.sosos(this) ?: 0.0
|
|
||||||
|
|
||||||
// sodos
|
|
||||||
val Pairable.sodos: Double get() = historyHelper.sodos(this) ?: 0.0
|
|
||||||
}
|
}
|
||||||
|
@@ -1,23 +1,39 @@
|
|||||||
package org.jeudego.pairgoth.pairing
|
package org.jeudego.pairgoth.pairing
|
||||||
|
|
||||||
import org.jeudego.pairgoth.model.Game
|
import org.jeudego.pairgoth.model.*
|
||||||
import org.jeudego.pairgoth.model.Pairable
|
|
||||||
import org.jeudego.pairgoth.model.Pairing
|
|
||||||
import kotlin.math.abs
|
import kotlin.math.abs
|
||||||
|
|
||||||
class SwissSolver(history: List<Game>, pairables: List<Pairable>, pairingParams: Pairing.PairingParams): Solver(history, pairables, pairingParams) {
|
class SwissSolver(round: Int,
|
||||||
|
history: List<Game>,
|
||||||
override fun sort(p: Pairable, q: Pairable): Int =
|
pairables: List<Pairable>,
|
||||||
when (p.score) {
|
pairingParams: Pairing.PairingParams,
|
||||||
q.score -> q.rating - p.rating
|
placementParams: PlacementParams):
|
||||||
else -> ((q.score - p.score) * 1000).toInt()
|
Solver(round, history, pairables, pairingParams, placementParams) {
|
||||||
}
|
|
||||||
|
|
||||||
|
// In a Swiss tournament the main criterion is the number of wins and already computed
|
||||||
override fun mainCriterion(p1: Pairable): Int {
|
override fun mainCriterion(p1: Pairable): Int {
|
||||||
TODO("Not yet implemented")
|
return p1.nbW.toInt() // Rounded Down TODO make it a parameter ?
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun mainCriterionMinMax(): Pair<Int, Int> {
|
override fun mainCriterionMinMax(): Pair<Int, Int> {
|
||||||
TODO("Not yet implemented")
|
return Pair(0, round-1)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun computeStandingScore(): Map<ID, Double> {
|
||||||
|
return historyHelper.numberWins
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getSpecificCriterionValue(p: Pairable, criterion: PlacementCriterion): Int {
|
||||||
|
// TODO solve this double/int conflict
|
||||||
|
return when (criterion) {
|
||||||
|
PlacementCriterion.NBW -> p.nbW.toInt()
|
||||||
|
PlacementCriterion.SOSW -> p.sos.toInt()
|
||||||
|
PlacementCriterion.SOSWM1 -> p.sosm1.toInt()
|
||||||
|
PlacementCriterion.SOSWM2 -> p.sosm2.toInt()
|
||||||
|
PlacementCriterion.SODOSW -> p.sodos.toInt()
|
||||||
|
PlacementCriterion.SOSOSW -> p.sosos.toInt()
|
||||||
|
PlacementCriterion.CUSSW -> p.cums.toInt()
|
||||||
|
else -> -1
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user