Skip to content

Commit fe625e2

Browse files
author
Tomasz bla Fortuna
committed
New example: demo_case.py
Shows why genetic algorithms works. Few fixes in organism code.
1 parent b7b7072 commit fe625e2

File tree

5 files changed

+180
-8
lines changed

5 files changed

+180
-8
lines changed

demo_case.py

+163
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,163 @@
1+
#! /usr/bin/env python
2+
"""
3+
demo that tries to show one reason why genetic algorithms are successful.
4+
5+
It tries to crack open a suitcase with a few locks, each with 1-5
6+
digits. Fitness function can only determine that the lock is open, not
7+
the progress of opening the lock (how much the lock is opened)
8+
9+
Genetic algorithm can keep partial results (e.g. 1 lock open) while
10+
trying other locks.
11+
12+
In general each lock represents a partial solution to the problem
13+
described by organism.
14+
"""
15+
16+
import random
17+
from time import time
18+
import sys
19+
20+
from pygene.gene import IntGene, IntGeneRandom, IntGeneExchange
21+
from pygene.organism import Organism, GenomeSplitOrganism
22+
from pygene.population import Population
23+
24+
# Parameters
25+
locks = 8
26+
digits_in_lock = 3
27+
28+
# Generate codes
29+
codes = []
30+
for lock in range(locks):
31+
code = [random.randint(0, 9) for i in range(digits_in_lock)]
32+
codes.append(code)
33+
34+
35+
class DigitCodeGene(IntGeneRandom):
36+
"""
37+
a gene which holds a single digit, and can mutate into another digit.
38+
Mutation randomizes gene for IntGeneRandom class.
39+
"""
40+
mutProb = 0.3
41+
# mutAmt = 2
42+
randMin = 0
43+
randMax = 9
44+
45+
def __repr__(self):
46+
return str(self.value)
47+
48+
# generate a genome, one gene for each digit in suitcase
49+
genome = {}
50+
for l in range(locks):
51+
for d in range(digits_in_lock):
52+
key = '%d_%d' % (l, d)
53+
genome[key] = DigitCodeGene
54+
55+
# an organism that evolves towards the required string
56+
57+
class CodeHacker(GenomeSplitOrganism):
58+
59+
genome = genome
60+
61+
def get_code(self, lock):
62+
"Decode the chromosome (genome) into code for specific lock"
63+
code = []
64+
for d in range(digits_in_lock):
65+
key = '%d_%d' % (lock, d)
66+
code.append(self[key])
67+
return code
68+
69+
def fitness(self):
70+
"calculate fitness - number of locks opened by genome."
71+
opened_locks = 0
72+
for l in range(locks):
73+
code = self.get_code(l)
74+
if code == codes[l]:
75+
opened_locks += 1
76+
77+
# The lower the better
78+
# add 0 - 0.5 to force randomization of organisms selection
79+
fitness = float(locks - opened_locks) #+ random.uniform(0, 0.5)
80+
return fitness
81+
82+
def __repr__(self):
83+
"Display result nicely"
84+
s='<CodeHacker '
85+
for l in range(locks):
86+
code = self.get_code(l)
87+
code_str = "".join(str(i) for i in code)
88+
if code == codes[l]:
89+
s += " %s " % code_str # space - opened lock
90+
else:
91+
s += "(%s)" % code_str # () - closed lock
92+
s = s.strip() + ">"
93+
return s
94+
95+
96+
class CodeHackerPopulation(Population):
97+
"Configuration of population"
98+
species = CodeHacker
99+
100+
initPopulation = 500
101+
102+
# Tips: Leave a space for mutants to live.
103+
104+
# cull to this many children after each generation
105+
childCull = 600
106+
107+
# number of children to create after each generation
108+
childCount = 500
109+
110+
# Add this many mutated organisms.
111+
mutants = 1.0
112+
113+
# Mutate organisms after mating (better results with False)
114+
mutateAfterMating = False
115+
116+
numNewOrganisms = 0
117+
118+
# Add X best parents into new population
119+
# Good configuration should in general work without an incest.
120+
# Incest can cover up too much mutation
121+
incest = 2
122+
123+
124+
def main():
125+
126+
# Display codes
127+
print "CODES TO BREAK:",
128+
for code in codes:
129+
print "".join(str(digit) for digit in code),
130+
print
131+
132+
# Display some statistics
133+
combinations = 10**(locks * digits_in_lock)
134+
operations = 10000 * 10**6
135+
print "Theoretical number of combinations", combinations
136+
print "Optimistic operations per second:", operations
137+
print "Direct bruteforce time:", 1.0* combinations / operations / 60.0/60/24, "days"
138+
139+
# Hack the case.
140+
started = time()
141+
142+
# Create population
143+
ph = CodeHackerPopulation()
144+
145+
i = 0
146+
while True:
147+
b = ph.best()
148+
print "generation %02d: %s best=%s average=%s)" % (
149+
i, repr(b), b.get_fitness(), ph.fitness())
150+
151+
if b.get_fitness() < 1:
152+
#for org in ph:
153+
# print " ", org
154+
155+
print "cracked in ", i, "generations and ", time() - started, "seconds"
156+
break
157+
158+
sys.stdout.flush()
159+
i += 1
160+
ph.gen()
161+
162+
if __name__ == '__main__':
163+
main()

demo_string.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,7 @@ class StringHackerPopulation(Population):
7474
childCull = 10
7575

7676
# number of children to create after each generation
77-
childCount = 50
77+
childCount = 100
7878

7979
mutants = 0.25
8080

pygene/gene.py

+14
Original file line numberDiff line numberDiff line change
@@ -279,6 +279,7 @@ def mutate(self):
279279
"""
280280
self.value = self.randomValue()
281281

282+
282283
class FloatGeneRandRange(FloatGene):
283284
def __add__(self, other):
284285
"""
@@ -367,6 +368,19 @@ def __add__(self, other):
367368
return max(self.value, other.value)
368369

369370

371+
class IntGeneRandom(IntGene):
372+
"""
373+
Variant of IntGene where mutation always randomises the value
374+
"""
375+
def mutate(self):
376+
"""
377+
Randomise the gene
378+
379+
perform mutation IN-PLACE, ie don't return mutated copy
380+
"""
381+
self.value = self.randomValue()
382+
383+
370384
class IntGeneExchange(IntGene):
371385
def __add__(self, other):
372386
"""

pygene/organism.py

+2-4
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616
programming.
1717
"""
1818

19-
from random import random, randrange, choice
19+
from random import random, randrange, randint, choice
2020

2121
from gene import BaseGene, rndPair
2222
from gamete import Gamete
@@ -418,8 +418,7 @@ def mate(self, partner):
418418
# g.g.g.g.g.g
419419
# ^- split in 5:
420420
# G.G.G.G.G.G
421-
intersection = randrange(0, len(self.genome))
422-
421+
intersection = randint(0, len(self.genome))
423422
# gene by gene, we assign our and partner's genes randomly
424423
for i, name in enumerate(sorted(self.genome.keys())):
425424
cls = self.genome[name]
@@ -443,7 +442,6 @@ def mate(self, partner):
443442
# got the genotypes, now create the child organisms
444443
child1 = self.__class__(**genotype1)
445444
child2 = self.__class__(**genotype2)
446-
447445
return (child1, child2)
448446

449447
class MendelOrganism(BaseOrganism):

pygene/population.py

-3
Original file line numberDiff line numberDiff line change
@@ -231,7 +231,6 @@ def gen(self, nfittest=None, nchildren=None):
231231

232232
children.extend([child1, child2])
233233

234-
235234
# if incestuous, add in best adults
236235
if self.incest:
237236
children.extend(self[:self.incest])
@@ -269,9 +268,7 @@ def gen(self, nfittest=None, nchildren=None):
269268
children.extend(mutants)
270269
children.sort()
271270
#print "added %s mutants" % numMutants
272-
273271
# sort the children by fitness
274-
275272
# take the best 'nfittest', make them the new population
276273
self.organisms[:] = children[:nfittest]
277274

0 commit comments

Comments
 (0)