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.
Files changed (74) hide show
  1. checksums.yaml +7 -0
  2. data/ext/nmatrix/data/complex.h +388 -0
  3. data/ext/nmatrix/data/data.h +652 -0
  4. data/ext/nmatrix/data/meta.h +64 -0
  5. data/ext/nmatrix/data/ruby_object.h +389 -0
  6. data/ext/nmatrix/math/asum.h +120 -0
  7. data/ext/nmatrix/math/cblas_enums.h +36 -0
  8. data/ext/nmatrix/math/cblas_templates_core.h +507 -0
  9. data/ext/nmatrix/math/gemm.h +241 -0
  10. data/ext/nmatrix/math/gemv.h +178 -0
  11. data/ext/nmatrix/math/getrf.h +255 -0
  12. data/ext/nmatrix/math/getrs.h +121 -0
  13. data/ext/nmatrix/math/imax.h +79 -0
  14. data/ext/nmatrix/math/laswp.h +165 -0
  15. data/ext/nmatrix/math/long_dtype.h +49 -0
  16. data/ext/nmatrix/math/math.h +745 -0
  17. data/ext/nmatrix/math/nrm2.h +160 -0
  18. data/ext/nmatrix/math/rot.h +117 -0
  19. data/ext/nmatrix/math/rotg.h +106 -0
  20. data/ext/nmatrix/math/scal.h +71 -0
  21. data/ext/nmatrix/math/trsm.h +332 -0
  22. data/ext/nmatrix/math/util.h +148 -0
  23. data/ext/nmatrix/nm_memory.h +60 -0
  24. data/ext/nmatrix/nmatrix.h +438 -0
  25. data/ext/nmatrix/ruby_constants.h +106 -0
  26. data/ext/nmatrix/storage/common.h +177 -0
  27. data/ext/nmatrix/storage/dense/dense.h +129 -0
  28. data/ext/nmatrix/storage/list/list.h +138 -0
  29. data/ext/nmatrix/storage/storage.h +99 -0
  30. data/ext/nmatrix/storage/yale/class.h +1139 -0
  31. data/ext/nmatrix/storage/yale/iterators/base.h +143 -0
  32. data/ext/nmatrix/storage/yale/iterators/iterator.h +131 -0
  33. data/ext/nmatrix/storage/yale/iterators/row.h +450 -0
  34. data/ext/nmatrix/storage/yale/iterators/row_stored.h +140 -0
  35. data/ext/nmatrix/storage/yale/iterators/row_stored_nd.h +169 -0
  36. data/ext/nmatrix/storage/yale/iterators/stored_diagonal.h +124 -0
  37. data/ext/nmatrix/storage/yale/math/transpose.h +110 -0
  38. data/ext/nmatrix/storage/yale/yale.h +203 -0
  39. data/ext/nmatrix/types.h +55 -0
  40. data/ext/nmatrix/util/io.h +115 -0
  41. data/ext/nmatrix/util/sl_list.h +144 -0
  42. data/ext/nmatrix/util/util.h +78 -0
  43. data/ext/nmatrix_fftw/extconf.rb +122 -0
  44. data/ext/nmatrix_fftw/nmatrix_fftw.cpp +274 -0
  45. data/lib/nmatrix/fftw.rb +343 -0
  46. data/spec/00_nmatrix_spec.rb +736 -0
  47. data/spec/01_enum_spec.rb +190 -0
  48. data/spec/02_slice_spec.rb +389 -0
  49. data/spec/03_nmatrix_monkeys_spec.rb +78 -0
  50. data/spec/2x2_dense_double.mat +0 -0
  51. data/spec/4x4_sparse.mat +0 -0
  52. data/spec/4x5_dense.mat +0 -0
  53. data/spec/blas_spec.rb +193 -0
  54. data/spec/elementwise_spec.rb +303 -0
  55. data/spec/homogeneous_spec.rb +99 -0
  56. data/spec/io/fortran_format_spec.rb +88 -0
  57. data/spec/io/harwell_boeing_spec.rb +98 -0
  58. data/spec/io/test.rua +9 -0
  59. data/spec/io_spec.rb +149 -0
  60. data/spec/lapack_core_spec.rb +482 -0
  61. data/spec/leakcheck.rb +16 -0
  62. data/spec/math_spec.rb +807 -0
  63. data/spec/nmatrix_yale_resize_test_associations.yaml +2802 -0
  64. data/spec/nmatrix_yale_spec.rb +286 -0
  65. data/spec/plugins/fftw/fftw_spec.rb +348 -0
  66. data/spec/rspec_monkeys.rb +56 -0
  67. data/spec/rspec_spec.rb +34 -0
  68. data/spec/shortcuts_spec.rb +310 -0
  69. data/spec/slice_set_spec.rb +157 -0
  70. data/spec/spec_helper.rb +149 -0
  71. data/spec/stat_spec.rb +203 -0
  72. data/spec/test.pcd +20 -0
  73. data/spec/utm5940.mtx +83844 -0
  74. metadata +151 -0
@@ -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