longchute

about

Toy Genetic Algorithm, Part 2

27 Dec 2013

NOTE: Further updates to this code will be posted on GitHub.

This builds on the last example by using the simulate function of a Circuit to drive a circuit simulator (Ahkab) with a Population of random Circuits made of random Components (Resistor, Inductor. and Capacitor) representing low-pass filters. It runs an AC analysis and minimizes the maximum attenuation in the pass band and maximizes the minimum attenuation in the stop band. If pre_seed is altered to pre_seed = True, the population will be seeded with two pre-existing circuits found during earlier runs.

The score code in Circuit is largely derived from this example. A Population holds population_size Circuits. When the simulate generator of a Population is iterated over, the score and simulate methods of each Circuit in the Population are called. Circuits that fail to simulate correctly are removed from the scoring before being returned.

In each generation:

mutate has a random chance of mutate_add, mutate_delete, mutate_value, mutate_node, or no effect. Recombination selects randomly from the pool of potential circuits and selects random components from the two circuits to form a new circuit of the average size of the parent circuits. All circuits being recombined are done in a single pass. If no viable scores exist in a generation, the population is repopulated.

Note: This expects a folder (ramdisk) within the current directory. Ahkab uses this as a scratch pad for simulation results. Ahkab's cvslib expects a string for filename, so passing a tempfile.SpooledTemporaryFile isn't possible. You should consider mounting a ramdisk at ramdisk for increased speed and reduced wear on your drive. For example:

$ mkdir ramdisk
$ sudo mount -t tmpfs none ramdisk

If you want to see the raw results of Ahkab's analysis, you can cat ramdisk/sim.ac, or for near-realtime viewing, you can watch -n0.1 cat ramdisk/sim.ac. You can set debug = True to increase the simulation verbosity and include mutation and recombination details.

This is tested on Python 2.7.6 with Ahkab fbd9777e7a, SciPy 0.12.1, NumPy 1.7.1, and SymPy 0.7.2.

Usage: ./filter.py

Sample Output:

[(60.202476256287355,
  [<__main__.Resistor object at 0x3925e90> R3 0 n4 100000,
   <__main__.Resistor object at 0x3925ed0> R4 n5 0 300,
   <__main__.Resistor object at 0x3925450> R5 n2 n3 330,
   <__main__.Resistor object at 0x3925f90> R6 n8 n6 160,
   <__main__.Inductor object at 0x39256d0> L0 n8 n5 6.2e-08,
   <__main__.Inductor object at 0x3925690> L1 n4 n6 3.3e-07,
   <__main__.Inductor object at 0x3929c90> L2 n1 n3 1.3e-08,
   <__main__.Capacitor object at 0x3929fd0> C1 n2 n7 8.2e-08,
   <__main__.Capacitor object at 0x3929ed0> C2 n7 n5 6.8e-06,
   <__main__.Capacitor object at 0x3929e10> C3 n6 0 3.9e-07]),

...

Top Score (generation 55): 60.2024762563

* TITLE: Untitled
R3 0 n4 100000
R4 n5 0 300
R5 n2 n3 330
R6 n8 n6 160
L0 n8 n5 6.2e-08
L1 n4 n6 3.3e-07
L2 n1 n3 1.3e-08
C1 n2 n7 8.2e-08
C2 n7 n5 6.8e-06
C3 n6 0 3.9e-07
V1 n1 0 type=vdc vdc=5 vac=1 arg=0 type=pulse v1=0 v2=1 td=5e-07 per=2 tr=1e-12 tf=1e-12 pw=1
(models and analysis directives are omitted)
log((MASB / MAPB), 10) = 4.961829
Maximum attenuation in the pass band (0-2000 Hz) is 3.33976e-05 dB
Minimum attenuation in the stop band (6500 Hz - Inf) is 3.05875 dB

filter.py

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
#!/usr/bin/env python

import random
import ahkab
from ahkab import circuit, printing, devices
import scipy
import scipy.interpolate
import numpy
import math
import time
import copy
from pprint import pprint as pp

debug = False

class Component(object):
    def __init__(self, value=None, part_id=None, ext_n1=None, ext_n2=None):
        self.value      = value
        self.part_id    = part_id
        self.ext_n1     = ext_n1
        self.ext_n2     = ext_n2

    def __repr__(self):
        return '<%s.%s object at %s> %s %s %s %s' % (
            self.__class__.__module__,
            self.__class__.__name__,
            hex(id(self)),
            self.part_id,
            self.ext_n1,
            self.ext_n2,
            self.value)

class Resistor(Component):
    common = [  #   In ohms
        10, 11, 12, 13, 15, 16, 18, 20, 22, 24, 27, 30, 33, 36, 39, 43, 47, 51, 56, 62, 68, 75, 82,
        91, 100, 110, 120, 130, 150, 160, 180, 200, 220, 240, 270, 300, 330, 360, 390, 430, 470, 
        510, 560, 620, 680, 750, 820, 910, 1000, 1100, 1200, 1300, 1500, 1600, 1800, 2000, 2200, 
        2400, 2700, 3000, 3300, 3600, 3900, 4300, 4700, 5100, 5600, 6200, 6800, 7500, 8200, 9100, 
        10000, 11000, 12000, 13000, 15000, 16000, 18000, 20000, 22000, 24000, 27000, 30000, 33000,
        36000, 39000, 43000, 47000, 51000, 56000, 62000, 68000, 75000, 82000, 91000, 100000, 
        110000, 120000, 130000, 150000, 160000, 180000, 200000, 220000, 240000, 270000, 300000, 
        330000, 360000, 390000, 430000, 470000, 510000, 560000, 620000, 680000, 750000, 820000, 
        910000, 1000000, 1100000, 1200000, 1300000, 1500000, 1600000, 1800000, 2000000, 2200000, 
        2400000, 2700000, 3000000, 3300000, 3600000, 3900000, 4300000, 4700000, 5100000, 5600000, 
        6200000, 6800000, 7500000, 8200000, 9100000, 10000000, 11000000, 12000000, 13000000, 
        15000000, 16000000, 18000000, 20000000, 22000000, 24000000, 27000000, 30000000, 33000000, 
        36000000, 39000000, 43000000, 47000000, 51000000, 56000000, 62000000, 68000000, 75000000, 
        82000000, 91000000]

class Inductor(Component):
    common = [  #   In henries
        1e-09, 1e-08, 1e-07, 1e-06, 1e-05, 1e-09, 1.1e-08, 1.1e-07, 1.1e-06, 1.1e-05, 1e-09, 
        1.2e-08, 1.2e-07, 1.2e-06, 1.2e-05, 1e-09, 1.3e-08, 1.3e-07, 1.3e-06, 1.3e-05, 2e-09, 
        1.5e-08, 1.5e-07, 1.5e-06, 1.5e-05, 2e-09, 1.6e-08, 1.6e-07, 1.6e-06, 1.6e-05, 2e-09, 
        1.8e-08, 1.8e-07, 1.8e-06, 1.8e-05, 2e-09, 2e-08, 2e-07, 2e-06, 2e-05, 2e-09, 2.2e-08, 
        2.2e-07, 2.2e-06, 2.2e-05, 2e-09, 2.4e-08, 2.4e-07, 2.4e-06, 2.4e-05, 3e-09, 2.7e-08, 
        2.7e-07, 2.7e-06, 2.7e-05, 3e-09, 3e-08, 3e-07, 3e-06, 3e-05, 3e-09, 3.3e-08, 3.3e-07, 
        3.3e-06, 3.3e-05, 4e-09, 3.6e-08, 3.6e-07, 3.6e-06, 3.6e-05, 4e-09, 3.9e-08, 3.9e-07, 
        3.9e-06, 3.9e-05, 4e-09, 4.3e-08, 4.3e-07, 4.3e-06, 4.3e-05, 5e-09, 4.7e-08, 4.7e-07, 
        4.7e-06, 4.7e-05, 5e-09, 5.1e-08, 5.1e-07, 5.1e-06, 5.1e-05, 6e-09, 5.6e-08, 5.6e-07, 
        5.6e-06, 5.6e-05, 6e-09, 6.2e-08, 6.2e-07, 6.2e-06, 6.2e-05, 7e-09, 6.8e-08, 6.8e-07, 
        6.8e-06, 6.8e-05, 8e-09, 7.5e-08, 7.5e-07, 7.5e-06, 7.5e-05, 8e-09, 8.2e-08, 8.2e-07, 
        8.2e-06, 8.2e-05, 9e-09, 8.7e-08, 8.7e-07, 8.7e-06, 8.7e-05, 9e-09, 9.1e-08, 9.1e-07, 
        9.1e-06, 9.1e-05]

class Capacitor(Component):
    common = [  #   In farads
        1e-11, 1e-10, 1e-09, 1e-08, 1e-07, 1e-06, 1.2e-11, 1.2e-10, 1.2e-09, 1.2e-08, 1.2e-07, 
        1.2e-06, 1.5e-11, 1.5e-10, 1.5e-09, 1.5e-08, 1.5e-07, 1.5e-06, 1.8e-11, 1.8e-10, 1.8e-09, 
        1.8e-08, 1.8e-07, 1.8e-06, 2.2e-11, 2.2e-10, 2.2e-09, 2.2e-08, 2.2e-07, 2.2e-06, 2.7e-11, 
        2.7e-10, 2.7e-09, 2.7e-08, 2.7e-07, 2.7e-06, 3.3e-11, 3.3e-10, 3.3e-09, 3.3e-08, 3.3e-07, 
        3.3e-06, 3.9e-11, 3.9e-10, 3.9e-09, 3.9e-08, 3.9e-07, 3.9e-06, 4.7e-11, 4.7e-10, 4.7e-09, 
        4.7e-08, 4.7e-07, 4.7e-06, 5.6e-11, 5.6e-10, 5.6e-09, 5.6e-08, 5.6e-07, 5.6e-06, 6.8e-11, 
        6.8e-10, 6.8e-09, 6.8e-08, 6.8e-07, 6.8e-06, 8.2e-11, 8.2e-10, 8.2e-09, 8.2e-08, 8.2e-07, 
        8.2e-06]

class Circuit(list):       
    def __init__(self, title="Untitled", num_nodes=0, outfile='ramdisk/sim.ac', weights=None, random=None):
        self.title      = title         #   Title of the circuit
        self.num_nodes  = num_nodes     #   Number of connection nodes
        self.outfile    = outfile       #   The filename for Ahkab's scratchpad
        self.weights    = weights if weights else [
             10.0,  #   log 10 (Minimum attenuation in the stop band / Maximum attenuation in the pass band)
            -100.0, #   Maximum attenuation in the pass band
             10.0,  #   Minimum attenuation in the stop band
            -2.0,   #   Number of nodes
            -2.0    #   Number of parts
        ]
        
        self.circuit                    = None  #   An Ahkab circuit object
        self.max_attenuation_pass_band  = None  #   Tuple of (pass band upper frequency, maximum attenuation)
        self.min_attenuation_stop_band  = None  #   Tuple of (stop band lower frequency, minimum attenuation)

        #   Table of `Component` types to lists of [subclass, init method for Ahkab, subclass count]
        self.component_types = {     
            'R': [Resistor,     'add_resistor',     0],   
            'L': [Inductor,     'add_inductor',     0],    
            'C': [Capacitor,    'add_capacitor',    0]
        }

        #   Populate the `Circuit` with random `Component`s
        if random: self.random()

    #   Creates many random `Component`s
    def random(self):
        self.num_nodes = random.randint(3, 8)

        for i in range(0, random.randint(4, 20)):
            #   Make sure to append to self (a `Circuit`) instead of overwriting with a basic `list`
            self.append(self.random_part())
            
    #    Creates a random `Component`
    def random_part(self):
        #   Create a list of all the nodes to select from, emphasizing ground
        all_nodes = (['0'] * 3) + ["n%d" % i for i in range(1, self.num_nodes)]

        a_type                  = random.choice(self.component_types.keys())
        a_class, an_init, an_id = self.component_types[a_type]
        a_part                  = a_class()
        a_part.value            = random.choice(a_part.common)
        a_part.part_id          = "%s%d" % (a_type, an_id)
        a_part.ext_n1           = random.choice(all_nodes)
        a_part.ext_n2           = random.choice(all_nodes)

        #   Increment per-subclass counter (for next `an_id`)
        self.component_types[a_type][2] += 1

        return a_part

    #   Mutations to fine-tune the `top_n` solutions
    def mutate(self):
        mutation = random.randint(0, 1000)

        if debug:
            print "Mutating (from):"
            pp(self)

        if  mutation < 400:     self.mutate_delete()
        elif mutation < 500:    self.mutate_add()
        elif mutation < 800:    self.mutate_value()
        elif mutation < 900:    self.mutate_node()

        if debug:
            print "Mutating (to):"
            pp(self)
            print "\n"

    #   Deletes a `Component`
    def mutate_delete(self):
        if debug:
            print "Mutate 'delete'"

        size = len(self)
        if size > 1:
            del self[random.randint(0, size-1)]

    #   Adds a `Component`
    def mutate_add(self):
        if debug:
            print "Mutate 'add'"

        self.append(self.random_part())

    #   Selects a new `value` for a random `Component`
    def mutate_value(self):
        if debug:
            print "Mutate 'value'"

        a_part          = self[random.randint(0, len(self)-1)]
        a_part.value    = random.choice(a_part.common)

    def mutate_node(self):
        if debug:
            print "Mutate 'node'"

        #   Create a list of all the nodes to select from, emphasizing ground
        all_nodes = (['0'] * 3) + ["n%d" % i for i in range(1, self.num_nodes)]

        #   Choose a random `Component`
        a_part = self[random.randint(0, len(self)-1)]

        #   Choose a random connection node to swap
        if random.randint(0, 1):
            a_part.ext_n1 = random.choice(all_nodes)
        else:
            a_part.ext_n2 = random.choice(all_nodes)

    #   Drive the Ahkab circuit simulator
    def simulate(self):
        #   Build a copy of the circuit for Ahkab
        self.circuit = circuit.circuit(title=self.title)

        #   Create nodes
        for i in range(0, self.num_nodes):
            self.circuit.create_node("n%d" % i)

        #   Create passive components
        for i in self:
            #   Call the init method of the current component type
            getattr(self.circuit, self.component_types[i.part_id[0]][1])(
                i.part_id, 
                i.ext_n1,
                i.ext_n2,
                **{i.part_id[0]: i.value})  #   Create 'R', 'L', or 'C' kwarg for `self.circuit.add_*`

        #   Add a voltage source
        voltage_step = devices.pulse(v1=0, v2=1, td=500e-9, tr=1e-12, pw=1, tf=1e-12, per=2)
        self.circuit.add_vsource(name="V1", ext_n1='n1', ext_n2='0', vdc=5, vac=1, function=voltage_step)

        #   Simulate the circuit with an AC analysis
        return ahkab.ac.ac_analysis(self.circuit, 1e3, 100, 1e5, 'LOG', outfile=self.outfile, verbose=0)

    def score(self):
        r = self.simulate()
        print "\n"

        #   Didn't simulate
        if not r: return None
        
        try:
            # Normalize the output to the low frequency value and convert to array
            norm_out = numpy.asarray(r['|Vn4|'].T/r['|Vn4|'].max())
            
            # Convert to dB
            norm_out_db = 20 * numpy.log10(norm_out)
            
            # Reshape to be scipy-friendly
            norm_out_db = norm_out_db.reshape((max(norm_out_db.shape),))
            
            # Convert angular frequencies to Hz and convert matrix to array
            frequencies = numpy.asarray(r['w'].T/2/math.pi)
            
            # Reshape to be scipy-friendly
            frequencies = frequencies.reshape((max(frequencies.shape),))
            
            # call scipy to interpolate
            norm_out_db_interpolated = scipy.interpolate.interp1d(frequencies, norm_out_db)
            
            self.max_attenuation_pass_band = (2e3, -1.0*norm_out_db_interpolated(2e3))
            self.min_attenuation_stop_band = (6.5e3, -1.0*norm_out_db_interpolated(6.5e3))
        #   If ANYTHING goes wrong, we just mark the circuit as bad. Lazy. (Possibly bad. Probably bad.)
        except: return None

        #   Eliminate unusable results
        if (self.max_attenuation_pass_band[1] == -0) or math.isnan(self.max_attenuation_pass_band[1]):
            return None
        if (self.min_attenuation_stop_band[1] == -0) or math.isnan(self.min_attenuation_stop_band[1]):
            return None

        if debug:
            printing.print_circuit(self.circuit)
            print "log((MASB / MAPB), 10) = %f" % math.log((self.min_attenuation_stop_band[1] / self.max_attenuation_pass_band[1]), 10)
            print "Maximum attenuation in the pass band (0-%g Hz) is %g dB" % self.max_attenuation_pass_band
            print "Minimum attenuation in the stop band (%g Hz - Inf) is %g dB\n" % self.min_attenuation_stop_band

        #   Form a draft of the final score
        a_score = [
            math.log((self.min_attenuation_stop_band[1] / self.max_attenuation_pass_band[1]), 10),
            self.max_attenuation_pass_band[1], 
            self.min_attenuation_stop_band[1], 
            self.num_nodes,
            len(self)
        ]

        #   Weight the draft version of the final score
        return sum([a_weight * a_score for (a_weight, a_score) in zip(self.weights, a_score)])

class Population(list):
    def __init__(self, population=None, population_size=200, top_n=5, generation=0):
        self.population_size    = population_size
        self.top_n              = top_n
        self.generation         = generation

        self.repopulate(population=population, population_size=self.population_size)

    def repopulate(self, population=None, population_size=None):
        if population_size: self.population_size = population_size

        #   Make sure to append to self (a `Population`) instead of overwriting with a basic `list`
        del self[:]
        if population:
            self += population
        else:
            self += [Circuit(random=True) for i in range(0, self.population_size)]

    def recombine(self, a, b):
        a_circuit = Circuit()

        #   Create a circuit with the mean number of components of `a` and `b`, choosing 
        #   components randomly from either `a` or `b`. Renumber the components to avoid overlaps.
        for i in range(0, ((len(a) + len(b)) / 2)):
            a_part          = copy.deepcopy(random.choice(random.choice([a, b])))
            a_part.part_id  = "%s%d" % (a_part.part_id[0], a_circuit.component_types[a_part.part_id[0]][2])
            a_circuit.append(a_part)
            a_circuit.component_types[a_part.part_id[0]][2] += 1

        return a_circuit

    def simulate(self):
        while True:
            scores = []

            #   Simulate and score each member of the population
            for a_member in self:
                a_score = a_member.score()
                if a_score: scores.append((a_score, a_member))

            #   Sort the results
            scores.sort(key=lambda x: -x[0])

            #   No surviving circuits in this generation
            if not scores:
                self.repopulate()
                print "Generation %d: mulligan" % self.generation
                self.generation += 1

                #   Pause for humans to read post-generation results (Optional)
                time.sleep(1)
                continue

            #   Return a tuple of (last simulated generation, [(score, circuit)...])
            yield (self.generation, scores)
            self.generation += 1

            #   Pristine copies of the `top_n`
            new_population          =   [copy.deepcopy(a_score[1]) for a_score in scores[:self.top_n]]

            #   Mutated copies of the `top_n`
            mutated_population      =   (copy.deepcopy([scores[0][1]]) * self.top_n)   + \
                                        (copy.deepcopy([scores[1][1]]) * self.top_n)   + \
                                        (copy.deepcopy([scores[2][1]]) * self.top_n)   + \
                                        [copy.deepcopy(a_score[1]) for a_score in scores[:self.top_n]]

            for a_member in mutated_population:
                a_member.mutate()

            new_population += mutated_population

            #   Recombined copies of the `top_n`
            recombined_population   =   (copy.deepcopy([scores[0][1]]) * self.top_n)   + \
                                        (copy.deepcopy([scores[1][1]]) * self.top_n)   + \
                                        (copy.deepcopy([scores[2][1]]) * self.top_n)   + \
                                        [copy.deepcopy(a_score[1]) for a_score in scores[:self.top_n]]

            for i in range(0, len(recombined_population) * 2):
                a_member    = random.choice(recombined_population)
                b_member    = random.choice(recombined_population)
                new_member  = self.recombine(a_member, b_member)
                
                if debug:
                    print "\nCircuit A\n---------"
                    pp(a_member)
                    print "\nCircuit B\n---------"
                    pp(b_member)
                    print "\nNew Circuit\n---------"
                    pp(new_member)
                    print

                new_population.append(new_member)

            #   Random new population
            new_population += [Circuit(random=True) for i in range(0, self.population_size - len(new_population))]

            #   Make sure to append to self (a `Population`) instead of overwriting with a basic `list`
            del self[:]
            self += new_population

if __name__ == "__main__":
    desired_score   = 100
    top_score       = None
    a_population    = Population()
    pre_seed        = False

    if pre_seed:
        #   Create a seed circuit (discovered by this GA and saved)
        a_circuit = Circuit()
        a_circuit.component_types['R'][2] = 7
        a_circuit.component_types['L'][2] = 4
        a_circuit.component_types['C'][2] = 4
        a_circuit += [
            Resistor(   part_id='R0',   ext_n1='0',     ext_n2='n8',    value=130000),
            Resistor(   part_id='R1',   ext_n1='n3',    ext_n2='n1',    value=510),
            Resistor(   part_id='R2',   ext_n1='0',     ext_n2='n1',    value=100000),
            Resistor(   part_id='R3',   ext_n1='0',     ext_n2='n4',    value=100000),
            Resistor(   part_id='R4',   ext_n1='n5',    ext_n2='0',     value=300),
            Resistor(   part_id='R5',   ext_n1='n2',    ext_n2='n3',    value=330),
            Resistor(   part_id='R6',   ext_n1='n8',    ext_n2='n6',    value=160),
            Inductor(   part_id='L0',   ext_n1='n8',    ext_n2='n5',    value=6.2e-08),
            Inductor(   part_id='L1',   ext_n1='n4',    ext_n2='n6',    value=3.3e-07),
            Inductor(   part_id='L2',   ext_n1='n1',    ext_n2='n3',    value=1.3e-08),
            Inductor(   part_id='L3',   ext_n1='0',     ext_n2='0',     value=1.3e-07),
            Capacitor(  part_id='C0',   ext_n1='0',     ext_n2='n4',    value=5.6e-09),
            Capacitor(  part_id='C1',   ext_n1='n2',    ext_n2='n7',    value=8.2e-08),
            Capacitor(  part_id='C2',   ext_n1='n7',    ext_n2='n5',    value=6.8e-06),
            Capacitor(  part_id='C3',   ext_n1='n6',    ext_n2='0',     value=3.9e-07)
        ]

        #   Append the seed circuit to the population
        a_population.append(a_circuit)
        a_population.population_size += 1

        #   Create a second seed circuit
        a_circuit = Circuit()
        a_circuit.component_types['R'][2] = 4
        a_circuit.component_types['L'][2] = 3
        a_circuit.component_types['C'][2] = 3
        a_circuit += [
            Resistor(   part_id='R0',   ext_n1='0',     ext_n2='n4',    value=100000),
            Resistor(   part_id='R1',   ext_n1='n5',    ext_n2='0',     value=300),
            Resistor(   part_id='R2',   ext_n1='n2',    ext_n2='n3',    value=330),
            Resistor(   part_id='R3',   ext_n1='n8',    ext_n2='n6',    value=160),
            Inductor(   part_id='L0',   ext_n1='n8',    ext_n2='n5',    value=6.2e-08),
            Inductor(   part_id='L1',   ext_n1='n4',    ext_n2='n6',    value=3.3e-07),
            Inductor(   part_id='L2',   ext_n1='n1',    ext_n2='n3',    value=1.3e-08),
            Capacitor(  part_id='C0',   ext_n1='n2',    ext_n2='n7',    value=8.2e-08),
            Capacitor(  part_id='C1',   ext_n1='n7',    ext_n2='n5',    value=6.8e-06),
            Capacitor(  part_id='C2',   ext_n1='n6',    ext_n2='0',     value=3.9e-07)
        ]

        #   Append the seed circuit to the population
        a_population.append(a_circuit)
        a_population.population_size += 1

    for (generation, scores) in a_population.simulate() :
        #   Print out the remaining circuits
        print "\n\n"
        top_score = scores[0]
        print "Scores\n------\n"
        pp(scores)
        print "\n"        

        #   Print the top score
        print "Top Score (generation %d): %s\n\n" % (generation, top_score[0])
        printing.print_circuit(top_score[1].circuit)
        print "log((MASB / MAPB), 10) = %f" % math.log((top_score[1].min_attenuation_stop_band[1] / top_score[1].max_attenuation_pass_band[1]), 10)
        print "Maximum attenuation in the pass band (0-%g Hz) is %g dB" % top_score[1].max_attenuation_pass_band
        print "Minimum attenuation in the stop band (%g Hz - Inf) is %g dB" % top_score[1].min_attenuation_stop_band

        #   Good Enough answer found
        if top_score[0] > desired_score:
            break

        #   Pause for humans to read post-generation results (Optional)
        time.sleep(2)