pnmatrix 1.2.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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,3130 @@
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
+ // == ruby_nmatrix.c
25
+ //
26
+ // Ruby-facing NMatrix C functions. Not compiled directly -- included
27
+ // into nmatrix.cpp.
28
+ //
29
+
30
+ /*
31
+ * Forward Declarations
32
+ */
33
+
34
+ static VALUE nm_init(int argc, VALUE* argv, VALUE nm);
35
+ static VALUE nm_init_copy(VALUE copy, VALUE original);
36
+ static VALUE nm_init_transposed(VALUE self);
37
+ static VALUE nm_read(int argc, VALUE* argv, VALUE self);
38
+ static VALUE nm_write(int argc, VALUE* argv, VALUE self);
39
+ static VALUE nm_init_yale_from_old_yale(VALUE shape, VALUE dtype, VALUE ia, VALUE ja, VALUE a, VALUE from_dtype, VALUE nm);
40
+ static VALUE nm_alloc(VALUE klass);
41
+ static VALUE nm_dtype(VALUE self);
42
+ static VALUE nm_stype(VALUE self);
43
+ static VALUE nm_default_value(VALUE self);
44
+ static size_t effective_dim(STORAGE* s);
45
+ static VALUE nm_effective_dim(VALUE self);
46
+ static VALUE nm_dim(VALUE self);
47
+ static VALUE nm_offset(VALUE self);
48
+ static VALUE nm_shape(VALUE self);
49
+ static VALUE nm_supershape(VALUE self);
50
+ static VALUE nm_capacity(VALUE self);
51
+ static VALUE nm_each_with_indices(VALUE nmatrix);
52
+ static VALUE nm_each_stored_with_indices(VALUE nmatrix);
53
+ static VALUE nm_each_ordered_stored_with_indices(VALUE nmatrix);
54
+ static VALUE nm_map_stored(VALUE nmatrix);
55
+
56
+ static void init_slice_no_alloc(SLICE* slice, size_t dim, int argc, VALUE* arg, size_t* shape);
57
+ static VALUE nm_xslice(int argc, VALUE* argv, void* (*slice_func)(const STORAGE*, SLICE*), void (*delete_func)(NMATRIX*), VALUE self);
58
+ static VALUE nm_mset(int argc, VALUE* argv, VALUE self);
59
+ static VALUE nm_mget(int argc, VALUE* argv, VALUE self);
60
+ static VALUE nm_mref(int argc, VALUE* argv, VALUE self);
61
+ static VALUE nm_is_ref(VALUE self);
62
+
63
+ static VALUE is_symmetric(VALUE self, bool hermitian);
64
+
65
+ static VALUE nm_guess_dtype(VALUE self, VALUE v);
66
+ static VALUE nm_min_dtype(VALUE self, VALUE v);
67
+
68
+ static VALUE nm_data_pointer(VALUE self);
69
+
70
+ /*
71
+ * Macro defines an element-wise accessor function for some operation.
72
+ *
73
+ * This is only responsible for the Ruby accessor! You still have to write the actual functions, obviously.
74
+ */
75
+ #define DEF_ELEMENTWISE_RUBY_ACCESSOR(oper, name) \
76
+ static VALUE nm_ew_##name(VALUE left_val, VALUE right_val) { \
77
+ return elementwise_op(nm::EW_##oper, left_val, right_val); \
78
+ }
79
+
80
+ #define DEF_UNARY_RUBY_ACCESSOR(oper, name) \
81
+ static VALUE nm_unary_##name(VALUE self) { \
82
+ return unary_op(nm::UNARY_##oper, self); \
83
+ }
84
+
85
+ #define DEF_NONCOM_ELEMENTWISE_RUBY_ACCESSOR(oper, name) \
86
+ static VALUE nm_noncom_ew_##name(int argc, VALUE* argv, VALUE self) { \
87
+ if (argc > 1) { \
88
+ return noncom_elementwise_op(nm::NONCOM_EW_##oper, self, argv[0], argv[1]); \
89
+ } else { \
90
+ return noncom_elementwise_op(nm::NONCOM_EW_##oper, self, argv[0], Qfalse); \
91
+ } \
92
+ }
93
+
94
+
95
+ /*
96
+ * Macro declares a corresponding accessor function prototype for some element-wise operation.
97
+ */
98
+ #define DECL_ELEMENTWISE_RUBY_ACCESSOR(name) static VALUE nm_ew_##name(VALUE left_val, VALUE right_val);
99
+ #define DECL_UNARY_RUBY_ACCESSOR(name) static VALUE nm_unary_##name(VALUE self);
100
+ #define DECL_NONCOM_ELEMENTWISE_RUBY_ACCESSOR(name) static VALUE nm_noncom_ew_##name(int argc, VALUE* argv, VALUE self);
101
+
102
+ DECL_ELEMENTWISE_RUBY_ACCESSOR(add)
103
+ DECL_ELEMENTWISE_RUBY_ACCESSOR(subtract)
104
+ DECL_ELEMENTWISE_RUBY_ACCESSOR(multiply)
105
+ DECL_ELEMENTWISE_RUBY_ACCESSOR(divide)
106
+ DECL_ELEMENTWISE_RUBY_ACCESSOR(power)
107
+ DECL_ELEMENTWISE_RUBY_ACCESSOR(mod)
108
+ DECL_ELEMENTWISE_RUBY_ACCESSOR(eqeq)
109
+ DECL_ELEMENTWISE_RUBY_ACCESSOR(neq)
110
+ DECL_ELEMENTWISE_RUBY_ACCESSOR(lt)
111
+ DECL_ELEMENTWISE_RUBY_ACCESSOR(gt)
112
+ DECL_ELEMENTWISE_RUBY_ACCESSOR(leq)
113
+ DECL_ELEMENTWISE_RUBY_ACCESSOR(geq)
114
+ DECL_UNARY_RUBY_ACCESSOR(sin)
115
+ DECL_UNARY_RUBY_ACCESSOR(cos)
116
+ DECL_UNARY_RUBY_ACCESSOR(tan)
117
+ DECL_UNARY_RUBY_ACCESSOR(asin)
118
+ DECL_UNARY_RUBY_ACCESSOR(acos)
119
+ DECL_UNARY_RUBY_ACCESSOR(atan)
120
+ DECL_UNARY_RUBY_ACCESSOR(sinh)
121
+ DECL_UNARY_RUBY_ACCESSOR(cosh)
122
+ DECL_UNARY_RUBY_ACCESSOR(tanh)
123
+ DECL_UNARY_RUBY_ACCESSOR(asinh)
124
+ DECL_UNARY_RUBY_ACCESSOR(acosh)
125
+ DECL_UNARY_RUBY_ACCESSOR(atanh)
126
+ DECL_UNARY_RUBY_ACCESSOR(exp)
127
+ DECL_UNARY_RUBY_ACCESSOR(log2)
128
+ DECL_UNARY_RUBY_ACCESSOR(log10)
129
+ DECL_UNARY_RUBY_ACCESSOR(sqrt)
130
+ DECL_UNARY_RUBY_ACCESSOR(erf)
131
+ DECL_UNARY_RUBY_ACCESSOR(erfc)
132
+ DECL_UNARY_RUBY_ACCESSOR(cbrt)
133
+ DECL_UNARY_RUBY_ACCESSOR(gamma)
134
+ DECL_UNARY_RUBY_ACCESSOR(negate)
135
+ DECL_UNARY_RUBY_ACCESSOR(floor)
136
+ DECL_UNARY_RUBY_ACCESSOR(ceil)
137
+ DECL_NONCOM_ELEMENTWISE_RUBY_ACCESSOR(atan2)
138
+ DECL_NONCOM_ELEMENTWISE_RUBY_ACCESSOR(ldexp)
139
+ DECL_NONCOM_ELEMENTWISE_RUBY_ACCESSOR(hypot)
140
+
141
+ //log/round can be unary, but also take a base argument, as with Math.log
142
+ static VALUE nm_unary_log(int argc, VALUE* argv, VALUE self);
143
+ static VALUE nm_unary_round(int argc, VALUE* argv, VALUE self);
144
+
145
+ static VALUE elementwise_op(nm::ewop_t op, VALUE left_val, VALUE right_val);
146
+ static VALUE unary_op(nm::unaryop_t op, VALUE self);
147
+ static VALUE noncom_elementwise_op(nm::noncom_ewop_t op, VALUE self, VALUE other, VALUE orderflip);
148
+
149
+ static VALUE nm_symmetric(VALUE self);
150
+ static VALUE nm_hermitian(VALUE self);
151
+
152
+ static VALUE nm_eqeq(VALUE left, VALUE right);
153
+
154
+ static VALUE matrix_multiply_scalar(NMATRIX* left, VALUE scalar);
155
+ static VALUE matrix_multiply(NMATRIX* left, NMATRIX* right);
156
+ static VALUE nm_multiply(VALUE left_v, VALUE right_v);
157
+ static VALUE nm_det_exact(VALUE self);
158
+ static VALUE nm_hessenberg(VALUE self, VALUE a);
159
+ static VALUE nm_inverse(VALUE self, VALUE inverse, VALUE bang);
160
+ static VALUE nm_inverse_exact(VALUE self, VALUE inverse, VALUE lda, VALUE ldb);
161
+ static VALUE nm_complex_conjugate_bang(VALUE self);
162
+ static VALUE nm_reshape_bang(VALUE self, VALUE arg);
163
+
164
+ static nm::dtype_t interpret_dtype(int argc, VALUE* argv, nm::stype_t stype);
165
+ static void* interpret_initial_value(VALUE arg, nm::dtype_t dtype);
166
+ static size_t* interpret_shape(VALUE arg, size_t* dim);
167
+ static nm::stype_t interpret_stype(VALUE arg);
168
+
169
+ /* Singleton methods */
170
+ static VALUE nm_upcast(VALUE self, VALUE t1, VALUE t2);
171
+
172
+
173
+ #ifdef BENCHMARK
174
+ static double get_time(void);
175
+ #endif
176
+
177
+ ///////////////////
178
+ // Ruby Bindings //
179
+ ///////////////////
180
+
181
+ void Init_nmatrix() {
182
+
183
+
184
+ ///////////////////////
185
+ // Class Definitions //
186
+ ///////////////////////
187
+
188
+ cNMatrix = rb_define_class("NMatrix", rb_cObject);
189
+
190
+ // Special exceptions
191
+
192
+ /*
193
+ * Exception raised when there's a problem with data.
194
+ */
195
+ nm_eDataTypeError = rb_define_class("DataTypeError", rb_eStandardError);
196
+
197
+ /*
198
+ * Exception raised when something goes wrong with the storage of a matrix.
199
+ */
200
+ nm_eStorageTypeError = rb_define_class("StorageTypeError", rb_eStandardError);
201
+
202
+ /*
203
+ * Exception raise when the matrix shape is not appropriate for a given operation.
204
+ */
205
+ nm_eShapeError = rb_define_class("ShapeError", rb_eStandardError);
206
+
207
+ /*
208
+ * Exception raise when an inverse is requested but the matrix is not invertible.
209
+ */
210
+ nm_eNotInvertibleError = rb_define_class("NotInvertibleError", rb_eStandardError);
211
+
212
+ /*
213
+ * :nodoc:
214
+ * Class that holds values in use by the C code.
215
+ */
216
+ cNMatrix_GC_holder = rb_define_class("NMGCHolder", rb_cObject);
217
+
218
+
219
+ ///////////////////
220
+ // Class Methods //
221
+ ///////////////////
222
+
223
+ rb_define_alloc_func(cNMatrix, nm_alloc);
224
+
225
+ ///////////////////////
226
+ // Singleton Methods //
227
+ ///////////////////////
228
+
229
+ rb_define_singleton_method(cNMatrix, "upcast", (METHOD)nm_upcast, 2); /* in ext/nmatrix/nmatrix.cpp */
230
+ rb_define_singleton_method(cNMatrix, "guess_dtype", (METHOD)nm_guess_dtype, 1);
231
+ rb_define_singleton_method(cNMatrix, "min_dtype", (METHOD)nm_min_dtype, 1);
232
+
233
+ //////////////////////
234
+ // Instance Methods //
235
+ //////////////////////
236
+
237
+ rb_define_method(cNMatrix, "initialize", (METHOD)nm_init, -1);
238
+ rb_define_method(cNMatrix, "initialize_copy", (METHOD)nm_init_copy, 1);
239
+ rb_define_singleton_method(cNMatrix, "read", (METHOD)nm_read, -1);
240
+
241
+ rb_define_method(cNMatrix, "write", (METHOD)nm_write, -1);
242
+
243
+ // Technically, the following function is a copy constructor.
244
+ rb_define_protected_method(cNMatrix, "clone_transpose", (METHOD)nm_init_transposed, 0);
245
+
246
+ rb_define_method(cNMatrix, "dtype", (METHOD)nm_dtype, 0);
247
+ rb_define_method(cNMatrix, "stype", (METHOD)nm_stype, 0);
248
+ rb_define_method(cNMatrix, "cast_full", (METHOD)nm_cast, 3);
249
+ rb_define_method(cNMatrix, "default_value", (METHOD)nm_default_value, 0);
250
+ rb_define_protected_method(cNMatrix, "__list_default_value__", (METHOD)nm_list_default_value, 0);
251
+ rb_define_protected_method(cNMatrix, "__yale_default_value__", (METHOD)nm_yale_default_value, 0);
252
+
253
+ rb_define_method(cNMatrix, "[]", (METHOD)nm_mref, -1);
254
+ rb_define_method(cNMatrix, "slice", (METHOD)nm_mget, -1);
255
+ rb_define_method(cNMatrix, "[]=", (METHOD)nm_mset, -1);
256
+ rb_define_method(cNMatrix, "is_ref?", (METHOD)nm_is_ref, 0);
257
+ rb_define_method(cNMatrix, "dimensions", (METHOD)nm_dim, 0);
258
+ rb_define_method(cNMatrix, "effective_dimensions", (METHOD)nm_effective_dim, 0);
259
+
260
+ rb_define_protected_method(cNMatrix, "__list_to_hash__", (METHOD)nm_to_hash, 0); // handles list and dense, which are n-dimensional
261
+
262
+ rb_define_method(cNMatrix, "shape", (METHOD)nm_shape, 0);
263
+ rb_define_method(cNMatrix, "supershape", (METHOD)nm_supershape, 0);
264
+ rb_define_method(cNMatrix, "offset", (METHOD)nm_offset, 0);
265
+ rb_define_method(cNMatrix, "det_exact", (METHOD)nm_det_exact, 0);
266
+ rb_define_method(cNMatrix, "complex_conjugate!", (METHOD)nm_complex_conjugate_bang, 0);
267
+
268
+ rb_define_protected_method(cNMatrix, "reshape_bang", (METHOD)nm_reshape_bang, 1);
269
+
270
+ // Iterators public methods
271
+ rb_define_method(cNMatrix, "each_with_indices", (METHOD)nm_each_with_indices, 0);
272
+ rb_define_method(cNMatrix, "each_stored_with_indices", (METHOD)nm_each_stored_with_indices, 0);
273
+ rb_define_method(cNMatrix, "map_stored", (METHOD)nm_map_stored, 0);
274
+ rb_define_method(cNMatrix, "each_ordered_stored_with_indices", (METHOD)nm_each_ordered_stored_with_indices, 0);
275
+
276
+ // Iterators protected methods
277
+ rb_define_protected_method(cNMatrix, "__dense_each__", (METHOD)nm_dense_each, 0);
278
+ rb_define_protected_method(cNMatrix, "__dense_map__", (METHOD)nm_dense_map, 0);
279
+ rb_define_protected_method(cNMatrix, "__dense_map_pair__", (METHOD)nm_dense_map_pair, 1);
280
+ rb_define_protected_method(cNMatrix, "__list_map_merged_stored__", (METHOD)nm_list_map_merged_stored, 2);
281
+ rb_define_protected_method(cNMatrix, "__list_map_stored__", (METHOD)nm_list_map_stored, 1);
282
+ rb_define_protected_method(cNMatrix, "__yale_map_merged_stored__", (METHOD)nm_yale_map_merged_stored, 2);
283
+ rb_define_protected_method(cNMatrix, "__yale_map_stored__", (METHOD)nm_yale_map_stored, 0);
284
+ rb_define_protected_method(cNMatrix, "__yale_stored_diagonal_each_with_indices__", (METHOD)nm_yale_stored_diagonal_each_with_indices, 0);
285
+ rb_define_protected_method(cNMatrix, "__yale_stored_nondiagonal_each_with_indices__", (METHOD)nm_yale_stored_nondiagonal_each_with_indices, 0);
286
+
287
+ rb_define_method(cNMatrix, "==", (METHOD)nm_eqeq, 1);
288
+
289
+ rb_define_method(cNMatrix, "+", (METHOD)nm_ew_add, 1);
290
+ rb_define_method(cNMatrix, "-", (METHOD)nm_ew_subtract, 1);
291
+ rb_define_method(cNMatrix, "*", (METHOD)nm_ew_multiply, 1);
292
+ rb_define_method(cNMatrix, "/", (METHOD)nm_ew_divide, 1);
293
+ rb_define_method(cNMatrix, "**", (METHOD)nm_ew_power, 1);
294
+ rb_define_method(cNMatrix, "%", (METHOD)nm_ew_mod, 1);
295
+
296
+ rb_define_method(cNMatrix, "atan2", (METHOD)nm_noncom_ew_atan2, -1);
297
+ rb_define_method(cNMatrix, "ldexp", (METHOD)nm_noncom_ew_ldexp, -1);
298
+ rb_define_method(cNMatrix, "hypot", (METHOD)nm_noncom_ew_hypot, -1);
299
+
300
+ rb_define_method(cNMatrix, "sin", (METHOD)nm_unary_sin, 0);
301
+ rb_define_method(cNMatrix, "cos", (METHOD)nm_unary_cos, 0);
302
+ rb_define_method(cNMatrix, "tan", (METHOD)nm_unary_tan, 0);
303
+ rb_define_method(cNMatrix, "asin", (METHOD)nm_unary_asin, 0);
304
+ rb_define_method(cNMatrix, "acos", (METHOD)nm_unary_acos, 0);
305
+ rb_define_method(cNMatrix, "atan", (METHOD)nm_unary_atan, 0);
306
+ rb_define_method(cNMatrix, "sinh", (METHOD)nm_unary_sinh, 0);
307
+ rb_define_method(cNMatrix, "cosh", (METHOD)nm_unary_cosh, 0);
308
+ rb_define_method(cNMatrix, "tanh", (METHOD)nm_unary_tanh, 0);
309
+ rb_define_method(cNMatrix, "asinh", (METHOD)nm_unary_asinh, 0);
310
+ rb_define_method(cNMatrix, "acosh", (METHOD)nm_unary_acosh, 0);
311
+ rb_define_method(cNMatrix, "atanh", (METHOD)nm_unary_atanh, 0);
312
+ rb_define_method(cNMatrix, "exp", (METHOD)nm_unary_exp, 0);
313
+ rb_define_method(cNMatrix, "log2", (METHOD)nm_unary_log2, 0);
314
+ rb_define_method(cNMatrix, "log10", (METHOD)nm_unary_log10, 0);
315
+ rb_define_method(cNMatrix, "sqrt", (METHOD)nm_unary_sqrt, 0);
316
+ rb_define_method(cNMatrix, "erf", (METHOD)nm_unary_erf, 0);
317
+ rb_define_method(cNMatrix, "erfc", (METHOD)nm_unary_erfc, 0);
318
+ rb_define_method(cNMatrix, "cbrt", (METHOD)nm_unary_cbrt, 0);
319
+ rb_define_method(cNMatrix, "gamma", (METHOD)nm_unary_gamma, 0);
320
+ rb_define_method(cNMatrix, "log", (METHOD)nm_unary_log, -1);
321
+ rb_define_method(cNMatrix, "-@", (METHOD)nm_unary_negate,0);
322
+ rb_define_method(cNMatrix, "floor", (METHOD)nm_unary_floor, 0);
323
+ rb_define_method(cNMatrix, "ceil", (METHOD)nm_unary_ceil, 0);
324
+ rb_define_method(cNMatrix, "round", (METHOD)nm_unary_round, -1);
325
+
326
+
327
+ rb_define_method(cNMatrix, "=~", (METHOD)nm_ew_eqeq, 1);
328
+ rb_define_method(cNMatrix, "!~", (METHOD)nm_ew_neq, 1);
329
+ rb_define_method(cNMatrix, "<=", (METHOD)nm_ew_leq, 1);
330
+ rb_define_method(cNMatrix, ">=", (METHOD)nm_ew_geq, 1);
331
+ rb_define_method(cNMatrix, "<", (METHOD)nm_ew_lt, 1);
332
+ rb_define_method(cNMatrix, ">", (METHOD)nm_ew_gt, 1);
333
+
334
+ /////////////////////////////
335
+ // Helper Instance Methods //
336
+ /////////////////////////////
337
+ rb_define_protected_method(cNMatrix, "__yale_vector_set__", (METHOD)nm_vector_set, -1);
338
+
339
+ /////////////////////////
340
+ // Matrix Math Methods //
341
+ /////////////////////////
342
+ rb_define_method(cNMatrix, "dot", (METHOD)nm_multiply, 1);
343
+ rb_define_method(cNMatrix, "symmetric?", (METHOD)nm_symmetric, 0);
344
+ rb_define_method(cNMatrix, "hermitian?", (METHOD)nm_hermitian, 0);
345
+ rb_define_method(cNMatrix, "capacity", (METHOD)nm_capacity, 0);
346
+
347
+ // protected methods
348
+ rb_define_protected_method(cNMatrix, "__inverse__", (METHOD)nm_inverse, 2);
349
+ rb_define_protected_method(cNMatrix, "__inverse_exact__", (METHOD)nm_inverse_exact, 3);
350
+
351
+ // private methods
352
+ rb_define_private_method(cNMatrix, "__hessenberg__", (METHOD)nm_hessenberg, 1);
353
+
354
+ /////////////////
355
+ // FFI Methods //
356
+ /////////////////
357
+ rb_define_method(cNMatrix, "data_pointer", (METHOD)nm_data_pointer, 0);
358
+
359
+ /////////////
360
+ // Aliases //
361
+ /////////////
362
+
363
+ rb_define_alias(cNMatrix, "dim", "dimensions");
364
+ rb_define_alias(cNMatrix, "effective_dim", "effective_dimensions");
365
+ rb_define_alias(cNMatrix, "equal?", "eql?");
366
+
367
+ ////////////
368
+ //Epsilons//
369
+ ////////////
370
+ rb_define_const(cNMatrix, "FLOAT64_EPSILON", rb_const_get(rb_cFloat, rb_intern("EPSILON")));
371
+ rb_define_const(cNMatrix, "FLOAT32_EPSILON", DBL2NUM(FLT_EPSILON));
372
+
373
+ ///////////////////////
374
+ // Symbol Generation //
375
+ ///////////////////////
376
+
377
+ nm_init_ruby_constants();
378
+
379
+ //////////////////////////
380
+ // YaleFunctions module //
381
+ //////////////////////////
382
+
383
+ nm_init_yale_functions();
384
+
385
+ /////////////////
386
+ // BLAS module //
387
+ /////////////////
388
+
389
+ nm_math_init_blas();
390
+
391
+ ///////////////
392
+ // IO module //
393
+ ///////////////
394
+ nm_init_io();
395
+
396
+ /////////////////////////////////////////////////
397
+ // Force compilation of necessary constructors //
398
+ /////////////////////////////////////////////////
399
+ nm_init_data();
400
+ }
401
+
402
+
403
+ //////////////////
404
+ // Ruby Methods //
405
+ //////////////////
406
+
407
+ /*
408
+ * Allocator.
409
+ */
410
+ static VALUE nm_alloc(VALUE klass) {
411
+ NMATRIX* mat = NM_ALLOC(NMATRIX);
412
+ mat->storage = NULL;
413
+
414
+ // DO NOT MARK This STRUCT. It has no storage allocated, and no stype, so mark will do an invalid something.
415
+ return Data_Wrap_Struct(klass, NULL, nm_delete, mat);
416
+ }
417
+
418
+ /*
419
+ * Find the capacity of an NMatrix. The capacity only differs from the size for
420
+ * Yale matrices, which occasionally allocate more space than they need. For
421
+ * list and dense, capacity gives the number of elements in the matrix.
422
+ *
423
+ * If you call this on a slice, it may behave unpredictably. Most likely it'll
424
+ * just return the original matrix's capacity.
425
+ */
426
+ static VALUE nm_capacity(VALUE self) {
427
+ NM_CONSERVATIVE(nm_register_value(&self));
428
+ VALUE cap;
429
+
430
+ switch(NM_STYPE(self)) {
431
+ case nm::YALE_STORE:
432
+ cap = UINT2NUM(reinterpret_cast<YALE_STORAGE*>(NM_STORAGE_YALE(self)->src)->capacity);
433
+ break;
434
+
435
+ case nm::DENSE_STORE:
436
+ cap = UINT2NUM(nm_storage_count_max_elements( NM_STORAGE_DENSE(self) ));
437
+ break;
438
+
439
+ case nm::LIST_STORE:
440
+ cap = UINT2NUM(nm_list_storage_count_elements( NM_STORAGE_LIST(self) ));
441
+ break;
442
+
443
+ default:
444
+ NM_CONSERVATIVE(nm_unregister_value(&self));
445
+ rb_raise(nm_eStorageTypeError, "unrecognized stype in nm_capacity()");
446
+ }
447
+
448
+ NM_CONSERVATIVE(nm_unregister_value(&self));
449
+ return cap;
450
+ }
451
+
452
+
453
+ /*
454
+ * Mark function.
455
+ */
456
+ void nm_mark(NMATRIX* mat) {
457
+ STYPE_MARK_TABLE(mark)
458
+ mark[mat->stype](mat->storage);
459
+ }
460
+
461
+
462
+ /*
463
+ * Destructor.
464
+ */
465
+ void nm_delete(NMATRIX* mat) {
466
+ static void (*ttable[nm::NUM_STYPES])(STORAGE*) = {
467
+ nm_dense_storage_delete,
468
+ nm_list_storage_delete,
469
+ nm_yale_storage_delete
470
+ };
471
+ ttable[mat->stype](mat->storage);
472
+
473
+ NM_FREE(mat);
474
+ }
475
+
476
+ /*
477
+ * Slicing destructor.
478
+ */
479
+ void nm_delete_ref(NMATRIX* mat) {
480
+ static void (*ttable[nm::NUM_STYPES])(STORAGE*) = {
481
+ nm_dense_storage_delete_ref,
482
+ nm_list_storage_delete_ref,
483
+ nm_yale_storage_delete_ref
484
+ };
485
+ ttable[mat->stype](mat->storage);
486
+
487
+ NM_FREE(mat);
488
+ }
489
+
490
+
491
+ /**
492
+ * These variables hold a linked list of VALUEs that are registered to be in
493
+ * use by nmatrix so that they can be marked when GC runs.
494
+ */
495
+ static VALUE* gc_value_holder = NULL;
496
+ static NM_GC_HOLDER* gc_value_holder_struct = NULL;
497
+ static NM_GC_HOLDER* allocated_pool = NULL; // an object pool for linked list nodes; using pooling is in some cases a substantial performance improvement
498
+
499
+ /**
500
+ * GC Marking function for the values that have been registered.
501
+ */
502
+ static void __nm_mark_value_container(NM_GC_HOLDER* gc_value_holder_struct) {
503
+ if (gc_value_holder_struct && gc_value_holder_struct->start) {
504
+ NM_GC_LL_NODE* curr = gc_value_holder_struct->start;
505
+ while (curr) {
506
+ rb_gc_mark_locations(curr->val, curr->val + curr->n);
507
+ curr = curr->next;
508
+ }
509
+ }
510
+ }
511
+
512
+ /**
513
+ * Initilalizes the linked list of in-use VALUEs if it hasn't been done
514
+ * already.
515
+ */
516
+ static void __nm_initialize_value_container() {
517
+ if (gc_value_holder == NULL) {
518
+ gc_value_holder_struct = NM_ALLOC_NONRUBY(NM_GC_HOLDER);
519
+ allocated_pool = NM_ALLOC_NONRUBY(NM_GC_HOLDER);
520
+ gc_value_holder = NM_ALLOC_NONRUBY(VALUE);
521
+ gc_value_holder_struct->start = NULL;
522
+ allocated_pool->start = NULL;
523
+ *gc_value_holder = Data_Wrap_Struct(cNMatrix_GC_holder, __nm_mark_value_container, NULL, gc_value_holder_struct);
524
+ rb_global_variable(gc_value_holder);
525
+ }
526
+ }
527
+
528
+ /*
529
+ * Register an array of VALUEs to avoid their collection
530
+ * while using them internally.
531
+ */
532
+ void nm_register_values(VALUE* values, size_t n) {
533
+ if (!gc_value_holder_struct)
534
+ __nm_initialize_value_container();
535
+ if (values) {
536
+ NM_GC_LL_NODE* to_insert = NULL;
537
+ if (allocated_pool->start) {
538
+ to_insert = allocated_pool->start;
539
+ allocated_pool->start = to_insert->next;
540
+ } else {
541
+ to_insert = NM_ALLOC_NONRUBY(NM_GC_LL_NODE);
542
+ }
543
+ to_insert->val = values;
544
+ to_insert->n = n;
545
+ to_insert->next = gc_value_holder_struct->start;
546
+ gc_value_holder_struct->start = to_insert;
547
+ }
548
+ }
549
+
550
+ /*
551
+ * Unregister an array of VALUEs with the gc to allow normal
552
+ * garbage collection to occur again.
553
+ */
554
+ void nm_unregister_values(VALUE* values, size_t n) {
555
+ if (values) {
556
+ if (gc_value_holder_struct) {
557
+ NM_GC_LL_NODE* curr = gc_value_holder_struct->start;
558
+ NM_GC_LL_NODE* last = NULL;
559
+ while (curr) {
560
+ if (curr->val == values) {
561
+ if (last) {
562
+ last->next = curr->next;
563
+ } else {
564
+ gc_value_holder_struct->start = curr->next;
565
+ }
566
+ curr->next = allocated_pool->start;
567
+ curr->val = NULL;
568
+ curr->n = 0;
569
+ allocated_pool->start = curr;
570
+ break;
571
+ }
572
+ last = curr;
573
+ curr = curr->next;
574
+ }
575
+ }
576
+ }
577
+ }
578
+
579
+
580
+ /**
581
+ * Register a single VALUE as in use to avoid garbage collection.
582
+ */
583
+ void nm_register_value(VALUE* val) {
584
+ nm_register_values(val, 1);
585
+ }
586
+
587
+ /**
588
+ * Unregister a single VALUE to allow normal garbage collection.
589
+ */
590
+ void nm_unregister_value(VALUE* val) {
591
+ nm_unregister_values(val, 1);
592
+ }
593
+
594
+ /**
595
+ * Removes all instances of a single VALUE in the gc list. This can be
596
+ * dangerous. Primarily used when something is about to be
597
+ * freed and replaced so that and residual registrations won't access after
598
+ * free.
599
+ **/
600
+ void nm_completely_unregister_value(VALUE* val) {
601
+ if (gc_value_holder_struct) {
602
+ NM_GC_LL_NODE* curr = gc_value_holder_struct->start;
603
+ NM_GC_LL_NODE* last = NULL;
604
+ while (curr) {
605
+ if (curr->val == val) {
606
+ if (last) {
607
+ last->next = curr->next;
608
+ } else {
609
+ gc_value_holder_struct->start = curr->next;
610
+ }
611
+ NM_GC_LL_NODE* temp_next = curr->next;
612
+ curr->next = allocated_pool->start;
613
+ curr->val = NULL;
614
+ curr->n = 0;
615
+ allocated_pool->start = curr;
616
+ curr = temp_next;
617
+ } else {
618
+ last = curr;
619
+ curr = curr->next;
620
+ }
621
+ }
622
+ }
623
+ }
624
+
625
+
626
+
627
+ /**
628
+ * Register a STORAGE struct of the supplied stype to avoid garbage collection
629
+ * of its internals.
630
+ *
631
+ * Delegates to the storage-specific methods. They will check dtype and ignore
632
+ * non-rubyobject dtypes, so it's safe to pass any storage in.
633
+ */
634
+ void nm_register_storage(nm::stype_t stype, const STORAGE* storage) {
635
+ STYPE_REGISTER_TABLE(ttable);
636
+ ttable[stype](storage);
637
+ }
638
+
639
+ /**
640
+ * Unregister a STORAGE struct of the supplied stype to allow normal garbage collection
641
+ * of its internals.
642
+ *
643
+ * Delegates to the storage-specific methods. They will check dtype and ignore
644
+ * non-rubyobject dtypes, so it's safe to pass any storage in.
645
+ *
646
+ */
647
+ void nm_unregister_storage(nm::stype_t stype, const STORAGE* storage) {
648
+ STYPE_UNREGISTER_TABLE(ttable);
649
+ ttable[stype](storage);
650
+ }
651
+
652
+ /**
653
+ * Registers an NMATRIX struct to avoid garbage collection of its internals.
654
+ */
655
+ void nm_register_nmatrix(NMATRIX* nmatrix) {
656
+ if (nmatrix)
657
+ nm_register_storage(nmatrix->stype, nmatrix->storage);
658
+ }
659
+
660
+ /**
661
+ * Unregisters an NMATRIX struct to avoid garbage collection of its internals.
662
+ */
663
+ void nm_unregister_nmatrix(NMATRIX* nmatrix) {
664
+ if (nmatrix)
665
+ nm_unregister_storage(nmatrix->stype, nmatrix->storage);
666
+ }
667
+
668
+ /*
669
+ * call-seq:
670
+ * dtype -> Symbol
671
+ *
672
+ * Get the data type (dtype) of a matrix, e.g., :byte, :int8, :int16, :int32,
673
+ * :int64, :float32, :float64, :complex64, :complex128,
674
+ * or :object (the last is a Ruby object).
675
+ */
676
+ static VALUE nm_dtype(VALUE self) {
677
+ ID dtype = rb_intern(DTYPE_NAMES[NM_DTYPE(self)]);
678
+ return ID2SYM(dtype);
679
+ }
680
+
681
+
682
+ /*
683
+ * call-seq:
684
+ * upcast(first_dtype, second_dtype) -> Symbol
685
+ *
686
+ * Given a binary operation between types t1 and t2, what type will be returned?
687
+ *
688
+ * This is a singleton method on NMatrix, e.g., NMatrix.upcast(:int32, :int64)
689
+ */
690
+ static VALUE nm_upcast(VALUE self, VALUE t1, VALUE t2) {
691
+ nm::dtype_t d1 = nm_dtype_from_rbsymbol(t1),
692
+ d2 = nm_dtype_from_rbsymbol(t2);
693
+
694
+ return ID2SYM(rb_intern( DTYPE_NAMES[ Upcast[d1][d2] ] ));
695
+ }
696
+
697
+
698
+ /*
699
+ * call-seq:
700
+ default_value -> ...
701
+ *
702
+ * Get the default value for the matrix. For dense, this is undefined and will return Qnil. For list, it is user-defined.
703
+ * For yale, it's going to be some variation on zero, but may be Qfalse or Qnil.
704
+ */
705
+ static VALUE nm_default_value(VALUE self) {
706
+ switch(NM_STYPE(self)) {
707
+ case nm::YALE_STORE:
708
+ return nm_yale_default_value(self);
709
+ case nm::LIST_STORE:
710
+ return nm_list_default_value(self);
711
+ case nm::DENSE_STORE:
712
+ default:
713
+ return Qnil;
714
+ }
715
+ }
716
+
717
+
718
+ /*
719
+ * call-seq:
720
+ * each_with_indices -> Enumerator
721
+ *
722
+ * Iterate over all entries of any matrix in standard storage order (as with #each), and include the indices.
723
+ */
724
+ static VALUE nm_each_with_indices(VALUE nmatrix) {
725
+ NM_CONSERVATIVE(nm_register_value(&nmatrix));
726
+ VALUE to_return = Qnil;
727
+
728
+ switch(NM_STYPE(nmatrix)) {
729
+ case nm::YALE_STORE:
730
+ to_return = nm_yale_each_with_indices(nmatrix);
731
+ break;
732
+ case nm::DENSE_STORE:
733
+ to_return = nm_dense_each_with_indices(nmatrix);
734
+ break;
735
+ case nm::LIST_STORE:
736
+ to_return = nm_list_each_with_indices(nmatrix, false);
737
+ break;
738
+ default:
739
+ NM_CONSERVATIVE(nm_unregister_value(&nmatrix));
740
+ rb_raise(nm_eDataTypeError, "Not a proper storage type");
741
+ }
742
+
743
+ NM_CONSERVATIVE(nm_unregister_value(&nmatrix));
744
+ return to_return;
745
+ }
746
+
747
+ /*
748
+ * call-seq:
749
+ * each_stored_with_indices -> Enumerator
750
+ *
751
+ * Iterate over the stored entries of any matrix. For dense and yale, this iterates over non-zero
752
+ * entries; for list, this iterates over non-default entries. Yields dim+1 values for each entry:
753
+ * i, j, ..., and the entry itself.
754
+ */
755
+ static VALUE nm_each_stored_with_indices(VALUE nmatrix) {
756
+ NM_CONSERVATIVE(nm_register_value(&nmatrix));
757
+ VALUE to_return = Qnil;
758
+
759
+ switch(NM_STYPE(nmatrix)) {
760
+ case nm::YALE_STORE:
761
+ to_return = nm_yale_each_stored_with_indices(nmatrix);
762
+ break;
763
+ case nm::DENSE_STORE:
764
+ to_return = nm_dense_each_with_indices(nmatrix);
765
+ break;
766
+ case nm::LIST_STORE:
767
+ to_return = nm_list_each_with_indices(nmatrix, true);
768
+ break;
769
+ default:
770
+ NM_CONSERVATIVE(nm_unregister_value(&nmatrix));
771
+ rb_raise(nm_eDataTypeError, "Not a proper storage type");
772
+ }
773
+
774
+ NM_CONSERVATIVE(nm_unregister_value(&nmatrix));
775
+ return to_return;
776
+ }
777
+
778
+
779
+ /*
780
+ * call-seq:
781
+ * map_stored -> Enumerator
782
+ *
783
+ * Iterate over the stored entries of any matrix. For dense and yale, this iterates over non-zero
784
+ * entries; for list, this iterates over non-default entries. Yields dim+1 values for each entry:
785
+ * i, j, ..., and the entry itself.
786
+ */
787
+ static VALUE nm_map_stored(VALUE nmatrix) {
788
+ NM_CONSERVATIVE(nm_register_value(&nmatrix));
789
+ VALUE to_return = Qnil;
790
+
791
+ switch(NM_STYPE(nmatrix)) {
792
+ case nm::YALE_STORE:
793
+ to_return = nm_yale_map_stored(nmatrix);
794
+ break;
795
+ case nm::DENSE_STORE:
796
+ to_return = nm_dense_map(nmatrix);
797
+ break;
798
+ case nm::LIST_STORE:
799
+ to_return = nm_list_map_stored(nmatrix, Qnil);
800
+ break;
801
+ default:
802
+ NM_CONSERVATIVE(nm_unregister_value(&nmatrix));
803
+ rb_raise(nm_eDataTypeError, "Not a proper storage type");
804
+ }
805
+
806
+ NM_CONSERVATIVE(nm_unregister_value(&nmatrix));
807
+ return to_return;
808
+ }
809
+
810
+ /*
811
+ * call-seq:
812
+ * each_ordered_stored_with_indices -> Enumerator
813
+ *
814
+ * Very similar to #each_stored_with_indices. The key difference is that it enforces matrix ordering rather
815
+ * than storage ordering, which only matters if your matrix is Yale.
816
+ */
817
+ static VALUE nm_each_ordered_stored_with_indices(VALUE nmatrix) {
818
+ NM_CONSERVATIVE(nm_register_value(&nmatrix));
819
+ VALUE to_return = Qnil;
820
+
821
+ switch(NM_STYPE(nmatrix)) {
822
+ case nm::YALE_STORE:
823
+ to_return = nm_yale_each_ordered_stored_with_indices(nmatrix);
824
+ break;
825
+ case nm::DENSE_STORE:
826
+ to_return = nm_dense_each_with_indices(nmatrix);
827
+ break;
828
+ case nm::LIST_STORE:
829
+ to_return = nm_list_each_with_indices(nmatrix, true);
830
+ break;
831
+ default:
832
+ NM_CONSERVATIVE(nm_unregister_value(&nmatrix));
833
+ rb_raise(nm_eDataTypeError, "Not a proper storage type");
834
+ }
835
+
836
+ NM_CONSERVATIVE(nm_unregister_value(&nmatrix));
837
+ return to_return;
838
+ }
839
+
840
+
841
+ /*
842
+ * Equality operator. Returns a single true or false value indicating whether
843
+ * the matrices are equivalent.
844
+ *
845
+ * For elementwise, use =~ instead.
846
+ *
847
+ * This method will raise an exception if dimensions do not match.
848
+ *
849
+ * When stypes differ, this function calls a protected Ruby method.
850
+ */
851
+ static VALUE nm_eqeq(VALUE left, VALUE right) {
852
+ NM_CONSERVATIVE(nm_register_value(&left));
853
+ NM_CONSERVATIVE(nm_register_value(&right));
854
+
855
+ NMATRIX *l, *r;
856
+
857
+ CheckNMatrixType(left);
858
+ CheckNMatrixType(right);
859
+
860
+ UnwrapNMatrix(left, l);
861
+ UnwrapNMatrix(right, r);
862
+
863
+ bool result = false;
864
+
865
+ // Check that the shapes match before going any further.
866
+ if (l->storage->dim != r->storage->dim) {
867
+ NM_CONSERVATIVE(nm_unregister_value(&left));
868
+ NM_CONSERVATIVE(nm_unregister_value(&right));
869
+ rb_raise(nm_eShapeError, "cannot compare matrices with different dimension");
870
+ }
871
+
872
+ size_t dim = l->storage->dim;
873
+ for (size_t i=0; i<dim; i++) {
874
+ if (l->storage->shape[i] != r->storage->shape[i]) {
875
+ NM_CONSERVATIVE(nm_unregister_value(&left));
876
+ NM_CONSERVATIVE(nm_unregister_value(&right));
877
+ rb_raise(nm_eShapeError, "cannot compare matrices with different shapes");
878
+ }
879
+ }
880
+
881
+ if (l->stype != r->stype) { // DIFFERENT STYPES
882
+
883
+ if (l->stype == nm::DENSE_STORE)
884
+ result = rb_funcall(left, rb_intern("dense_eql_sparse?"), 1, right);
885
+ else if (r->stype == nm::DENSE_STORE)
886
+ result = rb_funcall(right, rb_intern("dense_eql_sparse?"), 1, left);
887
+ else
888
+ result = rb_funcall(left, rb_intern("sparse_eql_sparse?"), 1, right);
889
+
890
+ } else {
891
+
892
+ switch(l->stype) { // SAME STYPES
893
+ case nm::DENSE_STORE:
894
+ result = nm_dense_storage_eqeq(l->storage, r->storage);
895
+ break;
896
+ case nm::LIST_STORE:
897
+ result = nm_list_storage_eqeq(l->storage, r->storage);
898
+ break;
899
+ case nm::YALE_STORE:
900
+ result = nm_yale_storage_eqeq(l->storage, r->storage);
901
+ break;
902
+ }
903
+ }
904
+
905
+ NM_CONSERVATIVE(nm_unregister_value(&left));
906
+ NM_CONSERVATIVE(nm_unregister_value(&right));
907
+
908
+ return result ? Qtrue : Qfalse;
909
+ }
910
+
911
+ DEF_ELEMENTWISE_RUBY_ACCESSOR(ADD, add)
912
+ DEF_ELEMENTWISE_RUBY_ACCESSOR(SUB, subtract)
913
+ DEF_ELEMENTWISE_RUBY_ACCESSOR(MUL, multiply)
914
+ DEF_ELEMENTWISE_RUBY_ACCESSOR(DIV, divide)
915
+ DEF_ELEMENTWISE_RUBY_ACCESSOR(POW, power)
916
+ DEF_ELEMENTWISE_RUBY_ACCESSOR(MOD, mod)
917
+ DEF_ELEMENTWISE_RUBY_ACCESSOR(EQEQ, eqeq)
918
+ DEF_ELEMENTWISE_RUBY_ACCESSOR(NEQ, neq)
919
+ DEF_ELEMENTWISE_RUBY_ACCESSOR(LEQ, leq)
920
+ DEF_ELEMENTWISE_RUBY_ACCESSOR(GEQ, geq)
921
+ DEF_ELEMENTWISE_RUBY_ACCESSOR(LT, lt)
922
+ DEF_ELEMENTWISE_RUBY_ACCESSOR(GT, gt)
923
+
924
+ DEF_UNARY_RUBY_ACCESSOR(SIN, sin)
925
+ DEF_UNARY_RUBY_ACCESSOR(COS, cos)
926
+ DEF_UNARY_RUBY_ACCESSOR(TAN, tan)
927
+ DEF_UNARY_RUBY_ACCESSOR(ASIN, asin)
928
+ DEF_UNARY_RUBY_ACCESSOR(ACOS, acos)
929
+ DEF_UNARY_RUBY_ACCESSOR(ATAN, atan)
930
+ DEF_UNARY_RUBY_ACCESSOR(SINH, sinh)
931
+ DEF_UNARY_RUBY_ACCESSOR(COSH, cosh)
932
+ DEF_UNARY_RUBY_ACCESSOR(TANH, tanh)
933
+ DEF_UNARY_RUBY_ACCESSOR(ASINH, asinh)
934
+ DEF_UNARY_RUBY_ACCESSOR(ACOSH, acosh)
935
+ DEF_UNARY_RUBY_ACCESSOR(ATANH, atanh)
936
+ DEF_UNARY_RUBY_ACCESSOR(EXP, exp)
937
+ DEF_UNARY_RUBY_ACCESSOR(LOG2, log2)
938
+ DEF_UNARY_RUBY_ACCESSOR(LOG10, log10)
939
+ DEF_UNARY_RUBY_ACCESSOR(SQRT, sqrt)
940
+ DEF_UNARY_RUBY_ACCESSOR(ERF, erf)
941
+ DEF_UNARY_RUBY_ACCESSOR(ERFC, erfc)
942
+ DEF_UNARY_RUBY_ACCESSOR(CBRT, cbrt)
943
+ DEF_UNARY_RUBY_ACCESSOR(GAMMA, gamma)
944
+ DEF_UNARY_RUBY_ACCESSOR(NEGATE, negate)
945
+ DEF_UNARY_RUBY_ACCESSOR(FLOOR, floor)
946
+ DEF_UNARY_RUBY_ACCESSOR(CEIL, ceil)
947
+
948
+ DEF_NONCOM_ELEMENTWISE_RUBY_ACCESSOR(ATAN2, atan2)
949
+ DEF_NONCOM_ELEMENTWISE_RUBY_ACCESSOR(LDEXP, ldexp)
950
+ DEF_NONCOM_ELEMENTWISE_RUBY_ACCESSOR(HYPOT, hypot)
951
+
952
+ static VALUE nm_unary_log(int argc, VALUE* argv, VALUE self) {
953
+ NM_CONSERVATIVE(nm_register_values(argv, argc));
954
+ const double default_log_base = exp(1.0);
955
+ NMATRIX* left;
956
+ UnwrapNMatrix(self, left);
957
+ std::string sym;
958
+
959
+ switch(left->stype) {
960
+ case nm::DENSE_STORE:
961
+ sym = "__dense_unary_log__";
962
+ break;
963
+ case nm::YALE_STORE:
964
+ sym = "__yale_unary_log__";
965
+ break;
966
+ case nm::LIST_STORE:
967
+ sym = "__list_unary_log__";
968
+ break;
969
+ }
970
+ NM_CONSERVATIVE(nm_unregister_values(argv, argc));
971
+ if (argc > 0) { //supplied a base
972
+ return rb_funcall(self, rb_intern(sym.c_str()), 1, argv[0]);
973
+ }
974
+ return rb_funcall(self, rb_intern(sym.c_str()), 1, nm::RubyObject(default_log_base).rval);
975
+ }
976
+
977
+ static VALUE nm_unary_round(int argc, VALUE* argv, VALUE self) {
978
+ NM_CONSERVATIVE(nm_register_values(argv, argc));
979
+ const int default_precision = 0;
980
+ NMATRIX* left;
981
+ UnwrapNMatrix(self, left);
982
+ std::string sym;
983
+
984
+ switch(left->stype) {
985
+ case nm::DENSE_STORE:
986
+ sym = "__dense_unary_round__";
987
+ break;
988
+ case nm::YALE_STORE:
989
+ sym = "__yale_unary_round__";
990
+ break;
991
+ case nm::LIST_STORE:
992
+ sym = "__list_unary_round__";
993
+ break;
994
+ }
995
+ NM_CONSERVATIVE(nm_unregister_values(argv, argc));
996
+ if (argc > 0) { //supplied precision
997
+ return rb_funcall(self, rb_intern(sym.c_str()), 1, argv[0]);
998
+ }
999
+ return rb_funcall(self, rb_intern(sym.c_str()), 1, nm::RubyObject(default_precision).rval);
1000
+ }
1001
+
1002
+ //DEF_ELEMENTWISE_RUBY_ACCESSOR(ATAN2, atan2)
1003
+ //DEF_ELEMENTWISE_RUBY_ACCESSOR(LDEXP, ldexp)
1004
+ //DEF_ELEMENTWISE_RUBY_ACCESSOR(HYPOT, hypot)
1005
+
1006
+ /*
1007
+ * call-seq:
1008
+ * hermitian? -> Boolean
1009
+ *
1010
+ * Is this matrix hermitian?
1011
+ *
1012
+ * Definition: http://en.wikipedia.org/wiki/Hermitian_matrix
1013
+ *
1014
+ * For non-complex matrices, this function should return the same result as symmetric?.
1015
+ */
1016
+ static VALUE nm_hermitian(VALUE self) {
1017
+ return is_symmetric(self, true);
1018
+ }
1019
+
1020
+
1021
+ /*
1022
+ * call-seq:
1023
+ * complex_conjugate_bang -> NMatrix
1024
+ *
1025
+ * Transform the matrix (in-place) to its complex conjugate. Only works on complex matrices.
1026
+ *
1027
+ * Bang should imply that no copy is being made, even temporarily.
1028
+ */
1029
+ static VALUE nm_complex_conjugate_bang(VALUE self) {
1030
+
1031
+ NMATRIX* m;
1032
+ void* elem;
1033
+ size_t size, p;
1034
+
1035
+ UnwrapNMatrix(self, m);
1036
+
1037
+ if (m->stype == nm::DENSE_STORE) {
1038
+
1039
+ size = nm_storage_count_max_elements(NM_STORAGE(self));
1040
+ elem = NM_STORAGE_DENSE(self)->elements;
1041
+
1042
+ } else if (m->stype == nm::YALE_STORE) {
1043
+
1044
+ size = nm_yale_storage_get_size(NM_STORAGE_YALE(self));
1045
+ elem = NM_STORAGE_YALE(self)->a;
1046
+
1047
+ } else {
1048
+ rb_raise(rb_eNotImpError, "please cast to yale or dense (complex) first");
1049
+ }
1050
+
1051
+ // Walk through and negate the imaginary component
1052
+ if (NM_DTYPE(self) == nm::COMPLEX64) {
1053
+
1054
+ for (p = 0; p < size; ++p) {
1055
+ reinterpret_cast<nm::Complex64*>(elem)[p].i = -reinterpret_cast<nm::Complex64*>(elem)[p].i;
1056
+ }
1057
+
1058
+ } else if (NM_DTYPE(self) == nm::COMPLEX128) {
1059
+
1060
+ for (p = 0; p < size; ++p) {
1061
+ reinterpret_cast<nm::Complex128*>(elem)[p].i = -reinterpret_cast<nm::Complex128*>(elem)[p].i;
1062
+ }
1063
+
1064
+ }
1065
+ return self;
1066
+ }
1067
+
1068
+ /*
1069
+ * call-seq:
1070
+ * __reshape!__ -> NMatrix
1071
+ *
1072
+ * Reshapes the matrix (in-place) to the desired shape. Note that this function does not do a resize; the product of
1073
+ * the new and old shapes' components must be equal.
1074
+ *
1075
+ */
1076
+ static VALUE nm_reshape_bang(VALUE self, VALUE arg){
1077
+ NMATRIX* m;
1078
+ UnwrapNMatrix(self, m);
1079
+ if(m->stype == nm::DENSE_STORE){
1080
+ DENSE_STORAGE* s = NM_STORAGE_DENSE(self);
1081
+ VALUE shape_ary = arg;
1082
+ size_t dim;
1083
+ size_t size = nm_storage_count_max_elements(s);
1084
+ size_t new_size = 1;
1085
+ size_t* shape = interpret_shape(shape_ary, &dim);
1086
+ for (size_t index = 0; index < dim; ++index){
1087
+ new_size *= shape[index];}
1088
+
1089
+ if (size == new_size){
1090
+ s->shape = shape;
1091
+ s->dim = dim;
1092
+ NM_FREE(s->offset);
1093
+ s->offset = NM_ALLOC_N(size_t, dim);
1094
+ memset(s->offset, 0, sizeof(size_t)*dim);
1095
+ size_t i, j;
1096
+ size_t* stride = NM_ALLOC_N(size_t, dim);
1097
+ for (i = 0; i < dim; ++i) {
1098
+ stride[i] = 1;
1099
+ for (j = i+1; j < dim; ++j) {
1100
+ stride[i] *= shape[j];
1101
+ }
1102
+ }
1103
+ NM_FREE(s->stride);
1104
+ s->stride = stride;
1105
+ return self;
1106
+ }
1107
+ else
1108
+ rb_raise(rb_eArgError, "reshape cannot resize; size of new and old matrices must match");
1109
+ }
1110
+ else {
1111
+ rb_raise(rb_eNotImpError, "reshape in place only for dense stype");
1112
+ }
1113
+ }
1114
+
1115
+ /*
1116
+ * Helper function for creating a matrix. You have to create the storage and pass it in, but you don't
1117
+ * need to worry about deleting it.
1118
+ */
1119
+ NMATRIX* nm_create(nm::stype_t stype, STORAGE* storage) {
1120
+ nm_register_storage(stype, storage);
1121
+ NMATRIX* mat = NM_ALLOC(NMATRIX);
1122
+
1123
+ mat->stype = stype;
1124
+ mat->storage = storage;
1125
+
1126
+ nm_unregister_storage(stype, storage);
1127
+ return mat;
1128
+ }
1129
+
1130
+ /*
1131
+ * @see nm_init
1132
+ */
1133
+ static VALUE nm_init_new_version(int argc, VALUE* argv, VALUE self) {
1134
+ NM_CONSERVATIVE(nm_register_values(argv, argc));
1135
+ NM_CONSERVATIVE(nm_register_value(&self));
1136
+ VALUE shape_ary, initial_ary, hash;
1137
+ //VALUE shape_ary, default_val, capacity, initial_ary, dtype_sym, stype_sym;
1138
+ // Mandatory args: shape, dtype, stype
1139
+ rb_scan_args(argc, argv, "11:", &shape_ary, &initial_ary, &hash); // &stype_sym, &dtype_sym, &default_val, &capacity);
1140
+
1141
+ NM_CONSERVATIVE(nm_register_value(&shape_ary));
1142
+ NM_CONSERVATIVE(nm_register_value(&initial_ary));
1143
+ NM_CONSERVATIVE(nm_register_value(&hash));
1144
+ // Get the shape.
1145
+ size_t dim;
1146
+ size_t* shape = interpret_shape(shape_ary, &dim);
1147
+ void* init;
1148
+ void* v = NULL;
1149
+ size_t v_size = 0;
1150
+
1151
+ nm::stype_t stype = nm::DENSE_STORE;
1152
+ nm::dtype_t dtype = nm::RUBYOBJ;
1153
+ VALUE dtype_sym = Qnil, stype_sym = Qnil, default_val_num = Qnil, capacity_num = Qnil;
1154
+ size_t capacity = 0;
1155
+ if (!NIL_P(hash)) {
1156
+ dtype_sym = rb_hash_aref(hash, ID2SYM(nm_rb_dtype));
1157
+ stype_sym = rb_hash_aref(hash, ID2SYM(nm_rb_stype));
1158
+ capacity_num = rb_hash_aref(hash, ID2SYM(nm_rb_capacity));
1159
+ NM_CONSERVATIVE(nm_register_value(&capacity_num));
1160
+ default_val_num = rb_hash_aref(hash, ID2SYM(nm_rb_default));
1161
+ NM_CONSERVATIVE(nm_register_value(&default_val_num));
1162
+ }
1163
+
1164
+ // stype ||= :dense
1165
+ stype = !NIL_P(stype_sym) ? nm_stype_from_rbsymbol(stype_sym) : nm::DENSE_STORE;
1166
+
1167
+ // dtype ||= h[:dtype] || guess_dtype(initial_ary) || :object
1168
+ if (NIL_P(initial_ary) && NIL_P(dtype_sym))
1169
+ dtype = nm::RUBYOBJ;
1170
+ else if (NIL_P(dtype_sym))
1171
+ dtype = nm_dtype_guess(initial_ary);
1172
+ else
1173
+ dtype = nm_dtype_from_rbsymbol(dtype_sym);
1174
+
1175
+ // if stype != :dense
1176
+ // if initial_ary.nil?
1177
+ // init = h[:default] || 0
1178
+ // elsif initial_ary.is_a?(Array)
1179
+ // init = initial_ary.size > 1 ? (h[:default] || 0) : initial_ary[0]
1180
+ // else
1181
+ // init = initial_ary # not an array, just a value
1182
+ // end
1183
+ // end
1184
+ if (stype != nm::DENSE_STORE) {
1185
+ if (!NIL_P(default_val_num))
1186
+ init = rubyobj_to_cval(default_val_num, dtype);
1187
+ else if (NIL_P(initial_ary))
1188
+ init = NULL;
1189
+ else if (RB_TYPE_P(initial_ary, T_ARRAY))
1190
+ init = RARRAY_LEN(initial_ary) == 1 ? rubyobj_to_cval(rb_ary_entry(initial_ary, 0), dtype) : NULL;
1191
+ else
1192
+ init = rubyobj_to_cval(initial_ary, dtype);
1193
+
1194
+ if (dtype == nm::RUBYOBJ) {
1195
+ nm_register_values(reinterpret_cast<VALUE*>(init), 1);
1196
+ }
1197
+ }
1198
+
1199
+ // capacity = h[:capacity] || 0
1200
+ if (stype == nm::YALE_STORE) {
1201
+ if (!NIL_P(capacity_num)) capacity = FIX2INT(capacity_num);
1202
+ }
1203
+
1204
+ if (!NIL_P(initial_ary)) {
1205
+
1206
+ if (RB_TYPE_P(initial_ary, T_ARRAY)) v_size = RARRAY_LEN(initial_ary);
1207
+ else v_size = 1;
1208
+
1209
+ v = interpret_initial_value(initial_ary, dtype);
1210
+
1211
+ if (dtype == nm::RUBYOBJ) {
1212
+ nm_register_values(reinterpret_cast<VALUE*>(v), v_size);
1213
+ }
1214
+ }
1215
+
1216
+ // :object matrices MUST be initialized.
1217
+ else if (stype == nm::DENSE_STORE && dtype == nm::RUBYOBJ) {
1218
+ // Pretend [nil] was passed for RUBYOBJ.
1219
+ v = NM_ALLOC(VALUE);
1220
+ *(VALUE*)v = Qnil;
1221
+
1222
+ v_size = 1;
1223
+
1224
+ }
1225
+
1226
+ NMATRIX* nmatrix;
1227
+ UnwrapNMatrix(self, nmatrix);
1228
+
1229
+ nmatrix->stype = stype;
1230
+
1231
+ switch (stype) {
1232
+ case nm::DENSE_STORE:
1233
+ nmatrix->storage = (STORAGE*)nm_dense_storage_create(dtype, shape, dim, v, v_size);
1234
+ break;
1235
+
1236
+ case nm::LIST_STORE:
1237
+ nmatrix->storage = (STORAGE*)nm_list_storage_create(dtype, shape, dim, init);
1238
+ break;
1239
+
1240
+ case nm::YALE_STORE:
1241
+ nmatrix->storage = (STORAGE*)nm_yale_storage_create(dtype, shape, dim, capacity);
1242
+ nm_yale_storage_init((YALE_STORAGE*)(nmatrix->storage), init);
1243
+ break;
1244
+ }
1245
+
1246
+ nm_register_storage(stype, nmatrix->storage);
1247
+
1248
+ // If we're not creating a dense, and an initial array was provided, use that and multi-slice-set
1249
+ // to set the contents of the matrix right now.
1250
+ if (stype != nm::DENSE_STORE && v_size > 1) {
1251
+ VALUE* slice_argv = NM_ALLOCA_N(VALUE, dim);
1252
+ nm_register_values(slice_argv, dim);
1253
+ size_t* tmp_shape = NM_ALLOC_N(size_t, dim);
1254
+ for (size_t m = 0; m < dim; ++m) {
1255
+ slice_argv[m] = ID2SYM(nm_rb_mul); // :* -- full range
1256
+ tmp_shape[m] = shape[m];
1257
+ }
1258
+
1259
+ SLICE slice_s;
1260
+ SLICE* slice = &slice_s;
1261
+ slice->coords = NM_ALLOCA_N(size_t, dim);
1262
+ slice->lengths = NM_ALLOCA_N(size_t, dim);
1263
+ init_slice_no_alloc(slice, dim, dim, slice_argv, shape);
1264
+
1265
+ // Create a temporary dense matrix and use it to do a slice assignment on self.
1266
+ NMATRIX* tmp = nm_create(nm::DENSE_STORE, (STORAGE*)nm_dense_storage_create(dtype, tmp_shape, dim, v, v_size));
1267
+ nm_register_nmatrix(tmp);
1268
+ VALUE rb_tmp = Data_Wrap_Struct(CLASS_OF(self), nm_mark, nm_delete, tmp);
1269
+ nm_unregister_nmatrix(tmp);
1270
+ nm_register_value(&rb_tmp);
1271
+ if (stype == nm::YALE_STORE) nm_yale_storage_set(self, slice, rb_tmp);
1272
+ else nm_list_storage_set(self, slice, rb_tmp);
1273
+
1274
+ // We need to free v if it's not the same size as tmp -- because tmp will have made a copy instead.
1275
+ //if (nm_storage_count_max_elements(tmp->storage) != v_size)
1276
+ // NM_FREE(v);
1277
+
1278
+ // nm_delete(tmp); // This seems to enrage the garbage collector (because rb_tmp is still available). It'd be better if we could force it to free immediately, but no sweat.
1279
+
1280
+ nm_unregister_value(&rb_tmp);
1281
+ nm_unregister_values(slice_argv, dim);
1282
+ }
1283
+
1284
+ if (!NIL_P(initial_ary) && dtype == nm::RUBYOBJ) {
1285
+ nm_unregister_values(reinterpret_cast<VALUE*>(v), v_size);
1286
+ }
1287
+
1288
+ if (stype != nm::DENSE_STORE && dtype == nm::RUBYOBJ) {
1289
+ nm_unregister_values(reinterpret_cast<VALUE*>(init), 1);
1290
+ }
1291
+
1292
+ if (!NIL_P(hash)) {
1293
+ NM_CONSERVATIVE(nm_unregister_value(&capacity_num));
1294
+ NM_CONSERVATIVE(nm_unregister_value(&default_val_num));
1295
+ }
1296
+
1297
+ NM_CONSERVATIVE(nm_unregister_value(&shape_ary));
1298
+ NM_CONSERVATIVE(nm_unregister_value(&initial_ary));
1299
+ NM_CONSERVATIVE(nm_unregister_value(&hash));
1300
+
1301
+ NM_CONSERVATIVE(nm_unregister_value(&self));
1302
+ NM_CONSERVATIVE(nm_unregister_values(argv, argc));
1303
+ nm_unregister_storage(stype, nmatrix->storage);
1304
+
1305
+ return self;
1306
+ }
1307
+
1308
+ /*
1309
+ * call-seq:
1310
+ * new(shape) -> NMatrix
1311
+ * new(shape, initial_value) -> NMatrix
1312
+ * new(shape, initial_array) -> NMatrix
1313
+ * new(shape, initial_value, options) -> NMatrix
1314
+ * new(shape, initial_array, options) -> NMatrix
1315
+ *
1316
+ * Create a new NMatrix.
1317
+ *
1318
+ * The only mandatory argument is shape, which may be a positive integer or an array of positive integers.
1319
+ *
1320
+ * It is recommended that you supply an initialization value or array of values. Without one, Yale and List matrices will
1321
+ * be initialized to 0; and dense matrices will be undefined.
1322
+ *
1323
+ * Additional options may be provided using keyword arguments. The keywords are +:dtype, +:stype+, +:capacity+, and
1324
+ * +:default+. Only Yale uses a capacity argument, which is used to reserve the initial size of its storage vectors.
1325
+ * List and Yale both accept a default value (which itself defaults to 0). This default is taken from the initial value
1326
+ * if such a value is given; it is more likely to be required when an initial array is provided.
1327
+ *
1328
+ * The storage type, or stype, is used to specify whether we want a +:dense+, +:list+, or +:yale+ matrix; dense is the
1329
+ * default.
1330
+ *
1331
+ * The data type, or dtype, can be one of: :byte, :int8, :int16, :int32, :int64, :float32, :float64, :complex64,
1332
+ * :complex128, or :object. The constructor will attempt to guess it from the initial value/array/default
1333
+ * provided, if any. Otherwise, the default is :object, which stores any type of Ruby object.
1334
+ *
1335
+ * In addition to the above, there is a legacy constructor from the alpha version. To use that version, you must be
1336
+ * providing exactly four arguments. It is now deprecated.
1337
+ *
1338
+ * There is one additional constructor for advanced users, which takes seven arguments and is only for creating Yale
1339
+ * matrices with known IA, JA, and A arrays. This is used primarily internally for IO, e.g., reading Matlab matrices,
1340
+ * which are stored in old Yale (not our Yale) format. But be careful; there are no overflow warnings. All of these
1341
+ * constructors are defined for power-users. Everyone else should probably resort to the shortcut functions defined in
1342
+ * shortcuts.rb.
1343
+ */
1344
+ static VALUE nm_init(int argc, VALUE* argv, VALUE nm) {
1345
+ NM_CONSERVATIVE(nm_register_value(&nm));
1346
+ NM_CONSERVATIVE(nm_register_values(argv, argc));
1347
+
1348
+ if (argc <= 3) { // Call the new constructor unless all four arguments are given (or the 7-arg version is given)
1349
+ NM_CONSERVATIVE(nm_unregister_values(argv, argc));
1350
+ NM_CONSERVATIVE(nm_unregister_value(&nm));
1351
+ return nm_init_new_version(argc, argv, nm);
1352
+ }
1353
+
1354
+ /* First, determine stype (dense by default) */
1355
+ nm::stype_t stype;
1356
+ size_t offset = 0;
1357
+
1358
+ if (!SYMBOL_P(argv[0]) && !RB_TYPE_P(argv[0], T_STRING)) {
1359
+ stype = nm::DENSE_STORE;
1360
+
1361
+ } else {
1362
+ // 0: String or Symbol
1363
+ stype = interpret_stype(argv[0]);
1364
+ offset = 1;
1365
+ }
1366
+
1367
+ // If there are 7 arguments and Yale, refer to a different init function with fewer sanity checks.
1368
+ if (argc == 7) {
1369
+ if (stype == nm::YALE_STORE) {
1370
+ NM_CONSERVATIVE(nm_unregister_values(argv, argc));
1371
+ NM_CONSERVATIVE(nm_unregister_value(&nm));
1372
+ return nm_init_yale_from_old_yale(argv[1], argv[2], argv[3], argv[4], argv[5], argv[6], nm);
1373
+
1374
+ } else {
1375
+ NM_CONSERVATIVE(nm_unregister_values(argv, argc));
1376
+ NM_CONSERVATIVE(nm_unregister_value(&nm));
1377
+ rb_raise(rb_eArgError, "Expected 2-4 arguments (or 7 for internal Yale creation)");
1378
+ }
1379
+ }
1380
+
1381
+ // 1: Array or Fixnum
1382
+ size_t dim;
1383
+ size_t* shape = interpret_shape(argv[offset], &dim);
1384
+
1385
+ // 2-3: dtype
1386
+ nm::dtype_t dtype = interpret_dtype(argc-1-offset, argv+offset+1, stype);
1387
+
1388
+ size_t init_cap = 0, init_val_len = 0;
1389
+ void* init_val = NULL;
1390
+ if (!SYMBOL_P(argv[1+offset]) || RB_TYPE_P(argv[1+offset], T_ARRAY)) {
1391
+ // Initial value provided (could also be initial capacity, if yale).
1392
+
1393
+ if (stype == nm::YALE_STORE && NM_RUBYVAL_IS_NUMERIC(argv[1+offset])) {
1394
+ init_cap = FIX2UINT(argv[1+offset]);
1395
+
1396
+ } else {
1397
+ // 4: initial value / dtype
1398
+ init_val = interpret_initial_value(argv[1+offset], dtype);
1399
+
1400
+ if (RB_TYPE_P(argv[1+offset], T_ARRAY)) init_val_len = RARRAY_LEN(argv[1+offset]);
1401
+ else init_val_len = 1;
1402
+ }
1403
+
1404
+ } else {
1405
+ // DType is RUBYOBJ.
1406
+
1407
+ if (stype == nm::DENSE_STORE) {
1408
+ /*
1409
+ * No need to initialize dense with any kind of default value unless it's
1410
+ * an RUBYOBJ matrix.
1411
+ */
1412
+ if (dtype == nm::RUBYOBJ) {
1413
+ // Pretend [nil] was passed for RUBYOBJ.
1414
+ init_val = NM_ALLOC(VALUE);
1415
+ *(VALUE*)init_val = Qnil;
1416
+
1417
+ init_val_len = 1;
1418
+
1419
+ } else {
1420
+ init_val = NULL;
1421
+ }
1422
+ } else if (stype == nm::LIST_STORE) {
1423
+ init_val = NM_ALLOC_N(char, DTYPE_SIZES[dtype]);
1424
+ std::memset(init_val, 0, DTYPE_SIZES[dtype]);
1425
+ }
1426
+ }
1427
+
1428
+ if (dtype == nm::RUBYOBJ) {
1429
+ nm_register_values(reinterpret_cast<VALUE*>(init_val), init_val_len);
1430
+ }
1431
+
1432
+ // TODO: Update to allow an array as the initial value.
1433
+ NMATRIX* nmatrix;
1434
+ UnwrapNMatrix(nm, nmatrix);
1435
+
1436
+ nmatrix->stype = stype;
1437
+
1438
+ switch (stype) {
1439
+ case nm::DENSE_STORE:
1440
+ nmatrix->storage = (STORAGE*)nm_dense_storage_create(dtype, shape, dim, init_val, init_val_len);
1441
+ break;
1442
+
1443
+ case nm::LIST_STORE:
1444
+ nmatrix->storage = (STORAGE*)nm_list_storage_create(dtype, shape, dim, init_val);
1445
+ break;
1446
+
1447
+ case nm::YALE_STORE:
1448
+ nmatrix->storage = (STORAGE*)nm_yale_storage_create(dtype, shape, dim, init_cap);
1449
+ nm_yale_storage_init((YALE_STORAGE*)(nmatrix->storage), NULL);
1450
+ break;
1451
+ }
1452
+
1453
+ if (dtype == nm::RUBYOBJ) {
1454
+ nm_unregister_values(reinterpret_cast<VALUE*>(init_val), init_val_len);
1455
+ }
1456
+
1457
+ NM_CONSERVATIVE(nm_unregister_values(argv, argc));
1458
+ NM_CONSERVATIVE(nm_unregister_value(&nm));
1459
+
1460
+ return nm;
1461
+ }
1462
+
1463
+
1464
+ /*
1465
+ * Helper for nm_cast_with_types which uses the C types instead of the Ruby objects.
1466
+ * Called by nm_cast_with_types.
1467
+ */
1468
+ NMATRIX* nm_cast_with_ctype_args(NMATRIX* self, nm::stype_t new_stype, nm::dtype_t new_dtype, void* init_ptr) {
1469
+
1470
+ nm_register_nmatrix(self);
1471
+
1472
+ NMATRIX* lhs = NM_ALLOC(NMATRIX);
1473
+ lhs->stype = new_stype;
1474
+
1475
+ // Copy the storage
1476
+ CAST_TABLE(cast_copy);
1477
+ lhs->storage = cast_copy[lhs->stype][self->stype](self->storage, new_dtype, init_ptr);
1478
+
1479
+ nm_unregister_nmatrix(self);
1480
+
1481
+ return lhs;
1482
+ }
1483
+
1484
+ /*
1485
+ * Cast NMatrix with given new_stype and new_dtype. Called by nm_cast.
1486
+ */
1487
+ VALUE nm_cast_with_types(VALUE self, nm::stype_t new_stype, nm::dtype_t new_dtype,
1488
+ void* init_ptr) {
1489
+ NMATRIX *rhs;
1490
+
1491
+ UnwrapNMatrix( self, rhs );
1492
+
1493
+ NMATRIX* m = nm_cast_with_ctype_args(rhs, new_stype, new_dtype, init_ptr);
1494
+ nm_register_nmatrix(m);
1495
+
1496
+ VALUE to_return = Data_Wrap_Struct(CLASS_OF(self), nm_mark, nm_delete, m);
1497
+
1498
+ nm_unregister_nmatrix(m);
1499
+ return to_return;
1500
+ }
1501
+
1502
+ /*
1503
+ * call-seq:
1504
+ * cast_full(stype) -> NMatrix
1505
+ * cast_full(stype, dtype, sparse_basis) -> NMatrix
1506
+ *
1507
+ * Copy constructor for changing dtypes and stypes.
1508
+ */
1509
+ VALUE nm_cast(VALUE self, VALUE new_stype_symbol, VALUE new_dtype_symbol, VALUE init) {
1510
+ NM_CONSERVATIVE(nm_register_value(&self));
1511
+ NM_CONSERVATIVE(nm_register_value(&init));
1512
+
1513
+ nm::dtype_t new_dtype = nm_dtype_from_rbsymbol(new_dtype_symbol);
1514
+ nm::stype_t new_stype = nm_stype_from_rbsymbol(new_stype_symbol);
1515
+
1516
+ CheckNMatrixType(self);
1517
+ void* init_ptr = NM_ALLOCA_N(char, DTYPE_SIZES[new_dtype]);
1518
+ rubyval_to_cval(init, new_dtype, init_ptr);
1519
+
1520
+ VALUE to_return = nm_cast_with_types(self, new_stype, new_dtype, init_ptr);
1521
+
1522
+ NM_CONSERVATIVE(nm_unregister_value(&self));
1523
+ NM_CONSERVATIVE(nm_unregister_value(&init));
1524
+ return to_return;
1525
+
1526
+ }
1527
+
1528
+ /*
1529
+ * Copy constructor for transposing.
1530
+ */
1531
+ static VALUE nm_init_transposed(VALUE self) {
1532
+ NM_CONSERVATIVE(nm_register_value(&self));
1533
+
1534
+ static STORAGE* (*storage_copy_transposed[nm::NUM_STYPES])(const STORAGE* rhs_base) = {
1535
+ nm_dense_storage_copy_transposed,
1536
+ nm_list_storage_copy_transposed,
1537
+ nm_yale_storage_copy_transposed
1538
+ };
1539
+
1540
+ NMATRIX* lhs = nm_create( NM_STYPE(self),
1541
+ storage_copy_transposed[NM_STYPE(self)]( NM_STORAGE(self) )
1542
+ );
1543
+ nm_register_nmatrix(lhs);
1544
+ VALUE to_return = Data_Wrap_Struct(CLASS_OF(self), nm_mark, nm_delete, lhs);
1545
+
1546
+ nm_unregister_nmatrix(lhs);
1547
+ NM_CONSERVATIVE(nm_unregister_value(&self));
1548
+ return to_return;
1549
+ }
1550
+
1551
+ /*
1552
+ * Copy constructor for no change of dtype or stype (used for #initialize_copy hook).
1553
+ */
1554
+ static VALUE nm_init_copy(VALUE copy, VALUE original) {
1555
+ NM_CONSERVATIVE(nm_register_value(&copy));
1556
+ NM_CONSERVATIVE(nm_register_value(&original));
1557
+
1558
+ NMATRIX *lhs, *rhs;
1559
+
1560
+ CheckNMatrixType(original);
1561
+
1562
+ if (copy == original) {
1563
+ NM_CONSERVATIVE(nm_unregister_value(&copy));
1564
+ NM_CONSERVATIVE(nm_unregister_value(&original));
1565
+ return copy;
1566
+ }
1567
+
1568
+ UnwrapNMatrix( original, rhs );
1569
+ UnwrapNMatrix( copy, lhs );
1570
+
1571
+ lhs->stype = rhs->stype;
1572
+
1573
+ // Copy the storage
1574
+ CAST_TABLE(ttable);
1575
+ lhs->storage = ttable[lhs->stype][rhs->stype](rhs->storage, rhs->storage->dtype, NULL);
1576
+
1577
+ NM_CONSERVATIVE(nm_unregister_value(&copy));
1578
+ NM_CONSERVATIVE(nm_unregister_value(&original));
1579
+
1580
+ return copy;
1581
+ }
1582
+
1583
+ /*
1584
+ * Get major, minor, and release components of NMatrix::VERSION. Store in function parameters. Doesn't get
1585
+ * the "pre" field currently (beta1/rc1/etc).
1586
+ */
1587
+ static void get_version_info(uint16_t& major, uint16_t& minor, uint16_t& release) {
1588
+ // Get VERSION and split it on periods. Result is an Array.
1589
+ VALUE cVersion = rb_const_get(cNMatrix, rb_intern("VERSION"));
1590
+
1591
+ // Convert each to an integer
1592
+ major = FIX2INT(rb_const_get(cVersion, rb_intern("MAJOR")));
1593
+ minor = FIX2INT(rb_const_get(cVersion, rb_intern("MINOR")));
1594
+ release = FIX2INT(rb_const_get(cVersion, rb_intern("TINY")));
1595
+ }
1596
+
1597
+
1598
+ /*
1599
+ * Interpret the NMatrix::write symmetry argument (which should be nil or a symbol). Return a symm_t (enum).
1600
+ */
1601
+ static nm::symm_t interpret_symm(VALUE symm) {
1602
+ if (symm == Qnil) return nm::NONSYMM;
1603
+
1604
+ ID rb_symm = rb_intern("symmetric"),
1605
+ rb_skew = rb_intern("skew"),
1606
+ rb_herm = rb_intern("hermitian");
1607
+ // nm_rb_upper, nm_rb_lower already set
1608
+
1609
+ ID symm_id = rb_to_id(symm);
1610
+
1611
+ if (symm_id == rb_symm) return nm::SYMM;
1612
+ else if (symm_id == rb_skew) return nm::SKEW;
1613
+ else if (symm_id == rb_herm) return nm::HERM;
1614
+ else if (symm_id == nm_rb_upper) return nm::UPPER;
1615
+ else if (symm_id == nm_rb_lower) return nm::LOWER;
1616
+ else rb_raise(rb_eArgError, "unrecognized symmetry argument");
1617
+
1618
+ return nm::NONSYMM;
1619
+ }
1620
+
1621
+
1622
+
1623
+ void read_padded_shape(std::ifstream& f, size_t dim, size_t* shape) {
1624
+ size_t bytes_read = 0;
1625
+
1626
+ // Read shape
1627
+ for (size_t i = 0; i < dim; ++i) {
1628
+ size_t s;
1629
+ f.read(reinterpret_cast<char*>(&s), sizeof(size_t));
1630
+ shape[i] = s;
1631
+
1632
+ bytes_read += sizeof(size_t);
1633
+ }
1634
+
1635
+ // Ignore padding
1636
+ f.ignore(bytes_read % 8);
1637
+ }
1638
+
1639
+
1640
+ void write_padded_shape(std::ofstream& f, size_t dim, size_t* shape) {
1641
+ size_t bytes_written = 0;
1642
+
1643
+ // Write shape
1644
+ for (size_t i = 0; i < dim; ++i) {
1645
+ size_t s = shape[i];
1646
+ f.write(reinterpret_cast<const char*>(&s), sizeof(size_t));
1647
+
1648
+ bytes_written += sizeof(size_t);
1649
+ }
1650
+
1651
+ // Pad with zeros
1652
+ size_t zero = 0;
1653
+ while (bytes_written % 8) {
1654
+ f.write(reinterpret_cast<const char*>(&zero), sizeof(size_t));
1655
+
1656
+ bytes_written += sizeof(IType);
1657
+ }
1658
+ }
1659
+
1660
+
1661
+ void read_padded_yale_elements(std::ifstream& f, YALE_STORAGE* storage, size_t length, nm::symm_t symm, nm::dtype_t dtype) {
1662
+ NAMED_DTYPE_TEMPLATE_TABLE_NO_ROBJ(ttable, nm::read_padded_yale_elements, void, std::ifstream&, YALE_STORAGE*, size_t, nm::symm_t)
1663
+
1664
+ ttable[dtype](f, storage, length, symm);
1665
+ }
1666
+
1667
+
1668
+ void write_padded_yale_elements(std::ofstream& f, YALE_STORAGE* storage, size_t length, nm::symm_t symm, nm::dtype_t dtype) {
1669
+ NAMED_DTYPE_TEMPLATE_TABLE_NO_ROBJ(ttable, nm::write_padded_yale_elements, void, std::ofstream& f, YALE_STORAGE*, size_t, nm::symm_t)
1670
+
1671
+ ttable[dtype](f, storage, length, symm);
1672
+ }
1673
+
1674
+
1675
+ void read_padded_dense_elements(std::ifstream& f, DENSE_STORAGE* storage, nm::symm_t symm, nm::dtype_t dtype) {
1676
+ NAMED_DTYPE_TEMPLATE_TABLE_NO_ROBJ(ttable, nm::read_padded_dense_elements, void, std::ifstream&, DENSE_STORAGE*, nm::symm_t)
1677
+
1678
+ ttable[dtype](f, storage, symm);
1679
+ }
1680
+
1681
+
1682
+ void write_padded_dense_elements(std::ofstream& f, DENSE_STORAGE* storage, nm::symm_t symm, nm::dtype_t dtype) {
1683
+ NAMED_DTYPE_TEMPLATE_TABLE_NO_ROBJ(ttable, nm::write_padded_dense_elements, void, std::ofstream& f, DENSE_STORAGE*, nm::symm_t)
1684
+
1685
+ ttable[dtype](f, storage, symm);
1686
+ }
1687
+
1688
+
1689
+ /*
1690
+ * Helper function to get exceptions in the module Errno (e.g., ENOENT). Example:
1691
+ *
1692
+ * rb_raise(rb_get_errno_exc("ENOENT"), RSTRING_PTR(filename));
1693
+ */
1694
+ static VALUE rb_get_errno_exc(const char* which) {
1695
+ return rb_const_get(rb_const_get(rb_cObject, rb_intern("Errno")), rb_intern(which));
1696
+ }
1697
+
1698
+
1699
+
1700
+ /*
1701
+ * Binary file writer for NMatrix standard format. file should be a path, which we aren't going to
1702
+ * check very carefully (in other words, this function should generally be called from a Ruby
1703
+ * helper method). Function also takes a symmetry argument, which allows us to specify that we only want to
1704
+ * save the upper triangular portion of the matrix (or if the matrix is a lower triangular matrix, only
1705
+ * the lower triangular portion). nil means regular storage.
1706
+ */
1707
+ static VALUE nm_write(int argc, VALUE* argv, VALUE self) {
1708
+ using std::ofstream;
1709
+
1710
+ if (argc < 1 || argc > 2) {
1711
+ rb_raise(rb_eArgError, "Expected one or two arguments");
1712
+ }
1713
+
1714
+ NM_CONSERVATIVE(nm_register_values(argv, argc));
1715
+ NM_CONSERVATIVE(nm_register_value(&self));
1716
+
1717
+ VALUE file = argv[0],
1718
+ symm = argc == 1 ? Qnil : argv[1];
1719
+
1720
+ NMATRIX* nmatrix;
1721
+ UnwrapNMatrix( self, nmatrix );
1722
+
1723
+ nm::symm_t symm_ = interpret_symm(symm);
1724
+
1725
+ if (nmatrix->storage->dtype == nm::RUBYOBJ) {
1726
+ NM_CONSERVATIVE(nm_unregister_values(argv, argc));
1727
+ NM_CONSERVATIVE(nm_unregister_value(&self));
1728
+ rb_raise(rb_eNotImpError, "Ruby Object writing is not implemented yet");
1729
+ }
1730
+
1731
+ // Get the dtype, stype, itype, and symm and ensure they're the correct number of bytes.
1732
+ uint8_t st = static_cast<uint8_t>(nmatrix->stype),
1733
+ dt = static_cast<uint8_t>(nmatrix->storage->dtype),
1734
+ sm = static_cast<uint8_t>(symm_);
1735
+ uint16_t dim = nmatrix->storage->dim;
1736
+
1737
+ //FIXME: Cast the matrix to the smallest possible index type. Write that in the place of IType.
1738
+
1739
+ // Check arguments before starting to write.
1740
+ if (nmatrix->stype == nm::LIST_STORE) {
1741
+ NM_CONSERVATIVE(nm_unregister_values(argv, argc));
1742
+ NM_CONSERVATIVE(nm_unregister_value(&self));
1743
+ rb_raise(nm_eStorageTypeError, "cannot save list matrix; cast to yale or dense first");
1744
+ }
1745
+ if (symm_ != nm::NONSYMM) {
1746
+ NM_CONSERVATIVE(nm_unregister_values(argv, argc));
1747
+ NM_CONSERVATIVE(nm_unregister_value(&self));
1748
+
1749
+ if (dim != 2) rb_raise(rb_eArgError, "symmetry/triangularity not defined for a non-2D matrix");
1750
+ if (nmatrix->storage->shape[0] != nmatrix->storage->shape[1])
1751
+ rb_raise(rb_eArgError, "symmetry/triangularity not defined for a non-square matrix");
1752
+ if (symm_ == nm::HERM &&
1753
+ dt != static_cast<uint8_t>(nm::COMPLEX64) && dt != static_cast<uint8_t>(nm::COMPLEX128) && dt != static_cast<uint8_t>(nm::RUBYOBJ))
1754
+ rb_raise(rb_eArgError, "cannot save a non-complex matrix as hermitian");
1755
+ }
1756
+
1757
+ ofstream f(RSTRING_PTR(file), std::ios::out | std::ios::binary);
1758
+
1759
+ // Get the NMatrix version information.
1760
+ uint16_t major, minor, release, null16 = 0;
1761
+ get_version_info(major, minor, release);
1762
+
1763
+ // WRITE FIRST 64-BIT BLOCK
1764
+ f.write(reinterpret_cast<const char*>(&major), sizeof(uint16_t));
1765
+ f.write(reinterpret_cast<const char*>(&minor), sizeof(uint16_t));
1766
+ f.write(reinterpret_cast<const char*>(&release), sizeof(uint16_t));
1767
+ f.write(reinterpret_cast<const char*>(&null16), sizeof(uint16_t));
1768
+
1769
+ uint8_t ZERO = 0;
1770
+ // WRITE SECOND 64-BIT BLOCK
1771
+ f.write(reinterpret_cast<const char*>(&dt), sizeof(uint8_t));
1772
+ f.write(reinterpret_cast<const char*>(&st), sizeof(uint8_t));
1773
+ f.write(reinterpret_cast<const char*>(&ZERO),sizeof(uint8_t));
1774
+ f.write(reinterpret_cast<const char*>(&sm), sizeof(uint8_t));
1775
+ f.write(reinterpret_cast<const char*>(&null16), sizeof(uint16_t));
1776
+ f.write(reinterpret_cast<const char*>(&dim), sizeof(uint16_t));
1777
+
1778
+ // Write shape (in 64-bit blocks)
1779
+ write_padded_shape(f, nmatrix->storage->dim, nmatrix->storage->shape);
1780
+
1781
+ if (nmatrix->stype == nm::DENSE_STORE) {
1782
+ write_padded_dense_elements(f, reinterpret_cast<DENSE_STORAGE*>(nmatrix->storage), symm_, nmatrix->storage->dtype);
1783
+ } else if (nmatrix->stype == nm::YALE_STORE) {
1784
+ YALE_STORAGE* s = reinterpret_cast<YALE_STORAGE*>(nmatrix->storage);
1785
+ uint32_t ndnz = s->ndnz,
1786
+ length = nm_yale_storage_get_size(s);
1787
+ f.write(reinterpret_cast<const char*>(&ndnz), sizeof(uint32_t));
1788
+ f.write(reinterpret_cast<const char*>(&length), sizeof(uint32_t));
1789
+
1790
+ write_padded_yale_elements(f, s, length, symm_, s->dtype);
1791
+ }
1792
+
1793
+ f.close();
1794
+
1795
+ NM_CONSERVATIVE(nm_unregister_values(argv, argc));
1796
+ NM_CONSERVATIVE(nm_unregister_value(&self));
1797
+
1798
+ return Qtrue;
1799
+ }
1800
+
1801
+
1802
+ /*
1803
+ * Binary file reader for NMatrix standard format. file should be a path, which we aren't going to
1804
+ * check very carefully (in other words, this function should generally be called from a Ruby
1805
+ * helper method).
1806
+ *
1807
+ * Note that currently, this function will by default refuse to read files that are newer than
1808
+ * your version of NMatrix. To force an override, set the second argument to anything other than nil.
1809
+ *
1810
+ * Returns an NMatrix Ruby object.
1811
+ */
1812
+ static VALUE nm_read(int argc, VALUE* argv, VALUE self) {
1813
+ using std::ifstream;
1814
+
1815
+ NM_CONSERVATIVE(nm_register_values(argv, argc));
1816
+ NM_CONSERVATIVE(nm_register_value(&self));
1817
+
1818
+ VALUE file, force_;
1819
+
1820
+ // Read the arguments
1821
+ rb_scan_args(argc, argv, "11", &file, &force_);
1822
+ bool force = (force_ != Qnil && force_ != Qfalse);
1823
+
1824
+
1825
+ if (!RB_FILE_EXISTS(file)) { // FIXME: Errno::ENOENT
1826
+ NM_CONSERVATIVE(nm_unregister_values(argv, argc));
1827
+ NM_CONSERVATIVE(nm_unregister_value(&self));
1828
+ rb_raise(rb_get_errno_exc("ENOENT"), "%s", RSTRING_PTR(file));
1829
+ }
1830
+
1831
+ // Open a file stream
1832
+ ifstream f(RSTRING_PTR(file), std::ios::in | std::ios::binary);
1833
+
1834
+ uint16_t major, minor, release;
1835
+ get_version_info(major, minor, release); // compare to NMatrix version
1836
+
1837
+ uint16_t fmajor, fminor, frelease, null16;
1838
+
1839
+ // READ FIRST 64-BIT BLOCK
1840
+ f.read(reinterpret_cast<char*>(&fmajor), sizeof(uint16_t));
1841
+ f.read(reinterpret_cast<char*>(&fminor), sizeof(uint16_t));
1842
+ f.read(reinterpret_cast<char*>(&frelease), sizeof(uint16_t));
1843
+ f.read(reinterpret_cast<char*>(&null16), sizeof(uint16_t));
1844
+
1845
+ int ver = major * 10000 + minor * 100 + release,
1846
+ fver = fmajor * 10000 + fminor * 100 + release;
1847
+ if (fver > ver && force == false) {
1848
+ NM_CONSERVATIVE(nm_unregister_values(argv, argc));
1849
+ NM_CONSERVATIVE(nm_unregister_value(&self));
1850
+ rb_raise(rb_eIOError, "File was created in newer version of NMatrix than current (%u.%u.%u)", fmajor, fminor, frelease);
1851
+ }
1852
+ if (null16 != 0) rb_warn("nm_read: Expected zero padding was not zero (0)\n");
1853
+
1854
+ uint8_t dt, st, it, sm;
1855
+ uint16_t dim;
1856
+
1857
+ // READ SECOND 64-BIT BLOCK
1858
+ f.read(reinterpret_cast<char*>(&dt), sizeof(uint8_t));
1859
+ f.read(reinterpret_cast<char*>(&st), sizeof(uint8_t));
1860
+ f.read(reinterpret_cast<char*>(&it), sizeof(uint8_t)); // FIXME: should tell how few bytes indices are stored as
1861
+ f.read(reinterpret_cast<char*>(&sm), sizeof(uint8_t));
1862
+ f.read(reinterpret_cast<char*>(&null16), sizeof(uint16_t));
1863
+ f.read(reinterpret_cast<char*>(&dim), sizeof(uint16_t));
1864
+
1865
+ if (null16 != 0) rb_warn("nm_read: Expected zero padding was not zero (1)");
1866
+ nm::stype_t stype = static_cast<nm::stype_t>(st);
1867
+ nm::dtype_t dtype = static_cast<nm::dtype_t>(dt);
1868
+ nm::symm_t symm = static_cast<nm::symm_t>(sm);
1869
+ //nm::itype_t itype = static_cast<nm::itype_t>(it);
1870
+
1871
+ // READ NEXT FEW 64-BIT BLOCKS
1872
+ size_t* shape = NM_ALLOC_N(size_t, dim);
1873
+ read_padded_shape(f, dim, shape);
1874
+
1875
+ STORAGE* s;
1876
+ if (stype == nm::DENSE_STORE) {
1877
+ s = nm_dense_storage_create(dtype, shape, dim, NULL, 0);
1878
+ nm_register_storage(stype, s);
1879
+
1880
+ read_padded_dense_elements(f, reinterpret_cast<DENSE_STORAGE*>(s), symm, dtype);
1881
+
1882
+ } else if (stype == nm::YALE_STORE) {
1883
+ uint32_t ndnz, length;
1884
+
1885
+ // READ YALE-SPECIFIC 64-BIT BLOCK
1886
+ f.read(reinterpret_cast<char*>(&ndnz), sizeof(uint32_t));
1887
+ f.read(reinterpret_cast<char*>(&length), sizeof(uint32_t));
1888
+
1889
+ s = nm_yale_storage_create(dtype, shape, dim, length); // set length as init capacity
1890
+
1891
+ nm_register_storage(stype, s);
1892
+
1893
+ read_padded_yale_elements(f, reinterpret_cast<YALE_STORAGE*>(s), length, symm, dtype);
1894
+ } else {
1895
+ NM_CONSERVATIVE(nm_unregister_values(argv, argc));
1896
+ NM_CONSERVATIVE(nm_unregister_value(&self));
1897
+ rb_raise(nm_eStorageTypeError, "please convert to yale or dense before saving");
1898
+ }
1899
+
1900
+ NMATRIX* nm = nm_create(stype, s);
1901
+
1902
+ // Return the appropriate matrix object (Ruby VALUE)
1903
+ // FIXME: This should probably return CLASS_OF(self) instead of cNMatrix, but I don't know how that works for
1904
+ // FIXME: class methods.
1905
+ nm_register_nmatrix(nm);
1906
+ VALUE to_return = Data_Wrap_Struct(cNMatrix, nm_mark, nm_delete, nm);
1907
+
1908
+ nm_unregister_nmatrix(nm);
1909
+ NM_CONSERVATIVE(nm_unregister_values(argv, argc));
1910
+ NM_CONSERVATIVE(nm_unregister_value(&self));
1911
+ nm_unregister_storage(stype, s);
1912
+
1913
+ switch(stype) {
1914
+ case nm::DENSE_STORE:
1915
+ case nm::YALE_STORE:
1916
+ return to_return;
1917
+ default: // this case never occurs (due to earlier rb_raise)
1918
+ return Qnil;
1919
+ }
1920
+
1921
+ }
1922
+
1923
+
1924
+
1925
+ /*
1926
+ * Create a new NMatrix helper for handling internal ia, ja, and a arguments.
1927
+ *
1928
+ * This constructor is only called by Ruby code, so we can skip most of the
1929
+ * checks.
1930
+ */
1931
+ static VALUE nm_init_yale_from_old_yale(VALUE shape, VALUE dtype, VALUE ia, VALUE ja, VALUE a, VALUE from_dtype, VALUE nm) {
1932
+ size_t dim = 2;
1933
+ size_t* shape_ = interpret_shape(shape, &dim);
1934
+ nm::dtype_t dtype_ = nm_dtype_from_rbsymbol(dtype);
1935
+ char *ia_ = RSTRING_PTR(ia),
1936
+ *ja_ = RSTRING_PTR(ja),
1937
+ *a_ = RSTRING_PTR(a);
1938
+ nm::dtype_t from_dtype_ = nm_dtype_from_rbsymbol(from_dtype);
1939
+ NMATRIX* nmatrix;
1940
+
1941
+ UnwrapNMatrix( nm, nmatrix );
1942
+
1943
+ nmatrix->stype = nm::YALE_STORE;
1944
+ nmatrix->storage = (STORAGE*)nm_yale_storage_create_from_old_yale(dtype_, shape_, ia_, ja_, a_, from_dtype_);
1945
+
1946
+ return nm;
1947
+ }
1948
+
1949
+ /*
1950
+ * Check to determine whether matrix is a reference to another matrix.
1951
+ */
1952
+ static VALUE nm_is_ref(VALUE self) {
1953
+ if (NM_SRC(self) == NM_STORAGE(self)) return Qfalse;
1954
+ return Qtrue;
1955
+ }
1956
+
1957
+ /*
1958
+ * call-seq:
1959
+ * slice -> ...
1960
+ *
1961
+ * Access the contents of an NMatrix at given coordinates, using copying.
1962
+ *
1963
+ * n.slice(3,3) # => 5.0
1964
+ * n.slice(0..1,0..1) #=> matrix [2,2]
1965
+ *
1966
+ */
1967
+ static VALUE nm_mget(int argc, VALUE* argv, VALUE self) {
1968
+ static void* (*ttable[nm::NUM_STYPES])(const STORAGE*, SLICE*) = {
1969
+ nm_dense_storage_get,
1970
+ nm_list_storage_get,
1971
+ nm_yale_storage_get
1972
+ };
1973
+ nm::stype_t stype = NM_STYPE(self);
1974
+ return nm_xslice(argc, argv, ttable[stype], nm_delete, self);
1975
+ }
1976
+
1977
+ /*
1978
+ * call-seq:
1979
+ * matrix[indices] -> ...
1980
+ *
1981
+ * Access the contents of an NMatrix at given coordinates by reference.
1982
+ *
1983
+ * n[3,3] # => 5.0
1984
+ * n[0..1,0..1] #=> matrix [2,2]
1985
+ *
1986
+ */
1987
+ static VALUE nm_mref(int argc, VALUE* argv, VALUE self) {
1988
+ static void* (*ttable[nm::NUM_STYPES])(const STORAGE*, SLICE*) = {
1989
+ nm_dense_storage_ref,
1990
+ nm_list_storage_ref,
1991
+ nm_yale_storage_ref
1992
+ };
1993
+ nm::stype_t stype = NM_STYPE(self);
1994
+ return nm_xslice(argc, argv, ttable[stype], nm_delete_ref, self);
1995
+ }
1996
+
1997
+ /*
1998
+ * Modify the contents of an NMatrix in the given cell
1999
+ *
2000
+ * n[3,3] = 5.0
2001
+ *
2002
+ * Also returns the new contents, so you can chain:
2003
+ *
2004
+ * n[3,3] = n[2,3] = 5.0
2005
+ */
2006
+ static VALUE nm_mset(int argc, VALUE* argv, VALUE self) {
2007
+
2008
+ size_t dim = NM_DIM(self); // last arg is the value
2009
+
2010
+ VALUE to_return = Qnil;
2011
+
2012
+ if ((size_t)(argc) > NM_DIM(self)+1) {
2013
+ rb_raise(rb_eArgError, "wrong number of arguments (%d for %lu)", argc, effective_dim(NM_STORAGE(self))+1);
2014
+ } else {
2015
+ NM_CONSERVATIVE(nm_register_value(&self));
2016
+ NM_CONSERVATIVE(nm_register_values(argv, argc));
2017
+
2018
+ SLICE slice_s;
2019
+ SLICE* slice = &slice_s;
2020
+ slice->coords = NM_ALLOCA_N(size_t, dim);
2021
+ slice->lengths = NM_ALLOCA_N(size_t, dim);
2022
+ init_slice_no_alloc(slice, dim, argc-1, argv, NM_STORAGE(self)->shape);
2023
+
2024
+ static void (*ttable[nm::NUM_STYPES])(VALUE, SLICE*, VALUE) = {
2025
+ nm_dense_storage_set,
2026
+ nm_list_storage_set,
2027
+ nm_yale_storage_set
2028
+ };
2029
+
2030
+ ttable[NM_STYPE(self)](self, slice, argv[argc-1]);
2031
+
2032
+ to_return = argv[argc-1];
2033
+
2034
+ NM_CONSERVATIVE(nm_unregister_value(&self));
2035
+ NM_CONSERVATIVE(nm_unregister_values(argv, argc));
2036
+ }
2037
+
2038
+ return to_return;
2039
+ }
2040
+
2041
+ /*
2042
+ * Matrix multiply (dot product): against another matrix or a vector.
2043
+ *
2044
+ * For elementwise, use * instead.
2045
+ *
2046
+ * The two matrices must be of the same stype (for now). If dtype differs, an upcast will occur.
2047
+ */
2048
+ static VALUE nm_multiply(VALUE left_v, VALUE right_v) {
2049
+ NM_CONSERVATIVE(nm_register_value(&left_v));
2050
+ NM_CONSERVATIVE(nm_register_value(&right_v));
2051
+
2052
+ NMATRIX *left, *right;
2053
+
2054
+ UnwrapNMatrix( left_v, left );
2055
+
2056
+ if (NM_RUBYVAL_IS_NUMERIC(right_v)) {
2057
+ NM_CONSERVATIVE(nm_unregister_value(&left_v));
2058
+ NM_CONSERVATIVE(nm_unregister_value(&right_v));
2059
+ return matrix_multiply_scalar(left, right_v);
2060
+ }
2061
+
2062
+ else if (RB_TYPE_P(right_v, T_ARRAY)) {
2063
+ NM_CONSERVATIVE(nm_unregister_value(&left_v));
2064
+ NM_CONSERVATIVE(nm_unregister_value(&right_v));
2065
+ rb_raise(rb_eNotImpError, "please convert array to nx1 or 1xn NMatrix first");
2066
+ }
2067
+
2068
+ else { // both are matrices (probably)
2069
+ CheckNMatrixType(right_v);
2070
+ UnwrapNMatrix( right_v, right );
2071
+
2072
+ // work like vector dot product for 1dim
2073
+ if (left->storage->dim == 1 && right->storage->dim == 1) {
2074
+ if (left->storage->shape[0] != right->storage->shape[0]) {
2075
+ NM_CONSERVATIVE(nm_unregister_value(&left_v));
2076
+ NM_CONSERVATIVE(nm_unregister_value(&right_v));
2077
+ rb_raise(rb_eArgError, "The left- and right-hand sides of the operation must have the same dimensionality.");
2078
+ } else {
2079
+ VALUE result = elementwise_op(nm::EW_MUL, left_v, right_v);
2080
+ VALUE to_return = rb_funcall(result, rb_intern("sum"),0);
2081
+ NM_CONSERVATIVE(nm_unregister_value(&left_v));
2082
+ NM_CONSERVATIVE(nm_unregister_value(&right_v));
2083
+ return to_return;
2084
+ }
2085
+ }
2086
+
2087
+ if (left->storage->shape[1] != right->storage->shape[0]) {
2088
+ NM_CONSERVATIVE(nm_unregister_value(&left_v));
2089
+ NM_CONSERVATIVE(nm_unregister_value(&right_v));
2090
+ rb_raise(rb_eArgError, "incompatible dimensions");
2091
+ }
2092
+
2093
+ if (left->stype != right->stype) {
2094
+ NM_CONSERVATIVE(nm_unregister_value(&left_v));
2095
+ NM_CONSERVATIVE(nm_unregister_value(&right_v));
2096
+ rb_raise(rb_eNotImpError, "matrices must have same stype");
2097
+ }
2098
+
2099
+ NM_CONSERVATIVE(nm_unregister_value(&left_v));
2100
+ NM_CONSERVATIVE(nm_unregister_value(&right_v));
2101
+ return matrix_multiply(left, right);
2102
+
2103
+ }
2104
+
2105
+ NM_CONSERVATIVE(nm_unregister_value(&left_v));
2106
+ NM_CONSERVATIVE(nm_unregister_value(&right_v));
2107
+
2108
+ return Qnil;
2109
+ }
2110
+
2111
+
2112
+ /*
2113
+ * call-seq:
2114
+ * dim -> Integer
2115
+ *
2116
+ * Get the number of dimensions of a matrix.
2117
+ *
2118
+ * In other words, if you set your matrix to be 3x4, the dim is 2. If the
2119
+ * matrix was initialized as 3x4x3, the dim is 3.
2120
+ *
2121
+ * Use #effective_dim to get the dimension of an NMatrix which acts as a vector (e.g., a column or row).
2122
+ */
2123
+ static VALUE nm_dim(VALUE self) {
2124
+ return INT2FIX(NM_STORAGE(self)->dim);
2125
+ }
2126
+
2127
+ /*
2128
+ * call-seq:
2129
+ * shape -> Array
2130
+ *
2131
+ * Get the shape (dimensions) of a matrix.
2132
+ */
2133
+ static VALUE nm_shape(VALUE self) {
2134
+ NM_CONSERVATIVE(nm_register_value(&self));
2135
+ STORAGE* s = NM_STORAGE(self);
2136
+
2137
+ // Copy elements into a VALUE array and then use those to create a Ruby array with rb_ary_new4.
2138
+ VALUE* shape = NM_ALLOCA_N(VALUE, s->dim);
2139
+ nm_register_values(shape, s->dim);
2140
+ for (size_t index = 0; index < s->dim; ++index)
2141
+ shape[index] = INT2FIX(s->shape[index]);
2142
+
2143
+ nm_unregister_values(shape, s->dim);
2144
+ NM_CONSERVATIVE(nm_unregister_value(&self));
2145
+ return rb_ary_new4(s->dim, shape);
2146
+ }
2147
+
2148
+
2149
+ /*
2150
+ * call-seq:
2151
+ * offset -> Array
2152
+ *
2153
+ * Get the offset (slice position) of a matrix. Typically all zeros, unless you have a reference slice.
2154
+ */
2155
+ static VALUE nm_offset(VALUE self) {
2156
+ NM_CONSERVATIVE(nm_register_value(&self));
2157
+ STORAGE* s = NM_STORAGE(self);
2158
+
2159
+ // Copy elements into a VALUE array and then use those to create a Ruby array with rb_ary_new4.
2160
+ VALUE* offset = NM_ALLOCA_N(VALUE, s->dim);
2161
+ nm_register_values(offset, s->dim);
2162
+ for (size_t index = 0; index < s->dim; ++index)
2163
+ offset[index] = INT2FIX(s->offset[index]);
2164
+
2165
+ nm_unregister_values(offset, s->dim);
2166
+ NM_CONSERVATIVE(nm_unregister_value(&self));
2167
+ return rb_ary_new4(s->dim, offset);
2168
+ }
2169
+
2170
+
2171
+ /*
2172
+ * call-seq:
2173
+ * supershape -> Array
2174
+ *
2175
+ * Get the shape of a slice's parent.
2176
+ */
2177
+ static VALUE nm_supershape(VALUE self) {
2178
+
2179
+ STORAGE* s = NM_STORAGE(self);
2180
+ if (s->src == s) {
2181
+ return nm_shape(self); // easy case (not a slice)
2182
+ }
2183
+ else s = s->src;
2184
+
2185
+ NM_CONSERVATIVE(nm_register_value(&self));
2186
+
2187
+ VALUE* shape = NM_ALLOCA_N(VALUE, s->dim);
2188
+ nm_register_values(shape, s->dim);
2189
+ for (size_t index = 0; index < s->dim; ++index)
2190
+ shape[index] = INT2FIX(s->shape[index]);
2191
+
2192
+ nm_unregister_values(shape, s->dim);
2193
+ NM_CONSERVATIVE(nm_unregister_value(&self));
2194
+ return rb_ary_new4(s->dim, shape);
2195
+ }
2196
+
2197
+ /*
2198
+ * call-seq:
2199
+ * stype -> Symbol
2200
+ *
2201
+ * Get the storage type (stype) of a matrix, e.g., :yale, :dense, or :list.
2202
+ */
2203
+ static VALUE nm_stype(VALUE self) {
2204
+ NM_CONSERVATIVE(nm_register_value(&self));
2205
+ VALUE stype = ID2SYM(rb_intern(STYPE_NAMES[NM_STYPE(self)]));
2206
+ NM_CONSERVATIVE(nm_unregister_value(&self));
2207
+ return stype;
2208
+ }
2209
+
2210
+ /*
2211
+ * call-seq:
2212
+ * symmetric? -> Boolean
2213
+ *
2214
+ * Is this matrix symmetric?
2215
+ */
2216
+ static VALUE nm_symmetric(VALUE self) {
2217
+ return is_symmetric(self, false);
2218
+ }
2219
+
2220
+
2221
+ /*
2222
+ * Gets the dimension of a matrix which might be a vector (have one or more shape components of size 1).
2223
+ */
2224
+ static size_t effective_dim(STORAGE* s) {
2225
+ size_t d = 0;
2226
+ for (size_t i = 0; i < s->dim; ++i) {
2227
+ if (s->shape[i] != 1) d++;
2228
+ }
2229
+ return d;
2230
+ }
2231
+
2232
+
2233
+ /*
2234
+ * call-seq:
2235
+ * effective_dim -> Fixnum
2236
+ *
2237
+ * Returns the number of dimensions that don't have length 1. Guaranteed to be less than or equal to #dim.
2238
+ */
2239
+ static VALUE nm_effective_dim(VALUE self) {
2240
+ return INT2FIX(effective_dim(NM_STORAGE(self)));
2241
+ }
2242
+
2243
+
2244
+ /*
2245
+ * Get a slice of an NMatrix.
2246
+ */
2247
+ static VALUE nm_xslice(int argc, VALUE* argv, void* (*slice_func)(const STORAGE*, SLICE*), void (*delete_func)(NMATRIX*), VALUE self) {
2248
+ VALUE result = Qnil;
2249
+
2250
+ STORAGE* s = NM_STORAGE(self);
2251
+
2252
+ if (NM_DIM(self) < (size_t)(argc)) {
2253
+ rb_raise(rb_eArgError, "wrong number of arguments (%d for %lu)", argc, effective_dim(s));
2254
+ } else {
2255
+
2256
+ NM_CONSERVATIVE(nm_register_values(argv, argc));
2257
+ NM_CONSERVATIVE(nm_register_value(&self));
2258
+
2259
+ nm_register_value(&result);
2260
+
2261
+ SLICE slice_s;
2262
+ SLICE* slice = &slice_s;
2263
+ size_t dim = NM_DIM(self);
2264
+ slice->coords = NM_ALLOCA_N(size_t, dim);
2265
+ slice->lengths = NM_ALLOCA_N(size_t, dim);
2266
+ init_slice_no_alloc(slice, dim, argc, argv, s->shape);
2267
+
2268
+ if (slice->single) {
2269
+ static void* (*ttable[nm::NUM_STYPES])(const STORAGE*, SLICE*) = {
2270
+ nm_dense_storage_ref,
2271
+ nm_list_storage_ref,
2272
+ nm_yale_storage_ref
2273
+ };
2274
+
2275
+ if (NM_DTYPE(self) == nm::RUBYOBJ) result = *reinterpret_cast<VALUE*>( ttable[NM_STYPE(self)](s, slice) );
2276
+ else result = nm::rubyobj_from_cval( ttable[NM_STYPE(self)](s, slice), NM_DTYPE(self) ).rval;
2277
+
2278
+ } else {
2279
+
2280
+ NMATRIX* mat = NM_ALLOC(NMATRIX);
2281
+ mat->stype = NM_STYPE(self);
2282
+ mat->storage = (STORAGE*)((*slice_func)( s, slice ));
2283
+ nm_register_nmatrix(mat);
2284
+ result = Data_Wrap_Struct(CLASS_OF(self), nm_mark, delete_func, mat);
2285
+ nm_unregister_nmatrix(mat);
2286
+ }
2287
+ }
2288
+
2289
+ nm_unregister_value(&result);
2290
+ NM_CONSERVATIVE(nm_unregister_values(argv, argc));
2291
+ NM_CONSERVATIVE(nm_unregister_value(&self));
2292
+
2293
+ return result;
2294
+ }
2295
+
2296
+ //////////////////////
2297
+ // Helper Functions //
2298
+ //////////////////////
2299
+
2300
+ static VALUE unary_op(nm::unaryop_t op, VALUE self) {
2301
+ NM_CONSERVATIVE(nm_register_value(&self));
2302
+ NMATRIX* left;
2303
+ UnwrapNMatrix(self, left);
2304
+ std::string sym;
2305
+
2306
+ switch(left->stype) {
2307
+ case nm::DENSE_STORE:
2308
+ sym = "__dense_unary_" + nm::UNARYOPS[op] + "__";
2309
+ break;
2310
+ case nm::YALE_STORE:
2311
+ sym = "__yale_unary_" + nm::UNARYOPS[op] + "__";
2312
+ break;
2313
+ case nm::LIST_STORE:
2314
+ sym = "__list_unary_" + nm::UNARYOPS[op] + "__";
2315
+ break;
2316
+ }
2317
+
2318
+ NM_CONSERVATIVE(nm_unregister_value(&self));
2319
+ return rb_funcall(self, rb_intern(sym.c_str()), 0);
2320
+ }
2321
+
2322
+ static void check_dims_and_shape(VALUE left_val, VALUE right_val) {
2323
+ // Check that the left- and right-hand sides have the same dimensionality.
2324
+ if (NM_DIM(left_val) != NM_DIM(right_val)) {
2325
+ rb_raise(rb_eArgError, "The left- and right-hand sides of the operation must have the same dimensionality.");
2326
+ }
2327
+ // Check that the left- and right-hand sides have the same shape.
2328
+ if (memcmp(&NM_SHAPE(left_val, 0), &NM_SHAPE(right_val, 0), sizeof(size_t) * NM_DIM(left_val)) != 0) {
2329
+ rb_raise(rb_eArgError, "The left- and right-hand sides of the operation must have the same shape.");
2330
+ }
2331
+ }
2332
+
2333
+ static VALUE elementwise_op(nm::ewop_t op, VALUE left_val, VALUE right_val) {
2334
+
2335
+ NM_CONSERVATIVE(nm_register_value(&left_val));
2336
+ NM_CONSERVATIVE(nm_register_value(&right_val));
2337
+
2338
+ NMATRIX* left;
2339
+ NMATRIX* result;
2340
+
2341
+ CheckNMatrixType(left_val);
2342
+ UnwrapNMatrix(left_val, left);
2343
+
2344
+ if (!IsNMatrixType(right_val)) {
2345
+ // This is a matrix-scalar element-wise operation.
2346
+ std::string sym;
2347
+ switch(left->stype) {
2348
+ case nm::DENSE_STORE:
2349
+ sym = "__dense_scalar_" + nm::EWOP_NAMES[op] + "__";
2350
+ break;
2351
+ case nm::YALE_STORE:
2352
+ sym = "__yale_scalar_" + nm::EWOP_NAMES[op] + "__";
2353
+ break;
2354
+ case nm::LIST_STORE:
2355
+ sym = "__list_scalar_" + nm::EWOP_NAMES[op] + "__";
2356
+ break;
2357
+ default:
2358
+ NM_CONSERVATIVE(nm_unregister_value(&left_val));
2359
+ NM_CONSERVATIVE(nm_unregister_value(&right_val));
2360
+ rb_raise(rb_eNotImpError, "unknown storage type requested scalar element-wise operation");
2361
+ }
2362
+ VALUE symv = rb_intern(sym.c_str());
2363
+ NM_CONSERVATIVE(nm_unregister_value(&left_val));
2364
+ NM_CONSERVATIVE(nm_unregister_value(&right_val));
2365
+ return rb_funcall(left_val, symv, 1, right_val);
2366
+
2367
+ } else {
2368
+
2369
+ check_dims_and_shape(left_val, right_val);
2370
+
2371
+ NMATRIX* right;
2372
+ UnwrapNMatrix(right_val, right);
2373
+
2374
+ if (left->stype == right->stype) {
2375
+ std::string sym;
2376
+
2377
+ switch(left->stype) {
2378
+ case nm::DENSE_STORE:
2379
+ sym = "__dense_elementwise_" + nm::EWOP_NAMES[op] + "__";
2380
+ break;
2381
+ case nm::YALE_STORE:
2382
+ sym = "__yale_elementwise_" + nm::EWOP_NAMES[op] + "__";
2383
+ break;
2384
+ case nm::LIST_STORE:
2385
+ sym = "__list_elementwise_" + nm::EWOP_NAMES[op] + "__";
2386
+ break;
2387
+ default:
2388
+ NM_CONSERVATIVE(nm_unregister_value(&left_val));
2389
+ NM_CONSERVATIVE(nm_unregister_value(&right_val));
2390
+ rb_raise(rb_eNotImpError, "unknown storage type requested element-wise operation");
2391
+ }
2392
+
2393
+ VALUE symv = rb_intern(sym.c_str());
2394
+ NM_CONSERVATIVE(nm_unregister_value(&left_val));
2395
+ NM_CONSERVATIVE(nm_unregister_value(&right_val));
2396
+ return rb_funcall(left_val, symv, 1, right_val);
2397
+
2398
+ } else {
2399
+ NM_CONSERVATIVE(nm_unregister_value(&left_val));
2400
+ NM_CONSERVATIVE(nm_unregister_value(&right_val));
2401
+ rb_raise(rb_eArgError, "Element-wise operations are not currently supported between matrices with differing stypes.");
2402
+ }
2403
+ }
2404
+
2405
+ NM_CONSERVATIVE(nm_unregister_value(&left_val));
2406
+ NM_CONSERVATIVE(nm_unregister_value(&right_val));
2407
+ return Data_Wrap_Struct(CLASS_OF(left_val), nm_mark, nm_delete, result);
2408
+ }
2409
+
2410
+ static VALUE noncom_elementwise_op(nm::noncom_ewop_t op, VALUE self, VALUE other, VALUE flip) {
2411
+
2412
+ NM_CONSERVATIVE(nm_register_value(&self));
2413
+ NM_CONSERVATIVE(nm_register_value(&other));
2414
+
2415
+ NMATRIX* self_nm;
2416
+ NMATRIX* result;
2417
+
2418
+ CheckNMatrixType(self);
2419
+ UnwrapNMatrix(self, self_nm);
2420
+
2421
+ if (!IsNMatrixType(other)) {
2422
+ // This is a matrix-scalar element-wise operation.
2423
+ std::string sym;
2424
+ switch(self_nm->stype) {
2425
+ case nm::DENSE_STORE:
2426
+ sym = "__dense_scalar_" + nm::NONCOM_EWOP_NAMES[op] + "__";
2427
+ break;
2428
+ case nm::YALE_STORE:
2429
+ sym = "__yale_scalar_" + nm::NONCOM_EWOP_NAMES[op] + "__";
2430
+ break;
2431
+ case nm::LIST_STORE:
2432
+ sym = "__list_scalar_" + nm::NONCOM_EWOP_NAMES[op] + "__";
2433
+ break;
2434
+ default:
2435
+ NM_CONSERVATIVE(nm_unregister_value(&self));
2436
+ NM_CONSERVATIVE(nm_unregister_value(&other));
2437
+ rb_raise(rb_eNotImpError, "unknown storage type requested scalar element-wise operation");
2438
+ }
2439
+ NM_CONSERVATIVE(nm_unregister_value(&self));
2440
+ NM_CONSERVATIVE(nm_unregister_value(&other));
2441
+ return rb_funcall(self, rb_intern(sym.c_str()), 2, other, flip);
2442
+
2443
+ } else {
2444
+
2445
+ check_dims_and_shape(self, other);
2446
+
2447
+ NMATRIX* other_nm;
2448
+ UnwrapNMatrix(other, other_nm);
2449
+
2450
+ if (self_nm->stype == other_nm->stype) {
2451
+ std::string sym;
2452
+
2453
+ switch(self_nm->stype) {
2454
+ case nm::DENSE_STORE:
2455
+ sym = "__dense_elementwise_" + nm::NONCOM_EWOP_NAMES[op] + "__";
2456
+ break;
2457
+ case nm::YALE_STORE:
2458
+ sym = "__yale_elementwise_" + nm::NONCOM_EWOP_NAMES[op] + "__";
2459
+ break;
2460
+ case nm::LIST_STORE:
2461
+ sym = "__list_elementwise_" + nm::NONCOM_EWOP_NAMES[op] + "__";
2462
+ break;
2463
+ default:
2464
+ NM_CONSERVATIVE(nm_unregister_value(&self));
2465
+ NM_CONSERVATIVE(nm_unregister_value(&other));
2466
+ rb_raise(rb_eNotImpError, "unknown storage type requested element-wise operation");
2467
+ }
2468
+ NM_CONSERVATIVE(nm_unregister_value(&self));
2469
+ NM_CONSERVATIVE(nm_unregister_value(&other));
2470
+ return rb_funcall(self, rb_intern(sym.c_str()), 2, other, flip);
2471
+
2472
+ } else {
2473
+ nm_unregister_value(&self);
2474
+ nm_unregister_value(&other);
2475
+ rb_raise(rb_eArgError, "Element-wise operations are not currently supported between matrices with differing stypes.");
2476
+ }
2477
+ }
2478
+ NM_CONSERVATIVE(nm_unregister_value(&self));
2479
+ NM_CONSERVATIVE(nm_unregister_value(&other));
2480
+ return Data_Wrap_Struct(CLASS_OF(self), nm_mark, nm_delete, result);
2481
+ }
2482
+
2483
+ /*
2484
+ * Check to determine whether matrix is a reference to another matrix.
2485
+ */
2486
+ bool is_ref(const NMATRIX* matrix) {
2487
+ return matrix->storage->src != matrix->storage;
2488
+ }
2489
+
2490
+ /*
2491
+ * Helper function for nm_symmetric and nm_hermitian.
2492
+ */
2493
+ static VALUE is_symmetric(VALUE self, bool hermitian) {
2494
+ NM_CONSERVATIVE(nm_register_value(&self));
2495
+
2496
+ NMATRIX* m;
2497
+ UnwrapNMatrix(self, m);
2498
+ bool is_symmetric = false;
2499
+
2500
+ if (m->storage->shape[0] == m->storage->shape[1] and m->storage->dim == 2) {
2501
+ if (NM_STYPE(self) == nm::DENSE_STORE) {
2502
+ if (hermitian) {
2503
+ is_symmetric = nm_dense_storage_is_hermitian((DENSE_STORAGE*)(m->storage), m->storage->shape[0]);
2504
+
2505
+ } else {
2506
+ is_symmetric = nm_dense_storage_is_symmetric((DENSE_STORAGE*)(m->storage), m->storage->shape[0]);
2507
+ }
2508
+
2509
+ } else {
2510
+ // TODO: Implement, at the very least, yale_is_symmetric. Model it after yale/transp.template.c.
2511
+ NM_CONSERVATIVE(nm_unregister_value(&self));
2512
+ rb_raise(rb_eNotImpError, "symmetric? and hermitian? only implemented for dense currently");
2513
+ }
2514
+
2515
+ }
2516
+ NM_CONSERVATIVE(nm_unregister_value(&self));
2517
+ return is_symmetric ? Qtrue : Qfalse;
2518
+ }
2519
+
2520
+ ///////////////////////
2521
+ // Utility Functions //
2522
+ ///////////////////////
2523
+
2524
+ /*
2525
+ * Guess the dtype given a Ruby VALUE and return it as a symbol.
2526
+ *
2527
+ * Not to be confused with nm_dtype_guess, which returns an nm::dtype_t. (This calls that.)
2528
+ */
2529
+ static VALUE nm_guess_dtype(VALUE self, VALUE v) {
2530
+ return ID2SYM(rb_intern(DTYPE_NAMES[nm_dtype_guess(v)]));
2531
+ }
2532
+
2533
+ /*
2534
+ * Get the minimum allowable dtype for a Ruby VALUE and return it as a symbol.
2535
+ */
2536
+ static VALUE nm_min_dtype(VALUE self, VALUE v) {
2537
+ return ID2SYM(rb_intern(DTYPE_NAMES[nm_dtype_min(v)]));
2538
+ }
2539
+
2540
+ /*
2541
+ * Helper for nm_dtype_min(), handling integers.
2542
+ */
2543
+ nm::dtype_t nm_dtype_min_fixnum(int64_t v) {
2544
+ if (v >= 0 && v <= UCHAR_MAX) return nm::BYTE;
2545
+ else {
2546
+ v = std::abs(v);
2547
+ if (v <= CHAR_MAX) return nm::INT8;
2548
+ else if (v <= SHRT_MAX) return nm::INT16;
2549
+ else if (v <= INT_MAX) return nm::INT32;
2550
+ else return nm::INT64;
2551
+ }
2552
+ }
2553
+
2554
+ /*
2555
+ * Return the minimum dtype required to store a given value.
2556
+ *
2557
+ * This is kind of arbitrary. For Float, it always returns :float32 for example, since in some cases neither :float64
2558
+ * not :float32 are sufficient.
2559
+ *
2560
+ * This function is used in upcasting for scalar math. We want to ensure that :int8 + 1 does not return an :int64, basically.
2561
+ *
2562
+ * FIXME: Eventually, this function should actually look at the value stored in Fixnums (for example), so that it knows
2563
+ * whether to return :int64 or :int32.
2564
+ */
2565
+ nm::dtype_t nm_dtype_min(VALUE v) {
2566
+
2567
+ if (RB_TYPE_P(v, T_FIXNUM))
2568
+ return nm_dtype_min_fixnum(FIX2LONG(v));
2569
+ else if (RB_TYPE_P(v, T_BIGNUM))
2570
+ return nm::INT64;
2571
+ else if (RB_TYPE_P(v, T_FLOAT))
2572
+ return nm::FLOAT32;
2573
+ else if (RB_TYPE_P(v, T_COMPLEX))
2574
+ return nm::COMPLEX64;
2575
+ else if (RB_TYPE_P(v, T_STRING))
2576
+ return RSTRING_LEN(v) == 1 ? nm::BYTE : nm::RUBYOBJ;
2577
+ else if (RB_TYPE_P(v, T_TRUE) || RB_TYPE_P(v, T_FALSE) || RB_TYPE_P(v, T_NIL))
2578
+ return nm::RUBYOBJ;
2579
+ else
2580
+ return nm::RUBYOBJ;
2581
+ }
2582
+
2583
+
2584
+ /*
2585
+ * Guess the data type given a value.
2586
+ *
2587
+ * TODO: Probably needs some work for Bignum.
2588
+ */
2589
+ nm::dtype_t nm_dtype_guess(VALUE v) {
2590
+ if (RB_TYPE_P(v, T_TRUE) || RB_TYPE_P(v, T_FALSE) || RB_TYPE_P(v, T_NIL))
2591
+ return nm::RUBYOBJ;
2592
+ else if (RB_TYPE_P(v, T_STRING))
2593
+ return RSTRING_LEN(v) == 1 ? nm::BYTE : nm::RUBYOBJ;
2594
+ else if (RB_TYPE_P(v, T_FIXNUM))
2595
+ #if SIZEOF_INT == 8
2596
+ return nm::INT64;
2597
+ #elif SIZEOF_INT == 4
2598
+ return nm::INT32;
2599
+ #else
2600
+ return nm::INT16;
2601
+ #endif
2602
+ else if (RB_TYPE_P(v, T_BIGNUM))
2603
+ return nm::INT64;
2604
+ #if SIZEOF_FLOAT == 4
2605
+ else if (RB_TYPE_P(v, T_COMPLEX))
2606
+ return nm::COMPLEX128;
2607
+ else if (RB_TYPE_P(v, T_FLOAT))
2608
+ return nm::FLOAT64;
2609
+ #elif SIZEOF_FLOAT == 2
2610
+ else if (RB_TYPE_P(v, T_COMPLEX))
2611
+ return nm::COMPLEX64;
2612
+ else if (RB_TYPE_P(v, T_FLOAT))
2613
+ return nm::FLOAT32;
2614
+ #endif
2615
+ else if (RB_TYPE_P(v, T_ARRAY))
2616
+ /*
2617
+ * May be passed for dense -- for now, just look at the first element.
2618
+ *
2619
+ * TODO: Look at entire array for most specific type.
2620
+ */
2621
+ return nm_dtype_guess(RARRAY_AREF(v, 0));
2622
+ else {
2623
+ RB_P(v);
2624
+ rb_raise(rb_eArgError, "Unable to guess a data type from provided parameters; data type must be specified manually.");
2625
+ }
2626
+ }
2627
+
2628
+ /*
2629
+ * Modify an existing SLICE object (with properly allocated memory),
2630
+ * so that it will contain the appropriate coordinate and length information
2631
+ * for accessing some part of a matrix.
2632
+ */
2633
+ static void init_slice_no_alloc(SLICE* slice, size_t dim, int argc, VALUE* arg, size_t* shape) {
2634
+ NM_CONSERVATIVE(nm_register_values(arg, argc));
2635
+
2636
+ VALUE beg, end;
2637
+ int excl;
2638
+
2639
+ slice->single = true;
2640
+
2641
+ // r is the shape position; t is the slice position. They may differ when we're dealing with a
2642
+ // matrix where the effective dimension is less than the dimension (e.g., a vector).
2643
+ for (size_t r = 0, t = 0; r < dim; ++r) {
2644
+ VALUE v = t == (unsigned int)argc ? Qnil : arg[t];
2645
+
2646
+ // if the current shape indicates a vector and fewer args were supplied than necessary, just use 0
2647
+ if (argc - t + r < dim && shape[r] == 1) {
2648
+ slice->coords[r] = 0;
2649
+ slice->lengths[r] = 1;
2650
+
2651
+ } else if (FIXNUM_P(v)) { // this used CLASS_OF before, which is inefficient for fixnum
2652
+ int v_ = FIX2INT(v);
2653
+ if (v_ < 0) // checking for negative indexes
2654
+ slice->coords[r] = shape[r]+v_;
2655
+ else
2656
+ slice->coords[r] = v_;
2657
+ slice->lengths[r] = 1;
2658
+ t++;
2659
+
2660
+ } else if (SYMBOL_P(v) && rb_to_id(v) == nm_rb_mul) { // :* means the whole possible range
2661
+
2662
+ slice->coords[r] = 0;
2663
+ slice->lengths[r] = shape[r];
2664
+ slice->single = false;
2665
+ t++;
2666
+
2667
+ } else if (CLASS_OF(v) == rb_cRange) {
2668
+ rb_range_values(arg[t], &beg, &end, &excl);
2669
+
2670
+ int begin_ = FIX2INT(beg);
2671
+ int end_ = FIX2INT(end);
2672
+
2673
+ slice->coords[r] = (begin_ < 0) ? shape[r] + begin_ : begin_;
2674
+
2675
+ // Exclude last element for a...b range
2676
+ if (end_ < 0)
2677
+ slice->lengths[r] = shape[r] + end_ - slice->coords[r] + (excl ? 0 : 1);
2678
+ else
2679
+ slice->lengths[r] = end_ - slice->coords[r] + (excl ? 0 : 1);
2680
+
2681
+ slice->single = false;
2682
+ t++;
2683
+
2684
+ } else {
2685
+ NM_CONSERVATIVE(nm_unregister_values(arg, argc));
2686
+ rb_raise(rb_eArgError, "expected Fixnum or Range for slice component instead of %s", rb_obj_classname(v));
2687
+ }
2688
+
2689
+ if (slice->coords[r] > shape[r] || slice->coords[r] + slice->lengths[r] > shape[r]) {
2690
+ NM_CONSERVATIVE(nm_unregister_values(arg, argc));
2691
+ rb_raise(rb_eRangeError, "slice is larger than matrix in dimension %lu (slice component %lu)", r, t);
2692
+ }
2693
+ }
2694
+
2695
+ NM_CONSERVATIVE(nm_unregister_values(arg, argc));
2696
+ }
2697
+
2698
+ #ifdef BENCHMARK
2699
+ /*
2700
+ * A simple function used when benchmarking NMatrix.
2701
+ */
2702
+ static double get_time(void) {
2703
+ struct timeval t;
2704
+ struct timezone tzp;
2705
+
2706
+ gettimeofday(&t, &tzp);
2707
+
2708
+ return t.tv_sec + t.tv_usec*1e-6;
2709
+ }
2710
+ #endif
2711
+
2712
+ /*
2713
+ * The argv parameter will be either 1 or 2 elements. If 1, could be either
2714
+ * initial or dtype. If 2, is initial and dtype. This function returns the
2715
+ * dtype.
2716
+ */
2717
+ static nm::dtype_t interpret_dtype(int argc, VALUE* argv, nm::stype_t stype) {
2718
+ int offset;
2719
+
2720
+ switch (argc) {
2721
+ case 1:
2722
+ offset = 0;
2723
+ break;
2724
+
2725
+ case 2:
2726
+ offset = 1;
2727
+ break;
2728
+
2729
+ default:
2730
+ rb_raise(rb_eArgError, "Need an initial value or a dtype.");
2731
+ break;
2732
+ }
2733
+
2734
+ if (SYMBOL_P(argv[offset])) {
2735
+ return nm_dtype_from_rbsymbol(argv[offset]);
2736
+
2737
+ } else if (RB_TYPE_P(argv[offset], T_STRING)) {
2738
+ return nm_dtype_from_rbstring(StringValue(argv[offset]));
2739
+
2740
+ } else if (stype == nm::YALE_STORE) {
2741
+ rb_raise(rb_eArgError, "Yale storage class requires a dtype.");
2742
+
2743
+ } else {
2744
+ return nm_dtype_guess(argv[0]);
2745
+ }
2746
+ }
2747
+
2748
+ /*
2749
+ * Convert an Ruby value or an array of Ruby values into initial C values.
2750
+ */
2751
+ static void* interpret_initial_value(VALUE arg, nm::dtype_t dtype) {
2752
+ NM_CONSERVATIVE(nm_register_value(&arg));
2753
+
2754
+ unsigned int index;
2755
+ void* init_val;
2756
+
2757
+ if (RB_TYPE_P(arg, T_ARRAY)) {
2758
+ // Array
2759
+ init_val = NM_ALLOC_N(char, DTYPE_SIZES[dtype] * RARRAY_LEN(arg));
2760
+ NM_CHECK_ALLOC(init_val);
2761
+ for (index = 0; index < RARRAY_LEN(arg); ++index) {
2762
+ rubyval_to_cval(RARRAY_AREF(arg, index), dtype, (char*)init_val + (index * DTYPE_SIZES[dtype]));
2763
+ }
2764
+
2765
+ } else {
2766
+ // Single value
2767
+ init_val = rubyobj_to_cval(arg, dtype);
2768
+ }
2769
+
2770
+ NM_CONSERVATIVE(nm_unregister_value(&arg));
2771
+ return init_val;
2772
+ }
2773
+
2774
+ /*
2775
+ * Convert the shape argument, which may be either a Ruby value or an array of
2776
+ * Ruby values, into C values. The second argument is where the dimensionality
2777
+ * of the matrix will be stored. The function itself returns a pointer to the
2778
+ * array describing the shape, which must be freed manually.
2779
+ */
2780
+ static size_t* interpret_shape(VALUE arg, size_t* dim) {
2781
+ NM_CONSERVATIVE(nm_register_value(&arg));
2782
+ size_t* shape;
2783
+
2784
+ if (RB_TYPE_P(arg, T_ARRAY)) {
2785
+ *dim = RARRAY_LEN(arg);
2786
+ shape = NM_ALLOC_N(size_t, *dim);
2787
+
2788
+ for (size_t index = 0; index < *dim; ++index) {
2789
+ shape[index] = FIX2UINT( RARRAY_AREF(arg, index) );
2790
+ }
2791
+
2792
+ } else if (FIXNUM_P(arg)) {
2793
+ *dim = 2;
2794
+ shape = NM_ALLOC_N(size_t, *dim);
2795
+
2796
+ shape[0] = FIX2UINT(arg);
2797
+ shape[1] = FIX2UINT(arg);
2798
+
2799
+ } else {
2800
+ nm_unregister_value(&arg);
2801
+ rb_raise(rb_eArgError, "Expected an array of numbers or a single Fixnum for matrix shape");
2802
+ }
2803
+
2804
+ NM_CONSERVATIVE(nm_unregister_value(&arg));
2805
+ return shape;
2806
+ }
2807
+
2808
+ /*
2809
+ * Convert a Ruby symbol or string into an storage type.
2810
+ */
2811
+ static nm::stype_t interpret_stype(VALUE arg) {
2812
+ if (SYMBOL_P(arg)) {
2813
+ return nm_stype_from_rbsymbol(arg);
2814
+
2815
+ } else if (RB_TYPE_P(arg, T_STRING)) {
2816
+ return nm_stype_from_rbstring(StringValue(arg));
2817
+
2818
+ } else {
2819
+ rb_raise(rb_eArgError, "Expected storage type");
2820
+ }
2821
+ }
2822
+
2823
+ //////////////////
2824
+ // Math Helpers //
2825
+ //////////////////
2826
+
2827
+ STORAGE* matrix_storage_cast_alloc(NMATRIX* matrix, nm::dtype_t new_dtype) {
2828
+ if (matrix->storage->dtype == new_dtype && !is_ref(matrix))
2829
+ return matrix->storage;
2830
+
2831
+ CAST_TABLE(cast_copy_storage);
2832
+ return cast_copy_storage[matrix->stype][matrix->stype](matrix->storage, new_dtype, NULL);
2833
+ }
2834
+
2835
+ STORAGE_PAIR binary_storage_cast_alloc(NMATRIX* left_matrix, NMATRIX* right_matrix) {
2836
+ nm_register_nmatrix(left_matrix);
2837
+ nm_register_nmatrix(right_matrix);
2838
+
2839
+ STORAGE_PAIR casted;
2840
+ nm::dtype_t new_dtype = Upcast[left_matrix->storage->dtype][right_matrix->storage->dtype];
2841
+
2842
+ casted.left = matrix_storage_cast_alloc(left_matrix, new_dtype);
2843
+ nm_register_storage(left_matrix->stype, casted.left);
2844
+ casted.right = matrix_storage_cast_alloc(right_matrix, new_dtype);
2845
+
2846
+ nm_unregister_nmatrix(left_matrix);
2847
+ nm_unregister_nmatrix(right_matrix);
2848
+ nm_unregister_storage(left_matrix->stype, casted.left);
2849
+
2850
+ return casted;
2851
+ }
2852
+
2853
+ static VALUE matrix_multiply_scalar(NMATRIX* left, VALUE scalar) {
2854
+ rb_raise(rb_eNotImpError, "matrix-scalar multiplication not implemented yet");
2855
+ return Qnil;
2856
+ }
2857
+
2858
+ static VALUE matrix_multiply(NMATRIX* left, NMATRIX* right) {
2859
+ nm_register_nmatrix(left);
2860
+ nm_register_nmatrix(right);
2861
+ ///TODO: multiplication for non-dense and/or non-decimal matrices
2862
+
2863
+ // Make sure both of our matrices are of the correct type.
2864
+ STORAGE_PAIR casted = binary_storage_cast_alloc(left, right);
2865
+ nm_register_storage(left->stype, casted.left);
2866
+ nm_register_storage(right->stype, casted.right);
2867
+
2868
+ size_t* resulting_shape = NM_ALLOC_N(size_t, 2);
2869
+ resulting_shape[0] = left->storage->shape[0];
2870
+ resulting_shape[1] = right->storage->shape[1];
2871
+
2872
+ // Sometimes we only need to use matrix-vector multiplication (e.g., GEMM versus GEMV). Find out.
2873
+ bool vector = false;
2874
+ if (resulting_shape[1] == 1) vector = true;
2875
+
2876
+ static STORAGE* (*storage_matrix_multiply[nm::NUM_STYPES])(const STORAGE_PAIR&, size_t*, bool) = {
2877
+ nm_dense_storage_matrix_multiply,
2878
+ nm_list_storage_matrix_multiply,
2879
+ nm_yale_storage_matrix_multiply
2880
+ };
2881
+
2882
+ STORAGE* resulting_storage = storage_matrix_multiply[left->stype](casted, resulting_shape, vector);
2883
+ NMATRIX* result = nm_create(left->stype, resulting_storage);
2884
+ nm_register_nmatrix(result);
2885
+
2886
+ // Free any casted-storage we created for the multiplication.
2887
+ // TODO: Can we make the Ruby GC take care of this stuff now that we're using it?
2888
+ // If we did that, we night not have to re-create these every time, right? Or wrong? Need to do
2889
+ // more research.
2890
+ static void (*free_storage[nm::NUM_STYPES])(STORAGE*) = {
2891
+ nm_dense_storage_delete,
2892
+ nm_list_storage_delete,
2893
+ nm_yale_storage_delete
2894
+ };
2895
+
2896
+ nm_unregister_storage(left->stype, casted.left);
2897
+ if (left->storage != casted.left) free_storage[result->stype](casted.left);
2898
+
2899
+ nm_unregister_storage(right->stype, casted.right);
2900
+ if (right->storage != casted.right) free_storage[result->stype](casted.right);
2901
+
2902
+ VALUE to_return = result ? Data_Wrap_Struct(cNMatrix, nm_mark, nm_delete, result) : Qnil; // Only if we try to multiply list matrices should we return Qnil.
2903
+
2904
+ nm_unregister_nmatrix(left);
2905
+ nm_unregister_nmatrix(right);
2906
+ nm_unregister_nmatrix(result);
2907
+
2908
+ return to_return;
2909
+ }
2910
+
2911
+ /*
2912
+ * Reduce a matrix to hessenberg form.
2913
+ *
2914
+ * == Arguments
2915
+ *
2916
+ * a - The NMatrix to be reduced. This matrix is replaced with the hessenberg form.
2917
+ *
2918
+ * == Notes
2919
+ *
2920
+ * LAPACK free.
2921
+ */
2922
+ static VALUE nm_hessenberg(VALUE self, VALUE a) {
2923
+ nm_math_hessenberg(a);
2924
+
2925
+ return a;
2926
+ }
2927
+
2928
+ /*
2929
+ * Calculate the inverse of a matrix with in-place Gauss-Jordan elimination.
2930
+ * Inverse will fail if the largest element in any column in zero.
2931
+ *
2932
+ * LAPACK free.
2933
+ */
2934
+ static VALUE nm_inverse(VALUE self, VALUE inverse, VALUE bang) {
2935
+
2936
+ if (NM_STYPE(self) != nm::DENSE_STORE) {
2937
+ rb_raise(rb_eNotImpError, "needs exact determinant implementation for this matrix stype");
2938
+ return Qnil;
2939
+ }
2940
+
2941
+ if (NM_DIM(self) != 2 || NM_SHAPE0(self) != NM_SHAPE1(self)) {
2942
+ rb_raise(nm_eShapeError, "matrices must be square to have an inverse defined");
2943
+ return Qnil;
2944
+ }
2945
+
2946
+ if (bang == Qtrue) {
2947
+ nm_math_inverse(NM_SHAPE0(self), NM_STORAGE_DENSE(self)->elements,
2948
+ NM_DTYPE(self));
2949
+
2950
+ return self;
2951
+ }
2952
+
2953
+ nm_math_inverse(NM_SHAPE0(inverse), NM_STORAGE_DENSE(inverse)->elements,
2954
+ NM_DTYPE(inverse));
2955
+
2956
+ return inverse;
2957
+ }
2958
+
2959
+ /*
2960
+ * Calculate the exact inverse of a 2x2 or 3x3 matrix.
2961
+ *
2962
+ * Does not test for invertibility!
2963
+ */
2964
+ static VALUE nm_inverse_exact(VALUE self, VALUE inverse, VALUE lda, VALUE ldb) {
2965
+ if (NM_DIM(self) != 2 || NM_SHAPE0(self) != NM_SHAPE1(self)) {
2966
+ rb_raise(nm_eShapeError, "matrices must be square to have an inverse defined");
2967
+ return Qnil;
2968
+ }
2969
+
2970
+ nm::dtype_t dtype = NM_DTYPE(self);
2971
+ void* result = NM_ALLOCA_N(char, DTYPE_SIZES[dtype]);
2972
+ if (dtype == nm::RUBYOBJ) {
2973
+ nm_register_values(reinterpret_cast<VALUE*>(result), 1);
2974
+ }
2975
+ nm::stype_t old_stype = NM_STYPE(self);
2976
+ if (old_stype == nm::LIST_STORE) {
2977
+ self = nm_cast_with_types(self, nm::YALE_STORE, dtype, result);
2978
+ inverse = nm_cast_with_types(inverse, nm::YALE_STORE, dtype, result);
2979
+ }
2980
+
2981
+ if (NM_STYPE(self) == nm::DENSE_STORE) {
2982
+ nm_math_inverse_exact_from_dense(NM_SHAPE0(self),
2983
+ NM_STORAGE_DENSE(self)->elements, FIX2INT(lda),
2984
+ NM_STORAGE_DENSE(inverse)->elements, FIX2INT(ldb), dtype);
2985
+ } else {
2986
+ nm_math_inverse_exact_from_yale(NM_SHAPE0(self),
2987
+ NM_STORAGE_YALE(self), FIX2INT(lda),
2988
+ NM_STORAGE_YALE(inverse), FIX2INT(ldb), dtype);
2989
+ }
2990
+
2991
+ if (old_stype == nm::LIST_STORE) {
2992
+ inverse = nm_cast_with_types(inverse, nm::LIST_STORE, dtype, result);
2993
+ }
2994
+ if (dtype == nm::RUBYOBJ) {
2995
+ nm_unregister_values(reinterpret_cast<VALUE*>(result), 1);
2996
+ }
2997
+ return inverse;
2998
+ }
2999
+
3000
+ /*
3001
+ * Calculate the exact determinant of a dense matrix.
3002
+ *
3003
+ * Returns nil for dense matrices which are not square or number of dimensions other than 2.
3004
+ *
3005
+ * Note: Currently only implemented for 2x2 and 3x3 matrices.
3006
+ */
3007
+ static VALUE nm_det_exact(VALUE self) {
3008
+
3009
+ if (NM_DIM(self) != 2 || NM_SHAPE0(self) != NM_SHAPE1(self)) {
3010
+ rb_raise(nm_eShapeError, "matrices must be square to have a determinant defined");
3011
+ return Qnil;
3012
+ }
3013
+
3014
+ nm::dtype_t dtype = NM_DTYPE(self);
3015
+ void* result = NM_ALLOCA_N(char, DTYPE_SIZES[dtype]);
3016
+ if (NM_STYPE(self) == nm::LIST_STORE) {
3017
+ self = nm_cast_with_types(self, nm::YALE_STORE, dtype, result);
3018
+ }
3019
+
3020
+ NM_CONSERVATIVE(nm_register_value(&self));
3021
+
3022
+ // Calculate the determinant and then assign it to the return value
3023
+ if (NM_STYPE(self) == nm::DENSE_STORE) {
3024
+ nm_math_det_exact_from_dense(NM_SHAPE0(self), NM_STORAGE_DENSE(self)->elements,
3025
+ NM_SHAPE0(self), NM_DTYPE(self), result);
3026
+ } else {
3027
+ nm_math_det_exact_from_yale(NM_SHAPE0(self), NM_STORAGE_YALE(self),
3028
+ NM_SHAPE0(self), NM_DTYPE(self), result);
3029
+ }
3030
+
3031
+ VALUE to_return;
3032
+ if (dtype == nm::RUBYOBJ) {
3033
+ to_return = *reinterpret_cast<VALUE*>(result);
3034
+
3035
+ } else {
3036
+ to_return = nm::rubyobj_from_cval(result, NM_DTYPE(self)).rval;
3037
+ }
3038
+ NM_CONSERVATIVE(nm_unregister_value(&self));
3039
+
3040
+ return to_return;
3041
+ }
3042
+
3043
+
3044
+
3045
+ /*
3046
+ * Returns the pointer to the matrix storage's data. This is useful primarily when you are using FFI with NMatrix --
3047
+ * say, for example, you want to pass a float* to some function, and your NMatrix is a :float32 :dense matrix. Then you
3048
+ * can call this function and get that pointer directly instead of copying the data.
3049
+ */
3050
+ static VALUE nm_data_pointer(VALUE self) {
3051
+ //if (NM_DTYPE(self) == nm::LIST_STORE)
3052
+ // rb_warn("pointer requested for list storage, which may be meaningless");
3053
+
3054
+ // This is actually pretty easy, since all of the storage types have their elements positioned in the same place
3055
+ // relative to one another. So yes, believe it or not, this should work just as well for Yale or list storage as for
3056
+ // dense.
3057
+ return INT2FIX(NM_STORAGE_DENSE(self)->elements);
3058
+ }
3059
+
3060
+
3061
+ /////////////////
3062
+ // Exposed API //
3063
+ /////////////////
3064
+
3065
+ /*
3066
+ * Create a dense matrix. Used by the NMatrix GSL fork. Unlike nm_create, this one copies all of the
3067
+ * arrays and such passed in -- so you don't have to allocate and pass a new shape object for every
3068
+ * matrix you want to create, for example. Same goes for elements.
3069
+ *
3070
+ * Returns a properly-wrapped Ruby object as a VALUE.
3071
+ *
3072
+ * *** Note that this function is for API only. Please do not use it internally.
3073
+ *
3074
+ * TODO: Add a column-major option for libraries that use column-major matrices.
3075
+ */
3076
+ VALUE rb_nmatrix_dense_create(nm::dtype_t dtype, size_t* shape, size_t dim, void* elements, size_t length) {
3077
+
3078
+ if (dtype == nm::RUBYOBJ) {
3079
+ nm_register_values(reinterpret_cast<VALUE*>(elements), length);
3080
+ }
3081
+
3082
+ NMATRIX* nm;
3083
+ size_t nm_dim;
3084
+ size_t* shape_copy;
3085
+
3086
+ // Do not allow a dim of 1. Treat it as a column or row matrix.
3087
+ if (dim == 1) {
3088
+ nm_dim = 2;
3089
+ shape_copy = NM_ALLOC_N(size_t, nm_dim);
3090
+ shape_copy[0] = shape[0];
3091
+ shape_copy[1] = 1;
3092
+
3093
+ } else {
3094
+ nm_dim = dim;
3095
+ shape_copy = NM_ALLOC_N(size_t, nm_dim);
3096
+ memcpy(shape_copy, shape, sizeof(size_t)*nm_dim);
3097
+ }
3098
+
3099
+ // Copy elements
3100
+ void* elements_copy = NM_ALLOC_N(char, DTYPE_SIZES[dtype]*length);
3101
+ memcpy(elements_copy, elements, DTYPE_SIZES[dtype]*length);
3102
+
3103
+ // allocate and create the matrix and its storage
3104
+ nm = nm_create(nm::DENSE_STORE, nm_dense_storage_create(dtype, shape_copy, dim, elements_copy, length));
3105
+
3106
+ nm_register_nmatrix(nm);
3107
+
3108
+ VALUE to_return = Data_Wrap_Struct(cNMatrix, nm_mark, nm_delete, nm);
3109
+
3110
+ nm_unregister_nmatrix(nm);
3111
+ if (dtype == nm::RUBYOBJ) {
3112
+ nm_unregister_values(reinterpret_cast<VALUE*>(elements), length);
3113
+ }
3114
+
3115
+ // tell Ruby about the matrix and its storage, particularly how to garbage collect it.
3116
+ return to_return;
3117
+ }
3118
+
3119
+ /*
3120
+ * Create a dense vector. Used by the NMatrix GSL fork.
3121
+ *
3122
+ * Basically just a convenience wrapper for rb_nmatrix_dense_create().
3123
+ *
3124
+ * Returns a properly-wrapped Ruby NMatrix object as a VALUE. Included for backwards compatibility
3125
+ * for when NMatrix had an NVector class.
3126
+ */
3127
+ VALUE rb_nvector_dense_create(nm::dtype_t dtype, void* elements, size_t length) {
3128
+ size_t dim = 1, shape = length;
3129
+ return rb_nmatrix_dense_create(dtype, &shape, dim, elements, length);
3130
+ }