from numpy import *
from pyec.util.TernaryString import TernaryString
import copy, binascii, struct
from pyec.distribution.basic import PopulationDistribution
from pyec.util.partitions import Segment, Partition
[docs]class Crosser(object):
""" given a list of organisms, perform recombination"""
def __init__(self, config):
self.config = config
def __call__(self, orgs, prob):
return orgs[0]
[docs]class OnePointDualCrosser(Crosser):
dual = True
def __call__(self, orgs, prob=1.0):
if random.random_sample() > prob:
return orgs[0], orgs[1]
idx = random.random_integers(0, len(orgs[0]) - 1)
return append(orgs[0][:idx], orgs[1][idx:], axis=0), append(orgs[1][:idx], orgs[0][idx:], axis=0)
[docs]class OnePointCrosser(Crosser):
def __call__(self, orgs, prob=1.0):
if random.random_sample() > prob:
return orgs[0], orgs[1]
idx = random.random_integers(0, len(orgs[0]) - 1)
return append(orgs[0][:idx], orgs[1][idx:], axis=0)
[docs]class TwoPointCrosser(Crosser):
def __call__(self, orgs, prob=1.0):
if random.random_sample() > prob:
return orgs[0]
idx1 = random.random_integers(0, len(orgs[0]) - 1)
idx2 = idx1
while idx1 == idx2:
idx2 = random.random_integers(0, len(orgs[0]) - 1)
if idx1 > idx2:
a = idx1
idx1 = idx2
idx2 = a
return append(append(orgs[0][:idx1], orgs[1][idx1:idx2],axis=0), orgs[0][idx2:], axis=0)
[docs]class DominantCrosser(Crosser):
def __call__(self, orgs, prob=1.0):
x = []
for i in xrange(len(orgs[0])):
idx = random.randint(0, len(orgs) - 1)
x.append(orgs[idx][i])
return array(x)
class DifferentialCrosser(Crosser):
def __init__(self, learningRate, crossoverProb):
self.CR = crossoverProb
self.F = learningRate
def __call__(self, orgs, prob=1.0):
y, a, b, c = orgs
d = random.randint(0, len(y))
idx2 = 0
for yi in y:
r = random.random_sample()
if idx2 == d or r < self.CR:
y[idx2] = a[idx2] + self.F * (b[idx2] - c[idx2])
idx2 += 1
return y
[docs]class Mutation(PopulationDistribution):
def __init__(self, config):
super(Mutation, self).__init__(config)
self.population = None
[docs] def mutate(self, x):
return x
def batch(self, popSize):
pop = []
for i, val in enumerate(self.population):
x,s = val
self.idx = i
z = self.mutate(x)
if isinstance(z, ndarray) and self.config.bounded:
while not self.config.in_bounds(z):
z = self.mutate(x)
pop.append(z)
return pop
def update(self, generation, population):
self.population = population
[docs]class Crossover(Mutation):
def __init__(self, selector, crossFunc, order=2):
super(Crossover, self).__init__(selector.config)
self.selector = selector
self.crosser = crossFunc
self.order = order
self.n = 1
self.dual = hasattr(crossFunc, 'dual') and crossFunc.dual
def mutate(self, x):
raise Exception, "Operation not supported"
def batch(self, popSize):
if self.dual:
psize = popSize / 4
else:
psize = popSize
crossoverProb = hasattr(self.config, 'crossoverProb') and self.config.crossoverProb or 1.0
if crossoverProb < 1e-16:
return [x for x,s in self.population1]
if hasattr(self.config, 'passArea') and self.config.passArea:
pops = [[x[0] for x,s in self.population1]]
areas = [x[1] for x,s in self.population1]
crossoverProb *= array(areas) ** (1. / self.config.dim)
else:
pops = [[x for x,s in self.population1]]
crossoverProb = crossoverProb * ones(psize) * sqrt(1./self.n)
for i in xrange(self.order - 1):
if hasattr(self.config, 'passArea') and self.config.passArea:
pops.append([x[0] for x in self.selector.batch(psize)])
else:
pops.append(self.selector.batch(psize))
pops.append(list(crossoverProb))
newpop = [self.crosser(orgs[:-1], orgs[-1]) for orgs in zip(*pops)]
if self.dual:
pop = []
for i in xrange(popSize/2):
pop.append(pops[0][i])
for x,y in newpop:
pop.append(x)
pop.append(y)
newpop = pop
if hasattr(self.config, 'passArea') and self.config.passArea:
return zip(newpop, areas)
else:
return newpop
def update(self, generation, population):
self.n = generation
self.population1 = population # copy.copy(population)
# self.selector should already be updated, update is called to give us
# the selected result
self.selector.update(generation, population)
[docs]class Gaussian(Mutation):
def __init__(self, config):
super(Gaussian, self).__init__(config)
self.sd = config.stddev
def mutate(self, x):
if hasattr(self.config, 'mutationProb'):
p = random.binomial(1, self.config.mutationProb, len(x))
return x + p * random.randn(len(x)) * self.sd
return x + random.randn(len(x)) * self.sd
def density(self, center, point):
return exp(-(1. / (2. * self.sd * self.sd)) * ((point - center) ** 2).sum()) / self.sd / sqrt(2*pi)
[docs]class DecayedGaussian(Gaussian):
def update(self, generation, population):
super(DecayedGaussian, self).update(generation, population)
self.sd = self.config.varInit * exp(-(generation * self.config.varDecay) ** self.config.varExp)
[docs]class AreaSensitiveGaussian(Mutation):
sdavg = 0
sdcnt = 0
sdmin = 1e100
gen = 1
def mutate(self, x):
center = self.config.center
scale = self.config.scale
if self.config.bounded and hasattr(self.config.in_bounds, 'extent'):
center, scale = self.config.in_bounds.extent()
else:
scale = self.config.spaceScale
y = x[0]
area = x[1]
# sd = 2 * scale / (-log(area))
if area < 0.0:
area = 1e-100
sd = self.config.varInit * scale * (area ** (1./len(y)))
if len(y) > 10:
sd /= self.gen ** .5
self.sdavg = (self.sdavg * self.sdcnt + sd) / (self.sdcnt + 1.0)
self.sdcnt += 1
self.sdmin = (sd < self.sdmin) and sd or self.sdmin
ret = y + random.randn(len(y)) * sd
if self.config.bounded and not self.config.in_bounds(ret):
ret = maximum(minimum(ret, scale+center),center-scale)
#y + random.randn(1) * sd
return ret
def density(self, info, point):
scale = self.config.scale
if self.config.bounded and hasattr(self.config.in_bounds, 'extent'):
dummy, scale = self.config.in_bounds.extent()
center, area = info
sd = self.config.varInit * scale * (area ** (1./len(point)))
return exp(-(1./ (2 * sd * sd)) * ((point - center) ** 2).sum()) / sd / sqrt(2 * pi)
def update(self, generation, population):
super(AreaSensitiveGaussian, self).update(generation, population)
self.sdcnt = 0
self.sdavg = 0.0
self.sd = self.sdmin
self.sdmin = 1e100
self.gen = generation
[docs]class Cauchy(Mutation):
def __init__(self, config):
super(Cauchy, self).__init__(config)
self.sd = config.stddev
def mutate(self, x):
return x + random.standard_cauchy(len(x))
[docs]class OnePointCauchy(Mutation):
def __init__(self, config):
super(OnePointCauchy, self).__init__(config)
self.sd = config.stddev
def mutate(self, x):
if self.idx >= len(self.population) / 2 \
and random.random_sample() < self.config.mutationProb:
idx = random.randint(0, len(x))
x[idx] += .3 * random.standard_cauchy()
return x
[docs]class DecayedCauchy(Cauchy):
def update(self, generation, population):
super(DecayedGaussian, self).update(generation, population)
self.sd = self.config.varInit * exp(-(generation * self.config.varDecay) ** self.config.varExp)
[docs]class Bernoulli(Mutation):
def __init__(self, config):
super(Bernoulli, self).__init__(config)
self.bitFlipProbs = .5
if hasattr(config, 'bitFlipProbs'):
self.bitFlipProbs = config.bitFlipProbs
def mutate(self, x):
numBytes = int(ceil(self.config.dim / 8.0))
numFull = self.config.dim / 8
initial = ''
if numBytes != numFull:
extra = self.config.dim % 8
initMask = 0
for i in xrange(extra):
initMask <<= 1
initMask |= 1
initial = struct.pack('B',initMask)
start = (1L << (self.config.dim + 1)) - 1
p = self.bitFlipProbs
if (isinstance(p, ndarray) and (p > 1.0).any()) or (not isinstance(p, ndarray) and p > 1.0): raise Exception, "bit flip probability > 1.0: " + str(p)
base = 0L
active = TernaryString(x.known,x.known)
while (isinstance(p, ndarray) and (p > 1e-16).any()) or (not isinstance(p, ndarray) and p > 1e-16):
#print p
reps = minimum(100, -floor(log2(p)))
#print p, reps
q = 2.0 ** -reps
next = start
activeReps = TernaryString(active.base, active.known)
if isinstance(p, ndarray):
for j, pj in enumerate(p):
if pj < 1e-16:
active[j] = False
#print "active: ", active.toArray(p.size)
for i in xrange(int(max(reps))):
for j,r in enumerate(reps):
if i >= r:
activeReps[j] = False
#print "activeReps: ", activeReps.toArray(p.size)
next &= activeReps.base & long(binascii.hexlify(random.bytes(numBytes)), 16)
else:
for i in xrange(int(reps)):
next &= long(binascii.hexlify(random.bytes(numBytes)), 16)
base |= next & active.base
p = (p - q) / (1.0 - q)
base = x.base & ~base | ~x.base & base
known = x.known# long(binascii.hexlify(initial + '\xff'*numFull), 16)
return TernaryString(base, known)
[docs]class DecayedBernoulli(Bernoulli):
def update(self, generation, population):
super(DecayedBernoulli, self).update(generation, population)
self.bitFlipProbs = self.config.varInit * exp(-(generation * self.config.varDecay) ** self.config.varExp)
[docs]class AreaSensitiveBernoulli(Bernoulli):
bfavg = 0
bfcnt = 0
bfmin = 1e100
def density(self, x, z):
y = x[0]
area = x[1]
bf = self.config.varInit / (-log(area))
prod = 1.0
for i in xrange(self.config.dim):
if z[i] != y[i]:
prod *= bf
else:
prod *= (1. - bf)
return prod
def mutate(self, x):
y = x[0]
area = x[1]
bf = self.config.varInit / (-log(area))
#bf = .2 ** (-log(area)/self.config.rawdim)
self.bfavg = (self.bfavg * self.bfcnt + bf) / (self.bfcnt + 1.0)
self.bfcnt += 1
self.bfmin = (bf < self.bfmin) and bf or self.bfmin
self.bitFlipProbs = minimum(bf, 1.0)
ret = super(AreaSensitiveBernoulli, self).mutate(y)
#print "bit flip prob: ", self.bitFlipProbs, area
#print "percent flipped: ", abs(y.toArray(self.config.dim) - ret.toArray(self.config.dim)).sum() / self.config.dim
return ret
def update(self, generation, population):
super(AreaSensitiveBernoulli, self).update(generation, population)
self.bitFlipProbs = self.bfavg
self.bfmin = 1e100
self.bfcnt = 0
self.bfavg = 0.0
class BinaryRbmGibbs(Bernoulli):
def mutate(self, x):
"""
x is a ternary string
the model is in self.config.fitness.model
take a few gibbs steps along the model.
"""
model = self.config.fitness.model
v = x[:model.vsize].toArray(model.vsize)
h = x[model.vsize:].toArray(model.hsize)
for i in xrange(50):
h = dot(v, model.w) + model.bh
h = random.binomial(1, 1. / (1. + exp(-h)), model.hsize)
v = dot(model.w, h) + model.bv
v = random.binomial(1, 1. / (1. + exp(-v)), model.vsize)
return TernaryString.fromArray(append(v, h, axis=0))
[docs]class EndogeneousGaussian(Mutation):
def __init__(self, config):
super(EndogeneousGaussian, self).__init__(config)
self.sd = config.varInit
self.dim = config.dim
self.scale = config.scale
self.bounded = config.bounded
self.tau = self.sd / sqrt(2*sqrt(config.populationSize))
self.tau0 = self.sd / sqrt(2*config.populationSize)
self.numEndogeneous = self.dim
def mutate(self, x):
z = 2 * self.scale * ones(self.dim)
while (abs(z) > self.scale).any():
y = x[:self.dim]
sig = x[self.dim:]
rnd = self.tau * random.randn(len(sig))
sig2 = exp(self.tau0 * random.randn(1)) * sig * exp(rnd)
z = y + sig2 * random.randn(len(y))
if not self.bounded:
break
return append(z, sig2, axis=0)