nmatrix 0.2.3 → 0.2.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|