numo-linalg 0.1.2 → 0.1.3

Sign up to get free protection for your applications and to get access to all the features.
Files changed (47) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +7 -3
  3. data/ext/numo/linalg/blas/extconf.rb +1 -2
  4. data/ext/numo/linalg/blas/numo_blas.h +6 -0
  5. data/ext/numo/linalg/blas/tmpl/mv.c +3 -2
  6. data/ext/numo/linalg/lapack/gen/spec.rb +5 -0
  7. data/ext/numo/linalg/lapack/lapack.c +29 -0
  8. data/ext/numo/linalg/lapack/numo_lapack.h +3 -0
  9. data/ext/numo/linalg/lapack/tmpl/gqr.c +1 -1
  10. data/ext/numo/linalg/lapack/tmpl/sygvx.c +130 -0
  11. data/ext/numo/linalg/mkmf_linalg.rb +2 -19
  12. data/lib/numo/linalg/function.rb +168 -77
  13. data/lib/numo/linalg/loader.rb +6 -14
  14. data/lib/numo/linalg/version.rb +1 -1
  15. data/numo-linalg.gemspec +2 -1
  16. data/spec/linalg/autoloader_spec.rb +27 -0
  17. data/spec/linalg/function/cho_fact_spec.rb +31 -0
  18. data/spec/linalg/function/cho_inv_spec.rb +39 -0
  19. data/spec/linalg/function/cho_solve_spec.rb +66 -0
  20. data/spec/linalg/function/cholesky_spec.rb +43 -0
  21. data/spec/linalg/function/cond_spec.rb +57 -0
  22. data/spec/linalg/function/det_spec.rb +21 -0
  23. data/spec/linalg/function/dot_spec.rb +84 -0
  24. data/spec/linalg/function/eig_spec.rb +53 -0
  25. data/spec/linalg/function/eigh_spec.rb +81 -0
  26. data/spec/linalg/function/eigvals_spec.rb +27 -0
  27. data/spec/linalg/function/eigvalsh_spec.rb +60 -0
  28. data/spec/linalg/function/inv_spec.rb +57 -0
  29. data/spec/linalg/function/ldl_spec.rb +51 -0
  30. data/spec/linalg/function/lstsq_spec.rb +80 -0
  31. data/spec/linalg/function/lu_fact_spec.rb +34 -0
  32. data/spec/linalg/function/lu_inv_spec.rb +21 -0
  33. data/spec/linalg/function/lu_solve_spec.rb +40 -0
  34. data/spec/linalg/function/lu_spec.rb +46 -0
  35. data/spec/linalg/function/matmul_spec.rb +41 -0
  36. data/spec/linalg/function/matrix_power_spec.rb +31 -0
  37. data/spec/linalg/function/matrix_rank_spec.rb +33 -0
  38. data/spec/linalg/function/norm_spec.rb +81 -0
  39. data/spec/linalg/function/pinv_spec.rb +48 -0
  40. data/spec/linalg/function/qr_spec.rb +82 -0
  41. data/spec/linalg/function/slogdet_spec.rb +21 -0
  42. data/spec/linalg/function/solve_spec.rb +98 -0
  43. data/spec/linalg/function/svd_spec.rb +88 -0
  44. data/spec/linalg/function/svdvals_spec.rb +40 -0
  45. data/spec/spec_helper.rb +55 -0
  46. metadata +79 -6
  47. data/spec/lapack_spec.rb +0 -13
@@ -0,0 +1,81 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'spec_helper'
4
+
5
+ RSpec.describe Numo::Linalg do
6
+ describe 'eigh' do
7
+ let(:m) { 5 }
8
+ let(:n) { 3 }
9
+ let(:mat_a) { rand_symmetric_mat(m) }
10
+ let(:mat_b) { rand_symmetric_mat(m) + 5.1 * Numo::DFloat.eye(m) } # for avoiding non-singular matrix
11
+ let(:mat_c) { rand_hermitian_mat(m) }
12
+ let(:mat_d) { rand_hermitian_mat(m) + 5.1 * Numo::DFloat.eye(m) } # for avoiding non-singular matrix
13
+
14
+ it 'raises ShapeError given a rectangular matrix' do
15
+ expect { described_class.eigh(Numo::DFloat.new(2, 4).rand) }.to raise_error(Numo::NArray::ShapeError)
16
+ end
17
+
18
+ it 'finds eigenvalues and eigenvectors by solving eigenvalue problem for a symmetric matrix' do
19
+ v, w = described_class.eigh(mat_a)
20
+ r = w.transpose.dot(mat_a.dot(w))
21
+ r = r[r.diag_indices]
22
+ expect((v - r).abs.mean).to be < ERR_TOL
23
+ end
24
+
25
+ it 'finds eigenvalues and eigenvectors by solving eigenvalue problem for a hermitian matrix' do
26
+ v, w = described_class.eigh(mat_c)
27
+ r = w.transpose.conj.dot(mat_c.dot(w))
28
+ r = r[r.diag_indices].real
29
+ expect((v - r).abs.mean).to be < ERR_TOL
30
+ end
31
+
32
+ it 'finds eigenvalues and eigenvectors by solving generalized eigenvalue problem for symmetric matrices' do
33
+ v, w = described_class.eigh(mat_a, mat_b)
34
+ r = w.transpose.dot(mat_a.dot(w))
35
+ r = r[r.diag_indices]
36
+ e = w.transpose.dot(mat_b.dot(w))
37
+ e = e[e.diag_indices]
38
+ expect((v - r).abs.mean).to be < ERR_TOL
39
+ expect((Numo::DFloat.ones(m) - e).abs.mean).to be < ERR_TOL
40
+ end
41
+
42
+ it 'finds eigenvalues and eigenvectors by solving generalized eigenvalue problem for hermitian matrices' do
43
+ v, w = described_class.eigh(mat_c, mat_d)
44
+ r = w.transpose.conj.dot(mat_c.dot(w))
45
+ r = r[r.diag_indices].real
46
+ e = w.transpose.conj.dot(mat_d.dot(w))
47
+ e = e[e.diag_indices].real
48
+ expect((v - r).abs.mean).to be < ERR_TOL
49
+ expect((Numo::DFloat.ones(m) - e).abs.mean).to be < ERR_TOL
50
+ end
51
+
52
+ it 'finds eigenvalues and eigenvectors by solving generalized eigenvalue problem for symmetric and hermitian matrices' do
53
+ v, w = described_class.eigh(mat_a, mat_d)
54
+ r = w.transpose.conj.dot(mat_a.dot(w))
55
+ r = r[r.diag_indices].real
56
+ e = w.transpose.conj.dot(mat_d.dot(w))
57
+ e = e[e.diag_indices].real
58
+ expect((v - r).abs.mean).to be < ERR_TOL
59
+ expect((Numo::DFloat.ones(m) - e).abs.mean).to be < ERR_TOL
60
+ end
61
+
62
+ it 'finds eigenvalues and eigenvectors for a symmetric matrix with divide and conquer algorithm' do
63
+ v, w = described_class.eigh(mat_a, turbo: true)
64
+ r = w.transpose.dot(mat_a.dot(w))
65
+ r = r[r.diag_indices]
66
+ expect((v - r).abs.mean).to be < ERR_TOL
67
+ end
68
+
69
+ it 'finds eigenvalues and eigenvectors in the range of the specified indices' do
70
+ v1, w1 = described_class.eigh(mat_a, mat_b)
71
+ v2, w2 = described_class.eigh(mat_a, mat_b, vals_range: 0...n)
72
+ r = w2.transpose.dot(mat_a.dot(w2))
73
+ r = r[r.diag_indices]
74
+ e = w2.transpose.dot(mat_b.dot(w2))
75
+ e = e[e.diag_indices]
76
+ expect((v2 - r).abs.mean).to be < ERR_TOL
77
+ expect((v1[0...n] - r).abs.mean).to be < ERR_TOL
78
+ expect((Numo::DFloat.ones(n) - e).abs.mean).to be < ERR_TOL
79
+ end
80
+ end
81
+ end
@@ -0,0 +1,27 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'spec_helper'
4
+
5
+ RSpec.describe Numo::Linalg do
6
+ describe 'eigvals' do
7
+ let(:m) { 5 }
8
+ let(:mat_a) { Numo::DFloat.new(m, m).rand - 0.5 }
9
+ let(:mat_b) { mat_a + Complex::I * (Numo::DFloat.new(m, m).rand - 0.5) }
10
+
11
+ it 'raises ShapeError given a rectangular matrix' do
12
+ expect { described_class.eigh(Numo::DFloat.new(2, 4).rand) }.to raise_error(Numo::NArray::ShapeError)
13
+ end
14
+
15
+ it 'finds eigenvalues for a square nonsymmetric matrix' do
16
+ w1 = described_class.eigvals(mat_a)
17
+ w2, = described_class.eig(mat_a, left: false, right: false)
18
+ expect((w1 - w2).abs.max).to be < ERR_TOL
19
+ end
20
+
21
+ it 'finds eigenvalues for a square complex nonsymmetric matrix' do
22
+ w1 = described_class.eigvals(mat_b)
23
+ w2, = described_class.eig(mat_b, left: false, right: false)
24
+ expect((w1 - w2).abs.max).to be < ERR_TOL
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,60 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'spec_helper'
4
+
5
+ RSpec.describe Numo::Linalg do
6
+ describe 'eigvash' do
7
+ let(:m) { 5 }
8
+ let(:n) { 3 }
9
+ let(:mat_a) { rand_symmetric_mat(m) }
10
+ let(:mat_b) { rand_symmetric_mat(m) + 5.1 * Numo::DFloat.eye(m) } # for avoiding non-singular matrix
11
+ let(:mat_c) { rand_hermitian_mat(m) }
12
+ let(:mat_d) { rand_hermitian_mat(m) + 5.1 * Numo::DFloat.eye(m) } # for avoiding non-singular matrix
13
+
14
+ it 'raises ShapeError given a rectangular matrix' do
15
+ expect { described_class.eigh(Numo::DFloat.new(2, 4).rand) }.to raise_error(Numo::NArray::ShapeError)
16
+ end
17
+
18
+ it 'finds eigenvalues by solving eigenvalue problem for a symmetric matrix' do
19
+ v1 = described_class.eigvalsh(mat_a)
20
+ v2, = described_class.eigh(mat_a)
21
+ expect((v1 - v2).abs.mean).to be < ERR_TOL
22
+ end
23
+
24
+ it 'finds eigenvalues by solving eigenvalue problem for a hermitian matrix' do
25
+ v1 = described_class.eigvalsh(mat_c)
26
+ v2, = described_class.eigh(mat_c)
27
+ expect((v1 - v2).abs.mean).to be < ERR_TOL
28
+ end
29
+
30
+ it 'finds eigenvalues by solving generalized eigenvalue problem for symmetric matrices' do
31
+ v1 = described_class.eigvalsh(mat_a, mat_b)
32
+ v2, = described_class.eigh(mat_a, mat_b)
33
+ expect((v1 - v2).abs.mean).to be < ERR_TOL
34
+ end
35
+
36
+ it 'finds eigenvalues by solving generalized eigenvalue problem for hermitian matrices' do
37
+ v1 = described_class.eigvalsh(mat_c, mat_d)
38
+ v2, = described_class.eigh(mat_c, mat_d)
39
+ expect((v1 - v2).abs.mean).to be < ERR_TOL
40
+ end
41
+
42
+ it 'finds eigenvalues by solving generalized eigenvalue problem for symmetric and hermitian matrices' do
43
+ v1 = described_class.eigvalsh(mat_a, mat_d)
44
+ v2, = described_class.eigh(mat_a, mat_d)
45
+ expect((v1 - v2).abs.mean).to be < ERR_TOL
46
+ end
47
+
48
+ it 'finds eigenvalues for a symmetric matrix with divide and conquer algorithm' do
49
+ v1 = described_class.eigvalsh(mat_a, turbo: true)
50
+ v2, = described_class.eigh(mat_a, turbo: true)
51
+ expect((v1 - v2).abs.mean).to be < ERR_TOL
52
+ end
53
+
54
+ it 'finds eigenvalues in the range of the specified indices' do
55
+ v1 = described_class.eigvalsh(mat_a, mat_b, vals_range: 0...n)
56
+ v2, = described_class.eigh(mat_a, mat_b, vals_range: 0...n)
57
+ expect((v1 - v2).abs.mean).to be < ERR_TOL
58
+ end
59
+ end
60
+ end
@@ -0,0 +1,57 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'spec_helper'
4
+
5
+ RSpec.describe Numo::Linalg do
6
+ describe 'inv' do
7
+ let(:m) { 5 }
8
+ let(:mat_a) { rand_square_real_mat(m) }
9
+ let(:mat_b) { rand_square_complex_mat(m) }
10
+ let(:mat_s) { rand_symmetric_mat(m) }
11
+ let(:mat_h) { rand_hermitian_mat(m) }
12
+
13
+ it 'raises ArgumentError given a invalid driver option' do
14
+ expect { described_class.inv(mat_a, driver: 'foo') }.to raise_error(ArgumentError)
15
+ end
16
+
17
+ it 'calculates the inverse of a square real matrix solving linear equation' do
18
+ inv_mat_a = described_class.inv(mat_a, driver: 'gesv')
19
+ expect((inv_mat_a.dot(mat_a) - Numo::DFloat.eye(m)).abs.max).to be < ERR_TOL
20
+ end
21
+
22
+ it 'calculates the inverse of a square complex matrix solving linear equation' do
23
+ inv_mat_b = described_class.inv(mat_b, driver: 'gesv')
24
+ expect((inv_mat_b.dot(mat_b) - Numo::DFloat.eye(m)).abs.max).to be < ERR_TOL
25
+ end
26
+
27
+ it 'calculates the inverse of a symmetric matrix solving linear equation' do
28
+ inv_mat_s = described_class.inv(mat_s, driver: 'sysv')
29
+ expect((inv_mat_s.dot(mat_s) - Numo::DFloat.eye(m)).abs.max).to be < ERR_TOL
30
+ end
31
+
32
+ it 'calculates the inverse of a hermitian matrix solving linear equation' do
33
+ inv_mat_h = described_class.inv(mat_h, driver: 'hesv')
34
+ expect((inv_mat_h.dot(mat_h) - Numo::DFloat.eye(m)).abs.max).to be < ERR_TOL
35
+ end
36
+
37
+ it 'calculates the inverse of a square real matrix using the LU factorization' do
38
+ inv_mat_a = described_class.inv(mat_a, driver: 'getrf')
39
+ expect((inv_mat_a.dot(mat_a) - Numo::DFloat.eye(m)).abs.max).to be < ERR_TOL
40
+ end
41
+
42
+ it 'calculates the inverse of a square complex matrix using the LU factorization' do
43
+ inv_mat_b = described_class.inv(mat_b, driver: 'getrf')
44
+ expect((inv_mat_b.dot(mat_b) - Numo::DFloat.eye(m)).abs.max).to be < ERR_TOL
45
+ end
46
+
47
+ it 'calculates the inverse of a symmetric matrix using the LU factorization' do
48
+ inv_mat_s = described_class.inv(mat_s, driver: 'sytrf')
49
+ expect((inv_mat_s.dot(mat_s) - Numo::DFloat.eye(m)).abs.max).to be < ERR_TOL
50
+ end
51
+
52
+ it 'calculates the inverse of a hermitian matrix using the LU factorization' do
53
+ inv_mat_h = described_class.inv(mat_h, driver: 'hetrf')
54
+ expect((inv_mat_h.dot(mat_h) - Numo::DFloat.eye(m)).abs.max).to be < ERR_TOL
55
+ end
56
+ end
57
+ end
@@ -0,0 +1,51 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'spec_helper'
4
+
5
+ RSpec.describe Numo::Linalg do
6
+ describe 'ldl' do
7
+ let(:m) { 5 }
8
+ let(:mat_s) { rand_symmetric_mat(m) }
9
+ let(:mat_c) { rand_symmetric_mat(m) + Complex::I * rand_symmetric_mat(m) }
10
+ let(:mat_h) { rand_hermitian_mat(m) }
11
+
12
+ it 'raises ShapeError given a vector' do
13
+ expect { described_class.ldl(Numo::DFloat.new(3).rand) }.to raise_error(Numo::NArray::ShapeError)
14
+ end
15
+
16
+ it 'raises ShapeError given a rectangular matrix' do
17
+ expect { described_class.ldl(Numo::DFloat.new(2, 4).rand) }.to raise_error(Numo::NArray::ShapeError)
18
+ end
19
+
20
+ it 'raises ArgumentError given an invalid uplo option' do
21
+ expect { described_class.ldl(mat_s, uplo: 'A') }.to raise_error(ArgumentError)
22
+ end
23
+
24
+ it 'calculates the LDLt or Bunch-Kaufman factorization of a symmetric real matrix' do
25
+ mat_u, mat_d, perm = described_class.ldl(mat_s, uplo: 'U')
26
+ expect((mat_s - mat_u.dot(mat_d.dot(mat_u.transpose))).abs.max).to be < ERR_TOL
27
+ expect(mat_u[perm, true].tril(-1)).to eq(Numo::DFloat.zeros(m, m))
28
+ mat_l, mat_d, perm = described_class.ldl(mat_s, uplo: 'L')
29
+ expect((mat_s - mat_l.dot(mat_d.dot(mat_l.transpose))).abs.max).to be < ERR_TOL
30
+ expect(mat_l[perm, true].triu(1)).to eq(Numo::DFloat.zeros(m, m))
31
+ end
32
+
33
+ it 'calculates the LDLt or Bunch-Kaufman factorization of a symmetric complex matrix' do
34
+ mat_u, mat_d, perm = described_class.ldl(mat_c, uplo: 'U', hermitian: false)
35
+ expect((mat_c - mat_u.dot(mat_d.dot(mat_u.transpose))).abs.max).to be < ERR_TOL
36
+ expect(mat_u[perm, true].tril(-1)).to eq(Numo::DComplex.zeros(m, m))
37
+ mat_l, mat_d, perm = described_class.ldl(mat_c, uplo: 'L', hermitian: false)
38
+ expect((mat_c - mat_l.dot(mat_d.dot(mat_l.transpose))).abs.max).to be < ERR_TOL
39
+ expect(mat_l[perm, true].triu(1)).to eq(Numo::DComplex.zeros(m, m))
40
+ end
41
+
42
+ it 'calculates the LDLt or Bunch-Kaufman factorization of a hermitian matrix' do
43
+ mat_u, mat_d, perm = described_class.ldl(mat_h, uplo: 'U')
44
+ expect((mat_h - mat_u.dot(mat_d.dot(mat_u.transpose.conj))).abs.max).to be < ERR_TOL
45
+ expect(mat_u[perm, true].tril(-1)).to eq(Numo::DComplex.zeros(m, m))
46
+ mat_l, mat_d, perm = described_class.ldl(mat_h, uplo: 'L')
47
+ expect((mat_h - mat_l.dot(mat_d.dot(mat_l.transpose.conj))).abs.max).to be < ERR_TOL
48
+ expect(mat_l[perm, true].triu(1)).to eq(Numo::DComplex.zeros(m, m))
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,80 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'spec_helper'
4
+
5
+ RSpec.describe Numo::Linalg do
6
+ describe 'lstsq' do
7
+ #let(:m) { 5 }
8
+ #let(:n) { 3 }
9
+ let(:m) { 3 }
10
+ let(:n) { 8 }
11
+ let(:nrhs) { 2 }
12
+ let(:mat_a) { rand_rect_real_mat(m, n) }
13
+ let(:mat_b) { rand_rect_real_mat(m, nrhs) }
14
+ let(:vec_b) { rand_real_vec(m) }
15
+ let(:mat_c) { rand_rect_complex_mat(m, n) }
16
+ let(:mat_d) { rand_rect_complex_mat(m, nrhs) }
17
+ let(:vec_d) { rand_complex_vec(m) }
18
+
19
+ it 'raises ArgumentError given a invalid driver option' do
20
+ expect { described_class.lstsq(mat_a, vec_b, driver: 'foo') }.to raise_error(ArgumentError)
21
+ end
22
+
23
+ it 'solves a linear least square problem min||b-Ax|| using the singuar value decomposition' do
24
+ vec_x, = described_class.lstsq(mat_a, vec_b, driver: 'lss')
25
+ expect(((vec_b - mat_a.dot(vec_x))**2).sum).to be < ERR_TOL
26
+ mat_x, = described_class.lstsq(mat_a, mat_b, driver: 'lss')
27
+ expect(((mat_b - mat_a.dot(mat_x))**2).sum(0).max).to be < ERR_TOL
28
+ vec_x, = described_class.lstsq(mat_a, vec_d, driver: 'lss')
29
+ expect(((vec_d - mat_a.dot(vec_x))**2).abs.sum).to be < ERR_TOL
30
+ mat_x, = described_class.lstsq(mat_a, mat_d, driver: 'lss')
31
+ expect(((mat_d - mat_a.dot(mat_x))**2).abs.sum(0).max).to be < ERR_TOL
32
+ vec_x, = described_class.lstsq(mat_c, vec_d, driver: 'lss')
33
+ expect(((vec_d - mat_c.dot(vec_x))**2).abs.sum).to be < ERR_TOL
34
+ mat_x, = described_class.lstsq(mat_c, mat_d, driver: 'lss')
35
+ expect(((mat_d - mat_c.dot(mat_x))**2).abs.sum(0).max).to be < ERR_TOL
36
+ vec_x, = described_class.lstsq(mat_c, vec_b, driver: 'lss')
37
+ expect(((vec_b - mat_c.dot(vec_x))**2).abs.sum).to be < ERR_TOL
38
+ mat_x, = described_class.lstsq(mat_c, mat_b, driver: 'lss')
39
+ expect(((mat_b - mat_c.dot(mat_x))**2).abs.sum(0).max).to be < ERR_TOL
40
+ end
41
+
42
+ it 'solves a linear least square problem min||b-Ax|| using the singuar value decomposition with divide and conquer method' do
43
+ vec_x, = described_class.lstsq(mat_a, vec_b, driver: 'lsd')
44
+ expect(((vec_b - mat_a.dot(vec_x))**2).sum).to be < ERR_TOL
45
+ mat_x, = described_class.lstsq(mat_a, mat_b, driver: 'lsd')
46
+ expect(((mat_b - mat_a.dot(mat_x))**2).sum(0).max).to be < ERR_TOL
47
+ vec_x, = described_class.lstsq(mat_a, vec_d, driver: 'lsd')
48
+ expect(((vec_d - mat_a.dot(vec_x))**2).abs.sum).to be < ERR_TOL
49
+ mat_x, = described_class.lstsq(mat_a, mat_d, driver: 'lsd')
50
+ expect(((mat_d - mat_a.dot(mat_x))**2).abs.sum(0).max).to be < ERR_TOL
51
+ vec_x, = described_class.lstsq(mat_c, vec_d, driver: 'lsd')
52
+ expect(((vec_d - mat_c.dot(vec_x))**2).abs.sum).to be < ERR_TOL
53
+ mat_x, = described_class.lstsq(mat_c, mat_d, driver: 'lsd')
54
+ expect(((mat_d - mat_c.dot(mat_x))**2).abs.sum(0).max).to be < ERR_TOL
55
+ vec_x, = described_class.lstsq(mat_c, vec_b, driver: 'lsd')
56
+ expect(((vec_b - mat_c.dot(vec_x))**2).abs.sum).to be < ERR_TOL
57
+ mat_x, = described_class.lstsq(mat_c, mat_b, driver: 'lsd')
58
+ expect(((mat_b - mat_c.dot(mat_x))**2).abs.sum(0).max).to be < ERR_TOL
59
+ end
60
+
61
+ it 'solves a linear least square problem min||b-Ax|| using a complete orthogonal factorization of A' do
62
+ vec_x, = described_class.lstsq(mat_a, vec_b, driver: 'lsy')
63
+ expect(((vec_b - mat_a.dot(vec_x))**2).sum).to be < ERR_TOL
64
+ mat_x, = described_class.lstsq(mat_a, mat_b, driver: 'lsy')
65
+ expect(((mat_b - mat_a.dot(mat_x))**2).sum(0).max).to be < ERR_TOL
66
+ vec_x, = described_class.lstsq(mat_a, vec_d, driver: 'lsy')
67
+ expect(((vec_d - mat_a.dot(vec_x))**2).abs.sum).to be < ERR_TOL
68
+ mat_x, = described_class.lstsq(mat_a, mat_d, driver: 'lsy')
69
+ expect(((mat_d - mat_a.dot(mat_x))**2).abs.sum(0).max).to be < ERR_TOL
70
+ vec_x, = described_class.lstsq(mat_c, vec_d, driver: 'lsy')
71
+ expect(((vec_d - mat_c.dot(vec_x))**2).abs.sum).to be < ERR_TOL
72
+ mat_x, = described_class.lstsq(mat_c, mat_d, driver: 'lsy')
73
+ expect(((mat_d - mat_c.dot(mat_x))**2).abs.sum(0).max).to be < ERR_TOL
74
+ vec_x, = described_class.lstsq(mat_c, vec_b, driver: 'lsy')
75
+ expect(((vec_b - mat_c.dot(vec_x))**2).abs.sum).to be < ERR_TOL
76
+ mat_x, = described_class.lstsq(mat_c, mat_b, driver: 'lsy')
77
+ expect(((mat_b - mat_c.dot(mat_x))**2).abs.sum(0).max).to be < ERR_TOL
78
+ end
79
+ end
80
+ end
@@ -0,0 +1,34 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'spec_helper'
4
+
5
+ RSpec.describe Numo::Linalg do
6
+ describe 'lu_fact' do
7
+ let(:m) { 5 }
8
+ let(:n) { 3 }
9
+ let(:mat_a) { rand_rect_real_mat(m, n) }
10
+ let(:mat_b) { rand_rect_complex_mat(m, n) }
11
+
12
+ def permutation_mat(ipiv)
13
+ Numo::DFloat.eye(m).tap do |mat|
14
+ ipiv.to_a.each_with_index { |a, b| mat[[a - 1, b], true] = mat[[b, a - 1], true].dup }
15
+ end
16
+ end
17
+
18
+ it 'calculates the LU factorization of a rectangular real matrix' do
19
+ lu, ipiv = described_class.lu_fact(mat_a)
20
+ mat_l = lu.tril.tap { |mat| mat[mat.diag_indices(0)] = 1.0 }
21
+ mat_u = lu.triu[0...n, 0...n]
22
+ mat_p = permutation_mat(ipiv)
23
+ expect((mat_p.dot(mat_a) - mat_l.dot(mat_u)).abs.max).to be < ERR_TOL
24
+ end
25
+
26
+ it 'calculates the LU factorization of a rectangular complex matrix' do
27
+ lu, ipiv = described_class.lu_fact(mat_b)
28
+ mat_l = lu.tril.tap { |mat| mat[mat.diag_indices(0)] = 1.0 }
29
+ mat_u = lu.triu[0...n, 0...n]
30
+ mat_p = permutation_mat(ipiv)
31
+ expect((mat_p.dot(mat_b) - mat_l.dot(mat_u)).abs.max).to be < ERR_TOL
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'spec_helper'
4
+
5
+ RSpec.describe Numo::Linalg do
6
+ describe 'lu_inv' do
7
+ let(:m) { 5 }
8
+ let(:mat_a) { rand_square_real_mat(m) }
9
+ let(:mat_b) { rand_square_complex_mat(m) }
10
+
11
+ it 'calculates the inverse of a square real matrix' do
12
+ inv_mat_a = described_class.inv(mat_a)
13
+ expect((inv_mat_a.dot(mat_a) - Numo::DFloat.eye(m)).abs.max).to be < ERR_TOL
14
+ end
15
+
16
+ it 'calculates the inverse of a square complex matrix' do
17
+ inv_mat_b = described_class.inv(mat_b)
18
+ expect((inv_mat_b.dot(mat_b) - Numo::DFloat.eye(m)).abs.max).to be < ERR_TOL
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,40 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'spec_helper'
4
+
5
+ RSpec.describe Numo::Linalg do
6
+ describe 'lu_solve' do
7
+ let(:m) { 5 }
8
+ let(:n) { 3 }
9
+ let(:mat_a) { rand_square_real_mat(m) }
10
+ let(:mat_b) { rand_rect_real_mat(m, n) }
11
+ let(:vec_b) { rand_real_vec(m) }
12
+ let(:mat_c) { rand_square_complex_mat(m) }
13
+ let(:mat_d) { rand_rect_complex_mat(m, n) }
14
+ let(:vec_d) { rand_complex_vec(m) }
15
+
16
+ it 'solves the linear equation A x = b with a square real matrix A' do
17
+ lu, ipiv = described_class.lu_fact(mat_a)
18
+ vec_x = described_class.lu_solve(lu, ipiv, vec_b)
19
+ expect((mat_a.dot(vec_x) - vec_b).abs.max).to be < ERR_TOL
20
+ mat_x = described_class.lu_solve(lu, ipiv, mat_b)
21
+ expect((mat_a.dot(mat_x) - mat_b).abs.max).to be < ERR_TOL
22
+ vec_x = described_class.lu_solve(lu, ipiv, vec_d)
23
+ expect((mat_a.dot(vec_x) - vec_d).abs.max).to be < ERR_TOL
24
+ mat_x = described_class.lu_solve(lu, ipiv, mat_d)
25
+ expect((mat_a.dot(mat_x) - mat_d).abs.max).to be < ERR_TOL
26
+ end
27
+
28
+ it 'solves the linear equation A x = b with a square complex matrix A' do
29
+ lu, ipiv = described_class.lu_fact(mat_c)
30
+ vec_x = described_class.lu_solve(lu, ipiv, vec_b)
31
+ expect((mat_c.dot(vec_x) - vec_b).abs.max).to be < ERR_TOL
32
+ mat_x = described_class.lu_solve(lu, ipiv, mat_b)
33
+ expect((mat_c.dot(mat_x) - mat_b).abs.max).to be < ERR_TOL
34
+ vec_x = described_class.lu_solve(lu, ipiv, vec_d)
35
+ expect((mat_c.dot(vec_x) - vec_d).abs.max).to be < ERR_TOL
36
+ mat_x = described_class.lu_solve(lu, ipiv, mat_d)
37
+ expect((mat_c.dot(mat_x) - mat_d).abs.max).to be < ERR_TOL
38
+ end
39
+ end
40
+ end