Reeng pairing in progress
This commit is contained in:
@@ -4,6 +4,7 @@ import com.republicate.kson.Json
|
|||||||
import com.republicate.kson.toJsonArray
|
import com.republicate.kson.toJsonArray
|
||||||
import org.jeudego.pairgoth.api.ApiHandler.Companion.badRequest
|
import org.jeudego.pairgoth.api.ApiHandler.Companion.badRequest
|
||||||
import org.jeudego.pairgoth.model.Pairing
|
import org.jeudego.pairgoth.model.Pairing
|
||||||
|
import org.jeudego.pairgoth.model.PairingType
|
||||||
import org.jeudego.pairgoth.model.getID
|
import org.jeudego.pairgoth.model.getID
|
||||||
import org.jeudego.pairgoth.model.toID
|
import org.jeudego.pairgoth.model.toID
|
||||||
import org.jeudego.pairgoth.model.toJson
|
import org.jeudego.pairgoth.model.toJson
|
||||||
@@ -28,7 +29,7 @@ object PairingHandler: PairgothApiHandler {
|
|||||||
val round = getSubSelector(request)?.toIntOrNull() ?: badRequest("invalid round number")
|
val round = getSubSelector(request)?.toIntOrNull() ?: badRequest("invalid round number")
|
||||||
val payload = getArrayPayload(request)
|
val payload = getArrayPayload(request)
|
||||||
val allPlayers = payload.size == 1 && payload[0] == "all"
|
val allPlayers = payload.size == 1 && payload[0] == "all"
|
||||||
if (!allPlayers && tournament.pairing.type == Pairing.PairingType.SWISS) badRequest("Swiss pairing requires all pairable players")
|
if (!allPlayers && tournament.pairing.type == PairingType.SWISS) badRequest("Swiss pairing requires all pairable players")
|
||||||
val playing = (tournament.games(round).values).flatMap {
|
val playing = (tournament.games(round).values).flatMap {
|
||||||
listOf(it.black, it.white)
|
listOf(it.black, it.white)
|
||||||
}.toSet()
|
}.toSet()
|
||||||
|
@@ -64,7 +64,6 @@ object TournamentHandler: PairgothApiHandler {
|
|||||||
clear()
|
clear()
|
||||||
putAll(tournament.games(round))
|
putAll(tournament.games(round))
|
||||||
}
|
}
|
||||||
updated.criteria.addAll(tournament.criteria)
|
|
||||||
Store.replaceTournament(updated)
|
Store.replaceTournament(updated)
|
||||||
tournament.dispatchEvent(tournamentUpdated, tournament.toJson())
|
tournament.dispatchEvent(tournamentUpdated, tournament.toJson())
|
||||||
return Json.Object("success" to true)
|
return Json.Object("success" to true)
|
||||||
|
@@ -2,200 +2,305 @@ package org.jeudego.pairgoth.model
|
|||||||
|
|
||||||
import com.republicate.kson.Json
|
import com.republicate.kson.Json
|
||||||
import org.jeudego.pairgoth.api.ApiHandler.Companion.badRequest
|
import org.jeudego.pairgoth.api.ApiHandler.Companion.badRequest
|
||||||
import org.jeudego.pairgoth.ext.OpenGothaFormat
|
import org.jeudego.pairgoth.model.PairingType.*
|
||||||
import org.jeudego.pairgoth.model.Pairing.PairingType.*
|
import org.jeudego.pairgoth.model.MainCritParams.SeedMethod.*
|
||||||
import org.jeudego.pairgoth.pairing.MacMahonSolver
|
import org.jeudego.pairgoth.pairing.MacMahonSolver
|
||||||
import org.jeudego.pairgoth.pairing.SwissSolver
|
import org.jeudego.pairgoth.pairing.SwissSolver
|
||||||
|
|
||||||
// Below are some constants imported from opengotha
|
// base pairing parameters
|
||||||
/**
|
data class BasePairingParams(
|
||||||
* Max value for BaAvoidDuplGame.
|
// standard NX1 factor for concavity curves
|
||||||
* In order to be compatible with max value of long (8 * 10^18),
|
val nx1: Double = 0.5,
|
||||||
* with max number of games (8000),
|
val dupWeight: Double = MAX_AVOIDDUPGAME,
|
||||||
* with relative weight of this parameter (1/2)
|
val random: Double = 0.0,
|
||||||
*BA_MAX_AVOIDDUPLGAME should be strictly limited to 5 * 10^14
|
val deterministic: Boolean = true,
|
||||||
*/
|
val colorBalance: Double = MAX_COLOR_BALANCE
|
||||||
private const val BA_MAX_AVOIDDUPLGAME: Long = 500000000000000L // 5e14
|
) {
|
||||||
|
init {
|
||||||
|
if (nx1 < 0.0 || nx1 > 1.0) throw Error("invalid standardNX1Factor")
|
||||||
|
if (dupWeight < 0.0 || dupWeight > MAX_AVOIDDUPGAME) throw Error("invalid avoidDuplGame value")
|
||||||
|
if (random < 0.0 || random > MAX_RANDOM) throw Error("invalid random")
|
||||||
|
if (colorBalance > 0.0 || colorBalance > MAX_COLOR_BALANCE) throw Error("invalid balanceWB")
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
companion object {
|
||||||
* Max value for BaRandom.
|
const val MAX_AVOIDDUPGAME = 500000000000000.0 // 5e14
|
||||||
* Due to internal coding,
|
const val MAX_RANDOM = 1000000000.0 // 1e9
|
||||||
* BA_MAX_RANDOM should be strictly limited to 2 * 10^9
|
const val MAX_COLOR_BALANCE = 1000000.0 // 1e6
|
||||||
*/
|
val default = BasePairingParams()
|
||||||
private const val BA_MAX_RANDOM: Long = 1000000000L // 2e9
|
}
|
||||||
private const val BA_MAX_BALANCEWB: Long = 1000000L // 1e6
|
}
|
||||||
|
|
||||||
private const val MA_MAX_AVOID_MIXING_CATEGORIES: Long = 20000000000000L // 2e13
|
// main criterium parameters
|
||||||
// Ratio between MA_MAX_MINIMIZE_SCORE_DIFFERENCE and MA_MAX_AVOID_MIXING_CATEGORIES should stay below 1/ nbcat^2
|
data class MainCritParams(
|
||||||
private const val MA_MAX_MINIMIZE_SCORE_DIFFERENCE: Long = 100000000000L // 1e11
|
// TB - TODO move avoidmixingcategories to swiss with category?
|
||||||
private const val MA_MAX_DUDD_WEIGHT: Long = MA_MAX_MINIMIZE_SCORE_DIFFERENCE / 1000; // Draw-ups Draw-downs
|
val categoriesWeight: Double = MAX_CATEGORIES_WEIGHT, // opengotha avoidMixingCategories
|
||||||
enum class MA_DUDD {TOP, MID, BOT}
|
val scoreWeight: Double = MAX_SCORE_WEIGHT, // opengotha minimizeScoreDifference
|
||||||
|
val drawUpDownWeight: Double = MAX_DRAW_UP_DOWN_WEIGHT, // opengotha DUDDWeight
|
||||||
|
val compensateDrawUpDown: Boolean = true,
|
||||||
|
val drawUpDownUpperMode: DrawUpDown = DrawUpDown.MIDDLE,
|
||||||
|
val drawUpDownLowerMode: DrawUpDown = DrawUpDown.MIDDLE,
|
||||||
|
val seedingWeight: Double = MAX_SEEDING_WEIGHT, // 5 *10^6, opengotha maximizeSeeding
|
||||||
|
val lastRoundForSeedSystem1: Int = 1,
|
||||||
|
val seedSystem1: SeedMethod = SeedMethod.SPLIT_AND_RANDOM,
|
||||||
|
val seedSystem2: SeedMethod = SeedMethod.SPLIT_AND_FOLD,
|
||||||
|
val additionalPlacementCritSystem1: Criterion = Criterion.RATING,
|
||||||
|
val additionalPlacementCritSystem2: Criterion = Criterion.NONE,
|
||||||
|
) {
|
||||||
|
enum class DrawUpDown {TOP, MIDDLE, BOTTOM}
|
||||||
|
enum class SeedMethod { SPLIT_AND_FOLD, SPLIT_AND_RANDOM, SPLIT_AND_SLIP }
|
||||||
|
companion object {
|
||||||
|
const val MAX_CATEGORIES_WEIGHT = 20000000000000.0 // 2e13
|
||||||
|
// Ratio between MAX_SCORE_WEIGHT and MAX_CATEGORIES_WEIGHT should stay below 1/ nbcat^2
|
||||||
|
const val MAX_SCORE_WEIGHT = 100000000000.0 // 1e11
|
||||||
|
const val MAX_DRAW_UP_DOWN_WEIGHT = MAX_SCORE_WEIGHT / 1000.0; // Draw-ups Draw-downs
|
||||||
|
const val MAX_SEEDING_WEIGHT = MAX_SCORE_WEIGHT / 20000.0;
|
||||||
|
val default = MainCritParams()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private const val MA_MAX_MAXIMIZE_SEEDING: Long = MA_MAX_MINIMIZE_SCORE_DIFFERENCE / 20000;
|
// secondary criterium parameters
|
||||||
|
data class SecondaryCritParams(
|
||||||
|
val barThresholdActive: Boolean = true, // Do not apply secondary criteria for players above bar
|
||||||
|
val rankThreshold: Int = 0, // Do not apply secondary criteria above 1D rank
|
||||||
|
val nbWinsThresholdActive: Boolean = true, // Do not apply secondary criteria when nbWins >= nbRounds / 2
|
||||||
|
val defSecCrit: Double = MainCritParams.MAX_CATEGORIES_WEIGHT, // Should be MA_MAX_MINIMIZE_SCORE_DIFFERENCE for MM, MA_MAX_AVOID_MIXING_CATEGORIES for others
|
||||||
|
) {
|
||||||
|
companion object {
|
||||||
|
val default = SecondaryCritParams()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
enum class SeedMethod { SPLIT_AND_FOLD, SPLIT_AND_RANDOM, SPLIT_AND_SLIP }
|
// geographical pairing params
|
||||||
|
data class GeographicalParams(
|
||||||
|
val avoidSameGeo: Double = 0.0, // Should be SecondaryCritParams.defSecCrit for SwCat and MM, 0 for Swiss
|
||||||
|
val preferMMSDiffRatherThanSameCountry: Int = 1, // Typically = 1
|
||||||
|
val preferMMSDiffRatherThanSameClubsGroup: Int = 2, // Typically = 2
|
||||||
|
val preferMMSDiffRatherThanSameClub: Int = 3, // Typically = 3
|
||||||
|
) {
|
||||||
|
companion object {
|
||||||
|
val disabled = GeographicalParams(avoidSameGeo = 0.0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
sealed class Pairing(val type: PairingType, val pairingParams: PairingParams = PairingParams(), val placementParams: PlacementParams) {
|
// handicap params
|
||||||
|
data class HandicapParams(
|
||||||
|
// minimizeHandicap is a secondary criteria but moved here
|
||||||
|
val weight: Double = 0.0, // Should be paiSeDefSecCrit for SwCat, 0 for others
|
||||||
|
val useMMS: Boolean = true, // if useMMS is false, hd will be based on rank
|
||||||
|
// When one player in the game has a rank of at least hdNoHdRankThreshold,
|
||||||
|
// then the game will be without handicap
|
||||||
|
val rankThreshold: Int = 0, // 0 is 1d
|
||||||
|
val correction: Int = 1, // Handicap will be decreased by hdCorrection
|
||||||
|
val ceiling: Int = 9, // Possible values are between 0 and 9
|
||||||
|
) {
|
||||||
|
companion object {
|
||||||
|
val default = HandicapParams(
|
||||||
|
weight = 0.0, // default disables handicap
|
||||||
|
useMMS = false,
|
||||||
|
rankThreshold = -30, // 30k
|
||||||
|
ceiling = 0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
enum class PairingType { SWISS, MAC_MAHON, ROUND_ROBIN }
|
||||||
|
|
||||||
|
data class PairingParams(
|
||||||
|
val base: BasePairingParams = BasePairingParams(),
|
||||||
|
val main: MainCritParams = MainCritParams(),
|
||||||
|
val secondary: SecondaryCritParams = SecondaryCritParams(),
|
||||||
|
val geo: GeographicalParams = GeographicalParams(),
|
||||||
|
val handicap: HandicapParams = HandicapParams()
|
||||||
|
)
|
||||||
|
|
||||||
|
sealed class Pairing(
|
||||||
|
val type: PairingType,
|
||||||
|
val pairingParams: PairingParams,
|
||||||
|
val placementParams: PlacementParams) {
|
||||||
companion object {}
|
companion object {}
|
||||||
enum class PairingType { SWISS, MAC_MAHON, ROUND_ROBIN }
|
|
||||||
data class PairingParams(
|
|
||||||
// Standard NX1 factor ( = Rather N X 1 than 1 X N)
|
|
||||||
val standardNX1Factor: Double = 0.5,
|
|
||||||
// Base criteria
|
|
||||||
val baseAvoidDuplGame: Long = BA_MAX_AVOIDDUPLGAME,
|
|
||||||
val baseRandom: Long = 0,
|
|
||||||
val baseDeterministic: Boolean = true,
|
|
||||||
val baseBalanceWB: Long = BA_MAX_BALANCEWB,
|
|
||||||
|
|
||||||
// Main criteria
|
|
||||||
// TODO move avoidmixingcategories to swiss with category
|
|
||||||
//val maAvoidMixingCategories: Double = MA_MAX_AVOID_MIXING_CATEGORIES,
|
|
||||||
val mainMinimizeScoreDifference: Long = MA_MAX_MINIMIZE_SCORE_DIFFERENCE,
|
|
||||||
|
|
||||||
val maDUDDWeight: Long = MA_MAX_DUDD_WEIGHT,
|
|
||||||
val maCompensateDUDD: Boolean = true,
|
|
||||||
val maDUDDUpperMode: MA_DUDD = MA_DUDD.MID,
|
|
||||||
val maDUDDLowerMode: MA_DUDD = MA_DUDD.MID,
|
|
||||||
|
|
||||||
val maMaximizeSeeding: Long = MA_MAX_MAXIMIZE_SEEDING, // 5 *10^6
|
|
||||||
val maLastRoundForSeedSystem1: Int = 1,
|
|
||||||
val maSeedSystem1: SeedMethod = SeedMethod.SPLIT_AND_RANDOM,
|
|
||||||
val maSeedSystem2: SeedMethod = SeedMethod.SPLIT_AND_FOLD,
|
|
||||||
val maAdditionalPlacementCritSystem1: PlacementCriterion = PlacementCriterion.RATING,
|
|
||||||
val maAdditionalPlacementCritSystem2: PlacementCriterion = PlacementCriterion.NULL,
|
|
||||||
|
|
||||||
// Secondary criteria
|
|
||||||
val seBarThresholdActive: Boolean = true, // Do not apply secondary criteria for players above bar
|
|
||||||
val seRankThreshold: Int = 0, // Do not apply secondary criteria above 1D rank
|
|
||||||
val seNbWinsThresholdActive: Boolean = true, // Do not apply secondary criteria when nbWins >= nbRounds / 2
|
|
||||||
val seDefSecCrit: Long = MA_MAX_AVOID_MIXING_CATEGORIES, // Should be MA_MAX_MINIMIZE_SCORE_DIFFERENCE for MM, MA_MAX_AVOID_MIXING_CATEGORIES for others
|
|
||||||
|
|
||||||
// Geographical params
|
|
||||||
val geo: GeographicalParams = GeographicalParams(avoidSameGeo = seDefSecCrit),
|
|
||||||
|
|
||||||
// Handicap related settings
|
|
||||||
val hd: HandicapParams = HandicapParams(minimizeHandicap = seDefSecCrit),
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
abstract fun pair(tournament: Tournament<*>, round: Int, pairables: List<Pairable>): List<Game>
|
abstract fun pair(tournament: Tournament<*>, round: Int, pairables: List<Pairable>): List<Game>
|
||||||
}
|
}
|
||||||
|
|
||||||
data class GeographicalParams(
|
private fun Tournament<*>.historyBefore(round: Int) =
|
||||||
val avoidSameGeo: Long, // Should be SeDefSecCrit for SwCat and MM, 0 for Swiss
|
|
||||||
val preferMMSDiffRatherThanSameCountry: Int = 1, // Typically = 1
|
|
||||||
val preferMMSDiffRatherThanSameClubsGroup: Int = 2, // Typically = 2
|
|
||||||
val preferMMSDiffRatherThanSameClub: Int = 3, // Typically = 3
|
|
||||||
) {
|
|
||||||
companion object {
|
|
||||||
fun disabled() = GeographicalParams(avoidSameGeo = 0L)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
data class HandicapParams(
|
|
||||||
// minimizeHandicap is a secondary criteria but moved here
|
|
||||||
val minimizeHandicap: Long, // Should be paiSeDefSecCrit for SwCat, 0 for others
|
|
||||||
val basedOnMMS: Boolean = true, // if hdBasedOnMMS is false, hd will be based on rank
|
|
||||||
// When one player in the game has a rank of at least hdNoHdRankThreshold,
|
|
||||||
// then the game will be without handicap
|
|
||||||
val noHdRankThreshold: Int = 0, // 0 is 1d
|
|
||||||
val correction: Int = 1, // Handicap will be decreased by hdCorrection
|
|
||||||
val ceiling: Int = 9, // Possible values are between 0 and 9
|
|
||||||
) {
|
|
||||||
companion object {
|
|
||||||
fun disabled() = HandicapParams(
|
|
||||||
minimizeHandicap = 0L,
|
|
||||||
basedOnMMS = false,
|
|
||||||
noHdRankThreshold=-30, // 30k
|
|
||||||
ceiling=0)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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).flatMap { games(round).values }
|
||||||
|
|
||||||
class Swiss(): Pairing(SWISS, PairingParams(
|
class Swiss(
|
||||||
maSeedSystem1 = SeedMethod.SPLIT_AND_SLIP,
|
pairingParams: PairingParams = PairingParams(
|
||||||
maSeedSystem2 = SeedMethod.SPLIT_AND_SLIP,
|
base = BasePairingParams(),
|
||||||
|
main = MainCritParams(
|
||||||
seBarThresholdActive = true, // not relevant
|
seedSystem1 = SPLIT_AND_SLIP,
|
||||||
seRankThreshold = -30,
|
seedSystem2 = SPLIT_AND_SLIP
|
||||||
seNbWinsThresholdActive = true, // not relevant
|
),
|
||||||
seDefSecCrit = MA_MAX_AVOID_MIXING_CATEGORIES,
|
secondary = SecondaryCritParams(
|
||||||
|
barThresholdActive = true,
|
||||||
geo = GeographicalParams.disabled(),
|
rankThreshold = -30,
|
||||||
hd = HandicapParams.disabled(),
|
nbWinsThresholdActive = true,
|
||||||
), PlacementParams(PlacementCriterion.NBW, PlacementCriterion.SOSW, PlacementCriterion.SOSOSW)) {
|
defSecCrit = MainCritParams.MAX_CATEGORIES_WEIGHT
|
||||||
|
),
|
||||||
|
geo = GeographicalParams.disabled,
|
||||||
|
handicap = HandicapParams.default
|
||||||
|
),
|
||||||
|
placementParams: PlacementParams = PlacementParams(
|
||||||
|
Criterion.NBW, Criterion.SOSW, Criterion.SOSOSW
|
||||||
|
)
|
||||||
|
): Pairing(SWISS, pairingParams, placementParams) {
|
||||||
|
companion object {}
|
||||||
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(round, tournament.historyBefore(round), pairables, pairingParams, placementParams).pair()
|
return SwissSolver(round, tournament.historyBefore(round), pairables, pairingParams, placementParams).pair()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class MacMahon(
|
class MacMahon(
|
||||||
var bar: Int = 0,
|
pairingParams: PairingParams = PairingParams(
|
||||||
var minLevel: Int = -30,
|
base = BasePairingParams(),
|
||||||
var reducer: Int = 1
|
main = MainCritParams(),
|
||||||
): Pairing(MAC_MAHON, PairingParams(seDefSecCrit = MA_MAX_MINIMIZE_SCORE_DIFFERENCE),
|
secondary = SecondaryCritParams(
|
||||||
PlacementParams(PlacementCriterion.MMS, PlacementCriterion.SOSM, PlacementCriterion.SOSOSM)) {
|
defSecCrit = MainCritParams.MAX_SCORE_WEIGHT
|
||||||
val groups = mutableListOf<Int>()
|
),
|
||||||
|
geo = GeographicalParams(
|
||||||
|
avoidSameGeo = MainCritParams.MAX_SCORE_WEIGHT
|
||||||
|
),
|
||||||
|
handicap = HandicapParams()
|
||||||
|
),
|
||||||
|
placementParams: PlacementParams = PlacementParams(
|
||||||
|
Criterion.NBW, Criterion.SOSW, Criterion.SOSOSW
|
||||||
|
)
|
||||||
|
): Pairing(MAC_MAHON, pairingParams, placementParams) {
|
||||||
|
companion object {}
|
||||||
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(round, tournament.historyBefore(round), pairables, pairingParams, placementParams, mmBase = minLevel, mmBar = bar, reducer = reducer).pair()
|
return MacMahonSolver(round, tournament.historyBefore(round), pairables, pairingParams, placementParams).pair()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class RoundRobin: Pairing(ROUND_ROBIN, PairingParams(), PlacementParams(PlacementCriterion.NBW, PlacementCriterion.RATING)) {
|
class RoundRobin(
|
||||||
|
pairingParams: PairingParams = PairingParams(),
|
||||||
|
placementParams: PlacementParams = PlacementParams(Criterion.NBW, Criterion.RATING)
|
||||||
|
): Pairing(ROUND_ROBIN, pairingParams, placementParams) {
|
||||||
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()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Serialization
|
// Serialization
|
||||||
// TODO failing on serialization
|
|
||||||
fun HandicapParams.toJson() = Json.Object(
|
|
||||||
"minimize_hd" to minimizeHandicap,
|
|
||||||
"mms_based" to basedOnMMS,
|
|
||||||
"no_hd_thresh" to noHdRankThreshold,
|
|
||||||
"correction" to correction,
|
|
||||||
"ceiling" to ceiling, )
|
|
||||||
|
|
||||||
fun HandicapParams.fromJson(json: Json.Object) = HandicapParams(
|
fun BasePairingParams.Companion.fromJson(json: Json.Object) = BasePairingParams(
|
||||||
minimizeHandicap=json.getLong("minimize_hd")!!,
|
nx1 = json.getDouble("nx1") ?: default.nx1,
|
||||||
basedOnMMS=json.getBoolean("mms_based")!!,
|
dupWeight = json.getDouble("dupWeight") ?: default.dupWeight,
|
||||||
noHdRankThreshold=json.getInt("no_hd_thresh")!!,
|
random = json.getDouble("random") ?: default.random,
|
||||||
correction=json.getInt("correction")!!,
|
deterministic = json.getBoolean("deterministic") ?: default.deterministic,
|
||||||
ceiling=json.getInt("ceiling")!!,
|
colorBalance = json.getDouble("colorBalanceWeight") ?: default.colorBalance
|
||||||
|
)
|
||||||
|
|
||||||
|
fun BasePairingParams.toJson() = Json.Object(
|
||||||
|
"nx1" to nx1,
|
||||||
|
"dupWeight" to dupWeight,
|
||||||
|
"random" to random,
|
||||||
|
"colorBalanceWeight" to colorBalance
|
||||||
|
)
|
||||||
|
|
||||||
|
fun MainCritParams.Companion.fromJson(json: Json.Object) = MainCritParams(
|
||||||
|
categoriesWeight = json.getDouble("catWeight") ?: default.categoriesWeight,
|
||||||
|
scoreWeight = json.getDouble("scoreWeight") ?: default.scoreWeight,
|
||||||
|
drawUpDownWeight = json.getDouble("upDownWeight") ?: default.drawUpDownWeight,
|
||||||
|
compensateDrawUpDown = json.getBoolean("upDownCompensate") ?: default.compensateDrawUpDown,
|
||||||
|
drawUpDownLowerMode = json.getString("upDownLowerMode")?.let { MainCritParams.DrawUpDown.valueOf(it) } ?: default.drawUpDownLowerMode,
|
||||||
|
drawUpDownUpperMode = json.getString("upDownUpperMode")?.let { MainCritParams.DrawUpDown.valueOf(it) } ?: default.drawUpDownUpperMode,
|
||||||
|
seedingWeight = json.getDouble("maximizeSeeding") ?: default.seedingWeight,
|
||||||
|
lastRoundForSeedSystem1 = json.getInt("firstSeedLastRound") ?: default.lastRoundForSeedSystem1,
|
||||||
|
seedSystem1 = json.getString("firstSeed")?.let { MainCritParams.SeedMethod.valueOf(it) } ?: default.seedSystem1,
|
||||||
|
seedSystem2 = json.getString("secondSeed")?.let { MainCritParams.SeedMethod.valueOf(it) } ?: default.seedSystem2,
|
||||||
|
additionalPlacementCritSystem1 = json.getString("firstSeedAddCrit")?.let { Criterion.valueOf(it) } ?: default.additionalPlacementCritSystem1,
|
||||||
|
additionalPlacementCritSystem2 = json.getString("secondSeedAddCrit")?.let { Criterion.valueOf(it) } ?: default.additionalPlacementCritSystem2
|
||||||
|
)
|
||||||
|
|
||||||
|
fun MainCritParams.toJson() = Json.Object(
|
||||||
|
"catWeight" to categoriesWeight,
|
||||||
|
"scoreWeight" to scoreWeight,
|
||||||
|
"upDownWeight" to drawUpDownWeight,
|
||||||
|
"upDownCompensate" to compensateDrawUpDown,
|
||||||
|
"upDownLowerMode" to drawUpDownLowerMode,
|
||||||
|
"upDownUpperMode" to drawUpDownUpperMode,
|
||||||
|
"maximizeSeeding" to seedingWeight,
|
||||||
|
"firstSeedLastRound" to lastRoundForSeedSystem1,
|
||||||
|
"firstSeed" to seedSystem1,
|
||||||
|
"secondSeed" to seedSystem2,
|
||||||
|
"firstSeedAddCrit" to additionalPlacementCritSystem1,
|
||||||
|
"secondSeedAddCrit" to additionalPlacementCritSystem2
|
||||||
|
)
|
||||||
|
|
||||||
|
fun SecondaryCritParams.Companion.fromJson(json: Json.Object) = SecondaryCritParams(
|
||||||
|
barThresholdActive = json.getBoolean("barTreshold") ?: default.barThresholdActive,
|
||||||
|
rankThreshold = json.getInt("rankTreshold") ?: default.rankThreshold,
|
||||||
|
nbWinsThresholdActive = json.getBoolean("winsTreshold") ?: default.nbWinsThresholdActive,
|
||||||
|
defSecCrit = json.getDouble("secWeight") ?: default.defSecCrit
|
||||||
|
)
|
||||||
|
|
||||||
|
fun SecondaryCritParams.toJson() = Json.Object(
|
||||||
|
"barTreshold" to barThresholdActive,
|
||||||
|
"rankTreshold" to rankThreshold,
|
||||||
|
"winsTreshold" to nbWinsThresholdActive,
|
||||||
|
"secWeight" to defSecCrit
|
||||||
|
)
|
||||||
|
|
||||||
|
fun GeographicalParams.Companion.fromJson(json: Json.Object) = GeographicalParams(
|
||||||
|
avoidSameGeo = json.getDouble("weight") ?: disabled.avoidSameGeo,
|
||||||
|
preferMMSDiffRatherThanSameCountry = json.getInt("mmsDiffCountry") ?: disabled.preferMMSDiffRatherThanSameCountry,
|
||||||
|
preferMMSDiffRatherThanSameClubsGroup = json.getInt("mmsDiffClubGroup") ?: disabled.preferMMSDiffRatherThanSameClubsGroup,
|
||||||
|
preferMMSDiffRatherThanSameClub = json.getInt("mmsDiffClub") ?: disabled.preferMMSDiffRatherThanSameClub
|
||||||
)
|
)
|
||||||
|
|
||||||
fun GeographicalParams.toJson() = Json.Object(
|
fun GeographicalParams.toJson() = Json.Object(
|
||||||
"avoid_same_geo" to avoidSameGeo,
|
"weight" to avoidSameGeo,
|
||||||
"country" to preferMMSDiffRatherThanSameCountry,
|
"mmsDiffCountry" to preferMMSDiffRatherThanSameCountry,
|
||||||
"club_group" to preferMMSDiffRatherThanSameClubsGroup,
|
"mmsDiffClubGroup" to preferMMSDiffRatherThanSameClubsGroup,
|
||||||
"club" to preferMMSDiffRatherThanSameClub,)
|
"mmsDiffClub" to preferMMSDiffRatherThanSameClub
|
||||||
|
|
||||||
fun GeographicalParams.fromJson(json: Json.Object) = GeographicalParams(
|
|
||||||
avoidSameGeo=json.getLong("avoid_same_geo")!!,
|
|
||||||
preferMMSDiffRatherThanSameCountry=json.getInt("country")!!,
|
|
||||||
preferMMSDiffRatherThanSameClubsGroup=json.getInt("club_group")!!,
|
|
||||||
preferMMSDiffRatherThanSameClub=json.getInt("club")!!,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
fun HandicapParams.Companion.fromJson(json: Json.Object) = HandicapParams(
|
||||||
|
weight = json.getDouble("weight") ?: default.weight,
|
||||||
|
useMMS = json.getBoolean("useMMS") ?: default.useMMS,
|
||||||
|
rankThreshold = json.getInt("treshold") ?: default.rankThreshold,
|
||||||
|
correction = json.getInt("correction") ?: default.correction,
|
||||||
|
ceiling = json.getInt("ceiling") ?: default.ceiling
|
||||||
|
)
|
||||||
|
|
||||||
fun Pairing.Companion.fromJson(json: Json.Object) = when (json.getString("type")?.let { Pairing.PairingType.valueOf(it) } ?: badRequest("missing pairing type")) {
|
fun HandicapParams.toJson() = Json.Object(
|
||||||
SWISS -> Swiss()
|
"weight" to weight,
|
||||||
MAC_MAHON -> MacMahon(
|
"useMMS" to useMMS,
|
||||||
bar = json.getInt("bar") ?: 0,
|
"treshold" to rankThreshold,
|
||||||
minLevel = json.getInt("minLevel") ?: -30,
|
"correction" to correction,
|
||||||
reducer = json.getInt("reducer") ?: 1
|
"ceiling" to ceiling
|
||||||
)
|
)
|
||||||
ROUND_ROBIN -> RoundRobin()
|
|
||||||
}
|
fun Pairing.Companion.fromJson(json: Json.Object): Pairing {
|
||||||
|
// get default values for each type
|
||||||
fun Pairing.toJson() = when (this) {
|
val type = json.getString("type")?.let { PairingType.valueOf(it) } ?: badRequest("missing pairing type")
|
||||||
is Swiss ->
|
val defaultParams = when (type) {
|
||||||
Json.Object("type" to type.name, "geo" to pairingParams.geo.toJson(), "hd" to pairingParams.hd.toJson())
|
SWISS -> Swiss()
|
||||||
is MacMahon -> Json.Object("type" to type.name, "bar" to bar, "minLevel" to minLevel, "reducer" to reducer)
|
MAC_MAHON -> MacMahon()
|
||||||
is RoundRobin -> Json.Object("type" to type.name)
|
ROUND_ROBIN -> RoundRobin()
|
||||||
|
}
|
||||||
|
val base = json.getObject("base")?.let { BasePairingParams.fromJson(it) } ?: defaultParams.pairingParams.base
|
||||||
|
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 geo = json.getObject("geo")?.let { GeographicalParams.fromJson(it) } ?: defaultParams.pairingParams.geo
|
||||||
|
val hd = json.getObject("handicap")?.let { HandicapParams.fromJson(it) } ?: defaultParams.pairingParams.handicap
|
||||||
|
val pairingParams = PairingParams(base, main, secondary, geo, hd)
|
||||||
|
val placementParams = json.getArray("placement")?.let { PlacementParams.fromJson(it) } ?: defaultParams.placementParams
|
||||||
|
return when (type) {
|
||||||
|
SWISS -> Swiss(pairingParams, placementParams)
|
||||||
|
MAC_MAHON -> MacMahon(pairingParams, placementParams)
|
||||||
|
ROUND_ROBIN -> RoundRobin(pairingParams, placementParams)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun Pairing.toJson() = Json.Object(
|
||||||
|
"type" to type.name,
|
||||||
|
"base" to pairingParams.base.toJson(),
|
||||||
|
"main" to pairingParams.main.toJson(),
|
||||||
|
"secondary" to pairingParams.main.toJson(),
|
||||||
|
"geo" to pairingParams.geo.toJson(),
|
||||||
|
"handicap" to pairingParams.handicap.toJson(),
|
||||||
|
"placement" to placementParams.toJson()
|
||||||
|
)
|
||||||
|
@@ -1,7 +1,9 @@
|
|||||||
package org.jeudego.pairgoth.model
|
package org.jeudego.pairgoth.model
|
||||||
|
|
||||||
enum class PlacementCriterion {
|
import com.republicate.kson.Json
|
||||||
NULL, // No ranking/tie-break
|
|
||||||
|
enum class Criterion {
|
||||||
|
NONE, // No ranking / tie-break
|
||||||
|
|
||||||
CATEGORY,
|
CATEGORY,
|
||||||
RANK,
|
RANK,
|
||||||
@@ -35,24 +37,23 @@ enum class PlacementCriterion {
|
|||||||
DC, // Direct confrontation
|
DC, // Direct confrontation
|
||||||
}
|
}
|
||||||
|
|
||||||
class PlacementParams(vararg criteria: PlacementCriterion) {
|
class PlacementParams(vararg crit: Criterion) {
|
||||||
companion object {
|
companion object {}
|
||||||
const val MAX_NUMBER_OF_CRITERIA: Int = 6
|
|
||||||
|
val criteria = crit.toList().also {
|
||||||
|
check()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun addNullCriteria(criteria: Array<out PlacementCriterion>): ArrayList<PlacementCriterion> {
|
private fun check() {
|
||||||
var criteria = arrayListOf(*criteria)
|
// throws an exception if criteria are incoherent
|
||||||
while (criteria.size < MAX_NUMBER_OF_CRITERIA) {
|
// TODO - if (not coherent) throw Error("...")
|
||||||
criteria.add(PlacementCriterion.NULL)
|
|
||||||
}
|
|
||||||
return criteria
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
val criteria = addNullCriteria(criteria)
|
fun PlacementParams.Companion.fromJson(json: Json.Array) = PlacementParams(*json.map {
|
||||||
|
Criterion.valueOf(it!! as String)
|
||||||
|
}.toTypedArray())
|
||||||
|
|
||||||
open fun checkWarnings(): String {
|
fun PlacementParams.toJson() = Json.Array(*criteria.map {
|
||||||
// Returns a warning message if criteria are incoherent
|
it.name
|
||||||
// TODO
|
}.toTypedArray())
|
||||||
return ""
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
@@ -36,10 +36,6 @@ sealed class Tournament <P: Pairable>(
|
|||||||
TEAM5(5);
|
TEAM5(5);
|
||||||
}
|
}
|
||||||
|
|
||||||
enum class Criterion {
|
|
||||||
NBW, MMS, SOS, SOSOS, SODOS
|
|
||||||
}
|
|
||||||
|
|
||||||
// players per id
|
// players per id
|
||||||
abstract val players: MutableMap<ID, Player>
|
abstract val players: MutableMap<ID, Player>
|
||||||
|
|
||||||
@@ -69,13 +65,6 @@ sealed class Tournament <P: Pairable>(
|
|||||||
if (round > games.size + 1) throw Error("invalid round")
|
if (round > games.size + 1) throw Error("invalid round")
|
||||||
else mutableMapOf<ID, Game>().also { games.add(it) }
|
else mutableMapOf<ID, Game>().also { games.add(it) }
|
||||||
fun lastRound() = games.size
|
fun lastRound() = games.size
|
||||||
|
|
||||||
// standings criteria
|
|
||||||
val criteria = mutableListOf<Criterion>(
|
|
||||||
if (pairing.type == Pairing.PairingType.MAC_MAHON) Criterion.MMS else Criterion.NBW,
|
|
||||||
Criterion.SOS,
|
|
||||||
Criterion.SOSOS
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// standard tournament of individuals
|
// standard tournament of individuals
|
||||||
|
@@ -4,19 +4,19 @@ import org.jeudego.pairgoth.model.*
|
|||||||
|
|
||||||
open class HistoryHelper(protected val history: List<Game>, computeScore: () -> Map<ID, Double>) {
|
open class HistoryHelper(protected val history: List<Game>, computeScore: () -> Map<ID, Double>) {
|
||||||
|
|
||||||
fun getCriterionValue(p: Pairable, crit: PlacementCriterion): Double {
|
fun getCriterionValue(p: Pairable, crit: Criterion): Double {
|
||||||
// Returns generic criterion
|
// Returns generic criterion
|
||||||
// Specific criterion are computed by solvers directly
|
// Specific criterion are computed by solvers directly
|
||||||
return when (crit) {
|
return when (crit) {
|
||||||
PlacementCriterion.NULL -> 0.0
|
Criterion.NONE -> 0.0
|
||||||
PlacementCriterion.CATEGORY -> TODO()
|
Criterion.CATEGORY -> TODO()
|
||||||
PlacementCriterion.RANK -> p.rank.toDouble()
|
Criterion.RANK -> p.rank.toDouble()
|
||||||
PlacementCriterion.RATING -> p.rating.toDouble()
|
Criterion.RATING -> p.rating.toDouble()
|
||||||
|
|
||||||
PlacementCriterion.EXT -> TODO()
|
Criterion.EXT -> TODO()
|
||||||
PlacementCriterion.EXR -> TODO()
|
Criterion.EXR -> TODO()
|
||||||
PlacementCriterion.SDC -> TODO()
|
Criterion.SDC -> TODO()
|
||||||
PlacementCriterion.DC -> TODO()
|
Criterion.DC -> TODO()
|
||||||
else -> -1.0
|
else -> -1.0
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -1,14 +1,10 @@
|
|||||||
package org.jeudego.pairgoth.pairing
|
package org.jeudego.pairgoth.pairing
|
||||||
|
|
||||||
import org.jeudego.pairgoth.model.*
|
import org.jeudego.pairgoth.model.*
|
||||||
import kotlin.math.abs
|
|
||||||
import kotlin.math.max
|
|
||||||
import kotlin.math.roundToInt
|
|
||||||
import kotlin.math.sign
|
|
||||||
|
|
||||||
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) {
|
class MacMahonSolver(round: Int, history: 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
|
||||||
|
|
||||||
// CB TODO - configurable criteria
|
// CB TODO - configurable criteria
|
||||||
override fun mainCriterion(p1: Pairable): Int {
|
override fun mainCriterion(p1: Pairable): Int {
|
||||||
@@ -23,16 +19,16 @@ class MacMahonSolver(round: Int, history: List<Game>, pairables: List<Pairable>,
|
|||||||
TODO("Not yet implemented")
|
TODO("Not yet implemented")
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getSpecificCriterionValue(p: Pairable, criterion: PlacementCriterion): Double {
|
override fun getSpecificCriterionValue(p: Pairable, criterion: Criterion): Double {
|
||||||
// TODO solve this double/int conflict
|
// TODO solve this double/int conflict
|
||||||
return when (criterion) {
|
return when (criterion) {
|
||||||
PlacementCriterion.MMS -> TODO()
|
Criterion.MMS -> TODO()
|
||||||
PlacementCriterion.SOSM -> p.sos
|
Criterion.SOSM -> p.sos
|
||||||
PlacementCriterion.SOSMM1 -> p.sosm1
|
Criterion.SOSMM1 -> p.sosm1
|
||||||
PlacementCriterion.SOSMM2 -> p.sosm2
|
Criterion.SOSMM2 -> p.sosm2
|
||||||
PlacementCriterion.SODOSM -> p.sodos
|
Criterion.SODOSM -> p.sodos
|
||||||
PlacementCriterion.SOSOSM -> p.sosos
|
Criterion.SOSOSM -> p.sosos
|
||||||
PlacementCriterion.CUSSM -> p.cums
|
Criterion.CUSSM -> p.cums
|
||||||
else -> -1.0
|
else -> -1.0
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -1,6 +1,7 @@
|
|||||||
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.MainCritParams.SeedMethod.*
|
||||||
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
|
||||||
@@ -14,8 +15,7 @@ import kotlin.math.min
|
|||||||
|
|
||||||
val DEBUG_EXPORT_WEIGHT = true
|
val DEBUG_EXPORT_WEIGHT = true
|
||||||
|
|
||||||
private fun detRandom(max: Long, p1: Pairable, p2: Pairable): Long {
|
private fun detRandom(max: Double, p1: Pairable, p2: Pairable): Double {
|
||||||
var nR: Long = 0
|
|
||||||
var inverse = false
|
var inverse = false
|
||||||
|
|
||||||
val seed1 = p1.nameSeed()
|
val seed1 = p1.nameSeed()
|
||||||
@@ -28,28 +28,25 @@ private fun detRandom(max: Long, p1: Pairable, p2: Pairable): Long {
|
|||||||
inverse = true
|
inverse = true
|
||||||
}
|
}
|
||||||
val s = name1 + name2
|
val s = name1 + name2
|
||||||
|
var nR = 0.0
|
||||||
for (i in s.indices) {
|
for (i in s.indices) {
|
||||||
val c = s[i]
|
val c = s[i]
|
||||||
nR += (c.code * (i + 1)).toLong()
|
nR += (c.code * (i + 1)).toDouble()
|
||||||
}
|
}
|
||||||
nR = nR * 1234567 % (max + 1)
|
nR = nR * 1234567 % (max + 1)
|
||||||
if (inverse) nR = max - nR
|
if (inverse) nR = max - nR
|
||||||
return nR
|
return nR
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun nonDetRandom(max: Long): Long {
|
private fun nonDetRandom(max: Double) =
|
||||||
if (max == 0L) {
|
if (max == 0.0) 0.0
|
||||||
return 0
|
else Math.random() * (max + 1.0)
|
||||||
}
|
|
||||||
val r = Math.random() * (max + 1)
|
|
||||||
return r.toLong()
|
|
||||||
}
|
|
||||||
|
|
||||||
sealed class Solver(
|
sealed class Solver(
|
||||||
val round: Int,
|
val round: Int,
|
||||||
history: List<Game>,
|
history: List<Game>,
|
||||||
val pairables: List<Pairable>,
|
val pairables: List<Pairable>,
|
||||||
val pairingParams: Pairing.PairingParams,
|
val pairingParams: PairingParams,
|
||||||
val placementParams: PlacementParams) {
|
val placementParams: PlacementParams) {
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
@@ -66,28 +63,23 @@ sealed class Solver(
|
|||||||
}
|
}
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
open fun weight(p1: Pairable, p2: Pairable): Double {
|
open fun weight(p1: Pairable, p2: Pairable) =
|
||||||
var score = 1L // 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) +
|
||||||
|
applyMainCriteria(p1, p2) +
|
||||||
|
applySecondaryCriteria(p1, p2)
|
||||||
|
|
||||||
score += applyBaseCriteria(p1, p2)
|
|
||||||
|
|
||||||
score += applyMainCriteria(p1, p2)
|
|
||||||
|
|
||||||
score += applySecondaryCriteria(p1, p2)
|
|
||||||
|
|
||||||
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
|
// SOS and variants will be computed based on this score
|
||||||
abstract fun computeStandingScore(): Map<ID, Double>
|
abstract fun computeStandingScore(): Map<ID, Double>
|
||||||
// This function needs to be overridden for criterion specific to the current pairing mode
|
// This function needs to be overridden for criterion specific to the current pairing mode
|
||||||
open fun getSpecificCriterionValue(p1: Pairable, criterion: PlacementCriterion): Double {
|
open fun getSpecificCriterionValue(p1: Pairable, criterion: Criterion): Double {
|
||||||
return -1.0
|
return -1.0
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun getCriterionValue(p1: Pairable, criterion: PlacementCriterion): Double {
|
private fun getCriterionValue(p1: Pairable, criterion: Criterion): Double {
|
||||||
val genericCritVal = historyHelper.getCriterionValue(p1, criterion)
|
val genericCritVal = historyHelper.getCriterionValue(p1, criterion)
|
||||||
// If the value from the history helper is > 0 it means that it is a generic criterion
|
// If the value from the history helper is > 0 it means that it is a generic criterion
|
||||||
// Just returns the value
|
// Just returns the value
|
||||||
@@ -122,8 +114,8 @@ sealed class Solver(
|
|||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
open fun applyBaseCriteria(p1: Pairable, p2: Pairable): Long {
|
open fun applyBaseCriteria(p1: Pairable, p2: Pairable): Double {
|
||||||
var score = 0L
|
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 ?
|
||||||
@@ -137,8 +129,8 @@ sealed class Solver(
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Main criteria
|
// Main criteria
|
||||||
open fun applyMainCriteria(p1: Pairable, p2: Pairable): Long {
|
open fun applyMainCriteria(p1: Pairable, p2: Pairable): Double {
|
||||||
var score = 0L
|
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
|
||||||
// TODO
|
// TODO
|
||||||
@@ -155,8 +147,8 @@ sealed class Solver(
|
|||||||
return score
|
return score
|
||||||
}
|
}
|
||||||
|
|
||||||
open fun applySecondaryCriteria(p1: Pairable, p2: Pairable): Long {
|
open fun applySecondaryCriteria(p1: Pairable, p2: Pairable): Double {
|
||||||
var score = 0L
|
var score = 0.0
|
||||||
// See Swiss with category for minimizing handicap criterion
|
// See Swiss with category for minimizing handicap criterion
|
||||||
|
|
||||||
// TODO understand where opengotha test if need to be applied
|
// TODO understand where opengotha test if need to be applied
|
||||||
@@ -169,23 +161,23 @@ sealed class Solver(
|
|||||||
|
|
||||||
// Weight score computation details
|
// Weight score computation details
|
||||||
// Base criteria
|
// Base criteria
|
||||||
open fun avoidDuplicatingGames(p1: Pairable, p2: Pairable): Long {
|
open fun avoidDuplicatingGames(p1: Pairable, p2: Pairable): Double {
|
||||||
if (p1.played(p2)) {
|
if (p1.played(p2)) {
|
||||||
return 0 // We get no score if pairables already played together
|
return 0.0 // We get no score if pairables already played together
|
||||||
} else {
|
} else {
|
||||||
return pairingParams.baseAvoidDuplGame
|
return pairingParams.base.dupWeight
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
open fun applyRandom(p1: Pairable, p2: Pairable): Long {
|
open fun applyRandom(p1: Pairable, p2: Pairable): Double {
|
||||||
if (pairingParams.baseDeterministic) {
|
if (pairingParams.base.deterministic) {
|
||||||
return detRandom(pairingParams.baseRandom, p1, p2)
|
return detRandom(pairingParams.base.random, p1, p2)
|
||||||
} else {
|
} else {
|
||||||
return nonDetRandom(pairingParams.baseRandom)
|
return nonDetRandom(pairingParams.base.random)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
open fun applyBalanceBW(p1: Pairable, p2: Pairable): Long {
|
open fun applyBalanceBW(p1: Pairable, p2: Pairable): Double {
|
||||||
// This cost is never applied if potential Handicap != 0
|
// 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 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
|
// It is half applied if one of wbBalance is 0 and the other is >=2
|
||||||
@@ -194,58 +186,58 @@ sealed class Solver(
|
|||||||
val wb1: Int = p1.colorBalance
|
val wb1: Int = p1.colorBalance
|
||||||
val wb2: Int = p2.colorBalance
|
val wb2: Int = p2.colorBalance
|
||||||
if (wb1 * wb2 < 0) {
|
if (wb1 * wb2 < 0) {
|
||||||
return pairingParams.baseBalanceWB
|
return pairingParams.base.colorBalance
|
||||||
} else if (wb1 == 0 && abs(wb2) >= 2) {
|
} else if (wb1 == 0 && abs(wb2) >= 2) {
|
||||||
return pairingParams.baseBalanceWB / 2
|
return pairingParams.base.colorBalance / 2
|
||||||
} else if (wb2 == 0 && abs(wb1) >= 2) {
|
} else if (wb2 == 0 && abs(wb1) >= 2) {
|
||||||
return pairingParams.baseBalanceWB / 2
|
return pairingParams.base.colorBalance / 2
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return 0
|
return 0.0
|
||||||
}
|
}
|
||||||
|
|
||||||
open fun minimizeScoreDifference(p1: Pairable, p2: Pairable): Long {
|
open fun minimizeScoreDifference(p1: Pairable, p2: Pairable): Double {
|
||||||
var score = 0L
|
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) as Double / scoreRange.toDouble()
|
||||||
val k: Double = pairingParams.standardNX1Factor
|
val k: Double = pairingParams.base.nx1
|
||||||
score = (pairingParams.mainMinimizeScoreDifference * (1.0 - x) * (1.0 + k * x)) as Long
|
score = pairingParams.main.scoreWeight * (1.0 - x) * (1.0 + k * x)
|
||||||
|
|
||||||
return score
|
return score
|
||||||
}
|
}
|
||||||
|
|
||||||
fun applySeeding(p1: Pairable, p2: Pairable): Long {
|
fun applySeeding(p1: Pairable, p2: Pairable): Double {
|
||||||
var score = 0L
|
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.maMaximizeSeeding
|
val maxSeedingWeight = pairingParams.main.seedingWeight
|
||||||
|
|
||||||
val currentSeedSystem: SeedMethod = if (round <= pairingParams.maLastRoundForSeedSystem1)
|
val currentSeedSystem: MainCritParams.SeedMethod = if (round <= pairingParams.main.lastRoundForSeedSystem1)
|
||||||
pairingParams.maSeedSystem1 else pairingParams.maSeedSystem2
|
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
|
||||||
SeedMethod.SPLIT_AND_SLIP -> {
|
SPLIT_AND_SLIP -> {
|
||||||
val x = 2 * abs(cla1 - cla2) - groupSize
|
val x = 2.0 * abs(cla1 - cla2) - groupSize
|
||||||
maxSeedingWeight - maxSeedingWeight * x / groupSize * x / groupSize
|
maxSeedingWeight - maxSeedingWeight * x / groupSize * x / groupSize
|
||||||
}
|
}
|
||||||
|
|
||||||
// The best is to get cla1 + cla2 - (groupSize - 1) close to 0
|
// The best is to get cla1 + cla2 - (groupSize - 1) close to 0
|
||||||
SeedMethod.SPLIT_AND_FOLD -> {
|
SPLIT_AND_FOLD -> {
|
||||||
val x = cla1 + cla2 - (groupSize - 1)
|
val x = cla1 + cla2 - (groupSize - 1)
|
||||||
maxSeedingWeight - maxSeedingWeight * x / (groupSize - 1) * x / (groupSize - 1)
|
maxSeedingWeight - maxSeedingWeight * x / (groupSize - 1) * x / (groupSize - 1)
|
||||||
}
|
}
|
||||||
|
|
||||||
SeedMethod.SPLIT_AND_RANDOM -> {
|
SPLIT_AND_RANDOM -> {
|
||||||
if ((2 * cla1 < groupSize && 2 * cla2 >= groupSize) || (2 * cla1 >= groupSize && 2 * cla2 < groupSize)) {
|
if ((2 * cla1 < groupSize && 2 * cla2 >= groupSize) || (2 * cla1 >= groupSize && 2 * cla2 < groupSize)) {
|
||||||
val randRange = (maxSeedingWeight * 0.2).toLong()
|
val randRange = maxSeedingWeight * 0.2
|
||||||
val rand = detRandom(randRange, p1, p2)
|
val rand = detRandom(randRange, p1, p2)
|
||||||
maxSeedingWeight - rand
|
maxSeedingWeight - rand
|
||||||
} else {
|
} else {
|
||||||
0L
|
0.0
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -260,7 +252,7 @@ sealed class Solver(
|
|||||||
// TODO understand where it is used
|
// TODO understand where it is used
|
||||||
}
|
}
|
||||||
|
|
||||||
fun avoidSameGeo(p1: Pairable, p2: Pairable): Long {
|
fun avoidSameGeo(p1: Pairable, p2: Pairable): Double {
|
||||||
val placementScoreRange = numberGroups
|
val placementScoreRange = numberGroups
|
||||||
|
|
||||||
val geoMaxCost = pairingParams.geo.avoidSameGeo
|
val geoMaxCost = pairingParams.geo.avoidSameGeo
|
||||||
@@ -310,8 +302,8 @@ sealed class Solver(
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 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.standardNX1Factor * geoRatio)
|
val dbGeoCost: Double = geoMaxCost.toDouble() * (1.0 - geoRatio) * (1.0 + pairingParams.base.nx1 * geoRatio)
|
||||||
var score: Long = pairingParams.mainMinimizeScoreDifference - dbGeoCost.toLong()
|
var score = pairingParams.main.scoreWeight - dbGeoCost
|
||||||
score = min(score, geoMaxCost)
|
score = min(score, geoMaxCost)
|
||||||
|
|
||||||
return score
|
return score
|
||||||
@@ -324,8 +316,8 @@ sealed class Solver(
|
|||||||
var pseudoRank1: Int = p1.rank
|
var pseudoRank1: Int = p1.rank
|
||||||
var pseudoRank2: Int = p2.rank
|
var pseudoRank2: Int = p2.rank
|
||||||
|
|
||||||
pseudoRank1 = min(pseudoRank1, pairingParams.hd.noHdRankThreshold)
|
pseudoRank1 = min(pseudoRank1, pairingParams.handicap.rankThreshold)
|
||||||
pseudoRank2 = min(pseudoRank2, pairingParams.hd.noHdRankThreshold)
|
pseudoRank2 = min(pseudoRank2, pairingParams.handicap.rankThreshold)
|
||||||
hd = pseudoRank1 - pseudoRank2
|
hd = pseudoRank1 - pseudoRank2
|
||||||
|
|
||||||
return clampHandicap(hd)
|
return clampHandicap(hd)
|
||||||
@@ -334,16 +326,16 @@ sealed class Solver(
|
|||||||
open fun clampHandicap(inputHd: Int): Int {
|
open fun clampHandicap(inputHd: Int): Int {
|
||||||
var hd = inputHd
|
var hd = inputHd
|
||||||
if (hd > 0) {
|
if (hd > 0) {
|
||||||
hd -= pairingParams.hd.correction
|
hd -= pairingParams.handicap.correction
|
||||||
hd = min(hd, 0)
|
hd = min(hd, 0)
|
||||||
}
|
}
|
||||||
if (hd < 0) {
|
if (hd < 0) {
|
||||||
hd += pairingParams.hd.correction
|
hd += pairingParams.handicap.correction
|
||||||
hd = max(hd, 0)
|
hd = max(hd, 0)
|
||||||
}
|
}
|
||||||
// Clamp handicap with ceiling
|
// Clamp handicap with ceiling
|
||||||
hd = min(hd, pairingParams.hd.ceiling)
|
hd = min(hd, pairingParams.handicap.ceiling)
|
||||||
hd = max(hd, -pairingParams.hd.ceiling)
|
hd = max(hd, -pairingParams.handicap.ceiling)
|
||||||
|
|
||||||
return hd
|
return hd
|
||||||
}
|
}
|
||||||
|
@@ -1,12 +1,11 @@
|
|||||||
package org.jeudego.pairgoth.pairing
|
package org.jeudego.pairgoth.pairing
|
||||||
|
|
||||||
import org.jeudego.pairgoth.model.*
|
import org.jeudego.pairgoth.model.*
|
||||||
import kotlin.math.abs
|
|
||||||
|
|
||||||
class SwissSolver(round: Int,
|
class SwissSolver(round: Int,
|
||||||
history: List<Game>,
|
history: List<Game>,
|
||||||
pairables: List<Pairable>,
|
pairables: List<Pairable>,
|
||||||
pairingParams: Pairing.PairingParams,
|
pairingParams: PairingParams,
|
||||||
placementParams: PlacementParams):
|
placementParams: PlacementParams):
|
||||||
Solver(round, history, pairables, pairingParams, placementParams) {
|
Solver(round, history, pairables, pairingParams, placementParams) {
|
||||||
|
|
||||||
@@ -23,16 +22,16 @@ class SwissSolver(round: Int,
|
|||||||
return historyHelper.numberWins
|
return historyHelper.numberWins
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getSpecificCriterionValue(p: Pairable, criterion: PlacementCriterion): Double {
|
override fun getSpecificCriterionValue(p: Pairable, criterion: Criterion): Double {
|
||||||
// TODO solve this double/int conflict
|
// TODO solve this double/int conflict
|
||||||
return when (criterion) {
|
return when (criterion) {
|
||||||
PlacementCriterion.NBW -> p.nbW
|
Criterion.NBW -> p.nbW
|
||||||
PlacementCriterion.SOSW -> p.sos
|
Criterion.SOSW -> p.sos
|
||||||
PlacementCriterion.SOSWM1 -> p.sosm1
|
Criterion.SOSWM1 -> p.sosm1
|
||||||
PlacementCriterion.SOSWM2 -> p.sosm2
|
Criterion.SOSWM2 -> p.sosm2
|
||||||
PlacementCriterion.SODOSW -> p.sodos
|
Criterion.SODOSW -> p.sodos
|
||||||
PlacementCriterion.SOSOSW -> p.sosos
|
Criterion.SOSOSW -> p.sosos
|
||||||
PlacementCriterion.CUSSW -> p.cums
|
Criterion.CUSSW -> p.cums
|
||||||
else -> -1.0
|
else -> -1.0
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user