Переглянути джерело

Bugfix + tests for expression copy, len & mutations

Yann Weber 1 рік тому
джерело
коміт
7531536af3

+ 1
- 1
Makefile Переглянути файл

@@ -56,7 +56,7 @@ doc/.doxygen.stamp: $(wildcard *.c) $(wildcard *.h) Doxyfile
56 56
 
57 57
 .PHONY: clean distclean checks runtest unittest benchmark
58 58
 
59
-checks: runtest unittest benchmark
59
+checks: runtest unittest
60 60
 
61 61
 benchmark: $(LIB)
62 62
 	PYTHONPATH=`pwd` $(PYTHON) tests/benchmark.py 0x500 0x2000;  \

+ 24
- 1
rpn_mutate.c Переглянути файл

@@ -27,6 +27,7 @@ int rpn_mutation_init_params(rpn_mutation_params_t *params)
27 27
 	params->_weights[1] = to_weight(total, params->w_del);
28 28
 	params->_weights[2] = to_weight(total, params->w_mut);
29 29
 	params->_weights[3] = to_weight(total, params->w_mut_soft);
30
+/*
30 31
 #ifdef DEBUG
31 32
 dprintf(2, "weights : %d %d %d %d\n",
32 33
 		params->_weights[0],
@@ -34,10 +35,12 @@ dprintf(2, "weights : %d %d %d %d\n",
34 35
 		params->_weights[2],
35 36
 		params->_weights[3]);
36 37
 #endif
38
+*/
37 39
 	for(uint8_t i=1; i<4; i++)
38 40
 	{
39 41
 		params->_weights[i] += params->_weights[i-1];
40 42
 	}
43
+/*
41 44
 #ifdef DEBUG
42 45
 dprintf(2, "summed weights : %d %d %d %d\n",
43 46
 		params->_weights[0],
@@ -45,6 +48,7 @@ dprintf(2, "summed weights : %d %d %d %d\n",
45 48
 		params->_weights[2],
46 49
 		params->_weights[3]);
47 50
 #endif
51
+*/
48 52
 
49 53
 	total = 0;
50 54
 	for(uint8_t i=0; i<3; i++)
@@ -85,6 +89,7 @@ err:
85 89
 
86 90
 int rpn_mutation(rpn_tokenized_t *toks, rpn_mutation_params_t *params)
87 91
 {
92
+	//_print_params(2, params);
88 93
 	assert(toks->argc < rnd_t_max);
89 94
 
90 95
 	if(toks->tokens_sz <= params->min_len)
@@ -117,7 +122,11 @@ int rpn_mutation_add(rpn_tokenized_t *toks, rpn_mutation_params_t *params)
117 122
 	rpn_token_t *new_toks, new_token;
118 123
 	size_t position;
119 124
 
120
-	if(rpn_rand_limit(toks->tokens_sz-1, &position) < 0)
125
+	if(toks->tokens_sz == 0)
126
+	{
127
+		position = 0;
128
+	}
129
+	else if(rpn_rand_limit(toks->tokens_sz, &position) < 0)
121 130
 	{
122 131
 		return -1;
123 132
 	}
@@ -347,3 +356,17 @@ int rpn_rand_limit(size_t max, size_t *res)
347 356
 	//*res = max - 1;
348 357
 	return 0;
349 358
 }
359
+
360
+void _print_params(int fd, rpn_mutation_params_t *params)
361
+{
362
+	dprintf(fd, "Minlen = %ld w[add=%.2f del=%.2f mut=%.2f msf=%.2f]\n",
363
+			params->min_len, params->w_add, params->w_del,
364
+			params->w_mut, params->w_mut_soft);
365
+	dprintf(fd, "w_add_elt [op=%.2f const=%.2f var=%.2f]\n",
366
+			params->w_add_elt[0], params->w_add_elt[1],
367
+			params->w_add_elt[2]);
368
+	dprintf(fd, "w_mut_elt [op=%.2f const=%.2f var=%.2f]\n",
369
+			params->w_mut_elt[0], params->w_mut_elt[1],
370
+			params->w_mut_elt[2]);
371
+}
372
+

+ 3
- 0
rpn_mutate.h Переглянути файл

@@ -98,4 +98,7 @@ int rpn_rand_limit(size_t max, size_t *res);
98 98
 int _rpn_random_choice(size_t sz, rnd_t *weights, size_t *res);
99 99
 void __rpn_random_choice(size_t sz, rnd_t *weights, rnd_t rand, size_t *res);
100 100
 
101
+// Debugging function
102
+void _print_params(int fd, rpn_mutation_params_t *params);
103
+
101 104
 #endif

+ 13
- 46
tests/tests_pyrpn.py Переглянути файл

@@ -1,7 +1,7 @@
1 1
 #!/usr/bin/python3
2
-# Copyright 2020 Weber Yann
2
+# Copyright 2020, 2023 Weber Yann
3 3
 #
4
-# This file is part of geneifs.
4
+# This file is part of rpnifs.
5 5
 #
6 6
 #        geneifs is free software: you can redistribute it and/or modify
7 7
 #        it under the terms of the GNU General Public License as published by
@@ -17,6 +17,7 @@
17 17
 #        along with geneifs.  If not, see <http://www.gnu.org/licenses/>.
18 18
 #
19 19
 
20
+import copy
20 21
 import sys
21 22
 import random
22 23
 import math
@@ -34,28 +35,6 @@ except (ImportError, NameError) as e:
34 35
           file=sys.stderr)
35 36
     raise e
36 37
 
37
-## TODO : move this function somewhere or implement it in C
38
-def decode_state(state):
39
-    """ Return a dict containing rpn state dump fields
40
-    """
41
-    res = dict()
42
-    res['real_sz'] = len(state)
43
-    res['total_sz'] = int.from_bytes(state[0:8], byteorder="little")
44
-    res['argc'] =  int.from_bytes(state[8:16], byteorder="little")
45
-    res['stack_sz_bytes'] = state[16:24]
46
-    res['stack_sz'] = state[16]
47
-    res['token_sz'] = int.from_bytes(state[24:32], byteorder="little", signed=True)
48
-    res['stack'] = [int.from_bytes(state[i:i+8], byteorder="little")
49
-                    for i in range(32, 32 + (8*res['stack_sz']),8)]
50
-    res['tokens_off'] = 32 + (8*res['stack_sz'])
51
-    res['tokens_real_sz'] = res['real_sz'] - res['tokens_off']
52
-    res['tokens'] = [state[i:i+24]
53
-                      for i in range(res['tokens_off'],
54
-                                     res['tokens_off'] + (24*res['token_sz']),
55
-                                     24)]
56
-    return res
57
-    pass
58
-
59 38
 
60 39
 class Test0RpnModule(unittest.TestCase):
61 40
 
@@ -113,6 +92,15 @@ class Test0RpnModule(unittest.TestCase):
113 92
             self.assertIn(long, known_ops)
114 93
             self.assertEqual(short, known_ops[long])
115 94
 
95
+    def testing_rand_expr_len(self):
96
+        """ Testing if RPNExpr.random_expr() returns expected expression """
97
+        for _ in range(100):
98
+            elen = random.randint(1, 50)
99
+            rnd_expr = pyrpn.random_expr(20, elen)
100
+            spl = [s for s in rnd_expr.split(' ') if len(s.strip())]
101
+            with self.subTest(expected_tokens=elen, tokens_count=len(spl), expr=rnd_expr):
102
+                self.assertEqual(len(spl), elen)
103
+
116 104
     def test_rand_expr(self):
117 105
         """ Testing RPNExpr.random_expr() """
118 106
         result = {}
@@ -142,27 +130,6 @@ class Test0RpnModule(unittest.TestCase):
142 130
         entropy = 1-sum([(n/all_count)**2
143 131
                           for _, n in counters.items()])
144 132
         self.assertGreater(entropy, 1-(1/all_ops), "Low entropy !")
145
-    
146
-    def test_pickle_state(self):
147
-        """ Testing pickling/unpickling """
148
-        e = pyrpn.RPNExpr('0x42 + A0', 2)
149
-        e2 = pickle.loads(pickle.dumps(e))
150
-        ds_e = decode_state(e.__getstate__())
151
-        ds_e2 = decode_state(e2.__getstate__())
152
-        self.assertEqual(e.__getstate__(), e2.__getstate__(),
153
-                         msg="EXPR: %r != %r (%r != %r)" % (e, e2, ds_e, ds_e2))
154
-        for i in range(100):
155
-            e = pyrpn.RPNExpr(pyrpn.random_expr(0), 2)
156
-
157
-            e2 = pickle.loads(pickle.dumps(e))
158
-
159
-            self.assertEqual(e.__getstate__(), e2.__getstate__(),
160
-                             msg="EXPR#%d : %r != %r" % (i,e, e2))
161
-
162
-            e3 = pickle.loads(pickle.dumps(e2))
163
-
164
-            self.assertEqual(e.__getstate__(), e3.__getstate__(), msg=e)
165
-            self.assertEqual(e2.__getstate__(), e3.__getstate__(), msg=e)
166
-
133
+   
167 134
 if __name__ == '__main__':
168 135
     unittest.main()

+ 1
- 1
tests/tests_rpn_compile.py Переглянути файл

@@ -67,7 +67,7 @@ class TestRpnCompile(unittest.TestCase):
67 67
     def test_very_long(self):
68 68
         """ Compile 3 very long expression (5242K op) """
69 69
         argc = 4
70
-        codelen = 0x500000
70
+        codelen = 0x50000
71 71
         import time
72 72
         for i in range(3):
73 73
             args = [random.randint(0,IMAX) for _ in range(argc)]

+ 109
- 0
tests/tests_rpn_copy.py Переглянути файл

@@ -0,0 +1,109 @@
1
+#!/usr/bin/python3
2
+# Copyright 2023 Weber Yann
3
+#
4
+# This file is part of rpnifs.
5
+#
6
+#        geneifs is free software: you can redistribute it and/or modify
7
+#        it under the terms of the GNU General Public License as published by
8
+#        the Free Software Foundation, either version 3 of the License, or
9
+#        (at your option) any later version.
10
+#
11
+#        geneifs is distributed in the hope that it will be useful,
12
+#        but WITHOUT ANY WARRANTY; without even the implied warranty of
13
+#        MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14
+#        GNU General Public License for more details.
15
+#
16
+#        You should have received a copy of the GNU General Public License
17
+#        along with geneifs.  If not, see <http://www.gnu.org/licenses/>.
18
+#
19
+
20
+import copy
21
+import pickle
22
+import sys
23
+
24
+import unittest
25
+
26
+
27
+try:
28
+    import pyrpn
29
+except (ImportError, NameError) as e:
30
+    print("Error importing pyrpn. Try to run make.",
31
+          file=sys.stderr)
32
+    raise e
33
+
34
+
35
+## TODO : move this function somewhere or implement it in C
36
+def decode_state(state):
37
+    """ Return a dict containing rpn state dump fields
38
+    """
39
+    res = dict()
40
+    res['real_sz'] = len(state)
41
+    res['total_sz'] = int.from_bytes(state[0:8], byteorder="little")
42
+    res['argc'] =  int.from_bytes(state[8:16], byteorder="little")
43
+    res['stack_sz_bytes'] = state[16:24]
44
+    res['stack_sz'] = state[16]
45
+    res['token_sz'] = int.from_bytes(state[24:32], byteorder="little", signed=True)
46
+    res['stack'] = [int.from_bytes(state[i:i+8], byteorder="little")
47
+                    for i in range(32, 32 + (8*res['stack_sz']),8)]
48
+    res['tokens_off'] = 32 + (8*res['stack_sz'])
49
+    res['tokens_real_sz'] = res['real_sz'] - res['tokens_off']
50
+    res['tokens'] = [state[i:i+24]
51
+                      for i in range(res['tokens_off'],
52
+                                     res['tokens_off'] + (24*res['token_sz']),
53
+                                     24)]
54
+    return res
55
+
56
+
57
+
58
+class TestRpnExprState(unittest.TestCase):
59
+    """ Testing RPNExpr "state" related methods (__copy__, __get/setstate__)
60
+    """
61
+
62
+    def test_pickle_state(self):
63
+        """ Testing pickling/unpickling """
64
+        e = pyrpn.RPNExpr('0x42 + A0', 2)
65
+        e2 = pickle.loads(pickle.dumps(e))
66
+        ds_e = decode_state(e.__getstate__())
67
+        ds_e2 = decode_state(e2.__getstate__())
68
+        self.assertEqual(e.__getstate__(), e2.__getstate__(),
69
+                         msg="EXPR: %r != %r (%r != %r)" % (e, e2, ds_e, ds_e2))
70
+        for i in range(100):
71
+            e = pyrpn.RPNExpr(pyrpn.random_expr(0), 2)
72
+
73
+            e2 = pickle.loads(pickle.dumps(e))
74
+
75
+            self.assertEqual(e.__getstate__(), e2.__getstate__(),
76
+                             msg="EXPR#%d : %r != %r" % (i,e, e2))
77
+
78
+            e3 = pickle.loads(pickle.dumps(e2))
79
+
80
+            self.assertEqual(e.__getstate__(), e3.__getstate__(), msg=e)
81
+            self.assertEqual(e2.__getstate__(), e3.__getstate__(), msg=e)
82
+
83
+
84
+
85
+    def test_copy(self):
86
+        """ Basic copy test based on pickling """
87
+        e = pyrpn.RPNExpr('0x42 + A0', 2)
88
+        e2 = copy.copy(e)
89
+        ds_e = decode_state(e.__getstate__())
90
+        ds_e2 = decode_state(e2.__getstate__())
91
+        self.assertEqual(e.__getstate__(), e2.__getstate__(),
92
+                         msg="EXPR: %r != %r (%r != %r)" % (e, e2, ds_e, ds_e2))
93
+        for i in range(100):
94
+            e = pyrpn.RPNExpr(pyrpn.random_expr(0), 2)
95
+
96
+            e2 = copy.copy(e)
97
+
98
+            self.assertEqual(e.__getstate__(), e2.__getstate__(),
99
+                             msg="EXPR#%d : %r != %r" % (i,e, e2))
100
+
101
+            e3 = pickle.loads(pickle.dumps(e2))
102
+
103
+            self.assertEqual(e.__getstate__(), e3.__getstate__(), msg=e)
104
+            self.assertEqual(e2.__getstate__(), e3.__getstate__(), msg=e)
105
+
106
+
107
+if __name__ == '__main__':
108
+    unittest.main()
109
+

+ 100
- 0
tests/tests_rpn_mutate.py Переглянути файл

@@ -0,0 +1,100 @@
1
+#!/usr/bin/python3
2
+# Copyright 2020, 2023 Weber Yann
3
+#
4
+# This file is part of rpnifs.
5
+#
6
+#        geneifs is free software: you can redistribute it and/or modify
7
+#        it under the terms of the GNU General Public License as published by
8
+#        the Free Software Foundation, either version 3 of the License, or
9
+#        (at your option) any later version.
10
+#
11
+#        geneifs is distributed in the hope that it will be useful,
12
+#        but WITHOUT ANY WARRANTY; without even the implied warranty of
13
+#        MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14
+#        GNU General Public License for more details.
15
+#
16
+#        You should have received a copy of the GNU General Public License
17
+#        along with geneifs.  If not, see <http://www.gnu.org/licenses/>.
18
+#
19
+
20
+import random
21
+import unittest
22
+
23
+
24
+try:
25
+    from tqdm import tqdm
26
+except (ImportError, NameError) as e:
27
+    tqdm = lambda i, *args, **kwargs:iter(i)
28
+
29
+try:
30
+    import pyrpn
31
+except (ImportError, NameError) as e:
32
+    print("Error importing pyrpn. Try to run make.",
33
+          file=sys.stderr)
34
+    raise e
35
+
36
+## TODO : test w_add_elt and w_mut_elt
37
+
38
+class TestRpnExprMutation(unittest.TestCase):
39
+
40
+    def test_base_mutation(self):
41
+        """ Testing mutation without parameters """
42
+        expr = pyrpn.RPNExpr("", 3)
43
+        expr.mutate()
44
+        expr.mutate(n_mutations=10)
45
+
46
+    def test_mutation_params_len(self):
47
+        """ Testing some value for parameters checking resulting expr len """
48
+
49
+        tests = (
50
+            ([1,1,0,0,0,(0,0,0),(0,0,0)], [1,0,100]),
51
+            ([1,2,1,0,0,(0,0,0),(0,0,0)], [1/3]),
52
+            ([1,2,0,1,0,(0,0,0),(0,0,0)], [2/3]),
53
+            ([1,3,1,2,0,(0,0,0),(0,0,0)], [2/6]),
54
+        )
55
+
56
+        for p_args, f_args in tests:
57
+            params = pyrpn.RPNMutationParamsTuple(p_args)
58
+            with self.subTest(params=params):
59
+                self._generic_mutation_params_lentest(params, *f_args)
60
+
61
+    def test_random_int_mutation_params(self):
62
+        """ Testing int random values for parameters checking resulting expr len """
63
+        for _ in range(50):
64
+            m_params = [random.randint(0,20) for _ in range(4)]
65
+            expt_len = (m_params[0]-m_params[1])/sum(m_params)
66
+            expt_len = 0 if expt_len < 0 else expt_len
67
+            params = [1] + m_params + [(0,0,0),(0,0,0)]
68
+            params = pyrpn.RPNMutationParamsTuple(params)
69
+            with self.subTest(params=params):
70
+                self._generic_mutation_params_lentest(params, expt_len)
71
+
72
+    def test_random_float_mutation_params(self):
73
+        """ Testing float random values for parameters checking resulting expr len """
74
+        for _ in range(50):
75
+            m_params = [random.randint(0,20)/20 for _ in range(4)]
76
+            expt_len = (m_params[0]-m_params[1])/sum(m_params)
77
+            expt_len = 0 if expt_len < 0 else expt_len
78
+            params = [1] + m_params + [(0,0,0),(0,0,0)]
79
+            params = pyrpn.RPNMutationParamsTuple(params)
80
+            with self.subTest(params=params):
81
+                self._generic_mutation_params_lentest(params, expt_len)
82
+
83
+    def _generic_mutation_params_lentest(self, params, expt_len, percent_error=.05,n_mut=5000):
84
+
85
+        expt_len = int(expt_len*n_mut)
86
+
87
+        expr = pyrpn.RPNExpr("", 3)
88
+        expr.mutate(params, n_mutations=n_mut)
89
+        msg = "Expected a len of %d ( [%d..%d] with %.1f%% error) but got %d"
90
+        self.assertTrue(abs(len(expr) - expt_len) <= n_mut*percent_error,
91
+                msg=msg % (expt_len,
92
+                           expt_len + n_mut*percent_error,
93
+                           expt_len - n_mut*percent_error,
94
+                           percent_error*100,
95
+                           len(expr)))
96
+
97
+
98
+if __name__ == '__main__':
99
+    unittest.main()
100
+

+ 66
- 0
tests/tests_rpn_sequence.py Переглянути файл

@@ -0,0 +1,66 @@
1
+#!/usr/bin/python3
2
+# Copyright 2023 Weber Yann
3
+#
4
+# This file is part of rpnifs.
5
+#
6
+#        geneifs is free software: you can redistribute it and/or modify
7
+#        it under the terms of the GNU General Public License as published by
8
+#        the Free Software Foundation, either version 3 of the License, or
9
+#        (at your option) any later version.
10
+#
11
+#        geneifs is distributed in the hope that it will be useful,
12
+#        but WITHOUT ANY WARRANTY; without even the implied warranty of
13
+#        MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14
+#        GNU General Public License for more details.
15
+#
16
+#        You should have received a copy of the GNU General Public License
17
+#        along with geneifs.  If not, see <http://www.gnu.org/licenses/>.
18
+#
19
+
20
+import copy
21
+import pickle
22
+import random
23
+import sys
24
+
25
+import unittest
26
+
27
+try:
28
+    import pyrpn
29
+except (ImportError, NameError) as e:
30
+    print("Error importing pyrpn. Try to run make.",
31
+          file=sys.stderr)
32
+    raise e
33
+
34
+
35
+class TestRpnExprCopy(unittest.TestCase):
36
+    """ Testing RPNExpr sequence method """
37
+
38
+
39
+    def test_len(self):
40
+        """ Testing __len__ on some expressions """
41
+
42
+        tests = (
43
+            (['0x42 + A0', 2], 3),
44
+            (['A0', 10], 1),
45
+            (['', 1], 0),
46
+        )
47
+
48
+        for args, elen in tests:
49
+            e = pyrpn.RPNExpr(*args)
50
+            with self.subTest(expr=e, expected_len=elen):
51
+                self.assertEqual(len(e), elen)
52
+
53
+
54
+    def test_rand_len(self):
55
+        """ Testing __len__ with random expressions """
56
+        for _ in range(100):
57
+            elen = random.randint(1,50)
58
+            rexpr = pyrpn.random_expr(30,elen)
59
+            expr = pyrpn.RPNExpr(rexpr, 30)
60
+            with self.subTest(expr=expr, expected_len=elen):
61
+                self.assertEqual(len(expr), elen)
62
+
63
+
64
+if __name__ == '__main__':
65
+    unittest.main()
66
+

Loading…
Відмінити
Зберегти