-
Notifications
You must be signed in to change notification settings - Fork 2
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
laowantong
authored and
laowantong
committed
Oct 26, 2017
1 parent
a2c744c
commit 7a43308
Showing
11 changed files
with
1,050 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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() | ||
|
||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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, | ||
}) | ||
|
||
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 | ||
|
||
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() |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
Oops, something went wrong.