gene 0.0.1 → 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (53) hide show
  1. data/Manifest +46 -0
  2. data/Rakefile +12 -9
  3. data/TODO +7 -0
  4. data/gene.gemspec +34 -0
  5. data/initializers/functional_extensions.rb +44 -0
  6. data/initializers/module_extensions.rb +14 -0
  7. data/initializers/object_extensions.rb +25 -0
  8. data/initializers/range_extensions.rb +9 -0
  9. data/initializers/runner.rb +15 -0
  10. data/initializers/symbol_extensions.rb +11 -0
  11. data/initializers/unbound_method_extensions.rb +3 -0
  12. data/lib/aligner.rb +34 -0
  13. data/lib/calculator.rb +42 -0
  14. data/lib/cell.rb +41 -0
  15. data/lib/color.rb +9 -0
  16. data/lib/dsl.rb +16 -0
  17. data/lib/gene.rb +67 -3
  18. data/lib/generator.rb +83 -0
  19. data/lib/geometry.rb +64 -0
  20. data/lib/hungarian.rb +205 -0
  21. data/lib/imagine.rb +22 -0
  22. data/lib/petri.rb +85 -0
  23. data/lib/point.rb +1 -0
  24. data/lib/trait.rb +60 -0
  25. data/tasks/test.rake +23 -0
  26. data/test/assets/Nova.jpg +0 -0
  27. data/test/assets/Rex.jpg +0 -0
  28. data/test/assets/Squares.jpg +0 -0
  29. data/test/test_helper.rb +6 -0
  30. data/test/unit/aligner_test.rb +91 -0
  31. data/test/unit/calculator_test.rb +100 -0
  32. data/test/unit/cell_test.rb +64 -0
  33. data/test/unit/color_test.rb +23 -0
  34. data/test/unit/dsl_test.rb +45 -0
  35. data/test/unit/functionals_extensions_test.rb +51 -0
  36. data/test/unit/gene_test.rb +76 -0
  37. data/test/unit/generator_test.rb +76 -0
  38. data/test/unit/geometry_test.rb +57 -0
  39. data/test/unit/hungarian_test.rb +196 -0
  40. data/test/unit/imagine_test.rb +54 -0
  41. data/test/unit/module_extensions_test.rb +40 -0
  42. data/test/unit/object_extensions_test.rb +34 -0
  43. data/test/unit/petri_test.rb +87 -0
  44. data/test/unit/range_extensions_test.rb +29 -0
  45. data/test/unit/symbol_extensions_test.rb +18 -0
  46. data/test/unit/trait_test.rb +97 -0
  47. data/test/unit/unbound_method_extensions_test.rb +11 -0
  48. metadata +118 -30
  49. data/History.txt +0 -6
  50. data/Manifest.txt +0 -7
  51. data/README.txt +0 -48
  52. data/bin/gene +0 -3
  53. data/test/test_gene.rb +0 -8
@@ -0,0 +1,83 @@
1
+ class Generator < Dsl
2
+ include Aligner
3
+
4
+ DEFAULT_XOVER_FREQ = 0.1
5
+ DEFAULT_MUTATION_FREQ = 0.25
6
+
7
+ attr_accessor :current_sequence
8
+ attr_reader :cells, :gene_map, :fitness_map, :xover_freq, :mutation_freq
9
+
10
+ def initialize(cell_1, cell_2)
11
+ @cells = (@cell_1, @cell_2 = cell_1, cell_2)
12
+
13
+ @gene_map = align_cells
14
+ @fitness_map = cells.map(&:fitness)
15
+ @current_sequence = rand(2)
16
+ super
17
+ end
18
+
19
+ def combine
20
+ Cell.new(&configuration)
21
+ end
22
+
23
+ private
24
+
25
+ def finish_init
26
+ @xover_freq ||= DEFAULT_XOVER_FREQ
27
+ @mutation_freq ||= DEFAULT_MUTATION_FREQ
28
+ end
29
+
30
+ def align_cells
31
+ alignment_for = align_crossover
32
+
33
+ [
34
+ @cell_1.genes_from_alignment_map(alignment_for[:cell_1]),
35
+ @cell_2.genes_from_alignment_map(alignment_for[:cell_2])
36
+ ]
37
+ end
38
+
39
+ def configuration
40
+ lambda do
41
+ num_genes.times.each do |index|
42
+ send(:"gene_#{index}", &new_gene_from(gene_map[read_sequence][index]))
43
+ end
44
+ end
45
+ end
46
+
47
+ def new_gene_from(model_gene)
48
+ lambda do
49
+ num_points.times.each do |index|
50
+ [:x, :y].each do |axis|
51
+ send(:"point_#{index}_#{axis}", &new_trait_from(model_gene.polygon.points[index].send(axis)))
52
+ end
53
+ end
54
+
55
+ model_gene.color.each_pair do |channel, model_trait|
56
+ send(:"trait_#{channel}", &new_trait_from(model_trait))
57
+ end
58
+ end
59
+ end
60
+
61
+ def new_trait_from(model_trait)
62
+ lambda do
63
+ set_value mutate(model_trait)
64
+ deviate_from fitness_map[current_sequence]
65
+ end
66
+ end
67
+
68
+ def mutate(trait)
69
+ rand(0) < mutation_freq ? trait.mutated_value : trait.value
70
+ end
71
+
72
+ def read_sequence
73
+ rand(0) < xover_freq ? self.current_sequence ^= 1 : current_sequence
74
+ end
75
+
76
+ def method_missing(name, *args, &block)
77
+ case name.to_s
78
+ when "set_xover_freq": @xover_freq = args.first
79
+ when "set_mutation_freq": @mutation_freq = args.first
80
+ else super
81
+ end
82
+ end
83
+ end
@@ -0,0 +1,64 @@
1
+ module Geometry
2
+ def hulled_sequence
3
+ format_points_in(hull)
4
+ end
5
+
6
+ private
7
+
8
+ def hull
9
+ ensure_hullable_with(polygon)
10
+
11
+ list_of_points = polygon.sort_by { |point| point.x.value }
12
+ left_point = list_of_points.shift
13
+ right_point = list_of_points.pop
14
+ determinant = determinant_function(left_point, right_point)
15
+
16
+ lower_list, upper_list = [left_point], [left_point]
17
+ lower_hull, upper_hull = [], []
18
+
19
+ partition_points_from(list_of_points, lower_list, upper_list, determinant)
20
+
21
+ trim_hull(lower_hull, lower_list << right_point, true)
22
+ trim_hull(upper_hull, upper_list << right_point, false)
23
+
24
+ lower_hull + upper_hull.reverse[1..-2]
25
+ end
26
+
27
+ def ensure_hullable_with(points)
28
+ assert_at_least 3, points.length, "Can not calculate the convex hull unless there are at least 3 points (#{points.size} #{points.size == 1 ? 'was' : 'were'} provided)"
29
+ end
30
+
31
+ def partition_points_from(list_of_points, lower_list, upper_list, determinant_lambda)
32
+ until list_of_points.empty?
33
+ point = list_of_points.shift
34
+ (determinant_lambda[point] < 0 ? lower_list : upper_list) << point
35
+ end
36
+ end
37
+
38
+ def trim_hull(hull, list, xor_boolean)
39
+ until list.empty?
40
+ hull << list.shift
41
+ while hull.size >= 3 && !convex?(hull.last(3), xor_boolean)
42
+ hull[-2] = hull[-1]
43
+ hull.pop
44
+ end
45
+ end
46
+ end
47
+
48
+ def determinant_function(point_1, point_2)
49
+ # This is a good candidate for functionals.rb apply_head.
50
+ lambda { |pivot| multiply_difference(point_1, point_2, pivot) - multiply_difference(pivot, point_2, point_1) }
51
+ end
52
+
53
+ def multiply_difference(point_1, point_2, point_3)
54
+ (point_1.x.value - point_2.x.value) * (point_3.y.value - point_2.y.value)
55
+ end
56
+
57
+ def convex?(points, xor_boolean)
58
+ (determinant_function(points[0], points[2])[points[1]] > 0) ^ xor_boolean
59
+ end
60
+
61
+ def format_points_in(array)
62
+ array.inject([]) { |list, point| list << point.x.value << point.y.value }
63
+ end
64
+ end
@@ -0,0 +1,205 @@
1
+ # Written by: Evan Senter
2
+ #
3
+ # - - - - - U S A G E - - - - -
4
+ #
5
+ # hungarian = Hungarian.new(matrix_to_solve)
6
+ # solution = hungarian.solve
7
+ #
8
+ # ...or...
9
+ #
10
+ # hungarian = Hungarian.new
11
+ # solution = hungarian.solve(matrix_to_solve)
12
+ #
13
+ # The method solve returns an array of cell locations such that the assignment problem is solved.
14
+ # See http://en.wikipedia.org/wiki/Hungarian_algorithm for more information on this problem.
15
+ #
16
+ # Adapted from: http://www.public.iastate.edu/~ddoty/HungarianAlgorithm.html
17
+
18
+ class Hungarian
19
+ EMPTY = 0
20
+ STAR = 1
21
+ PRIME = 2
22
+
23
+ def initialize(matrix = nil); setup(matrix) if matrix; end
24
+
25
+ def solve(matrix = nil)
26
+ setup(matrix) if matrix
27
+ raise(ArgumentError, "You must provide a matrix to solve.") unless @matrix
28
+
29
+ method = :minimize_rows
30
+ while method != :finished
31
+ method = self.send(*method)
32
+ end
33
+
34
+ return assignment
35
+ end
36
+
37
+ private
38
+
39
+ # - - - - - M A I N S U B M E T H O D S - - - - -
40
+
41
+ def minimize_rows
42
+ # For each row of the matrix, find the smallest element and subtract it from every element in its row.
43
+
44
+ @matrix.map! do |row|
45
+ min_value = row.min
46
+ row.map { |element| element - min_value }
47
+ end
48
+
49
+ return :star_zeroes
50
+ end
51
+
52
+ def star_zeroes
53
+ # Find a zero (Z) in the resulting matrix. If there is no starred zero in its row or column, star Z. Repeat for each element in the matrix.
54
+
55
+ traverse_indices do |row, column|
56
+ if @matrix[row][column].zero? && !location_covered?(row, column)
57
+ @mask[row][column] = STAR
58
+ cover_cell(row, column)
59
+ end
60
+ end
61
+ reset_covered_hash
62
+
63
+ return :mask_columns
64
+ end
65
+
66
+ def mask_columns
67
+ # Cover each column containing a starred zero. If all columns are covered, the starred zeros describe a complete set of unique assignments.
68
+
69
+ index_range.each do |index|
70
+ @covered[:columns][index] = true if column_mask_values_for(index).any? { |value| value == STAR }
71
+ end
72
+
73
+ return @covered[:columns].all? ? :finished : :prime_zeroes
74
+ end
75
+
76
+ def prime_zeroes
77
+ # Find a noncovered zero and prime it. If there is no starred zero in the row containing this primed zero, call augment_path.
78
+ # Otherwise, cover this row and uncover the column containing the starred zero. Continue in this manner until there are no uncovered zeros left
79
+ # and call adjust_matrix.
80
+
81
+ while (row, column = find_uncovered_zero) != [-1, -1]
82
+ @mask[row][column] = PRIME
83
+
84
+ if star_loc_in_row = row_mask_values_for(row).index(STAR)
85
+ @covered[:rows][row] = true
86
+ @covered[:columns][star_loc_in_row] = false
87
+ else
88
+ return :augment_path, row, column
89
+ end
90
+ end
91
+
92
+ return :adjust_matrix
93
+ end
94
+
95
+ def augment_path(starting_row, starting_column)
96
+ # Construct a series of alternating primed and starred zeroes. Let Z0 represent the uncovered primed zero passed in.
97
+ # Let Z1 denote the starred zero in the column of Z0 (if any). Let Z2 denote the primed zero in the row of Z1 (there will always be one).
98
+ # Continue until the series terminates at a primed zero that has no starred zero in its column. Unstar each starred zero of the series,
99
+ # star each primed zero of the series, erase all primes and uncover every line in the matrix, and call mask_columns.
100
+
101
+ path = [[starting_row, starting_column]]
102
+ path.instance_eval do
103
+ def previous_row; self.last[0]; end
104
+ def previous_column; self.last[1]; end
105
+ end
106
+
107
+ loop do
108
+ if row_containing_star = column_mask_values_for(path.previous_column).index(STAR)
109
+ path << [row_containing_star, path.previous_column]
110
+ else
111
+ break
112
+ end
113
+
114
+ col_containing_prime = row_mask_values_for(path.previous_row).index(PRIME)
115
+ path << [path.previous_row, col_containing_prime]
116
+ end
117
+
118
+ update_elements_in(path)
119
+ traverse_indices { |row, column| @mask[row][column] = EMPTY if @mask[row][column] == PRIME }
120
+ reset_covered_hash
121
+
122
+ return :mask_columns
123
+ end
124
+
125
+ def adjust_matrix
126
+ # Add the smallest value to every element of each covered row, subtract it from every element of each uncovered column, and call prime_zeroes.
127
+
128
+ smallest_value = nil
129
+ traverse_indices do |row, column|
130
+ if !location_covered?(row, column) && (smallest_value.nil? || @matrix[row][column] < smallest_value)
131
+ smallest_value = @matrix[row][column]
132
+ end
133
+ end
134
+
135
+ indices_of_covered_rows.each { |index| @matrix[index].map! { |value| value + smallest_value } }
136
+
137
+ covered_columns = indices_of_uncovered_columns
138
+ index_range.each { |row| covered_columns.each { |column| @matrix[row][column] -= smallest_value } }
139
+
140
+ return :prime_zeroes
141
+ end
142
+
143
+ # - - - - - H E L P E R M E T H O D S - - - - -
144
+
145
+ def setup(matrix)
146
+ @matrix = matrix
147
+ @length = @matrix.length
148
+ @mask = Array.new(@length) { Array.new(@length, EMPTY) } # 2D array of constants (listed above)
149
+ @covered = { :rows => Array.new(@length, false), :columns => Array.new(@length, false) } # Boolean arrays
150
+ end
151
+
152
+ def assignment
153
+ index_range.inject([]) { |path, row_index| path << [row_index, @mask[row_index].index(STAR)] }
154
+ end
155
+
156
+ def update_elements_in(path)
157
+ path.each do |cell|
158
+ @mask[cell[0]][cell[1]] = case @mask[cell[0]][cell[1]]
159
+ when STAR then EMPTY
160
+ when PRIME then STAR
161
+ end
162
+ end
163
+ end
164
+
165
+ def find_uncovered_zero
166
+ traverse_indices do |row, column|
167
+ return [row, column] if @matrix[row][column].zero? && !location_covered?(row, column)
168
+ end
169
+ [-1, -1]
170
+ end
171
+
172
+ def cover_cell(row, column)
173
+ @covered[:rows][row] = @covered[:columns][column] = true
174
+ end
175
+
176
+ def reset_covered_hash
177
+ @covered.values.each { |cover| cover.fill(false) }
178
+ end
179
+
180
+ def location_covered?(row, column)
181
+ @covered[:rows][row] || @covered[:columns][column]
182
+ end
183
+
184
+ def row_mask_values_for(row)
185
+ index_range.map { |column| @mask[row][column] }
186
+ end
187
+
188
+ def column_mask_values_for(column)
189
+ index_range.map { |row| @mask[row][column] }
190
+ end
191
+
192
+ def indices_of_covered_rows
193
+ index_range.select { |index| @covered[:rows][index] }
194
+ end
195
+
196
+ def indices_of_uncovered_columns
197
+ index_range.select { |index| !@covered[:columns][index] }
198
+ end
199
+
200
+ def traverse_indices(&block)
201
+ index_range.each { |row| index_range.each { |column| yield row, column } }
202
+ end
203
+
204
+ def index_range; 0...@length; end
205
+ end
@@ -0,0 +1,22 @@
1
+ module Imagine
2
+ def compare_image_to(image)
3
+ # http://en.wikipedia.org/wiki/RMSD
4
+ aggregate_deviation = 0.0
5
+
6
+ target_image.each_pixel do |target_pixel, x, y|
7
+ aggregate_deviation += compare_pixels(target_pixel, image.pixel_color(x, y))
8
+ end
9
+
10
+ 1 - Math.sqrt(aggregate_deviation / (Magick::MaxRGB ** 2 * image_dimensions.x * image_dimensions.y * 3))
11
+ end
12
+
13
+ private
14
+
15
+ def compare_pixels(target_pixel, actual_pixel)
16
+ [:red, :green, :blue].inject(0.0) { |distance, channel| distance + compare_channel(channel, target_pixel, actual_pixel) }
17
+ end
18
+
19
+ def compare_channel(name, target_pixel, actual_pixel)
20
+ (target_pixel.send(name) - actual_pixel.send(name)) ** 2
21
+ end
22
+ end
@@ -0,0 +1,85 @@
1
+ class Petri < Dsl
2
+ include Imagine
3
+
4
+ attr_reader :round, :target_image, :dish
5
+
6
+ def initialize(image_path)
7
+ @round = 0
8
+ prepare_image_at(image_path)
9
+ super
10
+ end
11
+
12
+ def evolve
13
+ top_k = dish[0, dish.length / 3 * 2]
14
+ @dish = (top_k + (dish.length - top_k.length).times.inject([]) do |new_cells, index|
15
+ new_cells << Generator.new(top_k[rand(top_k.length)], top_k[rand(top_k.length)]).combine
16
+ end)
17
+
18
+ calculate_fitnesses
19
+ sort_by_fitness!
20
+
21
+ if (round % 100).zero?
22
+ dish.first.image.write("#{round}.png")
23
+ puts dish.first.genes.map { |gene| "[" + gene.polygon.map { |point| "#{point.x.value}, #{point.y.value}" }.join(", ") + "]" }.join(", ")
24
+ puts "Fitness: #{dish.first.fitness}"
25
+ end
26
+
27
+ next_round
28
+ end
29
+
30
+ private
31
+
32
+ def prepare_image_at(image_path)
33
+ @target_image = Magick::Image.read(image_path).first
34
+ set_parameter(:image_dimensions, Point.new(target_image.columns, target_image.rows))
35
+ end
36
+
37
+ def finish_init
38
+ set_parameter(:num_cells, 6) unless self.class.respond_to?(:num_cells)
39
+ set_parameter(:num_genes, 3) unless self.class.respond_to?(:num_genes)
40
+ set_parameter(:num_points, 4) unless self.class.respond_to?(:num_points)
41
+
42
+ fill_out_cells
43
+ calculate_fitnesses
44
+ sort_by_fitness!
45
+ end
46
+
47
+ def fill_out_cells
48
+ @dish = num_cells.times.map { Cell.new }
49
+ end
50
+
51
+ def calculate_fitnesses
52
+ dish.each do |cell|
53
+ cell.fitness ||= compare_image_to(cell.image)
54
+ end
55
+ end
56
+
57
+ def sort_by_fitness!
58
+ dish.sort! { |a, b| b.fitness <=> a.fitness }
59
+ end
60
+
61
+ def next_round
62
+ @round += 1
63
+ end
64
+
65
+ def set_num_cells(desired)
66
+ set_parameter(:num_cells, (desired % 3) == 0 ? desired : desired + 3 - (desired % 3))
67
+ end
68
+
69
+ def set_parameter(name, value)
70
+ name = name.to_s.gsub(/^set_/, "")
71
+
72
+ class_metaclass.instance_eval do
73
+ attr_reader name
74
+ end
75
+
76
+ self.class.instance_variable_set(:"@#{name}", value)
77
+ end
78
+
79
+ def method_missing(name, *args, &block)
80
+ case name
81
+ when :set_num_genes, :set_num_points: set_parameter(name, args.first)
82
+ else super
83
+ end
84
+ end
85
+ end