# = NMatrix # # A linear algebra library for scientific computation in Ruby. # NMatrix is part of SciRuby. # # NMatrix was originally inspired by and derived from NArray, by # Masahiro Tanaka: http://narray.rubyforge.org # # == Copyright Information # # SciRuby is Copyright (c) 2010 - 2014, Ruby Science Foundation # NMatrix is Copyright (c) 2012 - 2014, John Woods and the Ruby Science Foundation # # Please see LICENSE.txt for additional copyright notices. # # == Contributing # # By contributing source code to SciRuby, you agree to be bound by # our Contributor Agreement: # # * https://github.com/SciRuby/sciruby/wiki/Contributor-Agreement # # == math_spec.rb # # Tests for non-BLAS and non-LAPACK math functions, or for simplified # versions of unfriendly BLAS and LAPACK functions. # require 'spec_helper' describe "math" do context "elementwise math functions" do [:dense,:list,:yale].each do |stype| context stype do [:int64,:float64].each do |dtype| context dtype do before :each do @size = [2,2] @m = NMatrix.seq(@size, dtype: dtype, stype: stype)+1 @a = @m.to_a.flatten end NMatrix::NMMath::METHODS_ARITY_1.each do |meth| #skip inverse regular trig functions next if meth.to_s.start_with?('a') and (not meth.to_s.end_with?('h')) \ and NMatrix::NMMath::METHODS_ARITY_1.include?( meth.to_s[1...meth.to_s.length].to_sym) next if meth == :atanh if meth == :-@ it "should correctly apply elementwise negation" do expect(@m.send(meth)).to eq N.new(@size, @a.map { |e| -e }, dtype: dtype, stype: stype) end next end it "should correctly apply elementwise #{meth}" do expect(@m.send(meth)).to eq N.new(@size, @a.map{ |e| Math.send(meth, e) }, dtype: :float64, stype: stype) end end NMatrix::NMMath::METHODS_ARITY_2.each do |meth| next if meth == :atan2 it "should correctly apply elementwise #{meth}" do expect(@m.send(meth, @m)).to eq N.new(@size, @a.map{ |e| Math.send(meth, e, e) }, dtype: :float64, stype: stype) end it "should correctly apply elementwise #{meth} with a scalar first arg" do expect(Math.send(meth, 1, @m)).to eq N.new(@size, @a.map { |e| Math.send(meth, 1, e) }, dtype: :float64, stype: stype) end it "should correctly apply elementwise #{meth} with a scalar second arg" do expect(@m.send(meth, 1)).to eq N.new(@size, @a.map { |e| Math.send(meth, e, 1) }, dtype: :float64, stype: stype) end end it "should correctly apply elementwise natural log" do expect(@m.log).to eq N.new(@size, [0, Math.log(2), Math.log(3), Math.log(4)], dtype: :float64, stype: stype) end it "should correctly apply elementwise log with arbitrary base" do expect(@m.log(3)).to eq N.new(@size, [0, Math.log(2,3), 1, Math.log(4,3)], dtype: :float64, stype: stype) end context "inverse trig functions" do before :each do @m = NMatrix.seq(@size, dtype: dtype, stype: stype)/4 @a = @m.to_a.flatten end [:asin, :acos, :atan, :atanh].each do |atf| it "should correctly apply elementwise #{atf}" do expect(@m.send(atf)).to eq N.new(@size, @a.map{ |e| Math.send(atf, e) }, dtype: :float64, stype: stype) end end it "should correctly apply elementtwise atan2" do expect(@m.atan2(@m*0+1)).to eq N.new(@size, @a.map { |e| Math.send(:atan2, e, 1) }, dtype: :float64, stype: stype) end it "should correctly apply elementwise atan2 with a scalar first arg" do expect(Math.atan2(1, @m)).to eq N.new(@size, @a.map { |e| Math.send(:atan2, 1, e) }, dtype: :float64, stype: stype) end it "should correctly apply elementwise atan2 with a scalar second arg" do expect(@m.atan2(1)).to eq N.new(@size, @a.map { |e| Math.send(:atan2, e, 1) }, dtype: :float64, stype: stype) end end end end context "Floor and ceil for #{stype}" do [:floor, :ceil].each do |meth| ALL_DTYPES.each do |dtype| context dtype do before :each do @size = [2,2] @m = NMatrix.seq(@size, dtype: dtype, stype: stype)+1 @a = @m.to_a.flatten end if dtype.to_s.match(/int/) or [:byte, :object].include?(dtype) it "should return #{dtype} for #{dtype}" do expect(@m.send(meth)).to eq N.new(@size, @a.map { |e| e.send(meth) }, dtype: dtype, stype: stype) if dtype == :object expect(@m.send(meth).dtype).to eq :object else expect(@m.send(meth).integer_dtype?).to eq true end end elsif dtype.to_s.match(/float/) it "should return dtype int64 for #{dtype}" do expect(@m.send(meth)).to eq N.new(@size, @a.map { |e| e.send(meth) }, dtype: dtype, stype: stype) expect(@m.send(meth).dtype).to eq :int64 end elsif dtype.to_s.match(/complex/) it "should properly calculate #{meth} for #{dtype}" do expect(@m.send(meth)).to eq N.new(@size, @a.map { |e| e = Complex(e.real.send(meth), e.imag.send(meth)) }, dtype: dtype, stype: stype) expect(@m.send(meth).dtype).to eq :complex64 if dtype == :complex64 expect(@m.send(meth).dtype).to eq :complex128 if dtype == :complex128 end end end end end end context "#round for #{stype}" do ALL_DTYPES.each do |dtype| context dtype do before :each do @size = [2,2] @mat = NMatrix.new @size, [1.33334, 0.9998, 1.9999, -8.9999], dtype: dtype, stype: stype @ans = @mat.to_a.flatten end it "rounds" do expect(@mat.round).to eq(N.new(@size, @ans.map { |a| a.round}, dtype: dtype, stype: stype)) end unless(/complex/ =~ dtype) it "rounds with args" do expect(@mat.round(2)).to eq(N.new(@size, @ans.map { |a| a.round(2)}, dtype: dtype, stype: stype)) end unless(/complex/ =~ dtype) it "rounds complex with args" do puts @mat.round(2) expect(@mat.round(2)).to be_within(0.0001).of(N.new [2,2], @ans.map {|a| Complex(a.real.round(2), a.imag.round(2))},dtype: dtype, stype: stype) end if(/complex/ =~ dtype) it "rounds complex" do expect(@mat.round).to eq(N.new [2,2], @ans.map {|a| Complex(a.real.round, a.imag.round)},dtype: dtype, stype: stype) end if(/complex/ =~ dtype) end end end end end end NON_INTEGER_DTYPES.each do |dtype| next if dtype == :object context dtype do before do @m = NMatrix.new([3,4], GETRF_EXAMPLE_ARRAY, dtype: dtype) @err = case dtype when :float32, :complex64 1e-6 when :float64, :complex128 1e-14 end end #haven't check this spec yet. Also it doesn't check all the elements of the matrix. it "should correctly factorize a matrix" do a = @m.factorize_lu expect(a).to be_within(@err).of(NMatrix.new([3,4], GETRF_SOLUTION_ARRAY, dtype: dtype)) end it "also returns the permutation matrix" do a, p = @m.factorize_lu perm_matrix: true expect(a).to be_within(@err).of(NMatrix.new([3,4], GETRF_SOLUTION_ARRAY, dtype: dtype)) p_true = NMatrix.new([3,3], [0,0,1,1,0,0,0,1,0], dtype: dtype) expect(p).to eq(p_true) end end end NON_INTEGER_DTYPES.each do |dtype| next if dtype == :object context dtype do it "calculates cholesky decomposition using potrf (lower)" do #a = NMatrix.new([3,3],[1,1,1, 1,2,2, 1,2,6], dtype: dtype) # We use the matrix # 1 1 1 # 1 2 2 # 1 2 6 # which is symmetric and positive-definite as required, but # we need only store the lower-half of the matrix. a = NMatrix.new([3,3],[1,0,0, 1,2,0, 1,2,6], dtype: dtype) begin r = a.potrf!(:lower) b = NMatrix.new([3,3],[1,0,0, 1,1,0, 1,1,2], dtype: dtype) expect(a).to eq(b) expect(r).to eq(b) rescue NotImplementedError pending "potrf! not implemented without plugins" end end it "calculates cholesky decomposition using potrf (upper)" do a = NMatrix.new([3,3],[1,1,1, 0,2,2, 0,0,6], dtype: dtype) begin r = a.potrf!(:upper) b = NMatrix.new([3,3],[1,1,1, 0,1,1, 0,0,2], dtype: dtype) expect(a).to eq(b) expect(r).to eq(b) rescue NotImplementedError pending "potrf! not implemented without plugins" end end it "calculates cholesky decomposition using #factorize_cholesky" do a = NMatrix.new([3,3],[1,2,1, 2,13,5, 1,5,6], dtype: dtype) begin u,l = a.factorize_cholesky l_true = NMatrix.new([3,3],[1,0,0, 2,3,0, 1,1,2], dtype: dtype) u_true = l_true.transpose expect(u).to eq(u_true) expect(l).to eq(l_true) rescue NotImplementedError pending "potrf! not implemented without plugins" end end end end ALL_DTYPES.each do |dtype| next if dtype == :byte #doesn't work for unsigned types next if dtype == :object context dtype do err = case dtype when :float32, :complex64 1e-4 else #integer matrices will return :float64 1e-13 end it "should correctly invert a matrix in place (bang)" do a = NMatrix.new(:dense, 5, [1, 8,-9, 7, 5, 0, 1, 0, 4, 4, 0, 0, 1, 2, 5, 0, 0, 0, 1,-5, 0, 0, 0, 0, 1 ], dtype) b = NMatrix.new(:dense, 5, [1,-8, 9, 7, 17, 0, 1, 0,-4,-24, 0, 0, 1,-2,-15, 0, 0, 0, 1, 5, 0, 0, 0, 0, 1,], dtype) if a.integer_dtype? expect{a.invert!}.to raise_error(DataTypeError) else #should return inverse as well as modifying a r = a.invert! expect(a).to be_within(err).of(b) expect(r).to be_within(err).of(b) end end it "should correctly invert a matrix out-of-place" do a = NMatrix.new(:dense, 3, [1,2,3,0,1,4,5,6,0], dtype) if a.integer_dtype? b = NMatrix.new(:dense, 3, [-24,18,5,20,-15,-4,-5,4,1], :float64) else b = NMatrix.new(:dense, 3, [-24,18,5,20,-15,-4,-5,4,1], dtype) end expect(a.invert).to be_within(err).of(b) end end end # TODO: Get it working with ROBJ too [:byte,:int8,:int16,:int32,:int64,:float32,:float64].each do |left_dtype| [:byte,:int8,:int16,:int32,:int64,:float32,:float64].each do |right_dtype| # Won't work if they're both 1-byte, due to overflow. next if [:byte,:int8].include?(left_dtype) && [:byte,:int8].include?(right_dtype) # For now, don't bother testing int-int mult. #next if [:int8,:int16,:int32,:int64].include?(left_dtype) && [:int8,:int16,:int32,:int64].include?(right_dtype) it "dense handles #{left_dtype.to_s} dot #{right_dtype.to_s} matrix multiplication" do #STDERR.puts "dtype=#{dtype.to_s}" #STDERR.puts "2" nary = if left_dtype.to_s =~ /complex/ COMPLEX_MATRIX43A_ARRAY else MATRIX43A_ARRAY end mary = if right_dtype.to_s =~ /complex/ COMPLEX_MATRIX32A_ARRAY else MATRIX32A_ARRAY end n = NMatrix.new([4,3], nary, dtype: left_dtype, stype: :dense) m = NMatrix.new([3,2], mary, dtype: right_dtype, stype: :dense) expect(m.shape[0]).to eq(3) expect(m.shape[1]).to eq(2) expect(m.dim).to eq(2) expect(n.shape[0]).to eq(4) expect(n.shape[1]).to eq(3) expect(n.dim).to eq(2) expect(n.shape[1]).to eq(m.shape[0]) r = n.dot m expect(r[0,0]).to eq(273.0) expect(r[0,1]).to eq(455.0) expect(r[1,0]).to eq(243.0) expect(r[1,1]).to eq(235.0) expect(r[2,0]).to eq(244.0) expect(r[2,1]).to eq(205.0) expect(r[3,0]).to eq(102.0) expect(r[3,1]).to eq(160.0) #r.dtype.should == :float64 unless left_dtype == :float32 && right_dtype == :float32 end end end [:byte,:int8,:int16,:int32,:int64,:float32,:float64].each do |left_dtype| [:byte,:int8,:int16,:int32,:int64,:float32,:float64].each do |right_dtype| # Won't work if they're both 1-byte, due to overflow. next if [:byte,:int8].include?(left_dtype) && [:byte,:int8].include?(right_dtype) it "dense handles #{left_dtype.to_s} dot #{right_dtype.to_s} vector multiplication" do #STDERR.puts "dtype=#{dtype.to_s}" #STDERR.puts "2" 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], dtype: left_dtype) m = NMatrix.new([3,1], [2.0, 1.0, 0.0], dtype: right_dtype) expect(m.shape[0]).to eq(3) expect(m.shape[1]).to eq(1) expect(n.shape[0]).to eq(4) expect(n.shape[1]).to eq(3) expect(n.dim).to eq(2) expect(n.shape[1]).to eq(m.shape[0]) r = n.dot m # r.class.should == NVector expect(r[0,0]).to eq(4) expect(r[1,0]).to eq(13) expect(r[2,0]).to eq(22) expect(r[3,0]).to eq(31) #r.dtype.should == :float64 unless left_dtype == :float32 && right_dtype == :float32 end end end ALL_DTYPES.each do |dtype| next if integer_dtype?(dtype) context "#cov dtype #{dtype}" do before do @n = NMatrix.new( [5,3], [4.0,2.0,0.60, 4.2,2.1,0.59, 3.9,2.0,0.58, 4.3,2.1,0.62, 4.1,2.2,0.63], dtype: dtype) end it "calculates variance co-variance matrix (sample)" do expect(@n.cov).to be_within(0.0001).of(NMatrix.new([3,3], [0.025 , 0.0075, 0.00175, 0.0075, 0.007 , 0.00135, 0.00175, 0.00135 , 0.00043 ], dtype: dtype) ) end it "calculates variance co-variance matrix (population)" do expect(@n.cov(for_sample_data: false)).to be_within(0.0001).of(NMatrix.new([3,3], [2.0000e-02, 6.0000e-03, 1.4000e-03, 6.0000e-03, 5.6000e-03, 1.0800e-03, 1.4000e-03, 1.0800e-03, 3.4400e-04], dtype: dtype) ) end end context "#corr #{dtype}" do it "calculates the correlation matrix" do n = NMatrix.new([5,3], [4.0,2.0,0.60, 4.2,2.1,0.59, 3.9,2.0,0.58, 4.3,2.1,0.62, 4.1,2.2,0.63], dtype: dtype) expect(n.corr).to be_within(0.001).of(NMatrix.new([3,3], [1.00000, 0.56695, 0.53374, 0.56695, 1.00000, 0.77813, 0.53374, 0.77813, 1.00000], dtype: dtype)) end unless dtype =~ /complex/ end context "#symmetric? for #{dtype}" do it "should return true for symmetric matrix" do n = NMatrix.new([3,3], [1.00000, 0.56695, 0.53374, 0.56695, 1.00000, 0.77813, 0.53374, 0.77813, 1.00000], dtype: dtype) expect(n.symmetric?).to be_truthy end end context "#hermitian? for #{dtype}" do it "should return true for complex hermitian or non-complex symmetric matrix" do n = NMatrix.new([3,3], [1.00000, 0.56695, 0.53374, 0.56695, 1.00000, 0.77813, 0.53374, 0.77813, 1.00000], dtype: dtype) unless dtype =~ /complex/ n = NMatrix.new([3,3], [1.1, Complex(1.2,1.3), Complex(1.4,1.5), Complex(1.2,-1.3), 1.9, Complex(1.8,1.7), Complex(1.4,-1.5), Complex(1.8,-1.7), 1.3], dtype: dtype) if dtype =~ /complex/ expect(n.hermitian?).to be_truthy end end context "#permute_columns for #{dtype}" do it "check that #permute_columns works correctly by considering every premutation of a 3x3 matrix" do n = NMatrix.new([3,3], [1,0,0, 0,2,0, 0,0,3], dtype: dtype) expect(n.permute_columns([0,1,2], {convention: :intuitive})).to eq(NMatrix.new([3,3], [1,0,0, 0,2,0, 0,0,3], dtype: dtype)) expect(n.permute_columns([0,2,1], {convention: :intuitive})).to eq(NMatrix.new([3,3], [1,0,0, 0,0,2, 0,3,0], dtype: dtype)) expect(n.permute_columns([1,0,2], {convention: :intuitive})).to eq(NMatrix.new([3,3], [0,1,0, 2,0,0, 0,0,3], dtype: dtype)) expect(n.permute_columns([1,2,0], {convention: :intuitive})).to eq(NMatrix.new([3,3], [0,0,1, 2,0,0, 0,3,0], dtype: dtype)) expect(n.permute_columns([2,0,1], {convention: :intuitive})).to eq(NMatrix.new([3,3], [0,1,0, 0,0,2, 3,0,0], dtype: dtype)) expect(n.permute_columns([2,1,0], {convention: :intuitive})).to eq(NMatrix.new([3,3], [0,0,1, 0,2,0, 3,0,0], dtype: dtype)) expect(n.permute_columns([0,1,2], {convention: :lapack})).to eq(NMatrix.new([3,3], [1,0,0, 0,2,0, 0,0,3], dtype: dtype)) expect(n.permute_columns([0,2,2], {convention: :lapack})).to eq(NMatrix.new([3,3], [1,0,0, 0,0,2, 0,3,0], dtype: dtype)) expect(n.permute_columns([1,1,2], {convention: :lapack})).to eq(NMatrix.new([3,3], [0,1,0, 2,0,0, 0,0,3], dtype: dtype)) expect(n.permute_columns([1,2,2], {convention: :lapack})).to eq(NMatrix.new([3,3], [0,0,1, 2,0,0, 0,3,0], dtype: dtype)) expect(n.permute_columns([2,2,2], {convention: :lapack})).to eq(NMatrix.new([3,3], [0,1,0, 0,0,2, 3,0,0], dtype: dtype)) expect(n.permute_columns([2,1,2], {convention: :lapack})).to eq(NMatrix.new([3,3], [0,0,1, 0,2,0, 3,0,0], dtype: dtype)) end it "additional tests for #permute_columns with convention :intuitive" do m = NMatrix.new([1,4], [0,1,2,3], dtype: dtype) perm = [1,0,3,2] expect(m.permute_columns(perm, {convention: :intuitive})).to eq(NMatrix.new([1,4], perm, dtype: dtype)) m = NMatrix.new([1,5], [0,1,2,3,4], dtype: dtype) perm = [1,0,4,3,2] expect(m.permute_columns(perm, {convention: :intuitive})).to eq(NMatrix.new([1,5], perm, dtype: dtype)) m = NMatrix.new([1,6], [0,1,2,3,4,5], dtype: dtype) perm = [2,4,1,0,5,3] expect(m.permute_columns(perm, {convention: :intuitive})).to eq(NMatrix.new([1,6], perm, dtype: dtype)) m = NMatrix.new([1,7], [0,1,2,3,4,5,6], dtype: dtype) perm = [1,3,5,6,0,2,4] expect(m.permute_columns(perm, {convention: :intuitive})).to eq(NMatrix.new([1,7], perm, dtype: dtype)) m = NMatrix.new([1,8], [0,1,2,3,4,5,6,7], dtype: dtype) perm = [6,7,5,4,1,3,0,2] expect(m.permute_columns(perm, {convention: :intuitive})).to eq(NMatrix.new([1,8], perm, dtype: dtype)) end end end context "#solve" do NON_INTEGER_DTYPES.each do |dtype| next if dtype == :object # LU factorization doesnt work for :object yet it "solves linear equation for dtype #{dtype}" do a = NMatrix.new [2,2], [3,1,1,2], dtype: dtype b = NMatrix.new [2,1], [9,8], dtype: dtype expect(a.solve(b)).to eq(NMatrix.new [2,1], [2,3], dtype: dtype) end it "solves linear equation for #{dtype} (non-symmetric matrix)" do a = NMatrix.new [3,3], [1,1,1, -1,0,1, 3,4,6], dtype: dtype b = NMatrix.new [3,1], [6,2,29], dtype: dtype err = case dtype when :float32, :complex64 1e-5 else 1e-14 end expect(a.solve(b)).to be_within(err).of(NMatrix.new([3,1], [1,2,3], dtype: dtype)) end it "solves linear equation for dtype #{dtype} (non-vector rhs)" do a = NMatrix.new [3,3], [1,0,0, -1,0,1, 2,1,1], dtype: dtype b = NMatrix.new [3,2], [1,0, 1,2, 4,2], dtype: dtype expect(a.solve(b)).to eq(NMatrix.new [3,2], [1,0, 0,0, 2,2], dtype: dtype) end end FLOAT_DTYPES.each do |dtype| context "when form: :lower_tri" do let(:a) { NMatrix.new([3,3], [1, 0, 0, 2, 0.5, 0, 3, 3, 9], dtype: dtype) } it "solves a lower triangular linear system A * x = b with vector b" do b = NMatrix.new([3,1], [1,2,3], dtype: dtype) x = a.solve(b, form: :lower_tri) r = a.dot(x) - b expect(r.abs.max).to be_within(1e-6).of(0.0) end it "solves a lower triangular linear system A * X = B with narrow B" do b = NMatrix.new([3,2], [1,2,3,4,5,6], dtype: dtype) x = a.solve(b, form: :lower_tri) r = (a.dot(x) - b).abs.to_flat_a expect(r.max).to be_within(1e-6).of(0.0) end it "solves a lower triangular linear system A * X = B with wide B" do b = NMatrix.new([3,5], (1..15).to_a, dtype: dtype) x = a.solve(b, form: :lower_tri) r = (a.dot(x) - b).abs.to_flat_a expect(r.max).to be_within(1e-6).of(0.0) end end context "when form: :upper_tri" do let(:a) { NMatrix.new([3,3], [3, 2, 1, 0, 2, 0.5, 0, 0, 9], dtype: dtype) } it "solves an upper triangular linear system A * x = b with vector b" do b = NMatrix.new([3,1], [1,2,3], dtype: dtype) x = a.solve(b, form: :upper_tri) r = a.dot(x) - b expect(r.abs.max).to be_within(1e-6).of(0.0) end it "solves an upper triangular linear system A * X = B with narrow B" do b = NMatrix.new([3,2], [1,2,3,4,5,6], dtype: dtype) x = a.solve(b, form: :upper_tri) r = (a.dot(x) - b).abs.to_flat_a expect(r.max).to be_within(1e-6).of(0.0) end it "solves an upper triangular linear system A * X = B with a wide B" do b = NMatrix.new([3,5], (1..15).to_a, dtype: dtype) x = a.solve(b, form: :upper_tri) r = (a.dot(x) - b).abs.to_flat_a expect(r.max).to be_within(1e-6).of(0.0) end end context "when form: :pos_def" do let(:a) { NMatrix.new([3,3], [4, 1, 2, 1, 5, 3, 2, 3, 6], dtype: dtype) } it "solves a linear system A * X = b with positive definite A and vector b" do b = NMatrix.new([3,1], [6,4,8], dtype: dtype) begin x = a.solve(b, form: :pos_def) expect(x).to be_within(1e-6).of(NMatrix.new([3,1], [1,0,1], dtype: dtype)) rescue NotImplementedError "Suppressing a NotImplementedError when the lapacke or atlas plugin is not available" end end it "solves a linear system A * X = B with positive definite A and matrix B" do b = NMatrix.new([3,2], [8,3,14,13,14,19], dtype: dtype) begin x = a.solve(b, form: :pos_def) expect(x).to be_within(1e-6).of(NMatrix.new([3,2], [1,-1,2,1,1,3], dtype: dtype)) rescue NotImplementedError "Suppressing a NotImplementedError when the lapacke or atlas plugin is not available" end end end end end context "#hessenberg" do FLOAT_DTYPES.each do |dtype| context dtype do before do @n = NMatrix.new [5,5], [0, 2, 0, 1, 1, 2, 2, 3, 2, 2, 4,-3, 0, 1, 3, 6, 1,-6,-5, 4, 5, 6, 4, 1, 5], dtype: dtype end it "transforms a matrix to Hessenberg form" do expect(@n.hessenberg).to be_within(0.0001).of(NMatrix.new([5,5], [0.00000,-1.66667, 0.79432,-0.45191,-1.54501, -9.00000, 2.95062,-6.89312, 3.22250,-0.19012, 0.00000,-8.21682,-0.57379, 5.26966,-1.69976, 0.00000, 0.00000,-3.74630,-0.80893, 3.99708, 0.00000, 0.00000, 0.00000, 0.04102, 0.43211], dtype: dtype)) end end end end ALL_DTYPES.each do |dtype| [:dense, :yale].each do |stype| answer_dtype = integer_dtype?(dtype) ? :int64 : dtype next if dtype == :byte context "#pow #{dtype} #{stype}" do before do @n = NMatrix.new [4,4], [0, 2, 0, 1, 2, 2, 3, 2, 4,-3, 0, 1, 6, 1,-6,-5], dtype: dtype, stype: stype end it "raises a square matrix to even power" do expect(@n.pow(4)).to eq(NMatrix.new([4,4], [292, 28,-63, -42, 360, 96, 51, -14, 448,-231,-24,-87, -1168, 595,234, 523], dtype: answer_dtype, stype: stype)) end it "raises a square matrix to odd power" do expect(@n.pow(9)).to eq(NMatrix.new([4,4],[-275128, 279917, 176127, 237451, -260104, 394759, 166893, 296081, -704824, 285700, 186411, 262002, 3209256,-1070870,-918741,-1318584], dtype: answer_dtype, stype: stype)) end it "raises a sqaure matrix to negative power" do expect(@n.pow(-3)).to be_within(0.00001).of (NMatrix.new([4,4], [1.0647e-02, 4.2239e-04,-6.2281e-05, 2.7680e-03, -1.6415e-02, 2.1296e-02, 1.0718e-02, 4.8589e-03, 8.6956e-03,-8.6569e-03, 2.8993e-02, 7.2015e-03, 5.0034e-02,-1.7500e-02,-3.6777e-02,-1.2128e-02], dtype: answer_dtype, stype: stype)) end unless stype =~ /yale/ or dtype == :object or ALL_DTYPES.grep(/int/).include? dtype it "raises a square matrix to zero" do expect(@n.pow(0)).to eq(NMatrix.eye([4,4], dtype: answer_dtype, stype: stype)) end it "raises a square matrix to one" do expect(@n.pow(1)).to eq(@n) end end end end ALL_DTYPES.each do |dtype| [:dense, :yale].each do |stype| context "#kron_prod #{dtype} #{stype}" do before do @a = NMatrix.new([2,2], [1,2, 3,4], dtype: dtype, stype: stype) @b = NMatrix.new([2,3], [1,1,1, 1,1,1], dtype: dtype, stype: stype) @c = NMatrix.new([4,6], [1, 1, 1, 2, 2, 2, 1, 1, 1, 2, 2, 2, 3, 3, 3, 4, 4, 4, 3, 3, 3, 4, 4, 4], dtype: dtype, stype: stype) end it "Compute the Kronecker product of two NMatrix" do expect(@a.kron_prod(@b)).to eq(@c) end end end end context "determinants" do ALL_DTYPES.each do |dtype| next if dtype == :object context dtype do before do @a = NMatrix.new([2,2], [1,2, 3,4], dtype: dtype) @b = NMatrix.new([3,3], [1,2,3, 5,0,1, 4,1,3], dtype: dtype) @c = NMatrix.new([4,4], [1, 0, 1, 1, 1, 2, 3, 1, 3, 3, 3, 1, 1, 2, 3, 4], dtype: dtype) @err = case dtype when :float32, :complex64 1e-6 when :float64, :complex128 1e-14 else 1e-64 # FIXME: should be 0, but be_within(0) does not work. end end it "computes the determinant of 2x2 matrix" do expect(@a.det).to be_within(@err).of(-2) end it "computes the determinant of 3x3 matrix" do expect(@b.det).to be_within(@err).of(-8) end it "computes the determinant of 4x4 matrix" do expect(@c.det).to be_within(@err).of(-18) end it "computes the exact determinant of 2x2 matrix" do if dtype == :byte expect{@a.det_exact}.to raise_error(DataTypeError) else expect(@a.det_exact).to be_within(@err).of(-2) end end it "computes the exact determinant of 3x3 matrix" do if dtype == :byte expect{@a.det_exact}.to raise_error(DataTypeError) else expect(@b.det_exact).to be_within(@err).of(-8) end end end end end end