299 lines
8.2 KiB
Plaintext
299 lines
8.2 KiB
Plaintext
{
|
|
"cells": [
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": null,
|
|
"metadata": {},
|
|
"outputs": [],
|
|
"source": [
|
|
"%use plotly"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": [
|
|
"# API"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": null,
|
|
"metadata": {},
|
|
"outputs": [],
|
|
"source": [
|
|
"enum class Severity(val penalty: Double){\n",
|
|
" MINOR(1.0),\n",
|
|
" MAJOR(2.0),\n",
|
|
" CRITICAL(3.0)\n",
|
|
"}\n",
|
|
"\n",
|
|
"enum class State{\n",
|
|
" OPEN,\n",
|
|
" ASSIGNED,\n",
|
|
" RESOLVED\n",
|
|
"}\n",
|
|
"\n",
|
|
"data class Issue(val id: String, val dayCreated: Int, val severity: Severity, val complexity: Int, \n",
|
|
" var state: State = State.OPEN, var dayAssigned: Int? = null, var dayResolved: Int? = null){\n",
|
|
" fun activate(day: Int){ \n",
|
|
" state = State.ASSIGNED\n",
|
|
" dayAssigned = day\n",
|
|
" }\n",
|
|
" \n",
|
|
" fun resolve(day: Int){\n",
|
|
" state = State.RESOLVED\n",
|
|
" dayResolved = day\n",
|
|
" }\n",
|
|
" \n",
|
|
" internal fun tryResolve(day: Int){\n",
|
|
" if(state == State.ASSIGNED && day >= (dayAssigned ?: 0) + complexity ){\n",
|
|
" resolve(day)\n",
|
|
" }\n",
|
|
" }\n",
|
|
"}\n",
|
|
"\n",
|
|
"class Worker(val name: String){\n",
|
|
" var currentIssue: Issue? = null\n",
|
|
" private set\n",
|
|
" \n",
|
|
" fun isBusy(): Boolean = currentIssue != null\n",
|
|
" \n",
|
|
" fun update(day: Int){\n",
|
|
" currentIssue?.tryResolve(day)\n",
|
|
" if(currentIssue?.state == State.RESOLVED){\n",
|
|
" currentIssue = null\n",
|
|
" }\n",
|
|
" }\n",
|
|
" \n",
|
|
" fun assign(day: Int, issue: Issue){\n",
|
|
" if(currentIssue != null) error(\"Can't assign work to a worker which is busy\")\n",
|
|
" issue.activate(day)\n",
|
|
" currentIssue = issue\n",
|
|
" }\n",
|
|
"}\n",
|
|
"\n",
|
|
"interface IssueGenerator{\n",
|
|
" fun generate(day: Int): List<Issue>\n",
|
|
"}\n",
|
|
"\n",
|
|
"interface Strategy{\n",
|
|
" fun selectIssue(day: Int, issues: List<Issue>): Issue?\n",
|
|
"}\n",
|
|
"\n",
|
|
"class WorkResult(val issues: List<Issue>, val workers: Int, val days: Int)\n",
|
|
"\n",
|
|
"@OptIn(kotlin.ExperimentalStdlibApi::class)\n",
|
|
"fun simulate(generator: IssueGenerator, strategy: Strategy, numWorkers: Int = 10, days: Int = 100): WorkResult{\n",
|
|
" val workers = (0 until numWorkers).map{Worker(\"worker $it\")}\n",
|
|
" val issues = buildList<Issue>{\n",
|
|
" for(day in 0 until days){\n",
|
|
" //update all workers\n",
|
|
" workers.forEach { it.update(day) }\n",
|
|
" //generate new issues\n",
|
|
" val newIssues = generator.generate(day)\n",
|
|
" addAll(newIssues)\n",
|
|
" //Select all free workers\n",
|
|
" workers.filter { !it.isBusy() }.forEach { worker->\n",
|
|
" val unasigned = filter { it.state == State.OPEN }\n",
|
|
" val anIssue = strategy.selectIssue(day, unasigned) //select an issue to assign from all unassigned issues\n",
|
|
" if(anIssue != null){\n",
|
|
" worker.assign(day, anIssue)\n",
|
|
" }\n",
|
|
" }\n",
|
|
" }\n",
|
|
" }\n",
|
|
" return WorkResult(issues, numWorkers, days)\n",
|
|
"}\n",
|
|
"\n",
|
|
"fun WorkResult.computeLoss(): Double = issues.sumByDouble { ((it.dayResolved ?: days) - it.dayCreated)*it.severity.penalty } / days / workers / issues.size"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": [
|
|
"# Implementations"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": null,
|
|
"metadata": {},
|
|
"outputs": [],
|
|
"source": [
|
|
"import kotlin.random.Random\n",
|
|
"import kotlin.math.pow\n",
|
|
"\n",
|
|
"/**\n",
|
|
"* Generate one random issue per day\n",
|
|
"*/\n",
|
|
"class RandomIssueGenerator(seed: Long, val issuesPerDay: Int = 4 ) : IssueGenerator{\n",
|
|
" private val random = Random(seed)\n",
|
|
" override fun generate(day: Int): List<Issue>{\n",
|
|
" return List(issuesPerDay){\n",
|
|
" val severity = Severity.values()[random.nextInt(3)]\n",
|
|
" val complexity = random.nextInt(15)\n",
|
|
" Issue(\"${day}_${it}\", day, severity, complexity)\n",
|
|
" }\n",
|
|
" }\n",
|
|
"}\n",
|
|
"\n",
|
|
"object TakeOldest: Strategy{\n",
|
|
" override fun selectIssue(day: Int, issues: List<Issue>): Issue?{\n",
|
|
" return issues.minByOrNull { it.dayCreated }\n",
|
|
" }\n",
|
|
"}\n",
|
|
"\n",
|
|
"class TakeRandom(seed: Long): Strategy{\n",
|
|
" private val random = Random(seed)\n",
|
|
" override fun selectIssue(day: Int, issues: List<Issue>): Issue?{\n",
|
|
" if(issues.isEmpty()) return null\n",
|
|
" return issues.random(random)\n",
|
|
" }\n",
|
|
"}\n",
|
|
"\n",
|
|
"object TakeCritical: Strategy{\n",
|
|
" override fun selectIssue(day: Int, issues: List<Issue>): Issue?{\n",
|
|
" return issues.maxByOrNull { it.severity.penalty*(day - it.dayCreated) }\n",
|
|
" }\n",
|
|
"}\n",
|
|
"\n"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": [
|
|
"# Simulate lossseverity"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": null,
|
|
"metadata": {},
|
|
"outputs": [],
|
|
"source": [
|
|
"val seed = 89L\n",
|
|
"val days = 100\n",
|
|
"val workers = 10"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": [
|
|
"## Take oldest"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": null,
|
|
"metadata": {},
|
|
"outputs": [],
|
|
"source": [
|
|
"val result = simulate(RandomIssueGenerator(seed, workers),TakeOldest, days = days)\n",
|
|
"//result.issues.forEach { println(it)}\n",
|
|
"result.computeLoss()"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": [
|
|
"## Take random"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": null,
|
|
"metadata": {},
|
|
"outputs": [],
|
|
"source": [
|
|
"simulate(RandomIssueGenerator(seed, workers),TakeRandom(seed), days = days).computeLoss()"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": [
|
|
"## Take critical"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": null,
|
|
"metadata": {},
|
|
"outputs": [],
|
|
"source": [
|
|
"simulate(RandomIssueGenerator(seed, workers), TakeCritical, days = days).computeLoss()"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": null,
|
|
"metadata": {},
|
|
"outputs": [],
|
|
"source": [
|
|
"val seeds = List(1000){Random.nextLong()}\n",
|
|
"\n",
|
|
"Plotly.plot{\n",
|
|
" trace{\n",
|
|
" x.numbers = seeds.map{ seed -> simulate(RandomIssueGenerator(seed, workers), TakeOldest, days = days).computeLoss()}\n",
|
|
" name = \"oldest\"\n",
|
|
" type = TraceType.histogram\n",
|
|
" }\n",
|
|
" trace{\n",
|
|
" x.numbers = seeds.map{ seed -> simulate(RandomIssueGenerator(seed, workers), TakeRandom(seed), days = days).computeLoss()}\n",
|
|
" name = \"random\"\n",
|
|
" type = TraceType.histogram\n",
|
|
" }\n",
|
|
" trace{\n",
|
|
" x.numbers = seeds.map{ seed -> simulate(RandomIssueGenerator(seed, workers), TakeCritical, days = days).computeLoss()}\n",
|
|
" name = \"critical\"\n",
|
|
" type = TraceType.histogram\n",
|
|
" }\n",
|
|
" layout{\n",
|
|
" title = \"Loss distribtution\"\n",
|
|
" xaxis {\n",
|
|
" title = \"Loss\"\n",
|
|
" }\n",
|
|
" }\n",
|
|
"}"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": null,
|
|
"metadata": {},
|
|
"outputs": [],
|
|
"source": []
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": null,
|
|
"metadata": {},
|
|
"outputs": [],
|
|
"source": []
|
|
}
|
|
],
|
|
"metadata": {
|
|
"kernelspec": {
|
|
"display_name": "Kotlin",
|
|
"language": "kotlin",
|
|
"name": "kotlin"
|
|
},
|
|
"language_info": {
|
|
"codemirror_mode": "text/x-kotlin",
|
|
"file_extension": ".kt",
|
|
"mimetype": "text/x-kotlin",
|
|
"name": "kotlin",
|
|
"nbconvert_exporter": "",
|
|
"pygments_lexer": "kotlin",
|
|
"version": "1.5.30-dev-598"
|
|
}
|
|
},
|
|
"nbformat": 4,
|
|
"nbformat_minor": 4
|
|
}
|