nmatrix 0.2.3 → 0.2.4

Sign up to get free protection for your applications and to get access to all the features.
@@ -102,8 +102,14 @@ extern "C" {
102
102
  void nm_math_solve(VALUE lu, VALUE b, VALUE x, VALUE ipiv);
103
103
  void nm_math_inverse(const int M, void* A_elements, nm::dtype_t dtype);
104
104
  void nm_math_hessenberg(VALUE a);
105
- void nm_math_det_exact(const int M, const void* elements, const int lda, nm::dtype_t dtype, void* result);
106
- void nm_math_inverse_exact(const int M, const void* A_elements, const int lda, void* B_elements, const int ldb, nm::dtype_t dtype);
105
+ void nm_math_det_exact_from_dense(const int M, const void* elements,
106
+ const int lda, nm::dtype_t dtype, void* result);
107
+ void nm_math_det_exact_from_yale(const int M, const YALE_STORAGE* storage,
108
+ const int lda, nm::dtype_t dtype, void* result);
109
+ void nm_math_inverse_exact_from_dense(const int M, const void* A_elements,
110
+ const int lda, void* B_elements, const int ldb, nm::dtype_t dtype);
111
+ void nm_math_inverse_exact_from_yale(const int M, const YALE_STORAGE* storage,
112
+ const int lda, YALE_STORAGE* inverse, const int ldb, nm::dtype_t dtype);
107
113
  }
108
114
 
109
115
 
@@ -53,7 +53,7 @@ static VALUE nm_each_stored_with_indices(VALUE nmatrix);
53
53
  static VALUE nm_each_ordered_stored_with_indices(VALUE nmatrix);
54
54
  static VALUE nm_map_stored(VALUE nmatrix);
55
55
 
56
- static SLICE* get_slice(size_t dim, int argc, VALUE* arg, size_t* shape);
56
+ static void init_slice_no_alloc(SLICE* slice, size_t dim, int argc, VALUE* arg, size_t* shape);
57
57
  static VALUE nm_xslice(int argc, VALUE* argv, void* (*slice_func)(const STORAGE*, SLICE*), void (*delete_func)(NMATRIX*), VALUE self);
58
58
  static VALUE nm_mset(int argc, VALUE* argv, VALUE self);
59
59
  static VALUE nm_mget(int argc, VALUE* argv, VALUE self);
@@ -398,28 +398,6 @@ void Init_nmatrix() {
398
398
  // Ruby Methods //
399
399
  //////////////////
400
400
 
401
-
402
- /*
403
- * Slice constructor.
404
- */
405
- static SLICE* alloc_slice(size_t dim) {
406
- SLICE* slice = NM_ALLOC(SLICE);
407
- slice->coords = NM_ALLOC_N(size_t, dim);
408
- slice->lengths = NM_ALLOC_N(size_t, dim);
409
- return slice;
410
- }
411
-
412
-
413
- /*
414
- * Slice destructor.
415
- */
416
- static void free_slice(SLICE* slice) {
417
- NM_FREE(slice->coords);
418
- NM_FREE(slice->lengths);
419
- NM_FREE(slice);
420
- }
421
-
422
-
423
401
  /*
424
402
  * Allocator.
425
403
  */
@@ -1272,7 +1250,12 @@ static VALUE nm_init_new_version(int argc, VALUE* argv, VALUE self) {
1272
1250
  tmp_shape[m] = shape[m];
1273
1251
  }
1274
1252
 
1275
- SLICE* slice = get_slice(dim, dim, slice_argv, shape);
1253
+ SLICE slice_s;
1254
+ SLICE* slice = &slice_s;
1255
+ slice->coords = NM_ALLOCA_N(size_t, dim);
1256
+ slice->lengths = NM_ALLOCA_N(size_t, dim);
1257
+ init_slice_no_alloc(slice, dim, dim, slice_argv, shape);
1258
+
1276
1259
  // Create a temporary dense matrix and use it to do a slice assignment on self.
1277
1260
  NMATRIX* tmp = nm_create(nm::DENSE_STORE, (STORAGE*)nm_dense_storage_create(dtype, tmp_shape, dim, v, v_size));
1278
1261
  nm_register_nmatrix(tmp);
@@ -1282,8 +1265,6 @@ static VALUE nm_init_new_version(int argc, VALUE* argv, VALUE self) {
1282
1265
  if (stype == nm::YALE_STORE) nm_yale_storage_set(self, slice, rb_tmp);
1283
1266
  else nm_list_storage_set(self, slice, rb_tmp);
1284
1267
 
1285
- free_slice(slice);
1286
-
1287
1268
  // We need to free v if it's not the same size as tmp -- because tmp will have made a copy instead.
1288
1269
  //if (nm_storage_count_max_elements(tmp->storage) != v_size)
1289
1270
  // NM_FREE(v);
@@ -1475,7 +1456,8 @@ static VALUE nm_init(int argc, VALUE* argv, VALUE nm) {
1475
1456
 
1476
1457
 
1477
1458
  /*
1478
- * Helper for nm_cast which uses the C types instead of the Ruby objects. Called by nm_cast.
1459
+ * Helper for nm_cast_with_types which uses the C types instead of the Ruby objects.
1460
+ * Called by nm_cast_with_types.
1479
1461
  */
1480
1462
  NMATRIX* nm_cast_with_ctype_args(NMATRIX* self, nm::stype_t new_stype, nm::dtype_t new_dtype, void* init_ptr) {
1481
1463
 
@@ -1493,6 +1475,23 @@ NMATRIX* nm_cast_with_ctype_args(NMATRIX* self, nm::stype_t new_stype, nm::dtype
1493
1475
  return lhs;
1494
1476
  }
1495
1477
 
1478
+ /*
1479
+ * Cast NMatrix with given new_stype and new_dtype. Called by nm_cast.
1480
+ */
1481
+ VALUE nm_cast_with_types(VALUE self, nm::stype_t new_stype, nm::dtype_t new_dtype,
1482
+ void* init_ptr) {
1483
+ NMATRIX *rhs;
1484
+
1485
+ UnwrapNMatrix( self, rhs );
1486
+
1487
+ NMATRIX* m = nm_cast_with_ctype_args(rhs, new_stype, new_dtype, init_ptr);
1488
+ nm_register_nmatrix(m);
1489
+
1490
+ VALUE to_return = Data_Wrap_Struct(CLASS_OF(self), nm_mark, nm_delete, m);
1491
+
1492
+ nm_unregister_nmatrix(m);
1493
+ return to_return;
1494
+ }
1496
1495
 
1497
1496
  /*
1498
1497
  * call-seq:
@@ -1509,19 +1508,11 @@ VALUE nm_cast(VALUE self, VALUE new_stype_symbol, VALUE new_dtype_symbol, VALUE
1509
1508
  nm::stype_t new_stype = nm_stype_from_rbsymbol(new_stype_symbol);
1510
1509
 
1511
1510
  CheckNMatrixType(self);
1512
- NMATRIX *rhs;
1513
-
1514
- UnwrapNMatrix( self, rhs );
1515
-
1516
1511
  void* init_ptr = NM_ALLOCA_N(char, DTYPE_SIZES[new_dtype]);
1517
1512
  rubyval_to_cval(init, new_dtype, init_ptr);
1518
1513
 
1519
- NMATRIX* m = nm_cast_with_ctype_args(rhs, new_stype, new_dtype, init_ptr);
1520
- nm_register_nmatrix(m);
1514
+ VALUE to_return = nm_cast_with_types(self, new_stype, new_dtype, init_ptr);
1521
1515
 
1522
- VALUE to_return = Data_Wrap_Struct(CLASS_OF(self), nm_mark, nm_delete, m);
1523
-
1524
- nm_unregister_nmatrix(m);
1525
1516
  NM_CONSERVATIVE(nm_unregister_value(&self));
1526
1517
  NM_CONSERVATIVE(nm_unregister_value(&init));
1527
1518
  return to_return;
@@ -2018,7 +2009,11 @@ static VALUE nm_mset(int argc, VALUE* argv, VALUE self) {
2018
2009
  NM_CONSERVATIVE(nm_register_value(&self));
2019
2010
  NM_CONSERVATIVE(nm_register_values(argv, argc));
2020
2011
 
2021
- SLICE* slice = get_slice(dim, argc-1, argv, NM_STORAGE(self)->shape);
2012
+ SLICE slice_s;
2013
+ SLICE* slice = &slice_s;
2014
+ slice->coords = NM_ALLOCA_N(size_t, dim);
2015
+ slice->lengths = NM_ALLOCA_N(size_t, dim);
2016
+ init_slice_no_alloc(slice, dim, argc-1, argv, NM_STORAGE(self)->shape);
2022
2017
 
2023
2018
  static void (*ttable[nm::NUM_STYPES])(VALUE, SLICE*, VALUE) = {
2024
2019
  nm_dense_storage_set,
@@ -2028,8 +2023,6 @@ static VALUE nm_mset(int argc, VALUE* argv, VALUE self) {
2028
2023
 
2029
2024
  ttable[NM_STYPE(self)](self, slice, argv[argc-1]);
2030
2025
 
2031
- free_slice(slice);
2032
-
2033
2026
  to_return = argv[argc-1];
2034
2027
 
2035
2028
  NM_CONSERVATIVE(nm_unregister_value(&self));
@@ -2259,7 +2252,12 @@ static VALUE nm_xslice(int argc, VALUE* argv, void* (*slice_func)(const STORAGE*
2259
2252
 
2260
2253
  nm_register_value(&result);
2261
2254
 
2262
- SLICE* slice = get_slice(NM_DIM(self), argc, argv, s->shape);
2255
+ SLICE slice_s;
2256
+ SLICE* slice = &slice_s;
2257
+ size_t dim = NM_DIM(self);
2258
+ slice->coords = NM_ALLOCA_N(size_t, dim);
2259
+ slice->lengths = NM_ALLOCA_N(size_t, dim);
2260
+ init_slice_no_alloc(slice, dim, argc, argv, s->shape);
2263
2261
 
2264
2262
  if (slice->single) {
2265
2263
  static void* (*ttable[nm::NUM_STYPES])(const STORAGE*, SLICE*) = {
@@ -2280,8 +2278,6 @@ static VALUE nm_xslice(int argc, VALUE* argv, void* (*slice_func)(const STORAGE*
2280
2278
  result = Data_Wrap_Struct(CLASS_OF(self), nm_mark, delete_func, mat);
2281
2279
  nm_unregister_nmatrix(mat);
2282
2280
  }
2283
-
2284
- free_slice(slice);
2285
2281
  }
2286
2282
 
2287
2283
  nm_unregister_value(&result);
@@ -2623,19 +2619,17 @@ nm::dtype_t nm_dtype_guess(VALUE v) {
2623
2619
  }
2624
2620
  }
2625
2621
 
2626
-
2627
-
2628
2622
  /*
2629
- * Allocate and return a SLICE object, which will contain the appropriate coordinate and length information for
2630
- * accessing some part of a matrix.
2623
+ * Modify an existing SLICE object (with properly allocated memory),
2624
+ * so that it will contain the appropriate coordinate and length information
2625
+ * for accessing some part of a matrix.
2631
2626
  */
2632
- static SLICE* get_slice(size_t dim, int argc, VALUE* arg, size_t* shape) {
2627
+ static void init_slice_no_alloc(SLICE* slice, size_t dim, int argc, VALUE* arg, size_t* shape) {
2633
2628
  NM_CONSERVATIVE(nm_register_values(arg, argc));
2634
2629
 
2635
2630
  VALUE beg, end;
2636
2631
  int excl;
2637
2632
 
2638
- SLICE* slice = alloc_slice(dim);
2639
2633
  slice->single = true;
2640
2634
 
2641
2635
  // r is the shape position; t is the slice position. They may differ when we're dealing with a
@@ -2693,7 +2687,6 @@ static SLICE* get_slice(size_t dim, int argc, VALUE* arg, size_t* shape) {
2693
2687
  }
2694
2688
 
2695
2689
  NM_CONSERVATIVE(nm_unregister_values(arg, argc));
2696
- return slice;
2697
2690
  }
2698
2691
 
2699
2692
  #ifdef BENCHMARK
@@ -2963,21 +2956,38 @@ static VALUE nm_inverse(VALUE self, VALUE inverse, VALUE bang) {
2963
2956
  * Does not test for invertibility!
2964
2957
  */
2965
2958
  static VALUE nm_inverse_exact(VALUE self, VALUE inverse, VALUE lda, VALUE ldb) {
2966
-
2967
- if (NM_STYPE(self) != nm::DENSE_STORE) {
2968
- rb_raise(rb_eNotImpError, "needs exact determinant implementation for this matrix stype");
2969
- return Qnil;
2970
- }
2971
-
2972
2959
  if (NM_DIM(self) != 2 || NM_SHAPE0(self) != NM_SHAPE1(self)) {
2973
2960
  rb_raise(nm_eShapeError, "matrices must be square to have an inverse defined");
2974
2961
  return Qnil;
2975
2962
  }
2976
2963
 
2977
- nm_math_inverse_exact(NM_SHAPE0(self),
2978
- NM_STORAGE_DENSE(self)->elements, FIX2INT(lda),
2979
- NM_STORAGE_DENSE(inverse)->elements, FIX2INT(ldb), NM_DTYPE(self));
2964
+ nm::dtype_t dtype = NM_DTYPE(self);
2965
+ void* result = NM_ALLOCA_N(char, DTYPE_SIZES[dtype]);
2966
+ if (dtype == nm::RUBYOBJ) {
2967
+ nm_register_values(reinterpret_cast<VALUE*>(result), 1);
2968
+ }
2969
+ nm::stype_t old_stype = NM_STYPE(self);
2970
+ if (old_stype == nm::LIST_STORE) {
2971
+ self = nm_cast_with_types(self, nm::YALE_STORE, dtype, result);
2972
+ inverse = nm_cast_with_types(inverse, nm::YALE_STORE, dtype, result);
2973
+ }
2974
+
2975
+ if (NM_STYPE(self) == nm::DENSE_STORE) {
2976
+ nm_math_inverse_exact_from_dense(NM_SHAPE0(self),
2977
+ NM_STORAGE_DENSE(self)->elements, FIX2INT(lda),
2978
+ NM_STORAGE_DENSE(inverse)->elements, FIX2INT(ldb), dtype);
2979
+ } else {
2980
+ nm_math_inverse_exact_from_yale(NM_SHAPE0(self),
2981
+ NM_STORAGE_YALE(self), FIX2INT(lda),
2982
+ NM_STORAGE_YALE(inverse), FIX2INT(ldb), dtype);
2983
+ }
2980
2984
 
2985
+ if (old_stype == nm::LIST_STORE) {
2986
+ inverse = nm_cast_with_types(inverse, nm::LIST_STORE, dtype, result);
2987
+ }
2988
+ if (dtype == nm::RUBYOBJ) {
2989
+ nm_unregister_values(reinterpret_cast<VALUE*>(result), 1);
2990
+ }
2981
2991
  return inverse;
2982
2992
  }
2983
2993
 
@@ -2990,21 +3000,27 @@ static VALUE nm_inverse_exact(VALUE self, VALUE inverse, VALUE lda, VALUE ldb) {
2990
3000
  */
2991
3001
  static VALUE nm_det_exact(VALUE self) {
2992
3002
 
2993
- if (NM_STYPE(self) != nm::DENSE_STORE) {
2994
- rb_raise(rb_eNotImpError, "can only calculate exact determinant for dense matrices");
2995
- return Qnil;
2996
- }
2997
3003
  if (NM_DIM(self) != 2 || NM_SHAPE0(self) != NM_SHAPE1(self)) {
2998
3004
  rb_raise(nm_eShapeError, "matrices must be square to have a determinant defined");
2999
3005
  return Qnil;
3000
3006
  }
3001
3007
 
3008
+ nm::dtype_t dtype = NM_DTYPE(self);
3009
+ void* result = NM_ALLOCA_N(char, DTYPE_SIZES[dtype]);
3010
+ if (NM_STYPE(self) == nm::LIST_STORE) {
3011
+ self = nm_cast_with_types(self, nm::YALE_STORE, dtype, result);
3012
+ }
3013
+
3002
3014
  NM_CONSERVATIVE(nm_register_value(&self));
3003
3015
 
3004
3016
  // Calculate the determinant and then assign it to the return value
3005
- void* result = NM_ALLOCA_N(char, DTYPE_SIZES[NM_DTYPE(self)]);
3006
- nm::dtype_t dtype = NM_DTYPE(self);
3007
- nm_math_det_exact(NM_SHAPE0(self), NM_STORAGE_DENSE(self)->elements, NM_SHAPE0(self), NM_DTYPE(self), result);
3017
+ if (NM_STYPE(self) == nm::DENSE_STORE) {
3018
+ nm_math_det_exact_from_dense(NM_SHAPE0(self), NM_STORAGE_DENSE(self)->elements,
3019
+ NM_SHAPE0(self), NM_DTYPE(self), result);
3020
+ } else {
3021
+ nm_math_det_exact_from_yale(NM_SHAPE0(self), NM_STORAGE_YALE(self),
3022
+ NM_SHAPE0(self), NM_DTYPE(self), result);
3023
+ }
3008
3024
 
3009
3025
  VALUE to_return;
3010
3026
  if (dtype == nm::RUBYOBJ) {
@@ -31,8 +31,12 @@ module NMatrix::BLAS
31
31
 
32
32
  #Add functions from C extension to main BLAS module
33
33
  class << self
34
- NMatrix::Internal::BLAS.singleton_methods.each do |m|
35
- define_method m, NMatrix::Internal::BLAS.method(m).to_proc
34
+ if jruby?
35
+ # BLAS functionalities for JRuby need to be implemented
36
+ else
37
+ NMatrix::Internal::BLAS.singleton_methods.each do |m|
38
+ define_method m, NMatrix::Internal::BLAS.method(m).to_proc
39
+ end
36
40
  end
37
41
  end
38
42
 
@@ -0,0 +1,744 @@
1
+ #--
2
+ # = NMatrix
3
+ #
4
+ # A linear algebra library for scientific computation in Ruby.
5
+ # NMatrix is part of SciRuby.
6
+ #
7
+ # NMatrix was originally inspired by and derived from NArray, by
8
+ # Masahiro Tanaka: http://narray.rubyforge.org
9
+ #
10
+ # == Copyright Information
11
+ #
12
+ # SciRuby is Copyright (c) 2010 - 2014, Ruby Science Foundation
13
+ # NMatrix is Copyright (c) 2012 - 2014, John Woods and the Ruby Science Foundation
14
+ #
15
+ # Please see LICENSE.txt for additional copyright notices.
16
+ #
17
+ # == Contributing
18
+ #
19
+ # By contributing source code to SciRuby, you agree to be bound by
20
+ # our Contributor Agreement:
21
+ #
22
+ # * https://github.com/SciRuby/sciruby/wiki/Contributor-Agreement
23
+ #
24
+ # == math.rb
25
+ #
26
+ # Math functionality for NMatrix, along with any NMatrix instance
27
+ # methods that correspond to ATLAS/BLAS/LAPACK functions (e.g.,
28
+ # laswp).
29
+ #++
30
+
31
+ class NMatrix
32
+
33
+ #
34
+ # call-seq:
35
+ # getrf! -> Array
36
+ #
37
+ # LU factorization of a general M-by-N matrix +A+ using partial pivoting with
38
+ # row interchanges. The LU factorization is A = PLU, where P is a row permutation
39
+ # matrix, L is a lower triangular matrix with unit diagonals, and U is an upper
40
+ # triangular matrix (note that this convention is different from the
41
+ # clapack_getrf behavior, but matches the standard LAPACK getrf).
42
+ # +A+ is overwritten with the elements of L and U (the unit
43
+ # diagonal elements of L are not saved). P is not returned directly and must be
44
+ # constructed from the pivot array ipiv. The row indices in ipiv are indexed
45
+ # starting from 1.
46
+ # Only works for dense matrices.
47
+ #
48
+ # * *Returns* :
49
+ # - The IPIV vector. The L and U matrices are stored in A.
50
+ # * *Raises* :
51
+ # - +StorageTypeError+ -> ATLAS functions only work on dense matrices.
52
+ #
53
+ def getrf!
54
+ raise(StorageTypeError, "ATLAS functions only work on dense matrices") unless self.dense?
55
+
56
+ #For row-major matrices, clapack_getrf uses a different convention than
57
+ #described above (U has unit diagonal elements instead of L and columns
58
+ #are interchanged rather than rows). For column-major matrices, clapack
59
+ #uses the stanard conventions. So we just transpose the matrix before
60
+ #and after calling clapack_getrf.
61
+ #Unfortunately, this is not a very good way, uses a lot of memory.
62
+ temp = self.transpose
63
+ ipiv = NMatrix::LAPACK::clapack_getrf(:col, self.shape[0], self.shape[1], temp, self.shape[0])
64
+ temp = temp.transpose
65
+ self[0...self.shape[0], 0...self.shape[1]] = temp
66
+
67
+ #for some reason, in clapack_getrf, the indices in ipiv start from 0
68
+ #instead of 1 as in LAPACK.
69
+ ipiv.each_index { |i| ipiv[i]+=1 }
70
+
71
+ return ipiv
72
+ end
73
+
74
+ #
75
+ # call-seq:
76
+ # geqrf! -> shape.min x 1 NMatrix
77
+ #
78
+ # QR factorization of a general M-by-N matrix +A+.
79
+ #
80
+ # The QR factorization is A = QR, where Q is orthogonal and R is Upper Triangular
81
+ # +A+ is overwritten with the elements of R and Q with Q being represented by the
82
+ # elements below A's diagonal and an array of scalar factors in the output NMatrix.
83
+ #
84
+ # The matrix Q is represented as a product of elementary reflectors
85
+ # Q = H(1) H(2) . . . H(k), where k = min(m,n).
86
+ #
87
+ # Each H(i) has the form
88
+ #
89
+ # H(i) = I - tau * v * v'
90
+ #
91
+ # http://www.netlib.org/lapack/explore-html/d3/d69/dgeqrf_8f.html
92
+ #
93
+ # Only works for dense matrices.
94
+ #
95
+ # * *Returns* :
96
+ # - Vector TAU. Q and R are stored in A. Q is represented by TAU and A
97
+ # * *Raises* :
98
+ # - +StorageTypeError+ -> LAPACK functions only work on dense matrices.
99
+ #
100
+ def geqrf!
101
+ # The real implementation is in lib/nmatrix/lapacke.rb
102
+ raise(NotImplementedError, "geqrf! requires the nmatrix-lapacke gem")
103
+ end
104
+
105
+ #
106
+ # call-seq:
107
+ # ormqr(tau) -> NMatrix
108
+ # ormqr(tau, side, transpose, c) -> NMatrix
109
+ #
110
+ # Returns the product Q * c or c * Q after a call to geqrf! used in QR factorization.
111
+ # +c+ is overwritten with the elements of the result NMatrix if supplied. Q is the orthogonal matrix
112
+ # represented by tau and the calling NMatrix
113
+ #
114
+ # Only works on float types, use unmqr for complex types.
115
+ #
116
+ # == Arguments
117
+ #
118
+ # * +tau+ - vector containing scalar factors of elementary reflectors
119
+ # * +side+ - direction of multiplication [:left, :right]
120
+ # * +transpose+ - apply Q with or without transpose [false, :transpose]
121
+ # * +c+ - NMatrix multplication argument that is overwritten, no argument assumes c = identity
122
+ #
123
+ # * *Returns* :
124
+ #
125
+ # - Q * c or c * Q Where Q may be transposed before multiplication.
126
+ #
127
+ #
128
+ # * *Raises* :
129
+ # - +StorageTypeError+ -> LAPACK functions only work on dense matrices.
130
+ # - +TypeError+ -> Works only on floating point matrices, use unmqr for complex types
131
+ # - +TypeError+ -> c must have the same dtype as the calling NMatrix
132
+ #
133
+ def ormqr(tau, side=:left, transpose=false, c=nil)
134
+ # The real implementation is in lib/nmatrix/lapacke.rb
135
+ raise(NotImplementedError, "ormqr requires the nmatrix-lapacke gem")
136
+
137
+ end
138
+
139
+ #
140
+ # call-seq:
141
+ # unmqr(tau) -> NMatrix
142
+ # unmqr(tau, side, transpose, c) -> NMatrix
143
+ #
144
+ # Returns the product Q * c or c * Q after a call to geqrf! used in QR factorization.
145
+ # +c+ is overwritten with the elements of the result NMatrix if it is supplied. Q is the orthogonal matrix
146
+ # represented by tau and the calling NMatrix
147
+ #
148
+ # Only works on complex types, use ormqr for float types.
149
+ #
150
+ # == Arguments
151
+ #
152
+ # * +tau+ - vector containing scalar factors of elementary reflectors
153
+ # * +side+ - direction of multiplication [:left, :right]
154
+ # * +transpose+ - apply Q as Q or its complex conjugate [false, :complex_conjugate]
155
+ # * +c+ - NMatrix multplication argument that is overwritten, no argument assumes c = identity
156
+ #
157
+ # * *Returns* :
158
+ #
159
+ # - Q * c or c * Q Where Q may be transformed to its complex conjugate before multiplication.
160
+ #
161
+ #
162
+ # * *Raises* :
163
+ # - +StorageTypeError+ -> LAPACK functions only work on dense matrices.
164
+ # - +TypeError+ -> Works only on floating point matrices, use unmqr for complex types
165
+ # - +TypeError+ -> c must have the same dtype as the calling NMatrix
166
+ #
167
+ def unmqr(tau, side=:left, transpose=false, c=nil)
168
+ # The real implementation is in lib/nmatrix/lapacke.rb
169
+ raise(NotImplementedError, "unmqr requires the nmatrix-lapacke gem")
170
+ end
171
+
172
+ #
173
+ # call-seq:
174
+ # potrf!(upper_or_lower) -> NMatrix
175
+ #
176
+ # Cholesky factorization of a symmetric positive-definite matrix -- or, if complex,
177
+ # a Hermitian positive-definite matrix +A+.
178
+ # The result will be written in either the upper or lower triangular portion of the
179
+ # matrix, depending on whether the argument is +:upper+ or +:lower+.
180
+ # Also the function only reads in the upper or lower part of the matrix,
181
+ # so it doesn't actually have to be symmetric/Hermitian.
182
+ # However, if the matrix (i.e. the symmetric matrix implied by the lower/upper
183
+ # half) is not positive-definite, the function will return nonsense.
184
+ #
185
+ # This functions requires either the nmatrix-atlas or nmatrix-lapacke gem
186
+ # installed.
187
+ #
188
+ # * *Returns* :
189
+ # the triangular portion specified by the parameter
190
+ # * *Raises* :
191
+ # - +StorageTypeError+ -> ATLAS functions only work on dense matrices.
192
+ # - +ShapeError+ -> Must be square.
193
+ # - +NotImplementedError+ -> If called without nmatrix-atlas or nmatrix-lapacke gem
194
+ #
195
+ def potrf!(which)
196
+ # The real implementation is in the plugin files.
197
+ raise(NotImplementedError, "potrf! requires either the nmatrix-atlas or nmatrix-lapacke gem")
198
+ end
199
+
200
+ def potrf_upper!
201
+ potrf! :upper
202
+ end
203
+
204
+ def potrf_lower!
205
+ potrf! :lower
206
+ end
207
+
208
+
209
+ #
210
+ # call-seq:
211
+ # factorize_cholesky -> [upper NMatrix, lower NMatrix]
212
+ #
213
+ # Calculates the Cholesky factorization of a matrix and returns the
214
+ # upper and lower matrices such that A=LU and L=U*, where * is
215
+ # either the transpose or conjugate transpose.
216
+ #
217
+ # Unlike potrf!, this makes method requires that the original is matrix is
218
+ # symmetric or Hermitian. However, it is still your responsibility to make
219
+ # sure it is positive-definite.
220
+ def factorize_cholesky
221
+ raise "Matrix must be symmetric/Hermitian for Cholesky factorization" unless self.hermitian?
222
+ l = self.clone.potrf_lower!.tril!
223
+ u = l.conjugate_transpose
224
+ [u,l]
225
+ end
226
+
227
+ #
228
+ # call-seq:
229
+ # factorize_lu -> ...
230
+ #
231
+ # LU factorization of a matrix. Optionally return the permutation matrix.
232
+ # Note that computing the permutation matrix will introduce a slight memory
233
+ # and time overhead.
234
+ #
235
+ # == Arguments
236
+ #
237
+ # +with_permutation_matrix+ - If set to *true* will return the permutation
238
+ # matrix alongwith the LU factorization as a second return value.
239
+ #
240
+ def factorize_lu with_permutation_matrix=nil
241
+ raise(NotImplementedError, "only implemented for dense storage") unless self.stype == :dense
242
+ raise(NotImplementedError, "matrix is not 2-dimensional") unless self.dimensions == 2
243
+
244
+ t = self.clone
245
+ pivot = t.getrf!
246
+ return t unless with_permutation_matrix
247
+
248
+ [t, FactorizeLUMethods.permutation_matrix_from(pivot)]
249
+ end
250
+
251
+ #
252
+ # call-seq:
253
+ # factorize_qr -> [Q,R]
254
+ #
255
+ # QR factorization of a matrix without column pivoting.
256
+ # Q is orthogonal and R is upper triangular if input is square or upper trapezoidal if
257
+ # input is rectangular.
258
+ #
259
+ # Only works for dense matrices.
260
+ #
261
+ # * *Returns* :
262
+ # - Array containing Q and R matrices
263
+ #
264
+ # * *Raises* :
265
+ # - +StorageTypeError+ -> only implemented for desnse storage.
266
+ # - +ShapeError+ -> Input must be a 2-dimensional matrix to have a QR decomposition.
267
+ #
268
+ def factorize_qr
269
+ raise(NotImplementedError, "only implemented for dense storage") unless self.stype == :dense
270
+ raise(ShapeError, "Input must be a 2-dimensional matrix to have a QR decomposition") unless self.dim == 2
271
+
272
+ rows, columns = self.shape
273
+ r = self.clone
274
+ tau = r.geqrf!
275
+
276
+ #Obtain Q
277
+ q = self.complex_dtype? ? r.unmqr(tau) : r.ormqr(tau)
278
+
279
+ #Obtain R
280
+ if rows <= columns
281
+ r.upper_triangle!
282
+ #Need to account for upper trapezoidal structure if R is a tall rectangle (rows > columns)
283
+ else
284
+ r[0...columns, 0...columns].upper_triangle!
285
+ r[columns...rows, 0...columns] = 0
286
+ end
287
+
288
+ [q,r]
289
+ end
290
+
291
+ # Solve the matrix equation AX = B, where A is +self+, B is the first
292
+ # argument, and X is returned. A must be a nxn square matrix, while B must be
293
+ # nxm. Only works with dense matrices and non-integer, non-object data types.
294
+ #
295
+ # == Arguments
296
+ #
297
+ # * +b+ - the right hand side
298
+ #
299
+ # == Options
300
+ #
301
+ # * +form+ - Signifies the form of the matrix A in the linear system AX=B.
302
+ # If not set then it defaults to +:general+, which uses an LU solver.
303
+ # Other possible values are +:lower_tri+, +:upper_tri+ and +:pos_def+ (alternatively,
304
+ # non-abbreviated symbols +:lower_triangular+, +:upper_triangular+,
305
+ # and +:positive_definite+ can be used.
306
+ # If +:lower_tri+ or +:upper_tri+ is set, then a specialized linear solver for linear
307
+ # systems AX=B with a lower or upper triangular matrix A is used. If +:pos_def+ is chosen,
308
+ # then the linear system is solved via the Cholesky factorization.
309
+ # Note that when +:lower_tri+ or +:upper_tri+ is used, then the algorithm just assumes that
310
+ # all entries in the lower/upper triangle of the matrix are zeros without checking (which
311
+ # can be useful in certain applications).
312
+ #
313
+ #
314
+ # == Usage
315
+ #
316
+ # a = NMatrix.new [2,2], [3,1,1,2], dtype: dtype
317
+ # b = NMatrix.new [2,1], [9,8], dtype: dtype
318
+ # a.solve(b)
319
+ #
320
+ # # solve an upper triangular linear system more efficiently:
321
+ # require 'benchmark'
322
+ # require 'nmatrix/lapacke'
323
+ # rand_mat = NMatrix.random([10000, 10000], dtype: :float64)
324
+ # a = rand_mat.triu
325
+ # b = NMatrix.random([10000, 10], dtype: :float64)
326
+ # Benchmark.bm(10) do |bm|
327
+ # bm.report('general') { a.solve(b) }
328
+ # bm.report('upper_tri') { a.solve(b, form: :upper_tri) }
329
+ # end
330
+ # # user system total real
331
+ # # general 73.170000 0.670000 73.840000 ( 73.810086)
332
+ # # upper_tri 0.180000 0.000000 0.180000 ( 0.182491)
333
+ #
334
+ def solve(b, opts = {})
335
+ raise(ShapeError, "Must be called on square matrix") unless self.dim == 2 && self.shape[0] == self.shape[1]
336
+ raise(ShapeError, "number of rows of b must equal number of cols of self") if
337
+ self.shape[1] != b.shape[0]
338
+ raise(ArgumentError, "only works with dense matrices") if self.stype != :dense
339
+ raise(ArgumentError, "only works for non-integer, non-object dtypes") if
340
+ integer_dtype? or object_dtype? or b.integer_dtype? or b.object_dtype?
341
+
342
+ opts = { form: :general }.merge(opts)
343
+ x = b.clone
344
+ n = self.shape[0]
345
+ nrhs = b.shape[1]
346
+
347
+ case opts[:form]
348
+ when :general
349
+ clone = self.clone
350
+ ipiv = NMatrix::LAPACK.clapack_getrf(:row, n, n, clone, n)
351
+ # When we call clapack_getrs with :row, actually only the first matrix
352
+ # (i.e. clone) is interpreted as row-major, while the other matrix (x)
353
+ # is interpreted as column-major. See here: http://math-atlas.sourceforge.net/faq.html#RowSolve
354
+ # So we must transpose x before and after
355
+ # calling it.
356
+ x = x.transpose
357
+ NMatrix::LAPACK.clapack_getrs(:row, :no_transpose, n, nrhs, clone, n, ipiv, x, n)
358
+ x.transpose
359
+ when :upper_tri, :upper_triangular
360
+ raise(ArgumentError, "upper triangular solver does not work with complex dtypes") if
361
+ complex_dtype? or b.complex_dtype?
362
+ # this is the correct function call; see https://github.com/SciRuby/nmatrix/issues/374
363
+ NMatrix::BLAS::cblas_trsm(:row, :left, :upper, false, :nounit, n, nrhs, 1.0, self, n, x, nrhs)
364
+ x
365
+ when :lower_tri, :lower_triangular
366
+ raise(ArgumentError, "lower triangular solver does not work with complex dtypes") if
367
+ complex_dtype? or b.complex_dtype?
368
+ NMatrix::BLAS::cblas_trsm(:row, :left, :lower, false, :nounit, n, nrhs, 1.0, self, n, x, nrhs)
369
+ x
370
+ when :pos_def, :positive_definite
371
+ u, l = self.factorize_cholesky
372
+ z = l.solve(b, form: :lower_tri)
373
+ u.solve(z, form: :upper_tri)
374
+ else
375
+ raise(ArgumentError, "#{opts[:form]} is not a valid form option")
376
+ end
377
+ end
378
+
379
+ #
380
+ # call-seq:
381
+ # least_squares(b) -> NMatrix
382
+ # least_squares(b, tolerance: 10e-10) -> NMatrix
383
+ #
384
+ # Provides the linear least squares approximation of an under-determined system
385
+ # using QR factorization provided that the matrix is not rank-deficient.
386
+ #
387
+ # Only works for dense matrices.
388
+ #
389
+ # * *Arguments* :
390
+ # - +b+ -> The solution column vector NMatrix of A * X = b.
391
+ # - +tolerance:+ -> Absolute tolerance to check if a diagonal element in A = QR is near 0
392
+ #
393
+ # * *Returns* :
394
+ # - NMatrix that is a column vector with the LLS solution
395
+ #
396
+ # * *Raises* :
397
+ # - +ArgumentError+ -> least squares approximation only works for non-complex types
398
+ # - +ShapeError+ -> system must be under-determined ( rows > columns )
399
+ #
400
+ # Examples :-
401
+ #
402
+ # a = NMatrix.new([3,2], [2.0, 0, -1, 1, 0, 2])
403
+ #
404
+ # b = NMatrix.new([3,1], [1.0, 0, -1])
405
+ #
406
+ # a.least_squares(b)
407
+ # =>[
408
+ # [ 0.33333333333333326 ]
409
+ # [ -0.3333333333333334 ]
410
+ # ]
411
+ #
412
+ def least_squares(b, tolerance: 10e-6)
413
+ raise(ArgumentError, "least squares approximation only works for non-complex types") if
414
+ self.complex_dtype?
415
+
416
+ rows, columns = self.shape
417
+
418
+ raise(ShapeError, "system must be under-determined ( rows > columns )") unless
419
+ rows > columns
420
+
421
+ #Perform economical QR factorization
422
+ r = self.clone
423
+ tau = r.geqrf!
424
+ q_transpose_b = r.ormqr(tau, :left, :transpose, b)
425
+
426
+ #Obtain R from geqrf! intermediate
427
+ r[0...columns, 0...columns].upper_triangle!
428
+ r[columns...rows, 0...columns] = 0
429
+
430
+ diagonal = r.diagonal
431
+
432
+ raise(ArgumentError, "rank deficient matrix") if diagonal.any? { |x| x == 0 }
433
+
434
+ if diagonal.any? { |x| x.abs < tolerance }
435
+ warn "warning: A diagonal element of R in A = QR is close to zero ;" <<
436
+ " indicates a possible loss of precision"
437
+ end
438
+
439
+ # Transform the system A * X = B to R1 * X = B2 where B2 = Q1_t * B
440
+ r1 = r[0...columns, 0...columns]
441
+ b2 = q_transpose_b[0...columns]
442
+
443
+ nrhs = b2.shape[1]
444
+
445
+ #Solve the upper triangular system
446
+ NMatrix::BLAS::cblas_trsm(:row, :left, :upper, false, :nounit, r1.shape[0], nrhs, 1.0, r1, r1.shape[0], b2, nrhs)
447
+ b2
448
+ end
449
+
450
+ #
451
+ # call-seq:
452
+ # gesvd! -> [u, sigma, v_transpose]
453
+ # gesvd! -> [u, sigma, v_conjugate_transpose] # complex
454
+ #
455
+ # Compute the singular value decomposition of a matrix using LAPACK's GESVD function.
456
+ # This is destructive, modifying the source NMatrix. See also #gesdd.
457
+ #
458
+ # Optionally accepts a +workspace_size+ parameter, which will be honored only if it is larger than what LAPACK
459
+ # requires.
460
+ #
461
+ def gesvd!(workspace_size=1)
462
+ NMatrix::LAPACK::gesvd(self, workspace_size)
463
+ end
464
+
465
+ #
466
+ # call-seq:
467
+ # gesvd -> [u, sigma, v_transpose]
468
+ # gesvd -> [u, sigma, v_conjugate_transpose] # complex
469
+ #
470
+ # Compute the singular value decomposition of a matrix using LAPACK's GESVD function.
471
+ #
472
+ # Optionally accepts a +workspace_size+ parameter, which will be honored only if it is larger than what LAPACK
473
+ # requires.
474
+ #
475
+ def gesvd(workspace_size=1)
476
+ self.clone.gesvd!(workspace_size)
477
+ end
478
+
479
+
480
+
481
+ #
482
+ # call-seq:
483
+ # gesdd! -> [u, sigma, v_transpose]
484
+ # gesdd! -> [u, sigma, v_conjugate_transpose] # complex
485
+ #
486
+ # Compute the singular value decomposition of a matrix using LAPACK's GESDD function. This uses a divide-and-conquer
487
+ # strategy. This is destructive, modifying the source NMatrix. See also #gesvd.
488
+ #
489
+ # Optionally accepts a +workspace_size+ parameter, which will be honored only if it is larger than what LAPACK
490
+ # requires.
491
+ #
492
+ def gesdd!(workspace_size=nil)
493
+ NMatrix::LAPACK::gesdd(self, workspace_size)
494
+ end
495
+
496
+ #
497
+ # call-seq:
498
+ # gesdd -> [u, sigma, v_transpose]
499
+ # gesdd -> [u, sigma, v_conjugate_transpose] # complex
500
+ #
501
+ # Compute the singular value decomposition of a matrix using LAPACK's GESDD function. This uses a divide-and-conquer
502
+ # strategy. See also #gesvd.
503
+ #
504
+ # Optionally accepts a +workspace_size+ parameter, which will be honored only if it is larger than what LAPACK
505
+ # requires.
506
+ #
507
+ def gesdd(workspace_size=nil)
508
+ self.clone.gesdd!(workspace_size)
509
+ end
510
+
511
+ #
512
+ # call-seq:
513
+ # laswp!(ary) -> NMatrix
514
+ #
515
+ # In-place permute the columns of a dense matrix using LASWP according to the order given as an array +ary+.
516
+ #
517
+ # If +:convention+ is +:lapack+, then +ary+ represents a sequence of pair-wise permutations which are
518
+ # performed successively. That is, the i'th entry of +ary+ is the index of the column to swap
519
+ # the i'th column with, having already applied all earlier swaps.
520
+ #
521
+ # If +:convention+ is +:intuitive+, then +ary+ represents the order of columns after the permutation.
522
+ # That is, the i'th entry of +ary+ is the index of the column that will be in position i after the
523
+ # reordering (Matlab-like behaviour). This is the default.
524
+ #
525
+ # Not yet implemented for yale or list.
526
+ #
527
+ # == Arguments
528
+ #
529
+ # * +ary+ - An Array specifying the order of the columns. See above for details.
530
+ #
531
+ # == Options
532
+ #
533
+ # * +:covention+ - Possible values are +:lapack+ and +:intuitive+. Default is +:intuitive+. See above for details.
534
+ #
535
+ def laswp!(ary, opts={})
536
+ raise(StorageTypeError, "ATLAS functions only work on dense matrices") unless self.dense?
537
+ opts = { convention: :intuitive }.merge(opts)
538
+
539
+ if opts[:convention] == :intuitive
540
+ if ary.length != ary.uniq.length
541
+ raise(ArgumentError, "No duplicated entries in the order array are allowed under convention :intuitive")
542
+ end
543
+ n = self.shape[1]
544
+ p = []
545
+ order = (0...n).to_a
546
+ 0.upto(n-2) do |i|
547
+ p[i] = order.index(ary[i])
548
+ order[i], order[p[i]] = order[p[i]], order[i]
549
+ end
550
+ p[n-1] = n-1
551
+ else
552
+ p = ary
553
+ end
554
+
555
+ NMatrix::LAPACK::laswp(self, p)
556
+ end
557
+
558
+ #
559
+ # call-seq:
560
+ # laswp(ary) -> NMatrix
561
+ #
562
+ # Permute the columns of a dense matrix using LASWP according to the order given in an array +ary+.
563
+ #
564
+ # If +:convention+ is +:lapack+, then +ary+ represents a sequence of pair-wise permutations which are
565
+ # performed successively. That is, the i'th entry of +ary+ is the index of the column to swap
566
+ # the i'th column with, having already applied all earlier swaps. This is the default.
567
+ #
568
+ # If +:convention+ is +:intuitive+, then +ary+ represents the order of columns after the permutation.
569
+ # That is, the i'th entry of +ary+ is the index of the column that will be in position i after the
570
+ # reordering (Matlab-like behaviour).
571
+ #
572
+ # Not yet implemented for yale or list.
573
+ #
574
+ # == Arguments
575
+ #
576
+ # * +ary+ - An Array specifying the order of the columns. See above for details.
577
+ #
578
+ # == Options
579
+ #
580
+ # * +:covention+ - Possible values are +:lapack+ and +:intuitive+. Default is +:lapack+. See above for details.
581
+ #
582
+ def laswp(ary, opts={})
583
+ self.clone.laswp!(ary, opts)
584
+ end
585
+
586
+ #
587
+ # call-seq:
588
+ # det -> determinant
589
+ #
590
+ # Calculate the determinant by way of LU decomposition. This is accomplished
591
+ # using clapack_getrf, and then by taking the product of the diagonal elements. There is a
592
+ # risk of underflow/overflow.
593
+ #
594
+ # There are probably also more efficient ways to calculate the determinant.
595
+ # This method requires making a copy of the matrix, since clapack_getrf
596
+ # modifies its input.
597
+ #
598
+ # For smaller matrices, you may be able to use +#det_exact+.
599
+ #
600
+ # This function is guaranteed to return the same type of data in the matrix
601
+ # upon which it is called.
602
+ #
603
+ # Integer matrices are converted to floating point matrices for the purposes of
604
+ # performing the calculation, as xGETRF can't work on integer matrices.
605
+ #
606
+ # * *Returns* :
607
+ # - The determinant of the matrix. It's the same type as the matrix's dtype.
608
+ # * *Raises* :
609
+ # - +ShapeError+ -> Must be used on square matrices.
610
+ #
611
+ def det
612
+ raise(ShapeError, "determinant can be calculated only for square matrices") unless self.dim == 2 && self.shape[0] == self.shape[1]
613
+
614
+ # Cast to a dtype for which getrf is implemented
615
+ new_dtype = self.integer_dtype? ? :float64 : self.dtype
616
+ copy = self.cast(:dense, new_dtype)
617
+
618
+ # Need to know the number of permutations. We'll add up the diagonals of
619
+ # the factorized matrix.
620
+ pivot = copy.getrf!
621
+
622
+ num_perm = 0 #number of permutations
623
+ pivot.each_with_index do |swap, i|
624
+ #pivot indexes rows starting from 1, instead of 0, so need to subtract 1 here
625
+ num_perm += 1 if swap-1 != i
626
+ end
627
+ prod = num_perm % 2 == 1 ? -1 : 1 # odd permutations => negative
628
+ [shape[0],shape[1]].min.times do |i|
629
+ prod *= copy[i,i]
630
+ end
631
+
632
+ # Convert back to an integer if necessary
633
+ new_dtype != self.dtype ? prod.round : prod #prevent rounding errors
634
+ end
635
+
636
+ #
637
+ # call-seq:
638
+ # complex_conjugate -> NMatrix
639
+ # complex_conjugate(new_stype) -> NMatrix
640
+ #
641
+ # Get the complex conjugate of this matrix. See also complex_conjugate! for
642
+ # an in-place operation (provided the dtype is already +:complex64+ or
643
+ # +:complex128+).
644
+ #
645
+ # Doesn't work on list matrices, but you can optionally pass in the stype you
646
+ # want to cast to if you're dealing with a list matrix.
647
+ #
648
+ # * *Arguments* :
649
+ # - +new_stype+ -> stype for the new matrix.
650
+ # * *Returns* :
651
+ # - If the original NMatrix isn't complex, the result is a +:complex128+ NMatrix. Otherwise, it's the original dtype.
652
+ #
653
+ def complex_conjugate(new_stype = self.stype)
654
+ self.cast(new_stype, NMatrix::upcast(dtype, :complex64)).complex_conjugate!
655
+ end
656
+
657
+ #
658
+ # call-seq:
659
+ # conjugate_transpose -> NMatrix
660
+ #
661
+ # Calculate the conjugate transpose of a matrix. If your dtype is already
662
+ # complex, this should only require one copy (for the transpose).
663
+ #
664
+ # * *Returns* :
665
+ # - The conjugate transpose of the matrix as a copy.
666
+ #
667
+ def conjugate_transpose
668
+ self.transpose.complex_conjugate!
669
+ end
670
+
671
+ #
672
+ # call-seq:
673
+ # absolute_sum -> Numeric
674
+ #
675
+ # == Arguments
676
+ # - +incx+ -> the skip size (defaults to 1, no skip)
677
+ # - +n+ -> the number of elements to include
678
+ #
679
+ # Return the sum of the contents of the vector. This is the BLAS asum routine.
680
+ def asum incx=1, n=nil
681
+ if self.shape == [1]
682
+ return self[0].abs unless self.complex_dtype?
683
+ return self[0].real.abs + self[0].imag.abs
684
+ end
685
+ return method_missing(:asum, incx, n) unless vector?
686
+ NMatrix::BLAS::asum(self, incx, self.size / incx)
687
+ end
688
+ alias :absolute_sum :asum
689
+
690
+ #
691
+ # call-seq:
692
+ # norm2 -> Numeric
693
+ #
694
+ # == Arguments
695
+ # - +incx+ -> the skip size (defaults to 1, no skip)
696
+ # - +n+ -> the number of elements to include
697
+ #
698
+ # Return the 2-norm of the vector. This is the BLAS nrm2 routine.
699
+ def nrm2 incx=1, n=nil
700
+ return method_missing(:nrm2, incx, n) unless vector?
701
+ NMatrix::BLAS::nrm2(self, incx, self.size / incx)
702
+ end
703
+ alias :norm2 :nrm2
704
+
705
+ #
706
+ # call-seq:
707
+ # scale! -> NMatrix
708
+ #
709
+ # == Arguments
710
+ # - +alpha+ -> Scalar value used in the operation.
711
+ # - +inc+ -> Increment used in the scaling function. Should generally be 1.
712
+ # - +n+ -> Number of elements of +vector+.
713
+ #
714
+ # This is a destructive method, modifying the source NMatrix. See also #scale.
715
+ # Return the scaling result of the matrix. BLAS scal will be invoked if provided.
716
+
717
+ def scale!(alpha, incx=1, n=nil)
718
+ raise(DataTypeError, "Incompatible data type for the scaling factor") unless
719
+ NMatrix::upcast(self.dtype, NMatrix::min_dtype(alpha)) == self.dtype
720
+ return NMatrix::BLAS::scal(alpha, self, incx, self.size / incx) if NMatrix::BLAS.method_defined? :scal
721
+ self.each_stored_with_indices do |e, *i|
722
+ self[*i] = e*alpha
723
+ end
724
+ end
725
+
726
+ #
727
+ # call-seq:
728
+ # scale -> NMatrix
729
+ #
730
+ # == Arguments
731
+ # - +alpha+ -> Scalar value used in the operation.
732
+ # - +inc+ -> Increment used in the scaling function. Should generally be 1.
733
+ # - +n+ -> Number of elements of +vector+.
734
+ #
735
+ # Return the scaling result of the matrix. BLAS scal will be invoked if provided.
736
+
737
+ def scale(alpha, incx=1, n=nil)
738
+ return self.clone.scale!(alpha, incx, n)
739
+ end
740
+
741
+ alias :permute_columns :laswp
742
+ alias :permute_columns! :laswp!
743
+
744
+ end