visionforge/plotly/examples/notebooks/Issue-sim.ipynb

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
}