ruby-gsl-ng 0.1.0 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,406 @@
1
+ module GSLng
2
+ # A fixed-size MxN matrix.
3
+ #
4
+ # =Notes
5
+ # See Vector notes. Everything applies with the following differences/additions:
6
+ # * The * operator performs actual matrix-matrix and matrix-vector products. To perform element-by-element
7
+ # multiplication use the ^ operator (or multiply method) instead. The rest of the operators work element-by-element.
8
+ # * Operators can handle matrix-matrix, matrix-vector and matrix-scalar (also in reversed order). See #coerce.
9
+ # * The [] and []= operators can handle a "wildcard" value for any dimension, just like MATLAB's colon (:).
10
+ class Matrix
11
+ attr_reader :m, :n, :ptr
12
+
13
+ alias_method :height, :m
14
+ alias_method :width, :n
15
+ alias_method :rows, :m
16
+ alias_method :columns, :n
17
+
18
+ # Returns [ #rows, #columns ]
19
+ def size; [ @m, @n ] end
20
+
21
+ #--------------------- constructors -------------------------#
22
+
23
+ # Create a Matrix of m-by-n (rows and columns). If zero is true, the Matrix is initialized with zeros.
24
+ # Otherwise, the Matrix will contain garbage.
25
+ # You can optionally pass a block, in which case #map_index! will be called with it (i.e.: it works like Array.new).
26
+ def initialize(m, n, zero = false)
27
+ @ptr = (zero ? GSLng.backend::gsl_matrix_calloc(m, n) : GSLng.backend::gsl_matrix_alloc(m, n))
28
+ GSLng.set_finalizer(self, :gsl_matrix_free, @ptr)
29
+
30
+ @m,@n = m,n
31
+ if (block_given?) then self.map_index!(&Proc.new) end
32
+ end
33
+
34
+ def initialize_copy(other) #:nodoc:
35
+ ObjectSpace.undefine_finalizer(self) # TODO: ruby bug?
36
+ @ptr = GSLng.backend::gsl_matrix_alloc(other.m, other.n)
37
+ GSLng.set_finalizer(self, :gsl_matrix_free, @ptr)
38
+
39
+ @m,@n = other.size
40
+ GSLng.backend::gsl_matrix_memcpy(@ptr, other.ptr)
41
+ end
42
+
43
+ # Same as Matrix.new(m, n, true)
44
+ def Matrix.zero(m, n); Matrix.new(m, n, true) end
45
+
46
+ # Create a matrix from an Array
47
+ # If array is unidimensional, a row Matrix is created. If it is multidimensional, each sub-array
48
+ # corresponds to a row of the resulting Matrix. Also, _array_ can be an Array of Ranges, in which case
49
+ # each Range will correspond to a row.
50
+ def Matrix.from_array(array)
51
+ if (array.empty?) then raise "Can't create empty matrix" end
52
+
53
+ if (Numeric === array[0]) then
54
+ Matrix.new(1, array.size) {|i,j| array[j]}
55
+ else
56
+ Matrix.new(array.size, array[0].to_a.size) {|i,j| array[i].to_a[j]}
57
+ end
58
+ end
59
+
60
+ # Create a Matrix from an Array of Arrays/Ranges (see #from_array). For example:
61
+ # Matrix[[1,2],[3,4]]
62
+ # Matrix[1,2,3]
63
+ # Matrix[[1..2],[5..10]]
64
+ def Matrix.[](*args)
65
+ Matrix.from_array(args)
66
+ end
67
+
68
+ # Generates a Matrix of m by n, of random numbers between 0 and 1.
69
+ # NOTE: This simply uses Kernel::rand
70
+ def Matrix.random(m, n)
71
+ Matrix.new(m, n).map!{|x| Kernel::rand}
72
+ end
73
+ class << self; alias_method :rand, :random end
74
+
75
+ #--------------------- setting values -------------------------#
76
+
77
+ # Set all values to _v_
78
+ def all!(v); GSLng.backend::gsl_matrix_set_all(@ptr, v); return self end
79
+ alias_method :set!, :all!
80
+ alias_method :fill!, :all!
81
+
82
+ # Set all values to zero
83
+ def zero!; GSLng.backend::gsl_matrix_set_zero(@ptr); return self end
84
+
85
+ # Set the identity matrix values
86
+ def identity; GSLng.backend::gsl_matrix_set_identity(@ptr); return self end
87
+
88
+ #--------------------- set/get -------------------------#
89
+
90
+ # Access the element (i,j), which means (row,column). *NOTE*: throws exception if out-of-bounds.
91
+ # If either i or j are :* or :all, it serves as a wildcard for that dimension, returning all rows or columns,
92
+ # respectively.
93
+ def [](i, j = :*)
94
+ if (Symbol === i && Symbol === j) then return self
95
+ elsif (Symbol === i)
96
+ col = Vector.new(@m)
97
+ GSLng.backend::gsl_matrix_get_col(col.ptr, @ptr, j)
98
+ return col
99
+ elsif (Symbol === j)
100
+ row = Vector.new(@n)
101
+ GSLng.backend::gsl_matrix_get_row(row.ptr, @ptr, i)
102
+ return row
103
+ else
104
+ GSLng.backend::gsl_matrix_get(@ptr, i, j)
105
+ end
106
+ end
107
+
108
+ # Set the element (i,j), which means (row,column). *NOTE*: throws exception if out-of-bounds.
109
+ # Same indexing options as #[].
110
+ # _value_ can be a single Numeric, a Vector or a Matrix, depending on the indexing.
111
+ def []=(i, j, value)
112
+ if (Symbol === i && Symbol === j) then
113
+ if (Numeric === value) then self.fill!(value)
114
+ else
115
+ x,y = self.coerce(value)
116
+ GSLng.backend::gsl_matrix_memcpy(@ptr, x.ptr)
117
+ end
118
+ elsif (Symbol === i)
119
+ col = Vector.new(@m)
120
+ x,y = col.coerce(value)
121
+ GSLng.backend::gsl_matrix_set_col(@ptr, j, x.ptr)
122
+ return col
123
+ elsif (Symbol === j)
124
+ row = Vector.new(@n)
125
+ x,y = row.coerce(value)
126
+ GSLng.backend::gsl_matrix_set_row(@ptr, i, x.ptr)
127
+ return row
128
+ else
129
+ GSLng.backend::gsl_matrix_set(@ptr, i, j, value)
130
+ end
131
+
132
+ return self
133
+ end
134
+
135
+ #--------------------- view -------------------------#
136
+
137
+ # Create a Matrix::View from this Matrix.
138
+ # If either _m_ or _n_ are nil, they're computed from _x_, _y_ and the Matrix's #size
139
+ def view(x = 0, y = 0, m = nil, n = nil)
140
+ View.new(self, x, y, (m or @m - x), (n or @n - y))
141
+ end
142
+ alias_method :submatrix_view, :view
143
+
144
+ # Shorthand for submatrix_view(..).to_matrix.
145
+ def submatrix(*args); self.submatrix_view(*args).to_matrix end
146
+
147
+ # Creates a Matrix::View for the i-th column
148
+ def column_view(i, offset = 0, size = nil); self.view(offset, i, (size or (self.m - offset)), 1) end
149
+
150
+ # Analogous to #submatrix
151
+ def column(*args); self.column_view(*args).to_matrix end
152
+
153
+ # Creates a Matrix::View for the i-th row
154
+ def row_view(i, offset = 0, size = nil); self.view(i, offset, 1, (size or (self.n - offset))) end
155
+
156
+ # Analogous to #submatrix
157
+ def row(*args); self.row_view(*args).to_matrix end
158
+
159
+ #--------------------- operators -------------------------#
160
+
161
+ # Add other to self
162
+ def add!(other)
163
+ case other
164
+ when Numeric; GSLng.backend::gsl_matrix_add_constant(self.ptr, other.to_f)
165
+ when Matrix; GSLng.backend::gsl_matrix_add(self.ptr, other.ptr)
166
+ else
167
+ x,y = other.coerce(self)
168
+ x.add!(y)
169
+ end
170
+ return self
171
+ end
172
+
173
+ # Substract other from self
174
+ def substract!(other)
175
+ case other
176
+ when Numeric; GSLng.backend::gsl_matrix_add_constant(self.ptr, -other.to_f)
177
+ when Matrix; GSLng.backend::gsl_matrix_sub(self.ptr, other.ptr)
178
+ else
179
+ x,y = other.coerce(self)
180
+ x.substract!(y)
181
+ end
182
+ return self
183
+ end
184
+ alias_method :sub!, :substract!
185
+
186
+ # Multiply (element-by-element) other with self
187
+ def multiply!(other)
188
+ case other
189
+ when Numeric; GSLng.backend::gsl_matrix_scale(self.ptr, other.to_f)
190
+ when Matrix; GSLng.backend::gsl_matrix_mul_elements(self.ptr, other.ptr)
191
+ else
192
+ x,y = other.coerce(self)
193
+ x.multiply!(y)
194
+ end
195
+ return self
196
+ end
197
+ alias_method :mul!, :multiply!
198
+
199
+ # Divide (element-by-element) self by other
200
+ def divide!(other)
201
+ case other
202
+ when Numeric; GSLng.backend::gsl_matrix_scale(self.ptr, 1.0 / other)
203
+ when Matrix; GSLng.backend::gsl_matrix_div_elements(self.ptr, other.ptr)
204
+ else
205
+ x,y = other.coerce(self)
206
+ x.divide!(y)
207
+ end
208
+ return self
209
+ end
210
+ alias_method :div!, :divide!
211
+
212
+ # Element-by-element addition
213
+ def +(other); self.dup.add!(other) end
214
+
215
+ # Element-by-element substraction
216
+ def -(other); self.dup.substract!(other) end
217
+
218
+ # Element-by-element division
219
+ def /(other); self.dup.divide!(other) end
220
+
221
+ # Element-by-element product. Both matrices should have same dimensions.
222
+ def ^(other); self.dup.multiply!(other) end
223
+ alias_method :multiply, :^
224
+ alias_method :mul, :^
225
+
226
+ # Matrix Product. self#n should equal other#m (or other#size, if a Vector).
227
+ # TODO: some cases could be optimized when doing Matrix-Matrix, by using dgemv
228
+ def *(other)
229
+ case other
230
+ when Numeric
231
+ self.multiply(other)
232
+ when Vector
233
+ matrix = Matrix.new(self.m, other.size)
234
+ GSLng.backend::gsl_blas_dgemm(:no_transpose, :no_transpose, 1, self.ptr, other.to_matrix.ptr, 0, matrix.ptr)
235
+ return matrix
236
+ when Matrix
237
+ matrix = Matrix.new(self.m, other.n)
238
+ GSLng.backend::gsl_blas_dgemm(:no_transpose, :no_transpose, 1, self.ptr, other.ptr, 0, matrix.ptr)
239
+ return matrix
240
+ else
241
+ x,y = other.coerce(self)
242
+ x * y
243
+ end
244
+ end
245
+
246
+ #--------------------- swap rows/columns -------------------------#
247
+
248
+ # Transposes in-place. Only for square matrices
249
+ def transpose!; GSLng.backend::gsl_matrix_transpose(self.ptr); return self end
250
+
251
+ # Returns the transpose of self, in a new matrix
252
+ def transpose; matrix = Matrix.new(@n, @m); GSLng.backend::gsl_matrix_transpose_memcpy(matrix.ptr, self.ptr); return matrix end
253
+
254
+ def swap_columns(i, j); GSLng.backend::gsl_matrix_swap_columns(self.ptr, i, j); return self end
255
+ def swap_rows(i, j); GSLng.backend::gsl_matrix_swap_rows(self.ptr, i, j); return self end
256
+
257
+ # Swap the i-th row with the j-th column. The Matrix must be square.
258
+ def swap_rowcol(i, j); GSLng.backend::gsl_matrix_swap_rowcol(self.ptr, i, j); return self end
259
+
260
+ #--------------------- predicate methods -------------------------#
261
+
262
+ # if all elements are zero
263
+ def zero?; GSLng.backend::gsl_matrix_isnull(@ptr) == 1 ? true : false end
264
+
265
+ # if all elements are strictly positive (>0)
266
+ def positive?; GSLng.backend::gsl_matrix_ispos(@ptr) == 1 ? true : false end
267
+
268
+ #if all elements are strictly negative (<0)
269
+ def negative?; GSLng.backend::gsl_matrix_isneg(@ptr) == 1 ? true : false end
270
+
271
+ # if all elements are non-negative (>=0)
272
+ def nonnegative?; GSLng.backend::gsl_matrix_isnonneg(@ptr) == 1 ? true : false end
273
+
274
+ # If this is a column Matrix
275
+ def column?; self.columns == 1 end
276
+
277
+ #--------------------- min/max -------------------------#
278
+
279
+ # Maximum element of the Matrix
280
+ def max; GSLng.backend::gsl_matrix_max(self.ptr) end
281
+
282
+ # Minimum element of the Matrix
283
+ def min; GSLng.backend::gsl_matrix_min(self.ptr) end
284
+
285
+ # Same as Array#minmax
286
+ def minmax
287
+ min = FFI::Buffer.new(:double)
288
+ max = FFI::Buffer.new(:double)
289
+ GSLng.backend::gsl_matrix_minmax(self.ptr, min, max)
290
+ return [min[0].get_float64(0),max[0].get_float64(0)]
291
+ end
292
+
293
+ # Same as #minmax, but returns the indices to the i-th and j-th min, and i-th and j-th max.
294
+ def minmax_index
295
+ i_min = FFI::Buffer.new(:size_t)
296
+ j_min = FFI::Buffer.new(:size_t)
297
+ i_max = FFI::Buffer.new(:size_t)
298
+ j_max = FFI::Buffer.new(:size_t)
299
+ GSLng.backend::gsl_matrix_minmax_index(self.ptr, i_min, j_min, i_max, j_max)
300
+ #return [min[0].get_size_t(0),max[0].get_size_t(0)]
301
+ return [i_min[0].get_ulong(0),j_min[0].get_ulong(0),i_max[0].get_ulong(0),j_max[0].get_ulong(0)]
302
+ end
303
+
304
+ # Same as #min, but returns the indices to the i-th and j-th minimum elements
305
+ def min_index
306
+ i_min = FFI::Buffer.new(:size_t)
307
+ j_min = FFI::Buffer.new(:size_t)
308
+ GSLng.backend::gsl_matrix_min_index(self.ptr, i_min, j_min)
309
+ return [i_min[0].get_ulong(0), j_min[0].get_ulong(0)]
310
+ end
311
+
312
+ # Same as #max, but returns the indices to the i-th and j-th maximum elements
313
+ def max_index
314
+ i_max = FFI::Buffer.new(:size_t)
315
+ j_max = FFI::Buffer.new(:size_t)
316
+ GSLng.backend::gsl_matrix_max_index(self.ptr, i_max, j_max)
317
+ return [i_max[0].get_ulong(0), j_max[0].get_ulong(0)]
318
+ end
319
+
320
+ #--------------------- block handling -------------------------#
321
+
322
+ # Yields the specified block for each element going row-by-row
323
+ def each # :yields: elem
324
+ @m.times {|i| @n.times {|j| yield(self[i,j]) } }
325
+ end
326
+
327
+ # Yields the specified block for each element going row-by-row
328
+ def each_with_index # :yields: elem, i, j
329
+ @m.times {|i| @n.times {|j| yield(self[i,j], i, j) } }
330
+ end
331
+
332
+ # Same as #each, but faster. The catch is that this method returns nothing.
333
+ def fast_each(&block) #:yield: elem
334
+ GSLng.backend::gsl_matrix_each(self.ptr, block)
335
+ end
336
+
337
+ # Efficient map! implementation
338
+ def map!(&block); GSLng.backend::gsl_matrix_map(@ptr, block); return self end
339
+
340
+ # Alternate version of #map!, in this case the block receives the index as a parameter.
341
+ def map_index!(&block); GSLng.backend::gsl_matrix_map_index(@ptr, block); return self end
342
+
343
+ # See #map!. Returns a Matrix.
344
+ def map(&block); self.dup.map!(block) end
345
+
346
+ #--------------------- conversions -------------------------#
347
+
348
+ # Same as Array#join, for example:
349
+ # Matrix[[1,2],[2,3]].join => "1.0 2.0 2.0 3.0"
350
+ def join(sep = $,)
351
+ s = ''
352
+ GSLng.backend::gsl_matrix_each(@ptr, lambda {|e| s += (s.empty?() ? e.to_s : "#{sep}#{e}")})
353
+ return s
354
+ end
355
+
356
+ # Converts the matrix to a String, separating each element with a space and each row with a ';' and a newline:
357
+ # Matrix[[1,2],[2,3]] => "[1.0 2.0;\n 2.0 3.0]"
358
+ def to_s
359
+ s = '['
360
+ @m.times do |i|
361
+ s += ' ' unless i == 0
362
+ @n.times do |j|
363
+ s += (j == 0 ? self[i,j].to_s : ' ' + self[i,j].to_s)
364
+ end
365
+ s += (i == (@m-1) ? ']' : ";\n")
366
+ end
367
+
368
+ return s
369
+ end
370
+
371
+ def inspect #:nodoc:
372
+ "#{self}:Matrix"
373
+ end
374
+
375
+ # Coerces _other_ to be of Matrix class.
376
+ # If _other_ is a scalar (Numeric) a Matrix filled with _other_ values is created.
377
+ # Vectors are coerced using Vector#to_matrix (which results in a row matrix).
378
+ def coerce(other)
379
+ case other
380
+ when Matrix
381
+ [ other, self ]
382
+ when Numeric
383
+ [ Matrix.new(@m, @n).fill!(other), self ]
384
+ when Vector
385
+ [ other.to_matrix, self ]
386
+ else
387
+ raise TypeError, "Can't coerce #{other.class} into #{self.class}"
388
+ end
389
+ end
390
+
391
+ #--------------------- equality -------------------------#
392
+
393
+ # Element-by-element comparison.
394
+ def ==(other)
395
+ if (self.m != other.m || self.n != other.n) then return false end
396
+
397
+ @m.times do |i|
398
+ @n.times do |j|
399
+ if (self[i,j] != other[i,j]) then return false end
400
+ end
401
+ end
402
+
403
+ return true
404
+ end
405
+ end
406
+ end
@@ -0,0 +1,39 @@
1
+ module GSLng
2
+ class Matrix
3
+ # A View of a Matrix.
4
+ #
5
+ # Views reference an existing Matrix and can be used to access parts of it without having to copy
6
+ # it entirely. You can treat a View just like a Matrix.
7
+ # But note that modifying elements of a View will modify the elements of the original matrix.
8
+ #
9
+ class View < Matrix
10
+ attr_reader :owner # The Matrix owning the data this View uses
11
+
12
+ # Create a MatrixView of the sub-matrix starting at (x,y), of size (m,n)
13
+ def initialize(owner, x, y, m, n) #:nodoc:
14
+ @owner = owner
15
+ @m,@n = m,n
16
+ @ptr = GSLng.backend::gsl_matrix_submatrix2(owner.ptr, x, y, m, n)
17
+ GSLng.set_finalizer(self, :gsl_matrix_free, @ptr)
18
+ end
19
+
20
+ # Returns a Matrix (*NOT* a View) copied from this view. In other words,
21
+ # you'll get a Matrix which you can modify without modifying #owner elements.
22
+ def dup
23
+ matrix = Matrix.new(@m, @n)
24
+ GSLng.backend::gsl_matrix_memcpy(matrix.ptr, @ptr)
25
+ return matrix
26
+ end
27
+ alias_method :clone, :dup
28
+ alias_method :to_matrix, :dup
29
+
30
+ def view #:nodoc:
31
+ raise "Can't create a View from a View"
32
+ end
33
+
34
+ def inspect #:nodoc:
35
+ "#{self}:MatrixView"
36
+ end
37
+ end
38
+ end
39
+ end