-
Notifications
You must be signed in to change notification settings - Fork 0
/
prion.py
777 lines (689 loc) · 30.7 KB
/
prion.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
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
#!/usr/local/bin/python
### prion.py - An extensible framework for LGP
### Copyright (C) 2018 Christopher J. Crowe
###
### This program is free software: you can redistribute it and/or modify
### it under the terms of the GNU General Public License as published by
### the Free Software Foundation, either version 3 of the License, or
### (at your option) any later version.
###
### This program 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 General Public License for more details.
###
### You should have received a copy of the GNU General Public License
### along with this program. If not, see <https://www.gnu.org/licenses/>
import random
# This is usually a minimal set of functions for the generated program to use.
# Can't imagine many reasons to not assume they will be included
# unless you imagine a good reason to do so, you need these for an actual system
# To really function at all.
# The "machine" (or processor), at its core is a simulated stack processor. In certain cases,
# stack-based machines demonstrate superior performance. In most of the remaineder, they
# seem to function no better or worse. Here is the naming scheme that will be utilized
# by the registers of this stack-based VM. The final implementation of the processor is uknown,
# so at this point in the design, we need to be careful about implementation decisions/constraints.
# I want prion to almost boot-strap itself up so the priomordial functions are going to be
# instantiated as fully-realized and executable functions for the interpreter. I do want some
# flexibility at this point. But this is a very critical point in the base design so some
# aspects bear dwelling upon:
# -- At this point we can assume that we will have an array of stacks which will be utilized
# as the registers of the processor
# -- These registers are capable of interpreting some intrinsic things or concepts. Such as the
# "push" and "pop" of the stack operations.
# -- We also assume the inputs are a RO bus from which a single value is transmitted when
# addressed by index
# Should you find any collisions in your local namespace for some odd reason, you can globally alter
# the naming conventions from here and the system can bootstrap itself out of you namespace
PRION_OUTPUT_NAME = "PRION_STACK"
PRION_INPUT_NAME = "PRION_WIRE"
PRION_ERC_NAME = "PRION_ERC"
# These next two will "feel" a tad strange, but the are part of the bootstapping process for this system
# The code will bootstrap itself everytime it comes up so please pay attention if you're changing anything
PRION_TAG_DELIMITER = "&"
PRION_OUTPUT_LBL = PRION_TAG_DELIMITER + "OUT"
PRION_INPUT_LBL = PRION_TAG_DELIMITER + "WIRE"
PRION_ERC_LBL = PRION_TAG_DELIMITER + "ERC"
PRION_FUNCTION_LBL = PRION_TAG_DELIMITER + "FUNC"
PRION_LABEL_ARRAY = [PRION_OUTPUT_LBL, PRION_INPUT_LBL, PRION_FUNCTION_LBL]
# Now we actually start writing the code for our primordial functions
PRION_OPCODE_INT = -1
PRION_OPCODE_NOP = 0
# prion_INP creation here
PRION_OPCODE_INP = PRION_OPCODE_NOP + 1
PRION_FUNCNAME_INP = "PRION_INP"
PRION_INP_FUNCTION_TEMP = """
def &FUNC(&OUT , &WIRE):
&OUT.append(&WIRE)
return
"""
PRION_INP_FUNCTION_DEF = PRION_INP_FUNCTION_TEMP.replace(PRION_FUNCTION_LBL, PRION_FUNCNAME_INP)
PRION_INP_FUNCTION_DEF = PRION_INP_FUNCTION_DEF.replace(PRION_OUTPUT_LBL, PRION_OUTPUT_NAME)
PRION_INP_FUNCTION_DEF = PRION_INP_FUNCTION_DEF.replace(PRION_INPUT_LBL, PRION_INPUT_NAME)
###############################
# Indentaton matters here #
# please be careful if #
# this is moved around #
###############################
exec(PRION_INP_FUNCTION_DEF) #
###############################
# prion_PUSH creation here
PRION_OPCODE_PUSH = PRION_OPCODE_INP + 1
PRION_FUNCNAME_PUSH = "PRION_PUSH"
PRION_PUSH_FUNCTION_TEMP = """
def &FUNC(&OUT , &ERC):
&OUT.append(&ERC)
return
"""
PRION_PUSH_FUNCTION_DEF = PRION_PUSH_FUNCTION_TEMP.replace(PRION_FUNCTION_LBL, PRION_FUNCNAME_PUSH)
PRION_PUSH_FUNCTION_DEF = PRION_PUSH_FUNCTION_DEF.replace(PRION_OUTPUT_LBL, PRION_OUTPUT_NAME)
PRION_PUSH_FUNCTION_DEF = PRION_PUSH_FUNCTION_DEF.replace(PRION_ERC_LBL, PRION_ERC_NAME)
###############################
# Indentaton matters here #
# please be careful if #
# this is moved around #
###############################
exec(PRION_PUSH_FUNCTION_DEF) #
###############################
# prion_POP creation here
PRION_OPCODE_POP = PRION_OPCODE_PUSH + 1
PRION_FUNCNAME_POP = "PRION_POP"
PRION_POP_FUNCTION_TEMP = """
def &FUNC(&OUT):
if len(&OUT) > 0:
&OUT.pop()
if len(&OUT) == 0:
&OUT.append(0)
return
"""
PRION_POP_FUNCTION_DEF = PRION_POP_FUNCTION_TEMP.replace(PRION_FUNCTION_LBL, PRION_FUNCNAME_POP)
PRION_POP_FUNCTION_DEF = PRION_POP_FUNCTION_DEF.replace(PRION_OUTPUT_LBL, PRION_OUTPUT_NAME)
###############################
# Indentaton matters here #
# please be careful if #
# this is moved around #
###############################
exec(PRION_POP_FUNCTION_DEF) #
###############################
# Arithmetic Functions
# Now we build our more complex arithmetic prion functions<br />
# -- Addition
# -- Subtraction
# -- Multiplication
# -- Division
# These functions need to validate the stack and store and in some cases
# temporary data. Some, like division, will require additional care
# it must be protected from div/0 errors. Also we must ensure that
# all functions leave the stack they operated on in a consistent
# and meaningful state for the next operation (and they will not
# know which operation is theoretically coming next in their raw form)
# prion_ADD creation here
PRION_OPCODE_ADD = PRION_OPCODE_POP + 1
PRION_FUNCNAME_ADD = "PRION_ADD"
PRION_ADD_FUNCTION_TEMP = """
def &FUNC(&OUT):
if len(&OUT) > 1:
&OUT.append(&OUT.pop() + &OUT.pop())
return
if len(&OUT) == 0:
&OUT.append(0)
return
return
"""
PRION_ADD_FUNCTION_DEF = PRION_ADD_FUNCTION_TEMP.replace(PRION_FUNCTION_LBL, PRION_FUNCNAME_ADD)
PRION_ADD_FUNCTION_DEF = PRION_ADD_FUNCTION_DEF.replace(PRION_OUTPUT_LBL, PRION_OUTPUT_NAME)
###############################
# Indentaton matters here #
# please be careful if #
# this is moved around #
###############################
exec(PRION_ADD_FUNCTION_DEF) #
###############################
# prion_SUB creation here
PRION_OPCODE_SUB = PRION_OPCODE_ADD + 1
PRION_FUNCNAME_SUB = "PRION_SUB"
PRION_SUB_FUNCTION_TEMP = """
def &FUNC(&OUT):
if len(&OUT) > 1:
&OUT.append(&OUT.pop() - &OUT.pop())
return
if len(&OUT) == 0:
&OUT.append(0)
return
return
"""
PRION_SUB_FUNCTION_DEF = PRION_SUB_FUNCTION_TEMP.replace(PRION_FUNCTION_LBL, PRION_FUNCNAME_SUB)
PRION_SUB_FUNCTION_DEF = PRION_SUB_FUNCTION_DEF.replace(PRION_OUTPUT_LBL, PRION_OUTPUT_NAME)
###############################
# Indentaton matters here #
# please be careful if #
# this is moved around #
###############################
exec(PRION_SUB_FUNCTION_DEF) #
###############################
# prion_MULT creation here
PRION_OPCODE_MULT = PRION_OPCODE_SUB + 1
PRION_FUNCNAME_MULT = "PRION_MULT"
PRION_MULT_FUNCTION_TEMP = """
def &FUNC(&OUT):
if len(&OUT) > 1:
&OUT.append(&OUT.pop() * &OUT.pop())
return
if len(&OUT) == 0:
&OUT.append(0)
return
return
"""
PRION_MULT_FUNCTION_DEF = PRION_MULT_FUNCTION_TEMP.replace(PRION_FUNCTION_LBL, PRION_FUNCNAME_MULT)
PRION_MULT_FUNCTION_DEF = PRION_MULT_FUNCTION_DEF.replace(PRION_OUTPUT_LBL, PRION_OUTPUT_NAME)
###############################
# Indentaton matters here #
# please be careful if #
# this is moved around #
###############################
exec(PRION_MULT_FUNCTION_DEF) #
###############################
# prion_DIV creation here
PRION_OPCODE_DIV = PRION_OPCODE_MULT + 1
PRION_FUNCNAME_DIV = "PRION_DIV"
PRION_DIV_FUNCTION_TEMP = """
def &FUNC(&OUT):
if len(&OUT) > 1:
x1 = &OUT.pop() * 1.0
x2 = &OUT.pop() * 1.0
if x2 == 0.0:
&OUT.append(x1)
&OUT.append(x2)
return
&OUT.append(x1 / x2)
return
if len(&OUT) == 0:
&OUT.append(0)
return
return
"""
PRION_DIV_FUNCTION_DEF = PRION_DIV_FUNCTION_TEMP.replace(PRION_FUNCTION_LBL, PRION_FUNCNAME_DIV)
PRION_DIV_FUNCTION_DEF = PRION_DIV_FUNCTION_DEF.replace(PRION_OUTPUT_LBL, PRION_OUTPUT_NAME)
###############################
# Indentaton matters here #
# please be careful if #
# this is moved around #
###############################
exec(PRION_DIV_FUNCTION_DEF) #
###############################
# prion_IF creation here
PRION_OPCODE_IF = PRION_OPCODE_DIV + 1
PRION_FUNCNAME_IF = "PRION_IF"
PRION_IF_FUNCTION_TEMP = """
def &FUNC(&OUT):
if len(&OUT) > 2:
x1 = &OUT.pop()
x2 = &OUT.pop()
x3 = &OUT.pop()
if x1 > x2:
&OUT.append(x1)
return
&OUT.append(x3)
return
if len(&OUT) == 0:
&OUT.append(0)
return
return
"""
PRION_IF_FUNCTION_DEF = PRION_IF_FUNCTION_TEMP.replace(PRION_FUNCTION_LBL, PRION_FUNCNAME_IF)
PRION_IF_FUNCTION_DEF = PRION_IF_FUNCTION_DEF.replace(PRION_OUTPUT_LBL, PRION_OUTPUT_NAME)
###############################
# Indentaton matters here #
# please be careful if #
# this is moved around #
###############################
exec(PRION_IF_FUNCTION_DEF) #
###############################
# To do: Add boolean
# To do: Add trig
# To do: Add hyperbolics
########################################################################
# You can define your own base functions in the area below
########################################################################
########################################################################
# Here is where we package up our functional primitives nicely for
# the consumption of classes that would have no interest in the above
########################################################################
PRION_OXF = { PRION_OPCODE_INP:PRION_INP, PRION_OPCODE_PUSH:PRION_PUSH,
PRION_OPCODE_POP:PRION_POP, PRION_OPCODE_ADD:PRION_ADD,
PRION_OPCODE_SUB:PRION_SUB, PRION_OPCODE_MULT:PRION_MULT,
PRION_OPCODE_DIV:PRION_DIV, PRION_OPCODE_IF:PRION_IF }
PRION_OXK = { PRION_OPCODE_INP:"INP", PRION_OPCODE_PUSH:"PUSH",
PRION_OPCODE_POP:"POP", PRION_OPCODE_ADD:"ADD",
PRION_OPCODE_SUB:"SUB", PRION_OPCODE_MULT:"MULT",
PRION_OPCODE_DIV:"DIV", PRION_OPCODE_IF:"IF" }
PRION_SXO = { "pop":PRION_OPCODE_POP, "+":PRION_OPCODE_ADD,
"-":PRION_OPCODE_SUB, "*":PRION_OPCODE_MULT,
"/":PRION_OPCODE_DIV, "?":PRION_OPCODE_IF }
PRION_FSET = [ PRION_SXO[key] for key in PRION_SXO ]
PRION_ERC_INT = 0
PRION_ERC_DBL = 1
PRION_MUTDEF = 5
PRION_O_IDX = 0
PRION_S_IDX = 1
PRION_I_IDX = 2
PRION_E_IDX = 2
PRION_F_SLCT = 1
PRION_I_SLCT = 2
PRION_E_SLCT = 3
########################################################################
# The genome class is going to be what you will ultimately be using
# pretty much constanly by every other object in the system. I am keeping
# it in herently simple:
# -- It has a type of ERC (Ephemoral Random Constant)
# -- It has an intrisic idea of bouns for whatever the ERC requested is
# -- It is capable of generating a single line of LGP code for our stack
# processor (a codon) and you have at least three available types:
# [INPUT, ERC, FUNCTION]
# You can ask for a specific class of codon, or simply call the basic
# codon() to get one of them randomly (with equal weight)
#
# A valid codon follows the three template types. each of the *codon()
# functions will give the folowing types of returns
# -- i_codon() = input_codon = [OPCODE, STACK_IDX, INPUT_IDX]
# -- e_codon() = erc_codon = [OPCODE, STACK_IDX, ERC_VAL]
# -- f_codon() = func_codon = [OPCODE, STACK_IDX]
#
# I append IDX to the stack and input identifiers since what we could be
# looking at an array of inputs but also, if solving for multiple outputs,
# we can have an arrsy of stack registers (the value left of the top of
# the stack register once the sequence terminates will denote the "answer").
########################################################################
class Genome:
def __init__(self, wcount, scount, erc_low=0, erc_high=1, fset=PRION_FSET, erc_type=PRION_ERC_INT):
self.wcount = wcount # Number of wires (Inputs)
self.scount = scount # Number of registes (Outputs)
self.fset = fset # Functions that this genome can offer (Defaults to PRION_FSET)
self.erc_type = erc_type # Data type of ERC (Defaults to Integer)
self.erc_low = erc_low # Lower bound of ERC (Defaults to 0)
self.erc_high = erc_high # Higher bound of ERC (Defaults to 1)
# -- e_codon() = erc_codon = [OPCODE, STACK_IDX, ERC_VAL]
def e_codon(self):
if self.erc_type == PRION_ERC_INT:
return [PRION_OPCODE_PUSH, random.randint(0, self.scount - 1), random.randint(self.erc_low, self.erc_high)]
if self.erc_type == PRION_ERC_FLOAT:
return [PRION_OPCODE_PUSH, random.randint(0, self.scount - 1), random.uniform(self.erc_low, self.erc_high)]
return [PRION_OPCODE_NOP]
# -- i_codon() = input_codon = [OPCODE, STACK_IDX, INPUT_IDX]
def i_codon(self):
return [PRION_OPCODE_INP, random.randint(0, self.scount - 1), random.randint(0, self.wcount - 1)]
# -- f_codon() = func_codon = [OPCODE, STACK_IDX]
def f_codon(self):
if len(self.fset) > 0:
return [random.choice(self.fset), random.randint(0, self.scount - 1)]
return [PRION_OPCODE_NOP]
# return one of the three base codons with equal probability
def codon(self):
x = random.choice([PRION_F_SLCT, PRION_I_SLCT, PRION_E_SLCT])
if x == PRION_E_SLCT:
return self.e_codon()
if x == PRION_I_SLCT:
return self.i_codon()
if x == PRION_F_SLCT:
return self.f_codon()
return [PRION_OPCODE_NOP]
########################################################################
# The sequencer is where you would put all of your enhanced filters for
# the genrated programs. This one is faily strightforward. Keep
# requesting codons at randome until the disred length is reached
# You can inherit and overload the sequence() function in derived classes
# to suit your problem domain as necessary
########################################################################
PRION_WEIGHT_DEF = [1, 1, 1]
PRION_WEIGHT_ERC_IDX = 0
PRION_WEIGHT_INP_IDX = 1
PRION_WEIGHT_FUNC_IDX = 2
PRION_ERC_REQ = 0
PRION_INP_REQ = 1
PRION_FUNC_REQ = 2
class Sequencer:
def __init__(self, genome, weights=PRION_WEIGHT_DEF):
self.genome = genome
if weights != PRION_WEIGHT_DEF:
self.wheel = [PRION_INP_REQ for x in range(weights[PRION_WEIGHT_INP_IDX])]
self.wheel += [PRION_ERC_REQ for x in range(weights[PRION_WEIGHT_ERC_IDX])]
self.wheel += [PRION_FUNC_REQ for x in range(weights[PRION_WEIGHT_FUNC_IDX])]
else:
self.wheel = [PRION_INP_REQ, PRION_ERC_REQ, PRION_FUNC_REQ]
def sequence(self, length):
rseq = []
while len(rseq) < length:
reqtype = random.choice(self.wheel)
if reqtype == PRION_INP_REQ:
rseq.append(self.genome.i_codon())
elif reqtype == PRION_ERC_REQ:
rseq.append(self.genome.e_codon())
elif reqtype == PRION_FUNC_REQ:
rseq.append(self.genome.f_codon())
else:
rseq.append([PRION_OPCODE_NOP])
return rseq
########################################################################
# The decoder is another simple base class provided mainly as a
# convenience to the user / researcher. It enables a sequence of valid
# codons to be slightly more readable. This of it as a reverse assembler
# for the virtual stack processor.
########################################################################
class Decoder:
def __init__(self, genome):
self.genome = genome
def decode(self, prog):
readable = ""
for x in prog:
readable += str(PRION_OXK[x[PRION_O_IDX]]) + "\t" + str(x[1:]) + "\n"
return readable
########################################################################
# The processor is the base implementation of the virtual stack
# processor on which the generated sequences will be run. It is assumed
# the processor can tak an array of inputs (the size of which is contained
# in the genome) and an array of stack registers (also specified in the
# genome. The base processor takes a prion program and runs the sequence
# against stacks which start with the initial state [0].
# This means that a stack register in this virtual processor will always
# have an initial value
########################################################################
class Processor:
def __init__(self, genome):
self.genome = genome
def evaluate(self, prog, inputs):
stacks = [[0] for x in range(self.genome.scount)]
for line in prog:
if line[PRION_O_IDX] == PRION_OPCODE_INT:
pass
elif line[PRION_O_IDX] == PRION_OPCODE_NOP:
pass
elif line[PRION_O_IDX] == PRION_OPCODE_INP:
stacks[line[PRION_S_IDX]].append(inputs[line[PRION_I_IDX]])
elif line[PRION_O_IDX] == PRION_OPCODE_PUSH:
stacks[line[PRION_S_IDX]].append(line[PRION_E_IDX])
else:
PRION_OXF[line[PRION_O_IDX]](stacks[line[PRION_S_IDX]])
return [x[-1] for x in stacks]
########################################################################
# The SPLICER CLASS is where we start seeing more of the biological
# aspect of the prion object model.
#
# The base splicer performs a simple crossover operation. It makes the
# basic assumption that two sequences can be of different lengths.
# To avoid sizing problems it will ensure that it picks a cutpoint that
# will not exceed the bounds of either sequence. This assumes that both
# sequences have been generated with compatable genomes.
#
# Desired bahaviour with sequences of varying lengths can be hard to
# imagine in all cases from the library perspective, so I am leaving it
# tunable by providing a few flags
# -- PRION_TRIM: Generate a sequence of the minimum of the 2 lengths
# -- PRION_BALANCE: Generate a sequence of the average of the 2 lengths
# -- PRION_GROW: Generate a sequence of the minimum of the 2 lengths
# By default, the base splicer will always trim. This can be modified
# by passing a different flag to the splice() function
########################################################################
PRION_TRIM = 0
PRION_BALANCE = 1
PRION_GROW = 2
class Splicer:
def __init__(self, genome):
self.genome = genome
def splice(self, prog1, prog2, mode=PRION_TRIM):
s1 = len(prog1)
s2 = len(prog2)
if s1 == s2:
cutpoint = random.randint(0, s1 - 1)
return prog1[0:cutpoint] + prog2[cutpoint:]
# If we get here, we are dealing with sequences that are not
# of the same length
if mode == PRION_TRIM:
if s1 < s2:
cutpoint = random.randint(0, s1 - 1)
return prog1[0:cutpoint] + prog2[cutpoint:s1 - 1]
else:
cutpoint = random.randint(0, s2 - 1)
return prog2[0:cutpoint] + prog1[cutpoint:s2 - 1]
if mode == PRION_GROW:
if s1 < s2:
cutpoint = random.randint(0, s1 - 1)
return prog1[0:cutpoint] + prog2[cutpoint:]
else:
cutpoint = random.randint(0, s2 - 1)
return prog2[0:cutpoint] + prog1[cutpoint:]
if mode == PRION_BALANCE:
if s1 < s2:
cutpoint = random.randint(0, s1 - 1)
return prog1[0:cutpoint] + prog2[cutpoint:s1 + random.randint(s1, s2) - 1]
else:
cutpoint = random.randint(0, s2 - 1)
return prog2[0:cutpoint] + prog1[cutpoint:s2 + random.randint(s2, s1) - 1]
########################################################################
# The mutator also shows some of its roots in biological/evolutionary
# processes. It is given a genome and a percentage p. If the
# random.uniform(0.0, 100.0) returns less than or equal to p, the current
# codon is replaced by a random one from the available genome
########################################################################
class Mutator:
def __init__(self, genome):
self.genome = genome
def mutate(self, seq, p=PRION_MUTDEF):
idx = 0
seqsz = len(seq)
rseq = []
for codon in seq:
if random.randint(0, 100) <= p:
rseq.append(self.genome.codon())
else:
rseq.append(codon)
return rseq
########################################################################
# The fitness class dtermines the overall score of a generated sequence.
# By default, the class will simply use a euclidan distance formula
# (although can be extended to cover many more).
# We will make the assumption that the "fitter" a program is, the closer
# the value will approach 1.0 (although overall fitness will be a number
# between (0.0 - 1.0]
# Fitness will always be positive in this context, so if the Fitness class
# is unable to calculate it for whatever reason, it will return -1
########################################################################
PRION_FIT_EUCLID = 0
class Fitness:
def fitness(self, param, ideal, ftype=PRION_FIT_EUCLID):
if ftype == PRION_FIT_EUCLID:
return self.f_euclidian(param, ideal)
return -1
def f_euclidian(self, param, ideal):
if len(param) != len(ideal):
return -1
tmp = []
idx = 0
while idx < len(param):
tmp.append((param[idx] - ideal[idx]) * (param[idx] - ideal[idx]) * 1.0)
idx += 1
return (1.0 / (1.0 + sum(tmp)))
########################################################################
# The population is a conatainer class for sequences. It is included as
# a helper (meaning you need not use it if you would rather manage
# populations of programs directly. It will utilize tournament selection
# for designating programs for "breeding" (crossover).
# the individual sequences are stored as a list of 4 elements with the
# fitness score stored at IDX0 and the source stored at IDX1.
# The geneology will be stored at IDX3 and the sequences unique ID for
# the current run will be stored at IDX4
########################################################################
PRION_POP_SCORE_IDX = 0
PRION_POP_SOURCE_IDX = 1
PRION_POP_GENY_IDX = 2
PRION_POP_ID_IDX = 3
PRION_POP_PARAM_IDX = 0
PRION_POP_IDEAL_IDX = 1
PRION_POP_DEFSIZE = 5000
PRION_CENSUS_HIGHLANDER = 0
PRION_CENSUS_HIDX = 1
PRION_CENSUS_HSCORE = 2
PRION_CENSUS_GEN = 3
PRION_CENSUS_MAXPOP = 4
PRION_CENSUS_CURPOP = 5
class Population:
def __init__(self, genome, bdata, maxpop=PRION_POP_DEFSIZE, fit=Fitness()):
self.genome = genome
self.maxpop = maxpop
self.gencount = 0
self.fit = fit
self.ID = 0
self.bdata = bdata
self.proc = Processor(genome)
self.holding = []
self.highlander = -1
self.hidx = -1
self.hscore = -1
self.splicer = Splicer(self.genome)
def add(self, Seq):
if len(self.holding) >= self.maxpop:
return
member = [[-1], [[]], [-1], [0]]
# Add source
member[PRION_POP_SOURCE_IDX] = Seq
# Assign run ID
member[PRION_POP_ID_IDX] = self.ID
self.ID += 1
# Score member
member[PRION_POP_SCORE_IDX] = self.s_score(Seq)
self.holding.append(member)
if self.hscore < member[PRION_POP_SCORE_IDX]:
self.hscore = member[PRION_POP_SCORE_IDX]
self.highlander = member[PRION_POP_ID_IDX]
self.hidx = len(self.holding) - 1
return
def s_score(self, Seq):
datac = len(self.bdata)
if datac == 0:
return -1
rsum = 0.0
for entry in self.bdata:
rsum += self.fit.fitness(self.proc.evaluate(Seq, entry[PRION_POP_PARAM_IDX]),
entry[PRION_POP_IDEAL_IDX])
return (rsum / datac)
def popdump(self):
return self.holding
def census(self):
d = {}
d[PRION_CENSUS_HIGHLANDER] = self.highlander
d[PRION_CENSUS_HIDX] = self.hidx
d[PRION_CENSUS_HSCORE] = self.hscore
d[PRION_CENSUS_GEN] = self.gencount
d[PRION_CENSUS_MAXPOP] = self.maxpop
d[PRION_CENSUS_CURPOP] = len(self.holding)
return d
def thunderdome(self, player1, player2):
if len(player1) != 1:
return max(self.thunderdome(player1[:(len(player1)/2)], player1[len(player1)/2:]),
self.thunderdome(player2[:(len(player2)/2)], player2[len(player2)/2:]))
if self.holding[player1[0]][PRION_POP_SCORE_IDX] > self.holding[player2[0]][PRION_POP_SCORE_IDX]:
return player1[0]
return player2[0]
def evolve(self, tsize=32):
next_holding = []
next_holding.append(self.holding[self.hidx])
self.hidx = 0
self.gencount += 1
while len(next_holding) < self.maxpop:
poolcount = len(self.holding)
players = [random.randint(0, poolcount - 1) for x in range(tsize)]
winner1_idx = self.thunderdome(players[:(len(players)/2)], players[len(players)/2:])
players = [random.randint(0, poolcount - 1) for x in range(tsize)]
winner2_idx = self.thunderdome(players[:(tsize/2)], players[(tsize/2):])
offspring = self.splicer.splice(self.holding[winner1_idx][PRION_POP_SOURCE_IDX],
self.holding[winner2_idx][PRION_POP_SOURCE_IDX])
member = [[-1], [[]], [self.gencount], [winner1_idx, winner2_idx]]
# Add source
member[PRION_POP_SOURCE_IDX] = offspring
# Assign run ID
member[PRION_POP_ID_IDX] = self.ID
self.ID += 1
# Score member
member[PRION_POP_SCORE_IDX] = self.s_score(offspring)
next_holding.append(member)
if self.hscore < member[PRION_POP_SCORE_IDX]:
self.hscore = member[PRION_POP_SCORE_IDX]
self.highlander = member[PRION_POP_ID_IDX]
self.hidx = len(next_holding) - 1
self.holding = next_holding
return
def PRION_LIBTEST():
print("Running test harness")
print("Please set PRION_NOISY to True at the top of this file for full bootstrap details")
print("Please set PRION_LIBTEST to True at the top of this file for full bootstrap tests")
print("---------------------------------------------")
print("Testing Genome Class")
g = Genome(5, 2, erc_low=-10, erc_high=10)
progs = []
print("creating list of 20 random ops")
progs.append([g.codon() for x in range(20)])
print(str(progs[0]))
print("creating list of 20 random ERC ops")
progs.append([g.e_codon() for x in range(20)])
print(str(progs[1]))
print("creating list of 20 random input ops")
progs.append([g.i_codon() for x in range(20)])
print(str(progs[2]))
print("creating list of 20 random function ops")
progs.append([g.f_codon() for x in range(20)])
print(str(progs[3]))
print("---------------------------------------------")
print("Testing Sequencer Class")
s = Sequencer(g)
print("creating list of 500 random programs of 256 codons in length")
progs = [s.sequence(256) for x in range(500)]
print("---------------------------------------------")
print("Testing Decoder Class")
d = Decoder(g)
print("Decoding Random Program")
print(d.decode(random.choice(progs)))
print("---------------------------------------------")
print("Testing Processor Class")
p = Processor(g)
print("Setting inputs to: [100, 20, 43, 91, 16]")
i = [100, 20, 43, 91, 16]
pcounter2 = 0
for prog in progs:
print("PROGRAM - " + str(pcounter2) + " (output)" + str(p.evaluate(prog, i)))
pcounter2 += 1
print("---------------------------------------------")
print("Testing Splicer Class")
s = Splicer(g)
print("First program")
rand_p1 = random.choice(progs)
print("Return val: " + str(p.evaluate(rand_p1, i)))
print("Second program")
rand_p2 = random.choice(progs)
print("Return val: " + str(p.evaluate(rand_p2, i)))
spliced_p = s.splice(rand_p1, rand_p2)
print("Spliced program")
print("Return val: " + str(p.evaluate(spliced_p, i)))
print("---------------------------------------------")
print("Testing Mutator Class")
m = Mutator(g)
print("Mutating spliced program")
print("Running 25 mutattion passes @ 30% (etremely high in most cases)")
for x in range(25):
print("Pass - " + str(x))
spliced_p = m.mutate(spliced_p, p=30)
print("Return val: " + str(p.evaluate(spliced_p, i)))
print("---------------------------------------------")
print("Testing Fitness Class")
f = Fitness()
print("Randomly selecting 10 programs - testing aginst ideal [1, 2]")
ideal = [1, 2]
for x in range(10):
print("Pass - " + str(x))
result = p.evaluate(random.choice(progs), i)
print("Return val: " + str(result))
print("Fitness: " + str(f.fitness(result, ideal)))
print("Testing fitness with correct answer")
print("Fitness: " + str(f.fitness(ideal, ideal)))
print("---------------------------------------------")
print("Tests complete")
print("---------------------------------------------")