CooCoo 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 (105) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +16 -0
  3. data/CooCoo.gemspec +47 -0
  4. data/Gemfile +4 -0
  5. data/Gemfile.lock +88 -0
  6. data/README.md +123 -0
  7. data/Rakefile +81 -0
  8. data/bin/cuda-dev-info +25 -0
  9. data/bin/cuda-free +28 -0
  10. data/bin/cuda-free-trend +7 -0
  11. data/bin/ffi-gen +267 -0
  12. data/bin/spec_runner_html.sh +42 -0
  13. data/bin/trainer +198 -0
  14. data/bin/trend-cost +13 -0
  15. data/examples/char-rnn.rb +405 -0
  16. data/examples/cifar/cifar.rb +94 -0
  17. data/examples/img-similarity.rb +201 -0
  18. data/examples/math_ops.rb +57 -0
  19. data/examples/mnist.rb +365 -0
  20. data/examples/mnist_classifier.rb +293 -0
  21. data/examples/mnist_dream.rb +214 -0
  22. data/examples/seeds.rb +268 -0
  23. data/examples/seeds_dataset.txt +210 -0
  24. data/examples/t10k-images-idx3-ubyte +0 -0
  25. data/examples/t10k-labels-idx1-ubyte +0 -0
  26. data/examples/train-images-idx3-ubyte +0 -0
  27. data/examples/train-labels-idx1-ubyte +0 -0
  28. data/ext/buffer/Rakefile +50 -0
  29. data/ext/buffer/buffer.pre.cu +727 -0
  30. data/ext/buffer/matrix.pre.cu +49 -0
  31. data/lib/CooCoo.rb +1 -0
  32. data/lib/coo-coo.rb +18 -0
  33. data/lib/coo-coo/activation_functions.rb +344 -0
  34. data/lib/coo-coo/consts.rb +5 -0
  35. data/lib/coo-coo/convolution.rb +298 -0
  36. data/lib/coo-coo/core_ext.rb +75 -0
  37. data/lib/coo-coo/cost_functions.rb +91 -0
  38. data/lib/coo-coo/cuda.rb +116 -0
  39. data/lib/coo-coo/cuda/device_buffer.rb +240 -0
  40. data/lib/coo-coo/cuda/device_buffer/ffi.rb +109 -0
  41. data/lib/coo-coo/cuda/error.rb +51 -0
  42. data/lib/coo-coo/cuda/host_buffer.rb +117 -0
  43. data/lib/coo-coo/cuda/runtime.rb +157 -0
  44. data/lib/coo-coo/cuda/vector.rb +315 -0
  45. data/lib/coo-coo/data_sources.rb +2 -0
  46. data/lib/coo-coo/data_sources/xournal.rb +25 -0
  47. data/lib/coo-coo/data_sources/xournal/bitmap_stream.rb +197 -0
  48. data/lib/coo-coo/data_sources/xournal/document.rb +377 -0
  49. data/lib/coo-coo/data_sources/xournal/loader.rb +144 -0
  50. data/lib/coo-coo/data_sources/xournal/renderer.rb +101 -0
  51. data/lib/coo-coo/data_sources/xournal/saver.rb +99 -0
  52. data/lib/coo-coo/data_sources/xournal/training_document.rb +78 -0
  53. data/lib/coo-coo/data_sources/xournal/training_document/constants.rb +15 -0
  54. data/lib/coo-coo/data_sources/xournal/training_document/document_maker.rb +89 -0
  55. data/lib/coo-coo/data_sources/xournal/training_document/document_reader.rb +105 -0
  56. data/lib/coo-coo/data_sources/xournal/training_document/example.rb +37 -0
  57. data/lib/coo-coo/data_sources/xournal/training_document/sets.rb +76 -0
  58. data/lib/coo-coo/debug.rb +8 -0
  59. data/lib/coo-coo/dot.rb +129 -0
  60. data/lib/coo-coo/drawing.rb +4 -0
  61. data/lib/coo-coo/drawing/cairo_canvas.rb +100 -0
  62. data/lib/coo-coo/drawing/canvas.rb +68 -0
  63. data/lib/coo-coo/drawing/chunky_canvas.rb +101 -0
  64. data/lib/coo-coo/drawing/sixel.rb +214 -0
  65. data/lib/coo-coo/enum.rb +17 -0
  66. data/lib/coo-coo/from_name.rb +58 -0
  67. data/lib/coo-coo/fully_connected_layer.rb +205 -0
  68. data/lib/coo-coo/generation_script.rb +38 -0
  69. data/lib/coo-coo/grapher.rb +140 -0
  70. data/lib/coo-coo/image.rb +286 -0
  71. data/lib/coo-coo/layer.rb +67 -0
  72. data/lib/coo-coo/layer_factory.rb +26 -0
  73. data/lib/coo-coo/linear_layer.rb +59 -0
  74. data/lib/coo-coo/math.rb +607 -0
  75. data/lib/coo-coo/math/abstract_vector.rb +121 -0
  76. data/lib/coo-coo/math/functions.rb +39 -0
  77. data/lib/coo-coo/math/interpolation.rb +7 -0
  78. data/lib/coo-coo/network.rb +264 -0
  79. data/lib/coo-coo/neuron.rb +112 -0
  80. data/lib/coo-coo/neuron_layer.rb +168 -0
  81. data/lib/coo-coo/option_parser.rb +18 -0
  82. data/lib/coo-coo/platform.rb +17 -0
  83. data/lib/coo-coo/progress_bar.rb +11 -0
  84. data/lib/coo-coo/recurrence/backend.rb +99 -0
  85. data/lib/coo-coo/recurrence/frontend.rb +101 -0
  86. data/lib/coo-coo/sequence.rb +187 -0
  87. data/lib/coo-coo/shell.rb +2 -0
  88. data/lib/coo-coo/temporal_network.rb +291 -0
  89. data/lib/coo-coo/trainer.rb +21 -0
  90. data/lib/coo-coo/trainer/base.rb +67 -0
  91. data/lib/coo-coo/trainer/batch.rb +82 -0
  92. data/lib/coo-coo/trainer/batch_stats.rb +27 -0
  93. data/lib/coo-coo/trainer/momentum_stochastic.rb +59 -0
  94. data/lib/coo-coo/trainer/stochastic.rb +47 -0
  95. data/lib/coo-coo/transformer.rb +272 -0
  96. data/lib/coo-coo/vector_layer.rb +194 -0
  97. data/lib/coo-coo/version.rb +3 -0
  98. data/lib/coo-coo/weight_deltas.rb +23 -0
  99. data/prototypes/convolution.rb +116 -0
  100. data/prototypes/linear_drop.rb +51 -0
  101. data/prototypes/recurrent_layers.rb +79 -0
  102. data/www/images/screamer.png +0 -0
  103. data/www/images/screamer.xcf +0 -0
  104. data/www/index.html +82 -0
  105. metadata +373 -0
@@ -0,0 +1,168 @@
1
+ require 'coo-coo/consts'
2
+ require 'coo-coo/math'
3
+ require 'coo-coo/debug'
4
+ require 'coo-coo/layer_factory'
5
+ require 'coo-coo/neuron'
6
+ require 'coo-coo/sequence'
7
+ require 'coo-coo/weight_deltas'
8
+
9
+ module CooCoo
10
+ class NeuronLayer
11
+ LayerFactory.register_type(self)
12
+
13
+ attr_accessor :activation_function
14
+
15
+ def initialize(num_inputs, size, activation_function = CooCoo.default_activation)
16
+ @activation_function = activation_function
17
+ @neurons = Array.new
18
+ size.times do |i|
19
+ @neurons[i] = Neuron.new(num_inputs, activation_function)
20
+ end
21
+ end
22
+
23
+ def neurons
24
+ @neurons
25
+ end
26
+
27
+ def num_inputs
28
+ @neurons[0].num_inputs
29
+ end
30
+
31
+ def size
32
+ @neurons.size
33
+ end
34
+
35
+ def forward(input, hidden_state)
36
+ o = @neurons.each_with_index.inject(CooCoo::Vector.zeros(size)) do |acc, (neuron, i)|
37
+ acc[i] = neuron.forward(input)
38
+ acc
39
+ end
40
+
41
+ return o, hidden_state
42
+ end
43
+
44
+ def backprop(input, output, errors, hidden_state)
45
+ o = @neurons.each_with_index.inject(CooCoo::Vector.zeros(size)) do |acc, (n, i)|
46
+ acc[i] = n.backprop(input, output[i], errors[i])
47
+ acc
48
+ end
49
+
50
+ return o, hidden_state
51
+ end
52
+
53
+ def transfer_error(deltas)
54
+ @neurons.each_with_index.inject(CooCoo::Vector.zeros(num_inputs)) do |acc, (n, i)|
55
+ acc + n.transfer_error(deltas[i])
56
+ end
57
+ end
58
+
59
+ def transfer_input_error(expecting)
60
+ (output - expecting).to_a
61
+ end
62
+
63
+ def update_weights!(inputs, deltas)
64
+ adjust_weights!(weight_deltas(inputs, deltas))
65
+ end
66
+
67
+ def adjust_weights!(deltas)
68
+ @neurons.each_with_index do |n, i|
69
+ n.adjust_weights!(deltas.bias_deltas[i], deltas.weight_deltas[i])
70
+ end
71
+
72
+ self
73
+ end
74
+
75
+ def weight_deltas(inputs, deltas)
76
+ WeightDeltas.new(*@neurons.each_with_index.inject([ CooCoo::Vector.zeros(size), CooCoo::Sequence.new(size) ]) do |acc, (n, i)|
77
+ acc[0][i], acc[1][i] = n.weight_deltas(inputs, deltas[i])
78
+ acc
79
+ end)
80
+ end
81
+
82
+ def to_hash(network = nil)
83
+ { type: self.class.to_s,
84
+ outputs: @neurons.size,
85
+ neurons: @neurons.collect(&:to_hash)
86
+ }
87
+ end
88
+
89
+ def resize!(new_size)
90
+ n = @neurons + Array.new(new_size - @neurons.size)
91
+ (@neurons.size...new_size).each do |i|
92
+ n[i] = Neuron.new(num_inputs)
93
+ end
94
+
95
+ @neurons = n
96
+
97
+ self
98
+ end
99
+
100
+ def update_neuron_from_hash!(neuron_index, h)
101
+ if neuron_index > @neurons.size
102
+ resize!(neuron_index)
103
+ end
104
+
105
+ @neurons[neuron_index].update_from_hash!(h)
106
+ end
107
+
108
+ def update_from_hash!(h)
109
+ resize!(h[:outputs])
110
+
111
+ h[:outputs].times do |i|
112
+ update_neuron_from_hash!(i, h[:neurons][i])
113
+ end
114
+
115
+ self
116
+ end
117
+
118
+ def ==(other)
119
+ other.kind_of?(self.class) &&
120
+ size == other.size &&
121
+ neurons.zip(other.neurons).all? { |a, b| a == b }
122
+ end
123
+
124
+ class << self
125
+ def from_hash(h, network = nil)
126
+ self.new(h[:neurons].size, h[:outputs]).update_from_hash!(h)
127
+ end
128
+ end
129
+ end
130
+ end
131
+
132
+ if __FILE__ == $0
133
+ layer = CooCoo::NeuronLayer.new(4, 2, CooCoo::ActivationFunctions.from_name(ENV.fetch("ACTIVATION", "Logistic")))
134
+ inputs = [ [ 1.0, 0.0, 0.0, 0.0 ],
135
+ [ 0.0, 0.0, 1.0, 0.0 ],
136
+ [ 0.0, 1.0, 0.0, 0.0],
137
+ [ 0.0, 0.0, 0.0, 1.0 ]
138
+ ].collect do |v|
139
+ CooCoo::Vector[v]
140
+ end
141
+ targets = [ [ 1.0, 0.0 ],
142
+ [ 0.0, 1.0 ],
143
+ [ 0.0, 0.0 ],
144
+ [ 0.0, 0.0 ]
145
+ ].collect do |v|
146
+ CooCoo::Vector[v]
147
+ end
148
+
149
+ inputs.zip(targets).cycle(ENV.fetch('LOOPS', 100).to_i).each do |(input, target)|
150
+ output, hidden_state = layer.forward(input, Hash.new)
151
+ puts("#{input} -> #{target}")
152
+ puts("\toutput: #{output}")
153
+
154
+ err = (output - target)
155
+ #err = err * err * 0.5
156
+ delta, hidden_state = layer.backprop(input, output, err, hidden_state)
157
+ puts("\tdelta: #{delta}")
158
+ puts("\terror: #{err}")
159
+ puts("\txfer: #{layer.transfer_error(delta)}")
160
+
161
+ layer.update_weights!(input, delta * 0.5)
162
+ end
163
+
164
+ inputs.zip(targets).each do |(input, target)|
165
+ output, hidden_state = layer.forward(input, Hash.new)
166
+ puts("#{input} -> #{output}\t#{target}")
167
+ end
168
+ end
@@ -0,0 +1,18 @@
1
+ require 'optparse'
2
+
3
+ module CooCoo
4
+ class OptionParser < ::OptionParser
5
+ def parse!(argv)
6
+ left_overs = []
7
+ begin
8
+ left_overs += super(argv)
9
+ rescue OptionParser::InvalidOption
10
+ left_overs += $!.args
11
+ left_overs << argv.shift
12
+ retry
13
+ end
14
+
15
+ left_overs
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,17 @@
1
+ module CooCoo
2
+ module Platform
3
+ def self.windows?
4
+ RbConfig::CONFIG['PLATFORM_DIR'] == 'win32'
5
+ end
6
+
7
+ ROOT = File.dirname(File.dirname(File.dirname(__FILE__)))
8
+ def self.root
9
+ ROOT
10
+ end
11
+ end
12
+
13
+ def self.root
14
+ Platform.root
15
+ end
16
+ end
17
+
@@ -0,0 +1,11 @@
1
+ require 'ruby-progressbar'
2
+
3
+ module CooCoo
4
+ module ProgressBar
5
+ Defaults = { :format => "%t %c/%C |%B| %a / %e" }
6
+
7
+ def self.create(opts)
8
+ ::ProgressBar.create(Defaults.merge(opts))
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,99 @@
1
+ require 'coo-coo/math'
2
+ require 'coo-coo/layer_factory'
3
+
4
+ module CooCoo
5
+ module Recurrence
6
+ class Backend
7
+ LayerFactory.register_type(self)
8
+
9
+ attr_reader :recurrence_layer
10
+
11
+ def initialize(recurrence_layer, outputs, recurrent_outputs)
12
+ @recurrence_layer = recurrence_layer
13
+ @outputs = outputs
14
+ @recurrent_size = recurrent_outputs
15
+ end
16
+
17
+ def num_inputs
18
+ size + recurrent_size
19
+ end
20
+
21
+ def size
22
+ @outputs
23
+ end
24
+
25
+ def recurrent_size
26
+ @recurrent_size
27
+ end
28
+
29
+ def activation_function
30
+ nil
31
+ end
32
+
33
+ def forward(inputs, hidden_state)
34
+ hidden_state ||= Hash.new
35
+ hidden_state[self] ||= Array.new
36
+ hidden_state[self].push(inputs[size, recurrent_size])
37
+
38
+ return inputs[0, size], hidden_state
39
+ end
40
+
41
+ def backprop(input, output, errors, hidden_state)
42
+ layer_state = hidden_state[@recurrence_layer]
43
+ rec_errors = (layer_state && layer_state.pop) || CooCoo::Vector.zeros(recurrent_size)
44
+ return errors.append(rec_errors), hidden_state
45
+ end
46
+
47
+ def transfer_error(deltas)
48
+ deltas
49
+ end
50
+
51
+ def update_weights!(inputs, deltas)
52
+ self
53
+ end
54
+
55
+ def adjust_weights!(deltas)
56
+ self
57
+ end
58
+
59
+ def weight_deltas(inputs, deltas)
60
+ inputs * deltas
61
+ end
62
+
63
+ def to_hash(network = nil)
64
+ { type: self.class.name,
65
+ outputs: @outputs,
66
+ recurrent_size: @recurrent_size,
67
+ recurrence_layer: network && network.layer_index(@recurrence_layer)
68
+ }
69
+ end
70
+
71
+ def ==(other)
72
+ other.kind_of?(self.class) &&
73
+ size = other.size &&
74
+ recurrence_layer == other.recurrence_layer &&
75
+ recurrent_size == other.recurrent_size
76
+ end
77
+
78
+ def update_from_hash!(h)
79
+ @outputs = h.fetch(:outputs)
80
+ @recurrent_size = h.fetch(:recurrent_size)
81
+ self
82
+ end
83
+
84
+ def self.from_hash(h, network)
85
+ frontend = network.layers[h.fetch(:recurrence_layer)]
86
+ raise ArgumentError.new("Frontend not found") unless frontend
87
+
88
+ layer = self.new(frontend,
89
+ h.fetch(:outputs),
90
+ h.fetch(:recurrent_size)).
91
+ update_from_hash!(h)
92
+
93
+ frontend.backend = layer
94
+
95
+ layer
96
+ end
97
+ end
98
+ end
99
+ end
@@ -0,0 +1,101 @@
1
+ require 'coo-coo/math'
2
+ require 'coo-coo/layer_factory'
3
+ require 'coo-coo/recurrence/backend'
4
+
5
+ module CooCoo
6
+ module Recurrence
7
+ class Frontend
8
+ LayerFactory.register_type(self)
9
+
10
+ def initialize(num_inputs, num_recurrent_outputs)
11
+ @num_inputs = num_inputs
12
+ @num_recurrent_outputs = num_recurrent_outputs
13
+ end
14
+
15
+ def num_inputs
16
+ @num_inputs
17
+ end
18
+
19
+ def activation_function
20
+ nil
21
+ end
22
+
23
+ def size
24
+ @num_inputs + recurrent_size
25
+ end
26
+
27
+ def recurrent_size
28
+ @num_recurrent_outputs
29
+ end
30
+
31
+ def backend
32
+ @backend ||= Backend.new(self, @num_inputs, recurrent_size)
33
+ end
34
+
35
+ def backend=(layer)
36
+ @backend = layer
37
+ end
38
+
39
+ def forward(inputs, hidden_state)
40
+ layer_state = hidden_state[@backend]
41
+ recurrent_input = layer_state && layer_state.pop
42
+ return inputs.append(recurrent_input || empty_input), hidden_state
43
+ end
44
+
45
+ def backprop(input, outputs, errors, hidden_state)
46
+ # split for real and recurrent errors
47
+ norm_errors = errors[0, num_inputs]
48
+ recurrent_errors = errors[num_inputs, recurrent_size]
49
+
50
+ # buffer the recurrent output
51
+ hidden_state ||= Hash.new
52
+ hidden_state[self] ||= Array.new
53
+ hidden_state[self].push(recurrent_errors)
54
+
55
+ # return real errors
56
+ return norm_errors, hidden_state
57
+ end
58
+
59
+ def transfer_error(deltas)
60
+ deltas
61
+ end
62
+
63
+ def weight_deltas(inputs, deltas)
64
+ inputs * deltas
65
+ end
66
+
67
+ def adjust_weights!(deltas)
68
+ self
69
+ end
70
+
71
+ def ==(other)
72
+ other.kind_of?(self.class) &&
73
+ num_inputs == other.num_inputs &&
74
+ recurrent_size == other.recurrent_size
75
+ end
76
+
77
+ def to_hash(network = nil)
78
+ { type: self.class.name,
79
+ inputs: @num_inputs,
80
+ recurrent_outputs: @num_recurrent_outputs
81
+ }
82
+ end
83
+
84
+ def update_from_hash!(h)
85
+ @num_inputs = h.fetch(:inputs)
86
+ @num_recurrent_outputs = h.fetch(:recurrent_outputs)
87
+
88
+ self
89
+ end
90
+
91
+ def self.from_hash(h, network = nil)
92
+ self.new(h.fetch(:inputs), h.fetch(:recurrent_outputs)).update_from_hash!(h)
93
+ end
94
+
95
+ private
96
+ def empty_input
97
+ CooCoo::Vector.zeros(recurrent_size)
98
+ end
99
+ end
100
+ end
101
+ end
@@ -0,0 +1,187 @@
1
+ require 'coo-coo/math'
2
+
3
+ module CooCoo
4
+ class Sequence < Math::AbstractVector
5
+ def initialize(length, &init)
6
+ if length.kind_of?(Array)
7
+ @elements = length
8
+ else
9
+ @elements = Array.new(length, &init)
10
+ end
11
+ end
12
+
13
+ def self.[](value, max_size = nil)
14
+ self.new(value.to_a)
15
+ # ret = new(max_size || value.size)
16
+ # value.each_with_index do |v, i|
17
+ # ret[i] = v
18
+ # end
19
+ # ret
20
+ end
21
+
22
+ # def coerce(other)
23
+ # if other.respond_to?(:each)
24
+ # return self.class[other], self
25
+ # else
26
+ # return self.class.new(self.size, other), self
27
+ # end
28
+ # end
29
+
30
+ def to_a
31
+ @elements
32
+ end
33
+
34
+ def to_s
35
+ values = each.collect do |e|
36
+ e.to_s
37
+ end
38
+
39
+ "[#{values.join(', ')}]"
40
+ end
41
+
42
+ def [](i, len = nil)
43
+ v = @elements[i, len || 1]
44
+ if len
45
+ self.class[v]
46
+ else
47
+ v[0]
48
+ end
49
+ end
50
+
51
+ def []=(i, v)
52
+ @elements[i] = v
53
+ self
54
+ end
55
+
56
+ def each(&block)
57
+ @elements.each(&block)
58
+ end
59
+
60
+ def each_with_index(&block)
61
+ each.each_with_index(&block)
62
+ end
63
+
64
+ include Enumerable
65
+
66
+ def collect(&block)
67
+ self.class[super]
68
+ end
69
+
70
+ def reverse
71
+ self.class[@elements.reverse]
72
+ end
73
+
74
+ def last(*args)
75
+ @elements.last(*args)
76
+ end
77
+
78
+ def append(other)
79
+ v = self.class.new(size + other.size)
80
+ each_with_index do |e, i|
81
+ v[i] = e
82
+ end
83
+ other.each_with_index do |e, i|
84
+ v[i + size] = e
85
+ end
86
+ v
87
+ end
88
+
89
+ def zero
90
+ self.class.new(size) do |i|
91
+ self[0].zero
92
+ end
93
+ end
94
+
95
+ def sum
96
+ @elements.drop(1).inject(@elements[0]) do |acc, e|
97
+ acc + e
98
+ end
99
+ end
100
+
101
+ def average
102
+ sum / size.to_f
103
+ end
104
+
105
+ def sqrt
106
+ self.class[@elements.collect(&:sqrt)]
107
+ end
108
+
109
+ def -@
110
+ self.class[@elements.collect(&:-@)]
111
+ end
112
+
113
+ def +(other)
114
+ v = if other.respond_to?(:each)
115
+ raise ArgumentError.new("Size mismatch: #{size} != #{other.size}") if size != other.size
116
+ other.each.zip(each).collect do |oe, se|
117
+ se + oe
118
+ end
119
+ else
120
+ each.collect do |e|
121
+ e + other
122
+ end
123
+ end
124
+
125
+ self.class[v]
126
+ end
127
+
128
+ def -(other)
129
+ v = if other.respond_to?(:each)
130
+ raise ArgumentError.new("Size mismatch #{size} != #{other.size}") if size != other.size
131
+ other.each.zip(each).collect do |oe, se|
132
+ se - oe
133
+ end
134
+ else
135
+ each.collect do |e|
136
+ e - other
137
+ end
138
+ end
139
+
140
+ self.class[v]
141
+ end
142
+
143
+ def size
144
+ @elements.size
145
+ end
146
+
147
+ def length
148
+ @elements.size
149
+ end
150
+
151
+ def *(other)
152
+ v = if other.respond_to?(:each)
153
+ raise ArgumentError.new("Size mismatch: #{size} != #{other.size}") if size != other.size
154
+ other.each.zip(each).collect do |oe, se|
155
+ se * oe
156
+ end
157
+ else
158
+ each.collect do |e|
159
+ e * other
160
+ end
161
+ end
162
+
163
+ self.class[v]
164
+ end
165
+
166
+ def /(other)
167
+ v = if other.respond_to?(:each)
168
+ raise ArgumentError.new("Size mismatch: #{size} != #{other.size}") if size != other.size
169
+ other.each.zip(each).collect do |oe, se|
170
+ se / oe
171
+ end
172
+ else
173
+ each.collect do |e|
174
+ e / other
175
+ end
176
+ end
177
+
178
+ self.class[v]
179
+ end
180
+
181
+ def ==(other)
182
+ other.size == size && each.zip(other.each).all? do |a, b|
183
+ a == b
184
+ end
185
+ end
186
+ end
187
+ end