numo-linalg 0.1.2 → 0.1.3

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