pnmatrix 1.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 +7 -0
- data/ext/nmatrix/binary_format.txt +53 -0
- data/ext/nmatrix/data/complex.h +388 -0
- data/ext/nmatrix/data/data.cpp +274 -0
- data/ext/nmatrix/data/data.h +651 -0
- data/ext/nmatrix/data/meta.h +64 -0
- data/ext/nmatrix/data/ruby_object.h +386 -0
- data/ext/nmatrix/extconf.rb +70 -0
- data/ext/nmatrix/math/asum.h +99 -0
- data/ext/nmatrix/math/cblas_enums.h +36 -0
- data/ext/nmatrix/math/cblas_templates_core.h +507 -0
- data/ext/nmatrix/math/gemm.h +241 -0
- data/ext/nmatrix/math/gemv.h +178 -0
- data/ext/nmatrix/math/getrf.h +255 -0
- data/ext/nmatrix/math/getrs.h +121 -0
- data/ext/nmatrix/math/imax.h +82 -0
- data/ext/nmatrix/math/laswp.h +165 -0
- data/ext/nmatrix/math/long_dtype.h +62 -0
- data/ext/nmatrix/math/magnitude.h +54 -0
- data/ext/nmatrix/math/math.h +751 -0
- data/ext/nmatrix/math/nrm2.h +165 -0
- data/ext/nmatrix/math/rot.h +117 -0
- data/ext/nmatrix/math/rotg.h +106 -0
- data/ext/nmatrix/math/scal.h +71 -0
- data/ext/nmatrix/math/trsm.h +336 -0
- data/ext/nmatrix/math/util.h +162 -0
- data/ext/nmatrix/math.cpp +1368 -0
- data/ext/nmatrix/nm_memory.h +60 -0
- data/ext/nmatrix/nmatrix.cpp +285 -0
- data/ext/nmatrix/nmatrix.h +476 -0
- data/ext/nmatrix/ruby_constants.cpp +151 -0
- data/ext/nmatrix/ruby_constants.h +106 -0
- data/ext/nmatrix/ruby_nmatrix.c +3130 -0
- data/ext/nmatrix/storage/common.cpp +77 -0
- data/ext/nmatrix/storage/common.h +183 -0
- data/ext/nmatrix/storage/dense/dense.cpp +1096 -0
- data/ext/nmatrix/storage/dense/dense.h +129 -0
- data/ext/nmatrix/storage/list/list.cpp +1628 -0
- data/ext/nmatrix/storage/list/list.h +138 -0
- data/ext/nmatrix/storage/storage.cpp +730 -0
- data/ext/nmatrix/storage/storage.h +99 -0
- data/ext/nmatrix/storage/yale/class.h +1139 -0
- data/ext/nmatrix/storage/yale/iterators/base.h +143 -0
- data/ext/nmatrix/storage/yale/iterators/iterator.h +131 -0
- data/ext/nmatrix/storage/yale/iterators/row.h +450 -0
- data/ext/nmatrix/storage/yale/iterators/row_stored.h +140 -0
- data/ext/nmatrix/storage/yale/iterators/row_stored_nd.h +169 -0
- data/ext/nmatrix/storage/yale/iterators/stored_diagonal.h +124 -0
- data/ext/nmatrix/storage/yale/math/transpose.h +110 -0
- data/ext/nmatrix/storage/yale/yale.cpp +2074 -0
- data/ext/nmatrix/storage/yale/yale.h +203 -0
- data/ext/nmatrix/types.h +55 -0
- data/ext/nmatrix/util/io.cpp +279 -0
- data/ext/nmatrix/util/io.h +115 -0
- data/ext/nmatrix/util/sl_list.cpp +627 -0
- data/ext/nmatrix/util/sl_list.h +144 -0
- data/ext/nmatrix/util/util.h +78 -0
- data/lib/nmatrix/blas.rb +378 -0
- data/lib/nmatrix/cruby/math.rb +744 -0
- data/lib/nmatrix/enumerate.rb +253 -0
- data/lib/nmatrix/homogeneous.rb +241 -0
- data/lib/nmatrix/io/fortran_format.rb +138 -0
- data/lib/nmatrix/io/harwell_boeing.rb +221 -0
- data/lib/nmatrix/io/market.rb +263 -0
- data/lib/nmatrix/io/point_cloud.rb +189 -0
- 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/lapack_core.rb +181 -0
- data/lib/nmatrix/lapack_plugin.rb +44 -0
- data/lib/nmatrix/math.rb +953 -0
- data/lib/nmatrix/mkmf.rb +100 -0
- data/lib/nmatrix/monkeys.rb +137 -0
- data/lib/nmatrix/nmatrix.rb +1172 -0
- data/lib/nmatrix/rspec.rb +75 -0
- data/lib/nmatrix/shortcuts.rb +1163 -0
- data/lib/nmatrix/version.rb +39 -0
- data/lib/nmatrix/yale_functions.rb +118 -0
- data/lib/nmatrix.rb +28 -0
- data/spec/00_nmatrix_spec.rb +892 -0
- data/spec/01_enum_spec.rb +196 -0
- data/spec/02_slice_spec.rb +407 -0
- data/spec/03_nmatrix_monkeys_spec.rb +80 -0
- data/spec/2x2_dense_double.mat +0 -0
- data/spec/4x4_sparse.mat +0 -0
- data/spec/4x5_dense.mat +0 -0
- data/spec/blas_spec.rb +215 -0
- data/spec/elementwise_spec.rb +311 -0
- data/spec/homogeneous_spec.rb +100 -0
- data/spec/io/fortran_format_spec.rb +88 -0
- data/spec/io/harwell_boeing_spec.rb +98 -0
- data/spec/io/test.rua +9 -0
- data/spec/io_spec.rb +159 -0
- data/spec/lapack_core_spec.rb +482 -0
- data/spec/leakcheck.rb +16 -0
- data/spec/math_spec.rb +1363 -0
- data/spec/nmatrix_yale_resize_test_associations.yaml +2802 -0
- data/spec/nmatrix_yale_spec.rb +286 -0
- data/spec/rspec_monkeys.rb +56 -0
- data/spec/rspec_spec.rb +35 -0
- data/spec/shortcuts_spec.rb +474 -0
- data/spec/slice_set_spec.rb +162 -0
- data/spec/spec_helper.rb +172 -0
- data/spec/stat_spec.rb +214 -0
- data/spec/test.pcd +20 -0
- data/spec/utm5940.mtx +83844 -0
- metadata +295 -0
|
@@ -0,0 +1,1172 @@
|
|
|
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
|
+
# == nmatrix.rb
|
|
25
|
+
#
|
|
26
|
+
# This file loads the C extension for NMatrix and all the ruby
|
|
27
|
+
# files and contains those core functionalities which can be
|
|
28
|
+
# implemented efficiently (or much more easily) in Ruby (e.g.,
|
|
29
|
+
# inspect, pretty_print, element-wise operations).
|
|
30
|
+
#++
|
|
31
|
+
|
|
32
|
+
# For some reason nmatrix.so ends up in a different place during gem build.
|
|
33
|
+
|
|
34
|
+
# Detect java
|
|
35
|
+
def jruby?
|
|
36
|
+
/java/ === RUBY_PLATFORM
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
if jruby?
|
|
40
|
+
require_relative 'jruby/nmatrix_java'
|
|
41
|
+
else
|
|
42
|
+
if File.exist?("lib/nmatrix/nmatrix.so") #|| File.exist?("lib/nmatrix/nmatrix.bundle")
|
|
43
|
+
# Development
|
|
44
|
+
require_relative "nmatrix/nmatrix.so"
|
|
45
|
+
else
|
|
46
|
+
# Gem
|
|
47
|
+
require_relative "../nmatrix.so"
|
|
48
|
+
require_relative './io/market'
|
|
49
|
+
require_relative './io/point_cloud'
|
|
50
|
+
|
|
51
|
+
require_relative './lapack_core.rb'
|
|
52
|
+
require_relative './yale_functions.rb'
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
require_relative './math.rb'
|
|
57
|
+
require_relative './monkeys'
|
|
58
|
+
|
|
59
|
+
# NMatrix is a matrix class that supports both multidimensional arrays
|
|
60
|
+
# (`:dense` stype) and sparse storage (`:list` or `:yale` stypes) and 13 data
|
|
61
|
+
# types, including complex numbers, various integer and
|
|
62
|
+
# floating-point sizes and ruby objects.
|
|
63
|
+
class NMatrix
|
|
64
|
+
# Read and write extensions for NMatrix.
|
|
65
|
+
module IO
|
|
66
|
+
extend AutoloadPatch
|
|
67
|
+
|
|
68
|
+
# Reader (and eventually writer) of Matlab .mat files.
|
|
69
|
+
#
|
|
70
|
+
# The .mat file format is documented in the following link:
|
|
71
|
+
# * http://www.mathworks.com/help/pdf_doc/matlab/matfile_format.pdf
|
|
72
|
+
module Matlab
|
|
73
|
+
extend AutoloadPatch
|
|
74
|
+
|
|
75
|
+
class << self
|
|
76
|
+
# call-seq:
|
|
77
|
+
# load(mat_file_path) -> NMatrix
|
|
78
|
+
# load_mat(mat_file_path) -> NMatrix
|
|
79
|
+
#
|
|
80
|
+
# Load a .mat file and return a NMatrix corresponding to it.
|
|
81
|
+
def load_mat(file_path)
|
|
82
|
+
NMatrix::IO::Matlab::Mat5Reader.new(File.open(file_path, "rb+")).to_ruby
|
|
83
|
+
end
|
|
84
|
+
alias :load :load_mat
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
class << self
|
|
90
|
+
# call-seq:
|
|
91
|
+
# load_matlab_file(path) -> Mat5Reader
|
|
92
|
+
#
|
|
93
|
+
# * *Arguments* :
|
|
94
|
+
# - +file_path+ -> The path to a version 5 .mat file.
|
|
95
|
+
# * *Returns* :
|
|
96
|
+
# - A Mat5Reader object.
|
|
97
|
+
def load_matlab_file(file_path)
|
|
98
|
+
NMatrix::IO::Matlab::Mat5Reader.new(File.open(file_path, 'rb')).to_ruby
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
# call-seq:
|
|
102
|
+
# load_pcd_file(path) -> PointCloudReader::MetaReader
|
|
103
|
+
#
|
|
104
|
+
# * *Arguments* :
|
|
105
|
+
# - +file_path+ -> The path to a PCL PCD file.
|
|
106
|
+
# * *Returns* :
|
|
107
|
+
# - A PointCloudReader::MetaReader object with the matrix stored in its +matrix+ property
|
|
108
|
+
def load_pcd_file(file_path)
|
|
109
|
+
NMatrix::IO::PointCloudReader::MetaReader.new(file_path)
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
# Calculate the size of an NMatrix of a given shape.
|
|
113
|
+
def size(shape)
|
|
114
|
+
shape = [shape,shape] unless shape.is_a?(Array)
|
|
115
|
+
(0...shape.size).inject(1) { |x,i| x * shape[i] }
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
# Make N-D coordinate arrays for vectorized evaluations of
|
|
119
|
+
# N-D scalar/vector fields over N-D grids, given N
|
|
120
|
+
# coordinate arrays arrs. N > 1.
|
|
121
|
+
#
|
|
122
|
+
# call-seq:
|
|
123
|
+
# meshgrid(arrs) -> Array of NMatrix
|
|
124
|
+
# meshgrid(arrs, options) -> Array of NMatrix
|
|
125
|
+
#
|
|
126
|
+
# * *Arguments* :
|
|
127
|
+
# - +vectors+ -> Array of N coordinate arrays (Array or NMatrix), if any have more than one dimension they will be flatten
|
|
128
|
+
# - +options+ -> Hash with options (:sparse Boolean, false by default; :indexing Symbol, may be :ij or :xy, :xy by default)
|
|
129
|
+
# * *Returns* :
|
|
130
|
+
# - Array of N N-D NMatrixes
|
|
131
|
+
# * *Examples* :
|
|
132
|
+
# x, y = NMatrix::meshgrid([[1, [2, 3]], [4, 5]])
|
|
133
|
+
# x.to_a #<= [[1, 2, 3], [1, 2, 3]]
|
|
134
|
+
# y.to_a #<= [[4, 4, 4], [5, 5, 5]]
|
|
135
|
+
#
|
|
136
|
+
# * *Using* *options* :
|
|
137
|
+
#
|
|
138
|
+
# x, y = NMatrix::meshgrid([[[1, 2], 3], [4, 5]], sparse: true)
|
|
139
|
+
# x.to_a #<= [[1, 2, 3]]
|
|
140
|
+
# y.to_a #<= [[4], [5]]
|
|
141
|
+
#
|
|
142
|
+
# x, y = NMatrix::meshgrid([[1, 2, 3], [[4], 5]], indexing: :ij)
|
|
143
|
+
# x.to_a #<= [[1, 1], [2, 2], [3, 3]]
|
|
144
|
+
# y.to_a #<= [[4, 5], [4, 5], [4, 5]]
|
|
145
|
+
def meshgrid(vectors, options = {})
|
|
146
|
+
raise(ArgumentError, 'Expected at least 2 arrays.') if vectors.size < 2
|
|
147
|
+
options[:indexing] ||= :xy
|
|
148
|
+
raise(ArgumentError, 'Indexing must be :xy of :ij') unless [:ij, :xy].include? options[:indexing]
|
|
149
|
+
mats = vectors.map { |arr| arr.respond_to?(:flatten) ? arr.flatten : arr.to_flat_array }
|
|
150
|
+
mats[0], mats[1] = mats[1], mats[0] if options[:indexing] == :xy
|
|
151
|
+
new_dim = mats.size
|
|
152
|
+
lengths = mats.map(&:size)
|
|
153
|
+
result = mats.map.with_index do |matrix, axis|
|
|
154
|
+
if options[:sparse]
|
|
155
|
+
new_shape = Array.new(new_dim, 1)
|
|
156
|
+
new_shape[axis] = lengths[axis]
|
|
157
|
+
new_elements = matrix
|
|
158
|
+
else
|
|
159
|
+
before_axis = lengths[0...axis].reduce(:*)
|
|
160
|
+
after_axis = lengths[(axis+1)..-1].reduce(:*)
|
|
161
|
+
new_shape = lengths
|
|
162
|
+
new_elements = after_axis ? matrix.map{ |el| [el] * after_axis }.flatten : matrix
|
|
163
|
+
new_elements *= before_axis if before_axis
|
|
164
|
+
end
|
|
165
|
+
NMatrix.new(new_shape, new_elements)
|
|
166
|
+
end
|
|
167
|
+
result[0], result[1] = result[1], result[0] if options[:indexing] == :xy
|
|
168
|
+
result
|
|
169
|
+
end
|
|
170
|
+
end
|
|
171
|
+
|
|
172
|
+
# TODO: Make this actually pretty.
|
|
173
|
+
def pretty_print(q) #:nodoc:
|
|
174
|
+
if self.shape.size > 1 and self.shape[1] > 100
|
|
175
|
+
self.inspect.pretty_print(q)
|
|
176
|
+
elsif self.dim > 3 || self.dim == 1
|
|
177
|
+
self.to_a.pretty_print(q)
|
|
178
|
+
else
|
|
179
|
+
# iterate through the whole matrix and find the longest number
|
|
180
|
+
longest = Array.new(self.shape[1], 0)
|
|
181
|
+
self.each_column.with_index do |col, j|
|
|
182
|
+
col.each do |elem|
|
|
183
|
+
elem_len = elem.inspect.size
|
|
184
|
+
longest[j] = elem_len if longest[j] < elem_len
|
|
185
|
+
end
|
|
186
|
+
end
|
|
187
|
+
|
|
188
|
+
if self.dim == 3
|
|
189
|
+
q.group(0, "\n{ layers:", "}") do
|
|
190
|
+
self.each_layer.with_index do |layer,k|
|
|
191
|
+
q.group(0, "\n [\n", " ]\n") do
|
|
192
|
+
layer.each_row.with_index do |row,i|
|
|
193
|
+
q.group(0, " [", "]\n") do
|
|
194
|
+
q.seplist(self[i,0...self.shape[1],k].to_flat_array, lambda { q.text ", "}, :each_with_index) { |v,j| q.text v.inspect.rjust(longest[j]) }
|
|
195
|
+
end
|
|
196
|
+
end
|
|
197
|
+
end
|
|
198
|
+
end
|
|
199
|
+
end
|
|
200
|
+
else # dim 2
|
|
201
|
+
q.group(0, "\n[\n ", "]") do
|
|
202
|
+
self.each_row.with_index do |row, i|
|
|
203
|
+
q.group(1, " [", "]\n") do
|
|
204
|
+
q.seplist(row.to_a, -> { q.text ", " }, :each_with_index) do |v,j|
|
|
205
|
+
q.text v.inspect.rjust(longest[j])
|
|
206
|
+
end
|
|
207
|
+
end
|
|
208
|
+
q.breakable unless i + 1 == self.shape[0]
|
|
209
|
+
end
|
|
210
|
+
end
|
|
211
|
+
end
|
|
212
|
+
end
|
|
213
|
+
end
|
|
214
|
+
|
|
215
|
+
#
|
|
216
|
+
# call-seq:
|
|
217
|
+
# cast(stype, dtype, default) -> NMatrix
|
|
218
|
+
# cast(stype, dtype) -> NMatrix
|
|
219
|
+
# cast(stype) -> NMatrix
|
|
220
|
+
# cast(options) -> NMatrix
|
|
221
|
+
#
|
|
222
|
+
# This is a user-friendly helper for calling #cast_full. The easiest way to call this function is using an
|
|
223
|
+
# options hash, e.g.,
|
|
224
|
+
#
|
|
225
|
+
# n.cast(:stype => :yale, :dtype => :int64, :default => false)
|
|
226
|
+
#
|
|
227
|
+
# For list and yale, :default sets the "default value" or "init" of the matrix. List allows a bit more freedom
|
|
228
|
+
# since non-zeros are permitted. For yale, unpredictable behavior may result if the value is not false, nil, or
|
|
229
|
+
# some version of 0. Dense discards :default.
|
|
230
|
+
#
|
|
231
|
+
# dtype and stype are inferred from the matrix upon which #cast is called -- so you only really need to provide
|
|
232
|
+
# one. You can actually call this function with no arguments, in which case it functions like #clone.
|
|
233
|
+
#
|
|
234
|
+
# If your dtype is :object and you are converting from :dense to a sparse type, it is recommended that you
|
|
235
|
+
# provide a :default, as 0 may behave differently from its Float or Complex equivalent. If no option
|
|
236
|
+
# is given, Integer 0 will be used.
|
|
237
|
+
def cast(*params)
|
|
238
|
+
if (params.size > 0 && params[0].is_a?(Hash))
|
|
239
|
+
opts = {
|
|
240
|
+
:stype => self.stype,
|
|
241
|
+
:dtype => self.dtype,
|
|
242
|
+
:default => self.stype == :dense ? 0 : self.default_value
|
|
243
|
+
}.merge(params[0])
|
|
244
|
+
|
|
245
|
+
self.cast_full(opts[:stype], opts[:dtype], opts[:default])
|
|
246
|
+
else
|
|
247
|
+
params << self.stype if params.size == 0
|
|
248
|
+
params << self.dtype if params.size == 1
|
|
249
|
+
#HACK: the default value can cause an exception if dtype is not complex
|
|
250
|
+
#and default_value is. (The ruby C code apparently won't convert these.)
|
|
251
|
+
#Perhaps this should be fixed in the C code (in rubyval_to_cval).
|
|
252
|
+
default_value = maybe_get_noncomplex_default_value(params[1])
|
|
253
|
+
params << (self.stype == :dense ? 0 : default_value) if params.size == 2
|
|
254
|
+
self.cast_full(*params)
|
|
255
|
+
end
|
|
256
|
+
|
|
257
|
+
end
|
|
258
|
+
|
|
259
|
+
|
|
260
|
+
#
|
|
261
|
+
# call-seq:
|
|
262
|
+
# rows -> Integer
|
|
263
|
+
#
|
|
264
|
+
# This shortcut use #shape to return the number of rows (the first dimension)
|
|
265
|
+
# of the matrix.
|
|
266
|
+
#
|
|
267
|
+
def rows
|
|
268
|
+
shape[0]
|
|
269
|
+
end
|
|
270
|
+
|
|
271
|
+
#
|
|
272
|
+
# call-seq:
|
|
273
|
+
# cols -> Integer
|
|
274
|
+
#
|
|
275
|
+
# This shortcut use #shape to return the number of columns (the second
|
|
276
|
+
# dimension) of the matrix.
|
|
277
|
+
#
|
|
278
|
+
def cols
|
|
279
|
+
shape[1]
|
|
280
|
+
end
|
|
281
|
+
|
|
282
|
+
# Return the main diagonal or antidiagonal a matrix. Only works with 2D matrices.
|
|
283
|
+
#
|
|
284
|
+
# == Arguments
|
|
285
|
+
#
|
|
286
|
+
# * +main_diagonal+ - Defaults to true. If passed 'false', then will return the
|
|
287
|
+
# antidiagonal of the matrix.
|
|
288
|
+
#
|
|
289
|
+
# == References
|
|
290
|
+
#
|
|
291
|
+
# * http://en.wikipedia.org/wiki/Main_diagonal
|
|
292
|
+
def diagonal main_diagonal=true
|
|
293
|
+
diag_size = [cols, rows].min
|
|
294
|
+
diag = NMatrix.new [diag_size], dtype: dtype
|
|
295
|
+
|
|
296
|
+
if main_diagonal
|
|
297
|
+
0.upto(diag_size-1) do |i|
|
|
298
|
+
diag[i] = self[i,i]
|
|
299
|
+
end
|
|
300
|
+
else
|
|
301
|
+
row = 0
|
|
302
|
+
(diag_size-1).downto(0) do |col|
|
|
303
|
+
diag[row] = self[row,col]
|
|
304
|
+
row += 1
|
|
305
|
+
end
|
|
306
|
+
end
|
|
307
|
+
|
|
308
|
+
diag
|
|
309
|
+
end
|
|
310
|
+
|
|
311
|
+
#
|
|
312
|
+
# call-seq:
|
|
313
|
+
# to_hash -> Hash
|
|
314
|
+
#
|
|
315
|
+
# Create a Ruby Hash from an NMatrix.
|
|
316
|
+
#
|
|
317
|
+
def to_hash
|
|
318
|
+
if stype == :yale
|
|
319
|
+
h = {}
|
|
320
|
+
each_stored_with_indices do |val,i,j|
|
|
321
|
+
next if val == 0 # Don't bother storing the diagonal zero values -- only non-zeros.
|
|
322
|
+
if h.has_key?(i)
|
|
323
|
+
h[i][j] = val
|
|
324
|
+
else
|
|
325
|
+
h[i] = {j => val}
|
|
326
|
+
end
|
|
327
|
+
end
|
|
328
|
+
h
|
|
329
|
+
else # dense and list should use a C internal function.
|
|
330
|
+
# FIXME: Write a C internal to_h function.
|
|
331
|
+
m = stype == :dense ? self.cast(:list, self.dtype) : self
|
|
332
|
+
m.__list_to_hash__
|
|
333
|
+
end
|
|
334
|
+
end
|
|
335
|
+
alias :to_h :to_hash
|
|
336
|
+
|
|
337
|
+
|
|
338
|
+
def inspect #:nodoc:
|
|
339
|
+
original_inspect = super()
|
|
340
|
+
original_inspect = original_inspect[0...original_inspect.size-1]
|
|
341
|
+
original_inspect + " " + inspect_helper.join(" ") + ">"
|
|
342
|
+
end
|
|
343
|
+
|
|
344
|
+
def __yale_ary__to_s(sym) #:nodoc:
|
|
345
|
+
ary = self.send("__yale_#{sym.to_s}__".to_sym)
|
|
346
|
+
|
|
347
|
+
'[' + ary.collect { |a| a ? a : 'nil'}.join(',') + ']'
|
|
348
|
+
end
|
|
349
|
+
|
|
350
|
+
|
|
351
|
+
# call-seq:
|
|
352
|
+
# integer_dtype?() -> Boolean
|
|
353
|
+
#
|
|
354
|
+
# Checks if dtype is an integer type
|
|
355
|
+
#
|
|
356
|
+
def integer_dtype?
|
|
357
|
+
[:byte, :int8, :int16, :int32, :int64].include?(self.dtype)
|
|
358
|
+
end
|
|
359
|
+
|
|
360
|
+
# call-seq:
|
|
361
|
+
# float_dtype?() -> Boolean
|
|
362
|
+
#
|
|
363
|
+
# Checks if dtype is a floating point type
|
|
364
|
+
#
|
|
365
|
+
def float_dtype?
|
|
366
|
+
[:float32, :float64].include?(dtype)
|
|
367
|
+
end
|
|
368
|
+
|
|
369
|
+
##
|
|
370
|
+
# call-seq:
|
|
371
|
+
# complex_dtype?() -> Boolean
|
|
372
|
+
#
|
|
373
|
+
# Checks if dtype is a complex type
|
|
374
|
+
#
|
|
375
|
+
def complex_dtype?
|
|
376
|
+
[:complex64, :complex128].include?(self.dtype)
|
|
377
|
+
end
|
|
378
|
+
|
|
379
|
+
##
|
|
380
|
+
# call-seq:
|
|
381
|
+
#
|
|
382
|
+
# object_dtype?() -> Boolean
|
|
383
|
+
#
|
|
384
|
+
# Checks if dtype is a ruby object
|
|
385
|
+
def object_dtype?
|
|
386
|
+
dtype == :object
|
|
387
|
+
end
|
|
388
|
+
|
|
389
|
+
|
|
390
|
+
#
|
|
391
|
+
# call-seq:
|
|
392
|
+
# to_f -> Float
|
|
393
|
+
#
|
|
394
|
+
# Converts an nmatrix with a single element (but any number of dimensions)
|
|
395
|
+
# to a float.
|
|
396
|
+
#
|
|
397
|
+
# Raises an IndexError if the matrix does not have just a single element.
|
|
398
|
+
#
|
|
399
|
+
def to_f
|
|
400
|
+
raise IndexError, 'to_f only valid for matrices with a single element' unless shape.all? { |e| e == 1 }
|
|
401
|
+
self[*Array.new(shape.size, 0)]
|
|
402
|
+
end
|
|
403
|
+
|
|
404
|
+
#
|
|
405
|
+
# call-seq:
|
|
406
|
+
# to_flat_array -> Array
|
|
407
|
+
# to_flat_a -> Array
|
|
408
|
+
#
|
|
409
|
+
# Converts an NMatrix to a one-dimensional Ruby Array.
|
|
410
|
+
#
|
|
411
|
+
def to_flat_array
|
|
412
|
+
ary = Array.new(self.size)
|
|
413
|
+
self.each.with_index { |v,i| ary[i] = v }
|
|
414
|
+
ary
|
|
415
|
+
end
|
|
416
|
+
alias :to_flat_a :to_flat_array
|
|
417
|
+
|
|
418
|
+
#
|
|
419
|
+
# call-seq:
|
|
420
|
+
# size -> Integer
|
|
421
|
+
#
|
|
422
|
+
# Returns the total size of the NMatrix based on its shape.
|
|
423
|
+
#
|
|
424
|
+
def size
|
|
425
|
+
NMatrix.size(self.shape)
|
|
426
|
+
end
|
|
427
|
+
|
|
428
|
+
|
|
429
|
+
def to_s #:nodoc:
|
|
430
|
+
self.to_flat_array.to_s
|
|
431
|
+
end
|
|
432
|
+
|
|
433
|
+
#
|
|
434
|
+
# call-seq:
|
|
435
|
+
# nvector? -> true or false
|
|
436
|
+
#
|
|
437
|
+
# Shortcut function for determining whether the effective dimension is less than the dimension.
|
|
438
|
+
# Useful when we take slices of n-dimensional matrices where n > 2.
|
|
439
|
+
#
|
|
440
|
+
def nvector?
|
|
441
|
+
self.effective_dim < self.dim
|
|
442
|
+
end
|
|
443
|
+
|
|
444
|
+
#
|
|
445
|
+
# call-seq:
|
|
446
|
+
# vector? -> true or false
|
|
447
|
+
#
|
|
448
|
+
# Shortcut function for determining whether the effective dimension is 1. See also #nvector?
|
|
449
|
+
#
|
|
450
|
+
def vector?
|
|
451
|
+
self.effective_dim == 1
|
|
452
|
+
end
|
|
453
|
+
|
|
454
|
+
|
|
455
|
+
#
|
|
456
|
+
# call-seq:
|
|
457
|
+
# to_a -> Array
|
|
458
|
+
#
|
|
459
|
+
# Converts an NMatrix to an array of arrays, or an NMatrix of effective dimension 1 to an array.
|
|
460
|
+
#
|
|
461
|
+
# Does not yet work for dimensions > 2
|
|
462
|
+
def to_a(dimen=nil)
|
|
463
|
+
if self.dim == 2
|
|
464
|
+
|
|
465
|
+
return self.to_flat_a if self.shape[0] == 1
|
|
466
|
+
|
|
467
|
+
ary = []
|
|
468
|
+
begin
|
|
469
|
+
self.each_row do |row|
|
|
470
|
+
ary << row.to_flat_a
|
|
471
|
+
end
|
|
472
|
+
#rescue NotImplementedError # Oops. Try copying instead
|
|
473
|
+
# self.each_row(:copy) do |row|
|
|
474
|
+
# ary << row.to_a.flatten
|
|
475
|
+
# end
|
|
476
|
+
end
|
|
477
|
+
ary
|
|
478
|
+
else
|
|
479
|
+
to_a_rec(0)
|
|
480
|
+
end
|
|
481
|
+
end
|
|
482
|
+
|
|
483
|
+
|
|
484
|
+
#
|
|
485
|
+
# call-seq:
|
|
486
|
+
# rank(dimension, row_or_column_number) -> NMatrix
|
|
487
|
+
# rank(dimension, row_or_column_number, :reference) -> NMatrix reference slice
|
|
488
|
+
#
|
|
489
|
+
# Returns the rank (e.g., row, column, or layer) specified, using slicing by copy as default.
|
|
490
|
+
#
|
|
491
|
+
# See @row (dimension = 0), @column (dimension = 1)
|
|
492
|
+
def rank(shape_idx, rank_idx, meth = :copy)
|
|
493
|
+
|
|
494
|
+
if shape_idx > (self.dim-1)
|
|
495
|
+
raise(RangeError, "#rank call was out of bounds")
|
|
496
|
+
end
|
|
497
|
+
|
|
498
|
+
params = Array.new(self.dim)
|
|
499
|
+
params.each.with_index do |v,d|
|
|
500
|
+
params[d] = d == shape_idx ? rank_idx : 0...self.shape[d]
|
|
501
|
+
end
|
|
502
|
+
|
|
503
|
+
meth == :reference ? self[*params] : self.slice(*params)
|
|
504
|
+
end
|
|
505
|
+
|
|
506
|
+
#
|
|
507
|
+
# call-seq:
|
|
508
|
+
# column(column_number) -> NMatrix
|
|
509
|
+
# column(column_number, get_by) -> NMatrix
|
|
510
|
+
#
|
|
511
|
+
# Returns the column specified. Uses slicing by copy as default.
|
|
512
|
+
#
|
|
513
|
+
# * *Arguments* :
|
|
514
|
+
# - +column_number+ -> Integer.
|
|
515
|
+
# - +get_by+ -> Type of slicing to use, +:copy+ or +:reference+.
|
|
516
|
+
# * *Returns* :
|
|
517
|
+
# - A NMatrix representing the requested column as a column vector.
|
|
518
|
+
#
|
|
519
|
+
# Examples:
|
|
520
|
+
#
|
|
521
|
+
# m = NMatrix.new(2, [1, 4, 9, 14], :int32) # => 1 4
|
|
522
|
+
# 9 14
|
|
523
|
+
#
|
|
524
|
+
# m.column(1) # => 4
|
|
525
|
+
# 14
|
|
526
|
+
#
|
|
527
|
+
def column(column_number, get_by = :copy)
|
|
528
|
+
rank(1, column_number, get_by)
|
|
529
|
+
end
|
|
530
|
+
|
|
531
|
+
alias :col :column
|
|
532
|
+
|
|
533
|
+
#
|
|
534
|
+
# call-seq:
|
|
535
|
+
# row(row_number) -> NMatrix
|
|
536
|
+
# row(row_number, get_by) -> NMatrix
|
|
537
|
+
#
|
|
538
|
+
# * *Arguments* :
|
|
539
|
+
# - +row_number+ -> Integer.
|
|
540
|
+
# - +get_by+ -> Type of slicing to use, +:copy+ or +:reference+.
|
|
541
|
+
# * *Returns* :
|
|
542
|
+
# - An NMatrix representing the requested row as a row vector.
|
|
543
|
+
#
|
|
544
|
+
def row(row_number, get_by = :copy)
|
|
545
|
+
rank(0, row_number, get_by)
|
|
546
|
+
end
|
|
547
|
+
|
|
548
|
+
#
|
|
549
|
+
# call-seq:
|
|
550
|
+
# last -> Element of self.dtype
|
|
551
|
+
#
|
|
552
|
+
# Returns the last element stored in an NMatrix
|
|
553
|
+
#
|
|
554
|
+
def last
|
|
555
|
+
self[*Array.new(self.dim, -1)]
|
|
556
|
+
end
|
|
557
|
+
|
|
558
|
+
|
|
559
|
+
#
|
|
560
|
+
# call-seq:
|
|
561
|
+
# reshape(new_shape) -> NMatrix
|
|
562
|
+
#
|
|
563
|
+
# Clone a matrix, changing the shape in the process. Note that this function does not do a resize; the product of
|
|
564
|
+
# the new and old shapes' components must be equal.
|
|
565
|
+
#
|
|
566
|
+
# * *Arguments* :
|
|
567
|
+
# - +new_shape+ -> Array of positive Integers.
|
|
568
|
+
# * *Returns* :
|
|
569
|
+
# - A copy with a different shape.
|
|
570
|
+
#
|
|
571
|
+
def reshape new_shape,*shapes
|
|
572
|
+
if new_shape.is_a?Integer
|
|
573
|
+
newer_shape = [new_shape]+shapes
|
|
574
|
+
else # new_shape is an Array
|
|
575
|
+
newer_shape = new_shape
|
|
576
|
+
end
|
|
577
|
+
t = reshape_clone_structure(newer_shape)
|
|
578
|
+
left_params = [:*]*newer_shape.size
|
|
579
|
+
right_params = [:*]*self.shape.size
|
|
580
|
+
t[*left_params] = self[*right_params]
|
|
581
|
+
t
|
|
582
|
+
end
|
|
583
|
+
|
|
584
|
+
|
|
585
|
+
#
|
|
586
|
+
# call-seq:
|
|
587
|
+
# reshape!(new_shape) -> NMatrix
|
|
588
|
+
# reshape! new_shape -> NMatrix
|
|
589
|
+
#
|
|
590
|
+
# Reshapes the matrix (in-place) to the desired shape. Note that this function does not do a resize; the product of
|
|
591
|
+
# the new and old shapes' components must be equal.
|
|
592
|
+
#
|
|
593
|
+
# * *Arguments* :
|
|
594
|
+
# - +new_shape+ -> Array of positive Integer.
|
|
595
|
+
#
|
|
596
|
+
def reshape! new_shape,*shapes
|
|
597
|
+
if self.is_ref?
|
|
598
|
+
raise(ArgumentError, "This operation cannot be performed on reference slices")
|
|
599
|
+
else
|
|
600
|
+
if new_shape.is_a?Integer
|
|
601
|
+
shape = [new_shape]+shapes
|
|
602
|
+
else # new_shape is an Array
|
|
603
|
+
shape = new_shape
|
|
604
|
+
end
|
|
605
|
+
self.reshape_bang(shape)
|
|
606
|
+
end
|
|
607
|
+
end
|
|
608
|
+
|
|
609
|
+
#
|
|
610
|
+
# call-seq:
|
|
611
|
+
# transpose -> NMatrix
|
|
612
|
+
# transpose(permutation) -> NMatrix
|
|
613
|
+
#
|
|
614
|
+
# Clone a matrix, transposing it in the process. If the matrix is two-dimensional, the permutation is taken to be [1,0]
|
|
615
|
+
# automatically (switch dimension 0 with dimension 1). If the matrix is n-dimensional, you must provide a permutation
|
|
616
|
+
# of +0...n+.
|
|
617
|
+
#
|
|
618
|
+
# * *Arguments* :
|
|
619
|
+
# - +permutation+ -> Optional Array giving a permutation.
|
|
620
|
+
# * *Returns* :
|
|
621
|
+
# - A copy of the matrix, but transposed.
|
|
622
|
+
#
|
|
623
|
+
def transpose(permute = nil)
|
|
624
|
+
if permute.nil?
|
|
625
|
+
if self.dim == 1
|
|
626
|
+
return self.clone
|
|
627
|
+
elsif self.dim == 2
|
|
628
|
+
new_shape = [self.shape[1], self.shape[0]]
|
|
629
|
+
else
|
|
630
|
+
raise(ArgumentError, "need permutation array of size #{self.dim}")
|
|
631
|
+
end
|
|
632
|
+
elsif !permute.is_a?(Array) || permute.sort.uniq != (0...self.dim).to_a
|
|
633
|
+
raise(ArgumentError, "invalid permutation array")
|
|
634
|
+
else
|
|
635
|
+
# Figure out the new shape based on the permutation given as an argument.
|
|
636
|
+
new_shape = permute.map { |p| self.shape[p] }
|
|
637
|
+
end
|
|
638
|
+
|
|
639
|
+
if self.dim > 2 # FIXME: For dense, several of these are basically equivalent to reshape.
|
|
640
|
+
|
|
641
|
+
# Make the new data structure.
|
|
642
|
+
t = self.reshape_clone_structure(new_shape)
|
|
643
|
+
|
|
644
|
+
self.each_stored_with_indices do |v,*indices|
|
|
645
|
+
p_indices = permute.map { |p| indices[p] }
|
|
646
|
+
t[*p_indices] = v
|
|
647
|
+
end
|
|
648
|
+
t
|
|
649
|
+
elsif self.list? # TODO: Need a C list transposition algorithm.
|
|
650
|
+
# Make the new data structure.
|
|
651
|
+
t = self.reshape_clone_structure(new_shape)
|
|
652
|
+
|
|
653
|
+
self.each_column.with_index do |col,j|
|
|
654
|
+
t[j,:*] = col.to_flat_array
|
|
655
|
+
end
|
|
656
|
+
t
|
|
657
|
+
else
|
|
658
|
+
# Call C versions of Yale and List transpose, which do their own copies
|
|
659
|
+
if jruby?
|
|
660
|
+
nmatrix = NMatrix.new :copy
|
|
661
|
+
nmatrix.shape = [@shape[1],@shape[0]]
|
|
662
|
+
twoDMat = self.twoDMat.transpose
|
|
663
|
+
nmatrix.s = ArrayRealVector.new(ArrayGenerator.getArrayDouble(twoDMat.getData(), shape[1],shape[0]))
|
|
664
|
+
return nmatrix
|
|
665
|
+
else
|
|
666
|
+
self.clone_transpose
|
|
667
|
+
end
|
|
668
|
+
end
|
|
669
|
+
end
|
|
670
|
+
|
|
671
|
+
|
|
672
|
+
# call-seq:
|
|
673
|
+
# matrix1.concat(*m2) -> NMatrix
|
|
674
|
+
# matrix1.concat(*m2, rank) -> NMatrix
|
|
675
|
+
# matrix1.hconcat(*m2) -> NMatrix
|
|
676
|
+
# matrix1.vconcat(*m2) -> NMatrix
|
|
677
|
+
# matrix1.dconcat(*m3) -> NMatrix
|
|
678
|
+
#
|
|
679
|
+
# Joins two matrices together into a new larger matrix. Attempts to determine
|
|
680
|
+
# which direction to concatenate on by looking for the first common element
|
|
681
|
+
# of the matrix +shape+ in reverse. In other words, concatenating two columns
|
|
682
|
+
# together without supplying +rank+ will glue them into an n x 2 matrix.
|
|
683
|
+
#
|
|
684
|
+
# You can also use hconcat, vconcat, and dconcat for the first three ranks.
|
|
685
|
+
# concat performs an hconcat when no rank argument is provided.
|
|
686
|
+
#
|
|
687
|
+
# The two matrices must have the same +dim+.
|
|
688
|
+
#
|
|
689
|
+
# * *Arguments* :
|
|
690
|
+
# - +matrices+ -> one or more matrices
|
|
691
|
+
# - +rank+ -> Integer (for rank); alternatively, may use :row, :column, or
|
|
692
|
+
# :layer for 0, 1, 2, respectively
|
|
693
|
+
def concat(*matrices)
|
|
694
|
+
rank = nil
|
|
695
|
+
rank = matrices.pop unless matrices.last.is_a?(NMatrix)
|
|
696
|
+
|
|
697
|
+
# Find the first matching dimension and concatenate along that (unless rank is specified)
|
|
698
|
+
if rank.nil?
|
|
699
|
+
rank = self.dim-1
|
|
700
|
+
self.shape.reverse_each.with_index do |s,i|
|
|
701
|
+
matrices.each do |m|
|
|
702
|
+
if m.shape[i] != s
|
|
703
|
+
rank -= 1
|
|
704
|
+
break
|
|
705
|
+
end
|
|
706
|
+
end
|
|
707
|
+
end
|
|
708
|
+
elsif rank.is_a?(Symbol) # Convert to numeric
|
|
709
|
+
rank = {:row => 0, :column => 1, :col => 1, :lay => 2, :layer => 2}[rank]
|
|
710
|
+
end
|
|
711
|
+
|
|
712
|
+
# Need to figure out the new shape.
|
|
713
|
+
new_shape = self.shape.dup
|
|
714
|
+
new_shape[rank] = matrices.inject(self.shape[rank]) { |total,m| total + m.shape[rank] }
|
|
715
|
+
|
|
716
|
+
# Now figure out the options for constructing the concatenated matrix.
|
|
717
|
+
opts = {stype: self.stype, default: self.default_value, dtype: self.dtype}
|
|
718
|
+
if self.yale?
|
|
719
|
+
# We can generally predict the new capacity for Yale. Subtract out the number of rows
|
|
720
|
+
# for each matrix being concatenated, and then add in the number of rows for the new
|
|
721
|
+
# shape. That takes care of the diagonal. The rest of the capacity is represented by
|
|
722
|
+
# the non-diagonal non-default values.
|
|
723
|
+
new_cap = matrices.inject(self.capacity - self.shape[0]) do |total,m|
|
|
724
|
+
total + m.capacity - m.shape[0]
|
|
725
|
+
end - self.shape[0] + new_shape[0]
|
|
726
|
+
opts = {capacity: new_cap}.merge(opts)
|
|
727
|
+
end
|
|
728
|
+
|
|
729
|
+
# Do the actual construction.
|
|
730
|
+
n = NMatrix.new(new_shape, opts)
|
|
731
|
+
|
|
732
|
+
# Figure out where to start concatenation. We don't know where it will end,
|
|
733
|
+
# because each matrix may have own size along concat dimension.
|
|
734
|
+
pos = Array.new(self.dim) { 0 }
|
|
735
|
+
|
|
736
|
+
matrices.unshift(self)
|
|
737
|
+
matrices.each do |m|
|
|
738
|
+
# Figure out where to start and stop the concatenation. We'll use
|
|
739
|
+
# NMatrices instead of Arrays because then we can do elementwise addition.
|
|
740
|
+
ranges = m.shape.map.with_index { |s,i| pos[i]...(pos[i] + s) }
|
|
741
|
+
|
|
742
|
+
n[*ranges] = m
|
|
743
|
+
|
|
744
|
+
# Move over by the requisite amount
|
|
745
|
+
pos[rank] = pos[rank] + m.shape[rank]
|
|
746
|
+
end
|
|
747
|
+
|
|
748
|
+
n
|
|
749
|
+
end
|
|
750
|
+
|
|
751
|
+
# Horizontal concatenation with +matrices+.
|
|
752
|
+
def hconcat(*matrices)
|
|
753
|
+
concat(*matrices, :column)
|
|
754
|
+
end
|
|
755
|
+
|
|
756
|
+
# Vertical concatenation with +matrices+.
|
|
757
|
+
def vconcat(*matrices)
|
|
758
|
+
concat(*matrices, :row)
|
|
759
|
+
end
|
|
760
|
+
|
|
761
|
+
# Depth concatenation with +matrices+.
|
|
762
|
+
def dconcat(*matrices)
|
|
763
|
+
concat(*matrices, :layer)
|
|
764
|
+
end
|
|
765
|
+
|
|
766
|
+
|
|
767
|
+
#
|
|
768
|
+
# call-seq:
|
|
769
|
+
# upper_triangle -> NMatrix
|
|
770
|
+
# upper_triangle(k) -> NMatrix
|
|
771
|
+
# triu -> NMatrix
|
|
772
|
+
# triu(k) -> NMatrix
|
|
773
|
+
#
|
|
774
|
+
# Returns the upper triangular portion of a matrix. This is analogous to the +triu+ method
|
|
775
|
+
# in MATLAB.
|
|
776
|
+
#
|
|
777
|
+
# * *Arguments* :
|
|
778
|
+
# - +k+ -> Positive integer. How many extra diagonals to include in the upper triangular portion.
|
|
779
|
+
#
|
|
780
|
+
def upper_triangle(k = 0)
|
|
781
|
+
raise(NotImplementedError, "only implemented for 2D matrices") if self.shape.size > 2
|
|
782
|
+
|
|
783
|
+
t = self.clone_structure
|
|
784
|
+
(0...self.shape[0]).each do |i|
|
|
785
|
+
if i - k < 0
|
|
786
|
+
t[i, :*] = self[i, :*]
|
|
787
|
+
else
|
|
788
|
+
t[i, 0...(i-k)] = 0
|
|
789
|
+
t[i, (i-k)...self.shape[1]] = self[i, (i-k)...self.shape[1]]
|
|
790
|
+
end
|
|
791
|
+
end
|
|
792
|
+
t
|
|
793
|
+
end
|
|
794
|
+
alias :triu :upper_triangle
|
|
795
|
+
|
|
796
|
+
|
|
797
|
+
#
|
|
798
|
+
# call-seq:
|
|
799
|
+
# upper_triangle! -> NMatrix
|
|
800
|
+
# upper_triangle!(k) -> NMatrix
|
|
801
|
+
# triu! -> NMatrix
|
|
802
|
+
# triu!(k) -> NMatrix
|
|
803
|
+
#
|
|
804
|
+
# Deletes the lower triangular portion of the matrix (in-place) so only the upper portion remains.
|
|
805
|
+
#
|
|
806
|
+
# * *Arguments* :
|
|
807
|
+
# - +k+ -> Integer. How many extra diagonals to include in the deletion.
|
|
808
|
+
#
|
|
809
|
+
def upper_triangle!(k = 0)
|
|
810
|
+
raise(NotImplementedError, "only implemented for 2D matrices") if self.shape.size > 2
|
|
811
|
+
|
|
812
|
+
(0...self.shape[0]).each do |i|
|
|
813
|
+
if i - k >= 0
|
|
814
|
+
self[i, 0...(i-k)] = 0
|
|
815
|
+
end
|
|
816
|
+
end
|
|
817
|
+
self
|
|
818
|
+
end
|
|
819
|
+
alias :triu! :upper_triangle!
|
|
820
|
+
|
|
821
|
+
|
|
822
|
+
#
|
|
823
|
+
# call-seq:
|
|
824
|
+
# lower_triangle -> NMatrix
|
|
825
|
+
# lower_triangle(k) -> NMatrix
|
|
826
|
+
# tril -> NMatrix
|
|
827
|
+
# tril(k) -> NMatrix
|
|
828
|
+
#
|
|
829
|
+
# Returns the lower triangular portion of a matrix. This is analogous to the +tril+ method
|
|
830
|
+
# in MATLAB.
|
|
831
|
+
#
|
|
832
|
+
# * *Arguments* :
|
|
833
|
+
# - +k+ -> Integer. How many extra diagonals to include in the lower triangular portion.
|
|
834
|
+
#
|
|
835
|
+
def lower_triangle(k = 0)
|
|
836
|
+
raise(NotImplementedError, "only implemented for 2D matrices") if self.shape.size > 2
|
|
837
|
+
|
|
838
|
+
t = self.clone_structure
|
|
839
|
+
(0...self.shape[0]).each do |i|
|
|
840
|
+
if i + k >= shape[0]
|
|
841
|
+
t[i, :*] = self[i, :*]
|
|
842
|
+
else
|
|
843
|
+
t[i, (i+k+1)...self.shape[1]] = 0
|
|
844
|
+
t[i, 0..(i+k)] = self[i, 0..(i+k)]
|
|
845
|
+
end
|
|
846
|
+
end
|
|
847
|
+
t
|
|
848
|
+
end
|
|
849
|
+
alias :tril :lower_triangle
|
|
850
|
+
|
|
851
|
+
|
|
852
|
+
#
|
|
853
|
+
# call-seq:
|
|
854
|
+
# lower_triangle! -> NMatrix
|
|
855
|
+
# lower_triangle!(k) -> NMatrix
|
|
856
|
+
# tril! -> NMatrix
|
|
857
|
+
# tril!(k) -> NMatrix
|
|
858
|
+
#
|
|
859
|
+
# Deletes the upper triangular portion of the matrix (in-place) so only the lower portion remains.
|
|
860
|
+
#
|
|
861
|
+
# * *Arguments* :
|
|
862
|
+
# - +k+ -> Integer. How many extra diagonals to include in the deletion.
|
|
863
|
+
#
|
|
864
|
+
def lower_triangle!(k = 0)
|
|
865
|
+
raise(NotImplementedError, "only implemented for 2D matrices") if self.shape.size > 2
|
|
866
|
+
|
|
867
|
+
(0...self.shape[0]).each do |i|
|
|
868
|
+
if i + k < shape[0]
|
|
869
|
+
self[i, (i+k+1)...self.shape[1]] = 0
|
|
870
|
+
end
|
|
871
|
+
end
|
|
872
|
+
self
|
|
873
|
+
end
|
|
874
|
+
alias :tril! :lower_triangle!
|
|
875
|
+
|
|
876
|
+
|
|
877
|
+
#
|
|
878
|
+
# call-seq:
|
|
879
|
+
# layer(layer_number) -> NMatrix
|
|
880
|
+
# row(layer_number, get_by) -> NMatrix
|
|
881
|
+
#
|
|
882
|
+
# * *Arguments* :
|
|
883
|
+
# - +layer_number+ -> Integer.
|
|
884
|
+
# - +get_by+ -> Type of slicing to use, +:copy+ or +:reference+.
|
|
885
|
+
# * *Returns* :
|
|
886
|
+
# - A NMatrix representing the requested layer as a layer vector.
|
|
887
|
+
#
|
|
888
|
+
def layer(layer_number, get_by = :copy)
|
|
889
|
+
layer = rank(2, layer_number, get_by)
|
|
890
|
+
|
|
891
|
+
if jruby?
|
|
892
|
+
nmatrix = NMatrix.new :copy
|
|
893
|
+
nmatrix.shape = layer.shape
|
|
894
|
+
nmatrix.s = layer.s
|
|
895
|
+
return nmatrix
|
|
896
|
+
else
|
|
897
|
+
layer
|
|
898
|
+
end
|
|
899
|
+
|
|
900
|
+
end
|
|
901
|
+
|
|
902
|
+
|
|
903
|
+
|
|
904
|
+
#
|
|
905
|
+
# call-seq:
|
|
906
|
+
# shuffle! -> ...
|
|
907
|
+
# shuffle!(random: rng) -> ...
|
|
908
|
+
#
|
|
909
|
+
# Re-arranges the contents of an NVector.
|
|
910
|
+
#
|
|
911
|
+
# TODO: Write more efficient version for Yale, list.
|
|
912
|
+
# TODO: Generalize for more dimensions.
|
|
913
|
+
def shuffle!(*args)
|
|
914
|
+
method_missing(:shuffle!, *args) if self.effective_dim > 1
|
|
915
|
+
ary = self.to_flat_a
|
|
916
|
+
ary.shuffle!(*args)
|
|
917
|
+
ary.each.with_index { |v,idx| self[idx] = v }
|
|
918
|
+
self
|
|
919
|
+
end
|
|
920
|
+
|
|
921
|
+
|
|
922
|
+
#
|
|
923
|
+
# call-seq:
|
|
924
|
+
# shuffle -> ...
|
|
925
|
+
# shuffle(rng) -> ...
|
|
926
|
+
#
|
|
927
|
+
# Re-arranges the contents of an NVector.
|
|
928
|
+
#
|
|
929
|
+
# TODO: Write more efficient version for Yale, list.
|
|
930
|
+
# TODO: Generalize for more dimensions.
|
|
931
|
+
def shuffle(*args)
|
|
932
|
+
method_missing(:shuffle!, *args) if self.effective_dim > 1
|
|
933
|
+
t = self.clone
|
|
934
|
+
t.shuffle!(*args)
|
|
935
|
+
end
|
|
936
|
+
|
|
937
|
+
|
|
938
|
+
#
|
|
939
|
+
# call-seq:
|
|
940
|
+
# sorted_indices -> Array
|
|
941
|
+
#
|
|
942
|
+
# Returns an array of the indices ordered by value sorted.
|
|
943
|
+
#
|
|
944
|
+
def sorted_indices
|
|
945
|
+
return method_missing(:sorted_indices) unless vector?
|
|
946
|
+
ary = self.to_flat_array
|
|
947
|
+
ary.each_index.sort_by { |i| ary[i] } # from: http://stackoverflow.com/a/17841159/170300
|
|
948
|
+
end
|
|
949
|
+
|
|
950
|
+
|
|
951
|
+
#
|
|
952
|
+
# call-seq:
|
|
953
|
+
# binned_sorted_indices -> Array
|
|
954
|
+
#
|
|
955
|
+
# Returns an array of arrays of indices ordered by value sorted. Functions basically like +sorted_indices+, but
|
|
956
|
+
# groups indices together for those values that are the same.
|
|
957
|
+
#
|
|
958
|
+
def binned_sorted_indices
|
|
959
|
+
return method_missing(:sorted_indices) unless vector?
|
|
960
|
+
ary = self.to_flat_array
|
|
961
|
+
ary2 = []
|
|
962
|
+
last_bin = ary.each_index.sort_by { |i| [ary[i]] }.inject([]) do |result, element|
|
|
963
|
+
if result.empty? || ary[result[-1]] == ary[element]
|
|
964
|
+
result << element
|
|
965
|
+
else
|
|
966
|
+
ary2 << result
|
|
967
|
+
[element]
|
|
968
|
+
end
|
|
969
|
+
end
|
|
970
|
+
ary2 << last_bin unless last_bin.empty?
|
|
971
|
+
ary2
|
|
972
|
+
end
|
|
973
|
+
|
|
974
|
+
|
|
975
|
+
def method_missing name, *args, &block #:nodoc:
|
|
976
|
+
if name.to_s =~ /^__list_elementwise_.*__$/
|
|
977
|
+
raise NotImplementedError, "requested undefined list matrix element-wise operation"
|
|
978
|
+
elsif name.to_s =~ /^__yale_scalar_.*__$/
|
|
979
|
+
raise NotImplementedError, "requested undefined yale scalar element-wise operation"
|
|
980
|
+
else
|
|
981
|
+
super(name, *args, &block)
|
|
982
|
+
end
|
|
983
|
+
end
|
|
984
|
+
|
|
985
|
+
|
|
986
|
+
def respond_to?(method, include_all = false) #:nodoc:
|
|
987
|
+
if [:shuffle, :shuffle!, :each_with_index, :sorted_indices, :binned_sorted_indices, :nrm2, :asum].include?(method.intern) # vector-only methods
|
|
988
|
+
return vector?
|
|
989
|
+
elsif [:each_layer, :layer].include?(method.intern) # 3-or-more dimensions only
|
|
990
|
+
return dim > 2
|
|
991
|
+
else
|
|
992
|
+
super
|
|
993
|
+
end
|
|
994
|
+
end
|
|
995
|
+
|
|
996
|
+
|
|
997
|
+
#
|
|
998
|
+
# call-seq:
|
|
999
|
+
# inject -> symbol
|
|
1000
|
+
#
|
|
1001
|
+
# This overrides the inject function to use map_stored for yale matrices
|
|
1002
|
+
#
|
|
1003
|
+
def inject(sym)
|
|
1004
|
+
return super(sym) unless self.yale?
|
|
1005
|
+
return self.map_stored.inject(sym)
|
|
1006
|
+
end
|
|
1007
|
+
|
|
1008
|
+
# Returns the index of the first occurence of the specified value. Returns
|
|
1009
|
+
# an array containing the position of the value, nil in case the value is not found.
|
|
1010
|
+
#
|
|
1011
|
+
def index(value)
|
|
1012
|
+
index = nil
|
|
1013
|
+
|
|
1014
|
+
self.each_with_indices do |yields|
|
|
1015
|
+
if yields.first == value
|
|
1016
|
+
yields.shift
|
|
1017
|
+
index = yields
|
|
1018
|
+
break
|
|
1019
|
+
end
|
|
1020
|
+
end
|
|
1021
|
+
|
|
1022
|
+
index
|
|
1023
|
+
end
|
|
1024
|
+
|
|
1025
|
+
#
|
|
1026
|
+
# call-seq:
|
|
1027
|
+
# clone_structure -> NMatrix
|
|
1028
|
+
#
|
|
1029
|
+
# This function is like clone, but it only copies the structure and the default value.
|
|
1030
|
+
# None of the other values are copied. It takes an optional capacity argument. This is
|
|
1031
|
+
# mostly only useful for dense, where you may not want to initialize; for other types,
|
|
1032
|
+
# you should probably use +zeros_like+.
|
|
1033
|
+
#
|
|
1034
|
+
def clone_structure(capacity = nil)
|
|
1035
|
+
opts = {stype: self.stype, default: self.default_value, dtype: self.dtype}
|
|
1036
|
+
opts = {capacity: capacity}.merge(opts) if self.yale?
|
|
1037
|
+
NMatrix.new(self.shape, opts)
|
|
1038
|
+
end
|
|
1039
|
+
|
|
1040
|
+
#
|
|
1041
|
+
# call-seq:
|
|
1042
|
+
# repeat(count, axis) -> NMatrix
|
|
1043
|
+
#
|
|
1044
|
+
# * *Arguments* :
|
|
1045
|
+
# - +count+ -> how many times NMatrix should be repeated
|
|
1046
|
+
# - +axis+ -> index of axis along which NMatrix should be repeated
|
|
1047
|
+
# * *Returns* :
|
|
1048
|
+
# - NMatrix created by repeating the existing one along an axis
|
|
1049
|
+
# * *Examples* :
|
|
1050
|
+
# m = NMatrix.new([2, 2], [1, 2, 3, 4])
|
|
1051
|
+
# m.repeat(2, 0).to_a #<= [[1, 2], [3, 4], [1, 2], [3, 4]]
|
|
1052
|
+
# m.repeat(2, 1).to_a #<= [[1, 2, 1, 2], [3, 4, 3, 4]]
|
|
1053
|
+
def repeat(count, axis)
|
|
1054
|
+
raise(ArgumentError, 'Matrix should be repeated at least 2 times.') if count < 2
|
|
1055
|
+
new_shape = shape
|
|
1056
|
+
new_shape[axis] *= count
|
|
1057
|
+
new_matrix = NMatrix.new(new_shape, dtype: dtype)
|
|
1058
|
+
slice = new_shape.map { |axis_size| 0...axis_size }
|
|
1059
|
+
start = 0
|
|
1060
|
+
count.times do
|
|
1061
|
+
slice[axis] = start...(start += shape[axis])
|
|
1062
|
+
new_matrix[*slice] = self
|
|
1063
|
+
end
|
|
1064
|
+
new_matrix
|
|
1065
|
+
end
|
|
1066
|
+
|
|
1067
|
+
# This is how you write an individual element-wise operation function:
|
|
1068
|
+
#def __list_elementwise_add__ rhs
|
|
1069
|
+
# self.__list_map_merged_stored__(rhs){ |l,r| l+r }.cast(self.stype, NMatrix.upcast(self.dtype, rhs.dtype))
|
|
1070
|
+
#end
|
|
1071
|
+
protected
|
|
1072
|
+
|
|
1073
|
+
def inspect_helper #:nodoc:
|
|
1074
|
+
ary = []
|
|
1075
|
+
ary << "shape:[#{shape.join(',')}]" << "dtype:#{dtype}" << "stype:#{stype}"
|
|
1076
|
+
|
|
1077
|
+
if stype == :yale
|
|
1078
|
+
ary << "capacity:#{capacity}"
|
|
1079
|
+
|
|
1080
|
+
# These are enabled by the DEBUG_YALE compiler flag in extconf.rb.
|
|
1081
|
+
if respond_to?(:__yale_a__)
|
|
1082
|
+
ary << "ija:#{__yale_ary__to_s(:ija)}" << "ia:#{__yale_ary__to_s(:ia)}" <<
|
|
1083
|
+
"ja:#{__yale_ary__to_s(:ja)}" << "a:#{__yale_ary__to_s(:a)}" << "d:#{__yale_ary__to_s(:d)}" <<
|
|
1084
|
+
"lu:#{__yale_ary__to_s(:lu)}" << "yale_size:#{__yale_size__}"
|
|
1085
|
+
end
|
|
1086
|
+
|
|
1087
|
+
end
|
|
1088
|
+
|
|
1089
|
+
ary
|
|
1090
|
+
end
|
|
1091
|
+
|
|
1092
|
+
|
|
1093
|
+
# Clone the structure as needed for a reshape
|
|
1094
|
+
def reshape_clone_structure(new_shape) #:nodoc:
|
|
1095
|
+
raise(ArgumentError, "reshape cannot resize; size of new and old matrices must match") unless self.size == new_shape.inject(1) { |p,i| p *= i }
|
|
1096
|
+
|
|
1097
|
+
opts = {stype: self.stype, default: self.default_value, dtype: self.dtype}
|
|
1098
|
+
if self.yale?
|
|
1099
|
+
# We can generally predict the change in capacity for Yale.
|
|
1100
|
+
opts = {capacity: self.capacity - self.shape[0] + new_shape[0]}.merge(opts)
|
|
1101
|
+
end
|
|
1102
|
+
NMatrix.new(new_shape, opts)
|
|
1103
|
+
end
|
|
1104
|
+
|
|
1105
|
+
|
|
1106
|
+
# Helper for converting a matrix into an array of arrays recursively
|
|
1107
|
+
def to_a_rec(dimen = 0) #:nodoc:
|
|
1108
|
+
return self.flat_map { |v| v } if dimen == self.dim-1
|
|
1109
|
+
|
|
1110
|
+
ary = []
|
|
1111
|
+
self.each_rank(dimen) do |sect|
|
|
1112
|
+
ary << sect.to_a_rec(dimen+1)
|
|
1113
|
+
end
|
|
1114
|
+
ary
|
|
1115
|
+
end
|
|
1116
|
+
|
|
1117
|
+
|
|
1118
|
+
# NMatrix constructor helper for sparse matrices. Uses multi-slice-setting to initialize a matrix
|
|
1119
|
+
# with a given array of initial values.
|
|
1120
|
+
def __sparse_initial_set__(ary) #:nodoc:
|
|
1121
|
+
self[0...self.shape[0],0...self.shape[1]] = ary
|
|
1122
|
+
end
|
|
1123
|
+
|
|
1124
|
+
|
|
1125
|
+
# This function assumes that the shapes of the two matrices have already
|
|
1126
|
+
# been tested and are the same.
|
|
1127
|
+
#
|
|
1128
|
+
# Called from inside NMatrix: nm_eqeq
|
|
1129
|
+
#
|
|
1130
|
+
# There are probably more efficient ways to do this, but currently it's unclear how.
|
|
1131
|
+
# We could use +each_row+, but for list matrices, it's still going to need to make a
|
|
1132
|
+
# reference to each of those rows, and that is going to require a seek.
|
|
1133
|
+
#
|
|
1134
|
+
# It might be more efficient to convert one sparse matrix type to the other with a
|
|
1135
|
+
# cast and then run the comparison. For now, let's assume that people aren't going
|
|
1136
|
+
# to be doing this very often, and we can optimize as needed.
|
|
1137
|
+
def dense_eql_sparse? m #:nodoc:
|
|
1138
|
+
m.each_with_indices do |v,*indices|
|
|
1139
|
+
return false if self[*indices] != v
|
|
1140
|
+
end
|
|
1141
|
+
|
|
1142
|
+
return true
|
|
1143
|
+
end
|
|
1144
|
+
alias :sparse_eql_sparse? :dense_eql_sparse?
|
|
1145
|
+
|
|
1146
|
+
|
|
1147
|
+
#
|
|
1148
|
+
# See the note in #cast about why this is necessary.
|
|
1149
|
+
# If this is a non-dense matrix with a complex dtype and to_dtype is
|
|
1150
|
+
# non-complex, then this will convert the default value to noncomplex.
|
|
1151
|
+
# Returns 0 if dense. Returns existing default_value if there isn't a
|
|
1152
|
+
# mismatch.
|
|
1153
|
+
#
|
|
1154
|
+
def maybe_get_noncomplex_default_value(to_dtype) #:nodoc:
|
|
1155
|
+
default_value = 0
|
|
1156
|
+
unless self.stype == :dense then
|
|
1157
|
+
if self.dtype.to_s.start_with?('complex') and not to_dtype.to_s.start_with?('complex') then
|
|
1158
|
+
default_value = self.default_value.real
|
|
1159
|
+
else
|
|
1160
|
+
default_value = self.default_value
|
|
1161
|
+
end
|
|
1162
|
+
end
|
|
1163
|
+
default_value
|
|
1164
|
+
end
|
|
1165
|
+
|
|
1166
|
+
end
|
|
1167
|
+
|
|
1168
|
+
require_relative './shortcuts.rb'
|
|
1169
|
+
require_relative './enumerate.rb'
|
|
1170
|
+
|
|
1171
|
+
require_relative './version.rb'
|
|
1172
|
+
require_relative './blas.rb'
|