pnmatrix 1.2.4

Sign up to get free protection for your applications and to get access to all the features.
Files changed (111) hide show
  1. checksums.yaml +7 -0
  2. data/ext/nmatrix/binary_format.txt +53 -0
  3. data/ext/nmatrix/data/complex.h +388 -0
  4. data/ext/nmatrix/data/data.cpp +274 -0
  5. data/ext/nmatrix/data/data.h +651 -0
  6. data/ext/nmatrix/data/meta.h +64 -0
  7. data/ext/nmatrix/data/ruby_object.h +386 -0
  8. data/ext/nmatrix/extconf.rb +70 -0
  9. data/ext/nmatrix/math/asum.h +99 -0
  10. data/ext/nmatrix/math/cblas_enums.h +36 -0
  11. data/ext/nmatrix/math/cblas_templates_core.h +507 -0
  12. data/ext/nmatrix/math/gemm.h +241 -0
  13. data/ext/nmatrix/math/gemv.h +178 -0
  14. data/ext/nmatrix/math/getrf.h +255 -0
  15. data/ext/nmatrix/math/getrs.h +121 -0
  16. data/ext/nmatrix/math/imax.h +82 -0
  17. data/ext/nmatrix/math/laswp.h +165 -0
  18. data/ext/nmatrix/math/long_dtype.h +62 -0
  19. data/ext/nmatrix/math/magnitude.h +54 -0
  20. data/ext/nmatrix/math/math.h +751 -0
  21. data/ext/nmatrix/math/nrm2.h +165 -0
  22. data/ext/nmatrix/math/rot.h +117 -0
  23. data/ext/nmatrix/math/rotg.h +106 -0
  24. data/ext/nmatrix/math/scal.h +71 -0
  25. data/ext/nmatrix/math/trsm.h +336 -0
  26. data/ext/nmatrix/math/util.h +162 -0
  27. data/ext/nmatrix/math.cpp +1368 -0
  28. data/ext/nmatrix/nm_memory.h +60 -0
  29. data/ext/nmatrix/nmatrix.cpp +285 -0
  30. data/ext/nmatrix/nmatrix.h +476 -0
  31. data/ext/nmatrix/ruby_constants.cpp +151 -0
  32. data/ext/nmatrix/ruby_constants.h +106 -0
  33. data/ext/nmatrix/ruby_nmatrix.c +3130 -0
  34. data/ext/nmatrix/storage/common.cpp +77 -0
  35. data/ext/nmatrix/storage/common.h +183 -0
  36. data/ext/nmatrix/storage/dense/dense.cpp +1096 -0
  37. data/ext/nmatrix/storage/dense/dense.h +129 -0
  38. data/ext/nmatrix/storage/list/list.cpp +1628 -0
  39. data/ext/nmatrix/storage/list/list.h +138 -0
  40. data/ext/nmatrix/storage/storage.cpp +730 -0
  41. data/ext/nmatrix/storage/storage.h +99 -0
  42. data/ext/nmatrix/storage/yale/class.h +1139 -0
  43. data/ext/nmatrix/storage/yale/iterators/base.h +143 -0
  44. data/ext/nmatrix/storage/yale/iterators/iterator.h +131 -0
  45. data/ext/nmatrix/storage/yale/iterators/row.h +450 -0
  46. data/ext/nmatrix/storage/yale/iterators/row_stored.h +140 -0
  47. data/ext/nmatrix/storage/yale/iterators/row_stored_nd.h +169 -0
  48. data/ext/nmatrix/storage/yale/iterators/stored_diagonal.h +124 -0
  49. data/ext/nmatrix/storage/yale/math/transpose.h +110 -0
  50. data/ext/nmatrix/storage/yale/yale.cpp +2074 -0
  51. data/ext/nmatrix/storage/yale/yale.h +203 -0
  52. data/ext/nmatrix/types.h +55 -0
  53. data/ext/nmatrix/util/io.cpp +279 -0
  54. data/ext/nmatrix/util/io.h +115 -0
  55. data/ext/nmatrix/util/sl_list.cpp +627 -0
  56. data/ext/nmatrix/util/sl_list.h +144 -0
  57. data/ext/nmatrix/util/util.h +78 -0
  58. data/lib/nmatrix/blas.rb +378 -0
  59. data/lib/nmatrix/cruby/math.rb +744 -0
  60. data/lib/nmatrix/enumerate.rb +253 -0
  61. data/lib/nmatrix/homogeneous.rb +241 -0
  62. data/lib/nmatrix/io/fortran_format.rb +138 -0
  63. data/lib/nmatrix/io/harwell_boeing.rb +221 -0
  64. data/lib/nmatrix/io/market.rb +263 -0
  65. data/lib/nmatrix/io/point_cloud.rb +189 -0
  66. data/lib/nmatrix/jruby/decomposition.rb +24 -0
  67. data/lib/nmatrix/jruby/enumerable.rb +13 -0
  68. data/lib/nmatrix/jruby/error.rb +4 -0
  69. data/lib/nmatrix/jruby/math.rb +501 -0
  70. data/lib/nmatrix/jruby/nmatrix_java.rb +840 -0
  71. data/lib/nmatrix/jruby/operators.rb +283 -0
  72. data/lib/nmatrix/jruby/slice.rb +264 -0
  73. data/lib/nmatrix/lapack_core.rb +181 -0
  74. data/lib/nmatrix/lapack_plugin.rb +44 -0
  75. data/lib/nmatrix/math.rb +953 -0
  76. data/lib/nmatrix/mkmf.rb +100 -0
  77. data/lib/nmatrix/monkeys.rb +137 -0
  78. data/lib/nmatrix/nmatrix.rb +1172 -0
  79. data/lib/nmatrix/rspec.rb +75 -0
  80. data/lib/nmatrix/shortcuts.rb +1163 -0
  81. data/lib/nmatrix/version.rb +39 -0
  82. data/lib/nmatrix/yale_functions.rb +118 -0
  83. data/lib/nmatrix.rb +28 -0
  84. data/spec/00_nmatrix_spec.rb +892 -0
  85. data/spec/01_enum_spec.rb +196 -0
  86. data/spec/02_slice_spec.rb +407 -0
  87. data/spec/03_nmatrix_monkeys_spec.rb +80 -0
  88. data/spec/2x2_dense_double.mat +0 -0
  89. data/spec/4x4_sparse.mat +0 -0
  90. data/spec/4x5_dense.mat +0 -0
  91. data/spec/blas_spec.rb +215 -0
  92. data/spec/elementwise_spec.rb +311 -0
  93. data/spec/homogeneous_spec.rb +100 -0
  94. data/spec/io/fortran_format_spec.rb +88 -0
  95. data/spec/io/harwell_boeing_spec.rb +98 -0
  96. data/spec/io/test.rua +9 -0
  97. data/spec/io_spec.rb +159 -0
  98. data/spec/lapack_core_spec.rb +482 -0
  99. data/spec/leakcheck.rb +16 -0
  100. data/spec/math_spec.rb +1363 -0
  101. data/spec/nmatrix_yale_resize_test_associations.yaml +2802 -0
  102. data/spec/nmatrix_yale_spec.rb +286 -0
  103. data/spec/rspec_monkeys.rb +56 -0
  104. data/spec/rspec_spec.rb +35 -0
  105. data/spec/shortcuts_spec.rb +474 -0
  106. data/spec/slice_set_spec.rb +162 -0
  107. data/spec/spec_helper.rb +172 -0
  108. data/spec/stat_spec.rb +214 -0
  109. data/spec/test.pcd +20 -0
  110. data/spec/utm5940.mtx +83844 -0
  111. metadata +295 -0
@@ -0,0 +1,953 @@
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
+ # == math.rb
25
+ #
26
+ # Math functionality for NMatrix, along with any NMatrix instance
27
+ # methods that correspond to ATLAS/BLAS/LAPACK functions (e.g.,
28
+ # laswp).
29
+ #++
30
+
31
+ class NMatrix
32
+
33
+ module NMMath #:nodoc:
34
+ METHODS_ARITY_2 = [:atan2, :ldexp, :hypot]
35
+ METHODS_ARITY_1 = [:cos, :sin, :tan, :acos, :asin, :atan, :cosh, :sinh, :tanh, :acosh,
36
+ :asinh, :atanh, :exp, :log2, :log10, :sqrt, :cbrt, :erf, :erfc, :gamma, :-@]
37
+ end
38
+
39
+ # Methods for generating permutation matrix from LU factorization results.
40
+ module FactorizeLUMethods
41
+ class << self
42
+ def permutation_matrix_from(pivot_array)
43
+ perm_arry = permutation_array_for(pivot_array)
44
+ n = NMatrix.zeros(perm_arry.size, dtype: :byte)
45
+
46
+ perm_arry.each_with_index { |e, i| n[e,i] = 1 }
47
+
48
+ n
49
+ end
50
+
51
+ def permutation_array_for(pivot_array)
52
+ perm_arry = Array.new(pivot_array.size) { |i| i }
53
+ perm_arry.each_index do |i|
54
+ #the pivot indices returned by LAPACK getrf are indexed starting
55
+ #from 1, so we need to subtract 1 here
56
+ perm_arry[i], perm_arry[pivot_array[i]-1] = perm_arry[pivot_array[i]-1], perm_arry[i]
57
+ end
58
+
59
+ perm_arry
60
+ end
61
+ end
62
+ end
63
+
64
+ #
65
+ # call-seq:
66
+ # invert! -> NMatrix
67
+ #
68
+ # Use LAPACK to calculate the inverse of the matrix (in-place) if available.
69
+ # Only works on dense matrices. Alternatively uses in-place Gauss-Jordan
70
+ # elimination.
71
+ #
72
+ # * *Raises* :
73
+ # - +StorageTypeError+ -> only implemented on dense matrices.
74
+ # - +ShapeError+ -> matrix must be square.
75
+ # - +DataTypeError+ -> cannot invert an integer matrix in-place.
76
+ #
77
+ def invert!
78
+ raise(StorageTypeError, "invert only works on dense matrices currently") unless self.dense?
79
+ raise(ShapeError, "Cannot invert non-square matrix") unless self.dim == 2 && self.shape[0] == self.shape[1]
80
+ raise(DataTypeError, "Cannot invert an integer matrix in-place") if self.integer_dtype?
81
+
82
+ #No internal implementation of getri, so use this other function
83
+ __inverse__(self, true)
84
+ end
85
+
86
+ #
87
+ # call-seq:
88
+ # invert -> NMatrix
89
+ #
90
+ # Make a copy of the matrix, then invert using Gauss-Jordan elimination.
91
+ # Works without LAPACK.
92
+ #
93
+ # * *Returns* :
94
+ # - A dense NMatrix. Will be the same type as the input NMatrix,
95
+ # except if the input is an integral dtype, in which case it will be a
96
+ # :float64 NMatrix.
97
+ #
98
+ # * *Raises* :
99
+ # - +StorageTypeError+ -> only implemented on dense matrices.
100
+ # - +ShapeError+ -> matrix must be square.
101
+ #
102
+ def invert
103
+ #write this in terms of invert! so plugins will only have to overwrite
104
+ #invert! and not invert
105
+ if self.integer_dtype?
106
+ cloned = self.cast(dtype: :float64)
107
+ cloned.invert!
108
+ else
109
+ cloned = self.clone
110
+ cloned.invert!
111
+ end
112
+ end
113
+ alias :inverse :invert
114
+
115
+ # call-seq:
116
+ # exact_inverse! -> NMatrix
117
+ #
118
+ # Calulates inverse_exact of a matrix of size 2 or 3.
119
+ # Only works on dense matrices.
120
+ #
121
+ # * *Raises* :
122
+ # - +DataTypeError+ -> cannot invert an integer matrix in-place.
123
+ # - +NotImplementedError+ -> cannot find exact inverse of matrix with size greater than 3 #
124
+ def exact_inverse!
125
+ raise(ShapeError, "Cannot invert non-square matrix") unless self.dim == 2 && self.shape[0] == self.shape[1]
126
+ raise(DataTypeError, "Cannot invert an integer matrix in-place") if self.integer_dtype?
127
+ #No internal implementation of getri, so use this other function
128
+ n = self.shape[0]
129
+ if n>3
130
+ raise(NotImplementedError, "Cannot find exact inverse of matrix of size greater than 3")
131
+ else
132
+ clond=self.clone
133
+ __inverse_exact__(clond, n, n)
134
+ end
135
+ end
136
+
137
+ #
138
+ # call-seq:
139
+ # exact_inverse -> NMatrix
140
+ #
141
+ # Make a copy of the matrix, then invert using exact_inverse
142
+ #
143
+ # * *Returns* :
144
+ # - A dense NMatrix. Will be the same type as the input NMatrix,
145
+ # except if the input is an integral dtype, in which case it will be a
146
+ # :float64 NMatrix.
147
+ #
148
+ # * *Raises* :
149
+ # - +StorageTypeError+ -> only implemented on dense matrices.
150
+ # - +ShapeError+ -> matrix must be square.
151
+ # - +NotImplementedError+ -> cannot find exact inverse of matrix with size greater than 3
152
+ #
153
+ def exact_inverse
154
+ #write this in terms of exact_inverse! so plugins will only have to overwrite
155
+ #exact_inverse! and not exact_inverse
156
+ if self.integer_dtype?
157
+ cloned = self.cast(dtype: :float64)
158
+ cloned.exact_inverse!
159
+ else
160
+ cloned = self.clone
161
+ cloned.exact_inverse!
162
+ end
163
+ end
164
+ alias :invert_exactly :exact_inverse
165
+
166
+
167
+
168
+ #
169
+ # call-seq:
170
+ # pinv -> NMatrix
171
+ #
172
+ # Compute the Moore-Penrose pseudo-inverse of a matrix using its
173
+ # singular value decomposition (SVD).
174
+ #
175
+ # This function requires the nmatrix-atlas gem installed.
176
+ #
177
+ # * *Arguments* :
178
+ # - +tolerance(optional)+ -> Cutoff for small singular values.
179
+ #
180
+ # * *Returns* :
181
+ # - Pseudo-inverse matrix.
182
+ #
183
+ # * *Raises* :
184
+ # - +NotImplementedError+ -> If called without nmatrix-atlas or nmatrix-lapacke gem.
185
+ # - +TypeError+ -> If called without float or complex data type.
186
+ #
187
+ # * *Examples* :
188
+ #
189
+ # a = NMatrix.new([2,2],[1,2,
190
+ # 3,4], dtype: :float64)
191
+ # a.pinv # => [ [-2.0000000000000018, 1.0000000000000007]
192
+ # [1.5000000000000016, -0.5000000000000008] ]
193
+ #
194
+ # b = NMatrix.new([4,1],[1,2,3,4], dtype: :float64)
195
+ # b.pinv # => [ [ 0.03333333, 0.06666667, 0.99999999, 0.13333333] ]
196
+ #
197
+ # == References
198
+ #
199
+ # * https://en.wikipedia.org/wiki/Moore%E2%80%93Penrose_pseudoinverse
200
+ # * G. Strang, Linear Algebra and Its Applications, 2nd Ed., Orlando, FL, Academic Press
201
+ #
202
+ def pinv(tolerance = 1e-15)
203
+ raise DataTypeError, "pinv works only with matrices of float or complex data type" unless
204
+ [:float32, :float64, :complex64, :complex128].include?(dtype)
205
+ if self.complex_dtype?
206
+ u, s, vt = self.complex_conjugate.gesvd # singular value decomposition
207
+ else
208
+ u, s, vt = self.gesvd
209
+ end
210
+ rows = self.shape[0]
211
+ cols = self.shape[1]
212
+ if rows < cols
213
+ u_reduced = u
214
+ vt_reduced = vt[0..rows - 1, 0..cols - 1].transpose
215
+ else
216
+ u_reduced = u[0..rows - 1, 0..cols - 1]
217
+ vt_reduced = vt.transpose
218
+ end
219
+ largest_singular_value = s.max.to_f
220
+ cutoff = tolerance * largest_singular_value
221
+ (0...[rows, cols].min).each do |i|
222
+ s[i] = 1 / s[i] if s[i] > cutoff
223
+ s[i] = 0 if s[i] <= cutoff
224
+ end
225
+ multiplier = u_reduced.dot(NMatrix.diagonal(s.to_a)).transpose
226
+ vt_reduced.dot(multiplier)
227
+ end
228
+ alias :pseudo_inverse :pinv
229
+ alias :pseudoinverse :pinv
230
+
231
+
232
+ #
233
+ # call-seq:
234
+ # adjugate! -> NMatrix
235
+ #
236
+ # Calculate the adjugate of the matrix (in-place).
237
+ # Only works on dense matrices.
238
+ #
239
+ # * *Raises* :
240
+ # - +StorageTypeError+ -> only implemented on dense matrices.
241
+ # - +ShapeError+ -> matrix must be square.
242
+ # - +DataTypeError+ -> cannot calculate adjugate of an integer matrix in-place.
243
+ #
244
+ def adjugate!
245
+ raise(StorageTypeError, "adjugate only works on dense matrices currently") unless self.dense?
246
+ raise(ShapeError, "Cannot calculate adjugate of a non-square matrix") unless self.dim == 2 && self.shape[0] == self.shape[1]
247
+ raise(DataTypeError, "Cannot calculate adjugate of an integer matrix in-place") if self.integer_dtype?
248
+ d = self.det
249
+ self.invert!
250
+ self.map! { |e| e * d }
251
+ self
252
+ end
253
+ alias :adjoint! :adjugate!
254
+
255
+ #
256
+ # call-seq:
257
+ # adjugate -> NMatrix
258
+ #
259
+ # Make a copy of the matrix and calculate the adjugate of the matrix.
260
+ # Only works on dense matrices.
261
+ #
262
+ # * *Returns* :
263
+ # - A dense NMatrix. Will be the same type as the input NMatrix,
264
+ # except if the input is an integral dtype, in which case it will be a
265
+ # :float64 NMatrix.
266
+ #
267
+ # * *Raises* :
268
+ # - +StorageTypeError+ -> only implemented on dense matrices.
269
+ # - +ShapeError+ -> matrix must be square.
270
+ #
271
+ def adjugate
272
+ raise(StorageTypeError, "adjugate only works on dense matrices currently") unless self.dense?
273
+ raise(ShapeError, "Cannot calculate adjugate of a non-square matrix") unless self.dim == 2 && self.shape[0] == self.shape[1]
274
+ d = self.det
275
+ mat = self.invert
276
+ mat.map! { |e| e * d }
277
+ mat
278
+ end
279
+ alias :adjoint :adjugate
280
+
281
+ # Reduce self to upper hessenberg form using householder transforms.
282
+ #
283
+ # == References
284
+ #
285
+ # * http://en.wikipedia.org/wiki/Hessenberg_matrix
286
+ # * http://www.mymathlib.com/c_source/matrices/eigen/hessenberg_orthog.c
287
+ def hessenberg
288
+ clone.hessenberg!
289
+ end
290
+
291
+ # Destructive version of #hessenberg
292
+ def hessenberg!
293
+ raise ShapeError, "Trying to reduce non 2D matrix to hessenberg form" if
294
+ shape.size != 2
295
+ raise ShapeError, "Trying to reduce non-square matrix to hessenberg form" if
296
+ shape[0] != shape[1]
297
+ raise StorageTypeError, "Matrix must be dense" if stype != :dense
298
+ raise TypeError, "Works with float matrices only" unless
299
+ [:float64,:float32].include?(dtype)
300
+
301
+ __hessenberg__(self)
302
+ self
303
+ end
304
+
305
+
306
+ # call-seq:
307
+ # matrix_norm -> Numeric
308
+ #
309
+ # Calculates the selected norm (defaults to 2-norm) of a 2D matrix.
310
+ #
311
+ # This should be used for small or medium sized matrices.
312
+ # For greater matrices, there should be a separate implementation where
313
+ # the norm is estimated rather than computed, for the sake of computation speed.
314
+ #
315
+ # Currently implemented norms are 1-norm, 2-norm, Frobenius, Infinity.
316
+ # A minus on the 1, 2 and inf norms returns the minimum instead of the maximum value.
317
+ #
318
+ # Tested mainly with dense matrices. Further checks and modifications might
319
+ # be necessary for sparse matrices.
320
+ #
321
+ # * *Returns* :
322
+ # - The selected norm of the matrix.
323
+ # * *Raises* :
324
+ # - +NotImplementedError+ -> norm can be calculated only for 2D matrices
325
+ # - +ArgumentError+ -> unrecognized norm
326
+ #
327
+ def matrix_norm type = 2
328
+ raise(NotImplementedError, "norm can be calculated only for 2D matrices") unless self.dim == 2
329
+ raise(NotImplementedError, "norm only implemented for dense storage") unless self.stype == :dense
330
+ raise(ArgumentError, "norm not defined for byte dtype")if self.dtype == :byte
331
+ case type
332
+ when nil, 2, -2
333
+ return self.two_matrix_norm (type == -2)
334
+ when 1, -1
335
+ return self.one_matrix_norm (type == -1)
336
+ when :frobenius, :fro
337
+ return self.fro_matrix_norm
338
+ when :infinity, :inf, :'-inf', :'-infinity'
339
+ return self.inf_matrix_norm (type == :'-inf' || type == :'-infinity')
340
+ else
341
+ raise ArgumentError.new("argument must be a valid integer or symbol")
342
+ end
343
+ end
344
+
345
+ # Calculate the variance co-variance matrix
346
+ #
347
+ # == Options
348
+ #
349
+ # * +:for_sample_data+ - Default true. If set to false will consider the denominator for
350
+ # population data (i.e. N, as opposed to N-1 for sample data).
351
+ #
352
+ # == References
353
+ #
354
+ # * http://stattrek.com/matrix-algebra/covariance-matrix.aspx
355
+ def cov(opts={})
356
+ raise TypeError, "Only works for non-integer dtypes" if integer_dtype?
357
+ opts = {
358
+ for_sample_data: true
359
+ }.merge(opts)
360
+
361
+ denominator = opts[:for_sample_data] ? rows - 1 : rows
362
+ ones = NMatrix.ones [rows,1]
363
+ deviation_scores = self - ones.dot(ones.transpose).dot(self) / rows
364
+ deviation_scores.transpose.dot(deviation_scores) / denominator
365
+ end
366
+
367
+ # Calculate the correlation matrix.
368
+ def corr
369
+ raise NotImplementedError, "Does not work for complex dtypes" if complex_dtype?
370
+ standard_deviation = std
371
+ cov / (standard_deviation.transpose.dot(standard_deviation))
372
+ end
373
+
374
+ # Raise a square matrix to a power. Be careful of numeric overflows!
375
+ # In case *n* is 0, an identity matrix of the same dimension is returned. In case
376
+ # of negative *n*, the matrix is inverted and the absolute value of *n* taken
377
+ # for computing the power.
378
+ #
379
+ # == Arguments
380
+ #
381
+ # * +n+ - Integer to which self is to be raised.
382
+ #
383
+ # == References
384
+ #
385
+ # * R.G Dromey - How to Solve it by Computer. Link -
386
+ # http://www.amazon.com/Solve-Computer-Prentice-Hall-International-Science/dp/0134340019/ref=sr_1_1?ie=UTF8&qid=1422605572&sr=8-1&keywords=how+to+solve+it+by+computer
387
+ def pow n
388
+ raise ShapeError, "Only works with 2D square matrices." if
389
+ shape[0] != shape[1] or shape.size != 2
390
+ raise TypeError, "Only works with integer powers" unless n.is_a?(Integer)
391
+
392
+ sequence = (integer_dtype? ? self.cast(dtype: :int64) : self).clone
393
+ product = NMatrix.eye shape[0], dtype: sequence.dtype, stype: sequence.stype
394
+
395
+ if n == 0
396
+ return NMatrix.eye(shape, dtype: dtype, stype: stype)
397
+ elsif n == 1
398
+ return sequence
399
+ elsif n < 0
400
+ n = n.abs
401
+ sequence.invert!
402
+ product = NMatrix.eye shape[0], dtype: sequence.dtype, stype: sequence.stype
403
+ end
404
+
405
+ # Decompose n to reduce the number of multiplications.
406
+ while n > 0
407
+ product = product.dot(sequence) if n % 2 == 1
408
+ n = n / 2
409
+ sequence = sequence.dot(sequence)
410
+ end
411
+
412
+ product
413
+ end
414
+
415
+ # Compute the Kronecker product of +self+ and other NMatrix
416
+ #
417
+ # === Arguments
418
+ #
419
+ # * +mat+ - A 2D NMatrix object
420
+ #
421
+ # === Usage
422
+ #
423
+ # a = NMatrix.new([2,2],[1,2,
424
+ # 3,4])
425
+ # b = NMatrix.new([2,3],[1,1,1,
426
+ # 1,1,1], dtype: :float64)
427
+ # a.kron_prod(b) # => [ [1.0, 1.0, 1.0, 2.0, 2.0, 2.0]
428
+ # [1.0, 1.0, 1.0, 2.0, 2.0, 2.0]
429
+ # [3.0, 3.0, 3.0, 4.0, 4.0, 4.0]
430
+ # [3.0, 3.0, 3.0, 4.0, 4.0, 4.0] ]
431
+ #
432
+ def kron_prod(mat)
433
+ unless self.dimensions==2 and mat.dimensions==2
434
+ raise ShapeError, "Implemented for 2D NMatrix objects only."
435
+ end
436
+
437
+ # compute the shape [n,m] of the product matrix
438
+ n, m = self.shape[0]*mat.shape[0], self.shape[1]*mat.shape[1]
439
+ # compute the entries of the product matrix
440
+ kron_prod_array = []
441
+ if self.yale?
442
+ # +:yale+ requires to get the row by copy in order to apply +#transpose+ to it
443
+ self.each_row(getby=:copy) do |selfr|
444
+ mat.each_row do |matr|
445
+ kron_prod_array += (selfr.transpose.dot matr).to_flat_a
446
+ end
447
+ end
448
+ else
449
+ self.each_row do |selfr|
450
+ mat.each_row do |matr|
451
+ kron_prod_array += (selfr.transpose.dot matr).to_flat_a
452
+ end
453
+ end
454
+ end
455
+
456
+ NMatrix.new([n,m], kron_prod_array)
457
+ end
458
+
459
+ #
460
+ # call-seq:
461
+ # trace -> Numeric
462
+ #
463
+ # Calculates the trace of an nxn matrix.
464
+ #
465
+ # * *Raises* :
466
+ # - +ShapeError+ -> Expected square matrix
467
+ #
468
+ # * *Returns* :
469
+ # - The trace of the matrix (a numeric value)
470
+ #
471
+ def trace
472
+ raise(ShapeError, "Expected square matrix") unless self.shape[0] == self.shape[1] && self.dim == 2
473
+
474
+ (0...self.shape[0]).inject(0) do |total,i|
475
+ total + self[i,i]
476
+ end
477
+ end
478
+
479
+ ##
480
+ # call-seq:
481
+ # mean() -> NMatrix
482
+ # mean(dimen) -> NMatrix
483
+ #
484
+ # Calculates the mean along the specified dimension.
485
+ #
486
+ # This will force integer types to float64 dtype.
487
+ #
488
+ # @see #inject_rank
489
+ #
490
+ def mean(dimen=0)
491
+ reduce_dtype = nil
492
+ if integer_dtype? then
493
+ reduce_dtype = :float64
494
+ end
495
+ inject_rank(dimen, 0.0, reduce_dtype) do |mean, sub_mat|
496
+ mean + sub_mat
497
+ end / shape[dimen]
498
+ end
499
+
500
+ ##
501
+ # call-seq:
502
+ # sum() -> NMatrix
503
+ # cumsum() -> NMatrix
504
+ # sum(dimen) -> NMatrix
505
+ # cumsum(dimen) -> NMatrix
506
+ #
507
+ # Calculates the sum along the specified dimension.
508
+ #
509
+ # @see #inject_rank
510
+ def sum(dimen=0)
511
+ inject_rank(dimen, 0.0) do |sum, sub_mat|
512
+ sum + sub_mat
513
+ end
514
+ end
515
+ alias :cumsum :sum
516
+
517
+ ##
518
+ # call-seq:
519
+ # min() -> NMatrix
520
+ # min(dimen) -> NMatrix
521
+ #
522
+ # Calculates the minimum along the specified dimension.
523
+ #
524
+ # @see #inject_rank
525
+ #
526
+ def min(dimen=0)
527
+ inject_rank(dimen) do |min, sub_mat|
528
+ if min.is_a? NMatrix then
529
+ min * (min <= sub_mat).cast(self.stype, self.dtype) + ((min)*0.0 + (min > sub_mat).cast(self.stype, self.dtype)) * sub_mat
530
+ else
531
+ min <= sub_mat ? min : sub_mat
532
+ end
533
+ end
534
+ end
535
+
536
+ ##
537
+ # call-seq:
538
+ # max() -> NMatrix
539
+ # max(dimen) -> NMatrix
540
+ #
541
+ # Calculates the maximum along the specified dimension.
542
+ #
543
+ # @see #inject_rank
544
+ #
545
+ def max(dimen=0)
546
+ inject_rank(dimen) do |max, sub_mat|
547
+ if max.is_a? NMatrix then
548
+ max * (max >= sub_mat).cast(self.stype, self.dtype) + ((max)*0.0 + (max < sub_mat).cast(self.stype, self.dtype)) * sub_mat
549
+ else
550
+ max >= sub_mat ? max : sub_mat
551
+ end
552
+ end
553
+ end
554
+
555
+
556
+ ##
557
+ # call-seq:
558
+ # variance() -> NMatrix
559
+ # variance(dimen) -> NMatrix
560
+ #
561
+ # Calculates the sample variance along the specified dimension.
562
+ #
563
+ # This will force integer types to float64 dtype.
564
+ #
565
+ # @see #inject_rank
566
+ #
567
+ def variance(dimen=0)
568
+ reduce_dtype = nil
569
+ if integer_dtype? then
570
+ reduce_dtype = :float64
571
+ end
572
+ m = mean(dimen)
573
+ inject_rank(dimen, 0.0, reduce_dtype) do |var, sub_mat|
574
+ var + (m - sub_mat)*(m - sub_mat)/(shape[dimen]-1)
575
+ end
576
+ end
577
+
578
+ ##
579
+ # call-seq:
580
+ # std() -> NMatrix
581
+ # std(dimen) -> NMatrix
582
+ #
583
+ #
584
+ # Calculates the sample standard deviation along the specified dimension.
585
+ #
586
+ # This will force integer types to float64 dtype.
587
+ #
588
+ # @see #inject_rank
589
+ #
590
+ def std(dimen=0)
591
+ variance(dimen).sqrt
592
+ end
593
+
594
+
595
+ #
596
+ # call-seq:
597
+ # abs_dtype -> Symbol
598
+ #
599
+ # Returns the dtype of the result of a call to #abs. In most cases, this is the same as dtype; it should only differ
600
+ # for :complex64 (where it's :float32) and :complex128 (:float64).
601
+ def abs_dtype
602
+ if self.dtype == :complex64
603
+ :float32
604
+ elsif self.dtype == :complex128
605
+ :float64
606
+ else
607
+ self.dtype
608
+ end
609
+ end
610
+
611
+
612
+ #
613
+ # call-seq:
614
+ # abs -> NMatrix
615
+ #
616
+ # Maps all values in a matrix to their absolute values.
617
+ def abs
618
+ if stype == :dense
619
+ self.__dense_map__ { |v| v.abs }
620
+ elsif stype == :list
621
+ # FIXME: Need __list_map_stored__, but this will do for now.
622
+ self.__list_map_merged_stored__(nil, nil) { |v,dummy| v.abs }
623
+ else
624
+ self.__yale_map_stored__ { |v| v.abs }
625
+ end.cast(self.stype, abs_dtype)
626
+ end
627
+
628
+ # Norm calculation methods
629
+ # Frobenius norm: the Euclidean norm of the matrix, treated as if it were a vector
630
+ def fro_matrix_norm
631
+ #float64 has to be used in any case, since nrm2 will not yield correct result for float32
632
+ self_cast = self.cast(:dtype => :float64)
633
+
634
+ column_vector = self_cast.reshape([self.size, 1])
635
+
636
+ return column_vector.nrm2
637
+ end
638
+
639
+ # 2-norm: the largest/smallest singular value of the matrix
640
+ def two_matrix_norm minus = false
641
+
642
+ self_cast = self.cast(:dtype => :float64)
643
+
644
+ #TODO: confirm if this is the desired svd calculation
645
+ svd = self_cast.gesvd
646
+ return svd[1][0, 0] unless minus
647
+ return svd[1][svd[1].rows-1, svd[1].cols-1]
648
+ end
649
+
650
+ # 1-norm: the maximum/minimum absolute column sum of the matrix
651
+ def one_matrix_norm minus = false
652
+ #TODO: change traversing method for sparse matrices
653
+ number_of_columns = self.cols
654
+ col_sums = []
655
+
656
+ number_of_columns.times do |i|
657
+ col_sums << self.col(i).inject(0) { |sum, number| sum += number.abs}
658
+ end
659
+
660
+ return col_sums.max unless minus
661
+ return col_sums.min
662
+ end
663
+
664
+ # Infinity norm: the maximum/minimum absolute row sum of the matrix
665
+ def inf_matrix_norm minus = false
666
+ number_of_rows = self.rows
667
+ row_sums = []
668
+
669
+ number_of_rows.times do |i|
670
+ row_sums << self.row(i).inject(0) { |sum, number| sum += number.abs}
671
+ end
672
+
673
+ return row_sums.max unless minus
674
+ return row_sums.min
675
+ end
676
+
677
+ #
678
+ # call-seq:
679
+ # positive_definite? -> boolean
680
+ #
681
+ # A matrix is positive definite if it’s symmetric and all its eigenvalues are positive
682
+ #
683
+ # * *Returns* :
684
+ # - A boolean value telling if the NMatrix is positive definite or not.
685
+ # * *Raises* :
686
+ # - +ShapeError+ -> Must be used on square matrices.
687
+ #
688
+ def positive_definite?
689
+ raise(ShapeError, "positive definite calculated only for square matrices") unless
690
+ self.dim == 2 && self.shape[0] == self.shape[1]
691
+ cond = 0
692
+ while cond != self.cols
693
+ if self[0..cond, 0..cond].det <= 0
694
+ return false
695
+ end
696
+ cond += 1
697
+ end
698
+ true
699
+ end
700
+
701
+ #
702
+ # call-seq:
703
+ # svd_rank() -> int
704
+ # svd_rank(tolerence) ->int
705
+ # Gives rank of the matrix based on the singular value decomposition.
706
+ # The rank of a matrix is computed as the number of diagonal elements in Sigma that are larger than a tolerance
707
+ #
708
+ #* *Returns* :
709
+ # - An integer equal to the rank of the matrix
710
+ #* *Raises* :
711
+ # - +ShapeError+ -> Is only computable on 2-D matrices
712
+ #
713
+ def svd_rank(tolerence="default")
714
+ raise(ShapeError, "rank calculated only for 2-D matrices") unless
715
+ self.dim == 2
716
+
717
+ sigmas = self.gesvd[1].to_a.flatten
718
+ eps = NMatrix::FLOAT64_EPSILON
719
+
720
+ # epsilon depends on the width of the number
721
+ if (self.dtype == :float32 || self.dtype == :complex64)
722
+ eps = NMatrix::FLOAT32_EPSILON
723
+ end
724
+ case tolerence
725
+ when "default"
726
+ tolerence = self.shape.max * sigmas.max * eps # tolerence of a Matrix A is max(size(A))*eps(norm(A)). norm(A) is nearly equal to max(sigma of A)
727
+ end
728
+ return sigmas.map { |x| x > tolerence ? 1 : 0 }.reduce(:+)
729
+ end
730
+
731
+
732
+
733
+ protected
734
+ # Define the element-wise operations for lists. Note that the __list_map_merged_stored__ iterator returns a Ruby Object
735
+ # matrix, which we then cast back to the appropriate type. If you don't want that, you can redefine these functions in
736
+ # your own code.
737
+ {add: :+, sub: :-, mul: :*, div: :/, pow: :**, mod: :%}.each_pair do |ewop, op|
738
+ define_method("__list_elementwise_#{ewop}__") do |rhs|
739
+ self.__list_map_merged_stored__(rhs, nil) { |l,r| l.send(op,r) }.cast(stype, NMatrix.upcast(dtype, rhs.dtype))
740
+ end
741
+ define_method("__dense_elementwise_#{ewop}__") do |rhs|
742
+ self.__dense_map_pair__(rhs) { |l,r| l.send(op,r) }.cast(stype, NMatrix.upcast(dtype, rhs.dtype))
743
+ end
744
+ define_method("__yale_elementwise_#{ewop}__") do |rhs|
745
+ self.__yale_map_merged_stored__(rhs, nil) { |l,r| l.send(op,r) }.cast(stype, NMatrix.upcast(dtype, rhs.dtype))
746
+ end
747
+ define_method("__list_scalar_#{ewop}__") do |rhs|
748
+ self.__list_map_merged_stored__(rhs, nil) { |l,r| l.send(op,r) }.cast(stype, NMatrix.upcast(dtype, NMatrix.min_dtype(rhs)))
749
+ end
750
+ define_method("__yale_scalar_#{ewop}__") do |rhs|
751
+ self.__yale_map_stored__ { |l| l.send(op,rhs) }.cast(stype, NMatrix.upcast(dtype, NMatrix.min_dtype(rhs)))
752
+ end
753
+ define_method("__dense_scalar_#{ewop}__") do |rhs|
754
+ self.__dense_map__ { |l| l.send(op,rhs) }.cast(stype, NMatrix.upcast(dtype, NMatrix.min_dtype(rhs)))
755
+ end
756
+ end
757
+
758
+ # These don't actually take an argument -- they're called reverse-polish style on the matrix.
759
+ # This group always gets casted to float64.
760
+ [:log, :log2, :log10, :sqrt, :sin, :cos, :tan, :acos, :asin, :atan, :cosh, :sinh, :tanh, :acosh,
761
+ :asinh, :atanh, :exp, :erf, :erfc, :gamma, :cbrt, :round].each do |ewop|
762
+ define_method("__list_unary_#{ewop}__") do
763
+ self.__list_map_stored__(nil) { |l| Math.send(ewop, l) }.cast(stype, NMatrix.upcast(dtype, :float64))
764
+ end
765
+ define_method("__yale_unary_#{ewop}__") do
766
+ self.__yale_map_stored__ { |l| Math.send(ewop, l) }.cast(stype, NMatrix.upcast(dtype, :float64))
767
+ end
768
+ define_method("__dense_unary_#{ewop}__") do
769
+ self.__dense_map__ { |l| Math.send(ewop, l) }.cast(stype, NMatrix.upcast(dtype, :float64))
770
+ end
771
+ end
772
+
773
+ #:stopdoc:
774
+ # log takes an optional single argument, the base. Default to natural log.
775
+ def __list_unary_log__(base)
776
+ self.__list_map_stored__(nil) { |l| Math.log(l, base) }.cast(stype, NMatrix.upcast(dtype, :float64))
777
+ end
778
+
779
+ def __yale_unary_log__(base)
780
+ self.__yale_map_stored__ { |l| Math.log(l, base) }.cast(stype, NMatrix.upcast(dtype, :float64))
781
+ end
782
+
783
+ def __dense_unary_log__(base)
784
+ self.__dense_map__ { |l| Math.log(l, base) }.cast(stype, NMatrix.upcast(dtype, :float64))
785
+ end
786
+
787
+ # These are for negating matrix contents using -@
788
+ def __list_unary_negate__
789
+ self.__list_map_stored__(nil) { |l| -l }.cast(stype, dtype)
790
+ end
791
+
792
+ def __yale_unary_negate__
793
+ self.__yale_map_stored__ { |l| -l }.cast(stype, dtype)
794
+ end
795
+
796
+ def __dense_unary_negate__
797
+ self.__dense_map__ { |l| -l }.cast(stype, dtype)
798
+ end
799
+ #:startdoc:
800
+
801
+ # These are for rounding each value of a matrix. Takes an optional argument
802
+ def __list_unary_round__(precision)
803
+ if self.complex_dtype?
804
+ self.__list_map_stored__(nil) { |l| Complex(l.real.round(precision), l.imag.round(precision)) }
805
+ .cast(stype, dtype)
806
+ else
807
+ self.__list_map_stored__(nil) { |l| l.round(precision) }.cast(stype, dtype)
808
+ end
809
+ end
810
+
811
+ def __yale_unary_round__(precision)
812
+ if self.complex_dtype?
813
+ self.__yale_map_stored__ { |l| Complex(l.real.round(precision), l.imag.round(precision)) }
814
+ .cast(stype, dtype)
815
+ else
816
+ self.__yale_map_stored__ { |l| l.round(precision) }.cast(stype, dtype)
817
+ end
818
+ end
819
+
820
+ def __dense_unary_round__(precision)
821
+ if self.complex_dtype?
822
+ self.__dense_map__ { |l| Complex(l.real.round(precision), l.imag.round(precision)) }
823
+ .cast(stype, dtype)
824
+ else
825
+ self.__dense_map__ { |l| l.round(precision) }.cast(stype, dtype)
826
+ end
827
+ end
828
+
829
+ # These are for calculating the floor or ceil of matrix
830
+ def dtype_for_floor_or_ceil
831
+ if self.integer_dtype? or [:complex64, :complex128, :object].include?(self.dtype)
832
+ return_dtype = dtype
833
+ elsif [:float32, :float64].include?(self.dtype)
834
+ return_dtype = :int64
835
+ end
836
+
837
+ return_dtype
838
+ end
839
+
840
+ [:floor, :ceil].each do |meth|
841
+ define_method("__list_unary_#{meth}__") do
842
+ return_dtype = dtype_for_floor_or_ceil
843
+
844
+ if [:complex64, :complex128].include?(self.dtype)
845
+ self.__list_map_stored__(nil) { |l| Complex(l.real.send(meth), l.imag.send(meth)) }.cast(stype, return_dtype)
846
+ else
847
+ self.__list_map_stored__(nil) { |l| l.send(meth) }.cast(stype, return_dtype)
848
+ end
849
+ end
850
+
851
+ define_method("__yale_unary_#{meth}__") do
852
+ return_dtype = dtype_for_floor_or_ceil
853
+
854
+ if [:complex64, :complex128].include?(self.dtype)
855
+ self.__yale_map_stored__ { |l| Complex(l.real.send(meth), l.imag.send(meth)) }.cast(stype, return_dtype)
856
+ else
857
+ self.__yale_map_stored__ { |l| l.send(meth) }.cast(stype, return_dtype)
858
+ end
859
+ end
860
+
861
+ define_method("__dense_unary_#{meth}__") do
862
+ return_dtype = dtype_for_floor_or_ceil
863
+
864
+ if [:complex64, :complex128].include?(self.dtype)
865
+ self.__dense_map__ { |l| Complex(l.real.send(meth), l.imag.send(meth)) }.cast(stype, return_dtype)
866
+ else
867
+ self.__dense_map__ { |l| l.send(meth) }.cast(stype, return_dtype)
868
+ end
869
+ end
870
+ end
871
+
872
+ # These take two arguments. One might be a matrix, and one might be a scalar.
873
+ # See also monkeys.rb, which contains Math module patches to let the first
874
+ # arg be a scalar
875
+ [:atan2, :ldexp, :hypot].each do |ewop|
876
+ define_method("__list_elementwise_#{ewop}__") do |rhs,order|
877
+ if order then
878
+ self.__list_map_merged_stored__(rhs, nil) { |r,l| Math.send(ewop,l,r) }
879
+ else
880
+ self.__list_map_merged_stored__(rhs, nil) { |l,r| Math.send(ewop,l,r) }
881
+ end.cast(stype, NMatrix.upcast(dtype, :float64))
882
+ end
883
+
884
+ define_method("__dense_elementwise_#{ewop}__") do |rhs, order|
885
+ if order then
886
+ self.__dense_map_pair__(rhs) { |r,l| Math.send(ewop,l,r) }
887
+ else
888
+ self.__dense_map_pair__(rhs) { |l,r| Math.send(ewop,l,r) }
889
+ end.cast(stype, NMatrix.upcast(dtype, :float64))
890
+ end
891
+
892
+ define_method("__yale_elementwise_#{ewop}__") do |rhs, order|
893
+ if order then
894
+ self.__yale_map_merged_stored__(rhs, nil) { |r,l| Math.send(ewop,l,r) }
895
+ else
896
+ self.__yale_map_merged_stored__(rhs, nil) { |l,r| Math.send(ewop,l,r) }
897
+ end.cast(stype, NMatrix.upcast(dtype, :float64))
898
+ end
899
+
900
+ define_method("__list_scalar_#{ewop}__") do |rhs,order|
901
+ if order then
902
+ self.__list_map_stored__(nil) { |l| Math.send(ewop, rhs, l) }
903
+ else
904
+ self.__list_map_stored__(nil) { |l| Math.send(ewop, l, rhs) }
905
+ end.cast(stype, NMatrix.upcast(dtype, :float64))
906
+ end
907
+
908
+ define_method("__yale_scalar_#{ewop}__") do |rhs,order|
909
+ if order then
910
+ self.__yale_map_stored__ { |l| Math.send(ewop, rhs, l) }
911
+ else
912
+ self.__yale_map_stored__ { |l| Math.send(ewop, l, rhs) }
913
+ end.cast(stype, NMatrix.upcast(dtype, :float64))
914
+ end
915
+
916
+ define_method("__dense_scalar_#{ewop}__") do |rhs,order|
917
+ if order
918
+ self.__dense_map__ { |l| Math.send(ewop, rhs, l) }
919
+ else
920
+ self.__dense_map__ { |l| Math.send(ewop, l, rhs) }
921
+ end.cast(stype, NMatrix.upcast(dtype, :float64))
922
+ end
923
+ end
924
+
925
+ # Equality operators do not involve a cast. We want to get back matrices of TrueClass and FalseClass.
926
+ {eqeq: :==, neq: :!=, lt: :<, gt: :>, leq: :<=, geq: :>=}.each_pair do |ewop, op|
927
+ define_method("__list_elementwise_#{ewop}__") do |rhs|
928
+ self.__list_map_merged_stored__(rhs, nil) { |l,r| l.send(op,r) }
929
+ end
930
+ define_method("__dense_elementwise_#{ewop}__") do |rhs|
931
+ self.__dense_map_pair__(rhs) { |l,r| l.send(op,r) }
932
+ end
933
+ define_method("__yale_elementwise_#{ewop}__") do |rhs|
934
+ self.__yale_map_merged_stored__(rhs, nil) { |l,r| l.send(op,r) }
935
+ end
936
+
937
+ define_method("__list_scalar_#{ewop}__") do |rhs|
938
+ self.__list_map_merged_stored__(rhs, nil) { |l,r| l.send(op,r) }
939
+ end
940
+ define_method("__yale_scalar_#{ewop}__") do |rhs|
941
+ self.__yale_map_stored__ { |l| l.send(op,rhs) }
942
+ end
943
+ define_method("__dense_scalar_#{ewop}__") do |rhs|
944
+ self.__dense_map__ { |l| l.send(op,rhs) }
945
+ end
946
+ end
947
+ end
948
+
949
+ if jruby?
950
+ require_relative "./jruby/math.rb"
951
+ else
952
+ require_relative "./cruby/math.rb"
953
+ end