nmatrix 0.1.0 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (78) hide show
  1. checksums.yaml +4 -4
  2. data/ext/nmatrix/data/complex.h +20 -55
  3. data/ext/nmatrix/data/data.cpp +11 -44
  4. data/ext/nmatrix/data/data.h +174 -311
  5. data/ext/nmatrix/data/meta.h +1 -7
  6. data/ext/nmatrix/data/ruby_object.h +3 -85
  7. data/ext/nmatrix/extconf.rb +2 -73
  8. data/ext/nmatrix/math.cpp +170 -813
  9. data/ext/nmatrix/math/asum.h +2 -25
  10. data/ext/nmatrix/math/{inc.h → cblas_enums.h} +11 -22
  11. data/ext/nmatrix/math/cblas_templates_core.h +507 -0
  12. data/ext/nmatrix/math/gemm.h +2 -32
  13. data/ext/nmatrix/math/gemv.h +1 -35
  14. data/ext/nmatrix/math/getrf.h +21 -6
  15. data/ext/nmatrix/math/getrs.h +0 -8
  16. data/ext/nmatrix/math/imax.h +0 -22
  17. data/ext/nmatrix/math/long_dtype.h +0 -3
  18. data/ext/nmatrix/math/math.h +11 -337
  19. data/ext/nmatrix/math/nrm2.h +2 -23
  20. data/ext/nmatrix/math/rot.h +1 -25
  21. data/ext/nmatrix/math/rotg.h +4 -13
  22. data/ext/nmatrix/math/scal.h +0 -22
  23. data/ext/nmatrix/math/trsm.h +0 -55
  24. data/ext/nmatrix/math/util.h +148 -0
  25. data/ext/nmatrix/nmatrix.cpp +0 -14
  26. data/ext/nmatrix/nmatrix.h +92 -84
  27. data/ext/nmatrix/ruby_constants.cpp +0 -2
  28. data/ext/nmatrix/ruby_constants.h +0 -2
  29. data/ext/nmatrix/ruby_nmatrix.c +86 -45
  30. data/ext/nmatrix/storage/dense/dense.cpp +1 -7
  31. data/ext/nmatrix/storage/storage.h +0 -1
  32. data/ext/nmatrix/ttable_helper.rb +0 -6
  33. data/ext/nmatrix/util/io.cpp +1 -1
  34. data/lib/nmatrix.rb +1 -19
  35. data/lib/nmatrix/blas.rb +33 -11
  36. data/lib/nmatrix/io/market.rb +3 -3
  37. data/lib/nmatrix/lapack_core.rb +181 -0
  38. data/lib/nmatrix/lapack_plugin.rb +44 -0
  39. data/lib/nmatrix/math.rb +382 -131
  40. data/lib/nmatrix/monkeys.rb +2 -3
  41. data/lib/nmatrix/nmatrix.rb +166 -13
  42. data/lib/nmatrix/shortcuts.rb +72 -7
  43. data/lib/nmatrix/version.rb +2 -2
  44. data/spec/00_nmatrix_spec.rb +154 -5
  45. data/spec/02_slice_spec.rb +2 -6
  46. data/spec/03_nmatrix_monkeys_spec.rb +7 -1
  47. data/spec/blas_spec.rb +60 -33
  48. data/spec/homogeneous_spec.rb +10 -10
  49. data/spec/lapack_core_spec.rb +482 -0
  50. data/spec/math_spec.rb +436 -52
  51. data/spec/shortcuts_spec.rb +28 -4
  52. data/spec/spec_helper.rb +14 -2
  53. data/spec/utm5940.mtx +83844 -0
  54. metadata +49 -76
  55. data/.gitignore +0 -27
  56. data/.rspec +0 -2
  57. data/.travis.yml +0 -15
  58. data/CONTRIBUTING.md +0 -82
  59. data/Gemfile +0 -2
  60. data/History.txt +0 -677
  61. data/LICENSE.txt +0 -23
  62. data/Manifest.txt +0 -92
  63. data/README.rdoc +0 -150
  64. data/Rakefile +0 -216
  65. data/ext/nmatrix/data/rational.h +0 -440
  66. data/ext/nmatrix/math/geev.h +0 -82
  67. data/ext/nmatrix/math/ger.h +0 -96
  68. data/ext/nmatrix/math/gesdd.h +0 -80
  69. data/ext/nmatrix/math/gesvd.h +0 -78
  70. data/ext/nmatrix/math/getf2.h +0 -86
  71. data/ext/nmatrix/math/getri.h +0 -108
  72. data/ext/nmatrix/math/potrs.h +0 -129
  73. data/ext/nmatrix/math/swap.h +0 -52
  74. data/lib/nmatrix/lapack.rb +0 -240
  75. data/nmatrix.gemspec +0 -55
  76. data/scripts/mac-brew-gcc.sh +0 -50
  77. data/scripts/mac-mavericks-brew-gcc.sh +0 -22
  78. data/spec/lapack_spec.rb +0 -459
@@ -258,8 +258,8 @@ describe "Slice operation" do
258
258
  end
259
259
 
260
260
  if stype == :dense
261
- [:byte,:int8,:int16,:int32,:int64,:float32,:float64,:rational64,:rational128].each do |left_dtype|
262
- [:byte,:int8,:int16,:int32,:int64,:float32,:float64,:rational64,:rational128].each do |right_dtype|
261
+ [:byte,:int8,:int16,:int32,:int64,:float32,:float64].each do |left_dtype|
262
+ [:byte,:int8,:int16,:int32,:int64,:float32,:float64].each do |right_dtype|
263
263
 
264
264
  # Won't work if they're both 1-byte, due to overflow.
265
265
  next if [:byte,:int8].include?(left_dtype) && [:byte,:int8].include?(right_dtype)
@@ -272,16 +272,12 @@ describe "Slice operation" do
272
272
 
273
273
  nary = if left_dtype.to_s =~ /complex/
274
274
  COMPLEX_MATRIX43A_ARRAY
275
- elsif left_dtype.to_s =~ /rational/
276
- RATIONAL_MATRIX43A_ARRAY
277
275
  else
278
276
  MATRIX43A_ARRAY
279
277
  end
280
278
 
281
279
  mary = if right_dtype.to_s =~ /complex/
282
280
  COMPLEX_MATRIX32A_ARRAY
283
- elsif right_dtype.to_s =~ /rational/
284
- RATIONAL_MATRIX32A_ARRAY
285
281
  else
286
282
  MATRIX32A_ARRAY
287
283
  end
@@ -59,7 +59,7 @@ describe Array do
59
59
 
60
60
  it "intuits shape of Array into multiple dimensions" do
61
61
  a = [[[0], [1]], [[2], [3]], [[4], [5]]]
62
- expect(a.to_nm).to eq(NMatrix.new([3,1,2], a.flatten))
62
+ expect(a.to_nm).to eq(NMatrix.new([3,2,1], a.flatten))
63
63
  expect(a).to eq(a.to_nm.to_a)
64
64
  end
65
65
 
@@ -67,6 +67,12 @@ describe Array do
67
67
  a = [[0, 1, 2], [3], [4, 5]]
68
68
  expect(a).to eq(a.to_nm.to_a)
69
69
  end
70
+
71
+ it "does not permanently alter the Array" do
72
+ a = [[0, 1], [2, 3], [4, 5]]
73
+ expect(a.to_nm).to eq(NMatrix.new([3,2], a.flatten))
74
+ expect(a).to eq([[0, 1], [2, 3], [4, 5]])
75
+ end
70
76
  end
71
77
  end
72
78
 
@@ -30,7 +30,6 @@ require 'spec_helper'
30
30
  describe NMatrix::BLAS do
31
31
  [:byte, :int8, :int16, :int32, :int64,
32
32
  :float32, :float64, :complex64, :complex128,
33
- :rational32, :rational64, :rational128,
34
33
  :object
35
34
  ].each do |dtype|
36
35
  context dtype do
@@ -48,49 +47,56 @@ describe NMatrix::BLAS do
48
47
  end
49
48
  end
50
49
 
51
- [:rational32, :rational64, :rational128, :float32, :float64, :complex64, :complex128].each do |dtype|
50
+ [:float32, :float64, :complex64, :complex128].each do |dtype|
52
51
  context dtype do
53
52
  # This is not the same as "exposes cblas trsm", which would be for a version defined in blas.rb (which
54
53
  # would greatly simplify the calling of cblas_trsm in terms of arguments, and which would be accessible
55
54
  # as NMatrix::BLAS::trsm)
56
55
  it "exposes unfriendly cblas_trsm" do
57
- a = NMatrix.new(3, [4,-1.quo(2), -3.quo(4), -2, 2, -1.quo(4), -4, -2, -1.quo(2)], dtype: dtype)
56
+ a = NMatrix.new(3, [4,-1.0/2, -3.0/4, -2, 2, -1.0/4, -4, -2, -1.0/2], dtype: dtype)
58
57
  b = NMatrix.new([3,1], [-1, 17, -9], dtype: dtype)
59
58
  NMatrix::BLAS::cblas_trsm(:row, :right, :lower, :transpose, :nonunit, 1, 3, 1.0, a, 3, b, 3)
60
59
 
61
60
  # These test results all come from actually running a matrix through BLAS. We use them to ensure that NMatrix's
62
- # version of these functions (for rationals) give similar results.
61
+ # version of these functions give similar results.
63
62
 
64
- expect(b[0]).to eq(-1.quo(4))
65
- expect(b[1]).to eq(33.quo(4))
63
+ expect(b[0]).to eq(-1.0/4)
64
+ expect(b[1]).to eq(33.0/4)
66
65
  expect(b[2]).to eq(-13)
67
66
 
68
67
  NMatrix::BLAS::cblas_trsm(:row, :right, :upper, :transpose, :unit, 1, 3, 1.0, a, 3, b, 3)
69
68
 
70
- expect(b[0]).to eq(-15.quo(2))
69
+ expect(b[0]).to eq(-15.0/2)
71
70
  expect(b[1]).to eq(5)
72
71
  expect(b[2]).to eq(-13)
73
72
  end
74
- end
75
- end
76
73
 
77
- [:rational32,:rational64,:rational128].each do |dtype|
78
- context dtype do
79
- it "exposes cblas rot"
80
- end
74
+ # trmm multiplies two matrices, where one of the two is required to be
75
+ # triangular
76
+ it "exposes cblas_trmm" do
77
+ a = NMatrix.new([3,3], [1,1,1, 0,1,2, 0,0,-1], dtype: dtype)
78
+ b = NMatrix.new([3,3], [1,2,3, 4,5,6, 7,8,9], dtype: dtype)
81
79
 
82
- context dtype do
83
- it "exposes cblas rotg"
80
+ begin
81
+ NMatrix::BLAS.cblas_trmm(:row, :left, :upper, false, :not_unit, 3, 3, 1, a, 3, b, 3)
82
+ rescue NotImplementedError => e
83
+ pending e.to_s
84
+ end
85
+
86
+ product = NMatrix.new([3,3], [12,15,18, 18,21,24, -7,-8,-9], dtype: dtype)
87
+ expect(b).to eq(product)
88
+ end
84
89
  end
85
90
  end
86
91
 
92
+ #should have a separate test for complex
87
93
  [:float32, :float64, :complex64, :complex128, :object].each do |dtype|
88
94
  context dtype do
89
95
 
90
96
  it "exposes cblas rot" do
91
97
  x = NMatrix.new([5,1], [1,2,3,4,5], dtype: dtype)
92
98
  y = NMatrix.new([5,1], [-5,-4,-3,-2,-1], dtype: dtype)
93
- x, y = NMatrix::BLAS::rot(x, y, 1.quo(2), Math.sqrt(3).quo(2), -1)
99
+ x, y = NMatrix::BLAS::rot(x, y, 1.0/2, Math.sqrt(3)/2, -1)
94
100
 
95
101
  expect(x).to be_within(1e-4).of(
96
102
  NMatrix.new([5,1], [-0.3660254037844386, -0.7320508075688772, -1.098076211353316, -1.4641016151377544, -1.8301270189221928], dtype: dtype)
@@ -111,19 +117,23 @@ describe NMatrix::BLAS do
111
117
  pending("broken for :object") if dtype == :object
112
118
 
113
119
  ab = NMatrix.new([2,1], [6,-8], dtype: dtype)
114
- c,s = NMatrix::BLAS::rotg(ab)
120
+ begin
121
+ c,s = NMatrix::BLAS::rotg(ab)
122
+ rescue NotImplementedError => e
123
+ pending e.to_s
124
+ end
115
125
 
116
126
  if [:float32, :float64].include?(dtype)
117
127
  expect(ab[0]).to be_within(1e-6).of(-10)
118
- expect(ab[1]).to be_within(1e-6).of(-5.quo(3))
119
- expect(c).to be_within(1e-6).of(-3.quo(5))
128
+ expect(ab[1]).to be_within(1e-6).of(-5.0/3)
129
+ expect(c).to be_within(1e-6).of(-3.0/5)
120
130
  else
121
131
  pending "need correct test cases"
122
132
  expect(ab[0]).to be_within(1e-6).of(10)
123
- expect(ab[1]).to be_within(1e-6).of(5.quo(3))
124
- expect(c).to be_within(1e-6).of(3.quo(5))
133
+ expect(ab[1]).to be_within(1e-6).of(5.0/3)
134
+ expect(c).to be_within(1e-6).of(3.0/5)
125
135
  end
126
- expect(s).to be_within(1e-6).of(4.quo(5))
136
+ expect(s).to be_within(1e-6).of(4.0/5)
127
137
  end
128
138
 
129
139
  # Note: this exposes gemm, not cblas_gemm (which is the unfriendly CBLAS no-error-checking version)
@@ -138,27 +148,44 @@ describe NMatrix::BLAS do
138
148
  expect(r).to eq(NMatrix.new([4,2], [273,455,243,235,244,205,102,160], dtype: dtype))
139
149
  end
140
150
 
141
-
142
151
  it "exposes gemv" do
143
- a = 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], dtype: :float64)
144
- x = NMatrix.new([3,1], [2.0, 1.0, 0.0], dtype: :float64)
145
-
146
- NMatrix::BLAS.gemv(a, x)
152
+ a = 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], dtype: dtype)
153
+ x = NMatrix.new([3,1], [2.0, 1.0, 0.0], dtype: dtype)
154
+ y = NMatrix::BLAS.gemv(a, x)
155
+ expect(y).to eq(NMatrix.new([4,1],[4.0,13.0,22.0,31.0],dtype: dtype))
147
156
  end
148
157
 
149
158
  it "exposes asum" do
150
- x = NMatrix.new([4,1], [1,2,3,4], dtype: :float64)
151
- expect(NMatrix::BLAS.asum(x)).to eq(10.0)
159
+ pending("broken for :object") if dtype == :object
160
+
161
+ x = NMatrix.new([4,1], [-1,2,3,4], dtype: dtype)
162
+ expect(NMatrix::BLAS.asum(x)).to eq(10)
152
163
  end
153
164
 
154
165
  it "exposes asum for single element" do
155
- x = NMatrix.new([1], [-1], dtype: :float64)
156
- expect(x.asum).to eq(1.0)
166
+ if [:complex64,:complex128].include?(dtype)
167
+ x = NMatrix.new([1], [Complex(-3,2)], dtype: dtype)
168
+ expect(x.asum).to eq(5.0)
169
+ else
170
+ x = NMatrix.new([1], [-1], dtype: dtype)
171
+ expect(x.asum).to eq(1.0)
172
+ end
157
173
  end
158
174
 
159
175
  it "exposes nrm2" do
160
- x = NMatrix.new([4,1], [2,-4,3,5], dtype: :float64)
161
- expect(NMatrix::BLAS.nrm2(x, 1, 3)).to be_within(1e-10).of(5.385164807134504)
176
+ pending("broken for :object") if dtype == :object
177
+ pending("Temporarily disable because the internal implementation of nrm2 is broken -WL 2015-05-17") if dtype == :complex64 || dtype == :complex128
178
+
179
+ x = NMatrix.new([4,1], [2,-4,3,5], dtype: dtype)
180
+ err = case dtype
181
+ when :float32, :complex64
182
+ 1e-6
183
+ when :float64, :complex128
184
+ 1e-14
185
+ else
186
+ 1e-14
187
+ end
188
+ expect(NMatrix::BLAS.nrm2(x, 1, 3)).to be_within(err).of(5.385164807134504)
162
189
  end
163
190
 
164
191
  end
@@ -33,10 +33,10 @@ require 'pry'
33
33
  describe 'NMatrix' do
34
34
  context ".x_rotation" do
35
35
  it "should generate a matrix representing a rotation about the x axis" do
36
- x = NMatrix.x_rotation(Math::PI.quo(6))
36
+ x = NMatrix.x_rotation(Math::PI/6)
37
37
  expect(x).to be_within(1e-8).of(NMatrix.new([4,4], [1.0, 0.0, 0.0, 0.0,
38
- 0.0, Math.cos(Math::PI.quo(6)), -0.5, 0.0,
39
- 0.0, 0.5, Math.cos(Math::PI.quo(6)), 0.0,
38
+ 0.0, Math.cos(Math::PI/6), -0.5, 0.0,
39
+ 0.0, 0.5, Math.cos(Math::PI/6), 0.0,
40
40
  0.0, 0.0, 0.0, 1.0] ))
41
41
  end
42
42
  end
@@ -44,19 +44,19 @@ describe 'NMatrix' do
44
44
 
45
45
  context ".y_rotation" do
46
46
  it "should generate a matrix representing a rotation about the y axis" do
47
- y = NMatrix.y_rotation(Math::PI.quo(6))
48
- expect(y).to be_within(1e-8).of(NMatrix.new([4,4], [Math.cos(Math::PI.quo(6)), 0.0, 0.5, 0.0,
47
+ y = NMatrix.y_rotation(Math::PI/6)
48
+ expect(y).to be_within(1e-8).of(NMatrix.new([4,4], [Math.cos(Math::PI/6), 0.0, 0.5, 0.0,
49
49
  0.0, 1.0, 0.0, 0.0,
50
- -0.5, 0.0, Math.cos(Math::PI.quo(6)), 0.0,
50
+ -0.5, 0.0, Math.cos(Math::PI/6), 0.0,
51
51
  0.0, 0.0, 0.0, 1.0] ))
52
52
  end
53
53
  end
54
54
 
55
55
  context ".z_rotation" do
56
56
  it "should generate a matrix representing a rotation about the z axis" do
57
- z = NMatrix.z_rotation(Math::PI.quo(6))
58
- expect(z).to be_within(1e-8).of(NMatrix.new([4,4], [Math.cos(Math::PI.quo(6)), -0.5, 0.0, 0.0,
59
- 0.5, Math.cos(Math::PI.quo(6)), 0.0, 0.0,
57
+ z = NMatrix.z_rotation(Math::PI/6)
58
+ expect(z).to be_within(1e-8).of(NMatrix.new([4,4], [Math.cos(Math::PI/6), -0.5, 0.0, 0.0,
59
+ 0.5, Math.cos(Math::PI/6), 0.0, 0.0,
60
60
  0.0, 0.0, 1.0, 0.0,
61
61
  0.0, 0.0, 0.0, 1.0] ))
62
62
  end
@@ -96,4 +96,4 @@ describe 'NMatrix' do
96
96
  expect(Math.sqrt(q[0]**2 + q[1]**2 + q[2]**2 + q[3]**2)).to be_within(1e-6).of(1.0)
97
97
  end
98
98
  end
99
- end
99
+ end
@@ -0,0 +1,482 @@
1
+ # = NMatrix
2
+ #
3
+ # A linear algebra library for scientific computation in Ruby.
4
+ # NMatrix is part of SciRuby.
5
+ #
6
+ # NMatrix was originally inspired by and derived from NArray, by
7
+ # Masahiro Tanaka: http://narray.rubyforge.org
8
+ #
9
+ # == Copyright Information
10
+ #
11
+ # SciRuby is Copyright (c) 2010 - 2014, Ruby Science Foundation
12
+ # NMatrix is Copyright (c) 2012 - 2014, John Woods and the Ruby Science Foundation
13
+ #
14
+ # Please see LICENSE.txt for additional copyright notices.
15
+ #
16
+ # == Contributing
17
+ #
18
+ # By contributing source code to SciRuby, you agree to be bound by
19
+ # our Contributor Agreement:
20
+ #
21
+ # * https://github.com/SciRuby/sciruby/wiki/Contributor-Agreement
22
+ #
23
+ # == lapack_core_spec.rb
24
+ #
25
+ # Tests for LAPACK functions that have internal implementations (i.e. they
26
+ # don't rely on external libraries) and also functions that are implemented
27
+ # by both nmatrix-atlas and nmatrix-lapacke. These tests will also be run for the
28
+ # plugins that do use external libraries, since they will override the
29
+ # internal implmentations.
30
+ #
31
+
32
+ require 'spec_helper'
33
+
34
+ describe "NMatrix::LAPACK functions with internal implementations" do
35
+ # where integer math is allowed
36
+ [:byte, :int8, :int16, :int32, :int64, :float32, :float64, :complex64, :complex128].each do |dtype|
37
+ context dtype do
38
+ # This spec seems a little weird. It looks like laswp ignores the last
39
+ # element of piv, though maybe I misunderstand smth. It would make
40
+ # more sense if piv were [2,1,3,3]
41
+ it "exposes clapack laswp" do
42
+ a = NMatrix.new(:dense, [3,4], [1,2,3,4,5,6,7,8,9,10,11,12], dtype)
43
+ NMatrix::LAPACK::clapack_laswp(3, a, 4, 0, 3, [2,1,3,0], 1)
44
+ b = NMatrix.new(:dense, [3,4], [3,2,4,1,7,6,8,5,11,10,12,9], dtype)
45
+ expect(a).to eq(b)
46
+ end
47
+
48
+ # This spec is OK, because the default behavior for permute_columns
49
+ # is :intuitive, which is different from :lapack (default laswp behavior)
50
+ it "exposes NMatrix#permute_columns and #permute_columns! (user-friendly laswp)" do
51
+ a = NMatrix.new(:dense, [3,4], [1,2,3,4,5,6,7,8,9,10,11,12], dtype)
52
+ b = NMatrix.new(:dense, [3,4], [3,2,4,1,7,6,8,5,11,10,12,9], dtype)
53
+ piv = [2,1,3,0]
54
+ r = a.permute_columns(piv)
55
+ expect(r).not_to eq(a)
56
+ expect(r).to eq(b)
57
+ a.permute_columns!(piv)
58
+ expect(a).to eq(b)
59
+ end
60
+ end
61
+ end
62
+
63
+ # where integer math is not allowed
64
+ [:float32, :float64, :complex64, :complex128].each do |dtype|
65
+ context dtype do
66
+
67
+ # clapack_getrf performs a LU decomposition, but unlike the
68
+ # standard LAPACK getrf, it's the upper matrix that has unit diagonals
69
+ # and the permutation is done in columns not rows. See the code for
70
+ # details.
71
+ # Also the rows in the pivot vector are indexed starting from 0,
72
+ # rather than 1 as in LAPACK
73
+ it "calculates LU decomposition using clapack_getrf (row-major, square)" do
74
+ a = NMatrix.new(3, [4,9,2,3,5,7,8,1,6], dtype: dtype)
75
+ ipiv = NMatrix::LAPACK::clapack_getrf(:row, a.shape[0], a.shape[1], a, a.shape[1])
76
+ b = NMatrix.new(3,[9, 2.0/9, 4.0/9,
77
+ 5, 53.0/9, 7.0/53,
78
+ 1, 52.0/9, 360.0/53], dtype: dtype)
79
+ ipiv_true = [1,2,2]
80
+
81
+ # delta varies for different dtypes
82
+ err = case dtype
83
+ when :float32, :complex64
84
+ 1e-6
85
+ when :float64, :complex128
86
+ 1e-15
87
+ end
88
+
89
+ expect(a).to be_within(err).of(b)
90
+ expect(ipiv).to eq(ipiv_true)
91
+ end
92
+
93
+ it "calculates LU decomposition using clapack_getrf (row-major, rectangular)" do
94
+ a = NMatrix.new([3,4], GETRF_EXAMPLE_ARRAY, dtype: dtype)
95
+ ipiv = NMatrix::LAPACK::clapack_getrf(:row, a.shape[0], a.shape[1], a, a.shape[1])
96
+ #we can't use GETRF_SOLUTION_ARRAY here, because of the different
97
+ #conventions of clapack_getrf
98
+ b = NMatrix.new([3,4],[10.0, -0.1, 0.0, 0.4,
99
+ 3.0, 9.3, 20.0/93, 38.0/93,
100
+ 1.0, 7.1, 602.0/93, 251.0/602], dtype: dtype)
101
+ ipiv_true = [2,2,2]
102
+
103
+ # delta varies for different dtypes
104
+ err = case dtype
105
+ when :float32, :complex64
106
+ 1e-6
107
+ when :float64, :complex128
108
+ 1e-15
109
+ end
110
+
111
+ expect(a).to be_within(err).of(b)
112
+ expect(ipiv).to eq(ipiv_true)
113
+ end
114
+
115
+ #Normally we wouldn't check column-major routines, since all our matrices
116
+ #are row-major, but we use the column-major version in #getrf!, so we
117
+ #want to test it here.
118
+ it "calculates LU decomposition using clapack_getrf (col-major, rectangular)" do
119
+ #this is supposed to represent the 3x2 matrix
120
+ # -1 2
121
+ # 0 3
122
+ # 1 -2
123
+ a = NMatrix.new([1,6], [-1,0,1,2,3,-2], dtype: dtype)
124
+ ipiv = NMatrix::LAPACK::clapack_getrf(:col, 3, 2, a, 3)
125
+ b = NMatrix.new([1,6], [-1,0,-1,2,3,0], dtype: dtype)
126
+ ipiv_true = [0,1]
127
+
128
+ # delta varies for different dtypes
129
+ err = case dtype
130
+ when :float32, :complex64
131
+ 1e-6
132
+ when :float64, :complex128
133
+ 1e-15
134
+ end
135
+
136
+ expect(a).to be_within(err).of(b)
137
+ expect(ipiv).to eq(ipiv_true)
138
+ end
139
+
140
+ it "calculates LU decomposition using #getrf! (rectangular)" do
141
+ a = NMatrix.new([3,4], GETRF_EXAMPLE_ARRAY, dtype: dtype)
142
+ ipiv = a.getrf!
143
+ b = NMatrix.new([3,4], GETRF_SOLUTION_ARRAY, dtype: dtype)
144
+ ipiv_true = [2,3,3]
145
+
146
+ # delta varies for different dtypes
147
+ err = case dtype
148
+ when :float32, :complex64
149
+ 1e-6
150
+ when :float64, :complex128
151
+ 1e-14
152
+ end
153
+
154
+ expect(a).to be_within(err).of(b)
155
+ expect(ipiv).to eq(ipiv_true)
156
+ end
157
+
158
+ it "calculates LU decomposition using #getrf! (square)" do
159
+ a = NMatrix.new([4,4], [0,1,2,3, 1,1,1,1, 0,-1,-2,0, 0,2,0,2], dtype: dtype)
160
+ ipiv = a.getrf!
161
+
162
+ b = NMatrix.new([4,4], [1,1,1,1, 0,2,0,2, 0,-0.5,-2,1, 0,0.5,-1,3], dtype: dtype)
163
+ ipiv_true = [2,4,3,4]
164
+
165
+ expect(a).to eq(b)
166
+ expect(ipiv).to eq(ipiv_true)
167
+ end
168
+
169
+ # Together, these calls are basically xGESV from LAPACK: http://www.netlib.org/lapack/double/dgesv.f
170
+ it "exposes clapack_getrs" do
171
+ a = NMatrix.new(3, [-2,4,-3, 3,-2,1, 0,-4,3], dtype: dtype)
172
+ ipiv = NMatrix::LAPACK::clapack_getrf(:row, 3, 3, a, 3)
173
+ b = NMatrix.new([3,1], [-1, 17, -9], dtype: dtype)
174
+
175
+ NMatrix::LAPACK::clapack_getrs(:row, false, 3, 1, a, 3, ipiv, b, 3)
176
+
177
+ expect(b[0]).to eq(5)
178
+ expect(b[1]).to eq(-15.0/2)
179
+ expect(b[2]).to eq(-13)
180
+ end
181
+
182
+ it "solves matrix equation (non-vector rhs) using clapack_getrs" do
183
+ a = NMatrix.new(3, [-2,4,-3, 3,-2,1, 0,-4,3], dtype: dtype)
184
+ b = NMatrix.new([3,2], [-1,2, 17,1, -9,-4], dtype: dtype)
185
+
186
+ n = a.shape[0]
187
+ nrhs = b.shape[1]
188
+
189
+ ipiv = NMatrix::LAPACK::clapack_getrf(:row, n, n, a, n)
190
+ # Even though we pass :row to clapack_getrs, it still interprets b as
191
+ # column-major, so need to transpose b before and after:
192
+ b = b.transpose
193
+ NMatrix::LAPACK::clapack_getrs(:row, false, n, nrhs, a, n, ipiv, b, n)
194
+ b = b.transpose
195
+
196
+ b_true = NMatrix.new([3,2], [5,1, -7.5,1, -13,0], dtype: dtype)
197
+ expect(b).to eq(b_true)
198
+ end
199
+
200
+ #posv is like potrf+potrs
201
+ #posv is implemented in both nmatrix-atlas and nmatrix-lapacke, so the spec
202
+ #needs to be shared here
203
+ it "solves a (symmetric positive-definite) matrix equation using posv (vector rhs)" do
204
+ a = NMatrix.new(3, [4, 0,-1,
205
+ 0, 2, 1,
206
+ 0, 0, 1], dtype: dtype)
207
+ b = NMatrix.new([3,1], [4,2,0], dtype: dtype)
208
+
209
+ begin
210
+ x = NMatrix::LAPACK::posv(:upper, a, b)
211
+ rescue NotImplementedError => e
212
+ pending e.to_s
213
+ end
214
+
215
+ x_true = NMatrix.new([3,1], [1, 1, 0], dtype: dtype)
216
+
217
+ err = case dtype
218
+ when :float32, :complex64
219
+ 1e-5
220
+ when :float64, :complex128
221
+ 1e-14
222
+ end
223
+
224
+ expect(x).to be_within(err).of(x_true)
225
+ end
226
+
227
+ it "solves a (symmetric positive-definite) matrix equation using posv (non-vector rhs)" do
228
+ a = NMatrix.new(3, [4, 0,-1,
229
+ 0, 2, 1,
230
+ 0, 0, 1], dtype: dtype)
231
+ b = NMatrix.new([3,2], [4,-1, 2,-1, 0,0], dtype: dtype)
232
+
233
+ begin
234
+ x = NMatrix::LAPACK::posv(:upper, a, b)
235
+ rescue NotImplementedError => e
236
+ pending e.to_s
237
+ end
238
+
239
+ x_true = NMatrix.new([3,2], [1,0, 1,-1, 0,1], dtype: dtype)
240
+
241
+ err = case dtype
242
+ when :float32, :complex64
243
+ 1e-5
244
+ when :float64, :complex128
245
+ 1e-14
246
+ end
247
+
248
+ expect(x).to be_within(err).of(x_true)
249
+ end
250
+
251
+ it "calculates the singular value decomposition with NMatrix#gesvd" do
252
+ #example from Wikipedia
253
+ m = 4
254
+ n = 5
255
+ mn_min = [m,n].min
256
+ a = NMatrix.new([m,n],[1,0,0,0,2, 0,0,3,0,0, 0,0,0,0,0, 0,4,0,0,0], dtype: dtype)
257
+
258
+ begin
259
+ u, s, vt = a.gesvd
260
+ rescue NotImplementedError => e
261
+ pending e.to_s
262
+ end
263
+
264
+ s_true = NMatrix.new([mn_min,1], [4,3,Math.sqrt(5),0], dtype: a.abs_dtype)
265
+ u_true = NMatrix.new([m,m], [0,0,1,0, 0,1,0,0, 0,0,0,-1, 1,0,0,0], dtype: dtype)
266
+ vt_true = NMatrix.new([n,n], [0,1,0,0,0, 0,0,1,0,0, Math.sqrt(0.2),0,0,0,Math.sqrt(0.8), 0,0,0,1,0, -Math.sqrt(0.8),0,0,0,Math.sqrt(0.2)], dtype: dtype)
267
+
268
+ err = case dtype
269
+ when :float32, :complex64
270
+ 1e-5
271
+ when :float64, :complex128
272
+ 1e-14
273
+ end
274
+
275
+ expect(s).to be_within(err).of(s_true)
276
+ expect(u).to be_within(err).of(u_true)
277
+ expect(vt).to be_within(err).of(vt_true)
278
+
279
+ expect(s.dtype).to eq(a.abs_dtype)
280
+ expect(u.dtype).to eq(dtype)
281
+ expect(vt.dtype).to eq(dtype)
282
+ end
283
+
284
+ it "calculates the singular value decomposition with NMatrix#gesdd" do
285
+ #example from Wikipedia
286
+ m = 4
287
+ n = 5
288
+ mn_min = [m,n].min
289
+ a = NMatrix.new([m,n],[1,0,0,0,2, 0,0,3,0,0, 0,0,0,0,0, 0,4,0,0,0], dtype: dtype)
290
+
291
+ begin
292
+ u, s, vt = a.gesdd
293
+ rescue NotImplementedError => e
294
+ pending e.to_s
295
+ end
296
+
297
+ s_true = NMatrix.new([mn_min,1], [4,3,Math.sqrt(5),0], dtype: a.abs_dtype)
298
+ u_true = NMatrix.new([m,m], [0,0,1,0, 0,1,0,0, 0,0,0,-1, 1,0,0,0], dtype: dtype)
299
+ vt_true = NMatrix.new([n,n], [0,1,0,0,0, 0,0,1,0,0, Math.sqrt(0.2),0,0,0,Math.sqrt(0.8), 0,0,0,1,0, -Math.sqrt(0.8),0,0,0,Math.sqrt(0.2)], dtype: dtype)
300
+
301
+ err = case dtype
302
+ when :float32, :complex64
303
+ 1e-5
304
+ when :float64, :complex128
305
+ 1e-14
306
+ end
307
+
308
+ expect(s).to be_within(err).of(s_true)
309
+ expect(u).to be_within(err).of(u_true)
310
+ expect(vt).to be_within(err).of(vt_true)
311
+ end
312
+
313
+
314
+ it "calculates eigenvalues and eigenvectors NMatrix::LAPACK.geev (real matrix, complex eigenvalues)" do
315
+ n = 3
316
+ a = NMatrix.new([n,n], [-1,0,0, 0,1,-2, 0,1,-1], dtype: dtype)
317
+
318
+ begin
319
+ eigenvalues, vl, vr = NMatrix::LAPACK.geev(a)
320
+ rescue NotImplementedError => e
321
+ pending e.to_s
322
+ end
323
+
324
+ eigenvalues_true = NMatrix.new([n,1], [Complex(0,1), -Complex(0,1), -1], dtype: NMatrix.upcast(dtype, :complex64))
325
+ vr_true = NMatrix.new([n,n],[0,0,1,
326
+ 2/Math.sqrt(6),2/Math.sqrt(6),0,
327
+ Complex(1,-1)/Math.sqrt(6),Complex(1,1)/Math.sqrt(6),0], dtype: NMatrix.upcast(dtype, :complex64))
328
+ vl_true = NMatrix.new([n,n],[0,0,1,
329
+ Complex(-1,1)/Math.sqrt(6),Complex(-1,-1)/Math.sqrt(6),0,
330
+ 2/Math.sqrt(6),2/Math.sqrt(6),0], dtype: NMatrix.upcast(dtype, :complex64))
331
+
332
+ err = case dtype
333
+ when :float32, :complex64
334
+ 1e-6
335
+ when :float64, :complex128
336
+ 1e-15
337
+ end
338
+
339
+ expect(eigenvalues).to be_within(err).of(eigenvalues_true)
340
+ expect(vr).to be_within(err).of(vr_true)
341
+ expect(vl).to be_within(err).of(vl_true)
342
+
343
+ expect(eigenvalues.dtype).to eq(NMatrix.upcast(dtype, :complex64))
344
+ expect(vr.dtype).to eq(NMatrix.upcast(dtype, :complex64))
345
+ expect(vl.dtype).to eq(NMatrix.upcast(dtype, :complex64))
346
+ end
347
+
348
+ it "calculates eigenvalues and eigenvectors NMatrix::LAPACK.geev (real matrix, real eigenvalues)" do
349
+ n = 3
350
+ a = NMatrix.new([n,n], [2,0,0, 0,3,2, 0,1,2], dtype: dtype)
351
+
352
+ begin
353
+ eigenvalues, vl, vr = NMatrix::LAPACK.geev(a)
354
+ rescue NotImplementedError => e
355
+ pending e.to_s
356
+ end
357
+
358
+ eigenvalues_true = NMatrix.new([n,1], [1, 4, 2], dtype: dtype)
359
+
360
+ # For some reason, some of the eigenvectors have different signs
361
+ # when we use the complex versions of geev. This is totally fine, since
362
+ # they are still normalized eigenvectors even with the sign flipped.
363
+ if a.complex_dtype?
364
+ vr_true = NMatrix.new([n,n],[0,0,1,
365
+ 1/Math.sqrt(2),2/Math.sqrt(5),0,
366
+ -1/Math.sqrt(2),1/Math.sqrt(5),0], dtype: dtype)
367
+ vl_true = NMatrix.new([n,n],[0,0,1,
368
+ -1/Math.sqrt(5),1/Math.sqrt(2),0,
369
+ 2/Math.sqrt(5),1/Math.sqrt(2),0], dtype: dtype)
370
+ else
371
+ vr_true = NMatrix.new([n,n],[0,0,1,
372
+ 1/Math.sqrt(2),-2/Math.sqrt(5),0,
373
+ -1/Math.sqrt(2),-1/Math.sqrt(5),0], dtype: dtype)
374
+ vl_true = NMatrix.new([n,n],[0,0,1,
375
+ 1/Math.sqrt(5),-1/Math.sqrt(2),0,
376
+ -2/Math.sqrt(5),-1/Math.sqrt(2),0], dtype: dtype)
377
+ end
378
+
379
+ err = case dtype
380
+ when :float32, :complex64
381
+ 1e-6
382
+ when :float64, :complex128
383
+ 1e-15
384
+ end
385
+
386
+ expect(eigenvalues).to be_within(err).of(eigenvalues_true)
387
+ expect(vr).to be_within(err).of(vr_true)
388
+ expect(vl).to be_within(err).of(vl_true)
389
+
390
+ expect(eigenvalues.dtype).to eq(dtype)
391
+ expect(vr.dtype).to eq(dtype)
392
+ expect(vl.dtype).to eq(dtype)
393
+ end
394
+
395
+ it "calculates eigenvalues and eigenvectors NMatrix::LAPACK.geev (left eigenvectors only)" do
396
+ n = 3
397
+ a = NMatrix.new([n,n], [-1,0,0, 0,1,-2, 0,1,-1], dtype: dtype)
398
+
399
+ begin
400
+ eigenvalues, vl = NMatrix::LAPACK.geev(a, :left)
401
+ rescue NotImplementedError => e
402
+ pending e.to_s
403
+ end
404
+
405
+ eigenvalues_true = NMatrix.new([n,1], [Complex(0,1), -Complex(0,1), -1], dtype: NMatrix.upcast(dtype, :complex64))
406
+ vl_true = NMatrix.new([n,n],[0,0,1,
407
+ Complex(-1,1)/Math.sqrt(6),Complex(-1,-1)/Math.sqrt(6),0,
408
+ 2/Math.sqrt(6),2/Math.sqrt(6),0], dtype: NMatrix.upcast(dtype, :complex64))
409
+
410
+ err = case dtype
411
+ when :float32, :complex64
412
+ 1e-6
413
+ when :float64, :complex128
414
+ 1e-15
415
+ end
416
+
417
+ expect(eigenvalues).to be_within(err).of(eigenvalues_true)
418
+ expect(vl).to be_within(err).of(vl_true)
419
+ end
420
+
421
+ it "calculates eigenvalues and eigenvectors NMatrix::LAPACK.geev (right eigenvectors only)" do
422
+ n = 3
423
+ a = NMatrix.new([n,n], [-1,0,0, 0,1,-2, 0,1,-1], dtype: dtype)
424
+
425
+ begin
426
+ eigenvalues, vr = NMatrix::LAPACK.geev(a, :right)
427
+ rescue NotImplementedError => e
428
+ pending e.to_s
429
+ end
430
+
431
+ eigenvalues_true = NMatrix.new([n,1], [Complex(0,1), -Complex(0,1), -1], dtype: NMatrix.upcast(dtype, :complex64))
432
+ vr_true = NMatrix.new([n,n],[0,0,1,
433
+ 2/Math.sqrt(6),2/Math.sqrt(6),0,
434
+ Complex(1,-1)/Math.sqrt(6),Complex(1,1)/Math.sqrt(6),0], dtype: NMatrix.upcast(dtype, :complex64))
435
+
436
+ err = case dtype
437
+ when :float32, :complex64
438
+ 1e-6
439
+ when :float64, :complex128
440
+ 1e-15
441
+ end
442
+
443
+ expect(eigenvalues).to be_within(err).of(eigenvalues_true)
444
+ expect(vr).to be_within(err).of(vr_true)
445
+ end
446
+ end
447
+ end
448
+
449
+ [:complex64, :complex128].each do |dtype|
450
+ context dtype do
451
+ it "calculates eigenvalues and eigenvectors NMatrix::LAPACK.geev (complex matrix)" do
452
+ n = 3
453
+ a = NMatrix.new([n,n], [Complex(0,1),0,0, 0,3,2, 0,1,2], dtype: dtype)
454
+
455
+ begin
456
+ eigenvalues, vl, vr = NMatrix::LAPACK.geev(a)
457
+ rescue NotImplementedError => e
458
+ pending e.to_s
459
+ end
460
+
461
+ eigenvalues_true = NMatrix.new([n,1], [1, 4, Complex(0,1)], dtype: dtype)
462
+ vr_true = NMatrix.new([n,n],[0,0,1,
463
+ 1/Math.sqrt(2),2/Math.sqrt(5),0,
464
+ -1/Math.sqrt(2),1/Math.sqrt(5),0], dtype: dtype)
465
+ vl_true = NMatrix.new([n,n],[0,0,1,
466
+ -1/Math.sqrt(5),1/Math.sqrt(2),0,
467
+ 2/Math.sqrt(5),1/Math.sqrt(2),0], dtype: dtype)
468
+
469
+ err = case dtype
470
+ when :float32, :complex64
471
+ 1e-6
472
+ when :float64, :complex128
473
+ 1e-15
474
+ end
475
+
476
+ expect(eigenvalues).to be_within(err).of(eigenvalues_true)
477
+ expect(vr).to be_within(err).of(vr_true)
478
+ expect(vl).to be_within(err).of(vl_true)
479
+ end
480
+ end
481
+ end
482
+ end