# = 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 # # == atlas_spec.rb # # Tests for interfaces that are only exposed by nmatrix-atlas # require 'spec_helper' require "./lib/nmatrix/atlas" describe "NMatrix::LAPACK implementation from nmatrix-atlas plugin" do [:float32, :float64, :complex64, :complex128].each do |dtype| context dtype do it "exposes clapack_getri" do a = NMatrix.new(:dense, 3, [1,0,4,1,1,6,-3,0,-10], dtype) ipiv = NMatrix::LAPACK::clapack_getrf(:row, 3, 3, a, 3) # get pivot from getrf, use for getri begin NMatrix::LAPACK::clapack_getri(:row, 3, a, 3, ipiv) b = NMatrix.new(:dense, 3, [-5,0,-2,-4,1,-1,1.5,0,0.5], dtype) expect(a).to eq(b) rescue NotImplementedError => e pending e.to_s end end # potrf decomposes a symmetric (or Hermitian) # positive-definite matrix. The matrix tested below isn't symmetric. # But this is okay since potrf just examines the upper/lower half # (as requested) of the matrix and assumes that the rest is symmetric, # so we just set the other part of the matrix to zero. it "exposes clapack_potrf upper" do pending "potrf requires clapack" unless NMatrix.has_clapack? a = NMatrix.new(:dense, 3, [25,15,-5, 0,18,0, 0,0,11], dtype) NMatrix::LAPACK::clapack_potrf(:row, :upper, 3, a, 3) b = NMatrix.new(:dense, 3, [5,3,-1, 0,3,1, 0,0,3], dtype) expect(a).to eq(b) end it "exposes clapack_potrf lower" do pending "potrf requires clapack" unless NMatrix.has_clapack? a = NMatrix.new(:dense, 3, [25,0,0, 15,18,0,-5,0,11], dtype) NMatrix::LAPACK::clapack_potrf(:row, :lower, 3, a, 3) b = NMatrix.new(:dense, 3, [5,0,0, 3,3,0, -1,1,3], dtype) expect(a).to eq(b) end it "exposes clapack_potri" do pending "potri requires clapack" unless NMatrix.has_clapack? a = NMatrix.new(3, [4, 0,-1, 0, 2, 1, 0, 0, 1], dtype: dtype) NMatrix::LAPACK::clapack_potrf(:row, :upper, 3, a, 3) NMatrix::LAPACK::clapack_potri(:row, :upper, 3, a, 3) b = NMatrix.new(3, [0.5, -0.5, 1, 0, 1.5, -2, 0, 0, 4], dtype: dtype) err = case dtype when :float32, :complex64 1e-6 when :float64, :complex128 1e-14 end expect(a).to be_within(err).of(b) end it "exposes clapack_potrs" do pending "potrs requires clapack" unless NMatrix.has_clapack? a = NMatrix.new(3, [4, 0,-1, 0, 2, 1, 0, 0, 1], dtype: dtype) b = NMatrix.new([3,1], [3,0,2], dtype: dtype) NMatrix::LAPACK::clapack_potrf(:row, :upper, 3, a, 3) NMatrix::LAPACK::clapack_potrs(:row, :upper, 3, 1, a, 3, b, 3) x = NMatrix.new([3,1], [3.5, -5.5, 11], dtype: dtype) err = case dtype when :float32, :complex64 1e-5 when :float64, :complex128 1e-14 end expect(b).to be_within(err).of(x) end end end [:float32, :float64, :complex64, :complex128].each do |dtype| context dtype do it "calculates the singular value decomposition with lapack_gesvd" do #example from Wikipedia m = 4 n = 5 mn_min = [m,n].min 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) s = NMatrix.new([mn_min], 0, dtype: a.abs_dtype) #s is always real and always returned as float/double, never as complex u = NMatrix.new([m,m], 0, dtype: dtype) vt = NMatrix.new([n,n], 0, dtype: dtype) # This is a pure LAPACK function so it expects column-major functions # So we need to transpose the input as well as the output a = a.transpose NMatrix::LAPACK.lapack_gesvd(:a, :a, m, n, a, m, s, u, m, vt, n, 500) u = u.transpose vt = vt.transpose s_true = NMatrix.new([mn_min], [4,3,Math.sqrt(5),0], dtype: a.abs_dtype) u_true = NMatrix.new([m,m], [0,0,1,0, 0,1,0,0, 0,0,0,-1, 1,0,0,0], dtype: dtype) 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) err = case dtype when :float32, :complex64 1e-5 when :float64, :complex128 1e-14 end expect(s).to be_within(err).of(s_true) expect(u).to be_within(err).of(u_true) expect(vt).to be_within(err).of(vt_true) end it "calculates the singular value decomposition with lapack_gesdd" do #example from Wikipedia m = 4 n = 5 mn_min = [m,n].min 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) s = NMatrix.new([mn_min], 0, dtype: a.abs_dtype) #s is always real and always returned as float/double, never as complex u = NMatrix.new([m,m], 0, dtype: dtype) vt = NMatrix.new([n,n], 0, dtype: dtype) # This is a pure LAPACK function so it expects column-major functions # So we need to transpose the input as well as the output a = a.transpose NMatrix::LAPACK.lapack_gesdd(:a, m, n, a, m, s, u, m, vt, n, 500) u = u.transpose vt = vt.transpose s_true = NMatrix.new([mn_min], [4,3,Math.sqrt(5),0], dtype: a.abs_dtype) u_true = NMatrix.new([m,m], [0,0,1,0, 0,1,0,0, 0,0,0,-1, 1,0,0,0], dtype: dtype) 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) err = case dtype when :float32, :complex64 1e-5 when :float64, :complex128 1e-14 end expect(s).to be_within(err).of(s_true) expect(u).to be_within(err).of(u_true) expect(vt).to be_within(err).of(vt_true) end it "exposes lapack_geev" do n = 3 a = NMatrix.new([n,n], [-1,0,0, 0,1,-2, 0,1,-1], dtype: dtype) w = NMatrix.new([n], dtype: dtype) if a.complex_dtype? #for real dtypes, imaginary parts of eigenvalues are stored in separate vector wi = nil else wi = NMatrix.new([n], dtype: dtype) end vl = NMatrix.new([n,n], dtype: dtype) vr = NMatrix.new([n,n], dtype: dtype) # This is a pure LAPACK routine so it expects column-major matrices, # so we need to transpose everything. a = a.transpose NMatrix::LAPACK::lapack_geev(:left, :right, n, a, n, w, wi, vl, n, vr, n, 2*n) vr = vr.transpose vl = vl.transpose if !a.complex_dtype? w = w + wi*Complex(0,1) end w_true = NMatrix.new([n], [Complex(0,1), -Complex(0,1), -1], dtype: NMatrix.upcast(dtype, :complex64)) if a.complex_dtype? #For complex types the right/left eigenvectors are stored as columns #of vr/vl. vr_true = NMatrix.new([n,n],[0,0,1, 2/Math.sqrt(6),2/Math.sqrt(6),0, Complex(1,-1)/Math.sqrt(6),Complex(1,1)/Math.sqrt(6),0], dtype: dtype) vl_true = NMatrix.new([n,n],[0,0,1, Complex(-1,1)/Math.sqrt(6),Complex(-1,-1)/Math.sqrt(6),0, 2/Math.sqrt(6),2/Math.sqrt(6),0], dtype: dtype) else #For real types, the real part of the first and second eigenvectors is #stored in the first column, the imaginary part of the first (= the #negative of the imaginary part of the second) eigenvector is stored #in the second column, and the third eigenvector (purely real) is the #third column. vr_true = NMatrix.new([n,n],[0,0,1, 2/Math.sqrt(6),0,0, 1/Math.sqrt(6),-1/Math.sqrt(6),0], dtype: dtype) vl_true = NMatrix.new([n,n],[0,0,1, -1/Math.sqrt(6),1/Math.sqrt(6),0, 2/Math.sqrt(6),0,0], dtype: dtype) end err = case dtype when :float32, :complex64 1e-6 when :float64, :complex128 1e-15 end expect(w).to be_within(err).of(w_true) expect(vr).to be_within(err).of(vr_true) expect(vl).to be_within(err).of(vl_true) end end end end