gene 0.0.1 → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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