nmatrix 0.0.6 → 0.0.7

Sign up to get free protection for your applications and to get access to all the features.
Files changed (67) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +2 -0
  3. data/Gemfile +5 -0
  4. data/History.txt +97 -0
  5. data/Manifest.txt +34 -7
  6. data/README.rdoc +13 -13
  7. data/Rakefile +36 -26
  8. data/ext/nmatrix/data/data.cpp +15 -2
  9. data/ext/nmatrix/data/data.h +4 -0
  10. data/ext/nmatrix/data/ruby_object.h +5 -14
  11. data/ext/nmatrix/extconf.rb +3 -2
  12. data/ext/nmatrix/{util/math.cpp → math.cpp} +296 -6
  13. data/ext/nmatrix/math/asum.h +143 -0
  14. data/ext/nmatrix/math/geev.h +82 -0
  15. data/ext/nmatrix/math/gemm.h +267 -0
  16. data/ext/nmatrix/math/gemv.h +208 -0
  17. data/ext/nmatrix/math/ger.h +96 -0
  18. data/ext/nmatrix/math/gesdd.h +80 -0
  19. data/ext/nmatrix/math/gesvd.h +78 -0
  20. data/ext/nmatrix/math/getf2.h +86 -0
  21. data/ext/nmatrix/math/getrf.h +240 -0
  22. data/ext/nmatrix/math/getri.h +107 -0
  23. data/ext/nmatrix/math/getrs.h +125 -0
  24. data/ext/nmatrix/math/idamax.h +86 -0
  25. data/ext/nmatrix/{util → math}/lapack.h +60 -356
  26. data/ext/nmatrix/math/laswp.h +165 -0
  27. data/ext/nmatrix/math/long_dtype.h +52 -0
  28. data/ext/nmatrix/math/math.h +1154 -0
  29. data/ext/nmatrix/math/nrm2.h +181 -0
  30. data/ext/nmatrix/math/potrs.h +125 -0
  31. data/ext/nmatrix/math/rot.h +141 -0
  32. data/ext/nmatrix/math/rotg.h +115 -0
  33. data/ext/nmatrix/math/scal.h +73 -0
  34. data/ext/nmatrix/math/swap.h +73 -0
  35. data/ext/nmatrix/math/trsm.h +383 -0
  36. data/ext/nmatrix/nmatrix.cpp +176 -152
  37. data/ext/nmatrix/nmatrix.h +1 -2
  38. data/ext/nmatrix/ruby_constants.cpp +9 -4
  39. data/ext/nmatrix/ruby_constants.h +1 -0
  40. data/ext/nmatrix/storage/dense.cpp +57 -41
  41. data/ext/nmatrix/storage/list.cpp +52 -50
  42. data/ext/nmatrix/storage/storage.cpp +59 -43
  43. data/ext/nmatrix/storage/yale.cpp +352 -333
  44. data/ext/nmatrix/storage/yale.h +4 -0
  45. data/lib/nmatrix.rb +2 -2
  46. data/lib/nmatrix/blas.rb +4 -4
  47. data/lib/nmatrix/enumerate.rb +241 -0
  48. data/lib/nmatrix/lapack.rb +54 -1
  49. data/lib/nmatrix/math.rb +462 -0
  50. data/lib/nmatrix/nmatrix.rb +210 -486
  51. data/lib/nmatrix/nvector.rb +0 -62
  52. data/lib/nmatrix/rspec.rb +75 -0
  53. data/lib/nmatrix/shortcuts.rb +136 -108
  54. data/lib/nmatrix/version.rb +1 -1
  55. data/spec/blas_spec.rb +20 -12
  56. data/spec/elementwise_spec.rb +22 -13
  57. data/spec/io_spec.rb +1 -0
  58. data/spec/lapack_spec.rb +197 -0
  59. data/spec/nmatrix_spec.rb +39 -38
  60. data/spec/nvector_spec.rb +3 -9
  61. data/spec/rspec_monkeys.rb +29 -0
  62. data/spec/rspec_spec.rb +34 -0
  63. data/spec/shortcuts_spec.rb +14 -16
  64. data/spec/slice_spec.rb +242 -186
  65. data/spec/spec_helper.rb +19 -0
  66. metadata +33 -5
  67. data/ext/nmatrix/util/math.h +0 -2612
@@ -23,15 +23,16 @@
23
23
  #
24
24
  # == nmatrix.rb
25
25
  #
26
- # This file adds a few additional pieces of functionality (e.g., inspect,
27
- # pretty_print).
26
+ # This file contains those core functionalities which can be
27
+ # implemented efficiently (or much more easily) in Ruby (e.g.,
28
+ # inspect, pretty_print, element-wise operations).
28
29
  #++
29
30
 
30
- require_relative './shortcuts.rb'
31
31
  require_relative './lapack.rb'
32
32
  require_relative './yale_functions.rb'
33
33
 
34
34
  class NMatrix
35
+
35
36
  # Read and write extensions for NMatrix. These are only loaded when needed.
36
37
  #
37
38
  module IO
@@ -51,36 +52,61 @@ class NMatrix
51
52
  autoload :Market, 'nmatrix/io/market'
52
53
  end
53
54
 
55
+ class << self
56
+ #
57
+ # call-seq:
58
+ # load_file(path) -> Mat5Reader
59
+ #
60
+ # * *Arguments* :
61
+ # - +path+ -> The path to a version 5 .mat file.
62
+ # * *Returns* :
63
+ # - A Mat5Reader object.
64
+ #
65
+ def load_file(file_path)
66
+ NMatrix::IO::Mat5Reader.new(File.open(file_path, 'rb')).to_ruby
67
+ end
68
+ end
69
+
54
70
  # TODO: Make this actually pretty.
55
- def pretty_print(q = nil) #:nodoc:
56
- if dim != 2 || (dim == 2 && shape[1] > 10) # FIXME: Come up with a better way of restricting the display
57
- puts self.inspect
71
+ def pretty_print(q) #:nodoc:
72
+ if self.dim > 3 || self.dim == 1
73
+ self.to_a.pretty_print(q)
58
74
  else
59
-
60
- arr = (0...shape[0]).map do |i|
61
- ary = []
62
- (0...shape[1]).each do |j|
63
- o = begin
64
- self[i, j]
65
- rescue ArgumentError
66
- nil
67
- end
68
- ary << (o.nil? ? 'nil' : o)
75
+ # iterate through the whole matrix and find the longest number
76
+ longest = Array.new(self.shape[1], 0)
77
+ self.each_column.with_index do |col, j|
78
+ col.each do |elem|
79
+ elem_len = elem.to_s.size
80
+ longest[j] = elem_len if longest[j] < elem_len
69
81
  end
70
- ary.inspect
71
82
  end
72
83
 
73
- if q.nil?
74
- puts "[" + arr.join("\n") + "]"
75
- else
76
- q.group(1, "", "\n") do
77
- q.seplist(arr, lambda { q.text " " }, :each) { |v| q.text v.to_s }
84
+ if self.dim == 3
85
+ q.group(0, "\n{ layers:", "}") do
86
+ self.each_layer.with_index do |layer,k|
87
+ q.group(0, "\n [\n", " ]\n") do
88
+ layer.each_row.with_index do |row,i|
89
+ q.group(0, " [", "]\n") do
90
+ q.seplist(self[i,0...self.shape[1],k].to_flat_array, lambda { q.text ", "}, :each_with_index) { |v,j| q.text v.to_s.rjust(longest[j]) }
91
+ end
92
+ end
93
+ end
94
+ end
95
+ end
96
+ else # dim 2
97
+ q.group(0, "\n[\n", "]") do
98
+ self.each_row.with_index do |row,i|
99
+ q.group(1, " [", "]") do
100
+ q.seplist(self.dim > 2 ? row.to_a[0] : row.to_a, lambda { q.text ", " }, :each_with_index) { |v,j| q.text v.to_s.rjust(longest[j]) }
101
+ end
102
+ q.breakable
103
+ end
78
104
  end
79
105
  end
80
-
81
106
  end
82
107
  end
83
- alias :pp :pretty_print
108
+ #alias :pp :pretty_print
109
+
84
110
 
85
111
 
86
112
  #
@@ -172,514 +198,234 @@ class NMatrix
172
198
  end
173
199
  alias :to_h :to_hash
174
200
 
175
- #
176
- # call-seq:
177
- # invert! -> NMatrix
178
- #
179
- # Use LAPACK to calculate the inverse of the matrix (in-place). Only works on
180
- # dense matrices.
181
- #
182
- # Note: If you don't have LAPACK, e.g., on a Mac, this may not work yet.
183
- #
184
- def invert!
185
- # Get the pivot array; factor the matrix
186
- pivot = self.getrf!
187
201
 
188
- # Now calculate the inverse using the pivot array
189
- NMatrix::LAPACK::clapack_getri(:row, self.shape[0], self, self.shape[0], pivot)
202
+ def inspect #:nodoc:
203
+ original_inspect = super()
204
+ original_inspect = original_inspect[0...original_inspect.size-1]
205
+ original_inspect + " " + inspect_helper.join(" ") + ">"
206
+ end
207
+
208
+ def __yale_ary__to_s(sym) #:nodoc:
209
+ ary = self.send("__yale_#{sym.to_s}__".to_sym)
190
210
 
191
- self
211
+ '[' + ary.collect { |a| a ? a : 'nil'}.join(',') + ']'
192
212
  end
193
213
 
214
+
215
+ ##
216
+ # call-seq:
217
+ # integer_dtype?() -> Boolean
194
218
  #
195
- # call-seq:
196
- # invert -> NMatrix
197
- #
198
- # Make a copy of the matrix, then invert it (requires LAPACK).
199
- #
200
- # * *Returns* :
201
- # - A dense NMatrix.
219
+ # Checks if dtype is an integer type
202
220
  #
203
- def invert
204
- self.cast(:dense, self.dtype).invert!
221
+ def integer_dtype?
222
+ [:byte, :int8, :int16, :int32, :int64].include?(self.dtype)
205
223
  end
206
- alias :inverse :invert
224
+
207
225
 
208
226
  #
209
227
  # call-seq:
210
- # getrf! -> NMatrix
228
+ # to_f -> Float
211
229
  #
212
- # LU factorization of a general M-by-N matrix +A+ using partial pivoting with
213
- # row interchanges. Only works in dense matrices.
230
+ # Converts an nmatrix with a single element (but any number of dimensions)
231
+ # to a float.
214
232
  #
215
- # * *Returns* :
216
- # - The IPIV vector. The L and U matrices are stored in A.
217
- # * *Raises* :
218
- # - +StorageTypeError+ -> ATLAS functions only work on dense matrices.
233
+ # Raises an IndexError if the matrix does not have just a single element.
219
234
  #
220
- def getrf!
221
- raise(StorageTypeError, "ATLAS functions only work on dense matrices") unless self.stype == :dense
222
- NMatrix::LAPACK::clapack_getrf(:row, self.shape[0], self.shape[1], self, self.shape[0])
235
+ def to_f
236
+ raise IndexError, 'to_f only valid for matrices with a single element' unless shape.all? { |e| e == 1 }
237
+ self[*Array.new(shape.size, 0)]
223
238
  end
224
239
 
225
240
  #
226
241
  # call-seq:
227
- # det -> determinant
228
- #
229
- # Calculate the determinant by way of LU decomposition. This is accomplished
230
- # using clapack_getrf, and then by summing the diagonal elements. There is a
231
- # risk of underflow/overflow.
232
- #
233
- # There are probably also more efficient ways to calculate the determinant.
234
- # This method requires making a copy of the matrix, since clapack_getrf
235
- # modifies its input.
242
+ # to_flat_array -> Array
243
+ # to_flat_a -> Array
236
244
  #
237
- # For smaller matrices, you may be able to use +#det_exact+.
245
+ # Converts an NMatrix to a one-dimensional Ruby Array.
238
246
  #
239
- # This function is guaranteed to return the same type of data in the matrix
240
- # upon which it is called.
241
- # In other words, if you call it on a rational matrix, you'll get a rational
242
- # number back.
247
+ def to_flat_array
248
+ ary = Array.new(self.size)
249
+ self.each.with_index { |v,i| ary[i] = v }
250
+ ary
251
+ end
252
+ alias :to_flat_a :to_flat_array
253
+
243
254
  #
244
- # Integer matrices are converted to rational matrices for the purposes of
245
- # performing the calculation, as xGETRF can't work on integer matrices.
255
+ # call-seq:
256
+ # size -> Fixnum
246
257
  #
247
- # * *Returns* :
248
- # - The determinant of the matrix. It's the same type as the matrix's dtype.
249
- # * *Raises* :
250
- # - +NotImplementedError+ -> Must be used in 2D matrices.
258
+ # Returns the total size of the NMatrix based on its shape.
251
259
  #
252
- def det
253
- raise(NotImplementedError, "determinant can be calculated only for 2D matrices") unless self.dim == 2
254
-
255
- # Cast to a dtype for which getrf is implemented
256
- new_dtype = [:byte,:int8,:int16,:int32,:int64].include?(self.dtype) ? :rational128 : self.dtype
257
- copy = self.cast(:dense, new_dtype)
258
-
259
- # Need to know the number of permutations. We'll add up the diagonals of
260
- # the factorized matrix.
261
- pivot = copy.getrf!
260
+ def size
261
+ s = self.shape
262
+ (0...self.dimensions).inject(1) { |x,i| x * s[i] }
263
+ end
262
264
 
263
- prod = pivot.size % 2 == 1 ? -1 : 1 # odd permutations => negative
264
- [shape[0],shape[1]].min.times do |i|
265
- prod *= copy[i,i]
266
- end
267
265
 
268
- # Convert back to an integer if necessary
269
- new_dtype != self.dtype ? prod.to_i : prod
266
+ def to_s #:nodoc:
267
+ self.to_flat_array.to_s
270
268
  end
271
269
 
272
270
  #
273
271
  # call-seq:
274
- # complex_conjugate -> NMatrix
275
- # complex_conjugate(new_stype) -> NMatrix
276
- #
277
- # Get the complex conjugate of this matrix. See also complex_conjugate! for
278
- # an in-place operation (provided the dtype is already +:complex64+ or
279
- # +:complex128+).
280
- #
281
- # Doesn't work on list matrices, but you can optionally pass in the stype you
282
- # want to cast to if you're dealing with a list matrix.
272
+ # nvector? -> true or false
283
273
  #
284
- # * *Arguments* :
285
- # - +new_stype+ -> stype for the new matrix.
286
- # * *Returns* :
287
- # - If the original NMatrix isn't complex, the result is a +:complex128+ NMatrix. Otherwise, it's the original dtype.
274
+ # Shortcut function for determining whether the effective dimension is less than the dimension.
275
+ # Useful when we take slices of n-dimensional matrices where n > 2.
288
276
  #
289
- def complex_conjugate(new_stype = self.stype)
290
- self.cast(new_stype, NMatrix::upcast(dtype, :complex64)).complex_conjugate!
277
+ def nvector?
278
+ self.effective_dim < self.dim
291
279
  end
292
280
 
293
281
  #
294
282
  # call-seq:
295
- # conjugate_transpose -> NMatrix
283
+ # vector? -> true or false
296
284
  #
297
- # Calculate the conjugate transpose of a matrix. If your dtype is already
298
- # complex, this should only require one copy (for the transpose).
299
- #
300
- # * *Returns* :
301
- # - The conjugate transpose of the matrix as a copy.
285
+ # Shortcut function for determining whether the effective dimension is 1. See also #nvector?
302
286
  #
303
- def conjugate_transpose
304
- self.transpose.complex_conjugate!
287
+ def vector?
288
+ self.effective_dim == 1
305
289
  end
306
290
 
291
+
307
292
  #
308
293
  # call-seq:
309
- # hermitian? -> Boolean
310
- #
311
- # A hermitian matrix is a complex square matrix that is equal to its
312
- # conjugate transpose. (http://en.wikipedia.org/wiki/Hermitian_matrix)
294
+ # to_a -> Array
313
295
  #
314
- # * *Returns* :
315
- # - True if +self+ is a hermitian matrix, nil otherwise.
296
+ # Converts an NMatrix to an array of arrays, or an NMatrix of effective dimension 1 to an array.
316
297
  #
317
- def hermitian?
318
- return false if self.dim != 2 or self.shape[0] != self.shape[1]
319
-
320
- if [:complex64, :complex128].include?(self.dtype)
321
- # TODO: Write much faster Hermitian test in C
322
- self.eql?(self.conjugate_transpose)
323
- else
324
- symmetric?
325
- end
326
- end
327
-
328
- def inspect #:nodoc:
329
- original_inspect = super()
330
- original_inspect = original_inspect[0...original_inspect.size-1]
331
- original_inspect + " " + inspect_helper.join(" ") + ">"
332
- end
333
-
334
- def __yale_ary__to_s(sym) #:nodoc:
335
- ary = self.send("__yale_#{sym.to_s}__".to_sym)
336
-
337
- '[' + ary.collect { |a| a ? a : 'nil'}.join(',') + ']'
338
- end
298
+ # Does not yet work for dimensions > 2
299
+ def to_a(dimen=nil)
300
+ if self.dim == 2
339
301
 
302
+ return self.to_flat_a if self.shape[0] == 1
340
303
 
341
- ##
342
- # call-seq:
343
- # each_along_dim() -> Enumerator
344
- # each_along_dim(dimen) -> Enumerator
345
- # each_along_dim() { |elem| block } -> NMatrix
346
- # each_along_dim(dimen) { |elem| block } -> NMatrix
347
- #
348
- # Successively yields submatrices at each coordinate along a specified
349
- # dimension. Each submatrix will have the same number of dimensions as
350
- # the matrix being iterated, but with the specified dimension's size
351
- # equal to 1.
352
- #
353
- # @param [Integer] dimen the dimension being iterated over.
354
- #
355
- def each_along_dim(dimen=0)
356
- return enum_for(:each_along_dim, dimen) unless block_given?
357
- dims = shape
358
- shape.each_index { |i| dims[i] = 0...(shape[i]) unless i == dimen }
359
- 0.upto(shape[dimen]-1) do |i|
360
- dims[dimen] = i
361
- yield self[*dims]
362
- end
363
- end
364
-
365
-
366
- ##
367
- # call-seq:
368
- # reduce_along_dim() -> Enumerator
369
- # reduce_along_dim(dimen) -> Enumerator
370
- # reduce_along_dim(dimen, initial) -> Enumerator
371
- # reduce_along_dim(dimen, initial, dtype) -> Enumerator
372
- # reduce_along_dim() { |elem| block } -> NMatrix
373
- # reduce_along_dim(dimen) { |elem| block } -> NMatrix
374
- # reduce_along_dim(dimen, initial) { |elem| block } -> NMatrix
375
- # reduce_along_dim(dimen, initial, dtype) { |elem| block } -> NMatrix
376
- #
377
- # Reduces an NMatrix using a supplied block over a specified dimension.
378
- # The block should behave the same way as for Enumerable#reduce.
379
- #
380
- # @param [Integer] dimen the dimension being reduced
381
- # @param [Numeric] initial the initial value for the reduction
382
- # (i.e. the usual parameter to Enumerable#reduce). Supply nil or do not
383
- # supply this argument to have it follow the usual Enumerable#reduce
384
- # behavior of using the first element as the initial value.
385
- # @param [Symbol] dtype if non-nil/false, forces the accumulated result to have this dtype
386
- # @return [NMatrix] an NMatrix with the same number of dimensions as the
387
- # input, but with the input dimension now having size 1. Each element
388
- # is the result of the reduction at that position along the specified
389
- # dimension.
390
- #
391
- def reduce_along_dim(dimen=0, initial=nil, dtype=nil)
392
-
393
- if dimen > shape.size then
394
- raise ArgumentError, "Requested dimension does not exist. Requested: #{dimen}, shape: #{shape}"
395
- end
396
-
397
- return enum_for(:reduce_along_dim, dimen, initial) unless block_given?
398
-
399
- new_shape = shape
400
- new_shape[dimen] = 1
401
-
402
- first_as_acc = false
403
-
404
- if initial then
405
- acc = NMatrix.new(new_shape, initial, dtype || self.dtype)
406
- else
407
- each_along_dim(dimen) do |sub_mat|
408
- if sub_mat.is_a?(NMatrix) and dtype and dtype != self.dtype then
409
- acc = sub_mat.cast(self.stype, dtype)
410
- else
411
- acc = sub_mat
304
+ ary = []
305
+ begin
306
+ self.each_row do |row|
307
+ ary << row.to_flat_a
412
308
  end
413
- break
309
+ #rescue NotImplementedError # Oops. Try copying instead
310
+ # self.each_row(:copy) do |row|
311
+ # ary << row.to_a.flatten
312
+ # end
414
313
  end
415
- first_as_acc = true
416
- end
417
-
418
- each_along_dim(dimen) do |sub_mat|
419
- if first_as_acc then
420
- first_as_acc = false
421
- next
422
- end
423
- acc = (yield acc, sub_mat)
314
+ ary
315
+ else
316
+ to_a_rec(0)
424
317
  end
425
-
426
- acc
427
-
428
318
  end
429
319
 
430
- alias_method :inject_along_dim, :reduce_along_dim
431
320
 
432
- ##
433
- # call-seq:
434
- # integer_dtype?() -> Boolean
435
321
  #
436
- # Checks if dtype is an integer type
437
- #
438
- def integer_dtype?
439
- [:byte, :int8, :int16, :int32, :int64].include?(self.dtype)
440
- end
441
-
442
- ##
443
322
  # call-seq:
444
- # mean() -> NMatrix
445
- # mean(dimen) -> NMatrix
323
+ # rank(dimension, row_or_column_number) -> NMatrix
324
+ # rank(dimension, row_or_column_number, :reference) -> NMatrix reference slice
446
325
  #
447
- # Calculates the mean along the specified dimension.
326
+ # Returns the rank (e.g., row, column, or layer) specified, using slicing by copy as default.
448
327
  #
449
- # This will force integer types to float64 dtype.
450
- #
451
- # @see #reduce_along_dim
452
- #
453
- def mean(dimen=0)
454
- reduce_dtype = nil
455
- if integer_dtype? then
456
- reduce_dtype = :float64
457
- end
458
- reduce_along_dim(dimen, 0.0, reduce_dtype) do |mean, sub_mat|
459
- mean + sub_mat/shape[dimen]
460
- end
461
- end
328
+ # See @row (dimension = 0), @column (dimension = 1)
329
+ def rank(shape_idx, rank_idx, meth = :copy)
462
330
 
463
- ##
464
- # call-seq:
465
- # sum() -> NMatrix
466
- # sum(dimen) -> NMatrix
467
- #
468
- # Calculates the sum along the specified dimension.
469
- #
470
- # @see #reduce_along_dim
471
- def sum(dimen=0)
472
- reduce_along_dim(dimen, 0.0) do |sum, sub_mat|
473
- sum + sub_mat
331
+ params = Array.new(self.dim)
332
+ params.each.with_index do |v,d|
333
+ params[d] = d == shape_idx ? rank_idx : 0...self.shape[d]
474
334
  end
475
- end
476
335
 
477
-
478
- ##
479
- # call-seq:
480
- # min() -> NMatrix
481
- # min(dimen) -> NMatrix
482
- #
483
- # Calculates the minimum along the specified dimension.
484
- #
485
- # @see #reduce_along_dim
486
- #
487
- def min(dimen=0)
488
- reduce_along_dim(dimen) do |min, sub_mat|
489
- if min.is_a? NMatrix then
490
- min * (min <= sub_mat).cast(self.stype, self.dtype) + ((min)*0.0 + (min > sub_mat).cast(self.stype, self.dtype)) * sub_mat
491
- else
492
- min <= sub_mat ? min : sub_mat
493
- end
494
- end
336
+ meth == :reference ? self[*params] : self.slice(*params)
495
337
  end
496
338
 
497
- ##
498
- # call-seq:
499
- # max() -> NMatrix
500
- # max(dimen) -> NMatrix
501
339
  #
502
- # Calculates the maximum along the specified dimension.
503
- #
504
- # @see #reduce_along_dim
505
- #
506
- def max(dimen=0)
507
- reduce_along_dim(dimen) do |max, sub_mat|
508
- if max.is_a? NMatrix then
509
- max * (max >= sub_mat).cast(self.stype, self.dtype) + ((max)*0.0 + (max < sub_mat).cast(self.stype, self.dtype)) * sub_mat
510
- else
511
- max >= sub_mat ? max : sub_mat
512
- end
513
- end
514
- end
515
-
516
-
517
- ##
518
340
  # call-seq:
519
- # variance() -> NMatrix
520
- # variance(dimen) -> NMatrix
521
- #
522
- # Calculates the sample variance along the specified dimension.
523
- #
524
- # This will force integer types to float64 dtype.
525
- #
526
- # @see #reduce_along_dim
341
+ # column(column_number) -> NMatrix
342
+ # column(column_number, get_by) -> NMatrix
527
343
  #
528
- def variance(dimen=0)
529
- reduce_dtype = nil
530
- if integer_dtype? then
531
- reduce_dtype = :float64
532
- end
533
- m = mean(dimen)
534
- reduce_along_dim(dimen, 0.0, reduce_dtype) do |var, sub_mat|
535
- var + (m - sub_mat)*(m - sub_mat)/(shape[dimen]-1)
536
- end
537
- end
538
-
539
- ##
540
- # call-seq:
541
- # std() -> NMatrix
542
- # std(dimen) -> NMatrix
344
+ # Returns the column specified. Uses slicing by copy as default.
543
345
  #
346
+ # * *Arguments* :
347
+ # - +column_number+ -> Integer.
348
+ # - +get_by+ -> Type of slicing to use, +:copy+ or +:reference+.
349
+ # * *Returns* :
350
+ # - A NMatrix representing the requested column as a column vector.
544
351
  #
545
- # Calculates the sample standard deviation along the specified dimension.
352
+ # Examples:
546
353
  #
547
- # This will force integer types to float64 dtype.
354
+ # m = NMatrix.new(2, [1, 4, 9, 14], :int32) # => 1 4
355
+ # 9 14
548
356
  #
549
- # @see #reduce_along_dim
357
+ # m.column(1) # => 4
358
+ # 14
550
359
  #
551
- def std(dimen=0)
552
- variance(dimen).map! { |e| Math.sqrt(e) }
360
+ def column(column_number, get_by = :copy)
361
+ rank(1, column_number, get_by)
553
362
  end
554
363
 
555
- ##
556
- # call-seq:
557
- # to_f -> Float
558
- #
559
- # Converts an nmatrix with a single element (but any number of dimensions)
560
- # to a float.
561
- #
562
- # Raises an IndexError if the matrix does not have just a single element.
563
- #
564
- # FIXME: Does this actually happen? Matrices should not have just one element.
565
- def to_f
566
- raise IndexError, 'to_f only valid for matrices with a single element' unless shape.all? { |e| e == 1 }
567
- self[*Array.new(shape.size, 0)]
568
- end
364
+ alias :col :column
569
365
 
570
- ##
571
- # call-seq:
572
- # each -> Enumerator
573
366
  #
574
- # Enumerate through the matrix. @see Enumerable#each
575
- #
576
- # For dense, this actually calls a specialized each iterator (in C). For yale and list, it relies upon
577
- # #each_with_indices (which is about as fast as reasonably possible for C code).
578
- def each &block
579
- if self.stype == :dense
580
- self.__dense_each__(&block)
581
- else
582
- self.each_with_indices(&block)
583
- end
584
- end
585
-
586
- ##
587
367
  # call-seq:
588
- # map -> Enumerator
589
- # map { |elem| block } -> NMatrix
368
+ # row(row_number) -> NMatrix
369
+ # row(row_number, get_by) -> NMatrix
590
370
  #
591
- # @see Enumerable#map
371
+ # * *Arguments* :
372
+ # - +row_number+ -> Integer.
373
+ # - +get_by+ -> Type of slicing to use, +:copy+ or +:reference+.
374
+ # * *Returns* :
375
+ # - A NMatrix representing the requested row as a row vector.
592
376
  #
593
- def map(&bl)
594
- return enum_for(:map) unless block_given?
595
- cp = self.dup
596
- cp.map! &bl
597
- cp
377
+ def row(row_number, get_by = :copy)
378
+ rank(0, row_number, get_by)
598
379
  end
599
380
 
600
- ##
381
+ #
601
382
  # call-seq:
602
- # map! -> Enumerator
603
- # map! { |elem| block } -> NMatrix
383
+ # layer(layer_number) -> NMatrix
384
+ # row(layer_number, get_by) -> NMatrix
604
385
  #
605
- # Maps in place.
606
- # @see #map
386
+ # * *Arguments* :
387
+ # - +layer_number+ -> Integer.
388
+ # - +get_by+ -> Type of slicing to use, +:copy+ or +:reference+.
389
+ # * *Returns* :
390
+ # - A NMatrix representing the requested layer as a layer vector.
607
391
  #
608
- def map!
609
- return enum_for(:map!) unless block_given?
610
- self.each_stored_with_indices do |e, *i|
611
- self[*i] = (yield e)
612
- end
613
- self
392
+ def layer(layer_number, get_by = :copy)
393
+ rank(2, layer_number, get_by)
614
394
  end
615
395
 
616
396
 
397
+
617
398
  #
618
399
  # call-seq:
619
- # each_row -> ...
620
- #
621
- # Iterate through each row, referencing it as an NVector.
622
- def each_row(get_by=:reference, &block)
623
- (0...self.shape[0]).each do |i|
624
- yield self.row(i, get_by)
625
- end
400
+ # shuffle! -> ...
401
+ # shuffle!(random: rng) -> ...
402
+ #
403
+ # Re-arranges the contents of an NVector.
404
+ #
405
+ # TODO: Write more efficient version for Yale, list.
406
+ # TODO: Generalize for more dimensions.
407
+ def shuffle!(*args)
408
+ method_missing(:shuffle!, *args) if self.effective_dim > 1
409
+ ary = self.to_flat_a
410
+ ary.shuffle!(*args)
411
+ ary.each.with_index { |v,idx| self[idx] = v }
626
412
  self
627
413
  end
628
414
 
415
+
629
416
  #
630
417
  # call-seq:
631
- # each_column -> ...
418
+ # shuffle -> ...
419
+ # shuffle(rng) -> ...
632
420
  #
633
- # Iterate through each column, referencing it as an NVector.
634
- def each_row(get_by=:reference, &block)
635
- (0...self.shape[0]).each do |i|
636
- yield self.row(i, get_by)
637
- end
638
- self
639
- end
640
-
641
-
642
- class << self
643
- #
644
- # call-seq:
645
- # load_file(path) -> Mat5Reader
646
- #
647
- # * *Arguments* :
648
- # - +path+ -> The path to a version 5 .mat file.
649
- # * *Returns* :
650
- # - A Mat5Reader object.
651
- #
652
- def load_file(file_path)
653
- NMatrix::IO::Mat5Reader.new(File.open(file_path, 'rb')).to_ruby
654
- end
655
-
656
- ##
657
- # call-seq:
658
- # ones_like(nm) -> NMatrix
659
- #
660
- # Creates a new matrix of ones with the same dtype and shape as the
661
- # provided matrix.
662
- #
663
- # @param [NMatrix] nm the nmatrix whose dtype and shape will be used
664
- # @return [NMatrix] a new nmatrix filled with ones.
665
- #
666
- def ones_like(nm)
667
- NMatrix.ones(nm.shape, nm.dtype)
668
- end
669
-
670
- ##
671
- # call-seq:
672
- # zeros_like(nm) -> NMatrix
673
- #
674
- # Creates a new matrix of zeros with the same stype, dtype, and shape
675
- # as the provided matrix.
676
- #
677
- # @param [NMatrix] nm the nmatrix whose stype, dtype, and shape will be used
678
- # @return [NMatrix] a new nmatrix filled with zeros.
679
- #
680
- def zeros_like(nm)
681
- NMatrix.zeros(nm.stype, nm.shape, nm.dtype)
682
- end
421
+ # Re-arranges the contents of an NVector.
422
+ #
423
+ # TODO: Write more efficient version for Yale, list.
424
+ # TODO: Generalize for more dimensions.
425
+ def shuffle(*args)
426
+ method_missing(:shuffle!, *args) if self.effective_dim > 1
427
+ t = self.clone
428
+ t.shuffle!(*args)
683
429
  end
684
430
 
685
431
 
@@ -693,54 +439,18 @@ class NMatrix
693
439
  end
694
440
  end
695
441
 
696
- protected
697
- # Define the element-wise operations for lists. Note that the __list_map_merged_stored__ iterator returns a Ruby Object
698
- # matrix, which we then cast back to the appropriate type. If you don't want that, you can redefine these functions in
699
- # your own code.
700
- {add: :+, sub: :-, mul: :*, div: :/, pow: :**, mod: :%}.each_pair do |ewop, op|
701
- define_method("__list_elementwise_#{ewop}__") do |rhs|
702
- self.__list_map_merged_stored__(rhs, nil) { |l,r| l.send(op,r) }.cast(stype, NMatrix.upcast(dtype, rhs.dtype))
703
- end
704
- define_method("__dense_elementwise_#{ewop}__") do |rhs|
705
- self.__dense_map_pair__(rhs) { |l,r| l.send(op,r) }.cast(stype, NMatrix.upcast(dtype, rhs.dtype))
706
- end
707
- define_method("__yale_elementwise_#{ewop}__") do |rhs|
708
- self.__yale_map_merged_stored__(rhs, nil) { |l,r| l.send(op,r) }.cast(stype, NMatrix.upcast(dtype, rhs.dtype))
709
- end
710
- define_method("__list_scalar_#{ewop}__") do |rhs|
711
- self.__list_map_merged_stored__(rhs, nil) { |l,r| l.send(op,r) }.cast(stype, NMatrix.upcast(dtype, NMatrix.min_dtype(rhs)))
712
- end
713
- define_method("__yale_scalar_#{ewop}__") do |rhs|
714
- self.__yale_map_stored__ { |l| l.send(op,rhs) }.cast(stype, NMatrix.upcast(dtype, NMatrix.min_dtype(rhs)))
715
- end
716
- define_method("__dense_scalar_#{ewop}__") do |rhs|
717
- self.__dense_map__ { |l| l.send(op,rhs) }.cast(stype, NMatrix.upcast(dtype, NMatrix.min_dtype(rhs)))
718
- end
719
- end
720
442
 
721
- # Equality operators do not involve a cast. We want to get back matrices of TrueClass and FalseClass.
722
- {eqeq: :==, neq: :!=, lt: :<, gt: :>, leq: :<=, geq: :>=}.each_pair do |ewop, op|
723
- define_method("__list_elementwise_#{ewop}__") do |rhs|
724
- self.__list_map_merged_stored__(rhs, nil) { |l,r| l.send(op,r) }
725
- end
726
- define_method("__dense_elementwise_#{ewop}__") do |rhs|
727
- self.__dense_map_pair__(rhs) { |l,r| l.send(op,r) }
728
- end
729
- define_method("__yale_elementwise_#{ewop}__") do |rhs|
730
- self.__yale_map_merged_stored__(rhs, nil) { |l,r| l.send(op,r) }
731
- end
732
-
733
- define_method("__list_scalar_#{ewop}__") do |rhs|
734
- self.__list_map_merged_stored__(rhs, nil) { |l,r| l.send(op,r) }
735
- end
736
- define_method("__yale_scalar_#{ewop}__") do |rhs|
737
- self.__yale_map_stored__ { |l| l.send(op,rhs) }
738
- end
739
- define_method("__dense_scalar_#{ewop}__") do |rhs|
740
- self.__dense_map__ { |l| l.send(op,rhs) }
443
+ def respond_to?(method) #:nodoc:
444
+ if [:shuffle, :shuffle!, :each_with_index].include?(method.intern) # vector-only methods
445
+ return vector?
446
+ elsif [:each_layer, :layer].include?(method.intern) # 3-or-more dimensions only
447
+ return dim > 2
448
+ else
449
+ super(method)
741
450
  end
742
451
  end
743
452
 
453
+
744
454
  # This is how you write an individual element-wise operation function:
745
455
  #def __list_elementwise_add__ rhs
746
456
  # self.__list_map_merged_stored__(rhs){ |l,r| l+r }.cast(self.stype, NMatrix.upcast(self.dtype, rhs.dtype))
@@ -766,4 +476,18 @@ protected
766
476
  end
767
477
 
768
478
 
479
+ # Helper for converting a matrix into an array of arrays recursively
480
+ def to_a_rec(dimen = 0) #:nodoc:
481
+ return self.flat_map { |v| v } if dimen == self.dim-1
482
+
483
+ ary = []
484
+ self.each_rank(dimen) do |sect|
485
+ ary << sect.to_a_rec(dimen+1)
486
+ end
487
+ ary
488
+ end
769
489
  end
490
+
491
+ require_relative './shortcuts.rb'
492
+ require_relative './math.rb'
493
+ require_relative './enumerate.rb'