Skip to content

Commit

Permalink
add source code
Browse files Browse the repository at this point in the history
  • Loading branch information
laowantong authored and laowantong committed Oct 26, 2017
1 parent a2c744c commit 7a43308
Show file tree
Hide file tree
Showing 11 changed files with 1,050 additions and 1 deletion.
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
This repository contains all the supplementary material accompanying the paper [_Algorithms for the Bin Packing Problem with Overlapping Items_](http://arxiv.org/abs/1605.00558). It comprises:
This repository contains all the supplementary material accompanying the paper [_Algorithms for the Bin Packing Problem with Overlapping Items_](http://arxiv.org/abs/1605.00558), to appear in _Computers & Industrial Engineering_ (full reference at https://authors.elsevier.com/tracking/article/details.do?aid=4952&jid=CAIE&surname=Grange). It comprises:

- A [dataset](gauss) of 10986 random instances distributed in 1831 JSON files.
- The Python 2.7 implementation of our algorithms. This code was mainly written for internal use. It is provided _as is_, without any guarantee, maintenance or further support.
- Our [numerical analysis of this dataset](http://nbviewer.jupyter.org/github/pagination-problem/pagination/blob/master/analysis.ipynb). This so-called Jupyter Notebook is self-contained and interactive. Reevaluating its cells, testing another ideas or carrying out your own experiments may require some installations on your machine. We recommend using the [Anaconda Distribution](https://www.continuum.io/downloads), which installs Python, the Jupyter Notebook, and other commonly used packages for scientific computing and data science.
- The [MIT License](LICENSE).
- This document.
Expand Down
187 changes: 187 additions & 0 deletions code/genetic.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,187 @@
#
# genetic.py
#

import random

class Individual:
chromosome = None
score = None
alleles = (0,1)
# length = 20
separator = ''
optimization = "MINIMIZE"

def __init__(self, chromosome = None):
self.chromosome = chromosome or self.makechromosome()
self.score = None # set during evaluation

def makechromosome(self):
"makes a chromosome from randomly selected alleles."
return [random.choice(self.alleles) for gene in range(self.length)]

def evaluate(self, optimum=None):
"this method MUST be overridden to evaluate individual fitness score."
pass

def crossover(self, other):
"override this method to use your preferred crossover method."
return self._twopoint(other)

def mutate(self, gene):
"override this method to use your preferred mutation method."
self._pick(gene)

# sample mutation method
def _pick(self, gene):
"chooses a random allele to replace this gene's allele."
self.chromosome[gene] = random.choice(self.alleles)

# sample crossover method
def _twopoint(self, other):
"creates offspring via two-point crossover between mates."
left, right = self._pickpivots()
def mate(p0, p1):
chromosome = p0.chromosome[:]
chromosome[left:right] = p1.chromosome[left:right]
child = p0.__class__(chromosome)
child._repair(p0, p1)
return child
return mate(self, other), mate(other, self)

# some crossover helpers ...
def _repair(self, parent1, parent2):
"override this method, if necessary, to fix duplicated genes."
pass

def _pickpivots(self):
left = random.randrange(1, self.length-2)
right = random.randrange(left, self.length-1)
return left, right

#
# other methods
#

def __repr__(self):
"returns string representation of self"
return '<%s chromosome="%s" score=%s>' % \
(self.__class__.__name__,
self.separator.join(map(str,self.chromosome[:20])), self.score)

def __cmp__(self, other):
if self.optimization == "MINIMIZE":
return cmp(self.score, other.score)
else:
return cmp(other.score, self.score)

def copy(self):
twin = self.__class__(self.chromosome[:])
twin.score = self.score
return twin


class Environment(object):
def __init__(self, kind, population=None, size=100, maxgenerations=100,
crossover_rate=0.90, mutation_rate=0.01, optimum=None, verbose = True):
self.kind = kind
self.optimum = optimum
if population:
self.population = [self.kind(chromosome) for chromosome in population]
else:
self.population = self._makepopulation(size)
self.size = len(self.population)
for individual in self.population:
individual.evaluate(self.optimum)
self.crossover_rate = crossover_rate
self.mutation_rate = mutation_rate
self.maxgenerations = maxgenerations
self.generation = 0
self.verbose = verbose
# self.population[0].score *= 2
self.previousBest = self.population[0]
self.report()

def _makepopulation(self,size):
return [self.kind() for _ in range(size)]

def run(self):
while not self._goal():
self.step()

def _goal(self):
return self.generation > self.maxgenerations or \
self.best.score == self.optimum

def step(self):
self.population.sort()
self._crossover()
self.generation += 1
self.report()

def _crossover(self):
next_population = [self.best.copy()]
while len(next_population) < self.size:
mate1 = self._select()
if random.random() < self.crossover_rate:
mate2 = self._select()
offspring = mate1.crossover(mate2)
else:
offspring = [mate1.copy()]
for individual in offspring:
self._mutate(individual)
individual.evaluate(self.optimum)
next_population.append(individual)
self.population = next_population[:self.size]

def _select(self):
"override this to use your preferred selection method"
return self._tournament()

def _mutate(self, individual):
gene = 0
while gene < individual.length: # no for loop: the mutation can result in a different length
if random.random() < self.mutation_rate:
individual.mutate(gene)
gene += 1

#
# sample selection method
#
def _tournament(self, size=8, choosebest=0.90):
competitors = [random.choice(self.population) for i in range(size)]
competitors.sort()
if random.random() < choosebest:
return competitors[0]
else:
return random.choice(competitors[1:])

def best():
doc = "individual with best fitness score in population."
def fget(self):
return self.population[0]
return locals()
best = property(**best())

def report(self):
if self.verbose and self.previousBest.score != self.best.score:
# print "="*70
# print "generation: ", self.generation
print self.generation,self.best.length,self.best.score
# print "best: ", self.best
# self.previousBest = self.best



if __name__ == "__main__":
class OneMax(Individual):
optimization = "MAXIMIZE"
def evaluate(self, optimum=None):
self.score = sum(self.chromosome)
def mutate(self, gene):
self.chromosome[gene] = 1 - self.chromosome[gene] # bit flip

env = Environment(OneMax, maxgenerations=1000, optimum=30)
env.run()


141 changes: 141 additions & 0 deletions code/solve.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
#!/usr/bin/env python
# encoding: utf-8

import json
import random
import collections
import os
import re
import time

import solver_tools

import solve_ff
import solve_best_fusion
import solve_genetic
import solve_genetic_group
import solve_overload_and_remove
import solve_overload_and_remove_presort

class Benchmark:

def __init__(self):
self.globalResults = []
self.globalHeader = None

def newInstance(self,testSet,name,testSetCount):
print "Solving %s (%s)" % (name,testSetCount)
self.filename = name
if "solvers" not in testSet:
testSet["solvers"] = {}
if testSet["pageCount"]:
algoName = re.sub(" \[.+", "", testSet.get("approximation","given"))
self.instancePaginations = [solver_tools.Pagination(testSet["capacity"],algoName)]
print "%(pageCount)s pages (current best)" % testSet
for page in testSet["paginations"]:
self.instancePaginations[-1].newPage([testSet["tiles"][tileIndex] for tileIndex in page])
testSet["solvers"][algoName] = testSet["pageCount"]
else:
self.instancePaginations = []
self.testSet = testSet

def add(self, solver, testSet, **kargs):
# if solver.name in testSet["times"] and testSet["times"][solver.name] < 500:
# print "aborted", solver.name
# return
self.dirty = True
random.seed(42)
time_1 = time.time()
pagination = solver.run(testSet, **kargs)
time_2 = time.time()
elapsed_time = round(time_2 - time_1, 2)
rawPageCount = len(pagination)
pagination.decantPages()
pagination.decantConnectedComponents()
pagination.decantTiles()
print "%s -> %s pages" % (rawPageCount,len(pagination)),
print ("" if pagination.isValid() else "*** invalid ***")
self.instancePaginations.append(pagination)
if pagination.isValid():
currentPageCount = self.testSet["solvers"].get(pagination.algoName, self.testSet["tileCount"])
if len(pagination) < currentPageCount:
self.testSet["solvers"][pagination.algoName] = len(pagination)
self.dirty = True
if pagination.algoName not in self.testSet["times"] or elapsed_time < self.testSet["times"][pagination.algoName]:
self.testSet["times"][pagination.algoName] = elapsed_time
self.dirty = True

def printInstanceConclusion(self):
if self.globalHeader is None:
self.globalHeader = [pagination.algoName for pagination in self.instancePaginations]
(minPageCount,bestPagination) = min([(len(pagination),pagination) for pagination in self.instancePaginations if pagination.isValid()])
bestAlgos = set(pagination.algoName for pagination in self.instancePaginations if pagination.isValid() and len(pagination)==minPageCount)
print "Best algorithms (%s pages): %s" % (minPageCount,", ".join(bestAlgos))
self.globalResults.append({
"bestPagination": bestPagination,
"minPageCount": minPageCount,
"pageCounts": [(len(pagination) if pagination.isValid() else None) for pagination in self.instancePaginations],
"bestAlgos": bestAlgos,
})
print

def mayUpdateTestSetsWithNewPaginations(self,testSets,filename):
improvementCount = 0
for (testSet,result) in zip(testSets,self.globalResults[-len(testSets):]):
if testSet["pageCount"] == 0 or testSet["pageCount"] > result["minPageCount"]:
best = result["bestPagination"]
d = best.getInfo(testSet["tiles"])
d["approximation"] = "%s [%s]" % (best.algoName,time.strftime('%Y-%m-%d %H:%M:%S'))
testSet.update(d)
improvementCount += 1
text = solver_tools.testSetsToText(testSets)
if self.dirty:
open(filename,"w").write(text)
if improvementCount:
print "Dumped %s with %s improvement(s)" % (filename,improvementCount)
else:
print "Dumped %s" % filename
print

def printGeneralConclusion(self):
print "%s\nBest algorithms for these %s instances:\n%s" % ("*"*40,len(self.globalResults),"*"*40)
bestInstanceCounts = [0] * len(self.globalHeader)
for (i,name) in enumerate(self.globalHeader):
for globalResult in self.globalResults:
if name in globalResult["bestAlgos"]:
bestInstanceCounts[i] += 1
print "%s: %3.2f%%" % (name,100.0*bestInstanceCounts[i]/len(self.globalResults))


def files_between(directory_path, min_prefix, max_prefix):
for name in os.listdir(directory_path):
if name.endswith(".json") and min_prefix <= name <= max_prefix:
yield os.path.join(directory_path, name)

def main():
directory_path = "../gauss"
min_prefix = "C0"
max_prefix = "C1"
b = Benchmark()
filenames = list(files_between(directory_path, min_prefix, max_prefix))
random.shuffle(filenames)
for filename in filenames:
testSets = json.loads(open(filename).read())
b.dirty = False
for (testSetCount,testSet) in enumerate(testSets, 1):
if "times" not in testSet:
testSet["times"] = {}
b.newInstance(testSet,filename,"%s/%s" % (testSetCount,len(testSets)))
b.add(solve_ff, testSet)
b.add(solve_genetic, testSet, verbose=False, size=80, maxgenerations=50)
b.add(solve_best_fusion, testSet)
b.add(solve_overload_and_remove, testSet)
# b.add(solve_overload_and_remove_presort, testSet)
b.add(solve_genetic_group, testSet, size=80, maxgenerations=50, verbose=False)
b.printInstanceConclusion()
# b.mayUpdateTestSetsWithNewPaginations(testSets,filename)
b.printGeneralConclusion()


if __name__=="__main__":
main()
22 changes: 22 additions & 0 deletions code/solve_best_fusion.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
#!/usr/bin/env python
# encoding: utf-8

import solver_tools

name = "BestFusion"

def run(testSet):
pages = solver_tools.Pagination(testSet["capacity"], name)
tiles = solver_tools.Batch(testSet["tiles"])
for tile in tiles:
candidates = [page for page in pages if tile.canFitIn(page)]
if candidates:
(minWeightedCost,bestPage) = min((tile.weightedCostIn(page),page) for page in candidates)
if minWeightedCost < len(tile):
bestPage.add(tile)
else:
pages.newPage(tile)
else:
pages.newPage(tile)
return pages

19 changes: 19 additions & 0 deletions code/solve_ff.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
#!/usr/bin/env python
# encoding: utf-8

import solver_tools


name = "FirstFit"

def run(testSet):
pages = solver_tools.Pagination(testSet["capacity"], name)
tiles = solver_tools.Batch(testSet["tiles"])
for tile in tiles:
for page in pages:
if tile.canFitIn(page):
page.add(tile)
break
else:
pages.newPage(tile)
return pages
Loading

0 comments on commit 7a43308

Please sign in to comment.