Files
pyfa/eos/slotFill.py
DarkPhoenix fd36a0b172 Replace submodules with actual files
Submodules never were actually useful
2013-06-10 22:12:34 +04:00

224 lines
8.0 KiB
Python
Executable File

#===============================================================================
# Copyright (C) 2010 Diego Duclos
#
# This file is part of eos.
#
# eos is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License as published by
# the Free Software Foundation, either version 2 of the License, or
# (at your option) any later version.
#
# eos is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with eos. If not, see <http://www.gnu.org/licenses/>.
#===============================================================================
from eos.types import Slot, Fit, Module, State
import random
import copy
import math
import bisect
import itertools
import time
class SlotFill(object):
def __init__(self, original, modules, attributeWeights=None, propertyWeights=None, specificWeights=None, defaultState = State.ACTIVE):
self.original = original
self.attributeWeights = attributeWeights or {}
self.propertyWeights = propertyWeights or {}
self.specificWeights = specificWeights or []
self.state = State.ACTIVE
self.modules = map(self.__newModule, modules)
def __newModule(self, item):
m = Module(item)
m.state = self.state
return m
def __getMetaParent(self, item):
metaGroup = item.metaGroup
return item if metaGroup is None else metaGroup.parent
def fitness(self, fit, chromosome):
modList = fit.modules
modAttr = fit.ship.getModifiedItemAttr
modList.extend(chromosome)
fit.clear()
fit.calculateModifiedAttributes()
if not fit.fits:
del modList[-len(chromosome):]
return 0
weight = 0
for attr, value in self.attributeWeights.iteritems():
weight += modAttr(attr) * (value if value >= 0 else 1.0 / -value)
for prop, value in self.propertyWeights.iteritems():
weight += getattr(fit, prop) * (value if value >= 0 else 1.0 / -value)
for specific in self.specificWeights:
weight += specific(fit)
totalVars = (fit.ship.getModifiedItemAttr("powerOutput"),
fit.ship.getModifiedItemAttr("cpuOutput"),
fit.ship.getModifiedItemAttr('upgradeCapacity'))
usedVars = (fit.pgUsed, fit.cpuUsed, fit.calibrationUsed)
total = 0
used = 0
for tv, uv in zip(totalVars, usedVars):
if uv > tv:
del modList[-len(chromosome):]
return 0
del modList[-len(chromosome):]
return weight
def run(self, elite = 0.05, crossoverChance = 0.8, slotMutationChance = 0.5, typeMutationChance = 0.5):
#Use a copy of the original for all our calcs. We don't want to damage it
fit = copy.deepcopy(self.original)
fit.unfill()
#First of all, lets check the number of slots we got to play with
chromLength = -1
slotAmounts = {}
for type in Slot.getTypes():
slot = Slot.getValue(type)
amount = fit.getSlotsFree(slot)
if amount > 0:
slotAmounts[slot] = amount
chromLength += amount
if not slotAmounts:
#Nothing to do, joy
return
slotModules = {}
metaModules = {}
for slotType in slotAmounts:
slotModules[slotType] = modules = []
for module in self.modules:
#Store the variations of each base for ease and speed
metaParent = self.__getMetaParent(module.item)
metaList = metaModules.get(metaParent)
if metaList is None:
metaList = metaModules[metaParent] = []
metaList.append(module)
#Sort stuff by slotType for ease and speed
slot = module.slot
if slot in slotModules:
slotModules[slot].append(module)
for slotType, modules in slotModules.iteritems():
if len(modules) == 0:
chromLength -= slotAmounts[slotType]
del slotAmounts[slotType]
#Now, we need an initial set, first thing to do is decide how big that set will be
setSize = 10
#Grab some variables locally for performance improvements
rchoice = random.choice
rrandom = random.random
rrandint = random.randint
bbisect = bisect.bisect
ccopy = copy.copy
#Get our list for storage of our chromosomes
chromosomes = []
# Helpers
weigher = lambda chromosome: (self.fitness(fit, chromosome), chromosome)
keyer = lambda info: info[0]
eliteCutout = int(math.floor(setSize * (1 - elite)))
lastEl = setSize - 1
#Generate our initial set entirely randomly
#Subtelies to take in mind:
# * modules of the same slotType are kept together for easy cross-overing
state = self.state
for _ in xrange(setSize):
chrom = []
for type, amount in slotAmounts.iteritems():
for _ in xrange(amount):
chrom.append(rchoice(slotModules[type]))
chromosomes.append(weigher(chrom))
#Sort our initial set
chromosomes.sort(key=keyer)
currentGeneration = chromosomes
#Yield the best result from our initial set, this is gonna be pretty bad
yield currentGeneration[lastEl]
#Setup's done, now we can actualy apply our genetic algorithm to optimize all this
while True:
moo = time.time()
#First thing we do, we're gonna be elitair
#Grab the top x%, we'll put em in the next generation
nextGeneration = []
for i in xrange(lastEl, eliteCutout - 1, -1):
nextGeneration.append(currentGeneration[i])
#Figure out our ratios to do our roulette wheel
fitnessList = map(keyer, currentGeneration)
totalFitness = float(sum(fitnessList))
curr = 0
ratios = []
for fitness in fitnessList:
curr += fitness
ratios.append(curr / (totalFitness or 1))
t = 0
#Do our pairing
for _ in xrange(0, eliteCutout):
# Crossover chance
mother = currentGeneration[bbisect(ratios, rrandom())][1]
father = currentGeneration[bbisect(ratios, rrandom())][1]
if rrandom() <= crossoverChance:
crosspoint = rrandint(0, chromLength)
luke = mother[:crosspoint] + father[crosspoint:]
else:
luke = father
#Chance for slot mutation
if rrandom() <= slotMutationChance:
target = rrandint(0, chromLength)
mod = luke[target]
luke[target] = rchoice(slotModules[mod.slot])
if rrandom() <= typeMutationChance:
#Mutation of an item to another one of the same type
target = rrandint(0, chromLength)
mod = luke[target]
vars = metaModules[self.__getMetaParent(mod.item)]
luke[target] = rchoice(vars)
tt = time.time()
nextGeneration.append(weigher(luke))
t += time.time() - tt
print "time spent weighing: ", t
nextGeneration.sort(key=keyer)
currentGeneration = nextGeneration
print "total time spent this iteration:", time.time() - moo
yield currentGeneration[lastEl]