example.rkt 7.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206
  1. #lang racket
  2. (require rackunit)
  3. (require srfi/1)
  4. ;; memory limit
  5. (define (Mb-to-B n) (* n 1024 1024))
  6. (define MAX-BYTES (Mb-to-B 256))
  7. (custodian-limit-memory (current-custodian) MAX-BYTES)
  8. (define (min a b)
  9. (if (< a b) a b))
  10. (define (max a b)
  11. (if (> a b) a b))
  12. (define (round-to-precision num digits)
  13. (let ([factor (expt 10 digits)])
  14. (/ (round (* factor num)) factor)))
  15. ;;;;;;;;;;;;;;;;;;;;;;;
  16. ;; GENETIC ALGORITHM ;;
  17. ;;;;;;;;;;;;;;;;;;;;;;;
  18. (struct Organism
  19. (dna)
  20. #:transparent)
  21. (define (calculate-fitness org comparator)
  22. (define (iter remaining-target remaining-org-dna result)
  23. (cond [(or (null? remaining-target) (null? remaining-org-dna))
  24. result]
  25. [(comparator (car remaining-target) (car remaining-org-dna))
  26. (iter (cdr remaining-target)
  27. (cdr remaining-org-dna)
  28. (add1 result))]
  29. [else (iter (cdr remaining-target)
  30. (cdr remaining-org-dna)
  31. result)]))
  32. (let ([res (iter TARGET (Organism-dna org) 0)])
  33. (/ res (length (Organism-dna org)))))
  34. (define (get-random-character)
  35. (let loop ([random-char (integer->char (+ 32 (random 95)))])
  36. (if (regexp-match ALPHABET-REGEX (list->string (list random-char)))
  37. (begin #;(printf "valid char: ~s~n" random-char)
  38. random-char)
  39. (begin #;(printf "illegal char: ~s~n" random-char)
  40. (loop (integer->char (+ 32 (random 95))))))))
  41. (define (create-organism)
  42. (let ([dna (for/list ([elem TARGET])
  43. (get-random-character))])
  44. (Organism dna)))
  45. (define (create-initial-population)
  46. (for/list ([counter POPULATION-SIZE])
  47. (create-organism)))
  48. ;; evolving the pool includes 2 actions:
  49. ;; - deciding which organisms to keep of the current generation
  50. ;; - creating an amount or organisms from the chosen ones to make a new generation
  51. (define (evolve-pool population mutation-probability generation#)
  52. ;; IDEA:
  53. ;; percentage of some fitness of the the max-fitness times the population size
  54. (let* ([fitness-sorted-orgs
  55. (sort population (λ (org1 org2)
  56. (> (calculate-fitness org1 char=?)
  57. (calculate-fitness org2 char=?))))]
  58. [survival-count (inexact->exact (floor (* (length population) SURVIVAL-RATIO)))]
  59. [survivors (take fitness-sorted-orgs survival-count)]
  60. [mated-survivors (natural-selection survivors)])
  61. ;; create new pool
  62. (for/list ([current-population-size (in-range POPULATION-SIZE)]
  63. [org (in-cycle mated-survivors)])
  64. (let* ([fitness (calculate-fitness org char=?)]
  65. [mutation-threshold
  66. (max (* (- 1 (sqrt fitness)) mutation-probability)
  67. MINIMUM-MUTATION-PROBABILITY)])
  68. #;(when (= current-population-size (sub1 POPULATION-SIZE))
  69. (printf "mutation-threshold: ~a~n" (exact->inexact mutation-threshold)))
  70. (mutate-organism org mutation-threshold)))))
  71. #| > Randomly pick 2 organisms from the breeding pool and
  72. use them as the parents to create the next generation of
  73. organism for the population. |#
  74. (define (natural-selection survivors)
  75. (define (mate-two survivors)
  76. (let ([the-two (take survivors 2)])
  77. #;(printf "~s~n" (length the-two))
  78. (crossover (car the-two) (cadr the-two))))
  79. (define (mate-survivors survivors result)
  80. (cond [(null? survivors)
  81. #;(printf "survivors empty: ~a~n" survivors)
  82. result]
  83. [(null? (cdr survivors))
  84. #;(printf "special case: ~a~n" survivors)
  85. (cons (car survivors) result)]
  86. [else
  87. #;(printf "remaining survivors: ~s~n" (length survivors))
  88. (let ([offspring (mate-two survivors)])
  89. (mate-survivors (cddr survivors) #;(cons (car survivors)
  90. (cons (cadr survivors)
  91. (cons offspring result)))
  92. (cons offspring result)))]))
  93. (mate-survivors (shuffle survivors) '())
  94. ;; TODO: remove later?
  95. (list (first survivors) (second survivors)))
  96. #|crossover does not calculate the fitness for the organism yet,
  97. because a crossed over organism needs to be mutated first.|#
  98. (define (crossover org1 org2)
  99. (let* ([org1-dna (Organism-dna org1)]
  100. [org2-dna (Organism-dna org2)]
  101. [mid-point (add1 (random (length org1-dna)))]
  102. [new-dna (append (take org1-dna mid-point)
  103. (drop org2-dna mid-point))])
  104. (Organism new-dna)))
  105. #|If a random number is below the mutation threshold,
  106. no mutation is done otherwise mutation is done.|#
  107. (define (mutate-organism org mutation-threshold)
  108. (let ([org-dna (Organism-dna org)])
  109. (struct-copy Organism org
  110. [dna (for/list ([dna-segment org-dna])
  111. (if (< (random) mutation-threshold)
  112. (get-random-character)
  113. dna-segment))])))
  114. ;; retrieves the fittest organism from a list of organisms
  115. (define (get-fittest-organism population)
  116. (car (sort population
  117. (λ (org1 org2)
  118. (> (calculate-fitness org1 char=?)
  119. (calculate-fitness org2 char=?))))))
  120. (random-seed 0)
  121. (define TARGET
  122. (string->list
  123. #;(string-append
  124. "I must not fear. "
  125. "Fear is the mind-killer. "
  126. "Fear is the little-death that brings total obliteration. "
  127. "I will face my fear. "
  128. "I will permit it to pass over me and through me. "
  129. "And when it has gone past I will turn the inner eye to see its path. "
  130. "Where the fear has gone there will be nothing. "
  131. "Only I will remain.")
  132. "To be or not to be."))
  133. (define ALPHABET-REGEX #rx"[a-zA-Z0-9 .,;–!?-]")
  134. (define MAX-GENERATIONS 4000) ; 1500
  135. (define POPULATION-SIZE 500) ; 300
  136. (define SURVIVAL-RATIO 1/20) ; 1/5
  137. (define MAX-TIME 90) ; 90
  138. (define INITIAL-MUTATION-PROBABILITY (/ 1 20)) ; 1/36
  139. (define MINIMUM-MUTATION-PROBABILITY (/ 1 (length TARGET))) ; (/ 1 (length TARGET))
  140. #|
  141. BEST RESULT SO FAR:
  142. gen-max: 1000
  143. pop-size: 400
  144. survi: 1/10
  145. max-time: 90
  146. INITIAL-MUTATION-PROBABILITY: 1/20
  147. MINIMUM-MUTATION-PROBABILITY: (/ 2.5 (length TARGET))
  148. |#
  149. (define (main)
  150. (define start-time (current-inexact-milliseconds))
  151. (printf "Starting time: ~s~n" start-time)
  152. (define INITIAL-POPULATION (create-initial-population))
  153. ;; loop until found
  154. (let loop ([generation# 0]
  155. [population INITIAL-POPULATION])
  156. (let* ([fittest-org (get-fittest-organism population)]
  157. [fittest-org-fitness (calculate-fitness fittest-org char=?)])
  158. #;(printf "fittest org: ~s~n"
  159. (list->string (take (Organism-dna fittest-org) (min 50 (length TARGET)))))
  160. #;(printf "time passed: ~ss~n" (/ (- (current-inexact-milliseconds) start-time) 1000))
  161. (cond [(or (>= (/ (- (current-inexact-milliseconds) start-time) 1000) MAX-TIME)
  162. (>= generation# MAX-GENERATIONS))
  163. (printf "Generation: ~s, Fitness: ~s~n"
  164. generation#
  165. (round-to-precision (exact->inexact fittest-org-fitness) 3))]
  166. [(equal? (Organism-dna fittest-org) TARGET)
  167. (printf "Generation: ~s, Fitness: ~s~n"
  168. generation#
  169. (round-to-precision (exact->inexact fittest-org-fitness) 3))]
  170. [else
  171. (printf "Generation: ~s, Fitness: ~s~n"
  172. generation#
  173. (round-to-precision (exact->inexact fittest-org-fitness) 3))
  174. (loop (add1 generation#)
  175. (evolve-pool population
  176. INITIAL-MUTATION-PROBABILITY
  177. generation#))])))
  178. (define end-time (current-inexact-milliseconds))
  179. (printf "Ending time: ~s~n" end-time)
  180. (printf "Time spent: ~ss~n" (/ (- end-time start-time) 1000)))
  181. (time (for ([i (in-range 50)]) (main)))