Skip to content

Commit beb266e

Browse files
pgtgrlytranslunar
authored andcommitted
added function svd_rank() to calculate rank via svd. Added tests for the rank in math_spec.rb
1 parent 293e2fb commit beb266e

File tree

4 files changed

+123
-0
lines changed

4 files changed

+123
-0
lines changed

ext/nmatrix/nmatrix.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@
3232
*/
3333

3434
#include <ruby.h>
35+
#include <cfloat>
3536
#include <algorithm> // std::min
3637
#include <fstream>
3738

ext/nmatrix/ruby_nmatrix.c

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -364,6 +364,12 @@ void Init_nmatrix() {
364364
rb_define_alias(cNMatrix, "effective_dim", "effective_dimensions");
365365
rb_define_alias(cNMatrix, "equal?", "eql?");
366366

367+
////////////
368+
//Epsilons//
369+
////////////
370+
rb_define_const(cNMatrix, "FLOAT64_EPSILON", rb_const_get(rb_cFloat, rb_intern("EPSILON")));
371+
rb_define_const(cNMatrix, "FLOAT32_EPSILON", DBL2NUM(FLT_EPSILON));
372+
367373
///////////////////////
368374
// Symbol Generation //
369375
///////////////////////

lib/nmatrix/math.rb

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -698,6 +698,38 @@ def positive_definite?
698698
true
699699
end
700700

701+
#
702+
# call-seq:
703+
# svd_rank() -> int
704+
# svd_rank(tolerence) ->int
705+
# Gives rank of the matrix based on the singular value decomposition.
706+
# The rank of a matrix is computed as the number of diagonal elements in Sigma that are larger than a tolerance
707+
#
708+
#* *Returns* :
709+
# - An integer equal to the rank of the matrix
710+
#* *Raises* :
711+
# - +ShapeError+ -> Is only computable on 2-D matrices
712+
#
713+
def svd_rank(tolerence="default")
714+
raise(ShapeError, "rank calculated only for 2-D matrices") unless
715+
self.dim == 2
716+
717+
sigmas = self.gesvd[1].to_a.flatten
718+
eps = NMatrix::FLOAT64_EPSILON
719+
720+
# epsilon depends on the width of the number
721+
if (self.dtype == :float32 || self.dtype == :complex64)
722+
eps = NMatrix::FLOAT32_EPSILON
723+
end
724+
case tolerence
725+
when "default"
726+
tolerence = self.shape.max * sigmas.max * eps # tolerence of a Matrix A is max(size(A))*eps(norm(A)). norm(A) is nearly equal to max(sigma of A)
727+
end
728+
return sigmas.map { |x| x > tolerence ? 1 : 0 }.reduce(:+)
729+
end
730+
731+
732+
701733
protected
702734
# Define the element-wise operations for lists. Note that the __list_map_merged_stored__ iterator returns a Ruby Object
703735
# matrix, which we then cast back to the appropriate type. If you don't want that, you can redefine these functions in

spec/math_spec.rb

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1276,4 +1276,88 @@
12761276
expect(n.positive_definite?).to be_truthy
12771277
end
12781278
end
1279+
1280+
context "#svd_rank" do
1281+
FLOAT_DTYPES.each do |dtype|
1282+
context dtype do
1283+
#examples from https://www.cliffsnotes.com/study-guides/algebra/linear-algebra/real-euclidean-vector-spaces/the-rank-of-a-matrix
1284+
it "calculates the rank of matrix using singular value decomposition with NMatrix on rectangular matrix without tolerence" do
1285+
pending("not yet implemented for NMatrix-JRuby") if jruby?
1286+
a = NMatrix.new([4,3],[2,-1,3, 1,0,1, 0,2,-1, 1,1,4], dtype: dtype)
1287+
1288+
begin
1289+
rank = a.svd_rank()
1290+
1291+
rank_true = 3
1292+
expect(rank).to eq (rank_true)
1293+
1294+
rescue NotImplementedError
1295+
pending "Suppressing a NotImplementedError when the lapacke plugin is not available"
1296+
end
1297+
end
1298+
1299+
it "calculates the rank of matrix using singular value decomposition with NMatrix on rectangular matrix with tolerence" do
1300+
1301+
a = NMatrix.new([4,3],[2,-1,3, 1,0,1, 0,2,-1, 1,1,4], dtype: dtype)
1302+
pending("not yet implemented for NMatrix-JRuby") if jruby?
1303+
begin
1304+
rank = a.svd_rank(4)
1305+
1306+
rank_true = 1
1307+
expect(rank).to eq (rank_true)
1308+
1309+
rescue NotImplementedError
1310+
pending "Suppressing a NotImplementedError when the lapacke plugin is not available"
1311+
end
1312+
end
1313+
1314+
it "calculates the rank of matrix using singular value decomposition with NMatrix on square matrix without tolerence" do
1315+
1316+
a = NMatrix.new([4,4],[1,-1,1,-1, -1,1,-1,1, 1,-1,1,-1, -1,1,-1,1], dtype: dtype)
1317+
pending("not yet implemented for NMatrix-JRuby") if jruby?
1318+
begin
1319+
rank = a.svd_rank()
1320+
1321+
rank_true = 1
1322+
expect(rank).to eq (rank_true)
1323+
1324+
rescue NotImplementedError
1325+
pending "Suppressing a NotImplementedError when the lapacke plugin is not available"
1326+
end
1327+
end
1328+
1329+
it "calculates the rank of matrix using singular value decomposition with NMatrix on square matrix with very small tolerence(for float32)" do
1330+
pending("not yet implemented for NMatrix-JRuby") if jruby?
1331+
a = NMatrix.new([4,4],[1,-1,1,-1, -1,1,-1,1, 1,-1,1,-1, -1,1,-1,1], dtype: :float32)
1332+
1333+
begin
1334+
rank = a.svd_rank(1.7881389169360773e-08)
1335+
1336+
rank_true = 2
1337+
expect(rank).to eq (rank_true)
1338+
1339+
rescue NotImplementedError
1340+
pending "Suppressing a NotImplementedError when the lapacke plugin is not available"
1341+
end
1342+
end
1343+
1344+
it "calculates the rank of matrix using singular value decomposition with NMatrix on square matrix with very small tolerence(for float64)" do
1345+
pending("not yet implemented for NMatrix-JRuby") if jruby?
1346+
a = NMatrix.new([4,4],[1,-1,1,-1, -1,1,-1,1, 1,-1,1,-1, -1,1,-1,1], dtype: :float64)
1347+
1348+
begin
1349+
rank = a.svd_rank(1.7881389169360773e-08)
1350+
1351+
rank_true = 1
1352+
expect(rank).to eq (rank_true)
1353+
1354+
rescue NotImplementedError
1355+
pending "Suppressing a NotImplementedError when the lapacke plugin is not available"
1356+
end
1357+
end
1358+
1359+
end
1360+
end
1361+
end
1362+
12791363
end

0 commit comments

Comments
 (0)