ruby-gsl-ng 0.1.0 → 0.2.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.
@@ -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