nmatrix 0.0.6 → 0.0.7

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 (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'