nmatrix-fftw 0.2.1
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/data/complex.h +388 -0
- data/ext/nmatrix/data/data.h +652 -0
- data/ext/nmatrix/data/meta.h +64 -0
- data/ext/nmatrix/data/ruby_object.h +389 -0
- data/ext/nmatrix/math/asum.h +120 -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 +79 -0
- data/ext/nmatrix/math/laswp.h +165 -0
- data/ext/nmatrix/math/long_dtype.h +49 -0
- data/ext/nmatrix/math/math.h +745 -0
- data/ext/nmatrix/math/nrm2.h +160 -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 +332 -0
- data/ext/nmatrix/math/util.h +148 -0
- data/ext/nmatrix/nm_memory.h +60 -0
- data/ext/nmatrix/nmatrix.h +438 -0
- data/ext/nmatrix/ruby_constants.h +106 -0
- data/ext/nmatrix/storage/common.h +177 -0
- data/ext/nmatrix/storage/dense/dense.h +129 -0
- data/ext/nmatrix/storage/list/list.h +138 -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.h +203 -0
- data/ext/nmatrix/types.h +55 -0
- data/ext/nmatrix/util/io.h +115 -0
- data/ext/nmatrix/util/sl_list.h +144 -0
- data/ext/nmatrix/util/util.h +78 -0
- data/ext/nmatrix_fftw/extconf.rb +122 -0
- data/ext/nmatrix_fftw/nmatrix_fftw.cpp +274 -0
- data/lib/nmatrix/fftw.rb +343 -0
- data/spec/00_nmatrix_spec.rb +736 -0
- data/spec/01_enum_spec.rb +190 -0
- data/spec/02_slice_spec.rb +389 -0
- data/spec/03_nmatrix_monkeys_spec.rb +78 -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 +193 -0
- data/spec/elementwise_spec.rb +303 -0
- data/spec/homogeneous_spec.rb +99 -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 +149 -0
- data/spec/lapack_core_spec.rb +482 -0
- data/spec/leakcheck.rb +16 -0
- data/spec/math_spec.rb +807 -0
- data/spec/nmatrix_yale_resize_test_associations.yaml +2802 -0
- data/spec/nmatrix_yale_spec.rb +286 -0
- data/spec/plugins/fftw/fftw_spec.rb +348 -0
- data/spec/rspec_monkeys.rb +56 -0
- data/spec/rspec_spec.rb +34 -0
- data/spec/shortcuts_spec.rb +310 -0
- data/spec/slice_set_spec.rb +157 -0
- data/spec/spec_helper.rb +149 -0
- data/spec/stat_spec.rb +203 -0
- data/spec/test.pcd +20 -0
- data/spec/utm5940.mtx +83844 -0
- metadata +151 -0
data/lib/nmatrix/fftw.rb
ADDED
@@ -0,0 +1,343 @@
|
|
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
|
+
# == fftw.rb
|
25
|
+
#
|
26
|
+
# ruby file for the nmatrix-fftw gem. Loads the C extension and defines
|
27
|
+
# nice ruby interfaces for FFTW functions.
|
28
|
+
#++
|
29
|
+
|
30
|
+
require 'nmatrix/nmatrix.rb'
|
31
|
+
require "nmatrix_fftw.so"
|
32
|
+
|
33
|
+
class NMatrix
|
34
|
+
|
35
|
+
# Compute 1D FFT of the matrix using FFTW default parameters.
|
36
|
+
# @return [NMatrix] NMatrix of dtype :complex128 containing computed values.
|
37
|
+
# @example Compute 1D FFT of an NMatrix.
|
38
|
+
# nm = NMatrix.new([10],
|
39
|
+
# [
|
40
|
+
# Complex(9.32,0), Complex(44,0), Complex(125,0), Complex(34,0),
|
41
|
+
# Complex(31,0), Complex(44,0), Complex(12,0), Complex(1,0),
|
42
|
+
# Complex(53.23,0),Complex(-23.23,0)
|
43
|
+
# ], dtype: :complex128)
|
44
|
+
# nm.fft
|
45
|
+
def fft
|
46
|
+
input = self.dtype == :complex128 ? self : self.cast(dtype: :complex128)
|
47
|
+
plan = NMatrix::FFTW::Plan.new([self.size])
|
48
|
+
plan.set_input input
|
49
|
+
plan.execute
|
50
|
+
plan.output
|
51
|
+
end
|
52
|
+
|
53
|
+
# Compute 2D FFT of a 2D matrix using FFTW default parameters.
|
54
|
+
# @return [NMatrix] NMatrix of dtype :complex128 containing computed values.
|
55
|
+
def fft2
|
56
|
+
raise ShapeError, "Shape must be 2 (is #{self.shape})" if self.shape.size != 2
|
57
|
+
input = self.dtype == :complex128 ? self : self.cast(dtype: :complex128)
|
58
|
+
plan = NMatrix::FFTW::Plan.new(self.shape, dim: 2)
|
59
|
+
plan.set_input input
|
60
|
+
plan.execute
|
61
|
+
plan.output
|
62
|
+
end
|
63
|
+
|
64
|
+
module FFTW
|
65
|
+
class Plan
|
66
|
+
# Hash which holds the numerical values of constants that determine
|
67
|
+
# the kind of transform that will be computed for a real input/real
|
68
|
+
# output instance. These are one-one mappings to the respective constants
|
69
|
+
# specified in FFTW. For example, for specifying the FFTW_R2HC constant
|
70
|
+
# as the 'kind', pass the symbol :r2hc.
|
71
|
+
#
|
72
|
+
# @see http://www.fftw.org/fftw3_doc/Real_002dto_002dReal-Transform-Kinds.html#Real_002dto_002dReal-Transform-Kinds
|
73
|
+
REAL_REAL_FFT_KINDS_HASH = {
|
74
|
+
r2hc: 0,
|
75
|
+
hc2r: 1,
|
76
|
+
dht: 2,
|
77
|
+
redft00: 3,
|
78
|
+
redft01: 4,
|
79
|
+
redft10: 5,
|
80
|
+
redft11: 6,
|
81
|
+
rodft00: 7,
|
82
|
+
rodft01: 9,
|
83
|
+
rodft10: 8,
|
84
|
+
rodft11: 10
|
85
|
+
}
|
86
|
+
|
87
|
+
# Hash holding the numerical values of the flags that are passed in the
|
88
|
+
# `flags` argument of a FFTW planner routine. Multiple flags can be passed
|
89
|
+
# to one instance of the planner. Their values are OR'd ('|') and then passed.
|
90
|
+
# For example, for passing the FFTW_ESTIMATE constant, use :estimate.
|
91
|
+
#
|
92
|
+
# nmatrix-fftw supports the following flags into the planning routine:
|
93
|
+
# * :estimate - Equivalent to FFTW_ESTIMATE. Specifies that, instead of
|
94
|
+
# actual measurements of different algorithms, a simple heuristic is
|
95
|
+
# used to pick a (probably sub-optimal) plan quickly. With this flag,
|
96
|
+
# the input/output arrays are not overwritten during planning.
|
97
|
+
# * :measure - Equivalent to FFTW_MEASURE. Tells FFTW to find an optimized
|
98
|
+
# plan by actually computing several FFTs and measuring their execution
|
99
|
+
# time. Depending on your machine, this can take some time (often a few
|
100
|
+
# seconds).
|
101
|
+
# * :patient - Equivalent to FFTW_PATIENT. Like FFTW_MEASURE, but considers
|
102
|
+
# a wider range of algorithms and often produces a “more optimal” plan
|
103
|
+
# (especially for large transforms), but at the expense of several times
|
104
|
+
# longer planning time (especially for large transforms).
|
105
|
+
# * :exhaustive - Equivalent to FFTW_EXHAUSTIVE. Like FFTW_PATIENT, but
|
106
|
+
# considers an even wider range of algorithms, including many that we
|
107
|
+
# think are unlikely to be fast, to produce the most optimal plan but
|
108
|
+
# with a substantially increased planning time.
|
109
|
+
#
|
110
|
+
# @see http://www.fftw.org/fftw3_doc/Planner-Flags.html#Planner-Flags
|
111
|
+
FLAG_VALUE_HASH = {
|
112
|
+
estimate: 64,
|
113
|
+
measure: 0,
|
114
|
+
exhaustive: 8,
|
115
|
+
patient: 32
|
116
|
+
}
|
117
|
+
|
118
|
+
# Hash holding numerical values of the direction in which a :complex_complex
|
119
|
+
# type FFT should be performed.
|
120
|
+
#
|
121
|
+
# @see http://www.fftw.org/fftw3_doc/Complex-One_002dDimensional-DFTs.html#Complex-One_002dDimensional-DFTs
|
122
|
+
# (The fourth argument, sign, can be either FFTW_FORWARD (-1) or
|
123
|
+
# FFTW_BACKWARD (+1), and indicates the direction of the transform you are
|
124
|
+
# interested in; technically, it is the sign of the exponent in the transform)
|
125
|
+
FFT_DIRECTION_HASH = {
|
126
|
+
forward: -1,
|
127
|
+
backward: 1
|
128
|
+
}
|
129
|
+
|
130
|
+
# Hash holding numerical equivalents of the DFT type. Used for determining
|
131
|
+
# DFT type in C level.
|
132
|
+
DATA_TYPE_HASH = {
|
133
|
+
complex_complex: 0,
|
134
|
+
real_complex: 1,
|
135
|
+
complex_real: 2,
|
136
|
+
real_real: 3
|
137
|
+
}
|
138
|
+
|
139
|
+
# Array holding valid options that can be passed into NMatrix::FFTW::Plan
|
140
|
+
# so that invalid options aren't passed.
|
141
|
+
VALID_OPTS = [:dim, :type, :direction, :flags, :real_real_kind]
|
142
|
+
|
143
|
+
# @!attribute [r] shape
|
144
|
+
# @return [Array] Shape of the plan. Sequence of Fixnums.
|
145
|
+
attr_reader :shape
|
146
|
+
|
147
|
+
# @!attribute [r] size
|
148
|
+
# @return [Numeric] Size of the plan.
|
149
|
+
attr_reader :size
|
150
|
+
|
151
|
+
# @!attribute [r] type
|
152
|
+
# @return [Symbol] Type of the plan. Can be :complex_complex,
|
153
|
+
# :complex_real, :real_complex or :real_real
|
154
|
+
attr_reader :type
|
155
|
+
|
156
|
+
# @!attribute [r] direction
|
157
|
+
# @return [Symbol] Can be :forward of :backward. Indicates the direction
|
158
|
+
# of the transform you are interested in; technically, it is the sign of
|
159
|
+
# the exponent in the transform. Valid only for :complex_complex type.
|
160
|
+
attr_reader :direction
|
161
|
+
|
162
|
+
# @!attribute [r] flags
|
163
|
+
# @return [Array<Symbol>] Can contain one or more symbols from
|
164
|
+
# FLAG_VALUE_HASH. Determines how the planner is prepared.
|
165
|
+
# @see FLAG_VALUE_HASH
|
166
|
+
attr_reader :flags
|
167
|
+
|
168
|
+
# @!attribute [r] dim
|
169
|
+
# @return [Fixnum] Dimension of the FFT. Should be 1 for 1-D FFT, 2 for
|
170
|
+
# 2-D FFT and so on.
|
171
|
+
attr_reader :dim
|
172
|
+
|
173
|
+
# @!attribute [r] input
|
174
|
+
# @return [NMatrix] Input NMatrix. Will be valid once the
|
175
|
+
# NMatrix::FFTW::Plan#set_input method has been called.
|
176
|
+
attr_reader :input
|
177
|
+
|
178
|
+
# @!attribute [r] output
|
179
|
+
# @return [NMatrix] Output NMatrix. Will be valid once the
|
180
|
+
# NMatrix::FFTW::Plan#execute method has been called.
|
181
|
+
attr_reader :output
|
182
|
+
|
183
|
+
# @!attribute [r] real_real_kind
|
184
|
+
# @return [Symbol] Specifies the kind of real to real FFT being performed.
|
185
|
+
# This is a symbol from REAL_REAL_FFT_KINDS_HASH. Only valid when type
|
186
|
+
# of transform is of type :real_real.
|
187
|
+
# @see REAL_REAL_FFT_KINDS_HASH
|
188
|
+
# @see http://www.fftw.org/fftw3_doc/Real_002dto_002dReal-Transform-Kinds.html#Real_002dto_002dReal-Transform-Kinds
|
189
|
+
attr_reader :real_real_kind
|
190
|
+
|
191
|
+
# Create a plan for a DFT. The FFTW library requires that you first create
|
192
|
+
# a plan for performing a DFT, so that FFTW can optimize its algorithms
|
193
|
+
# according to your computer's hardware and various user supplied options.
|
194
|
+
#
|
195
|
+
# @see http://www.fftw.org/doc/Using-Plans.html
|
196
|
+
# For a comprehensive explanation of the FFTW planner.
|
197
|
+
# @param shape [Array, Fixnum] Specify the shape of the plan. For 1D
|
198
|
+
# fourier transforms this can be a single number specifying the length of
|
199
|
+
# the input. For multi-dimensional transforms, specify an Array containing
|
200
|
+
# the length of each dimension.
|
201
|
+
# @param [Hash] opts the options to create a message with.
|
202
|
+
# @option opts [Fixnum] :dim (1) The number of dimensions of the Fourier
|
203
|
+
# transform. If 'shape' has more numbers than :dim, the number of dimensions
|
204
|
+
# specified by :dim will be considered when making the plan.
|
205
|
+
# @option opts [Symbol] :type (:complex_complex) The type of transform to
|
206
|
+
# perform based on the input and output data desired. The default value
|
207
|
+
# indicates that a transform is being planned that uses complex numbers
|
208
|
+
# as input and generates complex numbers as output. Similarly you can
|
209
|
+
# use :complex_real, :real_complex or :real_real to specify the kind
|
210
|
+
# of input and output that you will be supplying to the plan.
|
211
|
+
# @see DATA_TYPE_HASH
|
212
|
+
# @option opts [Symbol, Array] :flags (:estimate) Specify one or more flags
|
213
|
+
# which denote the methodology that is used for deciding the algorithm used
|
214
|
+
# when planning the fourier transform. Use one or more of :estimate, :measure,
|
215
|
+
# :exhaustive and :patient. These flags map to the planner flags specified
|
216
|
+
# at http://www.fftw.org/fftw3_doc/Planner-Flags.html#Planner-Flags.
|
217
|
+
# @see REAL_REAL_FFT_KINDS_HASH
|
218
|
+
# @option opts [Symbol] :direction (:forward) The direction of a DFT of
|
219
|
+
# type :complex_complex. Technically, it is the sign of the exponent in
|
220
|
+
# the transform. :forward corresponds to -1 and :backward to +1.
|
221
|
+
# @see FFT_DIRECTION_HASH
|
222
|
+
# @option opts [Array] :real_real_kind When the type of transform is :real_real,
|
223
|
+
# specify the kind of transform that should be performed FOR EACH AXIS
|
224
|
+
# of input. The position of the symbol in the Array corresponds to the
|
225
|
+
# axis of the input. The number of elements in :real_real_kind must be equal to
|
226
|
+
# :dim. Can accept one of the inputs specified in REAL_REAL_FFT_KINDS_HASH.
|
227
|
+
# @see REAL_REAL_FFT_KINDS_HASH
|
228
|
+
# @see http://www.fftw.org/fftw3_doc/Real_002dto_002dReal-Transform-Kinds.html#Real_002dto_002dReal-Transform-Kinds
|
229
|
+
# @example Create a plan for a basic 1D FFT and execute it.
|
230
|
+
# input = NMatrix.new([10],
|
231
|
+
# [
|
232
|
+
# Complex(9.32,0), Complex(44,0), Complex(125,0), Complex(34,0),
|
233
|
+
# Complex(31,0), Complex(44,0), Complex(12,0), Complex(1,0),
|
234
|
+
# Complex(53.23,0),Complex(-23.23,0),
|
235
|
+
# ], dtype: :complex128)
|
236
|
+
# plan = NMatrix::FFTW::Plan.new(10)
|
237
|
+
# plan.set_input input
|
238
|
+
# plan.execute
|
239
|
+
# print plan.output
|
240
|
+
def initialize shape, opts={}
|
241
|
+
verify_opts opts
|
242
|
+
opts = {
|
243
|
+
dim: 1,
|
244
|
+
flags: :estimate,
|
245
|
+
direction: :forward,
|
246
|
+
type: :complex_complex
|
247
|
+
}.merge(opts)
|
248
|
+
|
249
|
+
@type = opts[:type]
|
250
|
+
@dim = opts[:dim]
|
251
|
+
@direction = opts[:direction]
|
252
|
+
@shape = shape.is_a?(Array) ? shape : [shape]
|
253
|
+
@size = @shape[0...@dim].inject(:*)
|
254
|
+
@flags = opts[:flags].is_a?(Array) ? opts[:flags] : [opts[:flags]]
|
255
|
+
@real_real_kind = opts[:real_real_kind]
|
256
|
+
|
257
|
+
raise ArgumentError, ":real_real_kind option must be specified for :real_real type transforms" if
|
258
|
+
@real_real_kind.nil? and @type == :real_real
|
259
|
+
|
260
|
+
raise ArgumentError, "Specify kind of transform of each axis of input." if
|
261
|
+
@real_real_kind and @real_real_kind.size != @dim
|
262
|
+
|
263
|
+
raise ArgumentError, "dim (#{@dim}) cannot be more than size of shape #{@shape.size}" if
|
264
|
+
@dim > @shape.size
|
265
|
+
|
266
|
+
@plan_data = c_create_plan(@shape, @size, @dim,
|
267
|
+
combine_flags(@flags), FFT_DIRECTION_HASH[@direction],
|
268
|
+
DATA_TYPE_HASH[@type], encoded_rr_kind)
|
269
|
+
end
|
270
|
+
|
271
|
+
# Set input for the planned DFT.
|
272
|
+
# @param [NMatrix] ip An NMatrix specifying the input to the FFT routine.
|
273
|
+
# The data type of the NMatrix must be either :complex128 or :float64
|
274
|
+
# depending on the type of FFT that has been planned. Size must be same
|
275
|
+
# as the size of the planned routine.
|
276
|
+
# @raise [ArgumentError] if the input has any storage apart from :dense
|
277
|
+
# or if size/data type of the planned transform and the input matrix
|
278
|
+
# don't match.
|
279
|
+
def set_input ip
|
280
|
+
raise ArgumentError, "stype must be dense." if ip.stype != :dense
|
281
|
+
raise ArgumentError, "size of input (#{ip.size}) cannot be greater than planned input size #{@size}" if
|
282
|
+
ip.size != @size
|
283
|
+
|
284
|
+
case @type
|
285
|
+
when :complex_complex, :complex_real
|
286
|
+
raise ArgumentError, "dtype must be complex128." if ip.dtype != :complex128
|
287
|
+
when :real_complex, :real_real
|
288
|
+
raise ArgumentError, "dtype must be float64." if ip.dtype != :float64
|
289
|
+
else
|
290
|
+
raise "Invalid type #{@type}"
|
291
|
+
end
|
292
|
+
|
293
|
+
@input = ip
|
294
|
+
c_set_input(ip, @plan_data, DATA_TYPE_HASH[@type])
|
295
|
+
end
|
296
|
+
|
297
|
+
# Execute the DFT with the set plan.
|
298
|
+
# @return [TrueClass] If all goes well and the fourier transform has been
|
299
|
+
# sucessfully computed, 'true' will be returned and you can access the
|
300
|
+
# computed output from the NMatrix::FFTW::Plan#output accessor.
|
301
|
+
def execute
|
302
|
+
@output =
|
303
|
+
case @type
|
304
|
+
when :complex_complex
|
305
|
+
@input.clone_structure
|
306
|
+
when :real_complex
|
307
|
+
NMatrix.new([@input.size/2 + 1], dtype: :complex128)
|
308
|
+
when :complex_real, :real_real
|
309
|
+
NMatrix.new([@input.size], dtype: :float64)
|
310
|
+
else
|
311
|
+
raise TypeError, "Invalid type #{@type}"
|
312
|
+
end
|
313
|
+
|
314
|
+
c_execute(@output, @plan_data, DATA_TYPE_HASH[@type])
|
315
|
+
end
|
316
|
+
private
|
317
|
+
|
318
|
+
# Combine flags received from the user (Symbols) into their respective
|
319
|
+
# numeric equivalents and then 'OR' (|) all of them so the resulting number
|
320
|
+
# can be passed directly to the FFTW planner function.
|
321
|
+
def combine_flags flgs
|
322
|
+
temp = 0
|
323
|
+
flgs.each do |f|
|
324
|
+
temp |= FLAG_VALUE_HASH[f]
|
325
|
+
end
|
326
|
+
temp
|
327
|
+
end
|
328
|
+
|
329
|
+
# Verify options passed into the constructor to make sure that no invalid
|
330
|
+
# options have been passed.
|
331
|
+
def verify_opts opts
|
332
|
+
unless (opts.keys - VALID_OPTS).empty?
|
333
|
+
raise ArgumentError, "#{opts.keys - VALID_OPTS} are invalid opts."
|
334
|
+
end
|
335
|
+
end
|
336
|
+
|
337
|
+
# Get the numerical equivalents of the kind of real-real FFT to be computed.
|
338
|
+
def encoded_rr_kind
|
339
|
+
return @real_real_kind.map { |e| REAL_REAL_FFT_KINDS_HASH[e] } if @real_real_kind
|
340
|
+
end
|
341
|
+
end
|
342
|
+
end
|
343
|
+
end
|
@@ -0,0 +1,736 @@
|
|
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 - 2014, Ruby Science Foundation
|
12
|
+
# NMatrix is Copyright (c) 2012 - 2014, John Woods and the 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
|
+
# == 00_nmatrix_spec.rb
|
24
|
+
#
|
25
|
+
# Basic tests for NMatrix. These should load first, as they're
|
26
|
+
# essential to NMatrix operation.
|
27
|
+
#
|
28
|
+
require 'spec_helper'
|
29
|
+
|
30
|
+
describe NMatrix do
|
31
|
+
it "creates a matrix with the new constructor" do
|
32
|
+
n = NMatrix.new([2,2], [0,1,2,3], dtype: :int64)
|
33
|
+
expect(n.shape).to eq([2,2])
|
34
|
+
expect(n.entries).to eq([0,1,2,3])
|
35
|
+
expect(n.dtype).to eq(:int64)
|
36
|
+
end
|
37
|
+
|
38
|
+
it "adequately requires information to access a single entry of a dense matrix" do
|
39
|
+
n = NMatrix.new(:dense, 4, [0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15], :float64)
|
40
|
+
expect(n[0,0]).to eq(0)
|
41
|
+
expect { n[0] }.to raise_error(ArgumentError)
|
42
|
+
end
|
43
|
+
|
44
|
+
it "calculates exact determinants on small square matrices" do
|
45
|
+
expect(NMatrix.new(2, [1,2,3,4], stype: :dense, dtype: :int64).det_exact).to eq(-2)
|
46
|
+
end
|
47
|
+
|
48
|
+
it "calculates determinants" do
|
49
|
+
expect(NMatrix.new(3, [-2,2,3,-1,1,3,2,0,-1], stype: :dense, dtype: :int64).det).to eq(6)
|
50
|
+
end
|
51
|
+
|
52
|
+
it "allows casting to Ruby objects" do
|
53
|
+
m = NMatrix.new([3,3], [0,0,1,0,2,0,3,4,5], dtype: :int64, stype: :dense)
|
54
|
+
n = m.cast(:dense, :object)
|
55
|
+
expect(n).to eq(m)
|
56
|
+
end
|
57
|
+
|
58
|
+
it "allows casting from Ruby objects" do
|
59
|
+
m = NMatrix.new(:dense, [3,3], [0,0,1,0,2,0,3,4,5], :object)
|
60
|
+
n = m.cast(:dense, :int64)
|
61
|
+
expect(m).to eq(n)
|
62
|
+
end
|
63
|
+
|
64
|
+
it "allows stype casting of a dim 2 matrix between dense, sparse, and list (different dtypes)" do
|
65
|
+
m = NMatrix.new(:dense, [3,3], [0,0,1,0,2,0,3,4,5], :int64).
|
66
|
+
cast(:yale, :int32).
|
67
|
+
cast(:dense, :float64).
|
68
|
+
cast(:list, :object).
|
69
|
+
cast(:dense, :int16).
|
70
|
+
cast(:list, :int32).
|
71
|
+
cast(:yale, :int64) #.
|
72
|
+
#cast(:list, :int32).
|
73
|
+
#cast(:dense, :int16)
|
74
|
+
#m.should.equal?(original)
|
75
|
+
# For some reason this causes some weird garbage collector problems when we uncomment these. The above lines won't
|
76
|
+
# work at all in IRB, but work fine when run in a regular Ruby session.
|
77
|
+
end
|
78
|
+
|
79
|
+
it "fills dense Ruby object matrix with nil" do
|
80
|
+
n = NMatrix.new([4,3], dtype: :object)
|
81
|
+
expect(n[0,0]).to eq(nil)
|
82
|
+
end
|
83
|
+
|
84
|
+
it "fills dense with individual assignments" do
|
85
|
+
n = NMatrix.new([4,3], dtype: :float64)
|
86
|
+
n[0,0] = 14.0
|
87
|
+
n[0,1] = 9.0
|
88
|
+
n[0,2] = 3.0
|
89
|
+
n[1,0] = 2.0
|
90
|
+
n[1,1] = 11.0
|
91
|
+
n[1,2] = 15.0
|
92
|
+
n[2,0] = 0.0
|
93
|
+
n[2,1] = 12.0
|
94
|
+
n[2,2] = 17.0
|
95
|
+
n[3,0] = 5.0
|
96
|
+
n[3,1] = 2.0
|
97
|
+
n[3,2] = 3.0
|
98
|
+
|
99
|
+
expect(n[0,0]).to eq(14.0)
|
100
|
+
expect(n[0,1]).to eq(9.0)
|
101
|
+
expect(n[0,2]).to eq(3.0)
|
102
|
+
expect(n[1,0]).to eq(2.0)
|
103
|
+
expect(n[1,1]).to eq(11.0)
|
104
|
+
expect(n[1,2]).to eq(15.0)
|
105
|
+
expect(n[2,0]).to eq(0.0)
|
106
|
+
expect(n[2,1]).to eq(12.0)
|
107
|
+
expect(n[2,2]).to eq(17.0)
|
108
|
+
expect(n[3,0]).to eq(5.0)
|
109
|
+
expect(n[3,1]).to eq(2.0)
|
110
|
+
expect(n[3,2]).to eq(3.0)
|
111
|
+
end
|
112
|
+
|
113
|
+
it "fills dense with a single mass assignment" do
|
114
|
+
n = NMatrix.new([4,3], [14.0, 9.0, 3.0, 2.0, 11.0, 15.0, 0.0, 12.0, 17.0, 5.0, 2.0, 3.0])
|
115
|
+
|
116
|
+
expect(n[0,0]).to eq(14.0)
|
117
|
+
expect(n[0,1]).to eq(9.0)
|
118
|
+
expect(n[0,2]).to eq(3.0)
|
119
|
+
expect(n[1,0]).to eq(2.0)
|
120
|
+
expect(n[1,1]).to eq(11.0)
|
121
|
+
expect(n[1,2]).to eq(15.0)
|
122
|
+
expect(n[2,0]).to eq(0.0)
|
123
|
+
expect(n[2,1]).to eq(12.0)
|
124
|
+
expect(n[2,2]).to eq(17.0)
|
125
|
+
expect(n[3,0]).to eq(5.0)
|
126
|
+
expect(n[3,1]).to eq(2.0)
|
127
|
+
expect(n[3,2]).to eq(3.0)
|
128
|
+
end
|
129
|
+
|
130
|
+
it "fills dense with a single mass assignment, with dtype specified" do
|
131
|
+
m = NMatrix.new([4,3], [14.0, 9.0, 3.0, 2.0, 11.0, 15.0, 0.0, 12.0, 17.0, 5.0, 2.0, 3.0], dtype: :float32)
|
132
|
+
|
133
|
+
expect(m[0,0]).to eq(14.0)
|
134
|
+
expect(m[0,1]).to eq(9.0)
|
135
|
+
expect(m[0,2]).to eq(3.0)
|
136
|
+
expect(m[1,0]).to eq(2.0)
|
137
|
+
expect(m[1,1]).to eq(11.0)
|
138
|
+
expect(m[1,2]).to eq(15.0)
|
139
|
+
expect(m[2,0]).to eq(0.0)
|
140
|
+
expect(m[2,1]).to eq(12.0)
|
141
|
+
expect(m[2,2]).to eq(17.0)
|
142
|
+
expect(m[3,0]).to eq(5.0)
|
143
|
+
expect(m[3,1]).to eq(2.0)
|
144
|
+
expect(m[3,2]).to eq(3.0)
|
145
|
+
end
|
146
|
+
|
147
|
+
it "dense handles missing initialization value" do
|
148
|
+
n = NMatrix.new(3, dtype: :int8)
|
149
|
+
expect(n.stype).to eq(:dense)
|
150
|
+
expect(n.dtype).to eq(:int8)
|
151
|
+
|
152
|
+
m = NMatrix.new(4, dtype: :float64)
|
153
|
+
expect(m.stype).to eq(:dense)
|
154
|
+
expect(m.dtype).to eq(:float64)
|
155
|
+
end
|
156
|
+
|
157
|
+
[:dense, :list, :yale].each do |storage_type|
|
158
|
+
context storage_type do
|
159
|
+
it "can be duplicated" do
|
160
|
+
n = NMatrix.new([2,3], 1.1, stype: storage_type, dtype: :float64)
|
161
|
+
expect(n.stype).to eq(storage_type)
|
162
|
+
|
163
|
+
n[0,0] = 0.0
|
164
|
+
n[0,1] = 0.1
|
165
|
+
n[1,0] = 1.0
|
166
|
+
|
167
|
+
m = n.dup
|
168
|
+
expect(m.shape).to eq(n.shape)
|
169
|
+
expect(m.dim).to eq(n.dim)
|
170
|
+
expect(m.object_id).not_to eq(n.object_id)
|
171
|
+
expect(m.stype).to eq(storage_type)
|
172
|
+
expect(m[0,0]).to eq(n[0,0])
|
173
|
+
m[0,0] = 3.0
|
174
|
+
expect(m[0,0]).not_to eq(n[0,0])
|
175
|
+
end
|
176
|
+
|
177
|
+
it "enforces shape boundaries" do
|
178
|
+
expect { NMatrix.new([1,10], 0, dtype: :int8, stype: storage_type, default: 0)[1,0] }.to raise_error(RangeError)
|
179
|
+
expect { NMatrix.new([1,10], 0, dtype: :int8, stype: storage_type, default: 0)[0,10] }.to raise_error(RangeError)
|
180
|
+
end
|
181
|
+
|
182
|
+
it "sets and gets" do
|
183
|
+
n = NMatrix.new(2, 0, stype: storage_type, dtype: :int8)
|
184
|
+
n[0,1] = 1
|
185
|
+
expect(n[0,0]).to eq(0)
|
186
|
+
expect(n[1,0]).to eq(0)
|
187
|
+
expect(n[0,1]).to eq(1)
|
188
|
+
expect(n[1,1]).to eq(0)
|
189
|
+
end
|
190
|
+
|
191
|
+
it "sets and gets references" do
|
192
|
+
n = NMatrix.new(2, stype: storage_type, dtype: :int8, default: 0)
|
193
|
+
expect(n[0,1] = 1).to eq(1)
|
194
|
+
expect(n[0,1]).to eq(1)
|
195
|
+
end
|
196
|
+
|
197
|
+
# Tests Ruby object versus any C dtype (in this case we use :int64)
|
198
|
+
[:object, :int64].each do |dtype|
|
199
|
+
c = dtype == :object ? "Ruby object" : "non-Ruby object"
|
200
|
+
context c do
|
201
|
+
it "allows iteration of matrices" do
|
202
|
+
n = nil
|
203
|
+
if storage_type == :dense
|
204
|
+
n = NMatrix.new(:dense, [3,3], [1,2,3,4,5,6,7,8,9], dtype)
|
205
|
+
else
|
206
|
+
n = NMatrix.new([3,4], 0, stype: storage_type, dtype: dtype)
|
207
|
+
n[0,0] = 1
|
208
|
+
n[0,1] = 2
|
209
|
+
n[2,3] = 4
|
210
|
+
n[2,0] = 3
|
211
|
+
end
|
212
|
+
|
213
|
+
ary = []
|
214
|
+
n.each do |x|
|
215
|
+
ary << x
|
216
|
+
end
|
217
|
+
|
218
|
+
if storage_type == :dense
|
219
|
+
expect(ary).to eq([1,2,3,4,5,6,7,8,9])
|
220
|
+
else
|
221
|
+
expect(ary).to eq([1,2,0,0,0,0,0,0,3,0,0,4])
|
222
|
+
end
|
223
|
+
end
|
224
|
+
|
225
|
+
it "allows storage-based iteration of matrices" do
|
226
|
+
STDERR.puts storage_type.inspect
|
227
|
+
STDERR.puts dtype.inspect
|
228
|
+
n = NMatrix.new([3,3], 0, stype: storage_type, dtype: dtype)
|
229
|
+
n[0,0] = 1
|
230
|
+
n[0,1] = 2
|
231
|
+
n[2,0] = 5 if storage_type == :yale
|
232
|
+
n[2,1] = 4
|
233
|
+
n[2,2] = 3
|
234
|
+
|
235
|
+
values = []
|
236
|
+
is = []
|
237
|
+
js = []
|
238
|
+
|
239
|
+
n.each_stored_with_indices do |v,i,j|
|
240
|
+
values << v
|
241
|
+
is << i
|
242
|
+
js << j
|
243
|
+
end
|
244
|
+
|
245
|
+
if storage_type == :yale
|
246
|
+
expect(is).to eq([0,1,2,0,2,2])
|
247
|
+
expect(js).to eq([0,1,2,1,0,1])
|
248
|
+
expect(values).to eq([1,0,3,2,5,4])
|
249
|
+
elsif storage_type == :list
|
250
|
+
expect(values).to eq([1,2,4,3])
|
251
|
+
expect(is).to eq([0,0,2,2])
|
252
|
+
expect(js).to eq([0,1,1,2])
|
253
|
+
elsif storage_type == :dense
|
254
|
+
expect(values).to eq([1,2,0,0,0,0,0,4,3])
|
255
|
+
expect(is).to eq([0,0,0,1,1,1,2,2,2])
|
256
|
+
expect(js).to eq([0,1,2,0,1,2,0,1,2])
|
257
|
+
end
|
258
|
+
end
|
259
|
+
end
|
260
|
+
end
|
261
|
+
end
|
262
|
+
|
263
|
+
# dense and list, not yale
|
264
|
+
context "(storage: #{storage_type})" do
|
265
|
+
it "gets default value" do
|
266
|
+
expect(NMatrix.new(3, 0, stype: storage_type)[1,1]).to eq(0)
|
267
|
+
expect(NMatrix.new(3, 0.1, stype: storage_type)[1,1]).to eq(0.1)
|
268
|
+
expect(NMatrix.new(3, 1, stype: storage_type)[1,1]).to eq(1)
|
269
|
+
|
270
|
+
end
|
271
|
+
it "returns shape and dim" do
|
272
|
+
expect(NMatrix.new([3,2,8], 0, stype: storage_type).shape).to eq([3,2,8])
|
273
|
+
expect(NMatrix.new([3,2,8], 0, stype: storage_type).dim).to eq(3)
|
274
|
+
end
|
275
|
+
|
276
|
+
it "returns number of rows and columns" do
|
277
|
+
expect(NMatrix.new([7, 4], 3, stype: storage_type).rows).to eq(7)
|
278
|
+
expect(NMatrix.new([7, 4], 3, stype: storage_type).cols).to eq(4)
|
279
|
+
end
|
280
|
+
end unless storage_type == :yale
|
281
|
+
end
|
282
|
+
|
283
|
+
|
284
|
+
it "handles dense construction" do
|
285
|
+
expect(NMatrix.new(3,0)[1,1]).to eq(0)
|
286
|
+
expect(lambda { NMatrix.new(3,dtype: :int8)[1,1] }).to_not raise_error
|
287
|
+
end
|
288
|
+
|
289
|
+
it "converts from list to yale properly" do
|
290
|
+
m = NMatrix.new(3, 0, stype: :list)
|
291
|
+
m[0,2] = 333
|
292
|
+
m[2,2] = 777
|
293
|
+
n = m.cast(:yale, :int32)
|
294
|
+
#puts n.capacity
|
295
|
+
#n.extend NMatrix::YaleFunctions
|
296
|
+
#puts n.yale_ija.inspect
|
297
|
+
#puts n.yale_a.inspect
|
298
|
+
|
299
|
+
expect(n[0,0]).to eq(0)
|
300
|
+
expect(n[0,1]).to eq(0)
|
301
|
+
expect(n[0,2]).to eq(333)
|
302
|
+
expect(n[1,0]).to eq(0)
|
303
|
+
expect(n[1,1]).to eq(0)
|
304
|
+
expect(n[1,2]).to eq(0)
|
305
|
+
expect(n[2,0]).to eq(0)
|
306
|
+
expect(n[2,1]).to eq(0)
|
307
|
+
expect(n[2,2]).to eq(777)
|
308
|
+
end
|
309
|
+
|
310
|
+
it "should return an enumerator when each is called without a block" do
|
311
|
+
a = NMatrix.new(2, 1)
|
312
|
+
b = NMatrix.new(2, [-1,0,1,0])
|
313
|
+
enums = [a.each, b.each]
|
314
|
+
|
315
|
+
begin
|
316
|
+
atans = []
|
317
|
+
atans << Math.atan2(*enums.map(&:next)) while true
|
318
|
+
rescue StopIteration
|
319
|
+
end
|
320
|
+
end
|
321
|
+
|
322
|
+
context "dense" do
|
323
|
+
it "should return the matrix being iterated over when each is called with a block" do
|
324
|
+
a = NMatrix.new(2, 1)
|
325
|
+
val = (a.each { })
|
326
|
+
expect(val).to eq(a)
|
327
|
+
end
|
328
|
+
|
329
|
+
it "should return the matrix being iterated over when each_stored_with_indices is called with a block" do
|
330
|
+
a = NMatrix.new(2,1)
|
331
|
+
val = (a.each_stored_with_indices { })
|
332
|
+
expect(val).to eq(a)
|
333
|
+
end
|
334
|
+
end
|
335
|
+
|
336
|
+
[:list, :yale].each do |storage_type|
|
337
|
+
context storage_type do
|
338
|
+
it "should return the matrix being iterated over when each_stored_with_indices is called with a block" do
|
339
|
+
n = NMatrix.new([2,3], 1.1, stype: storage_type, dtype: :float64, default: 0)
|
340
|
+
val = (n.each_stored_with_indices { })
|
341
|
+
expect(val).to eq(n)
|
342
|
+
end
|
343
|
+
|
344
|
+
it "should return an enumerator when each_stored_with_indices is called without a block" do
|
345
|
+
n = NMatrix.new([2,3], 1.1, stype: storage_type, dtype: :float64, default: 0)
|
346
|
+
val = n.each_stored_with_indices
|
347
|
+
expect(val).to be_a Enumerator
|
348
|
+
end
|
349
|
+
end
|
350
|
+
end
|
351
|
+
|
352
|
+
it "should iterate through element 256 without a segfault" do
|
353
|
+
t = NVector.random(256)
|
354
|
+
t.each { |x| x + 0 }
|
355
|
+
end
|
356
|
+
end
|
357
|
+
|
358
|
+
|
359
|
+
describe 'NMatrix' do
|
360
|
+
context "#upper_triangle" do
|
361
|
+
it "should create a copy with the lower corner set to zero" do
|
362
|
+
n = NMatrix.seq(4)+1
|
363
|
+
expect(n.upper_triangle).to eq(NMatrix.new(4, [1,2,3,4,0,6,7,8,0,0,11,12,0,0,0,16]))
|
364
|
+
expect(n.upper_triangle(2)).to eq(NMatrix.new(4, [1,2,3,4,5,6,7,8,9,10,11,12,0,14,15,16]))
|
365
|
+
end
|
366
|
+
end
|
367
|
+
|
368
|
+
context "#lower_triangle" do
|
369
|
+
it "should create a copy with the lower corner set to zero" do
|
370
|
+
n = NMatrix.seq(4)+1
|
371
|
+
expect(n.lower_triangle).to eq(NMatrix.new(4, [1,0,0,0,5,6,0,0,9,10,11,0,13,14,15,16]))
|
372
|
+
expect(n.lower_triangle(2)).to eq(NMatrix.new(4, [1,2,3,0,5,6,7,8,9,10,11,12,13,14,15,16]))
|
373
|
+
end
|
374
|
+
end
|
375
|
+
|
376
|
+
context "#upper_triangle!" do
|
377
|
+
it "should create a copy with the lower corner set to zero" do
|
378
|
+
n = NMatrix.seq(4)+1
|
379
|
+
expect(n.upper_triangle!).to eq(NMatrix.new(4, [1,2,3,4,0,6,7,8,0,0,11,12,0,0,0,16]))
|
380
|
+
n = NMatrix.seq(4)+1
|
381
|
+
expect(n.upper_triangle!(2)).to eq(NMatrix.new(4, [1,2,3,4,5,6,7,8,9,10,11,12,0,14,15,16]))
|
382
|
+
end
|
383
|
+
end
|
384
|
+
|
385
|
+
context "#lower_triangle!" do
|
386
|
+
it "should create a copy with the lower corner set to zero" do
|
387
|
+
n = NMatrix.seq(4)+1
|
388
|
+
expect(n.lower_triangle!).to eq(NMatrix.new(4, [1,0,0,0,5,6,0,0,9,10,11,0,13,14,15,16]))
|
389
|
+
n = NMatrix.seq(4)+1
|
390
|
+
expect(n.lower_triangle!(2)).to eq(NMatrix.new(4, [1,2,3,0,5,6,7,8,9,10,11,12,13,14,15,16]))
|
391
|
+
end
|
392
|
+
end
|
393
|
+
|
394
|
+
context "#rank" do
|
395
|
+
it "should get the rank of a 2-dimensional matrix" do
|
396
|
+
n = NMatrix.seq([2,3])
|
397
|
+
expect(n.rank(0, 0)).to eq(N[[0,1,2]])
|
398
|
+
end
|
399
|
+
|
400
|
+
it "should raise an error when the rank is out of bounds" do
|
401
|
+
n = NMatrix.seq([2,3])
|
402
|
+
expect { n.rank(2, 0) }.to raise_error(RangeError)
|
403
|
+
end
|
404
|
+
end
|
405
|
+
|
406
|
+
context "#reshape" do
|
407
|
+
it "should change the shape of a matrix without the contents changing" do
|
408
|
+
n = NMatrix.seq(4)+1
|
409
|
+
expect(n.reshape([8,2]).to_flat_array).to eq(n.to_flat_array)
|
410
|
+
end
|
411
|
+
|
412
|
+
it "should permit a change of dimensionality" do
|
413
|
+
n = NMatrix.seq(4)+1
|
414
|
+
expect(n.reshape([8,1,2]).to_flat_array).to eq(n.to_flat_array)
|
415
|
+
end
|
416
|
+
|
417
|
+
it "should prevent a resize" do
|
418
|
+
n = NMatrix.seq(4)+1
|
419
|
+
expect { n.reshape([5,2]) }.to raise_error(ArgumentError)
|
420
|
+
end
|
421
|
+
|
422
|
+
it "should do the reshape operation in place" do
|
423
|
+
n = NMatrix.seq(4)+1
|
424
|
+
expect(n.reshape!([8,2]).eql?(n)).to eq(true) # because n itself changes
|
425
|
+
end
|
426
|
+
|
427
|
+
it "reshape and reshape! must produce same result" do
|
428
|
+
n = NMatrix.seq(4)+1
|
429
|
+
a = NMatrix.seq(4)+1
|
430
|
+
expect(n.reshape!([8,2])==a.reshape(8,2)).to eq(true) # because n itself changes
|
431
|
+
end
|
432
|
+
|
433
|
+
it "should prevent a resize in place" do
|
434
|
+
n = NMatrix.seq(4)+1
|
435
|
+
expect { n.reshape([5,2]) }.to raise_error(ArgumentError)
|
436
|
+
end
|
437
|
+
end
|
438
|
+
|
439
|
+
context "#transpose" do
|
440
|
+
[:dense, :list, :yale].each do |stype|
|
441
|
+
context(stype) do
|
442
|
+
it "should transpose a #{stype} matrix (2-dimensional)" do
|
443
|
+
n = NMatrix.seq(4, stype: stype)
|
444
|
+
expect(n.transpose.to_a.flatten).to eq([0,4,8,12,1,5,9,13,2,6,10,14,3,7,11,15])
|
445
|
+
end
|
446
|
+
end
|
447
|
+
end
|
448
|
+
|
449
|
+
[:dense, :list].each do |stype|
|
450
|
+
context(stype) do
|
451
|
+
it "should transpose a #{stype} matrix (3-dimensional)" do
|
452
|
+
n = NMatrix.new([4,4,1], [0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15], stype: stype)
|
453
|
+
expect(n.transpose([2,1,0]).to_flat_array).to eq([0,4,8,12,1,5,9,13,2,6,10,14,3,7,11,15])
|
454
|
+
expect(n.transpose([1,0,2]).to_flat_array).to eq([0,4,8,12,1,5,9,13,2,6,10,14,3,7,11,15])
|
455
|
+
expect(n.transpose([0,2,1]).to_flat_array).to eq(n.to_flat_array) # for dense, make this reshape!
|
456
|
+
end
|
457
|
+
end
|
458
|
+
|
459
|
+
it "should just copy a 1-dimensional #{stype} matrix" do
|
460
|
+
n = NMatrix.new([3], [1,2,3], stype: stype)
|
461
|
+
expect(n.transpose).to eq n
|
462
|
+
expect(n.transpose).not_to be n
|
463
|
+
end
|
464
|
+
|
465
|
+
it "should check permute argument if supplied for #{stype} matrix" do
|
466
|
+
n = NMatrix.new([2,2], [1,2,3,4], stype: stype)
|
467
|
+
expect{n.transpose *4 }.to raise_error(ArgumentError)
|
468
|
+
expect{n.transpose [1,1,2] }.to raise_error(ArgumentError)
|
469
|
+
end
|
470
|
+
end
|
471
|
+
end
|
472
|
+
|
473
|
+
context "#dot_product" do
|
474
|
+
[:dense].each do |stype| # list storage transpose not yet implemented
|
475
|
+
context(stype) do # yale support only 2-dim matrix
|
476
|
+
it "should work like vector product on a #{stype} (1-dimensional)" do
|
477
|
+
m = NMatrix.new([3], [1,2,3], stype: stype)
|
478
|
+
expect(m.dot(m)).to eq (NMatrix.new([1],[14]))
|
479
|
+
end
|
480
|
+
end
|
481
|
+
end
|
482
|
+
end
|
483
|
+
|
484
|
+
context "#==" do
|
485
|
+
[:dense, :list, :yale].each do |left|
|
486
|
+
[:dense, :list, :yale].each do |right|
|
487
|
+
context ("#{left}?#{right}") do
|
488
|
+
it "tests equality of two equal matrices" do
|
489
|
+
n = NMatrix.new([3,4], [0,0,1,2,0,0,3,4,0,0,0,0], stype: left)
|
490
|
+
m = NMatrix.new([3,4], [0,0,1,2,0,0,3,4,0,0,0,0], stype: right)
|
491
|
+
|
492
|
+
expect(n==m).to eq(true)
|
493
|
+
end
|
494
|
+
|
495
|
+
it "tests equality of two unequal matrices" do
|
496
|
+
n = NMatrix.new([3,4], [0,0,1,2,0,0,3,4,0,0,0,1], stype: left)
|
497
|
+
m = NMatrix.new([3,4], [0,0,1,2,0,0,3,4,0,0,0,0], stype: right)
|
498
|
+
|
499
|
+
expect(n==m).to eq(false)
|
500
|
+
end
|
501
|
+
|
502
|
+
it "tests equality of matrices with different shapes" do
|
503
|
+
n = NMatrix.new([2,2], [1,2, 3,4], stype: left)
|
504
|
+
m = NMatrix.new([2,3], [1,2, 3,4, 5,6], stype: right)
|
505
|
+
x = NMatrix.new([1,4], [1,2, 3,4], stype: right)
|
506
|
+
|
507
|
+
expect{n==m}.to raise_error(ShapeError)
|
508
|
+
expect{n==x}.to raise_error(ShapeError)
|
509
|
+
end
|
510
|
+
|
511
|
+
it "tests equality of matrices with different dimension" do
|
512
|
+
n = NMatrix.new([2,1], [1,2], stype: left)
|
513
|
+
m = NMatrix.new([2], [1,2], stype: right)
|
514
|
+
|
515
|
+
expect{n==m}.to raise_error(ShapeError)
|
516
|
+
end if left != :yale && right != :yale # yale must have dimension 2
|
517
|
+
end
|
518
|
+
end
|
519
|
+
end
|
520
|
+
end
|
521
|
+
|
522
|
+
context "#concat" do
|
523
|
+
it "should default to horizontal concatenation" do
|
524
|
+
n = NMatrix.new([1,3], [1,2,3])
|
525
|
+
expect(n.concat(n)).to eq(NMatrix.new([1,6], [1,2,3,1,2,3]))
|
526
|
+
end
|
527
|
+
|
528
|
+
it "should permit vertical concatenation" do
|
529
|
+
n = NMatrix.new([1,3], [1,2,3])
|
530
|
+
expect(n.vconcat(n)).to eq(NMatrix.new([2,3], [1,2,3]))
|
531
|
+
end
|
532
|
+
|
533
|
+
it "should permit depth concatenation on tensors" do
|
534
|
+
n = NMatrix.new([1,3,1], [1,2,3])
|
535
|
+
expect(n.dconcat(n)).to eq(NMatrix.new([1,3,2], [1,1,2,2,3,3]))
|
536
|
+
end
|
537
|
+
end
|
538
|
+
|
539
|
+
context "#[]" do
|
540
|
+
it "should return values based on indices" do
|
541
|
+
n = NMatrix.new([2,5], [1,2,3,4,5,6,7,8,9,0])
|
542
|
+
expect(n[1,0]).to eq 6
|
543
|
+
expect(n[1,0..3]).to eq NMatrix.new([1,4],[6,7,8,9])
|
544
|
+
end
|
545
|
+
|
546
|
+
it "should work for negative indices" do
|
547
|
+
n = NMatrix.new([1,5], [1,2,3,4,5])
|
548
|
+
expect(n[-1]).to eq(5)
|
549
|
+
expect(n[0,0..-2]).to eq(NMatrix.new([1,4],[1,2,3,4]))
|
550
|
+
end
|
551
|
+
end
|
552
|
+
|
553
|
+
context "#complex_conjugate!" do
|
554
|
+
[:dense, :yale, :list].each do |stype|
|
555
|
+
context(stype) do
|
556
|
+
it "should work in-place for complex dtypes" do
|
557
|
+
pending("not yet implemented for list stype") if stype == :list
|
558
|
+
n = NMatrix.new([2,3], [Complex(2,3)], stype: stype, dtype: :complex128)
|
559
|
+
n.complex_conjugate!
|
560
|
+
expect(n).to eq(NMatrix.new([2,3], [Complex(2,-3)], stype: stype, dtype: :complex128))
|
561
|
+
end
|
562
|
+
|
563
|
+
[:object, :int64].each do |dtype|
|
564
|
+
it "should work in-place for non-complex dtypes" do
|
565
|
+
pending("not yet implemented for list stype") if stype == :list
|
566
|
+
n = NMatrix.new([2,3], 1, stype: stype, dtype: dtype)
|
567
|
+
n.complex_conjugate!
|
568
|
+
expect(n).to eq(NMatrix.new([2,3], [1], stype: stype, dtype: dtype))
|
569
|
+
end
|
570
|
+
end
|
571
|
+
end
|
572
|
+
end
|
573
|
+
end
|
574
|
+
|
575
|
+
context "#complex_conjugate" do
|
576
|
+
[:dense, :yale, :list].each do |stype|
|
577
|
+
context(stype) do
|
578
|
+
it "should work out-of-place for complex dtypes" do
|
579
|
+
pending("not yet implemented for list stype") if stype == :list
|
580
|
+
n = NMatrix.new([2,3], [Complex(2,3)], stype: stype, dtype: :complex128)
|
581
|
+
expect(n.complex_conjugate).to eq(NMatrix.new([2,3], [Complex(2,-3)], stype: stype, dtype: :complex128))
|
582
|
+
end
|
583
|
+
|
584
|
+
[:object, :int64].each do |dtype|
|
585
|
+
it "should work out-of-place for non-complex dtypes" do
|
586
|
+
pending("not yet implemented for list stype") if stype == :list
|
587
|
+
n = NMatrix.new([2,3], 1, stype: stype, dtype: dtype)
|
588
|
+
expect(n.complex_conjugate).to eq(NMatrix.new([2,3], [1], stype: stype, dtype: dtype))
|
589
|
+
end
|
590
|
+
end
|
591
|
+
end
|
592
|
+
end
|
593
|
+
end
|
594
|
+
|
595
|
+
context "#inject" do
|
596
|
+
it "should sum columns of yale matrix correctly" do
|
597
|
+
n = NMatrix.new([4, 3], stype: :yale, default: 0)
|
598
|
+
n[0,0] = 1
|
599
|
+
n[1,1] = 2
|
600
|
+
n[2,2] = 4
|
601
|
+
n[3,2] = 8
|
602
|
+
column_sums = []
|
603
|
+
n.cols.times do |i|
|
604
|
+
column_sums << n.col(i).inject(:+)
|
605
|
+
end
|
606
|
+
expect(column_sums).to eq([1, 2, 12])
|
607
|
+
end
|
608
|
+
end
|
609
|
+
|
610
|
+
context "#index" do
|
611
|
+
it "returns index of first occurence of an element for a vector" do
|
612
|
+
n = NMatrix.new([5], [0,22,22,11,11])
|
613
|
+
|
614
|
+
expect(n.index(22)).to eq([1])
|
615
|
+
end
|
616
|
+
|
617
|
+
it "returns index of first occurence of an element for 2-D matrix" do
|
618
|
+
n = NMatrix.new([3,3], [23,11,23,
|
619
|
+
44, 2, 0,
|
620
|
+
33, 0, 32])
|
621
|
+
|
622
|
+
expect(n.index(0)).to eq([1,2])
|
623
|
+
end
|
624
|
+
|
625
|
+
it "returns index of first occerence of an element for N-D matrix" do
|
626
|
+
n = NMatrix.new([3,3,3], [23,11,23, 44, 2, 0, 33, 0, 32,
|
627
|
+
23,11,23, 44, 2, 0, 33, 0, 32,
|
628
|
+
23,11,23, 44, 2, 0, 33, 0, 32])
|
629
|
+
|
630
|
+
expect(n.index(44)).to eq([0,1,0])
|
631
|
+
end
|
632
|
+
end
|
633
|
+
|
634
|
+
context "#diagonal" do
|
635
|
+
ALL_DTYPES.each do |dtype|
|
636
|
+
before do
|
637
|
+
@square_matrix = NMatrix.new([3,3], [
|
638
|
+
23,11,23,
|
639
|
+
44, 2, 0,
|
640
|
+
33, 0, 32
|
641
|
+
], dtype: dtype
|
642
|
+
)
|
643
|
+
|
644
|
+
@rect_matrix = NMatrix.new([4,3], [
|
645
|
+
23,11,23,
|
646
|
+
44, 2, 0,
|
647
|
+
33, 0,32,
|
648
|
+
11,22,33
|
649
|
+
], dtype: dtype
|
650
|
+
)
|
651
|
+
end
|
652
|
+
|
653
|
+
it "returns main diagonal for square matrix" do
|
654
|
+
expect(@square_matrix.diagonal).to eq(NMatrix.new [3], [23,2,32])
|
655
|
+
end
|
656
|
+
|
657
|
+
it "returns main diagonal for rectangular matrix" do
|
658
|
+
expect(@rect_matrix.diagonal).to eq(NMatrix.new [3], [23,2,32])
|
659
|
+
end
|
660
|
+
|
661
|
+
it "returns anti-diagonal for square matrix" do
|
662
|
+
expect(@square_matrix.diagonal(false)).to eq(NMatrix.new [3], [23,2,33])
|
663
|
+
end
|
664
|
+
|
665
|
+
it "returns anti-diagonal for rectangular matrix" do
|
666
|
+
expect(@square_matrix.diagonal(false)).to eq(NMatrix.new [3], [23,2,33])
|
667
|
+
end
|
668
|
+
end
|
669
|
+
end
|
670
|
+
|
671
|
+
context "#repeat" do
|
672
|
+
before do
|
673
|
+
@sample_matrix = NMatrix.new([2, 2], [1, 2, 3, 4])
|
674
|
+
end
|
675
|
+
|
676
|
+
it "checks count argument" do
|
677
|
+
expect{@sample_matrix.repeat(1, 0)}.to raise_error(ArgumentError)
|
678
|
+
expect{@sample_matrix.repeat(-2, 0)}.to raise_error(ArgumentError)
|
679
|
+
end
|
680
|
+
|
681
|
+
it "returns repeated matrix" do
|
682
|
+
expect(@sample_matrix.repeat(2, 0)).to eq(NMatrix.new([4, 2], [1, 2, 3, 4, 1, 2, 3, 4]))
|
683
|
+
expect(@sample_matrix.repeat(2, 1)).to eq(NMatrix.new([2, 4], [1, 2, 1, 2, 3, 4, 3, 4]))
|
684
|
+
end
|
685
|
+
end
|
686
|
+
|
687
|
+
context "#meshgrid" do
|
688
|
+
before do
|
689
|
+
@x, @y, @z = [1, 2, 3], NMatrix.new([2, 1], [4, 5]), [6, 7]
|
690
|
+
@two_dim = NMatrix.new([2, 2], [1, 2, 3, 4])
|
691
|
+
@two_dim_array = [[4], [5]]
|
692
|
+
@expected_result = [NMatrix.new([2, 3], [1, 2, 3, 1, 2, 3]), NMatrix.new([2, 3], [4, 4, 4, 5, 5, 5])]
|
693
|
+
@expected_for_ij = [NMatrix.new([3, 2], [1, 1, 2, 2, 3, 3]), NMatrix.new([3, 2], [4, 5, 4, 5, 4, 5])]
|
694
|
+
@expected_for_sparse = [NMatrix.new([1, 3], [1, 2, 3]), NMatrix.new([2, 1], [4, 5])]
|
695
|
+
@expected_for_sparse_ij = [NMatrix.new([3, 1], [1, 2, 3]), NMatrix.new([1, 2], [4, 5])]
|
696
|
+
@expected_3dim = [NMatrix.new([1, 3, 1], [1, 2, 3]).repeat(2, 0).repeat(2, 2),
|
697
|
+
NMatrix.new([2, 1, 1], [4, 5]).repeat(3, 1).repeat(2, 2),
|
698
|
+
NMatrix.new([1, 1, 2], [6, 7]).repeat(2, 0).repeat(3, 1)]
|
699
|
+
@expected_3dim_sparse_ij = [NMatrix.new([3, 1, 1], [1, 2, 3]),
|
700
|
+
NMatrix.new([1, 2, 1], [4, 5]),
|
701
|
+
NMatrix.new([1, 1, 2], [6, 7])]
|
702
|
+
end
|
703
|
+
|
704
|
+
it "checks arrays count" do
|
705
|
+
expect{NMatrix.meshgrid([@x])}.to raise_error(ArgumentError)
|
706
|
+
expect{NMatrix.meshgrid([])}.to raise_error(ArgumentError)
|
707
|
+
end
|
708
|
+
|
709
|
+
it "flattens input arrays before use" do
|
710
|
+
expect(NMatrix.meshgrid([@two_dim, @two_dim_array])).to eq(NMatrix.meshgrid([@two_dim.to_flat_array, @two_dim_array.flatten]))
|
711
|
+
end
|
712
|
+
|
713
|
+
it "returns new NMatrixes" do
|
714
|
+
expect(NMatrix.meshgrid([@x, @y])).to eq(@expected_result)
|
715
|
+
end
|
716
|
+
|
717
|
+
it "has option :sparse" do
|
718
|
+
expect(NMatrix.meshgrid([@x, @y], sparse: true)).to eq(@expected_for_sparse)
|
719
|
+
end
|
720
|
+
|
721
|
+
it "has option :indexing" do
|
722
|
+
expect(NMatrix.meshgrid([@x, @y], indexing: :ij)).to eq(@expected_for_ij)
|
723
|
+
expect(NMatrix.meshgrid([@x, @y], indexing: :xy)).to eq(@expected_result)
|
724
|
+
expect{NMatrix.meshgrid([@x, @y], indexing: :not_ij_not_xy)}.to raise_error(ArgumentError)
|
725
|
+
end
|
726
|
+
|
727
|
+
it "works well with both options set" do
|
728
|
+
expect(NMatrix.meshgrid([@x, @y], sparse: true, indexing: :ij)).to eq(@expected_for_sparse_ij)
|
729
|
+
end
|
730
|
+
|
731
|
+
it "is able to take more than two arrays as arguments and works well with options" do
|
732
|
+
expect(NMatrix.meshgrid([@x, @y, @z])).to eq(@expected_3dim)
|
733
|
+
expect(NMatrix.meshgrid([@x, @y, @z], sparse: true, indexing: :ij)).to eq(@expected_3dim_sparse_ij)
|
734
|
+
end
|
735
|
+
end
|
736
|
+
end
|