@@ -803,6 +803,48 @@ def laswp(ary, opts={})
803803 self . clone . laswp! ( ary , opts )
804804 end
805805
806+ #
807+ # call-seq:
808+ # matrix_norm -> Numeric
809+ #
810+ # Calculates the selected norm (defaults to 2-norm) of a 2D matrix.
811+ #
812+ # This should be used for small or medium sized matrices.
813+ # For greater matrices, there should be a separate implementation where
814+ # the norm is estimated rather than computed, for the sake of computation speed.
815+ #
816+ # Currently implemented norms are 1-norm, 2-norm, Frobenius, Infinity.
817+ # A minus on the 1, 2 and inf norms returns the minimum instead of the maximum value.
818+ #
819+ # Tested mainly with dense matrices. Further checks and modifications might
820+ # be necessary for sparse matrices.
821+ #
822+ # * *Returns* :
823+ # - The selected norm of the matrix.
824+ # * *Raises* :
825+ # - +NotImplementedError+ -> norm can be calculated only for 2D matrices
826+ # - +ArgumentError+ -> unrecognized norm
827+ #
828+ def matrix_norm type = 2
829+ raise ( NotImplementedError , "norm can be calculated only for 2D matrices" ) unless self . dim == 2
830+ raise ( NotImplementedError , "norm only implemented for dense storage" ) unless self . stype == :dense
831+ raise ( ArgumentError , "norm not defined for byte dtype" ) if self . dtype == :byte
832+ case type
833+ when nil , 2 , -2
834+ return self . two_matrix_norm ( type == -2 )
835+ when 1 , -1
836+ return self . one_matrix_norm ( type == -1 )
837+ when :frobenius , :fro
838+ return self . fro_matrix_norm
839+ when :infinity , :inf , :'-inf' , :'-infinity'
840+ return self . inf_matrix_norm ( type == :'-inf' || type == :'-infinity' )
841+ else
842+ raise ArgumentError . new ( "argument must be a valid integer or symbol" )
843+ end
844+ end
845+
846+
847+
806848 #
807849 # call-seq:
808850 # det -> determinant
@@ -1205,6 +1247,55 @@ def nrm2 incx=1, n=nil
12051247 end
12061248 alias :norm2 :nrm2
12071249
1250+ # Norm calculation methods
1251+ # Frobenius norm: the Euclidean norm of the matrix, treated as if it were a vector
1252+ def fro_matrix_norm
1253+ #float64 has to be used in any case, since nrm2 will not yield correct result for float32
1254+ self_cast = self . cast ( :dtype => :float64 )
1255+
1256+ column_vector = self_cast . reshape ( [ self . size , 1 ] )
1257+
1258+ return column_vector . nrm2
1259+ end
1260+
1261+ # 2-norm: the largest/smallest singular value of the matrix
1262+ def two_matrix_norm minus = false
1263+
1264+ self_cast = self . cast ( :dtype => :float64 )
1265+
1266+ #TODO: confirm if this is the desired svd calculation
1267+ svd = self_cast . gesvd
1268+ return svd [ 1 ] [ 0 , 0 ] unless minus
1269+ return svd [ 1 ] [ svd [ 1 ] . rows -1 , svd [ 1 ] . cols -1 ]
1270+ end
1271+
1272+ # 1-norm: the maximum/minimum absolute column sum of the matrix
1273+ def one_matrix_norm minus = false
1274+ #TODO: change traversing method for sparse matrices
1275+ number_of_columns = self . cols
1276+ col_sums = [ ]
1277+
1278+ number_of_columns . times do |i |
1279+ col_sums << self . col ( i ) . inject ( 0 ) { |sum , number | sum += number . abs }
1280+ end
1281+
1282+ return col_sums . max unless minus
1283+ return col_sums . min
1284+ end
1285+
1286+ # Infinity norm: the maximum/minimum absolute row sum of the matrix
1287+ def inf_matrix_norm minus = false
1288+ number_of_rows = self . rows
1289+ row_sums = [ ]
1290+
1291+ number_of_rows . times do |i |
1292+ row_sums << self . row ( i ) . inject ( 0 ) { |sum , number | sum += number . abs }
1293+ end
1294+
1295+ return row_sums . max unless minus
1296+ return row_sums . min
1297+ end
1298+
12081299 #
12091300 # call-seq:
12101301 # scale! -> NMatrix
@@ -1458,4 +1549,4 @@ def dtype_for_floor_or_ceil
14581549 self . __dense_map__ { |l | l . send ( op , rhs ) }
14591550 end
14601551 end
1461- end
1552+ end
0 commit comments