nmatrix 0.0.2 → 0.0.3
Sign up to get free protection for your applications and to get access to all the features.
- data/Gemfile +1 -1
- data/History.txt +31 -3
- data/Manifest.txt +5 -0
- data/README.rdoc +29 -27
- data/ext/nmatrix/binary_format.txt +53 -0
- data/ext/nmatrix/data/data.cpp +18 -18
- data/ext/nmatrix/data/data.h +38 -7
- data/ext/nmatrix/data/rational.h +13 -0
- data/ext/nmatrix/data/ruby_object.h +10 -0
- data/ext/nmatrix/extconf.rb +2 -0
- data/ext/nmatrix/nmatrix.cpp +655 -103
- data/ext/nmatrix/nmatrix.h +26 -14
- data/ext/nmatrix/ruby_constants.cpp +4 -0
- data/ext/nmatrix/ruby_constants.h +2 -0
- data/ext/nmatrix/storage/dense.cpp +99 -41
- data/ext/nmatrix/storage/dense.h +3 -3
- data/ext/nmatrix/storage/list.cpp +36 -14
- data/ext/nmatrix/storage/list.h +4 -4
- data/ext/nmatrix/storage/storage.cpp +19 -19
- data/ext/nmatrix/storage/storage.h +11 -11
- data/ext/nmatrix/storage/yale.cpp +17 -20
- data/ext/nmatrix/storage/yale.h +13 -11
- data/ext/nmatrix/util/io.cpp +25 -23
- data/ext/nmatrix/util/io.h +5 -5
- data/ext/nmatrix/util/math.cpp +634 -17
- data/ext/nmatrix/util/math.h +958 -9
- data/ext/nmatrix/util/sl_list.cpp +7 -7
- data/ext/nmatrix/util/sl_list.h +2 -2
- data/lib/nmatrix.rb +9 -0
- data/lib/nmatrix/blas.rb +4 -4
- data/lib/nmatrix/io/market.rb +227 -0
- data/lib/nmatrix/io/mat_reader.rb +7 -7
- data/lib/nmatrix/lapack.rb +80 -0
- data/lib/nmatrix/nmatrix.rb +78 -52
- data/lib/nmatrix/shortcuts.rb +486 -0
- data/lib/nmatrix/version.rb +1 -1
- data/spec/2x2_dense_double.mat +0 -0
- data/spec/blas_spec.rb +59 -9
- data/spec/elementwise_spec.rb +25 -12
- data/spec/io_spec.rb +69 -1
- data/spec/lapack_spec.rb +53 -4
- data/spec/math_spec.rb +9 -0
- data/spec/nmatrix_list_spec.rb +95 -0
- data/spec/nmatrix_spec.rb +10 -53
- data/spec/nmatrix_yale_spec.rb +17 -15
- data/spec/shortcuts_spec.rb +154 -0
- metadata +22 -15
@@ -344,14 +344,14 @@ NODE* find_nearest_from(NODE* prev, size_t key) {
|
|
344
344
|
|
345
345
|
f = find_preceding_from(prev, key);
|
346
346
|
|
347
|
-
if (!f->next) {
|
347
|
+
if (!f->next) { // key exceeds final node; return final node.
|
348
348
|
return f;
|
349
349
|
|
350
|
-
} else if (key == f->next->key) {
|
350
|
+
} else if (key == f->next->key) { // node already present; return location
|
351
351
|
return f->next;
|
352
|
-
|
352
|
+
|
353
353
|
} else {
|
354
|
-
return
|
354
|
+
return f;
|
355
355
|
}
|
356
356
|
}
|
357
357
|
|
@@ -417,7 +417,7 @@ extern "C" {
|
|
417
417
|
/*
|
418
418
|
* C access for copying the contents of a list.
|
419
419
|
*/
|
420
|
-
void nm_list_cast_copy_contents(LIST* lhs, const LIST* rhs, dtype_t lhs_dtype, dtype_t rhs_dtype, size_t recursions) {
|
420
|
+
void nm_list_cast_copy_contents(LIST* lhs, const LIST* rhs, nm::dtype_t lhs_dtype, nm::dtype_t rhs_dtype, size_t recursions) {
|
421
421
|
LR_DTYPE_TEMPLATE_TABLE(nm::list::cast_copy_contents, void, LIST*, const LIST*, size_t);
|
422
422
|
|
423
423
|
ttable[lhs_dtype][rhs_dtype](lhs, rhs, recursions);
|
@@ -429,7 +429,7 @@ extern "C" {
|
|
429
429
|
* the default value is going to be a hash with default value of hash with default value of default_value, and so on.
|
430
430
|
* In other words, it's recursive.
|
431
431
|
*/
|
432
|
-
static VALUE empty_list_to_hash(const dtype_t dtype, size_t recursions, VALUE default_value) {
|
432
|
+
static VALUE empty_list_to_hash(const nm::dtype_t dtype, size_t recursions, VALUE default_value) {
|
433
433
|
VALUE h = rb_hash_new();
|
434
434
|
if (recursions) {
|
435
435
|
RHASH_IFNONE(h) = empty_list_to_hash(dtype, recursions-1, default_value);
|
@@ -443,7 +443,7 @@ extern "C" {
|
|
443
443
|
/*
|
444
444
|
* Copy a list to a Ruby Hash
|
445
445
|
*/
|
446
|
-
VALUE nm_list_copy_to_hash(const LIST* l, const dtype_t dtype, size_t recursions, VALUE default_value) {
|
446
|
+
VALUE nm_list_copy_to_hash(const LIST* l, const nm::dtype_t dtype, size_t recursions, VALUE default_value) {
|
447
447
|
|
448
448
|
// Create a hash with default values appropriately specified for a sparse matrix.
|
449
449
|
VALUE h = empty_list_to_hash(dtype, recursions, default_value);
|
data/ext/nmatrix/util/sl_list.h
CHANGED
@@ -248,8 +248,8 @@ void cast_copy_contents(LIST* lhs, const LIST* rhs, size_t recursions);
|
|
248
248
|
}} // end of namespace nm::list
|
249
249
|
|
250
250
|
extern "C" {
|
251
|
-
void nm_list_cast_copy_contents(LIST* lhs, const LIST* rhs, dtype_t lhs_dtype, dtype_t rhs_dtype, size_t recursions);
|
252
|
-
VALUE nm_list_copy_to_hash(const LIST* l, const dtype_t dtype, size_t recursions, VALUE default_value);
|
251
|
+
void nm_list_cast_copy_contents(LIST* lhs, const LIST* rhs, nm::dtype_t lhs_dtype, nm::dtype_t rhs_dtype, size_t recursions);
|
252
|
+
VALUE nm_list_copy_to_hash(const LIST* l, const nm::dtype_t dtype, size_t recursions, VALUE default_value);
|
253
253
|
} // end of extern "C" block
|
254
254
|
|
255
255
|
#endif // SL_LIST_H
|
data/lib/nmatrix.rb
CHANGED
data/lib/nmatrix/blas.rb
CHANGED
@@ -37,8 +37,8 @@ module NMatrix::BLAS
|
|
37
37
|
|
38
38
|
# NM_COMPLEX64 and NM_COMPLEX128 both require complex alpha and beta.
|
39
39
|
if a.dtype == :complex64 or a.dtype == :complex128
|
40
|
-
alpha = Complex
|
41
|
-
beta = Complex
|
40
|
+
alpha = Complex(1.0, 0.0) if alpha == 1.0
|
41
|
+
beta = Complex(0.0, 0.0) if beta == 0.0
|
42
42
|
end
|
43
43
|
|
44
44
|
# For argument descriptions, see: http://www.netlib.org/blas/dgemm.f
|
@@ -57,8 +57,8 @@ module NMatrix::BLAS
|
|
57
57
|
|
58
58
|
# NM_COMPLEX64 and NM_COMPLEX128 both require complex alpha and beta.
|
59
59
|
if a.dtype == :complex64 or a.dtype == :complex128
|
60
|
-
alpha = Complex
|
61
|
-
beta = Complex
|
60
|
+
alpha = Complex(1.0, 0.0) if alpha == 1.0
|
61
|
+
beta = Complex(0.0, 0.0) if beta == 0.0
|
62
62
|
end
|
63
63
|
|
64
64
|
::NMatrix::BLAS.cblas_gemv(transpose_a, m, n, alpha, a, lda, x, incx, beta, y, incy)
|
@@ -0,0 +1,227 @@
|
|
1
|
+
# = NMatrix
|
2
|
+
#
|
3
|
+
# A linear algebra library for scientific computation in Ruby.
|
4
|
+
# NMatrix is part of SciRuby.
|
5
|
+
#
|
6
|
+
# NMatrix was originally inspired by and derived from NArray, by
|
7
|
+
# Masahiro Tanaka: http://narray.rubyforge.org
|
8
|
+
#
|
9
|
+
# == Copyright Information
|
10
|
+
#
|
11
|
+
# SciRuby is Copyright (c) 2010 - 2012, Ruby Science Foundation
|
12
|
+
# NMatrix is Copyright (c) 2012, Ruby Science Foundation
|
13
|
+
#
|
14
|
+
# Please see LICENSE.txt for additional copyright notices.
|
15
|
+
#
|
16
|
+
# == Contributing
|
17
|
+
#
|
18
|
+
# By contributing source code to SciRuby, you agree to be bound by
|
19
|
+
# our Contributor Agreement:
|
20
|
+
#
|
21
|
+
# * https://github.com/SciRuby/sciruby/wiki/Contributor-Agreement
|
22
|
+
#
|
23
|
+
# == io/market.rb
|
24
|
+
#
|
25
|
+
# MatrixMarket reader and writer.
|
26
|
+
#
|
27
|
+
module NMatrix::IO::Market
|
28
|
+
CONVERTER_AND_DTYPE = {
|
29
|
+
:real => [:to_f, :float64],
|
30
|
+
:complex => [:to_c, :complex128],
|
31
|
+
:integer => [:to_i, :int64],
|
32
|
+
:pattern => [:to_i, :byte]
|
33
|
+
}
|
34
|
+
|
35
|
+
ENTRY_TYPE = {
|
36
|
+
:byte => :integer, :int8 => :integer, :int16 => :integer, :int32 => :integer, :int64 => :integer,
|
37
|
+
:float32 => :real, :float64 => :real, :complex64 => :complex, :complex128 => :complex
|
38
|
+
}
|
39
|
+
|
40
|
+
class << self
|
41
|
+
# Load a MatrixMarket file. Requires a filename as an argument.
|
42
|
+
def load filename
|
43
|
+
|
44
|
+
f = File.new(filename, "r")
|
45
|
+
|
46
|
+
header = f.gets
|
47
|
+
header.chomp!
|
48
|
+
raise(IOError, "expected type code line beginning with '%%MatrixMarket matrix'") if header !~ /^\%\%MatrixMarket\ matrix/
|
49
|
+
|
50
|
+
header = header.split
|
51
|
+
|
52
|
+
entry_type = header[3].downcase.to_sym
|
53
|
+
symmetry = header[4].downcase.to_sym
|
54
|
+
converter, default_dtype = CONVERTER_AND_DTYPE[entry_type]
|
55
|
+
|
56
|
+
if header[2] == 'coordinate'
|
57
|
+
load_coordinate f, converter, default_dtype, entry_type, symmetry
|
58
|
+
else
|
59
|
+
load_array f, converter, default_dtype, entry_type, symmetry
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
|
64
|
+
# Can optionally set :symmetry to :general, :symmetric, :hermitian; and can set :pattern => true if you're writing
|
65
|
+
# a sparse matrix and don't want values stored.
|
66
|
+
def save matrix, filename, options = {}
|
67
|
+
options = {:pattern => false,
|
68
|
+
:symmetry => :general}.merge(options)
|
69
|
+
|
70
|
+
mode = matrix.stype == :dense ? :array : :coordinate
|
71
|
+
if [:rational32,:rational64,:rational128,:object].include?(matrix.dtype)
|
72
|
+
raise(DataTypeError, "MatrixMarket does not support rational or Ruby objects")
|
73
|
+
end
|
74
|
+
entry_type = options[:pattern] ? :pattern : ENTRY_TYPE[matrix.dtype]
|
75
|
+
|
76
|
+
raise(ArgumentError, "expected two-dimensional NMatrix") if matrix.dim != 2
|
77
|
+
|
78
|
+
f = File.new(filename, 'w')
|
79
|
+
|
80
|
+
f.puts "%%MatrixMarket matrix #{mode} #{entry_type} #{options[:symmetry]}"
|
81
|
+
|
82
|
+
if matrix.stype == :dense
|
83
|
+
save_array matrix, f, options[:symmetry]
|
84
|
+
elsif [:list,:yale].include?(matrix.stype)
|
85
|
+
save_coordinate matrix, f, options[:symmetry], options[:pattern]
|
86
|
+
end
|
87
|
+
|
88
|
+
f.close
|
89
|
+
|
90
|
+
true
|
91
|
+
end
|
92
|
+
|
93
|
+
|
94
|
+
protected
|
95
|
+
|
96
|
+
def save_coordinate matrix, file, symmetry, pattern
|
97
|
+
# Convert to a hash in order to store
|
98
|
+
rows = matrix.to_h
|
99
|
+
|
100
|
+
# Count non-zeros
|
101
|
+
count = 0
|
102
|
+
rows.each_pair do |i, columns|
|
103
|
+
columns.each_pair do |j, val|
|
104
|
+
next if symmetry != :general && j > i
|
105
|
+
count += 1
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
# Print dimensions and non-zeros
|
110
|
+
file.puts "#{matrix.shape[0]}\t#{matrix.shape[1]}\t#{count}"
|
111
|
+
|
112
|
+
# Print coordinates
|
113
|
+
rows.each_pair do |i, columns|
|
114
|
+
columns.each_pair do |j, val|
|
115
|
+
next if symmetry != :general && j > i
|
116
|
+
file.puts(pattern ? "\t#{i+1}\t#{j+1}" : "\t#{i+1}\t#{j+1}\t#{val}")
|
117
|
+
end
|
118
|
+
end
|
119
|
+
|
120
|
+
file
|
121
|
+
end
|
122
|
+
|
123
|
+
|
124
|
+
def save_array matrix, file, symmetry
|
125
|
+
file.puts [matrix.shape[0], matrix.shape[1]].join("\t")
|
126
|
+
|
127
|
+
if symmetry == :general
|
128
|
+
(0...matrix.shape[1]).each do |j|
|
129
|
+
(0...matrix.shape[0]).each do |i|
|
130
|
+
file.puts matrix[i,j]
|
131
|
+
end
|
132
|
+
end
|
133
|
+
else # :symmetric, :'skew-symmetric', :hermitian
|
134
|
+
(0...matrix.shape[1]).each do |j|
|
135
|
+
(j...matrix.shape[0]).each do |i|
|
136
|
+
file.puts matrix[i,j]
|
137
|
+
end
|
138
|
+
end
|
139
|
+
end
|
140
|
+
|
141
|
+
file
|
142
|
+
end
|
143
|
+
|
144
|
+
|
145
|
+
def load_array file, converter, dtype, entry_type, symmetry
|
146
|
+
mat = nil
|
147
|
+
|
148
|
+
line = file.gets
|
149
|
+
line.chomp!
|
150
|
+
line.lstrip!
|
151
|
+
|
152
|
+
fields = line.split
|
153
|
+
|
154
|
+
mat = NMatrix.new :dense, [fields[0].to_i, fields[1].to_i], dtype
|
155
|
+
|
156
|
+
(0...mat.shape[1]).each do |j|
|
157
|
+
(0...mat.shape[0]).each do |i|
|
158
|
+
datum = file.gets.chomp.send(converter)
|
159
|
+
mat[i,j] = datum
|
160
|
+
|
161
|
+
unless i == j || symmetry == :general
|
162
|
+
if symmetry == :symmetric
|
163
|
+
mat[j,i] = datum
|
164
|
+
elsif symmetry == :hermitian
|
165
|
+
mat[j,i] = Complex.new(datum.real, -datum.imag)
|
166
|
+
elsif symmetry == :'skew-symmetric'
|
167
|
+
mat[j,i] = -datum
|
168
|
+
end
|
169
|
+
end
|
170
|
+
end
|
171
|
+
end
|
172
|
+
|
173
|
+
file.close
|
174
|
+
|
175
|
+
mat
|
176
|
+
end
|
177
|
+
|
178
|
+
|
179
|
+
# Creates a :list NMatrix from a coordinate-list MatrixMarket file.
|
180
|
+
def load_coordinate file, converter, dtype, entry_type, symmetry
|
181
|
+
|
182
|
+
mat = nil
|
183
|
+
|
184
|
+
# Read until we get the dimensions and nonzeros
|
185
|
+
while line = file.gets
|
186
|
+
line.chomp!
|
187
|
+
line.lstrip!
|
188
|
+
line, comment = line.split('%', 2) # ignore comments
|
189
|
+
if line.size > 4
|
190
|
+
shape0, shape1, nz = line.split
|
191
|
+
mat = NMatrix.new(:list, [shape0.to_i, shape1.to_i], 0, dtype)
|
192
|
+
break
|
193
|
+
end
|
194
|
+
end
|
195
|
+
|
196
|
+
# Now read the coordinates
|
197
|
+
while line = file.gets
|
198
|
+
line.chomp!
|
199
|
+
line.lstrip!
|
200
|
+
line, comment = line.split('%', 2) # ignore comments
|
201
|
+
|
202
|
+
next unless line.size >= 5 # ignore empty lines
|
203
|
+
|
204
|
+
fields = line.split
|
205
|
+
|
206
|
+
i = fields[0].to_i - 1
|
207
|
+
j = fields[1].to_i - 1
|
208
|
+
datum = entry_type == :pattern ? 1 : fields[2].send(converter)
|
209
|
+
|
210
|
+
mat[i, j] = datum # add to the matrix
|
211
|
+
unless i == j || symmetry == :general
|
212
|
+
if symmetry == :symmetric
|
213
|
+
mat[j, i] = datum
|
214
|
+
elsif symmetry == :'skew-symmetric'
|
215
|
+
mat[j, i] = -datum
|
216
|
+
elsif symmetry == :hermitian
|
217
|
+
mat[j, i] = Complex.new(datum.real, -datum.imag)
|
218
|
+
end
|
219
|
+
end
|
220
|
+
end
|
221
|
+
|
222
|
+
file.close
|
223
|
+
|
224
|
+
mat
|
225
|
+
end
|
226
|
+
end
|
227
|
+
end
|
@@ -21,7 +21,7 @@ require "packable"
|
|
21
21
|
#
|
22
22
|
# * https://github.com/SciRuby/sciruby/wiki/Contributor-Agreement
|
23
23
|
#
|
24
|
-
# == io/
|
24
|
+
# == io/mat_reader.rb
|
25
25
|
#
|
26
26
|
# Base class for .mat file reading (Matlab files).
|
27
27
|
#
|
@@ -42,8 +42,8 @@ module NMatrix::IO::Matlab
|
|
42
42
|
:miUINT16 => [Integer, {:signed => false, :bytes => 2}],
|
43
43
|
:miINT32 => [Integer, {:signed => true, :bytes => 4}],
|
44
44
|
:miUINT32 => [Integer, {:signed => false, :bytes => 4}],
|
45
|
-
:miSINGLE => [Float, {:precision => :single, :bytes => 4}],
|
46
|
-
:miDOUBLE => [Float, {:precision
|
45
|
+
:miSINGLE => [Float, {:precision => :single, :bytes => 4, :endian => :native}],
|
46
|
+
:miDOUBLE => [Float, {:precision => :double, :bytes => 4, :endian => :native}],
|
47
47
|
:miINT64 => [Integer, {:signed => true, :bytes => 8}],
|
48
48
|
:miUINT64 => [Integer, {:signed => false, :bytes => 8}]
|
49
49
|
}
|
@@ -54,10 +54,10 @@ module NMatrix::IO::Matlab
|
|
54
54
|
:int16 => [Integer, {:signed => true, :bytes => 2}],
|
55
55
|
:int32 => [Integer, {:signed => true, :bytes => 4}],
|
56
56
|
:int64 => [Integer, {:signed => true, :bytes => 8}],
|
57
|
-
:float32 => [Float, {:precision => :single, :bytes => 4}],
|
58
|
-
:float64 => [Float, {:precision => :double, :bytes => 8}],
|
59
|
-
:complex64 => [Float, {:precision => :single, :bytes => 4}], #2x
|
60
|
-
:complex128 => [Float, {:precision => :double, :bytes => 8}]
|
57
|
+
:float32 => [Float, {:precision => :single, :bytes => 4, :endian => :native}],
|
58
|
+
:float64 => [Float, {:precision => :double, :bytes => 8, :endian => :native}],
|
59
|
+
:complex64 => [Float, {:precision => :single, :bytes => 4, :endian => :native}], #2x
|
60
|
+
:complex128 => [Float, {:precision => :double, :bytes => 8, :endian => :native}]
|
61
61
|
}
|
62
62
|
|
63
63
|
ITYPE_PACK_ARGS = {
|
@@ -0,0 +1,80 @@
|
|
1
|
+
# = NMatrix
|
2
|
+
#
|
3
|
+
# A linear algebra library for scientific computation in Ruby.
|
4
|
+
# NMatrix is part of SciRuby.
|
5
|
+
#
|
6
|
+
# NMatrix was originally inspired by and derived from NArray, by
|
7
|
+
# Masahiro Tanaka: http://narray.rubyforge.org
|
8
|
+
#
|
9
|
+
# == Copyright Information
|
10
|
+
#
|
11
|
+
# SciRuby is Copyright (c) 2010 - 2012, Ruby Science Foundation
|
12
|
+
# NMatrix is Copyright (c) 2012, Ruby Science Foundation
|
13
|
+
#
|
14
|
+
# Please see LICENSE.txt for additional copyright notices.
|
15
|
+
#
|
16
|
+
# == Contributing
|
17
|
+
#
|
18
|
+
# By contributing source code to SciRuby, you agree to be bound by
|
19
|
+
# our Contributor Agreement:
|
20
|
+
#
|
21
|
+
# * https://github.com/SciRuby/sciruby/wiki/Contributor-Agreement
|
22
|
+
#
|
23
|
+
# == lapack.rb
|
24
|
+
#
|
25
|
+
# This file contains LAPACK functions accessible in their C versions,
|
26
|
+
# e.g., NMatrix::LAPACK::clapack_func. There are some exceptions,
|
27
|
+
# such as clapack_gesv, which is implemented in Ruby but calls
|
28
|
+
# clapack_getrf and clapack_getrs.
|
29
|
+
#
|
30
|
+
# Note: most of these functions are borrowed from ATLAS, which is available under a BSD-
|
31
|
+
# style license.
|
32
|
+
|
33
|
+
class NMatrix
|
34
|
+
module LAPACK
|
35
|
+
class << self
|
36
|
+
|
37
|
+
# clapack_gesv computes the solution to a system of linear equations
|
38
|
+
# A * X = B,
|
39
|
+
# where A is an N-by-N matrix and X and B are N-by-NRHS matrices.
|
40
|
+
# The LU factorization used to factor A is dependent on the Order parameter,
|
41
|
+
# as detailed in the leading comments of clapack_getrf.
|
42
|
+
# The factored form of A is then used solve the system of equations A * X = B.
|
43
|
+
# A is overwritten with the appropriate LU factorization, and B, which
|
44
|
+
# contains B on input, is overwritten with the solution X on output.
|
45
|
+
#
|
46
|
+
# From ATLAS 3.8.0.
|
47
|
+
#
|
48
|
+
# Note: Because this function is implemented in Ruby, the ATLAS lib version is
|
49
|
+
# never called! For float32, float64, complex64, and complex128, the ATLAS lib
|
50
|
+
# versions of getrf and getrs *will* be called.
|
51
|
+
def clapack_gesv(order, n, nrhs, a, lda, ipiv, b, ldb)
|
52
|
+
clapack_getrf(order, n, n, a, lda, ipiv)
|
53
|
+
clapack_getrs(order, :no_transpose, n, nrhs, a, lda, ipiv, b, ldb)
|
54
|
+
end
|
55
|
+
|
56
|
+
# clapack_posv computes the solution to a real system of linear equations
|
57
|
+
# A * X = B,
|
58
|
+
# where A is an N-by-N symmetric positive definite matrix and X and B
|
59
|
+
# are N-by-NRHS matrices.
|
60
|
+
#
|
61
|
+
# The Cholesky decomposition is used to factor A as
|
62
|
+
# A = U**T* U, if UPLO = 'U', or
|
63
|
+
# A = L * L**T, if UPLO = 'L',
|
64
|
+
# where U is an upper triangular matrix and L is a lower triangular
|
65
|
+
# matrix. The factored form of A is then used to solve the system of
|
66
|
+
# equations A * X = B.
|
67
|
+
#
|
68
|
+
# From ATLAS 3.8.0.
|
69
|
+
#
|
70
|
+
# Note: Because this function is implemented in Ruby, the ATLAS lib version is
|
71
|
+
# never called! For float32, float64, complex64, and complex128, the ATLAS lib
|
72
|
+
# versions of potrf and potrs *will* be called.
|
73
|
+
def clapack_posv(order, uplo, n, nrhs, a, lda, b, ldb)
|
74
|
+
clapack_potrf(order, uplo, n, a, lda)
|
75
|
+
clapack_potrs(order, uplo, n, nrhs, a, lda, b, ldb)
|
76
|
+
end
|
77
|
+
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
data/lib/nmatrix/nmatrix.rb
CHANGED
@@ -29,6 +29,8 @@
|
|
29
29
|
# Requires #
|
30
30
|
############
|
31
31
|
|
32
|
+
require_relative './shortcuts.rb'
|
33
|
+
|
32
34
|
#######################
|
33
35
|
# Classes and Modules #
|
34
36
|
#######################
|
@@ -47,7 +49,11 @@ class NMatrix
|
|
47
49
|
autoload :MatReader, 'nmatrix/io/mat_reader'
|
48
50
|
autoload :Mat5Reader, 'nmatrix/io/mat5_reader'
|
49
51
|
end
|
50
|
-
|
52
|
+
|
53
|
+
# FIXME: Remove autoloads
|
54
|
+
autoload :Market, 'nmatrix/io/market'
|
55
|
+
end
|
56
|
+
|
51
57
|
|
52
58
|
# TODO: Make this actually pretty.
|
53
59
|
def pretty_print(q = nil)
|
@@ -80,6 +86,74 @@ class NMatrix
|
|
80
86
|
end
|
81
87
|
alias :pp :pretty_print
|
82
88
|
|
89
|
+
# These shortcuts use #shape to return the number of rows and columns.
|
90
|
+
def rows
|
91
|
+
shape[0]
|
92
|
+
end
|
93
|
+
|
94
|
+
def cols
|
95
|
+
shape[1]
|
96
|
+
end
|
97
|
+
|
98
|
+
# Use LAPACK to calculate the inverse of the matrix (in-place). Only works on dense matrices.
|
99
|
+
#
|
100
|
+
# Note: If you don't have LAPACK, e.g., on a Mac, this may not work yet.
|
101
|
+
def invert!
|
102
|
+
# Get the pivot array; factor the matrix
|
103
|
+
pivot = self.getrf!
|
104
|
+
|
105
|
+
# Now calculate the inverse using the pivot array
|
106
|
+
NMatrix::LAPACK::clapack_getri(:row, self.shape[0], self, self.shape[0], pivot)
|
107
|
+
|
108
|
+
self
|
109
|
+
end
|
110
|
+
|
111
|
+
# Make a copy of the matrix, then invert it (requires LAPACK). Returns a dense matrix.
|
112
|
+
def invert
|
113
|
+
self.cast(:dense, self.dtype).invert!
|
114
|
+
end
|
115
|
+
|
116
|
+
alias :inverse :invert
|
117
|
+
|
118
|
+
# Calls clapack_getrf and returns the pivot array (dense only).
|
119
|
+
def getrf!
|
120
|
+
raise(StorageTypeError, "ATLAS functions only work on dense matrices") unless self.stype == :dense
|
121
|
+
NMatrix::LAPACK::clapack_getrf(:row, self.shape[0], self.shape[1], self, self.shape[0])
|
122
|
+
end
|
123
|
+
|
124
|
+
# Calculate the determinant by way of LU decomposition. This is accomplished using
|
125
|
+
# clapack_getrf, and then by summing the diagonal elements. There is a risk of
|
126
|
+
# underflow/overflow.
|
127
|
+
#
|
128
|
+
# There are probably also more efficient ways to calculate the determinant. This method
|
129
|
+
# requires making a copy of the matrix, since clapack_getrf modifies its input.
|
130
|
+
#
|
131
|
+
# For smaller matrices, you may be able to use det_exact.
|
132
|
+
#
|
133
|
+
# This function is guaranteed to return the same type of data in the matrix upon which it is called.
|
134
|
+
# In other words, if you call it on a rational matrix, you'll get a rational number back.
|
135
|
+
#
|
136
|
+
# Integer matrices are converted to rational matrices for the purposes of performing the calculation,
|
137
|
+
# as xGETRF can't work on integer matrices.
|
138
|
+
def det
|
139
|
+
raise(NotImplementedError, "determinant can be calculated only for 2D matrices") unless self.dim == 2
|
140
|
+
|
141
|
+
# Cast to a dtype for which getrf is implemented
|
142
|
+
new_dtype = [:byte,:int8,:int16,:int32,:int64].include?(self.dtype) ? :rational128 : self.dtype
|
143
|
+
copy = self.cast(:dense, new_dtype)
|
144
|
+
|
145
|
+
# Need to know the number of permutations. We'll add up the diagonals of the factorized matrix.
|
146
|
+
pivot = copy.getrf!
|
147
|
+
|
148
|
+
prod = pivot.size % 2 == 1 ? -1 : 1 # odd permutations => negative
|
149
|
+
[shape[0],shape[1]].min.times do |i|
|
150
|
+
prod *= copy[i,i]
|
151
|
+
end
|
152
|
+
|
153
|
+
# Convert back to an integer if necessary
|
154
|
+
new_dtype != self.dtype ? prod.to_i : prod
|
155
|
+
end
|
156
|
+
|
83
157
|
|
84
158
|
# Get the complex conjugate of this matrix. See also complex_conjugate! for
|
85
159
|
# an in-place operation (provided the dtype is already :complex64 or
|
@@ -124,57 +198,7 @@ class NMatrix
|
|
124
198
|
def load_file(file_path)
|
125
199
|
NMatrix::IO::Mat5Reader.new(File.open(file_path, 'rb')).to_ruby
|
126
200
|
end
|
127
|
-
|
128
|
-
# Helper function for loading a file in the first sparse format given here:
|
129
|
-
# http://math.nist.gov/MatrixMarket/formats.html
|
130
|
-
#
|
131
|
-
# Override type specifier (e.g., 'real') using :read_with => :to_f (or any other string-to-numeric conversion
|
132
|
-
# function), and with :dtype => :float32 or :dtype => :int8 to force storage in a lesser type.
|
133
|
-
def load_matrix_matrix_coordinate_file(filename, options = {})
|
134
|
-
f = File.new(filename, "r")
|
135
|
-
|
136
|
-
func = options[:read_with]
|
137
|
-
dtype = options[:dtype]
|
138
|
-
|
139
|
-
line = f.gets
|
140
|
-
raise IOError, 'Incorrect file type specifier.' unless line =~ /^%%MatrixMarket\ matrix\ coordinate/
|
141
|
-
spec = line.split
|
142
|
-
|
143
|
-
case spec[3]
|
144
|
-
when 'real'
|
145
|
-
func ||= :to_f
|
146
|
-
dtype ||= :float64
|
147
|
-
when 'integer'
|
148
|
-
func ||= :to_i
|
149
|
-
dtype ||= :int64
|
150
|
-
when 'complex'
|
151
|
-
func ||= :to_complex
|
152
|
-
dtype ||= :complex128
|
153
|
-
when 'rational'
|
154
|
-
func ||= :to_rational
|
155
|
-
dtype ||= :rational128
|
156
|
-
else
|
157
|
-
raise ArgumentError, 'Unrecognized dtype.'
|
158
|
-
end unless func and dtype
|
159
|
-
|
160
|
-
begin
|
161
|
-
line = f.gets
|
162
|
-
end while line =~ /^%/
|
163
|
-
|
164
|
-
# Close the file.
|
165
|
-
f.close
|
166
|
-
|
167
|
-
rows, cols, entries = line.split.collect { |x| x.to_i }
|
168
|
-
|
169
|
-
matrix = NMatrix.new(:yale, [rows, cols], entries, dtype)
|
170
|
-
|
171
|
-
entries.times do
|
172
|
-
i, j, v = line.split
|
173
|
-
matrix[i.to_i - 1, j.to_i - 1] = v.send(func)
|
174
|
-
end
|
175
|
-
|
176
|
-
matrix
|
177
|
-
end
|
201
|
+
|
178
202
|
end
|
179
203
|
|
180
204
|
protected
|
@@ -197,3 +221,5 @@ protected
|
|
197
221
|
ary
|
198
222
|
end
|
199
223
|
end
|
224
|
+
|
225
|
+
require_relative "./lapack.rb"
|