1
1
from numpy import random
2
+
2
3
# Mutation operators
3
4
# AOR: Arithmetic Operator Replacement: a + b -> a - b
4
5
# LCR: Logical Connector Replacement: a and b -> a or b
5
6
# ROR: Relational Operator Replacement: a > b -> a < b
6
- # UOI: Unary Operator Insertion: a -> not a (only in conditionals)
7
- # SBR: Statement Block Replacement: stmt -> 0
7
+ # UOI: Unary Operator Insertion: b -> not b, i -> i + 1
8
+ # SBR: Statement Block Replacement: stmt -> 0 TODO
8
9
9
10
from typing import Literal , Callable , Any
10
11
@@ -27,29 +28,122 @@ def convert_to_add(self, node):
27
28
def convert_to_sub (self , node ):
28
29
raise NotImplementedError
29
30
31
+ def convert_to_binop_a (self , node ):
32
+ raise NotImplementedError
33
+
34
+ def convert_to_binop_b (self , node ):
35
+ raise NotImplementedError
36
+
37
+ def convert_to_boolop_a (self , node ):
38
+ raise NotImplementedError
39
+
40
+ def convert_to_boolop_b (self , node ):
41
+ raise NotImplementedError
42
+
43
+ def convert_to_and (self , node ):
44
+ raise NotImplementedError
45
+
46
+ def convert_to_or (self , node ):
47
+ raise NotImplementedError
48
+
49
+ def convert_to_true (self , node ):
50
+ raise NotImplementedError
51
+
52
+ def convert_to_false (self , node ):
53
+ raise NotImplementedError
54
+
55
+ def convert_to_gt (self , node ):
56
+ raise NotImplementedError
57
+
58
+ def convert_to_lt (self , node ):
59
+ raise NotImplementedError
60
+
61
+ def convert_to_gte (self , node ):
62
+ raise NotImplementedError
63
+
64
+ def convert_to_lte (self , node ):
65
+ raise NotImplementedError
66
+
67
+ def convert_to_eq (self , node ):
68
+ raise NotImplementedError
69
+
70
+ def convert_to_neq (self , node ):
71
+ raise NotImplementedError
72
+
73
+ def convert_to_not (self , node ):
74
+ raise NotImplementedError
75
+
76
+ def convert_to_increment (self , node ):
77
+ raise NotImplementedError
78
+
79
+ def convert_to_decrement (self , node ):
80
+ raise NotImplementedError
81
+
82
+
83
+ class EngineConfig :
84
+ def __init__ (
85
+ self ,
86
+ aor_op_rate = 0.2 ,
87
+ lcr_op_rate = 0.2 ,
88
+ ror_op_rate = 0.2 ,
89
+ binop_expr_rate = 0.1 ,
90
+ boolop_expr_rate = 0.1 ,
91
+ num_lit_rate = 0.1 ,
92
+ max_mutations = 1 ,
93
+ ):
94
+ self .aor_op_rate = aor_op_rate
95
+ self .lcr_op_rate = lcr_op_rate
96
+ self .ror_op_rate = ror_op_rate
97
+ self .binop_expr_rate = binop_expr_rate
98
+ self .boolop_expr_rate = boolop_expr_rate
99
+ self .num_lit_rate = num_lit_rate
100
+ self .max_mutations = max_mutations
101
+
30
102
31
103
class Engine :
32
- def __init__ (self , mutation_rate : float = 0.1 , seed = 1337 ):
33
- self .mutation_rate = mutation_rate
104
+ def __init__ (
105
+ self ,
106
+ config : EngineConfig = EngineConfig (),
107
+ seed = 1337
108
+ ):
109
+ self .config = config
34
110
self .rng = random .default_rng (seed )
111
+ self .mutate_calls = 0
112
+ self .already_mutated = []
113
+ self .mutations = 0
114
+
115
+ def new (self ):
116
+ engine = Engine (config = self .config , seed = self .rng .integers (0 , 2 ** 32 ))
117
+ engine .already_mutated = self .already_mutated
118
+ return engine
35
119
36
120
def pick (self , iterable ):
37
121
return self .rng .choice (iterable )
38
122
39
- def mutate (self , node , conv_fn ):
40
- if self .rng .random () < self .mutation_rate :
123
+ def mutate_node (self , node , conv_fn , rate ):
124
+ self .mutate_calls += 1
125
+ if self .mutations < self .config .max_mutations \
126
+ and self .mutate_calls not in self .already_mutated \
127
+ and self .rng .random () < rate :
128
+ self .mutations += 1
129
+ self .already_mutated .append (self .mutate_calls )
41
130
return conv_fn (node )
42
131
else :
43
132
return node
44
133
45
134
46
135
class Mutator (Converter ):
47
136
AOR_OPS = ["+" , "-" , "*" , "/" ]
137
+ BIN_OPS = ["a" , "b" ]
138
+ BOOL_OPS = ["a" , "b" , "true" , "false" , "not" ]
139
+ LCR_OPS = ["and" , "or" ]
140
+ ROR_OPS = [">" , "<" , ">=" , "<=" , "==" , "!=" ]
141
+ NUM_LIT_OP = ["++" , "--" ]
48
142
49
143
def __init__ (self , engine : Engine ):
50
144
self .engine = engine
51
145
52
- def mutate_aor (self , node , current : Literal ["+" , "-" , "*" , "/" ]):
146
+ def mutate_aor_op (self , node , current : Literal ["+" , "-" , "*" , "/" ]):
53
147
possible = [op for op in self .AOR_OPS if op != current ]
54
148
picked = self .engine .pick (possible )
55
149
@@ -65,4 +159,83 @@ def mutate_aor(self, node, current: Literal["+", "-", "*", "/"]):
65
159
else :
66
160
raise Exception ("Unknown operator" )
67
161
68
- return self .engine .mutate (node , conv_fn )
162
+ return self .engine .mutate_node (node , conv_fn , self .engine .config .aor_op_rate )
163
+
164
+ def mutate_binop_expr (self , node ):
165
+ picked = self .engine .pick (self .BIN_OPS )
166
+
167
+ conv_fn = None
168
+ if picked == "a" :
169
+ conv_fn = self .convert_to_binop_a
170
+ elif picked == "b" :
171
+ conv_fn = self .convert_to_binop_b
172
+ else :
173
+ raise Exception ("Unknown operator" )
174
+
175
+ return self .engine .mutate_node (node , conv_fn , self .engine .config .binop_expr_rate )
176
+
177
+ def mutate_lcr (self , node , current : Literal ["and" , "or" ]):
178
+ possible = [op for op in self .LCR_OPS if op != current ]
179
+ picked = self .engine .pick (possible )
180
+
181
+ conv_fn = None
182
+ if picked == "and" :
183
+ conv_fn = self .convert_to_and
184
+ elif picked == "or" :
185
+ conv_fn = self .convert_to_or
186
+ else :
187
+ raise Exception ("Unknown operator" )
188
+
189
+ return self .engine .mutate_node (node , conv_fn , self .engine .config .lcr_op_rate )
190
+
191
+ def mutate_boolop_expr (self , node ):
192
+ picked = self .engine .pick (self .BOOL_OPS )
193
+
194
+ conv_fn = None
195
+ if picked == "a" :
196
+ conv_fn = self .convert_to_boolop_a
197
+ elif picked == "b" :
198
+ conv_fn = self .convert_to_boolop_b
199
+ elif picked == "true" :
200
+ conv_fn = self .convert_to_true
201
+ elif picked == "false" :
202
+ conv_fn = self .convert_to_false
203
+ elif picked == "not" :
204
+ conv_fn = self .convert_to_not
205
+ else :
206
+ raise Exception ("Unknown operator" )
207
+
208
+ return self .engine .mutate_node (node , conv_fn , self .engine .config .boolop_expr_rate )
209
+
210
+ def mutate_ror (self , node , current : Literal [">" , "<" , ">=" , "<=" , "==" , "!=" ]):
211
+ possible = [op for op in self .ROR_OPS if op != current ]
212
+ picked = self .engine .pick (possible )
213
+
214
+ conv_fn = None
215
+ if picked == ">" :
216
+ conv_fn = self .convert_to_gt
217
+ elif picked == "<" :
218
+ conv_fn = self .convert_to_lt
219
+ elif picked == ">=" :
220
+ conv_fn = self .convert_to_gte
221
+ elif picked == "<=" :
222
+ conv_fn = self .convert_to_lte
223
+ elif picked == "==" :
224
+ conv_fn = self .convert_to_eq
225
+ elif picked == "!=" :
226
+ conv_fn = self .convert_to_neq
227
+ else :
228
+ raise Exception ("Unknown operator" )
229
+
230
+ return self .engine .mutate_node (node , conv_fn , self .engine .config .ror_op_rate )
231
+
232
+ def mutate_number_literal (self , node ):
233
+ picked = self .engine .pick (self .NUM_LIT_OP )
234
+
235
+ conv_fn = None
236
+ if picked == "++" :
237
+ conv_fn = self .convert_to_increment
238
+ elif picked == "--" :
239
+ conv_fn = self .convert_to_decrement
240
+
241
+ return self .engine .mutate_node (node , conv_fn , self .engine .config .num_lit_rate )
0 commit comments