nmatrix-fftw 0.2.1

Sign up to get free protection for your applications and to get access to all the features.
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