nmatrix 0.2.3 → 0.2.4
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/ext/nmatrix/data/ruby_object.h +1 -1
- data/ext/nmatrix/math.cpp +274 -33
- data/ext/nmatrix/math/math.h +8 -2
- data/ext/nmatrix/ruby_nmatrix.c +81 -65
- data/lib/nmatrix/blas.rb +6 -2
- data/lib/nmatrix/cruby/math.rb +744 -0
- data/lib/nmatrix/enumerate.rb +3 -2
- data/lib/nmatrix/jruby/decomposition.rb +24 -0
- data/lib/nmatrix/jruby/enumerable.rb +13 -0
- data/lib/nmatrix/jruby/error.rb +4 -0
- data/lib/nmatrix/jruby/math.rb +501 -0
- data/lib/nmatrix/jruby/nmatrix_java.rb +840 -0
- data/lib/nmatrix/jruby/operators.rb +283 -0
- data/lib/nmatrix/jruby/slice.rb +264 -0
- data/lib/nmatrix/math.rb +233 -635
- data/lib/nmatrix/mkmf.rb +6 -9
- data/lib/nmatrix/monkeys.rb +2 -4
- data/lib/nmatrix/nmatrix.rb +62 -32
- data/lib/nmatrix/shortcuts.rb +8 -3
- data/lib/nmatrix/version.rb +1 -1
- data/spec/00_nmatrix_spec.rb +110 -3
- data/spec/01_enum_spec.rb +7 -1
- data/spec/02_slice_spec.rb +19 -1
- data/spec/03_nmatrix_monkeys_spec.rb +2 -0
- data/spec/elementwise_spec.rb +10 -2
- data/spec/homogeneous_spec.rb +1 -0
- data/spec/io_spec.rb +11 -1
- data/spec/math_spec.rb +346 -102
- data/spec/rspec_spec.rb +1 -0
- data/spec/shortcuts_spec.rb +47 -23
- data/spec/slice_set_spec.rb +7 -2
- data/spec/stat_spec.rb +11 -0
- metadata +20 -41
- data/ext/nmatrix/ttable_helper.rb +0 -115
data/ext/nmatrix/math/math.h
CHANGED
@@ -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
|
106
|
-
|
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
|
|
data/ext/nmatrix/ruby_nmatrix.c
CHANGED
@@ -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*
|
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
|
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
|
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
|
-
|
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
|
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
|
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
|
-
*
|
2630
|
-
*
|
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*
|
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
|
-
|
2978
|
-
|
2979
|
-
|
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
|
-
|
3006
|
-
|
3007
|
-
|
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) {
|
data/lib/nmatrix/blas.rb
CHANGED
@@ -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
|
-
|
35
|
-
|
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
|