Skip to content

Commit

Permalink
Switch to multiplatform setup, and many other things
Browse files Browse the repository at this point in the history
  • Loading branch information
rec0de committed Jan 17, 2022
1 parent 6f49ee6 commit 83ce5a3
Show file tree
Hide file tree
Showing 36 changed files with 557 additions and 413 deletions.
13 changes: 9 additions & 4 deletions build.gradle.kts
Original file line number Diff line number Diff line change
@@ -1,17 +1,14 @@
plugins {
kotlin("js") version "1.6.10"
kotlin("multiplatform") version "1.6.10"
}

group = "me.user"
version = "1.0-SNAPSHOT"

repositories {
jcenter()
mavenCentral()
}



kotlin {
js(IR) {
binaries.executable()
Expand All @@ -21,4 +18,12 @@ kotlin {
}
}
}
jvm()
sourceSets {
val commonTest by getting {
dependencies {
implementation(kotlin("test")) // This brings all the platform dependencies automatically
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -26,21 +26,21 @@ class LocalSearch<Problem, Solution, Move>(private val strategy: LocalSearchStra
if(consideredMoves.size > explorationLimit)
consideredMoves = consideredMoves.subList(0, explorationLimit)

val bestNextStep = consideredMoves.minByOrNull { strategy.deltaScoreMove(solution, bestCost, it) } ?: break // break if no next step
val bestNextStep = consideredMoves.minByOrNull { strategy.deltaScoreMove(solution, bestCost, it) }
val bestNextStepDelta = if(bestNextStep == null) 0.0 else strategy.deltaScoreMove(solution, bestCost, bestNextStep)

// break if no improvement
// TODO: redundant second evaluation of deltaScore
if(strategy.deltaScoreMove(solution, bestCost, bestNextStep) >= 0) {
if(bestNextStepDelta >= 0) {
noImprovement++
console.log("No improvement for $noImprovement moves, limit is $noImprovementLimit")
Logger.log("No improvement for $noImprovement moves, limit is $noImprovementLimit")
if(noImprovement == noImprovementLimit)
return Pair(solution, true)
else
continue
}
else {
console.log("Applied move: $bestNextStep")
solution = strategy.applyMove(solution, bestNextStep)
Logger.log("Applied move: $bestNextStep")
solution = strategy.applyMove(solution, bestNextStep!!)
bestCost = strategy.scoreSolution(solution)
noImprovement = 0
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package binpack

abstract class Algorithm {
abstract val name: String
abstract val shortName: String
abstract fun optimize(): BinPackSolution
abstract fun optimizeStep(limit: Int): Pair<BinPackSolution,Boolean>
abstract fun init(instance: BinPackProblem)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ open class Box(val w: Int, val h: Int) {

fun asPlaced(x: Int, y: Int) = PlacedBox(w, h, x, y)

fun fits(box: Box) = box.w <= w && box.h <= h

fun rotate() = Box(h, w)
open fun clone() = Box(w, h)
override fun toString() = "[$w x $h]"
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
package binpack

import binpack.greedy.Container
import kotlin.math.ceil
import kotlin.math.max

open class BinPackSolution(val containerSize: Int, val containers: List<Collection<PlacedBox>>) {
Expand All @@ -17,12 +15,17 @@ open class BinPackSolution(val containerSize: Int, val containers: List<Collecti

// check that all boxes are in-bounds and don't intersect each other (touching is allowed)
fun verify(): Boolean {
containers.forEach { container ->
containers.forEachIndexed { i, container ->
val boxes = container.toMutableList()
while(boxes.isNotEmpty()) {
val box = boxes.removeFirst()
if(box.outOfBounds(containerSize) || boxes.any { box.intersects(it) })
return false

if(box.outOfBounds(containerSize))
throw Exception("Box $box in container $i is out of bounds")
if(boxes.any { box.intersects(it) }) {
val overlapping = boxes.first{ box.intersects(it) }
throw Exception("Boxes $box and $overlapping in container $i have overlap")
}
}
}

Expand All @@ -34,12 +37,8 @@ open class BinPackSolution(val containerSize: Int, val containers: List<Collecti
val used = container.sumOf { box -> box.w * box.h }
return used.toDouble() / available
}

fun asSequenceSolution(insertionSequence: List<Box>) = SequenceSolution(containerSize, insertionSequence, containers)
}

class SequenceSolution(containerSize: Int, val insertionSequence: List<Box>, containers: List<Collection<PlacedBox>>) : BinPackSolution(containerSize, containers) {

}

class ContainerSolution(containerSize: Int, val containerObjs: List<Container>) : BinPackSolution(containerSize, containerObjs.map{ it.boxes })
class SegmentContainerSolution(containerSize: Int, val containerObjs: List<SegmentContainer>) : BinPackSolution(containerSize, containerObjs.map{ it.boxes })
class SpaceContainerSolution(containerSize: Int, val containerObjs: List<SpaceContainer>) : BinPackSolution(containerSize, containerObjs.map{ it.boxes })
class ContainerSolution<C : Container>(containerSize: Int, val containerObjs: List<C>) : BinPackSolution(containerSize, containerObjs.map{ it.boxes })
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,15 @@ import kotlin.random.Random

object BoxGenerator {

data class ProblemSpecification(val containerSize: Int, val minW: Int, val maxW: Int, val minH: Int, val maxH: Int, val nBoxes: Int, val sample: Int = 1) {
val defaultSeed: Int
get() = containerSize + minW * maxW * minH * maxH + nBoxes

override fun toString() = "$nBoxes boxes [${minW}x$minH] to [${maxW}x$maxH] in $containerSize^2 containers"
}

fun getProblemInstance(spec: ProblemSpecification, seed: Int) = getProblemInstance(spec.containerSize, spec.minW, spec.maxW, spec.minH, spec.maxH, spec.nBoxes, seed)

fun getProblemInstance(containerSize: Int, minW: Int, maxW: Int, minH: Int, maxH: Int, nBoxes: Int) = getProblemInstance(containerSize, minW, maxW, minH, maxH, nBoxes, Random.nextInt())

fun getProblemInstance(containerSize: Int, minW: Int, maxW: Int, minH: Int, maxH: Int, nBoxes: Int, seed: Int): BinPackProblem {
Expand Down
12 changes: 12 additions & 0 deletions src/commonMain/kotlin/binpack/Container.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package binpack

interface Container {
val boxes: Collection<PlacedBox>
val size: Int
var ci: Int
val hasAccessibleSpace: Boolean
val freeSpace: Int

fun add(placedBox: PlacedBox)
fun clone(): Container
}
85 changes: 85 additions & 0 deletions src/commonMain/kotlin/binpack/SegmentContainer.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
package binpack

class SegmentContainer(
override var ci: Int,
override val size: Int,
override val boxes: MutableList<PlacedBox> = mutableListOf(),
val segmentsX: MutableList<Segment> = mutableListOf(Segment(0, 0)),
val segmentsY: MutableList<Segment> = mutableListOf(Segment(0, 0)),
freeSpaceInit: Int = -1,
accessibleSpaceInit: Int = -1
) : Container {
override val hasAccessibleSpace: Boolean
get() = segmentsX.any { it.value < size && it.start < size }

override var freeSpace: Int = freeSpaceInit
get() {
if(field < 0)
field = size * size - boxes.sumOf { it.area }
return field
}

var accessibleSpace: Int = accessibleSpaceInit
get() {
if(field < 0)
field = segmentsX.mapIndexed { idx, seg ->
val width = size - seg.value
val height = if(idx == segmentsX.size-1) size - seg.start else segmentsX[idx+1].start - seg.start
width * height
}.sum()
return field
}

override fun clone() = SegmentContainer(ci, size, boxes.toMutableList(), segmentsX.toMutableList(), segmentsY.toMutableList(), freeSpace, accessibleSpace)

override fun add(box: PlacedBox) {
boxes.add(box)
freeSpace -= box.area
invalidateAccessible()

updateSegments(segmentsX, box.y, box.h, box.x, box.w)
updateSegments(segmentsY, box.x, box.w, box.y, box.h)
}

fun getRelevantSegments(segs: List<Segment>, boxStart: Int, boxMeasurement: Int): List<Segment> {
val boxEnd = boxStart + boxMeasurement
val lastSegmentIndex = normalizeBinarySearchIndex(segs.binarySearchBy(boxEnd - 1){ it.start })
val firstSegmentIndex = normalizeBinarySearchIndex(segs.binarySearchBy(boxStart){ it.start })
return segs.subList(firstSegmentIndex, lastSegmentIndex + 1)
}

fun swap(a: Int, b: Int) {
val tmp = boxes[a]
boxes[a] = boxes[b]
boxes[b] = tmp
}

private fun invalidateAccessible() {
accessibleSpace = -1
}

private fun updateSegments(segs: MutableList<Segment>, boxStart: Int, boxMeasurement: Int, value: Int, boxValue: Int) {
val boxEnd = boxStart + boxMeasurement
val lastSegmentIndex = normalizeBinarySearchIndex(segs.binarySearchBy(boxEnd){ it.start })
val lastUsedSegment = segs[lastSegmentIndex]

val segStart = getRelevantSegments(segs, boxStart, boxMeasurement).firstOrNull { it.value <= value }?.start
?: return

if(segStart >= boxEnd)
return

segs.removeAll { it.start in segStart until boxEnd }
val insertIndex = -(segs.binarySearchBy(segStart){ it.start } + 1)
segs.add(insertIndex, Segment(segStart, value + boxValue))

if(lastUsedSegment.start < boxEnd)
segs.add(insertIndex+1, Segment(boxEnd, lastUsedSegment.value))
}

private fun normalizeBinarySearchIndex(index: Int) = if(index < 0) (-(index+1)-1) else index
}

data class Segment(val start: Int, val value: Int) {
override fun toString() = "(s: $start, v: $value)"
}
47 changes: 47 additions & 0 deletions src/commonMain/kotlin/binpack/SpaceContainer.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
package binpack

class SpaceContainer(
override var ci: Int,
override val size: Int,
override val boxes: MutableList<PlacedBox> = mutableListOf(),
val spaces: MutableList<PlacedBox> = mutableListOf(PlacedBox(size, size, 0, 0))
) : Container {
override val hasAccessibleSpace: Boolean
get() = spaces.isNotEmpty()

override val freeSpace: Int
get() = spaces.sumOf { it.area }

override fun clone() = SpaceContainer(ci, size, boxes.toMutableList(), spaces.toMutableList())

override fun add(box: PlacedBox) {
val space = spaces.firstOrNull { it.intersects(box) }!!
add(box, space)
}

fun add(box: Box, space: PlacedBox) {
var box = box
if(!space.fits(box) && space.fits(box.rotate()))
box = box.rotate()
else if(!space.fits(box))
throw Exception("Space $space does not fit box $box")

boxes.add(box.asPlaced(space.x, space.y))
spaces.remove(space)
spaces.addAll(shatter(space, box))
}

fun remove(box: PlacedBox) {
boxes.remove(box)
spaces.add(box)
}

private fun shatter(space: PlacedBox, box: Box): List<PlacedBox> {
val newSpaces = mutableListOf<PlacedBox>()
if(space.w > box.w)
newSpaces.add(PlacedBox(space.w - box.w, space.h, space.x + box.w, space.y))
if(space.h > box.h)
newSpaces.add(PlacedBox(box.w, space.h - box.h, space.x, space.y + box.h))
return newSpaces
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,42 +15,64 @@ abstract class GenericGreedyConfig : Algorithm() {

object GreedyOnlineNPFF : GenericGreedyConfig() {
override val name = "Greedy-Online-NormalPosFirstFit"
override val shortName = "Gr. Online NPFF"
override fun init(instance: BinPackProblem) {
packer = GreedyPacker(OnlineOrdering, NormalPosFirstFitPacker, instance.containerSize, instance.boxes)
}
}

object GreedyOnlineNPCT : GenericGreedyConfig() {
override val name = "Greedy-Online-NormalPosCircTouch"
override val shortName = "Gr. Online CircTouch"
override fun init(instance: BinPackProblem) {
packer = GreedyPacker(OnlineOrdering, NormalPosCircTouchPacker, instance.containerSize, instance.boxes)
}
}

object GreedyAreaDescNPFF : GenericGreedyConfig() {
override val name = "Greedy-AreaDesc-NormalPosFirstFit"
override val shortName = "Gr. AreaDesc NPFF"
override fun init(instance: BinPackProblem) {
packer = GreedyPacker(AreaDescOrdering, NormalPosFirstFitPacker, instance.containerSize, instance.boxes)
}
}

object GreedyAreaDescNPCT : GenericGreedyConfig() {
override val name = "Greedy-AreaDesc-NormalPosCircTouch"
override val shortName = "Gr. AreaDesc CircTouch"
override fun init(instance: BinPackProblem) {
packer = GreedyPacker(AreaDescOrdering, NormalPosCircTouchPacker, instance.containerSize, instance.boxes)
}
}

object GreedyAreaDescNPCTBF : GenericGreedyConfig() {
override val name = "Greedy-AreaDesc-NormalPosCircTouch-BestFit"
override val shortName = "Gr. AreaDesc CircTouch BF"
override fun init(instance: BinPackProblem) {
packer = GreedyPacker(AreaDescOrdering, GoldStandardPacker(instance), instance.containerSize, instance.boxes)
}
}

object GreedyOnlineNPCTBF : GenericGreedyConfig() {
override val name = "Greedy-Online-NormalPosCircTouch-BestFit"
override val shortName = "Gr. Online CircTouch BF"
override fun init(instance: BinPackProblem) {
packer = GreedyPacker(OnlineOrdering, GoldStandardPacker(instance), instance.containerSize, instance.boxes)
}
}

object GreedyAreaDescSpaceFF : GenericGreedyConfig() {
override val name = "Greedy-AreaDesc-SpaceFF"
override val shortName = "Gr. AreaDesc SpaceFF"
override fun init(instance: BinPackProblem) {
packer = GreedyPacker(AreaDescOrdering, SpaceFirstFitPacker, instance.containerSize, instance.boxes)
}
}

object GreedyOnlineSpaceFF : GenericGreedyConfig() {
override val name = "Greedy-Online-SpaceFF"
override val shortName = "Gr. Online SpaceFF"
override fun init(instance: BinPackProblem) {
packer = GreedyPacker(OnlineOrdering, SpaceFirstFitPacker, instance.containerSize, instance.boxes)
}
}
Loading

0 comments on commit 83ce5a3

Please sign in to comment.