public
Description: Genetic programming library in Lisp!
Homepage:
Clone URL: git://github.com/jakevoytko/genesis.git
genesis / genesis.lisp
100644 206 lines (156 sloc) 7.346 kb
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
#|
 
genesis.lisp
 
Copyright (C) 2008 Jake Voytko
 
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation
files (the "Software"), to deal in the Software without restriction,
including without limitation the rights to use, copy, modify, merge,
publish, distribute, sublicense, and/or sell copies of the Software,
and to permit persons to whom the Software is furnished to do so,
subject to the following conditions:
 
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
 
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 
|#
 
 
(in-package "GENESIS")
 
(load "./utility.lisp")
 
(defstruct RULE
  "Defines a Genesis rule, consisting of a function (of arbitrary
arguments) and a list that evaluates to the function."
  list fun)
 
 
(defvar *CURRENT-POPULATION* nil
  "Stores the current population. This should be stored as a list. Be
aware that this is reset by a call to (genetic-algorithm).")
 
 
(defvar *MAX-RULE-SIZE* 15
  "Since some of the evolution functions cause the size of a rule set to grow non-linearly, we provide a maximum rule size.")
 
 
(defun prep-population (starting-rules population-size)
  "Sets 'rule-array' to an array of 'population-size' length, and with
the initial element 'starting-rules'."
  (make-array (list population-size) :initial-element starting-rules))
 
  
(defun modify-rule-list (f list)
  "Decide by 'coin-flip' whether or not to replace each element
of 'list' with the result of a function 'f'."
  (labels ((recurse-rule-list (f list)
             (if (null list)
                 nil
                 (cons (if (coin-flip)
                           (new-rule f)
                           (car list))
                       (modify-rule-list f (cdr list))))))
    (recurse-rule-list f list)))
 
 
(defun new-rule (rule-fun)
  "Defines a new instance of the RULE struct from the list generated by
rule-fun."
  (labels ((gen-new-rule (fun)
             (let ((rule (funcall fun)))
               (make-rule :list rule
                          :fun
                          (eval (list 'lambda '(num)
                                      rule))))))
    (do ((rule (gen-new-rule rule-fun) (gen-new-rule rule-fun)))
        ((not (null (prune-bad-rules (list rule)))) rule))))
 
 
(defun add-to-rule-list (f list)
  "Decide by 'coin-flip' whether or not to append new elements
to 'list' with the result of function 'f'. Appends at least
one new rule."
  (append list (at-least-once (lambda () (new-rule f)))))
 
 
(defun prune-bad-rules (rules)
  "Removes rules that evaluate to a number. This may or may not survive;
it may be designed away, which would be preferable."
  (if (null rules)
      nil
      (let ((value (ignore-errors (eval (rule-list (car rules))))))
        (if (numberp value)
            (prune-bad-rules (cdr rules))
            (cons (car rules) (prune-bad-rules (cdr rules)))))))
 
 
(defun funcall-best (population fitness-fun arg)
  "Calls (funcall-rules .. 'arg') with the best function in 'population'
as determined by 'fitness-fun'"
  (funcall-rules
   (reduce (lambda (a b)
             (let ((fit-a (funcall fitness-fun a))
                   (fit-b (funcall fitness-fun a)))
               (if (< fit-a fit-b) a b)))
           population)
   arg))
          
 
 
(defun funcall-rules (rules arg)
  "Actually perform a function call with the rules!"
  (let ((val arg))
    (dolist (fn rules)
      (setf val (funcall (rule-fun fn) val)))
    val))
 
 
(defun remove-item-from-list (list index)
  "Removes the element at index 'index' from the input list."
  (labels ((remove-from-list (lst ind &optional (i 0))
             (if (null lst)
                 nil
                 (if (eql ind i)
                     (remove-from-list (cdr lst) ind (+ i 1))
                     (cons (car lst)
                           (remove-from-list (cdr lst) ind (+ i 1)))))))
    (remove-from-list list index)))
    
 
 
(defun remove-random-item (rules)
  "Removes a random item from the list, except when 'rules' has a
single element."
  (if (or (null rules) (= 1 (length rules)))
      rules
      (let ((rule (random (length rules))))
        (remove-item-from-list rules rule))))
    
 
 
(defmacro mutate-rule-list (rule-fun rules)
  "Destructively modifies a list of rules by randomly adding and
replacing rules."
  `(progn
    (when (coin-flip) (setf ,rules (remove-random-item ,rules)))
    (when (coin-flip) (setf ,rules (add-to-rule-list ,rule-fun ,rules)))
    (when (coin-flip) (setf ,rules (modify-rule-list ,rule-fun ,rules)))))
 
 
(defun random-merge-rules (rule1 rule2)
  "Randomly merges elements of the lists rule1 and rule2 together."
  (if (eql rule1 rule2)
      rule1
      (merge 'list rule1 rule2 (lambda (a b) (coin-flip)))))
 
 
(defun random-breed (rule1 rule2)
  "Creates a new rule who has rule1 and rule2 as a parent by inserting
subsequences of rule2 into rule1. Extra insertions decided by coin-flip."
  (let* ((new-dna (random-subsequence rule2))
         (start-pos (random (length rule1)))
         (end-pos (+ (random (- (length rule1) start-pos))
                     1 start-pos)))
    (splice rule1 new-dna start-pos end-pos)))
 
 
(defmacro breed-rules (rule1 rule2)
  "Destructively breed genes from rule2 into rule1."
  `(when (not (or (null ,rule1) (null ,rule2)))
     (setf ,rule1
           (if (coin-flip)
               (random-merge-rules ,rule1 ,rule2)))))
; (random-breed ,rule1 ,rule2)))))
 
 
(defun run-generation (rule-fun fitness-fun)
  "Runs a single generation, and update critters in place instead of keeping
the n best."
  (dotimes (rulenum (length *CURRENT-POPULATION*))
    (let* ((cur-rule (aref *CURRENT-POPULATION* rulenum))
           (rules-score (funcall fitness-fun cur-rule))
           (rule-variation (copy-list cur-rule)))
      (if (> (random 6) 0)
          (mutate-rule-list rule-fun rule-variation)
          (breed-rules rule-variation
                       (copy-list (random-array-element *CURRENT-POPULATION*))))
      (when (> (length rule-variation) *MAX-RULE-SIZE*)
        (setf rule-variation (subseq rule-variation 0 *MAX-RULE-SIZE*)))
      (when (<= (funcall fitness-fun rule-variation)
                rules-score)
        (setf (aref *CURRENT-POPULATION* rulenum) rule-variation)))))
                                     
 
 
(defun genetic-algorithm (generations starting-rules rule-fun
                          fitness-fun &key (population-size 10))
  "Runs 'generations' number of generations. The rules are initially
set to to 'starting-rules', the rule generating function is
'rule-fun', and the evaluation function is 'fitness-fun'"
  (setf *CURRENT-POPULATION* (prep-population starting-rules population-size))
  (dotimes (gen-num generations)
    (format t "~A~%" gen-num)
    (run-generation rule-fun fitness-fun)))