nmatrix 0.0.4 → 0.0.5

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.
@@ -26,6 +26,6 @@ class NMatrix
26
26
  # Note that the format of the VERSION string is needed for NMatrix
27
27
  # native IO. If you change the format, please make sure that native
28
28
  # IO can still understand NMatrix::VERSION.
29
- VERSION = "0.0.4"
29
+ VERSION = "0.0.5"
30
30
  end
31
31
 
@@ -0,0 +1,112 @@
1
+ #--
2
+ # = NMatrix
3
+ #
4
+ # A linear algebra library for scientific computation in Ruby.
5
+ # NMatrix is part of SciRuby.
6
+ #
7
+ # NMatrix was originally inspired by and derived from NArray, by
8
+ # Masahiro Tanaka: http://narray.rubyforge.org
9
+ #
10
+ # == Copyright Information
11
+ #
12
+ # SciRuby is Copyright (c) 2010 - 2013, Ruby Science Foundation
13
+ # NMatrix is Copyright (c) 2013, Ruby Science Foundation
14
+ #
15
+ # Please see LICENSE.txt for additional copyright notices.
16
+ #
17
+ # == Contributing
18
+ #
19
+ # By contributing source code to SciRuby, you agree to be bound by
20
+ # our Contributor Agreement:
21
+ #
22
+ # * https://github.com/SciRuby/sciruby/wiki/Contributor-Agreement
23
+ #
24
+ # == yale_functions.rb
25
+ #
26
+ # This file contains some shortcut functions for the specialty
27
+ # Yale matrix extensions (mostly for debugging and experimental
28
+ # purposes, but sometimes applicable when you need to speed up
29
+ # your code a lot).
30
+ #++
31
+
32
+ module NMatrix::YaleFunctions
33
+ # call-seq:
34
+ # yale_nd_row_size(i) -> Fixnum
35
+ #
36
+ # Returns the size of a given non-diagonal row.
37
+ def yale_nd_row_size i
38
+ yale_ija(i+1) - yale_ija(i)
39
+ end
40
+
41
+ # call-seq:
42
+ # yale_nd_row_as_array -> Array
43
+ #
44
+ # Returns the non-diagonal column indices which are stored in a given row.
45
+ def yale_nd_row_as_array i
46
+ yale_nd_row(i, :array)
47
+ end
48
+
49
+ # call-seq:
50
+ # yale_nd_row_as_set -> Set
51
+ #
52
+ # Returns the non-diagonal column indices which are stored in a given row, as a Set.
53
+ def yale_nd_row_as_set i
54
+ require 'set'
55
+ yale_nd_row(i, :array).to_set
56
+ end
57
+
58
+ # call-seq:
59
+ # yale_nd_row_as_sorted_set -> SortedSet
60
+ #
61
+ # Returns the non-diagonal column indices which are stored in a given row, as a Set.
62
+ def yale_nd_row_as_sorted_set i
63
+ require 'set'
64
+ SortedSet.new(yale_nd_row(i, :array))
65
+ end
66
+
67
+ # call-seq:
68
+ # yale_nd_row_as_hash -> Hash
69
+ #
70
+ # Returns the non-diagonal column indices and entries stored in a given row.
71
+ def yale_nd_row_as_hash i
72
+ yale_nd_row(i, :hash)
73
+ end
74
+
75
+ # call-seq:
76
+ # yale_row_as_array -> Array
77
+ #
78
+ # Returns the diagonal and non-digonal column indices stored in a given row.
79
+ def yale_row_as_array i
80
+ ary = yale_nd_row(i, :array)
81
+ return ary if i >= self.shape[1] || self[i,i].nil? || self[i,i] == 0
82
+ ary << i
83
+ end
84
+
85
+ # call-seq:
86
+ # yale_row_as_set -> Set
87
+ #
88
+ # Returns the diagonal and non-diagonal column indices stored in a given row.
89
+ def yale_row_as_set i
90
+ require 'set'
91
+ yale_row_as_array(i).to_set
92
+ end
93
+
94
+ # call-seq:
95
+ # yale_row_as_sorted_set -> SortedSet
96
+ #
97
+ # Returns the diagonal and non-diagonal column indices stored in a given row.
98
+ def yale_row_as_sorted_set i
99
+ require 'set'
100
+ SortedSet.new(yale_row_as_array(i))
101
+ end
102
+
103
+ # call-seq:
104
+ # yale_row_as_hash -> Hash
105
+ #
106
+ # Returns the diagonal and non-diagonal column indices and entries stored in a given row.
107
+ def yale_row_as_hash i
108
+ h = yale_nd_row(i, :hash)
109
+ return h if i >= self.shape[1] || self[i,i].nil? || self[i,i] == 0
110
+ h[i] = self[i,i]
111
+ end
112
+ end
data/spec/blas_spec.rb CHANGED
@@ -170,6 +170,17 @@ describe NMatrix::BLAS do
170
170
  NMatrix::BLAS.gemv(a, x)
171
171
  end
172
172
 
173
+ it "exposes asum" do
174
+ x = NVector.new(4, [1,2,3,4], :float64)
175
+ NMatrix::BLAS.asum(x).should == 10.0
176
+ end
177
+
178
+
179
+ it "exposes nrm2" do
180
+ x = NVector.new(4, [2,-4,3,5], :float64)
181
+ NMatrix::BLAS.nrm2(x, 1, 3).should be_within(1e-10).of(5.385164807134504)
182
+ end
183
+
173
184
  end
174
185
  end
175
186
  end
@@ -74,7 +74,9 @@ describe NMatrix do
74
74
  (@n/m).should == r
75
75
  end
76
76
 
77
- it "should perform element-wise modulo"
77
+ it "should perform element-wise modulo" do
78
+ pending "% operator not yet implemented for matrices"
79
+ end
78
80
 
79
81
  it "should handle element-wise equality (=~)" do
80
82
  (@n =~ @m).cast(:dense, :byte).should == NMatrix.new(:dense, 2, [0, 1, 1, 0], :byte)
@@ -131,6 +133,7 @@ describe NMatrix do
131
133
  end
132
134
 
133
135
  it "modulo" do
136
+ pending "% operator not yet implemented"
134
137
  r = @n % @m
135
138
  r.should == NMatrix.new(:dense, [2,2], [0, 1, 2, 3], :int64)
136
139
  end
data/spec/io_spec.rb CHANGED
@@ -70,6 +70,14 @@ describe NMatrix::IO do
70
70
  `wc -l spec/utm5940.mtx`.split[0].should == `wc -l spec/utm5940.saved.mtx`.split[0]
71
71
  end
72
72
 
73
+ it "raises an error when reading a non-existent file" do
74
+ fn = rand(10000000).to_i.to_s
75
+ while File.exist?(fn)
76
+ fn = rand(10000000).to_i.to_s
77
+ end
78
+ expect{ NMatrix.read(fn) }.to raise_error(Errno::ENOENT)
79
+ end
80
+
73
81
  it "reads and writes NMatrix dense" do
74
82
  n = NMatrix.new(:dense, [4,3], [0,1,2,3,4,5,6,7,8,9,10,11], :int32)
75
83
  n.write("test-out")
data/spec/lapack_spec.rb CHANGED
@@ -47,13 +47,26 @@ describe NMatrix::LAPACK do
47
47
  it "exposes clapack getrf" do
48
48
  a = NMatrix.new(:dense, 3, [4,9,2,3,5,7,8,1,6], dtype)
49
49
  NMatrix::LAPACK::clapack_getrf(:row, 3, 3, a, 3)
50
- a[0,0].should == 8
51
- a[0,1].should == 1
52
- a[0,2].should == 6
53
- a[1,0].should == 1.quo(2)
54
- a[1,1].should == 17.quo(2)
55
- a[1,2].should == -1
56
- a[2,0].should == 3.quo(8)
50
+
51
+ # delta varies for different dtypes
52
+ err = case dtype
53
+ when :float32, :complex64
54
+ 1e-6
55
+ when :float64, :complex128
56
+ 1e-15
57
+ else
58
+ 1e-64 # FIXME: should be 0, but be_within(0) does not work.
59
+ end
60
+
61
+ a[0,0].should == 9 # 8
62
+ a[0,1].should be_within(err).of(2.quo(9)) # 1
63
+ a[0,2].should be_within(err).of(4.quo(9)) # 6
64
+ a[1,0].should == 5 # 1.quo(2)
65
+ a[1,1].should be_within(err).of(53.quo(9)) # 17.quo(2)
66
+ a[1,2].should be_within(err).of(7.quo(53)) # -1
67
+ a[2,0].should == 1 # 3.quo(8)
68
+ a[2,1].should be_within(err).of(52.quo(9))
69
+ a[2,2].should be_within(err).of(360.quo(53))
57
70
  # FIXME: these are rounded, == won't work
58
71
  #a[2,1].should == 0.544118
59
72
  #a[2,2].should == 5.294118
@@ -61,10 +74,14 @@ describe NMatrix::LAPACK do
61
74
 
62
75
  it "exposes clapack potrf" do
63
76
  # first do upper
64
- a = NMatrix.new(:dense, 3, [25,15,-5, 0,18,0, 0,0,11], dtype)
65
- NMatrix::LAPACK::clapack_potrf(:row, :upper, 3, a, 3)
66
- b = NMatrix.new(:dense, 3, [5,3,-1, 0,3,1, 0,0,3], dtype)
67
- a.should == b
77
+ begin
78
+ a = NMatrix.new(:dense, 3, [25,15,-5, 0,18,0, 0,0,11], dtype)
79
+ NMatrix::LAPACK::clapack_potrf(:row, :upper, 3, a, 3)
80
+ b = NMatrix.new(:dense, 3, [5,3,-1, 0,3,1, 0,0,3], dtype)
81
+ a.should == b
82
+ rescue NotImplementedError => e
83
+ pending e.to_s
84
+ end
68
85
 
69
86
  # then do lower
70
87
  a = NMatrix.new(:dense, 3, [25,0,0, 15,18,0,-5,0,11], dtype)
@@ -89,13 +106,18 @@ describe NMatrix::LAPACK do
89
106
  it "exposes clapack getri" do
90
107
  a = NMatrix.new(:dense, 3, [1,0,4,1,1,6,-3,0,-10], dtype)
91
108
  ipiv = NMatrix::LAPACK::clapack_getrf(:row, 3, 3, a, 3) # get pivot from getrf, use for getri
92
- NMatrix::LAPACK::clapack_getri(:row, 3, a, 3, ipiv)
93
109
 
94
- b = NMatrix.new(:dense, 3, [-5,0,-2,-4,1,-1,1.5,0,0.5], dtype)
95
- a.should == b
110
+ begin
111
+ NMatrix::LAPACK::clapack_getri(:row, 3, a, 3, ipiv)
112
+
113
+ b = NMatrix.new(:dense, 3, [-5,0,-2,-4,1,-1,1.5,0,0.5], dtype)
114
+ a.should == b
115
+ rescue NotImplementedError => e
116
+ pending e.to_s
117
+ end
96
118
  end
97
119
 
98
120
 
99
121
  end
100
122
  end
101
- end
123
+ end
data/spec/leakcheck.rb ADDED
@@ -0,0 +1,16 @@
1
+ require "./lib/nmatrix"
2
+
3
+ # Fixed:
4
+ #n = NMatrix.new(:yale, [8,2], :int64)
5
+ #m = NMatrix.new(:yale, [2,8], :int64)
6
+ #100.times do
7
+ # n.dot(m)
8
+ #end
9
+ #GC.start
10
+
11
+ # Remaining:
12
+ 100.times do |t|
13
+ n = NMatrix.new(:dense, 1000, :float64)
14
+ n[0,t] = 1.0
15
+ puts n[t,0]
16
+ end
data/spec/math_spec.rb CHANGED
@@ -50,7 +50,11 @@ describe "math" do
50
50
  it "should correctly invert a matrix" do
51
51
  a = NMatrix.new(:dense, 3, [1,0,4,1,1,6,-3,0,-10], dtype)
52
52
  b = NMatrix.new(:dense, 3, [-5,0,-2,-4,1,-1,1.5,0,0.5], dtype)
53
- a.invert!
53
+ begin
54
+ a.invert!
55
+ rescue NotImplementedError => e
56
+ pending e.to_s
57
+ end
54
58
  a.should == b
55
59
  end
56
60
  end
@@ -125,7 +129,7 @@ describe "math" do
125
129
  #STDERR.puts "2"
126
130
  n = NMatrix.new([4,3], [1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0, 11.0, 12.0], left_dtype)
127
131
 
128
- m = NVector.new(3, [2.0, 1.0, 0.0], right_dtype)
132
+ m = NVector.new(3, [2.0, 1.0, 0.0], right_dtype).transpose
129
133
 
130
134
  m.shape[0].should == 3
131
135
  m.shape[1].should == 1
data/spec/nmatrix_spec.rb CHANGED
@@ -25,8 +25,7 @@
25
25
  # Basic tests for NMatrix.
26
26
  #
27
27
 
28
- # Can we use require_relative here instead?
29
- require File.join(File.dirname(__FILE__), "spec_helper.rb")
28
+ require File.dirname(__FILE__) + "/spec_helper.rb"
30
29
 
31
30
  describe NMatrix do
32
31
 
@@ -142,7 +141,7 @@ describe NMatrix do
142
141
 
143
142
  [:dense, :list, :yale].each do |storage_type|
144
143
  context storage_type do
145
- it "can be duplicated" do
144
+ it "can be duplicated" do
146
145
  n = NMatrix.new(storage_type, [2,3], storage_type == :yale ? :float64 : 1.1)
147
146
  n.stype.should equal(storage_type)
148
147
 
@@ -292,8 +291,215 @@ describe NMatrix do
292
291
  end
293
292
  end
294
293
 
294
+ context "dense" do
295
+ it "should return the matrix being iterated over when each is called with a block" do
296
+ a = NMatrix.new(2, 1)
297
+ val = (a.each { })
298
+ val.should eq a
299
+ end
300
+
301
+ it "should return the matrix being iterated over when each_stored_with_indices is called with a block" do
302
+ a = NMatrix.new(2,1)
303
+ val = (a.each_stored_with_indices { })
304
+ val.should eq a
305
+ end
306
+ end
307
+
308
+ [:list, :yale].each do |storage_type|
309
+ context storage_type do
310
+ it "should return the matrix being iterated over when each_stored_with_indices is called with a block" do
311
+ n = NMatrix.new(storage_type, [2,3], storage_type == :yale ? :float64 : 1.1)
312
+ val = (n.each_stored_with_indices { })
313
+ val.should eq n
314
+ end
315
+
316
+ it "should return an enumerator when each_stored_with_indices is called without a block" do
317
+ n = NMatrix.new(storage_type, [2,3], storage_type == :yale ? :float64 : 1.1)
318
+ val = n.each_stored_with_indices
319
+ val.should be_a Enumerator
320
+ end
321
+
322
+ end
323
+ end
324
+
295
325
  it "should iterate through element 256 without a segfault" do
296
326
  t = NVector.random(256)
297
327
  t.each { |x| x + 0 }
298
328
  end
329
+
330
+ context "mapping and reduction related functions" do
331
+
332
+ before :each do
333
+ @nm_1d = N[5.0,0.0,1.0,2.0,3.0]
334
+ @nm_2d = N[[0.0,1.0],[2.0,3.0]]
335
+ end
336
+
337
+ it "behaves like Enumerable#reduce with no argument to reduce" do
338
+ @nm_1d.reduce_along_dim(0) { |acc, el| acc + el }.to_f.should eq 11
339
+ @nm_2d.reduce_along_dim(1) { |acc, el| acc + el }.should eq N[[1, 5]]
340
+ end
341
+
342
+ it "should calculate the mean along the specified dimension" do
343
+ @nm_1d.mean.should eq N[2.2]
344
+ @nm_2d.mean.should eq N[[1.0,2.0]]
345
+ end
346
+
347
+ it "should calculate the minimum along the specified dimension" do
348
+ @nm_1d.min.should eq 0.0
349
+ @nm_2d.min.should eq N[[0.0, 1.0]]
350
+ @nm_2d.min(1).should eq N[[0.0], [2.0]]
351
+ end
352
+
353
+ it "should calculate the maximum along the specified dimension" do
354
+ @nm_1d.max.should eq 5.0
355
+ @nm_2d.max.should eq N[[2.0, 3.0]]
356
+ end
357
+
358
+ it "should calculate the variance along the specified dimension" do
359
+ @nm_1d.variance.should eq N[3.7]
360
+ @nm_2d.variance(1).should eq N[[0.5], [0.5]]
361
+ end
362
+
363
+ it "should calculate the sum along the specified dimension" do
364
+ @nm_1d.sum.should eq N[11]
365
+ @nm_2d.sum.should eq N[[2], [4]]
366
+ end
367
+
368
+ it "should calculate the standard deviation along the specified dimension" do
369
+ @nm_1d.std.should eq N[Math.sqrt(3.7)]
370
+ @nm_2d.std(1).should eq N[[Math.sqrt(0.5)], [Math.sqrt(0.5)]]
371
+ end
372
+
373
+ it "should raise an ArgumentError when any invalid dimension is provided" do
374
+ expect { @nm_1d.mean(3) }.to raise_exception(ArgumentError)
375
+ end
376
+
377
+ it "should convert to float if it contains only a single element" do
378
+ N[4.0].to_f.should eq 4.0
379
+ N[[[[4.0]]]].to_f.should eq 4.0
380
+ end
381
+
382
+ it "should raise an index error if it contains more than a single element" do
383
+ expect { @nm_1d.to_f }.to raise_error(IndexError)
384
+ end
385
+
386
+ it "should map a block to all elements" do
387
+ @nm_1d.map { |e| e ** 2 }.should eq N[25.0,0.0,1.0,4.0,9.0]
388
+ @nm_2d.map { |e| e ** 2 }.should eq N[[0.0,1.0],[4.0,9.0]]
389
+ end
390
+
391
+ it "should map! a block to all elements in place" do
392
+ fct = Proc.new { |e| e ** 2 }
393
+ expected1 = @nm_1d.map &fct
394
+ expected2 = @nm_2d.map &fct
395
+ @nm_1d.map! &fct
396
+ @nm_1d.should eq expected1
397
+ @nm_2d.map! &fct
398
+ @nm_2d.should eq expected2
399
+ end
400
+
401
+ it "should return an enumerator for map without a block" do
402
+ @nm_1d.map.should be_a Enumerator
403
+ end
404
+
405
+ it "should return an enumerator for reduce without a block" do
406
+ @nm_1d.reduce_along_dim(0).should be_a Enumerator
407
+ end
408
+
409
+ it "should return an enumerator for each_along_dim without a block" do
410
+ @nm_1d.each_along_dim(0).should be_a Enumerator
411
+ end
412
+
413
+ it "should iterate correctly for map without a block" do
414
+ en = @nm_1d.map
415
+ en.each { |e| e**2 }.should eq @nm_1d.map { |e| e**2 }
416
+ en = @nm_2d.map
417
+ en.each { |e| e**2 }.should eq @nm_2d.map { |e| e**2 }
418
+ end
419
+
420
+ it "should iterate correctly for reduce without a block" do
421
+ en = @nm_1d.reduce_along_dim(0, 1.0)
422
+ en.each { |a, e| a+e }.to_f.should eq 12
423
+ en = @nm_2d.reduce_along_dim(1, 1.0)
424
+ en.each { |a, e| a+e }.should eq N[[2.0],[6.0]]
425
+ end
426
+
427
+ it "should iterate correctly for each_along_dim without a block" do
428
+ res = NMatrix.zeros_like(@nm_1d[0...1])
429
+ en = @nm_1d.each_along_dim(0)
430
+ en.each { |e| res += e }
431
+ res.to_f.should eq 11
432
+
433
+ res = NMatrix.zeros_like (@nm_2d[0...2, 0])
434
+ en = @nm_2d.each_along_dim(1)
435
+ en.each { |e| res += e }
436
+ res.should eq N[[1.0], [5.0]]
437
+ end
438
+
439
+ it "should yield matrices of matching dtype for each_along_dim" do
440
+ m = NMatrix.new([2,3], [1,2,3,3,4,5], :complex128)
441
+ m.each_along_dim(1) do |sub_m|
442
+ sub_m.dtype.should eq :complex128
443
+ end
444
+ end
445
+
446
+ it "should reduce to a matrix of matching dtype for reduce_along_dim" do
447
+ m = NMatrix.new([2,3], [1,2,3,3,4,5], :complex128)
448
+ m.reduce_along_dim(1) do |acc, sub_m|
449
+ sub_m.dtype.should eq :complex128
450
+ acc
451
+ end
452
+
453
+ m = NMatrix.new([2,3], [1,2,3,3,4,5], :complex128)
454
+ m.reduce_along_dim(1, 0.0) do |acc, sub_m|
455
+ sub_m.dtype.should eq :complex128
456
+ acc
457
+ end
458
+ end
459
+
460
+ it "should allow overriding the dtype for reduce_along_dim" do
461
+ m = N[[1,2,3], [3,4,5], :complex128]
462
+ m.reduce_along_dim(1, 0.0, :float64) do |acc, sub_m|
463
+ acc.dtype.should eq :float64
464
+ acc
465
+ end
466
+
467
+ m = N[[1,2,3], [3,4,5], :complex128]
468
+ m.reduce_along_dim(1, nil, :float64) do |acc, sub_m|
469
+ acc.dtype.should eq :float64
470
+ acc
471
+ end
472
+ end
473
+
474
+ it "should convert integer dtypes to float when calculating mean" do
475
+ m = N[[1,2,3], [3,4,5], :int32]
476
+ m.mean(0).dtype.should eq :float64
477
+ end
478
+
479
+ it "should convert integer dtypes to float when calculating variance" do
480
+ m = N[[1,2,3], [3,4,5], :int32]
481
+ m.mean(0).dtype.should eq :float64
482
+ end
483
+
484
+ it "should convert integer dtypes to float when calculating standard deviation" do
485
+ m = N[[1,2,3], [3,4,5], :int32]
486
+ m.mean(0).dtype.should eq :float64
487
+ end
488
+
489
+ context "_like constructors" do
490
+
491
+ it "should create an nmatrix of ones with dimensions and type the same as its argument" do
492
+ NMatrix.ones_like(@nm_1d).should eq N[1.0, 1.0, 1.0, 1.0, 1.0]
493
+ NMatrix.ones_like(@nm_2d).should eq N[[1.0, 1.0], [1.0, 1.0]]
494
+ end
495
+
496
+ it "should create an nmatrix of zeros with dimensions and type the same as its argument" do
497
+ NMatrix.zeros_like(@nm_1d).should eq N[0.0, 0.0, 0.0, 0.0, 0.0]
498
+ NMatrix.zeros_like(@nm_2d).should eq N[[0.0, 0.0], [0.0, 0.0]]
499
+ end
500
+
501
+ end
502
+
503
+ end
504
+
299
505
  end